mirror of
https://github.com/CatimaLoyalty/Android.git
synced 2025-12-24 23:57:53 -05:00
Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a408f8d727 | ||
|
|
2d7bb02d1a | ||
|
|
38c8e38ed6 | ||
|
|
28b95b8f75 | ||
|
|
c1ebbdb997 | ||
|
|
fadba7a11c | ||
|
|
fd61434565 | ||
|
|
398dff4b3c | ||
|
|
5c95b750b2 | ||
|
|
9851c0a2fa | ||
|
|
c121f846c5 | ||
|
|
c20ba027cf | ||
|
|
e5dd26b8ee | ||
|
|
3b449464ac | ||
|
|
3f4d4e38cd | ||
|
|
30ccd03686 | ||
|
|
5493947c28 | ||
|
|
4172903b42 | ||
|
|
00b1368176 | ||
|
|
09fee5628f | ||
|
|
7a7a2f8361 | ||
|
|
a9ced56023 | ||
|
|
a4af171598 | ||
|
|
164c82a779 | ||
|
|
608c3ab863 | ||
|
|
379a71c7ad | ||
|
|
ee6a6dffcf | ||
|
|
1d6a393914 | ||
|
|
5c4a905ac0 | ||
|
|
91ce71ea68 | ||
|
|
f07ac3e026 | ||
|
|
649f2c47b4 | ||
|
|
62dcc373ed | ||
|
|
e9b04adec6 | ||
|
|
636be16bdd | ||
|
|
5ad28f37b8 | ||
|
|
5ff6059e86 | ||
|
|
b9df712394 | ||
|
|
790fd7e48f | ||
|
|
bb43266a01 | ||
|
|
450cfce84a | ||
|
|
6cef56b38b | ||
|
|
fbb7cf7e9c | ||
|
|
9ab2a6a5b2 | ||
|
|
682fc8303c | ||
|
|
aa1274566b | ||
|
|
d8cd581cb0 | ||
|
|
2cd00f9103 | ||
|
|
ae07f94b25 | ||
|
|
22d671263a | ||
|
|
227f30361f | ||
|
|
b964652b83 | ||
|
|
f926ffa1d0 | ||
|
|
d03b8b5635 | ||
|
|
4e5fea7a52 | ||
|
|
95cb6c0a08 | ||
|
|
d350d0b2c7 | ||
|
|
ac0f6f6f3e | ||
|
|
ab030ba002 | ||
|
|
93103c8c6d | ||
|
|
576ec1e6de | ||
|
|
b602ce5d78 | ||
|
|
0ecd38ed1c | ||
|
|
d48e02463c | ||
|
|
b9d9c8d2e3 | ||
|
|
3ae665b70f | ||
|
|
9cf9959b6b | ||
|
|
d11e2c166b | ||
|
|
f783be7a4f | ||
|
|
9ee96b88e8 | ||
|
|
ba896fc1db | ||
|
|
47f1ea80b6 | ||
|
|
b5efa28e85 | ||
|
|
d456a8920d | ||
|
|
4ef0c96b29 | ||
|
|
343c77f9e3 | ||
|
|
1425d4af58 | ||
|
|
48510494eb | ||
|
|
d5d53b241a | ||
|
|
901c2d8154 | ||
|
|
84d7e15b5c | ||
|
|
b8fa4d7060 | ||
|
|
da9e3bb6b2 | ||
|
|
3a5973a04d | ||
|
|
673e64924b | ||
|
|
5f99f2b17e | ||
|
|
bf05103955 | ||
|
|
5ea6155c39 | ||
|
|
abb1cd29f0 | ||
|
|
52363cdff4 | ||
|
|
ac4f4e3a9a | ||
|
|
555387e20d | ||
|
|
70ae8ff167 | ||
|
|
86512532f1 | ||
|
|
ebf6318aa2 | ||
|
|
fd482a4cba | ||
|
|
54d91dc8a1 | ||
|
|
9fa7fe388f | ||
|
|
1415d8da3e | ||
|
|
0ad5de18e1 | ||
|
|
9659a2a2cd | ||
|
|
1dd894bd27 | ||
|
|
0d9151294e | ||
|
|
09dda99afc | ||
|
|
a4fb91b9aa | ||
|
|
036b821b28 | ||
|
|
479a35657f | ||
|
|
eca9d1c74c | ||
|
|
95055f1ce6 |
2
.github/workflows/autoclose-needs-info.yml
vendored
2
.github/workflows/autoclose-needs-info.yml
vendored
@@ -19,6 +19,4 @@ jobs:
|
||||
close-issue-message: 'This issue is missing necessary information and cannot be worked on in its current state. It has therefore been closed to keep the issue tracker clean. If you have more information, feel free to reopen it.'
|
||||
close-pr-message: 'This PR is missing necessary information and cannot be merged in its current state. It has therefore been closed to keep the issue tracker clean. If you have more information, feel free to reopen it.'
|
||||
only-labels: 'state: needs info'
|
||||
stale-issue-label: 'state: needs info'
|
||||
stale-pr-label: 'state: needs info'
|
||||
enable-statistics: true
|
||||
|
||||
44
.scripts/dump_stocard_stores.py
Executable file
44
.scripts/dump_stocard_stores.py
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import csv
|
||||
import json
|
||||
import msgpack
|
||||
|
||||
MSGPACK = "bootstrapdata.msgpack"
|
||||
OUTFILE = "stocard_stores.csv"
|
||||
|
||||
|
||||
def load(fh):
|
||||
data = []
|
||||
for r in msgpack.Unpacker(fh, raw=False):
|
||||
if r["collection"] == "/loyalty-card-providers/":
|
||||
d = json.loads(r["data"])
|
||||
data.append([r["resource_id"], d["name"], d["default_barcode_format"]])
|
||||
return data
|
||||
|
||||
|
||||
def save(data, output_file=OUTFILE):
|
||||
with open(output_file, "w") as fh:
|
||||
writer = csv.writer(fh, lineterminator="\n")
|
||||
writer.writerow(["_id", "name", "barcodeFormat"])
|
||||
for row in data:
|
||||
writer.writerow(row)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
epilog=f"INPUT_FILE must be a .msgpack or .apk and defaults to {MSGPACK}; "
|
||||
f"OUTPUT_FILE defaults to {OUTFILE}")
|
||||
parser.add_argument("input_file", metavar="INPUT_FILE", nargs="?", default=MSGPACK)
|
||||
parser.add_argument("output_file", metavar="OUTPUT_FILE", nargs="?", default=OUTFILE)
|
||||
args = parser.parse_args()
|
||||
if args.input_file.lower().endswith(".apk"):
|
||||
import zipfile
|
||||
with zipfile.ZipFile(args.input_file) as zf:
|
||||
with zf.open(f"assets/{MSGPACK}") as fh:
|
||||
data = load(fh)
|
||||
else:
|
||||
with open(args.input_file, "rb") as fh:
|
||||
data = load(fh)
|
||||
save(data, args.output_file)
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## v2.25.2 - 129 (2023-07-27)
|
||||
|
||||
- Improved Catima importer (fixes cards missing when importing)
|
||||
- Fix crash when rotating screen while setting valid from/expiry date
|
||||
- Minor UI tweaks
|
||||
|
||||
## v2.25.1 - 128 (2023-07-17)
|
||||
|
||||
- Fix rare crash
|
||||
|
||||
## v2.25.0 - 127 (2023-07-09)
|
||||
|
||||
- Barcode rendering improvements
|
||||
|
||||
34
Gemfile.lock
34
Gemfile.lock
@@ -8,20 +8,20 @@ GEM
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.771.0)
|
||||
aws-sdk-core (3.173.1)
|
||||
aws-partitions (1.793.0)
|
||||
aws-sdk-core (3.180.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.64.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sdk-kms (1.71.0)
|
||||
aws-sdk-core (~> 3, >= 3.177.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.122.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sdk-s3 (1.132.0)
|
||||
aws-sdk-core (~> 3, >= 3.179.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.5.2)
|
||||
aws-sigv4 (~> 1.6)
|
||||
aws-sigv4 (1.6.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.1.0)
|
||||
@@ -30,13 +30,13 @@ GEM
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.4)
|
||||
digest-crc (0.6.5)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.99.0)
|
||||
excon (0.100.0)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
@@ -66,7 +66,7 @@ GEM
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.7)
|
||||
fastlane (2.213.0)
|
||||
fastlane (2.214.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
@@ -106,9 +106,9 @@ GEM
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.42.0)
|
||||
google-apis-androidpublisher_v3 (0.46.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.0)
|
||||
google-apis-core (0.11.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
@@ -137,7 +137,7 @@ GEM
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.5.2)
|
||||
googleauth (1.7.0)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
@@ -150,7 +150,7 @@ GEM
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.6.2)
|
||||
json (2.6.3)
|
||||
jwt (2.7.0)
|
||||
jwt (2.7.1)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.2)
|
||||
@@ -161,14 +161,14 @@ GEM
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.7.0)
|
||||
public_suffix (5.0.1)
|
||||
public_suffix (5.0.3)
|
||||
rake (13.0.6)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.5)
|
||||
rexml (3.2.6)
|
||||
rouge (2.0.7)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
|
||||
13
SECURITY.md
Normal file
13
SECURITY.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Security Policy
|
||||
|
||||
Catima is designed to use as little permissions as possible to limit both the attack surface as well as the damage that can be done when abusing a security flaw.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the most recent stable release is supported.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Security vulnerabilities can be reported through [GitHub Security Advisories](https://github.com/CatimaLoyalty/Android/security/advisories) or [the contact info written on my personal website](https://sylviavanos.nl/#contact). Currently, Matrix is the only end-to-end encrypted option.
|
||||
|
||||
Please note that only security vulnerabilities in Catima should be reported as stated above. For other issues, including antivirus false positives and malicious applications trying to trick people into granting them Catima's "Read Cards" permission, please use [regular issues](https://github.com/CatimaLoyalty/Android/issues).
|
||||
@@ -19,8 +19,8 @@ android {
|
||||
applicationId "me.hackerchick.catima"
|
||||
minSdk 21
|
||||
targetSdk 33
|
||||
versionCode 127
|
||||
versionName "2.25.0"
|
||||
versionCode 129
|
||||
versionName "2.25.2"
|
||||
|
||||
vectorDrawables.useSupportLibrary true
|
||||
multiDexEnabled true
|
||||
|
||||
@@ -94,10 +94,12 @@ public class AboutContent {
|
||||
|
||||
public String getContributorInfo() {
|
||||
StringBuilder contributorInfo = new StringBuilder();
|
||||
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_contributors), getContributors()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
||||
contributorInfo.append(getCopyright());
|
||||
contributorInfo.append("\n\n");
|
||||
contributorInfo.append(context.getString(R.string.app_copyright_old));
|
||||
contributorInfo.append("\n\n");
|
||||
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_contributors), getContributors()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
||||
contributorInfo.append("\n\n");
|
||||
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_libraries), getThirdPartyLibraries()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
||||
contributorInfo.append("\n\n");
|
||||
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_resources), getUsedThirdPartyAssets()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
||||
|
||||
@@ -16,13 +16,18 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Currency;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class DBHelper extends SQLiteOpenHelper {
|
||||
public static final String DATABASE_NAME = "Catima.db";
|
||||
public static final int ORIGINAL_DATABASE_VERSION = 1;
|
||||
public static final int DATABASE_VERSION = 16;
|
||||
|
||||
// NB: changing this value requires a migration
|
||||
public static final int DEFAULT_ZOOM_LEVEL = 100;
|
||||
|
||||
public static class LoyaltyCardDbGroups {
|
||||
public static final String TABLE = "groups";
|
||||
public static final String ID = "_id";
|
||||
@@ -106,7 +111,7 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," +
|
||||
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'," +
|
||||
LoyaltyCardDbIds.LAST_USED + " INTEGER DEFAULT '0', " +
|
||||
LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '100', " +
|
||||
LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '" + DEFAULT_ZOOM_LEVEL + "', " +
|
||||
LoyaltyCardDbIds.ARCHIVE_STATUS + " INTEGER DEFAULT '0' )");
|
||||
|
||||
// create associative table for cards in groups
|
||||
@@ -323,6 +328,21 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<String> imageFiles(Context context, final SQLiteDatabase database) {
|
||||
Set<String> files = new HashSet<>();
|
||||
Cursor cardCursor = getLoyaltyCardCursor(database);
|
||||
while (cardCursor.moveToNext()) {
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
|
||||
for (ImageLocationType imageLocationType : ImageLocationType.values()) {
|
||||
String name = Utils.getCardImageFileName(card.id, imageLocationType);
|
||||
if (Utils.retrieveCardImageAsFile(context, name).exists()) {
|
||||
files.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private static ContentValues generateFTSContentValues(final int id, final String store, final String note) {
|
||||
// FTS on Android is severely limited and can only search for word starting with a certain string
|
||||
// So for each word, we grab every single substring
|
||||
|
||||
@@ -14,30 +14,28 @@ public class LoyaltyCard implements Parcelable {
|
||||
public final int id;
|
||||
public final String store;
|
||||
public final String note;
|
||||
@Nullable
|
||||
public final Date validFrom;
|
||||
@Nullable
|
||||
public final Date expiry;
|
||||
public final BigDecimal balance;
|
||||
@Nullable
|
||||
public final Currency balanceType;
|
||||
public final String cardId;
|
||||
|
||||
@Nullable
|
||||
public final String barcodeId;
|
||||
|
||||
@Nullable
|
||||
public final CatimaBarcode barcodeType;
|
||||
|
||||
@Nullable
|
||||
public final Integer headerColor;
|
||||
|
||||
public final int starStatus;
|
||||
public final int archiveStatus;
|
||||
public final long lastUsed;
|
||||
public int zoomLevel;
|
||||
|
||||
public LoyaltyCard(final int id, final String store, final String note, final Date validFrom,
|
||||
final Date expiry, final BigDecimal balance, final Currency balanceType,
|
||||
final String cardId, @Nullable final String barcodeId,
|
||||
@Nullable final CatimaBarcode barcodeType,
|
||||
public LoyaltyCard(final int id, final String store, final String note, @Nullable final Date validFrom,
|
||||
@Nullable final Date expiry, final BigDecimal balance, @Nullable final Currency balanceType,
|
||||
final String cardId, @Nullable final String barcodeId, @Nullable final CatimaBarcode barcodeType,
|
||||
@Nullable final Integer headerColor, final int starStatus,
|
||||
final long lastUsed, final int zoomLevel, final int archiveStatus) {
|
||||
this.id = id;
|
||||
@@ -145,6 +143,24 @@ public class LoyaltyCard implements Parcelable {
|
||||
return new LoyaltyCard(id, store, note, validFrom, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starred, lastUsed, zoomLevel, archived);
|
||||
}
|
||||
|
||||
public static boolean isDuplicate(final LoyaltyCard a, final LoyaltyCard b) {
|
||||
// Skip lastUsed & zoomLevel
|
||||
return a.id == b.id && // non-nullable int
|
||||
a.store.equals(b.store) && // non-nullable String
|
||||
a.note.equals(b.note) && // non-nullable String
|
||||
Utils.equals(a.validFrom, b.validFrom) && // nullable Date
|
||||
Utils.equals(a.expiry, b.expiry) && // nullable Date
|
||||
a.balance.equals(b.balance) && // non-nullable BigDecimal
|
||||
Utils.equals(a.balanceType, b.balanceType) && // nullable Currency
|
||||
a.cardId.equals(b.cardId) && // non-nullable String
|
||||
Utils.equals(a.barcodeId, b.barcodeId) && // nullable String
|
||||
Utils.equals(a.barcodeType == null ? null : a.barcodeType.format(),
|
||||
b.barcodeType == null ? null : b.barcodeType.format()) && // nullable CatimaBarcode with no overridden .equals(), so we need to check .format()
|
||||
Utils.equals(a.headerColor, b.headerColor) && // nullable Integer
|
||||
a.starStatus == b.starStatus && // non-nullable int
|
||||
a.archiveStatus == b.archiveStatus; // non-nullable int
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return id;
|
||||
|
||||
@@ -36,6 +36,20 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.google.android.material.chip.Chip;
|
||||
import com.google.android.material.chip.ChipGroup;
|
||||
import com.google.android.material.color.MaterialColors;
|
||||
@@ -67,20 +81,9 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import protect.card_locker.async.TaskHandler;
|
||||
import protect.card_locker.databinding.LayoutChipChoiceBinding;
|
||||
import protect.card_locker.databinding.LoyaltyCardEditActivityBinding;
|
||||
@@ -376,6 +379,19 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
addDateFieldTextChangedListener(expiryField, R.string.never, R.string.chooseExpiryDate, LoyaltyCardField.expiry);
|
||||
|
||||
DatePickerFragment.registerDatePickListener(this, (textFieldToEdit, newDate) -> {
|
||||
switch (textFieldToEdit) {
|
||||
case validFrom:
|
||||
formatDateField(this, validFromField, newDate);
|
||||
break;
|
||||
case expiry:
|
||||
formatDateField(this, expiryField, newDate);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unexpected field: " + textFieldToEdit);
|
||||
}
|
||||
});
|
||||
|
||||
balanceField.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
balanceField.setText(Utils.formatBalanceWithoutCurrencySymbol(tempLoyaltyCard.balance, tempLoyaltyCard.balanceType));
|
||||
@@ -967,9 +983,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
if (!lastValue.toString().equals(getString(chooseDateOptionStringId))) {
|
||||
dateField.setText(lastValue);
|
||||
}
|
||||
DialogFragment datePickerFragment = new DatePickerFragment(
|
||||
LoyaltyCardEditActivity.this,
|
||||
dateField,
|
||||
DialogFragment datePickerFragment = DatePickerFragment.newInstance(
|
||||
loyaltyCardField,
|
||||
(Date) dateField.getTag(),
|
||||
// if the expiry date is being set, set date picker's minDate to the 'valid from' date
|
||||
loyaltyCardField == LoyaltyCardField.expiry ? (Date) validFromField.getTag() : null,
|
||||
// if the 'valid from' date is being set, set date picker's maxDate to the expiry date
|
||||
@@ -1336,27 +1352,54 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
public static class DatePickerFragment extends DialogFragment
|
||||
implements DatePickerDialog.OnDateSetListener {
|
||||
|
||||
final Context context;
|
||||
final EditText textFieldEdit;
|
||||
@Nullable
|
||||
final Date minDate;
|
||||
@Nullable
|
||||
final Date maxDate;
|
||||
public interface OnDatePickListener {
|
||||
void onDatePicked(@NonNull LoyaltyCardField textFieldToEdit, @NonNull Date newDate);
|
||||
}
|
||||
|
||||
DatePickerFragment(Context context, EditText textFieldEdit, @Nullable Date minDate, @Nullable Date maxDate) {
|
||||
this.context = context;
|
||||
this.textFieldEdit = textFieldEdit;
|
||||
this.minDate = minDate;
|
||||
this.maxDate = maxDate;
|
||||
private static final String TEXT_FIELD_TO_EDIT_ARGUMENT_KEY = "text_field_to_edit";
|
||||
private static final String CURRENT_DATE_ARGUMENT_KEY = "current_date";
|
||||
private static final String MIN_DATE_ARGUMENT_KEY = "min_date";
|
||||
private static final String MAX_DATE_ARGUMENT_KEY = "max_date";
|
||||
private static final String PICK_DATE_REQUEST_KEY = "pick_date_request";
|
||||
private static final String NEWLY_PICKED_DATE_ARGUMENT_KEY = "newly_picked_date";
|
||||
|
||||
LoyaltyCardField textFieldEdit;
|
||||
@Nullable
|
||||
Date minDate;
|
||||
@Nullable
|
||||
Date maxDate;
|
||||
|
||||
public static DatePickerFragment newInstance(@NonNull LoyaltyCardField textField, @Nullable Date currentDate, @Nullable Date minDate, @Nullable Date maxDate) {
|
||||
Bundle args = new Bundle();
|
||||
args.putSerializable(TEXT_FIELD_TO_EDIT_ARGUMENT_KEY, textField);
|
||||
args.putSerializable(CURRENT_DATE_ARGUMENT_KEY, currentDate);
|
||||
args.putSerializable(MIN_DATE_ARGUMENT_KEY, minDate);
|
||||
args.putSerializable(MAX_DATE_ARGUMENT_KEY, maxDate);
|
||||
DatePickerFragment fragment = new DatePickerFragment();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public static void registerDatePickListener(@NonNull AppCompatActivity activity, @NonNull OnDatePickListener listener) {
|
||||
activity.getSupportFragmentManager().setFragmentResultListener(
|
||||
PICK_DATE_REQUEST_KEY,
|
||||
activity,
|
||||
(requestKey, result) -> listener.onDatePicked(
|
||||
(LoyaltyCardField) Objects.requireNonNull(result.getSerializable(TEXT_FIELD_TO_EDIT_ARGUMENT_KEY)),
|
||||
(Date) Objects.requireNonNull(result.getSerializable(NEWLY_PICKED_DATE_ARGUMENT_KEY))));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Bundle args = requireArguments();
|
||||
textFieldEdit = (LoyaltyCardField) args.getSerializable(TEXT_FIELD_TO_EDIT_ARGUMENT_KEY);
|
||||
minDate = (Date) args.getSerializable(MIN_DATE_ARGUMENT_KEY);
|
||||
maxDate = (Date) args.getSerializable(MAX_DATE_ARGUMENT_KEY);
|
||||
// Use the current date as the default date in the picker
|
||||
final Calendar c = Calendar.getInstance();
|
||||
|
||||
Date date = (Date) textFieldEdit.getTag();
|
||||
Date date = (Date) args.getSerializable(CURRENT_DATE_ARGUMENT_KEY);
|
||||
if (date != null) {
|
||||
c.setTime(date);
|
||||
}
|
||||
@@ -1398,7 +1441,10 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
Date date = new Date(unixTime);
|
||||
|
||||
formatDateField(context, textFieldEdit, date);
|
||||
Bundle result = new Bundle();
|
||||
result.putSerializable(TEXT_FIELD_TO_EDIT_ARGUMENT_KEY, textFieldEdit);
|
||||
result.putSerializable(NEWLY_PICKED_DATE_ARGUMENT_KEY, date);
|
||||
getParentFragmentManager().setFragmentResult(PICK_DATE_REQUEST_KEY, result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,19 +70,13 @@ class ShortcutHelper {
|
||||
ShortcutInfoCompat found = list.remove(foundIndex.intValue());
|
||||
list.addFirst(found);
|
||||
} else {
|
||||
// The item is new to the list. First, we need to trim the list
|
||||
// until it is able to accept a new item, then the item is
|
||||
// inserted.
|
||||
while (list.size() >= MAX_SHORTCUTS) {
|
||||
list.pollLast();
|
||||
}
|
||||
|
||||
// The item is new to the list. We add it and trim the list later.
|
||||
ShortcutInfoCompat shortcut = createShortcutBuilder(context, card).build();
|
||||
|
||||
list.addFirst(shortcut);
|
||||
}
|
||||
|
||||
LinkedList<ShortcutInfoCompat> finalList = new LinkedList<>();
|
||||
int rank = 0;
|
||||
|
||||
// The ranks are now updated; the order in the list is the rank.
|
||||
for (int index = 0; index < list.size(); index++) {
|
||||
@@ -90,11 +84,20 @@ class ShortcutHelper {
|
||||
|
||||
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(database, Integer.parseInt(prevShortcut.getId()));
|
||||
|
||||
ShortcutInfoCompat updatedShortcut = createShortcutBuilder(context, loyaltyCard)
|
||||
.setRank(index)
|
||||
.build();
|
||||
// skip outdated cards that no longer exist
|
||||
if (loyaltyCard != null) {
|
||||
ShortcutInfoCompat updatedShortcut = createShortcutBuilder(context, loyaltyCard)
|
||||
.setRank(rank)
|
||||
.build();
|
||||
|
||||
finalList.addLast(updatedShortcut);
|
||||
finalList.addLast(updatedShortcut);
|
||||
rank++;
|
||||
|
||||
// trim the list
|
||||
if (rank >= MAX_SHORTCUTS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShortcutManagerCompat.setDynamicShortcuts(context, finalList);
|
||||
|
||||
@@ -50,6 +50,8 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.Calendar;
|
||||
@@ -58,6 +60,8 @@ import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import protect.card_locker.preferences.Settings;
|
||||
|
||||
@@ -76,6 +80,8 @@ public class Utils {
|
||||
public static final int CARD_IMAGE_FROM_FILE_BACK = 9;
|
||||
public static final int CARD_IMAGE_FROM_FILE_ICON = 10;
|
||||
|
||||
public static final String CARD_IMAGE_FILENAME_REGEX = "^(card_)(\\d+)(_(?:front|back|icon)\\.png)$";
|
||||
|
||||
static final double LUMINANCE_MIDPOINT = 0.5;
|
||||
|
||||
static final int BITMAP_SIZE_SMALL = 512;
|
||||
@@ -380,6 +386,31 @@ public class Utils {
|
||||
return cardImageFileNameBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a card image filename (string) with the ID replaced according to the map if the input is a valid card image filename (string), otherwise null.
|
||||
*
|
||||
* @param fileName e.g. "card_1_front.png"
|
||||
* @param idMap e.g. Map.of(1, 2)
|
||||
* @return String e.g. "card_2_front.png"
|
||||
*/
|
||||
static public String getRenamedCardImageFileName(final String fileName, final Map<Integer, Integer> idMap) {
|
||||
Pattern pattern = Pattern.compile(CARD_IMAGE_FILENAME_REGEX);
|
||||
Matcher matcher = pattern.matcher(fileName);
|
||||
if (matcher.matches()) {
|
||||
StringBuilder cardImageFileNameBuilder = new StringBuilder();
|
||||
cardImageFileNameBuilder.append(matcher.group(1));
|
||||
try {
|
||||
int id = Integer.parseInt(matcher.group(2));
|
||||
cardImageFileNameBuilder.append(idMap.getOrDefault(id, id));
|
||||
} catch (NumberFormatException _e) {
|
||||
return null;
|
||||
}
|
||||
cardImageFileNameBuilder.append(matcher.group(3));
|
||||
return cardImageFileNameBuilder.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static public void saveCardImage(Context context, Bitmap bitmap, String fileName) throws FileNotFoundException {
|
||||
if (bitmap == null) {
|
||||
context.deleteFile(fileName);
|
||||
@@ -481,6 +512,18 @@ public class Utils {
|
||||
return new File(context.getCacheDir() + "/" + name);
|
||||
}
|
||||
|
||||
public static File copyToTempFile(Context context, InputStream input, String name) throws IOException {
|
||||
File file = createTempFile(context, name);
|
||||
try (input; FileOutputStream out = new FileOutputStream(file)) {
|
||||
byte[] buf = new byte[4096];
|
||||
int len;
|
||||
while ((len = input.read(buf)) != -1) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
public static String saveTempImage(Context context, Bitmap in, String name, Bitmap.CompressFormat format) {
|
||||
File image = createTempFile(context, name);
|
||||
try (FileOutputStream out = new FileOutputStream(image)) {
|
||||
@@ -630,4 +673,31 @@ public class Utils {
|
||||
public static int getHeaderColor(Context context, LoyaltyCard loyaltyCard) {
|
||||
return loyaltyCard.headerColor != null ? loyaltyCard.headerColor : LetterBitmap.getDefaultColor(context, loyaltyCard.store);
|
||||
}
|
||||
|
||||
public static String checksum(InputStream input) throws IOException {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
byte[] buf = new byte[4096];
|
||||
int len;
|
||||
while ((len = input.read(buf)) != -1) {
|
||||
md.update(buf, 0, len);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : md.digest()) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (NoSuchAlgorithmException _e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean equals(final Object a, final Object b) {
|
||||
if (a == null && b == null) {
|
||||
return true;
|
||||
} else if (a == null || b == null) {
|
||||
return false;
|
||||
}
|
||||
return a.equals(b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import org.apache.commons.csv.CSVRecord;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -22,12 +24,17 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Currency;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import protect.card_locker.CatimaBarcode;
|
||||
import protect.card_locker.DBHelper;
|
||||
import protect.card_locker.FormatException;
|
||||
import protect.card_locker.Group;
|
||||
import protect.card_locker.ImageLocationType;
|
||||
import protect.card_locker.LoyaltyCard;
|
||||
import protect.card_locker.Utils;
|
||||
import protect.card_locker.ZipUtils;
|
||||
|
||||
@@ -39,24 +46,42 @@ import protect.card_locker.ZipUtils;
|
||||
* A header is expected for the each table showing the names of the columns.
|
||||
*/
|
||||
public class CatimaImporter implements Importer {
|
||||
public void importData(Context context, SQLiteDatabase database, InputStream input, char[] password) throws IOException, FormatException, InterruptedException {
|
||||
InputStream bufferedInputStream = new BufferedInputStream(input);
|
||||
bufferedInputStream.mark(100);
|
||||
public static class ImportedData {
|
||||
public final List<LoyaltyCard> cards;
|
||||
public final List<String> groups;
|
||||
public final List<Map.Entry<Integer, String>> cardGroups;
|
||||
|
||||
ImportedData(final List<LoyaltyCard> cards, final List<String> groups, final List<Map.Entry<Integer, String>> cardGroups) {
|
||||
this.cards = cards;
|
||||
this.groups = groups;
|
||||
this.cardGroups = cardGroups;
|
||||
}
|
||||
}
|
||||
|
||||
public void importData(Context context, SQLiteDatabase database, File inputFile, char[] password) throws IOException, FormatException, InterruptedException {
|
||||
// Pass #1: get hashes and parse CSV
|
||||
InputStream input1 = new FileInputStream(inputFile);
|
||||
InputStream bufferedInputStream1 = new BufferedInputStream(input1);
|
||||
bufferedInputStream1.mark(100);
|
||||
ZipInputStream zipInputStream1 = new ZipInputStream(bufferedInputStream1, password);
|
||||
|
||||
// First, check if this is a zip file
|
||||
ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream, password);
|
||||
|
||||
boolean isZipFile = false;
|
||||
|
||||
LocalFileHeader localFileHeader;
|
||||
while ((localFileHeader = zipInputStream.getNextEntry()) != null) {
|
||||
Map<String, String> imageChecksums = new HashMap<>();
|
||||
ImportedData importedData = null;
|
||||
|
||||
while ((localFileHeader = zipInputStream1.getNextEntry()) != null) {
|
||||
isZipFile = true;
|
||||
|
||||
String fileName = Uri.parse(localFileHeader.getFileName()).getLastPathSegment();
|
||||
if (fileName.equals("catima.csv")) {
|
||||
importCSV(context, database, zipInputStream);
|
||||
importedData = importCSV(zipInputStream1);
|
||||
} else if (fileName.endsWith(".png")) {
|
||||
Utils.saveCardImage(context, ZipUtils.readImage(zipInputStream), fileName);
|
||||
if (!fileName.matches(Utils.CARD_IMAGE_FILENAME_REGEX)) {
|
||||
throw new FormatException("Unexpected PNG file in import: " + fileName);
|
||||
}
|
||||
imageChecksums.put(fileName, Utils.checksum(zipInputStream1));
|
||||
} else {
|
||||
throw new FormatException("Unexpected file in import: " + fileName);
|
||||
}
|
||||
@@ -64,35 +89,110 @@ public class CatimaImporter implements Importer {
|
||||
|
||||
if (!isZipFile) {
|
||||
// This is not a zip file, try importing as bare CSV
|
||||
bufferedInputStream.reset();
|
||||
importCSV(context, database, bufferedInputStream);
|
||||
bufferedInputStream1.reset();
|
||||
importedData = importCSV(bufferedInputStream1);
|
||||
}
|
||||
|
||||
input.close();
|
||||
input1.close();
|
||||
|
||||
if (importedData == null) {
|
||||
throw new FormatException("No imported data");
|
||||
}
|
||||
|
||||
Map<Integer, Integer> idMap = saveAndDeduplicate(context, database, importedData, imageChecksums);
|
||||
|
||||
if (isZipFile) {
|
||||
// Pass #2: save images
|
||||
InputStream input2 = new FileInputStream(inputFile);
|
||||
InputStream bufferedInputStream2 = new BufferedInputStream(input2);
|
||||
ZipInputStream zipInputStream2 = new ZipInputStream(bufferedInputStream2, password);
|
||||
|
||||
while ((localFileHeader = zipInputStream2.getNextEntry()) != null) {
|
||||
String fileName = Uri.parse(localFileHeader.getFileName()).getLastPathSegment();
|
||||
if (fileName.endsWith(".png")) {
|
||||
String newFileName = Utils.getRenamedCardImageFileName(fileName, idMap);
|
||||
Utils.saveCardImage(context, ZipUtils.readImage(zipInputStream2), newFileName);
|
||||
}
|
||||
}
|
||||
|
||||
input2.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void importCSV(Context context, SQLiteDatabase database, InputStream input) throws IOException, FormatException, InterruptedException {
|
||||
public Map<Integer, Integer> saveAndDeduplicate(Context context, SQLiteDatabase database, final ImportedData data, final Map<String, String> imageChecksums) throws IOException {
|
||||
Map<Integer, Integer> idMap = new HashMap<>();
|
||||
Set<String> existingImages = DBHelper.imageFiles(context, database);
|
||||
|
||||
for (LoyaltyCard card : data.cards) {
|
||||
LoyaltyCard existing = DBHelper.getLoyaltyCard(database, card.id);
|
||||
if (existing == null) {
|
||||
DBHelper.insertLoyaltyCard(database, card.id, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType,
|
||||
card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
|
||||
} else if (!isDuplicate(context, existing, card, existingImages, imageChecksums)) {
|
||||
long newId = DBHelper.insertLoyaltyCard(database, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType,
|
||||
card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
|
||||
idMap.put(card.id, (int) newId);
|
||||
}
|
||||
}
|
||||
|
||||
for (String group : data.groups) {
|
||||
DBHelper.insertGroup(database, group);
|
||||
}
|
||||
|
||||
for (Map.Entry<Integer, String> entry : data.cardGroups) {
|
||||
int cardId = idMap.getOrDefault(entry.getKey(), entry.getKey());
|
||||
String groupId = entry.getValue();
|
||||
// For existing & newly imported cards, add the groups from the import to the internal state
|
||||
List<Group> cardGroups = DBHelper.getLoyaltyCardGroups(database, cardId);
|
||||
cardGroups.add(DBHelper.getGroup(database, groupId));
|
||||
DBHelper.setLoyaltyCardGroups(database, cardId, cardGroups);
|
||||
}
|
||||
|
||||
return idMap;
|
||||
}
|
||||
|
||||
public boolean isDuplicate(Context context, final LoyaltyCard existing, final LoyaltyCard card, final Set<String> existingImages, final Map<String, String> imageChecksums) throws IOException {
|
||||
if (!LoyaltyCard.isDuplicate(existing, card)) {
|
||||
return false;
|
||||
}
|
||||
for (ImageLocationType imageLocationType : ImageLocationType.values()) {
|
||||
String name = Utils.getCardImageFileName(existing.id, imageLocationType);
|
||||
boolean exists = existingImages.contains(name);
|
||||
if (exists != imageChecksums.containsKey(name)) {
|
||||
return false;
|
||||
}
|
||||
if (exists) {
|
||||
File file = Utils.retrieveCardImageAsFile(context, name);
|
||||
if (!imageChecksums.get(name).equals(Utils.checksum(new FileInputStream(file)))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public ImportedData importCSV(InputStream input) throws IOException, FormatException, InterruptedException {
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
|
||||
|
||||
int version = parseVersion(bufferedReader);
|
||||
switch (version) {
|
||||
case 1:
|
||||
parseV1(database, bufferedReader);
|
||||
break;
|
||||
return parseV1(bufferedReader);
|
||||
case 2:
|
||||
parseV2(context, database, bufferedReader);
|
||||
break;
|
||||
return parseV2(bufferedReader);
|
||||
default:
|
||||
throw new FormatException(String.format("No code to parse version %s", version));
|
||||
}
|
||||
}
|
||||
|
||||
public void parseV1(SQLiteDatabase database, BufferedReader input) throws IOException, FormatException, InterruptedException {
|
||||
public ImportedData parseV1(BufferedReader input) throws IOException, FormatException, InterruptedException {
|
||||
ImportedData data = new ImportedData(new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
|
||||
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.builder().setHeader().build());
|
||||
|
||||
try {
|
||||
for (CSVRecord record : parser) {
|
||||
importLoyaltyCard(database, record);
|
||||
LoyaltyCard card = importLoyaltyCard(record);
|
||||
data.cards.add(card);
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
@@ -103,9 +203,15 @@ public class CatimaImporter implements Importer {
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw new FormatException("Issue parsing CSV data", e);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public void parseV2(Context context, SQLiteDatabase database, BufferedReader input) throws IOException, FormatException, InterruptedException {
|
||||
public ImportedData parseV2(BufferedReader input) throws IOException, FormatException, InterruptedException {
|
||||
List<LoyaltyCard> cards = new ArrayList<>();
|
||||
List<String> groups = new ArrayList<>();
|
||||
List<Map.Entry<Integer, String>> cardGroups = new ArrayList<>();
|
||||
|
||||
int part = 0;
|
||||
StringBuilder stringPart = new StringBuilder();
|
||||
|
||||
@@ -123,7 +229,7 @@ public class CatimaImporter implements Importer {
|
||||
break;
|
||||
case 1:
|
||||
try {
|
||||
parseV2Groups(database, stringPart.toString());
|
||||
groups = parseV2Groups(stringPart.toString());
|
||||
sectionParsed = true;
|
||||
} catch (FormatException e) {
|
||||
// We may have a multiline field, try again
|
||||
@@ -131,7 +237,7 @@ public class CatimaImporter implements Importer {
|
||||
break;
|
||||
case 2:
|
||||
try {
|
||||
parseV2Cards(context, database, stringPart.toString());
|
||||
cards = parseV2Cards(stringPart.toString());
|
||||
sectionParsed = true;
|
||||
} catch (FormatException e) {
|
||||
// We may have a multiline field, try again
|
||||
@@ -139,7 +245,7 @@ public class CatimaImporter implements Importer {
|
||||
break;
|
||||
case 3:
|
||||
try {
|
||||
parseV2CardGroups(database, stringPart.toString());
|
||||
cardGroups = parseV2CardGroups(stringPart.toString());
|
||||
sectionParsed = true;
|
||||
} catch (FormatException e) {
|
||||
// We may have a multiline field, try again
|
||||
@@ -166,9 +272,11 @@ public class CatimaImporter implements Importer {
|
||||
} catch (FormatException e) {
|
||||
throw new FormatException("Issue parsing CSV data", e);
|
||||
}
|
||||
|
||||
return new ImportedData(cards, groups, cardGroups);
|
||||
}
|
||||
|
||||
public void parseV2Groups(SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException {
|
||||
public List<String> parseV2Groups(String data) throws IOException, FormatException, InterruptedException {
|
||||
// Parse groups
|
||||
final CSVParser groupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.builder().setHeader().build());
|
||||
|
||||
@@ -188,12 +296,15 @@ public class CatimaImporter implements Importer {
|
||||
groupParser.close();
|
||||
}
|
||||
|
||||
List<String> groups = new ArrayList<>();
|
||||
for (CSVRecord record : records) {
|
||||
importGroup(database, record);
|
||||
String group = importGroup(record);
|
||||
groups.add(group);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
public void parseV2Cards(Context context, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException {
|
||||
public List<LoyaltyCard> parseV2Cards(String data) throws IOException, FormatException, InterruptedException {
|
||||
// Parse cards
|
||||
final CSVParser cardParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.builder().setHeader().build());
|
||||
|
||||
@@ -213,12 +324,15 @@ public class CatimaImporter implements Importer {
|
||||
cardParser.close();
|
||||
}
|
||||
|
||||
List<LoyaltyCard> cards = new ArrayList<>();
|
||||
for (CSVRecord record : records) {
|
||||
importLoyaltyCard(database, record);
|
||||
LoyaltyCard card = importLoyaltyCard(record);
|
||||
cards.add(card);
|
||||
}
|
||||
return cards;
|
||||
}
|
||||
|
||||
public void parseV2CardGroups(SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException {
|
||||
public List<Map.Entry<Integer, String>> parseV2CardGroups(String data) throws IOException, FormatException, InterruptedException {
|
||||
// Parse card group mappings
|
||||
final CSVParser cardGroupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.builder().setHeader().build());
|
||||
|
||||
@@ -238,9 +352,12 @@ public class CatimaImporter implements Importer {
|
||||
cardGroupParser.close();
|
||||
}
|
||||
|
||||
List<Map.Entry<Integer, String>> cardGroups = new ArrayList<>();
|
||||
for (CSVRecord record : records) {
|
||||
importCardGroupMapping(database, record);
|
||||
Map.Entry<Integer, String> entry = importCardGroupMapping(record);
|
||||
cardGroups.add(entry);
|
||||
}
|
||||
return cardGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,8 +393,7 @@ public class CatimaImporter implements Importer {
|
||||
* Import a single loyalty card into the database using the given
|
||||
* session.
|
||||
*/
|
||||
private void importLoyaltyCard(SQLiteDatabase database, CSVRecord record)
|
||||
throws FormatException {
|
||||
private LoyaltyCard importLoyaltyCard(CSVRecord record) throws FormatException {
|
||||
int id = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ID, record);
|
||||
|
||||
String store = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.STORE, record, "");
|
||||
@@ -374,28 +490,28 @@ public class CatimaImporter implements Importer {
|
||||
// We catch this exception so we can still import old backups
|
||||
}
|
||||
|
||||
DBHelper.insertLoyaltyCard(database, id, store, note, validFrom, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus, lastUsed, archiveStatus);
|
||||
return new LoyaltyCard(id, store, note, validFrom, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus, lastUsed, DBHelper.DEFAULT_ZOOM_LEVEL, archiveStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a single group into the database using the given
|
||||
* session.
|
||||
*/
|
||||
private void importGroup(SQLiteDatabase database, CSVRecord record) throws FormatException {
|
||||
private String importGroup(CSVRecord record) throws FormatException {
|
||||
String id = CSVHelpers.extractString(DBHelper.LoyaltyCardDbGroups.ID, record, null);
|
||||
|
||||
if (id == null) {
|
||||
throw new FormatException("Group has no ID: " + record);
|
||||
}
|
||||
|
||||
DBHelper.insertGroup(database, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a single card to group mapping into the database using the given
|
||||
* session.
|
||||
*/
|
||||
private void importCardGroupMapping(SQLiteDatabase database, CSVRecord record) throws FormatException {
|
||||
private Map.Entry<Integer, String> importCardGroupMapping(CSVRecord record) throws FormatException {
|
||||
int cardId = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIdsGroups.cardID, record);
|
||||
String groupId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIdsGroups.groupID, record, null);
|
||||
|
||||
@@ -403,8 +519,6 @@ public class CatimaImporter implements Importer {
|
||||
throw new FormatException("Group has no ID: " + record);
|
||||
}
|
||||
|
||||
List<Group> cardGroups = DBHelper.getLoyaltyCardGroups(database, cardId);
|
||||
cardGroups.add(DBHelper.getGroup(database, groupId));
|
||||
DBHelper.setLoyaltyCardGroups(database, cardId, cardGroups);
|
||||
return Map.entry(cardId, groupId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,21 @@ import org.apache.commons.csv.CSVParser;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import protect.card_locker.CatimaBarcode;
|
||||
import protect.card_locker.DBHelper;
|
||||
import protect.card_locker.FormatException;
|
||||
import protect.card_locker.LoyaltyCard;
|
||||
import protect.card_locker.Utils;
|
||||
|
||||
/**
|
||||
@@ -31,7 +36,16 @@ import protect.card_locker.Utils;
|
||||
* A header is expected for the each table showing the names of the columns.
|
||||
*/
|
||||
public class FidmeImporter implements Importer {
|
||||
public void importData(Context context, SQLiteDatabase database, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
||||
public static class ImportedData {
|
||||
public final List<LoyaltyCard> cards;
|
||||
|
||||
ImportedData(final List<LoyaltyCard> cards) {
|
||||
this.cards = cards;
|
||||
}
|
||||
}
|
||||
|
||||
public void importData(Context context, SQLiteDatabase database, File inputFile, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
||||
InputStream input = new FileInputStream(inputFile);
|
||||
// We actually retrieve a .zip file
|
||||
ZipInputStream zipInputStream = new ZipInputStream(input, password);
|
||||
|
||||
@@ -54,10 +68,14 @@ public class FidmeImporter implements Importer {
|
||||
}
|
||||
|
||||
final CSVParser fidmeParser = new CSVParser(new StringReader(loyaltyCards.toString()), CSVFormat.RFC4180.builder().setDelimiter(';').setHeader().build());
|
||||
ImportedData importedData = new ImportedData(new ArrayList<>());
|
||||
|
||||
try {
|
||||
for (CSVRecord record : fidmeParser) {
|
||||
importLoyaltyCard(context, database, record);
|
||||
LoyaltyCard card = importLoyaltyCard(context, record);
|
||||
if (card != null) {
|
||||
importedData.cards.add(card);
|
||||
}
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
@@ -70,14 +88,16 @@ public class FidmeImporter implements Importer {
|
||||
}
|
||||
|
||||
zipInputStream.close();
|
||||
input.close();
|
||||
|
||||
saveAndDeduplicate(database, importedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a single loyalty card into the database using the given
|
||||
* session.
|
||||
*/
|
||||
private void importLoyaltyCard(Context context, SQLiteDatabase database, CSVRecord record)
|
||||
throws FormatException {
|
||||
private LoyaltyCard importLoyaltyCard(Context context, CSVRecord record) throws FormatException {
|
||||
// A loyalty card export from Fidme contains the following fields:
|
||||
// Retailer (store name)
|
||||
// Program (program name)
|
||||
@@ -113,7 +133,7 @@ public class FidmeImporter implements Importer {
|
||||
// Fidme deletes the card id if a card is expired
|
||||
// Because Catima considers the card id a required field, we ignore these expired cards
|
||||
// https://github.com/CatimaLoyalty/Android/issues/1005
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sadly, Fidme exports don't contain the card type
|
||||
@@ -128,6 +148,17 @@ public class FidmeImporter implements Importer {
|
||||
|
||||
// TODO: Front and back image
|
||||
|
||||
DBHelper.insertLoyaltyCard(database, store, note, null, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, starStatus, null,archiveStatus);
|
||||
// use -1 for the ID, it will be ignored when inserting the card into the DB
|
||||
return new LoyaltyCard(-1, store, note, null, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, starStatus, Utils.getUnixTime(), DBHelper.DEFAULT_ZOOM_LEVEL, archiveStatus);
|
||||
}
|
||||
|
||||
public void saveAndDeduplicate(SQLiteDatabase database, final ImportedData data) {
|
||||
// This format does not have IDs that can cause conflicts
|
||||
// Proper deduplication for all formats will be implemented later
|
||||
for (LoyaltyCard card : data.cards) {
|
||||
// Do not use card.id which is set to -1
|
||||
DBHelper.insertLoyaltyCard(database, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType,
|
||||
card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.ParseException;
|
||||
|
||||
import protect.card_locker.FormatException;
|
||||
@@ -23,5 +23,5 @@ public interface Importer {
|
||||
* @throws IOException
|
||||
* @throws FormatException
|
||||
*/
|
||||
void importData(Context context, SQLiteDatabase database, InputStream input, char[] password) throws IOException, FormatException, InterruptedException, JSONException, ParseException;
|
||||
void importData(Context context, SQLiteDatabase database, File inputFile, char[] password) throws IOException, FormatException, InterruptedException, JSONException, ParseException;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,15 @@ import android.util.Log;
|
||||
|
||||
import net.lingala.zip4j.exception.ZipException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import protect.card_locker.Utils;
|
||||
|
||||
public class MultiFormatImporter {
|
||||
private static final String TAG = "Catima";
|
||||
private static final String TEMP_ZIP_NAME = MultiFormatImporter.class.getSimpleName() + ".zip";
|
||||
|
||||
/**
|
||||
* Attempts to import data from the input stream of the
|
||||
@@ -42,23 +47,33 @@ public class MultiFormatImporter {
|
||||
|
||||
String error = null;
|
||||
if (importer != null) {
|
||||
database.beginTransaction();
|
||||
File inputFile;
|
||||
try {
|
||||
importer.importData(context, database, input, password);
|
||||
database.setTransactionSuccessful();
|
||||
return new ImportExportResult(ImportExportResultType.Success);
|
||||
} catch (ZipException e) {
|
||||
if (e.getType().equals(ZipException.Type.WRONG_PASSWORD)) {
|
||||
return new ImportExportResult(ImportExportResultType.BadPassword);
|
||||
} else {
|
||||
inputFile = Utils.copyToTempFile(context, input, TEMP_ZIP_NAME);
|
||||
database.beginTransaction();
|
||||
try {
|
||||
importer.importData(context, database, inputFile, password);
|
||||
database.setTransactionSuccessful();
|
||||
return new ImportExportResult(ImportExportResultType.Success);
|
||||
} catch (ZipException e) {
|
||||
if (e.getType().equals(ZipException.Type.WRONG_PASSWORD)) {
|
||||
return new ImportExportResult(ImportExportResultType.BadPassword);
|
||||
} else {
|
||||
Log.e(TAG, "Failed to import data", e);
|
||||
error = e.toString();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to import data", e);
|
||||
error = e.toString();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
if (!inputFile.delete()) {
|
||||
Log.w(TAG, "Failed to delete temporary ZIP file (should not be a problem) " + inputFile);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to import data", e);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to copy ZIP file", e);
|
||||
error = e.toString();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
}
|
||||
} else {
|
||||
error = "Unsupported data format imported: " + format.name();
|
||||
|
||||
@@ -13,21 +13,29 @@ 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 org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import protect.card_locker.CatimaBarcode;
|
||||
import protect.card_locker.DBHelper;
|
||||
import protect.card_locker.FormatException;
|
||||
import protect.card_locker.ImageLocationType;
|
||||
import protect.card_locker.LoyaltyCard;
|
||||
import protect.card_locker.R;
|
||||
import protect.card_locker.Utils;
|
||||
import protect.card_locker.ZipUtils;
|
||||
@@ -40,11 +48,30 @@ import protect.card_locker.ZipUtils;
|
||||
* A header is expected for the each table showing the names of the columns.
|
||||
*/
|
||||
public class StocardImporter implements Importer {
|
||||
public static class ZIPData {
|
||||
public final HashMap<String, HashMap<String, Object>> loyaltyCardHashMap;
|
||||
public final HashMap<String, HashMap<String, Object>> providers;
|
||||
|
||||
ZIPData(final HashMap<String, HashMap<String, Object>> loyaltyCardHashMap, final HashMap<String, HashMap<String, Object>> providers) {
|
||||
this.loyaltyCardHashMap = loyaltyCardHashMap;
|
||||
this.providers = providers;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ImportedData {
|
||||
public final List<LoyaltyCard> cards;
|
||||
public final Map<Integer, Map<ImageLocationType, Bitmap>> images;
|
||||
|
||||
ImportedData(final List<LoyaltyCard> cards, final Map<Integer, Map<ImageLocationType, Bitmap>> images) {
|
||||
this.cards = cards;
|
||||
this.images = images;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
public void importData(Context context, SQLiteDatabase database, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
||||
HashMap<String, HashMap<String, Object>> loyaltyCardHashMap = new HashMap<>();
|
||||
HashMap<String, HashMap<String, Object>> providers = new HashMap<>();
|
||||
public void importData(Context context, SQLiteDatabase database, File inputFile, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
||||
ZIPData zipData = new ZIPData(new HashMap<>(), new HashMap<>());
|
||||
|
||||
final CSVParser parser = new CSVParser(new InputStreamReader(context.getResources().openRawResource(R.raw.stocard_stores), StandardCharsets.UTF_8), CSVFormat.RFC4180.builder().setHeader().build());
|
||||
|
||||
@@ -54,7 +81,7 @@ public class StocardImporter implements Importer {
|
||||
recordData.put("name", record.get("name"));
|
||||
recordData.put("barcodeFormat", record.get("barcodeFormat"));
|
||||
|
||||
providers.put(record.get("_id"), recordData);
|
||||
zipData.providers.put(record.get("_id"), recordData);
|
||||
}
|
||||
|
||||
parser.close();
|
||||
@@ -62,7 +89,23 @@ public class StocardImporter implements Importer {
|
||||
throw new FormatException("Issue parsing CSV data", e);
|
||||
}
|
||||
|
||||
InputStream input = new FileInputStream(inputFile);
|
||||
ZipInputStream zipInputStream = new ZipInputStream(input, password);
|
||||
zipData = importZIP(zipInputStream, zipData);
|
||||
zipInputStream.close();
|
||||
input.close();
|
||||
|
||||
if (zipData.loyaltyCardHashMap.keySet().size() == 0) {
|
||||
throw new FormatException("Couldn't find any loyalty cards in this Stocard export.");
|
||||
}
|
||||
|
||||
ImportedData importedData = importLoyaltyCardHashMap(context, zipData);
|
||||
saveAndDeduplicate(context, database, importedData);
|
||||
}
|
||||
|
||||
public ZIPData importZIP(ZipInputStream zipInputStream, final ZIPData zipData) throws IOException, JSONException {
|
||||
HashMap<String, HashMap<String, Object>> loyaltyCardHashMap = zipData.loyaltyCardHashMap;
|
||||
HashMap<String, HashMap<String, Object>> providers = zipData.providers;
|
||||
|
||||
String[] providersFileName = null;
|
||||
String[] customProvidersBaseName = null;
|
||||
@@ -142,6 +185,15 @@ public class StocardImporter implements Importer {
|
||||
jsonObject.getString("input_id")
|
||||
);
|
||||
|
||||
if (jsonObject.has("input_provider_name")) {
|
||||
loyaltyCardHashMap = appendToHashMap(
|
||||
loyaltyCardHashMap,
|
||||
cardName,
|
||||
"store",
|
||||
jsonObject.getString("input_provider_name")
|
||||
);
|
||||
}
|
||||
|
||||
// Provider ID can be either custom or not, extract whatever version is relevant
|
||||
String customProviderPrefix = "/users/" + nameParts[1] + "/loyalty-card-custom-providers/";
|
||||
String providerId = jsonObject
|
||||
@@ -176,14 +228,28 @@ public class StocardImporter implements Importer {
|
||||
ZipUtils.readJSON(zipInputStream)
|
||||
.getString("content")
|
||||
);
|
||||
} else if (fileName.endsWith("/images/front.png")) {
|
||||
} else if (fileName.endsWith("usage-statistics/content.json")) {
|
||||
JSONArray usages = ZipUtils.readJSON(zipInputStream).getJSONArray("usages");
|
||||
if (usages.length() > 0) {
|
||||
JSONObject lastUsedObject = usages.getJSONObject(usages.length() - 1);
|
||||
String lastUsedString = lastUsedObject.getJSONObject("time").getString("value");
|
||||
long timeStamp = Instant.parse(lastUsedString).getEpochSecond();
|
||||
|
||||
loyaltyCardHashMap = appendToHashMap(
|
||||
loyaltyCardHashMap,
|
||||
cardName,
|
||||
"lastUsed",
|
||||
timeStamp
|
||||
);
|
||||
}
|
||||
} else if (fileName.endsWith("/images/front.png") || fileName.endsWith("/images/front/front.jpg")) {
|
||||
loyaltyCardHashMap = appendToHashMap(
|
||||
loyaltyCardHashMap,
|
||||
cardName,
|
||||
"frontImage",
|
||||
ZipUtils.readImage(zipInputStream)
|
||||
);
|
||||
} else if (fileName.endsWith("/images/back.png")) {
|
||||
} else if (fileName.endsWith("/images/back.png") || fileName.endsWith("/images/back/back.jpg")) {
|
||||
loyaltyCardHashMap = appendToHashMap(
|
||||
loyaltyCardHashMap,
|
||||
cardName,
|
||||
@@ -194,11 +260,14 @@ public class StocardImporter implements Importer {
|
||||
}
|
||||
}
|
||||
|
||||
if (loyaltyCardHashMap.keySet().size() == 0) {
|
||||
throw new FormatException("Couldn't find any loyalty cards in this Stocard export.");
|
||||
}
|
||||
return new ZIPData(loyaltyCardHashMap, providers);
|
||||
}
|
||||
|
||||
for (HashMap<String, Object> loyaltyCardData : loyaltyCardHashMap.values()) {
|
||||
public ImportedData importLoyaltyCardHashMap(Context context, final ZIPData zipData) {
|
||||
ImportedData importedData = new ImportedData(new ArrayList<>(), new HashMap<>());
|
||||
int tempID = 0;
|
||||
|
||||
for (Map<String, Object> loyaltyCardData : zipData.loyaltyCardHashMap.values()) {
|
||||
String providerId = (String) loyaltyCardData.get("_providerId");
|
||||
|
||||
if (providerId == null) {
|
||||
@@ -206,9 +275,16 @@ public class StocardImporter implements Importer {
|
||||
continue;
|
||||
}
|
||||
|
||||
HashMap<String, Object> providerData = providers.get(providerId);
|
||||
HashMap<String, Object> providerData = zipData.providers.get(providerId);
|
||||
|
||||
// Read store from card, if not available (old export), fall back to providerData
|
||||
String store;
|
||||
if (loyaltyCardData.containsKey("store")) {
|
||||
store = (String) loyaltyCardData.get("store");
|
||||
} else {
|
||||
store = providerData != null ? providerData.get("name").toString() : providerId;
|
||||
}
|
||||
|
||||
String store = providerData != null ? providerData.get("name").toString() : providerId;
|
||||
String note = (String) Utils.mapGetOrDefault(loyaltyCardData, "note", "");
|
||||
String cardId = (String) loyaltyCardData.get("cardId");
|
||||
String barcodeTypeString = (String) Utils.mapGetOrDefault(loyaltyCardData, "barcodeType", providerData != null ? providerData.get("barcodeFormat") : null);
|
||||
@@ -230,21 +306,46 @@ public class StocardImporter implements Importer {
|
||||
headerColor = Utils.getHeaderColorFromImage(cardIcon, headerColor);
|
||||
}
|
||||
|
||||
long loyaltyCardInternalId = DBHelper.insertLoyaltyCard(database, store, note, null, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, 0, null,0);
|
||||
long lastUsed;
|
||||
if (loyaltyCardData.containsKey("lastUsed")) {
|
||||
lastUsed = (long) loyaltyCardData.get("lastUsed");
|
||||
} else {
|
||||
lastUsed = Utils.getUnixTime();
|
||||
}
|
||||
|
||||
LoyaltyCard card = new LoyaltyCard(tempID, store, note, null, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, 0, lastUsed, DBHelper.DEFAULT_ZOOM_LEVEL, 0);
|
||||
importedData.cards.add(card);
|
||||
|
||||
Map<ImageLocationType, Bitmap> images = new HashMap<>();
|
||||
|
||||
if (cardIcon != null) {
|
||||
Utils.saveCardImage(context, cardIcon, (int) loyaltyCardInternalId, ImageLocationType.icon);
|
||||
images.put(ImageLocationType.icon, cardIcon);
|
||||
}
|
||||
|
||||
if (loyaltyCardData.containsKey("frontImage")) {
|
||||
Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("frontImage"), (int) loyaltyCardInternalId, ImageLocationType.front);
|
||||
images.put(ImageLocationType.front, (Bitmap) loyaltyCardData.get("frontImage"));
|
||||
}
|
||||
if (loyaltyCardData.containsKey("backImage")) {
|
||||
Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("backImage"), (int) loyaltyCardInternalId, ImageLocationType.back);
|
||||
images.put(ImageLocationType.back, (Bitmap) loyaltyCardData.get("backImage"));
|
||||
}
|
||||
|
||||
importedData.images.put(tempID, images);
|
||||
tempID++;
|
||||
}
|
||||
|
||||
zipInputStream.close();
|
||||
return importedData;
|
||||
}
|
||||
|
||||
public void saveAndDeduplicate(Context context, SQLiteDatabase database, final ImportedData data) throws IOException {
|
||||
// This format does not have IDs that can cause conflicts
|
||||
// Proper deduplication for all formats will be implemented later
|
||||
for (LoyaltyCard card : data.cards) {
|
||||
// card.id is temporary and only used to index the images Map
|
||||
long id = DBHelper.insertLoyaltyCard(database, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType,
|
||||
card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
|
||||
for (Map.Entry<ImageLocationType, Bitmap> entry : data.images.get(card.id).entrySet()) {
|
||||
Utils.saveCardImage(context, entry.getValue(), (int) id, entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean startsWith(String[] full, String[] start, int minExtraLength) {
|
||||
@@ -272,4 +373,4 @@ public class StocardImporter implements Importer {
|
||||
|
||||
return loyaltyCardHashMap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -19,13 +21,16 @@ import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Currency;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import protect.card_locker.CatimaBarcode;
|
||||
import protect.card_locker.DBHelper;
|
||||
import protect.card_locker.FormatException;
|
||||
import protect.card_locker.LoyaltyCard;
|
||||
import protect.card_locker.Utils;
|
||||
|
||||
/**
|
||||
@@ -36,7 +41,16 @@ import protect.card_locker.Utils;
|
||||
* A header is expected for the each table showing the names of the columns.
|
||||
*/
|
||||
public class VoucherVaultImporter implements Importer {
|
||||
public void importData(Context context, SQLiteDatabase database, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
||||
public static class ImportedData {
|
||||
public final List<LoyaltyCard> cards;
|
||||
|
||||
ImportedData(final List<LoyaltyCard> cards) {
|
||||
this.cards = cards;
|
||||
}
|
||||
}
|
||||
|
||||
public void importData(Context context, SQLiteDatabase database, File inputFile, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
||||
InputStream input = new FileInputStream(inputFile);
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -46,6 +60,16 @@ public class VoucherVaultImporter implements Importer {
|
||||
}
|
||||
JSONArray jsonArray = new JSONArray(sb.toString());
|
||||
|
||||
bufferedReader.close();
|
||||
input.close();
|
||||
|
||||
ImportedData importedData = importJSON(jsonArray);
|
||||
saveAndDeduplicate(database, importedData);
|
||||
}
|
||||
|
||||
public ImportedData importJSON(JSONArray jsonArray) throws FormatException, JSONException, ParseException {
|
||||
ImportedData importedData = new ImportedData(new ArrayList<>());
|
||||
|
||||
// See https://github.com/tim-smart/vouchervault/issues/4#issuecomment-788226503 for more info
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
JSONObject jsonCard = jsonArray.getJSONObject(i);
|
||||
@@ -126,9 +150,20 @@ public class VoucherVaultImporter implements Importer {
|
||||
throw new FormatException("Unknown colour type found: " + colorFromJSON);
|
||||
}
|
||||
|
||||
DBHelper.insertLoyaltyCard(database, store, "", null, expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0, Utils.getUnixTime(),0);
|
||||
// use -1 for the ID, it will be ignored when inserting the card into the DB
|
||||
importedData.cards.add(new LoyaltyCard(-1, store, "", null, expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0, Utils.getUnixTime(), DBHelper.DEFAULT_ZOOM_LEVEL, 0));
|
||||
}
|
||||
|
||||
bufferedReader.close();
|
||||
return importedData;
|
||||
}
|
||||
|
||||
public void saveAndDeduplicate(SQLiteDatabase database, final ImportedData data) {
|
||||
// This format does not have IDs that can cause conflicts
|
||||
// Proper deduplication for all formats will be implemented later
|
||||
for (LoyaltyCard card : data.cards) {
|
||||
// Do not use card.id which is set to -1
|
||||
DBHelper.insertLoyaltyCard(database, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType,
|
||||
card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
|
||||
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="M7,14l5,-5 5,5z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/>
|
||||
</vector>
|
||||
@@ -1,5 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
|
||||
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="M7,10l5,5 5,-5z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
|
||||
</vector>
|
||||
@@ -43,7 +43,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:icon="@drawable/ic_baseline_arrow_drop_up_24"
|
||||
app:icon="@drawable/ic_baseline_keyboard_arrow_up_24"
|
||||
app:iconGravity="textStart"
|
||||
app:tint="?attr/colorOnPrimary"
|
||||
android:contentDescription="@string/moveUp"/>
|
||||
@@ -54,7 +54,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:icon="@drawable/ic_baseline_arrow_drop_down_24"
|
||||
app:icon="@drawable/ic_baseline_keyboard_arrow_down_24"
|
||||
app:iconGravity="textStart"
|
||||
app:tint="?attr/colorOnPrimary"
|
||||
android:contentDescription="@string/moveDown"/>
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="?attr/materialCardViewElevatedStyle"
|
||||
android:id="@+id/row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp">
|
||||
android:layout_margin="5dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -137,7 +138,6 @@
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:textAppearance="?attr/textAppearanceHeadlineSmall"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
app:layout_constraintTop_toBottomOf="@+id/icon_layout"
|
||||
@@ -154,7 +154,6 @@
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:textAppearance="?attr/textAppearanceBody2"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
app:layout_constraintTop_toBottomOf="@+id/store"
|
||||
@@ -187,7 +186,6 @@
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:textAppearance="?attr/textAppearanceBody2"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:drawableLeftCompat="@drawable/ic_baseline_payments_24"
|
||||
android:drawablePadding="4dp"
|
||||
android:visibility="gone"
|
||||
@@ -207,7 +205,6 @@
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:textAppearance="?attr/textAppearanceBody2"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:drawableLeftCompat="@drawable/ic_valid_from_24dp"
|
||||
android:drawablePadding="4dp"
|
||||
android:visibility="gone"
|
||||
@@ -227,7 +224,6 @@
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:textAppearance="?attr/textAppearanceBody2"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:drawableLeftCompat="@drawable/ic_baseline_access_time_24"
|
||||
android:drawablePadding="4dp"
|
||||
android:visibility="gone"
|
||||
|
||||
@@ -7,9 +7,10 @@ Heimen Stoffels
|
||||
Oğuz Ersen
|
||||
Katharine Chui
|
||||
mondstern
|
||||
SlavekB
|
||||
StoyanDimitrov
|
||||
IllusiveMan196
|
||||
SlavekB
|
||||
FC Stegerman
|
||||
Altonss
|
||||
Michael Moroni
|
||||
Gediminas Murauskas
|
||||
@@ -18,26 +19,25 @@ Joel A
|
||||
laralem
|
||||
Taco
|
||||
pfaffenrodt
|
||||
FC Stegerman
|
||||
gallegonovato
|
||||
Nyatsuki
|
||||
HudobniVolk
|
||||
Samantaz Fox
|
||||
Eric
|
||||
arno-github
|
||||
Ankit Tiwari
|
||||
Sergio Paredes
|
||||
Clxff H3r4ld0
|
||||
Aayush Gupta
|
||||
huuhaa
|
||||
Quentin PAGÈS
|
||||
Eric
|
||||
Balázs Meskó
|
||||
huuhaa
|
||||
Projjal Moitra
|
||||
Quentin PAGÈS
|
||||
Giovanni Donisi
|
||||
Alexander Ivanov
|
||||
arshbeerSingh
|
||||
Denis Shilin
|
||||
Freddo espresso
|
||||
Giovanni Donisi
|
||||
Silvério Santos
|
||||
Miha Frangež
|
||||
Eryk Michalak
|
||||
@@ -45,6 +45,7 @@ Arnis Jaundžeikars
|
||||
Dan
|
||||
sr093906
|
||||
mdvhimself
|
||||
Jiri Grönroos
|
||||
Katarzyna
|
||||
echo r"0xX4H" | rev
|
||||
Magnitudee
|
||||
@@ -57,7 +58,7 @@ enolp
|
||||
Evgeniy Khramov
|
||||
Jane Kong
|
||||
Jean Mareilles
|
||||
Jiri Grönroos
|
||||
José Rebelo
|
||||
K. Herbert
|
||||
Lisa A.
|
||||
Mawuena M. KODZO A.
|
||||
@@ -79,6 +80,8 @@ Astrohops1
|
||||
BMN
|
||||
balaraz
|
||||
BootVirtual
|
||||
Bottan Hermawan
|
||||
zChiip
|
||||
Clonewayx
|
||||
D. Domig
|
||||
Diego
|
||||
@@ -89,7 +92,6 @@ francescbassas
|
||||
Jason Li
|
||||
Jean-Luc Tibaux
|
||||
Jesse Davids
|
||||
José Rebelo
|
||||
Kis Dominik
|
||||
Lukas Grassauer
|
||||
Luna Jernberg
|
||||
@@ -154,6 +156,7 @@ danieluhrinyi
|
||||
Daniele Tricoli
|
||||
Kasina Dheeraj
|
||||
Donno
|
||||
Erik Spjelkavik
|
||||
Flav
|
||||
Franciszek Stefan
|
||||
Gael Caraballo
|
||||
@@ -171,6 +174,7 @@ Jean-Baptiste
|
||||
Kung-chih
|
||||
Karvjorm
|
||||
polar
|
||||
Kamborio
|
||||
krkk
|
||||
Laura Ferraz
|
||||
Lucas da Costa
|
||||
@@ -182,6 +186,7 @@ Mateo Gomez
|
||||
Mattia
|
||||
Md. Al-Amin
|
||||
Michael Gangolf
|
||||
Milan Šalka
|
||||
3DN1M
|
||||
Minecraft boom
|
||||
Mobashir Raihan
|
||||
@@ -197,6 +202,7 @@ vandman
|
||||
Piotr Strebski
|
||||
Piotr Zet
|
||||
Poorva Patidar
|
||||
Quang Trung
|
||||
Quang Nguyen
|
||||
Ratnesh
|
||||
Reza
|
||||
@@ -214,6 +220,7 @@ Subhradeep Bera
|
||||
Swayam Khare
|
||||
SziaTomi
|
||||
Mehedi Hasan
|
||||
Tim Trek
|
||||
Titas Pažereckas
|
||||
atakujonc
|
||||
tkraljevic
|
||||
@@ -221,6 +228,7 @@ Tony C
|
||||
Vancha March
|
||||
tyap-lyap-ivprod
|
||||
Waldemar Stoczkowski
|
||||
Wiktor Kwapisiewicz
|
||||
Yevgeny M
|
||||
Yusril A
|
||||
Ziad OUALHADJ
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# stocard_stores.csv
|
||||
|
||||
stocard_stores.csv was created by extracting /data/data/de.stocard/de.stocard.stocard/databases/stores on a rooted devices and running the following command over it:
|
||||
|
||||
```
|
||||
sqlite3 -header -csv sync_db "select id,content from synced_resources where collection = '/loyalty-card-providers/'" > stocard_providers.csv
|
||||
while IFS= read -r line; do
|
||||
if [ "$line" = "id,content" ]; then
|
||||
echo "_id,name,barcodeFormat" > stocard_stores.csv
|
||||
else
|
||||
id="$(echo "$line" | cut -d ',' -f1)"
|
||||
name="$(echo "$line" | cut -d ',' -f2- | sed 's/""/"/g' | sed 's/^"//g' | sed 's/"$//g' | jq -r .name)"
|
||||
barcodeFormat="$(echo "$line" | cut -d ',' -f2- | sed 's/""/"/g' | sed 's/^"//g' | sed 's/"$//g' | jq -r .default_barcode_format)"
|
||||
echo "$id,\"$name\",$barcodeFormat" >> stocard_stores.csv
|
||||
fi
|
||||
done < stocard_providers.csv
|
||||
```
|
||||
|
||||
Only used for data portability reasons (ensuring importing works). Do NOT copy this anywhere else or use it for any purpose other than ensuring we can import a GDPR-provided export. We want to make sure this stays under fair use.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -68,7 +68,7 @@
|
||||
<string name="sort_by_expiry">মেয়াদ শেষ করে সাজান</string>
|
||||
<string name="reverse">...উল্টো ক্রমে</string>
|
||||
<string name="sort_by">ক্রমানুসার</string>
|
||||
<string name="noCardExistsError">কার্ডটি পাওয়া যায়নি</string>
|
||||
<string name="noCardExistsError">কার্ডটি খুঁজে পাওয়া গেল না</string>
|
||||
<string name="noStoreError">স্টোরেজ ত্রুটি নেই</string>
|
||||
<string name="card_ids_copied">আইডি কপি করা হয়েছে</string>
|
||||
<string name="noCardsMessage">কোন কার্ড বার্তা নেই</string>
|
||||
@@ -124,22 +124,22 @@
|
||||
<string name="settings_portrait_orientation">প্রতিকৃতি</string>
|
||||
<string name="barcodeImageDescriptionWithType">ছবি <xliff:g>%s</xliff:g> বারকোড</string>
|
||||
<string name="exportName">রপ্তানি</string>
|
||||
<string name="failedParsingImportUriError">আমদানির ইউআরআই বোঝা যাচ্ছে না</string>
|
||||
<string name="failedParsingImportUriError">আমদানির URI-টি বোঝা যাচ্ছে না</string>
|
||||
<string name="importExport">আমদানি/রপ্তানি</string>
|
||||
<string name="cardShortcut">কার্ড শর্টকাট</string>
|
||||
<string name="exportFailed">বার করা যাচ্ছে না</string>
|
||||
<string name="exportFailed">রপ্তানি করা যাচ্ছে না</string>
|
||||
<string name="copy_to_clipboard_toast">আইডি ক্লিপবোর্ডে নকল করা হল</string>
|
||||
<string name="noCardIdError">কোনো আইডি দওয়া হয়নি</string>
|
||||
<string name="importExportHelp">নিজের তথ্য অন্য কোথাও সংরক্ষণ করে রাখলে পরে সেটা অন্য ফোনে আবার নিয়ে নাওয়া যাই।</string>
|
||||
<string name="importFailed">আনা যাচ্ছে না</string>
|
||||
<string name="importExportHelp">নিজের ডেটা অন্য কোথাও সংরক্ষণ করে রাখলে পরে সেটা অন্য ডিভাইসে সরিয়ে নিতে পারবেন।</string>
|
||||
<string name="importFailed">আমদানি করা গেল না</string>
|
||||
<string name="noGiftCardsGroup">কিছু কার্ড বানান আর এই গ্রুপে স্থির করুন।</string>
|
||||
<string name="scanCardBarcode">বারকোড স্ক্যান করুন</string>
|
||||
<string name="importSuccessfulTitle">আনা শেষ</string>
|
||||
<string name="importFailedTitle">আনা ব্যর্থ</string>
|
||||
<string name="exportSuccessfulTitle">বার করা শেষ</string>
|
||||
<string name="exportFailedTitle">বার করা ব্যর্থ</string>
|
||||
<string name="importing">আনা হচ্ছে…</string>
|
||||
<string name="exporting">বার করা হচ্ছে…</string>
|
||||
<string name="importSuccessfulTitle">আমদানি শেষ</string>
|
||||
<string name="importFailedTitle">আমদানি ব্যর্থ</string>
|
||||
<string name="exportSuccessfulTitle">রপ্তানি শেষ</string>
|
||||
<string name="exportFailedTitle">রপ্তানি ব্যর্থ</string>
|
||||
<string name="importing">আমদানি করা হচ্ছে…</string>
|
||||
<string name="exporting">রপ্তানি করা হচ্ছে…</string>
|
||||
<string name="storageReadPermissionRequired">এই কাজটির জন্য ফোনের স্টোরেজ দেখার অনুমতি লাগবে…</string>
|
||||
<string name="exportOptionExplanation">ডেটাটি আপনার পছন্দের জায়গায় রাখা হবে।</string>
|
||||
<string name="importOptionFilesystemTitle">স্টোরেজ থেকে আমদানি করুন</string>
|
||||
@@ -272,4 +272,4 @@
|
||||
<string name="icon_header_click_text">দীর্ঘক্ষন টাচ করে থাম্বনেইল এডিট করবেন</string>
|
||||
<string name="show_balance">ব্যালান্স দেখান</string>
|
||||
<string name="donate">দান করুন</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -270,10 +270,17 @@
|
||||
<string name="donate">Spenden</string>
|
||||
<string name="show_note">Notiz anzeigen</string>
|
||||
<string name="show_balance">Betrag anzeigen</string>
|
||||
<string name="show_validity">Gültigkeitsdauer anziegen</string>
|
||||
<string name="show_validity">Gültigkeitsdauer anzeigen</string>
|
||||
<string name="show_name_below_image_thumbnail">Namen unter Bildvorschau anzeigen</string>
|
||||
<string name="settings_allow_content_provider_read_title">Anderen Anwendungen den Zugriff auf meine Daten gestatten</string>
|
||||
<string name="permissionReadCardsLabel">Catima-Karten lesen</string>
|
||||
<string name="permissionReadCardsDescription">Lesen Sie Ihre Karten mit allen Details, einschließlich Notizen und Bildern</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Anwendungen müssen weiterhin eine Genehmigung beantragen, um Zugriff zu erhalten</string>
|
||||
</resources>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Erforderlich für das Funktionieren einiger Scanner</string>
|
||||
<string name="settings_keep_screen_on_summary">Deaktiviert die Bildschirmzeitüberschreitung beim Anzeigen einer Karte</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Deaktiviert die Bildschirmsperre während der Anzeige einer Karte</string>
|
||||
<string name="settings_oled_dark_summary">Reduziert den Batterieverbrauch bei OLED-Displays</string>
|
||||
<string name="settings_category_title_cards">Karten</string>
|
||||
<string name="settings_category_title_privacy">Datenschutz</string>
|
||||
<string name="settings_category_title_general">Allgemein</string>
|
||||
</resources>
|
||||
@@ -279,4 +279,15 @@
|
||||
<string name="show_note">Mostrar la nota</string>
|
||||
<string name="show_validity">Mostrar la validez</string>
|
||||
<string name="show_balance">Mostrar el saldo</string>
|
||||
</resources>
|
||||
<string name="permissionReadCardsLabel">Leer Tarjetas Catima</string>
|
||||
<string name="permissionReadCardsDescription">Lee tus tarjetas y todos sus detalles, incluidas notas e imágenes</string>
|
||||
<string name="settings_allow_content_provider_read_title">Permite a otras aplicaciones acceder a mis datos</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Necesario para que funcione en algunos escáneres</string>
|
||||
<string name="settings_keep_screen_on_summary">Deshabilita el tiempo de espera de la pantalla mientras se ve una tarjeta</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Las aplicaciones todavía tendrán que solicitar permiso para conseguir acceso</string>
|
||||
<string name="settings_oled_dark_summary">Reduce el uso de batería en pantallas OLED</string>
|
||||
<string name="settings_category_title_cards">Tarjetas</string>
|
||||
<string name="settings_category_title_general">General</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Deshabilita el bloqueo de pantalla mientras se ve una tarjeta</string>
|
||||
<string name="settings_category_title_privacy">Privacidad</string>
|
||||
</resources>
|
||||
@@ -188,7 +188,7 @@
|
||||
<item quantity="other"><xliff:g>%s</xliff:g> pistettä</item>
|
||||
</plurals>
|
||||
<string name="settings_oled_dark">Musta tausta tummalle teemalle</string>
|
||||
<string name="setIcon">Aseta kuvake</string>
|
||||
<string name="setIcon">Aseta pikkukuva</string>
|
||||
<string name="sort_by_name">Nimi</string>
|
||||
<string name="sort_by_most_recently_used">Viimeksi käytetyt</string>
|
||||
<string name="sort_by_expiry">Viimeinen voimassaoloaika</string>
|
||||
@@ -259,4 +259,28 @@
|
||||
<string name="anyDate">Mikä tahansa päivämäärä</string>
|
||||
<string name="chooseValidFromDate">Valitse kelvollinen päivämäärä</string>
|
||||
<string name="validFromSentence">Kelvollinen alkaen: <xliff:g>%s</xliff:g></string>
|
||||
</resources>
|
||||
<string name="donate">Lahjoita</string>
|
||||
<string name="permissionReadCardsLabel">Lue Catima-kortteja</string>
|
||||
<string name="permissionReadCardsDescription">Lue korttisi ja kaikki niiden tiedot, mukaan lukien huomautukset ja kuvat</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Sovellusten tulee silti pyytää lupaa saadakseen pääsyn</string>
|
||||
<string name="settings_category_title_privacy">Yksityisyys</string>
|
||||
<string name="height">Korkeus:</string>
|
||||
<string name="switchToFrontImage">Vaihda etukuvaan</string>
|
||||
<string name="switchToBarcode">Vaihda viivakoodiin</string>
|
||||
<string name="openFrontImageInGalleryApp">Avaa etukuva galleriasovelluksessa</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Välttämätön, jotta jotkin skannerit toimivat</string>
|
||||
<string name="settings_keep_screen_on_summary">Poistaa näytön aikakatkaisun korttia katsellessa</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Poistaa näyttölukituksen käytöstä korttia katsellessa</string>
|
||||
<string name="settings_allow_content_provider_read_title">Salli muiden sovellusten käyttää omaa dataa</string>
|
||||
<string name="settings_oled_dark_summary">Vähentää akun käyttöä OLED-näytöillä</string>
|
||||
<string name="switchToBackImage">Vaihda takakuvaan</string>
|
||||
<string name="openBackImageInGalleryApp">Avaa takakuva galleriasovelluksessa</string>
|
||||
<string name="setBarcodeHeight">Aseta viivakoodin korkeus</string>
|
||||
<string name="icon_header_click_text">Pitkä painallus pikkukuvan muokkaamiseksi</string>
|
||||
<string name="show_name_below_image_thumbnail">Näytä nimi pikkukuvan alapuolella</string>
|
||||
<string name="show_note">Näytä huomautus</string>
|
||||
<string name="show_balance">Näytä saldo</string>
|
||||
<string name="show_validity">Näytä kelpoisuus</string>
|
||||
<string name="settings_category_title_cards">Kortit</string>
|
||||
<string name="settings_category_title_general">Yleiset</string>
|
||||
</resources>
|
||||
@@ -283,4 +283,11 @@
|
||||
<string name="permissionReadCardsDescription">Lisez vos cartes et tous ses détails, y compris les notes et les images</string>
|
||||
<string name="settings_allow_content_provider_read_title">Autoriser d\'autres applications à accéder à mes données</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Les applications devront toujours demander une autorisation pour obtenir l\'accès</string>
|
||||
</resources>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Nécessaire au fonctionnement de certains scanneurs</string>
|
||||
<string name="settings_keep_screen_on_summary">Désactive la temporisation de l\'écran lors de la visualisation d\'une carte</string>
|
||||
<string name="settings_oled_dark_summary">Réduit l\'utilisation de la batterie sur les écrans OLED</string>
|
||||
<string name="settings_category_title_cards">Cartes</string>
|
||||
<string name="settings_category_title_general">Généraux</string>
|
||||
<string name="settings_category_title_privacy">Confidentialité</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Désactive le verrouillage de l\'écran pendant la visualisation d\'une carte</string>
|
||||
</resources>
|
||||
@@ -265,4 +265,15 @@
|
||||
<string name="icon_header_click_text">Tekan lama untuk mengedit thumbnail</string>
|
||||
<string name="show_name_below_image_thumbnail">Tampilkan nama di bawah thumbnail gambar</string>
|
||||
<string name="show_note">Tampilkan catatan</string>
|
||||
</resources>
|
||||
<string name="permissionReadCardsLabel">Baca Kartu Catima</string>
|
||||
<string name="permissionReadCardsDescription">Baca kartu Anda dan semua detailnya, termasuk catatan dan gambar</string>
|
||||
<string name="settings_allow_content_provider_read_title">Izinkan aplikasi lain mengakses data saya</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Aplikasi masih harus meminta izin untuk diberikan akses</string>
|
||||
<string name="settings_keep_screen_on_summary">Menonaktifkan batas waktu layar saat melihat kartu</string>
|
||||
<string name="settings_oled_dark_summary">Mengurangi penggunaan baterai pada layar OLED</string>
|
||||
<string name="settings_category_title_general">Umum</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Diperlukan agar beberapa pemindai dapat berfungsi</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Menonaktifkan kunci layar saat melihat kartu</string>
|
||||
<string name="settings_category_title_cards">Kartu</string>
|
||||
<string name="settings_category_title_privacy">Privasi</string>
|
||||
</resources>
|
||||
@@ -283,4 +283,11 @@
|
||||
<string name="settings_allow_content_provider_read_summary">Le applicazioni dovranno comunque richiedere l\'autorizzazione per ottenere l\'accesso</string>
|
||||
<string name="settings_allow_content_provider_read_title">Consenti ad altre applicazioni di accedere ai miei dati</string>
|
||||
<string name="permissionReadCardsDescription">Leggi le tue carte e tutti i suoi dettagli, comprese le note e le immagini</string>
|
||||
</resources>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Necessario per il funzionamento di alcuni scanner</string>
|
||||
<string name="settings_keep_screen_on_summary">Disattiva il timeout dello schermo durante la visualizzazione di una carta</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Disattiva il blocco dello schermo durante la visualizzazione di una carta</string>
|
||||
<string name="settings_oled_dark_summary">Riduce il consumo della batteria sui display OLED</string>
|
||||
<string name="settings_category_title_cards">Carte</string>
|
||||
<string name="settings_category_title_general">Generali</string>
|
||||
<string name="settings_category_title_privacy">Privacy</string>
|
||||
</resources>
|
||||
2
app/src/main/res/values-lzh/strings.xml
Normal file
2
app/src/main/res/values-lzh/strings.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?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 importer kort fra «⋮»-menyen</string>
|
||||
<string name="noGiftCards">Klikk på «+» (pluss)-knappen for å legge til eller importere kort fra «⋮»-menyen.</string>
|
||||
<string name="storeName">Navn</string>
|
||||
<string name="note">Merknad</string>
|
||||
<string name="cardId">Kort-ID</string>
|
||||
@@ -62,7 +62,7 @@
|
||||
<string name="failedParsingImportUriError">Kunne ikke tolke importerings-URI</string>
|
||||
<string name="share">Del</string>
|
||||
<string name="barcodeNoBarcode">Det er ingen strekkode</string>
|
||||
<string name="noMatchingGiftCards">Resultatløst. Prøv å endre søket.</string>
|
||||
<string name="noMatchingGiftCards">Ingen resultater. Prøv å endre søket.</string>
|
||||
<string name="action_search">Søk</string>
|
||||
<string name="starImage">Favorittstjerne</string>
|
||||
<string name="unstar">Fjern fra favoritter</string>
|
||||
@@ -187,7 +187,7 @@
|
||||
<string name="license">Lisens</string>
|
||||
<string name="version_history">Versjonshistorikk</string>
|
||||
<string name="importCatimaMessage">Velg din <i>catima.zip</i>-eksport fra Catima å importere.
|
||||
\nOpprett den fra «Importer/Eksporter»-menyen i en annen Catima-app ved å trykke på «Eksporter» der først</string>
|
||||
\nOpprett den fra «Importer/Eksporter»-menyen i en annen Catima-app ved å trykke på «Eksporter» der først.</string>
|
||||
<string name="source_repository">Kildekode</string>
|
||||
<string name="on_github">på GitHub</string>
|
||||
<string name="and_data_usage">og bruk av data</string>
|
||||
@@ -264,7 +264,7 @@
|
||||
<string name="switchToBackImage">Bytt til baksidebildet</string>
|
||||
<string name="switchToBarcode">Bytt til strekkode</string>
|
||||
<string name="icon_header_click_text">Lang-trykk for å redigere miniatyrbilde</string>
|
||||
<string name="show_name_below_image_thumbnail">Vis navn under bilde-miniatyrbilde</string>
|
||||
<string name="show_name_below_image_thumbnail">Vis navn under miniatyrbilde</string>
|
||||
<string name="show_balance">Vis saldo</string>
|
||||
<string name="show_note">Vis notat</string>
|
||||
<string name="show_validity">Vis gyldighet</string>
|
||||
@@ -283,4 +283,4 @@
|
||||
<string name="settings_category_title_general">Generelt</string>
|
||||
<string name="settings_category_title_privacy">Personvern</string>
|
||||
<string name="settings_oled_dark_summary">Reduserer batteribruk for OLED-skjermer</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -282,7 +282,19 @@
|
||||
<string name="donate">Darowizna</string>
|
||||
<string name="openBackImageInGalleryApp">Otwórz obraz z powrotem w aplikacji galerii</string>
|
||||
<string name="icon_header_click_text">Przytrzymaj, aby edytować miniaturę</string>
|
||||
<string name="show_name_below_image_thumbnail">Pokaż imię pod miniaturką zdjęcia</string>
|
||||
<string name="show_name_below_image_thumbnail">Pokaż nazwę pod miniaturką zdjęcia</string>
|
||||
<string name="show_balance">Pokaż balans</string>
|
||||
<string name="show_validity">Pokaż ważność</string>
|
||||
</resources>
|
||||
<string name="show_note">Pokaż notatkę</string>
|
||||
<string name="permissionReadCardsLabel">Odczytaj Karty Catima</string>
|
||||
<string name="permissionReadCardsDescription">Odczytaj swoje karty i ich szczegóły, włącznie z notatkami i obrazkami</string>
|
||||
<string name="settings_allow_content_provider_read_title">Pozwól innym aplikacjom na dostęp do moich danych</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Potrzebne aby niektóre skanery działały</string>
|
||||
<string name="settings_keep_screen_on_summary">Wyłącza wygaszanie ekranu kiedy wyświetlana jest karta</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Wyłącza blokadę ekranu kiedy wyświetlana jest karta</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Aplikacje będą wymagały pozwolenia aby otrzymać dostęp do danych</string>
|
||||
<string name="settings_oled_dark_summary">Zmniejsza zużycie baterii na wyświetlaczach OLED</string>
|
||||
<string name="settings_category_title_cards">Karty</string>
|
||||
<string name="settings_category_title_general">Ogólne</string>
|
||||
<string name="settings_category_title_privacy">Prywatność</string>
|
||||
</resources>
|
||||
@@ -8,12 +8,12 @@
|
||||
<string name="storeName">Nome</string>
|
||||
<string name="note">Nota</string>
|
||||
<string name="barcodeType">Tipo de código de barras</string>
|
||||
<string name="barcodeNoBarcode">Sem código de barras</string>
|
||||
<string name="barcodeNoBarcode">Não há código de barras</string>
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="save">Guardar</string>
|
||||
<string name="edit">Editar</string>
|
||||
<string name="noGiftCards">Clique no botão + para adicionar um cartão ou importe-o no menu ⋮.</string>
|
||||
<string name="noBarcode">Sem código de barras</string>
|
||||
<string name="noBarcode">sem código de barras</string>
|
||||
<string name="unstar">Retirar dos favoritos</string>
|
||||
<string name="importOptionFilesystemButton">Do sistema de ficheiros</string>
|
||||
<string name="importOptionApplicationTitle">Usar outra aplicação</string>
|
||||
@@ -208,7 +208,7 @@
|
||||
<string name="editGroup">A editar grupo: <xliff:g>%s</xliff:g></string>
|
||||
<string name="noGiftCardsGroup">Crie alguns cartões e atribua-os depois ao grupo aqui.</string>
|
||||
<string name="selectColor">Selecionar cor</string>
|
||||
<string name="setIcon">Definir ícone</string>
|
||||
<string name="setIcon">Definir miniatura</string>
|
||||
<string name="action_show_details">Mostrar detalhes</string>
|
||||
<string name="action_hide_details">Ocultar detalhes</string>
|
||||
<string name="shortcutSelectCard">Selecione um cartão</string>
|
||||
@@ -274,4 +274,20 @@
|
||||
<string name="openBackImageInGalleryApp">Abrir a imagem traseira na aplicação da galeria</string>
|
||||
<string name="setBarcodeHeight">Definir altura do código de barras</string>
|
||||
<string name="donate">Doar</string>
|
||||
</resources>
|
||||
<string name="show_validity">Mostrar validade</string>
|
||||
<string name="show_balance">Mostrar saldo</string>
|
||||
<string name="permissionReadCardsLabel">Ler Cartas Catima</string>
|
||||
<string name="permissionReadCardsDescription">Leia seus cartões e todos os seus detalhes, incluindo notas e imagens</string>
|
||||
<string name="show_note">Mostrar nota</string>
|
||||
<string name="show_name_below_image_thumbnail">Mostrar nome abaixo da miniatura do ícone</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Desativa o bloqueio de tela ao visualizar um cartão</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Necessário para alguns scanners funcionarem</string>
|
||||
<string name="settings_keep_screen_on_summary">Desativa o tempo limite da tela ao visualizar um cartão</string>
|
||||
<string name="settings_allow_content_provider_read_title">Permitir que outros aplicativos acessem meus dados</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Os aplicativos ainda terão que solicitar permissão para receber acesso</string>
|
||||
<string name="settings_oled_dark_summary">Reduz o uso da bateria em telas OLED</string>
|
||||
<string name="icon_header_click_text">Pressione e segure para editar o icone</string>
|
||||
<string name="settings_category_title_cards">cartões</string>
|
||||
<string name="settings_category_title_general">geral</string>
|
||||
<string name="settings_category_title_privacy">Privacidade</string>
|
||||
</resources>
|
||||
@@ -213,8 +213,8 @@
|
||||
<string name="noGiftCardsGroup">Создайте несколько карт, а затем распределите их по группам здесь.</string>
|
||||
<string name="setIcon">Выбор миниатюры</string>
|
||||
<string name="selectColor">Выбрать цвет</string>
|
||||
<string name="action_hide_details">Скрыть детали</string>
|
||||
<string name="action_show_details">Показать детали</string>
|
||||
<string name="action_hide_details">Скрытие подробностей</string>
|
||||
<string name="action_show_details">Отображение подробностей</string>
|
||||
<string name="translate_platform">на Weblate</string>
|
||||
<string name="shortcutSelectCard">Выбор карты</string>
|
||||
<string name="options">Параметры</string>
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
<string name="yes">Áno</string>
|
||||
<string name="importStocard">Import z aplikácie Stocard</string>
|
||||
<string name="selectColor">Vybrať farbu</string>
|
||||
<string name="setIcon">Nastaviť ikonu</string>
|
||||
<string name="setIcon">Nastavenie miniatúry</string>
|
||||
<string name="settings_catima_theme">Catima</string>
|
||||
<string name="settings_pink_theme">Ružová</string>
|
||||
<string name="settings_magenta_theme">Purpurová</string>
|
||||
@@ -266,4 +266,28 @@
|
||||
<string name="noUnarchivedCardsMessage">Nie sú žiadne karty vrátené z archívu</string>
|
||||
<string name="newBalanceSentence">Nový zostatok: <xliff:g>%s</xliff:g></string>
|
||||
<string name="failedLaunchingPhotoPicker">Nepodarilo sa nájsť podporovanú aplikáciu galérie</string>
|
||||
</resources>
|
||||
<string name="show_note">Zobraziť poznámku</string>
|
||||
<string name="icon_header_click_text">Dlhým stlačením upravíte miniatúru</string>
|
||||
<string name="settings_category_title_general">Všeobecné</string>
|
||||
<string name="settings_category_title_privacy">Súkromie</string>
|
||||
<string name="settings_keep_screen_on_summary">Zakázanie časového limitu obrazovky počas prezerania karty</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Potrebné pre fungovanie niektorých skenerov</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Aplikácie budú musieť stále žiadať o povolenie, aby im bol udelený prístup</string>
|
||||
<string name="openBackImageInGalleryApp">Otvorenie spätného obrázka v aplikácii galéria</string>
|
||||
<string name="openFrontImageInGalleryApp">Otvorenie predného obrázka v aplikácii galéria</string>
|
||||
<string name="setBarcodeHeight">Nastavenie výšky čiarového kódu</string>
|
||||
<string name="show_balance">Ukážte rovnováhu</string>
|
||||
<string name="show_name_below_image_thumbnail">Zobraziť názov pod miniatúrou obrázka</string>
|
||||
<string name="show_validity">Zobraziť platnosť</string>
|
||||
<string name="permissionReadCardsLabel">Načítať Catima karty</string>
|
||||
<string name="permissionReadCardsDescription">Čítanie kariet a všetkých ich detailov vrátane poznámok a obrázkov</string>
|
||||
<string name="switchToBackImage">Prepnutie na zadný obrázok</string>
|
||||
<string name="height">Výška:</string>
|
||||
<string name="switchToFrontImage">Prepnutie na predný obrázok</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Zakázanie uzamknutia obrazovky počas prezerania karty</string>
|
||||
<string name="settings_allow_content_provider_read_title">Povolenie prístupu k mojim údajom iným aplikáciám</string>
|
||||
<string name="settings_oled_dark_summary">Znižuje spotrebu batérie na displejoch OLED</string>
|
||||
<string name="switchToBarcode">Prepnutie na čiarový kód</string>
|
||||
<string name="settings_category_title_cards">Karty</string>
|
||||
<string name="donate">Darujte</string>
|
||||
</resources>
|
||||
@@ -286,4 +286,15 @@
|
||||
<string name="show_note">Показати примітку</string>
|
||||
<string name="show_validity">Показати термін дії</string>
|
||||
<string name="show_balance">Показати баланс</string>
|
||||
</resources>
|
||||
<string name="permissionReadCardsLabel">Читати карти Catima</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Додатки все ще муситимуть запитувати дозвіл на отримання доступу</string>
|
||||
<string name="permissionReadCardsDescription">Читати всі ваші карти та їх деталі, в тому числі нотатки та зображення</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Необхідно для роботи деяких сканерів</string>
|
||||
<string name="settings_keep_screen_on_summary">Вимикає тайм-аут екрана під час перегляду картки</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Вимикає блокування екрана під час перегляду картки</string>
|
||||
<string name="settings_allow_content_provider_read_title">Дозволити іншим програмам доступ до моїх даних</string>
|
||||
<string name="settings_oled_dark_summary">Зменшує використання батареї на екранах з OLED</string>
|
||||
<string name="settings_category_title_cards">Картки</string>
|
||||
<string name="settings_category_title_general">Загальні</string>
|
||||
<string name="settings_category_title_privacy">Конфіденційність</string>
|
||||
</resources>
|
||||
@@ -196,6 +196,35 @@ public class ImportExportTest {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
private void checkLoyaltyCardsAndDuplicates(int numCards) {
|
||||
Cursor cursor = DBHelper.getLoyaltyCardCursor(mDatabase);
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
|
||||
|
||||
// ID goes up for duplicates (b/c the cursor orders by store), down for originals
|
||||
int index = card.id > numCards ? card.id - numCards : numCards - card.id + 1;
|
||||
// balance is doubled for modified originals
|
||||
int balance = card.id > numCards ? index : index * 2;
|
||||
|
||||
String expectedStore = String.format("store, \"%4d", index);
|
||||
String expectedNote = String.format("note, \"%4d", index);
|
||||
|
||||
assertEquals(expectedStore, card.store);
|
||||
assertEquals(expectedNote, card.note);
|
||||
assertEquals(null, card.validFrom);
|
||||
assertEquals(null, card.expiry);
|
||||
assertEquals(new BigDecimal(String.valueOf(balance)), card.balance);
|
||||
assertEquals(null, card.balanceType);
|
||||
assertEquals(BARCODE_DATA, card.cardId);
|
||||
assertEquals(null, card.barcodeId);
|
||||
assertEquals(BARCODE_TYPE.format(), card.barcodeType.format());
|
||||
assertEquals(Integer.valueOf(index), card.headerColor);
|
||||
assertEquals(0, card.starStatus);
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that all of the cards follow the pattern
|
||||
* specified in addLoyaltyCardsSomeStarred(), and are in sequential order
|
||||
@@ -477,6 +506,40 @@ public class ImportExportTest {
|
||||
TestHelpers.getEmptyDb(activity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void importExistingCardsAfterModification() throws IOException {
|
||||
final int NUM_CARDS = 10;
|
||||
|
||||
TestHelpers.addLoyaltyCards(mDatabase, NUM_CARDS);
|
||||
|
||||
ByteArrayOutputStream outData = new ByteArrayOutputStream();
|
||||
OutputStreamWriter outStream = new OutputStreamWriter(outData);
|
||||
|
||||
// Export into CSV data
|
||||
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), mDatabase, outData, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResultType.Success, result.resultType());
|
||||
outStream.close();
|
||||
|
||||
// Modify existing cards
|
||||
for (int index = 1; index <= NUM_CARDS; index++) {
|
||||
int id = NUM_CARDS - index + 1;
|
||||
DBHelper.updateLoyaltyCardBalance(mDatabase, id, new BigDecimal(String.valueOf(index * 2)));
|
||||
}
|
||||
|
||||
ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray());
|
||||
|
||||
// Import the CSV data on top of the existing database
|
||||
result = MultiFormatImporter.importData(activity.getApplicationContext(), mDatabase, inData, DataFormat.Catima, null);
|
||||
assertEquals(ImportExportResultType.Success, result.resultType());
|
||||
|
||||
assertEquals(NUM_CARDS * 2, DBHelper.getLoyaltyCardCount(mDatabase));
|
||||
|
||||
checkLoyaltyCardsAndDuplicates(NUM_CARDS);
|
||||
|
||||
// Clear the database for the next format under test
|
||||
TestHelpers.getEmptyDb(activity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void corruptedImportNothingSaved() {
|
||||
final int NUM_CARDS = 10;
|
||||
@@ -755,8 +818,9 @@ public class ImportExportTest {
|
||||
@Test
|
||||
public void exportImportV2Zip() throws FileNotFoundException {
|
||||
// Prepare images
|
||||
Bitmap bitmap1 = new LetterBitmap(activity.getApplicationContext(), "1", "1", 12, 64, 64, Color.BLACK, Color.YELLOW).getLetterTile();
|
||||
Bitmap bitmap2 = new LetterBitmap(activity.getApplicationContext(), "2", "2", 12, 64, 64, Color.GREEN, Color.WHITE).getLetterTile();
|
||||
// NB: we can't use LetterBitmap as robolectric doesn't support Canvas enough
|
||||
Bitmap bitmap1 = BitmapFactory.decodeStream(getClass().getResourceAsStream("stocard-front.jpg"));
|
||||
Bitmap bitmap2 = BitmapFactory.decodeStream(getClass().getResourceAsStream("stocard-back.jpg"));
|
||||
|
||||
// Set up cards and groups
|
||||
HashMap<Integer, LoyaltyCard> loyaltyCardHashMap = new HashMap<>();
|
||||
@@ -1061,10 +1125,6 @@ public class ImportExportTest {
|
||||
|
||||
@Test
|
||||
public void importStocard() {
|
||||
// FIXME: The provided stocard.zip is a very old export (8 July 2021) manually edited to
|
||||
// look more like the Stocard files provided by users for #1242. It is not an up-to-date
|
||||
// export and the test is possibly unreliable. This should be replaced by an up-to-date
|
||||
// export.
|
||||
InputStream inputStream = getClass().getResourceAsStream("stocard.zip");
|
||||
|
||||
// Import the Stocard data
|
||||
@@ -1074,7 +1134,7 @@ public class ImportExportTest {
|
||||
|
||||
inputStream = getClass().getResourceAsStream("stocard.zip");
|
||||
|
||||
result = MultiFormatImporter.importData(activity.getApplicationContext(), mDatabase, inputStream, DataFormat.Stocard, "da811b40a4dac56f0cbb2d99b21bbb9a".toCharArray());
|
||||
result = MultiFormatImporter.importData(activity.getApplicationContext(), mDatabase, inputStream, DataFormat.Stocard, "sE0p0RiFDteqhlD4adwWpwjvmI0r0CFOTfyzRae4vEsgNe3NKL".toCharArray());
|
||||
assertEquals(ImportExportResultType.Success, result.resultType());
|
||||
assertEquals(3, DBHelper.getLoyaltyCardCount(mDatabase));
|
||||
|
||||
@@ -1090,6 +1150,7 @@ public class ImportExportTest {
|
||||
assertEquals(null, card.barcodeId);
|
||||
assertEquals(BarcodeFormat.EAN_13, card.barcodeType.format());
|
||||
assertEquals(0, card.starStatus);
|
||||
assertEquals(1625600883, card.lastUsed);
|
||||
|
||||
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 1, ImageLocationType.front));
|
||||
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 1, ImageLocationType.back));
|
||||
@@ -1107,6 +1168,7 @@ public class ImportExportTest {
|
||||
assertEquals(null, card.barcodeId);
|
||||
assertEquals(BarcodeFormat.EAN_13, card.barcodeType.format());
|
||||
assertEquals(0, card.starStatus);
|
||||
assertEquals(1625690099, card.lastUsed);
|
||||
|
||||
assertTrue(BitmapFactory.decodeStream(getClass().getResourceAsStream("stocard-front.jpg")).sameAs(Utils.retrieveCardImage(activity.getApplicationContext(), 2, ImageLocationType.front)));
|
||||
assertTrue(BitmapFactory.decodeStream(getClass().getResourceAsStream("stocard-back.jpg")).sameAs(Utils.retrieveCardImage(activity.getApplicationContext(), 2, ImageLocationType.back)));
|
||||
@@ -1124,6 +1186,7 @@ public class ImportExportTest {
|
||||
assertEquals(null, card.barcodeId);
|
||||
assertEquals(BarcodeFormat.RSS_EXPANDED, card.barcodeType.format());
|
||||
assertEquals(0, card.starStatus);
|
||||
assertEquals(1625600120, card.lastUsed);
|
||||
|
||||
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 3, ImageLocationType.front));
|
||||
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 3, ImageLocationType.back));
|
||||
|
||||
Binary file not shown.
5
docs/STOCARD.md
Normal file
5
docs/STOCARD.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Stocard Importer
|
||||
|
||||
The `app/src/main/res/raw/stocard_stores.csv` CSV file used by the Stocard importer was created using the `.scripts/dump_stocard_stores.py` script.
|
||||
|
||||
Only used for data portability reasons (ensuring importing works). Do NOT copy this anywhere else or use it for any purpose other than ensuring we can import a GDPR-provided export. We want to make sure this stays under fair use.
|
||||
1
fastlane/metadata/android/cs-CZ/changelogs/128.txt
Normal file
1
fastlane/metadata/android/cs-CZ/changelogs/128.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Oprava občasné havárie
|
||||
2
fastlane/metadata/android/cs-CZ/changelogs/129.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/129.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Vylepšení importu Catima (oprava chybějících karet při importu)
|
||||
- Oprava havárie při otáčení obrazovky během nastavení data platnosti od/vypršení
|
||||
1
fastlane/metadata/android/en-US/changelogs/128.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/128.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Fix rare crash
|
||||
3
fastlane/metadata/android/en-US/changelogs/129.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/129.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- Improved Catima importer (fixes cards missing when importing)
|
||||
- Fix crash when rotating screen while setting valid from/expiry date
|
||||
- Minor UI tweaks
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 87 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
1
fastlane/metadata/android/es-ES/changelogs/109.txt
Normal file
1
fastlane/metadata/android/es-ES/changelogs/109.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Corrección de color de texto incorrecto en el botón "No hay código de barras"
|
||||
5
fastlane/metadata/android/es-ES/changelogs/11.txt
Normal file
5
fastlane/metadata/android/es-ES/changelogs/11.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
- Cuando se edita un ID de tarjeta, pre-rellenar el ID existente al empezar. (pull #94 (https://github.com/brarcher/loyalty-card-locker/pull/94))
|
||||
- Limitar el ancho de los códigos de barras generados para reducir el uso de memoria y los errores de memoria agotada. (pull #103 (https://github.com/brarcher/loyalty-card-locker/pull/103))
|
||||
- Al editar una tarjeta, cambiar que el botón "Introducir Tarjeta" diga "Editar Tarjeta" si ya existe un ID de tarjeta. (pull #104 (https://github.com/brarcher/loyalty-card-locker/pull/104))
|
||||
-Cambiar la combinación de colores para ser más tenue y compatible con el icono de la aplicación, y cambiar la distribución al ver una tarjeta por una más limpia. (pull #107 (https://github.com/brarcher/loyalty-card-locker/pull/107))
|
||||
-Añadir un asistente de inicio que se ejecute en el primer uso de la aplicación. (pull #108 (https://github.com/brarcher/loyalty-card-locker/pull/108))
|
||||
7
fastlane/metadata/android/es-ES/changelogs/111.txt
Normal file
7
fastlane/metadata/android/es-ES/changelogs/111.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
- Soporte para idioma árabe
|
||||
- Mostrar el número de tarjetas archivadas en la vista general de grupo
|
||||
- Corrección de errores en el análisis de saldos (las cartas no se podían guardar en árabe y otros idiomas con números no occidentales)
|
||||
- Corrección cuando un tema personalizado no se aplica correctamente a las pantallas principales
|
||||
- Mejoras en la pantalla de selección de tarjetas
|
||||
- Corrección del fallo al salir de la vista de tarjeta en diseños RTL para tarjetas con caducidad o saldo
|
||||
- Corrección del fallo de la flecha para volver en la vista de tarjeta que apunta en la dirección incorrecta en diseños RTL
|
||||
1
fastlane/metadata/android/es-ES/changelogs/112.txt
Normal file
1
fastlane/metadata/android/es-ES/changelogs/112.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Hacer más visible la posibilidad de crear una cabecera personalizada
|
||||
3
fastlane/metadata/android/es-ES/changelogs/113.txt
Normal file
3
fastlane/metadata/android/es-ES/changelogs/113.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- Añadir los botones de anterior y siguiente a la vista de la tarjeta de fidelización
|
||||
- Corrección de color principal en el botón de editar
|
||||
- Reemplazar el icono de guardado de disquete por una marca de confirmación
|
||||
3
fastlane/metadata/android/es-ES/changelogs/114.txt
Normal file
3
fastlane/metadata/android/es-ES/changelogs/114.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- Añadir icono monocromático para Android 13
|
||||
- Mejora de la pantalla en la primera ejecución
|
||||
- Correcciones en la importación de Fidme
|
||||
4
fastlane/metadata/android/es-ES/changelogs/115.txt
Normal file
4
fastlane/metadata/android/es-ES/changelogs/115.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
- Abrir imagen en la galería con una pulsación larga
|
||||
- Aplicar estilo Material a los modales
|
||||
- Soporte para crear una tarjeta compartiendo una imagen en Catima
|
||||
- Añadir el botón de gastos rápidos a la pantalla de la tarjeta
|
||||
2
fastlane/metadata/android/es-ES/changelogs/116.txt
Normal file
2
fastlane/metadata/android/es-ES/changelogs/116.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Corrección de mensaje que no permitía el separador , en los gastos rápidos
|
||||
- Soporte para cargar una imagen desde el gestor de archivos
|
||||
2
fastlane/metadata/android/es-ES/changelogs/117.txt
Normal file
2
fastlane/metadata/android/es-ES/changelogs/117.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Eliminados permisos innecesarios
|
||||
- Objetivo Android 13
|
||||
2
fastlane/metadata/android/es-ES/changelogs/118.txt
Normal file
2
fastlane/metadata/android/es-ES/changelogs/118.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Soporte para establecer el inicio de la validez de la tarjeta
|
||||
- Corrección de importación Stocard (cambio en el formato de exportación de Stocard)
|
||||
1
fastlane/metadata/android/es-ES/changelogs/119.txt
Normal file
1
fastlane/metadata/android/es-ES/changelogs/119.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Uso de colores de Material You en más dispositivos (actualización de la biblioteca de Google)
|
||||
1
fastlane/metadata/android/es-ES/changelogs/12.txt
Normal file
1
fastlane/metadata/android/es-ES/changelogs/12.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Prevenir un fallo al rotar la pantalla en la primera ejecución del asistente.
|
||||
3
fastlane/metadata/android/es-ES/changelogs/120.txt
Normal file
3
fastlane/metadata/android/es-ES/changelogs/120.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- Rediseño completo de la pantalla principal y de vista de tarjeta de fidelidad
|
||||
- Diseño Material You para la pantalla de ajustes
|
||||
- Corrección de error al usar "Toma una foto" con la cámara de la aplicación deshabilitada
|
||||
1
fastlane/metadata/android/es-ES/changelogs/121.txt
Normal file
1
fastlane/metadata/android/es-ES/changelogs/121.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Actualizar bibliotecas usadas
|
||||
2
fastlane/metadata/android/es-ES/changelogs/123.txt
Normal file
2
fastlane/metadata/android/es-ES/changelogs/123.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Pequeñas mejoras de UI
|
||||
- Corrección del nuevo diseño no disponible en dispositivos con pantallas cuadradas
|
||||
1
fastlane/metadata/android/es-ES/changelogs/124.txt
Normal file
1
fastlane/metadata/android/es-ES/changelogs/124.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Soporte para seleccionar exactamente que detalles ver en la vista general de tarjeta
|
||||
1
fastlane/metadata/android/es-ES/changelogs/126.txt
Normal file
1
fastlane/metadata/android/es-ES/changelogs/126.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Correcciones varias de RTL
|
||||
4
fastlane/metadata/android/es-ES/changelogs/127.txt
Normal file
4
fastlane/metadata/android/es-ES/changelogs/127.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
- Mejoras en el renderizado de los códigos de barras
|
||||
- Interoperabilidad básica con aplicaciones externas (Android 6.0+)
|
||||
- Pantalla de ajustes reorganizada
|
||||
- Corrección al importar desde algunos navegadores que añaden una / al final de la URL al compartir
|
||||
1
fastlane/metadata/android/es-ES/changelogs/128.txt
Normal file
1
fastlane/metadata/android/es-ES/changelogs/128.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Correción de error inusual
|
||||
2
fastlane/metadata/android/es-ES/changelogs/129.txt
Normal file
2
fastlane/metadata/android/es-ES/changelogs/129.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Mejorada la importación de Catima (corrige la tarjetas que faltan al importar)
|
||||
- Corrección del error al rotar la pantalla mientras se establece las fechas de validez desde/hasta
|
||||
3
fastlane/metadata/android/es-ES/changelogs/2.txt
Normal file
3
fastlane/metadata/android/es-ES/changelogs/2.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- Traducciones en italiano
|
||||
- Soporte para todos los tipos de códigos de barras 1D. (Originalmente sólo tenian soporte los productos con códigos de barras 1D)
|
||||
- Añadir permiso obligatorio para la cámara, que inicialmente faltaba.
|
||||
1
fastlane/metadata/android/es-ES/changelogs/20.txt
Normal file
1
fastlane/metadata/android/es-ES/changelogs/20.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Solución alternativa al fallo durante la instalación en algunas versiones de Android (como Android 5 o menor), (pull #184 (https://github.com/brarcher/loyalty-card-locker/pull/184))
|
||||
2
fastlane/metadata/android/es-ES/changelogs/21.txt
Normal file
2
fastlane/metadata/android/es-ES/changelogs/21.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Mejorada la distribución en la lista de tarjetas. (pull #188 (https://github.com/brarcher/loyalty-card-locker/pull/188))
|
||||
- Mejorada la distribución al ver una tarjeta. (pull #190 (https://github.com/brarcher/loyalty-card-locker/pull/190))
|
||||
1
fastlane/metadata/android/es-ES/changelogs/22.txt
Normal file
1
fastlane/metadata/android/es-ES/changelogs/22.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Cambios al mostrar una nota en la vista de tarjeta, permitir que el ID de tarjeta ocupe varias líneas, y mostrar el nomber de la tienda. (pull #197 (https://github.com/brarcher/loyalty-card-locker/pull/197))
|
||||
1
fastlane/metadata/android/es-ES/changelogs/33.txt
Normal file
1
fastlane/metadata/android/es-ES/changelogs/33.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Actualizar y añadir traducciones
|
||||
1
fastlane/metadata/android/es-ES/changelogs/34.txt
Normal file
1
fastlane/metadata/android/es-ES/changelogs/34.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Actualizar traducciones en ruso
|
||||
2
fastlane/metadata/android/es-ES/changelogs/35.txt
Normal file
2
fastlane/metadata/android/es-ES/changelogs/35.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Habilitar las copias de seguridad de la aplicación
|
||||
- Actualizar las traducciones en francés y esloveno
|
||||
1
fastlane/metadata/android/hu-HU/changelogs/124.txt
Normal file
1
fastlane/metadata/android/hu-HU/changelogs/124.txt
Normal file
@@ -0,0 +1 @@
|
||||
- A kártyaáttekintőben megjelenítendő pontos részletek kiválasztása
|
||||
@@ -1,3 +1,4 @@
|
||||
- Vonalkód renderelés javítások
|
||||
- Vonalkód-megjelenítési javítások
|
||||
- Alapvető együttműködési lehetőség külső alkalmazásokkal (Android 6.0+)
|
||||
- Átszervezett beállítási képernyő
|
||||
- Az olyan böngészőkből történő importálás javítása, amelyek záró / jelet adnak hozzá a megosztott webcímekhez
|
||||
|
||||
1
fastlane/metadata/android/hu-HU/changelogs/128.txt
Normal file
1
fastlane/metadata/android/hu-HU/changelogs/128.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Ritka összeomlás javítása
|
||||
1
fastlane/metadata/android/hu-HU/changelogs/129.txt
Normal file
1
fastlane/metadata/android/hu-HU/changelogs/129.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Javított Catima importáló (javítja az importálás után hiányzó kártyákat)
|
||||
@@ -1,5 +1,5 @@
|
||||
- Saat mengedit ID kartu, isi dulu ID yang ada untuk memulai. (pull #94 (https://github.com/brarcher/loyalty-card-locker/pull/94))
|
||||
- Batasi lebar kode batang yang dihasilkan untuk mengurangi penggunaan memori dan kesalahan memori. (pull #103 (https://github.com/brarcher/loyalty-card-locker/pull/103))
|
||||
- Saat mengedit ID kartu, isi terlebih dahulu ID yang sudah ada untuk memulai. (pull #94 (https://github.com/brarcher/loyalty-card-locker/pull/94))
|
||||
- Batasi lebar barcode yang dihasilkan untuk mengurangi penggunaan memori dan kesalahan karena kehabisan memori. (pull #103 (https://github.com/brarcher/loyalty-card-locker/pull/103))
|
||||
- Saat mengedit kartu, ubah tombol "Masukkan Kartu" menjadi "Edit Kartu" jika ID kartu sudah ada. (pull #104 (https://github.com/brarcher/loyalty-card-locker/pull/104))
|
||||
- Ubah skema warna menjadi lebih lembut dan kompatibel dengan ikon aplikasi, dan ubah tata letak saat melihat kartu menjadi lebih bersih. (pull #107 (https://github.com/brarcher/loyalty-card-locker/pull/107))
|
||||
- Tambahkan wizard intro yang diluncurkan pada peluncuran pertama aplikasi. (pull #108 (https://github.com/brarcher/loyalty-card-locker/pull/108))
|
||||
- Menambahkan wizard intro yang diluncurkan pada saat pertama kali aplikasi dijalankan. (pull #108 (https://github.com/brarcher/loyalty-card-locker/pull/108))
|
||||
|
||||
1
fastlane/metadata/android/id/changelogs/125.txt
Normal file
1
fastlane/metadata/android/id/changelogs/125.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Menangani warna header yang hilang dengan lebih anggun
|
||||
1
fastlane/metadata/android/id/changelogs/126.txt
Normal file
1
fastlane/metadata/android/id/changelogs/126.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Berbagai perbaikan RTL
|
||||
4
fastlane/metadata/android/id/changelogs/127.txt
Normal file
4
fastlane/metadata/android/id/changelogs/127.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
- Peningkatan tampilan barcode
|
||||
- Interoperabilitas dasar dengan aplikasi eksternal (Android 6.0+)
|
||||
- Tampilan pengaturan yang ditata ulang
|
||||
- Perbaikan proses impor dari beberapa browser yang menambahkan garis miring (/) di akhir URL berbagi
|
||||
1
fastlane/metadata/android/id/changelogs/128.txt
Normal file
1
fastlane/metadata/android/id/changelogs/128.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Perbaikan pada kerusakan yang jarang terjadi
|
||||
@@ -1,5 +1,5 @@
|
||||
- Tambahkan dukungan untuk menambahkan pintasan ke layar beranda saat menambahkan atau mengedit kartu. (pull #155 (https://github.com/brarcher/loyalty-card-locker/pull/155))
|
||||
- Hapus widget, karena merupakan pengganti pintasan yang buruk. (pull #155 (https://github.com/brarcher/loyalty-card-locker/pull/155))
|
||||
- Perbaiki mengekspor cadangan di Android 7+. (pull #153 (https://github.com/brarcher/loyalty-card-locker/pull/153))
|
||||
- Laporkan jenis pantomim yang lebih akurat saat mengekspor data cadangan. (pull #156 (https://github.com/brarcher/loyalty-card-locker/pull/156))
|
||||
- Menambahkan dukungan untuk menambahkan pintasan ke layar beranda saat menambahkan atau mengedit kartu. (pull #155 (https://github.com/brarcher/loyalty-card-locker/pull/155))
|
||||
- Widget dihapus karena pengganti pintasan yang buruk. (pull #155 (https://github.com/brarcher/loyalty-card-locker/pull/155))
|
||||
- Perbaikan pada ekspor cadangan di Android 7+. (pull #153 (https://github.com/brarcher/loyalty-card-locker/pull/153))
|
||||
- Laporkan jenis mime yang lebih akurat saat mengekspor data cadangan. (pull #156 (https://github.com/brarcher/loyalty-card-locker/pull/156))
|
||||
- Memperbaiki bug di mana kartu tidak dapat diedit. (pull #155 (https://github.com/brarcher/loyalty-card-locker/pull/155))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
- Tambahkan pengaturan untuk mengontrol kecerahan layar saat menampilkan kode batang (pull #259 (https://github.com/brarcher/loyalty-card-locker/pull/259))
|
||||
- Tambahkan terjemahan bahasa Yunani (pull #252 (https://github.com/brarcher/loyalty-card-locker/pull/252))
|
||||
- Tambahkan terjemahan bahasa Slovenia (pull #260 (https://github.com/brarcher/loyalty-card-locker/pull/260))
|
||||
- Perbarui terjemahan (pull #260 (https://github.com/brarcher/loyalty-card-locker/pull/260), pull #254 (https://github.com/brarcher/loyalty-card-locker/pull /254))
|
||||
- Tambah pengaturan kecerahan layar saat menampilkan barcode (pull #259 (https://github.com/brarcher/loyalty-card-locker/pull/259))
|
||||
- Tambah terjemahan Yunani (pull #252 (https://github.com/brarcher/loyalty-card-locker/pull/252))
|
||||
- Tambah terjemahan Slovenia (pull #260 (https://github.com/brarcher/loyalty-card-locker/pull/260))
|
||||
- Perbarui terjemahan (pull #260 (https://github.com/brarcher/loyalty-card-locker/pull/260), pull #254 (https://github.com/brarcher/loyalty-card-locker/pull/254))
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
- Tambahkan kemampuan untuk mencari kartu (#320 (https://github.com/brarcher/loyalty-card-locker/pull/320))
|
||||
- Tambahkan kemampuan untuk berbagi dan menerima kartu loyalitas (#321 (https://github.com/brarcher/loyalty-card-locker/pull/321))
|
||||
- Menambahkan kemampuan untuk mencari kartu (#320 (https://github.com/brarcher/loyalty-card-locker/pull/320))
|
||||
- Menambahkan kemampuan untuk berbagi dan menerima kartu loyalitas (#211 (https://github.com/brarcher/loyalty-card-locker/pull/321))
|
||||
- Dukungan mode gelap (#322 (https://github.com/brarcher/loyalty-card-locker/pull/322))
|
||||
- Kartu loyalitas sekarang dapat tanpa kode batang (misalnya tidak memiliki kode batang) (#324 (https://github.com/brarcher/loyalty-card-locker/pull/324))
|
||||
- Kartu loyalitas sekarang dapat menjadi barcodeless (misalnya tidak memiliki barcode) (#324 (https://github.com/brarcher/loyalty-card-locker/pull/324))
|
||||
- Catatan dapat menjangkau beberapa baris (#326 (https://github.com/brarcher/loyalty-card-locker/pull/326))
|
||||
- Perbaikan dengan ukuran catatan (#319 (https://github.com/brarcher/loyalty-card-locker/pull/319))
|
||||
- Tingkatkan visibilitas ikon notifikasi dan aplikasi (#330 (https://github.com/brarcher/loyalty-card-locker/pull/330))
|
||||
- Perbarui SDK target ke Android 10
|
||||
- Tingkatkan terjemahan berikut:
|
||||
- Meningkatkan visibilitas notifikasi dan ikon aplikasi (#330 (https://github.com/brarcher/loyalty-card-locker/pull/330))
|
||||
- Memperbarui SDK target ke Android 10
|
||||
- Memperbaiki terjemahan berikut ini:
|
||||
- Jerman
|
||||
- Italia
|
||||
- Belanda
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
- Izinkan pengguna memasukkan kode batang secara manual. Jika pengguna memilih untuk memasukkan kode batang secara manual, daftar semua gambar kode batang yang valid dan didukung akan ditampilkan. Pengguna kemudian dapat memilih gambar barcode yang sesuai dengan keinginan pengguna. issue #33 (https://github.com/brarcher/loyalty-card-locker/issues/33), pull #44 (https://github.com/brarcher/loyalty-card-locker/pull/44)
|
||||
- Selesaikan masalah di mana beberapa barcode yang ditampilkan buram. (issue #37 (https://github.com/brarcher/loyalty-card-locker/issues/37))
|
||||
- Izinkan pengguna memasukkan barcode secara manual. Jika pengguna memilih memasukkan barcode secara manual, tampilkan daftar gambar barcode yang valid dan didukung. Pengguna dapat memilih gambar barcode yang sesuai dengan keinginannya. issue #33 (https://github.com/brarcher/loyalty-card-locker/issues/33), pull #44 (https://github.com/brarcher/loyalty-card-locker/pull/44)
|
||||
- Mengatasi masalah barcode yang ditampilkan buram. (issue #37 (https://github.com/brarcher/loyalty-card-locker/issues/37))
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
- BREAKING CHANGE: Format cadangan berubah, lihat https://github.com/TheLastProject/Catima/wiki/Export-format
|
||||
- BREAKING CHANGE: Format pencadangan berubah, lihat https://github.com/TheLastProject/Catima/wiki/Export-format
|
||||
- BREAKING CHANGE: Format berbagi URL berubah, lihat https://github.com/TheLastProject/Catima/wiki/Card-sharing-URL-format
|
||||
- Memungkinkan untuk aktifkan atau nonaktifkan senter saat memindai
|
||||
- Tambahkan dukungan UPC-E
|
||||
- Dukungan penambahan foto depan dan belakang ke setiap kartu
|
||||
- Dukungan mengimpor file zip yang dilindungi kata sandi
|
||||
- Dukungan mengimpor dari Stocard (Beta)
|
||||
- Perbaiki spasi kosong yang tidak berguna dalam catatan dari impor Fidme
|
||||
- Mendukung format ekspor Voucher Vault baru
|
||||
- Perbaiki Tombol Mengambang di belakang elemen UI di Android 4
|
||||
- Memungkinkan untuk mengaktifkan atau menonaktifkan senter saat memindai
|
||||
- Menambahkan dukungan UPC-E
|
||||
- Mendukung penambahan foto depan dan belakang ke setiap kartu
|
||||
- Mendukung pengimporan file zip yang dilindungi kata sandi
|
||||
- Mendukung pengimporan dari Stocard (Beta)
|
||||
- Memperbaiki spasi yang tidak berguna pada catatan dari impor Fidme
|
||||
- Mendukung format ekspor Voucher Vault yang baru
|
||||
- Perbaiki Tombol Aksi Mengambang yang berada di belakang elemen UI lain di Android 4
|
||||
- Perbaiki margin atas appbar penampil kartu loyalitas
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
- Kecerahan layar meningkat secara maksimal saat menampilkan kartu, untuk membantu pemindai kode batang berhasil menangkap kode batang. (pull #54 (https://github.com/brarcher/loyalty-card-locker/pull/54))
|
||||
- Tambahkan konfirmasi hapus saat menghapus kartu. (pull #55 (https://github.com/brarcher/loyalty-card-locker/pull/55))
|
||||
- Tambahkan terjemahan untuk bahasa Jerman (pull #57 (https://github.com/brarcher/loyalty-card-locker/pull/57)) dan Ceko (pull #58 (https://github.com/brarcher/loyalty-card-locker/pull/58)).
|
||||
- Perubahan klarifikasi untuk terjemahan Italia. (pull #66 (https://github.com/brarcher/loyalty-card-locker/pull/66))
|
||||
- Kecerahan layar ditingkatkan hingga maksimum ketika menampilkan kartu, untuk membantu pemindai barcode berhasil menangkap barcode. (pull #54 (https://github.com/brarcher/loyalty-card-locker/pull/54))
|
||||
- Menambahkan konfirmasi hapus saat menghapus kartu. (pull #55 (https://github.com/brarcher/loyalty-card-locker/pull/55))
|
||||
- Menambahkan terjemahan untuk bahasa Jerman (pull #57 (https://github.com/brarcher/loyalty-card-locker/pull/57)) dan Ceko (pull #58 (https://github.com/brarcher/loyalty-card-locker/pull/58)).
|
||||
- Perubahan klarifikasi untuk terjemahan bahasa Italia. (pull #66 (https://github.com/brarcher/loyalty-card-locker/pull/66))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Bagian "Locker" dari nama itu tidak intuitif. Untuk membantu memperbaiki ini, ikon aplikasi baru dibuat oleh betsythefc yang lebih mewakili tujuan aplikasi: untuk menyimpan kartu loyalitas yang menggunakan kode batang. Seiring dengan ikon baru ini, nama aplikasi telah diubah menjadi "Loyalty Card Keychain".
|
||||
Bagian "Locker" dari nama itu tidak intuitif. Untuk membantu memperbaiki hal ini, sebuah ikon aplikasi baru telah dibuat oleh betsythefc yang lebih baik mewakili tujuan dari aplikasi ini: untuk menyimpan kartu loyalitas yang menggunakan barcode. Seiring dengan ikon baru ini, nama aplikasi telah diubah menjadi "Loyalty Card Keychain".
|
||||
|
||||
Fitur tambahan/peningkatan:
|
||||
|
||||
- Mengimpor/Mengekspor kartu diubah menjadi lebih fleksibel. (pull #76 (https://github.com/brarcher/loyalty-card-locker/pull/76))
|
||||
- Mengimpor / Mengekspor kartu diubah menjadi lebih fleksibel. (pull #76 (https://github.com/brarcher/loyalty-card-locker/pull/76))
|
||||
- Terjemahan untuk bahasa Lituania ditambahkan. (pull #62 (https://github.com/brarcher/loyalty-card-locker/pull/62))
|
||||
- Terjemahan untuk bahasa Prancis ditambahkan. (pull #80 (https://github.com/brarcher/loyalty-card-locker/pull/80))
|
||||
|
||||
1
fastlane/metadata/android/pt-PT/changelogs/121.txt
Normal file
1
fastlane/metadata/android/pt-PT/changelogs/121.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Atualizar bibliotecas usadas
|
||||
3
fastlane/metadata/android/pt-PT/changelogs/122.txt
Normal file
3
fastlane/metadata/android/pt-PT/changelogs/122.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- Pressione e segure o ícone do cartão na atividade de exibição para alterá-lo
|
||||
- Melhorar o estilo dos botões na tela Grupos
|
||||
- Corrigir valores longos de código de barras, fazendo com que o código de barras reduza para nada
|
||||
2
fastlane/metadata/android/pt-PT/changelogs/123.txt
Normal file
2
fastlane/metadata/android/pt-PT/changelogs/123.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Pequenas melhorias na interface do usuário
|
||||
- Corrige o novo design que não pode ser usado em dispositivos com telas quadradas
|
||||
1
fastlane/metadata/android/pt-PT/changelogs/124.txt
Normal file
1
fastlane/metadata/android/pt-PT/changelogs/124.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Suporte para selecionar exatamente quais detalhes visualizar na visão geral do cartão
|
||||
1
fastlane/metadata/android/pt-PT/changelogs/125.txt
Normal file
1
fastlane/metadata/android/pt-PT/changelogs/125.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Lide com mais elegância com cores de cabeçalho ausentes
|
||||
1
fastlane/metadata/android/pt-PT/changelogs/126.txt
Normal file
1
fastlane/metadata/android/pt-PT/changelogs/126.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Várias correções de RTL
|
||||
4
fastlane/metadata/android/pt-PT/changelogs/127.txt
Normal file
4
fastlane/metadata/android/pt-PT/changelogs/127.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
- Melhorias na renderização do código de barras
|
||||
- Interoperabilidade básica com aplicativos externos (Android 6.0+)
|
||||
- Tela de configurações reorganizada
|
||||
- Corrige a importação de alguns navegadores que adicionam um / à direita no URL de compartilhamento
|
||||
1
fastlane/metadata/android/pt-PT/changelogs/128.txt
Normal file
1
fastlane/metadata/android/pt-PT/changelogs/128.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Corrigir falha rara
|
||||
@@ -1,3 +1,4 @@
|
||||
- Улучшения визуализации штрих‐кодов
|
||||
- Базовая совместимость с внешними приложениями (Android 6.0+)
|
||||
- Реорганизация экрана настроек
|
||||
- Исправление импорта из некоторых браузеров, добавляющих в URL-адрес ресурса концевой символ /
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user