From ccf12bf028e0bbcbfdaa2376e5fccacb7ae0a4e5 Mon Sep 17 00:00:00 2001 From: polarhun <33174751+polarhun@users.noreply.github.com> Date: Sun, 16 Oct 2022 13:25:26 +0100 Subject: [PATCH] #1044 - Automatic Balance Update (#1073) --- .../java/protect/card_locker/DBHelper.java | 9 ++ .../card_locker/LoyaltyCardViewActivity.java | 88 +++++++++++++++++++ .../drawable/ic_baseline_shopping_cart_24.xml | 5 ++ .../res/layout/loyalty_card_view_layout.xml | 13 +++ app/src/main/res/values/strings.xml | 5 ++ .../protect/card_locker/DatabaseTest.java | 26 ++++++ 6 files changed, 146 insertions(+) create mode 100644 app/src/main/res/drawable/ic_baseline_shopping_cart_24.xml diff --git a/app/src/main/java/protect/card_locker/DBHelper.java b/app/src/main/java/protect/card_locker/DBHelper.java index 7ae2f28bd..90e4270dd 100644 --- a/app/src/main/java/protect/card_locker/DBHelper.java +++ b/app/src/main/java/protect/card_locker/DBHelper.java @@ -494,6 +494,15 @@ public class DBHelper extends SQLiteOpenHelper { return (rowsUpdated == 1); } + public static boolean updateLoyaltyCardBalance(SQLiteDatabase database, final int id, final BigDecimal newBalance) { + ContentValues contentValues = new ContentValues(); + contentValues.put(LoyaltyCardDbIds.BALANCE, newBalance.toString()); + int rowsUpdated = database.update(LoyaltyCardDbIds.TABLE, contentValues, + whereAttrs(LoyaltyCardDbIds.ID), + withArgs(id)); + return (rowsUpdated == 1); + } + public static LoyaltyCard getLoyaltyCard(SQLiteDatabase database, final int id) { Cursor data = database.query(LoyaltyCardDbIds.TABLE, null, whereAttrs(LoyaltyCardDbIds.ID), withArgs(id), null, null, null); diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java index 9192d4416..6019a3953 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java @@ -1,6 +1,8 @@ package protect.card_locker; import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.ColorStateList; @@ -13,6 +15,8 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.text.Editable; +import android.text.InputType; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; @@ -25,12 +29,15 @@ import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowInsets; import android.view.WindowInsetsController; import android.view.WindowManager; +import android.widget.EditText; +import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; @@ -64,8 +71,10 @@ import java.io.File; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.text.DateFormat; +import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Currency; import java.util.List; import protect.card_locker.async.TaskHandler; @@ -85,6 +94,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements ImageButton bottomAppBarInfoButton; ImageButton bottomAppBarPreviousButton; ImageButton bottomAppBarNextButton; + ImageButton bottomAppBarUpdateBalanceButton; AppCompatTextView storeName; ImageButton maximizeButton; ImageView mainImage; @@ -363,6 +373,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements bottomAppBarInfoButton = binding.buttonShowInfo; bottomAppBarPreviousButton = binding.buttonPrevious; bottomAppBarNextButton = binding.buttonNext; + bottomAppBarUpdateBalanceButton = binding.buttonUpdateBalance; barcodeImageGenerationFinishedCallback = () -> { if (!(boolean) mainImage.getTag()) { @@ -439,6 +450,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements bottomAppBarInfoButton.setOnClickListener(view -> showInfoDialog()); bottomAppBarPreviousButton.setOnClickListener(view -> prevNextCard(false)); bottomAppBarNextButton.setOnClickListener(view -> prevNextCard(true)); + bottomAppBarUpdateBalanceButton.setOnClickListener(view -> showBalanceUpdateDialog()); mGestureDetector = new GestureDetector(this, this); View.OnTouchListener gestureTouchListener = (v, event) -> mGestureDetector.onTouchEvent(event); @@ -453,6 +465,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements iconImage.setClipBounds(new Rect(left, top, right, bottom)); } }); + this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); } private SpannableStringBuilder padSpannableString(SpannableStringBuilder spannableStringBuilder) { @@ -523,6 +536,78 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements infoDialog.create().show(); } + private void showBalanceUpdateDialog() { + AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); + builder.setTitle(R.string.updateBalanceTitle); + FrameLayout container = new FrameLayout(this); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + params.leftMargin = 60; + params.rightMargin = 60; + + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + + TextView currentTextview = new TextView(this); + currentTextview.setText(getString(R.string.currentBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType))); + layout.addView(currentTextview); + + TextView updateTextView = new TextView(this); + updateTextView.setText(getString(R.string.newBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType))); + layout.addView(updateTextView); + + final EditText input = new EditText(this); + Context dialogContext = this; + input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL); + input.setHint(R.string.updateBalanceHint); + input.addTextChangedListener(new SimpleTextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + BigDecimal newBalance; + try { + newBalance = calculateNewBalance(loyaltyCard.balance, loyaltyCard.balanceType, s.toString()); + } catch (ParseException e) { + input.setTag(null); + updateTextView.setText(getString(R.string.newBalanceSentence, Utils.formatBalance(dialogContext, loyaltyCard.balance, loyaltyCard.balanceType))); + return; + } + + // Save new balance into this element + input.setTag(newBalance); + updateTextView.setText(getString(R.string.newBalanceSentence, Utils.formatBalance(dialogContext, newBalance, loyaltyCard.balanceType))); + } + }); + layout.addView(input); + layout.setLayoutParams(params); + container.addView(layout); + + builder.setView(container); + builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> { + // Grab calculated balance from input field + BigDecimal newBalance = (BigDecimal) input.getTag(); + if (newBalance == null) { + return; + } + + // Actually update balance + DBHelper.updateLoyaltyCardBalance(database, loyaltyCardId, newBalance); + // Reload UI + this.onResume(); + }); + builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel()); + AlertDialog dialog = builder.create(); + dialog.show(); + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + input.requestFocus(); + } + + private BigDecimal calculateNewBalance(BigDecimal currentBalance, Currency currency, String unparsedSubtraction) throws ParseException { + BigDecimal subtraction = Utils.parseBalance(unparsedSubtraction, currency); + return currentBalance.subtract(subtraction).max(new BigDecimal(0)); + } + private void setBottomAppBarButtonState() { if (!loyaltyCard.note.isEmpty() || !loyaltyCardGroups.isEmpty() || hasBalance(loyaltyCard) || loyaltyCard.expiry != null) { bottomAppBarInfoButton.setVisibility(View.VISIBLE); @@ -537,6 +622,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements bottomAppBarPreviousButton.setVisibility(View.VISIBLE); bottomAppBarNextButton.setVisibility(View.VISIBLE); } + + bottomAppBarUpdateBalanceButton.setVisibility(hasBalance(loyaltyCard) ? View.VISIBLE : View.GONE); } private void prevNextCard(boolean next) { @@ -709,6 +796,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements fixImageButtonColor(bottomAppBarInfoButton); fixImageButtonColor(bottomAppBarPreviousButton); fixImageButtonColor(bottomAppBarNextButton); + fixImageButtonColor(bottomAppBarUpdateBalanceButton); setBottomAppBarButtonState(); // Make notification area light if dark icons are needed diff --git a/app/src/main/res/drawable/ic_baseline_shopping_cart_24.xml b/app/src/main/res/drawable/ic_baseline_shopping_cart_24.xml new file mode 100644 index 000000000..cbd723557 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_shopping_cart_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/loyalty_card_view_layout.xml b/app/src/main/res/layout/loyalty_card_view_layout.xml index 823681605..af280f19f 100644 --- a/app/src/main/res/layout/loyalty_card_view_layout.xml +++ b/app/src/main/res/layout/loyalty_card_view_layout.xml @@ -8,6 +8,7 @@ android:layout_height="fill_parent" android:fitsSystemWindows="true"> + + + Sort Show info Hide info + Update balance Swipe to switch images, hold to open image in the gallery app Failed to retrieve image file Only images can be opened in the gallery app @@ -300,4 +301,8 @@ View archive (%1$d cards) Welcome to Catima + How much did you spend? + Enter amount + Current balance: %s + New balance: %s \ 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 eef904d03..78e9429c7 100644 --- a/app/src/test/java/protect/card_locker/DatabaseTest.java +++ b/app/src/test/java/protect/card_locker/DatabaseTest.java @@ -504,4 +504,30 @@ public class DatabaseTest { assertEquals(0, card2.lastUsed); assertEquals(100, card2.zoomLevel); } + + @Test + public void updateGiftCardOnlyBalance() { + long id = DBHelper.insertLoyaltyCard(mDatabase, "store", "note", null, new BigDecimal("100"), null, "cardId", null, CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A), DEFAULT_HEADER_COLOR, 0, null,0); + boolean result = (id != -1); + assertTrue(result); + assertEquals(1, DBHelper.getLoyaltyCardCount(mDatabase)); + + result = DBHelper.updateLoyaltyCardBalance(mDatabase, 1, new BigDecimal(60)); + assertTrue(result); + assertEquals(1, DBHelper.getLoyaltyCardCount(mDatabase)); + + LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(mDatabase, 1); + assertNotNull(loyaltyCard); + assertEquals("store", loyaltyCard.store); + assertEquals("note", loyaltyCard.note); + assertEquals(null, loyaltyCard.expiry); + assertEquals(new BigDecimal(60), loyaltyCard.balance); + assertEquals(null, loyaltyCard.balanceType); + assertEquals("cardId", loyaltyCard.cardId); + assertEquals(null, loyaltyCard.barcodeId); + assertEquals(BarcodeFormat.UPC_A, loyaltyCard.barcodeType.format()); + assertEquals(DEFAULT_HEADER_COLOR, loyaltyCard.headerColor); + assertEquals(0, loyaltyCard.starStatus); + assertEquals(0, loyaltyCard.archiveStatus); + } }