From 4dc228d185e409779bd8d7f135c4f94364d59e27 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Sun, 21 Jun 2026 01:42:51 -0700 Subject: [PATCH] Add option to display card ID in card list --- .../card_locker/LoyaltyCardCursorAdapter.java | 9 ++- .../LoyaltyCardListDisplayOptionsManager.java | 18 +++++ app/src/main/res/drawable/ic_card_id_24dp.xml | 10 +++ .../main/res/layout/loyalty_card_layout.xml | 21 +++++- app/src/main/res/values/strings.xml | 2 + .../protect/card_locker/MainActivityTest.java | 69 +++++++++++++++++++ 6 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/drawable/ic_card_id_24dp.xml diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java b/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java index 84968d753b..537b85c071 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java @@ -123,6 +123,12 @@ public void onBindViewHolder(LoyaltyCardListItemViewHolder inputHolder, Cursor i inputHolder.setExtraField(inputHolder.mExpiryField, null, null, false); } + if (mLoyaltyCardListDisplayOptions.showingCardId() && !loyaltyCard.cardId.isEmpty()) { + inputHolder.setExtraField(inputHolder.mCardIdField, loyaltyCard.cardId, null, showDivider); + } else { + inputHolder.setExtraField(inputHolder.mCardIdField, null, null, false); + } + inputHolder.mCardIcon.setContentDescription(loyaltyCard.store); Utils.setIconOrTextWithBackground(mContext, loyaltyCard, icon, inputHolder.mCardIcon, inputHolder.mCardText, new Settings(mContext).getPreferredColumnCount()); @@ -211,7 +217,7 @@ public interface CardAdapterListener { public class LoyaltyCardListItemViewHolder extends RecyclerView.ViewHolder { - public TextView mCardText, mStoreField, mNoteField, mBalanceField, mValidFromField, mExpiryField; + public TextView mCardText, mStoreField, mNoteField, mBalanceField, mValidFromField, mExpiryField, mCardIdField; public ImageView mCardIcon, mTickIcon; public MaterialCardView mRow; public ConstraintLayout mStar, mArchived; @@ -227,6 +233,7 @@ protected LoyaltyCardListItemViewHolder(LoyaltyCardLayoutBinding loyaltyCardLayo mBalanceField = loyaltyCardLayoutBinding.balance; mValidFromField = loyaltyCardLayoutBinding.validFrom; mExpiryField = loyaltyCardLayoutBinding.expiry; + mCardIdField = loyaltyCardLayoutBinding.cardId; mCardIcon = loyaltyCardLayoutBinding.thumbnail; mCardText = loyaltyCardLayoutBinding.thumbnailText; mStar = loyaltyCardLayoutBinding.star; diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardListDisplayOptionsManager.java b/app/src/main/java/protect/card_locker/LoyaltyCardListDisplayOptionsManager.java index 9ece34ed38..ce6eaa811d 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardListDisplayOptionsManager.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardListDisplayOptionsManager.java @@ -37,6 +37,7 @@ public static class LoyaltyCardDisplayOption { private boolean mShowNote; private boolean mShowBalance; private boolean mShowValidity; + private boolean mShowCardId; private boolean mShowArchivedCards; public LoyaltyCardListDisplayOptionsManager(Context context, @NonNull Runnable refreshCardsCallback, @Nullable Runnable swapCursorCallback) { @@ -52,6 +53,7 @@ public LoyaltyCardListDisplayOptionsManager(Context context, @NonNull Runnable r mShowNote = mCardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_note), true); mShowBalance = mCardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_balance), true); mShowValidity = mCardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_validity), true); + mShowCardId = mCardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_card_id), false); mShowArchivedCards = mCardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_archived_cards), true); } @@ -105,6 +107,17 @@ public boolean showingValidity() { return mShowValidity; } + public void showCardId(boolean show) { + mShowCardId = show; + mRefreshCardsCallback.run(); + + saveDetailState(R.string.sharedpreference_card_details_show_card_id, show); + } + + public boolean showingCardId() { + return mShowCardId; + } + public void showArchivedCards(boolean show) { if (mSwapCursorCallback == null) { throw new IllegalStateException("No swap cursor callback is available, can not manage archive state"); @@ -147,6 +160,11 @@ public void showDisplayOptionsDialog() { showingValidity(), this::showValidity )); + displayOptions.add(new LoyaltyCardDisplayOption( + mContext.getString(R.string.show_card_id), + showingCardId(), + this::showCardId + )); // Hide "Show archived cards" option unless the callback exists if (mSwapCursorCallback != null) { diff --git a/app/src/main/res/drawable/ic_card_id_24dp.xml b/app/src/main/res/drawable/ic_card_id_24dp.xml new file mode 100644 index 0000000000..eddfb60f2d --- /dev/null +++ b/app/src/main/res/drawable/ic_card_id_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/loyalty_card_layout.xml b/app/src/main/res/layout/loyalty_card_layout.xml index 34b745b167..2b2cd94cc5 100644 --- a/app/src/main/res/layout/loyalty_card_layout.xml +++ b/app/src/main/res/layout/loyalty_card_layout.xml @@ -213,11 +213,30 @@ android:drawablePadding="4dp" android:visibility="gone" app:layout_constraintTop_toBottomOf="@+id/validFrom" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@+id/cardId" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" tools:visibility="visible" tools:text="Tomorrow"/> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3cbdea0793..566e591e7d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -280,10 +280,12 @@ Show note Show balance Show validity + Show card ID sharedpreference_card_details_show_name_below_thumbnail sharedpreference_card_details_show_note sharedpreference_card_details_show_balance sharedpreference_card_details_show_validity + sharedpreference_card_details_show_card_id sharedpreference_card_details_show_archived_cards Card view Cards overview diff --git a/app/src/test/java/protect/card_locker/MainActivityTest.java b/app/src/test/java/protect/card_locker/MainActivityTest.java index 754360f217..d0e8642d50 100644 --- a/app/src/test/java/protect/card_locker/MainActivityTest.java +++ b/app/src/test/java/protect/card_locker/MainActivityTest.java @@ -20,6 +20,7 @@ import androidx.appcompat.widget.SearchView; import androidx.recyclerview.widget.RecyclerView; +import androidx.test.core.app.ApplicationProvider; import com.google.android.material.tabs.TabLayout; import com.google.zxing.BarcodeFormat; @@ -116,6 +117,74 @@ public void addOneLoyaltyCard() { database.close(); } + @Test + public void cardIdHiddenByDefault() { + ActivityController activityController = Robolectric.buildActivity(MainActivity.class).create(); + + Activity mainActivity = (Activity) activityController.get(); + activityController.start(); + activityController.resume(); + activityController.visible(); + + RecyclerView list = mainActivity.findViewById(R.id.list); + + SQLiteDatabase database = TestHelpers.getEmptyDb(mainActivity).getWritableDatabase(); + DBHelper.insertLoyaltyCard(database, "store", "", null, null, new BigDecimal("0"), null, "1234567890", null, CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A), StandardCharsets.ISO_8859_1, Color.BLACK, 0, null, 0); + + activityController.pause(); + activityController.resume(); + activityController.visible(); + + assertEquals(1, list.getAdapter().getItemCount()); + + list.measure(0, 0); + list.layout(0, 0, 100, 1000); + + // Card ID is hidden by default + TextView cardIdField = list.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.cardId); + assertEquals(View.GONE, cardIdField.getVisibility()); + + database.close(); + } + + @Test + public void showCardIdDisplayOption() { + // The display option is read in the adapter's constructor (during onCreate), so the + // preference must be set before the activity is built. + SharedPreferences cardDetailsPref = ApplicationProvider.getApplicationContext().getSharedPreferences( + ApplicationProvider.getApplicationContext().getString(R.string.sharedpreference_card_details), + Activity.MODE_PRIVATE); + cardDetailsPref.edit().putBoolean( + ApplicationProvider.getApplicationContext().getString(R.string.sharedpreference_card_details_show_card_id), true).apply(); + + ActivityController activityController = Robolectric.buildActivity(MainActivity.class).create(); + + Activity mainActivity = (Activity) activityController.get(); + activityController.start(); + activityController.resume(); + activityController.visible(); + + RecyclerView list = mainActivity.findViewById(R.id.list); + + SQLiteDatabase database = TestHelpers.getEmptyDb(mainActivity).getWritableDatabase(); + DBHelper.insertLoyaltyCard(database, "store", "", null, null, new BigDecimal("0"), null, "1234567890", null, CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A), StandardCharsets.ISO_8859_1, Color.BLACK, 0, null, 0); + + activityController.pause(); + activityController.resume(); + activityController.visible(); + + assertEquals(1, list.getAdapter().getItemCount()); + + list.measure(0, 0); + list.layout(0, 0, 100, 1000); + + TextView cardIdField = list.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.cardId); + assertEquals(View.VISIBLE, cardIdField.getVisibility()); + assertEquals("1234567890", cardIdField.getText().toString()); + + database.close(); + } + @Test public void addFourLoyaltyCardsTwoStarred() // Main screen showing starred cards on top correctly {