diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3602f9e97..1e81afb71 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/Group.java b/app/src/main/java/protect/card_locker/Group.java index 1d5de8603..f39de2b50 100644 --- a/app/src/main/java/protect/card_locker/Group.java +++ b/app/src/main/java/protect/card_locker/Group.java @@ -2,6 +2,8 @@ package protect.card_locker; import android.database.Cursor; +import androidx.annotation.Nullable; + public class Group { public final String _id; @@ -19,4 +21,22 @@ public class Group return new Group(_id, order); } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == null){ + return false; + } + if (!(obj instanceof Group)){ + return false; + } + Group anotherGroup = (Group)obj; + return _id.equals(anotherGroup._id) && order == anotherGroup.order; + } + + @Override + public int hashCode(){ + String combined = _id + "_" + order; + return combined.hashCode(); + } } diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java b/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java index 8572fe152..f259768c1 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java @@ -35,8 +35,8 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter { + String currentGroupName = mGroupNameText.getText().toString().trim(); + if(!currentGroupName.equals(mGroup._id)){ + if(currentGroupName.length() == 0){ + Toast.makeText(getApplicationContext(), R.string.group_name_is_empty, Toast.LENGTH_SHORT).show(); + return; + } + if(!mGroupNameNotInUse) { + Toast.makeText(getApplicationContext(), R.string.group_name_already_in_use, Toast.LENGTH_SHORT).show(); + return; + } + } + + mAdapter.commitToDatabase(); + if(!currentGroupName.equals(mGroup._id)){ + mDB.updateGroup(mGroup._id, currentGroupName); + } + Toast.makeText(getApplicationContext(), R.string.group_updated, Toast.LENGTH_SHORT).show(); + finish(); + }); + // this setText is here because content_main.xml is reused from main activity + mHelpText.setText(getResources().getText(R.string.noGiftCardsGroup)); + updateLoyaltyCardList(); + } + + private ArrayList adapterStateToIntegerArray(HashMap adapterState){ + ArrayList ret = new ArrayList<>(adapterState.size() * 2); + for (Map.Entry entry : adapterState.entrySet()) { + ret.add(entry.getKey()); + ret.add(entry.getValue()?1:0); + } + return ret; + } + + private HashMap integerArrayToAdapterState(ArrayList in) { + HashMap ret = new HashMap<>(); + if (in.size() % 2 != 0){ + throw(new RuntimeException("failed restoring adapterState from integer array list")); + } + for(int i = 0;i < in.size(); i += 2){ + ret.put(in.get(i), in.get(i+1) == 1); + } + return ret; + } + + + @Override + protected void onSaveInstanceState (@NonNull Bundle outState){ + super.onSaveInstanceState(outState); + + outState.putIntegerArrayList(SAVE_INSTANCE_ADAPTER_STATE, adapterStateToIntegerArray(mAdapter.exportInGroupState())); + outState.putString(SAVE_INSTANCE_CURRENT_GROUP_NAME, mGroupNameText.getText().toString()); + } + + private void updateLoyaltyCardList() { + mAdapter.swapCursor(mDB.getLoyaltyCardCursor()); + + if(mAdapter.getCountFromCursor() == 0) + { + mCardList.setVisibility(View.GONE); + mHelpText.setVisibility(View.VISIBLE); + }else { + mCardList.setVisibility(View.VISIBLE); + mHelpText.setVisibility(View.GONE); + } + } + + private void leaveWithoutSaving(){ + if (hasChanged()){ + AlertDialog.Builder builder = new AlertDialog.Builder(ManageGroupActivity.this); + builder.setTitle(R.string.leaveWithoutSaveTitle); + builder.setMessage(R.string.leaveWithoutSaveConfirmation); + 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 void onBackPressed() + { + leaveWithoutSaving(); + } + + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; + } + + private boolean hasChanged(){ + return mAdapter.hasChanged() || !mGroup._id.equals(mGroupNameText.getText().toString().trim()); + } + + @Override + public void onRowLongClicked(int inputPosition) + { + // do nothing for now + } + + @Override + public void onRowClicked(int inputPosition) + { + mAdapter.toggleSelection(inputPosition); + + } +} 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..96f62fdfa --- /dev/null +++ b/app/src/main/java/protect/card_locker/ManageGroupCursorAdapter.java @@ -0,0 +1,111 @@ +package protect.card_locker; + +import android.content.Context; +import android.database.Cursor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ManageGroupCursorAdapter extends LoyaltyCardCursorAdapter { + private HashMap mIndexCardMap; + private HashMap mInGroupOverlay; + private HashMap mIsLoyaltyCardInGroupCache; + private HashMap> mGetGroupCache; + final private Group mGroup; + final private DBHelper mDb; + public ManageGroupCursorAdapter(Context inputContext, Cursor inputCursor, CardAdapterListener inputListener, Group group){ + super(inputContext, inputCursor, inputListener); + mGroup = new Group(group._id, group.order); + mInGroupOverlay = new HashMap<>(); + mDb = new DBHelper(inputContext); + } + + @Override + public void swapCursor(Cursor inputCursor) { + super.swapCursor(inputCursor); + mIndexCardMap = new HashMap<>(); + mIsLoyaltyCardInGroupCache = new HashMap<>(); + mGetGroupCache = new HashMap<>(); + } + + @Override + public void onBindViewHolder(LoyaltyCardListItemViewHolder inputHolder, Cursor inputCursor){ + LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(inputCursor); + Boolean overlayValue = mInGroupOverlay.get(loyaltyCard.id); + if((overlayValue != null? overlayValue: isLoyaltyCardInGroup(loyaltyCard.id))) { + mAnimationItemsIndex.put(inputCursor.getPosition(), true); + mSelectedItems.put(inputCursor.getPosition(), true); + } + mIndexCardMap.put(inputCursor.getPosition(), loyaltyCard.id); + super.onBindViewHolder(inputHolder, inputCursor); + } + + private List getGroups(int cardId){ + List cache = mGetGroupCache.get(cardId); + if(cache != null){ + return cache; + } + List groups = mDb.getLoyaltyCardGroups(cardId); + mGetGroupCache.put(cardId, groups); + return groups; + } + + private boolean isLoyaltyCardInGroup(int cardId){ + Boolean cache = mIsLoyaltyCardInGroupCache.get(cardId); + if(cache != null){ + return cache; + } + List groups = getGroups(cardId); + if (groups.contains(mGroup)){ + mIsLoyaltyCardInGroupCache.put(cardId, true); + return true; + } + mIsLoyaltyCardInGroupCache.put(cardId, false); + return false; + } + + @Override + public void toggleSelection(int inputPosition){ + super.toggleSelection(inputPosition); + Integer cardId = mIndexCardMap.get(inputPosition); + if (cardId == null){ + throw(new RuntimeException("cardId should not be null here")); + } + Boolean overlayValue = mInGroupOverlay.get(cardId); + if (overlayValue == null){ + mInGroupOverlay.put(cardId, !isLoyaltyCardInGroup(cardId)); + }else{ + mInGroupOverlay.remove(cardId); + } + } + + public boolean hasChanged() { + return mInGroupOverlay.size() > 0; + } + + public void commitToDatabase(){ + for(Map.Entry entry: mInGroupOverlay.entrySet()){ + int cardId = entry.getKey(); + List groups = getGroups(cardId); + if(entry.getValue()){ + groups.add(mGroup); + }else{ + groups.remove(mGroup); + } + mDb.setLoyaltyCardGroups(cardId, groups); + } + } + + public void importInGroupState(HashMap cardIdInGroupMap) { + mInGroupOverlay = new HashMap<>(cardIdInGroupMap); + } + + public HashMap exportInGroupState(){ + return new HashMap<>(mInGroupOverlay); + } + + public int getCountFromCursor() { + return super.getCursor().getCount(); + } +} diff --git a/app/src/main/java/protect/card_locker/ManageGroupsActivity.java b/app/src/main/java/protect/card_locker/ManageGroupsActivity.java index 60e6dd35f..116dc7a1c 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; @@ -9,6 +10,7 @@ import android.view.View; import android.view.WindowManager; import android.widget.EditText; import android.widget.TextView; +import android.widget.Toast; import com.google.android.material.floatingactionbutton.FloatingActionButton; @@ -117,7 +119,16 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro builder.setView(input); builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> { - mDb.insertGroup(input.getText().toString()); + String inputString = input.getText().toString().trim(); + if(inputString.length() == 0){ + Toast.makeText(getApplicationContext(), R.string.group_name_is_empty, Toast.LENGTH_SHORT).show(); + return; + } + if (mDb.getGroup(inputString) != null) { + Toast.makeText(getApplicationContext(), R.string.group_name_already_in_use, Toast.LENGTH_SHORT).show(); + return; + } + mDb.insertGroup(inputString); updateGroupList(); }); builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel()); @@ -176,25 +187,9 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro @Override public void onEditButtonClicked(View view) { - final String groupName = getGroupName(view); - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.enter_group_name); - final EditText input = new EditText(this); - input.setInputType(InputType.TYPE_CLASS_TEXT); - input.setText(groupName); - builder.setView(input); - - builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> { - String newGroupName = input.getText().toString(); - mDb.updateGroup(groupName, newGroupName); - updateGroupList(); - }); - 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(); + Intent intent = new Intent(this, ManageGroupActivity.class); + intent.putExtra("group", getGroupName(view)); + startActivity(intent); } @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..4a1855f48 --- /dev/null +++ b/app/src/main/res/layout/activity_manage_group.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4e14f6b22..3e5e9621c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - + Search Add @@ -7,6 +7,7 @@ %d cards selected Click the + plus button to add a card, or import some from the ⋮ menu first. + You don\'t have any loyalty cards yet. Once you\'ve added some you can add them to the group here. Didn\'t find anything. Try changing your search. Name Note @@ -123,12 +124,16 @@ Card data exported Enter group name Groups + Edit Group Click the + plus button to add groups for categorization first. This group does not contain any cards %d card %d cards + Group name already in use + Group name cannot be empty + Group updated All Delete group? Install a file manager first. @@ -139,6 +144,7 @@ Manually enter card ID Select image from gallery Groups: %s + Editing Group: %s Expires: %s Expired: %s Balance: %s