Merge pull request #2038 from CatimaLoyalty/feature/pkpass2024

Add Pkpass parser
This commit is contained in:
Sylvia van Os
2024-12-07 17:33:50 +01:00
committed by GitHub
41 changed files with 1668 additions and 671 deletions

View File

@@ -4,6 +4,7 @@ import com.github.spotbugs.snom.SpotBugsTask
plugins {
id("com.android.application")
id("com.github.spotbugs")
id("org.jetbrains.kotlin.android")
}
spotbugs {
@@ -62,8 +63,8 @@ android {
// Flag to enable support for the new language APIs
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
sourceSets {
@@ -84,25 +85,26 @@ android {
lint {
lintConfig = file("lint.xml")
}
kotlinOptions {
jvmTarget = "17"
}
}
dependencies {
// AndroidX
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("androidx.constraintlayout:constraintlayout:2.2.0")
implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.exifinterface:exifinterface:1.3.7")
implementation("androidx.palette:palette:1.0.0")
implementation("androidx.preference:preference:1.2.1")
implementation("com.google.android.material:material:1.12.0")
implementation("com.github.yalantis:ucrop:2.2.10")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.3")
// Splash Screen
implementation("androidx.core:core-splashscreen:1.0.1")
// Third-party
implementation("com.journeyapps:zxing-android-embedded:4.3.0@aar")
implementation("com.github.yalantis:ucrop:2.2.10")
implementation("com.google.zxing:core:3.5.3")
implementation("org.apache.commons:commons-csv:1.9.0")
implementation("com.jaredrummler:colorpicker:1.1.0")

View File

@@ -40,12 +40,24 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content"/>
<data android:host="*"/>
<data android:mimeType="image/*" />
<data android:mimeType="application/pdf" />
<data android:mimeType="application/vnd.apple.pkpass" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
<data android:mimeType="image/*" />
<data android:mimeType="application/pdf" />
<data android:mimeType="application/vnd.apple.pkpass" />
</intent-filter>
</activity>
<activity

View File

@@ -1,29 +0,0 @@
package protect.card_locker;
import androidx.annotation.Nullable;
public class BarcodeValues {
@Nullable
private final CatimaBarcode mFormat;
private final String mContent;
private String mNote;
public BarcodeValues(@Nullable CatimaBarcode format, String content) {
mFormat = format;
mContent = content;
}
public void setNote(String note) {
mNote = note;
}
public @Nullable CatimaBarcode format() {
return mFormat;
}
public String content() {
return mContent;
}
public String note() { return mNote; }
}

View File

@@ -1,6 +0,0 @@
package protect.card_locker;
public interface BarcodeValuesListDisambiguatorCallback {
void onUserChoseBarcode(BarcodeValues barcodeValues);
void onUserDismissedSelector();
}

View File

@@ -66,7 +66,7 @@ public class CardShortcutConfigure extends CatimaAppCompatActivity implements Lo
private void onClickAction(int position) {
Cursor selected = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.All);
selected.moveToPosition(position);
LoyaltyCard loyaltyCard = LoyaltyCard.fromCursor(selected);
LoyaltyCard loyaltyCard = LoyaltyCard.fromCursor(CardShortcutConfigure.this, selected);
Log.d(TAG, "Creating shortcut for card " + loyaltyCard.store + "," + loyaltyCard.id);

View File

@@ -42,7 +42,7 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
Cursor loyaltyCardCursor = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.Unarchived);
return subscriber -> {
while (loyaltyCardCursor.moveToNext()) {
LoyaltyCard card = LoyaltyCard.fromCursor(loyaltyCardCursor);
LoyaltyCard card = LoyaltyCard.fromCursor(this, loyaltyCardCursor);
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(LoyaltyCardViewActivity.BUNDLE_ID, card.id);
@@ -69,7 +69,7 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
for (String controlId : controlIds) {
Control control;
Integer cardId = this.controlIdToCardId(controlId);
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, cardId);
LoyaltyCard card = DBHelper.getLoyaltyCard(this, mDatabase, cardId);
if (card != null) {
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -99,7 +99,7 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
}
private Bitmap getIcon(Context context, LoyaltyCard loyaltyCard) {
Bitmap cardIcon = Utils.retrieveCardImage(context, loyaltyCard.id, ImageLocationType.icon);
Bitmap cardIcon = loyaltyCard.getImageThumbnail(context);
if (cardIcon != null) {
return cardIcon;

View File

@@ -332,10 +332,10 @@ public class DBHelper extends SQLiteOpenHelper {
Set<String> files = new HashSet<>();
Cursor cardCursor = getLoyaltyCardCursor(database);
while (cardCursor.moveToNext()) {
LoyaltyCard card = LoyaltyCard.fromCursor(cardCursor);
LoyaltyCard card = LoyaltyCard.fromCursor(context, cardCursor);
for (ImageLocationType imageLocationType : ImageLocationType.values()) {
String name = Utils.getCardImageFileName(card.id, imageLocationType);
if (Utils.retrieveCardImageAsFile(context, name).exists()) {
if (card.getImageForImageLocationType(context, imageLocationType) != null) {
files.add(name);
}
}
@@ -535,14 +535,14 @@ public class DBHelper extends SQLiteOpenHelper {
return (rowsUpdated == 1);
}
public static LoyaltyCard getLoyaltyCard(SQLiteDatabase database, final int id) {
public static LoyaltyCard getLoyaltyCard(Context context, SQLiteDatabase database, final int id) {
Cursor data = database.query(LoyaltyCardDbIds.TABLE, null, whereAttrs(LoyaltyCardDbIds.ID), withArgs(id), null, null, null);
LoyaltyCard card = null;
if (data.getCount() == 1) {
data.moveToFirst();
card = LoyaltyCard.fromCursor(data);
card = LoyaltyCard.fromCursor(context, data);
}
data.close();

View File

@@ -125,7 +125,29 @@ public class ImportURIHelper {
headerColor = Integer.parseInt(unparsedHeaderColor);
}
return new LoyaltyCard(-1, store, note, validFrom, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0, Utils.getUnixTime(), 100, 0);
return new LoyaltyCard(
-1,
store,
note,
validFrom,
expiry,
balance,
balanceType,
cardId,
barcodeId,
barcodeType,
headerColor,
0,
Utils.getUnixTime(),
100,
0,
null,
null,
null,
null,
null,
null
);
} catch (NumberFormatException | UnsupportedEncodingException | ArrayIndexOutOfBoundsException ex) {
throw new InvalidObjectException("Not a valid import URI");
}

View File

@@ -1,9 +1,9 @@
package protect.card_locker;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -11,11 +11,10 @@ import androidx.annotation.Nullable;
import java.math.BigDecimal;
import java.util.Currency;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.Objects;
public class LoyaltyCard implements Parcelable {
public class LoyaltyCard {
public int id;
public String store;
public String note;
@@ -38,6 +37,19 @@ public class LoyaltyCard implements Parcelable {
public int zoomLevel;
public int archiveStatus;
@Nullable
private Bitmap imageThumbnail;
@Nullable
private String imageThumbnailPath;
@Nullable
private Bitmap imageFront;
@Nullable
private String imageFrontPath;
@Nullable
private Bitmap imageBack;
@Nullable
private String imageBackPath;
public static final String BUNDLE_LOYALTY_CARD_ID = "loyaltyCardId";
public static final String BUNDLE_LOYALTY_CARD_STORE = "loyaltyCardStore";
public static final String BUNDLE_LOYALTY_CARD_NOTE = "loyaltyCardNote";
@@ -53,6 +65,13 @@ public class LoyaltyCard implements Parcelable {
public static final String BUNDLE_LOYALTY_CARD_LAST_USED = "loyaltyCardLastUsed";
public static final String BUNDLE_LOYALTY_CARD_ZOOM_LEVEL = "loyaltyCardZoomLevel";
public static final String BUNDLE_LOYALTY_CARD_ARCHIVE_STATUS = "loyaltyCardArchiveStatus";
public static final String BUNDLE_LOYALTY_CARD_IMAGE_THUMBNAIL = "loyaltyCardImageThumbnail";
public static final String BUNDLE_LOYALTY_CARD_IMAGE_FRONT = "loyaltyCardImageFront";
public static final String BUNDLE_LOYALTY_CARD_IMAGE_BACK = "loyaltyCardImageBack";
private static final String TEMP_IMAGE_THUMBNAIL_FILE_NAME = "loyaltyCardTempImageThumbnailFileName";
private static final String TEMP_IMAGE_FRONT_FILE_NAME = "loyaltyCardTempImageFrontFileName";
private static final String TEMP_IMAGE_BACK_FILE_NAME = "loyaltyCardTempImageBackFileName";
/**
* Create a loyalty card object with default values
@@ -73,6 +92,9 @@ public class LoyaltyCard implements Parcelable {
setLastUsed(Utils.getUnixTime());
setZoomLevel(100);
setArchiveStatus(0);
setImageThumbnail(null, null);
setImageFront(null, null);
setImageBack(null, null);
}
/**
@@ -98,7 +120,10 @@ public class LoyaltyCard implements Parcelable {
@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,
final long lastUsed, final int zoomLevel, final int archiveStatus) {
final long lastUsed, final int zoomLevel, final int archiveStatus,
@Nullable Bitmap imageThumbnail, @Nullable String imageThumbnailPath,
@Nullable Bitmap imageFront, @Nullable String imageFrontPath,
@Nullable Bitmap imageBack, @Nullable String imageBackPath) {
setId(id);
setStore(store);
setNote(note);
@@ -114,6 +139,63 @@ public class LoyaltyCard implements Parcelable {
setLastUsed(lastUsed);
setZoomLevel(zoomLevel);
setArchiveStatus(archiveStatus);
setImageThumbnail(imageThumbnail, imageThumbnailPath);
setImageFront(imageFront, imageFrontPath);
setImageBack(imageBack, imageBackPath);
}
@Nullable
public Bitmap getImageThumbnail(Context context) {
if (imageThumbnailPath != null) {
if (imageThumbnailPath.equals(TEMP_IMAGE_THUMBNAIL_FILE_NAME)) {
imageThumbnail = Utils.loadTempImage(context, imageThumbnailPath);
} else {
imageThumbnail = Utils.retrieveCardImage(context, imageThumbnailPath);
}
imageThumbnailPath = null;
}
if (imageThumbnail == null) {
return null;
}
return imageThumbnail.copy(imageThumbnail.getConfig(), imageThumbnail.isMutable());
}
@Nullable
public Bitmap getImageFront(Context context) {
if (imageFrontPath != null) {
if (imageFrontPath.equals(TEMP_IMAGE_FRONT_FILE_NAME)) {
imageFront = Utils.loadTempImage(context, imageFrontPath);
} else {
imageFront = Utils.retrieveCardImage(context, imageFrontPath);
}
imageFrontPath = null;
}
if (imageFront == null) {
return null;
}
return imageFront.copy(imageFront.getConfig(), imageFront.isMutable());
}
@Nullable
public Bitmap getImageBack(Context context) {
if (imageBackPath != null) {
if (imageBackPath.equals(TEMP_IMAGE_BACK_FILE_NAME)) {
imageBack = Utils.loadTempImage(context, imageBackPath);
} else {
imageBack = Utils.retrieveCardImage(context, imageBackPath);
}
imageBackPath = null;
}
if (imageBack == null) {
return null;
}
return imageBack.copy(imageBack.getConfig(), imageBack.isMutable());
}
public void setId(int id) {
@@ -188,59 +270,47 @@ public class LoyaltyCard implements Parcelable {
this.archiveStatus = archiveStatus;
}
protected LoyaltyCard(Parcel in) {
setId(in.readInt());
setStore(Objects.requireNonNull(in.readString()));
setNote(Objects.requireNonNull(in.readString()));
long tmpValidFrom = in.readLong();
setValidFrom(tmpValidFrom > 0 ? new Date(tmpValidFrom) : null);
long tmpExpiry = in.readLong();
setExpiry(tmpExpiry > 0 ? new Date(tmpExpiry) : null);
setBalance((BigDecimal) in.readValue(BigDecimal.class.getClassLoader()));
setBalanceType((Currency) in.readValue(Currency.class.getClassLoader()));
setCardId(Objects.requireNonNull(in.readString()));
setBarcodeId(in.readString());
String tmpBarcodeType = in.readString();
setBarcodeType((tmpBarcodeType != null && !tmpBarcodeType.isEmpty()) ? CatimaBarcode.fromName(tmpBarcodeType) : null);
int tmpHeaderColor = in.readInt();
setHeaderColor(tmpHeaderColor != -1 ? tmpHeaderColor : null);
setStarStatus(in.readInt());
setLastUsed(in.readLong());
setZoomLevel(in.readInt());
setArchiveStatus(in.readInt());
public void setImageThumbnail(@Nullable Bitmap imageThumbnail, @Nullable String imageThumbnailPath) {
if (imageThumbnail != null && imageThumbnailPath != null) {
throw new IllegalArgumentException("Cannot set both thumbnail and path");
}
this.imageThumbnailPath = imageThumbnailPath;
this.imageThumbnail = imageThumbnail != null ? imageThumbnail.copy(imageThumbnail.getConfig(), imageThumbnail.isMutable()) : null;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(id);
parcel.writeString(store);
parcel.writeString(note);
parcel.writeLong(validFrom != null ? validFrom.getTime() : -1);
parcel.writeLong(expiry != null ? expiry.getTime() : -1);
parcel.writeValue(balance);
parcel.writeValue(balanceType);
parcel.writeString(cardId);
parcel.writeString(barcodeId);
parcel.writeString(barcodeType != null ? barcodeType.name() : "");
parcel.writeInt(headerColor != null ? headerColor : -1);
parcel.writeInt(starStatus);
parcel.writeLong(lastUsed);
parcel.writeInt(zoomLevel);
parcel.writeInt(archiveStatus);
public void setImageFront(@Nullable Bitmap imageFront, @Nullable String imageFrontPath) {
if (imageFront != null && imageFrontPath != null) {
throw new IllegalArgumentException("Cannot set both thumbnail and path");
}
this.imageFrontPath = imageFrontPath;
this.imageFront = imageFront != null ? imageFront.copy(imageFront.getConfig(), imageFront.isMutable()) : null;
}
public static LoyaltyCard fromBundle(Bundle bundle, boolean requireFull) {
// Grab default card
LoyaltyCard loyaltyCard = new LoyaltyCard();
public void setImageBack(@Nullable Bitmap imageBack, @Nullable String imageBackPath) {
if (imageBack != null && imageBackPath != null) {
throw new IllegalArgumentException("Cannot set both thumbnail and path");
}
// Update from bundle
loyaltyCard.updateFromBundle(bundle, requireFull);
// Return updated version
return loyaltyCard;
this.imageBackPath = imageBackPath;
this.imageBack = imageBack != null ? imageBack.copy(imageBack.getConfig(), imageBack.isMutable()) : null;
}
public void updateFromBundle(Bundle bundle, boolean requireFull) {
@Nullable
public Bitmap getImageForImageLocationType(Context context, ImageLocationType imageLocationType) {
if (imageLocationType == ImageLocationType.icon) {
return getImageThumbnail(context);
} else if (imageLocationType == ImageLocationType.front) {
return getImageFront(context);
} else if (imageLocationType == ImageLocationType.back) {
return getImageBack(context);
}
throw new IllegalArgumentException("Unknown image location type");
}
public void updateFromBundle(@NonNull Bundle bundle, boolean requireFull) {
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_ID)) {
setId(bundle.getInt(BUNDLE_LOYALTY_CARD_ID));
} else if (requireFull) {
@@ -321,41 +391,107 @@ public class LoyaltyCard implements Parcelable {
} else if (requireFull) {
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_ARCHIVE_STATUS);
}
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_IMAGE_THUMBNAIL)) {
setImageThumbnail(null, bundle.getString(BUNDLE_LOYALTY_CARD_IMAGE_THUMBNAIL));
} else if (requireFull) {
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_IMAGE_THUMBNAIL);
}
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_IMAGE_FRONT)) {
setImageFront(null, bundle.getString(BUNDLE_LOYALTY_CARD_IMAGE_FRONT));
} else if (requireFull) {
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_IMAGE_FRONT);
}
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_IMAGE_BACK)) {
setImageBack(null, bundle.getString(BUNDLE_LOYALTY_CARD_IMAGE_BACK));
} else if (requireFull) {
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_IMAGE_BACK);
}
}
public Bundle toBundle() {
public Bundle toBundle(Context context, List<String> exportLimit) {
boolean exportIsLimited = !exportLimit.isEmpty();
Bundle bundle = new Bundle();
bundle.putInt(BUNDLE_LOYALTY_CARD_ID, id);
bundle.putString(BUNDLE_LOYALTY_CARD_STORE, store);
bundle.putString(BUNDLE_LOYALTY_CARD_NOTE, note);
if (validFrom != null) {
bundle.putLong(BUNDLE_LOYALTY_CARD_VALID_FROM, validFrom.getTime());
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_ID)) {
bundle.putInt(BUNDLE_LOYALTY_CARD_ID, id);
}
if (expiry != null) {
bundle.putLong(BUNDLE_LOYALTY_CARD_EXPIRY, expiry.getTime());
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_STORE)) {
bundle.putString(BUNDLE_LOYALTY_CARD_STORE, store);
}
bundle.putString(BUNDLE_LOYALTY_CARD_BALANCE, balance.toString());
if (balanceType != null) {
bundle.putString(BUNDLE_LOYALTY_CARD_BALANCE_TYPE, balanceType.toString());
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_NOTE)) {
bundle.putString(BUNDLE_LOYALTY_CARD_NOTE, note);
}
bundle.putString(BUNDLE_LOYALTY_CARD_CARD_ID, cardId);
bundle.putString(BUNDLE_LOYALTY_CARD_BARCODE_ID, barcodeId);
if (barcodeType != null) {
bundle.putString(BUNDLE_LOYALTY_CARD_BARCODE_TYPE, barcodeType.name());
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_VALID_FROM)) {
bundle.putLong(BUNDLE_LOYALTY_CARD_VALID_FROM, validFrom != null ? validFrom.getTime() : -1);
}
if (headerColor != null) {
bundle.putInt(BUNDLE_LOYALTY_CARD_HEADER_COLOR, headerColor);
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_EXPIRY)) {
bundle.putLong(BUNDLE_LOYALTY_CARD_EXPIRY, expiry != null ? expiry.getTime() : -1);
}
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_BALANCE)) {
bundle.putString(BUNDLE_LOYALTY_CARD_BALANCE, balance.toString());
}
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_BALANCE_TYPE)) {
bundle.putString(BUNDLE_LOYALTY_CARD_BALANCE_TYPE, balanceType != null ? balanceType.toString() : null);
}
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_CARD_ID)) {
bundle.putString(BUNDLE_LOYALTY_CARD_CARD_ID, cardId);
}
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_BARCODE_ID)) {
bundle.putString(BUNDLE_LOYALTY_CARD_BARCODE_ID, barcodeId);
}
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_HEADER_COLOR)) {
bundle.putInt(BUNDLE_LOYALTY_CARD_HEADER_COLOR, headerColor != null ? headerColor : -1);
}
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_STAR_STATUS)) {
bundle.putInt(BUNDLE_LOYALTY_CARD_STAR_STATUS, starStatus);
}
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_LAST_USED)) {
bundle.putLong(BUNDLE_LOYALTY_CARD_LAST_USED, lastUsed);
}
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_ZOOM_LEVEL)) {
bundle.putInt(BUNDLE_LOYALTY_CARD_ZOOM_LEVEL, zoomLevel);
}
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_ARCHIVE_STATUS)) {
bundle.putInt(BUNDLE_LOYALTY_CARD_ARCHIVE_STATUS, archiveStatus);
}
// There is an (undocumented) size limit to bundles of around 2MB(?), when going over it you will experience a random crash
// So, instead of storing the bitmaps directly, we write the bitmap to a temp file and store the path
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_IMAGE_THUMBNAIL)) {
Bitmap thumbnail = getImageThumbnail(context);
if (thumbnail != null) {
Utils.saveTempImage(context, thumbnail, TEMP_IMAGE_THUMBNAIL_FILE_NAME, Bitmap.CompressFormat.PNG);
bundle.putString(BUNDLE_LOYALTY_CARD_IMAGE_THUMBNAIL, TEMP_IMAGE_THUMBNAIL_FILE_NAME);
} else {
bundle.putString(BUNDLE_LOYALTY_CARD_IMAGE_THUMBNAIL, null);
}
}
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_IMAGE_FRONT)) {
Bitmap front = getImageFront(context);
if (front != null) {
Utils.saveTempImage(context, front, TEMP_IMAGE_FRONT_FILE_NAME, Bitmap.CompressFormat.PNG);
bundle.putString(BUNDLE_LOYALTY_CARD_IMAGE_FRONT, TEMP_IMAGE_FRONT_FILE_NAME);
} else {
bundle.putString(BUNDLE_LOYALTY_CARD_IMAGE_FRONT, null);
}
}
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_IMAGE_BACK)) {
Bitmap back = getImageBack(context);
if (back != null) {
Utils.saveTempImage(context, back, TEMP_IMAGE_BACK_FILE_NAME, Bitmap.CompressFormat.PNG);
bundle.putString(BUNDLE_LOYALTY_CARD_IMAGE_BACK, TEMP_IMAGE_BACK_FILE_NAME);
} else {
bundle.putString(BUNDLE_LOYALTY_CARD_IMAGE_BACK, null);
}
}
bundle.putInt(BUNDLE_LOYALTY_CARD_STAR_STATUS, starStatus);
bundle.putLong(BUNDLE_LOYALTY_CARD_LAST_USED, lastUsed);
bundle.putInt(BUNDLE_LOYALTY_CARD_ZOOM_LEVEL, zoomLevel);
bundle.putInt(BUNDLE_LOYALTY_CARD_ARCHIVE_STATUS, archiveStatus);
return bundle;
}
public static LoyaltyCard fromCursor(Cursor cursor) {
public static LoyaltyCard fromCursor(Context context, Cursor cursor) {
// id
int id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
// store
@@ -393,10 +529,33 @@ public class LoyaltyCard implements Parcelable {
// archiveStatus
int archiveStatus = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS));
return new LoyaltyCard(id, store, note, validFrom, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus, lastUsed, zoomLevel, archiveStatus);
return new LoyaltyCard(
id,
store,
note,
validFrom,
expiry,
balance,
balanceType,
cardId,
barcodeId,
barcodeType,
headerColor,
starStatus,
lastUsed,
zoomLevel,
archiveStatus,
null,
Utils.getCardImageFileName(id, ImageLocationType.icon),
null,
Utils.getCardImageFileName(id, ImageLocationType.front),
null,
Utils.getCardImageFileName(id, ImageLocationType.back)
);
}
public static boolean isDuplicate(final LoyaltyCard a, final LoyaltyCard b) {
public static boolean isDuplicate(Context context, final LoyaltyCard a, final LoyaltyCard b) {
// Note: Bitmap comparing is slow, be careful when calling this method
// Skip lastUsed & zoomLevel
return a.id == b.id && // non-nullable int
a.store.equals(b.store) && // non-nullable String
@@ -411,12 +570,23 @@ public class LoyaltyCard implements Parcelable {
b.barcodeType == null ? null : b.barcodeType.format()) && // nullable CatimaBarcode with no overridden .equals(), so we need to check .format()
Utils.equals(a.headerColor, b.headerColor) && // nullable Integer
a.starStatus == b.starStatus && // non-nullable int
a.archiveStatus == b.archiveStatus; // non-nullable int
a.archiveStatus == b.archiveStatus && // non-nullable int
nullableBitmapsEqual(a.getImageThumbnail(context), b.getImageThumbnail(context)) && // nullable Bitmap
nullableBitmapsEqual(a.getImageFront(context), b.getImageFront(context)) && // nullable Bitmap
nullableBitmapsEqual(a.getImageBack(context), b.getImageBack(context)); // nullable Bitmap
}
@Override
public int describeContents() {
return 0;
public static boolean nullableBitmapsEqual(@Nullable Bitmap a, @Nullable Bitmap b) {
if (a == null && b == null) {
return true;
}
if (a != null && b != null) {
return a.sameAs(b);
}
// One is null and the other isn't, so it's not equal
return false;
}
@NonNull
@@ -425,7 +595,8 @@ public class LoyaltyCard implements Parcelable {
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"
+ " headerColor=%s,%n starStatus=%s,%n lastUsed=%s,%n zoomLevel=%s,%n archiveStatus=%s%n}",
+ " headerColor=%s,%n starStatus=%s,%n lastUsed=%s,%n zoomLevel=%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,
this.store,
this.note,
@@ -440,19 +611,13 @@ public class LoyaltyCard implements Parcelable {
this.starStatus,
this.lastUsed,
this.zoomLevel,
this.archiveStatus
this.archiveStatus,
this.imageThumbnail,
this.imageThumbnailPath,
this.imageFront,
this.imageFrontPath,
this.imageBack,
this.imageBackPath
);
}
public static final Creator<LoyaltyCard> CREATOR = new Creator<LoyaltyCard>() {
@Override
public LoyaltyCard createFromParcel(Parcel in) {
return new LoyaltyCard(in);
}
@Override
public LoyaltyCard[] newArray(int size) {
return new LoyaltyCard[size];
}
};
}

View File

@@ -80,7 +80,7 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
public LoyaltyCard getCard(int position) {
mCursor.moveToPosition(position);
return LoyaltyCard.fromCursor(mCursor);
return LoyaltyCard.fromCursor(mContext, mCursor);
}
public void onBindViewHolder(LoyaltyCardListItemViewHolder inputHolder, Cursor inputCursor) {
@@ -88,8 +88,8 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
boolean showDivider = false;
inputHolder.mDivider.setVisibility(View.GONE);
LoyaltyCard loyaltyCard = LoyaltyCard.fromCursor(inputCursor);
Bitmap icon = Utils.retrieveCardImage(mContext, loyaltyCard.id, ImageLocationType.icon);
LoyaltyCard loyaltyCard = LoyaltyCard.fromCursor(mContext, inputCursor);
Bitmap icon = loyaltyCard.getImageThumbnail(mContext);
if (mLoyaltyCardListDisplayOptions.showingNameBelowThumbnail() && icon != null) {
showDivider = true;
@@ -193,7 +193,7 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
int i;
for (i = 0; i < mSelectedItems.size(); i++) {
mCursor.moveToPosition(mSelectedItems.keyAt(i));
result.add(LoyaltyCard.fromCursor(mCursor));
result.add(LoyaltyCard.fromCursor(mContext, mCursor));
}
return result;

View File

@@ -45,6 +45,7 @@ import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.exifinterface.media.ExifInterface;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
@@ -85,24 +86,12 @@ import java.util.concurrent.Callable;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.databinding.LayoutChipChoiceBinding;
import protect.card_locker.databinding.LoyaltyCardEditActivityBinding;
import protect.card_locker.viewmodels.LoyaltyCardEditActivityViewModel;
public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements BarcodeImageWriterResultCallback, ColorPickerDialogListener {
private LoyaltyCardEditActivityBinding binding;
private static final String TAG = "Catima";
private final String STATE_TAB_INDEX = "savedTab";
private final String STATE_TEMP_CARD = "tempLoyaltyCard";
private final String STATE_TEMP_CARD_FIELD = "tempLoyaltyCardField";
private final String STATE_REQUESTED_IMAGE = "requestedImage";
private final String STATE_FRONT_IMAGE_UNSAVED = "frontImageUnsaved";
private final String STATE_BACK_IMAGE_UNSAVED = "backImageUnsaved";
private final String STATE_ICON_UNSAVED = "iconUnsaved";
private final String STATE_UPDATE_LOYALTY_CARD = "updateLoyaltyCard";
private final String STATE_HAS_CHANGED = "hasChange";
private final String STATE_FRONT_IMAGE_REMOVED = "frontImageRemoved";
private final String STATE_BACK_IMAGE_REMOVED = "backImageRemoved";
private final String STATE_ICON_REMOVED = "iconRemoved";
private final String STATE_OPEN_SET_ICON_MENU = "openSetIconMenu";
protected LoyaltyCardEditActivityViewModel viewModel;
private LoyaltyCardEditActivityBinding binding;
private static final String PICK_DATE_REQUEST_KEY = "pick_date_request";
private static final String NEWLY_PICKED_DATE_ARGUMENT_KEY = "newly_picked_date";
@@ -111,11 +100,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
private final String TEMP_CROP_IMAGE_NAME = LoyaltyCardEditActivity.class.getSimpleName() + "_crop_image.png";
private final Bitmap.CompressFormat TEMP_CROP_IMAGE_FORMAT = Bitmap.CompressFormat.PNG;
private final String TEMP_UNSAVED_FRONT_IMAGE_NAME = LoyaltyCardEditActivity.class.getSimpleName() + "_front_image.png";
private final String TEMP_UNSAVED_BACK_IMAGE_NAME = LoyaltyCardEditActivity.class.getSimpleName() + "_back_image.png";
private final String TEMP_UNSAVED_ICON_NAME = LoyaltyCardEditActivity.class.getSimpleName() + "_icon.png";
private final Bitmap.CompressFormat TEMP_UNSAVED_IMAGE_FORMAT = Bitmap.CompressFormat.PNG;
private static final int PERMISSION_REQUEST_CAMERA_IMAGE_FRONT = 100;
private static final int PERMISSION_REQUEST_CAMERA_IMAGE_BACK = 101;
private static final int PERMISSION_REQUEST_CAMERA_IMAGE_ICON = 102;
@@ -129,8 +113,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
public static final String BUNDLE_OPEN_SET_ICON_MENU = "openSetIconMenu";
public static final String BUNDLE_ADDGROUP = "addGroup";
TabLayout tabs;
ImageView thumbnail;
ImageView thumbnailEditIcon;
EditText storeFieldEdit;
@@ -155,17 +137,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
Toolbar toolbar;
int loyaltyCardId;
boolean updateLoyaltyCard;
boolean duplicateFromLoyaltyCardId;
boolean openSetIconMenu;
String addGroup;
Uri importLoyaltyCardUri = null;
SQLiteDatabase mDatabase;
boolean hasChanged = false;
String tempStoredOldBarcodeValue = null;
boolean initDone = false;
boolean onResuming = false;
@@ -176,28 +149,13 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
HashMap<String, Currency> currencies = new HashMap<>();
HashMap<String, String> currencySymbols = new HashMap<>();
LoyaltyCard tempLoyaltyCard = new LoyaltyCard();
LoyaltyCardField tempLoyaltyCardField;
ActivityResultLauncher<Uri> mPhotoTakerLauncher;
ActivityResultLauncher<Intent> mPhotoPickerLauncher;
ActivityResultLauncher<Intent> mCardIdAndBarCodeEditorLauncher;
ActivityResultLauncher<Intent> mCropperLauncher;
int mRequestedImage = 0;
int mCropperFinishedType = 0;
UCrop.Options mCropperOptions;
boolean mFrontImageUnsaved = false;
boolean mBackImageUnsaved = false;
boolean mIconUnsaved = false;
boolean mFrontImageRemoved = false;
boolean mBackImageRemoved = false;
boolean mIconRemoved = false;
final private TaskHandler mTasks = new TaskHandler();
// store system locale for Build.VERSION.SDK_INT < Build.VERSION_CODES.N
private Locale mSystemLocale;
@@ -209,95 +167,100 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
protected void setLoyaltyCardStore(@NonNull String store) {
tempLoyaltyCard.setStore(store);
viewModel.getLoyaltyCard().setStore(store);
hasChanged = true;
viewModel.setHasChanged(true);
}
protected void setLoyaltyCardNote(@NonNull String note) {
tempLoyaltyCard.setNote(note);
viewModel.getLoyaltyCard().setNote(note);
hasChanged = true;
viewModel.setHasChanged(true);
}
protected void setLoyaltyCardValidFrom(@Nullable Date validFrom) {
tempLoyaltyCard.setValidFrom(validFrom);
viewModel.getLoyaltyCard().setValidFrom(validFrom);
hasChanged = true;
viewModel.setHasChanged(true);
}
protected void setLoyaltyCardExpiry(@Nullable Date expiry) {
tempLoyaltyCard.setExpiry(expiry);
viewModel.getLoyaltyCard().setExpiry(expiry);
hasChanged = true;
viewModel.setHasChanged(true);
}
protected void setLoyaltyCardBalance(@NonNull BigDecimal balance) {
tempLoyaltyCard.setBalance(balance);
viewModel.getLoyaltyCard().setBalance(balance);
hasChanged = true;
viewModel.setHasChanged(true);
}
protected void setLoyaltyCardBalanceType(@Nullable Currency balanceType) {
tempLoyaltyCard.setBalanceType(balanceType);
viewModel.getLoyaltyCard().setBalanceType(balanceType);
hasChanged = true;
viewModel.setHasChanged(true);
}
protected void setLoyaltyCardCardId(@NonNull String cardId) {
tempLoyaltyCard.setCardId(cardId);
viewModel.getLoyaltyCard().setCardId(cardId);
generateBarcode();
hasChanged = true;
viewModel.setHasChanged(true);
}
protected void setLoyaltyCardBarcodeId(@Nullable String barcodeId) {
tempLoyaltyCard.setBarcodeId(barcodeId);
viewModel.getLoyaltyCard().setBarcodeId(barcodeId);
generateBarcode();
hasChanged = true;
viewModel.setHasChanged(true);
}
protected void setLoyaltyCardBarcodeType(@Nullable CatimaBarcode barcodeType) {
tempLoyaltyCard.setBarcodeType(barcodeType);
viewModel.getLoyaltyCard().setBarcodeType(barcodeType);
generateBarcode();
hasChanged = true;
viewModel.setHasChanged(true);
}
protected void setLoyaltyCardHeaderColor(@Nullable Integer headerColor) {
tempLoyaltyCard.setHeaderColor(headerColor);
viewModel.getLoyaltyCard().setHeaderColor(headerColor);
hasChanged = true;
viewModel.setHasChanged(true);
}
/* Extract intent fields and return if code should keep running */
private boolean extractIntentFields(Intent intent) {
final Bundle b = intent.getExtras();
addGroup = b != null ? b.getString(BUNDLE_ADDGROUP) : null;
openSetIconMenu = b != null && b.getBoolean(BUNDLE_OPEN_SET_ICON_MENU, false);
viewModel.setAddGroup(b != null ? b.getString(BUNDLE_ADDGROUP) : null);
viewModel.setOpenSetIconMenu(b != null && b.getBoolean(BUNDLE_OPEN_SET_ICON_MENU, false));
loyaltyCardId = b != null ? b.getInt(BUNDLE_ID) : 0;
updateLoyaltyCard = b != null && b.getBoolean(BUNDLE_UPDATE, false);
duplicateFromLoyaltyCardId = b != null && b.getBoolean(BUNDLE_DUPLICATE_ID, false);
importLoyaltyCardUri = intent.getData();
viewModel.setLoyaltyCardId(b != null ? b.getInt(BUNDLE_ID) : 0);
viewModel.setUpdateLoyaltyCard(b != null && b.getBoolean(BUNDLE_UPDATE, false));
viewModel.setDuplicateFromLoyaltyCardId(b != null && b.getBoolean(BUNDLE_DUPLICATE_ID, false));
viewModel.setImportLoyaltyCardUri(intent.getData());
Uri importLoyaltyCardUri = viewModel.getImportLoyaltyCardUri();
// If we have to import a loyalty card, do so
if (updateLoyaltyCard || duplicateFromLoyaltyCardId) {
tempLoyaltyCard = DBHelper.getLoyaltyCard(mDatabase, loyaltyCardId);
if (tempLoyaltyCard == null) {
Log.w(TAG, "Could not lookup loyalty card " + loyaltyCardId);
if (viewModel.getUpdateLoyaltyCard() || viewModel.getDuplicateFromLoyaltyCardId()) {
// Retrieve from database
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(this, mDatabase, viewModel.getLoyaltyCardId());
if (loyaltyCard == null) {
Log.w(TAG, "Could not lookup loyalty card " + viewModel.getLoyaltyCardId());
Toast.makeText(this, R.string.noCardExistsError, Toast.LENGTH_LONG).show();
finish();
return false;
}
viewModel.setLoyaltyCard(loyaltyCard);
} else if (importLoyaltyCardUri != null) {
// Load from URI
try {
tempLoyaltyCard = new ImportURIHelper(this).parse(importLoyaltyCardUri);
viewModel.setLoyaltyCard(new ImportURIHelper(this).parse(importLoyaltyCardUri));
} catch (InvalidObjectException ex) {
Toast.makeText(this, R.string.failedParsingImportUriError, Toast.LENGTH_LONG).show();
finish();
@@ -307,11 +270,13 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
// If the intent contains any loyalty card fields, override those fields in our current temp card
if (b != null) {
tempLoyaltyCard.updateFromBundle(b, false);
LoyaltyCard loyaltyCard = viewModel.getLoyaltyCard();
loyaltyCard.updateFromBundle(b, false);
viewModel.setLoyaltyCard(loyaltyCard);
}
Log.d(TAG, "Edit activity: id=" + loyaltyCardId
+ ", updateLoyaltyCard=" + updateLoyaltyCard);
Log.d(TAG, "Edit activity: id=" + viewModel.getLoyaltyCardId()
+ ", updateLoyaltyCard=" + viewModel.getUpdateLoyaltyCard());
return true;
}
@@ -319,59 +284,12 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
@Override
public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
tabs = binding.tabs;
savedInstanceState.putInt(STATE_TAB_INDEX, tabs.getSelectedTabPosition());
savedInstanceState.putParcelable(STATE_TEMP_CARD, tempLoyaltyCard);
savedInstanceState.putSerializable(STATE_TEMP_CARD_FIELD, tempLoyaltyCardField);
savedInstanceState.putInt(STATE_REQUESTED_IMAGE, mRequestedImage);
Object cardImageFrontObj = cardImageFront.getTag();
if (mFrontImageUnsaved && (cardImageFrontObj instanceof Bitmap) && Utils.saveTempImage(this, (Bitmap) cardImageFrontObj, TEMP_UNSAVED_FRONT_IMAGE_NAME, TEMP_UNSAVED_IMAGE_FORMAT) != null) {
savedInstanceState.putInt(STATE_FRONT_IMAGE_UNSAVED, 1);
} else {
savedInstanceState.putInt(STATE_FRONT_IMAGE_UNSAVED, 0);
}
Object cardImageBackObj = cardImageBack.getTag();
if (mBackImageUnsaved && (cardImageBackObj instanceof Bitmap) && Utils.saveTempImage(this, (Bitmap) cardImageBackObj, TEMP_UNSAVED_BACK_IMAGE_NAME, TEMP_UNSAVED_IMAGE_FORMAT) != null) {
savedInstanceState.putInt(STATE_BACK_IMAGE_UNSAVED, 1);
} else {
savedInstanceState.putInt(STATE_BACK_IMAGE_UNSAVED, 0);
}
Object thumbnailObj = thumbnail.getTag();
if (mIconUnsaved && (thumbnailObj instanceof Bitmap) && Utils.saveTempImage(this, (Bitmap) thumbnailObj, TEMP_UNSAVED_ICON_NAME, TEMP_UNSAVED_IMAGE_FORMAT) != null) {
savedInstanceState.putInt(STATE_ICON_UNSAVED, 1);
} else {
savedInstanceState.putInt(STATE_ICON_UNSAVED, 0);
}
savedInstanceState.putInt(STATE_UPDATE_LOYALTY_CARD, updateLoyaltyCard ? 1 : 0);
savedInstanceState.putInt(STATE_HAS_CHANGED, hasChanged ? 1 : 0);
savedInstanceState.putInt(STATE_FRONT_IMAGE_REMOVED, mFrontImageRemoved ? 1 : 0);
savedInstanceState.putInt(STATE_BACK_IMAGE_REMOVED, mBackImageRemoved ? 1 : 0);
savedInstanceState.putInt(STATE_ICON_REMOVED, mIconRemoved ? 1 : 0);
savedInstanceState.putInt(STATE_OPEN_SET_ICON_MENU, openSetIconMenu ? 1 : 0);
}
@Override
public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
onRestoring = true;
tempLoyaltyCard = savedInstanceState.getParcelable(STATE_TEMP_CARD);
tempLoyaltyCardField = (LoyaltyCardField) savedInstanceState.getSerializable(STATE_TEMP_CARD_FIELD);
super.onRestoreInstanceState(savedInstanceState);
tabs = binding.tabs;
tabs.selectTab(tabs.getTabAt(savedInstanceState.getInt(STATE_TAB_INDEX)));
mRequestedImage = savedInstanceState.getInt(STATE_REQUESTED_IMAGE);
mFrontImageUnsaved = savedInstanceState.getInt(STATE_FRONT_IMAGE_UNSAVED) == 1;
mBackImageUnsaved = savedInstanceState.getInt(STATE_BACK_IMAGE_UNSAVED) == 1;
mIconUnsaved = savedInstanceState.getInt(STATE_ICON_UNSAVED) == 1;
updateLoyaltyCard = savedInstanceState.getInt(STATE_UPDATE_LOYALTY_CARD) == 1;
hasChanged = savedInstanceState.getInt(STATE_HAS_CHANGED) == 1;
mFrontImageRemoved = savedInstanceState.getInt(STATE_FRONT_IMAGE_REMOVED) == 1;
mBackImageRemoved = savedInstanceState.getInt(STATE_BACK_IMAGE_REMOVED) == 1;
mIconRemoved = savedInstanceState.getInt(STATE_ICON_REMOVED) == 1;
openSetIconMenu = savedInstanceState.getInt(STATE_OPEN_SET_ICON_MENU) == 1;
}
@Override
@@ -379,14 +297,20 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
super.onCreate(savedInstanceState);
binding = LoyaltyCardEditActivityBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
viewModel = new ViewModelProvider(this).get(LoyaltyCardEditActivityViewModel.class);
toolbar = binding.toolbar;
setSupportActionBar(toolbar);
enableToolbarBackButton();
mDatabase = new DBHelper(this).getWritableDatabase();
if (!extractIntentFields(getIntent())) {
return;
if (!viewModel.getInitialized()) {
if (!extractIntentFields(getIntent())) {
return;
}
viewModel.setInitialized(true);
}
for (Currency currency : Currency.getAvailableCurrencies()) {
@@ -394,7 +318,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
currencySymbols.put(currency.getCurrencyCode(), currency.getSymbol());
}
tabs = binding.tabs;
thumbnail = binding.thumbnail;
thumbnailEditIcon = binding.thumbnailEditIcon;
storeFieldEdit = binding.storeNameEdit;
@@ -425,7 +348,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
setLoyaltyCardStore(storeName);
generateIcon(storeName);
if (storeName.length() == 0) {
if (storeName.isEmpty()) {
storeFieldEdit.setError(getString(R.string.field_must_not_be_empty));
} else {
storeFieldEdit.setError(null);
@@ -452,7 +375,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
setLoyaltyCardBalance(BigDecimal.valueOf(0));
}
balanceField.setText(Utils.formatBalanceWithoutCurrencySymbol(tempLoyaltyCard.balance, tempLoyaltyCard.balanceType));
balanceField.setText(Utils.formatBalanceWithoutCurrencySymbol(viewModel.getLoyaltyCard().balance, viewModel.getLoyaltyCard().balanceType));
}
});
@@ -461,7 +384,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (onResuming || onRestoring) return;
try {
BigDecimal balance = Utils.parseBalance(s.toString(), tempLoyaltyCard.balanceType);
BigDecimal balance = Utils.parseBalance(s.toString(), viewModel.getLoyaltyCard().balanceType);
setLoyaltyCardBalance(balance);
balanceField.setError(null);
validBalance = true;
@@ -486,8 +409,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
setLoyaltyCardBalanceType(currency);
if (tempLoyaltyCard.balance != null && !onResuming && !onRestoring) {
balanceField.setText(Utils.formatBalanceWithoutCurrencySymbol(tempLoyaltyCard.balance, currency));
if (viewModel.getLoyaltyCard().balance != null && !onResuming && !onRestoring) {
balanceField.setText(Utils.formatBalanceWithoutCurrencySymbol(viewModel.getLoyaltyCard().balance, currency));
}
}
@@ -533,7 +456,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
// We changed the card ID, save the current barcode ID in a temp
// variable and make sure to ask the user later if they also want to
// update the barcode ID
if (tempLoyaltyCard.barcodeId != null) {
if (viewModel.getLoyaltyCard().barcodeId != null) {
// If it is not set to "same as Card ID", save as tempStoredOldBarcodeValue
tempStoredOldBarcodeValue = barcodeIdField.getText().toString();
}
@@ -588,8 +511,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
input.setLayoutParams(params);
container.addView(input);
if (tempLoyaltyCard.barcodeId != null) {
input.setText(tempLoyaltyCard.barcodeId);
if (viewModel.getLoyaltyCard().barcodeId != null) {
input.setText(viewModel.getLoyaltyCard().barcodeId);
}
builder.setView(container);
@@ -650,10 +573,11 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
});
tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
binding.tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
public void onTabSelected(TabLayout.Tab tab) {
viewModel.setTabIndex(tab.getPosition());
showPart(tab.getText().toString());
}
@@ -665,12 +589,12 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
@Override
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
public void onTabReselected(TabLayout.Tab tab) {
viewModel.setTabIndex(tab.getPosition());
showPart(tab.getText().toString());
}
});
tabs.selectTab(tabs.getTabAt(0));
selectTab(viewModel.getTabIndex());
mPhotoTakerLauncher = registerForActivityResult(new ActivityResultContracts.TakePicture(), result -> {
if (result) {
@@ -693,27 +617,23 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
mCardIdAndBarCodeEditorLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == RESULT_OK) {
Intent intent = result.getData();
if (intent == null) {
Log.d("barcode card id editor", "barcode and card id editor picker returned without an intent");
Intent resultIntent = result.getData();
if (resultIntent == null) {
Log.d(TAG, "barcode and card id editor picker returned without an intent");
return;
}
List<BarcodeValues> barcodeValuesList = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, getApplicationContext());
Bundle resultIntentBundle = resultIntent.getExtras();
if (resultIntentBundle == null) {
Log.d(TAG, "barcode and card id editor picker returned without a bundle");
return;
}
Utils.makeUserChooseBarcodeFromList(this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
@Override
public void onUserChoseBarcode(BarcodeValues barcodeValues) {
setLoyaltyCardCardId(barcodeValues.content());
setLoyaltyCardBarcodeType(barcodeValues.format());
setLoyaltyCardBarcodeId("");
}
@Override
public void onUserDismissedSelector() {
}
});
LoyaltyCard loyaltyCard = viewModel.getLoyaltyCard();
loyaltyCard.updateFromBundle(resultIntentBundle, false);
viewModel.setLoyaltyCard(loyaltyCard);
generateBarcode();
viewModel.setHasChanged(true);
}
});
@@ -733,21 +653,17 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
if (bitmap != null) {
if (requestedFrontImage()) {
mFrontImageRemoved = false;
mFrontImageUnsaved = true;
setCardImage(cardImageFront, Utils.resizeBitmap(bitmap, Utils.BITMAP_SIZE_BIG), true);
setCardImage(ImageLocationType.front, cardImageFront, Utils.resizeBitmap(bitmap, Utils.BITMAP_SIZE_BIG), true);
} else if (requestedBackImage()) {
mBackImageRemoved = false;
mBackImageUnsaved = true;
setCardImage(cardImageBack, Utils.resizeBitmap(bitmap, Utils.BITMAP_SIZE_BIG), true);
} else {
mIconRemoved = false;
mIconUnsaved = true;
setCardImage(ImageLocationType.back, cardImageBack, Utils.resizeBitmap(bitmap, Utils.BITMAP_SIZE_BIG), true);
} else if (requestedIcon()) {
setThumbnailImage(Utils.resizeBitmap(bitmap, Utils.BITMAP_SIZE_SMALL));
} else {
Toast.makeText(this, R.string.generic_error_please_retry, Toast.LENGTH_LONG).show();
return;
}
Log.d("cropper", "mRequestedImage: " + mRequestedImage);
mCropperFinishedType = mRequestedImage;
hasChanged = true;
Log.d("cropper", "requestedImageType: " + viewModel.getRequestedImageType());
viewModel.setHasChanged(true);
} else {
Toast.makeText(LoyaltyCardEditActivity.this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
}
@@ -770,6 +686,11 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
});
}
private void selectTab(int index) {
binding.tabs.selectTab(binding.tabs.getTabAt(index));
viewModel.setTabIndex(index);
}
// ucrop 2.2.6 initial aspect ratio is glitched when 0x0 is used as the initial ratio option
// https://github.com/Yalantis/uCrop/blob/281c8e6438d81f464d836fc6b500517144af264a/ucrop/src/main/java/com/yalantis/ucrop/UCropActivity.java#L264
// so source width height has to be provided for now, depending on whether future versions of ucrop will support 0x0 as the default option
@@ -807,36 +728,22 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.i(TAG, "Received new intent");
extractIntentFields(intent);
}
private boolean requestedFrontImage() {
return mRequestedImage == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || mRequestedImage == Utils.CARD_IMAGE_FROM_FILE_FRONT;
}
int requestedImageType = viewModel.getRequestedImageType();
private boolean croppedFrontImage() {
return mCropperFinishedType == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || mCropperFinishedType == Utils.CARD_IMAGE_FROM_FILE_FRONT;
return requestedImageType == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || requestedImageType == Utils.CARD_IMAGE_FROM_FILE_FRONT;
}
private boolean requestedBackImage() {
return mRequestedImage == Utils.CARD_IMAGE_FROM_CAMERA_BACK || mRequestedImage == Utils.CARD_IMAGE_FROM_FILE_BACK;
}
int requestedImageType = viewModel.getRequestedImageType();
private boolean croppedBackImage() {
return mCropperFinishedType == Utils.CARD_IMAGE_FROM_CAMERA_BACK || mCropperFinishedType == Utils.CARD_IMAGE_FROM_FILE_BACK;
return requestedImageType == Utils.CARD_IMAGE_FROM_CAMERA_BACK || requestedImageType == Utils.CARD_IMAGE_FROM_FILE_BACK;
}
private boolean requestedIcon() {
return mRequestedImage == Utils.CARD_IMAGE_FROM_CAMERA_ICON || mRequestedImage == Utils.CARD_IMAGE_FROM_FILE_ICON;
}
int requestedImageType = viewModel.getRequestedImageType();
private boolean croppedIcon() {
return mCropperFinishedType == Utils.CARD_IMAGE_FROM_CAMERA_ICON || mCropperFinishedType == Utils.CARD_IMAGE_FROM_FILE_ICON;
return requestedImageType == Utils.CARD_IMAGE_FROM_CAMERA_ICON || requestedImageType == Utils.CARD_IMAGE_FROM_FILE_ICON;
}
@SuppressLint("DefaultLocale")
@@ -844,67 +751,41 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
protected void onResume() {
super.onResume();
Log.i(TAG, "To view card: " + loyaltyCardId);
Log.i(TAG, "To view card: " + viewModel.getLoyaltyCardId());
onResuming = true;
if (!initDone) {
if (updateLoyaltyCard) {
setTitle(R.string.editCardTitle);
} else {
setTitle(R.string.addCardTitle);
}
if (updateLoyaltyCard || duplicateFromLoyaltyCardId) {
if (!mFrontImageUnsaved && !croppedFrontImage() && !mFrontImageRemoved) {
setCardImage(cardImageFront, Utils.retrieveCardImage(this, tempLoyaltyCard.id, ImageLocationType.front), true);
}
if (!mBackImageUnsaved && !croppedBackImage() && !mBackImageRemoved) {
setCardImage(cardImageBack, Utils.retrieveCardImage(this, tempLoyaltyCard.id, ImageLocationType.back), true);
}
if (!mIconUnsaved && !croppedIcon() && !mIconRemoved) {
setThumbnailImage(Utils.retrieveCardImage(this, tempLoyaltyCard.id, ImageLocationType.icon));
}
} else {
setTitle(R.string.addCardTitle);
}
if (mFrontImageUnsaved && !croppedFrontImage()) {
setCardImage(cardImageFront, Utils.loadTempImage(this, TEMP_UNSAVED_FRONT_IMAGE_NAME), true);
}
if (mBackImageUnsaved && !croppedBackImage()) {
setCardImage(cardImageBack, Utils.loadTempImage(this, TEMP_UNSAVED_BACK_IMAGE_NAME), true);
}
if (mIconUnsaved && !croppedIcon()) {
setThumbnailImage(Utils.loadTempImage(this, TEMP_UNSAVED_ICON_NAME));
}
if (viewModel.getUpdateLoyaltyCard()) {
setTitle(R.string.editCardTitle);
} else {
setTitle(R.string.addCardTitle);
}
mCropperFinishedType = 0;
boolean hadChanges = viewModel.getHasChanged();
boolean hadChanges = hasChanged;
storeFieldEdit.setText(tempLoyaltyCard.store);
noteFieldEdit.setText(tempLoyaltyCard.note);
formatDateField(this, validFromField, tempLoyaltyCard.validFrom);
formatDateField(this, expiryField, tempLoyaltyCard.expiry);
cardIdFieldView.setText(tempLoyaltyCard.cardId);
barcodeIdField.setText(tempLoyaltyCard.barcodeId != null ? tempLoyaltyCard.barcodeId : getString(R.string.sameAsCardId));
barcodeTypeField.setText(tempLoyaltyCard.barcodeType != null ? tempLoyaltyCard.barcodeType.prettyName() : getString(R.string.noBarcode));
storeFieldEdit.setText(viewModel.getLoyaltyCard().store);
noteFieldEdit.setText(viewModel.getLoyaltyCard().note);
formatDateField(this, validFromField, viewModel.getLoyaltyCard().validFrom);
formatDateField(this, expiryField, viewModel.getLoyaltyCard().expiry);
cardIdFieldView.setText(viewModel.getLoyaltyCard().cardId);
String barcodeId = viewModel.getLoyaltyCard().barcodeId;
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));
// 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.
formatBalanceCurrencyField(tempLoyaltyCard.balanceType);
BigDecimal balance = tempLoyaltyCard.balance == null ? new BigDecimal("0") : tempLoyaltyCard.balance;
formatBalanceCurrencyField(viewModel.getLoyaltyCard().balanceType);
BigDecimal balance = viewModel.getLoyaltyCard().balance == null ? new BigDecimal("0") : viewModel.getLoyaltyCard().balance;
setLoyaltyCardBalance(balance);
balanceField.setText(Utils.formatBalanceWithoutCurrencySymbol(tempLoyaltyCard.balance, tempLoyaltyCard.balanceType));
balanceField.setText(Utils.formatBalanceWithoutCurrencySymbol(viewModel.getLoyaltyCard().balance, viewModel.getLoyaltyCard().balanceType));
validBalance = true;
Log.d(TAG, "Setting balance to " + balance);
if (groupsChips.getChildCount() == 0) {
List<Group> existingGroups = DBHelper.getGroups(mDatabase);
List<Group> loyaltyCardGroups = DBHelper.getLoyaltyCardGroups(mDatabase, loyaltyCardId);
List<Group> loyaltyCardGroups = DBHelper.getLoyaltyCardGroups(mDatabase, viewModel.getLoyaltyCardId());
if (existingGroups.isEmpty()) {
groupsChips.setVisibility(View.GONE);
@@ -919,7 +800,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
chip.setText(group._id);
chip.setTag(group);
if (group._id.equals(addGroup)) {
if (group._id.equals(viewModel.getAddGroup())) {
chip.setChecked(true);
} else {
chip.setChecked(false);
@@ -932,7 +813,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
chip.setOnTouchListener((v, event) -> {
hasChanged = true;
viewModel.setHasChanged(true);
return false;
});
@@ -941,36 +822,19 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
}
if (tempLoyaltyCard.headerColor == null) {
if (viewModel.getLoyaltyCard().headerColor == null) {
// If name is set, pick colour relevant for name. Otherwise pick randomly
setLoyaltyCardHeaderColor(tempLoyaltyCard.store.isEmpty() ? Utils.getRandomHeaderColor(this) : Utils.getHeaderColor(this, tempLoyaltyCard));
setLoyaltyCardHeaderColor(viewModel.getLoyaltyCard().store.isEmpty() ? Utils.getRandomHeaderColor(this) : Utils.getHeaderColor(this, viewModel.getLoyaltyCard()));
}
// Fix up some fields
if (tempLoyaltyCard.barcodeType != null) {
try {
barcodeTypeField.setText(tempLoyaltyCard.barcodeType.prettyName());
} catch (IllegalArgumentException e) {
barcodeTypeField.setText(getString(R.string.noBarcode));
}
}
if (tempLoyaltyCard.cardId != null) {
cardIdFieldView.setText(tempLoyaltyCard.cardId);
}
if (tempLoyaltyCard.barcodeId != null) {
if (!tempLoyaltyCard.barcodeId.isEmpty()) {
barcodeIdField.setText(tempLoyaltyCard.barcodeId);
} else {
barcodeIdField.setText(getString(R.string.sameAsCardId));
}
}
setThumbnailImage(viewModel.getLoyaltyCard().getImageThumbnail(this));
setCardImage(ImageLocationType.front, cardImageFront, viewModel.getLoyaltyCard().getImageFront(this), true);
setCardImage(ImageLocationType.back, cardImageBack, viewModel.getLoyaltyCard().getImageBack(this), true);
// Initialization has finished
if (!initDone) {
initDone = true;
hasChanged = hadChanges;
viewModel.setHasChanged(hadChanges);
}
generateBarcode();
@@ -987,7 +851,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
generateIcon(storeFieldEdit.getText().toString().trim());
Integer headerColor = tempLoyaltyCard.headerColor;
Integer headerColor = viewModel.getLoyaltyCard().headerColor;
if (headerColor != null) {
thumbnail.setOnClickListener(new ChooseCardImage());
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
@@ -999,17 +863,17 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
// Fake click on the edit icon to cause the set icon option to pop up if the icon was
// long-pressed in the view activity
if (openSetIconMenu) {
openSetIconMenu = false;
if (viewModel.getOpenSetIconMenu()) {
viewModel.setOpenSetIconMenu(false);
thumbnail.callOnClick();
}
}
protected void setThumbnailImage(@Nullable Bitmap bitmap) {
setCardImage(thumbnail, bitmap, false);
setCardImage(ImageLocationType.icon, thumbnail, bitmap, false);
if (bitmap != null) {
int headerColor = Utils.getHeaderColorFromImage(bitmap, Utils.getHeaderColor(this, tempLoyaltyCard));
int headerColor = Utils.getHeaderColorFromImage(bitmap, Utils.getHeaderColor(this, viewModel.getLoyaltyCard()));
setLoyaltyCardHeaderColor(headerColor);
@@ -1020,15 +884,25 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
} else {
generateIcon(storeFieldEdit.getText().toString().trim());
if (tempLoyaltyCard.headerColor != null) {
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(tempLoyaltyCard.headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(tempLoyaltyCard.headerColor) ? Color.WHITE : Color.BLACK);
Integer headerColor = viewModel.getLoyaltyCard().headerColor;
if (headerColor != null) {
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
}
}
}
protected void setCardImage(ImageView imageView, Bitmap bitmap, boolean applyFallback) {
imageView.setTag(bitmap);
protected void setCardImage(ImageLocationType imageLocationType, ImageView imageView, Bitmap bitmap, boolean applyFallback) {
if (imageLocationType == ImageLocationType.icon) {
viewModel.getLoyaltyCard().setImageThumbnail(bitmap, null);
} else if (imageLocationType == ImageLocationType.front) {
viewModel.getLoyaltyCard().setImageFront(bitmap, null);
} else if (imageLocationType == ImageLocationType.back) {
viewModel.getLoyaltyCard().setImageBack(bitmap, null);
} else {
throw new IllegalArgumentException("Unknown image type");
}
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
@@ -1208,7 +1082,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
private void askBeforeQuitIfChanged() {
if (!hasChanged) {
if (!viewModel.getHasChanged()) {
if (tempStoredOldBarcodeValue != null) {
askBarcodeChange(this::askBeforeQuitIfChanged);
return;
@@ -1235,7 +1109,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
private void takePhotoForCard(int type) {
Uri photoURI = FileProvider.getUriForFile(LoyaltyCardEditActivity.this, BuildConfig.APPLICATION_ID, Utils.createTempFile(this, TEMP_CAMERA_IMAGE_NAME));
mRequestedImage = type;
viewModel.setRequestedImageType(type);
try {
mPhotoTakerLauncher.launch(photoURI);
@@ -1246,7 +1120,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
private void selectImageFromGallery(int type) {
mRequestedImage = type;
viewModel.setRequestedImageType(type);
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType("image/*");
@@ -1285,30 +1159,30 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
class ChooseCardImage implements View.OnClickListener {
@Override
public void onClick(View v) throws NoSuchElementException {
Bitmap currentImage;
ImageLocationType imageLocationType;
ImageView targetView;
if (v.getId() == R.id.frontImageHolder) {
currentImage = viewModel.getLoyaltyCard().getImageFront(LoyaltyCardEditActivity.this);
imageLocationType = ImageLocationType.front;
targetView = cardImageFront;
} else if (v.getId() == R.id.backImageHolder) {
currentImage = viewModel.getLoyaltyCard().getImageBack(LoyaltyCardEditActivity.this);
imageLocationType = ImageLocationType.back;
targetView = cardImageBack;
} else if (v.getId() == R.id.thumbnail) {
currentImage = viewModel.getLoyaltyCard().getImageThumbnail(LoyaltyCardEditActivity.this);
imageLocationType = ImageLocationType.icon;
targetView = thumbnail;
} else {
throw new IllegalArgumentException("Invalid IMAGE ID " + v.getId());
}
LinkedHashMap<String, Callable<Void>> cardOptions = new LinkedHashMap<>();
if (targetView.getTag() != null && v.getId() != R.id.thumbnail) {
if (currentImage != null && v.getId() != R.id.thumbnail) {
cardOptions.put(getString(R.string.removeImage), () -> {
if (targetView == cardImageFront) {
mFrontImageRemoved = true;
mFrontImageUnsaved = false;
} else {
mBackImageRemoved = true;
mBackImageUnsaved = false;
}
setCardImage(targetView, null, true);
setCardImage(imageLocationType, targetView, null, true);
return null;
});
}
@@ -1317,8 +1191,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
cardOptions.put(getString(R.string.selectColor), () -> {
ColorPickerDialog.Builder dialogBuilder = ColorPickerDialog.newBuilder();
if (tempLoyaltyCard.headerColor != null) {
dialogBuilder.setColor(tempLoyaltyCard.headerColor);
if (viewModel.getLoyaltyCard().headerColor != null) {
dialogBuilder.setColor(viewModel.getLoyaltyCard().headerColor);
}
ColorPickerDialog dialog = dialogBuilder.create();
@@ -1364,21 +1238,19 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
});
if (v.getId() == R.id.thumbnail) {
if (cardImageFront.getTag() instanceof Bitmap) {
Bitmap imageFront = viewModel.getLoyaltyCard().getImageFront(LoyaltyCardEditActivity.this);
if (imageFront != null) {
cardOptions.put(getString(R.string.useFrontImage), () -> {
mIconRemoved = false;
mIconUnsaved = true;
setThumbnailImage(Utils.resizeBitmap((Bitmap) cardImageFront.getTag(), Utils.BITMAP_SIZE_SMALL));
setThumbnailImage(Utils.resizeBitmap(imageFront, Utils.BITMAP_SIZE_SMALL));
return null;
});
}
if (cardImageBack.getTag() instanceof Bitmap) {
Bitmap imageBack = viewModel.getLoyaltyCard().getImageBack(LoyaltyCardEditActivity.this);
if (imageBack != null) {
cardOptions.put(getString(R.string.useBackImage), () -> {
mIconRemoved = false;
mIconUnsaved = true;
setThumbnailImage(Utils.resizeBitmap((Bitmap) cardImageBack.getTag(), Utils.BITMAP_SIZE_SMALL));
setThumbnailImage(Utils.resizeBitmap(imageBack, Utils.BITMAP_SIZE_SMALL));
return null;
});
@@ -1429,8 +1301,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
setLoyaltyCardHeaderColor(color);
// Unset image if set
mIconRemoved = true;
mIconUnsaved = false;
setThumbnailImage(null);
}
@@ -1481,7 +1351,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
// Required to handle configuration changes
// See https://github.com/material-components/material-components-android/issues/1688
tempLoyaltyCardField = loyaltyCardField;
viewModel.setTempLoyaltyCardField(loyaltyCardField);
getSupportFragmentManager().addFragmentOnAttachListener((fragmentManager, fragment) -> {
if (fragment instanceof MaterialDatePicker && Objects.equals(fragment.getTag(), PICK_DATE_REQUEST_KEY)) {
((MaterialDatePicker<Long>) fragment).addOnPositiveButtonClickListener(selection -> {
@@ -1514,6 +1384,12 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
long selection = result.getLong(NEWLY_PICKED_DATE_ARGUMENT_KEY);
Date newDate = new Date(selection);
LoyaltyCardField tempLoyaltyCardField = viewModel.getTempLoyaltyCardField();
if (tempLoyaltyCardField == null) {
throw new AssertionError("tempLoyaltyCardField is null unexpectedly!");
}
switch (tempLoyaltyCardField) {
case validFrom:
formatDateField(LoyaltyCardEditActivity.this, validFromField, newDate);
@@ -1555,22 +1431,22 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
boolean hasError = false;
if (tempLoyaltyCard.store.isEmpty()) {
if (viewModel.getLoyaltyCard().store.isEmpty()) {
storeFieldEdit.setError(getString(R.string.field_must_not_be_empty));
// Focus element
tabs.selectTab(tabs.getTabAt(0));
selectTab(0);
storeFieldEdit.requestFocus();
hasError = true;
}
if (tempLoyaltyCard.cardId.isEmpty()) {
if (viewModel.getLoyaltyCard().cardId.isEmpty()) {
cardIdFieldView.setError(getString(R.string.field_must_not_be_empty));
// Focus element if first error element
if (!hasError) {
tabs.selectTab(tabs.getTabAt(0));
selectTab(0);
cardIdFieldView.requestFocus();
hasError = true;
}
@@ -1581,7 +1457,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
// Focus element if first error element
if (!hasError) {
tabs.selectTab(tabs.getTabAt(1));
selectTab(1);
balanceField.requestFocus();
hasError = true;
}
@@ -1601,25 +1477,25 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
// Both update and new card save with lastUsed set to null
// 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 (updateLoyaltyCard) {
DBHelper.updateLoyaltyCard(mDatabase, loyaltyCardId, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.validFrom, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor, tempLoyaltyCard.starStatus, null, tempLoyaltyCard.archiveStatus);
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);
} else {
loyaltyCardId = (int) DBHelper.insertLoyaltyCard(mDatabase, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.validFrom, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.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().headerColor, 0, null, 0));
}
try {
Utils.saveCardImage(this, (Bitmap) cardImageFront.getTag(), loyaltyCardId, ImageLocationType.front);
Utils.saveCardImage(this, (Bitmap) cardImageBack.getTag(), loyaltyCardId, ImageLocationType.back);
Utils.saveCardImage(this, (Bitmap) thumbnail.getTag(), loyaltyCardId, ImageLocationType.icon);
Utils.saveCardImage(this, viewModel.getLoyaltyCard().getImageFront(this), viewModel.getLoyaltyCardId(), ImageLocationType.front);
Utils.saveCardImage(this, viewModel.getLoyaltyCard().getImageBack(this), viewModel.getLoyaltyCardId(), ImageLocationType.back);
Utils.saveCardImage(this, viewModel.getLoyaltyCard().getImageThumbnail(this), viewModel.getLoyaltyCardId(), ImageLocationType.icon);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
DBHelper.setLoyaltyCardGroups(mDatabase, loyaltyCardId, selectedGroups);
DBHelper.setLoyaltyCardGroups(mDatabase, viewModel.getLoyaltyCardId(), selectedGroups);
ShortcutHelper.updateShortcuts(this, DBHelper.getLoyaltyCard(mDatabase, loyaltyCardId));
ShortcutHelper.updateShortcuts(this, DBHelper.getLoyaltyCard(this, mDatabase, viewModel.getLoyaltyCardId()));
if (duplicateFromLoyaltyCardId) {
if (viewModel.getDuplicateFromLoyaltyCardId()) {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
startActivity(intent);
}
@@ -1662,6 +1538,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
mCropperOptions.setToolbarTitle(getResources().getString(R.string.setBackImage));
} else if (requestedIcon()) {
mCropperOptions.setToolbarTitle(getResources().getString(R.string.setIcon));
} else {
Toast.makeText(this, R.string.generic_error_please_retry, Toast.LENGTH_LONG).show();
return;
}
if (requestedIcon()) {
@@ -1712,10 +1591,10 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
private void generateBarcode() {
mTasks.flushTaskList(TaskHandler.TYPE.BARCODE, true, false, false);
viewModel.getTaskHandler().flushTaskList(TaskHandler.TYPE.BARCODE, true, false, false);
String cardIdString = tempLoyaltyCard.barcodeId != null ? tempLoyaltyCard.barcodeId : tempLoyaltyCard.cardId;
CatimaBarcode barcodeFormat = tempLoyaltyCard.barcodeType;
String cardIdString = viewModel.getLoyaltyCard().barcodeId != null ? viewModel.getLoyaltyCard().barcodeId : viewModel.getLoyaltyCard().cardId;
CatimaBarcode barcodeFormat = viewModel.getLoyaltyCard().barcodeType;
if (cardIdString == null || cardIdString.isEmpty() || barcodeFormat == null) {
barcodeImageLayout.setVisibility(View.GONE);
@@ -1736,25 +1615,27 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
Log.d(TAG, "ImageView size now known");
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, LoyaltyCardEditActivity.this, true);
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
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);
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
viewModel.getTaskHandler().executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
}
}
private void generateIcon(String store) {
if (tempLoyaltyCard.headerColor == null) {
Integer headerColor = viewModel.getLoyaltyCard().headerColor;
if (headerColor == null) {
return;
}
if (thumbnail.getTag() == null) {
thumbnail.setBackgroundColor(tempLoyaltyCard.headerColor);
if (viewModel.getLoyaltyCard().getImageThumbnail(this) == null) {
thumbnail.setBackgroundColor(headerColor);
LetterBitmap letterBitmap = Utils.generateIcon(this, store, tempLoyaltyCard.headerColor);
LetterBitmap letterBitmap = Utils.generateIcon(this, store, headerColor);
if (letterBitmap != null) {
thumbnail.setImageBitmap(letterBitmap.getLetterTile());

View File

@@ -329,7 +329,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
binding.bottomAppBarUpdateBalanceButton.setOnClickListener(view -> showBalanceUpdateDialog());
binding.iconContainer.setOnClickListener(view -> {
if (Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.icon) != null) {
if (loyaltyCard.getImageThumbnail(this) != null) {
openImageInGallery(ImageType.ICON);
} else {
Toast.makeText(LoyaltyCardViewActivity.this, R.string.icon_header_click_text, Toast.LENGTH_LONG).show();
@@ -660,7 +660,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
window.setAttributes(attributes);
}
loyaltyCard = DBHelper.getLoyaltyCard(database, loyaltyCardId);
loyaltyCard = DBHelper.getLoyaltyCard(this, database, loyaltyCardId);
if (loyaltyCard == null) {
Log.w(TAG, "Could not lookup loyalty card " + loyaltyCardId);
Toast.makeText(this, R.string.noCardExistsError, Toast.LENGTH_LONG).show();
@@ -719,7 +719,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
editButtonIcon.setTint(Utils.needsDarkForeground(complementaryColor) ? Color.BLACK : Color.WHITE);
binding.fabEdit.setImageDrawable(editButtonIcon);
Bitmap icon = Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.icon);
Bitmap icon = loyaltyCard.getImageThumbnail(this);
Utils.setIconOrTextWithBackground(this, loyaltyCard, icon, binding.iconImage, binding.iconText, 1);
// If the background is very bright, we should use dark icons
@@ -748,12 +748,12 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
imageTypes.add(ImageType.BARCODE);
}
frontImageBitmap = Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.front);
frontImageBitmap = loyaltyCard.getImageFront(this);
if (frontImageBitmap != null) {
imageTypes.add(ImageType.IMAGE_FRONT);
}
backImageBitmap = Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.back);
backImageBitmap = loyaltyCard.getImageBack(this);
if (backImageBitmap != null) {
imageTypes.add(ImageType.IMAGE_BACK);
}

View File

@@ -7,6 +7,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.database.CursorIndexOutOfBoundsException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.DisplayMetrics;
@@ -34,6 +35,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -200,6 +202,28 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
protected void onCreate(Bundle inputSavedInstanceState) {
SplashScreen.installSplashScreen(this);
super.onCreate(inputSavedInstanceState);
// Delete old cache files
// These could be temporary images for the cropper, temporary images in LoyaltyCard toBundle/writeParcel/ etc.
new Thread(() -> {
long twentyFourHoursAgo = System.currentTimeMillis() - (1000 * 60 * 60 * 24);
File[] tempFiles = getCacheDir().listFiles();
if (tempFiles == null) {
Log.e(TAG, "getCacheDir().listFiles() somehow returned null, this should never happen... Skipping cache cleanup...");
return;
}
for (File file : tempFiles) {
if (file.lastModified() < twentyFourHoursAgo) {
if (!file.delete()) {
Log.w(TAG, "Failed to delete cache file " + file.getPath());
}
};
}
}).start();
// We should extract the share intent after we called the super.onCreate as it may need to spawn a dialog window and the app needs to be initialized to not crash
extractIntentFields(getIntent());
@@ -257,12 +281,9 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
return;
}
Intent intent = result.getData();
List<BarcodeValues> barcodeValuesList = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, this);
Bundle inputBundle = intent.getExtras();
String group = inputBundle != null ? inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) : null;
processBarcodeValuesList(barcodeValuesList, group, false);
Intent editIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
editIntent.putExtras(result.getData().getExtras());
startActivity(editIntent);
});
mSettingsLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
@@ -422,21 +443,16 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
}
private void processBarcodeValuesList(List<BarcodeValues> barcodeValuesList, String group, boolean closeAppOnNoBarcode) {
if (barcodeValuesList.isEmpty()) {
throw new IllegalArgumentException("barcodesValues may not be empty");
private void processParseResultList(List<ParseResult> parseResultList, String group, boolean closeAppOnNoBarcode) {
if (parseResultList.isEmpty()) {
throw new IllegalArgumentException("parseResultList may not be empty");
}
Utils.makeUserChooseBarcodeFromList(MainActivity.this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
Utils.makeUserChooseParseResultFromList(MainActivity.this, parseResultList, new ParseResultListDisambiguatorCallback() {
@Override
public void onUserChoseBarcode(BarcodeValues barcodeValues) {
CatimaBarcode barcodeType = barcodeValues.format();
public void onUserChoseParseResult(ParseResult parseResult) {
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle bundle = new Bundle();
bundle.putString(LoyaltyCard.BUNDLE_LOYALTY_CARD_CARD_ID, barcodeValues.content());
bundle.putString(LoyaltyCard.BUNDLE_LOYALTY_CARD_BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null);
bundle.putString(LoyaltyCard.BUNDLE_LOYALTY_CARD_BARCODE_ID, null);
Bundle bundle = parseResult.toLoyaltyCardBundle(MainActivity.this);
if (group != null) {
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
}
@@ -457,28 +473,48 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
String receivedAction = intent.getAction();
String receivedType = intent.getType();
// Check if an image or file was shared to us
if (Intent.ACTION_SEND.equals(receivedAction)) {
List<BarcodeValues> barcodeValuesList;
if (receivedAction == null || receivedType == null) {
return;
}
if (receivedType.equals("text/plain")) {
barcodeValuesList = Collections.singletonList(new BarcodeValues(null, intent.getStringExtra(Intent.EXTRA_TEXT)));
} else if (receivedType.startsWith("image/")) {
barcodeValuesList = Utils.retrieveBarcodesFromImage(this, intent.getParcelableExtra(Intent.EXTRA_STREAM));
List<ParseResult> parseResultList;
// Check for shared text
if (receivedAction.equals(Intent.ACTION_SEND) && receivedType.equals("text/plain")) {
LoyaltyCard loyaltyCard = new LoyaltyCard();
loyaltyCard.setCardId(intent.getStringExtra(Intent.EXTRA_TEXT));
parseResultList = Collections.singletonList(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
} else {
// Parse whatever file was sent, regardless of opening or sharing
Uri data;
if (receivedAction.equals(Intent.ACTION_VIEW)) {
data = intent.getData();
} else if (receivedAction.equals(Intent.ACTION_SEND)) {
data = intent.getParcelableExtra(Intent.EXTRA_STREAM);
} else {
Log.e(TAG, "Wrong action type to parse intent");
return;
}
if (receivedType.startsWith("image/")) {
parseResultList = Utils.retrieveBarcodesFromImage(this, data);
} else if (receivedType.equals("application/pdf")) {
barcodeValuesList = Utils.retrieveBarcodesFromPdf(this, intent.getParcelableExtra(Intent.EXTRA_STREAM));
parseResultList = Utils.retrieveBarcodesFromPdf(this, data);
} else if (receivedType.equals("application/vnd.apple.pkpass")) {
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data);
} else {
Log.e(TAG, "Wrong mime-type");
return;
}
if (barcodeValuesList.isEmpty()) {
finish();
return;
}
processBarcodeValuesList(barcodeValuesList, null, true);
}
// Give up if we should parse but there is nothing to parse
if (parseResultList == null || parseResultList.isEmpty()) {
finish();
return;
}
processParseResultList(parseResultList, null, true);
}
private void extractIntentFields(Intent intent) {

View File

@@ -33,7 +33,7 @@ public class ManageGroupCursorAdapter extends LoyaltyCardCursorAdapter {
@Override
public void onBindViewHolder(LoyaltyCardListItemViewHolder inputHolder, Cursor inputCursor) {
LoyaltyCard loyaltyCard = LoyaltyCard.fromCursor(inputCursor);
LoyaltyCard loyaltyCard = LoyaltyCard.fromCursor(mContext, inputCursor);
Boolean overlayValue = mInGroupOverlay.get(loyaltyCard.id);
if ((overlayValue != null ? overlayValue : isLoyaltyCardInGroup(loyaltyCard.id))) {
mAnimationItemsIndex.put(inputCursor.getPosition(), true);

View File

@@ -0,0 +1,31 @@
package protect.card_locker
import android.content.Context
import android.os.Bundle
class ParseResult(
val parseResultType: ParseResultType,
val loyaltyCard: LoyaltyCard) {
var note: String? = null
fun toLoyaltyCardBundle(context: Context): Bundle {
when (parseResultType) {
ParseResultType.FULL -> return loyaltyCard.toBundle(context, listOf())
ParseResultType.BARCODE_ONLY -> {
val defaultLoyaltyCard = LoyaltyCard()
defaultLoyaltyCard.setBarcodeId(null)
defaultLoyaltyCard.setBarcodeType(loyaltyCard.barcodeType)
defaultLoyaltyCard.setCardId(loyaltyCard.cardId)
return defaultLoyaltyCard.toBundle(
context,
listOf(
LoyaltyCard.BUNDLE_LOYALTY_CARD_BARCODE_ID,
LoyaltyCard.BUNDLE_LOYALTY_CARD_BARCODE_TYPE,
LoyaltyCard.BUNDLE_LOYALTY_CARD_CARD_ID
)
)
}
}
}
}

View File

@@ -0,0 +1,6 @@
package protect.card_locker;
public interface ParseResultListDisambiguatorCallback {
void onUserChoseParseResult(ParseResult parseResult);
void onUserDismissedSelector();
}

View File

@@ -0,0 +1,6 @@
package protect.card_locker
enum class ParseResultType {
FULL,
BARCODE_ONLY
}

View File

@@ -0,0 +1,437 @@
package protect.card_locker
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.util.ArrayMap
import android.util.Log
import com.google.zxing.BarcodeFormat
import net.lingala.zip4j.io.inputstream.ZipInputStream
import net.lingala.zip4j.model.LocalFileHeader
import org.json.JSONException
import org.json.JSONObject
import java.io.FileNotFoundException
import java.io.IOException
import java.math.BigDecimal
import java.text.DateFormat
import java.text.ParseException
import java.time.ZonedDateTime
import java.time.format.DateTimeParseException
import java.util.Currency
import java.util.Date
class PkpassParser(context: Context, uri: Uri?) {
private var mContext = context
private var translations: ArrayMap<String, Map<String, String>> = ArrayMap()
private var passContent: JSONObject? = null
private var store: String? = null
private var note: String? = null
private var validFrom: Date? = null
private var expiry: Date? = null
private val balance: BigDecimal = BigDecimal(0)
private val balanceType: Currency? = null
private var cardId: String? = null
private var barcodeId: String? = null
private var barcodeType: CatimaBarcode? = null
private var headerColor: Int? = null
private val starStatus = 0
private val lastUsed: Long = 0
private val zoomLevel = DBHelper.DEFAULT_ZOOM_LEVEL
private var archiveStatus = 0
var image: Bitmap? = null
private set
private var logoSize = 0
init {
if (passContent != null) {
throw IllegalStateException("Pkpass instance already initialized!")
}
mContext = context
Log.i(TAG, "Received Pkpass file")
if (uri == null) {
Log.e(TAG, "Uri did not contain any data")
throw IOException(context.getString(R.string.errorReadingFile))
}
try {
mContext.contentResolver.openInputStream(uri).use { inputStream ->
ZipInputStream(inputStream).use { zipInputStream ->
var localFileHeader: LocalFileHeader
while ((zipInputStream.nextEntry.also { localFileHeader = it }) != null) {
// Ignore directories
if (localFileHeader.isDirectory) continue
// We assume there are three options, as per spec:
// language.lproj/pass.strings
// file.extension
// More directories are ignored
val filenameParts = localFileHeader.fileName.split('/')
if (filenameParts.size > 2) {
continue
} else if (filenameParts.size == 2) {
// Doesn't seem like a language directory, ignore
if (!filenameParts[0].endsWith(".lproj")) continue
val locale = filenameParts[0].removeSuffix(".lproj")
translations[locale] = parseLanguageStrings(ZipUtils.read(zipInputStream))
}
// Not a language, parse as normal files
when (localFileHeader.fileName) {
"logo.png" -> loadImageIfBiggerSize(1, zipInputStream)
"logo@2x.png" -> loadImageIfBiggerSize(2, zipInputStream)
"logo@3x.png" -> loadImageIfBiggerSize(3, zipInputStream)
"pass.json" -> passContent = ZipUtils.readJSON(zipInputStream) // Parse this last, so we're sure we have all language info
}
}
checkNotNull(passContent) { "File lacks pass.json" }
}
}
} catch (e: FileNotFoundException) {
throw IOException(mContext.getString(R.string.errorReadingFile))
} catch (e: Exception) {
throw e
}
}
fun listLocales(): List<String> {
return translations.keys.toList()
}
fun toLoyaltyCard(locale: String?): LoyaltyCard {
parsePassJSON(checkNotNull(passContent) { "Pkpass instance not yet initialized!" }, locale)
return LoyaltyCard(
-1,
store,
note,
validFrom,
expiry,
balance,
balanceType,
cardId,
barcodeId,
barcodeType,
headerColor,
starStatus,
lastUsed,
zoomLevel,
archiveStatus,
image,
null,
null,
null,
null,
null
)
}
private fun getTranslation(string: String, locale: String?): String {
if (locale == null) {
return string
}
val localeStrings = translations[locale]
return localeStrings?.get(string) ?: string
}
private fun loadImageIfBiggerSize(fileLogoSize: Int, zipInputStream: ZipInputStream) {
if (logoSize < fileLogoSize) {
image = ZipUtils.readImage(zipInputStream)
logoSize = fileLogoSize
}
}
private fun parseColor(color: String): Int? {
// First, try formats supported by Android natively
try {
return Color.parseColor(color)
} catch (ignored: IllegalArgumentException) {}
// If that didn't work, try parsing it as a rbg(0,0,255) value
val red: Int;
val green: Int;
val blue: Int;
// Parse rgb(0,0,0) string
val rgbInfo = Regex("""^rgb\(\s*(?<red>\d+)\s*,\s*(?<green>\d+)\s*,\s*(?<blue>\d+)\s*\)$""").find(color)
if (rgbInfo == null) {
return null
}
// Convert to integers
try {
red = rgbInfo.groups[1]!!.value.toInt()
green = rgbInfo.groups[2]!!.value.toInt()
blue = rgbInfo.groups[3]!!.value.toInt()
} catch (e: NumberFormatException) {
return null
}
// Ensure everything is in a valid range as Color.rgb does not do range checks
if (red < 0 || red > 255) return null
if (green < 0 || green > 255) return null
if (blue < 0 || blue > 255) return null
return Color.rgb(red, green, blue)
}
private fun parseDateTime(dateTime: String): Date {
return Date.from(ZonedDateTime.parse(dateTime).toInstant())
}
private fun parseLanguageStrings(data: String): Map<String, String> {
val output = ArrayMap<String, String>()
// Translations look like this:
// "key_name" = "Translated value";
//
// However, "Translated value" may be multiple lines and may contain " (however, it'll be escaped)
var translationLine = StringBuilder()
for (line in data.lines()) {
translationLine.append(line)
// Make sure we don't have a false ending (this is the escaped double quote: \";)
if (!line.endsWith("\\\";") and line.endsWith("\";")) {
// We reached a translation ending, time to parse it
// 1. Split into key and value
// 2. Remove cruft of each
// 3. Clean up escape sequences
val keyValue = translationLine.toString().split("=", ignoreCase = false, limit = 2)
val key = keyValue[0].trim().removePrefix("\"").removeSuffix("\"")
val value = keyValue[1].trim().removePrefix("\"").removeSuffix("\";").replace("\\", "")
output[key] = value
translationLine = StringBuilder()
} else {
translationLine.append("\n")
}
}
return output
}
private fun parsePassJSON(jsonObject: JSONObject, locale: String?) {
if (jsonObject.getInt("formatVersion") != 1) {
throw IllegalArgumentException(mContext.getString(R.string.unsupportedFile))
}
// Prefer logoText for store, it's generally shorter
try {
store = jsonObject.getString("logoText")
} catch (ignored: JSONException) {}
if (store.isNullOrEmpty()) {
store = jsonObject.getString("organizationName")
}
val noteText = StringBuilder()
noteText.append(getTranslation(jsonObject.getString("description"), locale))
try {
validFrom = parseDateTime(jsonObject.getString("relevantDate"))
} catch (ignored: JSONException) {}
try {
expiry = parseDateTime(jsonObject.getString("expirationDate"))
} catch (ignored: JSONException) {}
try {
headerColor = parseColor(jsonObject.getString("backgroundColor"))
} catch (ignored: JSONException) {}
var pkPassHasBarcodes = false
var validBarcodeFound = false
// Create a list of possible barcodes
val barcodes = ArrayList<JSONObject>()
// Append the non-deprecated entries
try {
val foundInBarcodesField = jsonObject.getJSONArray("barcodes")
for (i in 0 until foundInBarcodesField.length()) {
barcodes.add(foundInBarcodesField.getJSONObject(i))
}
} catch (ignored: JSONException) {}
// Append the deprecated entry if it exists
try {
barcodes.add(jsonObject.getJSONObject("barcode"))
} catch (ignored: JSONException) {}
for (barcode in barcodes) {
pkPassHasBarcodes = true
try {
parsePassJSONBarcodeField(barcode)
validBarcodeFound = true
break
} catch (ignored: IllegalArgumentException) {}
}
if (pkPassHasBarcodes && !validBarcodeFound) {
throw FormatException(mContext.getString(R.string.errorReadingFile))
}
// An used card being "archived" probably is the most sensible way to map "voided"
archiveStatus = try {
if (jsonObject.getBoolean("voided")) 1 else 0
} catch (ignored: JSONException) {
0
}
// Append type-specific info to the pass
noteText.append("\n\n")
// Find the relevant pass type and parse it
var hasPassData = false
for (passType in listOf("boardingPass", "coupon", "eventTicket", "generic")) {
try {
noteText.append(
parsePassJSONPassFields(
jsonObject.getJSONObject(passType),
locale
)
)
hasPassData = true
break
} catch (ignored: JSONException) {}
}
// Failed to parse anything, error out
if (!hasPassData) {
throw FormatException(mContext.getString(R.string.errorReadingFile))
}
note = noteText.toString()
}
/* Return success or failure */
private fun parsePassJSONBarcodeField(barcodeInfo: JSONObject) {
val format = barcodeInfo.getString("format")
// We only need to check these 4 formats as no other options are valid in the PkPass spec
barcodeType = when(format) {
"PKBarcodeFormatQR" -> CatimaBarcode.fromBarcode(BarcodeFormat.QR_CODE)
"PKBarcodeFormatPDF417" -> CatimaBarcode.fromBarcode(BarcodeFormat.PDF_417)
"PKBarcodeFormatAztec" -> CatimaBarcode.fromBarcode(BarcodeFormat.AZTEC)
"PKBarcodeFormatCode128" -> CatimaBarcode.fromBarcode(BarcodeFormat.CODE_128)
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")
} catch (ignored: JSONException) {
cardId = barcodeInfo.getString("message")
barcodeId = null
}
// Don't set barcodeId if it's the same as cardId
if (cardId == barcodeId) {
barcodeId = null
}
}
private fun parsePassJSONPassFields(fieldsParent: JSONObject, locale: String?): String {
// These fields contain a lot of info on where we're supposed to display them, but Catima doesn't really have anything for that
// So for now, throw them all into the description field in a logical order
val noteContents: MutableList<String> = ArrayList()
// Collect all the groups of fields that exist
for (fieldType in listOf("headerFields", "primaryFields", "secondaryFields", "auxiliaryFields", "backFields")) {
val content = StringBuilder()
try {
val fieldArray = fieldsParent.getJSONArray(fieldType)
for (i in 0 until fieldArray.length()) {
val entry = fieldArray.getJSONObject(i)
content.append(parsePassJSONPassField(entry, locale))
// If this is not the last part, add spacing on the end
if (i < (fieldArray.length() - 1)) {
content.append("\n")
}
}
} catch (ignore: JSONException) {
} catch (ignore: ParseException) {
}
if (content.isNotEmpty()) {
noteContents.add(content.toString())
}
}
// Merge all field groups together, one paragraph for field group
val output = StringBuilder()
for (i in 0 until noteContents.size) {
output.append(noteContents[i])
// If this is not the last part, add newlines to separate
if (i < (noteContents.size - 1)) {
output.append("\n\n")
}
}
return output.toString()
}
private fun parsePassJSONPassField(field: JSONObject, locale: String?): String {
// Value may be a localizable string, a date or a number. So let's try to parse it as a date first
var value = getTranslation(field.getString("value"), locale)
try {
value = DateFormat.getDateTimeInstance().format(parseDateTime(value))
} catch (ignored: DateTimeParseException) {
// It's fine if it's not a date
}
// FIXME: Use the Android thing for formatted strings here
if (field.has("currencyCode")) {
val valueCurrency = Currency.getInstance(field.getString("currencyCode"))
value = Utils.formatBalance(
mContext,
Utils.parseBalance(value, valueCurrency),
valueCurrency
)
} else if (field.has("numberStyle")) {
if (field.getString("numberStyle") == "PKNumberStylePercent") {
// FIXME: Android formatting string
value = "${value}%"
}
}
val label = getTranslation(field.getString("label"), locale)
if (label.isNotEmpty()) {
return "$label: $value"
}
return value
}
companion object {
private const val TAG = "Catima"
}
}

View File

@@ -67,6 +67,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
private static final int PERMISSION_SCAN_ADD_FROM_IMAGE = 100;
private static final int PERMISSION_SCAN_ADD_FROM_PDF = 101;
private static final int PERMISSION_SCAN_ADD_FROM_PKPASS = 102;
private CaptureManager capture;
private DecoratedBarcodeView barcodeScannerView;
@@ -79,6 +80,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
// can't use the pre-made contract because that launches the file manager for image type instead of gallery
private ActivityResultLauncher<Intent> photoPickerLauncher;
private ActivityResultLauncher<Intent> pdfPickerLauncher;
private ActivityResultLauncher<Intent> pkpassPickerLauncher;
static final String STATE_SCANNER_ACTIVE = "scannerActive";
private boolean mScannerActive = true;
@@ -107,6 +109,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
manualAddLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.SELECT_BARCODE_REQUEST, result.getResultCode(), result.getData()));
photoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_IMAGE_FILE, result.getResultCode(), result.getData()));
pdfPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_PDF_FILE, result.getResultCode(), result.getData()));
pkpassPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_PKPASS_FILE, result.getResultCode(), result.getData()));
customBarcodeScannerBinding.fabOtherOptions.setOnClickListener(view -> {
setScannerActive(false);
@@ -116,12 +119,14 @@ public class ScanActivity extends CatimaAppCompatActivity {
getString(R.string.addManually),
getString(R.string.addFromImage),
getString(R.string.addFromPdfFile),
getString(R.string.addFromPkpass)
};
Object[] icons = new Object[]{
R.drawable.baseline_block_24,
R.drawable.ic_edit,
R.drawable.baseline_image_24,
R.drawable.baseline_picture_as_pdf_24,
R.drawable.local_activity_24px
};
String[] columns = new String[]{"text", "icon"};
@@ -156,7 +161,10 @@ public class ScanActivity extends CatimaAppCompatActivity {
addFromImage();
break;
case 3:
addFromPdfFile();
addFromPdf();
break;
case 4:
addFromPkPass();
break;
default:
throw new IllegalArgumentException("Unknown 'Add a card in a different way' dialog option");
@@ -181,16 +189,11 @@ public class ScanActivity extends CatimaAppCompatActivity {
barcodeScannerView.decodeSingle(new BarcodeCallback() {
@Override
public void barcodeResult(BarcodeResult result) {
Intent scanResult = new Intent();
Bundle scanResultBundle = new Bundle();
scanResultBundle.putString(BARCODE_CONTENTS, result.getText());
scanResultBundle.putString(BARCODE_FORMAT, result.getBarcodeFormat().name());
if (addGroup != null) {
scanResultBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
}
scanResult.putExtras(scanResultBundle);
ScanActivity.this.setResult(RESULT_OK, scanResult);
finish();
LoyaltyCard loyaltyCard = new LoyaltyCard();
loyaltyCard.setCardId(result.getText());
loyaltyCard.setBarcodeType(CatimaBarcode.fromBarcode(result.getBarcodeFormat()));
returnResult(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
}
@Override
@@ -294,35 +297,31 @@ public class ScanActivity extends CatimaAppCompatActivity {
mScannerActive = isActive;
}
private void returnResult(String barcodeContents, String barcodeFormat) {
Intent manualResult = new Intent();
Bundle manualResultBundle = new Bundle();
manualResultBundle.putString(BARCODE_CONTENTS, barcodeContents);
manualResultBundle.putString(BARCODE_FORMAT, barcodeFormat);
private void returnResult(ParseResult parseResult) {
Intent result = new Intent();
Bundle bundle = parseResult.toLoyaltyCardBundle(ScanActivity.this);
if (addGroup != null) {
manualResultBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
}
manualResult.putExtras(manualResultBundle);
ScanActivity.this.setResult(RESULT_OK, manualResult);
result.putExtras(bundle);
ScanActivity.this.setResult(RESULT_OK, result);
finish();
}
private void handleActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
List<BarcodeValues> barcodeValuesList = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
List<ParseResult> parseResultList = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
if (barcodeValuesList.isEmpty()) {
if (parseResultList.isEmpty()) {
setScannerActive(true);
return;
}
Utils.makeUserChooseBarcodeFromList(this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
Utils.makeUserChooseParseResultFromList(this, parseResultList, new ParseResultListDisambiguatorCallback() {
@Override
public void onUserChoseBarcode(BarcodeValues barcodeValues) {
CatimaBarcode barcodeType = barcodeValues.format();
returnResult(barcodeValues.content(), barcodeType != null ? barcodeType.name() : null);
public void onUserChoseParseResult(ParseResult parseResult) {
returnResult(parseResult);
}
@Override
@@ -369,7 +368,9 @@ public class ScanActivity extends CatimaAppCompatActivity {
// Buttons
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
returnResult(input.getText().toString(), null);
LoyaltyCard loyaltyCard = new LoyaltyCard();
loyaltyCard.setCardId(input.getText().toString());
returnResult(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
});
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
AlertDialog dialog = builder.create();
@@ -418,10 +419,14 @@ public class ScanActivity extends CatimaAppCompatActivity {
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_IMAGE);
}
public void addFromPdfFile() {
public void addFromPdf() {
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PDF);
}
public void addFromPkPass() {
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PKPASS);
}
private void addFromImageOrFileAfterPermission(String mimeType, ActivityResultLauncher<Intent> launcher, int chooserText, int errorMessage) {
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType(mimeType);
@@ -511,12 +516,14 @@ public class ScanActivity extends CatimaAppCompatActivity {
} else {
showCameraPermissionMissingText();
}
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE || requestCode == PERMISSION_SCAN_ADD_FROM_PDF) {
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE || requestCode == PERMISSION_SCAN_ADD_FROM_PDF || requestCode == PERMISSION_SCAN_ADD_FROM_PKPASS) {
if (granted) {
if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
addFromImageOrFileAfterPermission("image/*", photoPickerLauncher, R.string.addFromImage, R.string.failedLaunchingPhotoPicker);
} else {
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_PDF) {
addFromImageOrFileAfterPermission("application/pdf", pdfPickerLauncher, R.string.addFromPdfFile, R.string.failedLaunchingFileManager);
} else {
addFromImageOrFileAfterPermission("application/*", pkpassPickerLauncher, R.string.addFromPkpass, R.string.failedLaunchingFileManager);
}
} else {
setScannerActive(true);

View File

@@ -86,7 +86,7 @@ class ShortcutHelper {
for (int index = 0; index < list.size(); index++) {
ShortcutInfoCompat prevShortcut = list.get(index);
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(database, Integer.parseInt(prevShortcut.getId()));
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(context, database, Integer.parseInt(prevShortcut.getId()));
// skip outdated cards that no longer exist
if (loyaltyCard != null) {
@@ -135,7 +135,7 @@ class ShortcutHelper {
bundle.putInt(LoyaltyCardViewActivity.BUNDLE_ID, loyaltyCard.id);
intent.putExtras(bundle);
Bitmap iconBitmap = Utils.retrieveCardImage(context, loyaltyCard.id, ImageLocationType.icon);
Bitmap iconBitmap = loyaltyCard.getImageThumbnail(context);
if (iconBitmap == null) {
iconBitmap = Utils.generateIcon(context, loyaltyCard, true).getLetterTile();
} else {

View File

@@ -79,6 +79,7 @@ import java.util.Collections;
import java.util.Currency;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -96,12 +97,13 @@ public class Utils {
public static final int BARCODE_SCAN = 3;
public static final int BARCODE_IMPORT_FROM_IMAGE_FILE = 4;
public static final int BARCODE_IMPORT_FROM_PDF_FILE = 5;
public static final int CARD_IMAGE_FROM_CAMERA_FRONT = 6;
public static final int CARD_IMAGE_FROM_CAMERA_BACK = 7;
public static final int CARD_IMAGE_FROM_CAMERA_ICON = 8;
public static final int CARD_IMAGE_FROM_FILE_FRONT = 9;
public static final int CARD_IMAGE_FROM_FILE_BACK = 10;
public static final int CARD_IMAGE_FROM_FILE_ICON = 11;
public static final int BARCODE_IMPORT_FROM_PKPASS_FILE = 6;
public static final int CARD_IMAGE_FROM_CAMERA_FRONT = 7;
public static final int CARD_IMAGE_FROM_CAMERA_BACK = 8;
public static final int CARD_IMAGE_FROM_CAMERA_ICON = 9;
public static final int CARD_IMAGE_FROM_FILE_FRONT = 10;
public static final int CARD_IMAGE_FROM_FILE_BACK = 11;
public static final int CARD_IMAGE_FROM_FILE_ICON = 12;
public static final String CARD_IMAGE_FILENAME_REGEX = "^(card_)(\\d+)(_(?:front|back|icon)\\.png)$";
@@ -144,7 +146,7 @@ public class Utils {
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
}
static public List<BarcodeValues> retrieveBarcodesFromImage(Context context, Uri uri) {
static public List<ParseResult> retrieveBarcodesFromImage(Context context, Uri uri) {
Log.i(TAG, "Received image file with possible barcode");
if (uri == null) {
@@ -163,7 +165,7 @@ public class Utils {
return new ArrayList<>();
}
List<BarcodeValues> barcodesFromBitmap = getBarcodesFromBitmap(bitmap);
List<ParseResult> barcodesFromBitmap = getBarcodesFromBitmap(bitmap);
if (barcodesFromBitmap.isEmpty()) {
Log.i(TAG, "No barcode found in image file");
@@ -173,7 +175,32 @@ public class Utils {
return barcodesFromBitmap;
}
static public List<BarcodeValues> retrieveBarcodesFromPdf(Context context, Uri uri) {
static public List<ParseResult> retrieveBarcodesFromPkPass(Context context, Uri uri) {
Log.i(TAG, "Received Pkpass file with possible barcode");
if (uri == null) {
Log.e(TAG, "Pkpass did not contain any data");
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
return null;
}
PkpassParser pkpassParser = new PkpassParser(context, uri);
List<String> locales = pkpassParser.listLocales();
if (locales.isEmpty()) {
return Collections.singletonList(new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(null)));
}
List<ParseResult> parseResultList = new ArrayList<>();
for (String locale : locales) {
ParseResult parseResult = new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(locale));
parseResult.setNote(locale);
parseResultList.add(parseResult);
}
return parseResultList;
}
static public List<ParseResult> retrieveBarcodesFromPdf(Context context, Uri uri) {
Log.i(TAG, "Received PDF file with possible barcode");
if (uri == null) {
Log.e(TAG, "Uri did not contain any data");
@@ -183,7 +210,7 @@ public class Utils {
ParcelFileDescriptor parcelFileDescriptor = null;
PdfRenderer renderer = null;
List<BarcodeValues> barcodesFromPdfPages = new ArrayList<>();
List<ParseResult> barcodesFromPdfPages = new ArrayList<>();
try {
parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, "r");
@@ -205,10 +232,10 @@ public class Utils {
page.render(renderedPage, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
page.close();
List<BarcodeValues> barcodesFromPage = getBarcodesFromBitmap(renderedPage);
for (BarcodeValues barcodeValues : barcodesFromPage) {
barcodeValues.setNote(String.format(context.getString(R.string.pageWithNumber), i+1));
barcodesFromPdfPages.add(barcodeValues);
List<ParseResult> barcodesFromPage = getBarcodesFromBitmap(renderedPage);
for (ParseResult parseResult : barcodesFromPage) {
parseResult.setNote(String.format(context.getString(R.string.pageWithNumber), i+1));
barcodesFromPdfPages.add(parseResult);
}
}
}
@@ -237,17 +264,17 @@ public class Utils {
}
/**
* Returns the Barcode format and content based on the result of an activity.
* It shows toasts to notify the end-user as needed itself and will return an empty
* BarcodeValues object if the activity was cancelled or nothing could be found.
* Returns the ParseResult based on the result of an activity.
* It shows toasts to notify the end-user as needed itself and will return an empty list if the
* activity was cancelled or nothing could be found.
*
* @param requestCode
* @param resultCode
* @param intent
* @param context
* @return BarcodeValues
* @return List<ParseResult>
*/
static public List<BarcodeValues> parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) {
static public List<ParseResult> parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) {
String contents;
String format;
@@ -263,6 +290,10 @@ public class Utils {
return retrieveBarcodesFromPdf(context, intent.getData());
}
if (requestCode == Utils.BARCODE_IMPORT_FROM_PKPASS_FILE) {
return retrieveBarcodesFromPkPass(context, intent.getData());
}
if (requestCode == Utils.BARCODE_SCAN || requestCode == Utils.SELECT_BARCODE_REQUEST) {
if (requestCode == Utils.BARCODE_SCAN) {
Log.i(TAG, "Received barcode information from camera");
@@ -276,7 +307,15 @@ public class Utils {
Log.i(TAG, "Read barcode id: " + contents);
Log.i(TAG, "Read format: " + format);
return Collections.singletonList(new BarcodeValues(format != null ? CatimaBarcode.fromName(format) : null, contents));
LoyaltyCard loyaltyCard = new LoyaltyCard();
if (format != null) {
loyaltyCard.setBarcodeType(CatimaBarcode.fromName(format));
}
if (contents != null) {
loyaltyCard.setCardId(contents);
}
return Collections.singletonList(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
}
throw new UnsupportedOperationException("Unknown request code for parseSetBarcodeActivityResult");
@@ -296,7 +335,7 @@ public class Utils {
return MediaStore.Images.Media.getBitmap(context.getContentResolver(), data);
}
static public List<BarcodeValues> getBarcodesFromBitmap(Bitmap bitmap) {
static public List<ParseResult> getBarcodesFromBitmap(Bitmap bitmap) {
// This function is vulnerable to OOM, so we try again with a smaller bitmap is we get OOM
for (int i = 0; i < 10; i++) {
try {
@@ -311,7 +350,7 @@ public class Utils {
return new ArrayList<>();
}
static private List<BarcodeValues> getBarcodesFromBitmapReal(Bitmap bitmap) {
static private List<ParseResult> getBarcodesFromBitmapReal(Bitmap bitmap) {
// In order to decode it, the Bitmap must first be converted into a pixel array...
int[] intArray = new int[bitmap.getWidth() * bitmap.getHeight()];
bitmap.getPixels(intArray, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
@@ -320,7 +359,7 @@ public class Utils {
LuminanceSource source = new RGBLuminanceSource(bitmap.getWidth(), bitmap.getHeight(), intArray);
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
List<BarcodeValues> barcodeValuesList = new ArrayList<>();
List<ParseResult> parseResultList = new ArrayList<>();
try {
MultiFormatReader multiFormatReader = new MultiFormatReader();
MultipleBarcodeReader multipleBarcodeReader = new GenericMultipleBarcodeReader(multiFormatReader);
@@ -331,37 +370,42 @@ public class Utils {
Log.i(TAG, "Read barcode id: " + barcodeResult.getText());
Log.i(TAG, "Read format: " + barcodeResult.getBarcodeFormat().name());
barcodeValuesList.add(new BarcodeValues(CatimaBarcode.fromBarcode(barcodeResult.getBarcodeFormat()), barcodeResult.getText()));
LoyaltyCard loyaltyCard = new LoyaltyCard();
loyaltyCard.setCardId(barcodeResult.getText());
loyaltyCard.setBarcodeType(CatimaBarcode.fromBarcode(barcodeResult.getBarcodeFormat()));
parseResultList.add(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
}
return barcodeValuesList;
return parseResultList;
} catch (NotFoundException e) {
return barcodeValuesList;
return parseResultList;
}
}
static public void makeUserChooseBarcodeFromList(Context context, List<BarcodeValues> barcodeValuesList, BarcodeValuesListDisambiguatorCallback callback) {
static public void makeUserChooseParseResultFromList(Context context, List<ParseResult> parseResultList, ParseResultListDisambiguatorCallback callback) {
// If there is only one choice, consider it chosen
if (barcodeValuesList.size() == 1) {
callback.onUserChoseBarcode(barcodeValuesList.get(0));
if (parseResultList.size() == 1) {
callback.onUserChoseParseResult(parseResultList.get(0));
return;
}
// Ask user to choose a barcode
// TODO: This should contain an image of the barcode in question to help users understand the choice they're making
CharSequence[] barcodeDescriptions = new CharSequence[barcodeValuesList.size()];
for (int i = 0; i < barcodeValuesList.size(); i++) {
BarcodeValues barcodeValues = barcodeValuesList.get(i);
CatimaBarcode catimaBarcode = barcodeValues.format();
CharSequence[] barcodeDescriptions = new CharSequence[parseResultList.size()];
for (int i = 0; i < parseResultList.size(); i++) {
ParseResult parseResult = parseResultList.get(i);
CatimaBarcode catimaBarcode = parseResult.getLoyaltyCard().barcodeType;
String barcodeContent = barcodeValues.content();
String barcodeContent = parseResult.getLoyaltyCard().cardId;
// Shorten overly long barcodes
if (barcodeContent.length() > 22) {
barcodeContent = barcodeContent.substring(0, 20) + "";
}
if (barcodeValues.note() != null) {
barcodeDescriptions[i] = String.format("%s: %s (%s)", barcodeValues.note(), catimaBarcode != null ? catimaBarcode.prettyName() : context.getString(R.string.noBarcode), barcodeContent);
String parseResultNote = parseResult.getNote();
if (parseResultNote != null) {
barcodeDescriptions[i] = String.format("%s: %s (%s)", parseResultNote, catimaBarcode != null ? catimaBarcode.prettyName() : context.getString(R.string.noBarcode), barcodeContent);
} else {
barcodeDescriptions[i] = String.format("%s (%s)", catimaBarcode != null ? catimaBarcode.prettyName() : context.getString(R.string.noBarcode), barcodeContent);
}
@@ -371,7 +415,7 @@ public class Utils {
builder.setTitle(context.getString(R.string.multipleBarcodesFoundPleaseChooseOne));
builder.setItems(
barcodeDescriptions,
(dialogInterface, i) -> callback.onUserChoseBarcode(barcodeValuesList.get(i))
(dialogInterface, i) -> callback.onUserChoseParseResult(parseResultList.get(i))
);
builder.setOnCancelListener(dialogInterface -> callback.onUserDismissedSelector());
builder.show();
@@ -791,7 +835,7 @@ public class Utils {
}
}
public static Bitmap loadImage(String path) {
public static @Nullable Bitmap loadImage(String path) {
try {
return BitmapFactory.decodeStream(new FileInputStream(path));
} catch (IOException e) {
@@ -800,7 +844,7 @@ public class Utils {
}
}
public static Bitmap loadTempImage(Context context, String name) {
public static @Nullable Bitmap loadTempImage(Context context, String name) {
return loadImage(context.getCacheDir() + "/" + name);
}
@@ -877,7 +921,7 @@ public class Utils {
return typedValue.data;
}
public static int getHeaderColorFromImage(Bitmap image, int fallback) {
public static int getHeaderColorFromImage(@Nullable Bitmap image, int fallback) {
if (image == null) {
return fallback;
}

View File

@@ -23,7 +23,7 @@ public class ZipUtils {
return new JSONObject(read(zipInputStream));
}
private static String read(ZipInputStream zipInputStream) throws IOException {
public static String read(ZipInputStream zipInputStream) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
Reader reader = new BufferedReader(new InputStreamReader(zipInputStream, StandardCharsets.UTF_8));
int c;

View File

@@ -49,7 +49,7 @@ public class CatimaExporter implements Exporter {
// Generate CSV
ByteArrayOutputStream catimaOutputStream = new ByteArrayOutputStream();
OutputStreamWriter catimaOutputStreamWriter = new OutputStreamWriter(catimaOutputStream, StandardCharsets.UTF_8);
writeCSV(database, catimaOutputStreamWriter);
writeCSV(context, database, catimaOutputStreamWriter);
// Add CSV to zip file
ZipParameters csvZipParameters = createZipParameters("catima.csv", password);
@@ -64,12 +64,12 @@ public class CatimaExporter implements Exporter {
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(database);
while (cardCursor.moveToNext()) {
// For each card
LoyaltyCard card = LoyaltyCard.fromCursor(cardCursor);
LoyaltyCard card = LoyaltyCard.fromCursor(context, cardCursor);
// For each image
for (ImageLocationType imageLocationType : ImageLocationType.values()) {
// If it exists, add to the .zip file
Bitmap image = Utils.retrieveCardImage(context, card.id, imageLocationType);
Bitmap image = card.getImageForImageLocationType(context, imageLocationType);
if (image != null) {
ZipParameters imageZipParameters = createZipParameters(Utils.getCardImageFileName(card.id, imageLocationType), password);
zipOutputStream.putNextEntry(imageZipParameters);
@@ -95,7 +95,7 @@ public class CatimaExporter implements Exporter {
return zipParameters;
}
private void writeCSV(SQLiteDatabase database, OutputStreamWriter output) throws IOException, InterruptedException {
private void writeCSV(Context context, SQLiteDatabase database, OutputStreamWriter output) throws IOException, InterruptedException {
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
// Print the version
@@ -142,7 +142,7 @@ public class CatimaExporter implements Exporter {
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(database);
while (cardCursor.moveToNext()) {
LoyaltyCard card = LoyaltyCard.fromCursor(cardCursor);
LoyaltyCard card = LoyaltyCard.fromCursor(context, cardCursor);
printer.printRecord(card.id,
card.store,
@@ -176,7 +176,7 @@ public class CatimaExporter implements Exporter {
Cursor cardCursor2 = DBHelper.getLoyaltyCardCursor(database);
while (cardCursor2.moveToNext()) {
LoyaltyCard card = LoyaltyCard.fromCursor(cardCursor2);
LoyaltyCard card = LoyaltyCard.fromCursor(context, cardCursor2);
for (Group group : DBHelper.getLoyaltyCardGroups(database, card.id)) {
printer.printRecord(card.id, group._id);

View File

@@ -124,7 +124,7 @@ public class CatimaImporter implements Importer {
Set<String> existingImages = DBHelper.imageFiles(context, database);
for (LoyaltyCard card : data.cards) {
LoyaltyCard existing = DBHelper.getLoyaltyCard(database, card.id);
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);
@@ -152,7 +152,7 @@ public class CatimaImporter implements Importer {
}
public boolean isDuplicate(Context context, final LoyaltyCard existing, final LoyaltyCard card, final Set<String> existingImages, final Map<String, String> imageChecksums) throws IOException {
if (!LoyaltyCard.isDuplicate(existing, card)) {
if (!LoyaltyCard.isDuplicate(context, existing, card)) {
return false;
}
for (ImageLocationType imageLocationType : ImageLocationType.values()) {
@@ -490,7 +490,29 @@ public class CatimaImporter implements Importer {
// We catch this exception so we can still import old backups
}
return new LoyaltyCard(id, store, note, validFrom, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus, lastUsed, DBHelper.DEFAULT_ZOOM_LEVEL, archiveStatus);
return new LoyaltyCard(
id,
store,
note,
validFrom,
expiry,
balance,
balanceType,
cardId,
barcodeId,
barcodeType,
headerColor,
starStatus,
lastUsed,
DBHelper.DEFAULT_ZOOM_LEVEL,
archiveStatus,
null,
null,
null,
null,
null,
null
);
}
/**

View File

@@ -149,7 +149,29 @@ public class FidmeImporter implements Importer {
// TODO: Front and back image
// use -1 for the ID, it will be ignored when inserting the card into the DB
return new LoyaltyCard(-1, store, note, null, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, starStatus, Utils.getUnixTime(), DBHelper.DEFAULT_ZOOM_LEVEL, archiveStatus);
return new LoyaltyCard(
-1,
store,
note,
null,
null,
BigDecimal.valueOf(0),
null,
cardId,
null,
barcodeType,
headerColor,
starStatus,
Utils.getUnixTime(),
DBHelper.DEFAULT_ZOOM_LEVEL,
archiveStatus,
null,
null,
null,
null,
null,
null
);
}
public void saveAndDeduplicate(SQLiteDatabase database, final ImportedData data) {

View File

@@ -354,7 +354,29 @@ public class StocardImporter implements Importer {
long lastUsed = record.lastUsed != null ? record.lastUsed : Utils.getUnixTime();
LoyaltyCard card = new LoyaltyCard(tempID, store, note, null, null, BigDecimal.valueOf(0), null, record.cardId, null, barcodeType, headerColor, 0, lastUsed, DBHelper.DEFAULT_ZOOM_LEVEL, 0);
LoyaltyCard card = new LoyaltyCard(
tempID,
store,
note,
null,
null,
BigDecimal.valueOf(0),
null,
record.cardId,
null,
barcodeType,
headerColor,
0,
lastUsed,
DBHelper.DEFAULT_ZOOM_LEVEL,
0,
null,
null,
null,
null,
null,
null
);
importedData.cards.add(card);
Map<ImageLocationType, Bitmap> images = new HashMap<>();

View File

@@ -151,7 +151,29 @@ public class VoucherVaultImporter implements Importer {
}
// use -1 for the ID, it will be ignored when inserting the card into the DB
importedData.cards.add(new LoyaltyCard(-1, store, "", null, expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0, Utils.getUnixTime(), DBHelper.DEFAULT_ZOOM_LEVEL, 0));
importedData.cards.add(new LoyaltyCard(
-1,
store,
"",
null,
expiry,
balance,
balanceType,
cardId,
null,
barcodeType,
headerColor,
0,
Utils.getUnixTime(),
DBHelper.DEFAULT_ZOOM_LEVEL,
0,
null,
null,
null,
null,
null,
null
));
}
return importedData;

View File

@@ -0,0 +1,27 @@
package protect.card_locker.viewmodels
import android.net.Uri
import androidx.lifecycle.ViewModel
import protect.card_locker.LoyaltyCard
import protect.card_locker.LoyaltyCardField
import protect.card_locker.async.TaskHandler
class LoyaltyCardEditActivityViewModel : ViewModel() {
var initialized: Boolean = false
var hasChanged: Boolean = false
var taskHandler: TaskHandler = TaskHandler();
var addGroup: String? = null
var openSetIconMenu: Boolean = false
var loyaltyCardId: Int = 0
var updateLoyaltyCard: Boolean = false
var duplicateFromLoyaltyCardId: Boolean = false
var importLoyaltyCardUri: Uri? = null
var tabIndex: Int = 0
var requestedImageType: Int = 0
var tempLoyaltyCardField: LoyaltyCardField? = null
var loyaltyCard: LoyaltyCard = LoyaltyCard()
}

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M368,640L480,556L590,640L548,504L660,416L524,416L480,280L436,416L300,416L410,504L368,640ZM160,800Q127,800 103.5,776.5Q80,753 80,720L80,585Q80,574 87,566Q94,558 105,556Q129,548 144.5,527Q160,506 160,480Q160,454 144.5,433Q129,412 105,404Q94,402 87,394Q80,386 80,375L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,375Q880,386 873,394Q866,402 855,404Q831,412 815.5,433Q800,454 800,480Q800,506 815.5,527Q831,548 855,556Q866,558 873,566Q880,574 880,585L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,618Q763,596 741.5,559.5Q720,523 720,480Q720,437 741.5,400.5Q763,364 800,342L800,240Q800,240 800,240Q800,240 800,240L160,240Q160,240 160,240Q160,240 160,240L160,342Q197,364 218.5,400.5Q240,437 240,480Q240,523 218.5,559.5Q197,596 160,618L160,720Q160,720 160,720Q160,720 160,720ZM480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>

View File

@@ -360,4 +360,7 @@
<string name="exportCancelled">Export cancelled</string>
<string name="useFrontImage">Use front image</string>
<string name="useBackImage">Use back image</string>
<string name="addFromPkpass">Select a Passbook file (.pkpass)</string>
<string name="unsupportedFile">This file is not supported</string>
<string name="generic_error_please_retry">Sorry, something went wrong, please try again...</string>
</resources>

View File

@@ -46,7 +46,7 @@ public class DatabaseTest {
assertTrue(result);
assertEquals(1, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(mActivity.getApplicationContext(), mDatabase, 1);
assertNotNull(loyaltyCard);
assertEquals("store", loyaltyCard.store);
assertEquals("note", loyaltyCard.note);
@@ -64,7 +64,7 @@ public class DatabaseTest {
result = DBHelper.deleteLoyaltyCard(mDatabase, mActivity, 1);
assertTrue(result);
assertEquals(0, DBHelper.getLoyaltyCardCount(mDatabase));
assertNull(DBHelper.getLoyaltyCard(mDatabase, 1));
assertNull(DBHelper.getLoyaltyCard(mActivity.getApplicationContext(), mDatabase, 1));
}
@Test
@@ -78,7 +78,7 @@ public class DatabaseTest {
assertTrue(result);
assertEquals(1, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(mActivity.getApplicationContext(), mDatabase, 1);
assertNotNull(loyaltyCard);
assertEquals("store1", loyaltyCard.store);
assertEquals("note1", loyaltyCard.note);
@@ -105,7 +105,7 @@ public class DatabaseTest {
assertTrue(result);
assertEquals(1, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(mActivity.getApplicationContext(), mDatabase, 1);
assertNotNull(loyaltyCard);
assertEquals("store", loyaltyCard.store);
assertEquals("note", loyaltyCard.note);
@@ -138,7 +138,7 @@ public class DatabaseTest {
assertTrue(result);
assertEquals(1, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(mActivity.getApplicationContext(), mDatabase, 1);
assertNotNull(loyaltyCard);
assertEquals("", loyaltyCard.store);
assertEquals("", loyaltyCard.note);
@@ -480,7 +480,7 @@ public class DatabaseTest {
dbHelper.onUpgrade(database, DBHelper.ORIGINAL_DATABASE_VERSION, DBHelper.DATABASE_VERSION);
// Determine that the entries are queryable and the fields are correct
LoyaltyCard card = DBHelper.getLoyaltyCard(database, newCardId);
LoyaltyCard card = DBHelper.getLoyaltyCard(mActivity.getApplicationContext(), database, newCardId);
assertEquals("store", card.store);
assertEquals("", card.note);
assertEquals(null, card.validFrom);
@@ -496,7 +496,7 @@ public class DatabaseTest {
assertEquals(100, card.zoomLevel);
// Determine that the entries are queryable and the fields are correct
LoyaltyCard card2 = DBHelper.getLoyaltyCard(database, newCardId2);
LoyaltyCard card2 = DBHelper.getLoyaltyCard(mActivity.getApplicationContext(), database, newCardId2);
assertEquals("store", card2.store);
assertEquals("", card2.note);
assertEquals(null, card2.validFrom);
@@ -523,7 +523,7 @@ public class DatabaseTest {
assertTrue(result);
assertEquals(1, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(mActivity.getApplicationContext(), mDatabase, 1);
assertNotNull(loyaltyCard);
assertEquals("store", loyaltyCard.store);
assertEquals("note", loyaltyCard.note);

View File

@@ -94,7 +94,7 @@ public class ImportExportTest {
boolean result = (id != -1);
assertTrue(result);
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, (int) id);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, (int) id);
assertEquals("No Expiry", card.store);
assertEquals("", card.note);
assertEquals(null, card.validFrom);
@@ -111,7 +111,7 @@ public class ImportExportTest {
result = (id != -1);
assertTrue(result);
card = DBHelper.getLoyaltyCard(mDatabase, (int) id);
card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, (int) id);
assertEquals("Past", card.store);
assertEquals("", card.note);
assertEquals(null, card.validFrom);
@@ -128,7 +128,7 @@ public class ImportExportTest {
result = (id != -1);
assertTrue(result);
card = DBHelper.getLoyaltyCard(mDatabase, (int) id);
card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, (int) id);
assertEquals("Today", card.store);
assertEquals("", card.note);
assertEquals(null, card.validFrom);
@@ -148,7 +148,7 @@ public class ImportExportTest {
result = (id != -1);
assertTrue(result);
card = DBHelper.getLoyaltyCard(mDatabase, (int) id);
card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, (int) id);
assertEquals("Future", card.store);
assertEquals("", card.note);
assertEquals(null, card.validFrom);
@@ -174,7 +174,7 @@ public class ImportExportTest {
int index = 1;
while (cursor.moveToNext()) {
LoyaltyCard card = LoyaltyCard.fromCursor(cursor);
LoyaltyCard card = LoyaltyCard.fromCursor(activity.getApplicationContext(), cursor);
String expectedStore = String.format("store, \"%4d", index);
String expectedNote = String.format("note, \"%4d", index);
@@ -200,7 +200,7 @@ public class ImportExportTest {
Cursor cursor = DBHelper.getLoyaltyCardCursor(mDatabase);
while (cursor.moveToNext()) {
LoyaltyCard card = LoyaltyCard.fromCursor(cursor);
LoyaltyCard card = LoyaltyCard.fromCursor(activity.getApplicationContext(), cursor);
// ID goes up for duplicates (b/c the cursor orders by store), down for originals
int index = card.id > numCards ? card.id - numCards : numCards - card.id + 1;
@@ -236,7 +236,7 @@ public class ImportExportTest {
while (index < 10) {
cursor.moveToNext();
LoyaltyCard card = LoyaltyCard.fromCursor(cursor);
LoyaltyCard card = LoyaltyCard.fromCursor(activity.getApplicationContext(), cursor);
String expectedStore = String.format("store, \"%4d", index);
String expectedNote = String.format("note, \"%4d", index);
@@ -258,7 +258,7 @@ public class ImportExportTest {
index = 1;
while (cursor.moveToNext() && index < 5) {
LoyaltyCard card = LoyaltyCard.fromCursor(cursor);
LoyaltyCard card = LoyaltyCard.fromCursor(activity.getApplicationContext(), cursor);
String expectedStore = String.format("store, \"%4d", index);
String expectedNote = String.format("note, \"%4d", index);
@@ -649,7 +649,7 @@ public class ImportExportTest {
assertEquals(ImportExportResultType.Success, result.resultType());
assertEquals(1, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
assertEquals("store", card.store);
assertEquals("note", card.note);
@@ -675,7 +675,7 @@ public class ImportExportTest {
assertEquals(ImportExportResultType.Success, result.resultType());
assertEquals(1, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
assertEquals("store", card.store);
assertEquals("note", card.note);
@@ -713,7 +713,7 @@ public class ImportExportTest {
assertEquals(ImportExportResultType.Success, result.resultType());
assertEquals(1, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
assertEquals("store", card.store);
assertEquals("note", card.note);
@@ -739,7 +739,7 @@ public class ImportExportTest {
assertEquals(ImportExportResultType.Success, result.resultType());
assertEquals(1, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
assertEquals("store", card.store);
assertEquals("note", card.note);
@@ -765,7 +765,7 @@ public class ImportExportTest {
assertEquals(ImportExportResultType.Success, result.resultType());
assertEquals(1, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
assertEquals("store", card.store);
assertEquals("note", card.note);
@@ -798,7 +798,7 @@ public class ImportExportTest {
assertEquals(ImportExportResultType.Success, result.resultType());
assertEquals(1, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
assertEquals("store", card.store);
assertEquals("note", card.note);
@@ -831,7 +831,7 @@ public class ImportExportTest {
// Create card 1
int loyaltyCardId = (int) DBHelper.insertLoyaltyCard(mDatabase, "Card 1", "Note 1", new Date(1601510400), new Date(1618053234), new BigDecimal("100"), Currency.getInstance("USD"), "1234", "5432", CatimaBarcode.fromBarcode(BarcodeFormat.QR_CODE), 1, 0, null,0);
loyaltyCardHashMap.put(loyaltyCardId, DBHelper.getLoyaltyCard(mDatabase, loyaltyCardId));
loyaltyCardHashMap.put(loyaltyCardId, DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, loyaltyCardId));
DBHelper.insertGroup(mDatabase, "One");
List<Group> groups = Arrays.asList(DBHelper.getGroup(mDatabase, "One"));
DBHelper.setLoyaltyCardGroups(mDatabase, loyaltyCardId, groups);
@@ -845,7 +845,7 @@ public class ImportExportTest {
// Create card 2
loyaltyCardId = (int) DBHelper.insertLoyaltyCard(mDatabase, "Card 2", "", null, null, new BigDecimal(0), null, "123456", null, null, 2, 1, null,0);
loyaltyCardHashMap.put(loyaltyCardId, DBHelper.getLoyaltyCard(mDatabase, loyaltyCardId));
loyaltyCardHashMap.put(loyaltyCardId, DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, loyaltyCardId));
// Export everything
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
@@ -863,7 +863,7 @@ public class ImportExportTest {
for (Integer loyaltyCardID : loyaltyCardHashMap.keySet()) {
LoyaltyCard loyaltyCard = loyaltyCardHashMap.get(loyaltyCardID);
LoyaltyCard dbLoyaltyCard = DBHelper.getLoyaltyCard(mDatabase, loyaltyCardID);
LoyaltyCard dbLoyaltyCard = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, loyaltyCardID);
assertEquals(loyaltyCard.id, dbLoyaltyCard.id);
assertEquals(loyaltyCard.store, dbLoyaltyCard.store);
@@ -950,7 +950,7 @@ public class ImportExportTest {
assertEquals(Arrays.asList(8, 6), DBHelper.getGroupCardIds(mDatabase, "Fashion"));
// Check all cards
LoyaltyCard card1 = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card1 = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
assertEquals("Card 1", card1.store);
assertEquals("Note 1", card1.note);
@@ -967,7 +967,7 @@ public class ImportExportTest {
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card1.id, ImageLocationType.back));
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card1.id, ImageLocationType.icon));
LoyaltyCard card8 = DBHelper.getLoyaltyCard(mDatabase, 8);
LoyaltyCard card8 = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 8);
assertEquals("Clothes Store", card8.store);
assertEquals("Note about store", card8.note);
@@ -984,7 +984,7 @@ public class ImportExportTest {
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card8.id, ImageLocationType.back));
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card8.id, ImageLocationType.icon));
LoyaltyCard card2 = DBHelper.getLoyaltyCard(mDatabase, 2);
LoyaltyCard card2 = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 2);
assertEquals("Department Store", card2.store);
assertEquals("", card2.note);
@@ -1001,7 +1001,7 @@ public class ImportExportTest {
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card2.id, ImageLocationType.back));
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card2.id, ImageLocationType.icon));
LoyaltyCard card3 = DBHelper.getLoyaltyCard(mDatabase, 3);
LoyaltyCard card3 = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 3);
assertEquals("Grocery Store", card3.store);
assertEquals("Multiline note about grocery store\n\nwith blank line", card3.note);
@@ -1018,7 +1018,7 @@ public class ImportExportTest {
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card3.id, ImageLocationType.back));
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card3.id, ImageLocationType.icon));
LoyaltyCard card4 = DBHelper.getLoyaltyCard(mDatabase, 4);
LoyaltyCard card4 = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 4);
assertEquals("Pharmacy", card4.store);
assertEquals("", card4.note);
@@ -1035,7 +1035,7 @@ public class ImportExportTest {
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card4.id, ImageLocationType.back));
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card4.id, ImageLocationType.icon));
LoyaltyCard card5 = DBHelper.getLoyaltyCard(mDatabase, 5);
LoyaltyCard card5 = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 5);
assertEquals("Restaurant", card5.store);
assertEquals("Note about restaurant here", card5.note);
@@ -1052,7 +1052,7 @@ public class ImportExportTest {
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card5.id, ImageLocationType.back));
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card5.id, ImageLocationType.icon));
LoyaltyCard card6 = DBHelper.getLoyaltyCard(mDatabase, 6);
LoyaltyCard card6 = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 6);
assertEquals("Shoe Store", card6.store);
assertEquals("", card6.note);
@@ -1081,7 +1081,7 @@ public class ImportExportTest {
assertEquals(ImportExportResultType.Success, result.resultType());
assertEquals(3, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
assertEquals("Hema", card.store);
assertEquals("2021-03-24 18:35:08 UTC", card.note);
@@ -1094,7 +1094,7 @@ public class ImportExportTest {
assertEquals(null, card.barcodeType);
assertEquals(0, card.starStatus);
card = DBHelper.getLoyaltyCard(mDatabase, 2);
card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 2);
assertEquals("test", card.store);
assertEquals("Test\n2021-03-24 18:34:19 UTC", card.note);
@@ -1107,7 +1107,7 @@ public class ImportExportTest {
assertEquals(null, card.barcodeType);
assertEquals(0, card.starStatus);
card = DBHelper.getLoyaltyCard(mDatabase, 3);
card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 3);
assertEquals("Albert Heijn", card.store);
assertEquals("Bonus Kaart\n2021-03-24 16:47:47 UTC\nFirst Last", card.note);
@@ -1138,7 +1138,7 @@ public class ImportExportTest {
assertEquals(ImportExportResultType.Success, result.resultType());
assertEquals(3, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
assertEquals("Air Miles", card.store);
assertEquals("szjsbs", card.note);
@@ -1156,7 +1156,7 @@ public class ImportExportTest {
assertTrue(BitmapFactory.decodeStream(getClass().getResourceAsStream("stocard-back.jpg")).sameAs(Utils.retrieveCardImage(activity.getApplicationContext(), 1, ImageLocationType.back)));
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 1, ImageLocationType.icon));
card = DBHelper.getLoyaltyCard(mDatabase, 2);
card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 2);
assertEquals("GAMMA", card.store);
assertEquals("", card.note);
@@ -1174,7 +1174,7 @@ public class ImportExportTest {
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 2, ImageLocationType.back));
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 2, ImageLocationType.icon));
card = DBHelper.getLoyaltyCard(mDatabase, 3);
card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 3);
assertEquals("", card.store);
assertEquals("", card.note);
@@ -1205,7 +1205,7 @@ public class ImportExportTest {
assertEquals(ImportExportResultType.Success, result.resultType());
assertEquals(4, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
assertEquals("Foo", card.store);
assertEquals("", card.note);
@@ -1223,7 +1223,7 @@ public class ImportExportTest {
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 1, ImageLocationType.back));
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 1, ImageLocationType.icon));
card = DBHelper.getLoyaltyCard(mDatabase, 2);
card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 2);
assertEquals("Air Miles", card.store);
assertEquals("szjsbs\nMiles", card.note);
@@ -1241,7 +1241,7 @@ public class ImportExportTest {
assertTrue(BitmapFactory.decodeStream(getClass().getResourceAsStream("stocard-back.jpg")).sameAs(Utils.retrieveCardImage(activity.getApplicationContext(), 2, ImageLocationType.back)));
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 2, ImageLocationType.icon));
card = DBHelper.getLoyaltyCard(mDatabase, 3);
card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 3);
assertEquals("GAMMA", card.store);
assertEquals("", card.note);
@@ -1259,7 +1259,7 @@ public class ImportExportTest {
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 3, ImageLocationType.back));
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 3, ImageLocationType.icon));
card = DBHelper.getLoyaltyCard(mDatabase, 4);
card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 4);
assertEquals("", card.store);
assertEquals("", card.note);
@@ -1289,7 +1289,7 @@ public class ImportExportTest {
assertEquals(ImportExportResultType.Success, result.resultType());
assertEquals(2, DBHelper.getLoyaltyCardCount(mDatabase));
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
assertEquals("Clothes Store", card.store);
assertEquals("", card.note);
@@ -1303,7 +1303,7 @@ public class ImportExportTest {
assertEquals(Color.GRAY, (long) card.headerColor);
assertEquals(0, card.starStatus);
card = DBHelper.getLoyaltyCard(mDatabase, 2);
card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 2);
assertEquals("Department Store", card.store);
assertEquals("", card.note);

View File

@@ -25,12 +25,13 @@ import java.util.Date;
@RunWith(RobolectricTestRunner.class)
public class ImportURITest {
private Activity activity;
private ImportURIHelper importURIHelper;
private SQLiteDatabase mDatabase;
@Before
public void setUp() {
Activity activity = Robolectric.setupActivity(MainActivity.class);
activity = Robolectric.setupActivity(MainActivity.class);
importURIHelper = new ImportURIHelper(activity);
mDatabase = TestHelpers.getEmptyDb(activity).getWritableDatabase();
}
@@ -43,7 +44,7 @@ public class ImportURITest {
DBHelper.insertLoyaltyCard(mDatabase, "store", "This note contains evil symbols like & and = that will break the parser if not escaped right $#!%()*+;:á", date, date, new BigDecimal("100"), null, BarcodeFormat.UPC_E.toString(), BarcodeFormat.UPC_A.toString(), CatimaBarcode.fromBarcode(BarcodeFormat.QR_CODE), Color.BLACK, 1, null,0);
// Get card
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
// Card to URI
Uri cardUri = importURIHelper.toUri(card);
@@ -73,7 +74,7 @@ public class ImportURITest {
DBHelper.insertLoyaltyCard(mDatabase, "store", "note", null, null, new BigDecimal("10.00"), Currency.getInstance("EUR"), BarcodeFormat.UPC_A.toString(), null, CatimaBarcode.fromBarcode(BarcodeFormat.QR_CODE), null, 0, null,0);
// Get card
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
// Card to URI
Uri cardUri = importURIHelper.toUri(card);
@@ -103,7 +104,7 @@ public class ImportURITest {
DBHelper.insertLoyaltyCard(mDatabase, "store", "note", null, null, new BigDecimal("10.00"), Currency.getInstance("EUR"), BarcodeFormat.UPC_A.toString(), null, CatimaBarcode.fromBarcode(BarcodeFormat.QR_CODE), null, 0, null,0);
// Get card
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
// Card to URI, with a trailing slash
Uri cardUri = importURIHelper.toUri(card).buildUpon().path("/share/").build();

View File

@@ -97,7 +97,7 @@ public class LoyaltyCardCursorAdapterTest {
@Test
public void TestCursorAdapterEmptyNote() {
DBHelper.insertLoyaltyCard(mDatabase, "store", "", null, null, new BigDecimal("0"), null, "cardId", null, CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A), Color.BLACK, 0, null,0);
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
Cursor cursor = DBHelper.getLoyaltyCardCursor(mDatabase);
cursor.moveToFirst();
@@ -112,7 +112,7 @@ public class LoyaltyCardCursorAdapterTest {
@Test
public void TestCursorAdapterWithNote() {
DBHelper.insertLoyaltyCard(mDatabase, "store", "note", null, null, new BigDecimal("0"), null, "cardId", null, CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A), Color.BLACK, 0, null,0);
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
Cursor cursor = DBHelper.getLoyaltyCardCursor(mDatabase);
cursor.moveToFirst();
@@ -137,7 +137,7 @@ public class LoyaltyCardCursorAdapterTest {
assertEquals(4, cursor.getCount());
assertTrue(cursor.moveToFirst());
LoyaltyCard loyaltyCard = LoyaltyCard.fromCursor(cursor);
LoyaltyCard loyaltyCard = LoyaltyCard.fromCursor(activity.getApplicationContext(), cursor);
assertEquals("storeD", loyaltyCard.store);
View view = createView(cursor);
ConstraintLayout star = view.findViewById(R.id.star);
@@ -146,7 +146,7 @@ public class LoyaltyCardCursorAdapterTest {
assertEquals(View.GONE, archive.getVisibility());
assertTrue(cursor.moveToNext());
loyaltyCard = LoyaltyCard.fromCursor(cursor);
loyaltyCard = LoyaltyCard.fromCursor(activity.getApplicationContext(), cursor);
assertEquals("storeC", loyaltyCard.store);
view = createView(cursor);
star = view.findViewById(R.id.star);
@@ -155,7 +155,7 @@ public class LoyaltyCardCursorAdapterTest {
assertEquals(View.GONE, archive.getVisibility());
assertTrue(cursor.moveToNext());
loyaltyCard = LoyaltyCard.fromCursor(cursor);
loyaltyCard = LoyaltyCard.fromCursor(activity.getApplicationContext(), cursor);
assertEquals("storeB", loyaltyCard.store);
view = createView(cursor);
star = view.findViewById(R.id.star);
@@ -164,7 +164,7 @@ public class LoyaltyCardCursorAdapterTest {
assertEquals(View.VISIBLE, archive.getVisibility());
assertTrue(cursor.moveToNext());
loyaltyCard = LoyaltyCard.fromCursor(cursor);
loyaltyCard = LoyaltyCard.fromCursor(activity.getApplicationContext(), cursor);
assertEquals("storeA", loyaltyCard.store);
view = createView(cursor);
star = view.findViewById(R.id.star);
@@ -178,7 +178,7 @@ public class LoyaltyCardCursorAdapterTest {
@Test
public void TestCursorAdapter0Points() {
DBHelper.insertLoyaltyCard(mDatabase, "store", "", null, null, new BigDecimal("0"), null, "cardId", null, CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A), Color.BLACK, 0, null,0);
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
Cursor cursor = DBHelper.getLoyaltyCardCursor(mDatabase);
cursor.moveToFirst();
@@ -193,7 +193,7 @@ public class LoyaltyCardCursorAdapterTest {
@Test
public void TestCursorAdapter0EUR() {
DBHelper.insertLoyaltyCard(mDatabase,"store", "", null, null, new BigDecimal("0"), Currency.getInstance("EUR"), "cardId", null, CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A), Color.BLACK, 0, null,0);
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
Cursor cursor = DBHelper.getLoyaltyCardCursor(mDatabase);
cursor.moveToFirst();
@@ -208,7 +208,7 @@ public class LoyaltyCardCursorAdapterTest {
@Test
public void TestCursorAdapter100Points() {
DBHelper.insertLoyaltyCard(mDatabase, "store", "note", null, null, new BigDecimal("100"), null, "cardId", null, CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A), Color.BLACK, 0, null,0);
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
Cursor cursor = DBHelper.getLoyaltyCardCursor(mDatabase);
cursor.moveToFirst();
@@ -223,7 +223,7 @@ public class LoyaltyCardCursorAdapterTest {
@Test
public void TestCursorAdapter10USD() {
DBHelper.insertLoyaltyCard(mDatabase, "store", "note", null, null, new BigDecimal("10.00"), Currency.getInstance("USD"), "cardId", null, CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A), Color.BLACK, 0, null,0);
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), mDatabase, 1);
Cursor cursor = DBHelper.getLoyaltyCardCursor(mDatabase);
cursor.moveToFirst();

View File

@@ -167,7 +167,7 @@ public class LoyaltyCardViewActivityTest {
assertEquals(1, DBHelper.getLoyaltyCardCount(database));
LoyaltyCard card = DBHelper.getLoyaltyCard(database, 1);
LoyaltyCard card = DBHelper.getLoyaltyCard(activity.getApplicationContext(), database, 1);
assertEquals(store, card.store);
assertEquals(note, card.note);
assertEquals(balance, card.balance);
@@ -216,7 +216,7 @@ public class LoyaltyCardViewActivityTest {
* Initiate and complete a barcode capture, either in success
* or in failure
*/
private void captureBarcodeWithResult(final Activity activity, final boolean success) throws IOException {
private void captureBarcodeWithResult(final Activity activity, final boolean success) {
// Start image capture
final Button startButton = activity.findViewById(R.id.enterButton);
startButton.performClick();
@@ -231,10 +231,14 @@ public class LoyaltyCardViewActivityTest {
assertNotNull(bundle);
Intent resultIntent = new Intent(intent);
Bundle resultBundle = new Bundle();
resultBundle.putString(BarcodeSelectorActivity.BARCODE_CONTENTS, BARCODE_DATA);
resultBundle.putString(BarcodeSelectorActivity.BARCODE_FORMAT, BARCODE_TYPE.name());
resultIntent.putExtras(resultBundle);
LoyaltyCard loyaltyCard = new LoyaltyCard();
loyaltyCard.setBarcodeId(null);
loyaltyCard.setBarcodeType(BARCODE_TYPE);
loyaltyCard.setCardId(BARCODE_DATA);
ParseResult parseResult = new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard);
resultIntent.putExtras(parseResult.toLoyaltyCardBundle(activity));
// Respond to image capture, success
shadowOf(activity).receiveResult(
@@ -247,7 +251,7 @@ public class LoyaltyCardViewActivityTest {
* Initiate and complete a barcode selection, either in success
* or in failure
*/
private void selectBarcodeWithResult(final Activity activity, final String barcodeData, final String barcodeType, final boolean success) throws IOException {
private void selectBarcodeWithResult(final Activity activity, final String barcodeData, final String barcodeType, final boolean success) {
// Start barcode selector
final Button startButton = activity.findViewById(R.id.enterButton);
startButton.performClick();
@@ -267,10 +271,14 @@ public class LoyaltyCardViewActivityTest {
assertNotNull(bundle);
Intent resultIntent = new Intent(intent);
Bundle resultBundle = new Bundle();
resultBundle.putString(BarcodeSelectorActivity.BARCODE_FORMAT, barcodeType);
resultBundle.putString(BarcodeSelectorActivity.BARCODE_CONTENTS, barcodeData);
resultIntent.putExtras(resultBundle);
LoyaltyCard loyaltyCard = new LoyaltyCard();
loyaltyCard.setBarcodeId(null);
loyaltyCard.setBarcodeType(barcodeType != null ? CatimaBarcode.fromName(barcodeType) : null);
loyaltyCard.setCardId(barcodeData);
ParseResult parseResult = new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard);
resultIntent.putExtras(parseResult.toLoyaltyCardBundle(activity));
// Respond to barcode selection, success
shadowOf(activity).receiveResult(
@@ -294,10 +302,17 @@ public class LoyaltyCardViewActivityTest {
} else if (fieldType == FieldTypeView.ImageView) {
ImageView imageView = (ImageView) view;
Bitmap image = null;
if (imageView.getTag() != null) {
try {
image = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
} catch (ClassCastException e) {
// This is probably a VectorDrawable, the placeholder image. Aka: No image.
}
assertEquals(contents, image);
if (contents == null && image == null) {
return;
}
assertTrue(image.sameAs((Bitmap) contents));
} else {
throw new UnsupportedOperationException();
}
@@ -411,8 +426,8 @@ public class LoyaltyCardViewActivityTest {
cardIdField.setText("12345678");
barcodeField.setText("87654321");
barcodeTypeField.setText(CatimaBarcode.fromBarcode(BarcodeFormat.QR_CODE).prettyName());
activity.setCardImage(frontImageView, frontBitmap, true);
activity.setCardImage(backImageView, backBitmap, true);
activity.setCardImage(ImageLocationType.front, frontImageView, frontBitmap, true);
activity.setCardImage(ImageLocationType.back, backImageView, backBitmap, true);
shadowOf(getMainLooper()).idle();
@@ -575,13 +590,13 @@ public class LoyaltyCardViewActivityTest {
// A change was made
shadowOf(activity).clickMenuItem(android.R.id.home);
assertEquals(true, activity.confirmExitDialog.isShowing());
assertEquals(true, activity.hasChanged);
assertEquals(true, activity.viewModel.getHasChanged());
assertEquals(false, activity.isFinishing());
// Exit after setting hasChanged to false
activity.hasChanged = false;
activity.viewModel.setHasChanged(false);
shadowOf(activity).clickMenuItem(android.R.id.home);
assertEquals(false, activity.hasChanged);
assertEquals(false, activity.viewModel.getHasChanged());
assertEquals(true, activity.isFinishing());
}
@@ -698,13 +713,13 @@ public class LoyaltyCardViewActivityTest {
// A change was made
shadowOf(activity).clickMenuItem(android.R.id.home);
assertEquals(true, activity.confirmExitDialog.isShowing());
assertEquals(true, activity.hasChanged);
assertEquals(true, activity.viewModel.getHasChanged());
assertEquals(false, activity.isFinishing());
// Exit after setting hasChanged to false
activity.hasChanged = false;
activity.viewModel.setHasChanged(false);
shadowOf(activity).clickMenuItem(android.R.id.home);
assertEquals(false, activity.hasChanged);
assertEquals(false, activity.viewModel.getHasChanged());
assertEquals(true, activity.isFinishing());
database.close();

View File

@@ -0,0 +1,238 @@
package protect.card_locker
import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.Color
import android.net.Uri
import androidx.test.core.app.ApplicationProvider
import com.google.zxing.BarcodeFormat
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.shadows.ShadowContentResolver
import org.robolectric.shadows.ShadowLog
import java.math.BigDecimal
import java.util.Date
@RunWith(RobolectricTestRunner::class)
class PkpassTest {
@Before
fun setUp() {
ShadowLog.stream = System.out
}
@Test
fun testEurowingsPass() {
// Prepare
val context: Context = ApplicationProvider.getApplicationContext()
val pkpass = "pkpass/Eurowings/Eurowings.pkpass"
val image = "pkpass/Eurowings/logo@2x.png"
val pkpassUri = Uri.parse(pkpass)
val imageUri = Uri.parse(image)
ShadowContentResolver().registerInputStream(pkpassUri, javaClass.getResourceAsStream(pkpass))
ShadowContentResolver().registerInputStream(imageUri, javaClass.getResourceAsStream(image))
val parser = PkpassParser(context, pkpassUri)
val imageBitmap = BitmapFactory.decodeStream(context.contentResolver.openInputStream(imageUri))
// Confirm this does not have languages
Assert.assertEquals(listOf("de", "en"), parser.listLocales())
// Confirm correct parsing (en)
var parsedCard = parser.toLoyaltyCard("de")
Assert.assertEquals(-1, parsedCard.id)
Assert.assertEquals("EUROWINGS", parsedCard.store)
Assert.assertEquals("Eurowings Boarding Pass\n" +
"\n" +
"Gate: B61\n" +
"Sitz: 12D\n" +
"\n" +
"Cologne-Bonn: CGN\n" +
"Dubrovnik: DBV\n" +
"\n" +
"Name: John Doe\n" +
"Status: -\n" +
"Gruppe: GROUP 1\n" +
"Tarif: SMART\n" +
"\n" +
"Flug: EW 954\n" +
"Datum: 08/09/2019\n" +
"Boarding: 05:00\n" +
"Gate Schließt: 05:15\n" +
"\n" +
"Eurowings wünscht Ihnen einen angenehmen Flug.\n" +
"\n" +
"Wir bitten Sie, sich zur angegeben Boarding Zeit am Gate einzufinden.\n" +
"Buchungscode: JBZPPP\n" +
"Sequenz: 73\n" +
"Hinweis: Bitte beachten Sie, dass obwohl Ihr Flug verspätet sein mag, Sie dennoch wie geplant pünktlich am Check-in und am Abfluggate erscheinen müssen.\n" +
"\n" +
"Kostenlose Mitnahme eines Handgepäckstücks (8 Kg, 55 x 40 x 23cm).\n" +
"Mitnahme von Flüssigkeiten im Handgepäck: Neben den sonstigen Beschränkungen für das Handgepäck ist für alle Abflüge innerhalb der Europäischen Union sowie vielen weiteren Ländern (u.a. Schweiz, Russland, Island, Kroatien, Israel, Ägypten, Marokko, Tunesien, Norwegen) die Mitnahme von vor der Fluggastkontrolle erworbenen bzw. mitgebrachten Flüssigkeiten und Gels nur noch eingeschränkt erlaubt:\n" +
"\n" +
"- Sämtliche Flüssigkeiten (wie Kosmetik- und Toilettenartikel, Gels, Pasten, Cremes, Lotionen, Gemische aus flüssigen und festen Stoffen, Parfums, Behälter unter Druck, Dosen, Wasserflaschen etc.) sowie wachs- oder gelartige Stoffe dürfen nur noch in Behältnissen bis zu 100 ml bzw. 100 g mit an Bord genommen werden.\n" +
"\n" +
"- Diese Flüssigkeiten bzw. Stoffe müssen in einem transparenten, wiederverschließbaren Plastikbeutel (max. 1 kg Inhalt) vollständig geschlossen, verpackt sein.\n" +
"\n" +
"- Diese Beutel müssen Fluggäste selbst vor dem Abflug erwerben. Sie sind in vielen Supermärkten z. B. als Gefrierbeutel erhältlich. Es besteht zurzeit keine Möglichkeit, entsprechende Plastikbeutel am Eurowings Check-In zu erwerben bzw. auszugeben.\n" +
"\n" +
"- Verschreibungspflichtige Medikamente sowie Babynahrung dürfen weiterhin im Handgepäck transportiert werden. Der Fluggast muss nachweisen, dass die Medikamente und Babynahrung während des Fluges benötigt werden.\n" +
"\n" +
"- Produkte und Beutel, die nicht den Maßgaben entsprechen oder die nur mit Gummiband oder ähnlichem verschlossen sind, müssen leider abgegeben werden.\n" +
"\n" +
"Flüssigkeiten und Gels, die Sie nicht zwingend während Ihres Aufenthalts an Bord benötigen, sollten zur raschen Fluggastabfertigung nach Möglichkeit im aufzugebenden Gepäck untergebracht werden.\n" +
"\n" +
"Selbstverständlich ist die Mitnahme von allen Flüssigkeiten/Gels/Getränken aus Travel-Value oder Duty Free-Shops, die nach der Fluggastkontrolle erworben werden, weiterhin erlaubt.\n" +
"\n" +
"Eurowings übernimmt keine Haftung für Gegenstände, die der Fluggast nicht im Handgepäck mitführen darf und deshalb aus Sicherheitsgründen an der Fluggastkontrolle abgeben muss.\n" +
"Kontakt: Sie erreichen das deutsche Call Center unter der Telefonnummer\n" +
"\n" +
"0180 6 320 320 ( 0:00 Uhr - 24:00 Uhr )\n" +
"\n" +
"(0,20 € pro Anruf aus dem Festnetz der Deutschen Telekom - Mobilfunk maximal 0,60 € pro Anruf).", parsedCard.note)
Assert.assertEquals(Date(1567911600000), parsedCard.validFrom)
Assert.assertEquals(null, parsedCard.expiry)
Assert.assertEquals(BigDecimal(0), parsedCard.balance)
Assert.assertEquals(null, parsedCard.balanceType)
Assert.assertEquals("M1DOE/JOHN JBZPPP CGNDBVEW 0954 251A012D0073 148>5181W 9250BEW 00000000000002A0000000000000 0 N", parsedCard.cardId)
Assert.assertEquals(null, parsedCard.barcodeId)
Assert.assertEquals(BarcodeFormat.AZTEC, parsedCard.barcodeType!!.format())
Assert.assertEquals(Color.parseColor("#FFFFFF"), parsedCard.headerColor)
Assert.assertEquals(0, parsedCard.starStatus)
Assert.assertEquals(0, parsedCard.archiveStatus)
Assert.assertEquals(0, parsedCard.lastUsed)
Assert.assertEquals(DBHelper.DEFAULT_ZOOM_LEVEL, parsedCard.zoomLevel)
// Confirm correct image is used
Assert.assertTrue(imageBitmap.sameAs(parser.image))
// Confirm correct parsing (en)
parsedCard = parser.toLoyaltyCard("en")
Assert.assertEquals(-1, parsedCard.id)
Assert.assertEquals("EUROWINGS", parsedCard.store)
Assert.assertEquals("Eurowings Boarding Pass\n" +
"\n" +
"Gate: B61\n" +
"Seat: 12D\n" +
"\n" +
"Cologne-Bonn: CGN\n" +
"Dubrovnik: DBV\n" +
"\n" +
"Name: John Doe\n" +
"Status: -\n" +
"Group: GROUP 1\n" +
"Fare: SMART\n" +
"\n" +
"Flight: EW 954\n" +
"Date: 08/09/2019\n" +
"Boarding: 05:00\n" +
"Gate closure: 05:15\n" +
"\n" +
"Eurowings wishes you a pleasant flight .\n" +
"\n" +
"We kindly ask you to be present at your departure gate on time.\n" +
"Booking code: JBZPPP\n" +
"Sequence: 73\n" +
"Notice: Please note that although your flight may be delayed, you will still need to check in and go to your departure gate on time as scheduled.\n" +
"\n" +
"Carry on one item of hand luggage (8 kg, 55 x 40 x 23 cm) for free.\n" +
"Carrying liquids in hand luggage: In addition to other restrictions on hand luggage, there are still restrictions on liquids and gels brought by the passenger or purchased before the security control on all departures within the European Union, as well as to many other countries (including Switzerland, Russia, Iceland, Croatia, Israel, Egypt, Morocco, Tunisia and Norway):\n" +
"\n" +
"- All liquids (such as toiletries and cosmetics, gels, pastes, creams, lotions, mixtures of liquids and solids, perfumes, pressurised containers, cans, water bottles etc) as well as wax and gel-like substances may only be carried on board in amounts less than 100ml or 100g.\n" +
"\n" +
"- These liquids or substances must be packed in closed containers in a transparent, re-sealable plastic bag (max. contents 1 kg).\n" +
"\n" +
"- It is the passengers responsibility to purchase this bag before departure. They are available in many supermarkets, e.g. as freezer bags. It is currently not possible for passengers to obtain or purchase the required bags from Eurowings check-in.\n" +
"\n" +
"- Prescription medicines and baby food may still be carried in hand baggage. The passenger must prove that such medicines and/or baby food are needed during the flight.\n" +
"\n" +
"- Products and bags which do not meet the requirements or are only sealed with a rubber band or similar will unfortunately have to be surrendered by passengers\n" +
"\n" +
"In order to pass through the airport as quickly as possible, you are strongly advised to pack any liquids or gels which are not essential for your journey on board the aircraft in your checked baggage if possible.\n" +
"\n" +
"As a matter of course, liquids from the Travel Value / Duty Free shops which have been purchased after you have passed through security are still allowed on board.\n" +
"\n" +
"Eurowings shall not be liable for any items which passengers are prohibited from carrying in their hand baggage for security reasons and are required to surrender at the security checkpoint.\n" +
"Contact: https://mobile.eurowings.com/booking/StaticContactInfo.aspx?culture=en-GB&back=home", parsedCard.note)
Assert.assertEquals(Date(1567911600000), parsedCard.validFrom)
Assert.assertEquals(null, parsedCard.expiry)
Assert.assertEquals(BigDecimal(0), parsedCard.balance)
Assert.assertEquals(null, parsedCard.balanceType)
Assert.assertEquals("M1DOE/JOHN JBZPPP CGNDBVEW 0954 251A012D0073 148>5181W 9250BEW 00000000000002A0000000000000 0 N", parsedCard.cardId)
Assert.assertEquals(null, parsedCard.barcodeId)
Assert.assertEquals(BarcodeFormat.AZTEC, parsedCard.barcodeType!!.format())
Assert.assertEquals(Color.parseColor("#FFFFFF"), parsedCard.headerColor)
Assert.assertEquals(0, parsedCard.starStatus)
Assert.assertEquals(0, parsedCard.archiveStatus)
Assert.assertEquals(0, parsedCard.lastUsed)
Assert.assertEquals(DBHelper.DEFAULT_ZOOM_LEVEL, parsedCard.zoomLevel)
// Confirm correct image is used
Assert.assertTrue(imageBitmap.sameAs(parser.image))
}
@Test
fun testDCBPkPass() {
// Prepare
val context: Context = ApplicationProvider.getApplicationContext()
val pkpass = "pkpass/DCBLN24/DCBLN24-QLUKT-1-passbook.pkpass"
val image = "pkpass/DCBLN24/logo.png"
val pkpassUri = Uri.parse(pkpass)
val imageUri = Uri.parse(image)
ShadowContentResolver().registerInputStream(pkpassUri, javaClass.getResourceAsStream(pkpass))
ShadowContentResolver().registerInputStream(imageUri, javaClass.getResourceAsStream(image))
val parser = PkpassParser(context, pkpassUri)
val imageBitmap = BitmapFactory.decodeStream(context.contentResolver.openInputStream(imageUri))
// Confirm this does not have languages
Assert.assertEquals(listOf<String>(), parser.listLocales())
// Confirm correct parsing
val parsedCard = parser.toLoyaltyCard(null)
Assert.assertEquals(-1, parsedCard.id)
Assert.assertEquals("droidcon Berlin 2024", parsedCard.store)
Assert.assertEquals("Ticket for droidcon Berlin 2024 (Speaker)\n" +
"\n" +
"Admission time: 2024-07-03 08:00\n" +
"\n" +
"Event: droidcon Berlin 2024\n" +
"\n" +
"Product: Speaker\n" +
"\n" +
"Attendee name: Sylvia van Os\n" +
"From: 2024-07-03 08:00\n" +
"To: 2024-07-05 18:30\n" +
"\n" +
"Admission time: 2024-07-03 08:00\n" +
"Attendee name: Sylvia van Os\n" +
"Ordered by: REDACTED@example.com\n" +
"Organizer: droidcon\n" +
"Organizer contact: global@droidcon.de\n" +
"Order code: REDACTED\n" +
"Purchase date: 2024-06-06 07:26\n" +
"Website: https://pretix.eu/droidcon/dcbln24/", parsedCard.note)
Assert.assertEquals(null, parsedCard.validFrom)
Assert.assertEquals(null, parsedCard.expiry)
Assert.assertEquals(BigDecimal(0), parsedCard.balance)
Assert.assertEquals(null, parsedCard.balanceType)
Assert.assertEquals("ca4phaix1ahkahD2eiVi5iepahxa6rei", parsedCard.cardId)
Assert.assertEquals(null, parsedCard.barcodeId)
Assert.assertEquals(BarcodeFormat.QR_CODE, parsedCard.barcodeType!!.format())
Assert.assertEquals(Color.parseColor("#0014e6"), parsedCard.headerColor)
Assert.assertEquals(0, parsedCard.starStatus)
Assert.assertEquals(0, parsedCard.archiveStatus)
Assert.assertEquals(0, parsedCard.lastUsed)
Assert.assertEquals(DBHelper.DEFAULT_ZOOM_LEVEL, parsedCard.zoomLevel)
// Confirm correct image is used
Assert.assertTrue(imageBitmap.sameAs(parser.image))
}
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

Binary file not shown.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -3,6 +3,7 @@
plugins {
id("com.android.application") version "8.7.3" apply false
id("com.github.spotbugs") version "5.1.4" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
allprojects {