Group management POC

This commit is contained in:
Katharine
2021-10-28 01:25:50 +08:00
parent d8794811a1
commit d5d921a1c8
13 changed files with 1358 additions and 46 deletions

View File

@@ -1,14 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="protect.card_locker"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="protect.card_locker">
<uses-permission
android:name="android.permission.CAMERA"/>
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature
android:name="android.hardware.camera"
@@ -17,8 +16,6 @@
android:name="android.hardware.camera.autofocus"
android:required="false" />
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android"/>
<application
android:name=".LoyaltyCardLockerApplication"
android:allowBackup="true"
@@ -27,98 +24,106 @@
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name="protect.card_locker.MainActivity"
android:name=".ManageGroupActivity"
android:exported="true"
android:theme="@style/Theme.App.Starting"/>
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.App.Starting"
android:exported="true">
android:theme="@style/Theme.App.Starting">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".AboutActivity"
android:label="@string/about"
android:theme="@style/AppTheme.NoActionBar">
</activity>
android:theme="@style/AppTheme.NoActionBar"></activity>
<activity
android:name=".ManageGroupsActivity"
android:label="@string/groups"
android:theme="@style/AppTheme.NoActionBar">
</activity>
android:theme="@style/AppTheme.NoActionBar"></activity>
<activity
android:name=".LoyaltyCardViewActivity"
android:exported="true"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:exported="true"/>
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".LoyaltyCardEditActivity"
android:exported="true"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:exported="true">
<intent-filter android:autoVerify="true" android:label="@string/app_name">
android:windowSoftInputMode="stateHidden">
<intent-filter
android:autoVerify="true"
android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Main card sharing URIs -->
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="@string/intent_import_card_from_url_host_catima_app"
<data
android:host="@string/intent_import_card_from_url_host_catima_app"
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_catima_app" />
</intent-filter>
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Old card sharing URIs -->
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="@string/intent_import_card_from_url_host_thelastproject"
<data
android:host="@string/intent_import_card_from_url_host_thelastproject"
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_thelastproject" />
<data android:host="@string/intent_import_card_from_url_host_brarcher"
<data
android:host="@string/intent_import_card_from_url_host_brarcher"
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_brarcher" />
</intent-filter>
</activity>
<activity
android:name=".ScanActivity"
android:label="@string/scanCardBarcode"
android:theme="@style/AppTheme.NoActionBar"/>
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".BarcodeSelectorActivity"
android:label="@string/selectBarcodeTitle"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden"/>
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".preferences.SettingsActivity"
android:label="@string/settings"
android:theme="@style/AppTheme.NoActionBar"/>
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".ImportExportActivity"
android:label="@string/importExport"
android:theme="@style/AppTheme.NoActionBar"/>
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".CardShortcutConfigure"
android:exported="true"
android:label="@string/cardShortcut"
android:theme="@style/AppTheme.NoActionBar"
android:exported="true">
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT"/>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:grantUriPermissions="true"
android:authorities="${applicationId}"
android:exported="false"
android:authorities="${applicationId}">
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths"/>
android:resource="@xml/file_provider_paths" />
</provider>
</application>
</manifest>
</manifest>

View File

@@ -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.
*

View File

@@ -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<Group> CREATOR = new Creator<Group>() {
@Override
public Group createFromParcel(Parcel in) {
return new Group(in);
}
@Override
public Group[] newArray(int size) {
return new Group[size];
}
};
}

View File

@@ -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<Integer, Boolean> 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);
}
}

View File

@@ -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<Integer, Boolean> map;
public ManageGroupActivityInGroupState(HashMap<Integer, Boolean> in){
map = (HashMap<Integer, Boolean>)in.clone();
}
@Override
public int describeContents(){
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(map.size());
for(Map.Entry<Integer, Boolean> entry : map.entrySet()){
dest.writeInt(entry.getKey());
dest.writeInt(entry.getValue() ? 1 : 0);
}
}
protected ManageGroupActivityInGroupState(Parcel in){
map = new HashMap<Integer, Boolean>();
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<ManageGroupActivityInGroupState> CREATOR = new Creator<ManageGroupActivityInGroupState>() {
@Override
public ManageGroupActivityInGroupState createFromParcel(Parcel in) {
return new ManageGroupActivityInGroupState(in);
}
@Override
public ManageGroupActivityInGroupState[] newArray(int size) {
return new ManageGroupActivityInGroupState[size];
}
};
public HashMap<Integer, Boolean> getMap(){
return (HashMap<Integer,Boolean>)map.clone();
}
}

View File

@@ -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<ManageGroupCursorAdapter.ManageGroupListItemViewHolder> {
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<Integer, ManageGroupLoyaltyCard> mIndexCardMap;
private HashMap<Integer, Boolean> 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<Integer, Boolean>();
swapCursor(mCursor);
}
@Override
public void swapCursor(Cursor inputCursor) {
mIndexCardMap = new HashMap<Integer, ManageGroupLoyaltyCard>();
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<Integer, ManageGroupLoyaltyCard> fetchWholeQuery (){
HashMap<Integer, ManageGroupLoyaltyCard> res = new HashMap<Integer, ManageGroupLoyaltyCard>();
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<Integer, ManageGroupLoyaltyCard> cache = fetchWholeQuery();
for(Map.Entry<Integer, Boolean> 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<Integer, ManageGroupLoyaltyCard> cache = fetchWholeQuery();
for(Map.Entry<Integer, Boolean> 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<Integer, Boolean> exportInGroupState(){
return (HashMap<Integer, Boolean>)mInGroupOverlay.clone();
}
public void importInGroupState(HashMap<Integer, Boolean> cardIdInGroupMap){
mInGroupOverlay = (HashMap<Integer, Boolean>)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;
}
}

View File

@@ -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<LoyaltyCard> CREATOR = new Creator<LoyaltyCard>() {
@Override
public LoyaltyCard createFromParcel(Parcel in) {
return new LoyaltyCard(in);
}
@Override
public LoyaltyCard[] newArray(int size) {
return new LoyaltyCard[size];
}
};
}

View File

@@ -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

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="protect.card_locker.ManageGroupActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/groups"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:tabMode="scrollable" />
</com.google.android.material.appbar.AppBarLayout>
<TextView
android:id="@+id/textViewEditGroupName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/enter_group_name" />
<EditText
android:id="@+id/editTextGroupName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName" />
<include
android:id="@+id/include"
layout="@layout/content_manage_group" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="protect.card_locker.ManageGroupActivity"
tools:showIn="@layout/activity_manage_group">
<TextView
style="@style/AppTheme.TextView.NoData"
android:id="@+id/helpText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/noGiftCards"
android:visibility="gone"/>
<TextView
style="@style/AppTheme.TextView.NoData"
android:id="@+id/noMatchingCardsText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/noMatchingGiftCards"
android:visibility="gone"/>
<TextView
style="@style/AppTheme.TextView.NoData"
android:id="@+id/noGroupCardsText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/noGroupCards"
android:visibility="gone"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
app:spanCount="@integer/main_view_card_columns"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:background="@color/mainLoyaltyCardBackground"
android:visibility="gone"/>
</RelativeLayout>

View File

@@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/row"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/information_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.card.MaterialCardView
android:id="@+id/thumbnail_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="4dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
>
<RelativeLayout
android:id="@+id/thumbnail_front"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/storeContainer"
android:layout_alignBottom="@+id/storeContainer"
android:layout_gravity="center_vertical">
<ImageView
android:id="@+id/thumbnail"
android:scaleType="centerCrop"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_centerVertical="true"
android:contentDescription="@string/thumbnailDescription"
android:src="@mipmap/ic_launcher" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/thumbnail_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical">
<ImageView
android:id="@+id/selected_thumbnail"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_centerInParent="true"
android:contentDescription="@string/thumbnailDescription"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_done" />
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/store"
android:paddingStart="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left"
android:layout_toRightOf="@+id/thumbnail_container"
android:layout_toLeftOf="@+id/star"
android:textAppearance="?attr/textAppearanceHeadline6" />
<ImageView
android:id="@+id/star"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_centerVertical="true"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_starred_white"
android:contentDescription="@string/starImage"
app:tint="?attr/colorControlNormal"
android:layout_alignParentEnd="true"
android:visibility="gone" />
</RelativeLayout>
<TextView
android:id="@+id/note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
android:visibility="gone" />
<View android:id="@+id/info_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:background="?android:attr/dividerVertical"
android:visibility="gone" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/balance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:paddingEnd="8dp"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
app:drawableLeftCompat="@drawable/ic_baseline_payments_24"
android:drawablePadding="4dp"
android:layout_alignParentStart="true"
android:visibility="gone" />
<TextView
android:id="@+id/expiry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
app:drawableLeftCompat="@drawable/ic_baseline_access_time_24"
android:drawablePadding="4dp"
android:layout_toEndOf="@+id/balance"
android:visibility="gone" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -0,0 +1,10 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="protect.card_locker.ManageGroupActivity">
<item
android:id="@+id/action_confirm"
android:icon="@drawable/ic_done"
android:title="@string/action_search"
app:showAsAction="always|collapseActionView" />
</menu>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_search">Search</string>
<string name="action_add">Add</string>
<plurals name="selectedCardCount">
@@ -129,6 +129,9 @@
<item quantity="one"><xliff:g>%d</xliff:g> card</item>
<item quantity="other"><xliff:g>%d</xliff:g> cards</item>
</plurals>
<string name="group_name_already_in_use">Group name already in use!</string>
<string name="group_name_is_empty">Group name cannot be empty!</string>
<string name="group_updated">Group updated!</string>
<string name="all">All</string>
<string name="deleteConfirmationGroup">Delete group?</string>
<string name="failedOpeningFileManager">Install a file manager first.</string>
@@ -245,4 +248,6 @@
<string name="rate_this_app">Rate this app</string>
<string name="on_google_play">on Google Play</string>
<string name="report_error">Report Error</string>
<string name="discard_changes">Discard Changes</string>
<string name="discard_changes_confirm">There are unsaved changes that were made, do you wish to leave without saving?</string>
</resources>