diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3602f9e97..4c6e441ce 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,14 +1,13 @@
-
+
-
-
-
+
+
+
+
+
-
-
+
+ android:theme="@style/Theme.App.Starting">
-
+
-
+
-
-
+ android:theme="@style/AppTheme.NoActionBar">
-
+ android:theme="@style/AppTheme.NoActionBar">
+ android:windowSoftInputMode="stateHidden" />
-
+ android:windowSoftInputMode="stateHidden">
+
+
-
-
+
-
-
-
+ android:theme="@style/AppTheme.NoActionBar" />
+ android:windowSoftInputMode="stateHidden" />
+ android:theme="@style/AppTheme.NoActionBar" />
+ android:theme="@style/AppTheme.NoActionBar" />
+ android:theme="@style/AppTheme.NoActionBar">
-
-
+
+
+
+
+ android:grantUriPermissions="true">
+ android:resource="@xml/file_provider_paths" />
-
+
\ No newline at end of file
diff --git a/app/src/main/java/protect/card_locker/DBHelper.java b/app/src/main/java/protect/card_locker/DBHelper.java
index 505f3be52..a55ae71fe 100644
--- a/app/src/main/java/protect/card_locker/DBHelper.java
+++ b/app/src/main/java/protect/card_locker/DBHelper.java
@@ -613,6 +613,21 @@ public class DBHelper extends SQLiteOpenHelper
}
}
+ public void addLoyaltyCardToGroup(int cardId, String groupId){
+ SQLiteDatabase db = getWritableDatabase();
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(LoyaltyCardDbIdsGroups.cardID, cardId);
+ contentValues.put(LoyaltyCardDbIdsGroups.groupID, groupId);
+ db.insert(LoyaltyCardDbIdsGroups.TABLE, null, contentValues);
+ }
+
+ public void removeLoyaltyCardFromGroup(int cardId, String groupId){
+ SQLiteDatabase db = getWritableDatabase();
+ db.delete(LoyaltyCardDbIdsGroups.TABLE,
+ whereAttrs(LoyaltyCardDbIdsGroups.cardID, LoyaltyCardDbIdsGroups.groupID),
+ withArgs(cardId, groupId));
+ }
+
public boolean deleteLoyaltyCard(final int id)
{
SQLiteDatabase db = getWritableDatabase();
@@ -718,6 +733,45 @@ public class DBHelper extends SQLiteOpenHelper
limitString, filter.trim().isEmpty() ? null : new String[] { TextUtils.join("* ", filter.split(" ")) + '*' }, null);
}
+ /**
+ * Returns a cursor to all loyalty cards with the filter text in either the store and whether card is in the provided group
+ *
+ * @param filter
+ * @param group
+ * @param order
+ * @return Cursor
+ */
+ public Cursor getIfLoyaltyCardsAreInGroupCursor(String filter, Group group, LoyaltyCardOrder order, LoyaltyCardOrderDirection direction) {
+ SQLiteDatabase db = getReadableDatabase();
+
+ if (group == null) {
+ throw new IllegalArgumentException("group cannot be null");
+ }
+ String orderField = getFieldForOrder(order);
+ String[] selectionArgs;
+ if(filter.trim().isEmpty()) {
+ selectionArgs = new String[]{
+ group._id
+ };
+ }else{
+ selectionArgs = new String[]{
+ group._id,
+ TextUtils.join("* ", filter.split(" ")) + '*'
+ };
+ }
+ return db.rawQuery("SELECT " + "*" +
+ " FROM " + LoyaltyCardDbIds.TABLE + " LEFT OUTER JOIN " +
+ " (SELECT " + LoyaltyCardDbIdsGroups.TABLE + ".*" +
+ " FROM " + LoyaltyCardDbIdsGroups.TABLE +
+ " WHERE " + LoyaltyCardDbIdsGroups.groupID + " = ? " +
+ " ) " + " AS " + LoyaltyCardDbIdsGroups.TABLE +
+ " ON " + LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.ID + " = " + LoyaltyCardDbIdsGroups.TABLE + "." + LoyaltyCardDbIdsGroups.cardID +
+ (filter.trim().isEmpty() ? "" : " WHERE " + LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.STORE + " MATCH ? ") +
+ " ORDER BY " + LoyaltyCardDbIds.TABLE + "." + orderField,
+ selectionArgs,
+ null);
+ }
+
/**
* Returns the amount of loyalty cards.
*
diff --git a/app/src/main/java/protect/card_locker/Group.java b/app/src/main/java/protect/card_locker/Group.java
index 1d5de8603..0120dd229 100644
--- a/app/src/main/java/protect/card_locker/Group.java
+++ b/app/src/main/java/protect/card_locker/Group.java
@@ -1,8 +1,10 @@
package protect.card_locker;
import android.database.Cursor;
+import android.os.Parcel;
+import android.os.Parcelable;
-public class Group
+public class Group implements Parcelable
{
public final String _id;
public final int order;
@@ -12,6 +14,25 @@ public class Group
this.order = order;
}
+ protected Group(Parcel in){
+ this._id = in.readString();
+ this.order = in.readInt();
+ }
+
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i){
+ parcel.writeString(_id);
+ parcel.writeInt(order);
+ }
+
+
+ @Override
+ public int describeContents() {
+ // group table does not have an integer ID
+ return 0;
+ }
+
public static Group toGroup(Cursor cursor)
{
String _id = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbGroups.ID));
@@ -19,4 +40,16 @@ public class Group
return new Group(_id, order);
}
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public Group createFromParcel(Parcel in) {
+ return new Group(in);
+ }
+
+ @Override
+ public Group[] newArray(int size) {
+ return new Group[size];
+ }
+ };
}
diff --git a/app/src/main/java/protect/card_locker/ManageGroupActivity.java b/app/src/main/java/protect/card_locker/ManageGroupActivity.java
new file mode 100644
index 000000000..f993ddfd4
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/ManageGroupActivity.java
@@ -0,0 +1,355 @@
+package protect.card_locker;
+
+import android.app.SearchManager;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.database.CursorIndexOutOfBoundsException;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+import com.google.android.material.tabs.TabLayout;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.view.ActionMode;
+import androidx.appcompat.widget.SearchView;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.graphics.BlendModeColorFilterCompat;
+import androidx.core.graphics.BlendModeCompat;
+import androidx.core.splashscreen.SplashScreen;
+import androidx.recyclerview.widget.RecyclerView;
+import protect.card_locker.preferences.SettingsActivity;
+
+public class ManageGroupActivity extends CatimaAppCompatActivity implements ManageGroupCursorAdapter.CardAdapterListener
+{
+ private static final String TAG = "Catima";
+
+ private final DBHelper mDB = new DBHelper(this);
+ private ManageGroupCursorAdapter mAdapter;
+ private ActionMode mCurrentActionMode;
+ private Menu mMenu;
+
+ // currently unused
+ protected String mFilter = "";
+ protected DBHelper.LoyaltyCardOrderDirection mOrderDirection = DBHelper.LoyaltyCardOrderDirection.Ascending;
+ protected DBHelper.LoyaltyCardOrder mOrder = DBHelper.LoyaltyCardOrder.Alpha;
+
+ protected Group mGroup = null;
+ private RecyclerView mCardList;
+ private View mHelpText;
+ private View mNoMatchingCardsText;
+ private View mNoGroupCardsText;
+ private EditText mGroupNameText;
+ private TextView mGroupNameLabel;
+ private ActionBar mActionBar;
+
+ private HashMap mAdapterState;
+ private String mCurrentGroupName;
+
+ private boolean mGroupNameNotInUse;
+
+ private boolean mDarkMode;
+
+ @Override
+ protected void onCreate(Bundle inputSavedInstanceState)
+ {
+ super.onCreate(inputSavedInstanceState);
+ setContentView(R.layout.activity_manage_group);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ mHelpText = findViewById(R.id.helpText);
+ mNoMatchingCardsText = findViewById(R.id.noMatchingCardsText);
+ mNoGroupCardsText = findViewById(R.id.noGroupCardsText);
+ mCardList = findViewById(R.id.list);
+
+ mGroupNameText = findViewById(R.id.editTextGroupName);
+ mGroupNameLabel = findViewById(R.id.textViewEditGroupName);
+
+ mAdapter = new ManageGroupCursorAdapter(this, null, this);
+ mCardList.setAdapter(mAdapter);
+ registerForContextMenu(mCardList);
+
+ mGroup = null;
+
+ mDarkMode = Utils.isDarkModeEnabled(getApplicationContext());
+
+ if (inputSavedInstanceState != null) {
+ ManageGroupActivityInGroupState adapterState = inputSavedInstanceState.getParcelable("mAdapterState");
+ if (adapterState != null) {
+ mAdapterState = adapterState.getMap();
+ }
+ mCurrentGroupName = inputSavedInstanceState.getString("mCurrentGroupName");
+ }
+
+ mActionBar = getSupportActionBar();
+
+ mActionBar.setDisplayHomeAsUpEnabled(true);
+ mActionBar.setDisplayShowHomeEnabled(true);
+
+ }
+
+ private void resetGroupNameTextColor() {
+ if (mDarkMode) {
+ mGroupNameText.setTextColor(Color.WHITE);
+ } else{
+ mGroupNameText.setTextColor(Color.BLACK);
+ }
+ }
+
+ private void checkIfGroupNameIsInUse(){
+ mGroupNameNotInUse = false;
+ String currentGroupName = mGroupNameText.getText().toString();
+ if (!mGroup._id.equals(currentGroupName.trim())) {
+ Group group = mDB.getGroup(currentGroupName.trim());
+ if (group != null) {
+ mGroupNameNotInUse = false;
+ mGroupNameText.setTextColor(Color.RED);
+ } else {
+ mGroupNameNotInUse = true;
+ }
+ }
+ }
+
+ @Override
+ protected void onResume()
+ {
+ super.onResume();
+
+ Intent intent = getIntent();
+ mGroup = intent.getParcelableExtra("group");
+ if (mGroup == null){
+ throw(new IllegalArgumentException("this activity expects a group loaded into it's intent"));
+ }
+
+ setTitle(getString(R.string.edit) + ": " + mGroup._id);
+
+ if (mCurrentGroupName == null){
+ mGroupNameText.setText(mGroup._id);
+ }else{
+ mGroupNameText.setText(mCurrentGroupName);
+ checkIfGroupNameIsInUse();
+ }
+
+ mGroupNameText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ return;
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ return;
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ resetGroupNameTextColor();
+ checkIfGroupNameIsInUse();
+ }
+ });
+
+ if (mAdapterState != null){
+ mAdapter.importInGroupState(mAdapterState);
+ }
+ updateLoyaltyCardList();
+ }
+
+ @Override
+ protected void onSaveInstanceState (Bundle outState){
+ super.onSaveInstanceState(outState);
+ if(mAdapterState != null){
+ ManageGroupActivityInGroupState state = new ManageGroupActivityInGroupState(mAdapterState);
+ outState.putParcelable("mAdapterState", state);
+ }
+ if(mCurrentGroupName != null){
+ outState.putString("mCurrentGroupName", mCurrentGroupName);
+ }
+
+ }
+
+ @Override
+ protected void onPause(){
+ super.onPause();
+
+ mAdapterState = mAdapter.exportInGroupState();
+ mCurrentGroupName = mGroupNameText.getText().toString();
+ }
+
+ private void updateLoyaltyCardList() {
+ mAdapter.swapCursor(mDB.getIfLoyaltyCardsAreInGroupCursor(mFilter, mGroup, mOrder, mOrderDirection));
+
+ if(mAdapter.getCountFromCursor() > 0)
+ {
+ // We want the cardList to be visible regardless of the filtered match count
+ // to ensure that the noMatchingCardsText doesn't end up being shown below
+ // the keyboard
+ mHelpText.setVisibility(View.GONE);
+ mNoGroupCardsText.setVisibility(View.GONE);
+ if(mAdapter.getItemCount() > 0)
+ {
+ mCardList.setVisibility(View.VISIBLE);
+ mNoMatchingCardsText.setVisibility(View.GONE);
+ }
+ else
+ {
+ mCardList.setVisibility(View.GONE);
+ if (!mFilter.isEmpty()) {
+ // Actual Empty Search Result
+ mNoMatchingCardsText.setVisibility(View.VISIBLE);
+ mNoGroupCardsText.setVisibility(View.GONE);
+ } else {
+ // Group Tab with no Group Cards
+ mNoMatchingCardsText.setVisibility(View.GONE);
+ mNoGroupCardsText.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+ else
+ {
+ mCardList.setVisibility(View.GONE);
+ mHelpText.setVisibility(View.VISIBLE);
+ mNoMatchingCardsText.setVisibility(View.GONE);
+ mNoGroupCardsText.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu inputMenu)
+ {
+ this.mMenu = inputMenu;
+
+ getMenuInflater().inflate(R.menu.manage_group_menu, inputMenu);
+
+ MenuItem confirmButton = inputMenu.findItem(R.id.action_confirm);
+ Drawable icon = confirmButton.getIcon();
+ icon.mutate();
+ icon.setTint(Color.WHITE);
+ confirmButton.setIcon(icon);
+ return super.onCreateOptionsMenu(inputMenu);
+ }
+
+ private void leaveWithoutSaving(){
+ if (hasChanged()){
+ AlertDialog.Builder builder = new AlertDialog.Builder(ManageGroupActivity.this);
+ builder.setTitle(R.string.discard_changes);
+ builder.setMessage(R.string.discard_changes_confirm);
+ builder.setPositiveButton(R.string.confirm, (dialog, which) -> finish());
+ builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }else{
+ finish();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem inputItem)
+ {
+ int id = inputItem.getItemId();
+
+ if (id == R.id.action_confirm)
+ {
+ String currentGroupName = mGroupNameText.getText().toString();
+ if(!currentGroupName.trim().equals(mGroup._id)){
+ if(!mGroupNameNotInUse) {
+ Toast toast = Toast.makeText(getApplicationContext(), R.string.group_name_already_in_use, Toast.LENGTH_SHORT);
+ toast.show();
+ return true;
+ }
+ if(currentGroupName.trim().length() == 0){
+ Toast toast = Toast.makeText(getApplicationContext(), R.string.group_name_is_empty, Toast.LENGTH_SHORT);
+ toast.show();
+ return true;
+ }
+ }
+
+ mAdapter.commitToDatabase(getApplicationContext(), mGroup._id);
+ Toast toast = Toast.makeText(getApplicationContext(), R.string.group_updated, Toast.LENGTH_SHORT);
+ if(!currentGroupName.trim().equals(mGroup._id)){
+ mDB.updateGroup(mGroup._id, currentGroupName.trim());
+ }
+ toast.show();
+ finish();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(inputItem);
+ }
+ @Override
+ public void onBackPressed()
+ {
+ leaveWithoutSaving();
+ }
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ onBackPressed();
+ return true;
+ }
+
+ private boolean hasChanged(){
+ return mAdapter.hasChanged() || !mGroup._id.equals(mGroupNameText.getText().toString().trim());
+ }
+
+ private void setSort(DBHelper.LoyaltyCardOrder order, DBHelper.LoyaltyCardOrderDirection direction) {
+ // Update values
+ mOrder = order;
+ mOrderDirection = direction;
+
+ // Store in Shared Preference to restore next app launch
+ SharedPreferences sortPref = getApplicationContext().getSharedPreferences(
+ getString(R.string.sharedpreference_sort),
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor sortPrefEditor = sortPref.edit();
+ sortPrefEditor.putString(getString(R.string.sharedpreference_sort_order), order.name());
+ sortPrefEditor.putString(getString(R.string.sharedpreference_sort_direction), direction.name());
+ sortPrefEditor.apply();
+
+ // Update card list
+ updateLoyaltyCardList();
+ }
+
+ @Override
+ public void onRowLongClicked(int inputPosition)
+ {
+ // do nothing for now
+ return;
+ }
+
+ @Override
+ public void onRowClicked(int inputPosition)
+ {
+ Cursor selected = mAdapter.getCursor();
+ mAdapter.toggleSelection(inputPosition);
+ }
+}
diff --git a/app/src/main/java/protect/card_locker/ManageGroupActivityInGroupState.java b/app/src/main/java/protect/card_locker/ManageGroupActivityInGroupState.java
new file mode 100644
index 000000000..e6c30f793
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/ManageGroupActivityInGroupState.java
@@ -0,0 +1,58 @@
+package protect.card_locker;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ManageGroupActivityInGroupState implements Parcelable {
+ HashMap map;
+
+
+
+ public ManageGroupActivityInGroupState(HashMap in){
+ map = (HashMap)in.clone();
+ }
+
+ @Override
+ public int describeContents(){
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(map.size());
+
+ for(Map.Entry entry : map.entrySet()){
+ dest.writeInt(entry.getKey());
+ dest.writeInt(entry.getValue() ? 1 : 0);
+ }
+ }
+
+ protected ManageGroupActivityInGroupState(Parcel in){
+ map = new HashMap();
+ int length = in.readInt();
+ for (int i = 0;i < length; i++){
+ int key = in.readInt();
+ boolean value = in.readInt() == 1 ? true : false;
+ map.put(key, value);
+ }
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public ManageGroupActivityInGroupState createFromParcel(Parcel in) {
+ return new ManageGroupActivityInGroupState(in);
+ }
+
+ @Override
+ public ManageGroupActivityInGroupState[] newArray(int size) {
+ return new ManageGroupActivityInGroupState[size];
+ }
+ };
+
+ public HashMap getMap(){
+ return (HashMap)map.clone();
+ }
+}
diff --git a/app/src/main/java/protect/card_locker/ManageGroupCursorAdapter.java b/app/src/main/java/protect/card_locker/ManageGroupCursorAdapter.java
new file mode 100644
index 000000000..c1a10d6d0
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/ManageGroupCursorAdapter.java
@@ -0,0 +1,365 @@
+package protect.card_locker;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.util.TypedValue;
+import android.view.HapticFeedbackConstants;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.google.android.material.card.MaterialCardView;
+
+import java.math.BigDecimal;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.graphics.BlendModeColorFilterCompat;
+import androidx.core.graphics.BlendModeCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import protect.card_locker.preferences.Settings;
+
+public class ManageGroupCursorAdapter extends BaseCursorAdapter {
+ private int mCurrentSelectedIndex = -1;
+ private Cursor mCursor;
+ Settings mSettings;
+ boolean mDarkModeEnabled;
+ private Context mContext;
+ private CardAdapterListener mListener;
+ private SparseBooleanArray mSelectedItems;
+ private SparseBooleanArray mAnimationItemsIndex;
+ private boolean mReverseAllAnimations = false;
+ private HashMap mIndexCardMap;
+ private HashMap mInGroupOverlay;
+
+
+ public ManageGroupCursorAdapter(Context inputContext, Cursor inputCursor, CardAdapterListener inputListener) {
+ super(inputCursor);
+ setHasStableIds(true);
+ mSettings = new Settings(inputContext);
+ mContext = inputContext;
+ mListener = inputListener;
+ mSelectedItems = new SparseBooleanArray();
+ mAnimationItemsIndex = new SparseBooleanArray();
+
+ mDarkModeEnabled = Utils.isDarkModeEnabled(inputContext);
+
+
+ mInGroupOverlay = new HashMap();
+ swapCursor(mCursor);
+ }
+
+ @Override
+ public void swapCursor(Cursor inputCursor) {
+ mIndexCardMap = new HashMap();
+
+ super.swapCursor(inputCursor);
+ mCursor = inputCursor;
+
+ }
+
+ @Override
+ public ManageGroupListItemViewHolder onCreateViewHolder(ViewGroup inputParent, int inputViewType) {
+ View itemView = LayoutInflater.from(inputParent.getContext()).inflate(R.layout.manage_group_loyalty_card_layout, inputParent, false);
+ return new ManageGroupListItemViewHolder(itemView, mListener);
+ }
+
+ public Cursor getCursor() {
+ return mCursor;
+ }
+
+ public void onBindViewHolder(ManageGroupListItemViewHolder inputHolder, Cursor inputCursor) {
+ // Invisible until we want to show something more
+ inputHolder.mDivider.setVisibility(View.GONE);
+
+ int size = mSettings.getFontSizeMax(mSettings.getSmallFont());
+
+ if (mDarkModeEnabled) {
+ inputHolder.mStarIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
+ }
+
+ ManageGroupLoyaltyCard cardEntry = ManageGroupLoyaltyCard.toCard(inputCursor);
+
+ inputHolder.mStoreField.setText(cardEntry.store);
+ inputHolder.mStoreField.setTextSize(mSettings.getFontSizeMax(mSettings.getMediumFont()));
+ if (!cardEntry.note.isEmpty()) {
+ inputHolder.mNoteField.setVisibility(View.VISIBLE);
+ inputHolder.mNoteField.setText(cardEntry.note);
+ inputHolder.mNoteField.setTextSize(size);
+ } else {
+ inputHolder.mNoteField.setVisibility(View.GONE);
+ }
+
+ if (!cardEntry.balance.equals(new BigDecimal("0"))) {
+ int drawableSize = dpToPx((size*24)/14, mContext);
+ inputHolder.mDivider.setVisibility(View.VISIBLE);
+ inputHolder.mBalanceField.setVisibility(View.VISIBLE);
+ Drawable balanceIcon = inputHolder.mBalanceField.getCompoundDrawables()[0];
+ balanceIcon.setBounds(0,0,drawableSize,drawableSize);
+ inputHolder.mBalanceField.setCompoundDrawablesRelative(balanceIcon, null, null, null);
+ if (mDarkModeEnabled) {
+ balanceIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
+ }
+ inputHolder.mBalanceField.setText(Utils.formatBalance(mContext, cardEntry.balance, cardEntry.balanceType));
+ inputHolder.mBalanceField.setTextSize(size);
+ } else {
+ inputHolder.mBalanceField.setVisibility(View.GONE);
+ }
+
+ if (cardEntry.expiry != null) {
+ int drawableSize = dpToPx((size*24)/14, mContext);
+ inputHolder.mDivider.setVisibility(View.VISIBLE);
+ inputHolder.mExpiryField.setVisibility(View.VISIBLE);
+ Drawable expiryIcon = inputHolder.mExpiryField.getCompoundDrawables()[0];
+ expiryIcon.setBounds(0,0, drawableSize, drawableSize);
+ inputHolder.mExpiryField.setCompoundDrawablesRelative(expiryIcon, null, null, null);
+ if (Utils.hasExpired(cardEntry.expiry)) {
+ expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.RED, BlendModeCompat.SRC_ATOP));
+ inputHolder.mExpiryField.setTextColor(Color.RED);
+ } else if (mDarkModeEnabled) {
+ expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
+ }
+ inputHolder.mExpiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(cardEntry.expiry));
+ inputHolder.mExpiryField.setTextSize(size);
+ } else {
+ inputHolder.mExpiryField.setVisibility(View.GONE);
+ }
+
+ // inputHolder.mStarIcon.setVisibility(cardEntry.starStatus != 0 ? View.VISIBLE : View.GONE);
+ inputHolder.mCardIcon.setImageBitmap(Utils.generateIcon(mContext, cardEntry.store, cardEntry.headerColor).getLetterTile());
+ int imageSize = dpToPx( (size*46)/14, mContext);
+ inputHolder.mCardIcon.getLayoutParams().height = imageSize;
+ inputHolder.mCardIcon.getLayoutParams().width = imageSize;
+ inputHolder.mStarIcon.getLayoutParams().height = imageSize;
+ inputHolder.mStarIcon.getLayoutParams().width = imageSize;
+ inputHolder.mTickIcon.getLayoutParams().height = imageSize;
+ inputHolder.mTickIcon.getLayoutParams().width = imageSize;
+
+ /* Changing Padding and Margin of different views according to font size
+ * Views Included:
+ * a) InformationContainer padding
+ * b) Store left padding
+ * c) Divider Margin
+ * d) note top margin
+ * e) row margin
+ * */
+ int marginPaddingSize = dpToPx((size*16)/14, mContext );
+ inputHolder.mInformationContainer.setPadding(marginPaddingSize, marginPaddingSize, marginPaddingSize, marginPaddingSize);
+ inputHolder.mStoreField.setPadding(marginPaddingSize, 0, 0, 0);
+ LinearLayout.LayoutParams lpDivider = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT );
+ lpDivider.setMargins(0, marginPaddingSize, 0, marginPaddingSize);
+ inputHolder.mDivider.setLayoutParams(lpDivider);
+ LinearLayout.LayoutParams lpNoteField = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT );
+ lpNoteField.setMargins(0, marginPaddingSize/2, 0, 0);
+ inputHolder.mNoteField.setLayoutParams(lpNoteField);
+ LinearLayout.LayoutParams lpRow = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT );
+ lpRow.setMargins(marginPaddingSize/2, marginPaddingSize/2, marginPaddingSize/2, marginPaddingSize/2);
+ inputHolder.mRow.setLayoutParams(lpRow);
+
+ inputHolder.itemView.setActivated(mSelectedItems.get(inputCursor.getPosition(), false));
+
+ Boolean overlayValue = mInGroupOverlay.get(cardEntry.id);
+ if((overlayValue != null? overlayValue: cardEntry.is_in_group)) {
+ mAnimationItemsIndex.put(inputCursor.getPosition(), true);
+ mSelectedItems.put(inputCursor.getPosition(), true);
+ }
+
+ applyIconAnimation(inputHolder, inputCursor.getPosition());
+ applyClickEvents(inputHolder, inputCursor.getPosition());
+
+ mIndexCardMap.put(inputCursor.getPosition(), cardEntry);
+ }
+
+ private void applyClickEvents(ManageGroupListItemViewHolder inputHolder, final int inputPosition) {
+ inputHolder.mRow.setOnClickListener(inputView -> mListener.onRowClicked(inputPosition));
+ inputHolder.mInformationContainer.setOnClickListener(inputView -> mListener.onRowClicked(inputPosition));
+
+ inputHolder.mRow.setOnLongClickListener(inputView -> {
+ mListener.onRowLongClicked(inputPosition);
+ inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ return true;
+ });
+
+ inputHolder.mInformationContainer.setOnLongClickListener(inputView -> {
+ mListener.onRowLongClicked(inputPosition);
+ inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ return true;
+ });
+ }
+
+ private void applyIconAnimation(ManageGroupListItemViewHolder inputHolder, int inputPosition) {
+ if (mSelectedItems.get(inputPosition, false)) {
+ inputHolder.mThumbnailFrontContainer.setVisibility(View.GONE);
+ resetIconYAxis(inputHolder.mThumbnailBackContainer);
+ inputHolder.mThumbnailBackContainer.setVisibility(View.VISIBLE);
+ inputHolder.mThumbnailBackContainer.setAlpha(1);
+ if (mCurrentSelectedIndex == inputPosition) {
+ LoyaltyCardAnimator.flipView(mContext, inputHolder.mThumbnailBackContainer, inputHolder.mThumbnailFrontContainer, true);
+ resetCurrentIndex();
+ }
+ } else {
+ inputHolder.mThumbnailBackContainer.setVisibility(View.GONE);
+ resetIconYAxis(inputHolder.mThumbnailFrontContainer);
+ inputHolder.mThumbnailFrontContainer.setVisibility(View.VISIBLE);
+ inputHolder.mThumbnailFrontContainer.setAlpha(1);
+ if ((mReverseAllAnimations && mAnimationItemsIndex.get(inputPosition, false)) || mCurrentSelectedIndex == inputPosition) {
+ LoyaltyCardAnimator.flipView(mContext, inputHolder.mThumbnailBackContainer, inputHolder.mThumbnailFrontContainer, false);
+ resetCurrentIndex();
+ }
+ }
+ }
+
+ private void resetIconYAxis(View inputView) {
+ if (inputView.getRotationY() != 0) {
+ inputView.setRotationY(0);
+ }
+ }
+
+ public void resetAnimationIndex() {
+ mReverseAllAnimations = false;
+ mAnimationItemsIndex.clear();
+ }
+
+
+ public void toggleSelection(int inputPosition) {
+ mCurrentSelectedIndex = inputPosition;
+ if (mSelectedItems.get(inputPosition, false)) {
+ mSelectedItems.delete(inputPosition);
+ mAnimationItemsIndex.delete(inputPosition);
+ } else {
+ mSelectedItems.put(inputPosition, true);
+ mAnimationItemsIndex.put(inputPosition, true);
+ }
+ ManageGroupLoyaltyCard cardEntry = mIndexCardMap.get(inputPosition);
+ Boolean overlayValue = mInGroupOverlay.get(cardEntry.id);
+ if (overlayValue == null){
+ mInGroupOverlay.put(cardEntry.id, !cardEntry.is_in_group);
+ }else{
+ mInGroupOverlay.put(cardEntry.id, !overlayValue);
+ }
+
+ notifyDataSetChanged();
+ }
+
+ private void resetCurrentIndex() {
+ mCurrentSelectedIndex = -1;
+ }
+
+ public interface CardAdapterListener {
+ void onRowClicked(int inputPosition);
+
+ void onRowLongClicked(int inputPosition);
+ }
+
+ private HashMap fetchWholeQuery (){
+ HashMap res = new HashMap();
+ int oldPosition = mCursor.getPosition();
+ mCursor.moveToFirst();
+ while(!mCursor.isAfterLast()){
+ ManageGroupLoyaltyCard cardEntry = ManageGroupLoyaltyCard.toCard(mCursor);
+ res.put(cardEntry.id, cardEntry);
+ mCursor.moveToNext();
+ }
+ mCursor.moveToPosition(oldPosition);
+ return res;
+ }
+
+ public void commitToDatabase(Context context, String groupId){
+ DBHelper dbHelper = new DBHelper(context);
+ HashMap cache = fetchWholeQuery();
+ for(Map.Entry entry : mInGroupOverlay.entrySet()){
+ ManageGroupLoyaltyCard cardEntry = cache.get(entry.getKey());
+ if (cardEntry == null){
+ Log.d("commitToDatabase", "card with id " + entry.getKey() + " was removed from database unexpectedly");
+ continue;
+ }
+ if (entry.getValue() != cardEntry.is_in_group) {
+ if (entry.getValue()) {
+ dbHelper.addLoyaltyCardToGroup(entry.getKey(), groupId);
+ } else {
+ dbHelper.removeLoyaltyCardFromGroup(entry.getKey(), groupId);
+ }
+ }
+ }
+ }
+
+ public boolean hasChanged(){
+ HashMap cache = fetchWholeQuery();
+ for(Map.Entry entry : mInGroupOverlay.entrySet()){
+ ManageGroupLoyaltyCard cardEntry = cache.get(entry.getKey());
+ if(cardEntry.is_in_group != entry.getValue()){
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int getCountFromCursor(){
+ return mCursor.getCount();
+ }
+
+ public HashMap exportInGroupState(){
+ return (HashMap)mInGroupOverlay.clone();
+ }
+
+ public void importInGroupState(HashMap cardIdInGroupMap){
+ mInGroupOverlay = (HashMap)cardIdInGroupMap.clone();
+ }
+
+
+ public static class ManageGroupListItemViewHolder extends RecyclerView.ViewHolder {
+
+ public TextView mStoreField, mNoteField, mBalanceField, mExpiryField;
+ public LinearLayout mInformationContainer;
+ public ImageView mCardIcon, mStarIcon, mTickIcon;
+ public MaterialCardView mRow;
+ public View mDivider;
+ public RelativeLayout mThumbnailFrontContainer, mThumbnailBackContainer;
+
+ public ManageGroupListItemViewHolder(View inputView, CardAdapterListener inputListener) {
+ super(inputView);
+ mRow = inputView.findViewById(R.id.row);
+ mDivider = inputView.findViewById(R.id.info_divider);
+ mThumbnailFrontContainer = inputView.findViewById(R.id.thumbnail_front);
+ mThumbnailBackContainer = inputView.findViewById(R.id.thumbnail_back);
+ mInformationContainer = inputView.findViewById(R.id.information_container);
+ mStoreField = inputView.findViewById(R.id.store);
+ mNoteField = inputView.findViewById(R.id.note);
+ mBalanceField = inputView.findViewById(R.id.balance);
+ mExpiryField = inputView.findViewById(R.id.expiry);
+ mCardIcon = inputView.findViewById(R.id.thumbnail);
+ mStarIcon = inputView.findViewById(R.id.star);
+ mTickIcon = inputView.findViewById(R.id.selected_thumbnail);
+ inputView.setOnLongClickListener(view -> {
+ inputListener.onRowClicked(getAdapterPosition());
+ inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ return true;
+ });
+ }
+ }
+
+ public int dpToPx(int dp, Context mContext){
+ Resources r = mContext.getResources();
+ int px = (int)TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
+ return px;
+ }
+}
diff --git a/app/src/main/java/protect/card_locker/ManageGroupLoyaltyCard.java b/app/src/main/java/protect/card_locker/ManageGroupLoyaltyCard.java
new file mode 100644
index 000000000..0010d1f19
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/ManageGroupLoyaltyCard.java
@@ -0,0 +1,168 @@
+package protect.card_locker;
+// Was thinking about extending LoyaltyCard but this makes things more modular..?
+import android.database.Cursor;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.math.BigDecimal;
+import java.util.Currency;
+import java.util.Date;
+
+import androidx.annotation.Nullable;
+
+public class ManageGroupLoyaltyCard implements Parcelable {
+ public final int id;
+ public final String store;
+ public final String note;
+ public final Date expiry;
+ public final BigDecimal balance;
+ public final Currency balanceType;
+ public final String cardId;
+
+ @Nullable
+ public final String barcodeId;
+
+ @Nullable
+ public final CatimaBarcode barcodeType;
+
+ @Nullable
+ public final Integer headerColor;
+
+ public final int starStatus;
+ public final long lastUsed;
+ public int zoomLevel;
+
+ public final boolean is_in_group;
+
+ public ManageGroupLoyaltyCard(final int id, final String store, final String note, final Date expiry,
+ final BigDecimal balance, final Currency balanceType, final String cardId,
+ @Nullable final String barcodeId, @Nullable final CatimaBarcode barcodeType,
+ @Nullable final Integer headerColor, final int starStatus, final long lastUsed,final int zoomLevel,
+ final boolean is_in_group)
+ {
+ this.id = id;
+ this.store = store;
+ this.note = note;
+ this.expiry = expiry;
+ this.balance = balance;
+ this.balanceType = balanceType;
+ this.cardId = cardId;
+ this.barcodeId = barcodeId;
+ this.barcodeType = barcodeType;
+ this.headerColor = headerColor;
+ this.starStatus = starStatus;
+ this.lastUsed = lastUsed;
+ this.zoomLevel = zoomLevel;
+ this.is_in_group = is_in_group;
+ }
+
+ protected ManageGroupLoyaltyCard(Parcel in) {
+ id = in.readInt();
+ store = in.readString();
+ note = in.readString();
+ long tmpExpiry = in.readLong();
+ expiry = tmpExpiry != -1 ? new Date(tmpExpiry) : null;
+ balance = (BigDecimal) in.readValue(BigDecimal.class.getClassLoader());
+ balanceType = (Currency) in.readValue(Currency.class.getClassLoader());
+ cardId = in.readString();
+ barcodeId = in.readString();
+ String tmpBarcodeType = in.readString();
+ barcodeType = !tmpBarcodeType.isEmpty() ? CatimaBarcode.fromName(tmpBarcodeType) : null;
+ int tmpHeaderColor = in.readInt();
+ headerColor = tmpHeaderColor != -1 ? tmpHeaderColor : null;
+ starStatus = in.readInt();
+ lastUsed = in.readLong();
+ zoomLevel = in.readInt();
+ if (in.readInt() == 1) {
+ is_in_group = true;
+ }else{
+ is_in_group = false;
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ parcel.writeInt(id);
+ parcel.writeString(store);
+ parcel.writeString(note);
+ parcel.writeLong(expiry != null ? expiry.getTime() : -1);
+ parcel.writeValue(balance);
+ parcel.writeValue(balanceType);
+ parcel.writeString(cardId);
+ parcel.writeString(barcodeId);
+ parcel.writeString(barcodeType != null ? barcodeType.name() : "");
+ parcel.writeInt(headerColor != null ? headerColor : -1);
+ parcel.writeInt(starStatus);
+ parcel.writeLong(lastUsed);
+ parcel.writeInt(zoomLevel);
+ if (is_in_group) {
+ parcel.writeInt(1);
+ }else{
+ parcel.writeInt(0);
+ }
+ }
+
+ public static ManageGroupLoyaltyCard toCard(Cursor cursor)
+ {
+ int id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
+ 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 barcodeId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_ID));
+ int starred = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS));
+ long lastUsed = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.LAST_USED));
+ int zoomLevel = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ZOOM_LEVEL));
+
+ int barcodeTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
+ int balanceTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE);
+ int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR);
+
+ boolean was_in_group = !cursor.isNull(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIdsGroups.groupID));
+
+ CatimaBarcode barcodeType = null;
+ Currency balanceType = null;
+ Date expiry = null;
+ Integer headerColor = null;
+
+ if (cursor.isNull(barcodeTypeColumn) == false)
+ {
+ barcodeType = CatimaBarcode.fromName(cursor.getString(barcodeTypeColumn));
+ }
+
+ if (cursor.isNull(balanceTypeColumn) == false)
+ {
+ balanceType = Currency.getInstance(cursor.getString(balanceTypeColumn));
+ }
+
+ if(expiryLong > 0)
+ {
+ expiry = new Date(expiryLong);
+ }
+
+ if(cursor.isNull(headerColorColumn) == false)
+ {
+ headerColor = cursor.getInt(headerColorColumn);
+ }
+
+ return new ManageGroupLoyaltyCard(id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starred, lastUsed,zoomLevel,was_in_group);
+ }
+
+ @Override
+ public int describeContents() {
+ return id;
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public LoyaltyCard createFromParcel(Parcel in) {
+ return new LoyaltyCard(in);
+ }
+
+ @Override
+ public LoyaltyCard[] newArray(int size) {
+ return new LoyaltyCard[size];
+ }
+ };
+}
diff --git a/app/src/main/java/protect/card_locker/ManageGroupsActivity.java b/app/src/main/java/protect/card_locker/ManageGroupsActivity.java
index 60e6dd35f..9140ef7de 100644
--- a/app/src/main/java/protect/card_locker/ManageGroupsActivity.java
+++ b/app/src/main/java/protect/card_locker/ManageGroupsActivity.java
@@ -1,6 +1,7 @@
package protect.card_locker;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.InputType;
@@ -176,7 +177,12 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
@Override
public void onEditButtonClicked(View view) {
- final String groupName = getGroupName(view);
+ Group group = mDb.getGroup(getGroupName(view));
+ Intent intent = new Intent(this, ManageGroupActivity.class);
+ intent.putExtra("group", group);
+ startActivity(intent);
+ /*
+ final String groupName = c;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.enter_group_name);
@@ -195,6 +201,7 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
dialog.show();
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
input.requestFocus();
+ */
}
@Override
diff --git a/app/src/main/res/layout/activity_manage_group.xml b/app/src/main/res/layout/activity_manage_group.xml
new file mode 100644
index 000000000..32f696a4c
--- /dev/null
+++ b/app/src/main/res/layout/activity_manage_group.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/content_manage_group.xml b/app/src/main/res/layout/content_manage_group.xml
new file mode 100644
index 000000000..71a305bcb
--- /dev/null
+++ b/app/src/main/res/layout/content_manage_group.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/manage_group_loyalty_card_layout.xml b/app/src/main/res/layout/manage_group_loyalty_card_layout.xml
new file mode 100644
index 000000000..4c3af71d5
--- /dev/null
+++ b/app/src/main/res/layout/manage_group_loyalty_card_layout.xml
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/manage_group_menu.xml b/app/src/main/res/menu/manage_group_menu.xml
new file mode 100644
index 000000000..ca6a8b551
--- /dev/null
+++ b/app/src/main/res/menu/manage_group_menu.xml
@@ -0,0 +1,10 @@
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4e14f6b22..b29e5858c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,5 +1,5 @@
-
+
Search
Add
@@ -129,6 +129,9 @@
- %d card
- %d cards
+ Group name already in use!
+ Group name cannot be empty!
+ Group updated!
All
Delete group?
Install a file manager first.
@@ -245,4 +248,6 @@
Rate this app
on Google Play
Report Error
+ Discard Changes
+ There are unsaved changes that were made, do you wish to leave without saving?