Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb979bfbec | ||
|
|
feabe353b0 | ||
|
|
7987932100 | ||
|
|
e496c69e15 | ||
|
|
e0300f8f21 | ||
|
|
64cbcb2ef7 | ||
|
|
7e24f02a73 | ||
|
|
ed89eab782 | ||
|
|
17863e8920 | ||
|
|
6e82e6fc5d | ||
|
|
64b1103f13 | ||
|
|
e4274df941 | ||
|
|
c786b4b5a7 | ||
|
|
95405270cb | ||
|
|
9ef8eb2934 | ||
|
|
4f70b06edb | ||
|
|
5677aa2b38 | ||
|
|
44997e1bbd | ||
|
|
a96c569314 | ||
|
|
5545220d53 | ||
|
|
4f47ee30ba | ||
|
|
4f9f318960 | ||
|
|
5dcfcf0ad0 | ||
|
|
eb809974c4 | ||
|
|
815b037be7 | ||
|
|
3eff150835 | ||
|
|
e191e8bbe6 | ||
|
|
5aaf135325 | ||
|
|
6fd4f617e5 | ||
|
|
621e4ce162 | ||
|
|
bf07e8935f | ||
|
|
e69d6a453b | ||
|
|
579c2fc640 | ||
|
|
ba870481dd | ||
|
|
0c10936e75 | ||
|
|
26cf4250ad | ||
|
|
a8d5f38d5c | ||
|
|
5e1bac4bcf | ||
|
|
6df8b7b1ea | ||
|
|
964c603405 | ||
|
|
f64eea6470 | ||
|
|
aa97ad49d2 | ||
|
|
6221da72f8 | ||
|
|
fe8ec38bf3 | ||
|
|
7324353d74 | ||
|
|
c5f0ee3a66 | ||
|
|
a8b4b25afb | ||
|
|
9744ede674 | ||
|
|
43505d427d | ||
|
|
65e54c63ef | ||
|
|
31b306d432 | ||
|
|
25bd1de09d | ||
|
|
c82e0d82a9 | ||
|
|
6625488d84 | ||
|
|
a1f632ace3 | ||
|
|
b9ea7fd1ed | ||
|
|
8dff8d392e | ||
|
|
b10d50d5a0 | ||
|
|
0b54d87f4f | ||
|
|
a9568d6adb | ||
|
|
18c5aa4707 | ||
|
|
f53c61bbec | ||
|
|
25c9c67ca2 | ||
|
|
2efbf664c9 | ||
|
|
44023969a7 | ||
|
|
0e5216b010 | ||
|
|
f258506936 | ||
|
|
8bad342374 | ||
|
|
476a219bec | ||
|
|
24e19e26ba | ||
|
|
3cb16ae3e9 | ||
|
|
c7ddf957fa | ||
|
|
0de8cd93ad | ||
|
|
614e5bac2d | ||
|
|
e76bd9363c | ||
|
|
97c508c920 | ||
|
|
0f178c1cac | ||
|
|
8eab852e1a | ||
|
|
539f8846d8 | ||
|
|
fb239b6974 | ||
|
|
85b553e17b | ||
|
|
f085f1e9e6 | ||
|
|
56b387c725 | ||
|
|
daf0cdaa71 | ||
|
|
6a7bebdbcd | ||
|
|
7f72c3bcaf | ||
|
|
52bff53756 | ||
|
|
73743b7f1e | ||
|
|
acfcd3d2d2 | ||
|
|
24e8d12b73 | ||
|
|
28068dcddc | ||
|
|
3e5ab76636 | ||
|
|
3dceec8ec0 | ||
|
|
0cc409d087 | ||
|
|
346acfa3f5 | ||
|
|
8d48da431e | ||
|
|
b913fad847 | ||
|
|
c900642f8e | ||
|
|
c7d0da9e20 | ||
|
|
f66b368cc2 | ||
|
|
4dad96472f | ||
|
|
01bb4f0fc4 | ||
|
|
9ae5d74c7c | ||
|
|
5af6d9b61c | ||
|
|
eb89a04c72 | ||
|
|
1019e40987 | ||
|
|
175d860885 | ||
|
|
de8aa6b6fd | ||
|
|
a5a7be02f6 | ||
|
|
14b7f8af81 | ||
|
|
62d01abf92 | ||
|
|
a328fa8f4a | ||
|
|
9e55271db1 | ||
|
|
e09ba941b8 | ||
|
|
7562b662b7 | ||
|
|
ce65163377 | ||
|
|
da445255ec | ||
|
|
fb36aecf42 | ||
|
|
3d624eae97 | ||
|
|
0a05676a87 | ||
|
|
bce6ae0da6 | ||
|
|
2268465d2e | ||
|
|
7a9953cfee | ||
|
|
ecc11c120b |
26
CHANGELOG.md
@@ -1,5 +1,31 @@
|
||||
# Changelog
|
||||
|
||||
## v2.0 (2021-07-14)
|
||||
|
||||
Breaking changes:
|
||||
- The backup format changed, see https://github.com/TheLastProject/Catima/wiki/Export-format
|
||||
- The URL sharing format changed, see https://github.com/TheLastProject/Catima/wiki/Card-sharing-URL-format
|
||||
|
||||
Changes:
|
||||
|
||||
- Make it possible to enable or disable the flashlight while scanning
|
||||
- Add UPC-E support
|
||||
- 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
|
||||
- Support new Voucher Vault export format
|
||||
- Fix Floating Action Buttons being behind other UI elements on Android 4
|
||||
- Fix loyalty card viewer appbar top margin
|
||||
|
||||
## v1.14.1 (2021-06-14)
|
||||
|
||||
Changes:
|
||||
|
||||
- Add missing barcode ID to export
|
||||
- Don't show update barcode dialog if value is the same as card ID
|
||||
- Add Finnish translation
|
||||
|
||||
## v1.14 (2021-06-07)
|
||||
|
||||
Changes:
|
||||
|
||||
@@ -2,7 +2,7 @@ GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.2)
|
||||
addressable (2.7.0)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.1.0)
|
||||
|
||||
@@ -11,15 +11,15 @@ spotbugs {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "30.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "me.hackerchick.catima"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 29
|
||||
versionCode 68
|
||||
versionName "1.14"
|
||||
targetSdkVersion 30
|
||||
versionCode 70
|
||||
versionName "2.0"
|
||||
|
||||
vectorDrawables.useSupportLibrary true
|
||||
}
|
||||
@@ -47,6 +47,12 @@ android {
|
||||
"MissingTranslation", "MissingPrefix"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
test {
|
||||
resources.srcDirs += ['src/test/res']
|
||||
}
|
||||
}
|
||||
|
||||
// Starting with Android Studio 3 Robolectric is unable to find resources.
|
||||
// The following allows it to find the resources.
|
||||
testOptions {
|
||||
@@ -78,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'
|
||||
|
||||
@@ -60,13 +60,16 @@
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!-- Accepts URIs that begin with "https://github.com/brarcher/loyalty-card-locker/” -->
|
||||
<!-- Listen to known card sharing URIs -->
|
||||
<data android:scheme="https"
|
||||
android:host="@string/intent_import_card_from_url_host"
|
||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix" />
|
||||
android:host="@string/intent_import_card_from_url_host_catima_app"
|
||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_catima_app" />
|
||||
<data android:scheme="https"
|
||||
android:host="@string/intent_import_card_from_url_host_old"
|
||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_old" />
|
||||
android:host="@string/intent_import_card_from_url_host_thelastproject"
|
||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_thelastproject" />
|
||||
<data android:scheme="https"
|
||||
android:host="@string/intent_import_card_from_url_host_brarcher"
|
||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_brarcher" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
|
||||
@@ -133,6 +133,8 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
||||
return "1003";
|
||||
case UPC_A:
|
||||
return "123456789012";
|
||||
case UPC_E:
|
||||
return "0123456";
|
||||
default:
|
||||
throw new IllegalArgumentException("No fallback known for this barcode type");
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
BarcodeFormat.ITF.name(),
|
||||
BarcodeFormat.PDF_417.name(),
|
||||
BarcodeFormat.QR_CODE.name(),
|
||||
BarcodeFormat.UPC_A.name()
|
||||
BarcodeFormat.UPC_A.name(),
|
||||
BarcodeFormat.UPC_E.name()
|
||||
));
|
||||
|
||||
private Map<String, Pair<Integer, Integer>> barcodeViewMap;
|
||||
@@ -90,6 +91,7 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
.put(BarcodeFormat.PDF_417.name(), new Pair<>(R.id.pdf417Barcode, R.id.pdf417BarcodeText))
|
||||
.put(BarcodeFormat.QR_CODE.name(), new Pair<>(R.id.qrcodeBarcode, R.id.qrcodeBarcodeText))
|
||||
.put(BarcodeFormat.UPC_A.name(), new Pair<>(R.id.upcaBarcode, R.id.upcaBarcodeText))
|
||||
.put(BarcodeFormat.UPC_E.name(), new Pair<>(R.id.upceBarcode, R.id.upceBarcodeText))
|
||||
.build();
|
||||
|
||||
EditText cardId = findViewById(R.id.cardId);
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Currency;
|
||||
@@ -52,9 +53,13 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
public static final String groupID = "groupId";
|
||||
}
|
||||
|
||||
private Context mContext;
|
||||
|
||||
public DBHelper(Context context)
|
||||
{
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,7 +83,7 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
|
||||
LoyaltyCardDbIds.BARCODE_ID + " TEXT," +
|
||||
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," +
|
||||
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0' )");
|
||||
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0')");
|
||||
|
||||
// create associative table for cards in groups
|
||||
db.execSQL("create table " + LoyaltyCardDbIdsGroups.TABLE + "(" +
|
||||
@@ -269,7 +274,7 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
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,
|
||||
@@ -287,10 +292,10 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
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,
|
||||
@@ -309,7 +314,7 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
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,
|
||||
@@ -426,7 +431,7 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
}
|
||||
}
|
||||
|
||||
public boolean deleteLoyaltyCard (final int id)
|
||||
public boolean deleteLoyaltyCard(final int id)
|
||||
{
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
// Delete card
|
||||
@@ -439,6 +444,14 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
LoyaltyCardDbIdsGroups.cardID + " = ? ",
|
||||
new String[]{String.format("%d", id)});
|
||||
|
||||
// Also wipe card images associated with this card
|
||||
try {
|
||||
Utils.saveCardImage(mContext, null, id, true);
|
||||
Utils.saveCardImage(mContext, null, id, false);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return (rowsDeleted == 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@ 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;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
@@ -20,6 +22,8 @@ import java.io.FileNotFoundException;
|
||||
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;
|
||||
@@ -27,6 +31,8 @@ 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
|
||||
{
|
||||
@@ -72,8 +78,8 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
// Check that there is a file manager available
|
||||
final Intent intentCreateDocumentAction = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||
intentCreateDocumentAction.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intentCreateDocumentAction.setType("text/csv");
|
||||
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "Catima.csv");
|
||||
intentCreateDocumentAction.setType("application/zip");
|
||||
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "Catima.zip");
|
||||
|
||||
Button exportButton = findViewById(R.id.exportButton);
|
||||
exportButton.setOnClickListener(new View.OnClickListener()
|
||||
@@ -115,9 +121,22 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
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:
|
||||
@@ -137,8 +156,14 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
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;
|
||||
@@ -162,19 +187,19 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -183,9 +208,9 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -210,7 +235,7 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
if(success == false)
|
||||
if(!success)
|
||||
{
|
||||
// External storage permission rejected, inform user that
|
||||
// import/export is prevented
|
||||
@@ -245,20 +270,43 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
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());
|
||||
|
||||
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);
|
||||
|
||||
if(success)
|
||||
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);
|
||||
@@ -274,53 +322,44 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -340,18 +379,13 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
@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");
|
||||
@@ -389,7 +423,7 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
|
||||
Log.e(TAG, "Starting file import with: " + uri.toString());
|
||||
|
||||
startImport(reader, uri, importDataFormat);
|
||||
startImport(reader, uri, importDataFormat, password);
|
||||
}
|
||||
}
|
||||
catch(FileNotFoundException e)
|
||||
@@ -397,12 +431,26 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
@@ -12,10 +13,12 @@ import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
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, Boolean>
|
||||
class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
|
||||
{
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
@@ -24,6 +27,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
|
||||
private DataFormat format;
|
||||
private OutputStream outputStream;
|
||||
private InputStream inputStream;
|
||||
private char[] password;
|
||||
private TaskCompleteListener listener;
|
||||
|
||||
private ProgressDialog progress;
|
||||
@@ -45,7 +49,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
|
||||
/**
|
||||
* Constructor which will setup a task for importing from the given InputStream.
|
||||
*/
|
||||
ImportExportTask(Activity activity, DataFormat format, InputStream input,
|
||||
ImportExportTask(Activity activity, DataFormat format, InputStream input, char[] password,
|
||||
TaskCompleteListener listener)
|
||||
{
|
||||
super();
|
||||
@@ -53,29 +57,27 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
|
||||
this.doImport = true;
|
||||
this.format = format;
|
||||
this.inputStream = input;
|
||||
this.password = password;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private boolean performImport(InputStream stream, DBHelper db)
|
||||
private ImportExportResult performImport(Context context, InputStream stream, DBHelper db, char[] password)
|
||||
{
|
||||
boolean result = false;
|
||||
ImportExportResult importResult = MultiFormatImporter.importData(context, db, stream, format, password);
|
||||
|
||||
Log.i(TAG, "Import result: " + importResult.name());
|
||||
|
||||
result = MultiFormatImporter.importData(db, stream, format);
|
||||
|
||||
Log.i(TAG, "Import result: " + result);
|
||||
|
||||
return result;
|
||||
return importResult;
|
||||
}
|
||||
|
||||
private boolean performExport(OutputStream stream, DBHelper db)
|
||||
private ImportExportResult performExport(Context context, OutputStream stream, DBHelper db)
|
||||
{
|
||||
boolean result = false;
|
||||
ImportExportResult result = ImportExportResult.GenericFailure;
|
||||
|
||||
try
|
||||
{
|
||||
OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("UTF-8"));
|
||||
result = MultiFormatExporter.exportData(db, writer, format);
|
||||
result = MultiFormatExporter.exportData(context, db, stream, format);
|
||||
writer.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
@@ -105,26 +107,26 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
|
||||
progress.show();
|
||||
}
|
||||
|
||||
protected Boolean doInBackground(Void... nothing)
|
||||
protected ImportExportResult doInBackground(Void... nothing)
|
||||
{
|
||||
final DBHelper db = new DBHelper(activity);
|
||||
boolean result;
|
||||
ImportExportResult result;
|
||||
|
||||
if(doImport)
|
||||
{
|
||||
result = performImport(inputStream, db);
|
||||
result = performImport(activity.getApplicationContext(), inputStream, db, password);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = performExport(outputStream, db);
|
||||
result = performExport(activity.getApplicationContext(), outputStream, db);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void onPostExecute(Boolean result)
|
||||
protected void onPostExecute(ImportExportResult result)
|
||||
{
|
||||
listener.onTaskComplete(result);
|
||||
listener.onTaskComplete(result, format);
|
||||
|
||||
progress.dismiss();
|
||||
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
|
||||
@@ -137,7 +139,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
|
||||
}
|
||||
interface TaskCompleteListener
|
||||
{
|
||||
void onTaskComplete(boolean success);
|
||||
void onTaskComplete(ImportExportResult result, DataFormat format);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,9 +7,14 @@ import android.net.Uri;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import java.io.InvalidObjectException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Currency;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class ImportURIHelper {
|
||||
@@ -21,29 +26,34 @@ public class ImportURIHelper {
|
||||
private static final String CARD_ID = DBHelper.LoyaltyCardDbIds.CARD_ID;
|
||||
private static final String BARCODE_ID = DBHelper.LoyaltyCardDbIds.BARCODE_ID;
|
||||
private static final String BARCODE_TYPE = DBHelper.LoyaltyCardDbIds.BARCODE_TYPE;
|
||||
|
||||
private static final String HEADER_COLOR = DBHelper.LoyaltyCardDbIds.HEADER_COLOR;
|
||||
|
||||
private final Context context;
|
||||
private final String host;
|
||||
private final String path;
|
||||
private final String oldHost;
|
||||
private final String oldPath;
|
||||
private final String[] hosts = new String[3];
|
||||
private final String[] paths = new String[3];
|
||||
private final String shareText;
|
||||
private final String shareMultipleText;
|
||||
|
||||
public ImportURIHelper(Context context) {
|
||||
this.context = context;
|
||||
host = context.getResources().getString(R.string.intent_import_card_from_url_host);
|
||||
path = context.getResources().getString(R.string.intent_import_card_from_url_path_prefix);
|
||||
oldHost = "brarcher.github.io";
|
||||
oldPath = "/loyalty-card-locker/share";
|
||||
hosts[0] = context.getResources().getString(R.string.intent_import_card_from_url_host_catima_app);
|
||||
paths[0] = context.getResources().getString(R.string.intent_import_card_from_url_path_prefix_catima_app);
|
||||
hosts[1] = context.getResources().getString(R.string.intent_import_card_from_url_host_thelastproject);
|
||||
paths[1] = context.getResources().getString(R.string.intent_import_card_from_url_path_prefix_thelastproject);
|
||||
hosts[2] = context.getResources().getString(R.string.intent_import_card_from_url_host_brarcher);
|
||||
paths[2] = context.getResources().getString(R.string.intent_import_card_from_url_path_prefix_brarcher);
|
||||
shareText = context.getResources().getString(R.string.intent_import_card_from_url_share_text);
|
||||
shareMultipleText = context.getResources().getString(R.string.intent_import_card_from_url_share_multiple_text);
|
||||
}
|
||||
|
||||
private boolean isImportUri(Uri uri) {
|
||||
return (uri.getHost().equals(host) && uri.getPath().equals(path)) || (uri.getHost().equals(oldHost) && uri.getPath().equals(oldPath));
|
||||
for (int i = 0; i < hosts.length; i++) {
|
||||
if (uri.getHost().equals(hosts[i]) && uri.getPath().equals(paths[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public LoyaltyCard parse(Uri uri) throws InvalidObjectException {
|
||||
@@ -59,77 +69,114 @@ public class ImportURIHelper {
|
||||
Currency balanceType = null;
|
||||
Integer headerColor = null;
|
||||
|
||||
String store = uri.getQueryParameter(STORE);
|
||||
String note = uri.getQueryParameter(NOTE);
|
||||
String cardId = uri.getQueryParameter(CARD_ID);
|
||||
String barcodeId = uri.getQueryParameter(BARCODE_ID);
|
||||
if (store == null || note == null || cardId == null) throw new InvalidObjectException("Not a valid import URI");
|
||||
// Store everything in a simple key/value hashmap
|
||||
HashMap<String, String> kv = new HashMap<>();
|
||||
|
||||
String unparsedBarcodeType = uri.getQueryParameter(BARCODE_TYPE);
|
||||
// First, grab all query parameters (backwards compatibility)
|
||||
for (String key : uri.getQueryParameterNames()) {
|
||||
kv.put(key, uri.getQueryParameter(key));
|
||||
}
|
||||
|
||||
// Then, parse the new and more private fragment part
|
||||
// Overriding old format entries if they exist
|
||||
String fragment = uri.getFragment();
|
||||
if (fragment != null) {
|
||||
for (String fragmentPart : fragment.split("&")) {
|
||||
String[] fragmentData = fragmentPart.split("=", 2);
|
||||
kv.put(fragmentData[0], URLDecoder.decode(fragmentData[1], StandardCharsets.UTF_8.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
// Then use all values we care about
|
||||
String store = kv.get(STORE);
|
||||
String note = kv.get(NOTE);
|
||||
String cardId = kv.get(CARD_ID);
|
||||
String barcodeId = kv.get(BARCODE_ID);
|
||||
if (store == null || note == null || cardId == null) throw new InvalidObjectException("Not a valid import URI: " + uri.toString());
|
||||
|
||||
String unparsedBarcodeType = kv.get(BARCODE_TYPE);
|
||||
if(unparsedBarcodeType != null && !unparsedBarcodeType.equals(""))
|
||||
{
|
||||
barcodeType = BarcodeFormat.valueOf(unparsedBarcodeType);
|
||||
}
|
||||
|
||||
String unparsedBalance = uri.getQueryParameter(BALANCE);
|
||||
String unparsedBalance = kv.get(BALANCE);
|
||||
if(unparsedBalance != null && !unparsedBalance.equals(""))
|
||||
{
|
||||
balance = new BigDecimal(unparsedBalance);
|
||||
}
|
||||
String unparsedBalanceType = uri.getQueryParameter(BALANCE_TYPE);
|
||||
String unparsedBalanceType = kv.get(BALANCE_TYPE);
|
||||
if (unparsedBalanceType != null && !unparsedBalanceType.equals(""))
|
||||
{
|
||||
balanceType = Currency.getInstance(unparsedBalanceType);
|
||||
}
|
||||
String unparsedExpiry = uri.getQueryParameter(EXPIRY);
|
||||
String unparsedExpiry = kv.get(EXPIRY);
|
||||
if(unparsedExpiry != null && !unparsedExpiry.equals(""))
|
||||
{
|
||||
expiry = new Date(Long.parseLong(unparsedExpiry));
|
||||
}
|
||||
|
||||
String unparsedHeaderColor = uri.getQueryParameter(HEADER_COLOR);
|
||||
String unparsedHeaderColor = kv.get(HEADER_COLOR);
|
||||
if(unparsedHeaderColor != null)
|
||||
{
|
||||
headerColor = Integer.parseInt(unparsedHeaderColor);
|
||||
}
|
||||
|
||||
return new LoyaltyCard(-1, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0);
|
||||
} catch (NullPointerException | NumberFormatException ex) {
|
||||
} catch (NullPointerException | NumberFormatException | UnsupportedEncodingException ex) {
|
||||
throw new InvalidObjectException("Not a valid import URI");
|
||||
}
|
||||
}
|
||||
|
||||
private StringBuilder appendFragment(StringBuilder fragment, String key, String value) throws UnsupportedEncodingException {
|
||||
if (fragment.length() > 0) {
|
||||
fragment.append("&");
|
||||
}
|
||||
|
||||
// Double-encode the value to make sure it can't accidentally contain symbols that'll break the parser
|
||||
fragment.append(key).append("=").append(URLEncoder.encode(value, StandardCharsets.UTF_8.toString()));
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
// Protected for usage in tests
|
||||
protected Uri toUri(LoyaltyCard loyaltyCard) {
|
||||
protected Uri toUri(LoyaltyCard loyaltyCard) throws UnsupportedEncodingException {
|
||||
Uri.Builder uriBuilder = new Uri.Builder();
|
||||
uriBuilder.scheme("https");
|
||||
uriBuilder.authority(host);
|
||||
uriBuilder.path(path);
|
||||
uriBuilder.appendQueryParameter(STORE, loyaltyCard.store);
|
||||
uriBuilder.appendQueryParameter(NOTE, loyaltyCard.note);
|
||||
uriBuilder.appendQueryParameter(BALANCE, loyaltyCard.balance.toString());
|
||||
uriBuilder.authority(hosts[0]);
|
||||
uriBuilder.path(paths[0]);
|
||||
|
||||
// Use fragment instead of QueryParameter to not leak this data to the server
|
||||
StringBuilder fragment = new StringBuilder();
|
||||
|
||||
fragment = appendFragment(fragment, STORE, loyaltyCard.store);
|
||||
fragment = appendFragment(fragment, NOTE, loyaltyCard.note);
|
||||
fragment = appendFragment(fragment, BALANCE, loyaltyCard.balance.toString());
|
||||
if (loyaltyCard.balanceType != null) {
|
||||
uriBuilder.appendQueryParameter(BALANCE_TYPE, loyaltyCard.balanceType.getCurrencyCode());
|
||||
fragment = appendFragment(fragment, BALANCE_TYPE, loyaltyCard.balanceType.getCurrencyCode());
|
||||
}
|
||||
if (loyaltyCard.expiry != null) {
|
||||
uriBuilder.appendQueryParameter(EXPIRY, String.valueOf(loyaltyCard.expiry.getTime()));
|
||||
fragment = appendFragment(fragment, EXPIRY, String.valueOf(loyaltyCard.expiry.getTime()));
|
||||
}
|
||||
uriBuilder.appendQueryParameter(CARD_ID, loyaltyCard.cardId);
|
||||
fragment = appendFragment(fragment, CARD_ID, loyaltyCard.cardId);
|
||||
if(loyaltyCard.barcodeId != null) {
|
||||
uriBuilder.appendQueryParameter(BARCODE_ID, loyaltyCard.barcodeId);
|
||||
fragment = appendFragment(fragment, BARCODE_ID, loyaltyCard.barcodeId);
|
||||
}
|
||||
|
||||
if(loyaltyCard.barcodeType != null) {
|
||||
uriBuilder.appendQueryParameter(BARCODE_TYPE, loyaltyCard.barcodeType.toString());
|
||||
fragment = appendFragment(fragment, BARCODE_TYPE, loyaltyCard.barcodeType.toString());
|
||||
}
|
||||
if(loyaltyCard.headerColor != null) {
|
||||
uriBuilder.appendQueryParameter(HEADER_COLOR, loyaltyCard.headerColor.toString());
|
||||
fragment = appendFragment(fragment, HEADER_COLOR, loyaltyCard.headerColor.toString());
|
||||
}
|
||||
//StarStatus will not be exported
|
||||
// Star status will not be exported
|
||||
// Front and back pictures are often too big to fit into a message in base64 nicely, not sharing either...
|
||||
|
||||
uriBuilder.fragment(fragment.toString());
|
||||
return uriBuilder.build();
|
||||
}
|
||||
|
||||
public void startShareIntent(List<LoyaltyCard> loyaltyCards) {
|
||||
public void startShareIntent(List<LoyaltyCard> loyaltyCards) throws UnsupportedEncodingException {
|
||||
int loyaltyCardCount = loyaltyCards.size();
|
||||
|
||||
StringBuilder text = new StringBuilder();
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.DatePickerDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.LocaleList;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
@@ -38,6 +45,9 @@ import com.google.zxing.BarcodeFormat;
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InvalidObjectException;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DateFormat;
|
||||
@@ -47,21 +57,33 @@ import java.util.Collections;
|
||||
import java.util.Currency;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private final String STATE_TAB_INDEX = "savedTab";
|
||||
|
||||
private static final int ID_IMAGE_FRONT = 0;
|
||||
private static final int ID_IMAGE_BACK = 1;
|
||||
|
||||
private static final int PERMISSION_REQUEST_CAMERA_IMAGE_FRONT = 100;
|
||||
private static final int PERMISSION_REQUEST_CAMERA_IMAGE_BACK = 101;
|
||||
|
||||
TabLayout tabs;
|
||||
|
||||
ImageView thumbnail;
|
||||
@@ -77,6 +99,10 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
ImageView barcodeImage;
|
||||
View barcodeImageLayout;
|
||||
View barcodeCaptureLayout;
|
||||
View cardImageFrontHolder;
|
||||
View cardImageBackHolder;
|
||||
ImageView cardImageFront;
|
||||
ImageView cardImageBack;
|
||||
|
||||
Button enterButton;
|
||||
|
||||
@@ -102,6 +128,8 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
|
||||
HashMap<String, Currency> currencies = new HashMap<>();
|
||||
|
||||
String tempCameraPicturePath;
|
||||
|
||||
private void extractIntentFields(Intent intent)
|
||||
{
|
||||
final Bundle b = intent.getExtras();
|
||||
@@ -169,6 +197,12 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
barcodeImage = findViewById(R.id.barcode);
|
||||
barcodeImageLayout = findViewById(R.id.barcodeLayout);
|
||||
barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
|
||||
cardImageFrontHolder = findViewById(R.id.frontImageHolder);
|
||||
cardImageBackHolder = findViewById(R.id.backImageHolder);
|
||||
cardImageFrontHolder.setId(ID_IMAGE_FRONT);
|
||||
cardImageBackHolder.setId(ID_IMAGE_BACK);
|
||||
cardImageFront = findViewById(R.id.frontImage);
|
||||
cardImageBack = findViewById(R.id.backImage);
|
||||
|
||||
enterButton = findViewById(R.id.enterButton);
|
||||
|
||||
@@ -489,6 +523,8 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
barcodeIdField.setTag(null);
|
||||
barcodeIdField.setText("");
|
||||
barcodeTypeField.setText("");
|
||||
cardImageFront.setTag(null);
|
||||
cardImageBack.setTag(null);
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@@ -563,6 +599,16 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
if(cardImageFront.getTag() == null)
|
||||
{
|
||||
setCardImage(cardImageFront, Utils.retrieveCardImage(this, loyaltyCard.id, true));
|
||||
}
|
||||
|
||||
if(cardImageBack.getTag() == null)
|
||||
{
|
||||
setCardImage(cardImageBack, Utils.retrieveCardImage(this, loyaltyCard.id, false));
|
||||
}
|
||||
|
||||
setTitle(R.string.editCardTitle);
|
||||
}
|
||||
else if(importLoyaltyCardUri != null)
|
||||
@@ -601,6 +647,8 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
balanceCurrencyField.setTag(null);
|
||||
formatBalanceCurrencyField(null);
|
||||
hideBarcode();
|
||||
setCardImage(cardImageFront, null);
|
||||
setCardImage(cardImageBack, null);
|
||||
}
|
||||
|
||||
if(groupsChips.getChildCount() == 0)
|
||||
@@ -679,12 +727,26 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
enterButton.setOnClickListener(new EditCardIdAndBarcode());
|
||||
barcodeImage.setOnClickListener(new EditCardIdAndBarcode());
|
||||
|
||||
cardImageFrontHolder.setOnClickListener(new ChooseCardImage());
|
||||
cardImageBackHolder.setOnClickListener(new ChooseCardImage());
|
||||
|
||||
FloatingActionButton saveButton = findViewById(R.id.fabSave);
|
||||
saveButton.setOnClickListener(v -> doSave());
|
||||
saveButton.bringToFront();
|
||||
|
||||
generateIcon(storeFieldEdit.getText().toString());
|
||||
}
|
||||
|
||||
private void setCardImage(ImageView imageView, Bitmap bitmap) {
|
||||
imageView.setTag(bitmap);
|
||||
|
||||
if (bitmap != null) {
|
||||
imageView.setImageBitmap(bitmap);
|
||||
} else {
|
||||
imageView.setImageResource(R.drawable.ic_camera_white);
|
||||
}
|
||||
}
|
||||
|
||||
private void formatExpiryField(Date expiry) {
|
||||
if (expiry == null) {
|
||||
expiryField.setText(getString(R.string.never));
|
||||
@@ -706,7 +768,36 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
askBeforeQuitIfChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
try {
|
||||
if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_FRONT) {
|
||||
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_FRONT);
|
||||
} else {
|
||||
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_BACK);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void askBarcodeChange(Runnable callback) {
|
||||
if (tempStoredOldBarcodeValue.equals(cardIdFieldView.getText().toString())) {
|
||||
// They are the same, don't ask
|
||||
barcodeIdField.setText(R.string.sameAsCardId);
|
||||
tempStoredOldBarcodeValue = null;
|
||||
|
||||
if (callback != null) {
|
||||
callback.run();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.updateBarcodeQuestionTitle)
|
||||
.setMessage(R.string.updateBarcodeQuestionText)
|
||||
@@ -726,6 +817,14 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
callback.run();
|
||||
}
|
||||
})
|
||||
.setOnDismissListener(dialogInterface -> {
|
||||
barcodeIdField.setText(tempStoredOldBarcodeValue);
|
||||
tempStoredOldBarcodeValue = null;
|
||||
|
||||
if (callback != null) {
|
||||
callback.run();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
@@ -762,12 +861,29 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
confirmExitDialog.show();
|
||||
}
|
||||
|
||||
private void takePhotoForCard(int type) throws IOException {
|
||||
Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
|
||||
String imageFileName = "CATIMA_" + new Date().getTime();
|
||||
File image = File.createTempFile(
|
||||
imageFileName,
|
||||
".jpg",
|
||||
getExternalFilesDir(Environment.DIRECTORY_PICTURES)
|
||||
);
|
||||
|
||||
tempCameraPicturePath = image.getAbsolutePath();
|
||||
|
||||
Uri photoURI = FileProvider.getUriForFile(LoyaltyCardEditActivity.this, BuildConfig.APPLICATION_ID, image);
|
||||
i.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
|
||||
|
||||
startActivityForResult(i, type);
|
||||
}
|
||||
|
||||
class EditCardIdAndBarcode implements View.OnClickListener
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
|
||||
Intent i = new Intent(getApplicationContext(), ScanActivity.class);
|
||||
final Bundle b = new Bundle();
|
||||
b.putString("cardId", cardIdFieldView.getText().toString());
|
||||
@@ -776,6 +892,56 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
class ChooseCardImage implements View.OnClickListener
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v) throws NoSuchElementException
|
||||
{
|
||||
ImageView targetView = v.getId() == ID_IMAGE_FRONT ? cardImageFront : cardImageBack;
|
||||
|
||||
LinkedHashMap<String, Callable<Void>> cardOptions = new LinkedHashMap<>();
|
||||
if (targetView.getTag() != null) {
|
||||
cardOptions.put(getString(R.string.removeImage), () -> {
|
||||
setCardImage(targetView, null);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
cardOptions.put(getString(R.string.takePhoto), () -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
requestPermissions(new String[]{Manifest.permission.CAMERA}, v.getId() == ID_IMAGE_FRONT ? PERMISSION_REQUEST_CAMERA_IMAGE_FRONT : PERMISSION_REQUEST_CAMERA_IMAGE_BACK);
|
||||
} else {
|
||||
takePhotoForCard(v.getId() == ID_IMAGE_FRONT ? Utils.CARD_IMAGE_FROM_CAMERA_FRONT : Utils.CARD_IMAGE_FROM_CAMERA_BACK);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
cardOptions.put(getString(R.string.chooseImageFromGallery), () -> {
|
||||
Intent i = new Intent(Intent.ACTION_PICK);
|
||||
i.setType("image/*");
|
||||
startActivityForResult(i, v.getId() == ID_IMAGE_FRONT ? Utils.CARD_IMAGE_FROM_FILE_FRONT : Utils.CARD_IMAGE_FROM_FILE_BACK);
|
||||
return null;
|
||||
});
|
||||
|
||||
new AlertDialog.Builder(LoyaltyCardEditActivity.this)
|
||||
.setTitle(v.getId() == ID_IMAGE_FRONT ? getString(R.string.setFrontImage) : getString(R.string.setBackImage))
|
||||
.setItems(cardOptions.keySet().toArray(new CharSequence[cardOptions.size()]), (dialog, which) -> {
|
||||
Iterator<Callable<Void>> callables = cardOptions.values().iterator();
|
||||
Callable<Void> callable = callables.next();
|
||||
|
||||
for (int i = 0; i < which; i++) {
|
||||
callable = callables.next();
|
||||
}
|
||||
|
||||
try {
|
||||
callable.call();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
class ColorSelectListener implements View.OnClickListener
|
||||
{
|
||||
@@ -858,8 +1024,7 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void doSave()
|
||||
{
|
||||
private void doSave() {
|
||||
if (tempStoredOldBarcodeValue != null) {
|
||||
askBarcodeChange(this::doSave);
|
||||
return;
|
||||
@@ -902,11 +1067,23 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
if(updateLoyaltyCard)
|
||||
{ //update of "starStatus" not necessary, since it cannot be changed in this activity (only in ViewActivity)
|
||||
db.updateLoyaltyCard(loyaltyCardId, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headingColorValue);
|
||||
try {
|
||||
Utils.saveCardImage(this, (Bitmap) cardImageFront.getTag(), loyaltyCardId, true);
|
||||
Utils.saveCardImage(this, (Bitmap) cardImageBack.getTag(), loyaltyCardId, false);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
|
||||
}
|
||||
else
|
||||
{
|
||||
loyaltyCardId = (int)db.insertLoyaltyCard(store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headingColorValue, 0);
|
||||
try {
|
||||
Utils.saveCardImage(this, (Bitmap) cardImageFront.getTag(), loyaltyCardId, true);
|
||||
Utils.saveCardImage(this, (Bitmap) cardImageBack.getTag(), loyaltyCardId, false);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
db.setLoyaltyCardGroups(loyaltyCardId, selectedGroups);
|
||||
@@ -982,12 +1159,57 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
{
|
||||
super.onActivityResult(requestCode, resultCode, intent);
|
||||
|
||||
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
|
||||
if (requestCode == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || requestCode == Utils.CARD_IMAGE_FROM_CAMERA_BACK) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
Bitmap bitmap = BitmapFactory.decodeFile(tempCameraPicturePath);
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
cardId = barcodeValues.content();
|
||||
barcodeType = barcodeValues.format();
|
||||
barcodeId = "";
|
||||
if (bitmap != null) {
|
||||
bitmap = Utils.resizeBitmap(bitmap);
|
||||
try {
|
||||
bitmap = Utils.rotateBitmap(bitmap, new ExifInterface(tempCameraPicturePath));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (requestCode == Utils.CARD_IMAGE_FROM_CAMERA_FRONT) {
|
||||
setCardImage(cardImageFront, bitmap);
|
||||
} else {
|
||||
setCardImage(cardImageBack, bitmap);
|
||||
}
|
||||
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
} else if (requestCode == Utils.CARD_IMAGE_FROM_FILE_FRONT || requestCode == Utils.CARD_IMAGE_FROM_FILE_BACK) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
Bitmap bitmap = null;
|
||||
try {
|
||||
bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), intent.getData());
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error getting data from image file");
|
||||
e.printStackTrace();
|
||||
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
if (bitmap != null) {
|
||||
bitmap = Utils.resizeBitmap(bitmap);
|
||||
if (requestCode == Utils.CARD_IMAGE_FROM_FILE_FRONT) {
|
||||
setCardImage(cardImageFront, bitmap);
|
||||
} else {
|
||||
setCardImage(cardImageBack, bitmap);
|
||||
}
|
||||
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
cardId = barcodeValues.content();
|
||||
barcodeType = barcodeValues.format();
|
||||
barcodeId = "";
|
||||
}
|
||||
}
|
||||
|
||||
onResume();
|
||||
@@ -1061,19 +1283,29 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
|
||||
View cardPart = findViewById(R.id.cardPart);
|
||||
View barcodePart = findViewById(R.id.barcodePart);
|
||||
View picturesPart = findViewById(R.id.picturesPart);
|
||||
|
||||
if (getString(R.string.card).equals(part)) {
|
||||
cardPart.setVisibility(View.VISIBLE);
|
||||
barcodePart.setVisibility(View.GONE);
|
||||
picturesPart.setVisibility(View.GONE);
|
||||
|
||||
// Explicitly hide barcode (fixes blurriness on redraw)
|
||||
hideBarcode();
|
||||
} else if (getString(R.string.barcode).equals(part)) {
|
||||
cardPart.setVisibility(View.GONE);
|
||||
barcodePart.setVisibility(View.VISIBLE);
|
||||
picturesPart.setVisibility(View.GONE);
|
||||
|
||||
// Redraw barcode due to size change (Visibility.GONE sets it to 0)
|
||||
generateOrHideBarcode();
|
||||
} else if (getString(R.string.photos).equals(part)) {
|
||||
cardPart.setVisibility(View.GONE);
|
||||
barcodePart.setVisibility(View.GONE);
|
||||
picturesPart.setVisibility(View.VISIBLE);
|
||||
|
||||
// Explicitly hide barcode (fixes blurriness on redraw)
|
||||
hideBarcode();
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package protect.card_locker;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
@@ -18,6 +19,7 @@ import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
@@ -27,6 +29,7 @@ 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;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Arrays;
|
||||
@@ -49,7 +52,12 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
TextView cardIdFieldView;
|
||||
BottomSheetBehavior behavior;
|
||||
View bottomSheet;
|
||||
View bottomSheetContentWrapper;
|
||||
ImageView bottomSheetButton;
|
||||
View frontImageView;
|
||||
ImageView frontImage;
|
||||
View backImageView;
|
||||
ImageView backImage;
|
||||
TextView noteView;
|
||||
TextView groupsView;
|
||||
TextView balanceView;
|
||||
@@ -76,9 +84,20 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
Guideline centerGuideline;
|
||||
SeekBar barcodeScaler;
|
||||
|
||||
Bitmap frontImageBitmap;
|
||||
Bitmap backImageBitmap;
|
||||
|
||||
boolean starred;
|
||||
boolean backgroundNeedsDarkIcons;
|
||||
boolean barcodeIsFullscreen = false;
|
||||
FullscreenType fullscreenType = FullscreenType.NONE;
|
||||
boolean isBarcodeSupported = true;
|
||||
|
||||
enum FullscreenType {
|
||||
NONE,
|
||||
BARCODE,
|
||||
IMAGE_FRONT,
|
||||
IMAGE_BACK
|
||||
}
|
||||
|
||||
private void extractIntentFields(Intent intent)
|
||||
{
|
||||
@@ -120,7 +139,12 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
|
||||
cardIdFieldView = findViewById(R.id.cardIdView);
|
||||
bottomSheet = findViewById(R.id.bottom_sheet);
|
||||
bottomSheetContentWrapper = findViewById(R.id.bottomSheetContentWrapper);
|
||||
bottomSheetButton = findViewById(R.id.bottomSheetButton);
|
||||
frontImageView = findViewById(R.id.frontImageView);
|
||||
frontImage = findViewById(R.id.frontImage);
|
||||
backImageView = findViewById(R.id.backImageView);
|
||||
backImage = findViewById(R.id.backImage);
|
||||
noteView = findViewById(R.id.noteView);
|
||||
groupsView = findViewById(R.id.groupsView);
|
||||
balanceView = findViewById(R.id.balanceView);
|
||||
@@ -144,7 +168,9 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
float scale = (float) progress / (float) barcodeScaler.getMax();
|
||||
Log.d(TAG, "Scaling to " + scale);
|
||||
|
||||
redrawBarcodeAfterResize();
|
||||
if (fullscreenType == FullscreenType.BARCODE) {
|
||||
redrawBarcodeAfterResize();
|
||||
}
|
||||
centerGuideline.setGuidelinePercent(0.5f * scale);
|
||||
}
|
||||
|
||||
@@ -162,18 +188,29 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
rotationEnabled = true;
|
||||
|
||||
// Allow making barcode fullscreen on tap
|
||||
maximizeButton.setOnClickListener(v -> setFullscreen(true));
|
||||
maximizeButton.setOnClickListener(v -> setFullscreen(FullscreenType.BARCODE));
|
||||
barcodeImage.setOnClickListener(view -> {
|
||||
if (barcodeIsFullscreen)
|
||||
{
|
||||
setFullscreen(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
setFullscreen(true);
|
||||
if (fullscreenType != FullscreenType.NONE) {
|
||||
setFullscreen(FullscreenType.NONE);
|
||||
} else {
|
||||
setFullscreen(FullscreenType.BARCODE);
|
||||
}
|
||||
});
|
||||
minimizeButton.setOnClickListener(v -> setFullscreen(false));
|
||||
frontImageView.setOnClickListener(view -> {
|
||||
if (fullscreenType != FullscreenType.IMAGE_FRONT) {
|
||||
setFullscreen(FullscreenType.IMAGE_FRONT);
|
||||
} else {
|
||||
setFullscreen(FullscreenType.NONE);
|
||||
}
|
||||
});
|
||||
backImageView.setOnClickListener(view -> {
|
||||
if (fullscreenType != FullscreenType.IMAGE_BACK) {
|
||||
setFullscreen(FullscreenType.IMAGE_BACK);
|
||||
} else {
|
||||
setFullscreen(FullscreenType.NONE);
|
||||
}
|
||||
});
|
||||
minimizeButton.setOnClickListener(v -> setFullscreen(FullscreenType.NONE));
|
||||
|
||||
editButton = findViewById(R.id.fabEdit);
|
||||
editButton.setOnClickListener(v -> {
|
||||
@@ -185,6 +222,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
startActivity(intent);
|
||||
finish();
|
||||
});
|
||||
editButton.bringToFront();
|
||||
|
||||
behavior = BottomSheetBehavior.from(bottomSheet);
|
||||
behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
|
||||
@@ -197,7 +235,12 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
editButton.hide();
|
||||
} else if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
bottomSheetButton.setImageResource(R.drawable.ic_baseline_arrow_drop_up_24);
|
||||
editButton.show();
|
||||
if (fullscreenType == FullscreenType.NONE) {
|
||||
editButton.show();
|
||||
}
|
||||
|
||||
// Scroll bottomsheet content back to top
|
||||
bottomSheetContentWrapper.setScrollY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +255,25 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||
}
|
||||
});
|
||||
|
||||
// Fix bottom sheet content sizing
|
||||
ViewTreeObserver viewTreeObserver = bottomSheet.getViewTreeObserver();
|
||||
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
bottomSheet.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
|
||||
int height = displayMetrics.heightPixels;
|
||||
int maxHeight = height - appBarLayout.getHeight() - bottomSheetButton.getHeight();
|
||||
Log.d(TAG, "Button sheet should be " + maxHeight + " pixels high");
|
||||
bottomSheetContentWrapper.setLayoutParams(
|
||||
new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
maxHeight
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -275,6 +337,22 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
settings.getFontSizeMin(settings.getLargeFont()), settings.getFontSizeMax(settings.getLargeFont()),
|
||||
1, TypedValue.COMPLEX_UNIT_SP);
|
||||
|
||||
frontImageBitmap = Utils.retrieveCardImage(this, loyaltyCard.id, true);
|
||||
if (frontImageBitmap != null) {
|
||||
frontImageView.setVisibility(View.VISIBLE);
|
||||
frontImage.setImageBitmap(frontImageBitmap);
|
||||
} else {
|
||||
frontImageView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
backImageBitmap = Utils.retrieveCardImage(this, loyaltyCard.id, false);
|
||||
if (backImageBitmap != null) {
|
||||
backImageView.setVisibility(View.VISIBLE);
|
||||
backImage.setImageBitmap(backImageBitmap);
|
||||
} else {
|
||||
backImageView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if(loyaltyCard.note.length() > 0)
|
||||
{
|
||||
noteView.setVisibility(View.VISIBLE);
|
||||
@@ -331,7 +409,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
}
|
||||
expiryView.setTag(loyaltyCard.expiry);
|
||||
|
||||
if (!barcodeIsFullscreen) {
|
||||
if (fullscreenType == FullscreenType.NONE) {
|
||||
makeBottomSheetVisibleIfUseful();
|
||||
}
|
||||
|
||||
@@ -384,8 +462,6 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
// Set shadow colour of store text so even same color on same color would be readable
|
||||
storeName.setShadowLayer(1, 1, 1, backgroundNeedsDarkIcons ? Color.BLACK : Color.WHITE);
|
||||
|
||||
Boolean isBarcodeSupported = true;
|
||||
|
||||
if (format != null && !BarcodeSelectorActivity.SUPPORTED_BARCODE_TYPES.contains(format.name())) {
|
||||
isBarcodeSupported = false;
|
||||
|
||||
@@ -394,7 +470,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
|
||||
if(format != null && isBarcodeSupported)
|
||||
{
|
||||
if (!barcodeIsFullscreen) {
|
||||
if (fullscreenType == FullscreenType.NONE) {
|
||||
maximizeButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
barcodeImage.setVisibility(View.VISIBLE);
|
||||
@@ -419,7 +495,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
// Force redraw fullscreen state
|
||||
setFullscreen(barcodeIsFullscreen);
|
||||
setFullscreen(fullscreenType);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -430,9 +506,9 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (barcodeIsFullscreen)
|
||||
if (fullscreenType != FullscreenType.NONE)
|
||||
{
|
||||
setFullscreen(false);
|
||||
setFullscreen(FullscreenType.NONE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -488,7 +564,12 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
break;
|
||||
|
||||
case R.id.action_share:
|
||||
importURIHelper.startShareIntent(Arrays.asList(loyaltyCard));
|
||||
try {
|
||||
importURIHelper.startShareIntent(Arrays.asList(loyaltyCard));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Toast.makeText(LoyaltyCardViewActivity.this, R.string.failedGeneratingShareURL, Toast.LENGTH_LONG).show();
|
||||
e.printStackTrace();
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.action_lock_unlock:
|
||||
@@ -566,7 +647,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
|
||||
private void makeBottomSheetVisibleIfUseful()
|
||||
{
|
||||
if (noteView.getVisibility() == View.VISIBLE || groupsView.getVisibility() == View.VISIBLE || balanceView.getVisibility() == View.VISIBLE || expiryView.getVisibility() == View.VISIBLE) {
|
||||
if (frontImageView.getVisibility() == View.VISIBLE || backImageView.getVisibility() == View.VISIBLE || noteView.getVisibility() == View.VISIBLE || groupsView.getVisibility() == View.VISIBLE || balanceView.getVisibility() == View.VISIBLE || expiryView.getVisibility() == View.VISIBLE) {
|
||||
bottomSheet.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else
|
||||
@@ -604,14 +685,22 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
* The purpose of this function is to make sure the barcode can be scanned from the phone
|
||||
* by machines which offer no space to insert the complete device.
|
||||
*/
|
||||
private void setFullscreen(boolean enable)
|
||||
private void setFullscreen(FullscreenType fullscreenType)
|
||||
{
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(enable)
|
||||
{
|
||||
Log.d(TAG, "Move into of fullscreen");
|
||||
// Prepare redraw after size change
|
||||
redrawBarcodeAfterResize();
|
||||
if (fullscreenType != FullscreenType.NONE) {
|
||||
Log.d(TAG, "Move into fullscreen");
|
||||
|
||||
if (fullscreenType == FullscreenType.IMAGE_FRONT) {
|
||||
barcodeImage.setImageBitmap(frontImageBitmap);
|
||||
barcodeImage.setVisibility(View.VISIBLE);
|
||||
} else if (fullscreenType == FullscreenType.IMAGE_BACK) {
|
||||
barcodeImage.setImageBitmap(backImageBitmap);
|
||||
barcodeImage.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
// Prepare redraw after size change
|
||||
redrawBarcodeAfterResize();
|
||||
}
|
||||
|
||||
// Hide maximize and show minimize button and scaler
|
||||
maximizeButton.setVisibility(View.GONE);
|
||||
@@ -630,7 +719,6 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
// Or the barcode will be centered instead of on top of the screen
|
||||
// Don't ask me why...
|
||||
appBarLayout.setVisibility(View.INVISIBLE);
|
||||
appBarLayout.setPadding(0, 0, 0, 0);
|
||||
collapsingToolbarLayout.setVisibility(View.GONE);
|
||||
findViewById(R.id.toolbar_landscape).setVisibility(View.GONE);
|
||||
|
||||
@@ -646,11 +734,8 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
);
|
||||
|
||||
// Set current state
|
||||
barcodeIsFullscreen = true;
|
||||
}
|
||||
else if(!enable)
|
||||
else
|
||||
{
|
||||
Log.d(TAG, "Move out of fullscreen");
|
||||
|
||||
@@ -658,10 +743,16 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
barcodeScaler.setProgress(100);
|
||||
|
||||
// Prepare redraw after size change
|
||||
redrawBarcodeAfterResize();
|
||||
if (format != null && isBarcodeSupported) {
|
||||
redrawBarcodeAfterResize();
|
||||
} else {
|
||||
barcodeImage.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Show maximize and hide minimize button and scaler
|
||||
maximizeButton.setVisibility(View.VISIBLE);
|
||||
if (format != null && isBarcodeSupported) {
|
||||
maximizeButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
minimizeButton.setVisibility(View.GONE);
|
||||
barcodeScaler.setVisibility(View.GONE);
|
||||
|
||||
@@ -676,7 +767,6 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
appBarLayout.setVisibility(View.VISIBLE);
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
appBarLayout.setPadding(0, (int) Math.ceil(metrics.density * 24), 0, 0);
|
||||
setupOrientation();
|
||||
|
||||
// Show other UI elements
|
||||
@@ -690,9 +780,8 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
);
|
||||
|
||||
// Set current state
|
||||
barcodeIsFullscreen = false;
|
||||
}
|
||||
|
||||
this.fullscreenType = fullscreenType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.widget.Toast;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@@ -97,7 +98,12 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
||||
else if (inputItem.getItemId() == R.id.action_share)
|
||||
{
|
||||
final ImportURIHelper importURIHelper = new ImportURIHelper(MainActivity.this);
|
||||
importURIHelper.startShareIntent(mAdapter.getSelectedItems());
|
||||
try {
|
||||
importURIHelper.startShareIntent(mAdapter.getSelectedItems());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Toast.makeText(MainActivity.this, R.string.failedGeneratingShareURL, Toast.LENGTH_LONG).show();
|
||||
e.printStackTrace();
|
||||
}
|
||||
inputMode.finish();
|
||||
return true;
|
||||
}
|
||||
@@ -269,14 +275,11 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
||||
// End of active tab logic
|
||||
|
||||
FloatingActionButton addButton = findViewById(R.id.fabAdd);
|
||||
addButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent i = new Intent(getApplicationContext(), ScanActivity.class);
|
||||
startActivityForResult(i, Utils.BARCODE_SCAN);
|
||||
}
|
||||
addButton.setOnClickListener(v -> {
|
||||
Intent i = new Intent(getApplicationContext(), ScanActivity.class);
|
||||
startActivityForResult(i, Utils.BARCODE_SCAN);
|
||||
});
|
||||
addButton.bringToFront();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -417,7 +420,7 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
||||
private void openPrivacyPolicy() {
|
||||
Intent browserIntent = new Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse("https://thelastproject.github.io/Catima/privacy-policy")
|
||||
Uri.parse("https://catima.app/privacy-policy")
|
||||
);
|
||||
startActivity(browserIntent);
|
||||
}
|
||||
|
||||
@@ -52,12 +52,8 @@ public class ManageGroupsActivity extends AppCompatActivity
|
||||
updateGroupList();
|
||||
|
||||
FloatingActionButton addButton = findViewById(R.id.fabAdd);
|
||||
addButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
createGroup();
|
||||
}
|
||||
});
|
||||
addButton.setOnClickListener(v -> createGroup());
|
||||
addButton.bringToFront();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,9 +2,11 @@ package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
@@ -34,6 +36,7 @@ public class ScanActivity extends AppCompatActivity {
|
||||
private DecoratedBarcodeView barcodeScannerView;
|
||||
|
||||
private String cardId;
|
||||
private boolean torch = false;
|
||||
|
||||
private void extractIntentFields(Intent intent) {
|
||||
final Bundle b = intent.getExtras();
|
||||
@@ -114,6 +117,18 @@ public class ScanActivity extends AppCompatActivity {
|
||||
return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
|
||||
getMenuInflater().inflate(R.menu.scan_menu, menu);
|
||||
}
|
||||
|
||||
barcodeScannerView.setTorchOff();
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
@@ -122,6 +137,19 @@ public class ScanActivity extends AppCompatActivity {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_toggle_flashlight)
|
||||
{
|
||||
if (torch) {
|
||||
torch = false;
|
||||
barcodeScannerView.setTorchOff();
|
||||
item.setTitle(R.string.turn_flashlight_on);
|
||||
item.setIcon(R.drawable.ic_flashlight_off_white_24dp);
|
||||
} else {
|
||||
torch = true;
|
||||
barcodeScannerView.setTorchOn();
|
||||
item.setTitle(R.string.turn_flashlight_off);
|
||||
item.setIcon(R.drawable.ic_flashlight_on_white_24dp);
|
||||
}
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
||||
@@ -4,8 +4,12 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.media.ExifInterface;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -17,6 +21,10 @@ import com.google.zxing.RGBLuminanceSource;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.NumberFormat;
|
||||
@@ -24,6 +32,8 @@ import java.util.Calendar;
|
||||
import java.util.Currency;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
|
||||
@@ -35,9 +45,15 @@ public class Utils {
|
||||
public static final int SELECT_BARCODE_REQUEST = 2;
|
||||
public static final int BARCODE_SCAN = 3;
|
||||
public static final int BARCODE_IMPORT_FROM_IMAGE_FILE = 4;
|
||||
public static final int CARD_IMAGE_FROM_CAMERA_FRONT = 5;
|
||||
public static final int CARD_IMAGE_FROM_CAMERA_BACK = 6;
|
||||
public static final int CARD_IMAGE_FROM_FILE_FRONT = 7;
|
||||
public static final int CARD_IMAGE_FROM_FILE_BACK = 8;
|
||||
|
||||
static final double LUMINANCE_MIDPOINT = 0.5;
|
||||
|
||||
static final int BITMAP_SIZE_BIG = 512;
|
||||
|
||||
static public LetterBitmap generateIcon(Context context, String store, Integer backgroundColor) {
|
||||
return generateIcon(context, store, backgroundColor, false);
|
||||
}
|
||||
@@ -208,4 +224,121 @@ public class Utils {
|
||||
// Parse as BigDecimal
|
||||
return new BigDecimal(value);
|
||||
}
|
||||
|
||||
static public byte[] bitmapToByteArray(Bitmap bitmap) {
|
||||
if (bitmap == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
|
||||
return bos.toByteArray();
|
||||
}
|
||||
|
||||
static public Bitmap resizeBitmap(Bitmap bitmap) {
|
||||
if (bitmap == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer maxSize = BITMAP_SIZE_BIG;
|
||||
|
||||
Integer width = bitmap.getWidth();
|
||||
Integer height = bitmap.getHeight();
|
||||
|
||||
if (height > width) {
|
||||
Integer scale = height / maxSize;
|
||||
height = maxSize;
|
||||
width = width / scale;
|
||||
} else if (width > height) {
|
||||
Integer scale = width / maxSize;
|
||||
width = maxSize;
|
||||
height = height / scale;
|
||||
} else {
|
||||
height = maxSize;
|
||||
width = maxSize;
|
||||
}
|
||||
|
||||
return Bitmap.createScaledBitmap(bitmap, width, height, true);
|
||||
}
|
||||
|
||||
static public Bitmap rotateBitmap(Bitmap bitmap, ExifInterface exifInterface) {
|
||||
switch (exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) {
|
||||
case ExifInterface.ORIENTATION_ROTATE_90:
|
||||
return rotateBitmap(bitmap, 90f);
|
||||
case ExifInterface.ORIENTATION_ROTATE_180:
|
||||
return rotateBitmap(bitmap, 180f);
|
||||
case ExifInterface.ORIENTATION_ROTATE_270:
|
||||
return rotateBitmap(bitmap, 270f);
|
||||
default:
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
static public Bitmap rotateBitmap(Bitmap bitmap, float rotation) {
|
||||
if (rotation == 0) {
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postRotate(rotation);
|
||||
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||
}
|
||||
|
||||
static public String getCardImageFileName(int loyaltyCardId, boolean front) {
|
||||
StringBuilder cardImageFileNameBuilder = new StringBuilder();
|
||||
|
||||
cardImageFileNameBuilder.append("card_");
|
||||
cardImageFileNameBuilder.append(loyaltyCardId);
|
||||
cardImageFileNameBuilder.append("_");
|
||||
if (front) {
|
||||
cardImageFileNameBuilder.append("front");
|
||||
} else {
|
||||
cardImageFileNameBuilder.append("back");
|
||||
}
|
||||
cardImageFileNameBuilder.append(".png");
|
||||
|
||||
return cardImageFileNameBuilder.toString();
|
||||
}
|
||||
|
||||
static public void saveCardImage(Context context, Bitmap bitmap, String fileName) throws FileNotFoundException {
|
||||
if (bitmap == null) {
|
||||
context.deleteFile(fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
FileOutputStream out = context.openFileOutput(fileName, Context.MODE_PRIVATE);
|
||||
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||
}
|
||||
|
||||
static public void saveCardImage(Context context, Bitmap bitmap, int loyaltyCardId, boolean front) throws FileNotFoundException {
|
||||
saveCardImage(context, bitmap, getCardImageFileName(loyaltyCardId, front));
|
||||
}
|
||||
|
||||
static public Bitmap retrieveCardImage(Context context, String fileName) {
|
||||
FileInputStream in;
|
||||
try {
|
||||
in = context.openFileInput(fileName);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return BitmapFactory.decodeStream(in);
|
||||
}
|
||||
|
||||
static public Bitmap retrieveCardImage(Context context, int loyaltyCardId, boolean front) {
|
||||
return retrieveCardImage(context, getCardImageFileName(loyaltyCardId, front));
|
||||
}
|
||||
|
||||
static public Object hashmapGetOrDefault(HashMap hashMap, Object key, Object defaultValue, Class keyType) {
|
||||
Object value = hashMap.get(keyType.cast(key));
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static public Object hashmapGetOrDefault(HashMap hashMap, String key, Object defaultValue) {
|
||||
return hashmapGetOrDefault(hashMap, key, defaultValue, String.class);
|
||||
}
|
||||
}
|
||||
|
||||
36
app/src/main/java/protect/card_locker/ZipUtils.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
|
||||
import net.lingala.zip4j.io.inputstream.ZipInputStream;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class ZipUtils {
|
||||
static public String read(ZipInputStream zipInputStream) throws IOException {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
Reader reader = new BufferedReader(new InputStreamReader(zipInputStream, Charset.forName(StandardCharsets.UTF_8.name())));
|
||||
int c;
|
||||
while ((c = reader.read()) != -1) {
|
||||
stringBuilder.append((char) c);
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
static public Bitmap readImage(ZipInputStream zipInputStream) {
|
||||
return BitmapFactory.decodeStream(zipInputStream);
|
||||
}
|
||||
|
||||
static public JSONObject readJSON(ZipInputStream zipInputStream) throws IOException, JSONException {
|
||||
return new JSONObject(read(zipInputStream));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import net.lingala.zip4j.ZipFile;
|
||||
import net.lingala.zip4j.io.outputstream.ZipOutputStream;
|
||||
import net.lingala.zip4j.model.ZipParameters;
|
||||
import net.lingala.zip4j.util.InternalZipConstants;
|
||||
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVPrinter;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import protect.card_locker.DBHelper;
|
||||
import protect.card_locker.Group;
|
||||
import protect.card_locker.LoyaltyCard;
|
||||
import protect.card_locker.Utils;
|
||||
|
||||
/**
|
||||
* Class for exporting the database into CSV (Comma Separate Values)
|
||||
* format.
|
||||
*/
|
||||
public class CatimaExporter implements Exporter
|
||||
{
|
||||
public void exportData(Context context, DBHelper db, OutputStream output) throws IOException, InterruptedException
|
||||
{
|
||||
// Necessary vars
|
||||
int readLen;
|
||||
byte[] readBuffer = new byte[InternalZipConstants.BUFF_SIZE];
|
||||
|
||||
// Create zip output stream
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(output);
|
||||
|
||||
// Generate CSV
|
||||
ByteArrayOutputStream catimaOutputStream = new ByteArrayOutputStream();
|
||||
OutputStreamWriter catimaOutputStreamWriter = new OutputStreamWriter(catimaOutputStream, StandardCharsets.UTF_8);
|
||||
writeCSV(db, catimaOutputStreamWriter);
|
||||
|
||||
// Add CSV to zip file
|
||||
ZipParameters csvZipParameters = new ZipParameters();
|
||||
csvZipParameters.setFileNameInZip("catima.csv");
|
||||
zipOutputStream.putNextEntry(csvZipParameters);
|
||||
InputStream csvInputStream = new ByteArrayInputStream(catimaOutputStream.toByteArray());
|
||||
while ((readLen = csvInputStream.read(readBuffer)) != -1) {
|
||||
zipOutputStream.write(readBuffer, 0, readLen);
|
||||
}
|
||||
zipOutputStream.closeEntry();
|
||||
|
||||
// Loop over all cards again
|
||||
Cursor cardCursor = db.getLoyaltyCardCursor();
|
||||
while(cardCursor.moveToNext())
|
||||
{
|
||||
// For each card
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
|
||||
|
||||
// Prepare looping over both front and back image
|
||||
boolean[] frontValues = new boolean[2];
|
||||
frontValues[0] = true;
|
||||
frontValues[1] = false;
|
||||
|
||||
// For each image
|
||||
for (boolean front : frontValues) {
|
||||
// If it exists, add to the .zip file
|
||||
Bitmap image = Utils.retrieveCardImage(context, card.id, front);
|
||||
if (image != null) {
|
||||
ZipParameters imageZipParameters = new ZipParameters();
|
||||
imageZipParameters.setFileNameInZip(Utils.getCardImageFileName(card.id, front));
|
||||
zipOutputStream.putNextEntry(imageZipParameters);
|
||||
InputStream imageInputStream = new ByteArrayInputStream(Utils.bitmapToByteArray(image));
|
||||
while ((readLen = imageInputStream.read(readBuffer)) != -1) {
|
||||
zipOutputStream.write(readBuffer, 0, readLen);
|
||||
}
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zipOutputStream.close();
|
||||
}
|
||||
|
||||
private void writeCSV(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException {
|
||||
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
|
||||
|
||||
// Print the version
|
||||
printer.printRecord("2");
|
||||
|
||||
printer.println();
|
||||
|
||||
// Print the header for groups
|
||||
printer.printRecord(DBHelper.LoyaltyCardDbGroups.ID);
|
||||
|
||||
Cursor groupCursor = db.getGroupCursor();
|
||||
|
||||
while(groupCursor.moveToNext())
|
||||
{
|
||||
Group group = Group.toGroup(groupCursor);
|
||||
|
||||
printer.printRecord(group._id);
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
groupCursor.close();
|
||||
|
||||
// Print an empty line
|
||||
printer.println();
|
||||
|
||||
// Print the header for cards
|
||||
printer.printRecord(DBHelper.LoyaltyCardDbIds.ID,
|
||||
DBHelper.LoyaltyCardDbIds.STORE,
|
||||
DBHelper.LoyaltyCardDbIds.NOTE,
|
||||
DBHelper.LoyaltyCardDbIds.EXPIRY,
|
||||
DBHelper.LoyaltyCardDbIds.BALANCE,
|
||||
DBHelper.LoyaltyCardDbIds.BALANCE_TYPE,
|
||||
DBHelper.LoyaltyCardDbIds.CARD_ID,
|
||||
DBHelper.LoyaltyCardDbIds.BARCODE_ID,
|
||||
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
|
||||
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
|
||||
DBHelper.LoyaltyCardDbIds.STAR_STATUS);
|
||||
|
||||
Cursor cardCursor = db.getLoyaltyCardCursor();
|
||||
|
||||
while(cardCursor.moveToNext())
|
||||
{
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
|
||||
|
||||
printer.printRecord(card.id,
|
||||
card.store,
|
||||
card.note,
|
||||
card.expiry != null ? card.expiry.getTime() : "",
|
||||
card.balance,
|
||||
card.balanceType,
|
||||
card.cardId,
|
||||
card.barcodeId,
|
||||
card.barcodeType,
|
||||
card.headerColor,
|
||||
card.starStatus);
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
cardCursor.close();
|
||||
|
||||
// Print an empty line
|
||||
printer.println();
|
||||
|
||||
// Print the header for card group mappings
|
||||
printer.printRecord(DBHelper.LoyaltyCardDbIdsGroups.cardID,
|
||||
DBHelper.LoyaltyCardDbIdsGroups.groupID);
|
||||
|
||||
Cursor cardCursor2 = db.getLoyaltyCardCursor();
|
||||
|
||||
while(cardCursor2.moveToNext())
|
||||
{
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor2);
|
||||
|
||||
for (Group group : db.getLoyaltyCardGroups(card.id)) {
|
||||
printer.printRecord(card.id, group._id);
|
||||
}
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
cardCursor2.close();
|
||||
|
||||
printer.close();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,20 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import net.lingala.zip4j.io.inputstream.ZipInputStream;
|
||||
import net.lingala.zip4j.model.LocalFileHeader;
|
||||
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -22,6 +28,8 @@ import java.util.List;
|
||||
import protect.card_locker.DBHelper;
|
||||
import protect.card_locker.FormatException;
|
||||
import protect.card_locker.Group;
|
||||
import protect.card_locker.Utils;
|
||||
import protect.card_locker.ZipUtils;
|
||||
|
||||
/**
|
||||
* Class for importing a database from CSV (Comma Separate Values)
|
||||
@@ -30,10 +38,36 @@ import protect.card_locker.Group;
|
||||
* 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 CsvDatabaseImporter implements DatabaseImporter
|
||||
public class CatimaImporter implements Importer
|
||||
{
|
||||
public void importData(DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException
|
||||
{
|
||||
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, InterruptedException {
|
||||
InputStream bufferedInputStream = new BufferedInputStream(input);
|
||||
bufferedInputStream.mark(100);
|
||||
|
||||
// First, check if this is a zip file
|
||||
ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream);
|
||||
LocalFileHeader localFileHeader = zipInputStream.getNextEntry();
|
||||
|
||||
if (localFileHeader == null) {
|
||||
// This is not a zip file, try importing as bare CSV
|
||||
bufferedInputStream.reset();
|
||||
importCSV(context, db, bufferedInputStream);
|
||||
return;
|
||||
}
|
||||
|
||||
importZipFile(context, db, zipInputStream, localFileHeader);
|
||||
}
|
||||
|
||||
public void importZipFile(Context context, DBHelper db, ZipInputStream input, LocalFileHeader localFileHeader) throws IOException, FormatException, InterruptedException {
|
||||
String fileName = localFileHeader.getFileName();
|
||||
if (fileName.equals("catima.csv")) {
|
||||
importCSV(context, db, new ByteArrayInputStream(ZipUtils.read(input).getBytes(StandardCharsets.UTF_8)));
|
||||
} else {
|
||||
Utils.saveCardImage(context, ZipUtils.readImage(input), fileName);
|
||||
}
|
||||
}
|
||||
|
||||
public void importCSV(Context context, DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException {
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
|
||||
|
||||
bufferedReader.mark(100);
|
||||
@@ -50,10 +84,10 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
|
||||
switch (version) {
|
||||
case 1:
|
||||
parseV1(db, bufferedReader);
|
||||
parseV1(context, db, bufferedReader);
|
||||
break;
|
||||
case 2:
|
||||
parseV2(db, bufferedReader);
|
||||
parseV2(context, db, bufferedReader);
|
||||
break;
|
||||
default:
|
||||
throw new FormatException(String.format("No code to parse version %s", version));
|
||||
@@ -62,7 +96,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
bufferedReader.close();
|
||||
}
|
||||
|
||||
public void parseV1(DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
|
||||
public void parseV1(Context context, DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
|
||||
{
|
||||
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.withHeader());
|
||||
|
||||
@@ -73,7 +107,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
{
|
||||
for (CSVRecord record : parser)
|
||||
{
|
||||
importLoyaltyCard(database, db, record);
|
||||
importLoyaltyCard(context, database, db, record);
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
@@ -95,7 +129,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
}
|
||||
}
|
||||
|
||||
public void parseV2(DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
|
||||
public void parseV2(Context context, DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
|
||||
{
|
||||
SQLiteDatabase database = db.getWritableDatabase();
|
||||
database.beginTransaction();
|
||||
@@ -116,7 +150,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
parseV2Groups(db, database, stringPart);
|
||||
break;
|
||||
case 2:
|
||||
parseV2Cards(db, database, stringPart);
|
||||
parseV2Cards(context, db, database, stringPart);
|
||||
break;
|
||||
case 3:
|
||||
parseV2CardGroups(db, database, stringPart);
|
||||
@@ -164,14 +198,14 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
}
|
||||
}
|
||||
|
||||
public void parseV2Cards(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
|
||||
public void parseV2Cards(Context context, DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
|
||||
{
|
||||
// Parse cards
|
||||
final CSVParser cardParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.withHeader());
|
||||
|
||||
try {
|
||||
for (CSVRecord record : cardParser) {
|
||||
importLoyaltyCard(database, db, record);
|
||||
importLoyaltyCard(context, database, db, record);
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
@@ -208,7 +242,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
* Import a single loyalty card into the database using the given
|
||||
* session.
|
||||
*/
|
||||
private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record)
|
||||
private void importLoyaltyCard(Context context, SQLiteDatabase database, DBHelper helper, CSVRecord record)
|
||||
throws IOException, FormatException
|
||||
{
|
||||
int id = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false);
|
||||
@@ -270,10 +304,11 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
try {
|
||||
starStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.STAR_STATUS, record, false);
|
||||
} catch (FormatException _e ) {
|
||||
// This field did not exist in versions 0.28 and before
|
||||
// This field did not exist in versions 0.278 and before
|
||||
// We catch this exception so we can still import old backups
|
||||
}
|
||||
if (starStatus != 1) starStatus = 0;
|
||||
|
||||
helper.insertLoyaltyCard(database, id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus);
|
||||
}
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVPrinter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
import protect.card_locker.DBHelper;
|
||||
import protect.card_locker.Group;
|
||||
import protect.card_locker.LoyaltyCard;
|
||||
|
||||
/**
|
||||
* Class for exporting the database into CSV (Comma Separate Values)
|
||||
* format.
|
||||
*/
|
||||
public class CsvDatabaseExporter implements DatabaseExporter
|
||||
{
|
||||
public void exportData(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException
|
||||
{
|
||||
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
|
||||
|
||||
// Print the version
|
||||
printer.printRecord("2");
|
||||
|
||||
printer.println();
|
||||
|
||||
// Print the header for groups
|
||||
printer.printRecord(DBHelper.LoyaltyCardDbGroups.ID);
|
||||
|
||||
Cursor groupCursor = db.getGroupCursor();
|
||||
|
||||
while(groupCursor.moveToNext())
|
||||
{
|
||||
Group group = Group.toGroup(groupCursor);
|
||||
|
||||
printer.printRecord(group._id);
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
groupCursor.close();
|
||||
|
||||
// Print an empty line
|
||||
printer.println();
|
||||
|
||||
// Print the header for cards
|
||||
printer.printRecord(DBHelper.LoyaltyCardDbIds.ID,
|
||||
DBHelper.LoyaltyCardDbIds.STORE,
|
||||
DBHelper.LoyaltyCardDbIds.NOTE,
|
||||
DBHelper.LoyaltyCardDbIds.EXPIRY,
|
||||
DBHelper.LoyaltyCardDbIds.BALANCE,
|
||||
DBHelper.LoyaltyCardDbIds.BALANCE_TYPE,
|
||||
DBHelper.LoyaltyCardDbIds.CARD_ID,
|
||||
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
|
||||
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
|
||||
DBHelper.LoyaltyCardDbIds.STAR_STATUS);
|
||||
|
||||
Cursor cardCursor = db.getLoyaltyCardCursor();
|
||||
|
||||
while(cardCursor.moveToNext())
|
||||
{
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
|
||||
|
||||
printer.printRecord(card.id,
|
||||
card.store,
|
||||
card.note,
|
||||
card.expiry != null ? card.expiry.getTime() : "",
|
||||
card.balance,
|
||||
card.balanceType,
|
||||
card.cardId,
|
||||
card.headerColor,
|
||||
card.barcodeType,
|
||||
card.starStatus);
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
cardCursor.close();
|
||||
|
||||
// Print an empty line
|
||||
printer.println();
|
||||
|
||||
// Print the header for card group mappings
|
||||
printer.printRecord(DBHelper.LoyaltyCardDbIdsGroups.cardID,
|
||||
DBHelper.LoyaltyCardDbIdsGroups.groupID);
|
||||
|
||||
Cursor cardCursor2 = db.getLoyaltyCardCursor();
|
||||
|
||||
while(cardCursor2.moveToNext())
|
||||
{
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor2);
|
||||
|
||||
for (Group group : db.getLoyaltyCardGroups(card.id)) {
|
||||
printer.printRecord(card.id, group._id);
|
||||
}
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
cardCursor2.close();
|
||||
|
||||
printer.close();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package protect.card_locker;
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
public enum DataFormat
|
||||
{
|
||||
Catima,
|
||||
Fidme,
|
||||
Stocard,
|
||||
VoucherVault
|
||||
;
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
import protect.card_locker.DBHelper;
|
||||
@@ -9,11 +12,11 @@ import protect.card_locker.DBHelper;
|
||||
* Interface for a class which can export the contents of the database
|
||||
* in a given format.
|
||||
*/
|
||||
public interface DatabaseExporter
|
||||
public interface Exporter
|
||||
{
|
||||
/**
|
||||
* Export the database to the output stream in a given format.
|
||||
* @throws IOException
|
||||
*/
|
||||
void exportData(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException;
|
||||
void exportData(Context context, DBHelper db, OutputStream output) throws IOException, InterruptedException;
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import net.lingala.zip4j.io.inputstream.ZipInputStream;
|
||||
import net.lingala.zip4j.model.LocalFileHeader;
|
||||
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
@@ -15,8 +19,6 @@ import java.io.StringReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import protect.card_locker.DBHelper;
|
||||
import protect.card_locker.FormatException;
|
||||
@@ -28,20 +30,20 @@ import protect.card_locker.FormatException;
|
||||
* 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 DatabaseImporter
|
||||
public class FidmeImporter implements Importer
|
||||
{
|
||||
public void importData(DBHelper db, InputStream input) throws IOException, FormatException, JSONException, ParseException {
|
||||
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);
|
||||
ZipInputStream zipInputStream = new ZipInputStream(input, password);
|
||||
|
||||
StringBuilder loyaltyCards = new StringBuilder();
|
||||
byte[] buffer = new byte[1024];
|
||||
int read = 0;
|
||||
|
||||
ZipEntry zipEntry;
|
||||
LocalFileHeader localFileHeader;
|
||||
|
||||
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
|
||||
if (zipEntry.getName().equals("loyalty_programs.csv")) {
|
||||
while ((localFileHeader = zipInputStream.getNextEntry()) != null) {
|
||||
if (localFileHeader.getFileName().equals("loyalty_programs.csv")) {
|
||||
while ((read = zipInputStream.read(buffer, 0, 1024)) >= 0) {
|
||||
loyaltyCards.append(new String(buffer, 0, read, StandardCharsets.UTF_8));
|
||||
}
|
||||
@@ -102,18 +104,18 @@ public class FidmeImporter implements DatabaseImporter
|
||||
}
|
||||
|
||||
// There seems to be no note field in the CSV? So let's combine other fields instead...
|
||||
String program = CSVHelpers.extractString("Program", record, "");
|
||||
String addedAt = CSVHelpers.extractString("Added At", record, "");
|
||||
String firstName = CSVHelpers.extractString("Firstname", record, "");
|
||||
String lastName = CSVHelpers.extractString("Lastname", record, "");
|
||||
String program = CSVHelpers.extractString("Program", record, "").trim();
|
||||
String addedAt = CSVHelpers.extractString("Added At", record, "").trim();
|
||||
String firstName = CSVHelpers.extractString("Firstname", record, "").trim();
|
||||
String lastName = CSVHelpers.extractString("Lastname", record, "").trim();
|
||||
|
||||
String combinedName = String.format("%s %s", firstName, lastName);
|
||||
String combinedName = String.format("%s %s", firstName, lastName).trim();
|
||||
|
||||
StringBuilder noteBuilder = new StringBuilder();
|
||||
if (!program.isEmpty()) noteBuilder.append(program).append('\n');
|
||||
if (!addedAt.isEmpty()) noteBuilder.append(addedAt).append('\n');
|
||||
if (!combinedName.isEmpty()) noteBuilder.append(combinedName).append('\n');
|
||||
String note = noteBuilder.toString();
|
||||
String note = noteBuilder.toString().trim();
|
||||
|
||||
// The ID is called reference
|
||||
String cardId = CSVHelpers.extractString("Reference", record, "");
|
||||
@@ -130,6 +132,8 @@ public class FidmeImporter implements DatabaseImporter
|
||||
// No favourite data in the export either
|
||||
int starStatus = 0;
|
||||
|
||||
// TODO: Front and back image
|
||||
|
||||
helper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, null, starStatus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
public enum ImportExportResult
|
||||
{
|
||||
Success,
|
||||
GenericFailure,
|
||||
BadPassword
|
||||
;
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -13,7 +15,7 @@ import protect.card_locker.FormatException;
|
||||
* Interface for a class which can import the contents of a stream
|
||||
* into the database.
|
||||
*/
|
||||
public interface DatabaseImporter
|
||||
public interface Importer
|
||||
{
|
||||
/**
|
||||
* Import data from the input stream in a given format into
|
||||
@@ -21,5 +23,5 @@ public interface DatabaseImporter
|
||||
* @throws IOException
|
||||
* @throws FormatException
|
||||
*/
|
||||
void importData(DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException, JSONException, ParseException;
|
||||
void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, InterruptedException, JSONException, ParseException;
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
import protect.card_locker.DBHelper;
|
||||
import protect.card_locker.DataFormat;
|
||||
|
||||
public class MultiFormatExporter
|
||||
{
|
||||
@@ -18,18 +19,18 @@ public class MultiFormatExporter
|
||||
*
|
||||
* The output stream is closed on success.
|
||||
*
|
||||
* @return true if the database was successfully exported,
|
||||
* false otherwise. If false, partial data may have been
|
||||
* @return ImportExportResult.Success if the database was successfully exported,
|
||||
* another ImportExportResult otherwise. If not Success, partial data may have been
|
||||
* written to the output stream, and it should be discarded.
|
||||
*/
|
||||
public static boolean exportData(DBHelper db, OutputStreamWriter output, DataFormat format)
|
||||
public static ImportExportResult exportData(Context context, DBHelper db, OutputStream output, DataFormat format)
|
||||
{
|
||||
DatabaseExporter exporter = null;
|
||||
Exporter exporter = null;
|
||||
|
||||
switch(format)
|
||||
{
|
||||
case Catima:
|
||||
exporter = new CsvDatabaseExporter();
|
||||
exporter = new CatimaExporter();
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Failed to export data, unknown format " + format.name());
|
||||
@@ -40,8 +41,8 @@ public class MultiFormatExporter
|
||||
{
|
||||
try
|
||||
{
|
||||
exporter.exportData(db, output);
|
||||
return true;
|
||||
exporter.exportData(context, db, output);
|
||||
return ImportExportResult.Success;
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
@@ -52,12 +53,12 @@ public class MultiFormatExporter
|
||||
Log.e(TAG, "Failed to export data", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
return ImportExportResult.GenericFailure;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.e(TAG, "Unsupported data format exported: " + format.name());
|
||||
return false;
|
||||
return ImportExportResult.GenericFailure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import net.lingala.zip4j.exception.ZipException;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -9,7 +12,6 @@ import java.io.InputStream;
|
||||
import java.text.ParseException;
|
||||
|
||||
import protect.card_locker.DBHelper;
|
||||
import protect.card_locker.DataFormat;
|
||||
import protect.card_locker.FormatException;
|
||||
|
||||
public class MultiFormatImporter
|
||||
@@ -23,22 +25,25 @@ public class MultiFormatImporter
|
||||
* The input stream is not closed, and doing so is the
|
||||
* responsibility of the caller.
|
||||
*
|
||||
* @return true if the database was successfully imported,
|
||||
* false otherwise. If false, no data was written to
|
||||
* @return ImportExportResult.Success if the database was successfully imported,
|
||||
* or another result otherwise. If no Success, no data was written to
|
||||
* the database.
|
||||
*/
|
||||
public static boolean importData(DBHelper db, InputStream input, DataFormat format)
|
||||
public static ImportExportResult importData(Context context, DBHelper db, InputStream input, DataFormat format, char[] password)
|
||||
{
|
||||
DatabaseImporter importer = null;
|
||||
Importer importer = null;
|
||||
|
||||
switch(format)
|
||||
{
|
||||
case Catima:
|
||||
importer = new CsvDatabaseImporter();
|
||||
importer = new CatimaImporter();
|
||||
break;
|
||||
case Fidme:
|
||||
importer = new FidmeImporter();
|
||||
break;
|
||||
case Stocard:
|
||||
importer = new StocardImporter();
|
||||
break;
|
||||
case VoucherVault:
|
||||
importer = new VoucherVaultImporter();
|
||||
break;
|
||||
@@ -48,10 +53,14 @@ public class MultiFormatImporter
|
||||
{
|
||||
try
|
||||
{
|
||||
importer.importData(db, input);
|
||||
return true;
|
||||
importer.importData(context, db, input, password);
|
||||
return ImportExportResult.Success;
|
||||
}
|
||||
catch(IOException | FormatException | InterruptedException | JSONException | ParseException e)
|
||||
catch(ZipException e)
|
||||
{
|
||||
return ImportExportResult.BadPassword;
|
||||
}
|
||||
catch(IOException | FormatException | InterruptedException | JSONException | ParseException | NullPointerException e)
|
||||
{
|
||||
Log.e(TAG, "Failed to import data", e);
|
||||
}
|
||||
@@ -61,6 +70,7 @@ public class MultiFormatImporter
|
||||
{
|
||||
Log.e(TAG, "Unsupported data format imported: " + format.name());
|
||||
}
|
||||
return false;
|
||||
|
||||
return ImportExportResult.GenericFailure;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import net.lingala.zip4j.io.inputstream.ZipInputStream;
|
||||
import net.lingala.zip4j.model.LocalFileHeader;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import protect.card_locker.DBHelper;
|
||||
import protect.card_locker.FormatException;
|
||||
import protect.card_locker.Utils;
|
||||
import protect.card_locker.ZipUtils;
|
||||
|
||||
/**
|
||||
* Class for importing a database from CSV (Comma Separate Values)
|
||||
* formatted data.
|
||||
*
|
||||
* The database's loyalty cards are expected to appear in the CSV data.
|
||||
* A header is expected for the each table showing the names of the columns.
|
||||
*/
|
||||
public class StocardImporter implements Importer
|
||||
{
|
||||
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
||||
HashMap<String, HashMap<String, Object>> loyaltyCardHashMap = new HashMap<>();
|
||||
HashMap<String, String> providers = new HashMap<>();
|
||||
|
||||
ZipInputStream zipInputStream = new ZipInputStream(input, password);
|
||||
|
||||
String[] providersFileName = null;
|
||||
String[] cardBaseName = null;
|
||||
String cardName = "";
|
||||
LocalFileHeader localFileHeader;
|
||||
while ((localFileHeader = zipInputStream.getNextEntry()) != null) {
|
||||
String fileName = localFileHeader.getFileName();
|
||||
String[] nameParts = fileName.split("/");
|
||||
|
||||
if (providersFileName == null) {
|
||||
providersFileName = new String[] {
|
||||
nameParts[0],
|
||||
"sync",
|
||||
"data",
|
||||
"users",
|
||||
nameParts[0],
|
||||
"analytics-properties.json"
|
||||
};
|
||||
cardBaseName = new String[] {
|
||||
nameParts[0],
|
||||
"sync",
|
||||
"data",
|
||||
"users",
|
||||
nameParts[0],
|
||||
"loyalty-cards"
|
||||
};
|
||||
}
|
||||
|
||||
if (startsWith(nameParts, providersFileName, 0) && !localFileHeader.isDirectory()) {
|
||||
providers = parseProviders(zipInputStream);
|
||||
} else if (startsWith(nameParts, cardBaseName, 1)) {
|
||||
// Extract cardName
|
||||
cardName = nameParts[cardBaseName.length].split("\\.", 2)[0];
|
||||
|
||||
// This is the card itself
|
||||
if (nameParts.length == cardBaseName.length + 1) {
|
||||
// Ignore the .txt file
|
||||
if (fileName.endsWith(".json")) {
|
||||
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
|
||||
|
||||
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
||||
loyaltyCardHashMap,
|
||||
cardName,
|
||||
"cardId",
|
||||
jsonObject.getString("input_id")
|
||||
);
|
||||
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
||||
loyaltyCardHashMap,
|
||||
cardName,
|
||||
"_providerId",
|
||||
jsonObject
|
||||
.getJSONObject("input_provider_reference")
|
||||
.getString("identifier")
|
||||
.substring("/loyalty-card-providers/".length())
|
||||
);
|
||||
|
||||
try {
|
||||
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
||||
loyaltyCardHashMap,
|
||||
cardName,
|
||||
"barcodeType",
|
||||
jsonObject.getString("input_barcode_format")
|
||||
);
|
||||
} catch (JSONException ignored) {}
|
||||
}
|
||||
} else if (fileName.endsWith("notes/default.json")) {
|
||||
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
||||
loyaltyCardHashMap,
|
||||
cardName,
|
||||
"note",
|
||||
ZipUtils.readJSON(zipInputStream)
|
||||
.getString("content")
|
||||
);
|
||||
} else if (fileName.endsWith("/images/front.png")) {
|
||||
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
||||
loyaltyCardHashMap,
|
||||
cardName,
|
||||
"frontImage",
|
||||
ZipUtils.readImage(zipInputStream)
|
||||
);
|
||||
} else if (fileName.endsWith("/images/back.png")) {
|
||||
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
||||
loyaltyCardHashMap,
|
||||
cardName,
|
||||
"backImage",
|
||||
ZipUtils.readImage(zipInputStream)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (loyaltyCardHashMap.keySet().size() == 0) {
|
||||
throw new FormatException("Couldn't find any loyalty cards in this Stocard export.");
|
||||
}
|
||||
|
||||
SQLiteDatabase database = db.getWritableDatabase();
|
||||
database.beginTransaction();
|
||||
|
||||
for (HashMap<String, Object> loyaltyCardData : loyaltyCardHashMap.values()) {
|
||||
String store = providers.get(loyaltyCardData.get("_providerId").toString());
|
||||
String note = (String) Utils.hashmapGetOrDefault(loyaltyCardData, "note", "");
|
||||
String cardId = (String) loyaltyCardData.get("cardId");
|
||||
String barcodeTypeString = (String) Utils.hashmapGetOrDefault(loyaltyCardData, "barcodeType", null);
|
||||
BarcodeFormat barcodeType = null;
|
||||
if (barcodeTypeString != null) {
|
||||
if (barcodeTypeString.equals("RSS_DATABAR_EXPANDED")) {
|
||||
barcodeType = BarcodeFormat.RSS_EXPANDED;
|
||||
} else {
|
||||
barcodeType = BarcodeFormat.valueOf(barcodeTypeString);
|
||||
}
|
||||
}
|
||||
|
||||
long loyaltyCardInternalId = db.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, null, 0);
|
||||
|
||||
if (loyaltyCardData.containsKey("frontImage")) {
|
||||
Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("frontImage"), (int) loyaltyCardInternalId, true);
|
||||
}
|
||||
if (loyaltyCardData.containsKey("backImage")) {
|
||||
Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("backImage"), (int) loyaltyCardInternalId, false);
|
||||
}
|
||||
}
|
||||
|
||||
database.setTransactionSuccessful();
|
||||
database.endTransaction();
|
||||
database.close();
|
||||
|
||||
zipInputStream.close();
|
||||
}
|
||||
|
||||
private boolean startsWith(String[] full, String[] start, int minExtraLength) {
|
||||
if (full.length - minExtraLength < start.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < start.length; i++) {
|
||||
if (!start[i].contentEquals(full[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private HashMap<String, HashMap<String, Object>> appendToLoyaltyCardHashMap(HashMap<String, HashMap<String, Object>> loyaltyCardHashMap, String cardID, String key, Object value) {
|
||||
HashMap<String, Object> loyaltyCardData = loyaltyCardHashMap.get(cardID);
|
||||
if (loyaltyCardData == null) {
|
||||
loyaltyCardData = new HashMap<>();
|
||||
}
|
||||
|
||||
loyaltyCardData.put(key, value);
|
||||
loyaltyCardHashMap.put(cardID, loyaltyCardData);
|
||||
|
||||
return loyaltyCardHashMap;
|
||||
}
|
||||
|
||||
private HashMap<String, String> parseProviders(ZipInputStream zipInputStream) throws IOException, JSONException {
|
||||
// FIXME: This is probably completely wrong, but it works for the one and only test file I have
|
||||
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
|
||||
|
||||
JSONArray providerIdList = jsonObject.getJSONArray("provider_id_list");
|
||||
JSONArray providerList = jsonObject.getJSONArray("provider_list");
|
||||
|
||||
// Resort, put IDs with - in them after IDs without any -
|
||||
List<String> providerIds = new ArrayList<>();
|
||||
List<String> customProviderIds = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < providerIdList.length(); i++) {
|
||||
String providerId = providerIdList.get(i).toString();
|
||||
if (providerId.contains("-")) {
|
||||
customProviderIds.add(providerId);
|
||||
} else {
|
||||
providerIds.add(providerId);
|
||||
}
|
||||
}
|
||||
providerIds.addAll(customProviderIds);
|
||||
|
||||
HashMap<String, String> providers = new HashMap<>();
|
||||
for (int i = 0; i < jsonObject.getInt("number_of_cards"); i++) {
|
||||
providers.put(providerIds.get(i), providerList.get(i).toString());
|
||||
}
|
||||
|
||||
return providers;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Color;
|
||||
|
||||
@@ -32,9 +33,9 @@ import protect.card_locker.FormatException;
|
||||
* The database's loyalty cards are expected to appear in the CSV data.
|
||||
* A header is expected for the each table showing the names of the columns.
|
||||
*/
|
||||
public class VoucherVaultImporter implements DatabaseImporter
|
||||
public class VoucherVaultImporter implements Importer
|
||||
{
|
||||
public void importData(DBHelper db, InputStream input) throws IOException, FormatException, JSONException, ParseException {
|
||||
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -60,11 +61,13 @@ public class VoucherVaultImporter implements DatabaseImporter
|
||||
expiry = dateFormat.parse(jsonCard.getString("expires"));
|
||||
}
|
||||
|
||||
BigDecimal balance;
|
||||
if (!jsonCard.isNull("balance")) {
|
||||
BigDecimal balance = new BigDecimal("0");
|
||||
if (jsonCard.has("balanceMilliunits")) {
|
||||
if (!jsonCard.isNull("balanceMilliunits")) {
|
||||
balance = new BigDecimal(String.valueOf(jsonCard.getInt("balanceMilliunits") / 1000.0));
|
||||
}
|
||||
} else if (!jsonCard.isNull("balance")) {
|
||||
balance = new BigDecimal(String.valueOf(jsonCard.getDouble("balance")));
|
||||
} else {
|
||||
balance = new BigDecimal("0");
|
||||
}
|
||||
|
||||
Currency balanceType = Currency.getInstance("USD");
|
||||
@@ -122,7 +125,7 @@ public class VoucherVaultImporter implements DatabaseImporter
|
||||
headerColor = Color.YELLOW;
|
||||
break;
|
||||
default:
|
||||
throw new FormatException("Unknown colour type foun: " + colorFromJSON);
|
||||
throw new FormatException("Unknown colour type found: " + colorFromJSON);
|
||||
}
|
||||
|
||||
db.insertLoyaltyCard(store, "", expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0);
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="192dp"
|
||||
android:height="192dp"
|
||||
android:viewportWidth="50.8"
|
||||
android:viewportHeight="50.8">
|
||||
<path
|
||||
android:pathData="M14.3354,20.1954l20.7318,-9.2304l5.7612,12.9398l-20.7318,9.2304z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#f0f0f0"
|
||||
android:strokeColor="#c80000"/>
|
||||
<path
|
||||
android:pathData="M14.8755,10.9648l23.2041,10.3311l-6.8874,15.4694l-23.2041,-10.3311z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#f0f0f0"
|
||||
android:strokeColor="#c80000"/>
|
||||
<path
|
||||
android:pathData="M16.5599,16.1348l26.5459,7.6119l-4.5489,15.8639l-26.5459,-7.6119z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.5875"
|
||||
android:fillColor="#c80000"
|
||||
android:strokeColor="#c80000"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M12.011,15.4955h27.6157v16.5032h-27.6157z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.5875"
|
||||
android:fillColor="#ff0000"
|
||||
android:strokeColor="#ff0000"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M7.8471,23.7471a4.3659,8.5899 0,1 0,8.7317 0a4.3659,8.5899 0,1 0,-8.7317 0z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.91078"
|
||||
android:fillColor="#ff0000"
|
||||
android:strokeColor="#ff0000"/>
|
||||
<path
|
||||
android:pathData="m24.4983,25.781a1.6711,1.6711 0,0 1,-1.3809 1.6457,1.6711 1.6711,0 0,1 -1.8605,-1.0741"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#f0f0f0"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="m27.7991,26.333a1.6711,1.6711 0,0 1,-1.8605 1.0741,1.6711 1.6711,0 0,1 -1.3809,-1.6457"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#f0f0f0"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="m16.0606,22.271 l2.6458,-2.6458 2.6458,2.6458"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#f0f0f0"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="m27.7023,22.271 l2.6458,-2.6458 2.6458,2.6458"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#f0f0f0"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
||||
@@ -1,5 +0,0 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M20,3h-1L19,1h-2v2L7,3L7,1L5,1v2L4,3c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,5c0,-1.1 -0.9,-2 -2,-2zM20,21L4,21L4,8h16v13z"/>
|
||||
</vector>
|
||||
15
app/src/main/res/drawable/ic_flashlight_off_white_24dp.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M18,5l0,-3l-12,0l0,1.17l1.83,1.83z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<path
|
||||
android:pathData="M16,11l2,-3l0,-1l-8.17,0l6.17,6.17z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<path
|
||||
android:pathData="M2.81,2.81L1.39,4.22L8,10.83V22h8v-3.17l3.78,3.78l1.41,-1.41L2.81,2.81z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/ic_flashlight_on_white_24dp.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M6,2h12v3h-12z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<path
|
||||
android:pathData="M6,7v1l2,3v11h8V11l2,-3V7H6zM12,15.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5S12.83,15.5 12,15.5z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@android:color/darker_gray"/>
|
||||
<size android:height="1dp"/>
|
||||
</shape>
|
||||
@@ -304,6 +304,27 @@
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/upceBarcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:id="@+id/upceBarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
@@ -38,6 +39,10 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/barcode"/>
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/photos"/>
|
||||
</com.google.android.material.tabs.TabLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
@@ -326,6 +331,84 @@
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
</TableLayout>
|
||||
|
||||
<TableLayout
|
||||
android:id="@+id/picturesPart"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<!-- Front image -->
|
||||
<LinearLayout
|
||||
android:id="@+id/frontImageHolder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
android:paddingTop="@dimen/inputPadding">
|
||||
|
||||
<!-- Front image -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="@dimen/activity_margin"
|
||||
android:layout_marginTop="@dimen/activity_margin"
|
||||
android:layout_marginEnd="@dimen/activity_margin"
|
||||
android:layout_marginBottom="@dimen/activity_margin"
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/frontImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:minHeight="50dp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:contentDescription="@string/frontImageDescription"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_camera_white" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Back image -->
|
||||
<LinearLayout
|
||||
android:id="@+id/backImageHolder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
android:paddingTop="@dimen/inputPadding">
|
||||
|
||||
<!-- Back image -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="@dimen/activity_margin"
|
||||
android:layout_marginTop="@dimen/activity_margin"
|
||||
android:layout_marginEnd="@dimen/activity_margin"
|
||||
android:layout_marginBottom="@dimen/activity_margin"
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/backImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:minHeight="50dp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:contentDescription="@string/backImageDescription"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_camera_white" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
</TableLayout>
|
||||
</TableLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
@@ -134,60 +134,122 @@
|
||||
android:id="@+id/bottom_sheet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/inputBackground"
|
||||
android:fitsSystemWindows="false"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp"
|
||||
android:visibility="gone"
|
||||
app:behavior_hideable="false"
|
||||
app:behavior_peekHeight="104dp"
|
||||
app:behavior_peekHeight="80dp"
|
||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
|
||||
tools:visibility="visible"
|
||||
android:fitsSystemWindows="true">
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/bottomSheetButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:layout_gravity="top|start"
|
||||
android:background="@color/colorPrimary"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_gravity="top|start"
|
||||
android:tint="@android:color/white"
|
||||
app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/noteView"
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/bottomSheetContentWrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/inputBackground"
|
||||
android:gravity="center"
|
||||
android:padding="20dp"
|
||||
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/groupsView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/inputBackground"
|
||||
android:gravity="center"
|
||||
android:padding="20dp"
|
||||
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/balanceView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/inputBackground"
|
||||
android:gravity="center"
|
||||
android:padding="20dp"
|
||||
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/expiryView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/inputBackground"
|
||||
android:gravity="center"
|
||||
android:padding="20dp"
|
||||
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/frontImageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="@dimen/activity_margin"
|
||||
android:layout_marginTop="@dimen/activity_margin"
|
||||
android:layout_marginEnd="@dimen/activity_margin"
|
||||
android:layout_marginBottom="@dimen/activity_margin"
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
android:layout_weight="1"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/frontImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@string/frontImageDescription"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/backImageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="@dimen/activity_margin"
|
||||
android:layout_marginTop="@dimen/activity_margin"
|
||||
android:layout_marginEnd="@dimen/activity_margin"
|
||||
android:layout_marginBottom="@dimen/activity_margin"
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
android:layout_weight="1"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/backImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@string/backImageDescription"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/noteView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="20dp"
|
||||
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/groupsView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="20dp"
|
||||
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/balanceView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="20dp"
|
||||
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/expiryView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="20dp"
|
||||
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
@@ -197,7 +259,6 @@
|
||||
android:clipToPadding="false"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="24dp"
|
||||
android:weightSum="1.0"
|
||||
android:fitsSystemWindows="true">
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
@@ -243,6 +304,7 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
app:contentInsetStart="72.0dip"
|
||||
app:layout_collapseMode="pin" />
|
||||
app:layout_collapseMode="pin"
|
||||
android:paddingTop="6dp" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
9
app/src/main/res/menu/scan_menu.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_toggle_flashlight"
|
||||
android:icon="@drawable/ic_flashlight_off_white_24dp"
|
||||
android:title="@string/turn_flashlight_on"
|
||||
app:showAsAction="always"/>
|
||||
</menu>
|
||||
@@ -93,7 +93,7 @@
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> Karten</item>
|
||||
</plurals>
|
||||
<string name="groupsList">Gruppen: <xliff:g>%s</xliff:g></string>
|
||||
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
|
||||
<string name="app_loyalty_card_keychain">Bonuskartenschlüsselring</string>
|
||||
<string name="chooseImportType">Daten importieren aus\?</string>
|
||||
<string name="parsingBalanceFailed"><xliff:g>%s</xliff:g> scheint kein gültiges Guthaben zu sein.</string>
|
||||
<string name="points">Punkte</string>
|
||||
@@ -137,7 +137,7 @@
|
||||
<string name="noBarcodeFound">Kein Strichcode gefunden</string>
|
||||
<string name="addFromImage">Bild aus der Galerie auswählen</string>
|
||||
<string name="settings_max_font_size_scale">Max. Schriftgröße</string>
|
||||
<string name="unsupportedBarcodeType">Dieser Strichcodetyp kann noch nicht angezeigt werden. Er wird möglicherweise in einer neueren Version der Anwendung unterstützt.</string>
|
||||
<string name="unsupportedBarcodeType">Dieser Strichcodetyp kann noch nicht angezeigt werden. Er wird möglicherweise in einer späteren Version der Anwendung unterstützt.</string>
|
||||
<string name="wrongValueForBarcodeType">Der Wert ist für den gewählten Strichcodetyp nicht gültig</string>
|
||||
<string name="app_resources">Freie Ressourcen von Drittanbietern: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Freie Bibliotheken von Drittanbietern: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
@@ -146,4 +146,23 @@
|
||||
<string name="copy_to_clipboard_multiple_toast">Kartennummern in die Zwischenablage kopiert</string>
|
||||
<string name="card_ids_copied">Kartennummer(n) kopiert</string>
|
||||
<string name="card_selected">"Ausgewählt: "</string>
|
||||
<string name="no">Nein</string>
|
||||
<string name="yes">Ja</string>
|
||||
<string name="updateBarcodeQuestionText">Sie haben die Karten-ID geändert. Möchten Sie auch den Strichcode aktualisieren, um denselben Wert zu verwenden\?</string>
|
||||
<string name="updateBarcodeQuestionTitle">Strichcodewert aktualisieren\?</string>
|
||||
<string name="chooseImageFromGallery">Bild aus der Galerie auswählen</string>
|
||||
<string name="takePhoto">Foto aufnehmen</string>
|
||||
<string name="removeImage">Bild entfernen</string>
|
||||
<string name="setBackImage">Rückseitenbild einstellen</string>
|
||||
<string name="setFrontImage">Vorderseitenbild einstellen</string>
|
||||
<string name="photos">Fotos</string>
|
||||
<string name="frontImageDescription">Bild der Kartenvorderseite</string>
|
||||
<string name="backImageDescription">Bild der Kartenrückseite</string>
|
||||
<string name="passwordRequired">Bitte geben Sie das Passwort ein</string>
|
||||
<string name="importStocardMessage">Suchen Sie Ihre Stocard-.zip-Datei zum Importieren, und wählen Sie anschließend die Strichcodetypen manuell aus.
|
||||
\nOder Sie erhalten sie, indem Sie eine E-Mail an support@stocardapp.com senden und zuerst um einen Export Ihrer Daten bitten.</string>
|
||||
<string name="importStocard">Von Stocard importieren</string>
|
||||
<string name="turn_flashlight_off">Taschenlampe ausschalten</string>
|
||||
<string name="turn_flashlight_on">Taschenlampe einschalten</string>
|
||||
<string name="failedGeneratingShareURL">Fehler beim Generieren der Freigabe-URL. Bitte melden Sie diesen Fehler!</string>
|
||||
</resources>
|
||||
56
app/src/main/res/values-eo/strings.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?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="deleteConfirmation">Ĉu forigi ĉi tiun karton\?</string>
|
||||
<string name="deleteTitle">Forigi karton</string>
|
||||
<string name="barcodeNoBarcode">Strekokodo mankas al karto</string>
|
||||
<string name="delete">Forigi</string>
|
||||
<string name="noBarcode">Sen strekokodo</string>
|
||||
<string name="barcodeType">Tipo de strekokodo</string>
|
||||
<string name="cardId">Identigilo de karto</string>
|
||||
<string name="settings_category_title_ui">Fasado</string>
|
||||
<string name="settings">Agordoj</string>
|
||||
<string name="selectBarcodeTitle">Elekti strekokodon</string>
|
||||
<string name="debug_version_fmt">Versio: <xliff:g id="version">%s</xliff:g></string>
|
||||
<string name="about_title_fmt">Pri <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Kopirajto © 2019–<xliff:g>%d</xliff:g> Sylvia van Os.</string>
|
||||
<string name="importOptionFilesystemButton">El dosiersistemo</string>
|
||||
<string name="importOptionFilesystemTitle">Enporti el dosiersistemo</string>
|
||||
<string name="exportFailedTitle">Elportado malsukcesis</string>
|
||||
<string name="exportSuccessfulTitle">Elportado sukcesis</string>
|
||||
<string name="importFailedTitle">Enportado malsukcesis</string>
|
||||
<string name="importSuccessfulTitle">Enportado sukcesis</string>
|
||||
<string name="exporting">Elportante…</string>
|
||||
<string name="importing">Enportante…</string>
|
||||
<string name="exportName">Elporti</string>
|
||||
<string name="importExport">Enporti/elporti</string>
|
||||
<string name="addCardTitle">Aldoni karton</string>
|
||||
<string name="editCardTitle">Redakti karton</string>
|
||||
<string name="sendLabel">Sendi…</string>
|
||||
<string name="takePhoto">Foti</string>
|
||||
<string name="no">Ne</string>
|
||||
<string name="yes">Jes</string>
|
||||
<string name="photos">Fotoj</string>
|
||||
<string name="points">Poentoj</string>
|
||||
<string name="currency">Valuto</string>
|
||||
<string name="editBarcode">Redakti strekokodon</string>
|
||||
<string name="barcode">Strekokodo</string>
|
||||
<string name="card">Karto</string>
|
||||
<string name="never">Neniam</string>
|
||||
<string name="groupsList">Grupoj: <xliff:g>%s</xliff:g></string>
|
||||
<string name="groups">Grupoj</string>
|
||||
<string name="settings_dark_theme">Malhela</string>
|
||||
<string name="settings_light_theme">Hela</string>
|
||||
<string name="settings_system_theme">Sistema</string>
|
||||
<string name="settings_theme">Etoso</string>
|
||||
<string name="about">Pri</string>
|
||||
<string name="note">Noto</string>
|
||||
<string name="storeName">Nomo</string>
|
||||
<string name="confirm">Konfirmi</string>
|
||||
<string name="ok">Bone</string>
|
||||
<string name="edit">Redakti</string>
|
||||
<string name="save">Konservi</string>
|
||||
<string name="cancel">Nuligi</string>
|
||||
<string name="card_selected">"Elektita: "</string>
|
||||
<string name="action_add">Aldoni</string>
|
||||
<string name="action_search">Serĉi</string>
|
||||
</resources>
|
||||
161
app/src/main/res/values-fi/strings.xml
Normal file
@@ -0,0 +1,161 @@
|
||||
<?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="noExternalStoragePermissionError">Salli käyttöoikeus ulkoisen tallennustilan käyttöön voidaksesi tuoda tai viedä kortteja</string>
|
||||
<string name="no">Ei</string>
|
||||
<string name="yes">Kyllä</string>
|
||||
<string name="updateBarcodeQuestionText">Vaihdoit kortin ID-tunnuksen. Haluatko päivittää myös viivakoodin käyttämään samaa arvoa\?</string>
|
||||
<string name="updateBarcodeQuestionTitle">Päivitä viivakoodin arvo\?</string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">Haluan jakaa joitain kortteja kanssasi</string>
|
||||
<string name="copy_to_clipboard_multiple_toast">Kopioitiin korttitunnukset leikepöydälle</string>
|
||||
<string name="wrongValueForBarcodeType">Arvo ei ole kelvollinen valitulle viivakoodityypille</string>
|
||||
<string name="unsupportedBarcodeType">Tätä viivakoodityyppiä ei voi vielä näyttää. Sitä saatetaan tukea sovelluksen uudemmassa versiossa.</string>
|
||||
<string name="setBarcodeId">Aseta viivakoodin arvo</string>
|
||||
<string name="sameAsCardId">Sama kuin kortin ID-tunnus</string>
|
||||
<string name="barcodeId">Viivakoodin arvo</string>
|
||||
<string name="importVoucherVaultMessage">Etsi tiedostoa joka on todennäköisesti nimetty nimellä <i>vouchervault.json</i> tuotavaksi.
|
||||
\nTai luo se Vie toiminnolla Voucher Vault sovelluksessa.</string>
|
||||
<string name="importVoucherVault">Tuo Voucher Vault varmuuskopiotiedostosta</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Etsi tiedostoa joka on todennäköisesti nimetty nimellä <i>LoyaltyCardKeychain.csv</i> tuotavaksi.
|
||||
\nTai luo se Tuo/Vie toiminnolla Loyalty Card Keychain sovelluksessa, valitsemalla valikosta Vie.</string>
|
||||
<string name="importLoyaltyCardKeychain">Tuo Loyalty Card Keychain varmuuskopiotiedostosta</string>
|
||||
<string name="importFidmeMessage">Etsi tiedostoa joka on todennäköisesti nimetty nimellä <i>fidme-export-request-xxxxxx.zip</i> tuotavaksi ja valitse viivakoodityypit manuaalisesti jälkeenpäin.
|
||||
\nTai luo se Tuo/Vie toiminnolla FidMe profiilistasi, valitsemalla Tietotosuoja ja sitten valitsemalla Vie tietoni.</string>
|
||||
<string name="importFidme">Tuo FidMe varmuuskopiotiedostosta</string>
|
||||
<string name="importCatimaMessage">Etsi tiedostoa joka on todennäköisesti nimetty nimellä <i>Catima.csv</i> tuotavaksi.
|
||||
\nTai luo se Tuo/Vie toiminnolla Catima sovelluksessa, valitsemalla valikosta Vie.</string>
|
||||
<string name="importCatima">Tuo Catima varmuuskopiotiedostosta</string>
|
||||
<string name="accept">Hyväksy</string>
|
||||
<string name="privacy_policy_popup_text">Tietosuojaseloste (joidenkin sovelluskauppojen vaatimus):
|
||||
\n
|
||||
\nMITÄÄN TIETOJA EI KERÄTÄ LAINKAAN, minkä kuka tahansa voi vahvistaa, koska sovelluksemma on vapaa ohjelmisto.</string>
|
||||
<string name="privacy_policy">Tietosuojakäytäntö</string>
|
||||
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
|
||||
<string name="chooseImportType">Tuo tietoja kohteesta\?</string>
|
||||
<string name="parsingBalanceFailed"><xliff:g>%s</xliff:g> ei vaikuta olevan kelvollinen saldo.</string>
|
||||
<string name="points">Pisteet</string>
|
||||
<string name="currency">Valuutta</string>
|
||||
<string name="balance">Saldo</string>
|
||||
<string name="errorReadingImage">Kuvaa ei voitu lukea</string>
|
||||
<string name="noBarcodeFound">Viivakoodia ei löytynyt</string>
|
||||
<string name="moveBarcodeToCenterOfScreen">Keskitä viivakoodi näytölle</string>
|
||||
<string name="moveBarcodeToTopOfScreen">Siirrä viivakoodi näytön yläosaan</string>
|
||||
<string name="chooseExpiryDate">Valitse viimeinen voimassaolopäivä</string>
|
||||
<string name="never">Ei koskaan</string>
|
||||
<string name="expiryDate">Viimeinen voimassaolopäivä</string>
|
||||
<string name="editBarcode">Muokkaa viivakoodia</string>
|
||||
<string name="barcode">Viivakoodi</string>
|
||||
<string name="card">Kortti</string>
|
||||
<string name="balancePoints"><xliff:g>%s</xliff:g> pistettä</string>
|
||||
<string name="balanceSentence">Saldo: <xliff:g>%s</xliff:g></string>
|
||||
<string name="expiryStateSentenceExpired">Vanhentunut: <xliff:g>%s</xliff:g></string>
|
||||
<string name="expiryStateSentence">Vanhenee: <xliff:g>%s</xliff:g></string>
|
||||
<string name="groupsList">Ryhmät: <xliff:g>%s</xliff:g></string>
|
||||
<string name="addFromImage">Valitse kuva galleriasta</string>
|
||||
<string name="addManually">Anna kortin ID-tunnus manuaalisesti</string>
|
||||
<string name="leaveWithoutSaveConfirmation">Poistu tallentamatta\?</string>
|
||||
<string name="leaveWithoutSaveTitle">Poistu</string>
|
||||
<string name="moveDown">Siirrä alaspäin</string>
|
||||
<string name="moveUp">Siirrä ylöspäin</string>
|
||||
<string name="failedOpeningFileManager">Asenna ensin tiedostonhallintaohjelma.</string>
|
||||
<string name="deleteConfirmationGroup">Poista ryhmä\?</string>
|
||||
<string name="all">Kaikki</string>
|
||||
<plurals name="groupCardCount">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> kortti</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> kortit</item>
|
||||
</plurals>
|
||||
<string name="noGroups">Napsauta + plus-painiketta lisätäksesi ensin ryhmät luokittelua varten.</string>
|
||||
<string name="groups">Ryhmät</string>
|
||||
<string name="enter_group_name">Anna ryhmän nimi</string>
|
||||
<string name="exportSuccessful">Korttitietojen vienti valmis</string>
|
||||
<string name="importSuccessful">Korttitietojen tuonti valmis</string>
|
||||
<string name="intent_import_card_from_url_share_text">Haluan jakaa kortin kanssasi</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card">Estä lukitusnäyttö</string>
|
||||
<string name="settings_keep_screen_on">Pidä näyttö päällä</string>
|
||||
<string name="settings_lock_barcode_orientation">Lukitse viivakoodin suunta</string>
|
||||
<string name="settings_display_barcode_max_brightness">Kirkasta viivakoodinäkymää</string>
|
||||
<string name="settings_max_font_size_scale">Fontin enimmäiskoko</string>
|
||||
<string name="settings_dark_theme">Tumma</string>
|
||||
<string name="settings_light_theme">Vaalea</string>
|
||||
<string name="settings_system_theme">Järjestelmän oletus</string>
|
||||
<string name="settings_theme">Teema</string>
|
||||
<string name="settings_category_title_ui">Käyttöliittymä</string>
|
||||
<string name="settings">Asetukset</string>
|
||||
<string name="starImage">Suosikki tähti</string>
|
||||
<string name="thumbnailDescription">Kortin pikkukuva</string>
|
||||
<string name="copy_to_clipboard_toast">Kortin ID-tunnus kopioitu leikepöydälle</string>
|
||||
<string name="enterBarcodeInstructions">Syötä kortin ID-tunnus ja valitse sen viivakoodityyppi, tai valitse \"Tällä kortilla ei ole viivakoodia\".</string>
|
||||
<string name="selectBarcodeTitle">Valitse viivakoodi</string>
|
||||
<string name="app_resources">Kolmannen osapuolen vapaat resurssit: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Kolmannen osapuolen vapaat kirjastot: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_revision_fmt">Muutostiedot: <xliff:g id="app_revision_url">%s</xliff:g></string>
|
||||
<string name="debug_version_fmt">Versio: <xliff:g id="version">%s</xliff:g></string>
|
||||
<string name="about_title_fmt">Tietoja <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="app_license">Copyleft (käyttäjänoikeus) - vapaa ohjelmisto, lisenssi GPLv3+.</string>
|
||||
<string name="app_copyright_old">Perustuu Loyalty Card Keychain sovellukseen
|
||||
\ntekijänoikeus © 2016–2020 Branden Archer.</string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Tekijänoikeus © 2019–<xliff:g>%d</xliff:g> Sylvia van Os.</string>
|
||||
<string name="about">Tietoja</string>
|
||||
<string name="importOptionApplicationButton">Käytä toista sovellusta</string>
|
||||
<string name="importOptionApplicationExplanation">Käytä mitä tahansa sovellusta tai suosikkitiedostonhallintaasi tiedoston avaamiseen.</string>
|
||||
<string name="importOptionApplicationTitle">Käytä toista sovellusta</string>
|
||||
<string name="importOptionFilesystemButton">Tiedostojärjestelmästä</string>
|
||||
<string name="importOptionFilesystemExplanation">Valitse tietty tiedosto tiedostojärjestelmästä.</string>
|
||||
<string name="importOptionFilesystemTitle">Tuo tiedostojärjestelmästä</string>
|
||||
<string name="exportOptionExplanation">Tiedot kirjoitetaan valitsemaasi sijaintiin.</string>
|
||||
<string name="exporting">Viedään…</string>
|
||||
<string name="importing">Tuodaan…</string>
|
||||
<string name="exportFailed">Kortteja ei voitu viedä</string>
|
||||
<string name="exportFailedTitle">Vienti epäonnistui</string>
|
||||
<string name="exportSuccessfulTitle">Vienti valmis</string>
|
||||
<string name="importFailed">Kortteja ei voitu tuoda</string>
|
||||
<string name="importFailedTitle">Tuonti epäonnistui</string>
|
||||
<string name="importSuccessfulTitle">Tuonti valmis</string>
|
||||
<string name="importExportHelp">Varmuuskopioimalla korttisi, voit siirtää ne toiseen laitteeseen.</string>
|
||||
<string name="exportName">Vie</string>
|
||||
<string name="importExport">Tuo/Vie</string>
|
||||
<string name="failedParsingImportUriError">Tuonnin URI: n jäsentäminen epäonnistui</string>
|
||||
<string name="noCardExistsError">Korttia ei löytynyt</string>
|
||||
<string name="noCardIdError">Kortin ID-tunnusta ei annettu</string>
|
||||
<string name="noStoreError">Nimeä ei annettu</string>
|
||||
<string name="barcodeImageDescription">Kuva kortin viivakoodista</string>
|
||||
<string name="card_ids_copied">Kopioidut korttitunnukset</string>
|
||||
<string name="noCardsMessage">Lisää ensin kortti</string>
|
||||
<string name="cardShortcut">Kortin pikakuvake</string>
|
||||
<string name="scanCardBarcode">Skannaa kortin viivakoodi</string>
|
||||
<string name="addCardTitle">Lisää kortti</string>
|
||||
<string name="editCardTitle">Muokkaa korttia</string>
|
||||
<string name="sendLabel">Lähetä…</string>
|
||||
<string name="share">Jaa</string>
|
||||
<string name="copy_to_clipboard">Kopioi ID-tunnus leikepöydälle</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="deleteConfirmation">Poista tämä kortti\?</string>
|
||||
<string name="deleteTitle">Poista kortti</string>
|
||||
<string name="unlockScreen">Poista kierron esto</string>
|
||||
<string name="lockScreen">Estä kierto</string>
|
||||
<string name="confirm">Vahvista</string>
|
||||
<string name="delete">Poista</string>
|
||||
<string name="edit">Muokkaa</string>
|
||||
<string name="save">Tallenna</string>
|
||||
<string name="cancel">Peruuta</string>
|
||||
<string name="unstar">Poista suosikeista</string>
|
||||
<string name="star">Lisää suosikkeihin</string>
|
||||
<string name="noBarcode">Ei viivakoodia</string>
|
||||
<string name="barcodeNoBarcode">Tällä kortilla ei ole viivakoodia</string>
|
||||
<string name="barcodeType">Viivakoodin tyyppi</string>
|
||||
<string name="cardId">Kortin ID-tunnus</string>
|
||||
<string name="note">Lisätieto</string>
|
||||
<string name="storeName">Nimi</string>
|
||||
<string name="noMatchingGiftCards">Ei hakutuloksia, kokeile toisella hakutermillä.</string>
|
||||
<string name="noGiftCards">Lisää ensin kortti napsauttamalla + plus-painiketta, tai mene ⋮ valikkoon tuodaksesi varmuuskopiosta.</string>
|
||||
<string name="card_selected">"Valittu: "</string>
|
||||
<string name="action_add">Lisää</string>
|
||||
<string name="action_search">Hae</string>
|
||||
<string name="takePhoto">Ota valokuva</string>
|
||||
<string name="chooseImageFromGallery">Valitse kuva galleriasta</string>
|
||||
<string name="removeImage">Poista kuva</string>
|
||||
<string name="setBackImage">Aseta takakuva</string>
|
||||
<string name="setFrontImage">Aseta etukuva</string>
|
||||
<string name="photos">Valokuvat</string>
|
||||
<string name="backImageDescription">Kortin takakuva</string>
|
||||
<string name="frontImageDescription">Kortin etukuva</string>
|
||||
</resources>
|
||||
@@ -118,17 +118,17 @@
|
||||
<string name="expiryStateSentence">Expire : <xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">%s</xliff:g></string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card">Empêcher le verrouillage de l’écran</string>
|
||||
<string name="settings_keep_screen_on">Garder l’écran allumé</string>
|
||||
<string name="importVoucherVaultMessage">Trouvez un fichier probablement nommé <i>vouchervault.json</i> à importer.
|
||||
\nOu créez-le en appuyant d’abord sur Exporter dans Voucher Vault.</string>
|
||||
<string name="importVoucherVaultMessage">Sélectionnez votre exportation <i>vouchervault.json</i> de Voucher Vault à importer.
|
||||
\nOu créez-la en appuyant d’abord sur Exporter dans Voucher Vault.</string>
|
||||
<string name="importVoucherVault">Importer depuis Voucher Vault</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Trouvez un fichier probablement nommé <i>LoyaltyCardKeychain.csv</i> à importer.
|
||||
\nOu créez-le à partir du menu Importer/Exporter du Loyalty Card Keychain en appuyant d’abord sur Exporter.</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Sélectionnez votre exportation <i>LoyaltyCardKeychain.csv</i> à partir de Loyalty Card Keychain pour l’importer.
|
||||
\nOu créez-la à partir du menu Importer/Exporter du Loyalty Card Keychain en appuyant d’abord sur Exporter.</string>
|
||||
<string name="importLoyaltyCardKeychain">Importer depuis Loyalty Card Keychain</string>
|
||||
<string name="importFidmeMessage">Trouvez un fichier probablement nommé <i>fidme-export-request-xxxxxx.zip</i> pour l’importer, et sélectionnez ensuite manuellement les types de codes-barres.
|
||||
\nVous pouvez aussi le créer à partir de votre profil FidMe en choisissant Protection des données, puis en cliquant sur Extraire mes données.</string>
|
||||
<string name="importFidmeMessage">Sélectionnez votre exportation <i>fidme-export-request-xxxxxx.zip</i> de FidMe pour l’importer, puis sélectionnez manuellement les types de codes-barres.
|
||||
\nOu créez-la à partir de votre profil FidMe en choisissant Protection des données, puis en cliquant sur Extraire mes données d’abord.</string>
|
||||
<string name="importFidme">Importer depuis FidMe</string>
|
||||
<string name="importCatimaMessage">Trouvez un fichier probablement nommé <i>Catima.csv</i> à importer.
|
||||
\nOu créez-le à partir du menu Importer/Exporter d’une autre application Catima en appuyant d’abord sur Exporter.</string>
|
||||
<string name="importCatimaMessage">Sélectionnez votre exportation <i>catima.zip</i> depuis Catima à importer.
|
||||
\nOu créez-la à partir du menu Importer/Exporter d’une autre application Catima en appuyant d’abord sur Exporter.</string>
|
||||
<string name="importCatima">Importer depuis Catima</string>
|
||||
<string name="addFromImage">Sélectionner dans la galerie</string>
|
||||
<string name="errorReadingImage">Impossible de lire l\'image</string>
|
||||
@@ -137,13 +137,32 @@
|
||||
<string name="sameAsCardId">Identique à l’identifiant de la carte</string>
|
||||
<string name="barcodeId">Valeur du code-barres</string>
|
||||
<string name="settings_max_font_size_scale">Taille max. de la police</string>
|
||||
<string name="unsupportedBarcodeType">Ce type de code-barres ne peut pas encore être affiché. Il sera peut-être pris en charge dans une version plus récente de l\'application.</string>
|
||||
<string name="unsupportedBarcodeType">Ce type de code-barres ne peut pas encore être affiché. Il sera peut-être pris en charge dans une version ultérieure de l’application.</string>
|
||||
<string name="wrongValueForBarcodeType">La valeur n\'est pas valide pour le type de code-barres sélectionné</string>
|
||||
<string name="app_resources">Ressources libres tierces : <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Bibliothèques libres tierces : <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Ressources tierces libres : <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Bibliothèques tierces libres : <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Copyright © 2019–<xliff:g>%d</xliff:g> Sylvia van Os.</string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">Je veux partager des cartes avec vous</string>
|
||||
<string name="copy_to_clipboard_multiple_toast">Nméros des cartes copiés dans le presse-papier</string>
|
||||
<string name="copy_to_clipboard_multiple_toast">Identifiants des cartes copiés dans le presse-papiers</string>
|
||||
<string name="card_ids_copied">Num. de la carte copié(s)</string>
|
||||
<string name="card_selected">"Sélectionnée : "</string>
|
||||
<string name="updateBarcodeQuestionText">Vous avez changé l\'identifiant de la carte. Voulez-vous également mettre à jour le code-barres pour utiliser la même valeur \?</string>
|
||||
<string name="no">Non</string>
|
||||
<string name="yes">Oui</string>
|
||||
<string name="updateBarcodeQuestionTitle">Mettre à jour la valeur du code-barres \?</string>
|
||||
<string name="chooseImageFromGallery">Choisir une image dans la galerie</string>
|
||||
<string name="takePhoto">Prendre une photo</string>
|
||||
<string name="removeImage">Retirer l’image</string>
|
||||
<string name="setBackImage">Définir l’image verso</string>
|
||||
<string name="setFrontImage">Définir l’image recto</string>
|
||||
<string name="photos">Photos</string>
|
||||
<string name="backImageDescription">Image verso de la carte</string>
|
||||
<string name="frontImageDescription">Image recto de la carte</string>
|
||||
<string name="passwordRequired">Veuillez entrer le mot de passe</string>
|
||||
<string name="importStocardMessage">Sélectionnez votre exportation <i>***-sync.zip</i> de Stocard pour l’importer, et sélectionnez les types de codes-barres manuellement par la suite.
|
||||
\nVous pouvez aussi l’obtenir en envoyant un courriel à support@stocardapp.com pour demander une exportation de vos données.</string>
|
||||
<string name="importStocard">Importer depuis Stocard</string>
|
||||
<string name="turn_flashlight_off">Éteindre la lampe de poche</string>
|
||||
<string name="turn_flashlight_on">Allumer la lampe de poche</string>
|
||||
<string name="failedGeneratingShareURL">Échec de la génération de l’URL de partage. Veuillez signaler ce problème !</string>
|
||||
</resources>
|
||||
@@ -137,7 +137,7 @@
|
||||
<string name="noBarcodeFound">Nessun codice a barre trovato</string>
|
||||
<string name="addFromImage">Seleziona immagine dalla galleria</string>
|
||||
<string name="settings_max_font_size_scale">Dimensione mass. caratteri</string>
|
||||
<string name="unsupportedBarcodeType">Questo tipo di codice a barre non può ancora essere visualizzato. Potrebbe essere supportato in una versione più recente dell\'applicazione.</string>
|
||||
<string name="unsupportedBarcodeType">Questo tipo di codice a barre non può ancora essere visualizzato. Potrebbe essere supportato in una versione successiva dell\'applicazione.</string>
|
||||
<string name="wrongValueForBarcodeType">Il valore non è valido per il tipo di codice a barre selezionato</string>
|
||||
<string name="app_resources">Risorse libre di terze parti: <xliff:g id="app_resources_list"> %s </xliff:g></string>
|
||||
<string name="app_libraries">Librerie libre di terze parti: <xliff:g id="app_libraries_list"> %s </xliff:g></string>
|
||||
@@ -146,4 +146,23 @@
|
||||
<string name="copy_to_clipboard_multiple_toast">Numeri delle carte copiati negli appunti</string>
|
||||
<string name="card_ids_copied">Numero/i della carta copiato/i</string>
|
||||
<string name="card_selected">"Selezionata: "</string>
|
||||
<string name="no">No</string>
|
||||
<string name="yes">Sì</string>
|
||||
<string name="updateBarcodeQuestionText">Hai cambiato l\'ID della carta. Vuoi anche aggiornare il codice a barre per usare lo stesso valore\?</string>
|
||||
<string name="updateBarcodeQuestionTitle">Aggiornare il valore del codice a barre\?</string>
|
||||
<string name="takePhoto">Scatta una foto</string>
|
||||
<string name="chooseImageFromGallery">Scegli un’immagine dalla galleria</string>
|
||||
<string name="removeImage">Rimuovi l’immagine</string>
|
||||
<string name="setBackImage">Imposta immagine posteriore</string>
|
||||
<string name="setFrontImage">Imposta immagine frontale</string>
|
||||
<string name="photos">Foto</string>
|
||||
<string name="backImageDescription">Immagine posteriore della carta</string>
|
||||
<string name="frontImageDescription">Immagine frontale della carta</string>
|
||||
<string name="passwordRequired">Si prega di inserire la password</string>
|
||||
<string name="importStocardMessage">Trova il tuo file .zip Stocard da importare e poi seleziona manualmente i tipi di codici a barre in seguito.
|
||||
\nO ottenerlo inviando un messaggio a support@stocardapp.com e chiedendo prima un\'esportazione dei tuoi dati.</string>
|
||||
<string name="importStocard">Importa da Stocard</string>
|
||||
<string name="turn_flashlight_off">Spegni la torcia</string>
|
||||
<string name="turn_flashlight_on">Accendi la torcia</string>
|
||||
<string name="failedGeneratingShareURL">Impossibile generare l\'URL di condivisione. Si prega di segnalare questo errore!</string>
|
||||
</resources>
|
||||
@@ -1,10 +1,8 @@
|
||||
<resources
|
||||
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="action_add">Pridėti</string>
|
||||
|
||||
<string name="noGiftCards">Šiuo metu neturite nė vienos įvestos lojalumo kortelės. Paspauskite "+" (pliuso) pliuso mygtuką, kad pradėtumėte.\n\nLoyalty Card Locker leidžia Jums visada nešiotis lojalumo kortelių informaciją savo telefone ar planšetėje, taip jos visada pasiekiamos.</string>
|
||||
<string name="storeName">Parduotuvė</string>
|
||||
<string name="noGiftCards">Norėdami pridėti kortelę, spustelėkite mygtuką + plius arba pirmiausia importuokite kortelę iš ⋮ meniu.</string>
|
||||
<string name="storeName">Pavadinimas</string>
|
||||
<string name="note">Užrašas</string>
|
||||
<string name="cardId">Kortelės ID</string>
|
||||
<string name="cancel">Atšaukti</string>
|
||||
@@ -13,22 +11,21 @@
|
||||
<string name="delete">Ištrinti</string>
|
||||
<string name="confirm">Patvirtinti</string>
|
||||
<string name="deleteTitle">Panaikinti lojalumo kortelę</string>
|
||||
<string name="deleteConfirmation">Prašome patvirtinti jog Jūs norite panaikinti šią lojalumo kortelę.</string>
|
||||
<string name="deleteConfirmation">Ištrinti šią kortelę\?</string>
|
||||
<string name="ok">Gerai</string>
|
||||
<string name="copy_to_clipboard">Nukopijuoti ID į iškarpinę</string>
|
||||
<string name="editCardTitle">Redaguoti lojalumo kortelę</string>
|
||||
<string name="addCardTitle">Pridėti lojalumo kortelę</string>
|
||||
<string name="scanCardBarcode">Nuskanuokite kortelės brūkšninį kodą</string>
|
||||
<string name="scanCardBarcode">Skenuoti kortelės brūkšninį kodą</string>
|
||||
<string name="barcodeImageDescription">Kortelės brūkšninio kodo paveikslėlis</string>
|
||||
|
||||
<string name="noStoreError">Parduotuvė neįvesta</string>
|
||||
<string name="noCardIdError">Neįvestas kortelės ID</string>
|
||||
<string name="importExport">Importuoti/Exportuoti</string>
|
||||
<string name="exportName">Exportuoti</string>
|
||||
<string name="importFailed">Nepavyko importuoti</string>
|
||||
<string name="exportFailed">Nepavyko eksportuoti</string>
|
||||
<string name="importing">Importuoja…</string>
|
||||
<string name="exporting">Eksportuoja…</string>
|
||||
<string name="importing">Importuoja…</string>
|
||||
<string name="exporting">Eksportuoja…</string>
|
||||
<string name="noExternalStoragePermissionError">Negalima importuoti/eksportuoti kortelių be išorinės atminties leidimo</string>
|
||||
<string name="about">Apie</string>
|
||||
<string name="app_license">Licenzijuota pagal GPLv3.</string>
|
||||
@@ -37,5 +34,17 @@
|
||||
<string name="app_revision_fmt">Revizijos informacija: <xliff:g id="app_revision_url">%s</xliff:g></string>
|
||||
<string name="selectBarcodeTitle">Pasirinkite brūkšninį kodą</string>
|
||||
<string name="copy_to_clipboard_toast">Kortelės ID nukopijuota į iškarpinę</string>
|
||||
|
||||
</resources>
|
||||
<string name="card_ids_copied">Nukopijuotos kortelės ID</string>
|
||||
<string name="noCardsMessage">Pirmiausia pridėkite kortelę</string>
|
||||
<string name="sendLabel">Siųsti…</string>
|
||||
<string name="unlockScreen">Atblokuoti pasukimą</string>
|
||||
<string name="lockScreen">Blokuoti pasukimą</string>
|
||||
<string name="unstar">Pašalinti iš mėgstamiausių</string>
|
||||
<string name="star">Pridėti prie mėgstamiausių</string>
|
||||
<string name="noBarcode">Nėra brūkšninio kodo</string>
|
||||
<string name="barcodeNoBarcode">Ši kortelė neturi brūkšninio kodo</string>
|
||||
<string name="barcodeType">Brūkšninio kodo tipas</string>
|
||||
<string name="noMatchingGiftCards">Nieko nerasta. Pabandykite pakeisti paiešką.</string>
|
||||
<string name="card_selected">"Pasirinkta: "</string>
|
||||
<string name="action_search">Ieškoti</string>
|
||||
</resources>
|
||||
@@ -1,8 +1,8 @@
|
||||
<?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="action_add">Legg til</string>
|
||||
<string name="noGiftCards">Klikk på «+» (pluss)-knappen for å legge til eller importere kort fra ⋮-menyen først-.</string>
|
||||
<string name="storeName">Butikk</string>
|
||||
<string name="noGiftCards">Klikk på «+» (pluss)-knappen for å legge til eller importer kort fra «⋮»-menyen først.</string>
|
||||
<string name="storeName">Navn</string>
|
||||
<string name="note">Merknad</string>
|
||||
<string name="cardId">Kort-ID</string>
|
||||
<string name="barcodeType">Strekkodetype</string>
|
||||
@@ -24,7 +24,7 @@
|
||||
<string name="cardShortcut">Kort-snarvei</string>
|
||||
<string name="noCardsMessage">Legg til et kort først</string>
|
||||
<string name="barcodeImageDescription">Bilde av kortets strekkode</string>
|
||||
<string name="noStoreError">Ingen butikk angitt</string>
|
||||
<string name="noStoreError">Navn ikke angitt</string>
|
||||
<string name="noCardIdError">Ingen kort-ID innskrevet</string>
|
||||
<string name="noCardExistsError">Kunne ikke finne kort</string>
|
||||
<string name="importExport">Import/eksport</string>
|
||||
@@ -43,16 +43,16 @@
|
||||
<string name="importOptionFilesystemTitle">Importer fra filsystem</string>
|
||||
<string name="importOptionFilesystemExplanation">Velg spesifikk fil fra filsystemet.</string>
|
||||
<string name="importOptionFilesystemButton">Fra filsystem</string>
|
||||
<string name="importOptionApplicationTitle">Bruk eksternt program</string>
|
||||
<string name="importOptionApplicationTitle">Bruk et annet program</string>
|
||||
<string name="importOptionApplicationExplanation">Bruk hvilket som helst program, eller din favoritt-filutforsker for å åpne en fil.</string>
|
||||
<string name="importOptionApplicationButton">Bruk eksternt program</string>
|
||||
<string name="importOptionApplicationButton">Bruk et annet program</string>
|
||||
<string name="about">Om</string>
|
||||
<string name="app_license">Gemenhetslig fri programvare, lisensiert GPLv3+.</string>
|
||||
<string name="about_title_fmt">Om <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="debug_version_fmt">Versjon: <xliff:g id="version">%s</xliff:g></string>
|
||||
<string name="app_revision_fmt">Utgivelsesinfo: <xliff:g id="app_revision_url">%s</xliff:g></string>
|
||||
<string name="selectBarcodeTitle">Velg strekkode</string>
|
||||
<string name="enterBarcodeInstructions">Skriv inn strekkodeverdien og velg så bildet som representerer strekkoden du ønsker å bruke.</string>
|
||||
<string name="enterBarcodeInstructions">Skriv inn kortets ID, og enten velg dens strekkode nedenfor, eller «Dette kortet har ingen strekkode».</string>
|
||||
<string name="copy_to_clipboard_toast">Kort-ID kopiert til utklippstavle</string>
|
||||
<string name="thumbnailDescription">Miniatyrbilde for kort</string>
|
||||
<string name="settings">Innstillinger</string>
|
||||
@@ -86,8 +86,8 @@
|
||||
<string name="leaveWithoutSaveConfirmation">Forlat uten å lagre\?</string>
|
||||
<string name="leaveWithoutSaveTitle">Avslutt</string>
|
||||
<string name="addManually">Skriv inn kort-ID manuelt</string>
|
||||
<string name="moveDown">Flytt nedover i listen</string>
|
||||
<string name="moveUp">Flytt oppover i listen</string>
|
||||
<string name="moveDown">Flytt nedover</string>
|
||||
<string name="moveUp">Flytt oppover</string>
|
||||
<plurals name="groupCardCount">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> kort</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> kort</item>
|
||||
@@ -110,12 +110,12 @@
|
||||
<string name="balancePoints"><xliff:g>%s</xliff:g> poeng</string>
|
||||
<string name="balanceSentence">Saldo: <xliff:g>%s</xliff:g></string>
|
||||
<string name="chooseImportType">Importer data fra\?</string>
|
||||
<string name="app_loyalty_card_keychain">Kundekortsknippe</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card">Skru av låseskjerm under kortvisning</string>
|
||||
<string name="settings_keep_screen_on">Behold skjerm påslått under kortvisning</string>
|
||||
<string name="privacy_policy_popup_text">Mange programbutikker krever at programmer viser personvernspraksisen sin ved første oppstart. Her er vår:
|
||||
<string name="app_loyalty_card_keychain">Kundekortknippe</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card">Forhindre skjermlås</string>
|
||||
<string name="settings_keep_screen_on">Behold skjerm påslått</string>
|
||||
<string name="privacy_policy_popup_text">Personvernspraksis-notis (påkrevd av noen programbutikker):
|
||||
\n
|
||||
\nVi SAMLER IKKE IN NOEN DATA overhodet, og programmet vårt er fri programvare, så alle kan bekrefte at dette stemmer.</string>
|
||||
\nINGEN DATA SAMLES INN, noe alle kan bekreftes siden programmet vårt er fri programvare.</string>
|
||||
<string name="accept">Godta</string>
|
||||
<string name="privacy_policy">Personvernspraksis</string>
|
||||
<string name="importFidme">Importer fra FidMe</string>
|
||||
@@ -133,17 +133,34 @@
|
||||
<string name="importLoyaltyCardKeychainMessage">Finn en fil som antagelig heter <i>LoyaltyCardKeychain.csv</i> å importere.
|
||||
\nEller opprett den i Import/eksport-menyen i Kundekortknippe ved å trykke «Eksporter» der først.</string>
|
||||
<string name="importLoyaltyCardKeychain">Importer fra Kundekortknippe</string>
|
||||
<string name="importFidmeMessage">Finn en fil som antagelig heter <i>fidme.export-request-xxxxx.zip</i> å importere, for så å så velge strekkodetypene manuelt etterpå-
|
||||
<string name="importFidmeMessage">Finn en fil som antagelig heter <i>fidme.export-request-xxxxx.zip</i> å importere, for så å velge strekkodetypene manuelt etterpå-
|
||||
\nEller opprett den i din FidMe-profil ved å velge «Databeskyttelse», for så å trykke «Pakk ut dataen min» først.</string>
|
||||
<string name="importCatimaMessage">Finn en fil som antagelig heter <i>Catima.csv</i> å importere.
|
||||
\nEller opprett den i Import/eksport-menyen i et annet Catima-program ved å trykke «Eksporter» der først.</string>
|
||||
<string name="settings_max_font_size_scale">Maksimal skriftstørrelse</string>
|
||||
<string name="wrongValueForBarcodeType">Verdien er ikke gyldig for valgt strekkodetype</string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">Jeg ønsker å dele noen kort med deg</string>
|
||||
<string name="copy_to_clipboard_multiple_toast">Kopierte kort-ID til utklippstavle</string>
|
||||
<string name="copy_to_clipboard_multiple_toast">Kopierte kort-ID-er til utklippstavle</string>
|
||||
<string name="app_resources">Frie tredjepartsressurser: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Frie tredjepartsbibliotek: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="card_selected">"Valgt: "</string>
|
||||
<string name="card_ids_copied">Kopierte kort-ID(er)</string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Opphavsrett © 2019–<xliff:g>%d</xliff:g> Sylvia van Os.</string>
|
||||
<string name="updateBarcodeQuestionText">Du har endret kortets ID. Ønsker du å også oppdatere strekkoden til samme verdi\?</string>
|
||||
<string name="no">Nei</string>
|
||||
<string name="yes">Ja</string>
|
||||
<string name="updateBarcodeQuestionTitle">Oppdater strekkodeverdi\?</string>
|
||||
<string name="takePhoto">Ta et bilde</string>
|
||||
<string name="chooseImageFromGallery">Velg bilde fra galleriet</string>
|
||||
<string name="removeImage">Fjern bilde</string>
|
||||
<string name="setBackImage">Sett bakside</string>
|
||||
<string name="setFrontImage">Sett forside</string>
|
||||
<string name="photos">Bilder</string>
|
||||
<string name="backImageDescription">Kortets bakside</string>
|
||||
<string name="frontImageDescription">Kortets forside</string>
|
||||
<string name="importStocardMessage">Finn din Stocard.zip-fil å importere, og velg strekkodetypene manuelt etterpå.
|
||||
\nEller få den ved å sende e-post til support@stocardapp.com der du etterspør eksport av dataen din.</string>
|
||||
<string name="passwordRequired">Skriv inn passordet</string>
|
||||
<string name="importStocard">Importer fra Stocard</string>
|
||||
<string name="failedGeneratingShareURL">Klarte ikke å lage delingsnettadresse. Rapporter denne feilen.</string>
|
||||
</resources>
|
||||
@@ -118,15 +118,15 @@
|
||||
\nER WORDEN GEEN GEGEVENS VERZAMELD. Bovendien is onze app open source, zodat een ieder met eigen ogen kan zien wat de app wel of niet doet.</string>
|
||||
<string name="privacy_policy">Privacybeleid</string>
|
||||
<string name="accept">Accepteren</string>
|
||||
<string name="importVoucherVaultMessage">Kies het te importeren Voucher Vault-exportbestand genaamd <i>vouchervault.json</i>.
|
||||
\nOf ga naar het exportmenu van Voucher Vault om exportbestand samen te stellen.</string>
|
||||
<string name="importVoucherVaultMessage">Kies het te importeren <i>vouchervault.json</i>-exportbestand.
|
||||
\nOf ga naar het exportmenu van Voucher Vault om een exportbestand samen te stellen.</string>
|
||||
<string name="importVoucherVault">Importeren uit Voucher Vault</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Kies het te importeren Klantenkaartkluis-exportbestand - genaamd <i>LoyaltyCardKeychain.csv</i>.
|
||||
<string name="importLoyaltyCardKeychainMessage">Kies het te importeren genaamd <i>LoyaltyCardKeychain.csv</i>-exportbestand.
|
||||
\nOf ga naar het import-/exportmenu van Klantenkaartkluis om een exportbestand samen te stellen.</string>
|
||||
<string name="importLoyaltyCardKeychain">Importeren uit Klantenkaartkluis</string>
|
||||
<string name="importFidmeMessage">Kies het te importeren FidMe-exportbestand genaamd <i>fidme-export-request-xxxxxx.zip</i>.
|
||||
<string name="importFidmeMessage">Kies het te importeren <i>fidme-export-request-xxxxxx.zip</i>-exportbestand en kies nadien de juiste barcodes.
|
||||
\nOf ga naar je FidMe-profiel en druk op ‘Gegevensbescherming’ om een exportbestand samen te stellen.</string>
|
||||
<string name="importCatimaMessage">Kies het te importeren exportbestand genaamd <i>Catima.csv</i>.
|
||||
<string name="importCatimaMessage">Kies het te importeren <i>Catima.zip</i>-exportbestand.
|
||||
\nOf ga naar het import-/exportmenu van Catima op een ander apparaat om een exportbestand samen te stellen.</string>
|
||||
<string name="importFidme">Importeren uit FidMe</string>
|
||||
<string name="importCatima">Importeren uit Catima</string>
|
||||
@@ -139,11 +139,30 @@
|
||||
<string name="settings_max_font_size_scale">Max. tekstgrootte</string>
|
||||
<string name="unsupportedBarcodeType">Dit type barcode kan nog niet worden getoond - we hopen hiervoor in een nieuwere versie ondersteuning toe te voegen.</string>
|
||||
<string name="wrongValueForBarcodeType">Deze waarde komt niet overeen met het gekozen barcodetype</string>
|
||||
<string name="app_resources">Externe vrije bronnen: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Externe vrije bibliotheken: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Vrije externe bronnen: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Vrije externe bibliotheken: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Auteursrecht © 2019–<xliff:g>%d</xliff:g> Sylvia van Os.</string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">Ik wil kaarten met je delen</string>
|
||||
<string name="copy_to_clipboard_multiple_toast">De kaart-id‘s zijn gekopieerd naar het klembord</string>
|
||||
<string name="copy_to_clipboard_multiple_toast">De kaart-id\'s zijn gekopieerd naar het klembord</string>
|
||||
<string name="card_ids_copied">De kaart-id‘s zijn gekopieerd</string>
|
||||
<string name="card_selected">"Geselecteerd: "</string>
|
||||
<string name="no">Nee</string>
|
||||
<string name="yes">Ja</string>
|
||||
<string name="updateBarcodeQuestionText">Je hebt de kaart-id aangepast. Wil je dezelfde waarde toekennen aan de barcode\?</string>
|
||||
<string name="updateBarcodeQuestionTitle">Barcodewaarde bijwerken\?</string>
|
||||
<string name="takePhoto">Foto maken</string>
|
||||
<string name="chooseImageFromGallery">Kiezen uit galerij</string>
|
||||
<string name="removeImage">Afbeelding verwijderen</string>
|
||||
<string name="setFrontImage">Voorzijde kiezen</string>
|
||||
<string name="setBackImage">Achterzijde kiezen</string>
|
||||
<string name="photos">Afbeeldingen</string>
|
||||
<string name="backImageDescription">Achterzijde van de kaart</string>
|
||||
<string name="frontImageDescription">Voorzijde van de kaart</string>
|
||||
<string name="passwordRequired">Voer het wachtwoord in</string>
|
||||
<string name="importStocardMessage">Kies het te importeren Stocard-exportbestand genaamd <i>***-sync.zip</i> en kies nadien de juiste barcodes.
|
||||
\nOf stuur een e-mail naar support@stocardapp.com waarin je vraagt om een exportbestand.</string>
|
||||
<string name="importStocard">Importeren uit Stocard</string>
|
||||
<string name="failedGeneratingShareURL">De te delen link kan niet worden gegenereerd. Meld deze bug!</string>
|
||||
<string name="turn_flashlight_off">Zaklamp uitzetten</string>
|
||||
<string name="turn_flashlight_on">Zaklamp aanzetten</string>
|
||||
</resources>
|
||||
@@ -68,7 +68,7 @@
|
||||
<string name="settings_dark_theme">Тёмная</string>
|
||||
<string name="settings_display_barcode_max_brightness">Максимальная яркость при показе карты</string>
|
||||
<string name="settings_lock_barcode_orientation">Портретная ориентация экрана при показе карты</string>
|
||||
<string name="intent_import_card_from_url_share_text">Я хочу поделиться картой с вами</string>
|
||||
<string name="intent_import_card_from_url_share_text">Я хочу поделиться с вами картой</string>
|
||||
<string name="exportSuccessful">Данные карт успешно экспортированы</string>
|
||||
<string name="all">Все</string>
|
||||
<string name="noGroups">Нажмите кнопку \"+\", чтобы добавить группы для упорядочивания записей.</string>
|
||||
@@ -120,15 +120,15 @@
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> карт</item>
|
||||
</plurals>
|
||||
<string name="accept">Принять</string>
|
||||
<string name="importVoucherVaultMessage">Выберите файл для импортирования, обычно именуемый <i>vouchervault.json</i>.
|
||||
<string name="importVoucherVaultMessage">Выберите файл для импортирования, именуемый <i>vouchervault.json</i>.
|
||||
\nФайл экспорта можно создать в приложении Voucher Vault, нажав кнопку \"Экспорт\".</string>
|
||||
<string name="importVoucherVault">Импорт из Voucher Vault</string>
|
||||
<string name="importFidmeMessage">Выберите файл для импортирования, обычно именуемый <i>fidme-export-request-xxxxxx.zip</i>, а затем вручную выберите типы штрих-кодов.
|
||||
<string name="importFidmeMessage">Выберите файл для импортирования, именуемый <i>fidme-export-request-xxxxxx.zip</i>, а затем вручную укажите типы штрих-кодов.
|
||||
\nФайл экспорта можно создать в приложении FidMe, перейдя в свой профиль, выбрав функцию \"Защита данных\", и затем нажав кнопку \"Извлечь мои данные\".</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Выберите файл экспорта, обычно именуемый <i>LoyaltyCardKeychain.csv</i>.
|
||||
<string name="importLoyaltyCardKeychainMessage">Выберите файл экспорта, именуемый <i>LoyaltyCardKeychain.csv</i>.
|
||||
\nФайл экспорта можно создать в приложении Loyalty Card Keychain, перейдя в меню \"Импорт/Экспорт\" и нажав кнопку \"Экспорт\".</string>
|
||||
<string name="importLoyaltyCardKeychain">Импорт из Loyalty Card Keychain</string>
|
||||
<string name="importCatimaMessage">Выберите файл для импортирования, обычно именуемый <i>Catima.csv</i>.
|
||||
<string name="importCatimaMessage">Выберите файл для импортирования, именуемый <i>Catima.zip</i>.
|
||||
\nФайл экспорта можно создать в другой копии Catima, перейдя в меню \"Импорт/Экспорт\" и нажав кнопку \"Экспорт\".</string>
|
||||
<string name="importFidme">Импорт из FidMe</string>
|
||||
<string name="importCatima">Импорт из Catima</string>
|
||||
@@ -141,11 +141,30 @@
|
||||
<string name="settings_max_font_size_scale">Максимальный размер шрифта</string>
|
||||
<string name="unsupportedBarcodeType">В настоящее время данный тип штрих-кодов не отображается. Его поддержка может быть добавлена в следующих версиях приложения.</string>
|
||||
<string name="wrongValueForBarcodeType">Недопустимое значение для выбранного типа штрих-кода</string>
|
||||
<string name="app_resources">Сторонние свободные ресурсы: <xliff:g id="app_resources_list">%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="app_libraries">Свободные сторонние библиотеки: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Авторские права © 2019–<xliff:g>%d</xliff:g> Sylvia van Os.</string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">Поделиться картами</string>
|
||||
<string name="card_ids_copied">Скопированные номера карт</string>
|
||||
<string name="copy_to_clipboard_multiple_toast">Номера карт скопированы в буфер обмена</string>
|
||||
<string name="card_selected">"Выбрано: "</string>
|
||||
<string name="updateBarcodeQuestionText">Вы изменили номер карты. Обновить также значение штрих-кода, чтобы использовалось одинаковое значение\?</string>
|
||||
<string name="no">Нет</string>
|
||||
<string name="yes">Да</string>
|
||||
<string name="updateBarcodeQuestionTitle">Обновить значение штрих-кода\?</string>
|
||||
<string name="setBackImage">Изображение задней стороны</string>
|
||||
<string name="setFrontImage">Изображение лицевой стороны</string>
|
||||
<string name="takePhoto">Сфотографировать</string>
|
||||
<string name="chooseImageFromGallery">Выбрать изображение из галереи</string>
|
||||
<string name="removeImage">Удалить изображение</string>
|
||||
<string name="backImageDescription">Задняя сторона карты</string>
|
||||
<string name="frontImageDescription">Лицевая сторона карты</string>
|
||||
<string name="photos">Фотографии</string>
|
||||
<string name="importStocardMessage">Выберите файл <i>***-sync.zip</i> для импортирования, а затем вручную укажите типы штрих-кодов.
|
||||
\nЭтот файл можно получить по электронной почте от support@stocardapp.com, предварительно запросив экспорт ваших данных.</string>
|
||||
<string name="passwordRequired">Введите пароль</string>
|
||||
<string name="importStocard">Импорт из Stocard</string>
|
||||
<string name="failedGeneratingShareURL">Невозможно создать URL для обмена. Пожалуйста, сообщите об ошибке!</string>
|
||||
<string name="turn_flashlight_off">Отключить вспышку</string>
|
||||
<string name="turn_flashlight_on">Включить вспышку</string>
|
||||
</resources>
|
||||
@@ -1,4 +1,155 @@
|
||||
<resources
|
||||
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
|
||||
</resources>
|
||||
<?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="privacy_policy_popup_text">Політика конфіденційності (вимагається деякими магазинами):
|
||||
\n
|
||||
\nЖОДНОЇ ІНФОРМАЦІЇ НЕ ЗБИРАЄТЬСЯ, що може підтвердити будь-хто, адже наша програма це вільне програмне забезпечення.</string>
|
||||
<string name="noGiftCards">Натисніть + щоб додати карту чи спочатку імпортуйте з ⋮ меню.</string>
|
||||
<string name="settings_display_barcode_max_brightness">Яскравіший штрих-код</string>
|
||||
<string name="enterBarcodeInstructions">Введіть ID карти та оберіть тип штрих-коду чи \"Ця карта не має штрих-коду\".</string>
|
||||
<string name="selectBarcodeTitle">Оберіть штрих-код</string>
|
||||
<string name="barcodeImageDescription">Зображення штрих-коду карти</string>
|
||||
<string name="scanCardBarcode">Відсканувати штрих-код карти</string>
|
||||
<string name="noBarcode">Без штрих-коду</string>
|
||||
<string name="barcodeNoBarcode">Ця карта не має штрих-коду</string>
|
||||
<string name="barcodeType">Тип штрих-коду</string>
|
||||
<plurals name="groupCardCount">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> картка</item>
|
||||
<item quantity="few"><xliff:g>%d</xliff:g> карток</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> картки</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> картки</item>
|
||||
</plurals>
|
||||
<string name="no">НІ</string>
|
||||
<string name="yes">Так</string>
|
||||
<string name="updateBarcodeQuestionText">Ви змінили ID картки. Чи ви бажаєте оновити штрих-код для використання цього ж значення\?</string>
|
||||
<string name="updateBarcodeQuestionTitle">Оновити значення штрих-коду\?</string>
|
||||
<string name="wrongValueForBarcodeType">Значення не дійсне для обраного типу штрих-коду</string>
|
||||
<string name="unsupportedBarcodeType">Цей тип штрих-коду поки що не відображається. Підтримку може бути додано в новішій версії програми.</string>
|
||||
<string name="setBarcodeId">Встановіть значення штрих-коду</string>
|
||||
<string name="sameAsCardId">Таке ж як ID картки</string>
|
||||
<string name="barcodeId">Значення штрих-коду</string>
|
||||
<string name="importVoucherVaultMessage">Знайдіть файл, скоріше за все названий <i>vouchervault.json</i> для імпорту.
|
||||
\nЧи створіть його натиснувши Експорт у Voucher Vault.</string>
|
||||
<string name="importVoucherVault">Імпорт з Voucher Vault</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Знайдіть файл, скоріше за все названий <i>LoyaltyCardKeychain.csv</i> для імпорту.
|
||||
\nЧи створіть його у меню імпорту/експорту Loyalty Card Keychain натиснувши Експорт.</string>
|
||||
<string name="importCatimaMessage">Знайдіть файл, скоріше за все названий <i>Catima.csv</i> для імпорту.
|
||||
\nЧи створіть його у меню імпорту/експорту у іншій Catima натиснувши Експорт.</string>
|
||||
<string name="importLoyaltyCardKeychain">Імпорт з Loyalty Card Keychain</string>
|
||||
<string name="importFidmeMessage">Знайдіть файл, скоріше за все названий <i>fidme-export-request-xxxxxx.zip</i> для імпорту і потім виставіть типи штрих-кодів вручну.
|
||||
\nЧи створіть його у вашому FidMe профілі обравши Захист даних -> Витяг даних.</string>
|
||||
<string name="importFidme">Імпорт з FidMe</string>
|
||||
<string name="importCatima">Імпорт з Catima</string>
|
||||
<string name="accept">Прийняти</string>
|
||||
<string name="privacy_policy">Політика конфіденційності</string>
|
||||
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
|
||||
<string name="chooseImportType">Імпортувати дані з\?</string>
|
||||
<string name="parsingBalanceFailed"><xliff:g>%s</xliff:g> здається, не є дійсним залишком.</string>
|
||||
<string name="points">Бали</string>
|
||||
<string name="currency">Валюта</string>
|
||||
<string name="balance">Баланс</string>
|
||||
<string name="errorReadingImage">Неможливо прочитати зображення</string>
|
||||
<string name="noBarcodeFound">Жодного штрих-коду не знайдено</string>
|
||||
<string name="moveBarcodeToCenterOfScreen">Відцентрувати штрих-код на екрані</string>
|
||||
<string name="moveBarcodeToTopOfScreen">Посунути штрих-код наверх екрану</string>
|
||||
<string name="chooseExpiryDate">Оберіть дату закінчення терміну дії</string>
|
||||
<string name="never">Ніколи</string>
|
||||
<string name="expiryDate">Дата закінчення терміну дії</string>
|
||||
<string name="editBarcode">Редагувати штрих-код</string>
|
||||
<string name="barcode">Штрих-код</string>
|
||||
<string name="card">Картка</string>
|
||||
<string name="balancePoints"><xliff:g>%s</xliff:g> балів</string>
|
||||
<string name="balanceSentence">Баланс: <xliff:g>%s</xliff:g></string>
|
||||
<string name="expiryStateSentenceExpired">Термін дії закінчився: <xliff:g>%s</xliff:g></string>
|
||||
<string name="expiryStateSentence">Термін дії закінчується: <xliff:g>%s</xliff:g></string>
|
||||
<string name="groupsList">Групи: <xliff:g>%s</xliff:g></string>
|
||||
<string name="addFromImage">Оберіть зображення з галереї</string>
|
||||
<string name="addManually">Ввести ID карти вручну</string>
|
||||
<string name="leaveWithoutSaveConfirmation">Вийти без збереження\?</string>
|
||||
<string name="leaveWithoutSaveTitle">Вихід</string>
|
||||
<string name="moveDown">Посунути донизу</string>
|
||||
<string name="moveUp">Посунути вгору</string>
|
||||
<string name="failedOpeningFileManager">Спочатку встановіть файловий менеджер.</string>
|
||||
<string name="deleteConfirmationGroup">Видалити групу\?</string>
|
||||
<string name="all">Усі</string>
|
||||
<string name="noGroups">Спочатку натисніть + щоб додати групи для категоризації.</string>
|
||||
<string name="groups">Групи</string>
|
||||
<string name="enter_group_name">Введіть ім\'я групи</string>
|
||||
<string name="exportSuccessful">Дані картки/карток експортовано</string>
|
||||
<string name="importSuccessful">Дані картки/карток імпортовано</string>
|
||||
<string name="intent_import_card_from_url_share_text">Я хочу поділитися картою з тобою</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card">Не блокувати екран</string>
|
||||
<string name="settings_keep_screen_on">Не вимикати екран</string>
|
||||
<string name="settings_lock_barcode_orientation">Заблокувати орієнтацію штрих-коду</string>
|
||||
<string name="settings_max_font_size_scale">Макс. розмір шрифту</string>
|
||||
<string name="settings_dark_theme">Темна</string>
|
||||
<string name="settings_light_theme">Світла</string>
|
||||
<string name="settings_system_theme">Система</string>
|
||||
<string name="settings_theme">Тема</string>
|
||||
<string name="settings_category_title_ui">Інтерфейс користувача</string>
|
||||
<string name="settings">Налаштування</string>
|
||||
<string name="starImage">Улюблена зірка</string>
|
||||
<string name="thumbnailDescription">Ескіз для карти</string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">Я хочу поділитися деякими картами з тобою</string>
|
||||
<string name="copy_to_clipboard_multiple_toast">ID карток скопійовано до буферу обміну</string>
|
||||
<string name="copy_to_clipboard_toast">ID карти скопійовано до буферу обміну</string>
|
||||
<string name="app_resources">Вільні ресурси третіх сторін: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Вільні бібліотеки третіх сторін: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_revision_fmt">Інформація про випуск: <xliff:g id="app_revision_url">%s</xliff:g></string>
|
||||
<string name="debug_version_fmt">Версія: <xliff:g id="version">%s</xliff:g></string>
|
||||
<string name="about_title_fmt">Про <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="app_license">Копілефт вільне програмне забезпечення, ліцензоване під GPLv3+.</string>
|
||||
<string name="app_copyright_old">Створено на основі Loyalty Card Keychain
|
||||
\nавторські права © 2016–2020 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="importOptionApplicationExplanation">Використайте іншу програму чи ваш улюблений файл-менеджер для відкриття файлу.</string>
|
||||
<string name="importOptionApplicationTitle">Використати іншу програму</string>
|
||||
<string name="importOptionFilesystemButton">З провідника</string>
|
||||
<string name="importOptionFilesystemExplanation">Оберіть конкретний файл у провіднику.</string>
|
||||
<string name="importOptionFilesystemTitle">Імпорт з провідника</string>
|
||||
<string name="exportOptionExplanation">Дані буде записано до локації обраної вами.</string>
|
||||
<string name="noExternalStoragePermissionError">Надайте дозвіл на доступ до пам\'яті пристрою для імпорту чи експорту карток</string>
|
||||
<string name="exporting">Експортуємо…</string>
|
||||
<string name="importing">Імпортуємо…</string>
|
||||
<string name="exportFailed">Неможливо експортувати картки</string>
|
||||
<string name="exportFailedTitle">Помилка експорту</string>
|
||||
<string name="exportSuccessfulTitle">Експортовано</string>
|
||||
<string name="importFailed">Неможливо імпортувати картки</string>
|
||||
<string name="importFailedTitle">Помилка імпорту</string>
|
||||
<string name="importSuccessfulTitle">Імпортовано</string>
|
||||
<string name="importExportHelp">Створення резервної копії ваших карток дозволяє перемістити їх до іншого пристрою.</string>
|
||||
<string name="exportName">Експорт</string>
|
||||
<string name="importExport">Імпорт/Експорт</string>
|
||||
<string name="failedParsingImportUriError">Неможливо опрацювати імпорт-URI</string>
|
||||
<string name="noCardExistsError">Карту не знайдено</string>
|
||||
<string name="noCardIdError">ID карти не введено</string>
|
||||
<string name="noStoreError">Ім\'я не введено</string>
|
||||
<string name="card_ids_copied">ID карти скопійовано</string>
|
||||
<string name="noCardsMessage">Спочатку додайте карту</string>
|
||||
<string name="cardShortcut">Швидкий виклик карти</string>
|
||||
<string name="addCardTitle">Додати карту</string>
|
||||
<string name="editCardTitle">Редагувати карту</string>
|
||||
<string name="sendLabel">Відправити…</string>
|
||||
<string name="share">Поділитися</string>
|
||||
<string name="copy_to_clipboard">Копіювати ID до буферу обміну</string>
|
||||
<string name="ok">ОК</string>
|
||||
<string name="deleteConfirmation">Бажаєте видалити карту\?</string>
|
||||
<string name="deleteTitle">Видалити карту</string>
|
||||
<string name="unlockScreen">Розблокувати обертання</string>
|
||||
<string name="lockScreen">Блокувати обертання</string>
|
||||
<string name="confirm">Підтвердити</string>
|
||||
<string name="delete">Видалити</string>
|
||||
<string name="edit">Редагувати</string>
|
||||
<string name="save">Зберегти</string>
|
||||
<string name="cancel">Відмінити</string>
|
||||
<string name="unstar">Видалити з улюблених</string>
|
||||
<string name="star">Додати до улюблених</string>
|
||||
<string name="cardId">ID картки</string>
|
||||
<string name="note">Замітка</string>
|
||||
<string name="storeName">Ім\'я</string>
|
||||
<string name="noMatchingGiftCards">Збігів не знайдено. Спробуйте змінити параметри пошуку.</string>
|
||||
<string name="card_selected">"Обрано: "</string>
|
||||
<string name="action_add">Додати</string>
|
||||
<string name="action_search">Пошук</string>
|
||||
</resources>
|
||||
@@ -4,6 +4,7 @@
|
||||
<item>Catima</item>
|
||||
<item>Fidme</item>
|
||||
<item>@string/app_loyalty_card_keychain</item>
|
||||
<item>Stocard</item>
|
||||
<item>Voucher Vault</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -72,8 +72,8 @@
|
||||
<string name="about_title_fmt">About <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="debug_version_fmt">Version: <xliff:g id="version">%s</xliff:g></string>
|
||||
<string name="app_revision_fmt">Revision Info: <xliff:g id="app_revision_url">%s</xliff:g></string>
|
||||
<string name="app_libraries">Third-party libre libraries: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Third-party libre resources: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Libre third-party libraries: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Libre third-party resources: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
|
||||
<string name="selectBarcodeTitle">Select Barcode</string>
|
||||
<string name="enterBarcodeInstructions">Enter the card ID, and either pick its barcode type below, or “This card has no barcode”.</string>
|
||||
@@ -109,10 +109,12 @@
|
||||
<string name="sharedpreference_privacy_policy_shown" translatable="false">sharedpreference_privacy_policy_shown</string>
|
||||
|
||||
<string name="intent_import_card_from_url_share_text">I want to share a card with you</string>
|
||||
<string name="intent_import_card_from_url_host" translatable="false">thelastproject.github.io</string>
|
||||
<string name="intent_import_card_from_url_path_prefix" translatable="false">/Catima/share</string>
|
||||
<string name="intent_import_card_from_url_host_old" translatable="false">brarcher.github.io</string>
|
||||
<string name="intent_import_card_from_url_path_prefix_old" translatable="false">/loyalty-card-locker/share</string>
|
||||
<string name="intent_import_card_from_url_host_catima_app" translatable="false">catima.app</string>
|
||||
<string name="intent_import_card_from_url_path_prefix_catima_app" translatable="false">/share</string>
|
||||
<string name="intent_import_card_from_url_host_thelastproject" translatable="false">thelastproject.github.io</string>
|
||||
<string name="intent_import_card_from_url_path_prefix_thelastproject" translatable="false">/Catima/share</string>
|
||||
<string name="intent_import_card_from_url_host_brarcher" translatable="false">brarcher.github.io</string>
|
||||
<string name="intent_import_card_from_url_path_prefix_brarcher" translatable="false">/loyalty-card-locker/share</string>
|
||||
<string name="importSuccessful">Card data imported</string>
|
||||
<string name="exportSuccessful">Card data exported</string>
|
||||
|
||||
@@ -163,22 +165,36 @@
|
||||
<string name="privacy_policy_popup_text">Privacy policy notice (required by some app stores):\n\nNO DATA IS COLLECTED AT ALL, which anyone can confirm since our app is libre software.</string>
|
||||
<string name="accept">Accept</string>
|
||||
<string name="importCatima">Import from Catima</string>
|
||||
<string name="importCatimaMessage">Find a file likely named <i>Catima.csv</i> to import.\nOr create it from the Import/Export menu of another Catima app by pressing "Export" there first.</string>
|
||||
<string name="importCatimaMessage">Select your <i>catima.zip</i> export from Catima to import.\nOr create it from the "Import/Export" menu of another Catima app by pressing "Export" there first.</string>
|
||||
<string name="importFidme">Import from FidMe</string>
|
||||
<string name="importFidmeMessage">Find a file likely named <i>fidme-export-request-xxxxxx.zip</i> to import, and then select the barcode types manually afterwards.\nOr create it from your FidMe profile by choosing "Data Protection" and then pressing "Extract my data" first.</string>
|
||||
<string name="importFidmeMessage">Select your <i>fidme-export-request-xxxxxx.zip</i> export from FidMe to import, and select the barcode types manually afterwards.\nOr create it from your FidMe profile by choosing "Data Protection" and then pressing "Extract my data" first.</string>
|
||||
<string name="importLoyaltyCardKeychain">Import from Loyalty Card Keychain</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Find a file most likely named <i>LoyaltyCardKeychain.csv</i> to import.\nOr create it from the Import/Export menu in Loyalty Card Keychain by pressing "Export" first.</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Select your <i>LoyaltyCardKeychain.csv</i> export from Loyalty Card Keychain to import.\nOr create it from the "Import/Export" menu in Loyalty Card Keychain by pressing "Export" there first.</string>
|
||||
<string name="importStocard">Import from Stocard</string>
|
||||
<string name="importStocardMessage">Select your <i>***-sync.zip</i> export from Stocard to import, and select the barcode types manually afterwards.\nOr get it by e-mailing support@stocardapp.com asking for an export of your data.</string>
|
||||
<string name="importVoucherVault">Import from Voucher Vault</string>
|
||||
<string name="importVoucherVaultMessage">Find a file likely named <i>vouchervault.json</i> to import.\nOr create it by pressing "Export" in Voucher Vault first.</string>
|
||||
<string name="importVoucherVaultMessage">Select your <i>vouchervault.json</i> export from Voucher Vault to import.\nOr create it by pressing "Export" in Voucher Vault first.</string>
|
||||
<string name="barcodeId">Barcode value</string>
|
||||
<string name="sameAsCardId">Same as card ID</string>
|
||||
<string name="setBarcodeId">Set barcode value</string>
|
||||
<string name="unsupportedBarcodeType">This barcode type can\'t yet be displayed. It may be supported in a newer version of the app.</string>
|
||||
<string name="unsupportedBarcodeType">This barcode type can\'t yet be displayed. It may be supported in a later version of the app.</string>
|
||||
<string name="wrongValueForBarcodeType">The value is not valid for the selected barcode type</string>
|
||||
<string name="copy_to_clipboard_multiple_toast">Copied card IDs to clipboard</string>
|
||||
<string name="copy_to_clipboard_multiple_toast">Card IDs copied to clipboard</string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">I want to share some cards with you</string>
|
||||
<string name="frontImageDescription">Card\'s front image</string>
|
||||
<string name="backImageDescription">Card\'s back image</string>
|
||||
<string name="photos">Photos</string>
|
||||
<string name="setFrontImage">Set front image</string>
|
||||
<string name="setBackImage">Set back image</string>
|
||||
<string name="removeImage">Remove image</string>
|
||||
<string name="chooseImageFromGallery">Choose image from gallery</string>
|
||||
<string name="takePhoto">Take a photo</string>
|
||||
<string name="updateBarcodeQuestionTitle">Update barcode value?</string>
|
||||
<string name="updateBarcodeQuestionText">You changed the card ID. Do you want to also update the barcode to use the same value?</string>
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
<string name="passwordRequired">Please enter the password</string>
|
||||
<string name="failedGeneratingShareURL">Failed generating share URL. Please report this bug!</string>
|
||||
<string name="turn_flashlight_on">Turn flashlight on</string>
|
||||
<string name="turn_flashlight_off">Turn flashlight off</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,9 +2,12 @@ package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.os.Environment;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
@@ -16,6 +19,7 @@ import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -24,6 +28,8 @@ import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -33,8 +39,12 @@ import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Currency;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
import protect.card_locker.importexport.DataFormat;
|
||||
import protect.card_locker.importexport.ImportExportResult;
|
||||
import protect.card_locker.importexport.MultiFormatExporter;
|
||||
import protect.card_locker.importexport.MultiFormatImporter;
|
||||
|
||||
@@ -59,6 +69,8 @@ public class ImportExportTest
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
ShadowLog.stream = System.out;
|
||||
|
||||
activity = Robolectric.setupActivity(MainActivity.class);
|
||||
db = TestHelpers.getEmptyDb(activity);
|
||||
nowMs = System.currentTimeMillis();
|
||||
@@ -323,8 +335,8 @@ public class ImportExportTest
|
||||
OutputStreamWriter outStream = new OutputStreamWriter(outData);
|
||||
|
||||
// Export data to CSV format
|
||||
boolean result = MultiFormatExporter.exportData(db, outStream, DataFormat.Catima);
|
||||
assertTrue(result);
|
||||
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
outStream.close();
|
||||
|
||||
TestHelpers.getEmptyDb(activity);
|
||||
@@ -332,8 +344,8 @@ public class ImportExportTest
|
||||
ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray());
|
||||
|
||||
// Import the CSV data
|
||||
result = MultiFormatImporter.importData(db, inData, DataFormat.Catima);
|
||||
assertTrue(result);
|
||||
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
|
||||
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
|
||||
|
||||
@@ -354,8 +366,8 @@ public class ImportExportTest
|
||||
OutputStreamWriter outStream = new OutputStreamWriter(outData);
|
||||
|
||||
// Export data to CSV format
|
||||
boolean result = MultiFormatExporter.exportData(db, outStream, DataFormat.Catima);
|
||||
assertTrue(result);
|
||||
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
outStream.close();
|
||||
|
||||
TestHelpers.getEmptyDb(activity);
|
||||
@@ -363,8 +375,8 @@ public class ImportExportTest
|
||||
ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray());
|
||||
|
||||
// Import the CSV data
|
||||
result = MultiFormatImporter.importData(db, inData, DataFormat.Catima);
|
||||
assertTrue(result);
|
||||
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
|
||||
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
|
||||
|
||||
@@ -427,8 +439,8 @@ public class ImportExportTest
|
||||
OutputStreamWriter outStream = new OutputStreamWriter(outData);
|
||||
|
||||
// Export data to CSV format
|
||||
boolean result = MultiFormatExporter.exportData(db, outStream, DataFormat.Catima);
|
||||
assertTrue(result);
|
||||
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
outStream.close();
|
||||
|
||||
TestHelpers.getEmptyDb(activity);
|
||||
@@ -436,8 +448,8 @@ public class ImportExportTest
|
||||
ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray());
|
||||
|
||||
// Import the CSV data
|
||||
result = MultiFormatImporter.importData(db, inData, DataFormat.Catima);
|
||||
assertTrue(result);
|
||||
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
|
||||
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
|
||||
assertEquals(NUM_GROUPS, db.getGroupCount());
|
||||
@@ -471,15 +483,15 @@ public class ImportExportTest
|
||||
OutputStreamWriter outStream = new OutputStreamWriter(outData);
|
||||
|
||||
// Export into CSV data
|
||||
boolean result = MultiFormatExporter.exportData(db, outStream, DataFormat.Catima);
|
||||
assertTrue(result);
|
||||
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
outStream.close();
|
||||
|
||||
ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray());
|
||||
|
||||
// Import the CSV data on top of the existing database
|
||||
result = MultiFormatImporter.importData(db, inData, DataFormat.Catima);
|
||||
assertTrue(result);
|
||||
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
|
||||
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
|
||||
|
||||
@@ -502,8 +514,8 @@ public class ImportExportTest
|
||||
OutputStreamWriter outStream = new OutputStreamWriter(outData);
|
||||
|
||||
// Export data to CSV format
|
||||
boolean result = MultiFormatExporter.exportData(db, outStream, DataFormat.Catima);
|
||||
assertTrue(result);
|
||||
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
|
||||
TestHelpers.getEmptyDb(activity);
|
||||
|
||||
@@ -516,8 +528,8 @@ public class ImportExportTest
|
||||
ByteArrayInputStream inData = new ByteArrayInputStream((outData.toString() + corruptEntry).getBytes());
|
||||
|
||||
// Attempt to import the data
|
||||
result = MultiFormatImporter.importData(db, inData, format);
|
||||
assertEquals(false, result);
|
||||
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, format, null);
|
||||
assertEquals(ImportExportResult.GenericFailure, result);
|
||||
|
||||
assertEquals(0, db.getLoyaltyCardCount());
|
||||
|
||||
@@ -527,11 +539,11 @@ public class ImportExportTest
|
||||
|
||||
class TestTaskCompleteListener implements ImportExportTask.TaskCompleteListener
|
||||
{
|
||||
Boolean success;
|
||||
ImportExportResult result;
|
||||
|
||||
public void onTaskComplete(boolean success)
|
||||
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat)
|
||||
{
|
||||
this.success = success;
|
||||
this.result = result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,8 +569,8 @@ public class ImportExportTest
|
||||
Robolectric.flushBackgroundThreadScheduler();
|
||||
|
||||
// Check that the listener was executed
|
||||
assertNotNull(listener.success);
|
||||
assertEquals(true, listener.success);
|
||||
assertNotNull(listener.result);
|
||||
assertEquals(ImportExportResult.Success, listener.result);
|
||||
|
||||
TestHelpers.getEmptyDb(activity);
|
||||
|
||||
@@ -568,15 +580,15 @@ public class ImportExportTest
|
||||
|
||||
FileInputStream fileStream = new FileInputStream(exportFile);
|
||||
|
||||
task = new ImportExportTask(activity, DataFormat.Catima, fileStream, listener);
|
||||
task = new ImportExportTask(activity, DataFormat.Catima, fileStream, null, listener);
|
||||
task.execute();
|
||||
|
||||
// Actually run the task to completion
|
||||
Robolectric.flushBackgroundThreadScheduler();
|
||||
|
||||
// Check that the listener was executed
|
||||
assertNotNull(listener.success);
|
||||
assertEquals(true, listener.success);
|
||||
assertNotNull(listener.result);
|
||||
assertEquals(ImportExportResult.Success, listener.result);
|
||||
|
||||
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
|
||||
|
||||
@@ -602,8 +614,8 @@ public class ImportExportTest
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Import the CSV data
|
||||
boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima);
|
||||
assertTrue(result);
|
||||
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
assertEquals(1, db.getLoyaltyCardCount());
|
||||
|
||||
LoyaltyCard card = db.getLoyaltyCard(1);
|
||||
@@ -640,8 +652,8 @@ public class ImportExportTest
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Import the CSV data
|
||||
boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima);
|
||||
assertTrue(result);
|
||||
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
assertEquals(1, db.getLoyaltyCardCount());
|
||||
|
||||
LoyaltyCard card = db.getLoyaltyCard(1);
|
||||
@@ -678,8 +690,8 @@ public class ImportExportTest
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Import the CSV data
|
||||
boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima);
|
||||
assertEquals(false, result);
|
||||
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResult.GenericFailure, result);
|
||||
assertEquals(0, db.getLoyaltyCardCount());
|
||||
|
||||
TestHelpers.getEmptyDb(activity);
|
||||
@@ -703,8 +715,8 @@ public class ImportExportTest
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Import the CSV data
|
||||
boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima);
|
||||
assertEquals(true, result);
|
||||
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
assertEquals(1, db.getLoyaltyCardCount());
|
||||
|
||||
LoyaltyCard card = db.getLoyaltyCard(1);
|
||||
@@ -741,8 +753,8 @@ public class ImportExportTest
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Import the CSV data
|
||||
boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima);
|
||||
assertEquals(true, result);
|
||||
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
assertEquals(1, db.getLoyaltyCardCount());
|
||||
|
||||
LoyaltyCard card = db.getLoyaltyCard(1);
|
||||
@@ -779,8 +791,8 @@ public class ImportExportTest
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Import the CSV data
|
||||
boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima);
|
||||
assertEquals(true, result);
|
||||
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
assertEquals(1, db.getLoyaltyCardCount());
|
||||
|
||||
LoyaltyCard card = db.getLoyaltyCard(1);
|
||||
@@ -817,8 +829,8 @@ public class ImportExportTest
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Import the CSV data
|
||||
boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima);
|
||||
assertTrue(result);
|
||||
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
assertEquals(1, db.getLoyaltyCardCount());
|
||||
|
||||
csvText = "";
|
||||
@@ -836,8 +848,8 @@ public class ImportExportTest
|
||||
inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Import the CSV data
|
||||
result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima);
|
||||
assertTrue(result);
|
||||
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
assertEquals(1, db.getLoyaltyCardCount());
|
||||
|
||||
LoyaltyCard card = db.getLoyaltyCard(1);
|
||||
@@ -857,33 +869,106 @@ public class ImportExportTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exportV2()
|
||||
public void exportImportV2Zip() throws FileNotFoundException
|
||||
{
|
||||
db.insertGroup("Example");
|
||||
int loyaltyCard = (int) db.insertLoyaltyCard("Card 1", "Note 1", new Date(1618053234), new BigDecimal("100"), Currency.getInstance("USD"), "1234", "5432", BarcodeFormat.QR_CODE, 1, 0);
|
||||
db.setLoyaltyCardGroups(loyaltyCard, Arrays.asList(db.getGroup("Example")));
|
||||
// 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());
|
||||
|
||||
Bitmap launcherBitmap = launcher.getBitmap();
|
||||
Bitmap roundLauncherBitmap = roundLauncher.getBitmap();
|
||||
|
||||
// Set up cards and groups
|
||||
HashMap<Integer, LoyaltyCard> loyaltyCardHashMap = new HashMap<>();
|
||||
HashMap<Integer, List<Group>> loyaltyCardGroups = new HashMap<>();
|
||||
HashMap<Integer, Bitmap> loyaltyCardFrontImages = new HashMap<>();
|
||||
HashMap<Integer, Bitmap> loyaltyCardBackImages = new HashMap<>();
|
||||
|
||||
// Create card 1
|
||||
int loyaltyCardId = (int) db.insertLoyaltyCard("Card 1", "Note 1", new Date(1618053234), new BigDecimal("100"), Currency.getInstance("USD"), "1234", "5432", BarcodeFormat.QR_CODE, 1, 0);
|
||||
loyaltyCardHashMap.put(loyaltyCardId, db.getLoyaltyCard(loyaltyCardId));
|
||||
db.insertGroup("One");
|
||||
List<Group> groups = Arrays.asList(db.getGroup("One"));
|
||||
db.setLoyaltyCardGroups(loyaltyCardId, groups);
|
||||
loyaltyCardGroups.put(loyaltyCardId, groups);
|
||||
Utils.saveCardImage(activity.getApplicationContext(), launcherBitmap, loyaltyCardId, true);
|
||||
Utils.saveCardImage(activity.getApplicationContext(), roundLauncherBitmap, loyaltyCardId, false);
|
||||
loyaltyCardFrontImages.put(loyaltyCardId, launcherBitmap);
|
||||
loyaltyCardBackImages.put(loyaltyCardId, roundLauncherBitmap);
|
||||
|
||||
// Create card 2
|
||||
loyaltyCardId = (int) db.insertLoyaltyCard("Card 2", "", null, new BigDecimal(0), null, "123456", null, null, 2, 1);
|
||||
loyaltyCardHashMap.put(loyaltyCardId, db.getLoyaltyCard(loyaltyCardId));
|
||||
|
||||
// Export everything
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
|
||||
MultiFormatExporter.exportData(activity.getApplicationContext(), db, outputStream, DataFormat.Catima);
|
||||
|
||||
MultiFormatExporter.exportData(db, outputStreamWriter, DataFormat.Catima);
|
||||
// Wipe database
|
||||
TestHelpers.getEmptyDb(activity);
|
||||
|
||||
String outputCsv = "2\r\n" +
|
||||
"\r\n" +
|
||||
"_id\r\n" +
|
||||
"Example\r\n" +
|
||||
"\r\n" +
|
||||
"_id,store,note,expiry,balance,balancetype,cardid,headercolor,barcodetype,starstatus\r\n" +
|
||||
"1,Card 1,Note 1,1618053234,100,USD,1234,1,QR_CODE,0\r\n" +
|
||||
"\r\n" +
|
||||
"cardId,groupId\r\n" +
|
||||
"1,Example\r\n";
|
||||
// Import everything
|
||||
MultiFormatImporter.importData(activity.getApplicationContext(), db, new ByteArrayInputStream(outputStream.toByteArray()), DataFormat.Catima, null);
|
||||
|
||||
assertEquals(outputCsv, outputStream.toString());
|
||||
// Ensure everything is there
|
||||
assertEquals(loyaltyCardHashMap.size(), db.getLoyaltyCardCount());
|
||||
|
||||
for (Integer loyaltyCardID : loyaltyCardHashMap.keySet()) {
|
||||
LoyaltyCard loyaltyCard = loyaltyCardHashMap.get(loyaltyCardID);
|
||||
|
||||
LoyaltyCard dbLoyaltyCard = db.getLoyaltyCard(loyaltyCardID);
|
||||
|
||||
assertEquals(loyaltyCard.id, dbLoyaltyCard.id);
|
||||
assertEquals(loyaltyCard.store, dbLoyaltyCard.store);
|
||||
assertEquals(loyaltyCard.note, dbLoyaltyCard.note);
|
||||
assertEquals(loyaltyCard.expiry, dbLoyaltyCard.expiry);
|
||||
assertEquals(loyaltyCard.balance, dbLoyaltyCard.balance);
|
||||
assertEquals(loyaltyCard.cardId, dbLoyaltyCard.cardId);
|
||||
assertEquals(loyaltyCard.barcodeId, dbLoyaltyCard.barcodeId);
|
||||
assertEquals(loyaltyCard.starStatus, dbLoyaltyCard.starStatus);
|
||||
assertEquals(loyaltyCard.barcodeType, dbLoyaltyCard.barcodeType);
|
||||
assertEquals(loyaltyCard.balanceType, dbLoyaltyCard.balanceType);
|
||||
assertEquals(loyaltyCard.headerColor, dbLoyaltyCard.headerColor);
|
||||
|
||||
List<Group> emptyGroup = new ArrayList<>();
|
||||
|
||||
assertEquals(
|
||||
groupsToGroupNames(
|
||||
(List<Group>) Utils.hashmapGetOrDefault(
|
||||
loyaltyCardGroups,
|
||||
loyaltyCardID,
|
||||
emptyGroup,
|
||||
Integer.class
|
||||
)
|
||||
),
|
||||
groupsToGroupNames(
|
||||
db.getLoyaltyCardGroups(
|
||||
loyaltyCardID
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
Bitmap expectedFrontImage = loyaltyCardFrontImages.get(loyaltyCardID);
|
||||
Bitmap expectedBackImage = loyaltyCardBackImages.get(loyaltyCardID);
|
||||
Bitmap actualFrontImage = Utils.retrieveCardImage(activity.getApplicationContext(), Utils.getCardImageFileName(loyaltyCardID, true));
|
||||
Bitmap actualBackImage = Utils.retrieveCardImage(activity.getApplicationContext(), Utils.getCardImageFileName(loyaltyCardID, false));
|
||||
|
||||
if (expectedFrontImage != null) {
|
||||
assertTrue(expectedFrontImage.sameAs(actualFrontImage));
|
||||
} else {
|
||||
assertNull(actualFrontImage);
|
||||
}
|
||||
|
||||
if (expectedBackImage != null) {
|
||||
assertTrue(expectedBackImage.sameAs(actualBackImage));
|
||||
} else {
|
||||
assertNull(actualBackImage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void importV2()
|
||||
public void importV2CSV()
|
||||
{
|
||||
String csvText = "2\n" +
|
||||
"\n" +
|
||||
@@ -893,12 +978,13 @@ public class ImportExportTest
|
||||
"Fashion\n" +
|
||||
"\n" +
|
||||
"_id,store,note,expiry,balance,balancetype,cardid,barcodeid,headercolor,barcodetype,starstatus\n" +
|
||||
"8,Clothes Store,Note about store,,0,,a,,-5317,,0\n" +
|
||||
"2,Department Store,,1618041729,0,,A,,-9977996,,0\n" +
|
||||
"3,Grocery Store,,,150,,dhd,,-9977996,,0\n" +
|
||||
"4,Pharmacy,,,0,,dhshsvshs,,-10902850,,1\n" +
|
||||
"5,Restaurant,Note about restaurant here,,0,,98765432,23456,-10902850,CODE_128,0\n" +
|
||||
"6,Shoe Store,,,12.50,EUR,a,-5317,,AZTEC,0\n" +
|
||||
"1,Card 1,Note 1,1618053234,100,USD,1234,5432,1,QR_CODE,0,\r\n" +
|
||||
"8,Clothes Store,Note about store,,0,,a,,-5317,,0,\n" +
|
||||
"2,Department Store,,1618041729,0,,A,,-9977996,,0,\n" +
|
||||
"3,Grocery Store,,,150,,dhd,,-9977996,,0,\n" +
|
||||
"4,Pharmacy,,,0,,dhshsvshs,,-10902850,,1,\n" +
|
||||
"5,Restaurant,Note about restaurant here,,0,,98765432,23456,-10902850,CODE_128,0,\n" +
|
||||
"6,Shoe Store,,,12.50,EUR,a,-5317,,AZTEC,0,\n" +
|
||||
"\n" +
|
||||
"cardId,groupId\n" +
|
||||
"8,Fashion\n" +
|
||||
@@ -910,9 +996,9 @@ public class ImportExportTest
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Import the CSV data
|
||||
boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.Catima);
|
||||
assertEquals(true, result);
|
||||
assertEquals(6, db.getLoyaltyCardCount());
|
||||
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
assertEquals(7, db.getLoyaltyCardCount());
|
||||
assertEquals(3, db.getGroupCount());
|
||||
|
||||
// Check all groups
|
||||
@@ -932,6 +1018,21 @@ public class ImportExportTest
|
||||
assertEquals(Arrays.asList(8, 6), db.getGroupCardIds("Fashion"));
|
||||
|
||||
// Check all cards
|
||||
LoyaltyCard card1 = db.getLoyaltyCard(1);
|
||||
|
||||
assertEquals("Card 1", card1.store);
|
||||
assertEquals("Note 1", card1.note);
|
||||
assertEquals(new Date(1618053234), card1.expiry);
|
||||
assertEquals(new BigDecimal("100"), card1.balance);
|
||||
assertEquals(Currency.getInstance("USD"), card1.balanceType);
|
||||
assertEquals("1234", card1.cardId);
|
||||
assertEquals("5432", card1.barcodeId);
|
||||
assertEquals(BarcodeFormat.QR_CODE, card1.barcodeType);
|
||||
assertEquals(1, (long) card1.headerColor);
|
||||
assertEquals(0, card1.starStatus);
|
||||
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card1.id, true));
|
||||
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card1.id, false));
|
||||
|
||||
LoyaltyCard card8 = db.getLoyaltyCard(8);
|
||||
|
||||
assertEquals("Clothes Store", card8.store);
|
||||
@@ -944,6 +1045,8 @@ public class ImportExportTest
|
||||
assertEquals(null, card8.barcodeType);
|
||||
assertEquals(-5317, (long) card8.headerColor);
|
||||
assertEquals(0, card8.starStatus);
|
||||
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card8.id, true));
|
||||
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card8.id, false));
|
||||
|
||||
LoyaltyCard card2 = db.getLoyaltyCard(2);
|
||||
|
||||
@@ -957,6 +1060,8 @@ public class ImportExportTest
|
||||
assertEquals(null, card2.barcodeType);
|
||||
assertEquals(-9977996, (long) card2.headerColor);
|
||||
assertEquals(0, card2.starStatus);
|
||||
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card2.id, true));
|
||||
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card2.id, false));
|
||||
|
||||
LoyaltyCard card3 = db.getLoyaltyCard(3);
|
||||
|
||||
@@ -970,6 +1075,8 @@ public class ImportExportTest
|
||||
assertEquals(null, card3.barcodeType);
|
||||
assertEquals(-9977996, (long) card3.headerColor);
|
||||
assertEquals(0, card3.starStatus);
|
||||
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card3.id, true));
|
||||
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card3.id, false));
|
||||
|
||||
LoyaltyCard card4 = db.getLoyaltyCard(4);
|
||||
|
||||
@@ -983,6 +1090,8 @@ public class ImportExportTest
|
||||
assertEquals(null, card4.barcodeType);
|
||||
assertEquals(-10902850, (long) card4.headerColor);
|
||||
assertEquals(1, card4.starStatus);
|
||||
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card4.id, true));
|
||||
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card4.id, false));
|
||||
|
||||
LoyaltyCard card5 = db.getLoyaltyCard(5);
|
||||
|
||||
@@ -996,6 +1105,8 @@ public class ImportExportTest
|
||||
assertEquals(BarcodeFormat.CODE_128, card5.barcodeType);
|
||||
assertEquals(-10902850, (long) card5.headerColor);
|
||||
assertEquals(0, card5.starStatus);
|
||||
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card5.id, true));
|
||||
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card5.id, false));
|
||||
|
||||
LoyaltyCard card6 = db.getLoyaltyCard(6);
|
||||
|
||||
@@ -1009,6 +1120,119 @@ public class ImportExportTest
|
||||
assertEquals(BarcodeFormat.AZTEC, card6.barcodeType);
|
||||
assertEquals(null, card6.headerColor);
|
||||
assertEquals(0, card6.starStatus);
|
||||
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card6.id, true));
|
||||
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card6.id, false));
|
||||
|
||||
TestHelpers.getEmptyDb(activity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void importFidme() {
|
||||
InputStream inputStream = getClass().getResourceAsStream("fidme.zip");
|
||||
|
||||
// Import the Fidme data
|
||||
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Fidme, null);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
assertEquals(3, db.getLoyaltyCardCount());
|
||||
|
||||
LoyaltyCard card = db.getLoyaltyCard(1);
|
||||
|
||||
assertEquals("Hema", card.store);
|
||||
assertEquals("2021-03-24 18:35:08 UTC", card.note);
|
||||
assertEquals(null, card.expiry);
|
||||
assertEquals(new BigDecimal("0"), card.balance);
|
||||
assertEquals(null, card.balanceType);
|
||||
assertEquals("82825292629272726", card.cardId);
|
||||
assertEquals(null, card.barcodeId);
|
||||
assertEquals(null, card.barcodeType);
|
||||
assertEquals(0, card.starStatus);
|
||||
|
||||
card = db.getLoyaltyCard(2);
|
||||
|
||||
assertEquals("test", card.store);
|
||||
assertEquals("Test\n2021-03-24 18:34:19 UTC", card.note);
|
||||
assertEquals(null, card.expiry);
|
||||
assertEquals(new BigDecimal("0"), card.balance);
|
||||
assertEquals(null, card.balanceType);
|
||||
assertEquals("123456", card.cardId);
|
||||
assertEquals(null, card.barcodeId);
|
||||
assertEquals(null, card.barcodeType);
|
||||
assertEquals(0, card.starStatus);
|
||||
|
||||
card = db.getLoyaltyCard(3);
|
||||
|
||||
assertEquals("Albert Heijn", card.store);
|
||||
assertEquals("Bonus Kaart\n2021-03-24 16:47:47 UTC\nFirst Last", card.note);
|
||||
assertEquals(null, card.expiry);
|
||||
assertEquals(new BigDecimal("0"), card.balance);
|
||||
assertEquals(null, card.balanceType);
|
||||
assertEquals("123435363634", card.cardId);
|
||||
assertEquals(null, card.barcodeId);
|
||||
assertEquals(null, card.barcodeType);
|
||||
assertEquals(0, card.starStatus);
|
||||
|
||||
TestHelpers.getEmptyDb(activity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void importStocard() throws IOException {
|
||||
InputStream inputStream = getClass().getResourceAsStream("stocard.zip");
|
||||
|
||||
// Import the Stocard data
|
||||
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Stocard, null);
|
||||
assertEquals(ImportExportResult.BadPassword, result);
|
||||
assertEquals(0, db.getLoyaltyCardCount());
|
||||
|
||||
inputStream = getClass().getResourceAsStream("stocard.zip");
|
||||
|
||||
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Stocard, "da811b40a4dac56f0cbb2d99b21bbb9a".toCharArray());
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
assertEquals(3, db.getLoyaltyCardCount());
|
||||
|
||||
LoyaltyCard card = db.getLoyaltyCard(1);
|
||||
|
||||
assertEquals("GAMMA", card.store);
|
||||
assertEquals("", card.note);
|
||||
assertEquals(null, card.expiry);
|
||||
assertEquals(new BigDecimal("0"), card.balance);
|
||||
assertEquals(null, card.balanceType);
|
||||
assertEquals("55555", card.cardId);
|
||||
assertEquals(null, card.barcodeId);
|
||||
assertEquals(null, card.barcodeType);
|
||||
assertEquals(0, card.starStatus);
|
||||
|
||||
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 1, true));
|
||||
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 1, false));
|
||||
|
||||
card = db.getLoyaltyCard(2);
|
||||
|
||||
assertEquals("Air Miles", card.store);
|
||||
assertEquals("szjsbs", card.note);
|
||||
assertEquals(null, card.expiry);
|
||||
assertEquals(new BigDecimal("0"), card.balance);
|
||||
assertEquals(null, card.balanceType);
|
||||
assertEquals("7649484", card.cardId);
|
||||
assertEquals(null, card.barcodeId);
|
||||
assertEquals(null, card.barcodeType);
|
||||
assertEquals(0, card.starStatus);
|
||||
|
||||
assertTrue(BitmapFactory.decodeStream(getClass().getResourceAsStream("stocard-front.jpg")).sameAs(Utils.retrieveCardImage(activity.getApplicationContext(), 2, true)));
|
||||
assertTrue(BitmapFactory.decodeStream(getClass().getResourceAsStream("stocard-back.jpg")).sameAs(Utils.retrieveCardImage(activity.getApplicationContext(), 2, false)));
|
||||
|
||||
card = db.getLoyaltyCard(3);
|
||||
|
||||
assertEquals("jö", card.store);
|
||||
assertEquals("", card.note);
|
||||
assertEquals(null, card.expiry);
|
||||
assertEquals(new BigDecimal("0"), card.balance);
|
||||
assertEquals(null, card.balanceType);
|
||||
assertEquals("(01)09010374000019(21)02097564604859211217(10)01231287693", card.cardId);
|
||||
assertEquals(null, card.barcodeId);
|
||||
assertEquals(BarcodeFormat.RSS_EXPANDED, card.barcodeType);
|
||||
assertEquals(0, card.starStatus);
|
||||
|
||||
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 3, true));
|
||||
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 3, false));
|
||||
|
||||
TestHelpers.getEmptyDb(activity);
|
||||
}
|
||||
@@ -1024,6 +1248,7 @@ public class ImportExportTest
|
||||
" \"expires\": null,\n" +
|
||||
" \"removeOnceExpired\": true,\n" +
|
||||
" \"balance\": null,\n" +
|
||||
" \"balanceMilliunits\": null,\n" +
|
||||
" \"color\": \"GREY\"\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
@@ -1033,7 +1258,8 @@ public class ImportExportTest
|
||||
" \"codeType\": \"CODE39\",\n" +
|
||||
" \"expires\": \"2021-03-26T00:00:00.000\",\n" +
|
||||
" \"removeOnceExpired\": true,\n" +
|
||||
" \"balance\": 3.5,\n" +
|
||||
" \"balance\": null,\n" +
|
||||
" \"balanceMilliunits\": 3500,\n" +
|
||||
" \"color\": \"PURPLE\"\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
@@ -1041,8 +1267,8 @@ public class ImportExportTest
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(jsonText.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Import the Voucher Vault data
|
||||
boolean result = MultiFormatImporter.importData(db, inputStream, DataFormat.VoucherVault);
|
||||
assertTrue(result);
|
||||
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.VoucherVault, null);
|
||||
assertEquals(ImportExportResult.Success, result);
|
||||
assertEquals(2, db.getLoyaltyCardCount());
|
||||
|
||||
LoyaltyCard card = db.getLoyaltyCard(1);
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.io.InvalidObjectException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Currency;
|
||||
import java.util.Date;
|
||||
@@ -37,12 +38,11 @@ public class ImportURITest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureNoDataLoss() throws InvalidObjectException
|
||||
{
|
||||
public void ensureNoDataLoss() throws InvalidObjectException, UnsupportedEncodingException {
|
||||
// Generate card
|
||||
Date date = new Date();
|
||||
|
||||
db.insertLoyaltyCard("store", "note", date, new BigDecimal("100"), null, BarcodeFormat.UPC_E.toString(), BarcodeFormat.UPC_A.toString(), BarcodeFormat.QR_CODE, Color.BLACK, 1);
|
||||
db.insertLoyaltyCard("store", "This note contains evil symbols like & and = that will break the parser if not escaped right $#!%()*+;:á", date, new BigDecimal("100"), null, BarcodeFormat.UPC_E.toString(), BarcodeFormat.UPC_A.toString(), BarcodeFormat.QR_CODE, Color.BLACK, 1);
|
||||
|
||||
// Get card
|
||||
LoyaltyCard card = db.getLoyaltyCard(1);
|
||||
@@ -68,8 +68,7 @@ public class ImportURITest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureNoCrashOnMissingHeaderFields() throws InvalidObjectException
|
||||
{
|
||||
public void ensureNoCrashOnMissingHeaderFields() throws InvalidObjectException, UnsupportedEncodingException {
|
||||
// Generate card
|
||||
db.insertLoyaltyCard("store", "note", null, new BigDecimal("10.00"), Currency.getInstance("EUR"), BarcodeFormat.UPC_A.toString(), null, BarcodeFormat.QR_CODE, null, 0);
|
||||
|
||||
@@ -110,35 +109,48 @@ public class ImportURITest {
|
||||
@Test
|
||||
public void failToParseBadData()
|
||||
{
|
||||
try {
|
||||
//"stare" instead of store
|
||||
importURIHelper.parse(Uri.parse("https://brarcher.github.io/loyalty-card-locker/share?stare=store¬e=note&cardid=12345&barcodetype=ITF&headercolor=-416706"));
|
||||
assertTrue(false); // Shouldn't get here
|
||||
} catch(InvalidObjectException ex) {
|
||||
// Desired behaviour
|
||||
String[] urls = new String[3];
|
||||
urls[0] = "https://brarcher.github.io/loyalty-card-locker/share?stare=store¬e=note&cardid=12345&barcodetype=ITF&headercolor=-416706";
|
||||
urls[1] = "https://thelastproject.github.io/Catima/share#stare%3Dstore%26note%3Dnote%26balance%3D0%26cardid%3D12345%26barcodetype%3DITF%26headercolor%3D-416706";
|
||||
urls[2] = "https://catima.app/share#stare%3Dstore%26note%3Dnote%26balance%3D0%26cardid%3D12345%26barcodetype%3DITF%26headercolor%3D-416706";
|
||||
|
||||
for (String url : urls) {
|
||||
try {
|
||||
//"stare" instead of store
|
||||
importURIHelper.parse(Uri.parse(url));
|
||||
assertTrue(false); // Shouldn't get here
|
||||
} catch (InvalidObjectException ex) {
|
||||
// Desired behaviour
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseAdditionalUnforeseenData()
|
||||
{
|
||||
LoyaltyCard parsedCard = null;
|
||||
try {
|
||||
parsedCard = importURIHelper.parse(Uri.parse("https://brarcher.github.io/loyalty-card-locker/share?store=store¬e=note&cardid=12345&barcodetype=ITF&headercolor=-416706&headertextcolor=-1¬foreseen=no"));
|
||||
} catch (InvalidObjectException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
public void parseAdditionalUnforeseenData() {
|
||||
String[] urls = new String[3];
|
||||
urls[0] = "https://brarcher.github.io/loyalty-card-locker/share?store=store¬e=note&cardid=12345&barcodetype=ITF&headercolor=-416706&headertextcolor=-1¬foreseen=no";
|
||||
urls[1] = "https://thelastproject.github.io/Catima/share#store%3Dstore%26note%3Dnote%26balance%3D0%26cardid%3D12345%26barcodetype%3DITF%26headercolor%3D-416706%26notforeseen%3Dno";
|
||||
urls[2] = "https://catima.app/share#store%3Dstore%26note%3Dnote%26balance%3D0%26cardid%3D12345%26barcodetype%3DITF%26headercolor%3D-416706%26notforeseen%3Dno";
|
||||
|
||||
// Compare everything
|
||||
assertEquals("store", parsedCard.store);
|
||||
assertEquals("note", parsedCard.note);
|
||||
assertEquals(null, parsedCard.expiry);
|
||||
assertEquals(new BigDecimal("0"), parsedCard.balance);
|
||||
assertEquals(null, parsedCard.balanceType);
|
||||
assertEquals("12345", parsedCard.cardId);
|
||||
assertEquals(null, parsedCard.barcodeId);
|
||||
assertEquals(BarcodeFormat.ITF, parsedCard.barcodeType);
|
||||
assertEquals(Integer.valueOf(-416706), parsedCard.headerColor);
|
||||
assertEquals(0, parsedCard.starStatus);
|
||||
for (String url : urls) {
|
||||
LoyaltyCard parsedCard = null;
|
||||
try {
|
||||
parsedCard = importURIHelper.parse(Uri.parse(url));
|
||||
} catch (InvalidObjectException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Compare everything
|
||||
assertEquals("store", parsedCard.store);
|
||||
assertEquals("note", parsedCard.note);
|
||||
assertEquals(null, parsedCard.expiry);
|
||||
assertEquals(new BigDecimal("0"), parsedCard.balance);
|
||||
assertEquals(null, parsedCard.balanceType);
|
||||
assertEquals("12345", parsedCard.cardId);
|
||||
assertEquals(null, parsedCard.barcodeId);
|
||||
assertEquals(BarcodeFormat.ITF, parsedCard.barcodeType);
|
||||
assertEquals(Integer.valueOf(-416706), parsedCard.headerColor);
|
||||
assertEquals(0, parsedCard.starStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
@@ -29,11 +28,9 @@ import androidx.preference.PreferenceManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import protect.card_locker.preferences.Settings;
|
||||
|
||||
import static android.os.Looper.getMainLooper;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(sdk = 23)
|
||||
|
||||
@@ -76,6 +76,13 @@ public class LoyaltyCardViewActivityTest
|
||||
;
|
||||
}
|
||||
|
||||
enum FieldTypeView
|
||||
{
|
||||
TextView,
|
||||
TextInputLayout,
|
||||
ImageView
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
@@ -277,20 +284,23 @@ public class LoyaltyCardViewActivityTest
|
||||
}
|
||||
|
||||
private void checkFieldProperties(final Activity activity, final int id, final int visibility,
|
||||
final String contents)
|
||||
final Object contents, final FieldTypeView fieldType)
|
||||
{
|
||||
final View view = activity.findViewById(id);
|
||||
assertNotNull(view);
|
||||
assertEquals(visibility, view.getVisibility());
|
||||
if(contents != null)
|
||||
{
|
||||
try {
|
||||
TextView textView = (TextView) view;
|
||||
assertEquals(contents, textView.getText().toString());
|
||||
} catch (ClassCastException e) {
|
||||
TextInputLayout textView = (TextInputLayout) view;
|
||||
assertEquals(contents, textView.getEditText().getText().toString());
|
||||
}
|
||||
|
||||
if (fieldType == FieldTypeView.TextView) {
|
||||
TextView textView = (TextView) view;
|
||||
assertEquals(contents, textView.getText().toString());
|
||||
} else if (fieldType == FieldTypeView.TextInputLayout) {
|
||||
TextInputLayout textView = (TextInputLayout) view;
|
||||
assertEquals(contents, textView.getEditText().getText().toString());
|
||||
} else if (fieldType == FieldTypeView.ImageView) {
|
||||
ImageView imageView = (ImageView) view;
|
||||
// TODO: implement
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,21 +312,23 @@ public class LoyaltyCardViewActivityTest
|
||||
{
|
||||
if(mode == ViewMode.VIEW_CARD)
|
||||
{
|
||||
checkFieldProperties(activity, R.id.cardIdView, View.VISIBLE, cardId);
|
||||
checkFieldProperties(activity, R.id.cardIdView, View.VISIBLE, cardId, FieldTypeView.TextView);
|
||||
}
|
||||
else
|
||||
{
|
||||
int editVisibility = View.VISIBLE;
|
||||
|
||||
checkFieldProperties(activity, R.id.storeNameEdit, editVisibility, store);
|
||||
checkFieldProperties(activity, R.id.noteEdit, editVisibility, note);
|
||||
checkFieldProperties(activity, R.id.expiryView, editVisibility, expiryString);
|
||||
checkFieldProperties(activity, R.id.balanceField, editVisibility, balanceString);
|
||||
checkFieldProperties(activity, R.id.balanceCurrencyField, editVisibility, balanceTypeString);
|
||||
checkFieldProperties(activity, R.id.cardIdView, View.VISIBLE, cardId);
|
||||
checkFieldProperties(activity, R.id.barcodeIdField, View.VISIBLE, barcodeId);
|
||||
checkFieldProperties(activity, R.id.barcodeTypeField, View.VISIBLE, barcodeType);
|
||||
checkFieldProperties(activity, R.id.barcode, View.VISIBLE, null);
|
||||
checkFieldProperties(activity, R.id.storeNameEdit, editVisibility, store, FieldTypeView.TextView);
|
||||
checkFieldProperties(activity, R.id.noteEdit, editVisibility, note, FieldTypeView.TextView);
|
||||
checkFieldProperties(activity, R.id.expiryView, editVisibility, expiryString, FieldTypeView.TextInputLayout);
|
||||
checkFieldProperties(activity, R.id.balanceField, editVisibility, balanceString, FieldTypeView.TextView);
|
||||
checkFieldProperties(activity, R.id.balanceCurrencyField, editVisibility, balanceTypeString, FieldTypeView.TextView);
|
||||
checkFieldProperties(activity, R.id.cardIdView, View.VISIBLE, cardId, FieldTypeView.TextView);
|
||||
checkFieldProperties(activity, R.id.barcodeIdField, View.VISIBLE, barcodeId, FieldTypeView.TextView);
|
||||
checkFieldProperties(activity, R.id.barcodeTypeField, View.VISIBLE, barcodeType, FieldTypeView.TextView);
|
||||
checkFieldProperties(activity, R.id.barcode, View.VISIBLE, null, FieldTypeView.ImageView);
|
||||
checkFieldProperties(activity, R.id.frontImage, View.VISIBLE, null, FieldTypeView.ImageView);
|
||||
checkFieldProperties(activity, R.id.backImage, View.VISIBLE, null, FieldTypeView.ImageView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,7 +507,7 @@ public class LoyaltyCardViewActivityTest
|
||||
activityController.visible();
|
||||
activityController.resume();
|
||||
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, "0", context.getString(R.string.points), BARCODE_DATA, null, BARCODE_TYPE.toString());
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, context.getString(R.string.sameAsCardId), BARCODE_TYPE.toString());
|
||||
|
||||
db.close();
|
||||
}
|
||||
@@ -533,12 +545,12 @@ public class LoyaltyCardViewActivityTest
|
||||
activityController.visible();
|
||||
activityController.resume();
|
||||
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, "0", context.getString(R.string.points), EAN_BARCODE_DATA, null, EAN_BARCODE_TYPE.toString());
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), EAN_BARCODE_DATA, context.getString(R.string.sameAsCardId), EAN_BARCODE_TYPE.toString());
|
||||
|
||||
// Complete barcode capture successfully
|
||||
captureBarcodeWithResult(activity, true);
|
||||
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, "0", context.getString(R.string.points), BARCODE_DATA, null, BARCODE_TYPE.toString());
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, context.getString(R.string.sameAsCardId), BARCODE_TYPE.toString());
|
||||
|
||||
db.close();
|
||||
}
|
||||
@@ -557,12 +569,12 @@ public class LoyaltyCardViewActivityTest
|
||||
activityController.visible();
|
||||
activityController.resume();
|
||||
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, "0", context.getString(R.string.points), EAN_BARCODE_DATA, null, EAN_BARCODE_TYPE.toString());
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), EAN_BARCODE_DATA, context.getString(R.string.sameAsCardId), EAN_BARCODE_TYPE.toString());
|
||||
|
||||
// Complete barcode capture successfully
|
||||
captureBarcodeWithResult(activity, true);
|
||||
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", null, "0", context.getString(R.string.points), BARCODE_DATA, null, BARCODE_TYPE.toString());
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, context.getString(R.string.sameAsCardId), BARCODE_TYPE.toString());
|
||||
|
||||
// Cancel the loyalty card creation
|
||||
assertEquals(false, activity.isFinishing());
|
||||
@@ -595,7 +607,7 @@ public class LoyaltyCardViewActivityTest
|
||||
activityController.visible();
|
||||
activityController.resume();
|
||||
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), EAN_BARCODE_DATA, null, EAN_BARCODE_TYPE.toString());
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), EAN_BARCODE_DATA, context.getString(R.string.sameAsCardId), EAN_BARCODE_TYPE.toString());
|
||||
|
||||
// Set date to today
|
||||
MaterialAutoCompleteTextView expiryField = activity.findViewById(R.id.expiryField);
|
||||
@@ -609,7 +621,7 @@ public class LoyaltyCardViewActivityTest
|
||||
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", DateFormat.getDateInstance(DateFormat.LONG).format(new Date()), "0", context.getString(R.string.points), EAN_BARCODE_DATA, null, EAN_BARCODE_TYPE.toString());
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", DateFormat.getDateInstance(DateFormat.LONG).format(new Date()), "0", context.getString(R.string.points), EAN_BARCODE_DATA, context.getString(R.string.sameAsCardId), EAN_BARCODE_TYPE.toString());
|
||||
|
||||
db.close();
|
||||
}
|
||||
@@ -628,13 +640,13 @@ public class LoyaltyCardViewActivityTest
|
||||
activityController.visible();
|
||||
activityController.resume();
|
||||
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", DateFormat.getDateInstance(DateFormat.LONG).format(new Date()), "0", context.getString(R.string.points), EAN_BARCODE_DATA, null, EAN_BARCODE_TYPE.toString());
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", DateFormat.getDateInstance(DateFormat.LONG).format(new Date()), "0", context.getString(R.string.points), EAN_BARCODE_DATA, context.getString(R.string.sameAsCardId), EAN_BARCODE_TYPE.toString());
|
||||
|
||||
// Set date to never
|
||||
MaterialAutoCompleteTextView expiryField = activity.findViewById(R.id.expiryField);
|
||||
expiryField.setText(expiryField.getAdapter().getItem(0).toString(), false);
|
||||
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), EAN_BARCODE_DATA, null, EAN_BARCODE_TYPE.toString());
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), EAN_BARCODE_DATA, context.getString(R.string.sameAsCardId), EAN_BARCODE_TYPE.toString());
|
||||
|
||||
db.close();
|
||||
}
|
||||
@@ -653,7 +665,7 @@ public class LoyaltyCardViewActivityTest
|
||||
activityController.visible();
|
||||
activityController.resume();
|
||||
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), EAN_BARCODE_DATA, null, EAN_BARCODE_TYPE.toString());
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), EAN_BARCODE_DATA, context.getString(R.string.sameAsCardId), EAN_BARCODE_TYPE.toString());
|
||||
|
||||
// Set balance to 10 points
|
||||
EditText balanceField = activity.findViewById(R.id.balanceField);
|
||||
@@ -704,7 +716,7 @@ public class LoyaltyCardViewActivityTest
|
||||
activityController.visible();
|
||||
activityController.resume();
|
||||
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "10.00", "$", EAN_BARCODE_DATA, null, EAN_BARCODE_TYPE.toString());
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "10.00", "$", EAN_BARCODE_DATA, context.getString(R.string.sameAsCardId), EAN_BARCODE_TYPE.toString());
|
||||
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
@@ -726,7 +738,7 @@ public class LoyaltyCardViewActivityTest
|
||||
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", "₩", EAN_BARCODE_DATA, null, EAN_BARCODE_TYPE.toString());
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", "₩", EAN_BARCODE_DATA, context.getString(R.string.sameAsCardId), EAN_BARCODE_TYPE.toString());
|
||||
|
||||
db.close();
|
||||
}
|
||||
@@ -869,13 +881,13 @@ public class LoyaltyCardViewActivityTest
|
||||
activityController.resume();
|
||||
|
||||
// First check if the card is as expected
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, null, BARCODE_TYPE.toString());
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, context.getString(R.string.sameAsCardId), BARCODE_TYPE.toString());
|
||||
|
||||
// Complete empty barcode selection successfully
|
||||
selectBarcodeWithResult(activity, BARCODE_DATA, "", true);
|
||||
|
||||
// Check if the barcode type is NO_BARCODE as expected
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, null, context.getString(R.string.noBarcode));
|
||||
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, context.getString(R.string.sameAsCardId), context.getString(R.string.noBarcode));
|
||||
assertEquals(View.GONE, activity.findViewById(R.id.barcodeLayout).getVisibility());
|
||||
|
||||
// Check if the special NO_BARCODE string doesn't get saved
|
||||
@@ -966,7 +978,7 @@ public class LoyaltyCardViewActivityTest
|
||||
|
||||
Activity activity = (Activity) activityController.get();
|
||||
DBHelper db = TestHelpers.getEmptyDb(activity);
|
||||
db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, null, BARCODE_TYPE, Color.BLACK,0);
|
||||
db.insertLoyaltyCard("store", "note", null, new BigDecimal("0"), null, BARCODE_DATA, null, BARCODE_TYPE, Color.BLACK, 0);
|
||||
activityController.start();
|
||||
activityController.visible();
|
||||
activityController.resume();
|
||||
@@ -1115,7 +1127,7 @@ public class LoyaltyCardViewActivityTest
|
||||
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
checkAllFields(activity, ViewMode.ADD_CARD, "Example Store", "", DateFormat.getDateInstance(DateFormat.LONG).format(date), "10.00", "$", "123456", null, "AZTEC");
|
||||
checkAllFields(activity, ViewMode.ADD_CARD, "Example Store", "", DateFormat.getDateInstance(DateFormat.LONG).format(date), "10.00", "$", "123456", context.getString(R.string.sameAsCardId), "AZTEC");
|
||||
assertEquals(-416706, ((ColorDrawable) activity.findViewById(R.id.thumbnail).getBackground()).getColor());
|
||||
}
|
||||
|
||||
@@ -1136,7 +1148,7 @@ public class LoyaltyCardViewActivityTest
|
||||
Activity activity = (Activity)activityController.get();
|
||||
final Context context = activity.getApplicationContext();
|
||||
|
||||
checkAllFields(activity, ViewMode.ADD_CARD, "Example Store", "", context.getString(R.string.never), "0", context.getString(R.string.points), "123456", null, "AZTEC");
|
||||
checkAllFields(activity, ViewMode.ADD_CARD, "Example Store", "", context.getString(R.string.never), "0", context.getString(R.string.points), "123456", context.getString(R.string.sameAsCardId), "AZTEC");
|
||||
assertEquals(-416706, ((ColorDrawable) activity.findViewById(R.id.thumbnail).getBackground()).getColor());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,9 @@ package protect.card_locker;
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
@@ -20,7 +18,6 @@ import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.android.controller.ActivityController;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowActivity;
|
||||
import org.w3c.dom.Text;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -1,11 +1,33 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TestHelpers {
|
||||
static public DBHelper getEmptyDb(Activity activity) {
|
||||
DBHelper db = new DBHelper(activity);
|
||||
|
||||
// Make sure no files remain
|
||||
Cursor cursor = db.getLoyaltyCardCursor();
|
||||
cursor.moveToFirst();
|
||||
while (!cursor.isAfterLast()) {
|
||||
int cardID = cursor.getColumnIndex(DBHelper.LoyaltyCardDbIds.ID);
|
||||
|
||||
try {
|
||||
Utils.saveCardImage(activity.getApplicationContext(), null, cardID, true);
|
||||
} catch (FileNotFoundException ignored) {}
|
||||
try {
|
||||
Utils.saveCardImage(activity.getApplicationContext(), null, cardID, false);
|
||||
} catch (FileNotFoundException ignored) {}
|
||||
|
||||
cursor.moveToNext();
|
||||
}
|
||||
|
||||
// Make sure DB is empty
|
||||
SQLiteDatabase database = db.getWritableDatabase();
|
||||
database.execSQL("delete from " + DBHelper.LoyaltyCardDbIds.TABLE);
|
||||
|
||||
BIN
app/src/test/res/protect/card_locker/fidme.zip
Normal file
BIN
app/src/test/res/protect/card_locker/stocard-back.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
app/src/test/res/protect/card_locker/stocard-front.jpg
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
app/src/test/res/protect/card_locker/stocard.zip
Normal file
@@ -9,7 +9,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||
classpath 'com.android.tools.build:gradle:4.2.2'
|
||||
classpath 'gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.7.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
||||
1
docs/CNAME
Normal file
@@ -0,0 +1 @@
|
||||
catima.app
|
||||
@@ -32,6 +32,7 @@ Supported barcodes:
|
||||
- PDF_417
|
||||
- QR_CODE
|
||||
- UPC_A
|
||||
- UPC_E
|
||||
|
||||
# Screenshots
|
||||
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
Bist du es auch leid, beim Bezahlen an der Kasse im Supermarkt jedes mal nach dieser Bonuskarte aus Plastik zu suchen? Genervt davon, dass die Brieftasche durch die ganzen Plastikkarten gefühlte fünf Meter dick ist? Wäre es nicht toll, eine <i>freie</i> Lösung für das Problem zu haben, die deine Daten nicht noch zusätzlich mit weiteren Anbietern teilt?
|
||||
|
||||
<i>Catima</i> ist eine Anwendung, die deine auf Strichcodes basierenden Kundenkarten auf deinem Smartphone verwaltet. <i>Catima</i> ist quelloffen und kann eines richtig gut: Deine Karten verwalten!
|
||||
|
||||
Neue Karten können mit Leichtigkeit hinzugefügt werden. Entweder nutzt du die Kamera deines Smartphones, um den Strichcode direkt einzulesen – oder du gibst die unter selbigem befindliche Ziffernfolge von Hand ein. An der Kasse zeigt <i>Catima</i> sodann den Strichcode auf dem Bildschirm deines Mobiltelefones an, sodass er vom Scanner gelesen werden kann. Sollte der Scanner dabei doch einmal streiken (was selten vorkommt – und wenn, dann meist nur bei älteren Scanner-Modellen), kann der Verkäufer die ebenfalls angezeigten Ziffern wiederum auch händisch erfassen.
|
||||
|
||||
Die Anwendung benötigt nur wenige Berechtigungen – und greift nie auf das Internet zu. <i>Catima</i> bietet auch die Möglichkeit, deine Kartensammlung zu exportieren sowie zu importieren. Damit kannst du die Daten z. B. auch auf ein anderes Gerät übertragen – oder etwa nach einem Werksreset wieder neu einlesen.
|
||||
Schluss mit der Suche nach Plastik-Belohnungskarten beim Bezahlen im Geschäft oder Webshop.
|
||||
<b>Scanne EAN-Codes mit der Kamera deines Geräts, du brauchst keine Karten mehr.</b>
|
||||
😺
|
||||
Du brauchst keine Geldbörse, oder mach sie federleicht für Wertsachen.
|
||||
😺
|
||||
Mit diesem unverzichtbaren (EDC)Werkzeug kannst du nutzloses Plastik durch Bargeld ersetzen.
|
||||
😺
|
||||
- Vermeide Spionage mit sehr wenigen Berechtigungen. Kein Internetzugang und keine Werbung.
|
||||
- Füge Karten oder Codes mit Namen und anpassbaren Farben hinzu.
|
||||
- Manuelle Code-Eingabe, wenn kein Barcode gespeichert ist oder nicht funktioniert.
|
||||
- Importiere Karten und Codes aus Dateien, Catima, Loyalty Card Keychain, Voucher Vault und FidMe.
|
||||
- Erstelle ein Backup aller Karten und übertrage diese auf ein neues Gerät, wenn du möchtest.
|
||||
- Teile Gutscheine, exklusive Angebote, Werbeaktionscodes oder Karten und Codes mit jeder Applikation.
|
||||
- Dunkler Modus und Zugänglichkeitsoptionen für sehbehinderte Nutzer.
|
||||
- Von der Freien-Software-Gemeinschaft für alle Menschen gemacht.
|
||||
- Lokalisierte, eigenhändige Übersetzungen für mehr als 20 Sprachen.
|
||||
- Kostenlos, unterstützt durch Gemeinschaftsbeiträge.
|
||||
- Verwende, studiere, ändere und teile es, wie du willst; <i>mit Allen</i>.
|
||||
- Nicht nur freie und quelloffene Software. <i>Copylefted</i> freie Software (GPLv3+) Kartenverwaltung.
|
||||
😺
|
||||
Vereinfache dein Leben und Einkäufe, und verliere nie wieder eine Papierrechung, eine Geschenkkarte für die Bezahlung im Geschäft oder ein Flugticket.
|
||||
Habe deine Prämien und Boni immer bei der Hand, und spare unterwegs.
|
||||
😺
|
||||
|
||||
@@ -1 +1 @@
|
||||
Keine Plastikverschmutzung. Speichere Rabatte, Mitgliedskarten und Strichcodes.
|
||||
Für Ihre EAN-Codes, Mitgliedschaften, Treueprogramme, Rabattmarkerl und Tickets.
|
||||
|
||||
@@ -1 +1 @@
|
||||
Catima – Kundenkarten- und Ticketverwaltung
|
||||
Catima — Das freie Kartenetui
|
||||
|
||||
@@ -8,7 +8,7 @@ With this essential everyday carry (EDC) tool you can replace useless plastic wi
|
||||
- Avoid spying with very few permissions. No Internet access and no ads.
|
||||
- Add cards or codes with names and customizable colours.
|
||||
- Manual code entry if there is no barcode to store, or it can't be used.
|
||||
- Import cards and codes from files, Catima, Loyalty Card Keychain, Voucher Vault, and FidMe.
|
||||
- Import cards and codes from files, Catima, FidMe, Loyalty Card Keychain, Stocard and Voucher Vault.
|
||||
- Make a backup of all your cards and transfer them to a new device if you want.
|
||||
- Share coupons, exclusive offers, promo codes, or cards and codes using any app.
|
||||
- Dark theme and accessibility options for vision impaired users.
|
||||
|
||||
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 61 KiB |
23
fastlane/metadata/android/fi-FI/full_description.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
Lopeta muovisten etukorttien etsiminen kaupan tai verkkokaupan kassalla.
|
||||
<b>Skannaa viivakoodit laitteeseesi sen kameran avulla, unohda kortit.</b>
|
||||
😺
|
||||
Unohda lompakko tai pidä se ultrakevyenä sinulle arvokkaita asioita varten.
|
||||
😺
|
||||
Tämän jokapäiväisen (EDC) työkalun avulla, voit korvata turhan muovin käteisellä.
|
||||
😺
|
||||
- Vältä vakoilua hyvin vähäisillä käyttöoikeuksilla. Ei Internet-yhteyttä eikä mainoksia.
|
||||
- Lisää kortteja tai koodeja nimettynä ja muokattavilla väreillä.
|
||||
- Koodin syöttäminen manuaalisesti, jos tallennettavaa viivakoodia ei ole tai sitä ei voida käyttää.
|
||||
- Tuo kortteja ja koodeja varmuuskopiotiedostoista, Catima, Loyalty Card Keychain, Voucher Vault, ja FidMe.
|
||||
- Tee varmuuskopio kaikista korteistasi ja siirrä ne halutessasi uuteen laitteeseen.
|
||||
- Jaa kuponkeja, erikoistarjouksia, tarjouskoodeja tai kortteja ja koodeja millä tahansa sovelluksella.
|
||||
- Tumma teema ja esteettömän käytön mahdollisuus näkövammaisille käyttäjille.
|
||||
- Vapaan ohjelmistoyhteisön kaikkia varten tekemä.
|
||||
- Lokalisoidut käsintehdyt käännökset yli 20 kielelle.
|
||||
- Vapaa, yhteisön tukema.
|
||||
- Käytä, opiskele, muuta ja jaa sitä toiveidesi mukaan; <i>kaikille</i>.
|
||||
- Ei vain vapaa ohjelma / Avoin lähdekoodi. <i>Copyleft</i> vapaa ohjelma (GPLv3+) korttien hallinta.
|
||||
😺
|
||||
Yksinkertaista elämääsi ja ostosten tekemistä, äläkä enää koskaan hukkaa paperikuittia, kaupassa maksettavaa lahjakorttia tai lentolippua.
|
||||
Ota kaikki edut ja bonukset mukaasi ja säästä missä menetkin.
|
||||
😺
|
||||
1
fastlane/metadata/android/fi-FI/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Viivakoodeillesi, jäsenyyksillesi, kanta-asiakkkuuksillesi ja lipuillesi.
|
||||
1
fastlane/metadata/android/fi-FI/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Catima — Vapaa korttilompakko
|
||||
@@ -8,7 +8,7 @@ Avec cet outil essentiel à emporter au quotidien, vous pouvez remplacer le plas
|
||||
- Évitez l'espionnage avec très peu de permissions. Aucun accès à Internet et aucune publicité.
|
||||
- Ajoutez des cartes ou des codes avec des noms et des couleurs personnalisables.
|
||||
- Saisie manuelle du code s'il n'y a pas de code-barres à stocker ou s'il ne peut pas être utilisé.
|
||||
- Importez des cartes et des codes depuis des fichiers, Catima, Loyalty Card Keychain, Voucher Vault et FidMe.
|
||||
- Importez des cartes et des codes depuis des fichiers, Catima, FidMe, Loyalty Card Keychain, Stocard et Voucher Vault.
|
||||
- Faites une sauvegarde de toutes vos cartes et transférez-les sur un nouvel appareil si vous le souhaitez.
|
||||
- Partagez des coupons, des offres exclusives, des codes promotionnels ou des cartes et des codes en utilisant n'importe quelle application.
|
||||
- Thème sombre et options d'accessibilité pour les utilisateurs malvoyants.
|
||||
|
||||
@@ -1 +1 @@
|
||||
Pas de pollution plastique. Stockez les cartes de fidélité dans ce portefeuille.
|
||||
Pour vos codes-barres, adhésions, programmes de fidélité, coupons et tickets.
|
||||
|
||||
@@ -1 +1 @@
|
||||
Catima – Cartes de fidélité, tickets et coupons
|
||||
Catima – Le porte-cartes libre
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
Sei stanco di cercare la tua carta fedeltà durante i pagamenti in negozio? Cerchi una soluzione gratuita che non catture le tue informazioni?
|
||||
|
||||
Catima è un'applicazione che memorizzerà sul tuo telefono le tue carte fedeltà basate su codici a barre. L'applicazione è open source e cerca di fare bene una cosa: gestire le tue carte!
|
||||
|
||||
Nuove carte possono essere aggiunte in un attimo. Utilizza la fotocamera per acquisire il codice a barre o digita il numero. Quando il codice a barre viene caricato nel negozio e visualizzato, può essere scansionato con un moderno lettore di codici a barre. (Alcuni negozi utilizzano lettori di codici a barre meno recenti, come scanner piani, invece di scanner di immagini. Questi non possono leggere il display dello smartphone. In questi casi, devi chiedere all'impiegato di digitare il numero manualmente).
|
||||
|
||||
L'applicazione richiede pochissime autorizzazioni e non tenta mai di accedere a Internet. E' possibile eseguire il backup delle carte nella memoria locale. E da lì puoi conservare il backup da qualche parte al sicuro.
|
||||
Basta con la ricerca di carte premio di plastica quando esci dal negozio.
|
||||
<b>Scannerizza i codici a barre sul tuo dispositivo usando la sua fotocamera, dimentica le carte.</b>
|
||||
😺
|
||||
Dimentica il tuo portafoglio, o tienilo ultraleggero per gli oggetti di valore.
|
||||
😺
|
||||
Con questo strumento essenziale per il trasporto quotidiano, puoi sostituire la plastica inutile con i contanti.
|
||||
😺
|
||||
- Evita di spiare con pochissimi permessi. Nessun accesso a Internet e nessuna pubblicità.
|
||||
- Aggiungi carte o codici con nomi e colori personalizzabili.
|
||||
- Inserimento manuale del codice se non c'è un codice a barre da memorizzare o non può essere utilizzato.
|
||||
- Importa carte e codici da file, Catima, Portachiavi delle carte fedeltà, Voucher Vault e FidMe.
|
||||
- Fai un backup di tutte le tue carte e trasferiscile su un nuovo dispositivo se vuoi.
|
||||
- Condividi coupon, offerte esclusive, codici promozionali o carte e codici utilizzando qualsiasi app.
|
||||
- Tema scuro e opzioni di accessibilità per gli utenti con problemi di vista.
|
||||
- Fatto per tutti dalla comunità del software libero.
|
||||
- Traduzioni localizzate a mano per 20+ lingue.
|
||||
- Gratis, supportato dai contributi della comunità.
|
||||
- Usalo, studialo, cambialo e condividilo come vuoi; <i>con tutti</i>.
|
||||
- Non solo un software libero. <i>Copylefted</i> software libero (GPLv3+) gestione schede.
|
||||
😺
|
||||
Semplifica la tua vita e gli acquisti, e non perdere mai più una ricevuta cartacea, una carta regalo con pagamento in negozio o un biglietto aereo.
|
||||
Porta con te tutti i tuoi premi e bonus, e risparmia mentre vai.
|
||||
😺
|
||||
|
||||
@@ -1 +1 @@
|
||||
Gestisce le carte fedeltà basate su codici a barre sul telefono
|
||||
Per i tuoi codici a barre, iscrizioni, programmi di fedeltà, coupon e biglietti.
|
||||
|
||||
@@ -1 +1 @@
|
||||
Catima – Gestore di carte fedeltà
|
||||
Catima — Il portafoglio di carte libero
|
||||
|
||||
23
fastlane/metadata/android/lt/full_description.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
Nustokite ieškoti plastikinių lojalumo kortelių parduotuvėje ar parduotuvėje internetinėje.
|
||||
<b> Nuskaitykite brūkšninius kodus į savo įrenginį naudodami jo kamerą ir pamirškite korteles.</b>
|
||||
😺
|
||||
Pamirškite piniginę arba laikykite ją itin lengvą tik vertingiems daiktams.
|
||||
😺
|
||||
Naudodami šį būtiną kasdienio naudojimo (EDC) įrankį galite nenaudingą plastiką pakeisti grynaisiais pinigais.
|
||||
😺
|
||||
- Išvenkite šnipinėjimo, programėlė prašo labai nedaug leidimų. Jokios prieigos prie interneto ir jokių reklamų.
|
||||
- Pridėkite korteles ar kodus su pavadinimais ir pasirenkamomis spalvomis.
|
||||
- Rankiniu būdu įveskite kodą, jei nėra saugotino brūkšninio kodo arba jo negalima naudoti.
|
||||
- Importuokite korteles ir kodus iš failų, "Catima", "FidMe", "Loyalty Card Keychain", "Stocard", "Voucher Vault".
|
||||
- Padarykite atsarginę visų kortelių kopiją ir, jei norite, perkelkite jas į naują įrenginį.
|
||||
- Dalinkitės kuponais, išskirtiniais pasiūlymais, reklaminiais kodais arba kortelėmis ir kodais naudodami bet kurią programą.
|
||||
- Tamsi tema ir parinktys naudotojams su regos sutrikimais.
|
||||
- "Libre" programinės įrangos bendruomenės sukurta visiems.
|
||||
- Lokalizuoti rankų darbo vertimai į daugiau nei 20 kalbų.
|
||||
- Nemokama, remiama bendruomenės įnašais.
|
||||
- Naudokite, studijuokite, keiskite ir dalinkitės, kaip norite; <i>su visais</i>.
|
||||
- Ne tik laisvoji programinė įranga / atvirasis kodas. <i>"Copylefted"</i> laisvosios programinės įrangos (GPLv3+) kortelių valdymas.
|
||||
😺
|
||||
Supaprastinkite savo gyvenimą ir apsipirkimą ir daugiau niekada nepraraskite popierinio kvito, parduotuvės dovanų kortelės ar lėktuvo bilieto.
|
||||
Pasiimkite su savimi visus nuolaidas ir premijas ir taupykite keliaudami.
|
||||
😺
|
||||
1
fastlane/metadata/android/lt/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Jūsų brūkšniniams kodams, narystėms, lojalumo programoms, kuponams ir bilietams.
|
||||
1
fastlane/metadata/android/lt/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Catima - Libre kortelių piniginė
|
||||
@@ -8,7 +8,7 @@ Met deze essentiële app kun je je waardeloze plastic kaarten weggooien.
|
||||
- Weinig rechten vereist, geen spionage/tracking, geen internettoegang en geen reclame;
|
||||
- Voorzie kaarten of codes van namen en zelfgekozen kleuren;
|
||||
- Voer codes handmatig in als er geen barcode is;
|
||||
- Importeer kaarten en codes uit bestanden, Catima, Klantenkaartkluis, Voucher Vault, en FidMe;
|
||||
- Importeer kaarten en codes uit bestanden, Catima, FidMe, Klantenkaartkluis, Stocard en Voucher Vault;
|
||||
- Maak een back-up van al je kaarten en importeer deze op een ander apparaat;
|
||||
- Deel coupons, exclusieve aanbiedingen, promocodes of kaarten en codes met andere apps;
|
||||
- Donker thema en toegankelijkheidsopties voor blinden en slechtzienden;
|
||||
|
||||
23
fastlane/metadata/android/uk/full_description.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
Тепер можна не шукати свої пластикові картки знижок та програм лояльності при покупці у магазині чи на сайті.
|
||||
<b>Відскануйте штрих-коди на свій пристрій за допомогою його камери та забудьте про картки.</b>
|
||||
😺
|
||||
Забудьте свій гаманець чи просто тримайте важливі речі у ньому.
|
||||
😺
|
||||
За допомогою цього важливого щоденного мобільного інструменту (ЩМІ) ви можете замінити непотрібний пластик готівкою.
|
||||
😺
|
||||
- Уникнення шпигунства за допомогою дуже малої кількості дозволів. Немає доступу до Інтернету та реклами.
|
||||
- Додавання карток або кодів з іменами та настроюваними кольорами.
|
||||
- Введення коду вручну, якщо немає штрих-коду для зберігання або він не може бути використаний.
|
||||
- Імпортуйте картки та коди з файлів, Catima, Loyalty Card Keychain, Voucher Vault та FidMe.
|
||||
- Зробіть резервну копію всіх своїх карток і перенесіть їх на новий пристрій, якщо хочете.
|
||||
- Діліться купонами, ексклюзивними пропозиціями, промо-кодами або картками та кодами за допомогою будь-якої програми.
|
||||
- Темна тема та параметри доступності для користувачів із вадами зору.
|
||||
- Створено для всіх спільнотою вільного програмного забезпечення.
|
||||
- Локалізовані власноручні переклади для понад 20 мов.
|
||||
- Безкоштовна, підтримані внесками громади.
|
||||
- Використовуйте, вивчайте, змінюйте та діліться програмою, як хочете; <i>з усіма</i>.
|
||||
- Не тільки вільне програмне забезпечення / відкритий код. <i>Копілефт</i> вільне програмне забезпечення (GPLv3 +).
|
||||
😺
|
||||
Спростіть своє життя та покупки, і ніколи більше не втрачайте паперову квитанцію, подарункову картку магазину або квиток на літак.
|
||||
Візьміть із собою всі свої нагороди та бонуси та економте на ходу.
|
||||
😺
|
||||
1
fastlane/metadata/android/uk/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Для ваших штрих-кодів, клубів, програм лояльності, купонів та квитків.
|
||||
1
fastlane/metadata/android/uk/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Catima — Вільний гаманець для карт
|
||||