From 5014a2024db92bb416bb00401acc5e5eb8daa827 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Sat, 27 Sep 2025 00:07:25 +0200 Subject: [PATCH] Add barcode encoding support - Add new barcodeencoding field to database - Read barcode encoding from pkpass file - On default, use zxing's GuessEncoding function in StringUtils (this should not use UTF-8 unless needed) - Allow manually forcing ISO-8859-1 or UTF-8 --- .../card_locker/BarcodeImageWriterTask.java | 25 +++++++++- .../card_locker/BarcodeSelectorAdapter.java | 4 +- .../java/protect/card_locker/DBHelper.java | 24 ++++++++-- .../protect/card_locker/ImportURIHelper.java | 1 + .../java/protect/card_locker/LoyaltyCard.java | 28 ++++++++++- .../card_locker/LoyaltyCardEditActivity.java | 47 +++++++++++++++++-- .../card_locker/LoyaltyCardViewActivity.java | 6 +++ .../java/protect/card_locker/PkpassParser.kt | 6 ++- .../importexport/CatimaImporter.java | 5 +- .../importexport/FidmeImporter.java | 3 +- .../importexport/VoucherVaultImporter.java | 3 +- .../res/layout/loyalty_card_edit_activity.xml | 26 ++++++++++ app/src/main/res/values/strings.xml | 2 + 13 files changed, 161 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java b/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java index fa7af57612..0588bffda8 100644 --- a/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java +++ b/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java @@ -4,17 +4,24 @@ import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PorterDuff; +import android.util.ArrayMap; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.Nullable; + +import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; +import com.google.zxing.common.StringUtils; import java.lang.ref.WeakReference; +import java.nio.charset.Charset; +import java.util.Map; import protect.card_locker.async.CompatCallable; @@ -39,6 +46,7 @@ public class BarcodeImageWriterTask implements CompatCallable { private final WeakReference textViewReference; private String cardId; private final CatimaBarcode format; + private final Charset encoding; private final int imageHeight; private final int imageWidth; private final int imagePadding; @@ -48,7 +56,7 @@ public class BarcodeImageWriterTask implements CompatCallable { BarcodeImageWriterTask( Context context, ImageView imageView, String cardIdString, - CatimaBarcode barcodeFormat, TextView textView, + CatimaBarcode barcodeFormat, @Nullable Charset barcodeEncoding, TextView textView, boolean showFallback, BarcodeImageWriterResultCallback callback, boolean roundCornerPadding, boolean isFullscreen ) { mContext = context; @@ -62,6 +70,7 @@ public class BarcodeImageWriterTask implements CompatCallable { cardId = cardIdString; format = barcodeFormat; + encoding = barcodeEncoding; int imageViewHeight = imageView.getHeight(); int imageViewWidth = imageView.getWidth(); @@ -172,10 +181,22 @@ private Bitmap generate() { } MultiFormatWriter writer = new MultiFormatWriter(); + + Map encodeHints = new ArrayMap<>(); + // Use charset if defined or guess otherwise + if (encoding != null) { + Log.d(TAG, "Encoding explicitly set, " + encoding.name()); + encodeHints.put(EncodeHintType.CHARACTER_SET, encoding); + } else { + String guessedEncoding = StringUtils.guessEncoding(cardId.getBytes(), new ArrayMap<>()); + Log.d(TAG, "Guessed encoding: " + guessedEncoding); + encodeHints.put(EncodeHintType.CHARACTER_SET, Charset.forName(guessedEncoding)); + } + BitMatrix bitMatrix; try { try { - bitMatrix = writer.encode(cardId, format.format(), imageWidth, imageHeight, null); + bitMatrix = writer.encode(cardId, format.format(), imageWidth, imageHeight, encodeHints); } catch (Exception e) { // Cast a wider net here and catch any exception, as there are some // cases where an encoder may fail if the data is invalid for the diff --git a/app/src/main/java/protect/card_locker/BarcodeSelectorAdapter.java b/app/src/main/java/protect/card_locker/BarcodeSelectorAdapter.java index 1ca94e6fc7..783b82ab22 100644 --- a/app/src/main/java/protect/card_locker/BarcodeSelectorAdapter.java +++ b/app/src/main/java/protect/card_locker/BarcodeSelectorAdapter.java @@ -92,13 +92,13 @@ public void onGlobalLayout() { Log.d(TAG, "Generating barcode for type " + formatType); - BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, text, true, null, true, false); + BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, null, text, true, null, true, false); mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter); } }); } else { Log.d(TAG, "Generating barcode for type " + formatType); - BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, text, true, null, true, false); + BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, null, text, true, null, true, false); mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter); } } diff --git a/app/src/main/java/protect/card_locker/DBHelper.java b/app/src/main/java/protect/card_locker/DBHelper.java index 88ad9167bb..23b59d56bd 100644 --- a/app/src/main/java/protect/card_locker/DBHelper.java +++ b/app/src/main/java/protect/card_locker/DBHelper.java @@ -10,8 +10,11 @@ import android.text.TextUtils; import android.util.Log; +import androidx.annotation.Nullable; + import java.io.FileNotFoundException; import java.math.BigDecimal; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Currency; @@ -23,7 +26,7 @@ public class DBHelper extends SQLiteOpenHelper { public static final String DATABASE_NAME = "Catima.db"; public static final int ORIGINAL_DATABASE_VERSION = 1; - public static final int DATABASE_VERSION = 17; + public static final int DATABASE_VERSION = 18; // NB: changing these values requires a migration public static final int DEFAULT_ZOOM_LEVEL = 100; @@ -49,6 +52,7 @@ public static class LoyaltyCardDbIds { public static final String CARD_ID = "cardid"; public static final String BARCODE_ID = "barcodeid"; public static final String BARCODE_TYPE = "barcodetype"; + public static final String BARCODE_ENCODING = "barcodeencoding"; public static final String STAR_STATUS = "starstatus"; public static final String LAST_USED = "lastused"; public static final String ZOOM_LEVEL = "zoomlevel"; @@ -112,6 +116,7 @@ public void onCreate(SQLiteDatabase db) { LoyaltyCardDbIds.CARD_ID + " TEXT not null," + LoyaltyCardDbIds.BARCODE_ID + " TEXT," + LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," + + LoyaltyCardDbIds.BARCODE_ENCODING + " TEXT," + LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'," + LoyaltyCardDbIds.LAST_USED + " INTEGER DEFAULT '0', " + LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '" + DEFAULT_ZOOM_LEVEL + "', " + @@ -335,6 +340,11 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE + " ADD COLUMN " + LoyaltyCardDbIds.ZOOM_LEVEL_WIDTH + " INTEGER DEFAULT '100' "); } + + if (oldVersion < 18 && newVersion >= 18) { + db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE + + " ADD COLUMN " + LoyaltyCardDbIds.BARCODE_ENCODING + " TEXT"); + } } public static Set imageFiles(Context context, final SQLiteDatabase database) { @@ -396,7 +406,8 @@ private static void updateFTS(final SQLiteDatabase db, final int id, final Strin public static long insertLoyaltyCard( final SQLiteDatabase database, final String store, final String note, final Date validFrom, final Date expiry, final BigDecimal balance, final Currency balanceType, final String cardId, - final String barcodeId, final CatimaBarcode barcodeType, final Integer headerColor, + final String barcodeId, final CatimaBarcode barcodeType, final @Nullable Charset barcodeEncoding, + final Integer headerColor, final int starStatus, final Long lastUsed, final int archiveStatus) { database.beginTransaction(); @@ -411,6 +422,7 @@ public static long insertLoyaltyCard( contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId); contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId); contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null); + contentValues.put(LoyaltyCardDbIds.BARCODE_ENCODING, barcodeEncoding != null ? barcodeEncoding.name() : null); contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor); contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus); contentValues.put(LoyaltyCardDbIds.LAST_USED, lastUsed != null ? lastUsed : Utils.getUnixTime()); @@ -430,7 +442,8 @@ public static long insertLoyaltyCard( final SQLiteDatabase database, final int id, final String store, final String note, final Date validFrom, final Date expiry, final BigDecimal balance, final Currency balanceType, final String cardId, final String barcodeId, - final CatimaBarcode barcodeType, final Integer headerColor, final int starStatus, + final CatimaBarcode barcodeType, final @Nullable Charset barcodeEncoding, + final Integer headerColor, final int starStatus, final Long lastUsed, final int archiveStatus) { database.beginTransaction(); @@ -446,6 +459,7 @@ public static long insertLoyaltyCard( contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId); contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId); contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null); + contentValues.put(LoyaltyCardDbIds.BARCODE_ENCODING, barcodeEncoding != null ? barcodeEncoding.name() : null); contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor); contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus); contentValues.put(LoyaltyCardDbIds.LAST_USED, lastUsed != null ? lastUsed : Utils.getUnixTime()); @@ -465,7 +479,8 @@ public static boolean updateLoyaltyCard( SQLiteDatabase database, final int id, final String store, final String note, final Date validFrom, final Date expiry, final BigDecimal balance, final Currency balanceType, final String cardId, final String barcodeId, - final CatimaBarcode barcodeType, final Integer headerColor, final int starStatus, + final CatimaBarcode barcodeType, final @Nullable Charset barcodeEncoding, + final Integer headerColor, final int starStatus, final Long lastUsed, final int archiveStatus) { database.beginTransaction(); @@ -480,6 +495,7 @@ public static boolean updateLoyaltyCard( contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId); contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId); contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null); + contentValues.put(LoyaltyCardDbIds.BARCODE_ENCODING, barcodeEncoding != null ? barcodeEncoding.name() : null); contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor); contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus); contentValues.put(LoyaltyCardDbIds.LAST_USED, lastUsed != null ? lastUsed : Utils.getUnixTime()); diff --git a/app/src/main/java/protect/card_locker/ImportURIHelper.java b/app/src/main/java/protect/card_locker/ImportURIHelper.java index 99c904b7e4..36e67be9e5 100644 --- a/app/src/main/java/protect/card_locker/ImportURIHelper.java +++ b/app/src/main/java/protect/card_locker/ImportURIHelper.java @@ -136,6 +136,7 @@ public LoyaltyCard parse(Uri uri) throws InvalidObjectException { cardId, barcodeId, barcodeType, + null, headerColor, 0, Utils.getUnixTime(), diff --git a/app/src/main/java/protect/card_locker/LoyaltyCard.java b/app/src/main/java/protect/card_locker/LoyaltyCard.java index b4fe334983..3a93183187 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCard.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCard.java @@ -9,6 +9,7 @@ import androidx.annotation.Nullable; import java.math.BigDecimal; +import java.nio.charset.Charset; import java.util.Currency; import java.util.Date; import java.util.List; @@ -31,6 +32,8 @@ public class LoyaltyCard { @Nullable public CatimaBarcode barcodeType; @Nullable + public Charset barcodeEncoding; + @Nullable public Integer headerColor; public int starStatus; public long lastUsed; @@ -62,6 +65,7 @@ public class LoyaltyCard { public static final String BUNDLE_LOYALTY_CARD_CARD_ID = "loyaltyCardCardId"; public static final String BUNDLE_LOYALTY_CARD_BARCODE_ID = "loyaltyCardBarcodeId"; public static final String BUNDLE_LOYALTY_CARD_BARCODE_TYPE = "loyaltyCardBarcodeType"; + public static final String BUNDLE_LOYALTY_CARD_BARCODE_ENCODING = "loyaltyCardBarcodeEncoding"; public static final String BUNDLE_LOYALTY_CARD_HEADER_COLOR = "loyaltyCardHeaderColor"; public static final String BUNDLE_LOYALTY_CARD_STAR_STATUS = "loyaltyCardStarStatus"; public static final String BUNDLE_LOYALTY_CARD_LAST_USED = "loyaltyCardLastUsed"; @@ -90,6 +94,7 @@ public LoyaltyCard() { setCardId(""); setBarcodeId(null); setBarcodeType(null); + setBarcodeEncoding(null); setHeaderColor(null); setStarStatus(0); setLastUsed(Utils.getUnixTime()); @@ -124,7 +129,7 @@ public LoyaltyCard() { public LoyaltyCard(final int id, final String store, final String note, @Nullable final Date validFrom, @Nullable final Date expiry, final BigDecimal balance, @Nullable final Currency balanceType, final String cardId, @Nullable final String barcodeId, @Nullable final CatimaBarcode barcodeType, - @Nullable final Integer headerColor, final int starStatus, + @Nullable final Charset barcodeEncoding, @Nullable final Integer headerColor, final int starStatus, final long lastUsed, final int zoomLevel, final int zoomLevelWidth, final int archiveStatus, @Nullable Bitmap imageThumbnail, @Nullable String imageThumbnailPath, @Nullable Bitmap imageFront, @Nullable String imageFrontPath, @@ -139,6 +144,7 @@ public LoyaltyCard(final int id, final String store, final String note, @Nullabl setCardId(cardId); setBarcodeId(barcodeId); setBarcodeType(barcodeType); + setBarcodeEncoding(barcodeEncoding); setHeaderColor(headerColor); setStarStatus(starStatus); setLastUsed(lastUsed); @@ -244,6 +250,10 @@ public void setBarcodeType(@Nullable CatimaBarcode barcodeType) { this.barcodeType = barcodeType; } + public void setBarcodeEncoding(@Nullable Charset barcodeEncoding) { + this.barcodeEncoding = barcodeEncoding; + } + public void setHeaderColor(@Nullable Integer headerColor) { this.headerColor = headerColor; } @@ -379,6 +389,11 @@ public void updateFromBundle(@NonNull Bundle bundle, boolean requireFull) { } else if (requireFull) { throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_BARCODE_TYPE); } + if (bundle.containsKey(BUNDLE_LOYALTY_CARD_BARCODE_ENCODING)) { + setBarcodeEncoding(Charset.forName(bundle.getString(BUNDLE_LOYALTY_CARD_BARCODE_ENCODING))); + } else if (requireFull) { + throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_BARCODE_ENCODING); + } if (bundle.containsKey(BUNDLE_LOYALTY_CARD_HEADER_COLOR)) { int tmpHeaderColor = bundle.getInt(BUNDLE_LOYALTY_CARD_HEADER_COLOR); setHeaderColor(tmpHeaderColor != -1 ? tmpHeaderColor : null); @@ -462,6 +477,9 @@ public Bundle toBundle(Context context, List exportLimit) { if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_BARCODE_TYPE)) { bundle.putString(BUNDLE_LOYALTY_CARD_BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null); } + if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_BARCODE_ENCODING)) { + bundle.putString(BUNDLE_LOYALTY_CARD_BARCODE_ENCODING, barcodeEncoding != null ? barcodeEncoding.name() : null); + } if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_HEADER_COLOR)) { bundle.putInt(BUNDLE_LOYALTY_CARD_HEADER_COLOR, headerColor != null ? headerColor : -1); } @@ -539,6 +557,9 @@ public static LoyaltyCard fromCursor(Context context, Cursor cursor) { // barcodeType int barcodeTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE); CatimaBarcode barcodeType = !cursor.isNull(barcodeTypeColumn) ? CatimaBarcode.fromName(cursor.getString(barcodeTypeColumn)) : null; + // barcodeEncoding + int barcodeEncodingColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_ENCODING); + Charset barcodeEncoding = !cursor.isNull(barcodeEncodingColumn) ? Charset.forName(cursor.getString(barcodeEncodingColumn)) : null; // headerColor int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR); Integer headerColor = !cursor.isNull(headerColorColumn) ? cursor.getInt(headerColorColumn) : null; @@ -564,6 +585,7 @@ public static LoyaltyCard fromCursor(Context context, Cursor cursor) { cardId, barcodeId, barcodeType, + barcodeEncoding, headerColor, starStatus, lastUsed, @@ -593,6 +615,7 @@ public static boolean isDuplicate(Context context, final LoyaltyCard a, final Lo Utils.equals(a.barcodeId, b.barcodeId) && // nullable String Utils.equals(a.barcodeType == null ? null : a.barcodeType.format(), b.barcodeType == null ? null : b.barcodeType.format()) && // nullable CatimaBarcode with no overridden .equals(), so we need to check .format() + Utils.equals(a.barcodeEncoding, b.barcodeEncoding) && // nullable String Utils.equals(a.headerColor, b.headerColor) && // nullable Integer a.starStatus == b.starStatus && // non-nullable int a.archiveStatus == b.archiveStatus && // non-nullable int @@ -619,7 +642,7 @@ public static boolean nullableBitmapsEqual(@Nullable Bitmap a, @Nullable Bitmap public String toString() { return String.format( "LoyaltyCard{%n id=%s,%n store=%s,%n note=%s,%n validFrom=%s,%n expiry=%s,%n" - + " balance=%s,%n balanceType=%s,%n cardId=%s,%n barcodeId=%s,%n barcodeType=%s,%n" + + " balance=%s,%n balanceType=%s,%n cardId=%s,%n barcodeId=%s,%n barcodeType=%s,%n barcodeEncoding=%s,%n" + " headerColor=%s,%n starStatus=%s,%n lastUsed=%s,%n zoomLevel=%s,%n zoomLevelWidth=%s,%n archiveStatus=%s%n" + " imageThumbnail=%s,%n imageThumbnailPath=%s,%n imageFront=%s,%n imageFrontPath=%s,%n imageBack=%s,%n imageBackPath=%s,%n}", this.id, @@ -632,6 +655,7 @@ public String toString() { this.cardId, this.barcodeId, this.barcodeType != null ? this.barcodeType.format() : null, + this.barcodeEncoding != null ? this.barcodeEncoding.name() : null, this.headerColor, this.starStatus, this.lastUsed, diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java index eb1c52dd3c..fd19152101 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java @@ -70,6 +70,8 @@ import java.io.IOException; import java.io.InvalidObjectException; import java.math.BigDecimal; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.ParseException; import java.util.ArrayList; @@ -128,6 +130,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements TextView cardIdFieldView; AutoCompleteTextView barcodeIdField; AutoCompleteTextView barcodeTypeField; + AutoCompleteTextView barcodeEncodingField; ImageView barcodeImage; View barcodeImageLayout; View barcodeCaptureLayout; @@ -229,6 +232,14 @@ protected void setLoyaltyCardBarcodeType(@Nullable CatimaBarcode barcodeType) { viewModel.setHasChanged(true); } + protected void setLoyaltyCardBarcodeEncoding(@Nullable Charset barcodeEncoding) { + viewModel.getLoyaltyCard().setBarcodeEncoding(barcodeEncoding); + + generateBarcode(); + + viewModel.setHasChanged(true); + } + protected void setLoyaltyCardHeaderColor(@Nullable Integer headerColor) { viewModel.getLoyaltyCard().setHeaderColor(headerColor); @@ -334,6 +345,7 @@ protected void onCreate(Bundle savedInstanceState) { cardIdFieldView = binding.cardIdView; barcodeIdField = binding.barcodeIdField; barcodeTypeField = binding.barcodeTypeField; + barcodeEncodingField = binding.barcodeEncodingField; barcodeImage = binding.barcode; barcodeImage.setClipToOutline(true); barcodeImageLayout = binding.barcodeLayout; @@ -577,6 +589,30 @@ public void afterTextChanged(Editable s) { } }); + barcodeEncodingField.addTextChangedListener(new SimpleTextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (!s.toString().isEmpty()) { + Log.d(TAG, "Setting barcode encoding to " + s.toString()); + if (s.toString().equals(getString(R.string.automatic))) { + setLoyaltyCardBarcodeEncoding(null); + } else { + setLoyaltyCardBarcodeEncoding(Charset.forName(s.toString())); + } + } + } + + @Override + public void afterTextChanged(Editable s) { + ArrayList barcodeEncodingList = new ArrayList<>(); + barcodeEncodingList.add(getString(R.string.automatic)); + barcodeEncodingList.add(StandardCharsets.ISO_8859_1.name()); + barcodeEncodingList.add(StandardCharsets.UTF_8.name()); + ArrayAdapter barcodeEncodingAdapter = new ArrayAdapter<>(LoyaltyCardEditActivity.this, android.R.layout.select_dialog_item, barcodeEncodingList); + barcodeEncodingField.setAdapter(barcodeEncodingAdapter); + } + }); + binding.tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { @@ -774,6 +810,8 @@ protected void onResume() { barcodeIdField.setText(barcodeId != null && !barcodeId.isEmpty() ? barcodeId : getString(R.string.sameAsCardId)); CatimaBarcode barcodeType = viewModel.getLoyaltyCard().barcodeType; barcodeTypeField.setText(barcodeType != null ? barcodeType.prettyName() : getString(R.string.noBarcode)); + Charset barcodeEncoding = viewModel.getLoyaltyCard().barcodeEncoding; + barcodeEncodingField.setText(barcodeEncoding != null ? barcodeEncoding.name() : getString(R.string.automatic)); // We set the balance here (with onResuming/onRestoring == true) to prevent formatBalanceCurrencyField() from setting it (via onTextChanged), // which can cause issues when switching locale because it parses the balance and e.g. the decimal separator may have changed. @@ -1480,9 +1518,9 @@ private void doSave() { // This makes the DBHelper set it to the current date // So that new and edited card are always on top when sorting by recently used if (viewModel.getUpdateLoyaltyCard()) { - DBHelper.updateLoyaltyCard(mDatabase, viewModel.getLoyaltyCardId(), viewModel.getLoyaltyCard().store, viewModel.getLoyaltyCard().note, viewModel.getLoyaltyCard().validFrom, viewModel.getLoyaltyCard().expiry, viewModel.getLoyaltyCard().balance, viewModel.getLoyaltyCard().balanceType, viewModel.getLoyaltyCard().cardId, viewModel.getLoyaltyCard().barcodeId, viewModel.getLoyaltyCard().barcodeType, viewModel.getLoyaltyCard().headerColor, viewModel.getLoyaltyCard().starStatus, null, viewModel.getLoyaltyCard().archiveStatus); + DBHelper.updateLoyaltyCard(mDatabase, viewModel.getLoyaltyCardId(), viewModel.getLoyaltyCard().store, viewModel.getLoyaltyCard().note, viewModel.getLoyaltyCard().validFrom, viewModel.getLoyaltyCard().expiry, viewModel.getLoyaltyCard().balance, viewModel.getLoyaltyCard().balanceType, viewModel.getLoyaltyCard().cardId, viewModel.getLoyaltyCard().barcodeId, viewModel.getLoyaltyCard().barcodeType, viewModel.getLoyaltyCard().barcodeEncoding, viewModel.getLoyaltyCard().headerColor, viewModel.getLoyaltyCard().starStatus, null, viewModel.getLoyaltyCard().archiveStatus); } else { - viewModel.setLoyaltyCardId((int) DBHelper.insertLoyaltyCard(mDatabase, viewModel.getLoyaltyCard().store, viewModel.getLoyaltyCard().note, viewModel.getLoyaltyCard().validFrom, viewModel.getLoyaltyCard().expiry, viewModel.getLoyaltyCard().balance, viewModel.getLoyaltyCard().balanceType, viewModel.getLoyaltyCard().cardId, viewModel.getLoyaltyCard().barcodeId, viewModel.getLoyaltyCard().barcodeType, viewModel.getLoyaltyCard().headerColor, 0, null, 0)); + viewModel.setLoyaltyCardId((int) DBHelper.insertLoyaltyCard(mDatabase, viewModel.getLoyaltyCard().store, viewModel.getLoyaltyCard().note, viewModel.getLoyaltyCard().validFrom, viewModel.getLoyaltyCard().expiry, viewModel.getLoyaltyCard().balance, viewModel.getLoyaltyCard().balanceType, viewModel.getLoyaltyCard().cardId, viewModel.getLoyaltyCard().barcodeId, viewModel.getLoyaltyCard().barcodeType, viewModel.getLoyaltyCard().barcodeEncoding, viewModel.getLoyaltyCard().headerColor, 0, null, 0)); } try { @@ -1597,6 +1635,7 @@ private void generateBarcode() { String cardIdString = viewModel.getLoyaltyCard().barcodeId != null ? viewModel.getLoyaltyCard().barcodeId : viewModel.getLoyaltyCard().cardId; CatimaBarcode barcodeFormat = viewModel.getLoyaltyCard().barcodeType; + Charset barcodeEncoding = viewModel.getLoyaltyCard().barcodeEncoding; if (cardIdString == null || cardIdString.isEmpty() || barcodeFormat == null) { barcodeImageLayout.setVisibility(View.GONE); @@ -1616,13 +1655,13 @@ public void onGlobalLayout() { barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this); Log.d(TAG, "ImageView size now known"); - BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, LoyaltyCardEditActivity.this, true, false); + BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, barcodeEncoding, null, false, LoyaltyCardEditActivity.this, true, false); viewModel.getTaskHandler().executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter); } }); } else { Log.d(TAG, "ImageView size known known, creating barcode"); - BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, this, true, false); + BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, barcodeEncoding, null, false, this, true, false); viewModel.getTaskHandler().executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter); } } diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java index 35c0673624..ca8e4b59e2 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java @@ -38,6 +38,7 @@ import android.widget.Toast; import androidx.activity.OnBackPressedCallback; +import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; @@ -57,6 +58,7 @@ import java.io.File; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; +import java.nio.charset.Charset; import java.text.DateFormat; import java.text.ParseException; import java.util.ArrayList; @@ -86,6 +88,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements String cardIdString; String barcodeIdString; CatimaBarcode format; + @Nullable + Charset barcodeEncoding; Bitmap frontImageBitmap; Bitmap backImageBitmap; @@ -685,6 +689,7 @@ protected void onResume() { format = loyaltyCard.barcodeType; cardIdString = loyaltyCard.cardId; barcodeIdString = loyaltyCard.barcodeId; + barcodeEncoding = loyaltyCard.barcodeEncoding; binding.mainImageDescription.setText(loyaltyCard.cardId); @@ -946,6 +951,7 @@ private void drawBarcode(boolean addPadding) { barcodeRenderTarget, barcodeIdString != null ? barcodeIdString : cardIdString, format, + barcodeEncoding, null, false, this, diff --git a/app/src/main/java/protect/card_locker/PkpassParser.kt b/app/src/main/java/protect/card_locker/PkpassParser.kt index 149236150c..b1638aadd7 100644 --- a/app/src/main/java/protect/card_locker/PkpassParser.kt +++ b/app/src/main/java/protect/card_locker/PkpassParser.kt @@ -14,6 +14,7 @@ import org.json.JSONObject import java.io.FileNotFoundException import java.io.IOException import java.math.BigDecimal +import java.nio.charset.Charset import java.text.DateFormat import java.text.ParseException import java.time.ZonedDateTime @@ -40,6 +41,7 @@ class PkpassParser(context: Context, uri: Uri?) { private var cardId: String = context.getString(R.string.noBarcode) private var barcodeId: String? = null private var barcodeType: CatimaBarcode? = null + private var barcodeEncoding: Charset? = null private var headerColor: Int? = null private val starStatus = 0 private val lastUsed: Long = 0 @@ -134,6 +136,7 @@ class PkpassParser(context: Context, uri: Uri?) { cardId, barcodeId, barcodeType, + barcodeEncoding, headerColor, starStatus, lastUsed, @@ -342,13 +345,14 @@ class PkpassParser(context: Context, uri: Uri?) { else -> throw IllegalArgumentException("No valid barcode type") } - // FIXME: We probably need to do something with the messageEncoding field try { cardId = barcodeInfo.getString("altText") barcodeId = barcodeInfo.getString("message") + barcodeEncoding = Charset.forName(barcodeInfo.getString("messageEncoding")) } catch (ignored: JSONException) { cardId = barcodeInfo.getString("message") barcodeId = null + barcodeEncoding = Charset.forName(barcodeInfo.getString("messageEncoding")) } // Don't set barcodeId if it's the same as cardId diff --git a/app/src/main/java/protect/card_locker/importexport/CatimaImporter.java b/app/src/main/java/protect/card_locker/importexport/CatimaImporter.java index 5cc348895c..33be59f33b 100644 --- a/app/src/main/java/protect/card_locker/importexport/CatimaImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/CatimaImporter.java @@ -127,10 +127,10 @@ public Map saveAndDeduplicate(Context context, SQLiteDatabase LoyaltyCard existing = DBHelper.getLoyaltyCard(context, database, card.id); if (existing == null) { DBHelper.insertLoyaltyCard(database, card.id, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType, - card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus); + card.cardId, card.barcodeId, card.barcodeType, card.barcodeEncoding, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus); } else if (!isDuplicate(context, existing, card, existingImages, imageChecksums)) { long newId = DBHelper.insertLoyaltyCard(database, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType, - card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus); + card.cardId, card.barcodeId, card.barcodeType, card.barcodeEncoding, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus); idMap.put(card.id, (int) newId); } } @@ -501,6 +501,7 @@ private LoyaltyCard importLoyaltyCard(CSVRecord record) throws FormatException { cardId, barcodeId, barcodeType, + null, headerColor, starStatus, lastUsed, diff --git a/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java b/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java index d60786ff60..4e61875e11 100644 --- a/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java @@ -160,6 +160,7 @@ private LoyaltyCard importLoyaltyCard(Context context, CSVRecord record) throws cardId, null, barcodeType, + null, headerColor, starStatus, Utils.getUnixTime(), @@ -181,7 +182,7 @@ public void saveAndDeduplicate(SQLiteDatabase database, final ImportedData data) for (LoyaltyCard card : data.cards) { // Do not use card.id which is set to -1 DBHelper.insertLoyaltyCard(database, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType, - card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus); + card.cardId, card.barcodeId, card.barcodeType, card.barcodeEncoding, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus); } } } \ No newline at end of file diff --git a/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java b/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java index bf0dc076a7..2eaecd9f78 100644 --- a/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java @@ -162,6 +162,7 @@ public ImportedData importJSON(JSONArray jsonArray) throws FormatException, JSON cardId, null, barcodeType, + null, headerColor, 0, Utils.getUnixTime(), @@ -186,7 +187,7 @@ public void saveAndDeduplicate(SQLiteDatabase database, final ImportedData data) for (LoyaltyCard card : data.cards) { // Do not use card.id which is set to -1 DBHelper.insertLoyaltyCard(database, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType, - card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus); + card.cardId, card.barcodeId, card.barcodeType, card.barcodeEncoding, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus); } } } \ No newline at end of file diff --git a/app/src/main/res/layout/loyalty_card_edit_activity.xml b/app/src/main/res/layout/loyalty_card_edit_activity.xml index d310cc5e28..f5b93d09e3 100644 --- a/app/src/main/res/layout/loyalty_card_edit_activity.xml +++ b/app/src/main/res/layout/loyalty_card_edit_activity.xml @@ -191,6 +191,32 @@ + + + + + + + + + + %s crash report Ask to send crash reports When enabled, you will be asked to report a crash when it happens. Crash reports are never sent automatically. + Barcode encoding + Automatic