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);
+ }
}