diff --git a/CHANGELOG.md b/CHANGELOG.md
index 73ff9aa57..360e28bd4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,23 @@
# Changelog
-## Unreleased - 90
+## v2.10.0 - 92 (2021-11-20)
+
+- New main screen layout
+- Fix bottomsheet sizing issues when switching in and out of fullscreen
+
+## v2.9.0 - 91 (2021-11-14)
+
+- Improved group management support
+- Support cropping images
+- Fix image data loss when saving after rotating in edit view
+- Ability to set a custom image as card icon
+
+## v2.8.1 - 90 (2021-10-27)
+
+- Fix dots in card view having the wrong colour when changing theme manually
+- Fix crash in card view on rotation/theme change
+- Fix flashing of cards list
+- Fix text overlaying star icon
## v2.8.0 - 89 (2021-10-25)
diff --git a/app/build.gradle b/app/build.gradle
index d409fcb67..21f7435be 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -18,8 +18,8 @@ android {
applicationId "me.hackerchick.catima"
minSdkVersion 21
targetSdkVersion 31
- versionCode 89
- versionName "2.8.0"
+ versionCode 92
+ versionName "2.10.0"
vectorDrawables.useSupportLibrary true
multiDexEnabled true
@@ -80,11 +80,13 @@ android {
dependencies {
// AndroidX
- implementation 'androidx.appcompat:appcompat:1.3.1'
- implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
+ implementation 'androidx.appcompat:appcompat:1.4.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.exifinterface:exifinterface:1.3.3'
+ implementation 'androidx.palette:palette:1.0.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'com.google.android.material:material:1.4.0'
+ implementation 'com.github.yalantis:ucrop:2.2.7'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
// Splash Screen
@@ -96,7 +98,7 @@ dependencies {
implementation 'org.apache.commons:commons-csv:1.9.0'
implementation 'com.jaredrummler:colorpicker:1.1.0'
implementation 'com.github.invissvenska:NumberPickerPreference:1.0.3'
- implementation 'net.lingala.zip4j:zip4j:2.9.0'
+ implementation 'net.lingala.zip4j:zip4j:2.9.1'
// SpotBugs
implementation 'io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0'
@@ -104,7 +106,7 @@ dependencies {
// Testing
testImplementation 'androidx.test:core:1.4.0'
testImplementation 'junit:junit:4.13.2'
- testImplementation 'org.robolectric:robolectric:4.6.1'
+ testImplementation 'org.robolectric:robolectric:4.7.2'
}
tasks.withType(SpotBugsTask) {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 48ab22cb0..2f7674721 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" />
@@ -128,4 +137,4 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/java/protect/card_locker/AboutActivity.java b/app/src/main/java/protect/card_locker/AboutActivity.java
index 739d5ef24..2a56836f0 100644
--- a/app/src/main/java/protect/card_locker/AboutActivity.java
+++ b/app/src/main/java/protect/card_locker/AboutActivity.java
@@ -24,22 +24,19 @@ import androidx.appcompat.widget.Toolbar;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.text.HtmlCompat;
-public class AboutActivity extends CatimaAppCompatActivity implements View.OnClickListener
-{
+public class AboutActivity extends CatimaAppCompatActivity implements View.OnClickListener {
private static final String TAG = "Catima";
ConstraintLayout version_history, translate, license, repo, privacy, error, credits, rate;
@Override
- protected void onCreate(Bundle savedInstanceState)
- {
+ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.about);
setContentView(R.layout.about_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
- if(actionBar != null)
- {
+ if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
@@ -59,7 +56,8 @@ public class AboutActivity extends CatimaAppCompatActivity implements View.OnCli
contributors.append(" ");
contributors.append(tmp);
}
- } catch (IOException ignored) {}
+ } catch (IOException ignored) {
+ }
final List USED_LIBRARIES = new ArrayList<>();
USED_LIBRARIES.add(new ThirdPartyInfo("Color Picker", "https://github.com/jaredrummler/ColorPicker", "Apache 2.0"));
@@ -73,14 +71,12 @@ public class AboutActivity extends CatimaAppCompatActivity implements View.OnCli
USED_ASSETS.add(new ThirdPartyInfo("Android icons", "https://fonts.google.com/icons?selected=Material+Icons", "Apache 2.0"));
StringBuilder libs = new StringBuilder().append(" ");
- for (ThirdPartyInfo entry : USED_LIBRARIES)
- {
+ for (ThirdPartyInfo entry : USED_LIBRARIES) {
libs.append(" ").append(entry.name()).append(" (").append(entry.license()).append(")");
}
StringBuilder resources = new StringBuilder().append(" ");
- for (ThirdPartyInfo entry : USED_ASSETS)
- {
+ for (ThirdPartyInfo entry : USED_ASSETS) {
resources.append(" ").append(entry.name()).append(" (").append(entry.license()).append(")");
}
@@ -88,13 +84,10 @@ public class AboutActivity extends CatimaAppCompatActivity implements View.OnCli
int year = Calendar.getInstance().get(Calendar.YEAR);
String version = "?";
- try
- {
+ try {
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
version = pi.versionName;
- }
- catch (PackageManager.NameNotFoundException e)
- {
+ } catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Package name not found", e);
}
@@ -134,13 +127,13 @@ public class AboutActivity extends CatimaAppCompatActivity implements View.OnCli
credits.setOnClickListener(view -> new AlertDialog.Builder(this)
.setTitle(R.string.credits)
.setMessage(contributorInfo.toString())
- .setPositiveButton(R.string.ok, (dialogInterface, i) -> {})
+ .setPositiveButton(R.string.ok, (dialogInterface, i) -> {
+ })
.show());
}
@Override
- public boolean onOptionsItemSelected(MenuItem item)
- {
+ public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
finish();
diff --git a/app/src/main/java/protect/card_locker/BarcodeSelectorActivity.java b/app/src/main/java/protect/card_locker/BarcodeSelectorActivity.java
index 0251e9230..e33df1614 100644
--- a/app/src/main/java/protect/card_locker/BarcodeSelectorActivity.java
+++ b/app/src/main/java/protect/card_locker/BarcodeSelectorActivity.java
@@ -6,45 +6,37 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
-import android.util.Pair;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewTreeObserver;
import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.TextView;
+import android.widget.ListView;
import android.widget.Toast;
import com.google.zxing.BarcodeFormat;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.ArrayList;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
-import protect.card_locker.async.TaskHandler;
-
/**
* This activity is callable and will allow a user to enter
* barcode data and generate all barcodes possible for
* the data. The user may then select any barcode, where its
* data and type will be returned to the caller.
*/
-public class BarcodeSelectorActivity extends CatimaAppCompatActivity {
+public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements BarcodeSelectorAdapter.BarcodeSelectorListener {
private static final String TAG = "Catima";
// Result this activity will return
public static final String BARCODE_CONTENTS = "contents";
public static final String BARCODE_FORMAT = "format";
- private Map> barcodeViewMap;
-
- final private TaskHandler mTasks = new TaskHandler();
-
private final Handler typingDelayHandler = new Handler(Looper.getMainLooper());
public static final Integer INPUT_DELAY = 250;
+ private BarcodeSelectorAdapter mAdapter;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -57,21 +49,10 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity {
actionBar.setDisplayHomeAsUpEnabled(true);
}
- barcodeViewMap = new HashMap<>();
- barcodeViewMap.put(BarcodeFormat.AZTEC.name(), new Pair<>(R.id.aztecBarcode, R.id.aztecBarcodeText));
- barcodeViewMap.put(BarcodeFormat.CODE_39.name(), new Pair<>(R.id.code39Barcode, R.id.code39BarcodeText));
- barcodeViewMap.put(BarcodeFormat.CODE_128.name(), new Pair<>(R.id.code128Barcode, R.id.code128BarcodeText));
- barcodeViewMap.put(BarcodeFormat.CODABAR.name(), new Pair<>(R.id.codabarBarcode, R.id.codabarBarcodeText));
- barcodeViewMap.put(BarcodeFormat.DATA_MATRIX.name(), new Pair<>(R.id.datamatrixBarcode, R.id.datamatrixBarcodeText));
- barcodeViewMap.put(BarcodeFormat.EAN_8.name(), new Pair<>(R.id.ean8Barcode, R.id.ean8BarcodeText));
- barcodeViewMap.put(BarcodeFormat.EAN_13.name(), new Pair<>(R.id.ean13Barcode, R.id.ean13BarcodeText));
- barcodeViewMap.put(BarcodeFormat.ITF.name(), new Pair<>(R.id.itfBarcode, R.id.itfBarcodeText));
- barcodeViewMap.put(BarcodeFormat.PDF_417.name(), new Pair<>(R.id.pdf417Barcode, R.id.pdf417BarcodeText));
- barcodeViewMap.put(BarcodeFormat.QR_CODE.name(), new Pair<>(R.id.qrcodeBarcode, R.id.qrcodeBarcodeText));
- barcodeViewMap.put(BarcodeFormat.UPC_A.name(), new Pair<>(R.id.upcaBarcode, R.id.upcaBarcodeText));
- barcodeViewMap.put(BarcodeFormat.UPC_E.name(), new Pair<>(R.id.upceBarcode, R.id.upceBarcodeText));
-
EditText cardId = findViewById(R.id.cardId);
+ ListView mBarcodeList = findViewById(R.id.barcodes);
+ mAdapter = new BarcodeSelectorAdapter(this, new ArrayList<>(), this);
+ mBarcodeList.setAdapter(mAdapter);
cardId.addTextChangedListener(new SimpleTextWatcher() {
@Override
@@ -104,16 +85,13 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity {
}
private void generateBarcodes(String value) {
- // Attempt to stop any async tasks which may not have been started yet
- // TODO this can be very much optimized by only generating Barcodes visible to the User
- mTasks.flushTaskList(TaskHandler.TYPE.BARCODE, true, false, false);
-
// Update barcodes
- for (Map.Entry> entry : barcodeViewMap.entrySet()) {
- ImageView image = findViewById(entry.getValue().first);
- TextView text = findViewById(entry.getValue().second);
- createBarcodeOption(image, entry.getKey(), value, text);
+ ArrayList barcodes = new ArrayList<>();
+ for (BarcodeFormat barcodeFormat : CatimaBarcode.barcodeFormats) {
+ CatimaBarcode catimaBarcode = CatimaBarcode.fromBarcode(barcodeFormat);
+ barcodes.add(new CatimaBarcodeWithValue(catimaBarcode, value));
}
+ mAdapter.setBarcodes(barcodes);
}
private void setButtonListener(final View button, final String cardId) {
@@ -127,48 +105,6 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity {
});
}
- private void createBarcodeOption(final ImageView image, final String formatType, final String cardId, final TextView text) {
- final CatimaBarcode format = CatimaBarcode.fromName(formatType);
-
- image.setImageBitmap(null);
- image.setOnClickListener(v -> {
- Log.d(TAG, "Selected barcode type " + formatType);
-
- if (!((boolean) image.getTag())) {
- Toast.makeText(BarcodeSelectorActivity.this, getString(R.string.wrongValueForBarcodeType), Toast.LENGTH_LONG).show();
- return;
- }
-
- Intent result = new Intent();
- result.putExtra(BARCODE_FORMAT, formatType);
- result.putExtra(BARCODE_CONTENTS, cardId);
- BarcodeSelectorActivity.this.setResult(RESULT_OK, result);
- finish();
- });
-
- if (image.getHeight() == 0) {
- // The size of the ImageView is not yet available as it has not
- // yet been drawn. Wait for it to be drawn so the size is available.
- image.getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- Log.d(TAG, "Global layout finished, type: + " + formatType + ", width: " + image.getWidth());
- image.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-
- Log.d(TAG, "Generating barcode for type " + formatType);
-
- BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), image, cardId, format, text, true, null);
- mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
- }
- });
- } else {
- Log.d(TAG, "Generating barcode for type " + formatType);
- BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), image, cardId, format, text, true, null);
- mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
- }
- }
-
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
@@ -179,4 +115,26 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity {
return super.onOptionsItemSelected(item);
}
+
+ @Override
+ public void onRowClicked(int inputPosition, View view) {
+ CatimaBarcodeWithValue barcodeWithValue = mAdapter.getItem(inputPosition);
+ CatimaBarcode catimaBarcode = barcodeWithValue.catimaBarcode();
+
+ if (!mAdapter.isValid(view)) {
+ Toast.makeText(this, getString(R.string.wrongValueForBarcodeType), Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ String barcodeFormat = catimaBarcode.format().name();
+ String value = barcodeWithValue.value();
+
+ Log.d(TAG, "Selected barcode type " + barcodeFormat);
+
+ Intent result = new Intent();
+ result.putExtra(BARCODE_FORMAT, barcodeFormat);
+ result.putExtra(BARCODE_CONTENTS, value);
+ BarcodeSelectorActivity.this.setResult(RESULT_OK, result);
+ finish();
+ }
}
diff --git a/app/src/main/java/protect/card_locker/BarcodeSelectorAdapter.java b/app/src/main/java/protect/card_locker/BarcodeSelectorAdapter.java
new file mode 100644
index 000000000..ff0e223a4
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/BarcodeSelectorAdapter.java
@@ -0,0 +1,102 @@
+package protect.card_locker;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import protect.card_locker.async.TaskHandler;
+
+public class BarcodeSelectorAdapter extends ArrayAdapter {
+ private static final String TAG = "Catima";
+
+ private final TaskHandler mTasks = new TaskHandler();
+ private final BarcodeSelectorListener mListener;
+
+ private static class ViewHolder {
+ ImageView image;
+ TextView text;
+ }
+
+ public interface BarcodeSelectorListener {
+ void onRowClicked(int inputPosition, View view);
+ }
+
+ public BarcodeSelectorAdapter(Context context, ArrayList barcodes, BarcodeSelectorListener barcodeSelectorListener) {
+ super(context, 0, barcodes);
+ mListener = barcodeSelectorListener;
+ }
+
+ public void setBarcodes(ArrayList barcodes) {
+ clear();
+ addAll(barcodes);
+ notifyDataSetChanged();
+ mTasks.flushTaskList(TaskHandler.TYPE.BARCODE, true, false, false);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ CatimaBarcodeWithValue catimaBarcodeWithValue = getItem(position);
+ CatimaBarcode catimaBarcode = catimaBarcodeWithValue.catimaBarcode();
+ String value = catimaBarcodeWithValue.value();
+
+ ViewHolder viewHolder;
+ if (convertView == null) {
+ viewHolder = new ViewHolder();
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ convertView = inflater.inflate(R.layout.barcode_layout, parent, false);
+ viewHolder.image = convertView.findViewById(R.id.barcodeImage);
+ viewHolder.text = convertView.findViewById(R.id.barcodeName);
+ convertView.setTag(viewHolder);
+ } else {
+ viewHolder = (ViewHolder) convertView.getTag();
+ }
+
+ createBarcodeOption(viewHolder.image, catimaBarcode.format().name(), value, viewHolder.text);
+
+ View finalConvertView = convertView;
+ convertView.setOnClickListener(view -> mListener.onRowClicked(position, finalConvertView));
+
+ return convertView;
+ }
+
+ public boolean isValid(View view) {
+ ViewHolder viewHolder = (ViewHolder) view.getTag();
+ return viewHolder.image.getTag() != null && (boolean) viewHolder.image.getTag();
+ }
+
+ private void createBarcodeOption(final ImageView image, final String formatType, final String cardId, final TextView text) {
+ final CatimaBarcode format = CatimaBarcode.fromName(formatType);
+
+ image.setImageBitmap(null);
+
+ if (image.getHeight() == 0) {
+ // The size of the ImageView is not yet available as it has not
+ // yet been drawn. Wait for it to be drawn so the size is available.
+ image.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ Log.d(TAG, "Global layout finished, type: + " + formatType + ", width: " + image.getWidth());
+ image.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+
+ Log.d(TAG, "Generating barcode for type " + formatType);
+
+ BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, text, true, null);
+ mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
+ }
+ });
+ } else {
+ Log.d(TAG, "Generating barcode for type " + formatType);
+ BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, text, true, null);
+ mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
+ }
+ }
+}
diff --git a/app/src/main/java/protect/card_locker/BaseCursorAdapter.java b/app/src/main/java/protect/card_locker/BaseCursorAdapter.java
index a20041e1d..7c9d77899 100644
--- a/app/src/main/java/protect/card_locker/BaseCursorAdapter.java
+++ b/app/src/main/java/protect/card_locker/BaseCursorAdapter.java
@@ -4,14 +4,12 @@ import android.database.Cursor;
import androidx.recyclerview.widget.RecyclerView;
-public abstract class BaseCursorAdapter extends RecyclerView.Adapter
-{
+public abstract class BaseCursorAdapter extends RecyclerView.Adapter {
private Cursor mCursor;
private boolean mDataValid;
private int mRowIDColumn;
- public BaseCursorAdapter(Cursor inputCursor)
- {
+ public BaseCursorAdapter(Cursor inputCursor) {
setHasStableIds(true);
swapCursor(inputCursor);
}
@@ -19,15 +17,12 @@ public abstract class BaseCursorAdapter exten
public abstract void onBindViewHolder(V inputHolder, Cursor inputCursor);
@Override
- public void onBindViewHolder(V inputHolder, int inputPosition)
- {
- if (!mDataValid)
- {
+ public void onBindViewHolder(V inputHolder, int inputPosition) {
+ if (!mDataValid) {
throw new IllegalStateException("Cannot bind view holder when cursor is in invalid state.");
}
- if (!mCursor.moveToPosition(inputPosition))
- {
+ if (!mCursor.moveToPosition(inputPosition)) {
throw new IllegalStateException("Could not move cursor to position " + inputPosition + " when trying to bind view holder");
}
@@ -35,49 +30,37 @@ public abstract class BaseCursorAdapter exten
}
@Override
- public int getItemCount()
- {
- if (mDataValid)
- {
+ public int getItemCount() {
+ if (mDataValid) {
return mCursor.getCount();
- }
- else
- {
+ } else {
return 0;
}
}
@Override
- public long getItemId(int inputPosition)
- {
- if (!mDataValid)
- {
+ public long getItemId(int inputPosition) {
+ if (!mDataValid) {
throw new IllegalStateException("Cannot lookup item id when cursor is in invalid state.");
}
- if (!mCursor.moveToPosition(inputPosition))
- {
+ if (!mCursor.moveToPosition(inputPosition)) {
throw new IllegalStateException("Could not move cursor to position " + inputPosition + " when trying to get an item id");
}
return mCursor.getLong(mRowIDColumn);
}
- public void swapCursor(Cursor inputCursor)
- {
- if (inputCursor == mCursor)
- {
+ public void swapCursor(Cursor inputCursor) {
+ if (inputCursor == mCursor) {
return;
}
- if (inputCursor != null)
- {
+ if (inputCursor != null) {
mCursor = inputCursor;
mDataValid = true;
notifyDataSetChanged();
- }
- else
- {
+ } else {
notifyItemRangeRemoved(0, getItemCount());
mCursor = null;
mRowIDColumn = -1;
diff --git a/app/src/main/java/protect/card_locker/CardShortcutConfigure.java b/app/src/main/java/protect/card_locker/CardShortcutConfigure.java
index 1b8d771a5..be9cc84d5 100644
--- a/app/src/main/java/protect/card_locker/CardShortcutConfigure.java
+++ b/app/src/main/java/protect/card_locker/CardShortcutConfigure.java
@@ -19,8 +19,7 @@ import androidx.recyclerview.widget.RecyclerView;
/**
* The configuration screen for creating a shortcut.
*/
-public class CardShortcutConfigure extends AppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener
-{
+public class CardShortcutConfigure extends AppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
static final String TAG = "Catima";
final DBHelper mDb = new DBHelper(this);
diff --git a/app/src/main/java/protect/card_locker/CatimaBarcode.java b/app/src/main/java/protect/card_locker/CatimaBarcode.java
index 2b295f5c5..c402f7991 100644
--- a/app/src/main/java/protect/card_locker/CatimaBarcode.java
+++ b/app/src/main/java/protect/card_locker/CatimaBarcode.java
@@ -8,33 +8,33 @@ import java.util.List;
public class CatimaBarcode {
public static final List barcodeFormats = Collections.unmodifiableList(Arrays.asList(
- BarcodeFormat.AZTEC,
- BarcodeFormat.CODE_39,
- BarcodeFormat.CODE_128,
- BarcodeFormat.CODABAR,
- BarcodeFormat.DATA_MATRIX,
- BarcodeFormat.EAN_8,
- BarcodeFormat.EAN_13,
- BarcodeFormat.ITF,
- BarcodeFormat.PDF_417,
- BarcodeFormat.QR_CODE,
- BarcodeFormat.UPC_A,
- BarcodeFormat.UPC_E
+ BarcodeFormat.AZTEC,
+ BarcodeFormat.CODE_39,
+ BarcodeFormat.CODE_128,
+ BarcodeFormat.CODABAR,
+ BarcodeFormat.DATA_MATRIX,
+ BarcodeFormat.EAN_8,
+ BarcodeFormat.EAN_13,
+ BarcodeFormat.ITF,
+ BarcodeFormat.PDF_417,
+ BarcodeFormat.QR_CODE,
+ BarcodeFormat.UPC_A,
+ BarcodeFormat.UPC_E
));
public static final List barcodePrettyNames = Collections.unmodifiableList(Arrays.asList(
- "Aztec",
- "Code 39",
- "Code 128",
- "Codabar",
- "Data Matrix",
- "EAN 8",
- "EAN 13",
- "ITF",
- "PDF 417",
- "QR Code",
- "UPC A",
- "UPC E"
+ "Aztec",
+ "Code 39",
+ "Code 128",
+ "Codabar",
+ "Data Matrix",
+ "EAN 8",
+ "EAN 13",
+ "ITF",
+ "PDF 417",
+ "QR Code",
+ "UPC A",
+ "UPC E"
));
private final BarcodeFormat mBarcodeFormat;
@@ -63,7 +63,7 @@ public class CatimaBarcode {
return barcodeFormats.contains(mBarcodeFormat);
}
- public boolean isSquare(){
+ public boolean isSquare() {
return mBarcodeFormat == BarcodeFormat.AZTEC
|| mBarcodeFormat == BarcodeFormat.DATA_MATRIX
|| mBarcodeFormat == BarcodeFormat.MAXICODE
diff --git a/app/src/main/java/protect/card_locker/CatimaBarcodeWithValue.java b/app/src/main/java/protect/card_locker/CatimaBarcodeWithValue.java
new file mode 100644
index 000000000..cd02ea3ee
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/CatimaBarcodeWithValue.java
@@ -0,0 +1,19 @@
+package protect.card_locker;
+
+public class CatimaBarcodeWithValue {
+ private final CatimaBarcode mCatimaBarcode;
+ private final String mValue;
+
+ public CatimaBarcodeWithValue(CatimaBarcode catimaBarcode, String value) {
+ mCatimaBarcode = catimaBarcode;
+ mValue = value;
+ }
+
+ public CatimaBarcode catimaBarcode() {
+ return mCatimaBarcode;
+ }
+
+ public String value() {
+ return mValue;
+ }
+}
diff --git a/app/src/main/java/protect/card_locker/DBHelper.java b/app/src/main/java/protect/card_locker/DBHelper.java
index 505f3be52..6b9aa515d 100644
--- a/app/src/main/java/protect/card_locker/DBHelper.java
+++ b/app/src/main/java/protect/card_locker/DBHelper.java
@@ -18,21 +18,18 @@ import java.util.Currency;
import java.util.Date;
import java.util.List;
-public class DBHelper extends SQLiteOpenHelper
-{
+public class DBHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "Catima.db";
public static final int ORIGINAL_DATABASE_VERSION = 1;
public static final int DATABASE_VERSION = 14;
- public static class LoyaltyCardDbGroups
- {
+ public static class LoyaltyCardDbGroups {
public static final String TABLE = "groups";
public static final String ID = "_id";
public static final String ORDER = "orderId";
}
- public static class LoyaltyCardDbIds
- {
+ public static class LoyaltyCardDbIds {
public static final String TABLE = "cards";
public static final String ID = "_id";
public static final String STORE = "store";
@@ -50,15 +47,13 @@ public class DBHelper extends SQLiteOpenHelper
public static final String ZOOM_LEVEL = "zoomlevel";
}
- public static class LoyaltyCardDbIdsGroups
- {
+ public static class LoyaltyCardDbIdsGroups {
public static final String TABLE = "cardsGroups";
public static final String cardID = "cardId";
public static final String groupID = "groupId";
}
- public static class LoyaltyCardDbFTS
- {
+ public static class LoyaltyCardDbFTS {
public static final String TABLE = "fts";
public static final String ID = "rowid"; // This should NEVER be changed
public static final String STORE = "store";
@@ -78,16 +73,14 @@ public class DBHelper extends SQLiteOpenHelper
private Context mContext;
- public DBHelper(Context context)
- {
+ public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
}
@Override
- public void onCreate(SQLiteDatabase db)
- {
+ public void onCreate(SQLiteDatabase db) {
// create table for card groups
db.execSQL("CREATE TABLE " + LoyaltyCardDbGroups.TABLE + "(" +
LoyaltyCardDbGroups.ID + " TEXT primary key not null," +
@@ -107,14 +100,14 @@ public class DBHelper extends SQLiteOpenHelper
LoyaltyCardDbIds.BARCODE_ID + " TEXT," +
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," +
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'," +
- LoyaltyCardDbIds.LAST_USED + " INTEGER DEFAULT '0', "+
+ LoyaltyCardDbIds.LAST_USED + " INTEGER DEFAULT '0', " +
LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '100' )");
// create associative table for cards in groups
db.execSQL("CREATE TABLE " + LoyaltyCardDbIdsGroups.TABLE + "(" +
LoyaltyCardDbIdsGroups.cardID + " INTEGER," +
LoyaltyCardDbIdsGroups.groupID + " TEXT," +
- "primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID +"))");
+ "primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID + "))");
// create FTS search table
db.execSQL("CREATE VIRTUAL TABLE " + LoyaltyCardDbFTS.TABLE + " USING fts4(" +
@@ -124,67 +117,57 @@ public class DBHelper extends SQLiteOpenHelper
@Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
- {
- if(oldVersion < 2 && newVersion >= 2)
- {
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion < 2 && newVersion >= 2) {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.NOTE + " TEXT not null default ''");
}
- if(oldVersion < 3 && newVersion >= 3)
- {
+ if (oldVersion < 3 && newVersion >= 3) {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.HEADER_COLOR + " INTEGER");
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER");
}
- if(oldVersion < 4 && newVersion >= 4)
- {
+ if (oldVersion < 4 && newVersion >= 4) {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'");
}
- if(oldVersion < 5 && newVersion >= 5)
- {
+ if (oldVersion < 5 && newVersion >= 5) {
db.execSQL("CREATE TABLE " + LoyaltyCardDbGroups.TABLE + "(" +
LoyaltyCardDbGroups.ID + " TEXT primary key not null)");
db.execSQL("CREATE TABLE " + LoyaltyCardDbIdsGroups.TABLE + "(" +
LoyaltyCardDbIdsGroups.cardID + " INTEGER," +
LoyaltyCardDbIdsGroups.groupID + " TEXT," +
- "primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID +"))");
+ "primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID + "))");
}
- if(oldVersion < 6 && newVersion >= 6)
- {
+ if (oldVersion < 6 && newVersion >= 6) {
db.execSQL("ALTER TABLE " + LoyaltyCardDbGroups.TABLE
+ " ADD COLUMN " + LoyaltyCardDbGroups.ORDER + " INTEGER DEFAULT '0'");
}
- if(oldVersion < 7 && newVersion >= 7)
- {
+ if (oldVersion < 7 && newVersion >= 7) {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.EXPIRY + " INTEGER");
}
- if(oldVersion < 8 && newVersion >= 8)
- {
+ if (oldVersion < 8 && newVersion >= 8) {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.BALANCE + " TEXT not null DEFAULT '0'");
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.BALANCE_TYPE + " TEXT");
}
- if(oldVersion < 9 && newVersion >= 9)
- {
+ if (oldVersion < 9 && newVersion >= 9) {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.BARCODE_ID + " TEXT");
}
- if(oldVersion < 10 && newVersion >= 10)
- {
+ if (oldVersion < 10 && newVersion >= 10) {
// SQLite doesn't support modify column
// So we need to create a temp column to make barcode type nullable
// Let's drop header text colour too while we're at it
@@ -277,14 +260,12 @@ public class DBHelper extends SQLiteOpenHelper
db.endTransaction();
}
- if(oldVersion < 11 && newVersion >= 11)
- {
+ if (oldVersion < 11 && newVersion >= 11) {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.LAST_USED + " INTEGER DEFAULT '0'");
}
- if(oldVersion < 12 && newVersion >= 12)
- {
+ if (oldVersion < 12 && newVersion >= 12) {
db.execSQL("CREATE VIRTUAL TABLE " + LoyaltyCardDbFTS.TABLE + " USING fts4(" +
LoyaltyCardDbFTS.STORE + ", " + LoyaltyCardDbFTS.NOTE + ", " +
"tokenize=unicode61);");
@@ -301,8 +282,7 @@ public class DBHelper extends SQLiteOpenHelper
}
}
- if(oldVersion < 13 && newVersion >= 13)
- {
+ if (oldVersion < 13 && newVersion >= 13) {
db.execSQL("DELETE FROM " + LoyaltyCardDbFTS.TABLE + ";");
Cursor cursor = db.rawQuery("SELECT * FROM " + LoyaltyCardDbIds.TABLE + ";", null, null);
@@ -323,7 +303,7 @@ public class DBHelper extends SQLiteOpenHelper
cursor.close();
}
- if(oldVersion < 14 && newVersion >= 14){
+ if (oldVersion < 14 && newVersion >= 14) {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '100' ");
}
@@ -374,8 +354,7 @@ public class DBHelper extends SQLiteOpenHelper
final BigDecimal balance, final Currency balanceType,
final String cardId, final String barcodeId,
final CatimaBarcode barcodeType, final Integer headerColor,
- final int starStatus, final Long lastUsed)
- {
+ final int starStatus, final Long lastUsed) {
SQLiteDatabase db = getWritableDatabase();
db.beginTransaction();
@@ -408,8 +387,7 @@ public class DBHelper extends SQLiteOpenHelper
final Currency balanceType, final String cardId,
final String barcodeId, final CatimaBarcode barcodeType,
final Integer headerColor, final int starStatus,
- final Long lastUsed)
- {
+ final Long lastUsed) {
db.beginTransaction();
// Card
@@ -441,8 +419,7 @@ public class DBHelper extends SQLiteOpenHelper
final Currency balanceType, final String cardId,
final String barcodeId, final CatimaBarcode barcodeType,
final Integer headerColor, final int starStatus,
- final Long lastUsed)
- {
+ final Long lastUsed) {
db.beginTransaction();
// Card
@@ -474,8 +451,7 @@ public class DBHelper extends SQLiteOpenHelper
final Date expiry, final BigDecimal balance,
final Currency balanceType, final String cardId,
final String barcodeId, final CatimaBarcode barcodeType,
- final Integer headerColor)
- {
+ final Integer headerColor) {
SQLiteDatabase db = getWritableDatabase();
db.beginTransaction();
@@ -502,11 +478,10 @@ public class DBHelper extends SQLiteOpenHelper
return (rowsUpdated == 1);
}
- public boolean updateLoyaltyCardStarStatus(final int id, final int starStatus)
- {
+ public boolean updateLoyaltyCardStarStatus(final int id, final int starStatus) {
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues = new ContentValues();
- contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus);
+ contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
int rowsUpdated = db.update(LoyaltyCardDbIds.TABLE, contentValues,
whereAttrs(LoyaltyCardDbIds.ID),
withArgs(id));
@@ -523,27 +498,25 @@ public class DBHelper extends SQLiteOpenHelper
return (rowsUpdated == 1);
}
- public boolean updateLoyaltyCardZoomLevel(int loyaltyCardId, int zoomLevel){
+ public boolean updateLoyaltyCardZoomLevel(int loyaltyCardId, int zoomLevel) {
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues = new ContentValues();
- contentValues.put(LoyaltyCardDbIds.ZOOM_LEVEL,zoomLevel);
- Log.d("updateLoyaltyCardZLevel","Card Id = "+loyaltyCardId+" Zoom level= "+zoomLevel);
- int rowsUpdated = db.update(LoyaltyCardDbIds.TABLE,contentValues,
+ contentValues.put(LoyaltyCardDbIds.ZOOM_LEVEL, zoomLevel);
+ Log.d("updateLoyaltyCardZLevel", "Card Id = " + loyaltyCardId + " Zoom level= " + zoomLevel);
+ int rowsUpdated = db.update(LoyaltyCardDbIds.TABLE, contentValues,
whereAttrs(LoyaltyCardDbIds.ID),
withArgs(loyaltyCardId));
- Log.d("updateLoyaltyCardZLevel","Rows changed = "+rowsUpdated);
+ Log.d("updateLoyaltyCardZLevel", "Rows changed = " + rowsUpdated);
return (rowsUpdated == 1);
}
- public LoyaltyCard getLoyaltyCard(final int id)
- {
+ public LoyaltyCard getLoyaltyCard(final int id) {
SQLiteDatabase db = getReadableDatabase();
Cursor data = db.query(LoyaltyCardDbIds.TABLE, null, whereAttrs(LoyaltyCardDbIds.ID), withArgs(id), null, null, null);
LoyaltyCard card = null;
- if(data.getCount() == 1)
- {
+ if (data.getCount() == 1) {
data.moveToFirst();
card = LoyaltyCard.toLoyaltyCard(data);
}
@@ -553,8 +526,7 @@ public class DBHelper extends SQLiteOpenHelper
return card;
}
- public List getLoyaltyCardGroups(final int id)
- {
+ public List getLoyaltyCardGroups(final int id) {
SQLiteDatabase db = getReadableDatabase();
Cursor data = db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE + " g " +
" LEFT JOIN " + LoyaltyCardDbIdsGroups.TABLE + " ig ON ig." + LoyaltyCardDbIdsGroups.groupID + " = g." + LoyaltyCardDbGroups.ID +
@@ -579,8 +551,7 @@ public class DBHelper extends SQLiteOpenHelper
return groups;
}
- public void setLoyaltyCardGroups(final int id, List groups)
- {
+ public void setLoyaltyCardGroups(final int id, List groups) {
SQLiteDatabase db = getWritableDatabase();
// First delete lookup table entries associated with this card
@@ -597,8 +568,7 @@ public class DBHelper extends SQLiteOpenHelper
}
}
- public void setLoyaltyCardGroups(final SQLiteDatabase db, final int id, List groups)
- {
+ public void setLoyaltyCardGroups(final SQLiteDatabase db, final int id, List groups) {
// First delete lookup table entries associated with this card
db.delete(LoyaltyCardDbIdsGroups.TABLE,
whereAttrs(LoyaltyCardDbIdsGroups.cardID),
@@ -613,8 +583,7 @@ public class DBHelper extends SQLiteOpenHelper
}
}
- public boolean deleteLoyaltyCard(final int id)
- {
+ public boolean deleteLoyaltyCard(final int id) {
SQLiteDatabase db = getWritableDatabase();
// Delete card
int rowsDeleted = db.delete(LoyaltyCardDbIds.TABLE,
@@ -632,18 +601,18 @@ public class DBHelper extends SQLiteOpenHelper
withArgs(id));
// Also wipe card images associated with this card
- try {
- Utils.saveCardImage(mContext, null, id, true);
- Utils.saveCardImage(mContext, null, id, false);
- } catch (FileNotFoundException e) {
- e.printStackTrace();
+ for (ImageLocationType imageLocationType : ImageLocationType.values()) {
+ try {
+ Utils.saveCardImage(mContext, null, id, imageLocationType);
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
}
return (rowsDeleted == 1);
}
- public Cursor getLoyaltyCardCursor()
- {
+ public Cursor getLoyaltyCardCursor() {
// An empty string will match everything
return getLoyaltyCardCursor("");
}
@@ -654,8 +623,7 @@ public class DBHelper extends SQLiteOpenHelper
* @param filter
* @return Cursor
*/
- public Cursor getLoyaltyCardCursor(final String filter)
- {
+ public Cursor getLoyaltyCardCursor(final String filter) {
return getLoyaltyCardCursor(filter, null);
}
@@ -666,8 +634,7 @@ public class DBHelper extends SQLiteOpenHelper
* @param group
* @return Cursor
*/
- public Cursor getLoyaltyCardCursor(final String filter, Group group)
- {
+ public Cursor getLoyaltyCardCursor(final String filter, Group group) {
return getLoyaltyCardCursor(filter, group, LoyaltyCardOrder.Alpha, LoyaltyCardOrderDirection.Ascending);
}
@@ -715,7 +682,7 @@ public class DBHelper extends SQLiteOpenHelper
" (CASE WHEN " + LoyaltyCardDbIds.TABLE + "." + orderField + " IS NULL THEN 1 ELSE 0 END), " +
LoyaltyCardDbIds.TABLE + "." + orderField + " COLLATE NOCASE " + getDbDirection(order, direction) + ", " +
LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC " +
- limitString, filter.trim().isEmpty() ? null : new String[] { TextUtils.join("* ", filter.split(" ")) + '*' }, null);
+ limitString, filter.trim().isEmpty() ? null : new String[]{TextUtils.join("* ", filter.split(" ")) + '*'}, null);
}
/**
@@ -723,8 +690,7 @@ public class DBHelper extends SQLiteOpenHelper
*
* @return Integer
*/
- public int getLoyaltyCardCount()
- {
+ public int getLoyaltyCardCount() {
SQLiteDatabase db = getReadableDatabase();
return (int) DatabaseUtils.queryNumEntries(db, LoyaltyCardDbIds.TABLE);
}
@@ -734,8 +700,7 @@ public class DBHelper extends SQLiteOpenHelper
*
* @return Cursor
*/
- public Cursor getGroupCursor()
- {
+ public Cursor getGroupCursor() {
SQLiteDatabase db = getReadableDatabase();
return db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE +
@@ -761,13 +726,11 @@ public class DBHelper extends SQLiteOpenHelper
return groups;
}
- public void reorderGroups(final List groups)
- {
+ public void reorderGroups(final List groups) {
Integer order = 0;
SQLiteDatabase db = getWritableDatabase();
- for (Group group : groups)
- {
+ for (Group group : groups) {
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbGroups.ORDER, order);
@@ -779,15 +742,13 @@ public class DBHelper extends SQLiteOpenHelper
}
}
- public Group getGroup(final String groupName)
- {
+ public Group getGroup(final String groupName) {
SQLiteDatabase db = getReadableDatabase();
Cursor data = db.query(LoyaltyCardDbGroups.TABLE, null,
whereAttrs(LoyaltyCardDbGroups.ID), withArgs(groupName), null, null, null);
Group group = null;
- if(data.getCount() == 1)
- {
+ if (data.getCount() == 1) {
data.moveToFirst();
group = Group.toGroup(data);
}
@@ -796,14 +757,12 @@ public class DBHelper extends SQLiteOpenHelper
return group;
}
- public int getGroupCount()
- {
+ public int getGroupCount() {
SQLiteDatabase db = getReadableDatabase();
return (int) DatabaseUtils.queryNumEntries(db, LoyaltyCardDbGroups.TABLE);
}
- public List getGroupCardIds(final String groupName)
- {
+ public List getGroupCardIds(final String groupName) {
SQLiteDatabase db = getReadableDatabase();
Cursor data = db.query(LoyaltyCardDbIdsGroups.TABLE, withArgs(LoyaltyCardDbIdsGroups.cardID),
whereAttrs(LoyaltyCardDbIdsGroups.groupID), withArgs(groupName), null, null, null);
@@ -824,8 +783,7 @@ public class DBHelper extends SQLiteOpenHelper
return cardIds;
}
- public long insertGroup(final String name)
- {
+ public long insertGroup(final String name) {
if (name.isEmpty()) return -1;
SQLiteDatabase db = getWritableDatabase();
@@ -835,8 +793,7 @@ public class DBHelper extends SQLiteOpenHelper
return db.insert(LoyaltyCardDbGroups.TABLE, null, contentValues);
}
- public boolean insertGroup(final SQLiteDatabase db, final String name)
- {
+ public boolean insertGroup(final SQLiteDatabase db, final String name) {
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbGroups.ID, name);
contentValues.put(LoyaltyCardDbGroups.ORDER, getGroupCount());
@@ -844,8 +801,7 @@ public class DBHelper extends SQLiteOpenHelper
return newId != -1;
}
- public boolean updateGroup(final String groupName, final String newName)
- {
+ public boolean updateGroup(final String groupName, final String newName) {
if (newName.isEmpty()) return false;
boolean success = false;
@@ -881,8 +837,7 @@ public class DBHelper extends SQLiteOpenHelper
return success;
}
- public boolean deleteGroup(final String groupName)
- {
+ public boolean deleteGroup(final String groupName) {
boolean success = false;
SQLiteDatabase db = getWritableDatabase();
@@ -913,8 +868,7 @@ public class DBHelper extends SQLiteOpenHelper
return success;
}
- public int getGroupCardCount(final String groupName)
- {
+ public int getGroupCardCount(final String groupName) {
SQLiteDatabase db = getReadableDatabase();
return (int) DatabaseUtils.queryNumEntries(db, LoyaltyCardDbIdsGroups.TABLE,
diff --git a/app/src/main/java/protect/card_locker/FormatException.java b/app/src/main/java/protect/card_locker/FormatException.java
index db23ebfd4..a44286a80 100644
--- a/app/src/main/java/protect/card_locker/FormatException.java
+++ b/app/src/main/java/protect/card_locker/FormatException.java
@@ -5,15 +5,12 @@ package protect.card_locker;
* encountered with the format of data being
* imported or exported.
*/
-public class FormatException extends Exception
-{
- public FormatException(String message)
- {
+public class FormatException extends Exception {
+ public FormatException(String message) {
super(message);
}
- public FormatException(String message, Exception rootCause)
- {
+ public FormatException(String message, Exception rootCause) {
super(message, rootCause);
}
}
diff --git a/app/src/main/java/protect/card_locker/Group.java b/app/src/main/java/protect/card_locker/Group.java
index 1d5de8603..90d8da69d 100644
--- a/app/src/main/java/protect/card_locker/Group.java
+++ b/app/src/main/java/protect/card_locker/Group.java
@@ -2,8 +2,9 @@ package protect.card_locker;
import android.database.Cursor;
-public class Group
-{
+import androidx.annotation.Nullable;
+
+public class Group {
public final String _id;
public final int order;
@@ -12,11 +13,28 @@ public class Group
this.order = order;
}
- public static Group toGroup(Cursor cursor)
- {
+ public static Group toGroup(Cursor cursor) {
String _id = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbGroups.ID));
int order = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbGroups.ORDER));
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/GroupCursorAdapter.java b/app/src/main/java/protect/card_locker/GroupCursorAdapter.java
index 1e89bb091..05cc1d9f6 100644
--- a/app/src/main/java/protect/card_locker/GroupCursorAdapter.java
+++ b/app/src/main/java/protect/card_locker/GroupCursorAdapter.java
@@ -12,8 +12,7 @@ import androidx.appcompat.widget.AppCompatImageButton;
import androidx.recyclerview.widget.RecyclerView;
import protect.card_locker.preferences.Settings;
-class GroupCursorAdapter extends BaseCursorAdapter
-{
+class GroupCursorAdapter extends BaseCursorAdapter {
Settings mSettings;
private Cursor mCursor;
private final Context mContext;
@@ -39,14 +38,12 @@ class GroupCursorAdapter extends BaseCursorAdapter mListener.onMoveDownButtonClicked(inputHolder.itemView));
inputHolder.mMoveUp.setOnClickListener(view -> mListener.onMoveUpButtonClicked(inputHolder.itemView));
inputHolder.mEdit.setOnClickListener(view -> mListener.onEditButtonClicked(inputHolder.itemView));
inputHolder.mDelete.setOnClickListener(view -> mListener.onDeleteButtonClicked(inputHolder.itemView));
}
- public interface GroupAdapterListener
- {
+ public interface GroupAdapterListener {
void onMoveDownButtonClicked(View view);
+
void onMoveUpButtonClicked(View view);
+
void onEditButtonClicked(View view);
+
void onDeleteButtonClicked(View view);
}
- public static class GroupListItemViewHolder extends RecyclerView.ViewHolder
- {
+ public static class GroupListItemViewHolder extends RecyclerView.ViewHolder {
public TextView mName, mCardCount;
public AppCompatImageButton mMoveUp, mMoveDown, mEdit, mDelete;
diff --git a/app/src/main/java/protect/card_locker/ImageLocationType.java b/app/src/main/java/protect/card_locker/ImageLocationType.java
new file mode 100644
index 000000000..457a03bdd
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/ImageLocationType.java
@@ -0,0 +1,7 @@
+package protect.card_locker;
+
+public enum ImageLocationType {
+ front,
+ back,
+ icon
+}
diff --git a/app/src/main/java/protect/card_locker/ImportExportActivity.java b/app/src/main/java/protect/card_locker/ImportExportActivity.java
index 444cc2d9e..b48090c3a 100644
--- a/app/src/main/java/protect/card_locker/ImportExportActivity.java
+++ b/app/src/main/java/protect/card_locker/ImportExportActivity.java
@@ -10,14 +10,20 @@ import android.os.Bundle;
import android.text.InputType;
import android.util.Log;
import android.view.MenuItem;
-import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.Toast;
-import java.io.File;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -26,12 +32,6 @@ import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.widget.Toolbar;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-
import protect.card_locker.async.TaskHandler;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult;
@@ -40,8 +40,6 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
private static final String TAG = "Catima";
private static final int PERMISSIONS_EXTERNAL_STORAGE = 1;
- private static final int CHOOSE_EXPORT_LOCATION = 2;
- private static final int IMPORT = 3;
private ImportExportTask importExporter;
@@ -50,6 +48,10 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
private DataFormat importDataFormat;
private String exportPassword;
+ private ActivityResultLauncher fileCreateLauncher;
+ private ActivityResultLauncher fileOpenLauncher;
+ private ActivityResultLauncher filePickerLauncher;
+
final private TaskHandler mTasks = new TaskHandler();
@Override
@@ -77,6 +79,49 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
PERMISSIONS_EXTERNAL_STORAGE);
}
+ // would use ActivityResultContracts.CreateDocument() but mime type cannot be set
+ fileCreateLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
+ Intent intent = result.getData();
+ if (intent == null) {
+ Log.e(TAG, "Activity returned NULL data");
+ return;
+ }
+ Uri uri = intent.getData();
+ if (uri == null) {
+ Log.e(TAG, "Activity returned NULL uri");
+ return;
+ }
+ try {
+ OutputStream writer = getContentResolver().openOutputStream(uri);
+ Log.e(TAG, "Starting file export with: " + result.toString());
+ startExport(writer, uri, exportPassword.toCharArray(), true);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to export file: " + result.toString(), e);
+ onExportComplete(ImportExportResult.GenericFailure, uri);
+ }
+
+ });
+ fileOpenLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), result -> {
+ if (result == null) {
+ Log.e(TAG, "Activity returned NULL data");
+ return;
+ }
+ openFileForImport(result, null);
+ });
+ filePickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
+ Intent intent = result.getData();
+ if (intent == null) {
+ Log.e(TAG, "Activity returned NULL data");
+ return;
+ }
+ Uri uri = intent.getData();
+ if (uri == null) {
+ Log.e(TAG, "Activity returned NULL uri");
+ return;
+ }
+ openFileForImport(intent.getData(), null);
+ });
+
// Check that there is a file manager available
final Intent intentCreateDocumentAction = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intentCreateDocumentAction.addCategory(Intent.CATEGORY_OPENABLE);
@@ -84,60 +129,57 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "catima.zip");
Button exportButton = findViewById(R.id.exportButton);
- exportButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- AlertDialog.Builder builder = new AlertDialog.Builder(ImportExportActivity.this);
- builder.setTitle(R.string.exportPassword);
+ exportButton.setOnClickListener(v -> {
+ AlertDialog.Builder builder = new AlertDialog.Builder(ImportExportActivity.this);
+ builder.setTitle(R.string.exportPassword);
- FrameLayout container = new FrameLayout(ImportExportActivity.this);
- FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- params.leftMargin = 50;
- params.rightMargin = 50;
+ FrameLayout container = new FrameLayout(ImportExportActivity.this);
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ params.leftMargin = 50;
+ params.rightMargin = 50;
- final EditText input = new EditText(ImportExportActivity.this);
- input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
- input.setLayoutParams(params);
- input.setHint(R.string.exportPasswordHint);
+ final EditText input = new EditText(ImportExportActivity.this);
+ input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ input.setLayoutParams(params);
+ input.setHint(R.string.exportPasswordHint);
- container.addView(input);
- builder.setView(container);
- builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
- exportPassword = input.getText().toString();
- chooseFileWithIntent(intentCreateDocumentAction, CHOOSE_EXPORT_LOCATION);
- });
- builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
- builder.show();
+ container.addView(input);
+ builder.setView(container);
+ builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
+ exportPassword = input.getText().toString();
+ try {
+ fileCreateLauncher.launch(intentCreateDocumentAction);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
+ Log.e(TAG, "No activity found to handle intent", e);
+ }
+ });
+ builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
+ builder.show();
- }
});
// Check that there is a file manager available
- final Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT);
- intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE);
- intentGetContentAction.setType("*/*");
-
Button importFilesystem = findViewById(R.id.importOptionFilesystemButton);
- importFilesystem.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- chooseImportType(intentGetContentAction);
- }
- });
+ importFilesystem.setOnClickListener(v -> chooseImportType(false));
// Check that there is an app that data can be imported from
- final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
-
Button importApplication = findViewById(R.id.importOptionApplicationButton);
- importApplication.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- chooseImportType(intentPickAction);
- }
- });
+ importApplication.setOnClickListener(v -> chooseImportType(true));
}
- private void chooseImportType(Intent baseIntent) {
+ private void openFileForImport(Uri uri, char[] password) {
+ try {
+ InputStream reader = getContentResolver().openInputStream(uri);
+ Log.e(TAG, "Starting file import with: " + uri.toString());
+ startImport(reader, uri, importDataFormat, password, true);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to import file: " + uri.toString(), e);
+ onImportComplete(ImportExportResult.GenericFailure, uri, importDataFormat);
+ }
+ }
+
+ private void chooseImportType(boolean choosePicker) {
List betaImportOptions = new ArrayList<>();
betaImportOptions.add("Fidme");
betaImportOptions.add("Stocard");
@@ -195,7 +237,17 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- chooseFileWithIntent(baseIntent, IMPORT);
+ try {
+ if (choosePicker) {
+ final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
+ filePickerLauncher.launch(intentPickAction);
+ } else {
+ fileOpenLauncher.launch("*/*");
+ }
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
+ Log.e(TAG, "No activity found to handle intent", e);
+ }
}
})
.setNegativeButton(R.string.cancel, null)
@@ -298,7 +350,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
builder.setView(input);
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
- activityResultParser(IMPORT, RESULT_OK, uri, input.getText().toString().toCharArray());
+ openFileForImport(uri, input.getText().toString().toCharArray());
});
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
@@ -374,69 +426,4 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
builder.create().show();
}
-
- private void chooseFileWithIntent(Intent intent, int requestCode) {
- try {
- startActivityForResult(intent, requestCode);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
- Log.e(TAG, "No activity found to handle intent", e);
- }
- }
-
- private void activityResultParser(int requestCode, int resultCode, Uri uri, char[] password) {
- if (resultCode != RESULT_OK) {
- Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
- return;
- }
-
- if (uri == null) {
- Log.e(TAG, "Activity returned a NULL URI");
- return;
- }
-
- try {
- if (requestCode == CHOOSE_EXPORT_LOCATION) {
-
- OutputStream writer;
- if (uri.getScheme() != null) {
- writer = getContentResolver().openOutputStream(uri);
- } else {
- writer = new FileOutputStream(new File(uri.toString()));
- }
- Log.e(TAG, "Starting file export with: " + uri.toString());
- startExport(writer, uri, exportPassword.toCharArray(), true);
- } else {
- InputStream reader;
- if (uri.getScheme() != null) {
- reader = getContentResolver().openInputStream(uri);
- } else {
- reader = new FileInputStream(new File(uri.toString()));
- }
-
- Log.e(TAG, "Starting file import with: " + uri.toString());
-
- startImport(reader, uri, importDataFormat, password, true);
- }
- } catch (IOException e) {
- Log.e(TAG, "Failed to import/export file: " + uri.toString(), e);
- if (requestCode == CHOOSE_EXPORT_LOCATION) {
- onExportComplete(ImportExportResult.GenericFailure, uri);
- } else {
- onImportComplete(ImportExportResult.GenericFailure, uri, importDataFormat);
- }
- }
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
-
- if (data == null) {
- Log.e(TAG, "Activity returned NULL data");
- return;
- }
-
- activityResultParser(requestCode, resultCode, data.getData(), null);
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/protect/card_locker/ImportURIHelper.java b/app/src/main/java/protect/card_locker/ImportURIHelper.java
index 812d5665c..4cb476708 100644
--- a/app/src/main/java/protect/card_locker/ImportURIHelper.java
+++ b/app/src/main/java/protect/card_locker/ImportURIHelper.java
@@ -4,8 +4,6 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
-import com.google.zxing.BarcodeFormat;
-
import java.io.InvalidObjectException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
@@ -57,7 +55,7 @@ public class ImportURIHelper {
}
public LoyaltyCard parse(Uri uri) throws InvalidObjectException {
- if(!isImportUri(uri)) {
+ if (!isImportUri(uri)) {
throw new InvalidObjectException("Not an import URI");
}
@@ -92,37 +90,33 @@ public class ImportURIHelper {
String note = kv.get(NOTE);
String cardId = kv.get(CARD_ID);
String barcodeId = kv.get(BARCODE_ID);
- if (store == null || note == null || cardId == null) throw new InvalidObjectException("Not a valid import URI: " + uri.toString());
+ if (store == null || note == null || cardId == null)
+ throw new InvalidObjectException("Not a valid import URI: " + uri.toString());
String unparsedBarcodeType = kv.get(BARCODE_TYPE);
- if(unparsedBarcodeType != null && !unparsedBarcodeType.equals(""))
- {
+ if (unparsedBarcodeType != null && !unparsedBarcodeType.equals("")) {
barcodeType = CatimaBarcode.fromName(unparsedBarcodeType);
}
String unparsedBalance = kv.get(BALANCE);
- if(unparsedBalance != null && !unparsedBalance.equals(""))
- {
+ if (unparsedBalance != null && !unparsedBalance.equals("")) {
balance = new BigDecimal(unparsedBalance);
}
String unparsedBalanceType = kv.get(BALANCE_TYPE);
- if (unparsedBalanceType != null && !unparsedBalanceType.equals(""))
- {
+ if (unparsedBalanceType != null && !unparsedBalanceType.equals("")) {
balanceType = Currency.getInstance(unparsedBalanceType);
}
String unparsedExpiry = kv.get(EXPIRY);
- if(unparsedExpiry != null && !unparsedExpiry.equals(""))
- {
+ if (unparsedExpiry != null && !unparsedExpiry.equals("")) {
expiry = new Date(Long.parseLong(unparsedExpiry));
}
String unparsedHeaderColor = kv.get(HEADER_COLOR);
- if(unparsedHeaderColor != null)
- {
+ if (unparsedHeaderColor != null) {
headerColor = Integer.parseInt(unparsedHeaderColor);
}
- return new LoyaltyCard(-1, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0, Utils.getUnixTime(),100);
+ return new LoyaltyCard(-1, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0, Utils.getUnixTime(), 100);
} catch (NullPointerException | NumberFormatException | UnsupportedEncodingException ex) {
throw new InvalidObjectException("Not a valid import URI");
}
@@ -159,14 +153,14 @@ public class ImportURIHelper {
fragment = appendFragment(fragment, EXPIRY, String.valueOf(loyaltyCard.expiry.getTime()));
}
fragment = appendFragment(fragment, CARD_ID, loyaltyCard.cardId);
- if(loyaltyCard.barcodeId != null) {
+ if (loyaltyCard.barcodeId != null) {
fragment = appendFragment(fragment, BARCODE_ID, loyaltyCard.barcodeId);
}
- if(loyaltyCard.barcodeType != null) {
+ if (loyaltyCard.barcodeType != null) {
fragment = appendFragment(fragment, BARCODE_TYPE, loyaltyCard.barcodeType.name());
}
- if(loyaltyCard.headerColor != null) {
+ if (loyaltyCard.headerColor != null) {
fragment = appendFragment(fragment, HEADER_COLOR, loyaltyCard.headerColor.toString());
}
// Star status will not be exported
diff --git a/app/src/main/java/protect/card_locker/LetterBitmap.java b/app/src/main/java/protect/card_locker/LetterBitmap.java
index 2afc75f2e..94b409168 100644
--- a/app/src/main/java/protect/card_locker/LetterBitmap.java
+++ b/app/src/main/java/protect/card_locker/LetterBitmap.java
@@ -18,8 +18,7 @@ import android.text.TextPaint;
* alphabet or digit, if there is no letter or digit available, a default image
* is shown instead.
*/
-class LetterBitmap
-{
+class LetterBitmap {
/**
* The number of available tile colors
@@ -37,39 +36,32 @@ class LetterBitmap
/**
* Constructor for LetterTileProvider
*
- * @param context The {@link Context} to use
- * @param displayName The name used to create the letter for the tile
- * @param key The key used to generate the background color for the tile
+ * @param context The {@link Context} to use
+ * @param displayName The name used to create the letter for the tile
+ * @param key The key used to generate the background color for the tile
* @param tileLetterFontSize The font size used to display the letter
- * @param width The desired width of the tile
- * @param height The desired height of the tile
- * @param backgroundColor (optional) color to use for background.
- * @param textColor (optional) color to use for text.
+ * @param width The desired width of the tile
+ * @param height The desired height of the tile
+ * @param backgroundColor (optional) color to use for background.
+ * @param textColor (optional) color to use for text.
*/
public LetterBitmap(Context context, String displayName, String key, int tileLetterFontSize,
- int width, int height, Integer backgroundColor, Integer textColor)
- {
+ int width, int height, Integer backgroundColor, Integer textColor) {
TextPaint paint = new TextPaint();
paint.setTypeface(Typeface.create("sans-serif-light", Typeface.BOLD));
- if(textColor != null)
- {
+ if (textColor != null) {
paint.setColor(textColor);
- }
- else
- {
+ } else {
paint.setColor(Color.WHITE);
}
paint.setTextAlign(Paint.Align.CENTER);
paint.setAntiAlias(true);
- if(backgroundColor == null)
- {
+ if (backgroundColor == null) {
mColor = getDefaultColor(context, key);
- }
- else
- {
+ } else {
mColor = backgroundColor;
}
@@ -80,7 +72,7 @@ class LetterBitmap
c.setBitmap(mBitmap);
c.drawColor(mColor);
- char [] firstCharArray = new char[1];
+ char[] firstCharArray = new char[1];
firstCharArray[0] = firstChar.toUpperCase().charAt(0);
paint.setTextSize(tileLetterFontSize);
@@ -97,16 +89,14 @@ class LetterBitmap
* alphabet or digit, if there is no letter or digit available, a
* default image is shown instead
*/
- public Bitmap getLetterTile()
- {
+ public Bitmap getLetterTile() {
return mBitmap;
}
/**
* @return background color used for letter title.
*/
- public int getBackgroundColor()
- {
+ public int getBackgroundColor() {
return mColor;
}
@@ -115,8 +105,7 @@ class LetterBitmap
* @return A new or previously chosen color for key used as the
* tile background color
*/
- private static int pickColor(String key, TypedArray colors)
- {
+ private static int pickColor(String key, TypedArray colors) {
// String.hashCode() is not supposed to change across java versions, so
// this should guarantee the same key always maps to the same color
final int color = Math.abs(key.hashCode()) % NUM_OF_TILE_COLORS;
@@ -127,8 +116,7 @@ class LetterBitmap
* Determine the color which the letter tile will use if no default
* color is provided.
*/
- public static int getDefaultColor(Context context, String key)
- {
+ public static int getDefaultColor(Context context, String key) {
final Resources res = context.getResources();
TypedArray colors = res.obtainTypedArray(R.array.letter_tile_colors);
diff --git a/app/src/main/java/protect/card_locker/LoyaltyCard.java b/app/src/main/java/protect/card_locker/LoyaltyCard.java
index 1ec44fa6d..06a0165a5 100644
--- a/app/src/main/java/protect/card_locker/LoyaltyCard.java
+++ b/app/src/main/java/protect/card_locker/LoyaltyCard.java
@@ -35,8 +35,7 @@ public class LoyaltyCard implements Parcelable {
public LoyaltyCard(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)
- {
+ @Nullable final Integer headerColor, final int starStatus, final long lastUsed, final int zoomLevel) {
this.id = id;
this.store = store;
this.note = note;
@@ -88,8 +87,7 @@ public class LoyaltyCard implements Parcelable {
parcel.writeInt(zoomLevel);
}
- public static LoyaltyCard toLoyaltyCard(Cursor cursor)
- {
+ public static LoyaltyCard toLoyaltyCard(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));
@@ -110,27 +108,23 @@ public class LoyaltyCard implements Parcelable {
Date expiry = null;
Integer headerColor = null;
- if (cursor.isNull(barcodeTypeColumn) == false)
- {
+ if (cursor.isNull(barcodeTypeColumn) == false) {
barcodeType = CatimaBarcode.fromName(cursor.getString(barcodeTypeColumn));
}
- if (cursor.isNull(balanceTypeColumn) == false)
- {
+ if (cursor.isNull(balanceTypeColumn) == false) {
balanceType = Currency.getInstance(cursor.getString(balanceTypeColumn));
}
- if(expiryLong > 0)
- {
+ if (expiryLong > 0) {
expiry = new Date(expiryLong);
}
- if(cursor.isNull(headerColorColumn) == false)
- {
+ if (cursor.isNull(headerColorColumn) == false) {
headerColor = cursor.getInt(headerColorColumn);
}
- return new LoyaltyCard(id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starred, lastUsed,zoomLevel);
+ return new LoyaltyCard(id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starred, lastUsed, zoomLevel);
}
@Override
diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java b/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java
index 1e974ae0f..c5a05ff6d 100644
--- a/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java
+++ b/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java
@@ -3,6 +3,7 @@ package protect.card_locker;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
+import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.SparseBooleanArray;
@@ -12,20 +13,19 @@ 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 androidx.core.content.ContextCompat;
+import androidx.core.graphics.BlendModeColorFilterCompat;
+import androidx.core.graphics.BlendModeCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.google.android.material.card.MaterialCardView;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.ArrayList;
-import androidx.core.graphics.BlendModeColorFilterCompat;
-import androidx.core.graphics.BlendModeCompat;
-import androidx.recyclerview.widget.RecyclerView;
-
import protect.card_locker.preferences.Settings;
public class LoyaltyCardCursorAdapter extends BaseCursorAdapter {
@@ -33,11 +33,12 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter 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(LoyaltyCardListItemViewHolder 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);
+ inputHolder.mCardIcon.setVisibility(View.GONE);
+ resetIconYAxis(inputHolder.mTickIcon);
+ inputHolder.mTickIcon.setVisibility(View.VISIBLE);
if (mCurrentSelectedIndex == inputPosition) {
- LoyaltyCardAnimator.flipView(mContext, inputHolder.mThumbnailBackContainer, inputHolder.mThumbnailFrontContainer, true);
+ LoyaltyCardAnimator.flipView(mContext, inputHolder.mTickIcon, inputHolder.mCardIcon, true);
resetCurrentIndex();
}
} else {
- inputHolder.mThumbnailBackContainer.setVisibility(View.GONE);
- resetIconYAxis(inputHolder.mThumbnailFrontContainer);
- inputHolder.mThumbnailFrontContainer.setVisibility(View.VISIBLE);
- inputHolder.mThumbnailFrontContainer.setAlpha(1);
+ inputHolder.mTickIcon.setVisibility(View.GONE);
+ resetIconYAxis(inputHolder.mCardIcon);
+ inputHolder.mCardIcon.setVisibility(View.VISIBLE);
if ((mReverseAllAnimations && mAnimationItemsIndex.get(inputPosition, false)) || mCurrentSelectedIndex == inputPosition) {
- LoyaltyCardAnimator.flipView(mContext, inputHolder.mThumbnailBackContainer, inputHolder.mThumbnailFrontContainer, false);
+ LoyaltyCardAnimator.flipView(mContext, inputHolder.mTickIcon, inputHolder.mCardIcon, false);
resetCurrentIndex();
}
}
@@ -264,23 +246,20 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter currencies = new HashMap<>();
- String tempCameraPicturePath;
-
LoyaltyCard tempLoyaltyCard;
+ ActivityResultLauncher mPhotoTakerLauncher;
+ ActivityResultLauncher mPhotoPickerLauncher;
+ ActivityResultLauncher mCardIdAndBarCodeEditorLauncher;
+
+ ActivityResultLauncher mCropperLauncher;
+ int mRequestedImage = 0;
+ int mCropperFinishedType = 0;
+ UCrop.Options mCropperOptions;
+
+ boolean mFrontImageUnsaved = false;
+ boolean mBackImageUnsaved = false;
+ boolean mIconUnsaved = false;
+
+ boolean mFrontImageRemoved = false;
+ boolean mBackImageRemoved = false;
+ boolean mIconRemoved = false;
+
final private TaskHandler mTasks = new TaskHandler();
private static LoyaltyCard updateTempState(LoyaltyCard loyaltyCard, LoyaltyCardField fieldName, Object value) {
@@ -156,7 +189,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
(CatimaBarcode) (fieldName == LoyaltyCardField.barcodeType ? value : loyaltyCard.barcodeType),
(Integer) (fieldName == LoyaltyCardField.headerColor ? value : loyaltyCard.headerColor),
(int) (fieldName == LoyaltyCardField.starStatus ? value : loyaltyCard.starStatus),
- Utils.getUnixTime(),100
+ Utils.getUnixTime(), 100
);
}
@@ -182,12 +215,41 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
+ ", updateLoyaltyCard=" + updateLoyaltyCard);
}
+
@Override
public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
tabs = findViewById(R.id.tabs);
savedInstanceState.putInt(STATE_TAB_INDEX, tabs.getSelectedTabPosition());
savedInstanceState.putParcelable(STATE_TEMP_CARD, tempLoyaltyCard);
+ savedInstanceState.putInt(STATE_REQUESTED_IMAGE, mRequestedImage);
+
+ Object cardImageFrontObj = cardImageFront.getTag();
+ if (mFrontImageUnsaved && (cardImageFrontObj instanceof Bitmap) && Utils.saveTempImage(this, (Bitmap) cardImageFrontObj, TEMP_UNSAVED_FRONT_IMAGE_NAME, TEMP_UNSAVED_IMAGE_FORMAT) != null) {
+ savedInstanceState.putInt(STATE_FRONT_IMAGE_UNSAVED, 1);
+ } else {
+ savedInstanceState.putInt(STATE_FRONT_IMAGE_UNSAVED, 0);
+ }
+
+ Object cardImageBackObj = cardImageBack.getTag();
+ if (mBackImageUnsaved && (cardImageBackObj instanceof Bitmap) && Utils.saveTempImage(this, (Bitmap) cardImageBackObj, TEMP_UNSAVED_BACK_IMAGE_NAME, TEMP_UNSAVED_IMAGE_FORMAT) != null) {
+ savedInstanceState.putInt(STATE_BACK_IMAGE_UNSAVED, 1);
+ } else {
+ savedInstanceState.putInt(STATE_BACK_IMAGE_UNSAVED, 0);
+ }
+
+ Object thumbnailObj = thumbnail.getTag();
+ if (mIconUnsaved && (thumbnailObj instanceof Bitmap) && Utils.saveTempImage(this, (Bitmap) thumbnailObj, TEMP_UNSAVED_ICON_NAME, TEMP_UNSAVED_IMAGE_FORMAT) != null) {
+ savedInstanceState.putInt(STATE_ICON_UNSAVED, 1);
+ } else {
+ savedInstanceState.putInt(STATE_ICON_UNSAVED, 0);
+ }
+
+ savedInstanceState.putInt(STATE_UPDATE_LOYALTY_CARD, updateLoyaltyCard ? 1 : 0);
+ savedInstanceState.putInt(STATE_HAS_CHANGED, hasChanged ? 1 : 0);
+ savedInstanceState.putInt(STATE_FRONT_IMAGE_REMOVED, mFrontImageRemoved ? 1 : 0);
+ savedInstanceState.putInt(STATE_BACK_IMAGE_REMOVED, mBackImageRemoved ? 1 : 0);
+ savedInstanceState.putInt(STATE_ICON_REMOVED, mIconRemoved ? 1 : 0);
}
@Override
@@ -196,6 +258,15 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
super.onRestoreInstanceState(savedInstanceState);
tabs = findViewById(R.id.tabs);
tabs.selectTab(tabs.getTabAt(savedInstanceState.getInt(STATE_TAB_INDEX)));
+ mRequestedImage = savedInstanceState.getInt(STATE_REQUESTED_IMAGE);
+ mFrontImageUnsaved = savedInstanceState.getInt(STATE_FRONT_IMAGE_UNSAVED) == 1;
+ mBackImageUnsaved = savedInstanceState.getInt(STATE_BACK_IMAGE_UNSAVED) == 1;
+ mIconUnsaved = savedInstanceState.getInt(STATE_ICON_UNSAVED) == 1;
+ updateLoyaltyCard = savedInstanceState.getInt(STATE_UPDATE_LOYALTY_CARD) == 1;
+ hasChanged = savedInstanceState.getInt(STATE_HAS_CHANGED) == 1;
+ mFrontImageRemoved = savedInstanceState.getInt(STATE_FRONT_IMAGE_REMOVED) == 1;
+ mBackImageRemoved = savedInstanceState.getInt(STATE_BACK_IMAGE_REMOVED) == 1;
+ mIconRemoved = savedInstanceState.getInt(STATE_ICON_REMOVED) == 1;
}
@Override
@@ -235,8 +306,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
cardImageFrontHolder = findViewById(R.id.frontImageHolder);
cardImageBackHolder = findViewById(R.id.backImageHolder);
- cardImageFrontHolder.setId(ID_IMAGE_FRONT);
- cardImageBackHolder.setId(ID_IMAGE_BACK);
cardImageFront = findViewById(R.id.frontImage);
cardImageBack = findViewById(R.id.backImage);
@@ -417,7 +486,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
if (!lastValue.toString().equals(getString(R.string.setBarcodeId))) {
barcodeIdField.setText(lastValue);
}
- ;
AlertDialog.Builder builder = new AlertDialog.Builder(LoyaltyCardEditActivity.this);
builder.setTitle(R.string.setBarcodeId);
@@ -509,6 +577,112 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
});
tabs.selectTab(tabs.getTabAt(0));
+
+
+ mPhotoTakerLauncher = registerForActivityResult(new ActivityResultContracts.TakePicture(), result -> {
+ if (result) {
+ startCropper(getCacheDir() + "/" + TEMP_CAMERA_IMAGE_NAME);
+ }
+ });
+
+ // android 11: wanted to swap it to ActivityResultContracts.GetContent but then it shows a file browsers that shows image mime types, offering gallery in the file browser
+ mPhotoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
+ if (result.getResultCode() == RESULT_OK) {
+ Intent intent = result.getData();
+ if (intent == null) {
+ Log.d("photo picker", "photo picker returned without an intent");
+ return;
+ }
+ Uri uri = intent.getData();
+ startCropperUri(uri);
+ }
+ });
+
+ mCardIdAndBarCodeEditorLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
+ if (result.getResultCode() == RESULT_OK) {
+ Intent intent = result.getData();
+ if (intent == null) {
+ Log.d("barcode card id editor", "barcode and card id editor picker returned without an intent");
+ return;
+ }
+ BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, getApplicationContext());
+
+ cardId = barcodeValues.content();
+ barcodeType = barcodeValues.format();
+ barcodeId = "";
+ }
+ });
+
+ mCropperLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
+ Intent intent = result.getData();
+ if (intent == null) {
+ Log.d("cropper", "ucrop returned a null intent");
+ return;
+ }
+ if (result.getResultCode() == Activity.RESULT_OK) {
+ Uri debugUri = UCrop.getOutput(intent);
+ if (debugUri == null) {
+ throw new RuntimeException("ucrop returned success but not destination uri!");
+ }
+ Log.d("cropper", "ucrop produced image at " + debugUri);
+ Bitmap bitmap = BitmapFactory.decodeFile(getCacheDir() + "/" + TEMP_CROP_IMAGE_NAME);
+
+ if (bitmap != null) {
+ if (requestedFrontImage()) {
+ mFrontImageUnsaved = true;
+ setCardImage(cardImageFront, Utils.resizeBitmap(bitmap, Utils.BITMAP_SIZE_BIG), true);
+ } else if (requestedBackImage()) {
+ mBackImageUnsaved = true;
+ setCardImage(cardImageBack, Utils.resizeBitmap(bitmap, Utils.BITMAP_SIZE_BIG), true);
+ } else {
+ mIconUnsaved = true;
+ setCardImage(thumbnail, Utils.resizeBitmap(bitmap, Utils.BITMAP_SIZE_SMALL), false);
+ thumbnail.setBackgroundColor(Color.TRANSPARENT);
+ setColorFromIcon();
+ }
+ Log.d("cropper", "mRequestedImage: " + mRequestedImage);
+ mCropperFinishedType = mRequestedImage;
+ hasChanged = true;
+ } else {
+ Toast.makeText(LoyaltyCardEditActivity.this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
+ }
+ } else if (result.getResultCode() == UCrop.RESULT_ERROR) {
+ Throwable e = UCrop.getError(intent);
+ if (e == null) {
+ throw new RuntimeException("ucrop returned error state but not an error!");
+ }
+ Log.e("cropper error", e.toString());
+ }
+ });
+
+ mCropperOptions = new UCrop.Options();
+ setCropperTheme();
+ }
+
+ // ucrop 2.2.6 initial aspect ratio is glitched when 0x0 is used as the initial ratio option
+ // https://github.com/Yalantis/uCrop/blob/281c8e6438d81f464d836fc6b500517144af264a/ucrop/src/main/java/com/yalantis/ucrop/UCropActivity.java#L264
+ // so source width height has to be provided for now, depending on whether future versions of ucrop will support 0x0 as the default option
+ private void setCropperOptions(boolean cardShapeDefault, float sourceWidth, float sourceHeight) {
+ mCropperOptions.setCompressionFormat(TEMP_CROP_IMAGE_FORMAT);
+ mCropperOptions.setFreeStyleCropEnabled(true);
+ mCropperOptions.setHideBottomControls(false);
+ // default aspect ratio workaround
+ int selectedByDefault = 1;
+ if (cardShapeDefault) {
+ selectedByDefault = 2;
+ }
+ mCropperOptions.setAspectRatioOptions(selectedByDefault,
+ new AspectRatio(null, 1, 1),
+ new AspectRatio(getResources().getString(R.string.ucrop_label_original).toUpperCase(), sourceWidth, sourceHeight),
+ new AspectRatio(getResources().getString(R.string.card).toUpperCase(), 85.6f, 53.98f)
+ );
+ }
+
+ private void setCropperTheme() {
+ mCropperOptions.setToolbarColor(ContextCompat.getColor(this, R.color.colorPrimary));
+ mCropperOptions.setStatusBarColor(ContextCompat.getColor(this, R.color.colorPrimaryDark));
+ mCropperOptions.setToolbarWidgetColor(Color.WHITE);
+ mCropperOptions.setActiveControlsWidgetColor(ContextCompat.getColor(this, R.color.colorPrimary));
}
@Override
@@ -519,6 +693,30 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
extractIntentFields(intent);
}
+ private boolean requestedFrontImage() {
+ return mRequestedImage == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || mRequestedImage == Utils.CARD_IMAGE_FROM_FILE_FRONT;
+ }
+
+ private boolean croppedFrontImage() {
+ return mCropperFinishedType == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || mCropperFinishedType == Utils.CARD_IMAGE_FROM_FILE_FRONT;
+ }
+
+ private boolean requestedBackImage() {
+ return mRequestedImage == Utils.CARD_IMAGE_FROM_CAMERA_BACK || mRequestedImage == Utils.CARD_IMAGE_FROM_FILE_BACK;
+ }
+
+ private boolean croppedBackImage() {
+ return mCropperFinishedType == Utils.CARD_IMAGE_FROM_CAMERA_BACK || mCropperFinishedType == Utils.CARD_IMAGE_FROM_FILE_BACK;
+ }
+
+ private boolean requestedIcon() {
+ return mRequestedImage == Utils.CARD_IMAGE_FROM_CAMERA_ICON || mRequestedImage == Utils.CARD_IMAGE_FROM_FILE_ICON;
+ }
+
+ private boolean croppedIcon() {
+ return mCropperFinishedType == Utils.CARD_IMAGE_FROM_CAMERA_ICON || mCropperFinishedType == Utils.CARD_IMAGE_FROM_FILE_ICON;
+ }
+
@SuppressLint("DefaultLocale")
@Override
public void onResume() {
@@ -528,6 +726,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
onResuming = true;
+
if (tempLoyaltyCard == null) {
if (updateLoyaltyCard) {
tempLoyaltyCard = db.getLoyaltyCard(loyaltyCardId);
@@ -537,9 +736,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
finish();
return;
}
- setTitle(R.string.editCardTitle);
- setCardImage(cardImageFront, Utils.retrieveCardImage(this, tempLoyaltyCard.id, true));
- setCardImage(cardImageBack, Utils.retrieveCardImage(this, tempLoyaltyCard.id, false));
} else if (importLoyaltyCardUri != null) {
try {
tempLoyaltyCard = importUriHelper.parse(importLoyaltyCardUri);
@@ -548,14 +744,45 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
finish();
return;
}
- setTitle(R.string.addCardTitle);
} else {
// New card, use default values
- tempLoyaltyCard = new LoyaltyCard(-1, "", "", null, new BigDecimal("0"), null, "", null, null, null, 0, Utils.getUnixTime(),100);
- setTitle(R.string.addCardTitle);
+ tempLoyaltyCard = new LoyaltyCard(-1, "", "", null, new BigDecimal("0"), null, "", null, null, null, 0, Utils.getUnixTime(), 100);
+
}
}
+ if (!initDone) {
+ if (updateLoyaltyCard) {
+ setTitle(R.string.editCardTitle);
+
+ if (!mFrontImageUnsaved && !croppedFrontImage() && !mFrontImageRemoved) {
+ setCardImage(cardImageFront, Utils.retrieveCardImage(this, tempLoyaltyCard.id, ImageLocationType.front), true);
+ }
+ if (!mBackImageUnsaved && !croppedBackImage() && !mBackImageRemoved) {
+ setCardImage(cardImageBack, Utils.retrieveCardImage(this, tempLoyaltyCard.id, ImageLocationType.back), true);
+ }
+ if (!mIconUnsaved && !croppedIcon() && !mIconRemoved) {
+ setCardImage(thumbnail, Utils.retrieveCardImage(this, tempLoyaltyCard.id, ImageLocationType.icon), false);
+ }
+ } else {
+ setTitle(R.string.addCardTitle);
+ }
+
+ if (mFrontImageUnsaved && !croppedFrontImage()) {
+ setCardImage(cardImageFront, Utils.loadTempImage(this, TEMP_UNSAVED_FRONT_IMAGE_NAME), true);
+ }
+ if (mBackImageUnsaved && !croppedBackImage()) {
+ setCardImage(cardImageBack, Utils.loadTempImage(this, TEMP_UNSAVED_BACK_IMAGE_NAME), true);
+ }
+ if (mIconUnsaved && !croppedIcon()) {
+ setCardImage(thumbnail, Utils.loadTempImage(this, TEMP_UNSAVED_ICON_NAME), false);
+ }
+ }
+
+ mCropperFinishedType = 0;
+
+ boolean hadChanges = hasChanged;
+
storeFieldEdit.setText(tempLoyaltyCard.store);
noteFieldEdit.setText(tempLoyaltyCard.note);
formatExpiryField(this, expiryField, tempLoyaltyCard.expiry);
@@ -614,7 +841,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
// It can't be null because we set it in updateTempState but SpotBugs insists it can be
// NP_NULL_ON_SOME_PATH: Possible null pointer dereference
if (tempLoyaltyCard.headerColor != null) {
- thumbnail.setOnClickListener(new ColorSelectListener());
+ thumbnail.setOnClickListener(new ChooseCardImage());
}
// Update from intent
@@ -645,8 +872,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
// Initialization has finished
if (!initDone) {
- hasChanged = false;
initDone = true;
+ hasChanged = hadChanges;
}
generateOrHideBarcode();
@@ -666,12 +893,21 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
onResuming = false;
}
- protected static void setCardImage(ImageView imageView, Bitmap bitmap) {
+ protected void setColorFromIcon() {
+ Object icon = thumbnail.getTag();
+ if (icon != null && (icon instanceof Bitmap)) {
+ updateTempState(LoyaltyCardField.headerColor, new Palette.Builder((Bitmap) icon).generate().getDominantColor(tempLoyaltyCard.headerColor != null ? tempLoyaltyCard.headerColor : ContextCompat.getColor(this, R.color.colorPrimary)));
+ } else {
+ Log.d("setColorFromIcon", "attempting header color change from icon but icon does not exist");
+ }
+ }
+
+ protected void setCardImage(ImageView imageView, Bitmap bitmap, boolean applyFallback) {
imageView.setTag(bitmap);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
- } else {
+ } else if (applyFallback) {
imageView.setImageResource(R.drawable.ic_camera_white);
}
}
@@ -707,8 +943,10 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
try {
if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_FRONT) {
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_FRONT);
- } else {
+ } else if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_BACK) {
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_BACK);
+ } else if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_ICON) {
+ takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_ICON);
}
} catch (Exception e) {
e.printStackTrace();
@@ -737,9 +975,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
dialog.dismiss();
})
- .setNegativeButton(R.string.no, (dialog, which) -> {
- dialog.dismiss();
- })
+ .setNegativeButton(R.string.no, (dialog, which) -> dialog.dismiss())
.setOnDismissListener(dialogInterface -> {
if (tempStoredOldBarcodeValue != null) {
barcodeIdField.setText(tempStoredOldBarcodeValue);
@@ -768,40 +1004,22 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.leaveWithoutSaveTitle);
builder.setMessage(R.string.leaveWithoutSaveConfirmation);
- builder.setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- finish();
- dialog.dismiss();
- }
- });
- builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
+ builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
+ finish();
+ dialog.dismiss();
});
+ builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
confirmExitDialog = builder.create();
}
confirmExitDialog.show();
}
- private void takePhotoForCard(int type) throws IOException {
- Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
- String imageFileName = "CATIMA_" + new Date().getTime();
- File image = File.createTempFile(
- imageFileName,
- ".jpg",
- getExternalFilesDir(Environment.DIRECTORY_PICTURES)
- );
+ private void takePhotoForCard(int type) {
+ Uri photoURI = FileProvider.getUriForFile(LoyaltyCardEditActivity.this, BuildConfig.APPLICATION_ID, Utils.createTempFile(this, TEMP_CAMERA_IMAGE_NAME));
+ mRequestedImage = type;
- tempCameraPicturePath = image.getAbsolutePath();
-
- Uri photoURI = FileProvider.getUriForFile(LoyaltyCardEditActivity.this, BuildConfig.APPLICATION_ID, image);
- i.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
-
- startActivityForResult(i, type);
+ mPhotoTakerLauncher.launch(photoURI);
}
class EditCardIdAndBarcode implements View.OnClickListener {
@@ -811,41 +1029,140 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
final Bundle b = new Bundle();
b.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, cardIdFieldView.getText().toString());
i.putExtras(b);
- startActivityForResult(i, Utils.BARCODE_SCAN);
+ mCardIdAndBarCodeEditorLauncher.launch(i);
}
}
class ChooseCardImage implements View.OnClickListener {
@Override
public void onClick(View v) throws NoSuchElementException {
- ImageView targetView = v.getId() == ID_IMAGE_FRONT ? cardImageFront : cardImageBack;
+ ImageView targetView;
+
+ if (v.getId() == R.id.frontImageHolder) {
+ targetView = cardImageFront;
+ } else if (v.getId() == R.id.backImageHolder) {
+ targetView = cardImageBack;
+ } else if (v.getId() == R.id.thumbnail) {
+ targetView = thumbnail;
+ } else {
+ throw new IllegalArgumentException("Invalid IMAGE ID " + v.getId());
+ }
LinkedHashMap> cardOptions = new LinkedHashMap<>();
- if (targetView.getTag() != null) {
+ if (targetView.getTag() != null && v.getId() != R.id.thumbnail) {
cardOptions.put(getString(R.string.removeImage), () -> {
- setCardImage(targetView, null);
+ if (targetView == cardImageFront) {
+ mFrontImageRemoved = true;
+ mFrontImageUnsaved = false;
+ } else {
+ mBackImageRemoved = true;
+ mBackImageUnsaved = false;
+ }
+
+ setCardImage(targetView, null, true);
+ return null;
+ });
+ }
+
+ if (v.getId() == R.id.thumbnail) {
+ cardOptions.put(getString(R.string.selectColor), () -> {
+ ColorPickerDialog.Builder dialogBuilder = ColorPickerDialog.newBuilder();
+
+ if (tempLoyaltyCard.headerColor != null) {
+ dialogBuilder.setColor(tempLoyaltyCard.headerColor);
+ }
+
+ ColorPickerDialog dialog = dialogBuilder.create();
+ dialog.setColorPickerDialogListener(new ColorPickerDialogListener() {
+ @Override
+ public void onColorSelected(int dialogId, int color) {
+ updateTempState(LoyaltyCardField.headerColor, color);
+
+ // Unset image if set
+ thumbnail.setTag(null);
+
+ generateIcon(storeFieldEdit.getText().toString());
+ }
+
+ @Override
+ public void onDialogDismissed(int dialogId) {
+ // Nothing to do, no change made
+ }
+ });
+ dialog.show(getSupportFragmentManager(), "color-picker-dialog");
+
+ setCardImage(targetView, null, false);
+ mIconRemoved = true;
+ mIconUnsaved = false;
+
return null;
});
}
cardOptions.put(getString(R.string.takePhoto), () -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- requestPermissions(new String[]{Manifest.permission.CAMERA}, v.getId() == ID_IMAGE_FRONT ? PERMISSION_REQUEST_CAMERA_IMAGE_FRONT : PERMISSION_REQUEST_CAMERA_IMAGE_BACK);
+ int permissionRequestType;
+
+ if (v.getId() == R.id.frontImageHolder) {
+ permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_FRONT;
+ } else if (v.getId() == R.id.backImageHolder) {
+ permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_BACK;
+ } else if (v.getId() == R.id.thumbnail) {
+ permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_ICON;
+ } else {
+ throw new IllegalArgumentException("Unknown ID type " + v.getId());
+ }
+
+ requestPermissions(new String[]{Manifest.permission.CAMERA}, permissionRequestType);
} else {
- takePhotoForCard(v.getId() == ID_IMAGE_FRONT ? Utils.CARD_IMAGE_FROM_CAMERA_FRONT : Utils.CARD_IMAGE_FROM_CAMERA_BACK);
+ int cardImageType;
+
+ if (v.getId() == R.id.frontImageHolder) {
+ cardImageType = Utils.CARD_IMAGE_FROM_CAMERA_FRONT;
+ } else if (v.getId() == R.id.backImageHolder) {
+ cardImageType = Utils.CARD_IMAGE_FROM_CAMERA_BACK;
+ } else if (v.getId() == R.id.thumbnail) {
+ cardImageType = Utils.CARD_IMAGE_FROM_CAMERA_ICON;
+ } else {
+ throw new IllegalArgumentException("Unknown ID type " + v.getId());
+ }
+
+ takePhotoForCard(cardImageType);
}
return null;
});
cardOptions.put(getString(R.string.addFromImage), () -> {
+ if (v.getId() == R.id.frontImageHolder) {
+ mRequestedImage = Utils.CARD_IMAGE_FROM_FILE_FRONT;
+ } else if (v.getId() == R.id.backImageHolder) {
+ mRequestedImage = Utils.CARD_IMAGE_FROM_FILE_BACK;
+ } else if (v.getId() == R.id.thumbnail) {
+ mRequestedImage = Utils.CARD_IMAGE_FROM_FILE_ICON;
+ } else {
+ throw new IllegalArgumentException("Unknown ID type " + v.getId());
+ }
+
Intent i = new Intent(Intent.ACTION_PICK);
i.setType("image/*");
- startActivityForResult(i, v.getId() == ID_IMAGE_FRONT ? Utils.CARD_IMAGE_FROM_FILE_FRONT : Utils.CARD_IMAGE_FROM_FILE_BACK);
+ mPhotoPickerLauncher.launch(i);
return null;
});
+ int titleResource;
+
+ if (v.getId() == R.id.frontImageHolder) {
+ titleResource = R.string.setFrontImage;
+ } else if (v.getId() == R.id.backImageHolder) {
+ titleResource = R.string.setBackImage;
+ } else if (v.getId() == R.id.thumbnail) {
+ titleResource = R.string.setIcon;
+ } else {
+ throw new IllegalArgumentException("Unknown ID type " + v.getId());
+ }
+
new AlertDialog.Builder(LoyaltyCardEditActivity.this)
- .setTitle(v.getId() == ID_IMAGE_FRONT ? getString(R.string.setFrontImage) : getString(R.string.setBackImage))
+ .setTitle(getString(titleResource))
.setItems(cardOptions.keySet().toArray(new CharSequence[cardOptions.size()]), (dialog, which) -> {
Iterator> callables = cardOptions.values().iterator();
Callable callable = callables.next();
@@ -864,33 +1181,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
}
}
- class ColorSelectListener implements View.OnClickListener {
- @Override
- public void onClick(View v) {
- ColorPickerDialog.Builder dialogBuilder = ColorPickerDialog.newBuilder();
-
- if (tempLoyaltyCard.headerColor != null) {
- dialogBuilder.setColor(tempLoyaltyCard.headerColor);
- }
-
- ColorPickerDialog dialog = dialogBuilder.create();
- dialog.setColorPickerDialogListener(new ColorPickerDialogListener() {
- @Override
- public void onColorSelected(int dialogId, int color) {
- updateTempState(LoyaltyCardField.headerColor, color);
-
- generateIcon(storeFieldEdit.getText().toString());
- }
-
- @Override
- public void onDialogDismissed(int dialogId) {
- // Nothing to do, no change made
- }
- });
- dialog.show(getSupportFragmentManager(), "color-picker-dialog");
- }
- }
-
public static class DatePickerFragment extends DialogFragment
implements DatePickerDialog.OnDateSetListener {
@@ -902,6 +1192,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
this.expiryFieldEdit = expiryFieldEdit;
}
+ @NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the current date as the default date in the picker
@@ -969,8 +1260,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
if (updateLoyaltyCard) { //update of "starStatus" not necessary, since it cannot be changed in this activity (only in ViewActivity)
db.updateLoyaltyCard(loyaltyCardId, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor);
try {
- Utils.saveCardImage(this, (Bitmap) cardImageFront.getTag(), loyaltyCardId, true);
- Utils.saveCardImage(this, (Bitmap) cardImageBack.getTag(), loyaltyCardId, false);
+ Utils.saveCardImage(this, (Bitmap) cardImageFront.getTag(), loyaltyCardId, ImageLocationType.front);
+ Utils.saveCardImage(this, (Bitmap) cardImageBack.getTag(), loyaltyCardId, ImageLocationType.back);
+ Utils.saveCardImage(this, (Bitmap) thumbnail.getTag(), loyaltyCardId, ImageLocationType.icon);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
@@ -978,8 +1270,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
} else {
loyaltyCardId = (int) db.insertLoyaltyCard(tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor, 0, tempLoyaltyCard.lastUsed);
try {
- Utils.saveCardImage(this, (Bitmap) cardImageFront.getTag(), loyaltyCardId, true);
- Utils.saveCardImage(this, (Bitmap) cardImageBack.getTag(), loyaltyCardId, false);
+ Utils.saveCardImage(this, (Bitmap) cardImageFront.getTag(), loyaltyCardId, ImageLocationType.front);
+ Utils.saveCardImage(this, (Bitmap) cardImageBack.getTag(), loyaltyCardId, ImageLocationType.back);
+ Utils.saveCardImage(this, (Bitmap) thumbnail.getTag(), loyaltyCardId, ImageLocationType.icon);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
@@ -987,6 +1280,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
db.setLoyaltyCardGroups(loyaltyCardId, selectedGroups);
+ ShortcutHelper.updateShortcuts(this, db.getLoyaltyCard(loyaltyCardId));
+
finish();
}
@@ -1035,68 +1330,60 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
return super.onOptionsItemSelected(item);
}
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent intent) {
- super.onActivityResult(requestCode, resultCode, intent);
+ public void startCropper(String sourceImagePath) {
+ startCropperUri(Uri.parse("file://" + sourceImagePath));
+ }
- if (resultCode == RESULT_OK) {
- if (requestCode == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || requestCode == Utils.CARD_IMAGE_FROM_CAMERA_BACK) {
- Bitmap bitmap = BitmapFactory.decodeFile(tempCameraPicturePath);
+ public void startCropperUri(Uri sourceUri) {
+ Log.d("cropper", "launching cropper with image " + sourceUri.getPath());
+ File cropOutput = Utils.createTempFile(this, TEMP_CROP_IMAGE_NAME);
+ Uri destUri = Uri.parse("file://" + cropOutput.getAbsolutePath());
+ Log.d("cropper", "asking cropper to output to " + destUri.toString());
- if (bitmap != null) {
- bitmap = Utils.resizeBitmap(bitmap);
- try {
- bitmap = Utils.rotateBitmap(bitmap, new ExifInterface(tempCameraPicturePath));
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- if (requestCode == Utils.CARD_IMAGE_FROM_CAMERA_FRONT) {
- setCardImage(cardImageFront, bitmap);
- } else {
- setCardImage(cardImageBack, bitmap);
- }
-
- hasChanged = true;
- } else {
- Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
- }
- } else if (requestCode == Utils.CARD_IMAGE_FROM_FILE_FRONT || requestCode == Utils.CARD_IMAGE_FROM_FILE_BACK) {
- Bitmap bitmap = null;
- try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- ImageDecoder.Source image_source = ImageDecoder.createSource(getContentResolver(), intent.getData());
- bitmap = ImageDecoder.decodeBitmap(image_source, (decoder, info, source) -> decoder.setMutableRequired(true));
- } else {
- bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), intent.getData());
- }
- } catch (IOException e) {
- Log.e(TAG, "Error getting data from image file");
- e.printStackTrace();
- }
-
- if (bitmap != null) {
- bitmap = Utils.resizeBitmap(bitmap);
- if (requestCode == Utils.CARD_IMAGE_FROM_FILE_FRONT) {
- setCardImage(cardImageFront, bitmap);
- } else {
- setCardImage(cardImageBack, bitmap);
- }
-
- hasChanged = true;
- } else {
- Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
- }
- } else {
- BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
-
- cardId = barcodeValues.content();
- barcodeType = barcodeValues.format();
- barcodeId = "";
- }
+ if (requestedFrontImage()) {
+ mCropperOptions.setToolbarTitle(getResources().getString(R.string.setFrontImage));
+ } else if (requestedBackImage()) {
+ mCropperOptions.setToolbarTitle(getResources().getString(R.string.setBackImage));
+ } else if (requestedIcon()) {
+ mCropperOptions.setToolbarTitle(getResources().getString(R.string.setIcon));
}
- onResume();
+ if (requestedIcon()) {
+ setCropperOptions(true, 0f, 0f);
+ } else {
+ // sniff the input image for width and height to work around a ucrop bug
+ Bitmap image = null;
+ try {
+ image = BitmapFactory.decodeStream(getContentResolver().openInputStream(sourceUri));
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ Log.d("cropper", "failed opening bitmap for initial width and height for ucrop " + sourceUri.toString());
+ }
+ if (image == null) {
+ Log.d("cropper", "failed loading bitmap for initial width and height for ucrop " + sourceUri.toString());
+ setCropperOptions(true, 0f, 0f);
+ } else {
+ try {
+ Bitmap imageRotated = Utils.rotateBitmap(image, new ExifInterface(getContentResolver().openInputStream(sourceUri)));
+ setCropperOptions(false, imageRotated.getWidth(), imageRotated.getHeight());
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ Log.d("cropper", "failed opening image for exif reading before setting initial width and height for ucrop");
+ setCropperOptions(false, image.getWidth(), image.getHeight());
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.d("cropper", "exif reading failed before setting initial width and height for ucrop");
+ setCropperOptions(false, image.getWidth(), image.getHeight());
+ }
+ }
+ }
+ mCropperLauncher.launch(
+ UCrop.of(
+ sourceUri,
+ destUri
+ ).withOptions(mCropperOptions)
+ .getIntent(this)
+ );
}
private void showBarcode() {
@@ -1120,7 +1407,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
private void generateBarcode(String cardIdString, CatimaBarcode barcodeFormat) {
mTasks.flushTaskList(TaskHandler.TYPE.BARCODE, true, false, false);
-
+
if (barcodeImage.getHeight() == 0) {
Log.d(TAG, "ImageView size is not known known at start, waiting for load");
// The size of the ImageView is not yet available as it has not
@@ -1150,14 +1437,16 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
return;
}
- thumbnail.setBackgroundColor(tempLoyaltyCard.headerColor);
+ if (thumbnail.getTag() == null) {
+ thumbnail.setBackgroundColor(tempLoyaltyCard.headerColor);
- LetterBitmap letterBitmap = Utils.generateIcon(this, store, tempLoyaltyCard.headerColor);
+ LetterBitmap letterBitmap = Utils.generateIcon(this, store, tempLoyaltyCard.headerColor);
- if (letterBitmap != null) {
- thumbnail.setImageBitmap(letterBitmap.getLetterTile());
- } else {
- thumbnail.setImageBitmap(null);
+ if (letterBitmap != null) {
+ thumbnail.setImageBitmap(letterBitmap.getLetterTile());
+ } else {
+ thumbnail.setImageBitmap(null);
+ }
}
thumbnail.setMinimumWidth(thumbnail.getHeight());
diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardLockerApplication.java b/app/src/main/java/protect/card_locker/LoyaltyCardLockerApplication.java
index c031f7904..4ceb46994 100644
--- a/app/src/main/java/protect/card_locker/LoyaltyCardLockerApplication.java
+++ b/app/src/main/java/protect/card_locker/LoyaltyCardLockerApplication.java
@@ -3,7 +3,6 @@ package protect.card_locker;
import android.app.Application;
import androidx.appcompat.app.AppCompatDelegate;
-
import protect.card_locker.preferences.Settings;
public class LoyaltyCardLockerApplication extends Application {
diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java
index 1249dfc63..d7b1b7335 100644
--- a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java
+++ b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java
@@ -5,11 +5,11 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Color;
+import android.graphics.Outline;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.GestureDetector;
@@ -17,6 +17,8 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
@@ -44,8 +46,10 @@ import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.appcompat.widget.Toolbar;
import androidx.constraintlayout.widget.Guideline;
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.core.widget.NestedScrollView;
import androidx.core.widget.TextViewCompat;
import protect.card_locker.async.TaskHandler;
@@ -56,10 +60,11 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
private GestureDetector mGestureDetector;
+ CoordinatorLayout coordinatorLayout;
TextView cardIdFieldView;
BottomSheetBehavior behavior;
- View bottomSheet;
- View bottomSheetContentWrapper;
+ LinearLayout bottomSheet;
+ NestedScrollView bottomSheetContentWrapper;
ImageView bottomSheetButton;
TextView noteView;
TextView groupsView;
@@ -72,6 +77,9 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
ImageButton minimizeButton;
View collapsingToolbarLayout;
AppBarLayout appBarLayout;
+ ImageView iconImage;
+ Toolbar landscapeToolbar;
+
int loyaltyCardId;
LoyaltyCard loyaltyCard;
boolean rotationEnabled;
@@ -99,8 +107,13 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
private ImageView[] dots;
boolean isBarcodeSupported = true;
+ int bottomSheetState;
+
static final String STATE_IMAGEINDEX = "imageIndex";
static final String STATE_FULLSCREEN = "isFullscreen";
+ static final String STATE_BOTTOMSHEET = "bottomSheetState";
+
+ private final int HEADER_FILTER_ALPHA = 127;
final private TaskHandler mTasks = new TaskHandler();
@@ -190,11 +203,15 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
Log.d(TAG, "View activity: id=" + loyaltyCardId);
}
- private Drawable getDotIcon(boolean active) {
+ private Drawable getDotIcon(boolean active, boolean darkMode) {
Drawable unwrappedIcon = AppCompatResources.getDrawable(this, active ? R.drawable.active_dot : R.drawable.inactive_dot);
assert unwrappedIcon != null;
Drawable wrappedIcon = DrawableCompat.wrap(unwrappedIcon);
- DrawableCompat.setTint(wrappedIcon, ContextCompat.getColor(getApplicationContext(), R.color.iconColor));
+ if (darkMode) {
+ DrawableCompat.setTint(wrappedIcon, Color.WHITE);
+ } else {
+ DrawableCompat.setTint(wrappedIcon, Color.BLACK);
+ }
return wrappedIcon;
}
@@ -212,6 +229,17 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
return wrappedIcon;
}
+ private void setCenterGuideline(int zoomLevel) {
+ float scale = zoomLevel / 100f;
+
+ if (format != null && format.isSquare()) {
+ centerGuideline.setGuidelinePercent(0.75f * scale);
+ } else {
+ centerGuideline.setGuidelinePercent(0.5f * scale);
+ }
+
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -219,6 +247,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
if (savedInstanceState != null) {
mainImageIndex = savedInstanceState.getInt(STATE_IMAGEINDEX);
isFullscreen = savedInstanceState.getBoolean(STATE_FULLSCREEN);
+ bottomSheetState = savedInstanceState.getInt(STATE_BOTTOMSHEET);
}
settings = new Settings(this);
@@ -230,6 +259,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
db = new DBHelper(this);
importURIHelper = new ImportURIHelper(this);
+ coordinatorLayout = findViewById(R.id.coordinator_layout);
cardIdFieldView = findViewById(R.id.cardIdView);
bottomSheet = findViewById(R.id.bottom_sheet);
bottomSheetContentWrapper = findViewById(R.id.bottomSheetContentWrapper);
@@ -245,6 +275,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
minimizeButton = findViewById(R.id.minimizeButton);
collapsingToolbarLayout = findViewById(R.id.collapsingToolbarLayout);
appBarLayout = findViewById(R.id.app_bar_layout);
+ iconImage = findViewById(R.id.icon_image);
+ landscapeToolbar = findViewById(R.id.toolbar_landscape);
centerGuideline = findViewById(R.id.centerGuideline);
barcodeScaler = findViewById(R.id.barcodeScaler);
@@ -254,21 +286,19 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
barcodeScaler.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (!fromUser) {
+ Log.d(TAG, "non user triggered onProgressChanged, ignoring, progress is " + progress);
+ return;
+ }
Log.d(TAG, "Progress is " + progress);
Log.d(TAG, "Max is " + barcodeScaler.getMax());
float scale = (float) progress / (float) barcodeScaler.getMax();
Log.d(TAG, "Scaling to " + scale);
- if(isFullscreen){
- loyaltyCard.zoomLevel = progress;
- db.updateLoyaltyCardZoomLevel(loyaltyCardId, loyaltyCard.zoomLevel);
- }
+ loyaltyCard.zoomLevel = progress;
+ db.updateLoyaltyCardZoomLevel(loyaltyCardId, loyaltyCard.zoomLevel);
- if (format != null && format.isSquare()) {
- centerGuideline.setGuidelinePercent(0.75f * scale);
- } else {
- centerGuideline.setGuidelinePercent(0.5f * scale);
- }
+ setCenterGuideline(loyaltyCard.zoomLevel);
drawMainImage(mainImageIndex, true);
}
@@ -306,20 +336,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
- if (newState == BottomSheetBehavior.STATE_DRAGGING) {
- editButton.hide();
- } else if (newState == BottomSheetBehavior.STATE_EXPANDED) {
- bottomSheetButton.setImageResource(R.drawable.ic_baseline_arrow_drop_down_24);
- editButton.hide();
- } else if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
- bottomSheetButton.setImageResource(R.drawable.ic_baseline_arrow_drop_up_24);
- if (!isFullscreen) {
- editButton.show();
- }
-
- // Scroll bottomsheet content back to top
- bottomSheetContentWrapper.setScrollY(0);
- }
+ changeUiToBottomSheetState(newState);
}
@Override
@@ -335,22 +352,18 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
});
- // Fix bottom sheet content sizing
- ViewTreeObserver viewTreeObserver = bottomSheet.getViewTreeObserver();
- viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ appBarLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
- public void onGlobalLayout() {
- bottomSheet.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
- int height = displayMetrics.heightPixels;
- int maxHeight = height - appBarLayout.getHeight() - bottomSheetButton.getHeight();
- Log.d(TAG, "Button sheet should be " + maxHeight + " pixels high");
- bottomSheetContentWrapper.setLayoutParams(
- new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT,
- maxHeight
- )
- );
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ adjustLayoutHeights();
+ }
+ });
+
+ appBarLayout.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ ViewOutlineProvider.BACKGROUND.getOutline(view, outline);
+ outline.setAlpha(0f);
}
});
@@ -359,6 +372,53 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
mainImage.setOnTouchListener(gestureTouchListener);
}
+ private void changeUiToBottomSheetState(int newState) {
+ if (newState == BottomSheetBehavior.STATE_DRAGGING) {
+ editButton.hide();
+ } else if (newState == BottomSheetBehavior.STATE_EXPANDED) {
+ bottomSheetButton.setImageResource(R.drawable.ic_baseline_arrow_drop_down_24);
+ editButton.hide();
+ } else if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
+ bottomSheetButton.setImageResource(R.drawable.ic_baseline_arrow_drop_up_24);
+ if (!isFullscreen) {
+ editButton.show();
+ }
+
+ // Scroll bottomsheet content back to top
+ bottomSheetContentWrapper.setScrollY(0);
+ }
+ bottomSheetState = newState;
+ }
+
+ private void adjustLayoutHeights() {
+ // use getLayoutParams instead of getHeight when heights are pre-determined in xml! getHeight could return 0 if a View is not inflated
+ if (iconImage.getLayoutParams().height != appBarLayout.getHeight()) {
+ Log.d("adjustLayoutHeights", "setting imageIcon height from: " + iconImage.getLayoutParams().height + " to: " + appBarLayout.getHeight());
+ iconImage.setLayoutParams(new CoordinatorLayout.LayoutParams(
+ CoordinatorLayout.LayoutParams.MATCH_PARENT, appBarLayout.getHeight())
+ );
+ }
+ int bottomSheetHeight = getResources().getDisplayMetrics().heightPixels - appBarLayout.getHeight() - bottomSheetButton.getLayoutParams().height;
+ ViewGroup.LayoutParams params = bottomSheetContentWrapper.getLayoutParams();
+ if (params.height != bottomSheetHeight || params.width != LinearLayout.LayoutParams.MATCH_PARENT) {
+ // XXX android 5 - 9 has so much quirks with setting bottomSheetContent height
+ // just invalidate the wrapper works on 10 onward
+ // bottomSheetContentWrapper.invalidate();
+ // The below worked on android 5 but not 6, reloading the card then it breaks again on 6, entirely random :(
+ // for (int i = 0; i < bottomSheetContentWrapper.getChildCount(); i++) {
+ // bottomSheetContentWrapper.getChildAt(i).invalidate();
+ // }
+ // since it's basically allergic to getting enlarged then shrunk again, and setting it at all when fullscreen makes no sense
+ if (!isFullscreen) {
+ Log.d("adjustLayoutHeights", "setting bottomSheet height from: " + params.height + " to: " + bottomSheetHeight);
+ bottomSheetContentWrapper.setLayoutParams(
+ new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, bottomSheetHeight)
+ );
+ }
+ }
+ }
+
+
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
@@ -371,6 +431,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt(STATE_IMAGEINDEX, mainImageIndex);
savedInstanceState.putBoolean(STATE_FULLSCREEN, isFullscreen);
+ savedInstanceState.putInt(STATE_BOTTOMSHEET, bottomSheetState);
super.onSaveInstanceState(savedInstanceState);
}
@@ -489,9 +550,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
backgroundHeaderColor = LetterBitmap.getDefaultColor(this, loyaltyCard.store);
}
- collapsingToolbarLayout.setBackgroundColor(backgroundHeaderColor);
- appBarLayout.setBackgroundColor(backgroundHeaderColor);
-
int textColor;
if (Utils.needsDarkForeground(backgroundHeaderColor)) {
textColor = Color.BLACK;
@@ -499,7 +557,21 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
textColor = Color.WHITE;
}
storeName.setTextColor(textColor);
- ((Toolbar) findViewById(R.id.toolbar_landscape)).setTitleTextColor(textColor);
+ landscapeToolbar.setTitleTextColor(textColor);
+
+ Bitmap icon = Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.icon);
+ if (icon != null) {
+ int backgroundAlphaColor = Utils.needsDarkForeground(backgroundHeaderColor) ? Color.WHITE : Color.BLACK;
+ Log.d("onResume", "setting icon image");
+ iconImage.setImageBitmap(icon);
+ int backgroundWithAlpha = Color.argb(HEADER_FILTER_ALPHA, Color.red(backgroundAlphaColor), Color.green(backgroundAlphaColor), Color.blue(backgroundAlphaColor));
+ // for images that has alpha
+ appBarLayout.setBackgroundColor(backgroundWithAlpha);
+ } else {
+ Bitmap plain = Bitmap.createBitmap(new int[]{backgroundHeaderColor}, 1, 1, Bitmap.Config.ARGB_8888);
+ iconImage.setImageBitmap(plain);
+ appBarLayout.setBackgroundColor(Color.TRANSPARENT);
+ }
// If the background is very bright, we should use dark icons
backgroundNeedsDarkIcons = Utils.needsDarkForeground(backgroundHeaderColor);
@@ -531,13 +603,12 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
imageTypes.add(ImageType.BARCODE);
}
- frontImageBitmap = Utils.retrieveCardImage(this, loyaltyCard.id, true);
- backImageBitmap = Utils.retrieveCardImage(this, loyaltyCard.id, false);
-
+ frontImageBitmap = Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.front);
if (frontImageBitmap != null) {
imageTypes.add(ImageType.IMAGE_FRONT);
}
+ backImageBitmap = Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.back);
if (backImageBitmap != null) {
imageTypes.add(ImageType.IMAGE_BACK);
}
@@ -545,10 +616,11 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
dotIndicator.removeAllViews();
if (imageTypes.size() >= 2) {
dots = new ImageView[imageTypes.size()];
+ boolean darkMode = Utils.isDarkModeEnabled(getApplicationContext());
for (int i = 0; i < imageTypes.size(); i++) {
dots[i] = new ImageView(this);
- dots[i].setImageDrawable(getDotIcon(false));
+ dots[i].setImageDrawable(getDotIcon(false, darkMode));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
params.setMargins(8, 0, 8, 0);
@@ -561,6 +633,9 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
setFullscreen(isFullscreen);
+ // restore bottomSheet UI states from changing orientation
+ changeUiToBottomSheetState(bottomSheetState);
+
db.updateLoyaltyCardLastUsed(loyaltyCard.id);
}
@@ -647,7 +722,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
private void setupOrientation() {
Toolbar portraitToolbar = findViewById(R.id.toolbar);
- Toolbar landscapeToolbar = findViewById(R.id.toolbar_landscape);
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
@@ -736,8 +810,9 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
if (dots != null) {
+ boolean darkMode = Utils.isDarkModeEnabled(getApplicationContext());
for (int i = 0; i < dots.length; i++) {
- dots[i].setImageDrawable(getDotIcon(i == index));
+ dots[i].setImageDrawable(getDotIcon(i == index, darkMode));
}
}
@@ -781,7 +856,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
/**
* When enabled, hides the status bar and moves the barcode to the top of the screen.
- *
+ *
* The purpose of this function is to make sure the barcode can be scanned from the phone
* by machines which offer no space to insert the complete device.
*/
@@ -795,6 +870,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
drawMainImage(mainImageIndex, true);
barcodeScaler.setProgress(loyaltyCard.zoomLevel);
+ setCenterGuideline(loyaltyCard.zoomLevel);
// Hide maximize and show minimize button and scaler
maximizeButton.setVisibility(View.GONE);
@@ -813,7 +889,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
// Don't ask me why...
appBarLayout.setVisibility(View.INVISIBLE);
collapsingToolbarLayout.setVisibility(View.GONE);
- findViewById(R.id.toolbar_landscape).setVisibility(View.GONE);
+ landscapeToolbar.setVisibility(View.GONE);
// Hide other UI elements
cardIdFieldView.setVisibility(View.GONE);
@@ -821,6 +897,9 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
editButton.hide();
+ // android 5-9, avoid padding growing on top of bottomSheet
+ coordinatorLayout.removeView(bottomSheet);
+
// Set Android to fullscreen mode
getWindow().getDecorView().setSystemUiVisibility(
getWindow().getDecorView().getSystemUiVisibility()
@@ -831,7 +910,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
Log.d(TAG, "Move out of fullscreen");
// Reset center guideline
- barcodeScaler.setProgress(100);
+ setCenterGuideline(100);
drawMainImage(mainImageIndex, true);
@@ -849,8 +928,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
// Show appropriate toolbar
// And restore 24dp paddingTop for appBarLayout
appBarLayout.setVisibility(View.VISIBLE);
- DisplayMetrics metrics = new DisplayMetrics();
- getWindowManager().getDefaultDisplay().getMetrics(metrics);
setupOrientation();
// Show other UI elements
@@ -864,8 +941,13 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
);
+
+ // android 5-9, avoid padding growing on top of bottomSheet
+ if (bottomSheet.getParent() != coordinatorLayout) {
+ coordinatorLayout.addView(bottomSheet);
+ }
}
- Log.d("setFullScreen","Is full screen enabled? "+enabled+" Zoom Level = "+barcodeScaler.getProgress());
+ Log.d("setFullScreen", "Is full screen enabled? " + enabled + " Zoom Level = " + barcodeScaler.getProgress());
}
}
diff --git a/app/src/main/java/protect/card_locker/MainActivity.java b/app/src/main/java/protect/card_locker/MainActivity.java
index 7092e9be7..5155041bc 100644
--- a/app/src/main/java/protect/card_locker/MainActivity.java
+++ b/app/src/main/java/protect/card_locker/MainActivity.java
@@ -6,7 +6,6 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.res.Configuration;
import android.database.Cursor;
import android.database.CursorIndexOutOfBoundsException;
import android.os.Bundle;
@@ -27,17 +26,18 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
-import androidx.core.app.ActivityCompat;
import androidx.core.splashscreen.SplashScreen;
import androidx.recyclerview.widget.RecyclerView;
+
import protect.card_locker.preferences.SettingsActivity;
-public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener, GestureDetector.OnGestureListener
-{
+public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener, GestureDetector.OnGestureListener {
private static final String TAG = "Catima";
private final DBHelper mDB = new DBHelper(this);
@@ -55,8 +55,10 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
private View mNoMatchingCardsText;
private View mNoGroupCardsText;
- private ActionMode.Callback mCurrentActionModeCallback = new ActionMode.Callback()
- {
+ private ActivityResultLauncher mMainRequestLauncher;
+ private ActivityResultLauncher mBarcodeScannerLauncher;
+
+ private ActionMode.Callback mCurrentActionModeCallback = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode inputMode, Menu inputMenu) {
inputMode.getMenuInflater().inflate(R.menu.card_longclick_menu, inputMenu);
@@ -64,8 +66,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
@Override
- public boolean onPrepareActionMode(ActionMode inputMode, Menu inputMenu)
- {
+ public boolean onPrepareActionMode(ActionMode inputMode, Menu inputMenu) {
return false;
}
@@ -109,7 +110,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
inputMode.finish();
return true;
- } else if(inputItem.getItemId() == R.id.action_edit) {
+ } else if (inputItem.getItemId() == R.id.action_edit) {
if (mAdapter.getSelectedItemCount() != 1) {
throw new IllegalArgumentException("Cannot edit more than 1 card at a time");
}
@@ -122,7 +123,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
startActivity(intent);
inputMode.finish();
return true;
- } else if(inputItem.getItemId() == R.id.action_delete) {
+ } else if (inputItem.getItemId() == R.id.action_delete) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
// The following may seem weird, but it is necessary to give translators enough flexibility.
// For example, in Russian, Android's plural quantity "one" actually refers to "any number ending on 1 but not ending in 11".
@@ -165,15 +166,12 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
@Override
- public void onDestroyActionMode(ActionMode inputMode)
- {
+ public void onDestroyActionMode(ActionMode inputMode) {
mAdapter.clearSelections();
mCurrentActionMode = null;
- mCardList.post(new Runnable()
- {
+ mCardList.post(new Runnable() {
@Override
- public void run()
- {
+ public void run() {
mAdapter.resetAnimationIndex();
}
});
@@ -181,8 +179,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
};
@Override
- protected void onCreate(Bundle inputSavedInstanceState)
- {
+ protected void onCreate(Bundle inputSavedInstanceState) {
super.onCreate(inputSavedInstanceState);
SplashScreen.installSplashScreen(this);
setTitle(R.string.app_name);
@@ -195,7 +192,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
@Override
public void onTabSelected(TabLayout.Tab tab) {
selectedTab = tab.getPosition();
- Log.d("onTabSelected","Tab Position "+tab.getPosition());
+ Log.d("onTabSelected", "Tab Position " + tab.getPosition());
mGroup = tab.getTag();
updateLoyaltyCardList();
// Store active tab in Shared Preference to restore next app launch
@@ -269,25 +266,49 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
.show();
}
*/
+
+ mMainRequestLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
+ // We're coming back from another view so clear the search
+ // We only do this now to prevent a flash of all entries right after picking one
+ mFilter = "";
+ if (mMenu != null) {
+ MenuItem searchItem = mMenu.findItem(R.id.action_search);
+ searchItem.collapseActionView();
+ }
+ });
+
+ mBarcodeScannerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
+ Intent intent = result.getData();
+ BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, this);
+
+ if (!barcodeValues.isEmpty()) {
+ Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
+ Bundle newBundle = new Bundle();
+ newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
+ newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
+ Bundle inputBundle = intent.getExtras();
+ if (inputBundle != null && inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) != null) {
+ newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP));
+ }
+ newIntent.putExtras(newBundle);
+ startActivity(newIntent);
+ }
+ });
}
@Override
- protected void onResume()
- {
+ protected void onResume() {
super.onResume();
- if(mCurrentActionMode != null)
- {
+ if (mCurrentActionMode != null) {
mAdapter.clearSelections();
mCurrentActionMode.finish();
}
- if (mMenu != null)
- {
+ if (mMenu != null) {
SearchView searchView = (SearchView) mMenu.findItem(R.id.action_search).getActionView();
- if (!searchView.isIconified())
- {
+ if (!searchView.isIconified()) {
mFilter = searchView.getQuery().toString();
}
}
@@ -307,7 +328,8 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
try {
mOrder = DBHelper.LoyaltyCardOrder.valueOf(sortPref.getString(getString(R.string.sharedpreference_sort_order), null));
mOrderDirection = DBHelper.LoyaltyCardOrderDirection.valueOf(sortPref.getString(getString(R.string.sharedpreference_sort_direction), null));
- } catch (IllegalArgumentException | NullPointerException ignored) {}
+ } catch (IllegalArgumentException | NullPointerException ignored) {
+ }
mGroup = null;
@@ -332,48 +354,13 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, groupsTabLayout.getTabAt(selectedTab).getText().toString());
}
intent.putExtras(bundle);
- startActivityForResult(intent, Utils.BARCODE_SCAN);
+ mBarcodeScannerLauncher.launch(intent);
});
addButton.bringToFront();
}
@Override
- protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
- super.onActivityResult(requestCode, resultCode, intent);
-
- if (requestCode == Utils.MAIN_REQUEST) {
- // We're coming back from another view so clear the search
- // We only do this now to prevent a flash of all entries right after picking one
- mFilter = "";
- if (mMenu != null)
- {
- MenuItem searchItem = mMenu.findItem(R.id.action_search);
- searchItem.collapseActionView();
- }
- ActivityCompat.recreate(this);
-
- return;
- }
-
- BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
-
- if(!barcodeValues.isEmpty()) {
- Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
- Bundle newBundle = new Bundle();
- newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
- newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
- Bundle inputBundle = intent.getExtras();
- if (inputBundle != null && inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) != null) {
- newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP));
- }
- newIntent.putExtras(newBundle);
- startActivity(newIntent);
- }
- }
-
- @Override
- public void onBackPressed()
- {
+ public void onBackPressed() {
if (mMenu != null) {
SearchView searchView = (SearchView) mMenu.findItem(R.id.action_search).getActionView();
@@ -394,20 +381,16 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
mAdapter.swapCursor(mDB.getLoyaltyCardCursor(mFilter, group, mOrder, mOrderDirection));
- if(mDB.getLoyaltyCardCount() > 0)
- {
+ if (mDB.getLoyaltyCardCount() > 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)
- {
+ if (mAdapter.getItemCount() > 0) {
mCardList.setVisibility(View.VISIBLE);
mNoMatchingCardsText.setVisibility(View.GONE);
- }
- else
- {
+ } else {
mCardList.setVisibility(View.GONE);
if (!mFilter.isEmpty()) {
// Actual Empty Search Result
@@ -419,9 +402,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
mNoGroupCardsText.setVisibility(View.VISIBLE);
}
}
- }
- else
- {
+ } else {
mCardList.setVisibility(View.GONE);
mHelpText.setVisibility(View.VISIBLE);
mNoMatchingCardsText.setVisibility(View.GONE);
@@ -433,8 +414,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
}
- public void updateTabGroups(TabLayout groupsTabLayout)
- {
+ public void updateTabGroups(TabLayout groupsTabLayout) {
final DBHelper db = new DBHelper(this);
List newGroups = db.getGroups();
@@ -463,15 +443,13 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
@Override
- public boolean onCreateOptionsMenu(Menu inputMenu)
- {
+ public boolean onCreateOptionsMenu(Menu inputMenu) {
this.mMenu = inputMenu;
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- if (searchManager != null)
- {
+ if (searchManager != null) {
SearchView searchView = (SearchView) inputMenu.findItem(R.id.action_search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setSubmitButtonEnabled(false);
@@ -481,17 +459,14 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
return false;
});
- searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener()
- {
+ searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
- public boolean onQueryTextSubmit(String query)
- {
+ public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
- public boolean onQueryTextChange(String newText)
- {
+ public boolean onQueryTextChange(String newText) {
mFilter = newText;
TabLayout groupsTabLayout = findViewById(R.id.groups);
@@ -508,12 +483,26 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
@Override
- public boolean onOptionsItemSelected(MenuItem inputItem)
- {
+ public boolean onOptionsItemSelected(MenuItem inputItem) {
int id = inputItem.getItemId();
- if (id == R.id.action_sort)
- {
+ if (id == R.id.action_unfold) {
+ boolean shouldShow = !mAdapter.showingDetails();
+
+ if (shouldShow) {
+ inputItem.setIcon(R.drawable.ic_baseline_unfold_less_24);
+ inputItem.setTitle(R.string.action_hide_details);
+ } else {
+ inputItem.setIcon(R.drawable.ic_baseline_unfold_more_24);
+ inputItem.setTitle(R.string.action_show_details);
+ }
+
+ mAdapter.showDetails(shouldShow);
+
+ return true;
+ }
+
+ if (id == R.id.action_sort) {
TabLayout.Tab tab = ((TabLayout) findViewById(R.id.groups)).getTabAt(selectedTab);
AtomicInteger currentIndex = new AtomicInteger();
List loyaltyCardOrders = Arrays.asList(DBHelper.LoyaltyCardOrder.values());
@@ -536,10 +525,9 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
builder.setSingleChoiceItems(R.array.sort_types_array, currentIndex.get(), (dialog, which) -> currentIndex.set(which));
builder.setPositiveButton(R.string.sort, (dialog, which) -> {
- if(ch.isChecked()) {
+ if (ch.isChecked()) {
setSort(loyaltyCardOrders.get(currentIndex.get()), DBHelper.LoyaltyCardOrderDirection.Descending);
- }
- else {
+ } else {
setSort(loyaltyCardOrders.get(currentIndex.get()), DBHelper.LoyaltyCardOrderDirection.Ascending);
}
dialog.dismiss();
@@ -553,31 +541,27 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
return true;
}
- if (id == R.id.action_manage_groups)
- {
+ if (id == R.id.action_manage_groups) {
Intent i = new Intent(getApplicationContext(), ManageGroupsActivity.class);
- startActivityForResult(i, Utils.MAIN_REQUEST);
+ mMainRequestLauncher.launch(i);
return true;
}
- if (id == R.id.action_import_export)
- {
+ if (id == R.id.action_import_export) {
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
- startActivityForResult(i, Utils.MAIN_REQUEST);
+ mMainRequestLauncher.launch(i);
return true;
}
- if (id == R.id.action_settings)
- {
+ if (id == R.id.action_settings) {
Intent i = new Intent(getApplicationContext(), SettingsActivity.class);
- startActivityForResult(i, Utils.MAIN_REQUEST);
+ mMainRequestLauncher.launch(i);
return true;
}
- if (id == R.id.action_about)
- {
+ if (id == R.id.action_about) {
Intent i = new Intent(getApplicationContext(), AboutActivity.class);
- startActivityForResult(i, Utils.MAIN_REQUEST);
+ mMainRequestLauncher.launch(i);
return true;
}
@@ -602,13 +586,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
updateLoyaltyCardList();
}
- protected static boolean isDarkModeEnabled(Context inputContext)
- {
- Configuration config = inputContext.getResources().getConfiguration();
- int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
- return (currentNightMode == Configuration.UI_MODE_NIGHT_YES);
- }
-
@Override
public boolean onDown(MotionEvent e) {
return false;
@@ -655,10 +632,10 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
Integer currentTab = groupsTabLayout.getSelectedTabPosition();
- Log.d("onFling","Current Tab "+currentTab);
+ Log.d("onFling", "Current Tab " + currentTab);
// Swipe right
if (velocityX < -150) {
- Log.d("onFling","Right Swipe detected "+velocityX);
+ Log.d("onFling", "Right Swipe detected " + velocityX);
Integer nextTab = currentTab + 1;
if (nextTab == groupsTabLayout.getTabCount()) {
@@ -672,7 +649,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
// Swipe left
if (velocityX > 150) {
- Log.d("onFling","Left Swipe detected "+velocityX);
+ Log.d("onFling", "Left Swipe detected " + velocityX);
Integer nextTab = currentTab - 1;
if (nextTab < 0) {
@@ -688,22 +665,18 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
@Override
- public void onRowLongClicked(int inputPosition)
- {
+ public void onRowLongClicked(int inputPosition) {
enableActionMode(inputPosition);
}
- private void enableActionMode(int inputPosition)
- {
- if (mCurrentActionMode == null)
- {
+ private void enableActionMode(int inputPosition) {
+ if (mCurrentActionMode == null) {
mCurrentActionMode = startSupportActionMode(mCurrentActionModeCallback);
}
toggleSelection(inputPosition);
}
- private void toggleSelection(int inputPosition)
- {
+ private void toggleSelection(int inputPosition) {
mAdapter.toggleSelection(inputPosition);
int count = mAdapter.getSelectedItemCount();
@@ -725,16 +698,11 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
}
-
@Override
- public void onRowClicked(int inputPosition)
- {
- if (mAdapter.getSelectedItemCount() > 0)
- {
+ public void onRowClicked(int inputPosition) {
+ if (mAdapter.getSelectedItemCount() > 0) {
enableActionMode(inputPosition);
- }
- else
- {
+ } else {
Cursor selected = mAdapter.getCursor();
selected.moveToPosition(inputPosition);
// FIXME
@@ -761,7 +729,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard);
- startActivityForResult(i, Utils.MAIN_REQUEST);
+ mMainRequestLauncher.launch(i);
}
}
}
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..8e0df0883
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/ManageGroupActivity.java
@@ -0,0 +1,216 @@
+package protect.card_locker;
+
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.widget.Toolbar;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class ManageGroupActivity extends CatimaAppCompatActivity implements ManageGroupCursorAdapter.CardAdapterListener {
+
+ private final DBHelper mDB = new DBHelper(this);
+ private ManageGroupCursorAdapter mAdapter;
+
+ private final String SAVE_INSTANCE_ADAPTER_STATE = "adapterState";
+ private final String SAVE_INSTANCE_CURRENT_GROUP_NAME = "currentGroupName";
+
+ protected Group mGroup = null;
+ private RecyclerView mCardList;
+ private TextView mHelpText;
+ private EditText mGroupNameText;
+
+ private boolean mGroupNameNotInUse;
+
+ @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);
+ mCardList = findViewById(R.id.list);
+ FloatingActionButton saveButton = findViewById(R.id.fabSave);
+
+ mGroupNameText = findViewById(R.id.editTextGroupName);
+
+ mGroupNameText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ mGroupNameNotInUse = true;
+ mGroupNameText.setError(null);
+ String currentGroupName = mGroupNameText.getText().toString().trim();
+ if (currentGroupName.length() == 0) {
+ mGroupNameText.setError(getResources().getText(R.string.group_name_is_empty));
+ return;
+ }
+ if (!mGroup._id.equals(currentGroupName)) {
+ if (mDB.getGroup(currentGroupName) != null) {
+ mGroupNameNotInUse = false;
+ mGroupNameText.setError(getResources().getText(R.string.group_name_already_in_use));
+ } else {
+ mGroupNameNotInUse = true;
+ }
+ }
+ }
+ });
+
+ Intent intent = getIntent();
+ String groupId = intent.getStringExtra("group");
+ if (groupId == null) {
+ throw (new IllegalArgumentException("this activity expects a group loaded into it's intent"));
+ }
+ Log.d("groupId", "groupId: " + groupId);
+ mGroup = mDB.getGroup(groupId);
+ if (mGroup == null) {
+ throw (new IllegalArgumentException("cannot load group " + groupId + " from database"));
+ }
+ mGroupNameText.setText(mGroup._id);
+ setTitle(getString(R.string.editGroup, mGroup._id));
+ mAdapter = new ManageGroupCursorAdapter(this, null, this, mGroup);
+ mCardList.setAdapter(mAdapter);
+ registerForContextMenu(mCardList);
+
+ if (inputSavedInstanceState != null) {
+ mAdapter.importInGroupState(integerArrayToAdapterState(inputSavedInstanceState.getIntegerArrayList(SAVE_INSTANCE_ADAPTER_STATE)));
+ mGroupNameText.setText(inputSavedInstanceState.getString(SAVE_INSTANCE_CURRENT_GROUP_NAME));
+ }
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar == null) {
+ throw (new RuntimeException("mActionBar is not expected to be null here"));
+ }
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setDisplayShowHomeEnabled(true);
+
+ saveButton.setOnClickListener(v -> {
+ 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..cef4fb8d1
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/ManageGroupCursorAdapter.java
@@ -0,0 +1,112 @@
+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..2573a9440 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;
@@ -21,8 +23,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-public class ManageGroupsActivity extends CatimaAppCompatActivity implements GroupCursorAdapter.GroupAdapterListener
-{
+public class ManageGroupsActivity extends CatimaAppCompatActivity implements GroupCursorAdapter.GroupAdapterListener {
private static final String TAG = "Catima";
private final DBHelper mDb = new DBHelper(this);
@@ -31,16 +32,14 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
GroupCursorAdapter mAdapter;
@Override
- protected void onCreate(Bundle savedInstanceState)
- {
+ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.groups);
setContentView(R.layout.manage_groups_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
- if(actionBar != null)
- {
+ if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
@@ -72,8 +71,7 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
super.onBackPressed();
}
- private void updateGroupList()
- {
+ private void updateGroupList() {
mAdapter.swapCursor(mDb.getGroupCursor());
if (mDb.getGroupCount() == 0) {
@@ -87,8 +85,7 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
mHelpText.setVisibility(View.GONE);
}
- private void invalidateHomescreenActiveTab()
- {
+ private void invalidateHomescreenActiveTab() {
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
Context.MODE_PRIVATE);
@@ -98,8 +95,7 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
}
@Override
- public boolean onOptionsItemSelected(MenuItem item)
- {
+ public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
@@ -110,14 +106,23 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
}
private void createGroup() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this,R.style.AlertDialogTheme);
+ AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.AlertDialogTheme);
builder.setTitle(R.string.enter_group_name);
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
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 +181,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/java/protect/card_locker/ScanActivity.java b/app/src/main/java/protect/card_locker/ScanActivity.java
index 826263c86..da2d7c763 100644
--- a/app/src/main/java/protect/card_locker/ScanActivity.java
+++ b/app/src/main/java/protect/card_locker/ScanActivity.java
@@ -20,12 +20,14 @@ import com.journeyapps.barcodescanner.DecoratedBarcodeView;
import java.util.List;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
/**
* Custom Scannner Activity extending from Activity to display a custom layout form scanner view.
- *
+ *
* Based on https://github.com/journeyapps/zxing-android-embedded/blob/0fdfbce9fb3285e985bad9971c5f7c0a7a334e7b/sample/src/main/java/example/zxing/CustomScannerActivity.java
* originally licensed under Apache 2.0
*/
@@ -39,6 +41,10 @@ public class ScanActivity extends CatimaAppCompatActivity {
private String addGroup;
private boolean torch = false;
+ private ActivityResultLauncher manualAddLauncher;
+ // can't use the pre-made contract because that launches the file manager for image type instead of gallery
+ private ActivityResultLauncher photoPickerLauncher;
+
private void extractIntentFields(Intent intent) {
final Bundle b = intent.getExtras();
cardId = b != null ? b.getString(LoyaltyCardEditActivity.BUNDLE_CARDID) : null;
@@ -54,13 +60,14 @@ public class ScanActivity extends CatimaAppCompatActivity {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
- if(actionBar != null)
- {
+ if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
extractIntentFields(getIntent());
+ manualAddLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.SELECT_BARCODE_REQUEST, result.getResultCode(), result.getData()));
+ photoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_IMAGE_FILE, result.getResultCode(), result.getData()));
findViewById(R.id.add_from_image).setOnClickListener(this::addFromImage);
findViewById(R.id.add_manually).setOnClickListener(this::addManually);
@@ -127,8 +134,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
}
@Override
- public boolean onCreateOptionsMenu(Menu menu)
- {
+ public boolean onCreateOptionsMenu(Menu menu) {
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
getMenuInflater().inflate(R.menu.scan_menu, menu);
}
@@ -139,10 +145,8 @@ public class ScanActivity extends CatimaAppCompatActivity {
}
@Override
- public boolean onOptionsItemSelected(MenuItem item)
- {
- if (item.getItemId() == android.R.id.home)
- {
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
setResult(Activity.RESULT_CANCELED);
finish();
return true;
@@ -163,9 +167,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
return super.onOptionsItemSelected(item);
}
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent intent)
- {
+ private void handleActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
BarcodeValues barcodeValues;
@@ -198,12 +200,12 @@ public class ScanActivity extends CatimaAppCompatActivity {
b.putString("initialCardId", cardId);
i.putExtras(b);
}
- startActivityForResult(i, Utils.SELECT_BARCODE_REQUEST);
+ manualAddLauncher.launch(i);
}
public void addFromImage(View view) {
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType("image/*");
- startActivityForResult(photoPickerIntent, Utils.BARCODE_IMPORT_FROM_IMAGE_FILE);
+ photoPickerLauncher.launch(photoPickerIntent);
}
}
diff --git a/app/src/main/java/protect/card_locker/ShortcutHelper.java b/app/src/main/java/protect/card_locker/ShortcutHelper.java
index 29bbff461..426983cf1 100644
--- a/app/src/main/java/protect/card_locker/ShortcutHelper.java
+++ b/app/src/main/java/protect/card_locker/ShortcutHelper.java
@@ -3,8 +3,12 @@ package protect.card_locker;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
import android.os.Bundle;
+import org.jetbrains.annotations.NotNull;
+
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
@@ -12,10 +16,10 @@ import java.util.List;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
+import androidx.core.graphics.ColorUtils;
import androidx.core.graphics.drawable.IconCompat;
-class ShortcutHelper
-{
+class ShortcutHelper {
// Android documentation says that no more than 5 shortcuts
// are supported. However, that may be too many, as not all
// launcher will show all 5. Instead, the number is limited
@@ -23,6 +27,14 @@ class ShortcutHelper
// chance of being shown.
private static final int MAX_SHORTCUTS = 3;
+ // https://developer.android.com/reference/android/graphics/drawable/AdaptiveIconDrawable.html
+ private static final int ADAPTIVE_BITMAP_SCALE = 1;
+ private static final int ADAPTIVE_BITMAP_SIZE = 108 * ADAPTIVE_BITMAP_SCALE;
+ private static final int ADAPTIVE_BITMAP_VISIBLE_SIZE = 72 * ADAPTIVE_BITMAP_SCALE;
+ private static final int ADAPTIVE_BITMAP_IMAGE_SIZE = ADAPTIVE_BITMAP_VISIBLE_SIZE + 5 * ADAPTIVE_BITMAP_SCALE;
+ private static final int PADDING_COLOR = Color.argb(255, 255, 255, 255);
+ private static final int PADDING_COLOR_OVERLAY = Color.argb(127, 0, 0, 0);
+
/**
* Add a card to the app shortcuts, and maintain a list of the most
* recently used cards. If there is already a shortcut for the card,
@@ -30,8 +42,7 @@ class ShortcutHelper
* card exceeds the max number of shortcuts, then the least recently
* used card shortcut is discarded.
*/
- static void updateShortcuts(Context context, LoyaltyCard card)
- {
+ static void updateShortcuts(Context context, LoyaltyCard card) {
LinkedList list = new LinkedList<>(ShortcutManagerCompat.getDynamicShortcuts(context));
DBHelper dbHelper = new DBHelper(context);
@@ -44,31 +55,25 @@ class ShortcutHelper
Integer foundIndex = null;
- for(int index = 0; index < list.size(); index++)
- {
- if(list.get(index).getId().equals(shortcutId))
- {
+ for (int index = 0; index < list.size(); index++) {
+ if (list.get(index).getId().equals(shortcutId)) {
// Found the item already
foundIndex = index;
break;
}
}
- if(foundIndex != null)
- {
+ if (foundIndex != null) {
// If the item is already found, then the list needs to be
// reordered, so that the selected item now has the lowest
// rank, thus letting it survive longer.
ShortcutInfoCompat found = list.remove(foundIndex.intValue());
list.addFirst(found);
- }
- else
- {
+ } else {
// The item is new to the list. First, we need to trim the list
// until it is able to accept a new item, then the item is
// inserted.
- while(list.size() >= MAX_SHORTCUTS)
- {
+ while (list.size() >= MAX_SHORTCUTS) {
list.pollLast();
}
@@ -80,15 +85,14 @@ class ShortcutHelper
LinkedList finalList = new LinkedList<>();
// The ranks are now updated; the order in the list is the rank.
- for(int index = 0; index < list.size(); index++)
- {
+ for (int index = 0; index < list.size(); index++) {
ShortcutInfoCompat prevShortcut = list.get(index);
LoyaltyCard loyaltyCard = dbHelper.getLoyaltyCard(Integer.parseInt(prevShortcut.getId()));
ShortcutInfoCompat updatedShortcut = createShortcutBuilder(context, loyaltyCard)
- .setRank(index)
- .build();
+ .setRank(index)
+ .build();
finalList.addLast(updatedShortcut);
}
@@ -100,16 +104,13 @@ class ShortcutHelper
* Remove the given card id from the app shortcuts, if such a
* shortcut exists.
*/
- static void removeShortcut(Context context, int cardId)
- {
+ static void removeShortcut(Context context, int cardId) {
List list = ShortcutManagerCompat.getDynamicShortcuts(context);
String shortcutId = Integer.toString(cardId);
- for(int index = 0; index < list.size(); index++)
- {
- if(list.get(index).getId().equals(shortcutId))
- {
+ for (int index = 0; index < list.size(); index++) {
+ if (list.get(index).getId().equals(shortcutId)) {
list.remove(index);
break;
}
@@ -118,6 +119,16 @@ class ShortcutHelper
ShortcutManagerCompat.setDynamicShortcuts(context, list);
}
+ static @NotNull
+ Bitmap createAdaptiveBitmap(@NotNull Bitmap in, int paddingColor) {
+ Bitmap ret = Bitmap.createBitmap(ADAPTIVE_BITMAP_SIZE, ADAPTIVE_BITMAP_SIZE, Bitmap.Config.ARGB_8888);
+ Canvas output = new Canvas(ret);
+ output.drawColor(ColorUtils.compositeColors(PADDING_COLOR_OVERLAY, paddingColor));
+ Bitmap resized = Utils.resizeBitmap(in, ADAPTIVE_BITMAP_IMAGE_SIZE);
+ output.drawBitmap(resized, (ADAPTIVE_BITMAP_SIZE - resized.getWidth()) / 2f, (ADAPTIVE_BITMAP_SIZE - resized.getHeight()) / 2f, null);
+ return ret;
+ }
+
static ShortcutInfoCompat.Builder createShortcutBuilder(Context context, LoyaltyCard loyaltyCard) {
Intent intent = new Intent(context, LoyaltyCardViewActivity.class);
intent.setAction(Intent.ACTION_MAIN);
@@ -129,7 +140,12 @@ class ShortcutHelper
bundle.putBoolean("view", true);
intent.putExtras(bundle);
- Bitmap iconBitmap = Utils.generateIcon(context, loyaltyCard, true).getLetterTile();
+ Bitmap iconBitmap = Utils.retrieveCardImage(context, loyaltyCard.id, ImageLocationType.icon);
+ if (iconBitmap == null) {
+ iconBitmap = Utils.generateIcon(context, loyaltyCard, true).getLetterTile();
+ } else {
+ iconBitmap = createAdaptiveBitmap(iconBitmap, loyaltyCard.headerColor == null ? PADDING_COLOR : loyaltyCard.headerColor);
+ }
IconCompat icon = IconCompat.createWithAdaptiveBitmap(iconBitmap);
diff --git a/app/src/main/java/protect/card_locker/SimpleTextWatcher.java b/app/src/main/java/protect/card_locker/SimpleTextWatcher.java
index d6483b4a8..05c9762e3 100644
--- a/app/src/main/java/protect/card_locker/SimpleTextWatcher.java
+++ b/app/src/main/java/protect/card_locker/SimpleTextWatcher.java
@@ -5,11 +5,14 @@ import android.text.TextWatcher;
public class SimpleTextWatcher implements TextWatcher {
@Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
@Override
- public void onTextChanged(CharSequence s, int start, int before, int count) { }
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
@Override
- public void afterTextChanged(Editable s) { }
+ public void afterTextChanged(Editable s) {
+ }
}
diff --git a/app/src/main/java/protect/card_locker/Utils.java b/app/src/main/java/protect/card_locker/Utils.java
index ff1bad0d7..34e697bd4 100644
--- a/app/src/main/java/protect/card_locker/Utils.java
+++ b/app/src/main/java/protect/card_locker/Utils.java
@@ -25,6 +25,7 @@ import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -38,6 +39,7 @@ import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Map;
+import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.graphics.ColorUtils;
import androidx.exifinterface.media.ExifInterface;
import protect.card_locker.preferences.Settings;
@@ -52,12 +54,15 @@ public class Utils {
public static final int BARCODE_IMPORT_FROM_IMAGE_FILE = 4;
public static final int CARD_IMAGE_FROM_CAMERA_FRONT = 5;
public static final int CARD_IMAGE_FROM_CAMERA_BACK = 6;
- public static final int CARD_IMAGE_FROM_FILE_FRONT = 7;
- public static final int CARD_IMAGE_FROM_FILE_BACK = 8;
+ public static final int CARD_IMAGE_FROM_CAMERA_ICON = 7;
+ public static final int CARD_IMAGE_FROM_FILE_FRONT = 8;
+ public static final int CARD_IMAGE_FROM_FILE_BACK = 9;
+ public static final int CARD_IMAGE_FROM_FILE_ICON = 10;
static final double LUMINANCE_MIDPOINT = 0.5;
- static final int BITMAP_SIZE_BIG = 512;
+ static final int BITMAP_SIZE_SMALL = 512;
+ static final int BITMAP_SIZE_BIG = 2048;
static public LetterBitmap generateIcon(Context context, LoyaltyCard loyaltyCard, boolean forShortcut) {
return generateIcon(context, loyaltyCard.store, loyaltyCard.headerColor, forShortcut);
@@ -264,13 +269,11 @@ public class Utils {
return bos.toByteArray();
}
- static public Bitmap resizeBitmap(Bitmap bitmap) {
+ static public Bitmap resizeBitmap(Bitmap bitmap, double maxSize) {
if (bitmap == null) {
return null;
}
- double maxSize = BITMAP_SIZE_BIG;
-
double width = bitmap.getWidth();
double height = bitmap.getHeight();
@@ -313,16 +316,20 @@ public class Utils {
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
- static public String getCardImageFileName(int loyaltyCardId, boolean front) {
+ static public String getCardImageFileName(int loyaltyCardId, ImageLocationType type) {
StringBuilder cardImageFileNameBuilder = new StringBuilder();
cardImageFileNameBuilder.append("card_");
cardImageFileNameBuilder.append(loyaltyCardId);
cardImageFileNameBuilder.append("_");
- if (front) {
+ if (type == ImageLocationType.front) {
cardImageFileNameBuilder.append("front");
- } else {
+ } else if (type == ImageLocationType.back) {
cardImageFileNameBuilder.append("back");
+ } else if (type == ImageLocationType.icon) {
+ cardImageFileNameBuilder.append("icon");
+ } else {
+ throw new IllegalArgumentException("Unknown image type");
}
cardImageFileNameBuilder.append(".png");
@@ -340,8 +347,8 @@ public class Utils {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
}
- static public void saveCardImage(Context context, Bitmap bitmap, int loyaltyCardId, boolean front) throws FileNotFoundException {
- saveCardImage(context, bitmap, getCardImageFileName(loyaltyCardId, front));
+ static public void saveCardImage(Context context, Bitmap bitmap, int loyaltyCardId, ImageLocationType type) throws FileNotFoundException {
+ saveCardImage(context, bitmap, getCardImageFileName(loyaltyCardId, type));
}
static public Bitmap retrieveCardImage(Context context, String fileName) {
@@ -355,11 +362,11 @@ public class Utils {
return BitmapFactory.decodeStream(in);
}
- static public Bitmap retrieveCardImage(Context context, int loyaltyCardId, boolean front) {
- return retrieveCardImage(context, getCardImageFileName(loyaltyCardId, front));
+ static public Bitmap retrieveCardImage(Context context, int loyaltyCardId, ImageLocationType type) {
+ return retrieveCardImage(context, getCardImageFileName(loyaltyCardId, type));
}
- static public U mapGetOrDefault(Map map, T key, U defaultValue) {
+ static public U mapGetOrDefault(Map map, T key, U defaultValue) {
U value = map.get(key);
if (value == null) {
return defaultValue;
@@ -401,4 +408,44 @@ public class Utils {
static public long getUnixTime() {
return System.currentTimeMillis() / 1000;
}
+
+ static public boolean isDarkModeEnabled(Context inputContext) {
+ int nightModeSetting = new Settings(inputContext).getTheme();
+ if (nightModeSetting == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) {
+ Configuration config = inputContext.getResources().getConfiguration();
+ int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ return (currentNightMode == Configuration.UI_MODE_NIGHT_YES);
+ } else {
+ return nightModeSetting == AppCompatDelegate.MODE_NIGHT_YES;
+ }
+ }
+
+ public static File createTempFile(Context context, String name) {
+ return new File(context.getCacheDir() + "/" + name);
+ }
+
+ public static String saveTempImage(Context context, Bitmap in, String name, Bitmap.CompressFormat format) {
+ File image = createTempFile(context, name);
+ try (FileOutputStream out = new FileOutputStream(image)) {
+ in.compress(format, 100, out);
+ return image.getAbsolutePath();
+ } catch (IOException e) {
+ Log.d("store temp image", "failed writing temp file for temporary image, name: " + name);
+ return null;
+ }
+ }
+
+ public static Bitmap loadImage(String path) {
+ try {
+ return BitmapFactory.decodeStream(new FileInputStream(path));
+ } catch (IOException e) {
+ Log.d("load image", "failed loading image from " + path);
+ return null;
+ }
+ }
+
+ public static Bitmap loadTempImage(Context context, String name) {
+ return loadImage(context.getCacheDir() + "/" + name);
+ }
+
}
diff --git a/app/src/main/java/protect/card_locker/async/CompatCallable.java b/app/src/main/java/protect/card_locker/async/CompatCallable.java
index e58d15c2c..352e0aeac 100644
--- a/app/src/main/java/protect/card_locker/async/CompatCallable.java
+++ b/app/src/main/java/protect/card_locker/async/CompatCallable.java
@@ -4,5 +4,6 @@ import java.util.concurrent.Callable;
public interface CompatCallable extends Callable {
void onPostExecute(Object result);
+
void onPreExecute();
}
diff --git a/app/src/main/java/protect/card_locker/async/TaskHandler.java b/app/src/main/java/protect/card_locker/async/TaskHandler.java
index c25b13e33..c417704be 100644
--- a/app/src/main/java/protect/card_locker/async/TaskHandler.java
+++ b/app/src/main/java/protect/card_locker/async/TaskHandler.java
@@ -13,11 +13,11 @@ import java.util.concurrent.TimeUnit;
/**
* AsyncTask has been deprecated so this provides very rudimentary compatibility without
* needing to redo too many Parts.
- *
+ *
* However this is a much, much more cooperative Behaviour than before so
* the callers need to ensure we do NOT rely on forced cancellation and feed less into the
* ThreadPools so we don't OOM/Overload the Users device
- *
+ *
* This assumes single-threaded callers.
*/
public class TaskHandler {
@@ -44,9 +44,10 @@ public class TaskHandler {
/**
* Replaces (or initializes) an Executor with a clean (new) one
+ *
* @param executors Map Reference
- * @param type Which Queue
- * @param flushOld attempt shutdown
+ * @param type Which Queue
+ * @param flushOld attempt shutdown
* @param waitOnOld wait for Termination
*/
private void replaceExecutor(HashMap executors, TYPE type, Boolean flushOld, Boolean waitOnOld) {
diff --git a/app/src/main/java/protect/card_locker/importexport/CSVHelpers.java b/app/src/main/java/protect/card_locker/importexport/CSVHelpers.java
index 74dd8f94e..d21b0f11a 100644
--- a/app/src/main/java/protect/card_locker/importexport/CSVHelpers.java
+++ b/app/src/main/java/protect/card_locker/importexport/CSVHelpers.java
@@ -12,18 +12,13 @@ public class CSVHelpers {
* if it is not null. Otherwise, a FormatException is thrown.
*/
static String extractString(String key, CSVRecord record, String defaultValue)
- throws FormatException
- {
+ throws FormatException {
String toReturn = defaultValue;
- if(record.isMapped(key))
- {
+ if (record.isMapped(key)) {
toReturn = record.get(key);
- }
- else
- {
- if(defaultValue == null)
- {
+ } else {
+ if (defaultValue == null) {
throw new FormatException("Field not used but expected: " + key);
}
}
@@ -38,25 +33,19 @@ public class CSVHelpers {
* int, a FormatException is thrown.
*/
static Integer extractInt(String key, CSVRecord record, boolean nullIsOk)
- throws FormatException
- {
- if(record.isMapped(key) == false)
- {
+ throws FormatException {
+ if (record.isMapped(key) == false) {
throw new FormatException("Field not used but expected: " + key);
}
String value = record.get(key);
- if(value.isEmpty() && nullIsOk)
- {
+ if (value.isEmpty() && nullIsOk) {
return null;
}
- try
- {
+ try {
return Integer.parseInt(record.get(key));
- }
- catch(NumberFormatException e)
- {
+ } catch (NumberFormatException e) {
throw new FormatException("Failed to parse field: " + key, e);
}
}
@@ -68,25 +57,19 @@ public class CSVHelpers {
* int, a FormatException is thrown.
*/
static Long extractLong(String key, CSVRecord record, boolean nullIsOk)
- throws FormatException
- {
- if(record.isMapped(key) == false)
- {
+ throws FormatException {
+ if (record.isMapped(key) == false) {
throw new FormatException("Field not used but expected: " + key);
}
String value = record.get(key);
- if(value.isEmpty() && nullIsOk)
- {
+ if (value.isEmpty() && nullIsOk) {
return null;
}
- try
- {
+ try {
return Long.parseLong(record.get(key));
- }
- catch(NumberFormatException e)
- {
+ } catch (NumberFormatException e) {
throw new FormatException("Failed to parse field: " + key, e);
}
}
diff --git a/app/src/main/java/protect/card_locker/importexport/CatimaExporter.java b/app/src/main/java/protect/card_locker/importexport/CatimaExporter.java
index 119443bba..b55337ad1 100644
--- a/app/src/main/java/protect/card_locker/importexport/CatimaExporter.java
+++ b/app/src/main/java/protect/card_locker/importexport/CatimaExporter.java
@@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets;
import protect.card_locker.DBHelper;
import protect.card_locker.Group;
+import protect.card_locker.ImageLocationType;
import protect.card_locker.LoyaltyCard;
import protect.card_locker.Utils;
@@ -29,10 +30,8 @@ import protect.card_locker.Utils;
* Class for exporting the database into CSV (Comma Separate Values)
* format.
*/
-public class CatimaExporter implements Exporter
-{
- public void exportData(Context context, DBHelper db, OutputStream output,char[] password) throws IOException, InterruptedException
- {
+public class CatimaExporter implements Exporter {
+ public void exportData(Context context, DBHelper db, OutputStream output, char[] password) throws IOException, InterruptedException {
// Necessary vars
int readLen;
byte[] readBuffer = new byte[InternalZipConstants.BUFF_SIZE];
@@ -40,10 +39,9 @@ public class CatimaExporter implements Exporter
// Create zip output stream
ZipOutputStream zipOutputStream;
- if(password!=null && password.length>0){
- zipOutputStream = new ZipOutputStream(output,password);
- }
- else{
+ if (password != null && password.length > 0) {
+ zipOutputStream = new ZipOutputStream(output, password);
+ } else {
zipOutputStream = new ZipOutputStream(output);
}
@@ -53,7 +51,7 @@ public class CatimaExporter implements Exporter
writeCSV(db, catimaOutputStreamWriter);
// Add CSV to zip file
- ZipParameters csvZipParameters = createZipParameters("catima.csv",password);
+ ZipParameters csvZipParameters = createZipParameters("catima.csv", password);
zipOutputStream.putNextEntry(csvZipParameters);
InputStream csvInputStream = new ByteArrayInputStream(catimaOutputStream.toByteArray());
while ((readLen = csvInputStream.read(readBuffer)) != -1) {
@@ -63,22 +61,16 @@ public class CatimaExporter implements Exporter
// Loop over all cards again
Cursor cardCursor = db.getLoyaltyCardCursor();
- while(cardCursor.moveToNext())
- {
+ while (cardCursor.moveToNext()) {
// For each card
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
- // Prepare looping over both front and back image
- boolean[] frontValues = new boolean[2];
- frontValues[0] = true;
- frontValues[1] = false;
-
// For each image
- for (boolean front : frontValues) {
+ for (ImageLocationType imageLocationType : ImageLocationType.values()) {
// If it exists, add to the .zip file
- Bitmap image = Utils.retrieveCardImage(context, card.id, front);
+ Bitmap image = Utils.retrieveCardImage(context, card.id, imageLocationType);
if (image != null) {
- ZipParameters imageZipParameters = createZipParameters(Utils.getCardImageFileName(card.id, front),password);
+ ZipParameters imageZipParameters = createZipParameters(Utils.getCardImageFileName(card.id, imageLocationType), password);
zipOutputStream.putNextEntry(imageZipParameters);
InputStream imageInputStream = new ByteArrayInputStream(Utils.bitmapToByteArray(image));
while ((readLen = imageInputStream.read(readBuffer)) != -1) {
@@ -92,10 +84,10 @@ public class CatimaExporter implements Exporter
zipOutputStream.close();
}
- private ZipParameters createZipParameters(String fileName, char[] password){
+ private ZipParameters createZipParameters(String fileName, char[] password) {
ZipParameters zipParameters = new ZipParameters();
zipParameters.setFileNameInZip(fileName);
- if(password!=null && password.length>0){
+ if (password != null && password.length > 0) {
zipParameters.setEncryptFiles(true);
zipParameters.setEncryptionMethod(EncryptionMethod.AES);
}
@@ -115,14 +107,12 @@ public class CatimaExporter implements Exporter
Cursor groupCursor = db.getGroupCursor();
- while(groupCursor.moveToNext())
- {
+ while (groupCursor.moveToNext()) {
Group group = Group.toGroup(groupCursor);
printer.printRecord(group._id);
- if(Thread.currentThread().isInterrupted())
- {
+ if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
}
@@ -148,8 +138,7 @@ public class CatimaExporter implements Exporter
Cursor cardCursor = db.getLoyaltyCardCursor();
- while(cardCursor.moveToNext())
- {
+ while (cardCursor.moveToNext()) {
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
printer.printRecord(card.id,
@@ -165,8 +154,7 @@ public class CatimaExporter implements Exporter
card.starStatus,
card.lastUsed);
- if(Thread.currentThread().isInterrupted())
- {
+ if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
}
@@ -182,16 +170,14 @@ public class CatimaExporter implements Exporter
Cursor cardCursor2 = db.getLoyaltyCardCursor();
- while(cardCursor2.moveToNext())
- {
+ while (cardCursor2.moveToNext()) {
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor2);
for (Group group : db.getLoyaltyCardGroups(card.id)) {
printer.printRecord(card.id, group._id);
}
- if(Thread.currentThread().isInterrupted())
- {
+ if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
}
diff --git a/app/src/main/java/protect/card_locker/importexport/CatimaImporter.java b/app/src/main/java/protect/card_locker/importexport/CatimaImporter.java
index 2ea858e1f..6b4fc05a5 100644
--- a/app/src/main/java/protect/card_locker/importexport/CatimaImporter.java
+++ b/app/src/main/java/protect/card_locker/importexport/CatimaImporter.java
@@ -4,8 +4,6 @@ import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
-import com.google.zxing.BarcodeFormat;
-
import net.lingala.zip4j.io.inputstream.ZipInputStream;
import net.lingala.zip4j.model.LocalFileHeader;
@@ -37,18 +35,17 @@ import protect.card_locker.ZipUtils;
/**
* Class for importing a database from CSV (Comma Separate Values)
* formatted data.
- *
+ *
* The database's loyalty cards are expected to appear in the CSV data.
* A header is expected for the each table showing the names of the columns.
*/
-public class CatimaImporter implements Importer
-{
+public class CatimaImporter implements Importer {
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, InterruptedException {
InputStream bufferedInputStream = new BufferedInputStream(input);
bufferedInputStream.mark(100);
// First, check if this is a zip file
- ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream,password);
+ ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream, password);
boolean isZipFile = false;
@@ -102,41 +99,32 @@ public class CatimaImporter implements Importer
bufferedReader.close();
}
- public void parseV1(Context context, DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
- {
+ public void parseV1(Context context, DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException {
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.builder().setHeader().build());
SQLiteDatabase database = db.getWritableDatabase();
database.beginTransaction();
- try
- {
- for (CSVRecord record : parser)
- {
+ try {
+ for (CSVRecord record : parser) {
importLoyaltyCard(context, database, db, record);
- if(Thread.currentThread().isInterrupted())
- {
+ if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
}
parser.close();
database.setTransactionSuccessful();
- }
- catch(IllegalArgumentException|IllegalStateException e)
- {
+ } catch (IllegalArgumentException | IllegalStateException e) {
throw new FormatException("Issue parsing CSV data", e);
- }
- finally
- {
+ } finally {
database.endTransaction();
database.close();
}
}
- public void parseV2(Context context, DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
- {
+ public void parseV2(Context context, DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException {
SQLiteDatabase database = db.getWritableDatabase();
database.beginTransaction();
@@ -206,8 +194,7 @@ public class CatimaImporter implements Importer
}
}
- public void parseV2Groups(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
- {
+ public void parseV2Groups(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException {
// Parse groups
final CSVParser groupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.builder().setHeader().build());
@@ -232,8 +219,7 @@ public class CatimaImporter implements Importer
}
}
- public void parseV2Cards(Context context, DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
- {
+ public void parseV2Cards(Context context, DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException {
// Parse cards
final CSVParser cardParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.builder().setHeader().build());
@@ -258,8 +244,7 @@ public class CatimaImporter implements Importer
}
}
- public void parseV2CardGroups(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
- {
+ public void parseV2CardGroups(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException {
// Parse card group mappings
final CSVParser cardGroupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.builder().setHeader().build());
@@ -289,13 +274,11 @@ public class CatimaImporter implements Importer
* session.
*/
private void importLoyaltyCard(Context context, SQLiteDatabase database, DBHelper helper, CSVRecord record)
- throws IOException, FormatException
- {
+ throws IOException, FormatException {
int id = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false);
String store = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.STORE, record, "");
- if(store.isEmpty())
- {
+ if (store.isEmpty()) {
throw new FormatException("No store listed, but is required");
}
@@ -303,12 +286,13 @@ public class CatimaImporter implements Importer
Date expiry = null;
try {
expiry = new Date(CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.EXPIRY, record, true));
- } catch (NullPointerException | FormatException e) { }
+ } catch (NullPointerException | FormatException e) {
+ }
BigDecimal balance;
try {
balance = new BigDecimal(CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE, record, null));
- } catch (FormatException _e ) {
+ } catch (FormatException _e) {
// These fields did not exist in versions 1.8.1 and before
// We catch this exception so we can still import old backups
balance = new BigDecimal("0");
@@ -316,33 +300,29 @@ public class CatimaImporter implements Importer
Currency balanceType = null;
String unparsedBalanceType = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE, record, "");
- if(!unparsedBalanceType.isEmpty()) {
+ if (!unparsedBalanceType.isEmpty()) {
balanceType = Currency.getInstance(unparsedBalanceType);
}
String cardId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.CARD_ID, record, "");
- if(cardId.isEmpty())
- {
+ if (cardId.isEmpty()) {
throw new FormatException("No card ID listed, but is required");
}
String barcodeId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BARCODE_ID, record, "");
- if(barcodeId.isEmpty())
- {
+ if (barcodeId.isEmpty()) {
barcodeId = null;
}
CatimaBarcode barcodeType = null;
String unparsedBarcodeType = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, record, "");
- if(!unparsedBarcodeType.isEmpty())
- {
+ if (!unparsedBarcodeType.isEmpty()) {
barcodeType = CatimaBarcode.fromName(unparsedBarcodeType);
}
Integer headerColor = null;
- if(record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR))
- {
+ if (record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR)) {
headerColor = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record, true);
}
@@ -371,8 +351,7 @@ public class CatimaImporter implements Importer
* session.
*/
private void importGroup(SQLiteDatabase database, DBHelper helper, CSVRecord record)
- throws IOException, FormatException
- {
+ throws IOException, FormatException {
String id = CSVHelpers.extractString(DBHelper.LoyaltyCardDbGroups.ID, record, null);
helper.insertGroup(database, id);
@@ -383,8 +362,7 @@ public class CatimaImporter implements Importer
* session.
*/
private void importCardGroupMapping(SQLiteDatabase database, DBHelper helper, CSVRecord record)
- throws IOException, FormatException
- {
+ throws IOException, FormatException {
Integer cardId = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIdsGroups.cardID, record, false);
String groupId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIdsGroups.groupID, record, null);
diff --git a/app/src/main/java/protect/card_locker/importexport/DataFormat.java b/app/src/main/java/protect/card_locker/importexport/DataFormat.java
index 26e97e745..fc4fdc531 100644
--- a/app/src/main/java/protect/card_locker/importexport/DataFormat.java
+++ b/app/src/main/java/protect/card_locker/importexport/DataFormat.java
@@ -1,10 +1,8 @@
package protect.card_locker.importexport;
-public enum DataFormat
-{
+public enum DataFormat {
Catima,
Fidme,
Stocard,
- VoucherVault
- ;
+ VoucherVault;
}
diff --git a/app/src/main/java/protect/card_locker/importexport/Exporter.java b/app/src/main/java/protect/card_locker/importexport/Exporter.java
index ad4928030..584572360 100644
--- a/app/src/main/java/protect/card_locker/importexport/Exporter.java
+++ b/app/src/main/java/protect/card_locker/importexport/Exporter.java
@@ -11,11 +11,11 @@ import protect.card_locker.DBHelper;
* Interface for a class which can export the contents of the database
* in a given format.
*/
-public interface Exporter
-{
+public interface Exporter {
/**
* Export the database to the output stream in a given format.
+ *
* @throws IOException
*/
- void exportData(Context context, DBHelper db, OutputStream output,char[] password) throws IOException, InterruptedException;
+ void exportData(Context context, DBHelper db, OutputStream output, char[] password) throws IOException, InterruptedException;
}
diff --git a/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java b/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java
index f5453db67..23c927b8b 100644
--- a/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java
+++ b/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java
@@ -3,8 +3,6 @@ package protect.card_locker.importexport;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
-import com.google.zxing.BarcodeFormat;
-
import net.lingala.zip4j.io.inputstream.ZipInputStream;
import net.lingala.zip4j.model.LocalFileHeader;
@@ -23,12 +21,11 @@ import java.text.ParseException;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
-import protect.card_locker.Utils;
/**
* Class for importing a database from CSV (Comma Separate Values)
* formatted data.
- *
+ *
* The database's loyalty cards are expected to appear in the CSV data.
* A header is expected for the each table showing the names of the columns.
*/
diff --git a/app/src/main/java/protect/card_locker/importexport/ImportExportResult.java b/app/src/main/java/protect/card_locker/importexport/ImportExportResult.java
index 9a8d71c08..8419d3ebe 100644
--- a/app/src/main/java/protect/card_locker/importexport/ImportExportResult.java
+++ b/app/src/main/java/protect/card_locker/importexport/ImportExportResult.java
@@ -1,9 +1,7 @@
package protect.card_locker.importexport;
-public enum ImportExportResult
-{
+public enum ImportExportResult {
Success,
GenericFailure,
- BadPassword
- ;
+ BadPassword;
}
diff --git a/app/src/main/java/protect/card_locker/importexport/Importer.java b/app/src/main/java/protect/card_locker/importexport/Importer.java
index fe47d05fe..a394e455b 100644
--- a/app/src/main/java/protect/card_locker/importexport/Importer.java
+++ b/app/src/main/java/protect/card_locker/importexport/Importer.java
@@ -15,11 +15,11 @@ import protect.card_locker.FormatException;
* Interface for a class which can import the contents of a stream
* into the database.
*/
-public interface Importer
-{
+public interface Importer {
/**
* Import data from the input stream in a given format into
* the database.
+ *
* @throws IOException
* @throws FormatException
*/
diff --git a/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java b/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java
index b45cf1eda..c4a3e2dd2 100644
--- a/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java
+++ b/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java
@@ -8,26 +8,23 @@ import java.io.OutputStream;
import protect.card_locker.DBHelper;
-public class MultiFormatExporter
-{
+public class MultiFormatExporter {
private static final String TAG = "Catima";
/**
* Attempts to export data to the output stream in the
* given format, if possible.
- *
+ *
* The output stream is closed on success.
*
* @return ImportExportResult.Success if the database was successfully exported,
* another ImportExportResult otherwise. If not Success, partial data may have been
* written to the output stream, and it should be discarded.
*/
- public static ImportExportResult exportData(Context context, DBHelper db, OutputStream output, DataFormat format,char[] password)
- {
+ public static ImportExportResult exportData(Context context, DBHelper db, OutputStream output, DataFormat format, char[] password) {
Exporter exporter = null;
- switch(format)
- {
+ switch (format) {
case Catima:
exporter = new CatimaExporter();
break;
@@ -36,26 +33,18 @@ public class MultiFormatExporter
break;
}
- if(exporter != null)
- {
- try
- {
- exporter.exportData(context, db, output,password);
+ if (exporter != null) {
+ try {
+ exporter.exportData(context, db, output, password);
return ImportExportResult.Success;
- }
- catch(IOException e)
- {
+ } catch (IOException e) {
Log.e(TAG, "Failed to export data", e);
- }
- catch(InterruptedException e)
- {
+ } catch (InterruptedException e) {
Log.e(TAG, "Failed to export data", e);
}
return ImportExportResult.GenericFailure;
- }
- else
- {
+ } else {
Log.e(TAG, "Unsupported data format exported: " + format.name());
return ImportExportResult.GenericFailure;
}
diff --git a/app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java b/app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java
index 3463f1073..21a520f41 100644
--- a/app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java
+++ b/app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java
@@ -14,14 +14,13 @@ import java.text.ParseException;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
-public class MultiFormatImporter
-{
+public class MultiFormatImporter {
private static final String TAG = "Catima";
/**
* Attempts to import data from the input stream of the
* given format into the database.
- *
+ *
* The input stream is not closed, and doing so is the
* responsibility of the caller.
*
@@ -29,12 +28,10 @@ public class MultiFormatImporter
* or another result otherwise. If no Success, no data was written to
* the database.
*/
- public static ImportExportResult importData(Context context, DBHelper db, InputStream input, DataFormat format, char[] password)
- {
+ public static ImportExportResult importData(Context context, DBHelper db, InputStream input, DataFormat format, char[] password) {
Importer importer = null;
- switch(format)
- {
+ switch (format) {
case Catima:
importer = new CatimaImporter();
break;
@@ -49,25 +46,17 @@ public class MultiFormatImporter
break;
}
- if (importer != null)
- {
- try
- {
+ if (importer != null) {
+ try {
importer.importData(context, db, input, password);
return ImportExportResult.Success;
- }
- catch(ZipException e)
- {
+ } catch (ZipException e) {
return ImportExportResult.BadPassword;
- }
- catch(IOException | FormatException | InterruptedException | JSONException | ParseException | NullPointerException e)
- {
+ } catch (IOException | FormatException | InterruptedException | JSONException | ParseException | NullPointerException e) {
Log.e(TAG, "Failed to import data", e);
}
- }
- else
- {
+ } else {
Log.e(TAG, "Unsupported data format imported: " + format.name());
}
diff --git a/app/src/main/java/protect/card_locker/importexport/StocardImporter.java b/app/src/main/java/protect/card_locker/importexport/StocardImporter.java
index 76761b83a..2f8280285 100644
--- a/app/src/main/java/protect/card_locker/importexport/StocardImporter.java
+++ b/app/src/main/java/protect/card_locker/importexport/StocardImporter.java
@@ -26,6 +26,7 @@ import java.util.HashMap;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
+import protect.card_locker.ImageLocationType;
import protect.card_locker.R;
import protect.card_locker.Utils;
import protect.card_locker.ZipUtils;
@@ -33,22 +34,19 @@ import protect.card_locker.ZipUtils;
/**
* Class for importing a database from CSV (Comma Separate Values)
* formatted data.
- *
+ *
* The database's loyalty cards are expected to appear in the CSV data.
* A header is expected for the each table showing the names of the columns.
*/
-public class StocardImporter implements Importer
-{
+public class StocardImporter implements Importer {
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
HashMap> loyaltyCardHashMap = new HashMap<>();
HashMap> providers = new HashMap<>();
final CSVParser parser = new CSVParser(new InputStreamReader(context.getResources().openRawResource(R.raw.stocard_stores), StandardCharsets.UTF_8), CSVFormat.RFC4180.builder().setHeader().build());
- try
- {
- for (CSVRecord record : parser)
- {
+ try {
+ for (CSVRecord record : parser) {
HashMap recordData = new HashMap<>();
recordData.put("name", record.get("name"));
recordData.put("barcodeFormat", record.get("barcodeFormat"));
@@ -57,7 +55,7 @@ public class StocardImporter implements Importer
}
parser.close();
- } catch(IllegalArgumentException|IllegalStateException e) {
+ } catch (IllegalArgumentException | IllegalStateException e) {
throw new FormatException("Issue parsing CSV data", e);
}
@@ -72,7 +70,7 @@ public class StocardImporter implements Importer
String[] nameParts = fileName.split("/");
if (providersFileName == null) {
- providersFileName = new String[] {
+ providersFileName = new String[]{
nameParts[0],
"sync",
"data",
@@ -80,7 +78,7 @@ public class StocardImporter implements Importer
nameParts[0],
"analytics-properties.json"
};
- cardBaseName = new String[] {
+ cardBaseName = new String[]{
nameParts[0],
"sync",
"data",
@@ -111,9 +109,9 @@ public class StocardImporter implements Importer
cardName,
"_providerId",
jsonObject
- .getJSONObject("input_provider_reference")
- .getString("identifier")
- .substring("/loyalty-card-providers/".length())
+ .getJSONObject("input_provider_reference")
+ .getString("identifier")
+ .substring("/loyalty-card-providers/".length())
);
if (jsonObject.has("input_barcode_format")) {
@@ -131,7 +129,7 @@ public class StocardImporter implements Importer
cardName,
"note",
ZipUtils.readJSON(zipInputStream)
- .getString("content")
+ .getString("content")
);
} else if (fileName.endsWith("/images/front.png")) {
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
@@ -178,10 +176,10 @@ public class StocardImporter implements Importer
long loyaltyCardInternalId = db.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, null, 0, null);
if (loyaltyCardData.containsKey("frontImage")) {
- Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("frontImage"), (int) loyaltyCardInternalId, true);
+ Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("frontImage"), (int) loyaltyCardInternalId, ImageLocationType.front);
}
if (loyaltyCardData.containsKey("backImage")) {
- Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("backImage"), (int) loyaltyCardInternalId, false);
+ Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("backImage"), (int) loyaltyCardInternalId, ImageLocationType.back);
}
}
diff --git a/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java b/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java
index ad38b1b62..0aa038e1b 100644
--- a/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java
+++ b/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java
@@ -31,12 +31,11 @@ import protect.card_locker.Utils;
/**
* Class for importing a database from CSV (Comma Separate Values)
* formatted data.
- *
+ *
* The database's loyalty cards are expected to appear in the CSV data.
* A header is expected for the each table showing the names of the columns.
*/
-public class VoucherVaultImporter implements Importer
-{
+public class VoucherVaultImporter implements Importer {
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
diff --git a/app/src/main/java/protect/card_locker/preferences/Settings.java b/app/src/main/java/protect/card_locker/preferences/Settings.java
index a28ac5f8a..977271ffc 100644
--- a/app/src/main/java/protect/card_locker/preferences/Settings.java
+++ b/app/src/main/java/protect/card_locker/preferences/Settings.java
@@ -12,44 +12,36 @@ import androidx.preference.PreferenceManager;
import protect.card_locker.R;
import protect.card_locker.Utils;
-public class Settings
-{
+public class Settings {
private Context context;
private SharedPreferences settings;
- public Settings(Context context)
- {
+ public Settings(Context context) {
this.context = context;
this.settings = PreferenceManager.getDefaultSharedPreferences(context);
}
- private String getResString(@StringRes int resId)
- {
+ private String getResString(@StringRes int resId) {
return context.getString(resId);
}
- private int getResInt(@IntegerRes int resId)
- {
+ private int getResInt(@IntegerRes int resId) {
return context.getResources().getInteger(resId);
}
- private String getString(@StringRes int keyId, String defaultValue)
- {
+ private String getString(@StringRes int keyId, String defaultValue) {
return settings.getString(getResString(keyId), defaultValue);
}
- private int getInt(@StringRes int keyId, @IntegerRes int defaultId)
- {
+ private int getInt(@StringRes int keyId, @IntegerRes int defaultId) {
return settings.getInt(getResString(keyId), getResInt(defaultId));
}
- private boolean getBoolean(@StringRes int keyId, boolean defaultValue)
- {
+ private boolean getBoolean(@StringRes int keyId, boolean defaultValue) {
return settings.getBoolean(getResString(keyId), defaultValue);
}
- public Locale getLocale()
- {
+ public Locale getLocale() {
String value = getString(R.string.settings_key_locale, "");
if (value.length() == 0) {
@@ -59,69 +51,55 @@ public class Settings
return Utils.stringToLocale(value);
}
- public int getTheme()
- {
+ public int getTheme() {
String value = getString(R.string.settings_key_theme, getResString(R.string.settings_key_system_theme));
- if(value.equals(getResString(R.string.settings_key_light_theme)))
- {
+ if (value.equals(getResString(R.string.settings_key_light_theme))) {
return AppCompatDelegate.MODE_NIGHT_NO;
- }
- else if(value.equals(getResString(R.string.settings_key_dark_theme)))
- {
+ } else if (value.equals(getResString(R.string.settings_key_dark_theme))) {
return AppCompatDelegate.MODE_NIGHT_YES;
}
return AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
}
- public double getFontSizeScale()
- {
+ public double getFontSizeScale() {
return getInt(R.string.settings_key_max_font_size_scale, R.integer.settings_max_font_size_scale_pct) / 100.0;
}
- public int getSmallFont()
- {
+ public int getSmallFont() {
return 14;
}
- public int getMediumFont()
- {
+ public int getMediumFont() {
return 28;
}
- public int getLargeFont()
- {
+ public int getLargeFont() {
return 40;
}
- public int getFontSizeMin(int fontSize)
- {
+ public int getFontSizeMin(int fontSize) {
return (int) (Math.round(fontSize / 2.0) - 1);
}
- public int getFontSizeMax(int fontSize)
- {
+ public int getFontSizeMax(int fontSize) {
return (int) Math.round(fontSize * getFontSizeScale());
}
- public boolean useMaxBrightnessDisplayingBarcode()
- {
+ public boolean useMaxBrightnessDisplayingBarcode() {
return getBoolean(R.string.settings_key_display_barcode_max_brightness, true);
}
- public boolean getLockBarcodeScreenOrientation()
- {
+ public boolean getLockBarcodeScreenOrientation() {
return getBoolean(R.string.settings_key_lock_barcode_orientation, false);
}
- public boolean getKeepScreenOn()
- {
+ public boolean getKeepScreenOn() {
return getBoolean(R.string.settings_key_keep_screen_on, true);
}
- public boolean getDisableLockscreenWhileViewingCard()
- {
+ public boolean getDisableLockscreenWhileViewingCard() {
return getBoolean(R.string.settings_key_disable_lockscreen_while_viewing_card, true);
}
}
diff --git a/app/src/main/java/protect/card_locker/preferences/SettingsActivity.java b/app/src/main/java/protect/card_locker/preferences/SettingsActivity.java
index 3e88fb87e..7bd73d668 100644
--- a/app/src/main/java/protect/card_locker/preferences/SettingsActivity.java
+++ b/app/src/main/java/protect/card_locker/preferences/SettingsActivity.java
@@ -24,19 +24,16 @@ import protect.card_locker.CatimaAppCompatActivity;
import protect.card_locker.R;
import protect.card_locker.Utils;
-public class SettingsActivity extends CatimaAppCompatActivity
-{
+public class SettingsActivity extends CatimaAppCompatActivity {
@Override
- protected void onCreate(Bundle savedInstanceState)
- {
+ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.settings);
setContentView(R.layout.settings_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
- if(actionBar != null)
- {
+ if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
@@ -48,12 +45,10 @@ public class SettingsActivity extends CatimaAppCompatActivity
}
@Override
- public boolean onOptionsItemSelected(MenuItem item)
- {
+ public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
- if(id == android.R.id.home)
- {
+ if (id == android.R.id.home) {
finish();
return true;
}
@@ -61,8 +56,7 @@ public class SettingsActivity extends CatimaAppCompatActivity
return super.onOptionsItemSelected(item);
}
- public static class SettingsFragment extends PreferenceFragmentCompat
- {
+ public static class SettingsFragment extends PreferenceFragmentCompat {
private static final String DIALOG_FRAGMENT_TAG = "SettingsFragment";
@Override
diff --git a/app/src/main/res/drawable/ic_baseline_unfold_less_24.xml b/app/src/main/res/drawable/ic_baseline_unfold_less_24.xml
new file mode 100644
index 000000000..621f087b7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_unfold_less_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_unfold_more_24.xml b/app/src/main/res/drawable/ic_baseline_unfold_more_24.xml
new file mode 100644
index 000000000..249979c04
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_unfold_more_24.xml
@@ -0,0 +1,5 @@
+
+
+
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/layout/barcode_layout.xml b/app/src/main/res/layout/barcode_layout.xml
new file mode 100644
index 000000000..0e94e54ad
--- /dev/null
+++ b/app/src/main/res/layout/barcode_layout.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/barcode_selector_activity.xml b/app/src/main/res/layout/barcode_selector_activity.xml
index 83d280398..60dfb6be9 100644
--- a/app/src/main/res/layout/barcode_selector_activity.xml
+++ b/app/src/main/res/layout/barcode_selector_activity.xml
@@ -1,318 +1,88 @@
-
+
-
-
-
+ android:orientation="vertical">
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:hint="AB1234"
+ android:importantForAutofill="no"
+ android:inputType="text"
+ android:minHeight="48dp" />
-
+
+
+
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
index acd9d0895..68b139564 100644
--- a/app/src/main/res/layout/content_main.xml
+++ b/app/src/main/res/layout/content_main.xml
@@ -38,7 +38,7 @@
+ app:cardElevation="0dp"
+ app:cardBackgroundColor="@android:color/transparent"
+ android:outlineProvider="none">
+ android:singleLine="true" />
@@ -121,7 +123,7 @@
android:id="@+id/cardIdView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- />
+ android:singleLine="true" />
diff --git a/app/src/main/res/layout/loyalty_card_layout.xml b/app/src/main/res/layout/loyalty_card_layout.xml
index 261b5d516..950ea814e 100644
--- a/app/src/main/res/layout/loyalty_card_layout.xml
+++ b/app/src/main/res/layout/loyalty_card_layout.xml
@@ -1,154 +1,147 @@
-
-
+ android:layout_marginBottom="4dp">
-
+
+
+
+
+
+
+
+
+
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="8dp"
+ android:layout_marginBottom="4dp"
+ android:textAppearance="?attr/textAppearanceHeadline1"
+ app:layout_constraintTop_toBottomOf="@+id/icon_layout"
+ app:layout_constraintBottom_toTopOf="@+id/note"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ tools:text="Example store"/>
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/loyalty_card_view_layout.xml b/app/src/main/res/layout/loyalty_card_view_layout.xml
index 936a9b750..5f8e06754 100644
--- a/app/src/main/res/layout/loyalty_card_view_layout.xml
+++ b/app/src/main/res/layout/loyalty_card_view_layout.xml
@@ -155,8 +155,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/inputBackground"
- android:fitsSystemWindows="false"
android:orientation="vertical"
+ android:paddingTop="0px"
android:visibility="gone"
app:behavior_hideable="false"
app:behavior_peekHeight="80dp"
@@ -168,10 +168,10 @@
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_gravity="top|start"
- android:scaleType="fitCenter"
android:contentDescription="@string/toggleMoreInfo"
- app:tint="#ffffff"
- app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24" />
+ android:scaleType="fitCenter"
+ app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24"
+ app:tint="#ffffff" />
+
+ >
+
+
+
+ app:expandedTitleGravity="top"
+ app:expandedTitleMarginEnd="64dp"
+ app:expandedTitleMarginStart="48dp">
+
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textAlignment="center"
+ android:textColor="@android:color/white"
+ android:textSize="40sp"
+ app:layout_collapseMode="parallax" />
+
+
-
+
+
+
+
diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml
index 1706c1a53..d7efa11ba 100644
--- a/app/src/main/res/menu/main_menu.xml
+++ b/app/src/main/res/menu/main_menu.xml
@@ -8,6 +8,11 @@
android:icon="@drawable/ic_search_white"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always|collapseActionView"/>
+ Докоснете бутона +, за да добавите списък.