Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into feature/power_screen_widgets

This commit is contained in:
Sylvia van Os
2021-10-26 21:42:14 +02:00
47 changed files with 1461 additions and 717 deletions

View File

@@ -24,5 +24,10 @@ with open('CHANGELOG.md') as changelog:
text.append(re.sub(r'\[(.*?)\]\((.*?)\)', r'\1 (\2)', line))
for version, description in changelogs.items():
description = "".join(description).strip()
if not description:
continue
with open(os.path.join("fastlane", "metadata", "android", "en-US", "changelogs", f"{version}.txt"), "w") as fastlane_file:
fastlane_file.write("".join(description).strip())
fastlane_file.write(description)

View File

@@ -1,10 +1,14 @@
# Changelog
## Unreleased - 89
## Unreleased - 90
## v2.8.0 - 89 (2021-10-25)
- Fix swiping between groups not working on an empty group
- Allow password-protecting exports
- Improve usage of space for QR codes
- Save the last used zoom level per card
- Fix a crash when swiping right after a tap
## v2.7.3 - 88 (2021-10-10)

View File

@@ -18,8 +18,8 @@ android {
applicationId "me.hackerchick.catima"
minSdkVersion 21
targetSdkVersion 31
versionCode 88
versionName "2.7.3"
versionCode 89
versionName "2.8.0"
vectorDrawables.useSupportLibrary true
multiDexEnabled true
@@ -91,7 +91,7 @@ dependencies {
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
// Third-party
implementation 'com.journeyapps:zxing-android-embedded:4.2.0@aar'
implementation 'com.journeyapps:zxing-android-embedded:4.3.0@aar'
implementation 'com.google.zxing:core:3.4.1'
implementation 'org.apache.commons:commons-csv:1.9.0'
implementation 'com.jaredrummler:colorpicker:1.1.0'

View File

@@ -4,30 +4,29 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.os.AsyncTask;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import java.lang.ref.WeakReference;
import protect.card_locker.async.CompatCallable;
/**
* This task will generate a barcode and load it into an ImageView.
* Only a weak reference of the ImageView is kept, so this class will not
* prevent the ImageView from being garbage collected.
*/
class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
{
public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
private static final String TAG = "Catima";
private static final int IS_VALID = 999;
private Context mContext;
private final Context mContext;
private boolean isSuccesful;
// When drawn in a smaller window 1D barcodes for some reason end up
@@ -44,10 +43,11 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
private final boolean showFallback;
private final Runnable callback;
BarcodeImageWriterTask(Context context, ImageView imageView, String cardIdString,
CatimaBarcode barcodeFormat, TextView textView,
boolean showFallback, Runnable callback)
{
BarcodeImageWriterTask(
Context context, ImageView imageView, String cardIdString,
CatimaBarcode barcodeFormat, TextView textView,
boolean showFallback, Runnable callback
) {
mContext = context;
isSuccesful = true;
@@ -62,26 +62,21 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
final int MAX_WIDTH = getMaxWidth(format);
if(imageView.getWidth() < MAX_WIDTH)
{
if (imageView.getWidth() < MAX_WIDTH) {
imageHeight = imageView.getHeight();
imageWidth = imageView.getWidth();
}
else
{
} else {
// Scale down the image to reduce the memory needed to produce it
imageWidth = MAX_WIDTH;
double ratio = (double)MAX_WIDTH / (double)imageView.getWidth();
imageHeight = (int)(imageView.getHeight() * ratio);
double ratio = (double) MAX_WIDTH / (double) imageView.getWidth();
imageHeight = (int) (imageView.getHeight() * ratio);
}
this.showFallback = showFallback;
}
private int getMaxWidth(CatimaBarcode format)
{
switch(format.format())
{
private int getMaxWidth(CatimaBarcode format) {
switch (format.format()) {
// 2D barcodes
case AZTEC:
case DATA_MATRIX:
@@ -108,10 +103,8 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
}
}
private String getFallbackString(CatimaBarcode format)
{
switch(format.format())
{
private String getFallbackString(CatimaBarcode format) {
switch (format.format()) {
// 2D barcodes
case AZTEC:
return "AZTEC";
@@ -144,23 +137,17 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
}
}
private Bitmap generate()
{
if (cardId.isEmpty())
{
private Bitmap generate() {
if (cardId.isEmpty()) {
return null;
}
MultiFormatWriter writer = new MultiFormatWriter();
BitMatrix bitMatrix;
try
{
try
{
try {
try {
bitMatrix = writer.encode(cardId, format.format(), imageWidth, imageHeight, null);
}
catch(Exception e)
{
} catch (Exception e) {
// Cast a wider net here and catch any exception, as there are some
// cases where an encoder may fail if the data is invalid for the
// barcode type. If this happens, we want to fail gracefully.
@@ -175,11 +162,9 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
int[] pixels = new int[bitMatrixWidth * bitMatrixHeight];
for (int y = 0; y < bitMatrixHeight; y++)
{
for (int y = 0; y < bitMatrixHeight; y++) {
int offset = y * bitMatrixWidth;
for (int x = 0; x < bitMatrixWidth; x++)
{
for (int x = 0; x < bitMatrixWidth; x++) {
int color = bitMatrix.get(x, y) ? BLACK : WHITE;
pixels[offset + x] = color;
}
@@ -199,19 +184,14 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
int widthScale = imageWidth / bitMatrixHeight;
int scalingFactor = Math.min(heightScale, widthScale);
if(scalingFactor > 1)
{
if (scalingFactor > 1) {
bitmap = Bitmap.createScaledBitmap(bitmap, bitMatrixWidth * scalingFactor, bitMatrixHeight * scalingFactor, false);
}
return bitmap;
}
catch (WriterException e)
{
} catch (WriterException e) {
Log.e(TAG, "Failed to generate barcode of type " + format + ": " + cardId, e);
}
catch(OutOfMemoryError e)
{
} catch (OutOfMemoryError e) {
Log.w(TAG, "Insufficient memory to render barcode, "
+ imageWidth + "x" + imageHeight + ", " + format.name()
+ ", length=" + cardId.length(), e);
@@ -220,29 +200,36 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
return null;
}
public Bitmap doInBackground(Void... params)
{
Bitmap bitmap = generate();
public Bitmap doInBackground(Void... params) {
// Only do the hard tasks if we've not already been cancelled
if (!Thread.currentThread().isInterrupted()) {
Bitmap bitmap = generate();
if (bitmap == null) {
isSuccesful = false;
if (bitmap == null) {
isSuccesful = false;
if (showFallback) {
Log.i(TAG, "Barcode generation failed, generating fallback...");
cardId = getFallbackString(format);
bitmap = generate();
if (showFallback && !Thread.currentThread().isInterrupted()) {
Log.i(TAG, "Barcode generation failed, generating fallback...");
cardId = getFallbackString(format);
bitmap = generate();
return bitmap;
}
} else {
return bitmap;
}
}
return bitmap;
// We've been interrupted - create a empty fallback
Bitmap.Config config = Bitmap.Config.ARGB_8888;
return Bitmap.createBitmap(imageWidth, imageHeight, config);
}
protected void onPostExecute(Bitmap result)
{
public void onPostExecute(Object castResult) {
Bitmap result = (Bitmap) castResult;
Log.i(TAG, "Finished generating barcode image of type " + format + ": " + cardId);
ImageView imageView = imageViewReference.get();
if(imageView == null)
{
if (imageView == null) {
// The ImageView no longer exists, nothing to do
return;
}
@@ -255,8 +242,7 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
imageView.setContentDescription(mContext.getString(R.string.barcodeImageDescriptionWithType, formatPrettyName));
TextView textView = textViewReference.get();
if(result != null)
{
if (result != null) {
Log.i(TAG, "Displaying barcode");
imageView.setVisibility(View.VISIBLE);
@@ -270,9 +256,7 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
textView.setVisibility(View.VISIBLE);
textView.setText(formatPrettyName);
}
}
else
{
} else {
Log.i(TAG, "Barcode generation failed, removing image from display");
imageView.setVisibility(View.GONE);
if (textView != null) {
@@ -284,4 +268,19 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
callback.run();
}
}
@Override
public void onPreExecute() {
// No Action
}
/**
* Provided to comply with Callable while keeping the original Syntax of AsyncTask
*
* @return generated Bitmap
*/
@Override
public Bitmap call() {
return doInBackground();
}
}

View File

@@ -2,8 +2,9 @@ package protect.card_locker;
import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.util.Pair;
import android.view.MenuItem;
@@ -16,24 +17,21 @@ import android.widget.Toast;
import com.google.zxing.BarcodeFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
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 {
private static final String TAG = "Catima";
// Result this activity will return
@@ -41,19 +39,21 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity
public static final String BARCODE_FORMAT = "format";
private Map<String, Pair<Integer, Integer>> barcodeViewMap;
private LinkedList<AsyncTask> barcodeGeneratorTasks = new LinkedList<>();
final private TaskHandler mTasks = new TaskHandler();
private final Handler typingDelayHandler = new Handler(Looper.getMainLooper());
public static final Integer INPUT_DELAY = 250;
@Override
protected void onCreate(Bundle savedInstanceState)
{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.selectBarcodeTitle);
setContentView(R.layout.barcode_selector_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
@@ -72,26 +72,31 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity
barcodeViewMap.put(BarcodeFormat.UPC_E.name(), new Pair<>(R.id.upceBarcode, R.id.upceBarcodeText));
EditText cardId = findViewById(R.id.cardId);
cardId.addTextChangedListener(new SimpleTextWatcher()
{
cardId.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count)
{
Log.d(TAG, "Entered text: " + s);
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Delay the input processing so we avoid overload
typingDelayHandler.removeCallbacksAndMessages(null);
generateBarcodes(s.toString());
typingDelayHandler.postDelayed(() -> {
Log.d(TAG, "Entered text: " + s);
View noBarcodeButtonView = findViewById(R.id.noBarcode);
setButtonListener(noBarcodeButtonView, s.toString());
noBarcodeButtonView.setEnabled(s.length() > 0);
runOnUiThread(() -> {
generateBarcodes(s.toString());
View noBarcodeButtonView = findViewById(R.id.noBarcode);
setButtonListener(noBarcodeButtonView, s.toString());
noBarcodeButtonView.setEnabled(s.length() > 0);
});
}, INPUT_DELAY);
}
});
final Bundle b = getIntent().getExtras();
final String initialCardId = b != null ? b.getString("initialCardId") : null;
if(initialCardId != null)
{
if (initialCardId != null) {
cardId.setText(initialCardId);
} else {
generateBarcodes("");
@@ -99,24 +104,19 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity
}
private void generateBarcodes(String value) {
// Stop any async tasks which may not have been started yet
for(AsyncTask task : barcodeGeneratorTasks)
{
task.cancel(false);
}
barcodeGeneratorTasks.clear();
// 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<String, Pair<Integer, Integer>> entry : barcodeViewMap.entrySet())
{
for (Map.Entry<String, Pair<Integer, Integer>> entry : barcodeViewMap.entrySet()) {
ImageView image = findViewById(entry.getValue().first);
TextView text = findViewById(entry.getValue().second);
createBarcodeOption(image, entry.getKey(), value, text);
}
}
private void setButtonListener(final View button, final String cardId)
{
private void setButtonListener(final View button, final String cardId) {
button.setOnClickListener(view -> {
Log.d(TAG, "Selected no barcode");
Intent result = new Intent();
@@ -127,8 +127,7 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity
});
}
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId, final TextView text)
{
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId, final TextView text) {
final CatimaBarcode format = CatimaBarcode.fromName(formatType);
image.setImageBitmap(null);
@@ -147,40 +146,32 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity
finish();
});
if(image.getHeight() == 0)
{
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);
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 task = new BarcodeImageWriterTask(getApplicationContext(), image, cardId, format, text, true, null);
barcodeGeneratorTasks.add(task);
task.execute();
}
});
}
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);
}
});
} else {
Log.d(TAG, "Generating barcode for type " + formatType);
BarcodeImageWriterTask task = new BarcodeImageWriterTask(getApplicationContext(), image, cardId, format, text, true, null);
barcodeGeneratorTasks.add(task);
task.execute();
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)
{
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
setResult(Activity.RESULT_CANCELED);
finish();
return true;

View File

@@ -8,6 +8,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import android.util.Log;
import java.io.FileNotFoundException;
import java.math.BigDecimal;
@@ -21,7 +22,7 @@ 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 = 13;
public static final int DATABASE_VERSION = 14;
public static class LoyaltyCardDbGroups
{
@@ -46,6 +47,7 @@ public class DBHelper extends SQLiteOpenHelper
public static final String BARCODE_TYPE = "barcodetype";
public static final String STAR_STATUS = "starstatus";
public static final String LAST_USED = "lastused";
public static final String ZOOM_LEVEL = "zoomlevel";
}
public static class LoyaltyCardDbIdsGroups
@@ -105,7 +107,8 @@ 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 + "(" +
@@ -119,6 +122,7 @@ public class DBHelper extends SQLiteOpenHelper
"tokenize=unicode61);");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
@@ -290,8 +294,10 @@ public class DBHelper extends SQLiteOpenHelper
cursor.moveToFirst();
while (cursor.moveToNext()) {
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(cursor);
insertFTS(db, loyaltyCard.id, loyaltyCard.store, loyaltyCard.note);
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));
insertFTS(db, id, store, note);
}
}
@@ -302,16 +308,25 @@ public class DBHelper extends SQLiteOpenHelper
Cursor cursor = db.rawQuery("SELECT * FROM " + LoyaltyCardDbIds.TABLE + ";", null, null);
if (cursor.moveToFirst()) {
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(cursor);
insertFTS(db, loyaltyCard.id, loyaltyCard.store, loyaltyCard.note);
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));
insertFTS(db, id, store, note);
while (cursor.moveToNext()) {
loyaltyCard = LoyaltyCard.toLoyaltyCard(cursor);
insertFTS(db, loyaltyCard.id, loyaltyCard.store, loyaltyCard.note);
id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
store = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE));
note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE));
insertFTS(db, id, store, note);
}
}
cursor.close();
}
if(oldVersion < 14 && newVersion >= 14){
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '100' ");
}
}
private ContentValues generateFTSContentValues(final int id, final String store, final String note) {
@@ -508,6 +523,18 @@ public class DBHelper extends SQLiteOpenHelper
return (rowsUpdated == 1);
}
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,
whereAttrs(LoyaltyCardDbIds.ID),
withArgs(loyaltyCardId));
Log.d("updateLoyaltyCardZLevel","Rows changed = "+rowsUpdated);
return (rowsUpdated == 1);
}
public LoyaltyCard getLoyaltyCard(final int id)
{
SQLiteDatabase db = getReadableDatabase();

View File

@@ -6,7 +6,6 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.InputType;
import android.util.Log;
@@ -32,11 +31,12 @@ 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;
public class ImportExportActivity extends CatimaAppCompatActivity
{
public class ImportExportActivity extends CatimaAppCompatActivity {
private static final String TAG = "Catima";
private static final int PERMISSIONS_EXTERNAL_STORAGE = 1;
@@ -50,17 +50,17 @@ public class ImportExportActivity extends CatimaAppCompatActivity
private DataFormat importDataFormat;
private String exportPassword;
final private TaskHandler mTasks = new TaskHandler();
@Override
protected void onCreate(Bundle savedInstanceState)
{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.importExport);
setContentView(R.layout.import_export_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
@@ -69,12 +69,11 @@ public class ImportExportActivity extends CatimaAppCompatActivity
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
{
ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(ImportExportActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE},
Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSIONS_EXTERNAL_STORAGE);
}
@@ -85,16 +84,14 @@ public class ImportExportActivity extends CatimaAppCompatActivity
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "catima.zip");
Button exportButton = findViewById(R.id.exportButton);
exportButton.setOnClickListener(new View.OnClickListener()
{
exportButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v)
{
public void onClick(View 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);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.leftMargin = 50;
params.rightMargin = 50;
@@ -121,11 +118,9 @@ public class ImportExportActivity extends CatimaAppCompatActivity
intentGetContentAction.setType("*/*");
Button importFilesystem = findViewById(R.id.importOptionFilesystemButton);
importFilesystem.setOnClickListener(new View.OnClickListener()
{
importFilesystem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v)
{
public void onClick(View v) {
chooseImportType(intentGetContentAction);
}
});
@@ -134,11 +129,9 @@ public class ImportExportActivity extends CatimaAppCompatActivity
final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
Button importApplication = findViewById(R.id.importOptionApplicationButton);
importApplication.setOnClickListener(new View.OnClickListener()
{
importApplication.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v)
{
public void onClick(View v) {
chooseImportType(intentPickAction);
}
});
@@ -200,24 +193,22 @@ public class ImportExportActivity extends CatimaAppCompatActivity
.setTitle(importAlertTitle)
.setMessage(importAlertMessage)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
chooseFileWithIntent(baseIntent, IMPORT);
}
})
@Override
public void onClick(DialogInterface dialog, int which) {
chooseFileWithIntent(baseIntent, IMPORT);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
});
builder.show();
}
private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password, final boolean closeWhenDone)
{
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password, final boolean closeWhenDone) {
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
@Override
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat)
{
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
onImportComplete(result, targetUri, dataFormat);
if (closeWhenDone) {
try {
@@ -231,13 +222,12 @@ public class ImportExportActivity extends CatimaAppCompatActivity
importExporter = new ImportExportTask(ImportExportActivity.this,
dataFormat, target, password, listener);
importExporter.execute();
mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter);
}
private void startExport(final OutputStream target, final Uri targetUri,char[] password, final boolean closeWhenDone)
{
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
private void startExport(final OutputStream target, final Uri targetUri, char[] password, final boolean closeWhenDone) {
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
@Override
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
onExportComplete(result, targetUri);
@@ -252,8 +242,8 @@ public class ImportExportActivity extends CatimaAppCompatActivity
};
importExporter = new ImportExportTask(ImportExportActivity.this,
DataFormat.Catima, target,password, listener);
importExporter.execute();
DataFormat.Catima, target, password, listener);
mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter);
}
@Override
@@ -281,22 +271,17 @@ public class ImportExportActivity extends CatimaAppCompatActivity
}
@Override
protected void onDestroy()
{
if(importExporter != null && importExporter.getStatus() != AsyncTask.Status.RUNNING)
{
importExporter.cancel(true);
}
protected void onDestroy() {
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
super.onDestroy();
}
@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;
}
@@ -330,13 +315,10 @@ public class ImportExportActivity extends CatimaAppCompatActivity
int messageId;
if (result == ImportExportResult.Success)
{
if (result == ImportExportResult.Success) {
builder.setTitle(R.string.importSuccessfulTitle);
messageId = R.string.importSuccessful;
}
else
{
} else {
builder.setTitle(R.string.importFailedTitle);
messageId = R.string.importFailed;
}
@@ -344,11 +326,9 @@ public class ImportExportActivity extends CatimaAppCompatActivity
final String message = getResources().getString(messageId);
builder.setMessage(message);
builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener()
{
builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which)
{
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
@@ -356,19 +336,15 @@ public class ImportExportActivity extends CatimaAppCompatActivity
builder.create().show();
}
private void onExportComplete(ImportExportResult result, final Uri path)
{
private void onExportComplete(ImportExportResult result, final Uri path) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
int messageId;
if(result == ImportExportResult.Success)
{
if (result == ImportExportResult.Success) {
builder.setTitle(R.string.exportSuccessfulTitle);
messageId = R.string.exportSuccessful;
}
else
{
} else {
builder.setTitle(R.string.exportFailedTitle);
messageId = R.string.exportFailed;
}
@@ -378,8 +354,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity
builder.setMessage(message);
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
if(result == ImportExportResult.Success)
{
if (result == ImportExportResult.Success) {
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel);
builder.setPositiveButton(sendLabel, (dialog, which) -> {
@@ -400,58 +375,42 @@ public class ImportExportActivity extends CatimaAppCompatActivity
builder.create().show();
}
private void chooseFileWithIntent(Intent intent, int requestCode)
{
try
{
private void chooseFileWithIntent(Intent intent, int requestCode) {
try {
startActivityForResult(intent, requestCode);
}
catch (ActivityNotFoundException e)
{
} 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)
{
if (resultCode != RESULT_OK) {
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
return;
}
if(uri == null)
{
if (uri == null) {
Log.e(TAG, "Activity returned a NULL URI");
return;
}
try
{
if (requestCode == CHOOSE_EXPORT_LOCATION)
{
try {
if (requestCode == CHOOSE_EXPORT_LOCATION) {
OutputStream writer;
if (uri.getScheme() != null)
{
if (uri.getScheme() != null) {
writer = getContentResolver().openOutputStream(uri);
}
else
{
} else {
writer = new FileOutputStream(new File(uri.toString()));
}
Log.e(TAG, "Starting file export with: " + uri.toString());
startExport(writer, uri,exportPassword.toCharArray(),true);
}
else
{
startExport(writer, uri, exportPassword.toCharArray(), true);
} else {
InputStream reader;
if(uri.getScheme() != null)
{
if (uri.getScheme() != null) {
reader = getContentResolver().openInputStream(uri);
}
else
{
} else {
reader = new FileInputStream(new File(uri.toString()));
}
@@ -459,28 +418,21 @@ public class ImportExportActivity extends CatimaAppCompatActivity
startImport(reader, uri, importDataFormat, password, true);
}
}
catch(IOException e)
{
} catch (IOException e) {
Log.e(TAG, "Failed to import/export file: " + uri.toString(), e);
if (requestCode == CHOOSE_EXPORT_LOCATION)
{
if (requestCode == CHOOSE_EXPORT_LOCATION) {
onExportComplete(ImportExportResult.GenericFailure, uri);
}
else
{
} else {
onImportComplete(ImportExportResult.GenericFailure, uri, importDataFormat);
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(data == null)
{
if (data == null) {
Log.e(TAG, "Activity returned NULL data");
return;
}

View File

@@ -4,7 +4,6 @@ import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.util.Log;
import java.io.IOException;
@@ -13,13 +12,13 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import protect.card_locker.async.CompatCallable;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult;
import protect.card_locker.importexport.MultiFormatExporter;
import protect.card_locker.importexport.MultiFormatImporter;
class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
{
public class ImportExportTask implements CompatCallable<ImportExportResult> {
private static final String TAG = "Catima";
private Activity activity;
@@ -35,9 +34,8 @@ class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
/**
* Constructor which will setup a task for exporting to the given file
*/
ImportExportTask(Activity activity, DataFormat format, OutputStream output,char[] password,
TaskCompleteListener listener)
{
ImportExportTask(Activity activity, DataFormat format, OutputStream output, char[] password,
TaskCompleteListener listener) {
super();
this.activity = activity;
this.doImport = false;
@@ -51,8 +49,7 @@ class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
* Constructor which will setup a task for importing from the given InputStream.
*/
ImportExportTask(Activity activity, DataFormat format, InputStream input, char[] password,
TaskCompleteListener listener)
{
TaskCompleteListener listener) {
super();
this.activity = activity;
this.doImport = true;
@@ -62,8 +59,7 @@ class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
this.listener = listener;
}
private ImportExportResult performImport(Context context, InputStream stream, DBHelper db, char[] password)
{
private ImportExportResult performImport(Context context, InputStream stream, DBHelper db, char[] password) {
ImportExportResult importResult = MultiFormatImporter.importData(context, db, stream, format, password);
Log.i(TAG, "Import result: " + importResult.name());
@@ -71,18 +67,14 @@ class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
return importResult;
}
private ImportExportResult performExport(Context context, OutputStream stream, DBHelper db,char[] password)
{
private ImportExportResult performExport(Context context, OutputStream stream, DBHelper db, char[] password) {
ImportExportResult result = ImportExportResult.GenericFailure;
try
{
try {
OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
result = MultiFormatExporter.exportData(context, db, stream, format,password);
result = MultiFormatExporter.exportData(context, db, stream, format, password);
writer.close();
}
catch (IOException e)
{
} catch (IOException e) {
Log.e(TAG, "Unable to export file", e);
}
@@ -91,55 +83,55 @@ class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
return result;
}
protected void onPreExecute()
{
public void onPreExecute() {
progress = new ProgressDialog(activity);
progress.setTitle(doImport ? R.string.importing : R.string.exporting);
progress.setOnDismissListener(new DialogInterface.OnDismissListener()
{
progress.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog)
{
ImportExportTask.this.cancel(true);
public void onDismiss(DialogInterface dialog) {
ImportExportTask.this.stop();
}
});
progress.show();
}
protected ImportExportResult doInBackground(Void... nothing)
{
protected ImportExportResult doInBackground(Void... nothing) {
final DBHelper db = new DBHelper(activity);
ImportExportResult result;
if(doImport)
{
if (doImport) {
result = performImport(activity.getApplicationContext(), inputStream, db, password);
}
else
{
result = performExport(activity.getApplicationContext(), outputStream, db,password);
} else {
result = performExport(activity.getApplicationContext(), outputStream, db, password);
}
return result;
}
protected void onPostExecute(ImportExportResult result)
{
listener.onTaskComplete(result, format);
public void onPostExecute(Object castResult) {
listener.onTaskComplete((ImportExportResult) castResult, format);
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
}
protected void onCancelled()
{
protected void onCancelled() {
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Cancelled");
}
interface TaskCompleteListener
{
protected void stop() {
// Whelp
}
@Override
public ImportExportResult call() {
return doInBackground();
}
interface TaskCompleteListener {
void onTaskComplete(ImportExportResult result, DataFormat format);
}

View File

@@ -122,7 +122,7 @@ public class ImportURIHelper {
headerColor = Integer.parseInt(unparsedHeaderColor);
}
return new LoyaltyCard(-1, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0, Utils.getUnixTime());
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");
}

View File

@@ -30,11 +30,12 @@ public class LoyaltyCard implements Parcelable {
public final int starStatus;
public final long lastUsed;
public int zoomLevel;
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)
@Nullable final Integer headerColor, final int starStatus, final long lastUsed,final int zoomLevel)
{
this.id = id;
this.store = store;
@@ -48,6 +49,7 @@ public class LoyaltyCard implements Parcelable {
this.headerColor = headerColor;
this.starStatus = starStatus;
this.lastUsed = lastUsed;
this.zoomLevel = zoomLevel;
}
protected LoyaltyCard(Parcel in) {
@@ -66,6 +68,7 @@ public class LoyaltyCard implements Parcelable {
headerColor = tmpHeaderColor != -1 ? tmpHeaderColor : null;
starStatus = in.readInt();
lastUsed = in.readLong();
zoomLevel = in.readInt();
}
@Override
@@ -82,6 +85,7 @@ public class LoyaltyCard implements Parcelable {
parcel.writeInt(headerColor != null ? headerColor : -1);
parcel.writeInt(starStatus);
parcel.writeLong(lastUsed);
parcel.writeInt(zoomLevel);
}
public static LoyaltyCard toLoyaltyCard(Cursor cursor)
@@ -95,6 +99,7 @@ public class LoyaltyCard implements Parcelable {
String barcodeId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_ID));
int starred = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS));
long lastUsed = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.LAST_USED));
int zoomLevel = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ZOOM_LEVEL));
int barcodeTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
int balanceTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE);
@@ -125,7 +130,7 @@ public class LoyaltyCard implements Parcelable {
headerColor = cursor.getInt(headerColorColumn);
}
return new LoyaltyCard(id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starred, lastUsed);
return new LoyaltyCard(id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starred, lastUsed,zoomLevel);
}
@Override

View File

@@ -1,11 +1,12 @@
package protect.card_locker;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.SparseBooleanArray;
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.View;
@@ -21,11 +22,13 @@ 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<LoyaltyCardCursorAdapter.LoyaltyCardListItemViewHolder>
{
public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCursorAdapter.LoyaltyCardListItemViewHolder> {
private int mCurrentSelectedIndex = -1;
private Cursor mCursor;
Settings mSettings;
@@ -36,8 +39,7 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
private SparseBooleanArray mAnimationItemsIndex;
private boolean mReverseAllAnimations = false;
public LoyaltyCardCursorAdapter(Context inputContext, Cursor inputCursor, CardAdapterListener inputListener)
{
public LoyaltyCardCursorAdapter(Context inputContext, Cursor inputCursor, CardAdapterListener inputListener) {
super(inputCursor);
setHasStableIds(true);
mSettings = new Settings(inputContext);
@@ -58,14 +60,12 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
}
@Override
public LoyaltyCardListItemViewHolder onCreateViewHolder(ViewGroup inputParent, int inputViewType)
{
public LoyaltyCardListItemViewHolder onCreateViewHolder(ViewGroup inputParent, int inputViewType) {
View itemView = LayoutInflater.from(inputParent.getContext()).inflate(R.layout.loyalty_card_layout, inputParent, false);
return new LoyaltyCardListItemViewHolder(itemView, mListener);
}
public Cursor getCursor()
{
public Cursor getCursor() {
return mCursor;
}
@@ -73,8 +73,10 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
// Invisible until we want to show something more
inputHolder.mDivider.setVisibility(View.GONE);
int size = mSettings.getFontSizeMax(mSettings.getSmallFont());
if (mDarkModeEnabled) {
inputHolder.mStarIcon.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
inputHolder.mStarIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
}
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(inputCursor);
@@ -84,49 +86,86 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
if (!loyaltyCard.note.isEmpty()) {
inputHolder.mNoteField.setVisibility(View.VISIBLE);
inputHolder.mNoteField.setText(loyaltyCard.note);
inputHolder.mNoteField.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
inputHolder.mNoteField.setTextSize(size);
} else {
inputHolder.mNoteField.setVisibility(View.GONE);
}
if (!loyaltyCard.balance.equals(new BigDecimal("0"))) {
int drawableSize = dpToPx((size*24)/14, mContext);
inputHolder.mDivider.setVisibility(View.VISIBLE);
inputHolder.mBalanceField.setVisibility(View.VISIBLE);
Drawable balanceIcon = inputHolder.mBalanceField.getCompoundDrawables()[0];
balanceIcon.setBounds(0,0,drawableSize,drawableSize);
inputHolder.mBalanceField.setCompoundDrawablesRelative(balanceIcon, null, null, null);
if (mDarkModeEnabled) {
inputHolder.mBalanceField.getCompoundDrawables()[0].setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
balanceIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
}
inputHolder.mBalanceField.setText(Utils.formatBalance(mContext, loyaltyCard.balance, loyaltyCard.balanceType));
inputHolder.mBalanceField.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
inputHolder.mBalanceField.setTextSize(size);
} else {
inputHolder.mBalanceField.setVisibility(View.GONE);
}
if (loyaltyCard.expiry != null) {
int drawableSize = dpToPx((size*24)/14, mContext);
inputHolder.mDivider.setVisibility(View.VISIBLE);
inputHolder.mExpiryField.setVisibility(View.VISIBLE);
Drawable expiryIcon = inputHolder.mExpiryField.getCompoundDrawables()[0];
expiryIcon.setBounds(0,0, drawableSize, drawableSize);
inputHolder.mExpiryField.setCompoundDrawablesRelative(expiryIcon, null, null, null);
if (Utils.hasExpired(loyaltyCard.expiry)) {
expiryIcon.setColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP);
expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.RED, BlendModeCompat.SRC_ATOP));
inputHolder.mExpiryField.setTextColor(Color.RED);
} else if (mDarkModeEnabled) {
expiryIcon.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
}
inputHolder.mExpiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.expiry));
inputHolder.mExpiryField.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
inputHolder.mExpiryField.setTextSize(size);
} else {
inputHolder.mExpiryField.setVisibility(View.GONE);
}
inputHolder.mStarIcon.setVisibility(loyaltyCard.starStatus != 0 ? View.VISIBLE : View.GONE);
inputHolder.mCardIcon.setImageBitmap(Utils.generateIcon(mContext, loyaltyCard.store, loyaltyCard.headerColor).getLetterTile());
int imageSize = dpToPx( (size*46)/14, mContext);
inputHolder.mCardIcon.getLayoutParams().height = imageSize;
inputHolder.mCardIcon.getLayoutParams().width = imageSize;
inputHolder.mStarIcon.getLayoutParams().height = imageSize;
inputHolder.mStarIcon.getLayoutParams().width = imageSize;
inputHolder.mTickIcon.getLayoutParams().height = imageSize;
inputHolder.mTickIcon.getLayoutParams().width = imageSize;
/* Changing Padding and Mragin of different views according to font size
* Views Included:
* a) InformationContainer padding
* b) Store left padding
* c) Divider Margin
* d) note top margin
* e) row margin
* */
int marginPaddingSize = dpToPx((size*16)/14, mContext );
inputHolder.mInformationContainer.setPadding(marginPaddingSize, marginPaddingSize, marginPaddingSize, marginPaddingSize);
inputHolder.mStoreField.setPadding(marginPaddingSize, 0, 0, 0);
LinearLayout.LayoutParams lpDivider = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT );
lpDivider.setMargins(0, marginPaddingSize, 0, marginPaddingSize);
inputHolder.mDivider.setLayoutParams(lpDivider);
LinearLayout.LayoutParams lpNoteField = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT );
lpNoteField.setMargins(0, marginPaddingSize/2, 0, 0);
inputHolder.mNoteField.setLayoutParams(lpNoteField);
LinearLayout.LayoutParams lpRow = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT );
lpRow.setMargins(marginPaddingSize/2, marginPaddingSize/2, marginPaddingSize/2, marginPaddingSize/2);
inputHolder.mRow.setLayoutParams(lpRow);
inputHolder.itemView.setActivated(mSelectedItems.get(inputCursor.getPosition(), false));
applyIconAnimation(inputHolder, inputCursor.getPosition());
applyClickEvents(inputHolder, inputCursor.getPosition());
}
private void applyClickEvents(LoyaltyCardListItemViewHolder inputHolder, final int inputPosition)
{
private void applyClickEvents(LoyaltyCardListItemViewHolder inputHolder, final int inputPosition) {
inputHolder.mRow.setOnClickListener(inputView -> mListener.onRowClicked(inputPosition));
inputHolder.mInformationContainer.setOnClickListener(inputView -> mListener.onRowClicked(inputPosition));
@@ -143,85 +182,68 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
});
}
private void applyIconAnimation(LoyaltyCardListItemViewHolder inputHolder, int inputPosition)
{
if (mSelectedItems.get(inputPosition, false))
{
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);
if (mCurrentSelectedIndex == inputPosition)
{
if (mCurrentSelectedIndex == inputPosition) {
LoyaltyCardAnimator.flipView(mContext, inputHolder.mThumbnailBackContainer, inputHolder.mThumbnailFrontContainer, true);
resetCurrentIndex();
}
}
else
{
} else {
inputHolder.mThumbnailBackContainer.setVisibility(View.GONE);
resetIconYAxis(inputHolder.mThumbnailFrontContainer);
inputHolder.mThumbnailFrontContainer.setVisibility(View.VISIBLE);
inputHolder.mThumbnailFrontContainer.setAlpha(1);
if ((mReverseAllAnimations && mAnimationItemsIndex.get(inputPosition, false)) || mCurrentSelectedIndex == inputPosition)
{
if ((mReverseAllAnimations && mAnimationItemsIndex.get(inputPosition, false)) || mCurrentSelectedIndex == inputPosition) {
LoyaltyCardAnimator.flipView(mContext, inputHolder.mThumbnailBackContainer, inputHolder.mThumbnailFrontContainer, false);
resetCurrentIndex();
}
}
}
private void resetIconYAxis(View inputView)
{
if (inputView.getRotationY() != 0)
{
private void resetIconYAxis(View inputView) {
if (inputView.getRotationY() != 0) {
inputView.setRotationY(0);
}
}
public void resetAnimationIndex()
{
public void resetAnimationIndex() {
mReverseAllAnimations = false;
mAnimationItemsIndex.clear();
}
public void toggleSelection(int inputPosition)
{
public void toggleSelection(int inputPosition) {
mCurrentSelectedIndex = inputPosition;
if (mSelectedItems.get(inputPosition, false))
{
if (mSelectedItems.get(inputPosition, false)) {
mSelectedItems.delete(inputPosition);
mAnimationItemsIndex.delete(inputPosition);
}
else
{
} else {
mSelectedItems.put(inputPosition, true);
mAnimationItemsIndex.put(inputPosition, true);
}
notifyDataSetChanged();
}
public void clearSelections()
{
public void clearSelections() {
mReverseAllAnimations = true;
mSelectedItems.clear();
notifyDataSetChanged();
}
public int getSelectedItemCount()
{
public int getSelectedItemCount() {
return mSelectedItems.size();
}
public ArrayList<LoyaltyCard> getSelectedItems()
{
public ArrayList<LoyaltyCard> getSelectedItems() {
ArrayList<LoyaltyCard> result = new ArrayList<>();
int i;
for(i = 0; i < mSelectedItems.size(); i++)
{
for (i = 0; i < mSelectedItems.size(); i++) {
mCursor.moveToPosition(mSelectedItems.keyAt(i));
result.add(LoyaltyCard.toLoyaltyCard(mCursor));
}
@@ -229,29 +251,26 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
return result;
}
private void resetCurrentIndex()
{
private void resetCurrentIndex() {
mCurrentSelectedIndex = -1;
}
public interface CardAdapterListener
{
public interface CardAdapterListener {
void onRowClicked(int inputPosition);
void onRowLongClicked(int inputPosition);
}
public static class LoyaltyCardListItemViewHolder extends RecyclerView.ViewHolder
{
public static class LoyaltyCardListItemViewHolder extends RecyclerView.ViewHolder {
public TextView mStoreField, mNoteField, mBalanceField, mExpiryField;
public LinearLayout mInformationContainer;
public ImageView mCardIcon, mStarIcon;
public ImageView mCardIcon, mStarIcon, mTickIcon;
public MaterialCardView mRow;
public View mDivider;
public RelativeLayout mThumbnailFrontContainer, mThumbnailBackContainer;
public LoyaltyCardListItemViewHolder(View inputView, CardAdapterListener inputListener)
{
public LoyaltyCardListItemViewHolder(View inputView, CardAdapterListener inputListener) {
super(inputView);
mRow = inputView.findViewById(R.id.row);
mDivider = inputView.findViewById(R.id.info_divider);
@@ -264,6 +283,7 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
mExpiryField = inputView.findViewById(R.id.expiry);
mCardIcon = inputView.findViewById(R.id.thumbnail);
mStarIcon = inputView.findViewById(R.id.star);
mTickIcon = inputView.findViewById(R.id.selected_thumbnail);
inputView.setOnLongClickListener(view -> {
inputListener.onRowClicked(getAdapterPosition());
inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
@@ -271,4 +291,10 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
});
}
}
}
public int dpToPx(int dp, Context mContext){
Resources r = mContext.getResources();
int px = (int)TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
return px;
}
}

View File

@@ -41,7 +41,6 @@ import com.google.android.material.chip.ChipGroup;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;
import com.google.zxing.BarcodeFormat;
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
@@ -72,8 +71,9 @@ import androidx.core.content.FileProvider;
import androidx.exifinterface.media.ExifInterface;
import androidx.fragment.app.DialogFragment;
public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
{
import protect.card_locker.async.TaskHandler;
public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
private static final String TAG = "Catima";
private final String STATE_TAB_INDEX = "savedTab";
@@ -141,6 +141,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
LoyaltyCard tempLoyaltyCard;
final private TaskHandler mTasks = new TaskHandler();
private static LoyaltyCard updateTempState(LoyaltyCard loyaltyCard, LoyaltyCardField fieldName, Object value) {
return new LoyaltyCard(
(int) (fieldName == LoyaltyCardField.id ? value : loyaltyCard.id),
@@ -154,7 +156,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()
Utils.getUnixTime(),100
);
}
@@ -164,8 +166,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
hasChanged = true;
}
private void extractIntentFields(Intent intent)
{
private void extractIntentFields(Intent intent) {
final Bundle b = intent.getExtras();
loyaltyCardId = b != null ? b.getInt(BUNDLE_ID) : 0;
updateLoyaltyCard = b != null && b.getBoolean(BUNDLE_UPDATE, false);
@@ -205,8 +206,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
@@ -416,7 +416,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
} else if (s.toString().equals(getString(R.string.setBarcodeId))) {
if (!lastValue.toString().equals(getString(R.string.setBarcodeId))) {
barcodeIdField.setText(lastValue);
};
}
;
AlertDialog.Builder builder = new AlertDialog.Builder(LoyaltyCardEditActivity.this);
builder.setTitle(R.string.setBarcodeId);
@@ -471,7 +472,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
if (!barcodeFormat.isSupported()) {
Toast.makeText(LoyaltyCardEditActivity.this, getString(R.string.unsupportedBarcodeType), Toast.LENGTH_LONG).show();
}
} catch (IllegalArgumentException e) {}
} catch (IllegalArgumentException e) {
}
}
generateOrHideBarcode();
@@ -510,8 +512,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
}
@Override
public void onNewIntent(Intent intent)
{
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.i(TAG, "Received new intent");
@@ -520,15 +521,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
@SuppressLint("DefaultLocale")
@Override
public void onResume()
{
public void onResume() {
super.onResume();
Log.i(TAG, "To view card: " + loyaltyCardId);
onResuming = true;
if(tempLoyaltyCard == null) {
if (tempLoyaltyCard == null) {
if (updateLoyaltyCard) {
tempLoyaltyCard = db.getLoyaltyCard(loyaltyCardId);
if (tempLoyaltyCard == null) {
@@ -551,7 +551,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
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());
tempLoyaltyCard = new LoyaltyCard(-1, "", "", null, new BigDecimal("0"), null, "", null, null, null, 0, Utils.getUnixTime(),100);
setTitle(R.string.addCardTitle);
}
}
@@ -564,8 +564,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
barcodeIdField.setText(tempLoyaltyCard.barcodeId != null ? tempLoyaltyCard.barcodeId : getString(R.string.sameAsCardId));
barcodeTypeField.setText(tempLoyaltyCard.barcodeType != null ? tempLoyaltyCard.barcodeType.prettyName() : getString(R.string.noBarcode));
if(groupsChips.getChildCount() == 0)
{
if (groupsChips.getChildCount() == 0) {
List<Group> existingGroups = db.getGroups();
List<Group> loyaltyCardGroups = db.getLoyaltyCardGroups(loyaltyCardId);
@@ -604,18 +603,17 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
}
// Generate random header color
if(tempLoyaltyCard.headerColor == null)
{
if (tempLoyaltyCard.headerColor == null) {
// Select a random color to start out with.
TypedArray colors = getResources().obtainTypedArray(R.array.letter_tile_colors);
final int color = (int)(Math.random() * colors.length());
final int color = (int) (Math.random() * colors.length());
updateTempState(LoyaltyCardField.headerColor, colors.getColor(color, Color.BLACK));
colors.recycle();
}
// 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) {
if (tempLoyaltyCard.headerColor != null) {
thumbnail.setOnClickListener(new ColorSelectListener());
}
@@ -806,11 +804,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
startActivityForResult(i, type);
}
class EditCardIdAndBarcode implements View.OnClickListener
{
class EditCardIdAndBarcode implements View.OnClickListener {
@Override
public void onClick(View v)
{
public void onClick(View v) {
Intent i = new Intent(getApplicationContext(), ScanActivity.class);
final Bundle b = new Bundle();
b.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, cardIdFieldView.getText().toString());
@@ -819,11 +815,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
}
}
class ChooseCardImage implements View.OnClickListener
{
class ChooseCardImage implements View.OnClickListener {
@Override
public void onClick(View v) throws NoSuchElementException
{
public void onClick(View v) throws NoSuchElementException {
ImageView targetView = v.getId() == ID_IMAGE_FRONT ? cardImageFront : cardImageBack;
LinkedHashMap<String, Callable<Void>> cardOptions = new LinkedHashMap<>();
@@ -870,11 +864,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
}
}
class ColorSelectListener implements View.OnClickListener
{
class ColorSelectListener implements View.OnClickListener {
@Override
public void onClick(View v)
{
public void onClick(View v) {
ColorPickerDialog.Builder dialogBuilder = ColorPickerDialog.newBuilder();
if (tempLoyaltyCard.headerColor != null) {
@@ -882,19 +874,16 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
}
ColorPickerDialog dialog = dialogBuilder.create();
dialog.setColorPickerDialogListener(new ColorPickerDialogListener()
{
dialog.setColorPickerDialogListener(new ColorPickerDialogListener() {
@Override
public void onColorSelected(int dialogId, int color)
{
public void onColorSelected(int dialogId, int color) {
updateTempState(LoyaltyCardField.headerColor, color);
generateIcon(storeFieldEdit.getText().toString());
}
@Override
public void onDialogDismissed(int dialogId)
{
public void onDialogDismissed(int dialogId) {
// Nothing to do, no change made
}
});
@@ -955,20 +944,17 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
return;
}
if(tempLoyaltyCard.store.isEmpty())
{
if (tempLoyaltyCard.store.isEmpty()) {
Snackbar.make(storeFieldEdit, R.string.noStoreError, Snackbar.LENGTH_LONG).show();
return;
}
if(tempLoyaltyCard.cardId.isEmpty())
{
if (tempLoyaltyCard.cardId.isEmpty()) {
Snackbar.make(cardIdFieldView, R.string.noCardIdError, Snackbar.LENGTH_LONG).show();
return;
}
if(!validBalance)
{
if (!validBalance) {
Snackbar.make(balanceField, getString(R.string.parsingBalanceFailed, balanceField.getText().toString()), Snackbar.LENGTH_LONG).show();
return;
}
@@ -980,8 +966,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
selectedGroups.add((Group) chip.getTag());
}
if(updateLoyaltyCard)
{ //update of "starStatus" not necessary, since it cannot be changed in this activity (only in ViewActivity)
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);
@@ -990,9 +975,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
e.printStackTrace();
}
Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
}
else
{
} 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);
@@ -1008,14 +991,10 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
if(updateLoyaltyCard)
{
public boolean onCreateOptionsMenu(Menu menu) {
if (updateLoyaltyCard) {
getMenuInflater().inflate(R.menu.card_update_menu, menu);
}
else
{
} else {
getMenuInflater().inflate(R.menu.card_add_menu, menu);
}
@@ -1023,12 +1002,10 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch(id)
{
switch (id) {
case android.R.id.home:
askBeforeQuitIfChanged();
break;
@@ -1059,8 +1036,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent)
{
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode == RESULT_OK) {
@@ -1143,6 +1119,8 @@ 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
@@ -1154,12 +1132,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Log.d(TAG, "ImageView size now known");
new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, warnOnInvalidBarcodeType).execute();
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, warnOnInvalidBarcodeType);
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
}
});
} else {
Log.d(TAG, "ImageView size known known, creating barcode");
new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, warnOnInvalidBarcodeType).execute();
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, warnOnInvalidBarcodeType);
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
}
showBarcode();

View File

@@ -30,7 +30,6 @@ import android.widget.Toast;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.zxing.BarcodeFormat;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
@@ -48,10 +47,11 @@ import androidx.constraintlayout.widget.Guideline;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.widget.TextViewCompat;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.preferences.Settings;
public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements GestureDetector.OnGestureListener
{
public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements GestureDetector.OnGestureListener {
private static final String TAG = "Catima";
private GestureDetector mGestureDetector;
@@ -102,6 +102,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
static final String STATE_IMAGEINDEX = "imageIndex";
static final String STATE_FULLSCREEN = "isFullscreen";
final private TaskHandler mTasks = new TaskHandler();
@Override
public boolean onDown(MotionEvent e) {
return true;
@@ -182,8 +184,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
IMAGE_BACK
}
private void extractIntentFields(Intent intent)
{
private void extractIntentFields(Intent intent) {
final Bundle b = intent.getExtras();
loyaltyCardId = b != null ? b.getInt("id") : 0;
Log.d(TAG, "View activity: id=" + loyaltyCardId);
@@ -198,17 +199,13 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
return wrappedIcon;
}
private Drawable getIcon(int icon, boolean dark)
{
private Drawable getIcon(int icon, boolean dark) {
Drawable unwrappedIcon = AppCompatResources.getDrawable(this, icon);
assert unwrappedIcon != null;
Drawable wrappedIcon = DrawableCompat.wrap(unwrappedIcon);
if(dark)
{
if (dark) {
DrawableCompat.setTint(wrappedIcon, Color.BLACK);
}
else
{
} else {
DrawableCompat.setTintList(wrappedIcon, null);
}
@@ -216,8 +213,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
@@ -251,9 +247,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
appBarLayout = findViewById(R.id.app_bar_layout);
centerGuideline = findViewById(R.id.centerGuideline);
centerGuideline.setGuidelinePercent(0.5f);
barcodeScaler = findViewById(R.id.barcodeScaler);
barcodeScaler.setProgress(100);
maximizeButton.setBackgroundColor(getThemeColor());
minimizeButton.setBackgroundColor(getThemeColor());
bottomSheetButton.setBackgroundColor(getThemeColor());
@@ -265,13 +259,18 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
float scale = (float) progress / (float) barcodeScaler.getMax();
Log.d(TAG, "Scaling to " + scale);
if (imageTypes.get(mainImageIndex) == ImageType.BARCODE) {
redrawBarcodeAfterResize();
if(isFullscreen){
loyaltyCard.zoomLevel = progress;
db.updateLoyaltyCardZoomLevel(loyaltyCardId, loyaltyCard.zoomLevel);
}
if(loyaltyCard!=null && format!=null && format.isSquare())
if (format != null && format.isSquare()) {
centerGuideline.setGuidelinePercent(0.75f * scale);
else
} else {
centerGuideline.setGuidelinePercent(0.5f * scale);
}
drawMainImage(mainImageIndex, true);
}
@Override
@@ -324,7 +323,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) { }
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
bottomSheetButton.setOnClickListener(v -> {
@@ -360,8 +360,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
@Override
public void onNewIntent(Intent intent)
{
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.i(TAG, "Received new intent");
@@ -377,8 +376,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
@Override
public void onResume()
{
public void onResume() {
super.onResume();
Log.i(TAG, "To view card: " + loyaltyCardId);
@@ -387,12 +385,10 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
// '1' is the brightest. We attempt to maximize the brightness
// to help barcode readers scan the barcode.
Window window = getWindow();
if(window != null)
{
if (window != null) {
WindowManager.LayoutParams attributes = window.getAttributes();
if (settings.useMaxBrightnessDisplayingBarcode())
{
if (settings.useMaxBrightnessDisplayingBarcode()) {
attributes.screenBrightness = 1F;
}
@@ -401,7 +397,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
if (settings.getDisableLockscreenWhileViewingCard()) {
window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD|
window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
}
@@ -409,8 +405,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
if(loyaltyCard == null)
{
if (loyaltyCard == null) {
Log.w(TAG, "Could not lookup loyalty card " + loyaltyCardId);
Toast.makeText(this, R.string.noCardExistsError, Toast.LENGTH_LONG).show();
finish();
@@ -420,12 +415,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
setupOrientation();
format = loyaltyCard.barcodeType;
if(format != null && format.isSquare()){
centerGuideline.setGuidelinePercent(0.75f);
}
else{
centerGuideline.setGuidelinePercent(0.5f);
}
cardIdString = loyaltyCard.cardId;
barcodeIdString = loyaltyCard.barcodeId;
@@ -434,20 +423,17 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
settings.getFontSizeMin(settings.getLargeFont()), settings.getFontSizeMax(settings.getLargeFont()),
1, TypedValue.COMPLEX_UNIT_SP);
if(loyaltyCard.note.length() > 0)
{
if (loyaltyCard.note.length() > 0) {
noteView.setVisibility(View.VISIBLE);
noteView.setText(loyaltyCard.note);
noteView.setTextSize(settings.getFontSizeMax(settings.getMediumFont()));
}
else
{
} else {
noteView.setVisibility(View.GONE);
}
List<Group> loyaltyCardGroups = db.getLoyaltyCardGroups(loyaltyCardId);
if(loyaltyCardGroups.size() > 0) {
if (loyaltyCardGroups.size() > 0) {
List<String> groupNames = new ArrayList<>();
for (Group group : loyaltyCardGroups) {
groupNames.add(group._id);
@@ -456,35 +442,29 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
groupsView.setVisibility(View.VISIBLE);
groupsView.setText(getString(R.string.groupsList, TextUtils.join(", ", groupNames)));
groupsView.setTextSize(settings.getFontSizeMax(settings.getMediumFont()));
}
else
{
} else {
groupsView.setVisibility(View.GONE);
}
if(!loyaltyCard.balance.equals(new BigDecimal(0))) {
if (!loyaltyCard.balance.equals(new BigDecimal(0))) {
balanceView.setVisibility(View.VISIBLE);
balanceView.setText(getString(R.string.balanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)));
balanceView.setTextSize(settings.getFontSizeMax(settings.getMediumFont()));
}
else
{
} else {
balanceView.setVisibility(View.GONE);
}
if(loyaltyCard.expiry != null) {
if (loyaltyCard.expiry != null) {
expiryView.setVisibility(View.VISIBLE);
int expiryString = R.string.expiryStateSentence;
if(Utils.hasExpired(loyaltyCard.expiry)) {
if (Utils.hasExpired(loyaltyCard.expiry)) {
expiryString = R.string.expiryStateSentenceExpired;
expiryView.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.alert));
}
expiryView.setText(getString(expiryString, DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.expiry)));
expiryView.setTextSize(settings.getFontSizeMax(settings.getMediumFont()));
}
else
{
} else {
expiryView.setVisibility(View.GONE);
}
expiryView.setTag(loyaltyCard.expiry);
@@ -503,12 +483,9 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
TypedValue.COMPLEX_UNIT_DIP);
int backgroundHeaderColor;
if(loyaltyCard.headerColor != null)
{
if (loyaltyCard.headerColor != null) {
backgroundHeaderColor = loyaltyCard.headerColor;
}
else
{
} else {
backgroundHeaderColor = LetterBitmap.getDefaultColor(this, loyaltyCard.store);
}
@@ -516,12 +493,9 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
appBarLayout.setBackgroundColor(backgroundHeaderColor);
int textColor;
if(Utils.needsDarkForeground(backgroundHeaderColor))
{
if (Utils.needsDarkForeground(backgroundHeaderColor)) {
textColor = Color.BLACK;
}
else
{
} else {
textColor = Color.WHITE;
}
storeName.setTextColor(textColor);
@@ -530,14 +504,12 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
// If the background is very bright, we should use dark icons
backgroundNeedsDarkIcons = Utils.needsDarkForeground(backgroundHeaderColor);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
if (actionBar != null) {
actionBar.setHomeAsUpIndicator(getIcon(R.drawable.ic_arrow_back_white, backgroundNeedsDarkIcons));
}
// Make notification area light if dark icons are needed
if(Build.VERSION.SDK_INT >= 23)
{
if (Build.VERSION.SDK_INT >= 23) {
window.getDecorView().setSystemUiVisibility(backgroundNeedsDarkIcons ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0);
}
window.setStatusBarColor(Color.TRANSPARENT);
@@ -594,8 +566,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
@Override
public void onBackPressed() {
if (isFullscreen)
{
if (isFullscreen) {
setFullscreen(false);
return;
}
@@ -604,16 +575,14 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.card_view_menu, menu);
// Always calculate lockscreen icon, it may need a black color
boolean lockBarcodeScreenOrientation = settings.getLockBarcodeScreenOrientation();
MenuItem item = menu.findItem(R.id.action_lock_unlock);
setOrientatonLock(item, lockBarcodeScreenOrientation);
if(lockBarcodeScreenOrientation)
{
if (lockBarcodeScreenOrientation) {
item.setVisible(false);
}
@@ -632,8 +601,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
if (starred) {
menu.findItem(R.id.action_star_unstar).setIcon(getIcon(R.drawable.ic_starred_white, backgroundNeedsDarkIcons));
menu.findItem(R.id.action_star_unstar).setTitle(R.string.unstar);
}
else {
} else {
menu.findItem(R.id.action_star_unstar).setIcon(getIcon(R.drawable.ic_unstarred_white, backgroundNeedsDarkIcons));
menu.findItem(R.id.action_star_unstar).setTitle(R.string.star);
}
@@ -641,12 +609,10 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch(id)
{
switch (id) {
case android.R.id.home:
finish();
break;
@@ -661,12 +627,9 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
return true;
case R.id.action_lock_unlock:
if(rotationEnabled)
{
if (rotationEnabled) {
setOrientatonLock(item, true);
}
else
{
} else {
setOrientatonLock(item, false);
}
rotationEnabled = !rotationEnabled;
@@ -682,8 +645,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
return super.onOptionsItemSelected(item);
}
private void setupOrientation()
{
private void setupOrientation() {
Toolbar portraitToolbar = findViewById(R.id.toolbar);
Toolbar landscapeToolbar = findViewById(R.id.toolbar_landscape);
@@ -711,51 +673,44 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
private void setOrientatonLock(MenuItem item, boolean lock)
{
if(lock)
{
private void setOrientatonLock(MenuItem item, boolean lock) {
if (lock) {
item.setIcon(getIcon(R.drawable.ic_lock_outline_white_24dp, backgroundNeedsDarkIcons));
item.setTitle(R.string.unlockScreen);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
}
else
{
} else {
item.setIcon(getIcon(R.drawable.ic_lock_open_white_24dp, backgroundNeedsDarkIcons));
item.setTitle(R.string.lockScreen);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
}
private void makeBottomSheetVisibleIfUseful()
{
private void makeBottomSheetVisibleIfUseful() {
if (noteView.getVisibility() == View.VISIBLE || groupsView.getVisibility() == View.VISIBLE || balanceView.getVisibility() == View.VISIBLE || expiryView.getVisibility() == View.VISIBLE) {
bottomSheet.setVisibility(View.VISIBLE);
}
else
{
} else {
bottomSheet.setVisibility(View.GONE);
}
}
private void drawBarcode() {
mTasks.flushTaskList(TaskHandler.TYPE.BARCODE, true, false, false);
if (format != null) {
new BarcodeImageWriterTask(
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(
getApplicationContext(),
mainImage,
barcodeIdString != null ? barcodeIdString : cardIdString,
format,
null,
false,
null)
.execute();
null);
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
}
}
@@ -771,7 +726,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
drawBarcode();
}
});
};
}
}
private void drawMainImage(int index, boolean waitForResize) {
@@ -831,6 +786,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
* by machines which offer no space to insert the complete device.
*/
private void setFullscreen(boolean enabled) {
isFullscreen = enabled;
ActionBar actionBar = getSupportActionBar();
if (enabled && !imageTypes.isEmpty()) {
@@ -838,14 +794,15 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
drawMainImage(mainImageIndex, true);
barcodeScaler.setProgress(loyaltyCard.zoomLevel);
// Hide maximize and show minimize button and scaler
maximizeButton.setVisibility(View.GONE);
minimizeButton.setVisibility(View.VISIBLE);
barcodeScaler.setVisibility(View.VISIBLE);
// Hide actionbar
if(actionBar != null)
{
if (actionBar != null) {
actionBar.hide();
}
@@ -870,9 +827,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_FULLSCREEN
);
}
else
{
} else {
Log.d(TAG, "Move out of fullscreen");
// Reset center guideline
@@ -887,8 +842,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
barcodeScaler.setVisibility(View.GONE);
// Show actionbar
if(actionBar != null)
{
if (actionBar != null) {
actionBar.show();
}
@@ -912,6 +866,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
);
}
isFullscreen = enabled;
Log.d("setFullScreen","Is full screen enabled? "+enabled+" Zoom Level = "+barcodeScaler.getProgress());
}
}

View File

@@ -8,7 +8,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.database.Cursor;
import android.net.Uri;
import android.database.CursorIndexOutOfBoundsException;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
@@ -737,7 +737,21 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
{
Cursor selected = mAdapter.getCursor();
selected.moveToPosition(inputPosition);
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
// FIXME
//
// There is a really nasty edge case that can happen when someone taps a card but right
// after it swipes (very small window, hard to reproduce). The cursor gets replaced and
// may not have a card at the ID number that is returned from onRowClicked.
//
// The proper fix, obviously, would involve makes sure an onFling can't happen while a
// click is being processed. Sadly, I have not yet found a way to make that possible.
LoyaltyCard loyaltyCard;
try {
loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
} catch (CursorIndexOutOfBoundsException e) {
Log.w(TAG, "Prevented crash from tap + swipe on ID " + inputPosition + ": " + e);
return;
}
Intent i = new Intent(this, LoyaltyCardViewActivity.class);
i.setAction("");

View File

@@ -0,0 +1,8 @@
package protect.card_locker.async;
import java.util.concurrent.Callable;
public interface CompatCallable<T> extends Callable<T> {
void onPostExecute(Object result);
void onPreExecute();
}

View File

@@ -0,0 +1,174 @@
package protect.card_locker.async;
import android.os.Handler;
import android.os.Looper;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
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 {
public enum TYPE {
BARCODE,
IMPORT,
EXPORT
}
HashMap<TYPE, ThreadPoolExecutor> executors = generateExecutors();
final private HashMap<TYPE, LinkedList<Future<?>>> taskList = new HashMap<>();
private final Handler uiHandler = new Handler(Looper.getMainLooper());
private HashMap<TYPE, ThreadPoolExecutor> generateExecutors() {
HashMap<TYPE, ThreadPoolExecutor> initExecutors = new HashMap<>();
for (TYPE type : TYPE.values()) {
replaceExecutor(initExecutors, type, false, false);
}
return initExecutors;
}
/**
* Replaces (or initializes) an Executor with a clean (new) one
* @param executors Map Reference
* @param type Which Queue
* @param flushOld attempt shutdown
* @param waitOnOld wait for Termination
*/
private void replaceExecutor(HashMap<TYPE, ThreadPoolExecutor> executors, TYPE type, Boolean flushOld, Boolean waitOnOld) {
ThreadPoolExecutor oldExecutor = executors.get(type);
if (oldExecutor != null) {
if (flushOld) {
oldExecutor.shutdownNow();
}
if (waitOnOld) {
try {
//noinspection ResultOfMethodCallIgnored
oldExecutor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
executors.put(type, (ThreadPoolExecutor) Executors.newCachedThreadPool());
}
/**
* Queue a Pseudo-AsyncTask for execution
*
* @param type Queue
* @param callable PseudoAsyncTask
*/
public void executeTask(TYPE type, CompatCallable<?> callable) {
Runnable runner = () -> {
try {
// Run on the UI Thread
uiHandler.post(callable::onPreExecute);
// Background
final Object result = callable.call();
// Post results on UI Thread so we can show them
uiHandler.post(() -> {
callable.onPostExecute(result);
});
} catch (Exception e) {
e.printStackTrace();
}
};
LinkedList<Future<?>> list = taskList.get(type);
if (list == null) {
list = new LinkedList<>();
}
ThreadPoolExecutor executor = executors.get(type);
if (executor != null) {
Future<?> task = executor.submit(runner);
// Test Queue Cancellation:
// task.cancel(true);
list.push(task);
taskList.put(type, list);
}
}
/**
* This will attempt to cancel a currently running list of Tasks
* Useful to ignore scheduled tasks - but not able to hard-stop tasks that are running
*
* @param type Which Queue to target
* @param forceCancel attempt to close the Queue and force-replace it after
* @param waitForFinish wait and return after the old executor finished. Times out after 5s
* @param waitForCurrentlyRunning wait before cancelling tasks. Useful for tests.
*/
public void flushTaskList(TYPE type, Boolean forceCancel, Boolean waitForFinish, Boolean waitForCurrentlyRunning) {
// Only used for Testing
if (waitForCurrentlyRunning) {
ThreadPoolExecutor oldExecutor = executors.get(type);
if (oldExecutor != null) {
try {
//noinspection ResultOfMethodCallIgnored
oldExecutor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// Attempt to cancel known Tasks and clean the List
LinkedList<Future<?>> tasks = taskList.get(type);
if (tasks != null) {
for (Future<?> task : tasks) {
if (!task.isDone() || !task.isCancelled()) {
// Interrupt any Task we can
task.cancel(true);
}
}
}
tasks = new LinkedList<>();
taskList.put(type, tasks);
if (forceCancel || waitForFinish) {
ThreadPoolExecutor oldExecutor = executors.get(type);
if (oldExecutor != null) {
if (forceCancel) {
if (waitForFinish) {
try {
//noinspection ResultOfMethodCallIgnored
oldExecutor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
oldExecutor.shutdownNow();
replaceExecutor(executors, type, true, false);
} else {
try {
//noinspection ResultOfMethodCallIgnored
oldExecutor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}

View File

@@ -104,7 +104,7 @@ public class CatimaImporter implements Importer
public void parseV1(Context context, DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
{
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.withHeader());
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.builder().setHeader().build());
SQLiteDatabase database = db.getWritableDatabase();
database.beginTransaction();
@@ -209,7 +209,7 @@ public class CatimaImporter implements Importer
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.withHeader());
final CSVParser groupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.builder().setHeader().build());
List<CSVRecord> records = new ArrayList<>();
@@ -235,7 +235,7 @@ public class CatimaImporter implements Importer
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.withHeader());
final CSVParser cardParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.builder().setHeader().build());
List<CSVRecord> records = new ArrayList<>();
@@ -261,7 +261,7 @@ public class CatimaImporter implements Importer
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.withHeader());
final CSVParser cardGroupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.builder().setHeader().build());
List<CSVRecord> records = new ArrayList<>();

View File

@@ -32,8 +32,7 @@ import protect.card_locker.Utils;
* 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 FidmeImporter implements Importer
{
public class FidmeImporter implements Importer {
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
// We actually retrieve a .zip file
ZipInputStream zipInputStream = new ZipInputStream(input, password);
@@ -59,7 +58,7 @@ public class FidmeImporter implements Importer
SQLiteDatabase database = db.getWritableDatabase();
database.beginTransaction();
final CSVParser fidmeParser = new CSVParser(new StringReader(loyaltyCards.toString()), CSVFormat.RFC4180.withDelimiter(';').withHeader());
final CSVParser fidmeParser = new CSVParser(new StringReader(loyaltyCards.toString()), CSVFormat.RFC4180.builder().setDelimiter(';').setHeader().build());
try {
for (CSVRecord record : fidmeParser) {
@@ -87,8 +86,7 @@ public class FidmeImporter implements Importer
* session.
*/
private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record)
throws IOException, FormatException
{
throws IOException, FormatException {
// A loyalty card export from Fidme contains the following fields:
// Retailer (store name)
// Program (program name)
@@ -100,8 +98,7 @@ public class FidmeImporter implements Importer
// The store is called Retailer
String store = CSVHelpers.extractString("Retailer", record, "");
if (store.isEmpty())
{
if (store.isEmpty()) {
throw new FormatException("No store listed, but is required");
}
@@ -121,8 +118,7 @@ public class FidmeImporter implements Importer
// The ID is called reference
String cardId = CSVHelpers.extractString("Reference", record, "");
if(cardId.isEmpty())
{
if (cardId.isEmpty()) {
throw new FormatException("No card ID listed, but is required");
}

View File

@@ -43,7 +43,7 @@ public class StocardImporter implements Importer
HashMap<String, HashMap<String, Object>> loyaltyCardHashMap = new HashMap<>();
HashMap<String, HashMap<String, String>> providers = new HashMap<>();
final CSVParser parser = new CSVParser(new InputStreamReader(context.getResources().openRawResource(R.raw.stocard_stores), StandardCharsets.UTF_8), CSVFormat.RFC4180.withHeader());
final CSVParser parser = new CSVParser(new InputStreamReader(context.getResources().openRawResource(R.raw.stocard_stores), StandardCharsets.UTF_8), CSVFormat.RFC4180.builder().setHeader().build());
try
{

View File

@@ -27,20 +27,27 @@
<com.google.android.material.card.MaterialCardView
android:id="@+id/thumbnail_container"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="4dp"
android:layout_alignParentStart="true">
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
>
<RelativeLayout
android:id="@+id/thumbnail_front"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/storeContainer"
android:layout_alignBottom="@+id/storeContainer"
android:layout_gravity="center_vertical">
<ImageView
android:id="@+id/thumbnail"
android:scaleType="centerCrop"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_centerVertical="true"
android:contentDescription="@string/thumbnailDescription"
android:src="@mipmap/ic_launcher" />
@@ -48,25 +55,29 @@
<RelativeLayout
android:id="@+id/thumbnail_back"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical">
<ImageView
android:id="@+id/selected_thumbnail"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_centerHorizontal="true"
android:layout_centerInParent="true"
android:contentDescription="@string/thumbnailDescription"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_done" />
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:minHeight="@dimen/cardThumbnailSize"
android:id="@+id/storeContainer"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:layout_toEndOf="@+id/thumbnail_container">
<TextView
@@ -82,6 +93,8 @@
android:id="@+id/star"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_centerVertical="true"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_starred_white"
android:contentDescription="@string/starImage"
app:tint="?attr/colorControlNormal"
@@ -114,6 +127,8 @@
android:id="@+id/balance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:paddingEnd="8dp"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
@@ -125,6 +140,8 @@
android:id="@+id/expiry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
app:drawableLeftCompat="@drawable/ic_baseline_access_time_24"

View File

@@ -122,6 +122,10 @@
<TextView
android:id="@+id/cardIdView"
android:enabled="true"
android:textIsSelectable="true"
android:focusable="true"
android:longClickable="true"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="10.0dip"
@@ -181,6 +185,10 @@
<TextView
android:id="@+id/noteView"
android:enabled="true"
android:textIsSelectable="true"
android:focusable="true"
android:longClickable="true"
android:autoLink="all"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -190,6 +198,10 @@
<TextView
android:id="@+id/groupsView"
android:enabled="true"
android:textIsSelectable="true"
android:focusable="true"
android:longClickable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
@@ -198,6 +210,10 @@
<TextView
android:id="@+id/balanceView"
android:enabled="true"
android:textIsSelectable="true"
android:focusable="true"
android:longClickable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
@@ -206,6 +222,10 @@
<TextView
android:id="@+id/expiryView"
android:enabled="true"
android:textIsSelectable="true"
android:focusable="true"
android:longClickable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"

View File

@@ -8,25 +8,26 @@ mondstern
Taco
IllusiveMan196
Petr Novák
Oğuz Ersen
Gediminas Murauskas
Joel A
Oğuz Ersen
StoyanDimitrov
Samantaz Fox
Nyatsuki
arno-github
Sergio Paredes
Ankit Tiwari
Sergio Paredes
arshbeerSingh
huuhaa
Michael Moroni
Olivia (Zoe)
betsythefc
waffshappen
Miha Frangež
K. Herbert
Quentin PAGÈS
String E. Fighter
Yurical
waffshappen
Adolfo Jayme-Barrientos
Alessandro Mandelli
KovalevArtem
@@ -41,9 +42,8 @@ Thomas Bertels
inesre
lgasp
phlostically
Kevin Sicong Jiang
Miha Frangež
Aditya Das
Kevin Sicong Jiang
Airat
BMN
Biren
@@ -61,6 +61,7 @@ QuangDNguyen2211
Rohan Babbar
Rose Liverman
Simone Dotto
Still Hsu
Subhashish Anand
Tymofii Lytvynenko
Tjipke van der Heide

View File

@@ -208,5 +208,6 @@
<string name="on_google_play">в Google Play</string>
<string name="and_data_usage">и използване на данни</string>
<string name="help_translate_this_app">Помогнете за превода на приложението</string>
</resources>
<string name="exportPasswordHint">Въведете парола</string>
<string name="exportPassword">Задаване на парола за защита на изнесеното (по избор)</string>
</resources>

View File

@@ -212,4 +212,6 @@
<string name="help_translate_this_app">Pomozte s překladem této aplikace</string>
<string name="report_error">Nahlásit chybu</string>
<string name="on_google_play">na Google Play</string>
<string name="exportPassword">Nastavení hesla pro ochranu exportu (volitelné)</string>
<string name="exportPasswordHint">Zadejte heslo</string>
</resources>

View File

@@ -86,4 +86,11 @@
<string name="settings_disable_lockscreen_while_viewing_card">Forebyg låseskærm</string>
<string name="settings_keep_screen_on">LHold skærm tændt</string>
<string name="settings_lock_barcode_orientation">Lås stregkode-orientering</string>
<string name="moveUp">Bevæg dig opad</string>
<string name="leaveWithoutSaveConfirmation">Forlade uden at gemme\?</string>
<string name="settings_display_barcode_max_brightness">Lysere stregkodevisning</string>
<string name="failedOpeningFileManager">Installer først en filhåndteringsprogram.</string>
<string name="moveDown">Bevæger sig nedad</string>
<string name="leaveWithoutSaveTitle">Afslut</string>
<string name="addManually">Indtast kort-ID manuelt</string>
</resources>

View File

@@ -79,4 +79,13 @@
<string name="unlockScreen">Malbloki Rotacio</string>
<string name="lockScreen">Bloko Rotacio</string>
<string name="star">Aldoni al miaj plej ŝatataj</string>
<string name="copy_to_clipboard_toast">Card ID kopiita al la tondujo</string>
<string name="settings_keep_screen_on">Teni sur ekrano</string>
<string name="importSuccessful">Karto datumo importitaj</string>
<string name="enter_group_name">Eniri nomo de la grupo</string>
<string name="noGroups">Klaki la + plus butonon por aldoni grupoj por categorization unua.</string>
<string name="all">Ĉiuj</string>
<string name="intent_import_card_from_url_share_text">Mi deziras dividi karto kun vi</string>
<string name="exportSuccessful">Karto datumo eksportita</string>
<string name="noGroupCards">Ĉi tiu grupo ne enhavas ajnan kartoj</string>
</resources>

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@@ -0,0 +1,190 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="app_revision_url">https://github.com/TheLastProject/Catima/releases</string>
<string name="storeName">Nama</string>
<string name="note">Keterangan</string>
<string name="delete">Hapus</string>
<string name="edit">Ubah</string>
<string name="save">Simpan</string>
<string name="deleteTitle">Hapus kartu</string>
<string name="cardId">Kartu ID</string>
<string name="barcodeType">Tipe barcode</string>
<string name="star">Tambahkan ke favorit</string>
<string name="unstar">Hapus dari favorit</string>
<string name="action_add">Tambah</string>
<string name="action_search">Cari</string>
<string name="sort_by_name">Nama</string>
<string name="sort_by_balance">Saldo</string>
<string name="sort_by">Sortir dengan</string>
<string name="sort">Sortir</string>
<string name="credits">Kredit</string>
<string name="license">Lisensi</string>
<string name="settings">Pengaturan</string>
<string name="settings_system_theme">Sistem</string>
<string name="selectBarcodeTitle">Pilih Barcode</string>
<string name="deleteConfirmation">Hapus kartu ini secara permanen?</string>
<string name="ok">OK</string>
<string name="share">Bagikan</string>
<string name="editCardTitle">Ubah Kartu</string>
<string name="addCardTitle">Tambah Kartu</string>
<string name="scanCardBarcode">Pindai Kartu Barcode</string>
<string name="barcodeNoBarcode">Kartu ini tidak memiliki barcode</string>
<string name="cancel">Batalkan</string>
<string name="importExport">Import/Ekspor</string>
<string name="settings_category_title_ui">Tampilan Pengguna</string>
<string name="settings_theme">Tema</string>
<string name="all">Semua</string>
<string name="leaveWithoutSaveTitle">Keluar</string>
<string name="card">Kartu</string>
<string name="barcode">Barcode</string>
<string name="chooseExpiryDate">Pilih masa berlaku</string>
<string name="noBarcodeFound">Barcode tidak ditemukan</string>
<string name="errorReadingImage">TIdak dapat membaca gambar</string>
<string name="balance">Saldo</string>
<string name="currency">Mata uang</string>
<string name="chooseImportType">Impor data dari?</string>
<string name="accept">Terima</string>
<string name="importCatima">Impor dari Catima</string>
<string name="importFidme">Impor dari FidMe</string>
<string name="barcodeId">Nilai barcode</string>
<string name="sameAsCardId">Sama denga kartu ID</string>
<string name="setBarcodeId">Tentukan nilai barcode</string>
<string name="photos">Foto</string>
<string name="setFrontImage">Atur gambar bagian depan</string>
<string name="report_error">Lapor Kesalahan</string>
<string name="rate_this_app">Beri nilai pada aplikasi ini</string>
<string name="sort_by_expiry">Masa berlaku</string>
<string name="sort_by_most_recently_used">Paling banyak digunakan</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_pink_theme">Merah Muda</string>
<string name="settings_blue_theme">Biru</string>
<string name="settings_green_theme">Hijau</string>
<string name="settings_sky_blue_theme">Biru Langit</string>
<string name="settings_grey_theme">Abu-abu</string>
<string name="settings_brown_theme">Cokelat</string>
<string name="settings_violet_theme">Ungu</string>
<string name="settings_magenta_theme">Magenta</string>
<string name="settings_theme_color">Warna tema</string>
<string name="settings_system_locale">Sistem</string>
<string name="settings_locale">Bahasa</string>
<string name="turn_flashlight_on">Hidupkan lampu flash</string>
<string name="turn_flashlight_off">Matikan lampu flash</string>
<string name="exportPasswordHint">Masukan kata sandi</string>
<string name="yes">Ya</string>
<string name="no">Tidak</string>
<string name="takePhoto">Ambil foto</string>
<string name="removeImage">Hapus gambar</string>
<string name="setBackImage">Atur gambar bagian belakang</string>
<string name="intent_import_card_from_url_share_multiple_text">Saya ingin berbagi kartu dengan anda</string>
<string name="noGiftCards">Klik tanda + tombol tambah untuk menambahkan kartu, atau mengimpor beberapa kartu melalui menu ⋮ terlebih dahulu.</string>
<string name="noMatchingGiftCards">Tidak menemukan apapun. Coba untuk mengubah pencarian anda.</string>
<string name="noBarcode">Bukan barcode</string>
<string name="confirm">Konfirmasi</string>
<string name="copy_to_clipboard">Salin ID</string>
<string name="sendLabel">Kirim…</string>
<string name="noCardsMessage">Tambah kartu terlebih dahulu</string>
<string name="noStoreError">Nama masih kosong</string>
<string name="noCardIdError">Kartu ID masih kosong</string>
<string name="noCardExistsError">Tidak dapat menemukan kartu</string>
<string name="failedParsingImportUriError">Tidak dapat menguraikan alamat impor situs web</string>
<string name="exportName">Ekspor</string>
<string name="importSuccessfulTitle">Sudah diimpor</string>
<string name="importFailedTitle">Impor gagal</string>
<string name="importFailed">Tidak dapat mengimpor kartu</string>
<string name="exportSuccessfulTitle">Sudah diekspor</string>
<string name="exportFailedTitle">Ekspor gagal</string>
<string name="exportFailed">Tidak dapat mengekspor kartu</string>
<string name="importing">Mengimpor…</string>
<string name="exporting">Mengekspor…</string>
<string name="noExternalStoragePermissionError">Berikan izin penyimpanan eksternal untuk mengimpor atau mengekspor kartu terlebih dahulu</string>
<string name="exportOptionExplanation">The data will be written to a location of your choice.</string>
<string name="importOptionFilesystemTitle">Impor dari sistem</string>
<string name="importOptionFilesystemExplanation">Pilih file dari sistem</string>
<string name="importOptionFilesystemButton">Dari sistem</string>
<string name="importOptionApplicationTitle">Gunakan aplikasi lain</string>
<string name="importOptionApplicationExplanation">Gunakan aplikasi lain atau pengelola file favorit anda untuk membuka file.</string>
<string name="importOptionApplicationButton">Gunakan aplikasi lain</string>
<string name="about">Tentang</string>
<string name="app_copyright_fmt">Hak Cipta © 2019<xliff:g>%d</xliff:g> Sylvia van Os</string>
<string name="app_copyright_old">Berdasarkan Loyalty Card Keychain hak cipta © 20162020 Branden Archer</string>
<string name="app_license">Perangkat lunak bebas copyleft, berlisensi GPLv3+</string>
<string name="about_title_fmt">Tentang <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versi: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Info Revisi: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries">Pustaka pihak ketiga gratis: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Sumber daya pihak ketiga gratis: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="enterBarcodeInstructions">Masukkan ID kartu, dan pilih jenis barcodenya di bawah atau \"Kartu ini tidak memiliki barcode\".</string>
<string name="copy_to_clipboard_toast">ID kartu telah disalin</string>
<string name="thumbnailDescription">Gambar tampilan untuk kartu</string>
<string name="starImage">Favorit</string>
<string name="settings_light_theme">Terang</string>
<string name="settings_dark_theme">Gelap</string>
<string name="settings_max_font_size_scale">Ukuran maksimal huruf</string>
<string name="settings_display_barcode_max_brightness">Terangkan tampilan barcode</string>
<string name="settings_lock_barcode_orientation">Kunci orientasi barcode</string>
<string name="settings_keep_screen_on">Biarkan layar menyala</string>
<string name="settings_disable_lockscreen_while_viewing_card">Mencegah layar menyala</string>
<string name="intent_import_card_from_url_share_text">Saya ingin berbagi kartu dengan anda</string>
<string name="importSuccessful">Data kartu terimpor</string>
<string name="exportSuccessful">Data kartu terekspor</string>
<string name="enter_group_name">Masukan nama grup</string>
<string name="groups">Grup</string>
<string name="noGroups">Klik pada tombol tambah + untuk menambahkan grup atau kategori terlebih dahulu.</string>
<string name="noGroupCards">Grup ini tidak memilik kartu</string>
<string name="deleteConfirmationGroup">Hapus grup?</string>
<string name="failedOpeningFileManager">Instal aplikasi pengelola file terlebih dahulu.</string>
<string name="moveUp">Pindah ke atas</string>
<string name="moveDown">Pindah ke bawah</string>
<string name="leaveWithoutSaveConfirmation">Keluar tanpa menyimpan?</string>
<string name="addManually">Masukan ID kartu</string>
<string name="addFromImage">Pilih gambar dari galeri</string>
<string name="groupsList">Grup: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentence">Masa ber: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentenceExpired">Kadaluwarsa: <xliff:g>%s</xliff:g></string>
<string name="balanceSentence">Saldo: <xliff:g>%s</xliff:g></string>
<string name="balancePoints"><xliff:g>%s</xliff:g> poin</string>
<string name="editBarcode">Ubah barcode</string>
<string name="expiryDate">Tanggal masa berlaku</string>
<string name="never">Tidak pernah</string>
<string name="moveBarcodeToTopOfScreen">Pindah barcode ke bagian paling depan</string>
<string name="moveBarcodeToCenterOfScreen">Pusatkan barcode pada layar</string>
<string name="points">Poin</string>
<string name="parsingBalanceFailed"><xliff:g>%s</xliff:g> sepertinya bukan saldo yang valid.</string>
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
<string name="privacy_policy">Kebijakan Privasi</string>
<string name="privacy_policy_popup_text">Pemberitahuan kebijakan privasi (diperlukan oleh beberapa toko aplikasi): TIDAK ADA DATA YANG DIKUMPULKAN SAMA SEKALI, yang dapat dikonfirmasi oleh siapa pun karena aplikasi kami adalah perangkat lunak gratis.</string>
<string name="importCatimaMessage">Pilih ekspor <i>catima.zip</i> Anda dari Catima untuk diimpor. Buat dari menu Impor/Ekspor aplikasi Catima lain dengan menekan Ekspor di sana terlebih dahulu.</string>
<string name="importFidmeMessage">Pilih ekspor <i>fidme-export-request-xxxxxx.zip</i> Anda dari FidMe untuk diimpor, dan pilih jenis barcode secara manual setelahnya. Buat dari profil FidMe Anda dengan memilih Perlindungan Data lalu tekan Ekstrak data saya terlebih dahulu.</string>
<string name="importLoyaltyCardKeychain">Impor dari Loyalty Card Keychain</string>
<string name="importLoyaltyCardKeychainMessage">Pilih ekspor <i>LoyaltyCardKeychain.csv</i> Anda dari Loyalty Card Keychain untuk diimpor. Buat dari menu Import/Export di Loyalty Card Keychain dengan menekan Export terlebih dahulu.</string>
<string name="importStocard">Impor dari Stocard</string>
<string name="importStocardMessage">Pilih ekspor <i>***-sync.zip</i> Anda dari Stocard untuk diimpor. Dapatkan dengan mengirim email ke support@stocardapp.com untuk meminta ekspor data Anda.</string>
<string name="importVoucherVault">Impor dari Voucher Vault</string>
<string name="importVoucherVaultMessage">Pilih ekspor <i>vouchervault.json</i> Anda dari Vault Voucher untuk diimpor. Buat dengan menekan Ekspor di Vault Voucher terlebih dahulu.</string>
<string name="unsupportedBarcodeType">Jenis barcode ini belum dapat ditampilkan. Ini mungkin didukung di versi aplikasi yang lebih baru.</string>
<string name="wrongValueForBarcodeType">Nilai tidak berlaku untuk jenis barcode yang dipilih</string>
<string name="copy_to_clipboard_multiple_toast">ID kartu telah disalin</string>
<string name="frontImageDescription">Gambar depan kartu</string>
<string name="backImageDescription">Gambar belakang kartu</string>
<string name="updateBarcodeQuestionTitle">Perbarui barcode?</string>
<string name="updateBarcodeQuestionText">Anda mengubah ID kartu. Apakah Anda juga ingin memperbarui barcode untuk menggunakan nilai yang sama?</string>
<string name="passwordRequired">Silahkan masukan kata sandi</string>
<string name="exportPassword">Tetapkan kata sandi untuk melindungi ekspor anda (opsional)</string>
<string name="failedGeneratingShareURL">Tidak dapat membuat alamat berbagi. Mohon laporkan ini.</string>
<string name="app_contributors">Pengembangan dibantu oleh: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="toggleMoreInfo">Tampilkan info selengkapnya</string>
<string name="swipeToSwitchImages">Geser atau tekan yang lama untuk mengganti gambar</string>
<string name="reverse">Ubah urutan</string>
<string name="version_history">Riwayat Versi</string>
<string name="help_translate_this_app">Bantu terjemahkan aplikasi ini</string>
<string name="source_repository">Sumber Repositori</string>
<string name="on_github">di GitHub</string>
<string name="and_data_usage">dan penggunaan data</string>
<string name="on_google_play">di Google Play</string>
<string name="lockScreen">Blokir rotasi</string>
<string name="unlockScreen">Buka blokir rotasi</string>
<string name="cardShortcut">Pintasan kartu</string>
<string name="card_ids_copied">ID kartu yang tersalin</string>
<string name="barcodeImageDescriptionWithType">Gambar dari jenis barcode <xliff:g>%s</xliff:g></string>
<string name="importExportHelp">Mencadangkan kartu memungkinkan anda memindahkannya ke perangkat lain.</string>
</resources>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="action_add">Bæta</string>
<string name="noBarcode">Nei strikamerkið</string>
<string name="action_search">Leita að</string>
<string name="delete">Eyða</string>
<string name="unlockScreen">Opna Snúningur</string>
<string name="noGiftCards">Smelltu á + plús takka til að bæta kort, eða að flytja inn sumir frá ⋮ matseðill fyrst.</string>
<string name="note">Athugið</string>
<string name="barcodeType">Strikamerkið tegund</string>
<string name="cancel">Hætta</string>
<string name="noMatchingGiftCards">Vissi ekki að finna neitt. Reyna að breyta leita.</string>
<string name="storeName">Nafnið</string>
<string name="barcodeNoBarcode">Þetta kort hefur ekki strikamerkið</string>
<string name="star">Bæta við eftirlæti</string>
<string name="unstar">Fjarlægja frá eftirlæti</string>
<string name="save">Sparaðu</string>
<string name="edit">Breyta</string>
<string name="confirm">Staðfesta</string>
<string name="lockScreen">Blokk Snúningur</string>
<string name="ok">OK</string>
<string name="sendLabel">Sendu…</string>
<string name="deleteConfirmation">Eyða þetta kort til frambúðar\?</string>
<string name="share">Deila</string>
<string name="editCardTitle">Breyta Kort</string>
<string name="addCardTitle">Bæta Kort</string>
<string name="scanCardBarcode">Skanna Kort Strikamerkið</string>
<string name="noCardsMessage">Bæta kort fyrstu</string>
<string name="exportFailedTitle">Flytja mistókst</string>
<string name="exportSuccessfulTitle">Flutt</string>
<string name="noStoreError">Ekkert nafn slegið</string>
<string name="noCardExistsError">Gæti ekki fundið kort</string>
<string name="failedParsingImportUriError">Get ekki lesið inn URI</string>
<string name="exportName">Flytja</string>
<string name="importExportHelp">Stuðningur upp spil gerir þér kleift að færa þá til annar tæki.</string>
<string name="importSuccessfulTitle">Flutt</string>
<string name="importFailedTitle">Innflutningur mistókst</string>
<string name="noExternalStoragePermissionError">Grant ytri geymslu leyfi til að flytja eða flytja spil fyrstu</string>
<string name="exportOptionExplanation">Gögnum verður skrifað á stað af eigin vali.</string>
<string name="importOptionFilesystemTitle">Innflutningur frá möppuna</string>
<string name="importOptionFilesystemExplanation">Velja ákveðna skrá frá möppuna.</string>
</resources>

View File

@@ -76,10 +76,10 @@
<string name="app_revision_fmt">Revision Info: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="debug_version_fmt">Version: <xliff:g id="version">%s</xliff:g></string>
<string name="about_title_fmt">About <xliff:g id="app_name">%s</xliff:g></string>
<string name="app_license">Copylefted libre software, licensed GPLv3+.</string>
<string name="app_license">Copylefted libre software, licensed GPLv3+</string>
<string name="app_copyright_old">Based on Loyalty Card Keychain
\ncopyright © 20162020 Branden Archer.</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Copyright © 2019<xliff:g>%d</xliff:g> Sylvia van Os.</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Copyright © 2019<xliff:g>%d</xliff:g> Sylvia van Os</string>
<string name="app_resources">Libre third-party resources: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="about">Catimaについて</string>
<string name="importOptionApplicationButton">外部のアプリを使う</string>
@@ -95,7 +95,7 @@
<string name="exportFailed">カードをエクスポートできませんでした</string>
<string name="exportFailedTitle">エクスポートに失敗しました</string>
<string name="exportSuccessfulTitle">エクスポートしました</string>
<string name="sameAsCardId">カードの表記と同一</string>
<string name="sameAsCardId">カード番号に合わせる</string>
<string name="barcodeId">バーコード番号</string>
<string name="importVoucherVaultMessage">Voucher Vaultでエクスポートした <i>vouchervault.json</i>ファイルを選択してください。
\nファイルがない場合、Voucher Vaultでファイルをエクスポートしてください。</string>
@@ -152,10 +152,10 @@
<string name="takePhoto">写真を撮影する</string>
<string name="removeImage">画像を削除</string>
<string name="setBackImage">裏面の画像を設定</string>
<string name="setFrontImage">面の画像を設定</string>
<string name="setFrontImage">オモテ面の画像を設定</string>
<string name="photos">フォト</string>
<string name="backImageDescription">裏面</string>
<string name="frontImageDescription"></string>
<string name="backImageDescription">カードの裏面</string>
<string name="frontImageDescription">カードのオモテ</string>
<string name="importStocardMessage">Stocardでエクスポートした<i>***-sync.zip</i>ファイルを選択してください。
\nファイルがない場合、e-mailing support@stocardapp.comにデータのエクスポートを要求してください。</string>
<string name="importStocard">Stocardからインポート</string>
@@ -183,7 +183,7 @@
<string name="settings_pink_theme">Pink</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_theme_color">テーマカラー</string>
<string name="settings_system_locale">システム</string>
<string name="settings_system_locale">システムに従う</string>
<string name="settings_locale">言語</string>
<string name="noGroupCards">このグループにはカードがありません</string>
<string name="swipeToSwitchImages">画像を切り替えるには長押し、またはスワイプ</string>
@@ -192,5 +192,18 @@
<string name="sort_by_expiry">期限</string>
<string name="sort_by_most_recently_used">最近使用したカード</string>
<string name="sort_by_name">名前</string>
<string name="sort">ソート</string>
<string name="sort">並び替え</string>
<string name="rate_this_app">このアプリを評価する</string>
<string name="on_github">GitHub</string>
<string name="source_repository">ソースリポジトリ</string>
<string name="exportPassword">パスワードを設定してエクスポートしたファイルを保護する(任意)</string>
<string name="exportPasswordHint">パスワードを入力してください</string>
<string name="version_history">更新履歴</string>
<string name="credits">貢献者</string>
<string name="help_translate_this_app">このアプリの翻訳を手伝う</string>
<string name="license">ライセンス</string>
<string name="on_google_play">Google Play</string>
<string name="report_error">エラーを報告する</string>
<string name="reverse">逆順</string>
<string name="and_data_usage">and data usage</string>
</resources>

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@@ -212,4 +212,6 @@
<string name="on_google_play">Google Play</string>
<string name="help_translate_this_app">Padėkite išversti šią programėlę</string>
<string name="report_error">Pranešti apie klaidą</string>
<string name="exportPasswordHint">Įveskite slaptažodį</string>
<string name="exportPassword">Nustatykite slaptažodį, kad apsaugotumėte eksportavimą (neprivaloma)</string>
</resources>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="action_add">Acrescentar</string>
<string name="importOptionFilesystemExplanation">Escolha um ficheiro específico a partir do sistema de ficheiros.</string>
<string name="action_search">Pesquisa</string>
<string name="star">Adicionar aos favoritos</string>
<string name="noMatchingGiftCards">Não encontrei nada. Tente alterar a sua pesquisa.</string>
<string name="storeName">Nome</string>
<string name="note">Nota</string>
<string name="barcodeType">Tipo de código de barras</string>
<string name="barcodeNoBarcode">Este cartão não tem código de barras</string>
<string name="cancel">Cancelar</string>
<string name="save">Guardar</string>
<string name="edit">Edição</string>
<string name="noGiftCards">Clique no botão + + para adicionar um cartão, ou importe algum do menu ⋮ primeiro.</string>
<string name="noBarcode">Não há código de barras</string>
<string name="unstar">Retirar dos favoritos</string>
<string name="importOptionFilesystemButton">Do sistema de arquivo</string>
<string name="importOptionApplicationTitle">Use outro aplicativo</string>
<string name="importOptionApplicationExplanation">Utilize qualquer aplicação ou o seu gestor de ficheiros favorito para abrir um ficheiro.</string>
<string name="importOptionApplicationButton">Utilize outro aplicativo</string>
<string name="about">Sobre</string>
<string name="app_license">Copylefted software livre, licenciado GPLv3+</string>
</resources>

View File

@@ -24,4 +24,79 @@
<string name="star">Adaugă la favorite</string>
<string name="noBarcode">Fără cod de bare</string>
<string name="barcodeNoBarcode">Acest card nu are cod de bare</string>
<string name="moveDown">Mutarea în jos</string>
<string name="card">Cardul</string>
<string name="settings_theme">Tema</string>
<string name="all">Toate</string>
<string name="noCardsMessage">Adăugați mai întâi o carte</string>
<string name="noCardExistsError">Nu a putut găsi cardul</string>
<string name="failedParsingImportUriError">Nu s-a putut analiza URI-ul de import</string>
<string name="importExport">Importație/Export</string>
<string name="exportName">Exportați</string>
<string name="importSuccessfulTitle">Importat</string>
<string name="importFailedTitle">Importul a eșuat</string>
<string name="importFailed">Nu s-a putut importa carduri</string>
<string name="noCardIdError">Nu s-a introdus niciun ID de card</string>
<string name="exportFailed">Nu s-a putut exporta carduri</string>
<string name="importing">Importul…</string>
<string name="exporting">Exportul…</string>
<string name="noExternalStoragePermissionError">Acordați mai întâi permisiunea de stocare externă pentru a importa sau exporta carduri</string>
<string name="exportOptionExplanation">Datele vor fi scrise într-o locație aleasă de dumneavoastră.</string>
<string name="importOptionFilesystemTitle">Import din sistemul de fișiere</string>
<string name="importOptionApplicationTitle">Utilizați o altă aplicație</string>
<string name="starImage">Steaua preferată</string>
<string name="settings">Setări</string>
<string name="settings_category_title_ui">Interfață utilizator</string>
<string name="intent_import_card_from_url_share_text">Vreau să împărtășesc o carte cu tine</string>
<string name="moveUp">Mutarea în sus</string>
<string name="editCardTitle">Editare card</string>
<string name="addCardTitle">Adaugă card</string>
<string name="importOptionFilesystemButton">Din sistemul de fișiere</string>
<string name="importOptionApplicationExplanation">Utilizați orice aplicație sau managerul de fișiere preferat pentru a deschide un fișier.</string>
<string name="about">Despre</string>
<string name="app_license">Software liber cu copyleft, licențiat GPLv3+</string>
<string name="enterBarcodeInstructions">Introduceți ID-ul cardului și alegeți fie tipul de cod de bare de mai jos, fie \"Acest card nu are cod de bare\".</string>
<string name="settings_system_theme">Sistemul</string>
<string name="settings_light_theme">Lumină</string>
<string name="settings_dark_theme">Întuneric</string>
<string name="noBarcodeFound">Nu a fost găsit niciun cod de bare</string>
<string name="settings_max_font_size_scale">Mărimea maximă a fontului</string>
<string name="settings_display_barcode_max_brightness">Luminați vizualizarea codurilor de bare</string>
<string name="settings_lock_barcode_orientation">Blocare orientare cod de bare</string>
<string name="settings_keep_screen_on">Păstrați ecranul pornit</string>
<string name="settings_disable_lockscreen_while_viewing_card">Evitați ecranul de blocare</string>
<string name="balance">Balanță</string>
<string name="importSuccessful">Date de card importate</string>
<string name="exportSuccessful">Datele cardului exportate</string>
<string name="noGroups">Faceți clic pe butonul + plus pentru a adăuga mai întâi grupuri pentru clasificare.</string>
<string name="deleteConfirmationGroup">Ștergeți grupul\?</string>
<string name="failedOpeningFileManager">Instalați mai întâi un manager de fișiere.</string>
<string name="leaveWithoutSaveTitle">Ieșire</string>
<string name="addManually">Introduceți manual ID-ul cardului</string>
<string name="barcode">Cod de bare</string>
<string name="currency">Monedă</string>
<string name="editBarcode">Editarea codului de bare</string>
<string name="chooseExpiryDate">Alegeți data de expirare</string>
<string name="moveBarcodeToCenterOfScreen">Centrați codul de bare pe ecran</string>
<string name="copy_to_clipboard_toast">ID-ul cardului copiat în clipboard</string>
<string name="noStoreError">Nici un nume introdus</string>
<string name="selectBarcodeTitle">Selectare cod de bare</string>
<string name="noGroupCards">Acest grup nu conține nicio carte</string>
<string name="addFromImage">Selectați imaginea din galerie</string>
<string name="never">Niciodată</string>
<string name="groups">Grupuri</string>
<string name="leaveWithoutSaveConfirmation">Să pleci fără să salvezi\?</string>
<string name="expiryDate">Data expirării</string>
<string name="moveBarcodeToTopOfScreen">Mutați codul de bare în partea de sus a ecranului</string>
<string name="errorReadingImage">Nu s-a putut citi imaginea</string>
<string name="points">Puncte</string>
<string name="cardShortcut">Scurtătură de card</string>
<string name="scanCardBarcode">Scanarea codului de bare al cardului</string>
<string name="importExportHelp">Copierea de rezervă a cardurilor vă permite să le mutați pe un alt dispozitiv.</string>
<string name="exportSuccessfulTitle">Exportată</string>
<string name="exportFailedTitle">Export eșuat</string>
<string name="importOptionFilesystemExplanation">Alegeți un anumit fișier din sistemul de fișiere.</string>
<string name="importOptionApplicationButton">Utilizați o altă aplicație</string>
<string name="thumbnailDescription">Miniatură pentru card</string>
<string name="enter_group_name">Introduceți numele grupului</string>
</resources>

View File

@@ -50,7 +50,7 @@
<string name="noBarcodeFound">Жодного штрих-коду не знайдено</string>
<string name="moveBarcodeToCenterOfScreen">Відцентрувати штрих-код на екрані</string>
<string name="moveBarcodeToTopOfScreen">Посунути штрих-код наверх екрану</string>
<string name="chooseExpiryDate">Оберіть дату закінчення терміну дії</string>
<string name="chooseExpiryDate">Оберіть дату</string>
<string name="never">Ніколи</string>
<string name="expiryDate">Дата закінчення терміну дії</string>
<string name="editBarcode">Редагувати штрих-код</string>
@@ -101,7 +101,7 @@
\nавторські права © 20162020 Branden Archer</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Авторські права © 2019<xliff:g>%d</xliff:g> Sylvia van Os</string>
<string name="about">Про програму</string>
<string name="importOptionApplicationButton">Використайте іншу програму</string>
<string name="importOptionApplicationButton">Вибір програми</string>
<string name="importOptionApplicationExplanation">Використайте іншу програму чи ваш улюблений файл-менеджер для відкриття файлу.</string>
<string name="importOptionApplicationTitle">З іншої програми</string>
<string name="importOptionFilesystemButton">Обрати файл</string>
@@ -216,4 +216,6 @@
<string name="rate_this_app">Оцінити програму</string>
<string name="on_google_play">у Google Play</string>
<string name="report_error">Повідомити про помилку</string>
<string name="exportPassword">Встановіть пароль для захисту експорту (необов\'язково)</string>
<string name="exportPasswordHint">Введіть пароль</string>
</resources>

View File

@@ -0,0 +1,207 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="app_revision_url">https://github.com/TheLastProject/Catima/releases</string>
<string name="action_search">搜尋</string>
<string name="action_add">新增</string>
<string name="noGiftCards">點選 + 按鈕以新增卡片\n或從 ⋮ 選單中匯入卡片</string>
<string name="noMatchingGiftCards">沒有找到任何東西。試試其他關鍵字。</string>
<string name="storeName">名稱</string>
<string name="note">註記</string>
<string name="cardId">卡片 ID</string>
<string name="barcodeType">條碼種類</string>
<string name="barcodeNoBarcode">此卡片沒有可用的條碼</string>
<string name="noBarcode">無條碼</string>
<string name="privacy_policy_popup_text">隱私權政策(某些應用程式商店需要此條目): 我們並不會收集任何資料!任何人都可以檢視我們的原始碼並驗證這點。</string>
<string name="star">新增至收藏</string>
<string name="app_license">公共版權 (Copylefted) 的自由軟體,許可 GPLv3+</string>
<string name="unstar">從收藏中移除</string>
<string name="cancel">取消</string>
<string name="save">儲存</string>
<string name="edit">編輯</string>
<string name="delete">刪除</string>
<string name="confirm">確認</string>
<string name="about">關於</string>
<string name="about_title_fmt">關於 <xliff:g id="app_name">%s</xliff:g></string>
<string name="accept">接受</string>
<string name="addCardTitle">新增卡片</string>
<string name="addFromImage">從圖庫中選擇圖片</string>
<string name="addManually">手動輸入卡片 ID</string>
<string name="all">全部</string>
<string name="balance">餘額</string>
<string name="balanceSentence">餘額: <xliff:g>%s</xliff:g></string>
<string name="balancePoints"><xliff:g>%s</xliff:g></string>
<string name="app_copyright_old">基於 Loyalty Card Keychain\n著作權所有 © 20162020 Branden Archer</string>
<string name="barcode">條碼</string>
<string name="barcodeId">條碼內容</string>
<string name="barcodeImageDescriptionWithType">條碼種類 <xliff:g>%s</xliff:g> 的圖片</string>
<string name="card">卡片</string>
<string name="card_ids_copied">已複製卡片 ID(s)</string>
<string name="cardShortcut">卡片捷徑</string>
<string name="chooseExpiryDate">選擇逾期日期</string>
<string name="chooseImportType">從哪裡匯入資料?</string>
<string name="copy_to_clipboard">已複製 ID 至剪貼簿中</string>
<string name="copy_to_clipboard_multiple_toast">已複製多個卡片 ID 至剪貼簿中</string>
<string name="copy_to_clipboard_toast">已複製卡片 ID 至剪貼簿中</string>
<string name="credits">貢獻者</string>
<string name="currency">幣別</string>
<string name="debug_version_fmt">版本:<xliff:g id="version">%s</xliff:g></string>
<string name="deleteConfirmation">永久刪除此卡片?</string>
<string name="deleteConfirmationGroup">刪除此群組?</string>
<string name="deleteTitle">刪除卡片</string>
<string name="editBarcode">編輯條碼</string>
<string name="editCardTitle">編輯圖片</string>
<string name="enter_group_name">輸入群組名稱</string>
<string name="enterBarcodeInstructions">輸入卡片 ID、選擇條碼種類、或選擇「此卡片沒有可用的條碼」。</string>
<string name="errorReadingImage">無法讀取此圖片</string>
<string name="expiryDate">逾期日期</string>
<string name="expiryStateSentence">逾期於:<xliff:g>%s</xliff:g></string>
<string name="expiryStateSentenceExpired">已逾期:<xliff:g>%s</xliff:g></string>
<string name="exportFailed">無法匯出卡片</string>
<string name="exportFailedTitle">匯出失敗</string>
<string name="exporting">匯出中…</string>
<string name="exportName">匯出</string>
<string name="exportOptionExplanation">資料將寫至您所選的位置。</string>
<string name="exportPassword">透過密碼保護您的匯出檔 (選用)</string>
<string name="exportPasswordHint">輸入密碼</string>
<string name="exportSuccessful">已匯出卡片資訊</string>
<string name="exportSuccessfulTitle">已匯出</string>
<string name="failedOpeningFileManager">請先安裝檔案管理員。</string>
<string name="failedParsingImportUriError">無法讀取匯入 URI</string>
<string name="frontImageDescription">卡片 (正面)</string>
<string name="groups">群組</string>
<string name="groupsList">群組:<xliff:g>%s</xliff:g></string>
<string name="help_translate_this_app">幫助翻譯本程式</string>
<string name="importExport">匯入/匯出</string>
<string name="importFailedTitle">匯入失敗</string>
<string name="importFailed">無法匯入卡片</string>
<string name="importing">匯入中…</string>
<string name="importSuccessfulTitle">已匯入</string>
<string name="intent_import_card_from_url_share_multiple_text">我想要分享些卡片給你</string>
<string name="intent_import_card_from_url_share_text">我想要分享張卡片給你</string>
<string name="leaveWithoutSaveConfirmation">不保存就離開?</string>
<string name="leaveWithoutSaveTitle">離開</string>
<string name="license">許可</string>
<string name="never">永不</string>
<string name="no"></string>
<string name="noBarcodeFound">找不到可用的條碼</string>
<string name="noCardExistsError">無法找到卡片</string>
<string name="noCardIdError">尚未輸入卡片 ID</string>
<string name="noCardsMessage">請先新增卡片</string>
<string name="sort">排列</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_pink_theme">粉紅</string>
<string name="settings_magenta_theme">品紅</string>
<string name="settings_violet_theme">紫色</string>
<string name="settings_blue_theme">藍色</string>
<string name="settings_sky_blue_theme">天空藍</string>
<string name="settings_green_theme">綠色</string>
<string name="settings_grey_theme">灰色</string>
<string name="settings_brown_theme">棕色</string>
<string name="sort_by_name">名稱</string>
<string name="sort_by_most_recently_used">最近使用</string>
<string name="sort_by_expiry">逾期日期</string>
<string name="sort_by_balance">餘額</string>
<string name="reverse">反序</string>
<string name="sort_by">排列方式</string>
<string name="version_history">版本歷史</string>
<string name="source_repository">原始碼版本庫</string>
<string name="on_github">於 GitHub</string>
<string name="and_data_usage">及資料使用</string>
<string name="rate_this_app">替本 App 評分</string>
<string name="on_google_play">於 Google Play</string>
<string name="report_error">回報錯誤</string>
<string name="setBarcodeId">設定條碼內容</string>
<string name="sameAsCardId">與卡片 ID 相同</string>
<string name="photos">圖片</string>
<string name="setFrontImage">設定正面圖片</string>
<string name="setBackImage">設定背面圖片</string>
<string name="removeImage">移除圖片</string>
<string name="takePhoto">拍照</string>
<string name="updateBarcodeQuestionTitle">更新條碼內容?</string>
<string name="yes"></string>
<string name="passwordRequired">請輸入密碼</string>
<string name="turn_flashlight_on">開啟手電筒</string>
<string name="turn_flashlight_off">關閉手電筒</string>
<string name="settings_locale">語言</string>
<string name="settings_system_locale">系統語言</string>
<string name="settings_theme_color">主題顏色</string>
<string name="app_contributors">感謝以下貢獻者: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="privacy_policy">隱私權政策</string>
<plurals name="selectedCardCount">
<item quantity="other">已選取 <xliff:g>%d</xliff:g> 張卡片</item>
</plurals>
<plurals name="deleteCardsTitle">
<item quantity="other">刪除 <xliff:g>%d</xliff:g> 張卡片</item>
</plurals>
<plurals name="deleteCardsConfirmation">
<item quantity="other">永久刪除 <xliff:g>%d</xliff:g> 張卡片?</item>
</plurals>
<plurals name="groupCardCount">
<item quantity="other"><xliff:g>%d</xliff:g> 張卡片</item>
</plurals>
<string name="settings">設定</string>
<string name="share">分享</string>
<string name="settings_system_theme">系統主題</string>
<string name="settings_theme">主題</string>
<string name="thumbnailDescription">卡片縮圖</string>
<string name="noGroupCards">此群組中無任何卡片</string>
<string name="settings_category_title_ui">用戶界面</string>
<string name="settings_max_font_size_scale">最大字體大小</string>
<string name="settings_light_theme">淺色</string>
<string name="settings_dark_theme">深色</string>
<string name="settings_display_barcode_max_brightness">調高條碼介面螢幕亮度</string>
<string name="settings_lock_barcode_orientation">鎖定條碼螢幕方向</string>
<string name="settings_keep_screen_on">螢幕恆亮</string>
<string name="settings_disable_lockscreen_while_viewing_card">防止螢幕鎖定</string>
<string name="importSuccessful">已匯入卡片資訊</string>
<string name="moveUp">往上移動</string>
<string name="moveDown">往下移動</string>
<string name="lockScreen">禁止旋轉</string>
<string name="unlockScreen">允許旋轉</string>
<string name="ok">OK</string>
<string name="sendLabel">送出…</string>
<string name="scanCardBarcode">掃描卡片條碼</string>
<string name="noStoreError">尚未輸入卡片名稱</string>
<string name="importExportHelp">備份您的卡片以將這些卡片移至其他裝置中。</string>
<string name="noExternalStoragePermissionError">在匯入及匯出卡片前,請先允許外部儲存裝置存取權限</string>
<string name="importOptionFilesystemTitle">自檔案系統中匯入</string>
<string name="importOptionFilesystemExplanation">自檔案系統中選取檔案。</string>
<string name="importOptionFilesystemButton">自檔案系統</string>
<string name="importOptionApplicationTitle">使用其他應用程式</string>
<string name="importOptionApplicationButton">使用其他應用程式</string>
<string name="importOptionApplicationExplanation">使用任何應用程式選擇您所想要匯入的檔案。</string>
<string name="app_copyright_fmt">著作權所有 © 2019<xliff:g>%d</xliff:g> Sylvia van Os</string>
<string name="importVoucherVault">自 Voucher Vault 中匯入</string>
<string name="importVoucherVaultMessage">選取您自 Voucher Vault 匯出的 <i>vouchervault.json</i> 檔案以進行匯入。
\n請您先透過 Voucher Vault 進行匯出。</string>
<string name="importStocard">自 Stocard 中匯入</string>
<string name="importStocardMessage">>選取您自 Stocard 匯出的 <i>***-sync.zip</i> 檔案以進行匯入。
\n請您寫封 Email 至 support@stocardapp.com 索取您的資料。</string>
<string name="importLoyaltyCardKeychain">自 Loyalty Card Keychain 中匯入</string>
<string name="importLoyaltyCardKeychainMessage">選取您自 Loyalty Card Keychain <i>LoyaltyCardKeychain.csv</i> 檔案以進行匯入。
\n請您先透過 Loyalty Card Keychain 的匯入/匯出選單進行匯出。</string>
<string name="importFidme">自 FidMe 匯入</string>
<string name="importFidmeMessage">選取您自 FidMe 匯出的<i>fidme-export-request-xxxxxx.zip</i> 檔案以進行匯入,並手動選擇條碼種類。
\n請您先透過您的 FidMe 個人檔案選取『Data Protection』並選擇『Extract my data』。</string>
<string name="importCatima">自 Catima 匯入</string>
<string name="importCatimaMessage">選取您自 Catima 匯出的 <i>catima.zip</i> 檔案以進行匯入。
\n您可透過其他裝置的 Catima 程式中的匯入/匯出選單進行匯出。</string>
<string name="points"></string>
<string name="parsingBalanceFailed"><xliff:g>%s</xliff:g> 似乎不是個可用的餘額數值。</string>
<string name="app_revision_fmt">修訂版本資訊:<xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries">第三方自由函示庫:<xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">第三方自由資源:<xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">選擇條碼</string>
<string name="noGroups">請先點選 + 加號按鈕新增群組。</string>
<string name="moveBarcodeToTopOfScreen">將條碼移至螢幕上方</string>
<string name="moveBarcodeToCenterOfScreen">將條碼移至螢幕中央</string>
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
<string name="unsupportedBarcodeType">尚支援此條碼種類,但未來版本的應用程式可能會支援此條碼種類。</string>
<string name="wrongValueForBarcodeType">條碼內容不適用於此條碼種類</string>
<string name="backImageDescription">卡片 (背面)</string>
<string name="updateBarcodeQuestionText">您已更新了條碼 ID是否要更新條碼內容以匹配此 ID</string>
<string name="failedGeneratingShareURL">無法建立可分享的 URL請回報此錯誤。</string>
<string name="toggleMoreInfo">切換顯示更多內容</string>
<string name="swipeToSwitchImages">滑動或長按以切換圖片</string>
<string name="starImage">收藏標示</string>
</resources>

View File

@@ -40,15 +40,20 @@
<string-array name="locale_values">
<item />
<item>bg</item>
<item>bn-rIN</item>
<item>bs</item>
<item>cs</item>
<item>da</item>
<item>de</item>
<item>el-rGR</item>
<item>eo</item>
<item>es-rAR</item>
<item>es</item>
<item>es-rAR</item>
<item>fi</item>
<item>fr</item>
<item>he-rIL</item>
<item>hr</item>
<item>in-rID</item>
<item>it</item>
<item>ja</item>
<item>ko</item>
@@ -57,11 +62,15 @@
<item>nl</item>
<item>oc</item>
<item>pl</item>
<item>pt</item>
<item>ro-rRO</item>
<item>ru</item>
<item>sk</item>
<item>sl</item>
<item>sv</item>
<item>tr</item>
<item>uk</item>
<item>zh-TW</item>
<item>zh-rCN</item>
</string-array>
@@ -82,4 +91,4 @@
<!-- Constraints -->
<integer name="settings_max_font_size_scale_pct_min">50</integer>
<integer name="settings_max_font_size_scale_pct_max">200</integer>
</resources>
</resources>

View File

@@ -2,7 +2,6 @@ package protect.card_locker;
import android.app.Activity;
import android.content.Intent;
import android.os.Looper;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
@@ -13,16 +12,19 @@ import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.robolectric.Shadows.shadowOf;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 23)
public class BarcodeSelectorActivityTest {
@Test
public void emptyStateTest()
{
public void emptyStateTest() {
ActivityController activityController = Robolectric.buildActivity(BarcodeSelectorActivity.class).create();
activityController.start();
activityController.resume();
@@ -41,8 +43,7 @@ public class BarcodeSelectorActivityTest {
}
@Test
public void nonEmptyStateTest() throws InterruptedException
{
public void nonEmptyStateTest() throws InterruptedException {
ActivityController activityController = Robolectric.buildActivity(BarcodeSelectorActivity.class).create();
activityController.start();
activityController.resume();
@@ -54,11 +55,12 @@ public class BarcodeSelectorActivityTest {
cardId.setText("abcdefg");
shadowOf(Looper.getMainLooper()).idle();
// Run the delayed Handler
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
// Button should be visible and enabled
assertEquals(View.VISIBLE, noBarcodeButton.getVisibility());
assertEquals(true, noBarcodeButton.isEnabled());
assertTrue(noBarcodeButton.isEnabled());
// Clicking button should create "empty" barcode
activity.findViewById(R.id.noBarcode).performClick();
@@ -70,8 +72,7 @@ public class BarcodeSelectorActivityTest {
}
@Test
public void nonEmptyToEmptyStateTest() throws InterruptedException
{
public void nonEmptyToEmptyStateTest() throws InterruptedException {
ActivityController activityController = Robolectric.buildActivity(BarcodeSelectorActivity.class).create();
activityController.start();
activityController.resume();
@@ -83,18 +84,20 @@ public class BarcodeSelectorActivityTest {
cardId.setText("abcdefg");
shadowOf(Looper.getMainLooper()).idle();
// Run the delayed Handler
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
// Button should be visible and enabled
assertEquals(View.VISIBLE, noBarcodeButton.getVisibility());
assertEquals(true, noBarcodeButton.isEnabled());
assertTrue(noBarcodeButton.isEnabled());
cardId.setText("");
shadowOf(Looper.getMainLooper()).idle();
// Run the delayed Handler
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
// Button should be visible but disabled
assertEquals(View.VISIBLE, noBarcodeButton.getVisibility());
assertEquals(false, noBarcodeButton.isEnabled());
assertFalse(noBarcodeButton.isEnabled());
}
}

View File

@@ -503,6 +503,8 @@ public class DatabaseTest
assertEquals(BarcodeFormat.UPC_A, card.barcodeType.format());
assertEquals(null, card.headerColor);
assertEquals(0, card.starStatus);
assertEquals(0,card.lastUsed);
assertEquals(100,card.zoomLevel);
// Determine that the entries are queryable and the fields are correct
LoyaltyCard card2 = db.getLoyaltyCard(newCardId2);
@@ -516,6 +518,8 @@ public class DatabaseTest
assertEquals(null, card2.barcodeType); // Empty string should've become null
assertEquals(null, card2.headerColor);
assertEquals(0, card2.starStatus);
assertEquals(0,card2.lastUsed);
assertEquals(100,card2.zoomLevel);
database.close();
}

View File

@@ -7,6 +7,7 @@ import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.os.Environment;
import android.os.Looper;
import android.util.DisplayMetrics;
import com.google.zxing.BarcodeFormat;
@@ -20,6 +21,7 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
import org.robolectric.shadows.ShadowLog;
import org.robolectric.shadows.ShadowLooper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -33,6 +35,7 @@ import java.io.OutputStreamWriter;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -42,6 +45,8 @@ import java.util.HashMap;
import java.util.List;
import androidx.core.content.res.ResourcesCompat;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult;
import protect.card_locker.importexport.MultiFormatExporter;
@@ -51,11 +56,11 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.robolectric.Shadows.shadowOf;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 23)
public class ImportExportTest
{
public class ImportExportTest {
private Activity activity;
private DBHelper db;
private long nowMs;
@@ -66,8 +71,7 @@ public class ImportExportTest
private final CatimaBarcode BARCODE_TYPE = CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A);
@Before
public void setUp()
{
public void setUp() {
ShadowLog.stream = System.out;
activity = Robolectric.setupActivity(MainActivity.class);
@@ -75,20 +79,19 @@ public class ImportExportTest
nowMs = System.currentTimeMillis();
Calendar lastYear = Calendar.getInstance();
lastYear.set(Calendar.YEAR, lastYear.get(Calendar.YEAR)-1);
lastYear.set(Calendar.YEAR, lastYear.get(Calendar.YEAR) - 1);
lastYearMs = lastYear.getTimeInMillis();
}
/**
* Add the given number of cards, each with
* an index in the store name.
*
* @param cardsToAdd
*/
private void addLoyaltyCards(int cardsToAdd)
{
private void addLoyaltyCards(int cardsToAdd) {
// Add in reverse order to test sorting
for(int index = cardsToAdd; index > 0; index--)
{
for (int index = cardsToAdd; index > 0; index--) {
String storeName = String.format("store, \"%4d", index);
String note = String.format("note, \"%4d", index);
long id = db.insertLoyaltyCard(storeName, note, null, new BigDecimal(String.valueOf(index)), null, BARCODE_DATA, null, BARCODE_TYPE, index, 0, null);
@@ -99,20 +102,17 @@ public class ImportExportTest
assertEquals(cardsToAdd, db.getLoyaltyCardCount());
}
private void addLoyaltyCardsFiveStarred()
{
private void addLoyaltyCardsFiveStarred() {
int cardsToAdd = 9;
// Add in reverse order to test sorting
for(int index = cardsToAdd; index > 4; index--)
{
for (int index = cardsToAdd; index > 4; index--) {
String storeName = String.format("store, \"%4d", index);
String note = String.format("note, \"%4d", index);
long id = db.insertLoyaltyCard(storeName, note, null, new BigDecimal(String.valueOf(index)), null, BARCODE_DATA, null, BARCODE_TYPE, index, 1, null);
boolean result = (id != -1);
assertTrue(result);
}
for(int index = cardsToAdd-5; index > 0; index--)
{
for (int index = cardsToAdd - 5; index > 0; index--) {
String storeName = String.format("store, \"%4d", index);
String note = String.format("note, \"%4d", index);
//if index is even
@@ -124,8 +124,7 @@ public class ImportExportTest
}
@Test
public void addLoyaltyCardsWithExpiryNeverPastTodayFuture()
{
public void addLoyaltyCardsWithExpiryNeverPastTodayFuture() {
long id = db.insertLoyaltyCard("No Expiry", "", null, new BigDecimal("0"), null, BARCODE_DATA, null, BARCODE_TYPE, 0, 0, null);
boolean result = (id != -1);
assertTrue(result);
@@ -165,8 +164,8 @@ public class ImportExportTest
card = db.getLoyaltyCard((int) id);
assertEquals("Today", card.store);
assertEquals("", card.note);
assertTrue(card.expiry.before(new Date(new Date().getTime()+86400)));
assertTrue(card.expiry.after(new Date(new Date().getTime()-86400)));
assertTrue(card.expiry.before(new Date(new Date().getTime() + 86400)));
assertTrue(card.expiry.after(new Date(new Date().getTime() - 86400)));
assertEquals(new BigDecimal("0"), card.balance);
assertEquals(null, card.balanceType);
assertEquals(BARCODE_DATA, card.cardId);
@@ -184,7 +183,7 @@ public class ImportExportTest
card = db.getLoyaltyCard((int) id);
assertEquals("Future", card.store);
assertEquals("", card.note);
assertTrue(card.expiry.after(new Date(new Date().getTime()+86400)));
assertTrue(card.expiry.after(new Date(new Date().getTime() + 86400)));
assertEquals(new BigDecimal("0"), card.balance);
assertEquals(null, card.balanceType);
assertEquals(BARCODE_DATA, card.cardId);
@@ -196,11 +195,9 @@ public class ImportExportTest
assertEquals(4, db.getLoyaltyCardCount());
}
private void addGroups(int groupsToAdd)
{
private void addGroups(int groupsToAdd) {
// Add in reverse order to test sorting
for(int index = groupsToAdd; index > 0; index--)
{
for (int index = groupsToAdd; index > 0; index--) {
String groupName = String.format("group, \"%4d", index);
long id = db.insertGroup(groupName);
boolean result = (id != -1);
@@ -215,13 +212,11 @@ public class ImportExportTest
* specified in addLoyaltyCards(), and are in sequential order
* where the smallest card's index is 1
*/
private void checkLoyaltyCards()
{
private void checkLoyaltyCards() {
Cursor cursor = db.getLoyaltyCardCursor();
int index = 1;
while(cursor.moveToNext())
{
while (cursor.moveToNext()) {
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
String expectedStore = String.format("store, \"%4d", index);
@@ -248,13 +243,11 @@ public class ImportExportTest
* specified in addLoyaltyCardsSomeStarred(), and are in sequential order
* with starred ones first
*/
private void checkLoyaltyCardsFiveStarred()
{
Cursor cursor = db.getLoyaltyCardCursor();
int index = 5;
private void checkLoyaltyCardsFiveStarred() {
Cursor cursor = db.getLoyaltyCardCursor();
int index = 5;
while(index<10)
{
while (index < 10) {
cursor.moveToNext();
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
@@ -276,26 +269,25 @@ public class ImportExportTest
}
index = 1;
while(cursor.moveToNext() && index<5)
{
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
while (cursor.moveToNext() && index < 5) {
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
String expectedStore = String.format("store, \"%4d", index);
String expectedNote = String.format("note, \"%4d", index);
String expectedStore = String.format("store, \"%4d", index);
String expectedNote = String.format("note, \"%4d", index);
assertEquals(expectedStore, card.store);
assertEquals(expectedNote, card.note);
assertEquals(null, card.expiry);
assertEquals(new BigDecimal(String.valueOf(index)), card.balance);
assertEquals(null, card.balanceType);
assertEquals(BARCODE_DATA, card.cardId);
assertEquals(null, card.barcodeId);
assertEquals(BARCODE_TYPE.format(), card.barcodeType.format());
assertEquals(Integer.valueOf(index), card.headerColor);
assertEquals(0, card.starStatus);
assertEquals(expectedStore, card.store);
assertEquals(expectedNote, card.note);
assertEquals(null, card.expiry);
assertEquals(new BigDecimal(String.valueOf(index)), card.balance);
assertEquals(null, card.balanceType);
assertEquals(BARCODE_DATA, card.cardId);
assertEquals(null, card.barcodeId);
assertEquals(BARCODE_TYPE.format(), card.barcodeType.format());
assertEquals(Integer.valueOf(index), card.headerColor);
assertEquals(0, card.starStatus);
index++;
}
index++;
}
cursor.close();
}
@@ -305,13 +297,11 @@ public class ImportExportTest
* specified in addGroups(), and are in sequential order
* where the smallest group's index is 1
*/
private void checkGroups()
{
private void checkGroups() {
Cursor cursor = db.getGroupCursor();
int index = db.getGroupCount();
while(cursor.moveToNext())
{
while (cursor.moveToNext()) {
Group group = Group.toGroup(cursor);
String expectedGroupName = String.format("group, \"%4d", index);
@@ -324,8 +314,7 @@ public class ImportExportTest
}
@Test
public void multipleCardsExportImport() throws IOException
{
public void multipleCardsExportImport() throws IOException {
final int NUM_CARDS = 10;
addLoyaltyCards(NUM_CARDS);
@@ -334,7 +323,7 @@ public class ImportExportTest
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima,null);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
outStream.close();
@@ -354,18 +343,17 @@ public class ImportExportTest
TestHelpers.getEmptyDb(activity);
}
public void multipleCardsExportImportPasswordProtected() throws IOException
{
public void multipleCardsExportImportPasswordProtected() throws IOException {
final int NUM_CARDS = 10;
List<char[]> passwords = Arrays.asList(null, "123456789".toCharArray());
for(char[] password : passwords){
for (char[] password : passwords) {
addLoyaltyCards(NUM_CARDS);
ByteArrayOutputStream outData = new ByteArrayOutputStream();
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima,password);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima, password);
assertEquals(ImportExportResult.Success, result);
outStream.close();
@@ -388,8 +376,7 @@ public class ImportExportTest
}
@Test
public void multipleCardsExportImportSomeStarred() throws IOException
{
public void multipleCardsExportImportSomeStarred() throws IOException {
final int NUM_CARDS = 9;
addLoyaltyCardsFiveStarred();
@@ -398,7 +385,7 @@ public class ImportExportTest
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima,null);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
outStream.close();
@@ -418,8 +405,7 @@ public class ImportExportTest
TestHelpers.getEmptyDb(activity);
}
private List<String> groupsToGroupNames(List<Group> groups)
{
private List<String> groupsToGroupNames(List<Group> groups) {
List<String> groupNames = new ArrayList<>();
for (Group group : groups) {
@@ -430,8 +416,7 @@ public class ImportExportTest
}
@Test
public void multipleCardsExportImportWithGroups() throws IOException
{
public void multipleCardsExportImportWithGroups() throws IOException {
final int NUM_CARDS = 10;
final int NUM_GROUPS = 3;
@@ -471,7 +456,7 @@ public class ImportExportTest
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima,null);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
outStream.close();
@@ -505,8 +490,7 @@ public class ImportExportTest
}
@Test
public void importExistingCardsNotReplace() throws IOException
{
public void importExistingCardsNotReplace() throws IOException {
final int NUM_CARDS = 10;
addLoyaltyCards(NUM_CARDS);
@@ -515,7 +499,7 @@ public class ImportExportTest
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export into CSV data
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima,null);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
outStream.close();
@@ -534,19 +518,17 @@ public class ImportExportTest
}
@Test
public void corruptedImportNothingSaved() throws IOException
{
public void corruptedImportNothingSaved() throws IOException {
final int NUM_CARDS = 10;
for(DataFormat format : DataFormat.values())
{
for (DataFormat format : DataFormat.values()) {
addLoyaltyCards(NUM_CARDS);
ByteArrayOutputStream outData = new ByteArrayOutputStream();
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima,null);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
TestHelpers.getEmptyDb(activity);
@@ -569,20 +551,17 @@ public class ImportExportTest
}
}
class TestTaskCompleteListener implements ImportExportTask.TaskCompleteListener
{
class TestTaskCompleteListener implements ImportExportTask.TaskCompleteListener {
ImportExportResult result;
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat)
{
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
this.result = result;
}
}
@Test
@LooperMode(LooperMode.Mode.LEGACY)
public void useImportExportTask() throws FileNotFoundException
{
@LooperMode(LooperMode.Mode.PAUSED)
public void useImportExportTask() throws FileNotFoundException {
final int NUM_CARDS = 10;
final File sdcardDir = Environment.getExternalStorageDirectory();
@@ -595,11 +574,15 @@ public class ImportExportTest
// Export to the file
final String password = "123456789";
FileOutputStream fileOutputStream = new FileOutputStream(exportFile);
ImportExportTask task = new ImportExportTask(activity, DataFormat.Catima, fileOutputStream,password.toCharArray(), listener);
task.execute();
ImportExportTask task = new ImportExportTask(activity, DataFormat.Catima, fileOutputStream, password.toCharArray(), listener);
TaskHandler mTasks = new TaskHandler();
mTasks.executeTask(TaskHandler.TYPE.EXPORT, task);
// Actually run the task to completion
Robolectric.flushBackgroundThreadScheduler();
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, false, false, true);
shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(5000));
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
// Check that the listener was executed
assertNotNull(listener.result);
@@ -614,10 +597,13 @@ public class ImportExportTest
FileInputStream fileStream = new FileInputStream(exportFile);
task = new ImportExportTask(activity, DataFormat.Catima, fileStream, password.toCharArray(), listener);
task.execute();
mTasks.executeTask(TaskHandler.TYPE.IMPORT, task);
// Actually run the task to completion
Robolectric.flushBackgroundThreadScheduler();
// I am CONVINCED there must be a better way than to wait on this Queue with a flush.
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, false, false, true);
shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(5000));
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
// Check that the listener was executed
assertNotNull(listener.result);
@@ -632,8 +618,7 @@ public class ImportExportTest
}
@Test
public void importWithoutColorsV1() throws IOException
{
public void importWithoutColorsV1() throws IOException {
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
DBHelper.LoyaltyCardDbIds.STORE + "," +
@@ -668,8 +653,7 @@ public class ImportExportTest
}
@Test
public void importWithoutNullColorsV1() throws IOException
{
public void importWithoutNullColorsV1() throws IOException {
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
DBHelper.LoyaltyCardDbIds.STORE + "," +
@@ -706,8 +690,7 @@ public class ImportExportTest
}
@Test
public void importWithoutInvalidColorsV1() throws IOException
{
public void importWithoutInvalidColorsV1() throws IOException {
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
DBHelper.LoyaltyCardDbIds.STORE + "," +
@@ -731,8 +714,7 @@ public class ImportExportTest
}
@Test
public void importWithNoBarcodeTypeV1() throws IOException
{
public void importWithNoBarcodeTypeV1() throws IOException {
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
DBHelper.LoyaltyCardDbIds.STORE + "," +
@@ -769,8 +751,7 @@ public class ImportExportTest
}
@Test
public void importWithStarredFieldV1() throws IOException
{
public void importWithStarredFieldV1() throws IOException {
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
DBHelper.LoyaltyCardDbIds.STORE + "," +
@@ -807,8 +788,7 @@ public class ImportExportTest
}
@Test
public void importWithNoStarredFieldV1() throws IOException
{
public void importWithNoStarredFieldV1() throws IOException {
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
DBHelper.LoyaltyCardDbIds.STORE + "," +
@@ -845,8 +825,7 @@ public class ImportExportTest
}
@Test
public void importWithInvalidStarFieldV1() throws IOException
{
public void importWithInvalidStarFieldV1() throws IOException {
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
DBHelper.LoyaltyCardDbIds.STORE + "," +
@@ -902,8 +881,7 @@ public class ImportExportTest
}
@Test
public void exportImportV2Zip() throws FileNotFoundException
{
public void exportImportV2Zip() throws FileNotFoundException {
// Prepare images
BitmapDrawable launcher = (BitmapDrawable) ResourcesCompat.getDrawableForDensity(activity.getResources(), R.mipmap.ic_launcher, DisplayMetrics.DENSITY_XXXHIGH, activity.getTheme());
BitmapDrawable roundLauncher = (BitmapDrawable) ResourcesCompat.getDrawableForDensity(activity.getResources(), R.mipmap.ic_launcher_round, DisplayMetrics.DENSITY_XXXHIGH, activity.getTheme());
@@ -935,7 +913,7 @@ public class ImportExportTest
// Export everything
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
MultiFormatExporter.exportData(activity.getApplicationContext(), db, outputStream, DataFormat.Catima,null);
MultiFormatExporter.exportData(activity.getApplicationContext(), db, outputStream, DataFormat.Catima, null);
// Wipe database
TestHelpers.getEmptyDb(activity);
@@ -959,25 +937,25 @@ public class ImportExportTest
assertEquals(loyaltyCard.cardId, dbLoyaltyCard.cardId);
assertEquals(loyaltyCard.barcodeId, dbLoyaltyCard.barcodeId);
assertEquals(loyaltyCard.starStatus, dbLoyaltyCard.starStatus);
assertEquals(loyaltyCard.barcodeType != null ? loyaltyCard.barcodeType.format() : null, dbLoyaltyCard.barcodeType != null? dbLoyaltyCard.barcodeType.format() : null);
assertEquals(loyaltyCard.barcodeType != null ? loyaltyCard.barcodeType.format() : null, dbLoyaltyCard.barcodeType != null ? dbLoyaltyCard.barcodeType.format() : null);
assertEquals(loyaltyCard.balanceType, dbLoyaltyCard.balanceType);
assertEquals(loyaltyCard.headerColor, dbLoyaltyCard.headerColor);
List<Group> emptyGroup = new ArrayList<>();
assertEquals(
groupsToGroupNames(
(List<Group>) Utils.mapGetOrDefault(
loyaltyCardGroups,
loyaltyCardID,
emptyGroup
groupsToGroupNames(
(List<Group>) Utils.mapGetOrDefault(
loyaltyCardGroups,
loyaltyCardID,
emptyGroup
)
),
groupsToGroupNames(
db.getLoyaltyCardGroups(
loyaltyCardID
)
)
),
groupsToGroupNames(
db.getLoyaltyCardGroups(
loyaltyCardID
)
)
);
Bitmap expectedFrontImage = loyaltyCardFrontImages.get(loyaltyCardID);
@@ -1000,8 +978,7 @@ public class ImportExportTest
}
@Test
public void importV2CSV()
{
public void importV2CSV() {
String csvText = "2\n" +
"\n" +
"_id\n" +

View File

@@ -11,4 +11,4 @@
- QR_CODE
- UPC_A
- Vygenerované čárové kódy jsou větší, snadněji se skenují ze skenovacího zařízení.
- Vygenerované čárové kódy jsou větší, snadněji se skenují ze skenovacího zařízení

View File

@@ -1,4 +1,4 @@
- Překlad do nizozemštiny
- Umožnit editaci pole se jménem po přidání věrnostní karty
- Přidat nepovinné pole pro poznámku
- Vyřešit všechny problémy identifikované nástrojem FindBugs a vyžadovat vyřešení všech problémů FindBugs před přijetím požadavku na stažení.
- Vyřešit všechny problémy identifikované nástrojem FindBugs a vyžadovat vyřešení všech problémů FindBugs před přijetím požadavku na stažení

View File

@@ -1,4 +1,5 @@
Počínaje touto verzí již není systém Android 4.4 podporován. Pokud chcete používat Catimu v systému Android 4.4, použijte verzi 2.6.1.
- Vylepšená podpora systému Android 12
- Vylepšená obrazovka o aplikaci
- Vylepšená obrazovka s informacemi
- Vyhledávání nyní ignoruje diakritiku

View File

@@ -0,0 +1,3 @@
- Oprava nefunkčního přecházení mezi skupinami v prázdné skupině
- Povolení exportu chráněného heslem
- Zlepšení využití prostoru pro kódy QR

View File

@@ -1,3 +1,5 @@
- Fix swiping between groups not working on an empty group
- Allow password-protecting exports
- Improve usage of space for QR codes
- Improve usage of space for QR codes
- Save the last used zoom level per card
- Fix a crash when swiping right after a tap

View File

@@ -1,2 +1,5 @@
- Gruplar arasında kaydırmanın boş bir grup üzerinde çalışmaması düzeltildi
- Parola korumalı dışa aktarmalara izin ver
- QR kodları için boşluk kullanımını iyileştir
- Kart başına en son kullanılan yakınlaştırma seviyesini kaydet
- Dokunduktan sonra sağa kaydırmada çökmeyi düzelt

View File

@@ -20,4 +20,3 @@
Спростіть своє життя та покупки, і ніколи більше не втрачайте паперову квитанцію, подарункову картку магазину або квиток на літак.
Візьміть із собою всі свої нагороди та бонуси та економте на ходу.