Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
Changes:

- Support adding a front and back photo to each card
- Support importing password-protected zip files
- Support importing from Stocard (Beta)
- Fix useless whitespace in notes from Fidme import

## v1.14.1 (2021-06-14)
Expand Down
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ dependencies {
implementation 'com.jaredrummler:colorpicker:1.1.0'
implementation 'com.google.guava:guava:30.1.1-jre'
implementation 'com.github.invissvenska:NumberPickerPreference:1.0.2'
implementation 'net.lingala.zip4j:zip4j:2.8.0'

// SpotBugs
implementation 'io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0'
Expand Down
9 changes: 4 additions & 5 deletions app/src/main/java/protect/card_locker/DBHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;

import com.google.zxing.BarcodeFormat;

Expand Down Expand Up @@ -275,7 +274,7 @@ public long insertLoyaltyCard(final String store, final String note, final Date
return newId;
}

public boolean insertLoyaltyCard(final SQLiteDatabase db, final String store,
public long insertLoyaltyCard(final SQLiteDatabase db, final String store,
final String note, final Date expiry, final BigDecimal balance,
final Currency balanceType, final String cardId,
final String barcodeId, final BarcodeFormat barcodeType,
Expand All @@ -293,10 +292,10 @@ public boolean insertLoyaltyCard(final SQLiteDatabase db, final String store,
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus);
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
return (newId != -1);
return newId;
}

public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id, final String store,
public long insertLoyaltyCard(final SQLiteDatabase db, final int id, final String store,
final String note, final Date expiry, final BigDecimal balance,
final Currency balanceType, final String cardId,
final String barcodeId, final BarcodeFormat barcodeType,
Expand All @@ -315,7 +314,7 @@ public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id, final St
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus);
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
return (newId != -1);
return newId;
}

public boolean updateLoyaltyCard(final int id, final String store, final String note,
Expand Down
144 changes: 96 additions & 48 deletions app/src/main/java/protect/card_locker/ImportExportActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.InputType;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import java.io.File;
Expand All @@ -20,13 +22,17 @@
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult;

public class ImportExportActivity extends AppCompatActivity
{
Expand Down Expand Up @@ -115,9 +121,22 @@ public void onClick(View v)
}

private void chooseImportType(Intent baseIntent) {
List<CharSequence> betaImportOptions = new ArrayList<>();
betaImportOptions.add("Fidme");
betaImportOptions.add("Stocard");
List<CharSequence> importOptions = new ArrayList<>();

for (String importOption : getResources().getStringArray(R.array.import_types_array)) {
if (betaImportOptions.contains(importOption)) {
importOption = importOption + " (BETA)";
}

importOptions.add(importOption);
}

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.chooseImportType)
.setItems(R.array.import_types_array, (dialog, which) -> {
.setItems(importOptions.toArray(new CharSequence[importOptions.size()]), (dialog, which) -> {
switch (which) {
// Catima
case 0:
Expand All @@ -137,8 +156,14 @@ private void chooseImportType(Intent baseIntent) {
importAlertMessage = getString(R.string.importLoyaltyCardKeychainMessage);
importDataFormat = DataFormat.Catima;
break;
// Voucher Vault
// Stocard
case 3:
importAlertTitle = getString(R.string.importStocard);
importAlertMessage = getString(R.string.importStocardMessage);
importDataFormat = DataFormat.Stocard;
break;
// Voucher Vault
case 4:
importAlertTitle = getString(R.string.importVoucherVault);
importAlertMessage = getString(R.string.importVoucherVaultMessage);
importDataFormat = DataFormat.VoucherVault;
Expand All @@ -162,19 +187,19 @@ public void onClick(DialogInterface dialog, int which) {
builder.show();
}

private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat)
private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password)
{
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
@Override
public void onTaskComplete(boolean success)
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat)
{
onImportComplete(success, targetUri);
onImportComplete(result, targetUri, dataFormat);
}
};

importExporter = new ImportExportTask(ImportExportActivity.this,
dataFormat, target, listener);
dataFormat, target, password, listener);
importExporter.execute();
}

Expand All @@ -183,9 +208,9 @@ private void startExport(final OutputStream target, final Uri targetUri)
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
@Override
public void onTaskComplete(boolean success)
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat)
{
onExportComplete(success, targetUri);
onExportComplete(result, targetUri);
}
};

Expand All @@ -210,7 +235,7 @@ public void onRequestPermissionsResult(int requestCode, String permissions[], in
}
}

if(success == false)
if(!success)
{
// External storage permission rejected, inform user that
// import/export is prevented
Expand Down Expand Up @@ -245,20 +270,43 @@ public boolean onOptionsItemSelected(MenuItem item)
return super.onOptionsItemSelected(item);
}

private void onImportComplete(boolean success, Uri path)
{
private void retryWithPassword(DataFormat dataFormat, Uri uri) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.passwordRequired);

final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
builder.setView(input);

builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
activityResultParser(IMPORT, RESULT_OK, uri, input.getText().toString().toCharArray());
});
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());

if(success)
builder.show();
}

private void onImportComplete(ImportExportResult result, Uri path, DataFormat dataFormat) {
if (result == ImportExportResult.BadPassword) {
retryWithPassword(dataFormat, path);
return;
}

AlertDialog.Builder builder = new AlertDialog.Builder(this);

int messageId;

if (result == ImportExportResult.Success)
{
builder.setTitle(R.string.importSuccessfulTitle);
messageId = R.string.importSuccessful;
}
else
{
builder.setTitle(R.string.importFailedTitle);
messageId = R.string.importFailed;
}

int messageId = success ? R.string.importSuccessful : R.string.importFailed;
final String message = getResources().getString(messageId);

builder.setMessage(message);
Expand All @@ -274,53 +322,44 @@ public void onClick(DialogInterface dialog, int which)
builder.create().show();
}

private void onExportComplete(boolean success, final Uri path)
private void onExportComplete(ImportExportResult result, final Uri path)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);

if(success)
int messageId;

if(result == ImportExportResult.Success)
{
builder.setTitle(R.string.exportSuccessfulTitle);
messageId = R.string.exportSuccessful;
}
else
{
builder.setTitle(R.string.exportFailedTitle);
messageId = R.string.exportFailed;
}

int messageId = success ? R.string.exportSuccessful : R.string.exportFailed;
final String message = getResources().getString(messageId);

builder.setMessage(message);
builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
});
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());

if(success)
if(result == ImportExportResult.Success)
{
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel);

builder.setPositiveButton(sendLabel, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, path);
sendIntent.setType("text/csv");
builder.setPositiveButton(sendLabel, (dialog, which) -> {
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, path);
sendIntent.setType("text/csv");

// set flag to give temporary permission to external app to use the FileProvider
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// set flag to give temporary permission to external app to use the FileProvider
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
sendLabel));
ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
sendLabel));

dialog.dismiss();
}
dialog.dismiss();
});
}

Expand All @@ -340,18 +379,13 @@ private void chooseFileWithIntent(Intent intent, int requestCode)
}
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);

private void activityResultParser(int requestCode, int resultCode, Uri uri, char[] password) {
if (resultCode != RESULT_OK)
{
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
return;
}

Uri uri = data.getData();
if(uri == null)
{
Log.e(TAG, "Activity returned a NULL URI");
Expand Down Expand Up @@ -389,20 +423,34 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data)

Log.e(TAG, "Starting file import with: " + uri.toString());

startImport(reader, uri, importDataFormat);
startImport(reader, uri, importDataFormat, password);
}
}
catch(FileNotFoundException e)
{
Log.e(TAG, "Failed to import/export file: " + uri.toString(), e);
if (requestCode == CHOOSE_EXPORT_LOCATION)
{
onExportComplete(false, uri);
onExportComplete(ImportExportResult.GenericFailure, uri);
}
else
{
onImportComplete(false, uri);
onImportComplete(ImportExportResult.GenericFailure, uri, importDataFormat);
}
}
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);

if(data == null)
{
Log.e(TAG, "Activity returned NULL data");
return;
}

activityResultParser(requestCode, resultCode, data.getData(), null);
}
}
Loading