diff --git a/CHANGELOG.md b/CHANGELOG.md index ecc08c7ec..a83b45718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,47 @@ # Changelog +## Unreleased + +Changes: + +- Support importing [Fidme](https://play.google.com/store/apps/details?id=fr.snapp.fidme) exports +- Fix multiline note cutoff +- Change "Thank you" text on privacy dialog to "Accept" because Huawei is overly pedantic + +## v1.11 (2021-03-21) + +Changes: + +- Add privacy policy dialog on first start (required by Huawei) + +## v1.10 (2021-03-07) + +Changes: + +- Support importing [Voucher Vault](https://github.com/tim-smart/vouchervault/) exports +- Option to keep the screen on while viewing a loyalty card +- Option to suspend the lock screen while viewing a loyalty card + +## v1.9.2 (2021-02-24) + +Changes: + +- Fix parsing balance for countries using space as separator + +## v1.9.1 (2021-02-23) + +Changes: + +- Improve balance parsing logic +- Fix currency decimal display on main screen + +## v1.9 (2021-02-22) + +Changes: + +- Add balance support +- Reorganize barcode tab of edit view + ## v1.8.1 (2021-02-12) Changes: diff --git a/app/build.gradle b/app/build.gradle index 829169df6..a8aff6174 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { applicationId "me.hackerchick.catima" minSdkVersion 19 targetSdkVersion 29 - versionCode 58 - versionName "1.8.1" + versionCode 64 + versionName "1.11" vectorDrawables.useSupportLibrary true } diff --git a/app/src/main/java/protect/card_locker/DBHelper.java b/app/src/main/java/protect/card_locker/DBHelper.java index 1c91b48e6..c81cbcddf 100644 --- a/app/src/main/java/protect/card_locker/DBHelper.java +++ b/app/src/main/java/protect/card_locker/DBHelper.java @@ -8,6 +8,8 @@ import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.graphics.Color; +import java.math.BigDecimal; +import java.util.Currency; import java.util.Date; import java.util.ArrayList; import java.util.List; @@ -16,21 +18,23 @@ public class DBHelper extends SQLiteOpenHelper { public static final String DATABASE_NAME = "Catima.db"; public static final int ORIGINAL_DATABASE_VERSION = 1; - public static final int DATABASE_VERSION = 7; + public static final int DATABASE_VERSION = 8; - static class LoyaltyCardDbGroups + public static class LoyaltyCardDbGroups { public static final String TABLE = "groups"; public static final String ID = "_id"; public static final String ORDER = "orderId"; } - static class LoyaltyCardDbIds + public static class LoyaltyCardDbIds { public static final String TABLE = "cards"; public static final String ID = "_id"; public static final String STORE = "store"; public static final String EXPIRY = "expiry"; + public static final String BALANCE = "balance"; + public static final String BALANCE_TYPE = "balancetype"; public static final String NOTE = "note"; public static final String HEADER_COLOR = "headercolor"; public static final String HEADER_TEXT_COLOR = "headertextcolor"; @@ -39,7 +43,7 @@ public class DBHelper extends SQLiteOpenHelper public static final String STAR_STATUS = "starstatus"; } - static class LoyaltyCardDbIdsGroups + public static class LoyaltyCardDbIdsGroups { public static final String TABLE = "cardsGroups"; public static final String cardID = "cardId"; @@ -60,11 +64,14 @@ public class DBHelper extends SQLiteOpenHelper LoyaltyCardDbGroups.ORDER + " INTEGER DEFAULT '0')"); // create table for cards + // Balance is TEXT and not REAL to be able to store a BigDecimal without precision loss db.execSQL("create table " + LoyaltyCardDbIds.TABLE + "(" + LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," + LoyaltyCardDbIds.STORE + " TEXT not null," + LoyaltyCardDbIds.NOTE + " TEXT not null," + LoyaltyCardDbIds.EXPIRY + " INTEGER," + + LoyaltyCardDbIds.BALANCE + " TEXT not null DEFAULT '0'," + + LoyaltyCardDbIds.BALANCE_TYPE + " TEXT," + LoyaltyCardDbIds.HEADER_COLOR + " INTEGER," + LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER," + LoyaltyCardDbIds.CARD_ID + " TEXT not null," + @@ -128,9 +135,18 @@ public class DBHelper extends SQLiteOpenHelper db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE + " ADD COLUMN " + LoyaltyCardDbIds.EXPIRY + " INTEGER"); } + + if(oldVersion < 8 && newVersion >= 8) + { + db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE + + " ADD COLUMN " + LoyaltyCardDbIds.BALANCE + " TEXT not null DEFAULT '0'"); + db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE + + " ADD COLUMN " + LoyaltyCardDbIds.BALANCE_TYPE + " TEXT"); + } } public long insertLoyaltyCard(final String store, final String note, final Date expiry, + final BigDecimal balance, final Currency balanceType, final String cardId, final String barcodeType, final Integer headerColor, final int starStatus) { @@ -139,6 +155,8 @@ public class DBHelper extends SQLiteOpenHelper contentValues.put(LoyaltyCardDbIds.STORE, store); contentValues.put(LoyaltyCardDbIds.NOTE, note); contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null); + contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString()); + contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null); contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId); contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType); contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor); @@ -148,8 +166,30 @@ public class DBHelper extends SQLiteOpenHelper return newId; } + public boolean insertLoyaltyCard(final SQLiteDatabase db, final String store, + final String note, final Date expiry, final BigDecimal balance, + final Currency balanceType, final String cardId, + final String barcodeType, final Integer headerColor, + final int starStatus) + { + ContentValues contentValues = new ContentValues(); + contentValues.put(LoyaltyCardDbIds.STORE, store); + contentValues.put(LoyaltyCardDbIds.NOTE, note); + contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null); + contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString()); + contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null); + contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId); + contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType); + contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor); + contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, Color.WHITE); + contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus); + final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues); + return (newId != -1); + } + public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id, final String store, - final String note, final Date expiry, final String cardId, + final String note, final Date expiry, final BigDecimal balance, + final Currency balanceType, final String cardId, final String barcodeType, final Integer headerColor, final int starStatus) { @@ -158,6 +198,8 @@ public class DBHelper extends SQLiteOpenHelper contentValues.put(LoyaltyCardDbIds.STORE, store); contentValues.put(LoyaltyCardDbIds.NOTE, note); contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null); + contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString()); + contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null); contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId); contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType); contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor); @@ -168,7 +210,8 @@ public class DBHelper extends SQLiteOpenHelper } public boolean updateLoyaltyCard(final int id, final String store, final String note, - final Date expiry, final String cardId, + final Date expiry, final BigDecimal balance, + final Currency balanceType, final String cardId, final String barcodeType, final Integer headerColor) { SQLiteDatabase db = getWritableDatabase(); @@ -176,6 +219,8 @@ public class DBHelper extends SQLiteOpenHelper contentValues.put(LoyaltyCardDbIds.STORE, store); contentValues.put(LoyaltyCardDbIds.NOTE, note); contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null); + contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString()); + contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null); contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId); contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType); contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor); diff --git a/app/src/main/java/protect/card_locker/DataFormat.java b/app/src/main/java/protect/card_locker/DataFormat.java index 722700f00..c030653a7 100644 --- a/app/src/main/java/protect/card_locker/DataFormat.java +++ b/app/src/main/java/protect/card_locker/DataFormat.java @@ -2,7 +2,8 @@ package protect.card_locker; public enum DataFormat { - CSV, - + Catima, + Fidme, + VoucherVault ; } diff --git a/app/src/main/java/protect/card_locker/DatabaseImporter.java b/app/src/main/java/protect/card_locker/DatabaseImporter.java deleted file mode 100644 index 33d2797bf..000000000 --- a/app/src/main/java/protect/card_locker/DatabaseImporter.java +++ /dev/null @@ -1,19 +0,0 @@ -package protect.card_locker; - -import java.io.IOException; -import java.io.InputStreamReader; - -/** - * Interface for a class which can import the contents of a stream - * into the database. - */ -public interface DatabaseImporter -{ - /** - * Import data from the input stream in a given format into - * the database. - * @throws IOException - * @throws FormatException - */ - void importData(DBHelper db, InputStreamReader input) throws IOException, FormatException, InterruptedException; -} diff --git a/app/src/main/java/protect/card_locker/ImportExportActivity.java b/app/src/main/java/protect/card_locker/ImportExportActivity.java index f5d89238d..97b243b75 100644 --- a/app/src/main/java/protect/card_locker/ImportExportActivity.java +++ b/app/src/main/java/protect/card_locker/ImportExportActivity.java @@ -16,6 +16,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; +import android.provider.ContactsContract; import android.util.Log; import android.view.MenuItem; import android.view.View; @@ -28,6 +29,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; +import java.text.DateFormat; public class ImportExportActivity extends AppCompatActivity { @@ -35,10 +37,14 @@ public class ImportExportActivity extends AppCompatActivity private static final int PERMISSIONS_EXTERNAL_STORAGE = 1; private static final int CHOOSE_EXPORT_LOCATION = 2; - private static final int CHOOSE_EXPORTED_FILE = 3; + private static final int IMPORT = 3; private ImportExportTask importExporter; + private String importAlertTitle; + private String importAlertMessage; + private DataFormat importDataFormat; + @Override protected void onCreate(Bundle savedInstanceState) { @@ -93,7 +99,7 @@ public class ImportExportActivity extends AppCompatActivity @Override public void onClick(View v) { - chooseFileWithIntent(intentGetContentAction, CHOOSE_EXPORTED_FILE); + chooseImportType(intentGetContentAction); } }); @@ -106,12 +112,60 @@ public class ImportExportActivity extends AppCompatActivity @Override public void onClick(View v) { - chooseFileWithIntent(intentPickAction, CHOOSE_EXPORTED_FILE); + chooseImportType(intentPickAction); } }); } - private void startImport(final InputStream target, final Uri targetUri) + private void chooseImportType(Intent baseIntent) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.chooseImportType) + .setItems(R.array.import_types_array, (dialog, which) -> { + switch (which) { + // Catima + case 0: + importAlertTitle = getString(R.string.importCatima); + importAlertMessage = getString(R.string.importCatimaMessage); + importDataFormat = DataFormat.Catima; + break; + // Fidme + case 1: + importAlertTitle = getString(R.string.importFidme); + importAlertMessage = getString(R.string.importFidmeMessage); + importDataFormat = DataFormat.Fidme; + break; + // Loyalty Card Keychain + case 2: + importAlertTitle = getString(R.string.importLoyaltyCardKeychain); + importAlertMessage = getString(R.string.importLoyaltyCardKeychainMessage); + importDataFormat = DataFormat.Catima; + break; + // Voucher Vault + case 3: + importAlertTitle = getString(R.string.importVoucherVault); + importAlertMessage = getString(R.string.importVoucherVaultMessage); + importDataFormat = DataFormat.VoucherVault; + break; + default: + throw new IllegalArgumentException("Unknown DataFormat"); + } + + new AlertDialog.Builder(this) + .setTitle(importAlertTitle) + .setMessage(importAlertMessage) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + chooseFileWithIntent(baseIntent, IMPORT); + } + }) + .setNegativeButton(R.string.cancel, null) + .show(); + }); + builder.show(); + } + + private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat) { ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() { @@ -123,7 +177,7 @@ public class ImportExportActivity extends AppCompatActivity }; importExporter = new ImportExportTask(ImportExportActivity.this, - DataFormat.CSV, target, listener); + dataFormat, target, listener); importExporter.execute(); } @@ -139,7 +193,7 @@ public class ImportExportActivity extends AppCompatActivity }; importExporter = new ImportExportTask(ImportExportActivity.this, - DataFormat.CSV, target, listener); + DataFormat.Catima, target, listener); importExporter.execute(); } @@ -294,7 +348,7 @@ public class ImportExportActivity extends AppCompatActivity { super.onActivityResult(requestCode, resultCode, data); - if (resultCode != RESULT_OK || (requestCode != CHOOSE_EXPORT_LOCATION && requestCode != CHOOSE_EXPORTED_FILE)) + if (resultCode != RESULT_OK) { Log.w(TAG, "Failed onActivityResult(), result=" + resultCode); return; @@ -336,8 +390,9 @@ public class ImportExportActivity extends AppCompatActivity reader = new FileInputStream(new File(uri.toString())); } - Log.e(TAG, "Starting file export with: " + uri.toString()); - startImport(reader, uri); + Log.e(TAG, "Starting file import with: " + uri.toString()); + + startImport(reader, uri, importDataFormat); } } catch(FileNotFoundException e) diff --git a/app/src/main/java/protect/card_locker/ImportExportTask.java b/app/src/main/java/protect/card_locker/ImportExportTask.java index 8537fbe99..9ea99c627 100644 --- a/app/src/main/java/protect/card_locker/ImportExportTask.java +++ b/app/src/main/java/protect/card_locker/ImportExportTask.java @@ -13,6 +13,9 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.Charset; +import protect.card_locker.importexport.MultiFormatExporter; +import protect.card_locker.importexport.MultiFormatImporter; + class ImportExportTask extends AsyncTask { private static final String TAG = "Catima"; @@ -58,16 +61,8 @@ class ImportExportTask extends AsyncTask { boolean result = false; - try - { - InputStreamReader reader = new InputStreamReader(stream, Charset.forName("UTF-8")); - result = MultiFormatImporter.importData(db, reader, format); - reader.close(); - } - catch(IOException e) - { - Log.e(TAG, "Unable to import file", e); - } + + result = MultiFormatImporter.importData(db, stream, format); Log.i(TAG, "Import result: " + result); diff --git a/app/src/main/java/protect/card_locker/ImportURIHelper.java b/app/src/main/java/protect/card_locker/ImportURIHelper.java index ba77d83cf..54add450f 100644 --- a/app/src/main/java/protect/card_locker/ImportURIHelper.java +++ b/app/src/main/java/protect/card_locker/ImportURIHelper.java @@ -4,12 +4,16 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import java.io.InvalidObjectException; +import java.math.BigDecimal; +import java.util.Currency; import java.util.Date; public class ImportURIHelper { private static final String STORE = DBHelper.LoyaltyCardDbIds.STORE; private static final String NOTE = DBHelper.LoyaltyCardDbIds.NOTE; private static final String EXPIRY = DBHelper.LoyaltyCardDbIds.EXPIRY; + private static final String BALANCE = DBHelper.LoyaltyCardDbIds.BALANCE; + private static final String BALANCE_TYPE = DBHelper.LoyaltyCardDbIds.BALANCE_TYPE; private static final String CARD_ID = DBHelper.LoyaltyCardDbIds.CARD_ID; private static final String BARCODE_TYPE = DBHelper.LoyaltyCardDbIds.BARCODE_TYPE; @@ -43,6 +47,8 @@ public class ImportURIHelper { try { // These values are allowed to be null Date expiry = null; + BigDecimal balance = new BigDecimal("0"); + Currency balanceType = null; Integer headerColor = null; Integer headerTextColor = null; @@ -52,18 +58,29 @@ public class ImportURIHelper { String barcodeType = uri.getQueryParameter(BARCODE_TYPE); if (store == null || note == null || cardId == null || barcodeType == null) throw new InvalidObjectException("Not a valid import URI"); + String unparsedBalance = uri.getQueryParameter(BALANCE); + if(unparsedBalance != null && !unparsedBalance.equals("")) + { + balance = new BigDecimal(unparsedBalance); + } + String unparsedBalanceType = uri.getQueryParameter(BALANCE_TYPE); + if (unparsedBalanceType != null && !unparsedBalanceType.equals("")) + { + balanceType = Currency.getInstance(unparsedBalanceType); + } String unparsedExpiry = uri.getQueryParameter(EXPIRY); if(unparsedExpiry != null && !unparsedExpiry.equals("")) { expiry = new Date(Long.parseLong(unparsedExpiry)); } + String unparsedHeaderColor = uri.getQueryParameter(HEADER_COLOR); if(unparsedHeaderColor != null) { headerColor = Integer.parseInt(unparsedHeaderColor); } - return new LoyaltyCard(-1, store, note, expiry, cardId, barcodeType, headerColor, headerTextColor, 0); + return new LoyaltyCard(-1, store, note, expiry, balance, balanceType, cardId, barcodeType, headerColor, headerTextColor, 0); } catch (NullPointerException | NumberFormatException ex) { throw new InvalidObjectException("Not a valid import URI"); } @@ -77,6 +94,10 @@ public class ImportURIHelper { uriBuilder.path(path); uriBuilder.appendQueryParameter(STORE, loyaltyCard.store); uriBuilder.appendQueryParameter(NOTE, loyaltyCard.note); + uriBuilder.appendQueryParameter(BALANCE, loyaltyCard.balance.toString()); + if (loyaltyCard.balanceType != null) { + uriBuilder.appendQueryParameter(BALANCE_TYPE, loyaltyCard.balanceType.getCurrencyCode()); + } if (loyaltyCard.expiry != null) { uriBuilder.appendQueryParameter(EXPIRY, String.valueOf(loyaltyCard.expiry.getTime())); } diff --git a/app/src/main/java/protect/card_locker/LoyaltyCard.java b/app/src/main/java/protect/card_locker/LoyaltyCard.java index b8dcaf1d8..64461a0e1 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCard.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCard.java @@ -2,7 +2,8 @@ package protect.card_locker; import android.database.Cursor; -import java.text.DateFormat; +import java.math.BigDecimal; +import java.util.Currency; import java.util.Date; import androidx.annotation.Nullable; @@ -13,6 +14,8 @@ public class LoyaltyCard public final String store; public final String note; public final Date expiry; + public final BigDecimal balance; + public final Currency balanceType; public final String cardId; public final String barcodeType; @@ -24,7 +27,8 @@ public class LoyaltyCard public final int starStatus; - public LoyaltyCard(final int id, final String store, final String note, final Date expiry, final String cardId, + public LoyaltyCard(final int id, final String store, final String note, final Date expiry, + final BigDecimal balance, final Currency balanceType, final String cardId, final String barcodeType, final Integer headerColor, final Integer headerTextColor, final int starStatus) { @@ -32,6 +36,8 @@ public class LoyaltyCard this.store = store; this.note = note; this.expiry = expiry; + this.balance = balance; + this.balanceType = balanceType; this.cardId = cardId; this.barcodeType = barcodeType; this.headerColor = headerColor; @@ -45,17 +51,25 @@ public class LoyaltyCard String store = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE)); String note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE)); long expiryLong = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.EXPIRY)); + BigDecimal balance = new BigDecimal(cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE))); String cardId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID)); String barcodeType = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE)); int starred = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS)); + int balanceTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE); int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR); int headerTextColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR); + Currency balanceType = null; Date expiry = null; Integer headerColor = null; Integer headerTextColor = null; + if (cursor.isNull(balanceTypeColumn) == false) + { + balanceType = Currency.getInstance(cursor.getString(balanceTypeColumn)); + } + if(expiryLong > 0) { expiry = new Date(expiryLong); @@ -71,6 +85,6 @@ public class LoyaltyCard headerTextColor = cursor.getInt(headerTextColorColumn); } - return new LoyaltyCard(id, store, note, expiry, cardId, barcodeType, headerColor, headerTextColor, starred); + return new LoyaltyCard(id, store, note, expiry, balance, balanceType, cardId, barcodeType, headerColor, headerTextColor, starred); } } diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java b/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java index fa7568547..e20a03769 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java @@ -9,6 +9,7 @@ import android.widget.CursorAdapter; import android.widget.ImageView; import android.widget.TextView; +import java.math.BigDecimal; import java.text.DateFormat; import java.util.Date; @@ -41,6 +42,7 @@ class LoyaltyCardCursorAdapter extends CursorAdapter ImageView thumbnail = view.findViewById(R.id.thumbnail); TextView storeField = view.findViewById(R.id.store); TextView noteField = view.findViewById(R.id.note); + TextView balanceField = view.findViewById(R.id.balance); TextView expiryField = view.findViewById(R.id.expiry); ImageView star = view.findViewById(R.id.star); @@ -63,6 +65,15 @@ class LoyaltyCardCursorAdapter extends CursorAdapter noteField.setVisibility(View.GONE); } + if(!loyaltyCard.balance.equals(new BigDecimal("0"))) { + balanceField.setVisibility(View.VISIBLE); + balanceField.setText(context.getString(R.string.balanceSentence, Utils.formatBalance(context, loyaltyCard.balance, loyaltyCard.balanceType))); + } + else + { + balanceField.setVisibility(View.GONE); + } + if(loyaltyCard.expiry != null) { expiryField.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java index 8eaa7f74c..095b859d4 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java @@ -1,5 +1,6 @@ package protect.card_locker; +import android.annotation.SuppressLint; import android.app.DatePickerDialog; import android.app.Dialog; import android.content.DialogInterface; @@ -8,6 +9,7 @@ import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Color; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import com.google.android.material.chip.Chip; @@ -20,6 +22,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.DialogFragment; +import android.os.LocaleList; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; @@ -31,26 +34,29 @@ import android.view.ViewTreeObserver; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.Button; -import android.widget.CalendarView; import android.widget.DatePicker; import android.widget.EditText; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.google.android.material.tabs.TabLayout; -import com.google.android.material.textfield.TextInputLayout; import com.google.zxing.BarcodeFormat; import com.jaredrummler.android.colorpicker.ColorPickerDialog; import com.jaredrummler.android.colorpicker.ColorPickerDialogListener; import java.io.InvalidObjectException; +import java.math.BigDecimal; import java.text.DateFormat; +import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; +import java.util.Currency; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Locale; public class LoyaltyCardEditActivity extends AppCompatActivity { @@ -63,7 +69,8 @@ public class LoyaltyCardEditActivity extends AppCompatActivity EditText noteFieldEdit; ChipGroup groupsChips; AutoCompleteTextView expiryField; - View cardAndBarcodeLayout; + EditText balanceField; + AutoCompleteTextView balanceCurrencyField; TextView cardIdFieldView; AutoCompleteTextView barcodeTypeField; ImageView barcodeImage; @@ -89,6 +96,10 @@ public class LoyaltyCardEditActivity extends AppCompatActivity boolean initDone = false; AlertDialog confirmExitDialog = null; + boolean validBalance = true; + + HashMap currencies = new HashMap<>(); + private void extractIntentFields(Intent intent) { final Bundle b = intent.getExtras(); @@ -123,13 +134,18 @@ public class LoyaltyCardEditActivity extends AppCompatActivity db = new DBHelper(this); importUriHelper = new ImportURIHelper(this); + for (Currency currency : Currency.getAvailableCurrencies()) { + currencies.put(currency.getSymbol(), currency); + } + tabs = findViewById(R.id.tabs); thumbnail = findViewById(R.id.thumbnail); storeFieldEdit = findViewById(R.id.storeNameEdit); noteFieldEdit = findViewById(R.id.noteEdit); groupsChips = findViewById(R.id.groupChips); expiryField = findViewById(R.id.expiryField); - cardAndBarcodeLayout = findViewById(R.id.cardAndBarcodeLayout); + balanceField = findViewById(R.id.balanceField); + balanceCurrencyField = findViewById(R.id.balanceCurrencyField); cardIdFieldView = findViewById(R.id.cardIdView); barcodeTypeField = findViewById(R.id.barcodeTypeField); barcodeImage = findViewById(R.id.barcode); @@ -186,6 +202,98 @@ public class LoyaltyCardEditActivity extends AppCompatActivity } }); + balanceField.setOnFocusChangeListener((v, hasFocus) -> { + if (!hasFocus) { + balanceField.setText(Utils.formatBalanceWithoutCurrencySymbol((BigDecimal) balanceField.getTag(), (Currency) balanceCurrencyField.getTag())); + } + }); + + balanceField.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + hasChanged = true; + + try { + BigDecimal balance = Utils.parseCurrency(s.toString(), Utils.currencyHasDecimals((Currency) balanceCurrencyField.getTag())); + validBalance = true; + + balanceField.setTag(balance); + } catch (NumberFormatException e) { + validBalance = false; + e.printStackTrace(); + } + } + + @Override + public void afterTextChanged(Editable s) { } + }); + + balanceCurrencyField.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + hasChanged = true; + + Currency currency; + + if (s.toString().equals(getString(R.string.points))) { + currency = null; + } else { + currency = currencies.get(s.toString()); + } + + balanceCurrencyField.setTag(currency); + + BigDecimal balance = (BigDecimal) balanceField.getTag(); + + if (balance != null) { + balanceField.setText(Utils.formatBalanceWithoutCurrencySymbol(balance, currency)); + } + } + + @Override + public void afterTextChanged(Editable s) { + ArrayList currencyList = new ArrayList<>(currencies.keySet()); + Collections.sort(currencyList, (o1, o2) -> { + boolean o1ascii = o1.matches("^[^a-zA-Z]*$"); + boolean o2ascii = o2.matches("^[^a-zA-Z]*$"); + + if (!o1ascii && o2ascii) { + return 1; + } else if (o1ascii && !o2ascii) { + return -1; + } + + return o1.compareTo(o2); + }); + + // Sort locale currencies on top + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + LocaleList locales = getApplicationContext().getResources().getConfiguration().getLocales(); + + for (int i = locales.size() - 1; i > 0; i--) { + Locale locale = locales.get(i); + String currencySymbol = Currency.getInstance(locale).getSymbol(); + currencyList.remove(currencySymbol); + currencyList.add(0, currencySymbol); + } + } else { + String currencySymbol = Currency.getInstance(getApplicationContext().getResources().getConfiguration().locale).getSymbol(); + currencyList.remove(currencySymbol); + currencyList.add(0, currencySymbol); + } + + currencyList.add(0, getString(R.string.points)); + ArrayAdapter currencyAdapter = new ArrayAdapter<>(LoyaltyCardEditActivity.this, android.R.layout.select_dialog_item, currencyList); + balanceCurrencyField.setAdapter(currencyAdapter); + } + }); + cardIdFieldView.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @@ -265,10 +373,15 @@ public class LoyaltyCardEditActivity extends AppCompatActivity noteFieldEdit.setText(""); expiryField.setTag(null); expiryField.setText(""); + balanceCurrencyField.setTag(null); + balanceCurrencyField.setText(""); + balanceField.setTag(null); + balanceField.setText(""); cardIdFieldView.setText(""); barcodeTypeField.setText(""); } + @SuppressLint("DefaultLocale") @Override public void onResume() { @@ -300,11 +413,19 @@ public class LoyaltyCardEditActivity extends AppCompatActivity if(expiryField.getText().length() == 0) { expiryField.setTag(loyaltyCard.expiry); - if (loyaltyCard.expiry == null) { - expiryField.setText(getString(R.string.never)); - } else { - expiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.expiry)); - } + formatExpiryField(loyaltyCard.expiry); + } + + if(balanceCurrencyField.getText().length() == 0) + { + balanceCurrencyField.setTag(loyaltyCard.balanceType); + formatBalanceCurrencyField(loyaltyCard.balanceType); + } + + if(balanceField.getText().length() == 0) + { + balanceField.setTag(loyaltyCard.balance); + balanceField.setText(Utils.formatBalanceWithoutCurrencySymbol(loyaltyCard.balance, loyaltyCard.balanceType)); } if(cardIdFieldView.getText().length() == 0) @@ -343,11 +464,10 @@ public class LoyaltyCardEditActivity extends AppCompatActivity storeFieldEdit.setText(importCard.store); noteFieldEdit.setText(importCard.note); expiryField.setTag(importCard.expiry); - if (importCard.expiry != null) { - expiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(importCard.expiry)); - } else { - expiryField.setText(R.string.never); - } + formatExpiryField(importCard.expiry); + balanceField.setTag(importCard.balance); + balanceCurrencyField.setTag(importCard.balanceType); + formatBalanceCurrencyField(importCard.balanceType); cardIdFieldView.setText(importCard.cardId); barcodeTypeField.setText(importCard.barcodeType); headingColorValue = importCard.headerColor; @@ -357,6 +477,9 @@ public class LoyaltyCardEditActivity extends AppCompatActivity setTitle(R.string.addCardTitle); expiryField.setTag(null); expiryField.setText(getString(R.string.never)); + balanceField.setTag(new BigDecimal("0")); + balanceCurrencyField.setTag(null); + formatBalanceCurrencyField(null); hideBarcode(); } @@ -384,13 +507,10 @@ public class LoyaltyCardEditActivity extends AppCompatActivity break; } } - chip.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - hasChanged = true; + chip.setOnTouchListener((v, event) -> { + hasChanged = true; - return false; - } + return false; }); groupsChips.addView(chip); @@ -449,16 +569,27 @@ public class LoyaltyCardEditActivity extends AppCompatActivity barcodeTypeField.setAdapter(barcodeAdapter); FloatingActionButton saveButton = findViewById(R.id.fabSave); - saveButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - doSave(); - } - }); + saveButton.setOnClickListener(v -> doSave()); generateIcon(storeFieldEdit.getText().toString()); } + private void formatExpiryField(Date expiry) { + if (expiry == null) { + expiryField.setText(getString(R.string.never)); + } else { + expiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(expiry)); + } + } + + private void formatBalanceCurrencyField(Currency balanceType) { + if (balanceType == null) { + balanceCurrencyField.setText(getString(R.string.points)); + } else { + balanceCurrencyField.setText(balanceType.getSymbol()); + } + } + @Override public void onBackPressed() { askBeforeQuitIfChanged(); @@ -588,12 +719,13 @@ public class LoyaltyCardEditActivity extends AppCompatActivity } } - private void doSave() { String store = storeFieldEdit.getText().toString(); String note = noteFieldEdit.getText().toString(); Date expiry = (Date) expiryField.getTag(); + BigDecimal balance = (BigDecimal) balanceField.getTag(); + Currency balanceType = balanceCurrencyField.getTag() != null ? ((Currency) balanceCurrencyField.getTag()) : null; String cardId = cardIdFieldView.getText().toString(); String barcodeType = barcodeTypeField.getText().toString(); @@ -616,6 +748,12 @@ public class LoyaltyCardEditActivity extends AppCompatActivity return; } + if(!validBalance) + { + Snackbar.make(balanceField, getString(R.string.parsingBalanceFailed, balanceField.getText().toString()), Snackbar.LENGTH_LONG).show(); + return; + } + List selectedGroups = new ArrayList<>(); for (Integer chipId : groupsChips.getCheckedChipIds()) { @@ -625,12 +763,12 @@ public class LoyaltyCardEditActivity extends AppCompatActivity if(updateLoyaltyCard) { //update of "starStatus" not necessary, since it cannot be changed in this activity (only in ViewActivity) - db.updateLoyaltyCard(loyaltyCardId, store, note, expiry, cardId, barcodeType, headingColorValue); + db.updateLoyaltyCard(loyaltyCardId, store, note, expiry, balance, balanceType, cardId, barcodeType, headingColorValue); Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId); } else { - loyaltyCardId = (int)db.insertLoyaltyCard(store, note, expiry, cardId, barcodeType, headingColorValue, 0); + loyaltyCardId = (int)db.insertLoyaltyCard(store, note, expiry, balance, balanceType, cardId, barcodeType, headingColorValue, 0); } db.setLoyaltyCardGroups(loyaltyCardId, selectedGroups); diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java index 3d103c5f5..6ae355190 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java @@ -39,6 +39,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.zxing.BarcodeFormat; +import java.math.BigDecimal; import java.text.DateFormat; import java.util.List; @@ -54,6 +55,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity ImageView bottomSheetButton; TextView noteView; TextView groupsView; + TextView balanceView; TextView expiryView; TextView storeName; ImageButton maximizeButton; @@ -123,6 +125,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity bottomSheetButton = findViewById(R.id.bottomSheetButton); noteView = findViewById(R.id.noteView); groupsView = findViewById(R.id.groupsView); + balanceView = findViewById(R.id.balanceView); expiryView = findViewById(R.id.expiryView); storeName = findViewById(R.id.storeName); maximizeButton = findViewById(R.id.maximizeButton); @@ -233,10 +236,24 @@ public class LoyaltyCardViewActivity extends AppCompatActivity // '1' is the brightest. We attempt to maximize the brightness // to help barcode readers scan the barcode. Window window = getWindow(); - if(window != null && settings.useMaxBrightnessDisplayingBarcode()) + if(window != null) { WindowManager.LayoutParams attributes = window.getAttributes(); - attributes.screenBrightness = 1F; + + if (settings.useMaxBrightnessDisplayingBarcode()) + { + attributes.screenBrightness = 1F; + } + + if (settings.getKeepScreenOn()) { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + if (settings.getDisableLockscreenWhileViewingCard()) { + window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD| + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + window.setAttributes(attributes); } @@ -287,6 +304,15 @@ public class LoyaltyCardViewActivity extends AppCompatActivity groupsView.setVisibility(View.GONE); } + if(!loyaltyCard.balance.equals(new BigDecimal(0))) { + balanceView.setVisibility(View.VISIBLE); + balanceView.setText(getString(R.string.balanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType))); + } + else + { + balanceView.setVisibility(View.GONE); + } + if(loyaltyCard.expiry != null) { expiryView.setVisibility(View.VISIBLE); @@ -523,7 +549,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity private void makeBottomSheetVisibleIfUseful() { - if (noteView.getVisibility() == View.VISIBLE || groupsView.getVisibility() == View.VISIBLE || expiryView.getVisibility() == View.VISIBLE) { + if (noteView.getVisibility() == View.VISIBLE || groupsView.getVisibility() == View.VISIBLE || balanceView.getVisibility() == View.VISIBLE || expiryView.getVisibility() == View.VISIBLE) { bottomSheet.setVisibility(View.VISIBLE); } else diff --git a/app/src/main/java/protect/card_locker/MainActivity.java b/app/src/main/java/protect/card_locker/MainActivity.java index 407c8bc15..2f5535903 100644 --- a/app/src/main/java/protect/card_locker/MainActivity.java +++ b/app/src/main/java/protect/card_locker/MainActivity.java @@ -1,13 +1,16 @@ package protect.card_locker; +import android.app.AlertDialog; import android.app.SearchManager; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.SearchView; @@ -92,6 +95,29 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O helpText.setOnTouchListener(gestureTouchListener); noMatchingCardsText.setOnTouchListener(gestureTouchListener); list.setOnTouchListener(gestureTouchListener); + + // Show privacy policy on first run + SharedPreferences privacyPolicyShownPref = getApplicationContext().getSharedPreferences( + getString(R.string.sharedpreference_privacy_policy_shown), + Context.MODE_PRIVATE); + + if (privacyPolicyShownPref.getInt(getString(R.string.sharedpreference_privacy_policy_shown), 0) == 0) { + SharedPreferences.Editor privacyPolicyShownPrefEditor = privacyPolicyShownPref.edit(); + privacyPolicyShownPrefEditor.putInt(getString(R.string.sharedpreference_privacy_policy_shown), 1); + privacyPolicyShownPrefEditor.apply(); + + new AlertDialog.Builder(this) + .setTitle(R.string.privacy_policy) + .setMessage(R.string.privacy_policy_popup_text) + .setPositiveButton(R.string.accept, null) + .setNegativeButton(R.string.privacy_policy, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + openPrivacyPolicy(); + } + }) + .setIcon(android.R.drawable.ic_dialog_info) + .show(); + }; } @Override @@ -290,6 +316,14 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O groupsTabLayout.setVisibility(View.VISIBLE); } + private void openPrivacyPolicy() { + Intent browserIntent = new Intent( + Intent.ACTION_VIEW, + Uri.parse("https://thelastproject.github.io/Catima/privacy-policy") + ); + startActivity(browserIntent); + } + @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { @@ -402,6 +436,12 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O return true; } + if(id == R.id.action_privacy_policy) + { + openPrivacyPolicy(); + return true; + } + if(id == R.id.action_about) { Intent i = new Intent(getApplicationContext(), AboutActivity.class); diff --git a/app/src/main/java/protect/card_locker/Utils.java b/app/src/main/java/protect/card_locker/Utils.java index 5a016170c..e497dab33 100644 --- a/app/src/main/java/protect/card_locker/Utils.java +++ b/app/src/main/java/protect/card_locker/Utils.java @@ -10,7 +10,10 @@ import android.util.Log; import android.widget.Toast; import java.io.IOException; +import java.math.BigDecimal; +import java.text.NumberFormat; import java.util.Calendar; +import java.util.Currency; import java.util.Date; import java.util.GregorianCalendar; @@ -122,4 +125,62 @@ public class Utils { return expiryDate.before(date.getTime()); } + + static public String formatBalance(Context context, BigDecimal value, Currency currency) { + NumberFormat numberFormat = NumberFormat.getInstance(); + + if (currency == null) { + numberFormat.setMaximumFractionDigits(0); + return context.getString(R.string.balancePoints, numberFormat.format(value)); + } + + NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(); + currencyFormat.setCurrency(currency); + currencyFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits()); + currencyFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits()); + + return currencyFormat.format(value); + } + + static public String formatBalanceWithoutCurrencySymbol(BigDecimal value, Currency currency) { + NumberFormat numberFormat = NumberFormat.getInstance(); + + if (currency == null) { + numberFormat.setMaximumFractionDigits(0); + return numberFormat.format(value); + } + + numberFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits()); + numberFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits()); + + return numberFormat.format(value); + } + + static public Boolean currencyHasDecimals(Currency currency) { + if (currency == null) { + return false; + } + + return currency.getDefaultFractionDigits() != 0; + } + + static public BigDecimal parseCurrency(String value, Boolean hasDecimals) throws NumberFormatException { + // If there are no decimals expected, remove all separators before parsing + if (!hasDecimals) { + value = value.replaceAll("[^0-9]", ""); + return new BigDecimal(value); + } + + // There are many ways users can write a currency, so we fix it up a bit + // 1. Replace all non-numbers with dots + value = value.replaceAll("[^0-9]", "."); + + // 2. Remove all but the last dot + while (value.split("\\.").length > 2) { + value = value.replaceFirst("\\.", ""); + } + + // Parse as BigDecimal + return new BigDecimal(value); + } } diff --git a/app/src/main/java/protect/card_locker/importexport/CSVHelpers.java b/app/src/main/java/protect/card_locker/importexport/CSVHelpers.java new file mode 100644 index 000000000..74dd8f94e --- /dev/null +++ b/app/src/main/java/protect/card_locker/importexport/CSVHelpers.java @@ -0,0 +1,93 @@ +package protect.card_locker.importexport; + +import org.apache.commons.csv.CSVRecord; + +import protect.card_locker.FormatException; + +public class CSVHelpers { + /** + * Extract a string from the items array. The index into the array + * is determined by looking up the index in the fields map using the + * "key" as the key. If no such key exists, defaultValue is returned + * if it is not null. Otherwise, a FormatException is thrown. + */ + static String extractString(String key, CSVRecord record, String defaultValue) + throws FormatException + { + String toReturn = defaultValue; + + if(record.isMapped(key)) + { + toReturn = record.get(key); + } + else + { + if(defaultValue == null) + { + throw new FormatException("Field not used but expected: " + key); + } + } + + return toReturn; + } + + /** + * Extract an integer from the items array. The index into the array + * is determined by looking up the index in the fields map using the + * "key" as the key. If no such key exists, or the data is not a valid + * int, a FormatException is thrown. + */ + static Integer extractInt(String key, CSVRecord record, boolean nullIsOk) + throws FormatException + { + if(record.isMapped(key) == false) + { + throw new FormatException("Field not used but expected: " + key); + } + + String value = record.get(key); + if(value.isEmpty() && nullIsOk) + { + return null; + } + + try + { + return Integer.parseInt(record.get(key)); + } + catch(NumberFormatException e) + { + throw new FormatException("Failed to parse field: " + key, e); + } + } + + /** + * Extract a long from the items array. The index into the array + * is determined by looking up the index in the fields map using the + * "key" as the key. If no such key exists, or the data is not a valid + * int, a FormatException is thrown. + */ + static Long extractLong(String key, CSVRecord record, boolean nullIsOk) + throws FormatException + { + if(record.isMapped(key) == false) + { + throw new FormatException("Field not used but expected: " + key); + } + + String value = record.get(key); + if(value.isEmpty() && nullIsOk) + { + return null; + } + + try + { + return Long.parseLong(record.get(key)); + } + catch(NumberFormatException e) + { + throw new FormatException("Failed to parse field: " + key, e); + } + } +} diff --git a/app/src/main/java/protect/card_locker/CsvDatabaseExporter.java b/app/src/main/java/protect/card_locker/importexport/CsvDatabaseExporter.java similarity index 90% rename from app/src/main/java/protect/card_locker/CsvDatabaseExporter.java rename to app/src/main/java/protect/card_locker/importexport/CsvDatabaseExporter.java index a4c8fdb28..0d58f4403 100644 --- a/app/src/main/java/protect/card_locker/CsvDatabaseExporter.java +++ b/app/src/main/java/protect/card_locker/importexport/CsvDatabaseExporter.java @@ -1,4 +1,4 @@ -package protect.card_locker; +package protect.card_locker.importexport; import android.database.Cursor; @@ -8,6 +8,10 @@ import org.apache.commons.csv.CSVPrinter; import java.io.IOException; import java.io.OutputStreamWriter; +import protect.card_locker.DBHelper; +import protect.card_locker.Group; +import protect.card_locker.LoyaltyCard; + /** * Class for exporting the database into CSV (Comma Separate Values) * format. @@ -50,6 +54,8 @@ public class CsvDatabaseExporter implements DatabaseExporter DBHelper.LoyaltyCardDbIds.STORE, DBHelper.LoyaltyCardDbIds.NOTE, DBHelper.LoyaltyCardDbIds.EXPIRY, + DBHelper.LoyaltyCardDbIds.BALANCE, + DBHelper.LoyaltyCardDbIds.BALANCE_TYPE, DBHelper.LoyaltyCardDbIds.CARD_ID, DBHelper.LoyaltyCardDbIds.HEADER_COLOR, DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, @@ -65,6 +71,8 @@ public class CsvDatabaseExporter implements DatabaseExporter card.store, card.note, card.expiry != null ? card.expiry.getTime() : "", + card.balance, + card.balanceType, card.cardId, card.headerColor, card.barcodeType, diff --git a/app/src/main/java/protect/card_locker/CsvDatabaseImporter.java b/app/src/main/java/protect/card_locker/importexport/CsvDatabaseImporter.java similarity index 68% rename from app/src/main/java/protect/card_locker/CsvDatabaseImporter.java rename to app/src/main/java/protect/card_locker/importexport/CsvDatabaseImporter.java index 5040ddfcd..2054521c7 100644 --- a/app/src/main/java/protect/card_locker/CsvDatabaseImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/CsvDatabaseImporter.java @@ -1,17 +1,31 @@ -package protect.card_locker; +package protect.card_locker.importexport; import android.database.sqlite.SQLiteDatabase; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; +import org.json.JSONException; import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringReader; +import java.math.BigDecimal; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.util.Currency; import java.util.Date; import java.util.List; +import java.util.zip.ZipFile; + +import protect.card_locker.DBHelper; +import protect.card_locker.FormatException; +import protect.card_locker.Group; /** * Class for importing a database from CSV (Comma Separate Values) @@ -22,9 +36,9 @@ import java.util.List; */ public class CsvDatabaseImporter implements DatabaseImporter { - public void importData(DBHelper db, InputStreamReader input) throws IOException, FormatException, InterruptedException + public void importData(DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException { - BufferedReader bufferedReader = new BufferedReader(input); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); bufferedReader.mark(100); @@ -194,92 +208,6 @@ public class CsvDatabaseImporter implements DatabaseImporter } } - /** - * Extract a string from the items array. The index into the array - * is determined by looking up the index in the fields map using the - * "key" as the key. If no such key exists, defaultValue is returned - * if it is not null. Otherwise, a FormatException is thrown. - */ - private String extractString(String key, CSVRecord record, String defaultValue) - throws FormatException - { - String toReturn = defaultValue; - - if(record.isMapped(key)) - { - toReturn = record.get(key); - } - else - { - if(defaultValue == null) - { - throw new FormatException("Field not used but expected: " + key); - } - } - - return toReturn; - } - - /** - * Extract an integer from the items array. The index into the array - * is determined by looking up the index in the fields map using the - * "key" as the key. If no such key exists, or the data is not a valid - * int, a FormatException is thrown. - */ - private Integer extractInt(String key, CSVRecord record, boolean nullIsOk) - throws FormatException - { - if(record.isMapped(key) == false) - { - throw new FormatException("Field not used but expected: " + key); - } - - String value = record.get(key); - if(value.isEmpty() && nullIsOk) - { - return null; - } - - try - { - return Integer.parseInt(record.get(key)); - } - catch(NumberFormatException e) - { - throw new FormatException("Failed to parse field: " + key, e); - } - } - - /** - * Extract a long from the items array. The index into the array - * is determined by looking up the index in the fields map using the - * "key" as the key. If no such key exists, or the data is not a valid - * int, a FormatException is thrown. - */ - private Long extractLong(String key, CSVRecord record, boolean nullIsOk) - throws FormatException - { - if(record.isMapped(key) == false) - { - throw new FormatException("Field not used but expected: " + key); - } - - String value = record.get(key); - if(value.isEmpty() && nullIsOk) - { - return null; - } - - try - { - return Long.parseLong(record.get(key)); - } - catch(NumberFormatException e) - { - throw new FormatException("Failed to parse field: " + key, e); - } - } - /** * Import a single loyalty card into the database using the given * session. @@ -287,44 +215,59 @@ public class CsvDatabaseImporter implements DatabaseImporter private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record) throws IOException, FormatException { - int id = extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false); + int id = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false); - String store = extractString(DBHelper.LoyaltyCardDbIds.STORE, record, ""); + String store = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.STORE, record, ""); if(store.isEmpty()) { throw new FormatException("No store listed, but is required"); } - String note = extractString(DBHelper.LoyaltyCardDbIds.NOTE, record, ""); + String note = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.NOTE, record, ""); Date expiry = null; try { - expiry = new Date(extractLong(DBHelper.LoyaltyCardDbIds.EXPIRY, record, true)); + expiry = new Date(CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.EXPIRY, record, true)); } catch (NullPointerException | FormatException e) { } - String cardId = extractString(DBHelper.LoyaltyCardDbIds.CARD_ID, record, ""); + BigDecimal balance; + try { + balance = new BigDecimal(CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE, record, null)); + } catch (FormatException _e ) { + // These fields did not exist in versions 1.8.1 and before + // We catch this exception so we can still import old backups + balance = new BigDecimal("0"); + } + + Currency balanceType = null; + String unparsedBalanceType = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE, record, ""); + if(!unparsedBalanceType.isEmpty()) { + balanceType = Currency.getInstance(unparsedBalanceType); + } + + String cardId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.CARD_ID, record, ""); if(cardId.isEmpty()) { throw new FormatException("No card ID listed, but is required"); } - String barcodeType = extractString(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, record, ""); + String barcodeType = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, record, ""); Integer headerColor = null; if(record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR)) { - headerColor = extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record, true); + headerColor = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record, true); } int starStatus = 0; try { - starStatus = extractInt(DBHelper.LoyaltyCardDbIds.STAR_STATUS, record, false); + starStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.STAR_STATUS, record, false); } catch (FormatException _e ) { // This field did not exist in versions 0.28 and before // We catch this exception so we can still import old backups } if (starStatus != 1) starStatus = 0; - helper.insertLoyaltyCard(database, id, store, note, expiry, cardId, barcodeType, headerColor, starStatus); + helper.insertLoyaltyCard(database, id, store, note, expiry, balance, balanceType, cardId, barcodeType, headerColor, starStatus); } /** @@ -334,7 +277,7 @@ public class CsvDatabaseImporter implements DatabaseImporter private void importGroup(SQLiteDatabase database, DBHelper helper, CSVRecord record) throws IOException, FormatException { - String id = extractString(DBHelper.LoyaltyCardDbGroups.ID, record, null); + String id = CSVHelpers.extractString(DBHelper.LoyaltyCardDbGroups.ID, record, null); helper.insertGroup(database, id); } @@ -346,8 +289,8 @@ public class CsvDatabaseImporter implements DatabaseImporter private void importCardGroupMapping(SQLiteDatabase database, DBHelper helper, CSVRecord record) throws IOException, FormatException { - Integer cardId = extractInt(DBHelper.LoyaltyCardDbIdsGroups.cardID, record, false); - String groupId = extractString(DBHelper.LoyaltyCardDbIdsGroups.groupID, record, null); + Integer cardId = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIdsGroups.cardID, record, false); + String groupId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIdsGroups.groupID, record, null); List cardGroups = helper.getLoyaltyCardGroups(cardId); cardGroups.add(helper.getGroup(groupId)); diff --git a/app/src/main/java/protect/card_locker/DatabaseExporter.java b/app/src/main/java/protect/card_locker/importexport/DatabaseExporter.java similarity index 83% rename from app/src/main/java/protect/card_locker/DatabaseExporter.java rename to app/src/main/java/protect/card_locker/importexport/DatabaseExporter.java index 94e4949d6..a12a124af 100644 --- a/app/src/main/java/protect/card_locker/DatabaseExporter.java +++ b/app/src/main/java/protect/card_locker/importexport/DatabaseExporter.java @@ -1,8 +1,10 @@ -package protect.card_locker; +package protect.card_locker.importexport; import java.io.IOException; import java.io.OutputStreamWriter; +import protect.card_locker.DBHelper; + /** * Interface for a class which can export the contents of the database * in a given format. diff --git a/app/src/main/java/protect/card_locker/importexport/DatabaseImporter.java b/app/src/main/java/protect/card_locker/importexport/DatabaseImporter.java new file mode 100644 index 000000000..afb320eda --- /dev/null +++ b/app/src/main/java/protect/card_locker/importexport/DatabaseImporter.java @@ -0,0 +1,27 @@ +package protect.card_locker.importexport; + +import org.json.JSONException; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.text.ParseException; + +import protect.card_locker.DBHelper; +import protect.card_locker.FormatException; + +/** + * Interface for a class which can import the contents of a stream + * into the database. + */ +public interface DatabaseImporter +{ + /** + * Import data from the input stream in a given format into + * the database. + * @throws IOException + * @throws FormatException + */ + void importData(DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException, JSONException, ParseException; +} diff --git a/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java b/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java new file mode 100644 index 000000000..1f57cf1fd --- /dev/null +++ b/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java @@ -0,0 +1,147 @@ +package protect.card_locker.importexport; + +import android.annotation.SuppressLint; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Color; + +import com.google.zxing.BarcodeFormat; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Currency; +import java.util.Date; +import java.util.TimeZone; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +import protect.card_locker.DBHelper; +import protect.card_locker.FormatException; + +/** + * Class for importing a database from CSV (Comma Separate Values) + * formatted data. + * + * The database's loyalty cards are expected to appear in the CSV data. + * A header is expected for the each table showing the names of the columns. + */ +public class FidmeImporter implements DatabaseImporter +{ + public void importData(DBHelper db, InputStream input) throws IOException, FormatException, JSONException, ParseException { + // We actually retrieve a .zip file + ZipInputStream zipInputStream = new ZipInputStream(input); + + StringBuilder loyaltyCards = new StringBuilder(); + byte[] buffer = new byte[1024]; + int read = 0; + + ZipEntry zipEntry; + + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + if (zipEntry.getName().equals("loyalty_programs.csv")) { + while ((read = zipInputStream.read(buffer, 0, 1024)) >= 0) { + loyaltyCards.append(new String(buffer, 0, read, StandardCharsets.UTF_8)); + } + } + } + + if (loyaltyCards.length() == 0) { + throw new FormatException("Couldn't find loyalty_programs.csv in zip file or it is empty"); + } + + SQLiteDatabase database = db.getWritableDatabase(); + database.beginTransaction(); + + final CSVParser fidmeParser = new CSVParser(new StringReader(loyaltyCards.toString()), CSVFormat.RFC4180.withDelimiter(';').withHeader()); + + try { + for (CSVRecord record : fidmeParser) { + importLoyaltyCard(database, db, record); + + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + } + } catch (IllegalArgumentException | IllegalStateException | InterruptedException e) { + throw new FormatException("Issue parsing CSV data", e); + } finally { + fidmeParser.close(); + } + + database.setTransactionSuccessful(); + database.endTransaction(); + database.close(); + + zipInputStream.close(); + } + + /** + * Import a single loyalty card into the database using the given + * session. + */ + private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record) + throws IOException, FormatException + { + // A loyalty card export from Fidme contains the following fields: + // Retailer (store name) + // Program (program name) + // Added at (YYYY-MM-DD HH:MM:SS UTC) + // Reference (card ID) + // Firstname (card holder first name) + // Lastname (card holder last name) + + // The store is called Retailer + String store = CSVHelpers.extractString("Retailer", record, ""); + + if (store.isEmpty()) + { + throw new FormatException("No store listed, but is required"); + } + + // There seems to be no note field in the CSV? So let's combine other fields instead... + String program = CSVHelpers.extractString("Program", record, ""); + String addedAt = CSVHelpers.extractString("Added At", record, ""); + String firstName = CSVHelpers.extractString("Firstname", record, ""); + String lastName = CSVHelpers.extractString("Lastname", record, ""); + + String combinedName = String.format("%s %s", firstName, lastName); + + StringBuilder noteBuilder = new StringBuilder(); + if (!program.isEmpty()) noteBuilder.append(program).append('\n'); + if (!addedAt.isEmpty()) noteBuilder.append(addedAt).append('\n'); + if (!combinedName.isEmpty()) noteBuilder.append(combinedName).append('\n'); + String note = noteBuilder.toString(); + + // The ID is called reference + String cardId = CSVHelpers.extractString("Reference", record, ""); + if(cardId.isEmpty()) + { + throw new FormatException("No card ID listed, but is required"); + } + + // Sadly, Fidme exports don't contain the card type + // I guess they have an online DB of all the different companies and what type they use + // TODO: Hook this into our own loyalty card DB if we ever get one + String barcodeType = ""; + + // No favourite data in the export either + int starStatus = 0; + + helper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, barcodeType, null, starStatus); + } +} \ No newline at end of file diff --git a/app/src/main/java/protect/card_locker/MultiFormatExporter.java b/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java similarity index 77% rename from app/src/main/java/protect/card_locker/MultiFormatExporter.java rename to app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java index 5298e2ad7..fedc446e7 100644 --- a/app/src/main/java/protect/card_locker/MultiFormatExporter.java +++ b/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java @@ -1,10 +1,15 @@ -package protect.card_locker; +package protect.card_locker.importexport; import android.util.Log; import java.io.IOException; import java.io.OutputStreamWriter; +import protect.card_locker.DBHelper; +import protect.card_locker.DataFormat; +import protect.card_locker.importexport.CsvDatabaseExporter; +import protect.card_locker.importexport.DatabaseExporter; + public class MultiFormatExporter { private static final String TAG = "Catima"; @@ -25,9 +30,12 @@ public class MultiFormatExporter switch(format) { - case CSV: + case Catima: exporter = new CsvDatabaseExporter(); break; + default: + Log.e(TAG, "Failed to export data, unknown format " + format.name()); + break; } if(exporter != null) diff --git a/app/src/main/java/protect/card_locker/MultiFormatImporter.java b/app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java similarity index 61% rename from app/src/main/java/protect/card_locker/MultiFormatImporter.java rename to app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java index 6a27af7e4..4d5324521 100644 --- a/app/src/main/java/protect/card_locker/MultiFormatImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java @@ -1,9 +1,19 @@ -package protect.card_locker; +package protect.card_locker.importexport; import android.util.Log; +import org.json.JSONException; + import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; +import java.text.ParseException; + +import protect.card_locker.DBHelper; +import protect.card_locker.DataFormat; +import protect.card_locker.FormatException; +import protect.card_locker.importexport.CsvDatabaseImporter; +import protect.card_locker.importexport.DatabaseImporter; public class MultiFormatImporter { @@ -20,15 +30,21 @@ public class MultiFormatImporter * false otherwise. If false, no data was written to * the database. */ - public static boolean importData(DBHelper db, InputStreamReader input, DataFormat format) + public static boolean importData(DBHelper db, InputStream input, DataFormat format) { DatabaseImporter importer = null; switch(format) { - case CSV: + case Catima: importer = new CsvDatabaseImporter(); break; + case Fidme: + importer = new FidmeImporter(); + break; + case VoucherVault: + importer = new VoucherVaultImporter(); + break; } if (importer != null) @@ -38,7 +54,7 @@ public class MultiFormatImporter importer.importData(db, input); return true; } - catch(IOException | FormatException | InterruptedException e) + catch(IOException | FormatException | InterruptedException | JSONException | ParseException e) { Log.e(TAG, "Failed to import data", e); } diff --git a/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java b/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java new file mode 100644 index 000000000..30c6bcb66 --- /dev/null +++ b/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java @@ -0,0 +1,134 @@ +package protect.card_locker.importexport; + +import android.annotation.SuppressLint; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Color; + +import com.google.zxing.BarcodeFormat; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Currency; +import java.util.Date; +import java.util.TimeZone; + +import protect.card_locker.DBHelper; +import protect.card_locker.FormatException; + +/** + * Class for importing a database from CSV (Comma Separate Values) + * formatted data. + * + * The database's loyalty cards are expected to appear in the CSV data. + * A header is expected for the each table showing the names of the columns. + */ +public class VoucherVaultImporter implements DatabaseImporter +{ + public void importData(DBHelper db, InputStream input) throws IOException, FormatException, JSONException, ParseException { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); + + StringBuilder sb = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + sb.append(line); + } + JSONArray jsonArray = new JSONArray(sb.toString()); + + SQLiteDatabase database = db.getWritableDatabase(); + database.beginTransaction(); + + // See https://github.com/tim-smart/vouchervault/issues/4#issuecomment-788226503 for more info + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject jsonCard = jsonArray.getJSONObject(i); + + String store = jsonCard.getString("description"); + + Date expiry = null; + if (!jsonCard.isNull("expires")) { + @SuppressLint("SimpleDateFormat") SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + expiry = dateFormat.parse(jsonCard.getString("expires")); + } + + BigDecimal balance; + if (!jsonCard.isNull("balance")) { + balance = new BigDecimal(String.valueOf(jsonCard.getDouble("balance"))); + } else { + balance = new BigDecimal("0"); + } + + Currency balanceType = Currency.getInstance("USD"); + + String cardId = jsonCard.getString("code"); + + String barcodeType = null; + + String codeTypeFromJSON = jsonCard.getString("codeType"); + switch (codeTypeFromJSON) { + case "CODE128": + barcodeType = BarcodeFormat.CODE_128.name(); + break; + case "CODE39": + barcodeType = BarcodeFormat.CODE_39.name(); + break; + case "EAN13": + barcodeType = BarcodeFormat.EAN_13.name(); + break; + case "QR": + barcodeType = BarcodeFormat.QR_CODE.name(); + break; + case "TEXT": + break; + default: + throw new FormatException("Unknown barcode type found: " + codeTypeFromJSON); + } + + int headerColor; + + String colorFromJSON = jsonCard.getString("color"); + switch (colorFromJSON) { + case "GREY": + headerColor = Color.GRAY; + break; + case "BLUE": + headerColor = Color.BLUE; + break; + case "GREEN": + headerColor = Color.GREEN; + break; + case "ORANGE": + headerColor = Color.rgb(255, 165, 0); + break; + case "PURPLE": + headerColor = Color.rgb(128, 0, 128); + break; + case "RED": + headerColor = Color.RED; + break; + case "YELLOW": + headerColor = Color.YELLOW; + break; + default: + throw new FormatException("Unknown colour type foun: " + colorFromJSON); + } + + db.insertLoyaltyCard(store, "", expiry, balance, balanceType, cardId, barcodeType, headerColor, 0); + } + + database.setTransactionSuccessful(); + database.endTransaction(); + database.close(); + + bufferedReader.close(); + } +} \ No newline at end of file diff --git a/app/src/main/java/protect/card_locker/preferences/Settings.java b/app/src/main/java/protect/card_locker/preferences/Settings.java index b6808f352..ab5bc96cb 100644 --- a/app/src/main/java/protect/card_locker/preferences/Settings.java +++ b/app/src/main/java/protect/card_locker/preferences/Settings.java @@ -90,4 +90,14 @@ public class Settings { return getBoolean(R.string.settings_key_lock_barcode_orientation, false); } + + public boolean getKeepScreenOn() + { + return getBoolean(R.string.settings_key_keep_screen_on, true); + } + + public boolean getDisableLockscreenWhileViewingCard() + { + return getBoolean(R.string.settings_key_disable_lockscreen_while_viewing_card, true); + } } diff --git a/app/src/main/res/layout/loyalty_card_edit_activity.xml b/app/src/main/res/layout/loyalty_card_edit_activity.xml index 7e02fb469..50f850b63 100644 --- a/app/src/main/res/layout/loyalty_card_edit_activity.xml +++ b/app/src/main/res/layout/loyalty_card_edit_activity.xml @@ -136,6 +136,7 @@ android:textSize="@dimen/inputSize" /> + - + + + + + + + + + + + + + + + + + + + - + + + android:orientation="horizontal"> + android:hint="@string/cardId" + android:labelFor="@+id/cardIdView"> + + + + + + + android:textSize="@dimen/singleCardNoteTextSizeMin" /> + android:textSize="@dimen/singleCardNoteTextSizeMin" /> + + + android:textSize="@dimen/singleCardNoteTextSizeMin" /> + - Suche - Neu + Suchen + Hinzufügen Klicken Sie auf die Schaltfläche + (plus), um zuerst eine Karte hinzuzufügen. \n \nCatima trägt Ihre Karten auf dem Gerät, so dass sie immer in Reichweite sind. @@ -11,7 +11,6 @@ Kartennummer Abbrechen Speichern - Karte bearbeiten Bearbeiten Löschen Bestätigen @@ -21,9 +20,9 @@ Aus der Favoritenliste entfernen Karte entfernen Bitte bestätigen Sie, dass diese Karte gelöscht werden soll. - Ok - Kopiere die Nummer in die Zwischenablage - Senden… + OK + Nummer in die Zwischenablage kopieren + Senden … Kundenkarte bearbeiten Neue Kundenkarte Strichcode scannen @@ -103,4 +102,47 @@ %d Karten Gruppen: %s - + Loyalty Card Keychain + Aus welcher App Daten importieren\? + %s scheint kein gültiges Guthaben zu sein. + Punkte + Währung + Guthaben + Strichcode auf dem Bildschirm zentrieren + Strichcode auf dem Bildschirm nach oben schieben + Ablaufdatum wählen + Nie + Ablaufdatum + Strichcode bearbeiten + Strichcode + Karte + %s Punkte + Guthaben: %s + Abgelaufen: %s + Läuft ab: %s + Sperrbildschirm deaktivieren während eine Karte angesehen wird + Bildschirm anlassen während eine Karte angesehen wird + Es wird oft verlangt, dass Anwendungen beim ersten Start ihre Datenschutzrichtlinien anzeigen. Hier ist unsere: +\n +\nWir sammeln KEINE DATEN und unsere Anwendung ist quelloffen, sodass jeder bestätigen kann, dass dies wahr ist. + Annehmen + Datenschutzrichtlinie + Bitte wählen Sie Ihre Voucher Vault-Exportdatei aus. Es heißt höchstwahrscheinlich vouchervault.json. +\n +\nEine Voucher Vault-Exportdatei kann durch Drücken von Exportieren erstellt werden. + Aus Voucher Vault importieren + Bitte wählen Sie Ihre Loyalty Card Keychain-Exportdatei aus. Es heißt höchstwahrscheinlich LoyaltyCardKeychain.csv. +\n +\nEine Loyalty Card Keychain-Exportdatei kann erstellt werden, indem Sie im Menü Import/Export auf Exportieren klicken. + Aus Loyalty Card Keychain importieren + Bitte wählen Sie Ihre Fidme-Exportdatei aus. Es wird höchstwahrscheinlich so etwas wie fidme-export-request-xxxxxx.zip genannt. +\n +\nEine Fidme-Exportdatei kann in der Fidme-Anwendung erstellt werden, indem Sie zu Ihrem Profil gehen, Datenschutz auswählen und dann auf Meine Daten extrahieren klicken. +\n +\nBitte beachten Sie, dass Fidme den Strichcode-Typ nicht in den Exportdaten speichert, sodass Sie die importierten Karten manuell bearbeiten müssen. + Aus Fidme importieren + Bitte wählen Sie Ihre Catima-Exportdatei aus. Es heißt höchstwahrscheinlich Catima.csv. +\n +\nEine Catima-Exportdatei kann erstellt werden, indem Sie im Menü Import/Export auf Exportieren klicken. + Aus Catima importieren + \ No newline at end of file diff --git a/app/src/main/res/values-el-rGR/strings.xml b/app/src/main/res/values-el-rGR/strings.xml index bfd7dded7..798f091d6 100644 --- a/app/src/main/res/values-el-rGR/strings.xml +++ b/app/src/main/res/values-el-rGR/strings.xml @@ -7,7 +7,6 @@ Κωδικός Κάρτας Άκυρο Αποθήκευση - Επεξεργασία Κάρτας Επεξεργασία Διαγραφή Επιβεβαίωση @@ -64,4 +63,5 @@ Σκοτεινό Φωτεινό Σύστημα + Γραμμικός κώδικας \ No newline at end of file diff --git a/app/src/main/res/values-es-rAR/strings.xml b/app/src/main/res/values-es-rAR/strings.xml index c8d3a6422..2de21eb26 100644 --- a/app/src/main/res/values-es-rAR/strings.xml +++ b/app/src/main/res/values-es-rAR/strings.xml @@ -1,4 +1,4 @@ - - - + + + Código de barras + \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 04069fc5c..9c5fd5422 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -9,7 +9,6 @@ Id. de tarjeta Cancelar Guardar - Editar tarjeta Editar Eliminar Confirmar @@ -103,4 +102,14 @@ %d tarjeta %d tarjetas + Puntos + Centrar el código de barras en la pantalla + Mover el código de barras a la zona superior de la pantalla + Elegir fecha de caducidad + Nunca + Fecha de caducidad + Editar el código de barras + Código de barras + Tarjeta + %s puntos diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index a222e1ec6..39abdc653 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -4,12 +4,11 @@ Appuyez d\'abord sur le bouton \"+\" (plus) pour ajouter une carte. \n \nCatima enregistre vos cartes sur votre appareil, pour toujours les avoir à portée de main. - Nom du Magasin + Magasin Notes Numéro Annuler Enregistrer - Modifier Modifier Supprimer Confirmer @@ -20,7 +19,7 @@ OK Copier le numéro dans le presse-papier Envoyer… - Modifier la carte de fidélité + Modifier la carte Ajouter une carte de fidélité Scanner le code-barres Raccourci de carte @@ -103,4 +102,47 @@ %d cartes Groupes : %s - + Accepter + De nombreux magasins d\'applications exigent que les applications affichent leur politique de confidentialité au premier démarrage. Voici la nôtre : +\n +\nNous ne collectons AUCUNE DONNÉE et le code source de notre application est ouvert afin que tout le monde puisse le confirmer. + Politique de confidentialité + Loyalty Card Keychain + Importer des données à partir de quelle application \? + <xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">%s</xliff:g> ne semble pas être un solde valide. + Points + Monnaie + Solde + Centrer le code-barres sur l\'écran + Déplacez le code-barres vers le haut de l\'écran + Choisissez la date d\'expiration + Jamais + Date d\'expiration + Modifier le code-barres + Code-barres + Carte + <xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">%s</xliff:g> points + Solde : <xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">%s</xliff:g> + Expiré : <xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">%s</xliff:g> + Expire : <xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">%s</xliff:g> + Désactiver l\'écran de verrouillage pendant l\'affichage d\'une carte + Garder l\'écran allumé pendant l\'affichage d\'une carte + Veuillez sélectionner votre fichier d\'exportation Voucher Vault. Il est probablement nommé vouchervault.json. +\n +\nUn fichier d\'exportation Voucher Vault peut être créé en appuyant sur Exporter. + Importer depuis Voucher Vault + Veuillez sélectionner votre fichier d\'exportation Loyalty Card Keychain . Il s\'appelle très probablement LoyaltyCardKeychain.csv. +\n +\nUn fichier d\'exportation Loyalty Card Keychain peut être créé en allant dans le menu Importer/Exporter et en appuyant sur Exporter. + Importer depuis Loyalty Card Keychain + Veuillez sélectionner votre fichier d\'exportation Fidme. Il est très probablement nommé quelque chose comme fidme-export-request-xxxxxx.zip. +\n +\nUn fichier d\'exportation Fidme peut être créé dans l\'application Fidme en accédant à votre profil, en choisissant Protection des données, puis en appuyant sur Extraire mes données. +\n +\nVeuillez noter que Fidme ne stocke pas le type de code-barres dans les données d\'exportation, vous devrez donc éditer les cartes importées manuellement. + Importer depuis Fidme + Veuillez sélectionner votre fichier d\'exportation Catima. Il est probablement nommé Catima.csv. +\n +\nUn fichier d\'exportation Catima peut être créé en allant dans le menu Importer/Exporter et en appuyant sur Exporter. + Importer depuis Catima + \ No newline at end of file diff --git a/app/src/main/res/values-he-rIL/strings.xml b/app/src/main/res/values-he-rIL/strings.xml index d19047bba..b04df4902 100644 --- a/app/src/main/res/values-he-rIL/strings.xml +++ b/app/src/main/res/values-he-rIL/strings.xml @@ -6,5 +6,4 @@ מזהה כרטיס ביטול שמור - עריכת כרטיס - + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 81e834f79..fce1c44d1 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -12,7 +12,6 @@ Questa carta non ha un codice a barre Annulla Salva - Modifica carta Modifica Elimina Conferma @@ -20,7 +19,7 @@ Sblocca rotazione Rimuovi carta fedeltà Conferma di voler eliminare questa carta. - Ok + OK Copia ID negli appunti Condividi Invia… @@ -103,4 +102,47 @@ %d carta %d carte - + <xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">%s</xliff:g> non sembra un saldo corretto. + Punti + Valuta + Saldo + Centra il codice a barre sullo schermo + Sposta il codice a barre in cima allo schermo + Scegli data di scadenza + Mai + Data di scadenza + Modifica il codice a barre + Codice a barre + Carta + %s punti + Saldo: %s + Scaduta: %s + Scade: %s + Mantieni schermo acceso durante la visualizzazione di una carta + Loyalty Card Keychain + Da quale app vuoi importare i dati\? + Mantieni schermo attivo mentre visualizzi una carta + Accetta + È spesso richiesto alle applicazioni di mostrare la loro politica sulla riservatezza al primo avvio. Ecco la nostra: +\n +\nNon raccogliamo ALCUN DATO e il codice sorgente della nostra applicazione è aperto, quindi chiunque può confermare che ciò sia vero. + Informativa sulla riservatezza + Seleziona il tuo file di esportazione Voucher Vault. Molto probabilmente si chiama vouchervault.json. +\n +\nÈ possibile creare un file di esportazione Voucher Vault premendo Esporta. + Importa da Voucher Vault + Seleziona il file di esportazione Loyalty Card Keychain . Molto probabilmente si chiama LoyaltyCardKeychain.csv. +\n +\nÈ possibile creare un file di esportazione Loyalty Card Keychain accedendo al menu Importa / Esporta e premendo Esporta. + Importa da Loyalty Card Keychain + Seleziona il tuo file di esportazione Fidme. Molto probabilmente si chiama qualcosa come fidme-export-request-xxxxxx.zip. +\n +\nUn file di esportazione Fidme può essere creato nell\'app Fidme andando sul tuo profilo, scegliendo Protezione dati e quindi premendo Estrai i miei dati. +\n +\nTieni presente che Fidme non memorizza il tipo di codice a barre nei dati di esportazione, quindi dovrai modificare manualmente le carte importate. + Importa da Fidme + Seleziona il tuo file di esportazione Catima. Molto probabilmente si chiama Catima.csv. +\n +\nÈ possibile creare un file di esportazione Catima andando nel menu Importa / Esporta e premendo Esporta. + Importa da Catima + \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index c576ff10e..4e5da5960 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -44,7 +44,6 @@ 확인 삭제 편집 - 카드 편집 저장 취소 즐겨찾기에서 제거 @@ -84,4 +83,5 @@ 매장을 입력하지 않음 즐겨찾기 별 바코드를 표시할 때 화면 밝기 높이기 + 바코드 \ No newline at end of file diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 170d09ddf..77cf28b46 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -10,7 +10,6 @@ Strekkodetype Avbryt Lagre - Rediger kort Rediger Slett Bekreft @@ -113,4 +112,19 @@ Utløpt: %s Sentrer strekkoden på skjermen Flytt strekkoden til toppen av skjermen + %s ser ikke ut til å være en gyldig saldo. + Poeng + Valuta + Saldo + %s poeng + Saldo: %s + Hvilket program vil du importere data fra\? + Kundekortsknippe + Skru av låseskjerm under kortvisning + Behold skjerm påslått under kortvisning + Mange programbutikker krever at programmer viser personvernspraksisen sin ved første oppstart. Her er vår: +\n +\nVi SAMLER IKKE IN NOEN DATA overhodet, og programmet vårt er fri programvare, så alle kan bekrefte at dette stemmer. + Godta + Personvernspraksis \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 700f63c87..f770af54f 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -13,7 +13,6 @@ Deze kaart heeft geen barcode Annuleren Opslaan - Kaart bewerken Bewerken Verwijderen Bevestigen @@ -113,4 +112,37 @@ Verlopen: %s Barcode verplaatsen naar midden van scherm Barcode verplaatsen naar bovenkant van scherm + %s lijkt geen geldig saldo te zijn. + Aantal punten + Valuta + Saldo + %s punten + Saldo: %s + Uit welke app wil je gegevens importeren\? + Klantenkaartkluis + Vergrendelscherm uitschakelen tijdens tonen van kaart + Scherm niet uitschakelen tijdens tonen van kaart + Veel appwinkels vereisen dat apps na de eerste keer opstarten hun privacybeleid tonen. Dat van ons is eenvoudig: +\n +\nWe verzamelen HELEMAAL NIKS. Bovendien is onze app open source, zodat een ieder met eigen ogen kan zien wat de app wel of niet doet. + Privacybeleid + Accepteren + Kies het gewenste Voucher Vault-exportbestand - normaliter heet dit bestand \'vouchervault.json\'. +\n +\nGa naar het Exportmenu om een Voucher Vault-exportbestand samen te stellen. + Importeren uit Voucher Vault + Kies het gewenste Klantenkaartkluis-exportbestand - normaliter heet dit bestand \'LoyaltyCardKeychain.csv\'. +\n +\nGa naar het Import-/Exportmenu om een Klantenkaartkluis-exportbestand samen te stellen. + Importeren uit Klantenkaartkluis + Kies het gewenste Fidme-exportbestand - normaliter heet dit bestand \'fidme-export-request-xxxxxx.zip\'. +\n +\nOpen de Fidme-app, ga naar je profiel en druk op \'Gegevensbescherming\' om een exportbestand samen te stellen. +\n +\nLet op: Fidme slaat de soorten barcodes niet op in het exportbestand, dus dat moet je na achteraf nog instellen. + Kies het gewenste Catima-exportbestand - normaliter heet dit bestand \'Catima.csv\'. +\n +\nGa naar het Import-/Exportmenu om een Catima-exportbestand samen te stellen. + Importeren uit Fidme + Importeren uit Catima \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 3ea0ca6bd..c5d3ba292 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -10,7 +10,6 @@ Ta karta nie ma kodu kreskowego Anuluj Zapisz - Edytuj kartę Edytuj Usuń Potwierdź diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index bca032d91..f10e24a13 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1,31 +1,30 @@ Поиск - Добавить карту - Нажмите кнопку + (плюс), чтобы сначала добавить карту. + Добавить + Нажмите кнопку + (плюс), чтобы добавить карту. \n \nCatima хранит ваши карты на устройстве, поэтому они всегда под рукой. - "Ничего не найдено. Попробуйте изменить свой поиск." + Ничего не найдено. Попробуйте изменить поисковый запрос. Магазин Примечание Номер карты - Тип штрихкода - Эта карта без штрихкода + Тип штрих-кода + Эта карта без штрих-кода Отменить Сохранить - Редактировать штрих-код - Редактировать + Изменить Удалить карту Подтвердить Блокировать поворот экрана Автоповорот экрана Удаление карты - Пожалуйста подтвердите удаление карты. + Подтвердите удаление карты. ОК - Скопировать номер карты в буфер обмена + Копировать номер карты Переслать Отправить… - Редактировать карту + Изменить карту Добавить карту Отсканируйте штрих-код Ярлык карты @@ -34,16 +33,16 @@ Название магазина не указано Номер карты не указан Карта не найдена - Не удалось разобрать импортируемый URI - Импорт/Экспорт + Невозможно разобрать импортируемый URI + Импорт/экспорт Экспорт Резервное копирование карт позволяет переместить их на другое устройство. Импортировано - Импорт не удался - Не удалось импортировать карты + Импорт не выполнен + Невозможно импортировать карты Экспортировано - Экспорт не удался - Не удалось экспортировать + Экспорт не выполнен + Невозможно экспортировать карты Импорт… Экспорт… Импорт или экспорт невозможен без разрешения на доступ к хранилищу @@ -54,15 +53,15 @@ Использование другого приложения Используйте любое приложение или ваш любимый файловый менеджер, чтобы открыть файл. Использовать другое приложение - О программе + О приложении Авторское лево свободного программного обеспечения, лицензия GPLv3+. - О программе %s + О приложении %s Версия: %s - Информация о версиях: %s + Информация о версиях: %s %s использует следующие сторонние библиотеки: %s %s использует следующие сторонние ресурсы: %s Выбор штрих-кода - Введите ID карты и выберите тип штрих-кода. + Введите номер карты и выберите тип штрих-кода. Номер карты скопирован в буфер обмена Логотип карты Настройки @@ -71,22 +70,22 @@ Системная Светлая Тёмная - Размер шрифта для названий карт - Размер шрифта примечания для списка - Размер шрифта названия карты + Размер шрифта названия карты (список) + Размер шрифта примечания (список) + Размер шрифта названия карты (просмотр) Размер шрифта номера карты Максимальная яркость при показе карты Портретная ориентация экрана при показе карты Я хочу поделиться картой с вами - Данные карты экспортированы + Данные карт успешно экспортированы Все - Нажмите кнопку + (плюс), чтобы сначала добавить группы. + Нажмите кнопку + (плюс), чтобы добавить группы. \n \nГруппы упрощают поиск. Группы Введите название группы - Данные карты лояльности успешно импортированы - Любимая звезда + Данные карт успешно импортированы + Звезда избранного На основе Loyalty Card Keychain, авторские права 2016–2020 Branden Archer. Удалить из избранного Добавить в избранное @@ -94,5 +93,58 @@ Подтвердите, что хотите удалить эту группу Вы уверены, что хотите покинуть этот экран\? Изменения не будут сохранены. Выйти без сохранения - Не удалось открыть файловый менеджер. Пожалуйста, убедитись, что он установлен. + Невозможно открыть файловый менеджер. Убедитесь, что он установлен. + %s баллов + Баланс: %s + Истёк срок действия: %s + Истекает срок действия: %s + Баллы + %s не похож на правильный баланс. + Ручной ввод номера карты + Многие магазины приложений требуют, чтобы приложение показывало свою политику конфиденциальности при первом запуске. Вот наша: +\n +\nМы НЕ собираем НИКАКИХ ДАННЫХ и наше приложение имеет открытый исходный код, так что любой может убедиться в том, что это правда. + Политика конфиденциальности + Loyalty Card Keychain + Из какого приложения импортировать данные\? + Баланс + Переместить штрих-код в верхнюю часть экрана + Центрировать штрих-код на экране + Валюта + Указать срок действия + Никогда + Окончание срока действия + Изменить штрих-код + Штрих-код + Карта + Группы: %s + Переместить ниже в вписке + Переместить выше в вписке + Не блокировать экран при показе карты + Не отключать экран при показе карты + + %d карта + %d карты + %d карт + %d карт + + Принять + Выберите файл экспорта Voucher Vault. Обычно он называется \"vouchervault.json\". +\n +\nФайл экспорта Voucher Vault можно создать, нажав кнопку \"Экспорт\". + Импорт из Voucher Vault + Выберите файл экспорта Fidme. Обычно он называется как-то вроде \"fidme-export-request-xxxxxx.zip\". +\n +\nФайл экспорта Fidme можно создать в приложении Fidme, перейдя в свой профиль, выбрав функцию \"Защита данных\", и затем нажав кнопку \"Извлечь мои данные\". +\n +\nОбратите внимание, что Fidme не сохраняет тип штрих-кода в экспортированных данных, поэтому вам придётся редактировать импортированные данные карт вручную. + Выберите файл экспорта Loyalty Card Keychain. Обычно он называется \"LoyaltyCardKeychain.csv\". +\n +\nФайл экспорта Loyalty Card Keychain можно создать, перейдя в меню \"Импорт/Экспорт\" и нажав кнопку \"Экспорт\". + Импорт из Loyalty Card Keychain + Выберите файл экспорта Catima. Обычно он называется \"Catima.csv\". +\n +\nФайл экспорта Catima можно создать, перейдя в меню \"Импорт/Экспорт\" и нажав кнопку \"Экспорт\". + Импорт из Fidme + Импорт из Catima \ No newline at end of file diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 7fbbce650..d675d748f 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -9,7 +9,6 @@ ID karty Zrušiť Uložiť - Úprava karty Upraviť Vymazať Potvrdiť diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 51f45c030..74ca7d549 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -9,7 +9,6 @@ Št. kartice Prekliči Shrani - Uredi kartico Uredi Izbriši Potrdi diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 000000000..504bd052d --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,9 @@ + + + + Catima + Fidme + @string/app_loyalty_card_keychain + Voucher Vault + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8aa8b38ab..67144e59c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,7 +20,6 @@ Cancel Save - Edit Card Edit Delete Confirm @@ -104,9 +103,13 @@ pref_display_card_max_brightness Lock barcode orientation pref_lock_barcode_orientation - + Keep screen on while viewing a card + pref_keep_screen_on + Disable lock screen while viewing a card + pref_disable_lockscreen_while_viewing_card sharedpreference_active_tab + sharedpreference_privacy_policy_shown I want to share a card with you thelastproject.github.io @@ -136,6 +139,8 @@ Groups: %s Expires: %s Expired: %s + Balance: %s + %s points Card Barcode @@ -145,6 +150,27 @@ Choose expiry date Move the barcode to the top of the screen Center the barcode on the screen + No barcode was found Error reading image + + Balance + Currency + Points + + %s does not seem to be a valid balance. + Import data from which app? + Loyalty Card Keychain + + Privacy Policy + Many app stores require apps to show their privacy policy on first start. Here is ours:\n\nWe collect NO DATA AT ALL and our app is Open Source so anyone can confirm this to be true. + Accept + Import from Catima + Please select your Catima export file. It is most likely named Catima.csv.\n\nA Catima export file can be created by going to the Import/Export menu and pressing "Export". + Import from Fidme + Please select your Fidme export file. It is most likely named something like fidme-export-request-xxxxxx.zip.\n\nA Fidme export file can be created in the Fidme app by going to your profile, choosing "Data Protection" and then pressing "Extract my data".\n\nPlease note that Fidme does not store the barcode type in the export data so you will have to edit the imported cards manually. + Import from Loyalty Card Keychain + Please select your Loyalty Card Keychain export file. It is most likely named LoyaltyCardKeychain.csv.\n\nA Loyalty Card Keychain export file can be created by going to the Import/Export menu and pressing "Export". + Import from Voucher Vault + Please select your Voucher Vault export file. It is most likely named vouchervault.json.\n\nA Voucher Vault export file can be created by pressing "Export". diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index b176a17a7..e8b14331d 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -58,6 +58,18 @@ android:key="@string/settings_key_lock_barcode_orientation" android:title="@string/settings_lock_barcode_orientation" app:iconSpaceReserved="false" /> + + + + \ No newline at end of file diff --git a/app/src/test/java/protect/card_locker/DatabaseTest.java b/app/src/test/java/protect/card_locker/DatabaseTest.java index c8db55818..a1f6b11da 100644 --- a/app/src/test/java/protect/card_locker/DatabaseTest.java +++ b/app/src/test/java/protect/card_locker/DatabaseTest.java @@ -15,7 +15,9 @@ import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Currency; import java.util.List; import static org.junit.Assert.assertEquals; @@ -43,7 +45,7 @@ public class DatabaseTest public void addRemoveOneGiftCard() { assertEquals(0, db.getLoyaltyCardCount()); - long id = db.insertLoyaltyCard("store", "note", null, "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, 0); + long id = db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, 0); boolean result = (id != -1); assertTrue(result); assertEquals(1, db.getLoyaltyCardCount()); @@ -53,6 +55,8 @@ public class DatabaseTest assertEquals("store", loyaltyCard.store); assertEquals("note", loyaltyCard.note); assertEquals(null, loyaltyCard.expiry); + assertEquals(new BigDecimal("0"), loyaltyCard.balance); + assertEquals(null, loyaltyCard.balanceType); assertEquals("cardId", loyaltyCard.cardId); assertEquals(0, loyaltyCard.starStatus); assertEquals(BarcodeFormat.UPC_A.toString(), loyaltyCard.barcodeType); @@ -66,12 +70,12 @@ public class DatabaseTest @Test public void updateGiftCard() { - long id = db.insertLoyaltyCard("store", "note", null, "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, 0); + long id = db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, 0); boolean result = (id != -1); assertTrue(result); assertEquals(1, db.getLoyaltyCardCount()); - result = db.updateLoyaltyCard(1, "store1", "note1", null, "cardId1", BarcodeFormat.AZTEC.toString(), DEFAULT_HEADER_COLOR); + result = db.updateLoyaltyCard(1, "store1", "note1", null, new BigDecimal("10.00"), Currency.getInstance("EUR"), "cardId1", BarcodeFormat.AZTEC.toString(), DEFAULT_HEADER_COLOR); assertTrue(result); assertEquals(1, db.getLoyaltyCardCount()); @@ -80,6 +84,8 @@ public class DatabaseTest assertEquals("store1", loyaltyCard.store); assertEquals("note1", loyaltyCard.note); assertEquals(null, loyaltyCard.expiry); + assertEquals(new BigDecimal("10.00"), loyaltyCard.balance); + assertEquals(Currency.getInstance("EUR"), loyaltyCard.balanceType); assertEquals("cardId1", loyaltyCard.cardId); assertEquals(0, loyaltyCard.starStatus); assertEquals(BarcodeFormat.AZTEC.toString(), loyaltyCard.barcodeType); @@ -88,7 +94,7 @@ public class DatabaseTest @Test public void updateGiftCardOnlyStar() { - long id = db.insertLoyaltyCard("store", "note", null, "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, 0); + long id = db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, 0); boolean result = (id != -1); assertTrue(result); assertEquals(1, db.getLoyaltyCardCount()); @@ -102,6 +108,8 @@ public class DatabaseTest assertEquals("store", loyaltyCard.store); assertEquals("note", loyaltyCard.note); assertEquals(null, loyaltyCard.expiry); + assertEquals(new BigDecimal("0"), loyaltyCard.balance); + assertEquals(null, loyaltyCard.balanceType); assertEquals("cardId", loyaltyCard.cardId); assertEquals(1, loyaltyCard.starStatus); assertEquals(BarcodeFormat.UPC_A.toString(), loyaltyCard.barcodeType); @@ -112,7 +120,7 @@ public class DatabaseTest { assertEquals(0, db.getLoyaltyCardCount()); - boolean result = db.updateLoyaltyCard(1, "store1", "note1", null, "cardId1", + boolean result = db.updateLoyaltyCard(1, "store1", "note1", null, new BigDecimal("0"), null, "cardId1", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR); assertEquals(false, result); assertEquals(0, db.getLoyaltyCardCount()); @@ -121,7 +129,7 @@ public class DatabaseTest @Test public void emptyGiftCardValues() { - long id = db.insertLoyaltyCard("", "", null, "", "", null, 0); + long id = db.insertLoyaltyCard("", "", null, new BigDecimal("0"), null, "", "", null, 0); boolean result = (id != -1); assertTrue(result); assertEquals(1, db.getLoyaltyCardCount()); @@ -131,6 +139,8 @@ public class DatabaseTest assertEquals("", loyaltyCard.store); assertEquals("", loyaltyCard.note); assertEquals(null, loyaltyCard.expiry); + assertEquals(new BigDecimal("0"), loyaltyCard.balance); + assertEquals(null, loyaltyCard.balanceType); assertEquals("", loyaltyCard.cardId); assertEquals("", loyaltyCard.barcodeType); } @@ -144,7 +154,7 @@ public class DatabaseTest // that they are sorted for(int index = CARDS_TO_ADD-1; index >= 0; index--) { - long id = db.insertLoyaltyCard("store" + index, "note" + index, null, "cardId" + index, + long id = db.insertLoyaltyCard("store" + index, "note" + index, null, new BigDecimal("0"), null, "cardId" + index, BarcodeFormat.UPC_A.toString(), index, 0); boolean result = (id != -1); assertTrue(result); @@ -164,6 +174,8 @@ public class DatabaseTest assertEquals("store"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE))); assertEquals("note"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE))); assertEquals(0, cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.EXPIRY))); + assertEquals("0", cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE))); + assertEquals(null, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE))); assertEquals("cardId"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID))); assertEquals(BarcodeFormat.UPC_A.toString(), cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE))); assertEquals(0, cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS))); @@ -187,12 +199,12 @@ public class DatabaseTest for(int index = CARDS_TO_ADD-1; index >= 0; index--) { if (index == CARDS_TO_ADD-1) { - id = db.insertLoyaltyCard("store" + index, "note" + index, null, "cardId" + index, + id = db.insertLoyaltyCard("store" + index, "note" + index, null, new BigDecimal("0"), null, "cardId" + index, BarcodeFormat.UPC_A.toString(), index, 1); } else { - id = db.insertLoyaltyCard("store" + index, "note" + index, null, "cardId" + index, + id = db.insertLoyaltyCard("store" + index, "note" + index, null, new BigDecimal("0"), null, "cardId" + index, BarcodeFormat.UPC_A.toString(), index, 0); } boolean result = (id != -1); @@ -211,6 +223,8 @@ public class DatabaseTest assertEquals("store"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE))); assertEquals("note"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE))); assertEquals(0, cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.EXPIRY))); + assertEquals("0", cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE))); + assertEquals(null, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE))); assertEquals("cardId"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID))); assertEquals(BarcodeFormat.UPC_A.toString(), cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE))); assertEquals(1, cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS))); @@ -222,7 +236,9 @@ public class DatabaseTest { assertEquals("store"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE))); assertEquals("note"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE))); - assertEquals("cardId"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID))); + assertEquals(null, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.EXPIRY))); + assertEquals("0", cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE))); + assertEquals(null, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE))); assertEquals("cardId"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID))); assertEquals(BarcodeFormat.UPC_A.toString(), cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE))); assertEquals(0, cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS))); @@ -288,7 +304,7 @@ public class DatabaseTest { // Create card assertEquals(0, db.getLoyaltyCardCount()); - long id = db.insertLoyaltyCard("store", "note", null, "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, 0); + long id = db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, 0); boolean result = (id != -1); assertTrue(result); assertEquals(1, db.getLoyaltyCardCount()); @@ -405,7 +421,7 @@ public class DatabaseTest { // Create card assertEquals(0, db.getLoyaltyCardCount()); - long id = db.insertLoyaltyCard("store", "note", null, "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, 0); + long id = db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, 0); boolean result = (id != -1); assertTrue(result); assertEquals(1, db.getLoyaltyCardCount()); @@ -465,6 +481,8 @@ public class DatabaseTest LoyaltyCard card = db.getLoyaltyCard(newCardId); assertEquals("store", card.store); assertEquals(null, card.expiry); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(null, card.balanceType); assertEquals("cardId", card.cardId); assertEquals(BarcodeFormat.UPC_A.toString(), card.barcodeType); assertEquals("", card.note); diff --git a/app/src/test/java/protect/card_locker/ImportExportTest.java b/app/src/test/java/protect/card_locker/ImportExportTest.java index ddf3b6c3f..645583942 100644 --- a/app/src/test/java/protect/card_locker/ImportExportTest.java +++ b/app/src/test/java/protect/card_locker/ImportExportTest.java @@ -3,10 +3,12 @@ package protect.card_locker; import android.app.Activity; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.graphics.Color; import android.os.Environment; import com.google.zxing.BarcodeFormat; +import org.json.JSONException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -24,12 +26,18 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.math.BigDecimal; import java.nio.charset.StandardCharsets; +import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; +import java.util.Currency; import java.util.Date; import java.util.List; +import protect.card_locker.importexport.MultiFormatExporter; +import protect.card_locker.importexport.MultiFormatImporter; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -72,7 +80,7 @@ public class ImportExportTest { String storeName = String.format("store, \"%4d", index); String note = String.format("note, \"%4d", index); - long id = db.insertLoyaltyCard(storeName, note, null, BARCODE_DATA, BARCODE_TYPE, index, 0); + long id = db.insertLoyaltyCard(storeName, note, null, new BigDecimal(String.valueOf(index)), null, BARCODE_DATA, BARCODE_TYPE, index, 0); boolean result = (id != -1); assertTrue(result); } @@ -88,7 +96,7 @@ public class ImportExportTest { String storeName = String.format("store, \"%4d", index); String note = String.format("note, \"%4d", index); - long id = db.insertLoyaltyCard(storeName, note, null, BARCODE_DATA, BARCODE_TYPE, index, 1); + long id = db.insertLoyaltyCard(storeName, note, null, new BigDecimal(String.valueOf(index)), null, BARCODE_DATA, BARCODE_TYPE, index, 1); boolean result = (id != -1); assertTrue(result); } @@ -97,33 +105,79 @@ public class ImportExportTest String storeName = String.format("store, \"%4d", index); String note = String.format("note, \"%4d", index); //if index is even - long id = db.insertLoyaltyCard(storeName, note, null, BARCODE_DATA, BARCODE_TYPE, index, 0); + long id = db.insertLoyaltyCard(storeName, note, null, new BigDecimal(String.valueOf(index)), null, BARCODE_DATA, BARCODE_TYPE, index, 0); boolean result = (id != -1); assertTrue(result); } assertEquals(cardsToAdd, db.getLoyaltyCardCount()); } - private void addLoyaltyCardsWithExpiryNeverPastTodayFuture() + @Test + public void addLoyaltyCardsWithExpiryNeverPastTodayFuture() { - long id = db.insertLoyaltyCard("No Expiry", "", null, BARCODE_DATA, BARCODE_TYPE, 0, 0); + long id = db.insertLoyaltyCard("No Expiry", "", null, new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, 0, 0); boolean result = (id != -1); assertTrue(result); - id = db.insertLoyaltyCard("Past", "", new Date((long) 1), BARCODE_DATA, BARCODE_TYPE, 0, 0); + LoyaltyCard card = db.getLoyaltyCard((int) id); + assertEquals("No Expiry", card.store); + assertEquals("", card.note); + assertEquals(null, card.expiry); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(null, card.balanceType); + assertEquals(BARCODE_DATA, card.cardId); + assertEquals(BARCODE_TYPE, card.barcodeType); + assertEquals(Integer.valueOf(0), card.headerColor); + assertEquals(0, card.starStatus); + + id = db.insertLoyaltyCard("Past", "", new Date((long) 1), new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, 0, 0); result = (id != -1); assertTrue(result); - id = db.insertLoyaltyCard("Today", "", new Date(), BARCODE_DATA, BARCODE_TYPE, 0, 0); + card = db.getLoyaltyCard((int) id); + assertEquals("Past", card.store); + assertEquals("", card.note); + assertTrue(card.expiry.before(new Date())); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(null, card.balanceType); + assertEquals(BARCODE_DATA, card.cardId); + assertEquals(BARCODE_TYPE, card.barcodeType); + assertEquals(Integer.valueOf(0), card.headerColor); + assertEquals(0, card.starStatus); + + id = db.insertLoyaltyCard("Today", "", new Date(), new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, 0, 0); result = (id != -1); assertTrue(result); + card = db.getLoyaltyCard((int) id); + assertEquals("Today", card.store); + assertEquals("", card.note); + assertTrue(card.expiry.before(new Date(new Date().getTime()+86400))); + assertTrue(card.expiry.after(new Date(new Date().getTime()-86400))); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(null, card.balanceType); + assertEquals(BARCODE_DATA, card.cardId); + assertEquals(BARCODE_TYPE, card.barcodeType); + assertEquals(Integer.valueOf(0), card.headerColor); + assertEquals(0, card.starStatus); + // This will break after 19 January 2038 // If someone is still maintaining this code base by then: I love you - id = db.insertLoyaltyCard("Future", "", new Date(2147483648L), BARCODE_DATA, BARCODE_TYPE, 0, 0); + id = db.insertLoyaltyCard("Future", "", new Date(2147483648000L), new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, 0, 0); result = (id != -1); assertTrue(result); + card = db.getLoyaltyCard((int) id); + assertEquals("Future", card.store); + assertEquals("", card.note); + assertTrue(card.expiry.after(new Date(new Date().getTime()+86400))); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(null, card.balanceType); + assertEquals(BARCODE_DATA, card.cardId); + assertEquals(BARCODE_TYPE, card.barcodeType); + assertEquals(Integer.valueOf(0), card.headerColor); + assertEquals(0, card.starStatus); + assertEquals(4, db.getLoyaltyCardCount()); } @@ -160,6 +214,8 @@ public class ImportExportTest assertEquals(expectedStore, card.store); assertEquals(expectedNote, card.note); + assertEquals(new BigDecimal(String.valueOf(index)), card.balance); + assertEquals(null, card.balanceType); assertEquals(BARCODE_DATA, card.cardId); assertEquals(BARCODE_TYPE, card.barcodeType); assertEquals(Integer.valueOf(index), card.headerColor); @@ -190,6 +246,8 @@ public class ImportExportTest assertEquals(expectedStore, card.store); assertEquals(expectedNote, card.note); + assertEquals(new BigDecimal(String.valueOf(index)), card.balance); + assertEquals(null, card.balanceType); assertEquals(BARCODE_DATA, card.cardId); assertEquals(BARCODE_TYPE, card.barcodeType); assertEquals(Integer.valueOf(index), card.headerColor); @@ -208,6 +266,8 @@ public class ImportExportTest assertEquals(expectedStore, card.store); assertEquals(expectedNote, card.note); + assertEquals(new BigDecimal(String.valueOf(index)), card.balance); + assertEquals(null, card.balanceType); assertEquals(BARCODE_DATA, card.cardId); assertEquals(BARCODE_TYPE, card.barcodeType); assertEquals(Integer.valueOf(index), card.headerColor); @@ -261,34 +321,30 @@ public class ImportExportTest { final int NUM_CARDS = 10; - for(DataFormat format : DataFormat.values()) - { - addLoyaltyCards(NUM_CARDS); + addLoyaltyCards(NUM_CARDS); - ByteArrayOutputStream outData = new ByteArrayOutputStream(); - OutputStreamWriter outStream = new OutputStreamWriter(outData); + ByteArrayOutputStream outData = new ByteArrayOutputStream(); + OutputStreamWriter outStream = new OutputStreamWriter(outData); - // Export data to CSV format - boolean result = MultiFormatExporter.exportData(db, outStream, format); - assertTrue(result); - outStream.close(); + // Export data to CSV format + boolean result = MultiFormatExporter.exportData(db, outStream, DataFormat.Catima); + assertTrue(result); + outStream.close(); - clearDatabase(); + clearDatabase(); - ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray()); - InputStreamReader inStream = new InputStreamReader(inData); + ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray()); - // Import the CSV data - result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV); - assertTrue(result); + // Import the CSV data + result = MultiFormatImporter.importData(db, inData, DataFormat.Catima); + assertTrue(result); - assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); + assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); - checkLoyaltyCards(); + checkLoyaltyCards(); - // Clear the database for the next format under test - clearDatabase(); - } + // Clear the database for the next format under test + clearDatabase(); } @Test @@ -296,34 +352,30 @@ public class ImportExportTest { final int NUM_CARDS = 9; - for(DataFormat format : DataFormat.values()) - { - addLoyaltyCardsFiveStarred(); + addLoyaltyCardsFiveStarred(); - ByteArrayOutputStream outData = new ByteArrayOutputStream(); - OutputStreamWriter outStream = new OutputStreamWriter(outData); + ByteArrayOutputStream outData = new ByteArrayOutputStream(); + OutputStreamWriter outStream = new OutputStreamWriter(outData); - // Export data to CSV format - boolean result = MultiFormatExporter.exportData(db, outStream, format); - assertTrue(result); - outStream.close(); + // Export data to CSV format + boolean result = MultiFormatExporter.exportData(db, outStream, DataFormat.Catima); + assertTrue(result); + outStream.close(); - clearDatabase(); + clearDatabase(); - ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray()); - InputStreamReader inStream = new InputStreamReader(inData); + ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray()); - // Import the CSV data - result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV); - assertTrue(result); + // Import the CSV data + result = MultiFormatImporter.importData(db, inData, DataFormat.Catima); + assertTrue(result); - assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); + assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); - checkLoyaltyCardsFiveStarred(); + checkLoyaltyCardsFiveStarred(); - // Clear the database for the next format under test - clearDatabase(); - } + // Clear the database for the next format under test + clearDatabase(); } private List groupsToGroupNames(List groups) @@ -343,77 +395,73 @@ public class ImportExportTest final int NUM_CARDS = 10; final int NUM_GROUPS = 3; - for(DataFormat format : DataFormat.values()) - { - addLoyaltyCards(NUM_CARDS); - addGroups(NUM_GROUPS); + addLoyaltyCards(NUM_CARDS); + addGroups(NUM_GROUPS); - List emptyGroup = new ArrayList<>(); + List emptyGroup = new ArrayList<>(); - List groupsForOne = new ArrayList<>(); - groupsForOne.add(db.getGroup("group, \" 1")); + List groupsForOne = new ArrayList<>(); + groupsForOne.add(db.getGroup("group, \" 1")); - List groupsForTwo = new ArrayList<>(); - groupsForTwo.add(db.getGroup("group, \" 1")); - groupsForTwo.add(db.getGroup("group, \" 2")); + List groupsForTwo = new ArrayList<>(); + groupsForTwo.add(db.getGroup("group, \" 1")); + groupsForTwo.add(db.getGroup("group, \" 2")); - List groupsForThree = new ArrayList<>(); - groupsForThree.add(db.getGroup("group, \" 1")); - groupsForThree.add(db.getGroup("group, \" 2")); - groupsForThree.add(db.getGroup("group, \" 3")); + List groupsForThree = new ArrayList<>(); + groupsForThree.add(db.getGroup("group, \" 1")); + groupsForThree.add(db.getGroup("group, \" 2")); + groupsForThree.add(db.getGroup("group, \" 3")); - List groupsForFour = new ArrayList<>(); - groupsForFour.add(db.getGroup("group, \" 1")); - groupsForFour.add(db.getGroup("group, \" 2")); - groupsForFour.add(db.getGroup("group, \" 3")); + List groupsForFour = new ArrayList<>(); + groupsForFour.add(db.getGroup("group, \" 1")); + groupsForFour.add(db.getGroup("group, \" 2")); + groupsForFour.add(db.getGroup("group, \" 3")); - List groupsForFive = new ArrayList<>(); - groupsForFive.add(db.getGroup("group, \" 1")); - groupsForFive.add(db.getGroup("group, \" 3")); + List groupsForFive = new ArrayList<>(); + groupsForFive.add(db.getGroup("group, \" 1")); + groupsForFive.add(db.getGroup("group, \" 3")); - db.setLoyaltyCardGroups(1, groupsForOne); - db.setLoyaltyCardGroups(2, groupsForTwo); - db.setLoyaltyCardGroups(3, groupsForThree); - db.setLoyaltyCardGroups(4, groupsForFour); - db.setLoyaltyCardGroups(5, groupsForFive); + db.setLoyaltyCardGroups(1, groupsForOne); + db.setLoyaltyCardGroups(2, groupsForTwo); + db.setLoyaltyCardGroups(3, groupsForThree); + db.setLoyaltyCardGroups(4, groupsForFour); + db.setLoyaltyCardGroups(5, groupsForFive); - ByteArrayOutputStream outData = new ByteArrayOutputStream(); - OutputStreamWriter outStream = new OutputStreamWriter(outData); + ByteArrayOutputStream outData = new ByteArrayOutputStream(); + OutputStreamWriter outStream = new OutputStreamWriter(outData); - // Export data to CSV format - boolean result = MultiFormatExporter.exportData(db, outStream, format); - assertTrue(result); - outStream.close(); + // Export data to CSV format + boolean result = MultiFormatExporter.exportData(db, outStream, DataFormat.Catima); + assertTrue(result); + outStream.close(); - clearDatabase(); + clearDatabase(); - ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray()); - InputStreamReader inStream = new InputStreamReader(inData); + ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray()); - // Import the CSV data - result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV); - assertTrue(result); + // Import the CSV data + result = MultiFormatImporter.importData(db, inData, DataFormat.Catima); + assertTrue(result); - assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); - assertEquals(NUM_GROUPS, db.getGroupCount()); + assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); + assertEquals(NUM_GROUPS, db.getGroupCount()); - checkLoyaltyCards(); - checkGroups(); + checkLoyaltyCards(); + checkGroups(); - assertEquals(groupsToGroupNames(groupsForOne), groupsToGroupNames(db.getLoyaltyCardGroups(1))); - assertEquals(groupsToGroupNames(groupsForTwo), groupsToGroupNames(db.getLoyaltyCardGroups(2))); - assertEquals(groupsToGroupNames(groupsForThree), groupsToGroupNames(db.getLoyaltyCardGroups(3))); - assertEquals(groupsToGroupNames(groupsForFour), groupsToGroupNames(db.getLoyaltyCardGroups(4))); - assertEquals(groupsToGroupNames(groupsForFive), groupsToGroupNames(db.getLoyaltyCardGroups(5))); - assertEquals(emptyGroup, db.getLoyaltyCardGroups(6)); - assertEquals(emptyGroup, db.getLoyaltyCardGroups(7)); - assertEquals(emptyGroup, db.getLoyaltyCardGroups(8)); - assertEquals(emptyGroup, db.getLoyaltyCardGroups(9)); - assertEquals(emptyGroup, db.getLoyaltyCardGroups(10)); + assertEquals(groupsToGroupNames(groupsForOne), groupsToGroupNames(db.getLoyaltyCardGroups(1))); + assertEquals(groupsToGroupNames(groupsForTwo), groupsToGroupNames(db.getLoyaltyCardGroups(2))); + assertEquals(groupsToGroupNames(groupsForThree), groupsToGroupNames(db.getLoyaltyCardGroups(3))); + assertEquals(groupsToGroupNames(groupsForFour), groupsToGroupNames(db.getLoyaltyCardGroups(4))); + assertEquals(groupsToGroupNames(groupsForFive), groupsToGroupNames(db.getLoyaltyCardGroups(5))); + assertEquals(emptyGroup, db.getLoyaltyCardGroups(6)); + assertEquals(emptyGroup, db.getLoyaltyCardGroups(7)); + assertEquals(emptyGroup, db.getLoyaltyCardGroups(8)); + assertEquals(emptyGroup, db.getLoyaltyCardGroups(9)); + assertEquals(emptyGroup, db.getLoyaltyCardGroups(10)); - // Clear the database for the next format under test - clearDatabase(); - } + // Clear the database for the next format under test + clearDatabase(); } @Test @@ -421,32 +469,28 @@ public class ImportExportTest { final int NUM_CARDS = 10; - for(DataFormat format : DataFormat.values()) - { - addLoyaltyCards(NUM_CARDS); + addLoyaltyCards(NUM_CARDS); - ByteArrayOutputStream outData = new ByteArrayOutputStream(); - OutputStreamWriter outStream = new OutputStreamWriter(outData); + ByteArrayOutputStream outData = new ByteArrayOutputStream(); + OutputStreamWriter outStream = new OutputStreamWriter(outData); - // Export into CSV data - boolean result = MultiFormatExporter.exportData(db, outStream, format); - assertTrue(result); - outStream.close(); + // Export into CSV data + boolean result = MultiFormatExporter.exportData(db, outStream, DataFormat.Catima); + assertTrue(result); + outStream.close(); - ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray()); - InputStreamReader inStream = new InputStreamReader(inData); + ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray()); - // Import the CSV data on top of the existing database - result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV); - assertTrue(result); + // Import the CSV data on top of the existing database + result = MultiFormatImporter.importData(db, inData, DataFormat.Catima); + assertTrue(result); - assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); + assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); - checkLoyaltyCards(); + checkLoyaltyCards(); - // Clear the database for the next format under test - clearDatabase(); - } + // Clear the database for the next format under test + clearDatabase(); } @Test @@ -462,7 +506,7 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export data to CSV format - boolean result = MultiFormatExporter.exportData(db, outStream, format); + boolean result = MultiFormatExporter.exportData(db, outStream, DataFormat.Catima); assertTrue(result); clearDatabase(); @@ -474,10 +518,9 @@ public class ImportExportTest String corruptEntry = "ThisStringIsLikelyNotPartOfAnyFormat,\"\"a"; ByteArrayInputStream inData = new ByteArrayInputStream((outData.toString() + corruptEntry).getBytes()); - InputStreamReader inStream = new InputStreamReader(inData); - // Attempt to import the CSV data - result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV); + // Attempt to import the data + result = MultiFormatImporter.importData(db, inData, format); assertEquals(false, result); assertEquals(0, db.getLoyaltyCardCount()); @@ -505,49 +548,46 @@ public class ImportExportTest final File sdcardDir = Environment.getExternalStorageDirectory(); final File exportFile = new File(sdcardDir, "Catima.csv"); - for(DataFormat format : DataFormat.values()) - { - addLoyaltyCards(NUM_CARDS); + addLoyaltyCards(NUM_CARDS); - TestTaskCompleteListener listener = new TestTaskCompleteListener(); + TestTaskCompleteListener listener = new TestTaskCompleteListener(); - // Export to the file - FileOutputStream fileOutputStream = new FileOutputStream(exportFile); - ImportExportTask task = new ImportExportTask(activity, format, fileOutputStream, listener); - task.execute(); + // Export to the file + FileOutputStream fileOutputStream = new FileOutputStream(exportFile); + ImportExportTask task = new ImportExportTask(activity, DataFormat.Catima, fileOutputStream, listener); + task.execute(); - // Actually run the task to completion - Robolectric.flushBackgroundThreadScheduler(); + // Actually run the task to completion + Robolectric.flushBackgroundThreadScheduler(); - // Check that the listener was executed - assertNotNull(listener.success); - assertEquals(true, listener.success); + // Check that the listener was executed + assertNotNull(listener.success); + assertEquals(true, listener.success); - clearDatabase(); + clearDatabase(); - // Import everything back from the default location + // Import everything back from the default location - listener = new TestTaskCompleteListener(); + listener = new TestTaskCompleteListener(); - FileInputStream fileStream = new FileInputStream(exportFile); + FileInputStream fileStream = new FileInputStream(exportFile); - task = new ImportExportTask(activity, format, fileStream, listener); - task.execute(); + task = new ImportExportTask(activity, DataFormat.Catima, fileStream, listener); + task.execute(); - // Actually run the task to completion - Robolectric.flushBackgroundThreadScheduler(); + // Actually run the task to completion + Robolectric.flushBackgroundThreadScheduler(); - // Check that the listener was executed - assertNotNull(listener.success); - assertEquals(true, listener.success); + // Check that the listener was executed + assertNotNull(listener.success); + assertEquals(true, listener.success); - assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); + assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); - checkLoyaltyCards(); + checkLoyaltyCards(); - // Clear the database for the next format under test - clearDatabase(); - } + // Clear the database for the next format under test + clearDatabase(); } @Test @@ -564,10 +604,9 @@ public class ImportExportTest csvText += "1,store,note,12345,type,0"; ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); - InputStreamReader inStream = new InputStreamReader(inputStream); // Import the CSV data - boolean result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV); + boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima); assertTrue(result); assertEquals(1, db.getLoyaltyCardCount()); @@ -576,6 +615,8 @@ public class ImportExportTest assertEquals("store", card.store); assertEquals("note", card.note); assertEquals(null, card.expiry); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(null, card.balanceType); assertEquals("12345", card.cardId); assertEquals("type", card.barcodeType); assertEquals(0, card.starStatus); @@ -600,10 +641,9 @@ public class ImportExportTest csvText += "1,store,note,12345,type,,,0"; ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); - InputStreamReader inStream = new InputStreamReader(inputStream); // Import the CSV data - boolean result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV); + boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima); assertTrue(result); assertEquals(1, db.getLoyaltyCardCount()); @@ -612,6 +652,8 @@ public class ImportExportTest assertEquals("store", card.store); assertEquals("note", card.note); assertEquals(null, card.expiry); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(null, card.balanceType); assertEquals("12345", card.cardId); assertEquals("type", card.barcodeType); assertEquals(0, card.starStatus); @@ -636,10 +678,9 @@ public class ImportExportTest csvText += "1,store,note,12345,type,not a number,invalid,0"; ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); - InputStreamReader inStream = new InputStreamReader(inputStream); // Import the CSV data - boolean result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV); + boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima); assertEquals(false, result); assertEquals(0, db.getLoyaltyCardCount()); @@ -662,10 +703,9 @@ public class ImportExportTest csvText += "1,store,note,12345,,1,1,0"; ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); - InputStreamReader inStream = new InputStreamReader(inputStream); // Import the CSV data - boolean result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV); + boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima); assertEquals(true, result); assertEquals(1, db.getLoyaltyCardCount()); @@ -674,6 +714,8 @@ public class ImportExportTest assertEquals("store", card.store); assertEquals("note", card.note); assertEquals(null, card.expiry); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(null, card.balanceType); assertEquals("12345", card.cardId); assertEquals("", card.barcodeType); assertEquals(0, card.starStatus); @@ -698,10 +740,9 @@ public class ImportExportTest csvText += "1,store,note,12345,type,1,1,1"; ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); - InputStreamReader inStream = new InputStreamReader(inputStream); // Import the CSV data - boolean result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV); + boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima); assertEquals(true, result); assertEquals(1, db.getLoyaltyCardCount()); @@ -710,6 +751,8 @@ public class ImportExportTest assertEquals("store", card.store); assertEquals("note", card.note); assertEquals(null, card.expiry); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(null, card.balanceType); assertEquals("12345", card.cardId); assertEquals("type", card.barcodeType); assertEquals(1, card.starStatus); @@ -734,10 +777,9 @@ public class ImportExportTest csvText += "1,store,note,12345,type,1,1,"; ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); - InputStreamReader inStream = new InputStreamReader(inputStream); // Import the CSV data - boolean result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV); + boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima); assertEquals(true, result); assertEquals(1, db.getLoyaltyCardCount()); @@ -746,6 +788,8 @@ public class ImportExportTest assertEquals("store", card.store); assertEquals("note", card.note); assertEquals(null, card.expiry); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(null, card.balanceType); assertEquals("12345", card.cardId); assertEquals("type", card.barcodeType); assertEquals(0, card.starStatus); @@ -770,10 +814,9 @@ public class ImportExportTest csvText += "1,store,note,12345,type,1,1,2"; ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); - InputStreamReader inStream = new InputStreamReader(inputStream); // Import the CSV data - boolean result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV); + boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima); assertTrue(result); assertEquals(1, db.getLoyaltyCardCount()); @@ -790,10 +833,9 @@ public class ImportExportTest csvText += "1,store,note,12345,type,1,1,text"; inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); - inStream = new InputStreamReader(inputStream); // Import the CSV data - result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV); + result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima); assertTrue(result); assertEquals(1, db.getLoyaltyCardCount()); @@ -802,6 +844,8 @@ public class ImportExportTest assertEquals("store", card.store); assertEquals("note", card.note); assertEquals(null, card.expiry); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(null, card.balanceType); assertEquals("12345", card.cardId); assertEquals("type", card.barcodeType); assertEquals(0, card.starStatus); @@ -810,51 +854,62 @@ public class ImportExportTest clearDatabase(); } + @Test + public void importVoucherVault() throws IOException, FormatException, JSONException, ParseException { + String jsonText = "[\n" + + " {\n" + + " \"uuid\": \"ae1ae525-3f27-481e-853a-8c30b7fa12d8\",\n" + + " \"description\": \"Clothes Store\",\n" + + " \"code\": \"123456\",\n" + + " \"codeType\": \"CODE128\",\n" + + " \"expires\": null,\n" + + " \"removeOnceExpired\": true,\n" + + " \"balance\": null,\n" + + " \"color\": \"GREY\"\n" + + " },\n" + + " {\n" + + " \"uuid\": \"29a5d3b3-eace-4311-a15c-4c7e6a010531\",\n" + + " \"description\": \"Department Store\",\n" + + " \"code\": \"26846363\",\n" + + " \"codeType\": \"CODE39\",\n" + + " \"expires\": \"2021-03-26T00:00:00.000\",\n" + + " \"removeOnceExpired\": true,\n" + + " \"balance\": 3.5,\n" + + " \"color\": \"PURPLE\"\n" + + " }\n" + + "]"; - private void checkLoyaltyCardsExpiry() - { - Cursor cursor = db.getLoyaltyCardCursor(); - cursor.moveToNext(); - LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor); - assertEquals("Never", card.store); + ByteArrayInputStream inputStream = new ByteArrayInputStream(jsonText.getBytes(StandardCharsets.UTF_8)); + + // Import the Voucher Vault data + boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.VoucherVault); + assertTrue(result); + assertEquals(2, db.getLoyaltyCardCount()); + + LoyaltyCard card = db.getLoyaltyCard(1); + + assertEquals("Clothes Store", card.store); assertEquals("", card.note); assertEquals(null, card.expiry); - assertEquals(BARCODE_DATA, card.cardId); - assertEquals(BARCODE_TYPE, card.barcodeType); - assertEquals(Integer.valueOf(0), card.headerColor); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(Currency.getInstance("USD"), card.balanceType); + assertEquals("123456", card.cardId); + assertEquals(BarcodeFormat.CODE_128.name(), card.barcodeType); assertEquals(0, card.starStatus); + assertEquals(Color.GRAY, (long) card.headerColor); - cursor.moveToNext(); - card = LoyaltyCard.toLoyaltyCard(cursor); - assertEquals("Past", card.store); + card = db.getLoyaltyCard(2); + + assertEquals("Department Store", card.store); assertEquals("", card.note); - assertTrue(card.expiry.before(new Date())); - assertEquals(BARCODE_DATA, card.cardId); - assertEquals(BARCODE_TYPE, card.barcodeType); - assertEquals(Integer.valueOf(0), card.headerColor); + assertEquals(new Date(1616716800000L), card.expiry); + assertEquals(new BigDecimal("3.5"), card.balance); + assertEquals(Currency.getInstance("USD"), card.balanceType); + assertEquals("26846363", card.cardId); + assertEquals(BarcodeFormat.CODE_39.name(), card.barcodeType); assertEquals(0, card.starStatus); + assertEquals(Color.rgb(128, 0, 128), (long) card.headerColor); - cursor.moveToNext(); - card = LoyaltyCard.toLoyaltyCard(cursor); - assertEquals("Today", card.store); - assertEquals("", card.note); - assertTrue(card.expiry.before(new Date(new Date().getTime()+86400))); - assertTrue(card.expiry.after(new Date(new Date().getTime()-86400))); - assertEquals(BARCODE_DATA, card.cardId); - assertEquals(BARCODE_TYPE, card.barcodeType); - assertEquals(Integer.valueOf(0), card.headerColor); - assertEquals(0, card.starStatus); - - cursor.moveToNext(); - card = LoyaltyCard.toLoyaltyCard(cursor); - assertEquals("Future", card.store); - assertEquals("", card.note); - assertTrue(card.expiry.after(new Date(new Date().getTime()+86400))); - assertEquals(BARCODE_DATA, card.cardId); - assertEquals(BARCODE_TYPE, card.barcodeType); - assertEquals(Integer.valueOf(0), card.headerColor); - assertEquals(0, card.starStatus); - - cursor.close(); + clearDatabase(); } } diff --git a/app/src/test/java/protect/card_locker/ImportURITest.java b/app/src/test/java/protect/card_locker/ImportURITest.java index 02bd27462..2b29c47cb 100644 --- a/app/src/test/java/protect/card_locker/ImportURITest.java +++ b/app/src/test/java/protect/card_locker/ImportURITest.java @@ -11,6 +11,8 @@ import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import java.io.InvalidObjectException; +import java.math.BigDecimal; +import java.util.Currency; import java.util.Date; import static org.junit.Assert.assertEquals; @@ -38,7 +40,7 @@ public class ImportURITest { // Generate card Date date = new Date(); - db.insertLoyaltyCard("store", "note", date, BarcodeFormat.UPC_A.toString(), LoyaltyCardDbIds.BARCODE_TYPE, Color.BLACK, 1); + db.insertLoyaltyCard("store", "note", date, new BigDecimal("100"), null, BarcodeFormat.UPC_A.toString(), LoyaltyCardDbIds.BARCODE_TYPE, Color.BLACK, 1); // Get card LoyaltyCard card = db.getLoyaltyCard(1); @@ -55,6 +57,8 @@ public class ImportURITest { assertEquals(card.headerColor, parsedCard.headerColor); assertEquals(card.note, parsedCard.note); assertEquals(card.expiry, parsedCard.expiry); + assertEquals(card.balance, parsedCard.balance); + assertEquals(card.balanceType, parsedCard.balanceType); assertEquals(card.store, parsedCard.store); // No export of starStatus for single cards foreseen therefore 0 will be imported assertEquals(0, parsedCard.starStatus); @@ -64,7 +68,7 @@ public class ImportURITest { public void ensureNoCrashOnMissingHeaderFields() throws InvalidObjectException { // Generate card - db.insertLoyaltyCard("store", "note", null, BarcodeFormat.UPC_A.toString(), LoyaltyCardDbIds.BARCODE_TYPE, null, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("10.00"), Currency.getInstance("EUR"), BarcodeFormat.UPC_A.toString(), LoyaltyCardDbIds.BARCODE_TYPE, null, 0); // Get card LoyaltyCard card = db.getLoyaltyCard(1); @@ -80,6 +84,8 @@ public class ImportURITest { assertEquals(card.cardId, parsedCard.cardId); assertEquals(card.note, parsedCard.note); assertEquals(card.expiry, parsedCard.expiry); + assertEquals(card.balance, parsedCard.balance); + assertEquals(card.balanceType, parsedCard.balanceType); assertEquals(card.store, parsedCard.store); assertNull(parsedCard.headerColor); assertNull(parsedCard.headerTextColor); @@ -125,5 +131,8 @@ public class ImportURITest { assertEquals("store", parsedCard.store); assertEquals(Integer.valueOf(-416706), parsedCard.headerColor); assertEquals(0, parsedCard.starStatus); + assertEquals(null, parsedCard.expiry); + assertEquals(new BigDecimal("0"), parsedCard.balance); + assertEquals(null, parsedCard.balanceType); } } diff --git a/app/src/test/java/protect/card_locker/LoyaltyCardCursorAdapterTest.java b/app/src/test/java/protect/card_locker/LoyaltyCardCursorAdapterTest.java index 3d8c25e2a..8055e8026 100644 --- a/app/src/test/java/protect/card_locker/LoyaltyCardCursorAdapterTest.java +++ b/app/src/test/java/protect/card_locker/LoyaltyCardCursorAdapterTest.java @@ -21,7 +21,9 @@ import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import java.math.BigDecimal; import java.text.DateFormat; +import java.util.Currency; import java.util.Date; import static org.junit.Assert.assertEquals; @@ -60,11 +62,12 @@ public class LoyaltyCardCursorAdapterTest return view; } - private void checkView(final View view, final String store, final String note, final String expiry, boolean checkFontSizes) + private void checkView(final View view, final String store, final String note, final String expiry, final String balance, boolean checkFontSizes) { final TextView storeField = view.findViewById(R.id.store); final TextView noteField = view.findViewById(R.id.note); final TextView expiryField = view.findViewById(R.id.expiry); + final TextView balanceField = view.findViewById(R.id.balance); if(checkFontSizes) { @@ -77,7 +80,7 @@ public class LoyaltyCardCursorAdapterTest } assertEquals(store, storeField.getText().toString()); - if(note.isEmpty() == false) + if(!note.isEmpty()) { assertEquals(View.VISIBLE, noteField.getVisibility()); assertEquals(note, noteField.getText().toString()); @@ -87,7 +90,7 @@ public class LoyaltyCardCursorAdapterTest assertEquals(View.GONE, noteField.getVisibility()); } - if(expiry.isEmpty() == false) + if(!expiry.isEmpty()) { assertEquals(View.VISIBLE, expiryField.getVisibility()); assertEquals(expiry, expiryField.getText().toString()); @@ -96,13 +99,23 @@ public class LoyaltyCardCursorAdapterTest { assertEquals(View.GONE, expiryField.getVisibility()); } + + if(!balance.isEmpty()) + { + assertEquals(View.VISIBLE, balanceField.getVisibility()); + assertEquals(balance, balanceField.getText().toString()); + } + else + { + assertEquals(View.GONE, balanceField.getVisibility()); + } } @Test public void TestCursorAdapterEmptyNote() { - db.insertLoyaltyCard("store", "", null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); + db.insertLoyaltyCard("store", "", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); LoyaltyCard card = db.getLoyaltyCard(1); Cursor cursor = db.getLoyaltyCardCursor(); @@ -110,7 +123,7 @@ public class LoyaltyCardCursorAdapterTest View view = createView(cursor); - checkView(view, card.store, card.note, "", false); + checkView(view, card.store, card.note, "", "",false); cursor.close(); } @@ -118,7 +131,7 @@ public class LoyaltyCardCursorAdapterTest @Test public void TestCursorAdapterWithNote() { - db.insertLoyaltyCard("store", "note", null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); LoyaltyCard card = db.getLoyaltyCard(1); Cursor cursor = db.getLoyaltyCardCursor(); @@ -126,7 +139,7 @@ public class LoyaltyCardCursorAdapterTest View view = createView(cursor); - checkView(view, card.store, card.note, "", false); + checkView(view, card.store, card.note, "", "",false); cursor.close(); } @@ -138,7 +151,7 @@ public class LoyaltyCardCursorAdapterTest Date expiryDate = new Date(); String dateString = context.getString(R.string.expiryStateSentence, DateFormat.getDateInstance(DateFormat.LONG).format(expiryDate)); - db.insertLoyaltyCard("store", "note", expiryDate, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", expiryDate, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); LoyaltyCard card = db.getLoyaltyCard(1); Cursor cursor = db.getLoyaltyCardCursor(); @@ -147,11 +160,11 @@ public class LoyaltyCardCursorAdapterTest setFontSizes(1, 2); View view = createView(cursor); - checkView(view, card.store, card.note, dateString, true); + checkView(view, card.store, card.note, dateString, "", true); setFontSizes(30, 31); view = createView(cursor); - checkView(view, card.store, card.note, dateString, true); + checkView(view, card.store, card.note, dateString, "",true); cursor.close(); } @@ -159,9 +172,9 @@ public class LoyaltyCardCursorAdapterTest @Test public void TestCursorAdapterStarring() { - db.insertLoyaltyCard("storeA", "note", null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); - db.insertLoyaltyCard("storeB", "note", null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 1); - db.insertLoyaltyCard("storeC", "note", null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 1); + db.insertLoyaltyCard("storeA", "note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); + db.insertLoyaltyCard("storeB", "note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 1); + db.insertLoyaltyCard("storeC", "note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 1); Cursor cursor = db.getLoyaltyCardCursor(); cursor.moveToFirst(); @@ -181,4 +194,68 @@ public class LoyaltyCardCursorAdapterTest cursor.close(); } + + @Test + public void TestCursorAdapter0Points() + { + db.insertLoyaltyCard("store", "", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); + LoyaltyCard card = db.getLoyaltyCard(1); + + Cursor cursor = db.getLoyaltyCardCursor(); + cursor.moveToFirst(); + + View view = createView(cursor); + + checkView(view, card.store, card.note, "", "",false); + + cursor.close(); + } + + @Test + public void TestCursorAdapter0EUR() + { + db.insertLoyaltyCard("store", "", null, new BigDecimal("0"), Currency.getInstance("EUR"), "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); + LoyaltyCard card = db.getLoyaltyCard(1); + + Cursor cursor = db.getLoyaltyCardCursor(); + cursor.moveToFirst(); + + View view = createView(cursor); + + checkView(view, card.store, card.note, "", "",false); + + cursor.close(); + } + + @Test + public void TestCursorAdapter100Points() + { + db.insertLoyaltyCard("store", "note", null, new BigDecimal("100"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); + LoyaltyCard card = db.getLoyaltyCard(1); + + Cursor cursor = db.getLoyaltyCardCursor(); + cursor.moveToFirst(); + + View view = createView(cursor); + + checkView(view, card.store, card.note, "", "Balance: 100 points",false); + + cursor.close(); + } + + @Test + public void TestCursorAdapter10USD() + { + db.insertLoyaltyCard("store", "note", null, new BigDecimal("10.00"), Currency.getInstance("USD"), "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); + LoyaltyCard card = db.getLoyaltyCard(1); + + Cursor cursor = db.getLoyaltyCardCursor(); + cursor.moveToFirst(); + + View view = createView(cursor); + + checkView(view, card.store, card.note, "", "Balance: $10.00",false); + + cursor.close(); + } } diff --git a/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java b/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java index d203f0393..3fa5926ae 100644 --- a/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java +++ b/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java @@ -39,7 +39,9 @@ import com.google.android.material.textfield.TextInputLayout; import com.google.zxing.BarcodeFormat; import com.google.zxing.client.android.Intents; import java.io.IOException; +import java.math.BigDecimal; import java.text.DateFormat; +import java.util.Currency; import java.util.Date; import org.junit.Before; import org.junit.Test; @@ -108,6 +110,8 @@ public class LoyaltyCardViewActivityTest private void saveLoyaltyCardWithArguments(final Activity activity, final String store, final String note, final Date expiry, + final BigDecimal balance, + final Currency balanceType, final String cardId, final String barcodeType, boolean creatingNewCard) @@ -125,12 +129,20 @@ public class LoyaltyCardViewActivityTest final EditText storeField = activity.findViewById(R.id.storeNameEdit); final EditText noteField = activity.findViewById(R.id.noteEdit); final TextInputLayout expiryView = activity.findViewById(R.id.expiryView); + final EditText balanceView = activity.findViewById(R.id.balanceField); + final EditText balanceCurrencyField = activity.findViewById(R.id.balanceCurrencyField); final TextView cardIdField = activity.findViewById(R.id.cardIdView); final TextView barcodeTypeField = activity.findViewById(R.id.barcodeTypeField); storeField.setText(store); noteField.setText(note); expiryView.setTag(expiry); + if (balance != null) { + balanceView.setText(balance.toPlainString()); + } + if (balanceType != null) { + balanceCurrencyField.setText(balanceType.getSymbol()); + } cardIdField.setText(cardId); barcodeTypeField.setText(barcodeType); @@ -143,6 +155,13 @@ public class LoyaltyCardViewActivityTest LoyaltyCard card = db.getLoyaltyCard(1); assertEquals(store, card.store); assertEquals(note, card.note); + assertEquals(expiry, card.expiry); + if (balance != null) { + assertEquals(balance, card.balance); + } else { + assertEquals(new BigDecimal("0"), card.balance); + } + assertEquals(balanceType, card.balanceType); assertEquals(cardId, card.cardId); // The special "No barcode" string shouldn't actually be written to the loyalty card @@ -253,7 +272,9 @@ public class LoyaltyCardViewActivityTest } private void checkAllFields(final Activity activity, ViewMode mode, - final String store, final String note, final String expiryString, final String cardId, final String barcodeType) + final String store, final String note, final String expiryString, + final String balanceString, final String balanceTypeString, + final String cardId, final String barcodeType) { if(mode == ViewMode.VIEW_CARD) { @@ -266,6 +287,8 @@ public class LoyaltyCardViewActivityTest checkFieldProperties(activity, R.id.storeNameEdit, editVisibility, store); checkFieldProperties(activity, R.id.noteEdit, editVisibility, note); checkFieldProperties(activity, R.id.expiryView, editVisibility, expiryString); + checkFieldProperties(activity, R.id.balanceField, editVisibility, balanceString); + checkFieldProperties(activity, R.id.balanceCurrencyField, editVisibility, balanceTypeString); checkFieldProperties(activity, R.id.cardIdView, View.VISIBLE, cardId); checkFieldProperties(activity, R.id.barcodeTypeField, View.VISIBLE, barcodeType); checkFieldProperties(activity, R.id.barcode, View.VISIBLE, null); @@ -283,7 +306,7 @@ public class LoyaltyCardViewActivityTest Activity activity = (Activity)activityController.get(); final Context context = ApplicationProvider.getApplicationContext(); - checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never) , "", ""); + checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never) , "0", context.getString(R.string.points), "", ""); } @Test @@ -301,7 +324,6 @@ public class LoyaltyCardViewActivityTest final EditText storeField = activity.findViewById(R.id.storeNameEdit); final EditText noteField = activity.findViewById(R.id.noteEdit); - final TextView cardIdField = activity.findViewById(R.id.cardIdView); activity.findViewById(R.id.fabSave).performClick(); assertEquals(0, db.getLoyaltyCardCount()); @@ -345,15 +367,17 @@ public class LoyaltyCardViewActivityTest Activity activity = (Activity)activityController.get(); final Context context = ApplicationProvider.getApplicationContext(); - checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never), "", ""); + checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never), "0", context.getString(R.string.points), "", ""); // Complete barcode capture successfully captureBarcodeWithResult(activity, true); - checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never), BARCODE_DATA, BARCODE_TYPE); + checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, BARCODE_TYPE); + + shadowOf(getMainLooper()).idle(); // Save and check the loyalty card - saveLoyaltyCardWithArguments(activity, "store", "note", null, BARCODE_DATA, BARCODE_TYPE, true); + saveLoyaltyCardWithArguments(activity, "store", "note", null, null, null, BARCODE_DATA, BARCODE_TYPE, true); } @Test @@ -367,12 +391,12 @@ public class LoyaltyCardViewActivityTest Activity activity = (Activity)activityController.get(); final Context context = ApplicationProvider.getApplicationContext(); - checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never), "", ""); + checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never), "0", context.getString(R.string.points), "", ""); // Complete barcode capture in failure captureBarcodeWithResult(activity, false); - checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never), "", ""); + checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never), "0", context.getString(R.string.points), "", ""); } @Test @@ -386,12 +410,12 @@ public class LoyaltyCardViewActivityTest LoyaltyCardEditActivity activity = (LoyaltyCardEditActivity) activityController.get(); final Context context = ApplicationProvider.getApplicationContext(); - checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never), "", ""); + checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never), "0", context.getString(R.string.points), "", ""); // Complete barcode capture successfully captureBarcodeWithResult(activity, true); - checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never), BARCODE_DATA, BARCODE_TYPE); + checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, BARCODE_TYPE); // Cancel the loyalty card creation assertEquals(false, activity.isFinishing()); @@ -438,15 +462,16 @@ public class LoyaltyCardViewActivityTest { ActivityController activityController = createActivityWithLoyaltyCard(true); Activity activity = (Activity)activityController.get(); + final Context context = ApplicationProvider.getApplicationContext(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); activityController.start(); activityController.visible(); activityController.resume(); - checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, BARCODE_DATA, BARCODE_TYPE); + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, "0", context.getString(R.string.points), BARCODE_DATA, BARCODE_TYPE); db.close(); } @@ -456,15 +481,16 @@ public class LoyaltyCardViewActivityTest { ActivityController activityController = createActivityWithLoyaltyCard(false); Activity activity = (Activity)activityController.get(); + final Context context = ApplicationProvider.getApplicationContext(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); activityController.start(); activityController.visible(); activityController.resume(); - checkAllFields(activity, ViewMode.VIEW_CARD, "store", "note", null, BARCODE_DATA, BARCODE_TYPE); + checkAllFields(activity, ViewMode.VIEW_CARD, "store", "note", null, "0", context.getString(R.string.points), BARCODE_DATA, BARCODE_TYPE); db.close(); } @@ -474,20 +500,21 @@ public class LoyaltyCardViewActivityTest { ActivityController activityController = createActivityWithLoyaltyCard(true); Activity activity = (Activity)activityController.get(); + final Context context = ApplicationProvider.getApplicationContext(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, 0); activityController.start(); activityController.visible(); activityController.resume(); - checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, EAN_BARCODE_DATA, EAN_BARCODE_TYPE); + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, "0", context.getString(R.string.points), EAN_BARCODE_DATA, EAN_BARCODE_TYPE); // Complete barcode capture successfully captureBarcodeWithResult(activity, true); - checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, BARCODE_DATA, BARCODE_TYPE); + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, "0", context.getString(R.string.points), BARCODE_DATA, BARCODE_TYPE); db.close(); } @@ -497,20 +524,21 @@ public class LoyaltyCardViewActivityTest { ActivityController activityController = createActivityWithLoyaltyCard(true); LoyaltyCardEditActivity activity = (LoyaltyCardEditActivity) activityController.get(); + final Context context = ApplicationProvider.getApplicationContext(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, 0); activityController.start(); activityController.visible(); activityController.resume(); - checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, EAN_BARCODE_DATA, EAN_BARCODE_TYPE); + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, "0", context.getString(R.string.points), EAN_BARCODE_DATA, EAN_BARCODE_TYPE); // Complete barcode capture successfully captureBarcodeWithResult(activity, true); - checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, BARCODE_DATA, BARCODE_TYPE); + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, "0", context.getString(R.string.points), BARCODE_DATA, BARCODE_TYPE); // Cancel the loyalty card creation assertEquals(false, activity.isFinishing()); @@ -537,13 +565,13 @@ public class LoyaltyCardViewActivityTest final Context context = ApplicationProvider.getApplicationContext(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, 0); activityController.start(); activityController.visible(); activityController.resume(); - checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), EAN_BARCODE_DATA, EAN_BARCODE_TYPE); + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), EAN_BARCODE_DATA, EAN_BARCODE_TYPE); // Set date to today MaterialAutoCompleteTextView expiryField = activity.findViewById(R.id.expiryField); @@ -557,7 +585,7 @@ public class LoyaltyCardViewActivityTest shadowOf(getMainLooper()).idle(); - checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", DateFormat.getDateInstance(DateFormat.LONG).format(new Date()), EAN_BARCODE_DATA, EAN_BARCODE_TYPE); + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", DateFormat.getDateInstance(DateFormat.LONG).format(new Date()), "0", context.getString(R.string.points), EAN_BARCODE_DATA, EAN_BARCODE_TYPE); db.close(); } @@ -570,19 +598,111 @@ public class LoyaltyCardViewActivityTest final Context context = ApplicationProvider.getApplicationContext(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", new Date(), EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", new Date(), new BigDecimal("0"), null, EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, 0); activityController.start(); activityController.visible(); activityController.resume(); - checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", DateFormat.getDateInstance(DateFormat.LONG).format(new Date()), EAN_BARCODE_DATA, EAN_BARCODE_TYPE); + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", DateFormat.getDateInstance(DateFormat.LONG).format(new Date()), "0", context.getString(R.string.points), EAN_BARCODE_DATA, EAN_BARCODE_TYPE); // Set date to never MaterialAutoCompleteTextView expiryField = activity.findViewById(R.id.expiryField); expiryField.setText(expiryField.getAdapter().getItem(0).toString(), false); - checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), EAN_BARCODE_DATA, EAN_BARCODE_TYPE); + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), EAN_BARCODE_DATA, EAN_BARCODE_TYPE); + + db.close(); + } + + @Test + public void startWithLoyaltyCardNoBalanceSetBalance() throws IOException + { + ActivityController activityController = createActivityWithLoyaltyCard(true); + Activity activity = (Activity)activityController.get(); + final Context context = ApplicationProvider.getApplicationContext(); + DBHelper db = new DBHelper(activity); + + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, 0); + + activityController.start(); + activityController.visible(); + activityController.resume(); + + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), EAN_BARCODE_DATA, EAN_BARCODE_TYPE); + + // Set balance to 10 points + EditText balanceField = activity.findViewById(R.id.balanceField); + balanceField.setText("10"); + + shadowOf(getMainLooper()).idle(); + + // Change points to EUR + MaterialAutoCompleteTextView balanceTypeField = activity.findViewById(R.id.balanceCurrencyField); + balanceTypeField.setText("€", false); + + shadowOf(getMainLooper()).idle(); + + // Ensure the balance is reformatted for EUR when focus is cleared + shadowOf(getMainLooper()).idle(); + balanceField.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + assertEquals("10.00", balanceField.getText().toString()); + + shadowOf(getMainLooper()).idle(); + + DatePickerDialog datePickerDialog = (DatePickerDialog) (ShadowDialog.getLatestDialog()); + assertNotNull(datePickerDialog); + datePickerDialog.getButton(DatePickerDialog.BUTTON_POSITIVE).performClick(); + + shadowOf(getMainLooper()).idle(); + + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", DateFormat.getDateInstance(DateFormat.LONG).format(new Date()), "10.00", "€", EAN_BARCODE_DATA, EAN_BARCODE_TYPE); + + db.close(); + } + }); + balanceField.clearFocus(); + } + + @Test + public void startWithLoyaltyCardBalanceSetNoBalance() throws IOException + { + ActivityController activityController = createActivityWithLoyaltyCard(true); + Activity activity = (Activity)activityController.get(); + final Context context = ApplicationProvider.getApplicationContext(); + DBHelper db = new DBHelper(activity); + + db.insertLoyaltyCard("store", "note", null, new BigDecimal("10.00"), Currency.getInstance("USD"), EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, 0); + + activityController.start(); + activityController.visible(); + activityController.resume(); + + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "10.00", "$", EAN_BARCODE_DATA, EAN_BARCODE_TYPE); + + shadowOf(getMainLooper()).idle(); + + // Change EUR to WON + MaterialAutoCompleteTextView balanceTypeField = activity.findViewById(R.id.balanceCurrencyField); + balanceTypeField.setText("₩", false); + + shadowOf(getMainLooper()).idle(); + + // Ensure the balance is reformatted for WON when focus is cleared + EditText balanceField = activity.findViewById(R.id.balanceField); + balanceField.clearFocus(); + assertEquals("10", balanceField.getText().toString()); + + shadowOf(getMainLooper()).idle(); + + // Set the balance to 0 + balanceField.setText("0"); + + shadowOf(getMainLooper()).idle(); + + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", "₩", EAN_BARCODE_DATA, EAN_BARCODE_TYPE); db.close(); } @@ -594,7 +714,7 @@ public class LoyaltyCardViewActivityTest Activity activity = (Activity)activityController.get(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); activityController.start(); activityController.visible(); @@ -642,7 +762,7 @@ public class LoyaltyCardViewActivityTest Activity activity = (Activity)activityController.get(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); activityController.start(); activityController.visible(); @@ -662,7 +782,7 @@ public class LoyaltyCardViewActivityTest Activity activity = (Activity)activityController.get(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, BARCODE_DATA, BARCODE_TYPE, null, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, null, 0); activityController.start(); activityController.visible(); @@ -682,14 +802,14 @@ public class LoyaltyCardViewActivityTest Activity activity = (Activity)activityController.get(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, BARCODE_DATA, BARCODE_TYPE, null, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, null, 0); activityController.start(); activityController.visible(); activityController.resume(); // Save and check the loyalty card - saveLoyaltyCardWithArguments(activity, "store", "note", null, BARCODE_DATA, BARCODE_TYPE, false); + saveLoyaltyCardWithArguments(activity, "store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, false); db.close(); } @@ -701,14 +821,14 @@ public class LoyaltyCardViewActivityTest Activity activity = (Activity)activityController.get(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, BARCODE_DATA, "", Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, "", Color.BLACK, 0); activityController.start(); activityController.visible(); activityController.resume(); // Save and check the loyalty card - saveLoyaltyCardWithArguments(activity, "store", "note", null, BARCODE_DATA, activity.getApplicationContext().getString(R.string.noBarcode), false); + saveLoyaltyCardWithArguments(activity, "store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, activity.getApplicationContext().getString(R.string.noBarcode), false); db.close(); } @@ -721,24 +841,24 @@ public class LoyaltyCardViewActivityTest final Context context = ApplicationProvider.getApplicationContext(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); activityController.start(); activityController.visible(); activityController.resume(); // First check if the card is as expected - checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), BARCODE_DATA, BARCODE_TYPE); + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, BARCODE_TYPE); // Complete empty barcode selection successfully selectBarcodeWithResult(activity, BARCODE_DATA, "", true); // Check if the barcode type is NO_BARCODE as expected - checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), BARCODE_DATA, activity.getApplicationContext().getString(R.string.noBarcode)); + checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, activity.getApplicationContext().getString(R.string.noBarcode)); assertEquals(View.GONE, activity.findViewById(R.id.barcodeLayout).getVisibility()); // Check if the special NO_BARCODE string doesn't get saved - saveLoyaltyCardWithArguments(activity, "store", "note", null, BARCODE_DATA, activity.getApplicationContext().getString(R.string.noBarcode), false); + saveLoyaltyCardWithArguments(activity, "store", "note", null, null, null, BARCODE_DATA, activity.getApplicationContext().getString(R.string.noBarcode), false); db.close(); } @@ -750,7 +870,7 @@ public class LoyaltyCardViewActivityTest Activity activity = (Activity)activityController.get(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); final int STORE_FONT_SIZE = 50; final int CARD_FONT_SIZE = 40; @@ -790,7 +910,7 @@ public class LoyaltyCardViewActivityTest Activity activity = (Activity)activityController.get(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity); settings.edit() @@ -827,7 +947,7 @@ public class LoyaltyCardViewActivityTest Activity activity = (Activity) activityController.get(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK,0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK,0); activityController.start(); activityController.visible(); activityController.resume(); @@ -862,7 +982,7 @@ public class LoyaltyCardViewActivityTest Activity activity = (Activity)activityController.get(); DBHelper db = new DBHelper(activity); - db.insertLoyaltyCard("store", "note", null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0); activityController.start(); activityController.visible(); @@ -960,7 +1080,7 @@ public class LoyaltyCardViewActivityTest { Date date = new Date(); - Uri importUri = Uri.parse("https://thelastproject.github.io/Catima/share?store=Example%20Store¬e=&expiry=" + date.getTime() + "&cardid=123456&barcodetype=AZTEC&headercolor=-416706&headertextcolor=-1"); + Uri importUri = Uri.parse("https://thelastproject.github.io/Catima/share?store=Example%20Store¬e=&expiry=" + date.getTime() + "&balance=10&balancetype=USD&cardid=123456&barcodetype=AZTEC&headercolor=-416706&headertextcolor=-1"); Intent intent = new Intent(); intent.setData(importUri); @@ -974,7 +1094,9 @@ public class LoyaltyCardViewActivityTest Activity activity = (Activity)activityController.get(); final Context context = ApplicationProvider.getApplicationContext(); - checkAllFields(activity, ViewMode.ADD_CARD, "Example Store", "", DateFormat.getDateInstance(DateFormat.LONG).format(date), "123456", "AZTEC"); + shadowOf(getMainLooper()).idle(); + + checkAllFields(activity, ViewMode.ADD_CARD, "Example Store", "", DateFormat.getDateInstance(DateFormat.LONG).format(date), "10.00", "$", "123456", "AZTEC"); assertEquals(-416706, ((ColorDrawable) activity.findViewById(R.id.thumbnail).getBackground()).getColor()); } @@ -995,7 +1117,7 @@ public class LoyaltyCardViewActivityTest Activity activity = (Activity)activityController.get(); final Context context = ApplicationProvider.getApplicationContext(); - checkAllFields(activity, ViewMode.ADD_CARD, "Example Store", "", context.getString(R.string.never), "123456", "AZTEC"); + checkAllFields(activity, ViewMode.ADD_CARD, "Example Store", "", context.getString(R.string.never), "0", context.getString(R.string.points), "123456", "AZTEC"); assertEquals(-416706, ((ColorDrawable) activity.findViewById(R.id.thumbnail).getBackground()).getColor()); } } diff --git a/app/src/test/java/protect/card_locker/MainActivityTest.java b/app/src/test/java/protect/card_locker/MainActivityTest.java index 5dcee2a93..a849fd7bf 100644 --- a/app/src/test/java/protect/card_locker/MainActivityTest.java +++ b/app/src/test/java/protect/card_locker/MainActivityTest.java @@ -21,6 +21,7 @@ import org.robolectric.annotation.Config; import org.robolectric.android.controller.ActivityController; import org.robolectric.shadows.ShadowActivity; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @@ -60,10 +61,11 @@ public class MainActivityTest assertTrue(menu != null); // The settings, import/export, groups, search and add button should be present - assertEquals(menu.size(), 5); + assertEquals(menu.size(), 6); assertEquals("Search", menu.findItem(R.id.action_search).getTitle().toString()); assertEquals("Groups", menu.findItem(R.id.action_manage_groups).getTitle().toString()); assertEquals("Import/Export", menu.findItem(R.id.action_import_export).getTitle().toString()); + assertEquals("Privacy Policy", menu.findItem(R.id.action_privacy_policy).getTitle().toString()); assertEquals("About", menu.findItem(R.id.action_about).getTitle().toString()); assertEquals("Settings", menu.findItem(R.id.action_settings).getTitle().toString()); } @@ -95,7 +97,7 @@ public class MainActivityTest assertEquals(0, list.getCount()); DBHelper db = new DBHelper(mainActivity); - db.insertLoyaltyCard("store", "note", null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); + db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); assertEquals(View.VISIBLE, helpText.getVisibility()); assertEquals(View.GONE, noMatchingCardsText.getVisibility()); @@ -131,10 +133,10 @@ public class MainActivityTest assertEquals(0, list.getCount()); DBHelper db = new DBHelper(mainActivity); - db.insertLoyaltyCard("storeB", "note", null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); - db.insertLoyaltyCard("storeA", "note", null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); - db.insertLoyaltyCard("storeD", "note", null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 1); - db.insertLoyaltyCard("storeC", "note", null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 1); + db.insertLoyaltyCard("storeB", "note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); + db.insertLoyaltyCard("storeA", "note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); + db.insertLoyaltyCard("storeD", "note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 1); + db.insertLoyaltyCard("storeC", "note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 1); assertEquals(View.VISIBLE, helpText.getVisibility()); assertEquals(View.GONE, noMatchingCardsText.getVisibility()); @@ -232,8 +234,8 @@ public class MainActivityTest TabLayout groupTabs = mainActivity.findViewById(R.id.groups); DBHelper db = new DBHelper(mainActivity); - db.insertLoyaltyCard("The First Store", "Initial note", null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); - db.insertLoyaltyCard("The Second Store", "Secondary note", null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); + db.insertLoyaltyCard("The First Store", "Initial note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); + db.insertLoyaltyCard("The Second Store", "Secondary note", null, new BigDecimal("0"), null, "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0); db.insertGroup("Group one"); List groups = new ArrayList<>(); diff --git a/app/src/test/java/protect/card_locker/UtilsTest.java b/app/src/test/java/protect/card_locker/UtilsTest.java new file mode 100644 index 000000000..761633feb --- /dev/null +++ b/app/src/test/java/protect/card_locker/UtilsTest.java @@ -0,0 +1,104 @@ +package protect.card_locker; + + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.util.Util; + +import java.math.BigDecimal; +import java.util.Currency; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = 23) +public class UtilsTest +{ + @Test + public void parseBalances() + { + assertEquals("1", Utils.parseCurrency("1", false).toPlainString()); + + assertEquals("1", Utils.parseCurrency("1", true).toPlainString()); + assertEquals("1.00", Utils.parseCurrency("1.00", true).toPlainString()); + assertEquals("1.00", Utils.parseCurrency("1,00", true).toPlainString()); + assertEquals("1.00", Utils.parseCurrency("1 00", true).toPlainString()); + + assertEquals("25", Utils.parseCurrency("2.5", false).toPlainString()); + assertEquals("25", Utils.parseCurrency("2,5", false).toPlainString()); + assertEquals("25", Utils.parseCurrency("2 5", false).toPlainString()); + assertEquals("205", Utils.parseCurrency("2.05", false).toPlainString()); + assertEquals("205", Utils.parseCurrency("2,05", false).toPlainString()); + assertEquals("205", Utils.parseCurrency("2 05", false).toPlainString()); + + assertEquals("2.5", Utils.parseCurrency("2.5", true).toPlainString()); + assertEquals("2.5", Utils.parseCurrency("2,5", true).toPlainString()); + assertEquals("2.5", Utils.parseCurrency("2 5", true).toPlainString()); + assertEquals("2.05", Utils.parseCurrency("2.05", true).toPlainString()); + assertEquals("2.05", Utils.parseCurrency("2,05", true).toPlainString()); + assertEquals("2.05", Utils.parseCurrency("2 05", true).toPlainString()); + assertEquals("2.50", Utils.parseCurrency("2.50", true).toPlainString()); + assertEquals("2.50", Utils.parseCurrency("2,50", true).toPlainString()); + assertEquals("2.50", Utils.parseCurrency("2 50", true).toPlainString()); + + assertEquals("995", Utils.parseCurrency("9.95", false).toPlainString()); + assertEquals("995", Utils.parseCurrency("9,95", false).toPlainString()); + assertEquals("995", Utils.parseCurrency("9 95", false).toPlainString()); + + assertEquals("9.95", Utils.parseCurrency("9.95", true).toPlainString()); + assertEquals("9.95", Utils.parseCurrency("9,95", true).toPlainString()); + assertEquals("9.95", Utils.parseCurrency("9 95", true).toPlainString()); + + assertEquals("1234", Utils.parseCurrency("1234", false).toPlainString()); + assertEquals("1234", Utils.parseCurrency("1.234", false).toPlainString()); + assertEquals("1234", Utils.parseCurrency("1,234", false).toPlainString()); + assertEquals("1234", Utils.parseCurrency("1 234", false).toPlainString()); + + assertEquals("1234", Utils.parseCurrency("1234", true).toPlainString()); + assertEquals("1234.00", Utils.parseCurrency("1234.00", true).toPlainString()); + assertEquals("1234.00", Utils.parseCurrency("1.234.00", true).toPlainString()); + assertEquals("1234.00", Utils.parseCurrency("1.234,00", true).toPlainString()); + assertEquals("1234.00", Utils.parseCurrency("1,234.00", true).toPlainString()); + assertEquals("1234.00", Utils.parseCurrency("1,234,00", true).toPlainString()); + assertEquals("1234.00", Utils.parseCurrency("1 234,00", true).toPlainString()); + assertEquals("1234.00", Utils.parseCurrency("1 234,00", true).toPlainString()); + assertEquals("1234.00", Utils.parseCurrency("1 234 00", true).toPlainString()); + + } + + @Test + public void formatBalances() + { + Currency euro = Currency.getInstance("EUR"); + + assertEquals("1", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("1"), null)); + assertEquals("1.00", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("1"), euro)); + + assertEquals("25", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("25"), null)); + assertEquals("25.00", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("25"), euro)); + + assertEquals("2", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("2.5"), null)); + assertEquals("2.50", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("2.5"), euro)); + + assertEquals("2", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("2.05"), null)); + assertEquals("2.05", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("2.05"), euro)); + + assertEquals("2", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("2.50"), null)); + assertEquals("2.50", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("2.50"), euro)); + + assertEquals("995", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("995"), null)); + assertEquals("995.00", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("995"), euro)); + + assertEquals("10", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("9.95"), null)); + assertEquals("9.95", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("9.95"), euro)); + + assertEquals("1,234", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("1234"), null)); + assertEquals("1,234.00", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("1234"), euro)); + + assertEquals("1,234", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("1234.00"), null)); + assertEquals("1,234.00", Utils.formatBalanceWithoutCurrencySymbol(new BigDecimal("1234.00"), euro)); + } +} diff --git a/docs/privacy-policy.md b/docs/privacy-policy.md index 55fc66ca2..c4bcfcc24 100644 --- a/docs/privacy-policy.md +++ b/docs/privacy-policy.md @@ -1,4 +1,4 @@ -# PRIVACY POLICY FOR LOYALTY CARD KEYCHAIN +# PRIVACY POLICY FOR CATIMA This privacy policy governs your use of the software application Catima (“Application”) for mobile devices that was created by Sylvia van Os. The Application is designed to store and display barcodes. diff --git a/fastlane/metadata/android/de-DE/full_description.txt b/fastlane/metadata/android/de-DE/full_description.txt index cd6d29abf..b4a80e4e5 100644 --- a/fastlane/metadata/android/de-DE/full_description.txt +++ b/fastlane/metadata/android/de-DE/full_description.txt @@ -1,7 +1,7 @@ -Bist Du es auch leid, beim Bezahlen an der Kasse im Supermarkt jedes mal nach dieser Bonuskarte aus Plastik zu suchen? Genervt davon, dass die Brieftasche durch die ganzen Plastikkarten gefühlte fünf Meter dick ist? Wäre es nicht toll, eine freie Lösung für das Problem zu haben, die Deine Daten nicht noch zusätzlich mit weiteren Anbietern teilt? +Bist du es auch leid, beim Bezahlen an der Kasse im Supermarkt jedes mal nach dieser Bonuskarte aus Plastik zu suchen? Genervt davon, dass die Brieftasche durch die ganzen Plastikkarten gefühlte fünf Meter dick ist? Wäre es nicht toll, eine freie Lösung für das Problem zu haben, die deine Daten nicht noch zusätzlich mit weiteren Anbietern teilt? -Catima ist eine App, die Deine auf Barcodes basierenden Kundenkarten auf Deinem Smartphone verwaltet. Catima ist Open Source und kann eines richtig gut: Deine Karten verwalten! +Catima ist eine Anwendung, die deine auf Strichcodes basierenden Kundenkarten auf deinem Smartphone verwaltet. Catima ist quelloffen und kann eines richtig gut: Deine Karten verwalten! -Neue Karten können mit Leichtigkeit hinzugefügt werden. Entweder nutzt Du die Kamera Deines Smartphones, um den Barcode direkt einzulesen – oder Du gibst die unter selbigem befindliche Ziffernfolge von Hand ein. An der Kasse zeigt Catima sodann den Barcode auf dem Display Deines Smartphones an, sodass er vom Scanner gelesen werden kann. Sollte der Scanner dabei doch einmal streiken (was selten vorkommt – und wenn, dann meist nur bei älteren Scanner-Modellen), kann der Verkäufer die ebenfalls angezeigten Ziffern wiederum auch händisch erfassen. +Neue Karten können mit Leichtigkeit hinzugefügt werden. Entweder nutzt du die Kamera deines Smartphones, um den Strichcode direkt einzulesen – oder du gibst die unter selbigem befindliche Ziffernfolge von Hand ein. An der Kasse zeigt Catima sodann den Strichcode auf dem Bildschirm deines Mobiltelefones an, sodass er vom Scanner gelesen werden kann. Sollte der Scanner dabei doch einmal streiken (was selten vorkommt – und wenn, dann meist nur bei älteren Scanner-Modellen), kann der Verkäufer die ebenfalls angezeigten Ziffern wiederum auch händisch erfassen. -Die App benötigt nur wenige Berechtigungen – und greift nie auf das Internet zu. Catima bietet auch die Möglichkeit, Deine Kartensammlung zu exportieren sowie zu importieren. Damit kannst Du die Daten z. B. auch auf ein anderes Gerät übertragen – oder etwa nach einem Werksreset wieder neu einlesen. +Die Anwendung benötigt nur wenige Berechtigungen – und greift nie auf das Internet zu. Catima bietet auch die Möglichkeit, deine Kartensammlung zu exportieren sowie zu importieren. Damit kannst du die Daten z. B. auch auf ein anderes Gerät übertragen – oder etwa nach einem Werksreset wieder neu einlesen. diff --git a/fastlane/metadata/android/de-DE/short_description.txt b/fastlane/metadata/android/de-DE/short_description.txt index f62e7d002..ee35cd54a 100644 --- a/fastlane/metadata/android/de-DE/short_description.txt +++ b/fastlane/metadata/android/de-DE/short_description.txt @@ -1 +1 @@ -Verwaltet barcode-basierte Kunden-/Treuekarten auf dem Handy \ No newline at end of file +Verwaltet Strichcode-basierte Kunden-/Treuekarten auf dem Mobiltelefon diff --git a/fastlane/metadata/android/de-DE/title.txt b/fastlane/metadata/android/de-DE/title.txt index 23c018cfe..71cca707c 100644 --- a/fastlane/metadata/android/de-DE/title.txt +++ b/fastlane/metadata/android/de-DE/title.txt @@ -1 +1 @@ -Catima +Catima - Kundenkarten- und Ticketverwaltung diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-02.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-02.png index 9be572857..505c8dd9f 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-02.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-02.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-06.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-06.png index b52d8d37f..aa3222545 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-06.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-06.png differ diff --git a/fastlane/metadata/android/fr-FR/title.txt b/fastlane/metadata/android/fr-FR/title.txt index fd0df72e6..a92975488 100644 --- a/fastlane/metadata/android/fr-FR/title.txt +++ b/fastlane/metadata/android/fr-FR/title.txt @@ -1 +1 @@ -Catima – Cartes de fidélité +Catima – Gestionnaire de cartes de fidélité diff --git a/fastlane/metadata/android/it/full_description.txt b/fastlane/metadata/android/it/full_description.txt new file mode 100644 index 000000000..886015caa --- /dev/null +++ b/fastlane/metadata/android/it/full_description.txt @@ -0,0 +1,7 @@ +Sei stanco di cercare la tua carta fedeltà durante i pagamenti in negozio? Cerchi una soluzione gratuita che non catture le tue informazioni? + +Catima è un'applicazione che memorizzerà sul tuo telefono le tue carte fedeltà basate su codici a barre. L'applicazione è open source e cerca di fare bene una cosa: gestire le tue carte! + +Nuove carte possono essere aggiunte in un attimo. Utilizza la fotocamera per acquisire il codice a barre o digita il numero. Quando il codice a barre viene caricato nel negozio e visualizzato, può essere scansionato con un moderno lettore di codici a barre. (Alcuni negozi utilizzano lettori di codici a barre meno recenti, come scanner piani, invece di scanner di immagini. Questi non possono leggere il display dello smartphone. In questi casi, devi chiedere all'impiegato di digitare il numero manualmente). + +L'applicazione richiede pochissime autorizzazioni e non tenta mai di accedere a Internet. E' possibile eseguire il backup delle carte nella memoria locale. E da lì puoi conservare il backup da qualche parte al sicuro. diff --git a/fastlane/metadata/android/it/short_description.txt b/fastlane/metadata/android/it/short_description.txt new file mode 100644 index 000000000..ff51269c3 --- /dev/null +++ b/fastlane/metadata/android/it/short_description.txt @@ -0,0 +1 @@ +Gestisce le carte fedeltà basate su codici a barre sul telefono diff --git a/fastlane/metadata/android/it/title.txt b/fastlane/metadata/android/it/title.txt new file mode 100644 index 000000000..10b2d1f4c --- /dev/null +++ b/fastlane/metadata/android/it/title.txt @@ -0,0 +1 @@ +Catima – Gestore di carte fedeltà diff --git a/fastlane/metadata/android/nl-NL/full_description.txt b/fastlane/metadata/android/nl-NL/full_description.txt index cfc0bbdfc..be63de060 100644 --- a/fastlane/metadata/android/nl-NL/full_description.txt +++ b/fastlane/metadata/android/nl-NL/full_description.txt @@ -2,6 +2,6 @@ Ben jij het ook zo zat om tijdens het afrekenen steeds weer te moeten graaien na Dan is Catima wat voor jou! Met deze app kun je je op barcode gebaseerde klantenkaarten opslaan op je telefoon. De app is open source en heeft maar één doel: het beheren en organiseren van je kaarten! -Je voegt nieuwe kaarten in een handomdraai toe, met je camera of door het nummer ervan in te voeren. In de winkel hoef je vervolgens alleen maar de kaart te openen zodat deze kan worden gescand door elke moderne barcodescanner. (Sommige winkels gebruiken nog ouderwetse barcodescanners, zoals flatbedscanners, welke je scherm niet kunnen aflezen. In dat geval moet de kassamedewerker het nummer handmatig invoeren.). +Je voegt nieuwe kaarten in een handomdraai toe, met je camera of door het nummer ervan in te voeren. In de winkel hoef je vervolgens alleen maar de kaart te openen zodat deze kan worden gescand door elke moderne barcodescanner. (Sommige winkels gebruiken nog ouderwetse barcodescanners, zoals scanners, welke je scherm niet kunnen aflezen. In dat geval moet de kassamedewerker het nummer handmatig invoeren.). Catima vereist nauwelijks rechten en maakt geen contact met het internet. Je kunt je kaarten back-uppen naar je lokale opslag en de back-up vervolgens overzetten naar een veilig opslagmedium. diff --git a/fastlane/metadata/android/ru-RU/full_description.txt b/fastlane/metadata/android/ru-RU/full_description.txt new file mode 100644 index 000000000..05743275f --- /dev/null +++ b/fastlane/metadata/android/ru-RU/full_description.txt @@ -0,0 +1,7 @@ +Надоело искать свою пластиковую скидочную карту на кассе в магазине? Ищете бесплатное решение, которое не будет собирать вашу личную информацию? + +Catima — это приложение, которое будет хранить карты лояльности на основе штрих-кодов на вашем смартфоне. Это приложение с открытым исходным кодом и пытается сделать хорошо только одну вещь : управлять вашими картами! + +Новые карты можно добавить в одно мгновение. Либо используйте камеру, чтобы считать штрих-код, либо введите номер карты вручную. Добавленный в приложение штрих-код можно считать с экрана смартфона в магазине с современным сканером штрих-кода. (В некоторых магазинах вместо сканеров изображений используются старые сканеры штрих-кодов, например, планшетные сканеры. Они не могут считывать данные с экрана. Вместо этого попросите сотрудника ввести номер вручную.). + +Приложению требуется очень мало разрешений и не нужен доступ в интернет. Есть возможность резервного копирования ваших карт в локальное хранилище. Оттуда вы сможете перенести данные резервной копии в надёжное место.