mirror of
https://github.com/CatimaLoyalty/Android.git
synced 2025-12-24 15:47:53 -05:00
Compare commits
387 Commits
create-pul
...
v2.40.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
479fce68d5 | ||
|
|
4370cd2383 | ||
|
|
0c4ef730e0 | ||
|
|
ee909e0047 | ||
|
|
6eee4a25f3 | ||
|
|
ffa99231c6 | ||
|
|
cbcd74f735 | ||
|
|
4f46a3c8ab | ||
|
|
5cec75c4c7 | ||
|
|
38d3731027 | ||
|
|
2a9f911a39 | ||
|
|
c762fcf6cc | ||
|
|
59db6642c3 | ||
|
|
eb5168ef83 | ||
|
|
7221ea64c8 | ||
|
|
af17bde7c5 | ||
|
|
67e2abde8b | ||
|
|
f28a9e7ba3 | ||
|
|
96f01b6a2c | ||
|
|
56bc429c4b | ||
|
|
163da4b021 | ||
|
|
6d9c168125 | ||
|
|
ccaef1adc5 | ||
|
|
f73222597c | ||
|
|
1714606744 | ||
|
|
ac3ef7fb36 | ||
|
|
9417814268 | ||
|
|
72737074fb | ||
|
|
e05386620b | ||
|
|
7cfe1ad833 | ||
|
|
00b975a140 | ||
|
|
424e57e41a | ||
|
|
cdb169c4e0 | ||
|
|
17aa18397e | ||
|
|
bb72eefc7f | ||
|
|
a007ed6a8f | ||
|
|
2219e86576 | ||
|
|
7f90e06ac5 | ||
|
|
5dd089c976 | ||
|
|
ef9aacd609 | ||
|
|
fa19960c5e | ||
|
|
a5428b80ff | ||
|
|
56512101c2 | ||
|
|
2bc8312511 | ||
|
|
43ccf9b48e | ||
|
|
e1a4ed6634 | ||
|
|
272e249d5e | ||
|
|
5636653c16 | ||
|
|
06b1b32ce7 | ||
|
|
202936b21f | ||
|
|
122f7f64b7 | ||
|
|
fbb913862a | ||
|
|
61892f9f22 | ||
|
|
166056fedd | ||
|
|
e03c883a9c | ||
|
|
1b4559fa3c | ||
|
|
ae8487f8d9 | ||
|
|
922b517d37 | ||
|
|
5a0d99fc80 | ||
|
|
c89a759c8b | ||
|
|
4c81fdcefb | ||
|
|
7a35b7f598 | ||
|
|
e1108c08ac | ||
|
|
6ab943f776 | ||
|
|
aac3570431 | ||
|
|
3abf287c67 | ||
|
|
cb26d23b02 | ||
|
|
9afcb0d7c6 | ||
|
|
acc50c4fda | ||
|
|
91fab12da6 | ||
|
|
e094b969ee | ||
|
|
3103d3a9cf | ||
|
|
c82b255eaa | ||
|
|
9ee4f0da9b | ||
|
|
3b5e6ac450 | ||
|
|
7ab270f323 | ||
|
|
6f9b4739c8 | ||
|
|
e654a657a0 | ||
|
|
ae6567a784 | ||
|
|
be12707ab1 | ||
|
|
e8d40ec679 | ||
|
|
e9c40c88a9 | ||
|
|
82bb9b2817 | ||
|
|
976dd6f80b | ||
|
|
2ac36fa415 | ||
|
|
19399dd17f | ||
|
|
acae8c1a0d | ||
|
|
64073e210e | ||
|
|
3adbf61be7 | ||
|
|
f0df4622eb | ||
|
|
81c0f284f6 | ||
|
|
fce60ca712 | ||
|
|
2be4d1cd2b | ||
|
|
72b19a8272 | ||
|
|
9f9d404632 | ||
|
|
157d1ecc49 | ||
|
|
54b21167ec | ||
|
|
34a125008f | ||
|
|
1776f8fd90 | ||
|
|
55269f748f | ||
|
|
6e42d0dcd9 | ||
|
|
48113eba18 | ||
|
|
41c8bb1815 | ||
|
|
eab4f9e123 | ||
|
|
05f4cfc07b | ||
|
|
b07b09e703 | ||
|
|
42e69916f0 | ||
|
|
254c9fee14 | ||
|
|
66e568aa06 | ||
|
|
1f38110e94 | ||
|
|
e92a98a956 | ||
|
|
875e90e940 | ||
|
|
5ebd23a88a | ||
|
|
6559857ba2 | ||
|
|
684b3e9836 | ||
|
|
d4b4599496 | ||
|
|
decd0a104d | ||
|
|
177f8e43e2 | ||
|
|
75234a6cd4 | ||
|
|
86b5c2998e | ||
|
|
0fee650cee | ||
|
|
61ee8b5910 | ||
|
|
dae66a63f1 | ||
|
|
934d7ba041 | ||
|
|
32b4dd73aa | ||
|
|
e558e7f6e4 | ||
|
|
20cc714b52 | ||
|
|
abf26c92e4 | ||
|
|
89bdbe1544 | ||
|
|
afe930a29f | ||
|
|
fc28ad0088 | ||
|
|
58b1c67abf | ||
|
|
ba563023c1 | ||
|
|
efe9487a44 | ||
|
|
2140066c4d | ||
|
|
32a70f10c0 | ||
|
|
c5ef9f4b1d | ||
|
|
91a3548613 | ||
|
|
1f370b86dd | ||
|
|
a93ddcb76e | ||
|
|
69aa985318 | ||
|
|
2855cf4569 | ||
|
|
01878a5ced | ||
|
|
bfb68e4118 | ||
|
|
709fffcfe5 | ||
|
|
5c0474b38e | ||
|
|
986ae4f0cb | ||
|
|
04937e8839 | ||
|
|
071062a939 | ||
|
|
058dc6c79b | ||
|
|
13a78dadb4 | ||
|
|
bf12bc4f9d | ||
|
|
7986428149 | ||
|
|
e17cbd5c9a | ||
|
|
ebfb105fe4 | ||
|
|
05a06eea27 | ||
|
|
663d7f3354 | ||
|
|
dbe5b88b52 | ||
|
|
c839fffadb | ||
|
|
8edfe53b45 | ||
|
|
0153fc54f1 | ||
|
|
f6b0af153f | ||
|
|
cfefce1baf | ||
|
|
ff1683d5b4 | ||
|
|
e181a866f7 | ||
|
|
1571d5766c | ||
|
|
1a4582adae | ||
|
|
88335b970f | ||
|
|
cac2dffb6c | ||
|
|
8a868e17bc | ||
|
|
1d05e96690 | ||
|
|
1d315d530f | ||
|
|
597fefa9c9 | ||
|
|
764834bbae | ||
|
|
582cfb4cf0 | ||
|
|
998fb16a03 | ||
|
|
40dd95f9c2 | ||
|
|
a27a6733e8 | ||
|
|
c76de152fc | ||
|
|
d0d75a4f50 | ||
|
|
df42111f83 | ||
|
|
da52a3685f | ||
|
|
bd85711e7f | ||
|
|
a02bf3e05c | ||
|
|
a397199834 | ||
|
|
27c16c2faf | ||
|
|
131004494b | ||
|
|
777bde7b5e | ||
|
|
3ba8f36108 | ||
|
|
773a0fa6d4 | ||
|
|
e88a537aec | ||
|
|
6ac60f9546 | ||
|
|
6fd6379ef3 | ||
|
|
2a4949a505 | ||
|
|
386a24305d | ||
|
|
45c4b89a4d | ||
|
|
73ea525d8b | ||
|
|
2ab267f601 | ||
|
|
ae54e91382 | ||
|
|
45c082fba9 | ||
|
|
aeedd9c3ac | ||
|
|
e76e4f42f2 | ||
|
|
f68a1f1c86 | ||
|
|
45a07d361c | ||
|
|
7f4937552d | ||
|
|
b786fd60b4 | ||
|
|
66646758a8 | ||
|
|
ece309fbde | ||
|
|
99c472330f | ||
|
|
246d5b5e4c | ||
|
|
8a8b243012 | ||
|
|
4612473e62 | ||
|
|
ab94e05e91 | ||
|
|
d2ecad5c3f | ||
|
|
a4c9d5a345 | ||
|
|
5b30a11da3 | ||
|
|
bda159a343 | ||
|
|
f473d31f13 | ||
|
|
1afe181085 | ||
|
|
647b7185df | ||
|
|
f301726a02 | ||
|
|
819be647b5 | ||
|
|
6215972732 | ||
|
|
90e406c30e | ||
|
|
d7a5a47393 | ||
|
|
8bacd4d1f5 | ||
|
|
a4d9ef0cb1 | ||
|
|
8bed9c753b | ||
|
|
47e598ede1 | ||
|
|
7edd41b08f | ||
|
|
a00dd69005 | ||
|
|
201c2b5964 | ||
|
|
5329a69e4d | ||
|
|
7ab0ffa0a3 | ||
|
|
33471e91be | ||
|
|
129bffe4b7 | ||
|
|
3f3a9ac807 | ||
|
|
c702efbd1e | ||
|
|
088098edad | ||
|
|
1e3e3c0e2e | ||
|
|
d9781e207c | ||
|
|
4a83c21d0d | ||
|
|
5d592e253b | ||
|
|
820091b8fa | ||
|
|
40c5eab3c5 | ||
|
|
c133fcf08a | ||
|
|
8094b7cc47 | ||
|
|
abd8716b56 | ||
|
|
cecad8351e | ||
|
|
4d1af69ed8 | ||
|
|
f468c06801 | ||
|
|
a0ef9b8d1b | ||
|
|
27f1f6f179 | ||
|
|
6ea1120517 | ||
|
|
2f7c44cbbe | ||
|
|
dd866a0f2b | ||
|
|
889d1beab4 | ||
|
|
357052ee42 | ||
|
|
19eda065ba | ||
|
|
5279c5c3b2 | ||
|
|
17be4e739f | ||
|
|
f2dd2e4d7e | ||
|
|
7c6ce077c1 | ||
|
|
45bf552eff | ||
|
|
633d412b52 | ||
|
|
54b8fb2d78 | ||
|
|
443e9f110b | ||
|
|
ac80bed084 | ||
|
|
802717c7a4 | ||
|
|
68b931f3b5 | ||
|
|
d4a4067754 | ||
|
|
ca18cfd6d1 | ||
|
|
18d80d2a4a | ||
|
|
ba4b9e4234 | ||
|
|
b87d531069 | ||
|
|
5cbb2505e3 | ||
|
|
e500a13c7e | ||
|
|
a4e9333c6e | ||
|
|
9dbe39e1a4 | ||
|
|
13c78eaee5 | ||
|
|
ef0e36b8be | ||
|
|
a1351563c1 | ||
|
|
303b40e572 | ||
|
|
622ea37554 | ||
|
|
8a80d16f11 | ||
|
|
4ea515c342 | ||
|
|
ce3dbaf902 | ||
|
|
a429b858e2 | ||
|
|
d8d228aa67 | ||
|
|
b31785a705 | ||
|
|
48b5e9f775 | ||
|
|
150ef5982a | ||
|
|
f91b94d100 | ||
|
|
6f25cc416f | ||
|
|
8358e982f9 | ||
|
|
637fdeebe6 | ||
|
|
96cf5274b1 | ||
|
|
1df5772857 | ||
|
|
44690dae55 | ||
|
|
ff46db7ac2 | ||
|
|
8f03595683 | ||
|
|
ac7494d08d | ||
|
|
e6ae0dab30 | ||
|
|
bc7da41da4 | ||
|
|
2fc5216cf1 | ||
|
|
8a792481b6 | ||
|
|
53e4e6b675 | ||
|
|
f06d338c5a | ||
|
|
fef65bd5d2 | ||
|
|
b830040639 | ||
|
|
2662178bef | ||
|
|
2a15ba9fe4 | ||
|
|
f777491dcd | ||
|
|
81445a21ff | ||
|
|
ff410542fb | ||
|
|
343e10f433 | ||
|
|
8023372a03 | ||
|
|
cd999f2346 | ||
|
|
4272d48fbf | ||
|
|
ae40737b75 | ||
|
|
4fe55be866 | ||
|
|
c5c4cf615f | ||
|
|
44e542ed5a | ||
|
|
bc2be8d33c | ||
|
|
73ed0edab7 | ||
|
|
a34a091cdb | ||
|
|
4f3d162d7a | ||
|
|
99605d7d18 | ||
|
|
fa152510a6 | ||
|
|
ddc868894e | ||
|
|
0a65fb607a | ||
|
|
921c76459c | ||
|
|
abf1ad61d6 | ||
|
|
fbcc2ef4fe | ||
|
|
699e7ce489 | ||
|
|
a45588abee | ||
|
|
44d5095101 | ||
|
|
b0b6de9a7d | ||
|
|
6b13e83146 | ||
|
|
cbac67728e | ||
|
|
400f4d20c1 | ||
|
|
3288b4602a | ||
|
|
74dec728ad | ||
|
|
aa72663440 | ||
|
|
f2fa6ed96d | ||
|
|
9b8e78a264 | ||
|
|
0e442beed5 | ||
|
|
ff1d38d159 | ||
|
|
5f8c8048e6 | ||
|
|
dc6d951241 | ||
|
|
9037ae0d53 | ||
|
|
83e7aa61fa | ||
|
|
11030b1e6a | ||
|
|
2f37e2a9c7 | ||
|
|
fcf891647c | ||
|
|
8465131d41 | ||
|
|
a33656d43b | ||
|
|
52397ab340 | ||
|
|
930a730252 | ||
|
|
37a707ba1d | ||
|
|
d9e4f58687 | ||
|
|
98bbca85b2 | ||
|
|
0f9aac76e2 | ||
|
|
7c933f888c | ||
|
|
9ae02ddb15 | ||
|
|
8333dd0d0c | ||
|
|
c21159c571 | ||
|
|
81db39d4e1 | ||
|
|
3e77ab6845 | ||
|
|
562ae9cd56 | ||
|
|
cb321ffdb8 | ||
|
|
a8d654b8d5 | ||
|
|
b2806cd000 | ||
|
|
d4166f681d | ||
|
|
19829be16a | ||
|
|
666ee288c3 | ||
|
|
2a8b5f983f | ||
|
|
adf8ae9878 | ||
|
|
7a6bee4a13 | ||
|
|
4a05031e42 | ||
|
|
8c86cc3c1a | ||
|
|
5205011610 | ||
|
|
f689cb6a8e | ||
|
|
412215603e | ||
|
|
97c34f8ae6 | ||
|
|
6563bc1b70 | ||
|
|
205bda34ae |
8
.github/workflows/android.yml
vendored
8
.github/workflows/android.yml
vendored
@@ -32,10 +32,8 @@ jobs:
|
||||
matrix:
|
||||
flavor: [Foss, Gplay]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Fail on bad translations
|
||||
run: if grep -ri "<xliff" app/src/main/res/values*/strings.xml; then echo "Invalidly escaped translations found"; exit 1; fi
|
||||
- uses: gradle/actions/wrapper-validation@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: gradle/actions/wrapper-validation@v5
|
||||
- name: set up OpenJDK 21
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -66,7 +64,7 @@ jobs:
|
||||
script: ./gradlew connected${{ matrix.flavor }}DebugAndroidTest
|
||||
- name: Archive test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
with:
|
||||
name: test-results-flavor${{ matrix.flavor }}
|
||||
path: app/build/reports
|
||||
|
||||
6
.github/workflows/changelog-to-fastlane.yml
vendored
6
.github/workflows/changelog-to-fastlane.yml
vendored
@@ -19,15 +19,15 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
id: checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6.0.0
|
||||
uses: actions/setup-python@v6.1.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Run converter script
|
||||
run: python .scripts/changelog_to_fastlane.py
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7.0.8
|
||||
uses: peter-evans/create-pull-request@v7.0.11
|
||||
with:
|
||||
title: "Update Fastlane changelogs"
|
||||
commit-message: "Update Fastlane changelogs"
|
||||
|
||||
4
.github/workflows/contributors-to-file.yml
vendored
4
.github/workflows/contributors-to-file.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
id: checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Update contributors
|
||||
id: update_contributors
|
||||
uses: TheLastProject/contributors-to-file-action@v3.2.0
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
file_in_repo: app/src/main/res/raw/contributors.txt
|
||||
min_commit_count: 5
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7.0.8
|
||||
uses: peter-evans/create-pull-request@v7.0.11
|
||||
with:
|
||||
title: "Update contributors"
|
||||
commit-message: "Update contributors"
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
generate-feature-graphic:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install requirements
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Generate featureGraphic.png for each language
|
||||
run: .scripts/generate_feature_graphic/generate_feature_graphic.sh
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7.0.8
|
||||
uses: peter-evans/create-pull-request@v7.0.11
|
||||
with:
|
||||
title: "Update feature graphic"
|
||||
commit-message: "Update feature graphic"
|
||||
|
||||
34
.github/workflows/i18n-check.yml
vendored
Normal file
34
.github/workflows/i18n-check.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: i18n check
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- staging
|
||||
- trying
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
permissions:
|
||||
actions: none
|
||||
checks: none
|
||||
contents: read
|
||||
deployments: none
|
||||
discussions: none
|
||||
id-token: none
|
||||
issues: none
|
||||
packages: none
|
||||
pages: none
|
||||
pull-requests: none
|
||||
repository-projects: none
|
||||
security-events: none
|
||||
statuses: none
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Fail on bad translations
|
||||
run: if grep -ri "<xliff" app/src/main/res/values*/strings.xml; then echo "Invalidly escaped translations found"; exit 1; fi
|
||||
- name: Check app_name consistency
|
||||
run: bash .scripts/check_app_name.sh
|
||||
2
.github/workflows/update-gradle-wrapper.yml
vendored
2
.github/workflows/update-gradle-wrapper.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Update Gradle Wrapper
|
||||
uses: gradle-update/update-gradle-wrapper-action@v2
|
||||
|
||||
4
.github/workflows/update-locales.yml
vendored
4
.github/workflows/update-locales.yml
vendored
@@ -17,13 +17,13 @@ jobs:
|
||||
update-locales:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Add new locales
|
||||
run: .scripts/new-locales.py
|
||||
- name: Update locales
|
||||
run: .scripts/locales.py
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7.0.8
|
||||
uses: peter-evans/create-pull-request@v7.0.11
|
||||
with:
|
||||
title: "Update locales"
|
||||
commit-message: "Update locales"
|
||||
|
||||
71
.scripts/check_app_name.sh
Normal file
71
.scripts/check_app_name.sh
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
shopt -s lastpipe # Run last command in a pipeline in the current shell.
|
||||
|
||||
# Colors
|
||||
LIGHTCYAN='\033[1;36m'
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Vars
|
||||
SUCCESS=1
|
||||
CANONICAL_TITLE="Catima"
|
||||
ALLOWLIST=("ar" "bn" "fa" "fa-IR" "he-IL" "hi" "hi-IN" "kn" "kn-IN" "ml" "mr" "ta" "ta-IN" "zh-rTW" "zh-TW") # TODO: Link values and fastlane with different codes together
|
||||
|
||||
function get_lang() {
|
||||
LANG_DIRNAME=$(dirname "$FILE" | xargs basename)
|
||||
LANG=${LANG_DIRNAME#values-} # Fetch lang name
|
||||
LANG=${LANG#values} # Handle "app/src/main/res/values"
|
||||
LANG=${LANG:-en} # Default to en
|
||||
}
|
||||
|
||||
# FIXME: This function should use its own variables and return a success/fail status, instead of working on global variables
|
||||
function check() {
|
||||
# FIXME: This allows inconsistency between values and fastlane if the app name is not Catima
|
||||
# When the app name is not Catima, it should still check if title.txt and strings.xml use the same app name (or start)
|
||||
if echo "${ALLOWLIST[*]}" | grep -w -q "${LANG}" || [[ -z ${APP_NAME} ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ${FILE} == *"title.txt" ]]; then
|
||||
if [[ ! ${APP_NAME} =~ ^${CANONICAL_TITLE} ]]; then
|
||||
echo -e "${RED}Error: ${LIGHTCYAN}title in $FILE ($LANG) is ${RED}'$APP_NAME'${LIGHTCYAN}, expected to start with ${GREEN}'$CANONICAL_TITLE'. ${NC}"
|
||||
SUCCESS=0
|
||||
fi
|
||||
else
|
||||
if [[ ${APP_NAME} != "${CANONICAL_TITLE}" ]]; then
|
||||
echo -e "${RED}Error: ${LIGHTCYAN}app_name in $FILE ($LANG) is ${RED}'$APP_NAME'${LIGHTCYAN}, expected ${GREEN}'$CANONICAL_TITLE'. ${NC}"
|
||||
SUCCESS=0
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# FIXME: This checks all title.txt and strings.xml files separately, but it needs to check if the title.txt and strings.xml match for a language as well
|
||||
echo -e "${LIGHTCYAN}Checking title.txt's. ${NC}"
|
||||
|
||||
find fastlane/metadata/android/* -maxdepth 1 -type f -name "title.txt" | while read -r FILE; do
|
||||
APP_NAME=$(head -n 1 "$FILE")
|
||||
|
||||
get_lang
|
||||
check
|
||||
done
|
||||
|
||||
echo -e "${LIGHTCYAN}Checking string.xml's. ${NC}"
|
||||
|
||||
find app/src/main/res/values* -maxdepth 1 -type f -name "strings.xml" | while read -r FILE; do
|
||||
# FIXME: This only checks app_name, but there are more strings with Catima inside it
|
||||
# It should check the original English text for all strings that contain Catima and ensure they use the correct app_name for consistency
|
||||
APP_NAME=$(grep -oP '<string name="app_name">\K[^<]+' "$FILE" | head -n1)
|
||||
|
||||
get_lang
|
||||
check
|
||||
done
|
||||
|
||||
if [[ $SUCCESS -eq 1 ]]; then
|
||||
echo -e "\n${GREEN}Success!! All app_name values match the canonical title. ${NC}"
|
||||
else
|
||||
echo -e "\n${RED}Unsuccessful!! Some app_name values did not match the canonical titles. ${NC}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -42,6 +42,7 @@ for lang in "$script_location/../../fastlane/metadata/android/"*; do
|
||||
ja-JP) sed -i "s/Lexend/Noto Sans CJK JP/" featureGraphic.svg ;;
|
||||
kn-IN) sed -i -e 's/font-size="150"/font-size="125"/' -e 's/\(<tspan x="469" \)y="270"/\1y="240"/' -e "s/Lobster/Noto Sans Kannada/" -e "s/Lexend/Noto Sans Kannada/" featureGraphic.svg ;;
|
||||
ko) sed -i "s/Lexend/Noto Sans CJK KR/" featureGraphic.svg ;;
|
||||
ta-IN) sed -i -e 's/font-size="150"/font-size="125"/' -e 's/\(<tspan x="469" \)y="270"/\1y="240"/' featureGraphic.svg ;;
|
||||
zh-CN) sed -i "s/Lexend/Noto Sans CJK SC/" featureGraphic.svg ;;
|
||||
zh-TW) sed -i -e "s/Lobster/Noto Sans CJK TC/" -e "s/Lexend/Noto Sans CJK TC/" featureGraphic.svg ;;
|
||||
*) ;;
|
||||
|
||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,10 +1,24 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased - 153
|
||||
## v2.40.0 - 156 (2025-12-08)
|
||||
|
||||
- Copy card ID to clipboard from view dialog or long press
|
||||
- Swap balance and currency fields to hopefully reduce unintended rounding
|
||||
|
||||
## v2.39.2 - 155 (2025-11-04)
|
||||
|
||||
- Preparations for future improvements (rewrote many classes to Kotlin)
|
||||
|
||||
## v2.39.1 - 154 (2025-10-01)
|
||||
|
||||
- Fix possible crash that could occur for cards missing colour information in the database
|
||||
|
||||
## v2.39.0 - 153 (2025-09-30)
|
||||
|
||||
- Target Android 16
|
||||
- Fix possible crash after removing image from card
|
||||
- Remove "Screen orientation" feature (Google removed the ability for apps to control screen rotation when targeting Android 16)
|
||||
- Add error reporting to FOSS build (not used in Google Play version, only in other app stores)
|
||||
- Add crash reporter to FOSS build (not used in Google Play version, only in other app stores)
|
||||
|
||||
## v2.38.0 - 152 (2025-09-12)
|
||||
|
||||
|
||||
@@ -23,6 +23,30 @@ for good reason.
|
||||
|
||||
## Code Changes
|
||||
|
||||
Note: submitting LLM ("AI") generated code is strongly discouraged, as such
|
||||
code is often (subtly) incorrect or overcomplicated (for example: unnecessarily
|
||||
pulling in extra libraries for functionality already covered by existing
|
||||
libraries). It also often makes unrelated changes that increase the risk of
|
||||
introducing new issues and complicates reviewing. Even when it doesn't do any
|
||||
of the before mentioned things, it will often not fit the coding style and flow
|
||||
of existing code, requiring excessive refactoring.
|
||||
|
||||
While we cannot ever control or be sure if LLMs were used to generate the
|
||||
submitted code, it is your responsibility to ensure that whatever code you
|
||||
submit is correct and fits within the design of existing code. It is never
|
||||
acceptable to defend a change by stating a LLM suggested it.
|
||||
|
||||
This is a personal plea more than anything: please understand that writing code
|
||||
is the easy part. The hard part is making sure the code fits the design of the
|
||||
rest of the application and is maintainable. Reviewing is a very time-consuming
|
||||
task for this reason. Please do not use LLMs to quickly generate a "fix" and
|
||||
moving the cost of labor to me as a reviewer. If you do use LLMs to generate
|
||||
part of your code, please be open about this, explain what was generated how
|
||||
and how you confirmed and refactored the code to fit the project and minimized
|
||||
risk.
|
||||
|
||||
Please never submit LLM-generated code as-is.
|
||||
|
||||
### Test Your Code
|
||||
|
||||
There are four possible tests you can run to verify your code. The first
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
**Last updated**
|
||||
August 30 2023
|
||||
September 30 2025
|
||||
|
||||
# Privacy Policy
|
||||
Catima does not collect or transmit any personal information.
|
||||
@@ -11,6 +11,12 @@ To ensure correct app functionality, we require access to the following:
|
||||
|
||||
Catima offers a feature to share cards with other users. All the relevant data is in the generated shareable URLs and never transmitted to our servers. When viewed through catima.app, the data in the URL is rendered using client-side Javascript to further ensure no data is ever transmitted to us.
|
||||
|
||||
## Crash reporting privacy
|
||||
|
||||
In the FOSS version of Catima (the version used on IzzyOnDroid, F-Droid and GitHub), the open source crash reporter ACRA is used for crash reporting. When a crash is detected, Catima will ask the user if they are willing to report the crash. If they choose to do so, the user's mail client is opened so they can review the data that would be sent. Crash reporting data is only sent when the user explicitly chooses to do so, it is **never** sent automatically. Crash reporting data is only used to solve crashes and no (potentially) sensitive information is ever shared. Users who do not want to be asked to report crashes can disable the "Ask to send crash reports" setting in Catima settings.
|
||||
|
||||
For the Google Play version of Catima, crash reporting is [managed by Google](https://support.google.com/googleplay/android-developer/answer/9859174?hl=en). Users can opt in or out of crash reporting through the Google app under the "Usage and diagnostics" setting.
|
||||
|
||||
# Changes
|
||||
This Privacy Policy may be updated from time to time for any reason. We will notify you of any changes to our Privacy Policy by posting the new Privacy Policy to https://catima.app/privacy-policy/. A snapshot of the Privacy Policy is available within the Catima app, though it may be outdated. When the Privacy Policy on the website and in the app differ, the website should be considered leading. You are advised to consult the Privacy Policy regularly for any changes, as continued use is deemed approval of all changes.
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
alias(libs.plugins.com.android.application)
|
||||
alias(libs.plugins.org.jetbrains.kotlin.android)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
@@ -17,8 +17,8 @@ android {
|
||||
applicationId = "me.hackerchick.catima"
|
||||
minSdk = 21
|
||||
targetSdk = 36
|
||||
versionCode = 152
|
||||
versionName = "2.38.0"
|
||||
versionCode = 156
|
||||
versionName = "2.40.0"
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
multiDexEnabled = true
|
||||
@@ -113,43 +113,38 @@ android {
|
||||
|
||||
dependencies {
|
||||
// AndroidX
|
||||
implementation("androidx.appcompat:appcompat:1.7.1")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
|
||||
implementation("androidx.core:core-ktx:1.17.0")
|
||||
implementation("androidx.core:core-remoteviews:1.1.0")
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
implementation("androidx.exifinterface:exifinterface:1.4.1")
|
||||
implementation("androidx.palette:palette:1.0.0")
|
||||
implementation("androidx.preference:preference:1.2.1")
|
||||
implementation("com.google.android.material:material:1.13.0")
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
|
||||
implementation(libs.androidx.appcompat.appcompat)
|
||||
implementation(libs.androidx.constraintlayout.constraintlayout)
|
||||
implementation(libs.androidx.core.core.ktx)
|
||||
implementation(libs.androidx.core.core.remoteviews)
|
||||
implementation(libs.androidx.core.core.splashscreen)
|
||||
implementation(libs.androidx.exifinterface.exifinterface)
|
||||
implementation(libs.androidx.palette.palette)
|
||||
implementation(libs.androidx.preference.preference)
|
||||
implementation(libs.com.google.android.material.material)
|
||||
coreLibraryDesugaring(libs.com.android.tools.desugar.jdk.libs)
|
||||
|
||||
// Third-party
|
||||
implementation("com.journeyapps:zxing-android-embedded:4.3.0@aar")
|
||||
implementation("com.github.yalantis:ucrop:2.2.10")
|
||||
implementation("com.google.zxing:core:3.5.3")
|
||||
implementation("org.apache.commons:commons-csv:1.9.0")
|
||||
implementation("com.jaredrummler:colorpicker:1.1.0")
|
||||
implementation("net.lingala.zip4j:zip4j:2.11.5")
|
||||
implementation(libs.com.journeyapps.zxing.android.embedded)
|
||||
implementation(libs.com.github.yalantis.ucrop)
|
||||
implementation(libs.com.google.zxing.core)
|
||||
implementation(libs.org.apache.commons.commons.csv)
|
||||
implementation(libs.com.jaredrummler.colorpicker)
|
||||
implementation(libs.net.lingala.zip4j.zip4j)
|
||||
|
||||
// Crash reporting
|
||||
val acraVersion = "5.12.0"
|
||||
implementation("ch.acra:acra-mail:$acraVersion")
|
||||
implementation("ch.acra:acra-dialog:$acraVersion")
|
||||
implementation(libs.bundles.acra)
|
||||
|
||||
// Testing
|
||||
val androidXTestVersion = "1.7.0"
|
||||
val junitVersion = "4.13.2"
|
||||
testImplementation("androidx.test:core:$androidXTestVersion")
|
||||
testImplementation("junit:junit:$junitVersion")
|
||||
testImplementation("org.robolectric:robolectric:4.16")
|
||||
testImplementation(libs.androidx.test.core)
|
||||
testImplementation(libs.junit.junit)
|
||||
testImplementation(libs.org.robolectric.robolectric)
|
||||
|
||||
androidTestImplementation("androidx.test:core:$androidXTestVersion")
|
||||
androidTestImplementation("junit:junit:$junitVersion")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.3.0")
|
||||
androidTestImplementation("androidx.test:runner:$androidXTestVersion")
|
||||
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
|
||||
androidTestImplementation(libs.bundles.androidx.test)
|
||||
androidTestImplementation(libs.junit.junit)
|
||||
androidTestImplementation(libs.androidx.test.ext.junit)
|
||||
androidTestImplementation(libs.androidx.test.uiautomator.uiautomator)
|
||||
androidTestImplementation(libs.androidx.test.espresso.espresso.core)
|
||||
}
|
||||
|
||||
tasks.register("copyRawResFiles", Copy::class) {
|
||||
|
||||
17
app/proguard-rules.pro
vendored
17
app/proguard-rules.pro
vendored
@@ -21,4 +21,19 @@
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# This keep the class and method names the same, for debugging stack traces
|
||||
-dontobfuscate
|
||||
-dontobfuscate
|
||||
|
||||
# Required for uCrop 2.2.11
|
||||
# This is generated automatically by the Android Gradle plugin.
|
||||
-dontwarn javax.annotation.processing.AbstractProcessor
|
||||
-dontwarn javax.annotation.processing.SupportedOptions
|
||||
-dontwarn okhttp3.Call
|
||||
-dontwarn okhttp3.Dispatcher
|
||||
-dontwarn okhttp3.OkHttpClient
|
||||
-dontwarn okhttp3.Request$Builder
|
||||
-dontwarn okhttp3.Request
|
||||
-dontwarn okhttp3.Response
|
||||
-dontwarn okhttp3.ResponseBody
|
||||
-dontwarn okio.BufferedSource
|
||||
-dontwarn okio.Okio
|
||||
-dontwarn okio.Sink
|
||||
|
||||
@@ -99,9 +99,9 @@ public class AboutContent {
|
||||
|
||||
public String getThirdPartyLibraries() {
|
||||
final List<ThirdPartyInfo> usedLibraries = new ArrayList<>();
|
||||
usedLibraries.add(new ThirdPartyInfo("ACRA", "https://github.com/ACRA/acra", "Apache 2.0"));
|
||||
usedLibraries.add(new ThirdPartyInfo("Color Picker", "https://github.com/jaredrummler/ColorPicker", "Apache 2.0"));
|
||||
usedLibraries.add(new ThirdPartyInfo("Commons CSV", "https://commons.apache.org/proper/commons-csv/", "Apache 2.0"));
|
||||
usedLibraries.add(new ThirdPartyInfo("NumberPickerPreference", "https://github.com/invissvenska/NumberPickerPreference", "GNU LGPL 3.0"));
|
||||
usedLibraries.add(new ThirdPartyInfo("uCrop", "https://github.com/Yalantis/uCrop", "Apache 2.0"));
|
||||
usedLibraries.add(new ThirdPartyInfo("Zip4j", "https://github.com/srikanth-lingala/zip4j", "Apache 2.0"));
|
||||
usedLibraries.add(new ThirdPartyInfo("ZXing", "https://github.com/zxing/zxing", "Apache 2.0"));
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
public interface BarcodeImageWriterResultCallback {
|
||||
void onBarcodeImageWriterResult(boolean success);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package protect.card_locker
|
||||
|
||||
interface BarcodeImageWriterResultCallback {
|
||||
fun onBarcodeImageWriterResult(success: Boolean)
|
||||
}
|
||||
@@ -1,395 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import protect.card_locker.async.TaskHandler;
|
||||
import protect.card_locker.databinding.ImportExportActivityBinding;
|
||||
import protect.card_locker.importexport.DataFormat;
|
||||
import protect.card_locker.importexport.ImportExportResult;
|
||||
import protect.card_locker.importexport.ImportExportResultType;
|
||||
|
||||
public class ImportExportActivity extends CatimaAppCompatActivity {
|
||||
private ImportExportActivityBinding binding;
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private ImportExportTask importExporter;
|
||||
|
||||
private String importAlertTitle;
|
||||
private String importAlertMessage;
|
||||
private DataFormat importDataFormat;
|
||||
private String exportPassword;
|
||||
|
||||
private ActivityResultLauncher<Intent> fileCreateLauncher;
|
||||
private ActivityResultLauncher<String> fileOpenLauncher;
|
||||
|
||||
final private TaskHandler mTasks = new TaskHandler();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = ImportExportActivityBinding.inflate(getLayoutInflater());
|
||||
setTitle(R.string.importExport);
|
||||
setContentView(binding.getRoot());
|
||||
Utils.applyWindowInsets(binding.getRoot());
|
||||
Toolbar toolbar = binding.toolbar;
|
||||
setSupportActionBar(toolbar);
|
||||
enableToolbarBackButton();
|
||||
|
||||
Intent fileIntent = getIntent();
|
||||
if (fileIntent != null && fileIntent.getType() != null) {
|
||||
chooseImportType(fileIntent.getData());
|
||||
}
|
||||
|
||||
// would use ActivityResultContracts.CreateDocument() but mime type cannot be set
|
||||
fileCreateLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||
Intent intent = result.getData();
|
||||
if (intent == null) {
|
||||
Log.e(TAG, "Activity returned NULL data");
|
||||
return;
|
||||
}
|
||||
Uri uri = intent.getData();
|
||||
if (uri == null) {
|
||||
Log.e(TAG, "Activity returned NULL uri");
|
||||
return;
|
||||
}
|
||||
// Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files
|
||||
// FIXME: This is still suboptimal, because showing that the export started is delayed until the network request finishes
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
OutputStream writer = getContentResolver().openOutputStream(uri);
|
||||
Log.d(TAG, "Starting file export with: " + result);
|
||||
startExport(writer, uri, exportPassword.toCharArray(), true);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to export file: " + result, e);
|
||||
onExportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, result.toString()), uri);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
});
|
||||
fileOpenLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), result -> {
|
||||
if (result == null) {
|
||||
Log.e(TAG, "Activity returned NULL data");
|
||||
return;
|
||||
}
|
||||
openFileForImport(result, null);
|
||||
});
|
||||
|
||||
// Check that there is a file manager available
|
||||
final Intent intentCreateDocumentAction = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||
intentCreateDocumentAction.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intentCreateDocumentAction.setType("application/zip");
|
||||
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "catima.zip");
|
||||
|
||||
Button exportButton = binding.exportButton;
|
||||
exportButton.setOnClickListener(v -> {
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(ImportExportActivity.this);
|
||||
builder.setTitle(R.string.exportPassword);
|
||||
|
||||
FrameLayout container = new FrameLayout(ImportExportActivity.this);
|
||||
|
||||
final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this);
|
||||
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.setMargins(50, 10, 50, 0);
|
||||
textInputLayout.setLayoutParams(params);
|
||||
|
||||
final EditText input = new EditText(ImportExportActivity.this);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
input.setHint(R.string.exportPasswordHint);
|
||||
|
||||
textInputLayout.addView(input);
|
||||
container.addView(textInputLayout);
|
||||
builder.setView(container);
|
||||
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
||||
exportPassword = input.getText().toString();
|
||||
try {
|
||||
fileCreateLauncher.launch(intentCreateDocumentAction);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
|
||||
Log.e(TAG, "No activity found to handle intent", e);
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
|
||||
builder.show();
|
||||
});
|
||||
|
||||
// Check that there is a file manager available
|
||||
Button importFilesystem = binding.importOptionFilesystemButton;
|
||||
importFilesystem.setOnClickListener(v -> chooseImportType(null));
|
||||
|
||||
// FIXME: The importer/exporter is currently quite broken
|
||||
// To prevent the screen from turning off during import/export and some devices killing Catima as it's no longer foregrounded, force the screen to stay on here
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
|
||||
private void openFileForImport(Uri uri, char[] password) {
|
||||
// Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files
|
||||
// FIXME: This is still suboptimal, because showing that the import started is delayed until the network request finishes
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
InputStream reader = getContentResolver().openInputStream(uri);
|
||||
Log.d(TAG, "Starting file import with: " + uri);
|
||||
startImport(reader, uri, importDataFormat, password, true);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to import file: " + uri, e);
|
||||
onImportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, e.toString()), uri, importDataFormat);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
private void chooseImportType(@Nullable Uri fileData) {
|
||||
|
||||
List<CharSequence> betaImportOptions = new ArrayList<>();
|
||||
betaImportOptions.add("Fidme");
|
||||
List<CharSequence> importOptions = new ArrayList<>();
|
||||
|
||||
for (String importOption : getResources().getStringArray(R.array.import_types_array)) {
|
||||
if (betaImportOptions.contains(importOption)) {
|
||||
importOption = importOption + " (BETA)";
|
||||
}
|
||||
|
||||
importOptions.add(importOption);
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle(R.string.chooseImportType)
|
||||
.setItems(importOptions.toArray(new CharSequence[importOptions.size()]), (dialog, which) -> {
|
||||
switch (which) {
|
||||
// Catima
|
||||
case 0:
|
||||
importAlertTitle = getString(R.string.importCatima);
|
||||
importAlertMessage = getString(R.string.importCatimaMessage);
|
||||
importDataFormat = DataFormat.Catima;
|
||||
break;
|
||||
// Fidme
|
||||
case 1:
|
||||
importAlertTitle = getString(R.string.importFidme);
|
||||
importAlertMessage = getString(R.string.importFidmeMessage);
|
||||
importDataFormat = DataFormat.Fidme;
|
||||
break;
|
||||
// Loyalty Card Keychain
|
||||
case 2:
|
||||
importAlertTitle = getString(R.string.importLoyaltyCardKeychain);
|
||||
importAlertMessage = getString(R.string.importLoyaltyCardKeychainMessage);
|
||||
importDataFormat = DataFormat.Catima;
|
||||
break;
|
||||
// Voucher Vault
|
||||
case 3:
|
||||
importAlertTitle = getString(R.string.importVoucherVault);
|
||||
importAlertMessage = getString(R.string.importVoucherVaultMessage);
|
||||
importDataFormat = DataFormat.VoucherVault;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown DataFormat");
|
||||
}
|
||||
|
||||
if (fileData != null) {
|
||||
openFileForImport(fileData, null);
|
||||
return;
|
||||
}
|
||||
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(importAlertTitle)
|
||||
.setMessage(importAlertMessage)
|
||||
.setPositiveButton(R.string.ok, (dialog1, which1) -> {
|
||||
try {
|
||||
fileOpenLauncher.launch("*/*");
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
|
||||
Log.e(TAG, "No activity found to handle intent", e);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password, final boolean closeWhenDone) {
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
|
||||
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
|
||||
@Override
|
||||
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
|
||||
onImportComplete(result, targetUri, dataFormat);
|
||||
if (closeWhenDone) {
|
||||
try {
|
||||
target.close();
|
||||
} catch (IOException ioException) {
|
||||
ioException.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
importExporter = new ImportExportTask(ImportExportActivity.this,
|
||||
dataFormat, target, password, listener);
|
||||
mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter);
|
||||
}
|
||||
|
||||
private void startExport(final OutputStream target, final Uri targetUri, char[] password, final boolean closeWhenDone) {
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
|
||||
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
|
||||
@Override
|
||||
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
|
||||
onExportComplete(result, targetUri);
|
||||
if (closeWhenDone) {
|
||||
try {
|
||||
target.close();
|
||||
} catch (IOException ioException) {
|
||||
ioException.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
importExporter = new ImportExportTask(ImportExportActivity.this,
|
||||
DataFormat.Catima, target, password, listener);
|
||||
mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
|
||||
if (id == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void retryWithPassword(DataFormat dataFormat, Uri uri) {
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle(R.string.passwordRequired);
|
||||
|
||||
FrameLayout container = new FrameLayout(ImportExportActivity.this);
|
||||
|
||||
final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this);
|
||||
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.setMargins(50, 10, 50, 0);
|
||||
textInputLayout.setLayoutParams(params);
|
||||
|
||||
final EditText input = new EditText(ImportExportActivity.this);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
input.setHint(R.string.exportPasswordHint);
|
||||
|
||||
textInputLayout.addView(input);
|
||||
container.addView(textInputLayout);
|
||||
builder.setView(container);
|
||||
|
||||
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
||||
openFileForImport(uri, input.getText().toString().toCharArray());
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
|
||||
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private String buildResultDialogMessage(ImportExportResult result, boolean isImport) {
|
||||
int messageId;
|
||||
|
||||
if (result.resultType() == ImportExportResultType.Success) {
|
||||
messageId = isImport ? R.string.importSuccessful : R.string.exportSuccessful;
|
||||
} else {
|
||||
messageId = isImport ? R.string.importFailed : R.string.exportFailed;
|
||||
}
|
||||
|
||||
StringBuilder messageBuilder = new StringBuilder(getResources().getString(messageId));
|
||||
if (result.developerDetails() != null) {
|
||||
messageBuilder.append("\n\n");
|
||||
messageBuilder.append(getResources().getString(R.string.include_if_asking_support));
|
||||
messageBuilder.append("\n\n");
|
||||
messageBuilder.append(result.developerDetails());
|
||||
}
|
||||
|
||||
return messageBuilder.toString();
|
||||
}
|
||||
|
||||
private void onImportComplete(ImportExportResult result, Uri path, DataFormat dataFormat) {
|
||||
ImportExportResultType resultType = result.resultType();
|
||||
|
||||
if (resultType == ImportExportResultType.BadPassword) {
|
||||
retryWithPassword(dataFormat, path);
|
||||
return;
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.importSuccessfulTitle : R.string.importFailedTitle);
|
||||
builder.setMessage(buildResultDialogMessage(result, true));
|
||||
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void onExportComplete(ImportExportResult result, final Uri path) {
|
||||
ImportExportResultType resultType = result.resultType();
|
||||
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.exportSuccessfulTitle : R.string.exportFailedTitle);
|
||||
builder.setMessage(buildResultDialogMessage(result, false));
|
||||
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
|
||||
|
||||
if (resultType == ImportExportResultType.Success) {
|
||||
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel);
|
||||
|
||||
builder.setPositiveButton(sendLabel, (dialog, which) -> {
|
||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, path);
|
||||
sendIntent.setType("text/csv");
|
||||
|
||||
// set flag to give temporary permission to external app to use the FileProvider
|
||||
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
|
||||
sendLabel));
|
||||
|
||||
dialog.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
}
|
||||
416
app/src/main/java/protect/card_locker/ImportExportActivity.kt
Normal file
416
app/src/main/java/protect/card_locker/ImportExportActivity.kt
Normal file
@@ -0,0 +1,416 @@
|
||||
package protect.card_locker
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import protect.card_locker.async.TaskHandler
|
||||
import protect.card_locker.databinding.ImportExportActivityBinding
|
||||
import protect.card_locker.importexport.DataFormat
|
||||
import protect.card_locker.importexport.ImportExportResult
|
||||
import protect.card_locker.importexport.ImportExportResultType
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
class ImportExportActivity : CatimaAppCompatActivity() {
|
||||
private lateinit var binding: ImportExportActivityBinding
|
||||
|
||||
private var importExporter: ImportExportTask? = null
|
||||
|
||||
private var importAlertTitle: String? = null
|
||||
private var importAlertMessage: String? = null
|
||||
private var importDataFormat: DataFormat? = null
|
||||
private var exportPassword: String? = null
|
||||
|
||||
private lateinit var fileCreateLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var fileOpenLauncher: ActivityResultLauncher<String>
|
||||
|
||||
private val mTasks = TaskHandler()
|
||||
|
||||
companion object {
|
||||
private const val TAG = "Catima"
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ImportExportActivityBinding.inflate(layoutInflater)
|
||||
setTitle(R.string.importExport)
|
||||
setContentView(binding.root)
|
||||
Utils.applyWindowInsets(binding.root)
|
||||
val toolbar: Toolbar = binding.toolbar
|
||||
setSupportActionBar(toolbar)
|
||||
enableToolbarBackButton()
|
||||
|
||||
val fileIntent = intent
|
||||
if (fileIntent?.type != null) {
|
||||
chooseImportType(fileIntent.data)
|
||||
}
|
||||
|
||||
// would use ActivityResultContracts.CreateDocument() but mime type cannot be set
|
||||
fileCreateLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
val intent = result.data
|
||||
if (intent == null) {
|
||||
Log.e(TAG, "Activity returned NULL data")
|
||||
return@registerForActivityResult
|
||||
}
|
||||
val uri = intent.data
|
||||
if (uri == null) {
|
||||
Log.e(TAG, "Activity returned NULL uri")
|
||||
return@registerForActivityResult
|
||||
}
|
||||
// Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files
|
||||
// FIXME: This is still suboptimal, because showing that the export started is delayed until the network request finishes
|
||||
Thread {
|
||||
try {
|
||||
val writer = contentResolver.openOutputStream(uri)
|
||||
Log.d(TAG, "Starting file export with: $result")
|
||||
startExport(writer, uri, exportPassword?.toCharArray(), true)
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Failed to export file: $result", e)
|
||||
onExportComplete(
|
||||
ImportExportResult(
|
||||
ImportExportResultType.GenericFailure,
|
||||
result.toString()
|
||||
), uri
|
||||
)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
fileOpenLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.GetContent()) { result ->
|
||||
if (result == null) {
|
||||
Log.e(TAG, "Activity returned NULL data")
|
||||
return@registerForActivityResult
|
||||
}
|
||||
openFileForImport(result, null)
|
||||
}
|
||||
|
||||
// Check that there is a file manager available
|
||||
val intentCreateDocumentAction = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/zip"
|
||||
putExtra(Intent.EXTRA_TITLE, "catima.zip")
|
||||
}
|
||||
|
||||
val exportButton: Button = binding.exportButton
|
||||
exportButton.setOnClickListener {
|
||||
val builder = MaterialAlertDialogBuilder(this@ImportExportActivity)
|
||||
builder.setTitle(R.string.exportPassword)
|
||||
|
||||
val container = FrameLayout(this@ImportExportActivity)
|
||||
|
||||
val textInputLayout = TextInputLayout(this@ImportExportActivity).apply {
|
||||
endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
setMargins(50, 10, 50, 0)
|
||||
}
|
||||
}
|
||||
|
||||
val input = EditText(this@ImportExportActivity).apply {
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
setHint(R.string.exportPasswordHint)
|
||||
}
|
||||
|
||||
textInputLayout.addView(input)
|
||||
container.addView(textInputLayout)
|
||||
builder.setView(container)
|
||||
builder.setPositiveButton(R.string.ok) { _, _ ->
|
||||
exportPassword = input.text.toString()
|
||||
try {
|
||||
fileCreateLauncher.launch(intentCreateDocumentAction)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.failedOpeningFileManager,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
Log.e(TAG, "No activity found to handle intent", e)
|
||||
}
|
||||
}
|
||||
builder.setNegativeButton(R.string.cancel) { dialogInterface, _ -> dialogInterface.cancel() }
|
||||
builder.show()
|
||||
}
|
||||
|
||||
// Check that there is a file manager available
|
||||
val importFilesystem: Button = binding.importOptionFilesystemButton
|
||||
importFilesystem.setOnClickListener { chooseImportType(null) }
|
||||
|
||||
// FIXME: The importer/exporter is currently quite broken
|
||||
// To prevent the screen from turning off during import/export and some devices killing Catima as it's no longer foregrounded, force the screen to stay on here
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
|
||||
private fun openFileForImport(uri: Uri, password: CharArray?) {
|
||||
// Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files
|
||||
// FIXME: This is still suboptimal, because showing that the import started is delayed until the network request finishes
|
||||
Thread {
|
||||
try {
|
||||
val reader = contentResolver.openInputStream(uri)
|
||||
Log.d(TAG, "Starting file import with: $uri")
|
||||
startImport(reader, uri, importDataFormat, password, true)
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Failed to import file: $uri", e)
|
||||
onImportComplete(
|
||||
ImportExportResult(
|
||||
ImportExportResultType.GenericFailure,
|
||||
e.toString()
|
||||
), uri, importDataFormat
|
||||
)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun chooseImportType(fileData: Uri?) {
|
||||
val betaImportOptions = mutableListOf<CharSequence>()
|
||||
betaImportOptions.add("Fidme")
|
||||
val importOptions = mutableListOf<CharSequence>()
|
||||
|
||||
for (importOption in resources.getStringArray(R.array.import_types_array)) {
|
||||
var option = importOption
|
||||
if (betaImportOptions.contains(importOption)) {
|
||||
option = "$importOption (BETA)"
|
||||
}
|
||||
importOptions.add(option)
|
||||
}
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(this)
|
||||
builder.setTitle(R.string.chooseImportType)
|
||||
.setItems(importOptions.toTypedArray()) { _, which ->
|
||||
when (which) {
|
||||
// Catima
|
||||
0 -> {
|
||||
importAlertTitle = getString(R.string.importCatima)
|
||||
importAlertMessage = getString(R.string.importCatimaMessage)
|
||||
importDataFormat = DataFormat.Catima
|
||||
}
|
||||
// Fidme
|
||||
1 -> {
|
||||
importAlertTitle = getString(R.string.importFidme)
|
||||
importAlertMessage = getString(R.string.importFidmeMessage)
|
||||
importDataFormat = DataFormat.Fidme
|
||||
}
|
||||
// Loyalty Card Keychain
|
||||
2 -> {
|
||||
importAlertTitle = getString(R.string.importLoyaltyCardKeychain)
|
||||
importAlertMessage = getString(R.string.importLoyaltyCardKeychainMessage)
|
||||
importDataFormat = DataFormat.Catima
|
||||
}
|
||||
// Voucher Vault
|
||||
3 -> {
|
||||
importAlertTitle = getString(R.string.importVoucherVault)
|
||||
importAlertMessage = getString(R.string.importVoucherVaultMessage)
|
||||
importDataFormat = DataFormat.VoucherVault
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("Unknown DataFormat")
|
||||
}
|
||||
|
||||
if (fileData != null) {
|
||||
openFileForImport(fileData, null)
|
||||
return@setItems
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(importAlertTitle)
|
||||
.setMessage(importAlertMessage)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
try {
|
||||
fileOpenLauncher.launch("*/*")
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.failedOpeningFileManager,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
Log.e(TAG, "No activity found to handle intent", e)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun startImport(
|
||||
target: InputStream?,
|
||||
targetUri: Uri,
|
||||
dataFormat: DataFormat?,
|
||||
password: CharArray?,
|
||||
closeWhenDone: Boolean
|
||||
) {
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false)
|
||||
val listener = ImportExportTask.TaskCompleteListener { result, dataFormat ->
|
||||
onImportComplete(result, targetUri, dataFormat)
|
||||
if (closeWhenDone) {
|
||||
try {
|
||||
target?.close()
|
||||
} catch (ioException: IOException) {
|
||||
ioException.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
importExporter = ImportExportTask(
|
||||
this@ImportExportActivity,
|
||||
dataFormat, target, password, listener
|
||||
)
|
||||
mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter)
|
||||
}
|
||||
|
||||
private fun startExport(
|
||||
target: OutputStream?,
|
||||
targetUri: Uri,
|
||||
password: CharArray?,
|
||||
closeWhenDone: Boolean
|
||||
) {
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false)
|
||||
val listener = ImportExportTask.TaskCompleteListener { result, dataFormat ->
|
||||
onExportComplete(result, targetUri)
|
||||
if (closeWhenDone) {
|
||||
try {
|
||||
target?.close()
|
||||
} catch (ioException: IOException) {
|
||||
ioException.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
importExporter = ImportExportTask(
|
||||
this@ImportExportActivity,
|
||||
DataFormat.Catima, target, password, listener
|
||||
)
|
||||
mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false)
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
val id = item.itemId
|
||||
|
||||
if (id == android.R.id.home) {
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun retryWithPassword(dataFormat: DataFormat, uri: Uri) {
|
||||
val builder = MaterialAlertDialogBuilder(this)
|
||||
builder.setTitle(R.string.passwordRequired)
|
||||
|
||||
val container = FrameLayout(this@ImportExportActivity)
|
||||
|
||||
val textInputLayout = TextInputLayout(this@ImportExportActivity).apply {
|
||||
endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
setMargins(50, 10, 50, 0)
|
||||
}
|
||||
}
|
||||
|
||||
val input = EditText(this@ImportExportActivity).apply {
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
setHint(R.string.exportPasswordHint)
|
||||
}
|
||||
|
||||
textInputLayout.addView(input)
|
||||
container.addView(textInputLayout)
|
||||
builder.setView(container)
|
||||
|
||||
builder.setPositiveButton(R.string.ok) { _, _ ->
|
||||
openFileForImport(uri, input.text.toString().toCharArray())
|
||||
}
|
||||
builder.setNegativeButton(R.string.cancel) { dialogInterface, _ -> dialogInterface.cancel() }
|
||||
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun buildResultDialogMessage(result: ImportExportResult, isImport: Boolean): String {
|
||||
val messageId = if (result.resultType() == ImportExportResultType.Success) {
|
||||
if (isImport) R.string.importSuccessful else R.string.exportSuccessful
|
||||
} else {
|
||||
if (isImport) R.string.importFailed else R.string.exportFailed
|
||||
}
|
||||
|
||||
val messageBuilder = StringBuilder(resources.getString(messageId))
|
||||
if (result.developerDetails() != null) {
|
||||
messageBuilder.append("\n\n")
|
||||
messageBuilder.append(resources.getString(R.string.include_if_asking_support))
|
||||
messageBuilder.append("\n\n")
|
||||
messageBuilder.append(result.developerDetails())
|
||||
}
|
||||
|
||||
return messageBuilder.toString()
|
||||
}
|
||||
|
||||
private fun onImportComplete(result: ImportExportResult, path: Uri, dataFormat: DataFormat?) {
|
||||
val resultType = result.resultType()
|
||||
|
||||
if (resultType == ImportExportResultType.BadPassword) {
|
||||
retryWithPassword(dataFormat!!, path)
|
||||
return
|
||||
}
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(this)
|
||||
builder.setTitle(if (resultType == ImportExportResultType.Success) R.string.importSuccessfulTitle else R.string.importFailedTitle)
|
||||
builder.setMessage(buildResultDialogMessage(result, true))
|
||||
builder.setNeutralButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||
|
||||
builder.create().show()
|
||||
}
|
||||
|
||||
private fun onExportComplete(result: ImportExportResult, path: Uri) {
|
||||
val resultType = result.resultType()
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(this)
|
||||
builder.setTitle(if (resultType == ImportExportResultType.Success) R.string.exportSuccessfulTitle else R.string.exportFailedTitle)
|
||||
builder.setMessage(buildResultDialogMessage(result, false))
|
||||
builder.setNeutralButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||
|
||||
if (resultType == ImportExportResultType.Success) {
|
||||
val sendLabel = this@ImportExportActivity.resources.getText(R.string.sendLabel)
|
||||
|
||||
builder.setPositiveButton(sendLabel) { dialog, _ ->
|
||||
val sendIntent = Intent(Intent.ACTION_SEND).apply {
|
||||
putExtra(Intent.EXTRA_STREAM, path)
|
||||
type = "text/csv"
|
||||
// set flag to give temporary permission to external app to use the FileProvider
|
||||
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
}
|
||||
|
||||
this@ImportExportActivity.startActivity(Intent.createChooser(sendIntent, sendLabel))
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
builder.create().show()
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.TextPaint;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.graphics.PaintCompat;
|
||||
|
||||
/**
|
||||
* Original from https://github.com/andOTP/andOTP/blob/master/app/src/main/java/org/shadowice/flocke/andotp/Utilities/LetterBitmap.java
|
||||
* which was originally from http://stackoverflow.com/questions/23122088/colored-boxed-with-letters-a-la-gmail
|
||||
* Used to create a {@link Bitmap} that contains a letter used in the English
|
||||
* alphabet or digit, if there is no letter or digit available, a default image
|
||||
* is shown instead.
|
||||
*/
|
||||
class LetterBitmap {
|
||||
|
||||
/**
|
||||
* The number of available tile colors
|
||||
*/
|
||||
private static final int NUM_OF_TILE_COLORS = 8;
|
||||
/**
|
||||
* The letter bitmap
|
||||
*/
|
||||
private final Bitmap mBitmap;
|
||||
/**
|
||||
* The background color of the letter bitmap
|
||||
*/
|
||||
private final Integer mColor;
|
||||
|
||||
/**
|
||||
* Constructor for <code>LetterTileProvider</code>
|
||||
*
|
||||
* @param context The {@link Context} to use
|
||||
* @param displayName The name used to create the letter for the tile
|
||||
* @param key The key used to generate the background color for the tile
|
||||
* @param tileLetterFontSize The font size used to display the letter
|
||||
* @param width The desired width of the tile
|
||||
* @param height The desired height of the tile
|
||||
* @param backgroundColor (optional) color to use for background.
|
||||
* @param textColor (optional) color to use for text.
|
||||
*/
|
||||
public LetterBitmap(Context context, String displayName, String key, int tileLetterFontSize,
|
||||
int width, int height, Integer backgroundColor, Integer textColor) {
|
||||
TextPaint paint = new TextPaint();
|
||||
|
||||
if (textColor != null) {
|
||||
paint.setColor(textColor);
|
||||
} else {
|
||||
paint.setColor(Color.WHITE);
|
||||
}
|
||||
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setTextSize(tileLetterFontSize);
|
||||
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
|
||||
|
||||
if (backgroundColor == null) {
|
||||
mColor = getDefaultColor(context, key);
|
||||
} else {
|
||||
mColor = backgroundColor;
|
||||
}
|
||||
|
||||
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
String firstChar = displayName.substring(0, 1).toUpperCase();
|
||||
int firstCharEnd = 2;
|
||||
while (firstCharEnd <= displayName.length()) {
|
||||
// Test for the longest render-able string
|
||||
// But ignore containing only a-Z0-9 to not render things like ffi as a single character
|
||||
String test = displayName.substring(0, firstCharEnd);
|
||||
if (!isAlphabetical(test) && PaintCompat.hasGlyph(paint, test)) {
|
||||
firstChar = test;
|
||||
}
|
||||
firstCharEnd++;
|
||||
}
|
||||
|
||||
Log.d("LetterBitmap", "using sequence " + firstChar + " to render first char which has length " + firstChar.length());
|
||||
|
||||
final Canvas c = new Canvas();
|
||||
c.setBitmap(mBitmap);
|
||||
c.drawColor(mColor);
|
||||
|
||||
Rect bounds = new Rect();
|
||||
paint.getTextBounds(firstChar, 0, firstChar.length(), bounds);
|
||||
c.drawText(firstChar,
|
||||
0, firstChar.length(),
|
||||
width / 2.0f, (height - (bounds.bottom + bounds.top)) / 2.0f
|
||||
, paint);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link Bitmap} that contains a letter used in the English
|
||||
* alphabet or digit, if there is no letter or digit available, a
|
||||
* default image is shown instead
|
||||
*/
|
||||
public Bitmap getLetterTile() {
|
||||
return mBitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return background color used for letter title.
|
||||
*/
|
||||
public int getBackgroundColor() {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key The key used to generate the tile color
|
||||
* @return A new or previously chosen color for <code>key</code> used as the
|
||||
* tile background color
|
||||
*/
|
||||
private static int pickColor(String key, TypedArray colors) {
|
||||
// String.hashCode() is not supposed to change across java versions, so
|
||||
// this should guarantee the same key always maps to the same color
|
||||
final int color = Math.abs(key.hashCode()) % NUM_OF_TILE_COLORS;
|
||||
return colors.getColor(color, Color.BLACK);
|
||||
}
|
||||
|
||||
private static boolean isAlphabetical(String string) {
|
||||
return string.matches("[a-zA-Z0-9]*");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the color which the letter tile will use if no default
|
||||
* color is provided.
|
||||
*/
|
||||
public static int getDefaultColor(Context context, String key) {
|
||||
final Resources res = context.getResources();
|
||||
|
||||
TypedArray colors = res.obtainTypedArray(R.array.letter_tile_colors);
|
||||
int color = pickColor(key, colors);
|
||||
colors.recycle();
|
||||
|
||||
return color;
|
||||
}
|
||||
}
|
||||
136
app/src/main/java/protect/card_locker/LetterBitmap.kt
Normal file
136
app/src/main/java/protect/card_locker/LetterBitmap.kt
Normal file
@@ -0,0 +1,136 @@
|
||||
package protect.card_locker
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.Typeface
|
||||
import android.text.TextPaint
|
||||
import android.util.Log
|
||||
import androidx.core.graphics.PaintCompat
|
||||
import java.util.Locale
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* Original from https://github.com/andOTP/andOTP/blob/master/app/src/main/java/org/shadowice/flocke/andotp/Utilities/LetterBitmap.java
|
||||
* which was originally from http://stackoverflow.com/questions/23122088/colored-boxed-with-letters-a-la-gmail
|
||||
* Used to create a {@link Bitmap} that contains a letter used in the English
|
||||
* alphabet or digit, if there is no letter or digit available, a default image
|
||||
* is shown instead.
|
||||
*
|
||||
* @constructor Constructor for <code>LetterTileProvider</code>
|
||||
* @param context The {@link Context} to use
|
||||
* @param displayName The name used to create the letter for the tile
|
||||
* @param key The key used to generate the background color for the tile
|
||||
* @param tileLetterFontSize The font size used to display the letter
|
||||
* @param width The desired width of the tile
|
||||
* @param height The desired height of the tile
|
||||
* @param backgroundColor (optional) color to use for background.
|
||||
* @param textColor (optional) color to use for text.
|
||||
*/
|
||||
class LetterBitmap(
|
||||
context: Context, displayName: String, key: String, tileLetterFontSize: Int,
|
||||
width: Int, height: Int, backgroundColor: Int?, textColor: Int?
|
||||
) {
|
||||
/**
|
||||
* A {@link Bitmap} that contains a letter used in the English
|
||||
* alphabet or digit, if there is no letter or digit available, a
|
||||
* default image is shown instead
|
||||
*/
|
||||
private val letterTile: Bitmap
|
||||
|
||||
/**
|
||||
* The background color of the letter bitmap
|
||||
*/
|
||||
private val mColor: Int
|
||||
|
||||
init {
|
||||
val paint = TextPaint().apply {
|
||||
color = textColor ?: Color.WHITE
|
||||
textAlign = Paint.Align.CENTER
|
||||
isAntiAlias = true
|
||||
textSize = tileLetterFontSize.toFloat()
|
||||
typeface = Typeface.defaultFromStyle(Typeface.BOLD)
|
||||
}
|
||||
|
||||
mColor = backgroundColor ?: getDefaultColor(context, key)
|
||||
|
||||
this.letterTile = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
|
||||
var firstChar = displayName.substring(0, 1).uppercase(Locale.getDefault())
|
||||
var firstCharEnd = 2
|
||||
while (firstCharEnd <= displayName.length) {
|
||||
// Test for the longest render-able string
|
||||
// But ignore containing only a-Z0-9 to not render things like ffi as a single character
|
||||
val test = displayName.substring(0, firstCharEnd)
|
||||
if (!isAlphabetical(test) && PaintCompat.hasGlyph(paint, test)) {
|
||||
firstChar = test
|
||||
}
|
||||
firstCharEnd++
|
||||
}
|
||||
|
||||
Log.d(
|
||||
"LetterBitmap",
|
||||
"using sequence $firstChar to render first char which has length ${firstChar.length}"
|
||||
)
|
||||
|
||||
Canvas().apply {
|
||||
setBitmap(this@LetterBitmap.letterTile)
|
||||
drawColor(mColor)
|
||||
|
||||
val bounds = Rect()
|
||||
paint.getTextBounds(firstChar, 0, firstChar.length, bounds)
|
||||
drawText(
|
||||
firstChar,
|
||||
0, firstChar.length,
|
||||
width / 2.0f, (height - (bounds.bottom + bounds.top)) / 2.0f,
|
||||
paint
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val backgroundColor: Int
|
||||
/**
|
||||
* @return background color used for letter title.
|
||||
*/
|
||||
get() = mColor
|
||||
|
||||
fun getLetterTile(): Bitmap {
|
||||
return letterTile
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* @param key The key used to generate the tile color
|
||||
* @return A new or previously chosen color for `key` used as the
|
||||
* tile background color
|
||||
*/
|
||||
private fun pickColor(key: String, colors: TypedArray): Int {
|
||||
// String.hashCode() is not supposed to change across java versions, so
|
||||
// this should guarantee the same key always maps to the same color
|
||||
val color = abs(key.hashCode()) % colors.length()
|
||||
return colors.getColor(color, Color.BLACK)
|
||||
}
|
||||
|
||||
private fun isAlphabetical(string: String): Boolean {
|
||||
return string.matches("[a-zA-Z0-9]*".toRegex())
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the color which the letter tile will use if no default
|
||||
* color is provided.
|
||||
*/
|
||||
fun getDefaultColor(context: Context, key: String): Int {
|
||||
val res = context.resources
|
||||
|
||||
val colors = res.obtainTypedArray(R.array.letter_tile_colors)
|
||||
val color: Int = pickColor(key, colors)
|
||||
colors.recycle()
|
||||
|
||||
return color
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,8 +123,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
ChipGroup groupsChips;
|
||||
AutoCompleteTextView validFromField;
|
||||
AutoCompleteTextView expiryField;
|
||||
EditText balanceField;
|
||||
AutoCompleteTextView balanceCurrencyField;
|
||||
EditText balanceField;
|
||||
TextView cardIdFieldView;
|
||||
AutoCompleteTextView barcodeIdField;
|
||||
AutoCompleteTextView barcodeTypeField;
|
||||
@@ -148,9 +148,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
boolean onRestoring = false;
|
||||
AlertDialog confirmExitDialog = null;
|
||||
|
||||
boolean validBalance = true;
|
||||
HashMap<String, Currency> currencies = new HashMap<>();
|
||||
HashMap<String, String> currencySymbols = new HashMap<>();
|
||||
boolean validBalance = true;
|
||||
|
||||
ActivityResultLauncher<Uri> mPhotoTakerLauncher;
|
||||
ActivityResultLauncher<Intent> mPhotoPickerLauncher;
|
||||
@@ -193,14 +193,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
viewModel.setHasChanged(true);
|
||||
}
|
||||
|
||||
protected void setLoyaltyCardBalance(@NonNull BigDecimal balance) {
|
||||
viewModel.getLoyaltyCard().setBalance(balance);
|
||||
protected void setLoyaltyCardBalanceType(@Nullable Currency balanceType) {
|
||||
viewModel.getLoyaltyCard().setBalanceType(balanceType);
|
||||
|
||||
viewModel.setHasChanged(true);
|
||||
}
|
||||
|
||||
protected void setLoyaltyCardBalanceType(@Nullable Currency balanceType) {
|
||||
viewModel.getLoyaltyCard().setBalanceType(balanceType);
|
||||
protected void setLoyaltyCardBalance(@NonNull BigDecimal balance) {
|
||||
viewModel.getLoyaltyCard().setBalance(balance);
|
||||
|
||||
viewModel.setHasChanged(true);
|
||||
}
|
||||
@@ -329,8 +329,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
groupsChips = binding.groupChips;
|
||||
validFromField = binding.validFromField;
|
||||
expiryField = binding.expiryField;
|
||||
balanceField = binding.balanceField;
|
||||
balanceCurrencyField = binding.balanceCurrencyField;
|
||||
balanceField = binding.balanceField;
|
||||
cardIdFieldView = binding.cardIdView;
|
||||
barcodeIdField = binding.barcodeIdField;
|
||||
barcodeTypeField = binding.barcodeTypeField;
|
||||
@@ -373,33 +373,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
setMaterialDatePickerResultListener();
|
||||
|
||||
balanceField.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus && !onResuming && !onRestoring) {
|
||||
if (balanceField.getText().toString().isEmpty()) {
|
||||
setLoyaltyCardBalance(BigDecimal.valueOf(0));
|
||||
}
|
||||
|
||||
balanceField.setText(Utils.formatBalanceWithoutCurrencySymbol(viewModel.getLoyaltyCard().balance, viewModel.getLoyaltyCard().balanceType));
|
||||
}
|
||||
});
|
||||
|
||||
balanceField.addTextChangedListener(new SimpleTextWatcher() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
if (onResuming || onRestoring) return;
|
||||
try {
|
||||
BigDecimal balance = Utils.parseBalance(s.toString(), viewModel.getLoyaltyCard().balanceType);
|
||||
setLoyaltyCardBalance(balance);
|
||||
balanceField.setError(null);
|
||||
validBalance = true;
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
balanceField.setError(getString(R.string.balanceParsingFailed));
|
||||
validBalance = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
balanceCurrencyField.addTextChangedListener(new SimpleTextWatcher() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
@@ -452,6 +425,33 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
}
|
||||
});
|
||||
|
||||
balanceField.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus && !onResuming && !onRestoring) {
|
||||
if (balanceField.getText().toString().isEmpty()) {
|
||||
setLoyaltyCardBalance(BigDecimal.valueOf(0));
|
||||
}
|
||||
|
||||
balanceField.setText(Utils.formatBalanceWithoutCurrencySymbol(viewModel.getLoyaltyCard().balance, viewModel.getLoyaltyCard().balanceType));
|
||||
}
|
||||
});
|
||||
|
||||
balanceField.addTextChangedListener(new SimpleTextWatcher() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
if (onResuming || onRestoring) return;
|
||||
try {
|
||||
BigDecimal balance = Utils.parseBalance(s.toString(), viewModel.getLoyaltyCard().balanceType);
|
||||
setLoyaltyCardBalance(balance);
|
||||
balanceField.setError(null);
|
||||
validBalance = true;
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
balanceField.setError(getString(R.string.balanceParsingFailed));
|
||||
validBalance = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cardIdFieldView.addTextChangedListener(new SimpleTextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
@@ -719,7 +719,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
int colorOnSurface = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSurface, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
|
||||
int colorBackground = MaterialColors.getColor(this, android.R.attr.colorBackground, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
|
||||
mCropperOptions.setToolbarColor(colorSurface);
|
||||
mCropperOptions.setStatusBarColor(colorSurface);
|
||||
mCropperOptions.setToolbarWidgetColor(colorOnSurface);
|
||||
mCropperOptions.setRootViewBackgroundColor(colorBackground);
|
||||
// set tool tip to be the darker of primary color
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
@@ -704,10 +705,22 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(LoyaltyCardViewActivity.this);
|
||||
builder.setTitle(R.string.cardId);
|
||||
builder.setView(cardIdView);
|
||||
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> dialogInterface.dismiss());
|
||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss());
|
||||
builder.setNeutralButton(R.string.copy_value, (dialog, which) -> {
|
||||
copyCardIdToClipboard();
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
});
|
||||
binding.mainImageDescription.setOnLongClickListener(view -> {
|
||||
if (mainImageIndex != 0) {
|
||||
// Don't copy to clipboard, we're showing something else
|
||||
return false;
|
||||
}
|
||||
|
||||
copyCardIdToClipboard();
|
||||
return true;
|
||||
});
|
||||
|
||||
int backgroundHeaderColor = Utils.getHeaderColor(this, loyaltyCard);
|
||||
|
||||
@@ -1085,6 +1098,12 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
}
|
||||
|
||||
private void setMainImagePreviousNextButtons() {
|
||||
// Ensure the main image index is valid. After a card update, some images (front/back/barcode)
|
||||
// may have been removed, so the index should not exceed the number of available images.
|
||||
if(mainImageIndex > imageTypes.size() - 1){
|
||||
mainImageIndex = 0;
|
||||
}
|
||||
|
||||
if (imageTypes.size() < 2) {
|
||||
binding.mainLeftButton.setVisibility(View.INVISIBLE);
|
||||
binding.mainRightButton.setVisibility(View.INVISIBLE);
|
||||
@@ -1241,4 +1260,20 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyCardIdToClipboard() {
|
||||
// Take the value that’s already displayed to the user
|
||||
String value = loyaltyCard.cardId;
|
||||
|
||||
if (value == null || value.isEmpty()) {
|
||||
Toast.makeText(this, R.string.nothing_to_copy, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
ClipboardManager cm = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(getString(R.string.cardId), value);
|
||||
cm.setPrimaryClip(clip);
|
||||
|
||||
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,882 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.SearchManager;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.CursorIndexOutOfBoundsException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.core.splashscreen.SplashScreen;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import protect.card_locker.databinding.ContentMainBinding;
|
||||
import protect.card_locker.databinding.MainActivityBinding;
|
||||
import protect.card_locker.databinding.SortingOptionBinding;
|
||||
import protect.card_locker.preferences.Settings;
|
||||
import protect.card_locker.preferences.SettingsActivity;
|
||||
|
||||
public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
|
||||
private MainActivityBinding binding;
|
||||
private ContentMainBinding contentMainBinding;
|
||||
private static final String TAG = "Catima";
|
||||
public static final String RESTART_ACTIVITY_INTENT = "restart_activity_intent";
|
||||
|
||||
private static final int MEDIUM_SCALE_FACTOR_DIP = 460;
|
||||
static final String STATE_SEARCH_QUERY = "SEARCH_QUERY";
|
||||
|
||||
private SQLiteDatabase mDatabase;
|
||||
private LoyaltyCardCursorAdapter mAdapter;
|
||||
private ActionMode mCurrentActionMode;
|
||||
private SearchView mSearchView;
|
||||
private int mLoyaltyCardCount = 0;
|
||||
protected String mFilter = "";
|
||||
private String currentQuery = "";
|
||||
private String finalQuery = "";
|
||||
protected Object mGroup = null;
|
||||
protected DBHelper.LoyaltyCardOrder mOrder = DBHelper.LoyaltyCardOrder.Alpha;
|
||||
protected DBHelper.LoyaltyCardOrderDirection mOrderDirection = DBHelper.LoyaltyCardOrderDirection.Ascending;
|
||||
protected int selectedTab = 0;
|
||||
private RecyclerView mCardList;
|
||||
private View mHelpSection;
|
||||
private View mNoMatchingCardsText;
|
||||
private View mNoGroupCardsText;
|
||||
private TabLayout groupsTabLayout;
|
||||
private Runnable mUpdateLoyaltyCardListRunnable;
|
||||
private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
|
||||
private ActivityResultLauncher<Intent> mSettingsLauncher;
|
||||
|
||||
private ActionMode.Callback mCurrentActionModeCallback = new ActionMode.Callback() {
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode inputMode, Menu inputMenu) {
|
||||
inputMode.getMenuInflater().inflate(R.menu.card_longclick_menu, inputMenu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode inputMode, Menu inputMenu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode inputMode, MenuItem inputItem) {
|
||||
if (inputItem.getItemId() == R.id.action_share) {
|
||||
final ImportURIHelper importURIHelper = new ImportURIHelper(MainActivity.this);
|
||||
try {
|
||||
importURIHelper.startShareIntent(mAdapter.getSelectedItems());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Toast.makeText(MainActivity.this, R.string.failedGeneratingShareURL, Toast.LENGTH_LONG).show();
|
||||
e.printStackTrace();
|
||||
}
|
||||
inputMode.finish();
|
||||
return true;
|
||||
} else if (inputItem.getItemId() == R.id.action_edit) {
|
||||
if (mAdapter.getSelectedItemCount() != 1) {
|
||||
throw new IllegalArgumentException("Cannot edit more than 1 card at a time");
|
||||
}
|
||||
|
||||
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(LoyaltyCardEditActivity.BUNDLE_ID, mAdapter.getSelectedItems().get(0).id);
|
||||
bundle.putBoolean(LoyaltyCardEditActivity.BUNDLE_UPDATE, true);
|
||||
intent.putExtras(bundle);
|
||||
startActivity(intent);
|
||||
inputMode.finish();
|
||||
return true;
|
||||
} else if (inputItem.getItemId() == R.id.action_delete) {
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(MainActivity.this);
|
||||
// The following may seem weird, but it is necessary to give translators enough flexibility.
|
||||
// For example, in Russian, Android's plural quantity "one" actually refers to "any number ending on 1 but not ending in 11".
|
||||
// So while in English the extra non-plural form seems unnecessary duplication, it is necessary to give translators enough flexibility.
|
||||
// In here, we use the plain string when meaning exactly 1, and otherwise use the plural forms
|
||||
if (mAdapter.getSelectedItemCount() == 1) {
|
||||
builder.setTitle(R.string.deleteTitle);
|
||||
builder.setMessage(R.string.deleteConfirmation);
|
||||
} else {
|
||||
builder.setTitle(getResources().getQuantityString(R.plurals.deleteCardsTitle, mAdapter.getSelectedItemCount(), mAdapter.getSelectedItemCount()));
|
||||
builder.setMessage(getResources().getQuantityString(R.plurals.deleteCardsConfirmation, mAdapter.getSelectedItemCount(), mAdapter.getSelectedItemCount()));
|
||||
}
|
||||
|
||||
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
|
||||
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||
Log.d(TAG, "Deleting card: " + loyaltyCard.id);
|
||||
|
||||
DBHelper.deleteLoyaltyCard(mDatabase, MainActivity.this, loyaltyCard.id);
|
||||
|
||||
ShortcutHelper.removeShortcut(MainActivity.this, loyaltyCard.id);
|
||||
}
|
||||
|
||||
TabLayout.Tab tab = groupsTabLayout.getTabAt(selectedTab);
|
||||
mGroup = tab != null ? tab.getTag() : null;
|
||||
|
||||
updateLoyaltyCardList(true);
|
||||
|
||||
dialog.dismiss();
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
|
||||
return true;
|
||||
} else if (inputItem.getItemId() == R.id.action_archive) {
|
||||
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||
Log.d(TAG, "Archiving card: " + loyaltyCard.id);
|
||||
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 1);
|
||||
ShortcutHelper.removeShortcut(MainActivity.this, loyaltyCard.id);
|
||||
updateLoyaltyCardList(false);
|
||||
inputMode.finish();
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
return true;
|
||||
} else if (inputItem.getItemId() == R.id.action_unarchive) {
|
||||
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||
Log.d(TAG, "Unarchiving card: " + loyaltyCard.id);
|
||||
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 0);
|
||||
updateLoyaltyCardList(false);
|
||||
inputMode.finish();
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
return true;
|
||||
} else if (inputItem.getItemId() == R.id.action_star) {
|
||||
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||
Log.d(TAG, "Starring card: " + loyaltyCard.id);
|
||||
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 1);
|
||||
updateLoyaltyCardList(false);
|
||||
inputMode.finish();
|
||||
}
|
||||
return true;
|
||||
} else if (inputItem.getItemId() == R.id.action_unstar) {
|
||||
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||
Log.d(TAG, "Unstarring card: " + loyaltyCard.id);
|
||||
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 0);
|
||||
updateLoyaltyCardList(false);
|
||||
inputMode.finish();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode inputMode) {
|
||||
mAdapter.clearSelections();
|
||||
mCurrentActionMode = null;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle inputSavedInstanceState) {
|
||||
SplashScreen.installSplashScreen(this);
|
||||
super.onCreate(inputSavedInstanceState);
|
||||
|
||||
// Delete old cache files
|
||||
// These could be temporary images for the cropper, temporary images in LoyaltyCard toBundle/writeParcel/ etc.
|
||||
new Thread(() -> {
|
||||
long twentyFourHoursAgo = System.currentTimeMillis() - (1000 * 60 * 60 * 24);
|
||||
|
||||
File[] tempFiles = getCacheDir().listFiles();
|
||||
|
||||
if (tempFiles == null) {
|
||||
Log.e(TAG, "getCacheDir().listFiles() somehow returned null, this should never happen... Skipping cache cleanup...");
|
||||
return;
|
||||
}
|
||||
|
||||
for (File file : tempFiles) {
|
||||
if (file.lastModified() < twentyFourHoursAgo) {
|
||||
if (!file.delete()) {
|
||||
Log.w(TAG, "Failed to delete cache file " + file.getPath());
|
||||
}
|
||||
};
|
||||
}
|
||||
}).start();
|
||||
|
||||
// We should extract the share intent after we called the super.onCreate as it may need to spawn a dialog window and the app needs to be initialized to not crash
|
||||
extractIntentFields(getIntent());
|
||||
|
||||
binding = MainActivityBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
Utils.applyWindowInsets(binding.getRoot());
|
||||
setSupportActionBar(binding.toolbar);
|
||||
groupsTabLayout = binding.groups;
|
||||
contentMainBinding = ContentMainBinding.bind(binding.include.getRoot());
|
||||
|
||||
mDatabase = new DBHelper(this).getWritableDatabase();
|
||||
|
||||
mUpdateLoyaltyCardListRunnable = () -> {
|
||||
updateLoyaltyCardList(false);
|
||||
};
|
||||
|
||||
groupsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) {
|
||||
selectedTab = tab.getPosition();
|
||||
Log.d("onTabSelected", "Tab Position " + tab.getPosition());
|
||||
mGroup = tab.getTag();
|
||||
updateLoyaltyCardList(false);
|
||||
// Store active tab in Shared Preference to restore next app launch
|
||||
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
|
||||
getString(R.string.sharedpreference_active_tab),
|
||||
Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor activeTabPrefEditor = activeTabPref.edit();
|
||||
activeTabPrefEditor.putInt(getString(R.string.sharedpreference_active_tab), tab.getPosition());
|
||||
activeTabPrefEditor.apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
mHelpSection = contentMainBinding.helpSection;
|
||||
mNoMatchingCardsText = contentMainBinding.noMatchingCardsText;
|
||||
mNoGroupCardsText = contentMainBinding.noGroupCardsText;
|
||||
mCardList = contentMainBinding.list;
|
||||
|
||||
mAdapter = new LoyaltyCardCursorAdapter(this, null, this, mUpdateLoyaltyCardListRunnable);
|
||||
mCardList.setAdapter(mAdapter);
|
||||
registerForContextMenu(mCardList);
|
||||
|
||||
mBarcodeScannerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||
// Exit early if the user cancelled the scan (pressed back/home)
|
||||
if (result.getResultCode() != RESULT_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent editIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||
editIntent.putExtras(result.getData().getExtras());
|
||||
startActivity(editIntent);
|
||||
});
|
||||
|
||||
mSettingsLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||
Intent intent = result.getData();
|
||||
if (intent != null && intent.getBooleanExtra(RESTART_ACTIVITY_INTENT, false)) {
|
||||
recreate();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mSearchView != null && !mSearchView.isIconified()) {
|
||||
mSearchView.setIconified(true);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (mCurrentActionMode != null) {
|
||||
mAdapter.clearSelections();
|
||||
mCurrentActionMode.finish();
|
||||
}
|
||||
|
||||
if (mSearchView != null && !mSearchView.isIconified()) {
|
||||
mFilter = mSearchView.getQuery().toString();
|
||||
}
|
||||
// Start of active tab logic
|
||||
updateTabGroups(groupsTabLayout);
|
||||
|
||||
// Restore selected tab from Shared Preference
|
||||
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
|
||||
getString(R.string.sharedpreference_active_tab),
|
||||
Context.MODE_PRIVATE);
|
||||
selectedTab = activeTabPref.getInt(getString(R.string.sharedpreference_active_tab), 0);
|
||||
|
||||
// Restore sort preferences from Shared Preferences
|
||||
mOrder = Utils.getLoyaltyCardOrder(this);
|
||||
mOrderDirection = Utils.getLoyaltyCardOrderDirection(this);
|
||||
|
||||
mGroup = null;
|
||||
|
||||
if (groupsTabLayout.getTabCount() != 0) {
|
||||
TabLayout.Tab tab = groupsTabLayout.getTabAt(selectedTab);
|
||||
if (tab == null) {
|
||||
tab = groupsTabLayout.getTabAt(0);
|
||||
}
|
||||
|
||||
groupsTabLayout.selectTab(tab);
|
||||
assert tab != null;
|
||||
mGroup = tab.getTag();
|
||||
} else {
|
||||
scaleScreen();
|
||||
}
|
||||
|
||||
updateLoyaltyCardList(true);
|
||||
// End of active tab logic
|
||||
|
||||
FloatingActionButton addButton = binding.fabAdd;
|
||||
|
||||
addButton.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(getApplicationContext(), ScanActivity.class);
|
||||
Bundle bundle = new Bundle();
|
||||
if (selectedTab != 0) {
|
||||
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, groupsTabLayout.getTabAt(selectedTab).getText().toString());
|
||||
}
|
||||
intent.putExtras(bundle);
|
||||
mBarcodeScannerLauncher.launch(intent);
|
||||
});
|
||||
addButton.bringToFront();
|
||||
|
||||
var layoutManager = (GridLayoutManager) mCardList.getLayoutManager();
|
||||
if (layoutManager != null) {
|
||||
var settings = new Settings(this);
|
||||
layoutManager.setSpanCount(settings.getPreferredColumnCount());
|
||||
}
|
||||
}
|
||||
|
||||
private void displayCardSetupOptions(Menu menu, boolean shouldShow) {
|
||||
for (int id : new int[]{R.id.action_search, R.id.action_display_options, R.id.action_sort}) {
|
||||
menu.findItem(id).setVisible(shouldShow);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLoyaltyCardCount() {
|
||||
mLoyaltyCardCount = DBHelper.getLoyaltyCardCount(mDatabase);
|
||||
}
|
||||
|
||||
private void updateLoyaltyCardList(boolean updateCount) {
|
||||
Group group = null;
|
||||
if (mGroup != null) {
|
||||
group = (Group) mGroup;
|
||||
}
|
||||
|
||||
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase, mFilter, group, mOrder, mOrderDirection, mAdapter.showingArchivedCards() ? DBHelper.LoyaltyCardArchiveFilter.All : DBHelper.LoyaltyCardArchiveFilter.Unarchived));
|
||||
|
||||
if (updateCount) {
|
||||
updateLoyaltyCardCount();
|
||||
// Update menu icons if necessary
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
if (mLoyaltyCardCount > 0) {
|
||||
// We want the cardList to be visible regardless of the filtered match count
|
||||
// to ensure that the noMatchingCardsText doesn't end up being shown below
|
||||
// the keyboard
|
||||
mHelpSection.setVisibility(View.GONE);
|
||||
mNoGroupCardsText.setVisibility(View.GONE);
|
||||
|
||||
if (mAdapter.getItemCount() > 0) {
|
||||
mCardList.setVisibility(View.VISIBLE);
|
||||
mNoMatchingCardsText.setVisibility(View.GONE);
|
||||
} else {
|
||||
mCardList.setVisibility(View.GONE);
|
||||
if (!mFilter.isEmpty()) {
|
||||
// Actual Empty Search Result
|
||||
mNoMatchingCardsText.setVisibility(View.VISIBLE);
|
||||
mNoGroupCardsText.setVisibility(View.GONE);
|
||||
} else {
|
||||
// Group Tab with no Group Cards
|
||||
mNoMatchingCardsText.setVisibility(View.GONE);
|
||||
mNoGroupCardsText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mCardList.setVisibility(View.GONE);
|
||||
mHelpSection.setVisibility(View.VISIBLE);
|
||||
|
||||
mNoMatchingCardsText.setVisibility(View.GONE);
|
||||
mNoGroupCardsText.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (mCurrentActionMode != null) {
|
||||
mCurrentActionMode.finish();
|
||||
}
|
||||
|
||||
new ListWidget().updateAll(mAdapter.mContext);
|
||||
}
|
||||
|
||||
private void processParseResultList(List<ParseResult> parseResultList, String group, boolean closeAppOnNoBarcode) {
|
||||
if (parseResultList.isEmpty()) {
|
||||
throw new IllegalArgumentException("parseResultList may not be empty");
|
||||
}
|
||||
|
||||
Utils.makeUserChooseParseResultFromList(MainActivity.this, parseResultList, new ParseResultListDisambiguatorCallback() {
|
||||
@Override
|
||||
public void onUserChoseParseResult(ParseResult parseResult) {
|
||||
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||
Bundle bundle = parseResult.toLoyaltyCardBundle(MainActivity.this);
|
||||
if (group != null) {
|
||||
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
|
||||
}
|
||||
intent.putExtras(bundle);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserDismissedSelector() {
|
||||
if (closeAppOnNoBarcode) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onSharedIntent(Intent intent) {
|
||||
String receivedAction = intent.getAction();
|
||||
String receivedType = intent.getType();
|
||||
|
||||
if (receivedAction == null || receivedType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<ParseResult> parseResultList;
|
||||
|
||||
// Check for shared text
|
||||
if (receivedAction.equals(Intent.ACTION_SEND) && receivedType.equals("text/plain")) {
|
||||
LoyaltyCard loyaltyCard = new LoyaltyCard();
|
||||
loyaltyCard.setCardId(intent.getStringExtra(Intent.EXTRA_TEXT));
|
||||
parseResultList = Collections.singletonList(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
|
||||
} else {
|
||||
// Parse whatever file was sent, regardless of opening or sharing
|
||||
Uri data;
|
||||
if (receivedAction.equals(Intent.ACTION_VIEW)) {
|
||||
data = intent.getData();
|
||||
} else if (receivedAction.equals(Intent.ACTION_SEND)) {
|
||||
data = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
} else {
|
||||
Log.e(TAG, "Wrong action type to parse intent");
|
||||
return;
|
||||
}
|
||||
|
||||
if (receivedType.startsWith("image/")) {
|
||||
parseResultList = Utils.retrieveBarcodesFromImage(this, data);
|
||||
} else if (receivedType.equals("application/pdf")) {
|
||||
parseResultList = Utils.retrieveBarcodesFromPdf(this, data);
|
||||
} else if (Arrays.asList("application/vnd.apple.pkpass", "application/vnd-com.apple.pkpass").contains(receivedType)) {
|
||||
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data);
|
||||
} else if (receivedType.equals("application/vnd.espass-espass")) {
|
||||
// FIXME: espass is not pkpass
|
||||
// However, several users stated in https://github.com/CatimaLoyalty/Android/issues/2197 that the formats are extremely similar to the point they could rename an .espass file to .pkpass and have it imported
|
||||
// So it makes sense to "unofficially" treat it as a PKPASS for now, even though not completely correct
|
||||
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data);
|
||||
} else if (receivedType.equals("application/vnd.apple.pkpasses")) {
|
||||
parseResultList = Utils.retrieveBarcodesFromPkPasses(this, data);
|
||||
} else {
|
||||
Log.e(TAG, "Wrong mime-type");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Give up if we should parse but there is nothing to parse
|
||||
if (parseResultList == null || parseResultList.isEmpty()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
processParseResultList(parseResultList, null, true);
|
||||
}
|
||||
|
||||
private void extractIntentFields(Intent intent) {
|
||||
onSharedIntent(intent);
|
||||
}
|
||||
|
||||
public void updateTabGroups(TabLayout groupsTabLayout) {
|
||||
List<Group> newGroups = DBHelper.getGroups(mDatabase);
|
||||
|
||||
if (newGroups.size() == 0) {
|
||||
groupsTabLayout.removeAllTabs();
|
||||
groupsTabLayout.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
groupsTabLayout.removeAllTabs();
|
||||
|
||||
TabLayout.Tab allTab = groupsTabLayout.newTab();
|
||||
allTab.setText(R.string.all);
|
||||
allTab.setTag(null);
|
||||
groupsTabLayout.addTab(allTab, false);
|
||||
|
||||
for (Group group : newGroups) {
|
||||
TabLayout.Tab tab = groupsTabLayout.newTab();
|
||||
tab.setText(group._id);
|
||||
tab.setTag(group);
|
||||
groupsTabLayout.addTab(tab, false);
|
||||
}
|
||||
|
||||
groupsTabLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
// Saving currentQuery to finalQuery for user, this will be used to restore search history, happens when user clicks a card from list
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
finalQuery = currentQuery;
|
||||
// Putting the query also into outState for later use in onRestoreInstanceState when rotating screen
|
||||
if (mSearchView != null) {
|
||||
outState.putString(STATE_SEARCH_QUERY, finalQuery);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
// Restoring instance state when rotation of screen happens with the goal to restore search query for user
|
||||
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
finalQuery = savedInstanceState.getString(STATE_SEARCH_QUERY, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu inputMenu) {
|
||||
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
|
||||
|
||||
displayCardSetupOptions(inputMenu, mLoyaltyCardCount > 0);
|
||||
|
||||
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
|
||||
if (searchManager != null) {
|
||||
MenuItem searchMenuItem = inputMenu.findItem(R.id.action_search);
|
||||
mSearchView = (SearchView) searchMenuItem.getActionView();
|
||||
mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
|
||||
mSearchView.setSubmitButtonEnabled(false);
|
||||
mSearchView.setOnCloseListener(() -> {
|
||||
invalidateOptionsMenu();
|
||||
return false;
|
||||
});
|
||||
|
||||
/*
|
||||
* On Android 13 and later, pressing Back while the search view is open hides the keyboard
|
||||
* and collapses the search view at the same time.
|
||||
* This brings back the old behavior on Android 12 and lower: pressing Back once
|
||||
* hides the keyboard, press again while keyboard is hidden to collapse the search view.
|
||||
*/
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
searchMenuItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(@NonNull MenuItem item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(@NonNull MenuItem item) {
|
||||
if (mSearchView.hasFocus()) {
|
||||
mSearchView.clearFocus();
|
||||
return false;
|
||||
}
|
||||
currentQuery = "";
|
||||
mFilter = "";
|
||||
updateLoyaltyCardList(false);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
mFilter = newText;
|
||||
// New logic to ensure search history after coming back from picked card - user will see the last search query
|
||||
if (newText.isEmpty()) {
|
||||
if(!finalQuery.isEmpty()){
|
||||
// Setting the query text for user after coming back from picked card from finalQuery
|
||||
mSearchView.setQuery(finalQuery, false);
|
||||
}
|
||||
else if(!currentQuery.isEmpty()){
|
||||
// Else if is needed in case user deletes search - expected behaviour is to show all cards
|
||||
currentQuery = "";
|
||||
mSearchView.setQuery(currentQuery, false);
|
||||
}
|
||||
} else {
|
||||
// Setting search query each time user changes the text in search to temporary variable to be used later in finalQuery String which will be used to restore search history
|
||||
currentQuery = newText;
|
||||
}
|
||||
TabLayout.Tab currentTab = groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition());
|
||||
mGroup = currentTab != null ? currentTab.getTag() : null;
|
||||
|
||||
updateLoyaltyCardList(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
// Check if we came from a picked card back to search, in that case we want to show the search view with previous search query
|
||||
if(!finalQuery.isEmpty()){
|
||||
// Expand the search view to show the query
|
||||
searchMenuItem.expandActionView();
|
||||
// Setting the query text to empty String due to behaviour of onQueryTextChange after coming back from picked card - onQueryTextChange is called automatically without users interaction
|
||||
finalQuery = "";
|
||||
mSearchView.setQuery(currentQuery, false);
|
||||
}
|
||||
}
|
||||
|
||||
return super.onCreateOptionsMenu(inputMenu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem inputItem) {
|
||||
int id = inputItem.getItemId();
|
||||
|
||||
if (id == android.R.id.home) {
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
}
|
||||
|
||||
if (id == R.id.action_display_options) {
|
||||
mAdapter.showDisplayOptionsDialog();
|
||||
invalidateOptionsMenu();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (id == R.id.action_sort) {
|
||||
AtomicInteger currentIndex = new AtomicInteger();
|
||||
List<DBHelper.LoyaltyCardOrder> loyaltyCardOrders = Arrays.asList(DBHelper.LoyaltyCardOrder.values());
|
||||
for (int i = 0; i < loyaltyCardOrders.size(); i++) {
|
||||
if (mOrder == loyaltyCardOrders.get(i)) {
|
||||
currentIndex.set(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(MainActivity.this);
|
||||
builder.setTitle(R.string.sort_by);
|
||||
|
||||
SortingOptionBinding sortingOptionBinding = SortingOptionBinding
|
||||
.inflate(LayoutInflater.from(MainActivity.this), null, false);
|
||||
final View customLayout = sortingOptionBinding.getRoot();
|
||||
builder.setView(customLayout);
|
||||
|
||||
CheckBox showReversed = sortingOptionBinding.checkBoxReverse;
|
||||
|
||||
|
||||
showReversed.setChecked(mOrderDirection == DBHelper.LoyaltyCardOrderDirection.Descending);
|
||||
|
||||
|
||||
builder.setSingleChoiceItems(R.array.sort_types_array, currentIndex.get(), (dialog, which) -> currentIndex.set(which));
|
||||
|
||||
builder.setPositiveButton(R.string.sort, (dialog, which) -> {
|
||||
|
||||
setSort(
|
||||
loyaltyCardOrders.get(currentIndex.get()),
|
||||
showReversed.isChecked() ? DBHelper.LoyaltyCardOrderDirection.Descending : DBHelper.LoyaltyCardOrderDirection.Ascending
|
||||
);
|
||||
|
||||
new ListWidget().updateAll(this);
|
||||
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (id == R.id.action_manage_groups) {
|
||||
Intent i = new Intent(getApplicationContext(), ManageGroupsActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (id == R.id.action_import_export) {
|
||||
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (id == R.id.action_settings) {
|
||||
Intent i = new Intent(getApplicationContext(), SettingsActivity.class);
|
||||
mSettingsLauncher.launch(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (id == R.id.action_about) {
|
||||
Intent i = new Intent(getApplicationContext(), AboutActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return super.onOptionsItemSelected(inputItem);
|
||||
}
|
||||
|
||||
private void setSort(DBHelper.LoyaltyCardOrder order, DBHelper.LoyaltyCardOrderDirection direction) {
|
||||
// Update values
|
||||
mOrder = order;
|
||||
mOrderDirection = direction;
|
||||
|
||||
// Store in Shared Preference to restore next app launch
|
||||
SharedPreferences sortPref = getApplicationContext().getSharedPreferences(
|
||||
getString(R.string.sharedpreference_sort),
|
||||
Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor sortPrefEditor = sortPref.edit();
|
||||
sortPrefEditor.putString(getString(R.string.sharedpreference_sort_order), order.name());
|
||||
sortPrefEditor.putString(getString(R.string.sharedpreference_sort_direction), direction.name());
|
||||
sortPrefEditor.apply();
|
||||
|
||||
// Update card list
|
||||
updateLoyaltyCardList(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRowLongClicked(int inputPosition) {
|
||||
enableActionMode(inputPosition);
|
||||
}
|
||||
|
||||
private void enableActionMode(int inputPosition) {
|
||||
if (mCurrentActionMode == null) {
|
||||
mCurrentActionMode = startSupportActionMode(mCurrentActionModeCallback);
|
||||
}
|
||||
toggleSelection(inputPosition);
|
||||
}
|
||||
|
||||
private void scaleScreen() {
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
|
||||
int screenHeight = displayMetrics.heightPixels;
|
||||
float mediumSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,MEDIUM_SCALE_FACTOR_DIP,getResources().getDisplayMetrics());
|
||||
boolean shouldScaleSmaller = screenHeight < mediumSizePx;
|
||||
|
||||
binding.include.welcomeIcon.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
private void toggleSelection(int inputPosition) {
|
||||
mAdapter.toggleSelection(inputPosition);
|
||||
int count = mAdapter.getSelectedItemCount();
|
||||
|
||||
if (count == 0) {
|
||||
mCurrentActionMode.finish();
|
||||
} else {
|
||||
mCurrentActionMode.setTitle(getResources().getQuantityString(R.plurals.selectedCardCount, count, count));
|
||||
|
||||
MenuItem editItem = mCurrentActionMode.getMenu().findItem(R.id.action_edit);
|
||||
MenuItem archiveItem = mCurrentActionMode.getMenu().findItem(R.id.action_archive);
|
||||
MenuItem unarchiveItem = mCurrentActionMode.getMenu().findItem(R.id.action_unarchive);
|
||||
MenuItem starItem = mCurrentActionMode.getMenu().findItem(R.id.action_star);
|
||||
MenuItem unstarItem = mCurrentActionMode.getMenu().findItem(R.id.action_unstar);
|
||||
|
||||
boolean hasStarred = false;
|
||||
boolean hasUnstarred = false;
|
||||
boolean hasArchived = false;
|
||||
boolean hasUnarchived = false;
|
||||
|
||||
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||
if (loyaltyCard.starStatus == 1) {
|
||||
hasStarred = true;
|
||||
} else {
|
||||
hasUnstarred = true;
|
||||
}
|
||||
|
||||
if (loyaltyCard.archiveStatus == 1) {
|
||||
hasArchived = true;
|
||||
} else {
|
||||
hasUnarchived = true;
|
||||
}
|
||||
|
||||
// We have all types, no need to keep checking
|
||||
if (hasStarred && hasUnstarred && hasArchived && hasUnarchived) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unarchiveItem.setVisible(hasArchived);
|
||||
archiveItem.setVisible(hasUnarchived);
|
||||
|
||||
if (count == 1) {
|
||||
starItem.setVisible(!hasStarred);
|
||||
unstarItem.setVisible(!hasUnstarred);
|
||||
editItem.setVisible(true);
|
||||
editItem.setEnabled(true);
|
||||
} else {
|
||||
starItem.setVisible(hasUnstarred);
|
||||
unstarItem.setVisible(hasStarred);
|
||||
|
||||
editItem.setVisible(false);
|
||||
editItem.setEnabled(false);
|
||||
}
|
||||
|
||||
mCurrentActionMode.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onRowClicked(int inputPosition) {
|
||||
if (mAdapter.getSelectedItemCount() > 0) {
|
||||
enableActionMode(inputPosition);
|
||||
} else {
|
||||
// FIXME
|
||||
//
|
||||
// There is a really nasty edge case that can happen when someone taps a card but right
|
||||
// after it swipes (very small window, hard to reproduce). The cursor gets replaced and
|
||||
// may not have a card at the ID number that is returned from onRowClicked.
|
||||
//
|
||||
// The proper fix, obviously, would involve makes sure an onFling can't happen while a
|
||||
// click is being processed. Sadly, I have not yet found a way to make that possible.
|
||||
LoyaltyCard loyaltyCard;
|
||||
try {
|
||||
loyaltyCard = mAdapter.getCard(inputPosition);
|
||||
} catch (CursorIndexOutOfBoundsException e) {
|
||||
Log.w(TAG, "Prevented crash from tap + swipe on ID " + inputPosition + ": " + e);
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, LoyaltyCardViewActivity.class);
|
||||
intent.setAction("");
|
||||
final Bundle b = new Bundle();
|
||||
b.putInt(LoyaltyCardViewActivity.BUNDLE_ID, loyaltyCard.id);
|
||||
|
||||
ArrayList<Integer> cardList = new ArrayList<>();
|
||||
for (int i = 0; i < mAdapter.getItemCount(); i++) {
|
||||
cardList.add(mAdapter.getCard(i).id);
|
||||
}
|
||||
|
||||
b.putIntegerArrayList(LoyaltyCardViewActivity.BUNDLE_CARDLIST, cardList);
|
||||
intent.putExtras(b);
|
||||
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
922
app/src/main/java/protect/card_locker/MainActivity.kt
Normal file
922
app/src/main/java/protect/card_locker/MainActivity.kt
Normal file
@@ -0,0 +1,922 @@
|
||||
package protect.card_locker
|
||||
|
||||
import android.app.SearchManager
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.database.CursorIndexOutOfBoundsException
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
|
||||
import protect.card_locker.DBHelper.LoyaltyCardOrder
|
||||
import protect.card_locker.DBHelper.LoyaltyCardOrderDirection
|
||||
import protect.card_locker.LoyaltyCardCursorAdapter.CardAdapterListener
|
||||
import protect.card_locker.databinding.ContentMainBinding
|
||||
import protect.card_locker.databinding.MainActivityBinding
|
||||
import protect.card_locker.databinding.SortingOptionBinding
|
||||
import protect.card_locker.preferences.Settings
|
||||
import protect.card_locker.preferences.SettingsActivity
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import androidx.core.content.edit
|
||||
|
||||
class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
private lateinit var binding: MainActivityBinding
|
||||
private lateinit var contentMainBinding: ContentMainBinding
|
||||
private lateinit var mDatabase: SQLiteDatabase
|
||||
private lateinit var mAdapter: LoyaltyCardCursorAdapter
|
||||
private var mCurrentActionMode: ActionMode? = null
|
||||
private var mSearchView: SearchView? = null
|
||||
private var mLoyaltyCardCount = 0
|
||||
@JvmField
|
||||
var mFilter: String = ""
|
||||
private var currentQuery = ""
|
||||
private var finalQuery = ""
|
||||
private var mGroup: Any? = null
|
||||
private var mOrder: LoyaltyCardOrder = LoyaltyCardOrder.Alpha
|
||||
private var mOrderDirection: LoyaltyCardOrderDirection = LoyaltyCardOrderDirection.Ascending
|
||||
private var selectedTab: Int = 0
|
||||
private lateinit var groupsTabLayout: TabLayout
|
||||
private lateinit var mUpdateLoyaltyCardListRunnable: Runnable
|
||||
private lateinit var mBarcodeScannerLauncher: ActivityResultLauncher<Intent?>
|
||||
private lateinit var mSettingsLauncher: ActivityResultLauncher<Intent?>
|
||||
|
||||
private val mCurrentActionModeCallback: ActionMode.Callback = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(inputMode: ActionMode, inputMenu: Menu?): Boolean {
|
||||
inputMode.menuInflater.inflate(R.menu.card_longclick_menu, inputMenu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(inputMode: ActionMode?, inputMenu: Menu?): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(inputMode: ActionMode, inputItem: MenuItem): Boolean {
|
||||
when (inputItem.itemId) {
|
||||
R.id.action_share -> {
|
||||
try {
|
||||
ImportURIHelper(this@MainActivity).startShareIntent(mAdapter.getSelectedItems())
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
Toast.makeText(
|
||||
this@MainActivity,
|
||||
R.string.failedGeneratingShareURL,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
e.printStackTrace()
|
||||
}
|
||||
inputMode.finish()
|
||||
return true
|
||||
}
|
||||
R.id.action_edit -> {
|
||||
require(mAdapter.selectedItemCount == 1) { "Cannot edit more than 1 card at a time" }
|
||||
|
||||
startActivity(
|
||||
Intent(applicationContext, LoyaltyCardEditActivity::class.java).apply {
|
||||
putExtras(Bundle().apply {
|
||||
putInt(
|
||||
LoyaltyCardEditActivity.BUNDLE_ID,
|
||||
mAdapter.getSelectedItems()[0].id
|
||||
)
|
||||
putBoolean(LoyaltyCardEditActivity.BUNDLE_UPDATE, true)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
inputMode.finish()
|
||||
return true
|
||||
}
|
||||
R.id.action_delete -> {
|
||||
MaterialAlertDialogBuilder(this@MainActivity).apply {
|
||||
// The following may seem weird, but it is necessary to give translators enough flexibility.
|
||||
// For example, in Russian, Android's plural quantity "one" actually refers to "any number ending on 1 but not ending in 11".
|
||||
// So while in English the extra non-plural form seems unnecessary duplication, it is necessary to give translators enough flexibility.
|
||||
// In here, we use the plain string when meaning exactly 1, and otherwise use the plural forms
|
||||
if (mAdapter.selectedItemCount == 1) {
|
||||
setTitle(R.string.deleteTitle)
|
||||
setMessage(R.string.deleteConfirmation)
|
||||
} else {
|
||||
setTitle(
|
||||
getResources().getQuantityString(
|
||||
R.plurals.deleteCardsTitle,
|
||||
mAdapter.selectedItemCount,
|
||||
mAdapter.selectedItemCount
|
||||
)
|
||||
)
|
||||
setMessage(
|
||||
getResources().getQuantityString(
|
||||
R.plurals.deleteCardsConfirmation,
|
||||
mAdapter.selectedItemCount,
|
||||
mAdapter.selectedItemCount
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
setPositiveButton(
|
||||
R.string.confirm
|
||||
) { dialog, _ ->
|
||||
for (loyaltyCard in mAdapter.getSelectedItems()) {
|
||||
Log.d(TAG, "Deleting card: " + loyaltyCard.id)
|
||||
|
||||
DBHelper.deleteLoyaltyCard(mDatabase, this@MainActivity, loyaltyCard.id)
|
||||
|
||||
ShortcutHelper.removeShortcut(this@MainActivity, loyaltyCard.id)
|
||||
}
|
||||
val tab = groupsTabLayout.getTabAt(selectedTab)
|
||||
mGroup = tab?.tag
|
||||
|
||||
updateLoyaltyCardList(true)
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
}.create().show()
|
||||
|
||||
return true
|
||||
}
|
||||
R.id.action_archive -> {
|
||||
for (loyaltyCard in mAdapter.getSelectedItems()) {
|
||||
Log.d(TAG, "Archiving card: " + loyaltyCard.id)
|
||||
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 1)
|
||||
ShortcutHelper.removeShortcut(this@MainActivity, loyaltyCard.id)
|
||||
updateLoyaltyCardList(false)
|
||||
inputMode.finish()
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
return true
|
||||
}
|
||||
R.id.action_unarchive -> {
|
||||
for (loyaltyCard in mAdapter.getSelectedItems()) {
|
||||
Log.d(TAG, "Unarchiving card: " + loyaltyCard.id)
|
||||
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 0)
|
||||
updateLoyaltyCardList(false)
|
||||
inputMode.finish()
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
return true
|
||||
}
|
||||
R.id.action_star -> {
|
||||
for (loyaltyCard in mAdapter.getSelectedItems()) {
|
||||
Log.d(TAG, "Starring card: " + loyaltyCard.id)
|
||||
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 1)
|
||||
updateLoyaltyCardList(false)
|
||||
inputMode.finish()
|
||||
}
|
||||
return true
|
||||
}
|
||||
R.id.action_unstar -> {
|
||||
for (loyaltyCard in mAdapter.getSelectedItems()) {
|
||||
Log.d(TAG, "Unstarring card: " + loyaltyCard.id)
|
||||
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 0)
|
||||
updateLoyaltyCardList(false)
|
||||
inputMode.finish()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(inputMode: ActionMode?) {
|
||||
mAdapter.clearSelections()
|
||||
mCurrentActionMode = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(inputSavedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
super.onCreate(inputSavedInstanceState)
|
||||
|
||||
// Delete old cache files
|
||||
// These could be temporary images for the cropper, temporary images in LoyaltyCard toBundle/writeParcel/ etc.
|
||||
Thread {
|
||||
val twentyFourHoursAgo = System.currentTimeMillis() - (1000 * 60 * 60 * 24)
|
||||
val tempFiles = cacheDir.listFiles()
|
||||
|
||||
if (tempFiles == null) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"getCacheDir().listFiles() somehow returned null, this should never happen... Skipping cache cleanup..."
|
||||
)
|
||||
return@Thread
|
||||
}
|
||||
for (file in tempFiles) {
|
||||
if (file.lastModified() < twentyFourHoursAgo) {
|
||||
if (!file.delete()) {
|
||||
Log.w(TAG, "Failed to delete cache file " + file.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
|
||||
// We should extract the share intent after we called the super.onCreate as it may need to spawn a dialog window and the app needs to be initialized to not crash
|
||||
extractIntentFields(intent)
|
||||
|
||||
binding = MainActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.getRoot())
|
||||
Utils.applyWindowInsets(binding.getRoot())
|
||||
setSupportActionBar(binding.toolbar)
|
||||
groupsTabLayout = binding.groups
|
||||
contentMainBinding = ContentMainBinding.bind(binding.include.getRoot())
|
||||
|
||||
mDatabase = DBHelper(this).writableDatabase
|
||||
|
||||
mUpdateLoyaltyCardListRunnable = Runnable {
|
||||
updateLoyaltyCardList(false)
|
||||
}
|
||||
|
||||
groupsTabLayout.addOnTabSelectedListener(object : OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||
selectedTab = tab.position
|
||||
Log.d("onTabSelected", "Tab Position " + tab.position)
|
||||
mGroup = tab.tag
|
||||
updateLoyaltyCardList(false)
|
||||
// Store active tab in Shared Preference to restore next app launch
|
||||
applicationContext.getSharedPreferences(
|
||||
getString(R.string.sharedpreference_active_tab),
|
||||
MODE_PRIVATE
|
||||
).edit {
|
||||
putInt(
|
||||
getString(R.string.sharedpreference_active_tab),
|
||||
tab.position
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab?) {
|
||||
}
|
||||
|
||||
override fun onTabReselected(tab: TabLayout.Tab?) {
|
||||
}
|
||||
})
|
||||
|
||||
mAdapter = LoyaltyCardCursorAdapter(this, null, this, mUpdateLoyaltyCardListRunnable)
|
||||
contentMainBinding.list.setAdapter(mAdapter)
|
||||
registerForContextMenu(contentMainBinding.list)
|
||||
|
||||
mBarcodeScannerLauncher = registerForActivityResult(
|
||||
StartActivityForResult(),
|
||||
ActivityResultCallback registerForActivityResult@{ result: ActivityResult? ->
|
||||
// Exit early if the user cancelled the scan (pressed back/home)
|
||||
if (result == null || result.resultCode != RESULT_OK) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
startActivity(
|
||||
Intent(applicationContext, LoyaltyCardEditActivity::class.java).apply {
|
||||
putExtras(result.data!!.extras!!)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
mSettingsLauncher = registerForActivityResult(
|
||||
StartActivityForResult()
|
||||
) { result: ActivityResult? ->
|
||||
if (result?.resultCode == RESULT_OK) {
|
||||
val intent = result.data
|
||||
if (intent != null && intent.getBooleanExtra(RESTART_ACTIVITY_INTENT, false)) {
|
||||
recreate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (mSearchView != null && !mSearchView!!.isIconified) {
|
||||
mSearchView!!.isIconified = true
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
if (mCurrentActionMode != null) {
|
||||
mAdapter.clearSelections()
|
||||
mCurrentActionMode!!.finish()
|
||||
}
|
||||
|
||||
if (mSearchView != null && !mSearchView!!.isIconified) {
|
||||
mFilter = mSearchView!!.query.toString()
|
||||
}
|
||||
// Start of active tab logic
|
||||
updateTabGroups(groupsTabLayout)
|
||||
|
||||
// Restore selected tab from Shared Preference
|
||||
selectedTab = applicationContext.getSharedPreferences(
|
||||
getString(R.string.sharedpreference_active_tab),
|
||||
MODE_PRIVATE
|
||||
).getInt(getString(R.string.sharedpreference_active_tab), 0)
|
||||
|
||||
// Restore sort preferences from Shared Preferences
|
||||
mOrder = Utils.getLoyaltyCardOrder(this)
|
||||
mOrderDirection = Utils.getLoyaltyCardOrderDirection(this)
|
||||
|
||||
mGroup = null
|
||||
|
||||
if (groupsTabLayout.tabCount != 0) {
|
||||
var tab = groupsTabLayout.getTabAt(selectedTab)
|
||||
if (tab == null) {
|
||||
tab = groupsTabLayout.getTabAt(0)
|
||||
}
|
||||
|
||||
groupsTabLayout.selectTab(tab)
|
||||
checkNotNull(tab)
|
||||
mGroup = tab.tag
|
||||
} else {
|
||||
scaleScreen()
|
||||
}
|
||||
|
||||
updateLoyaltyCardList(true)
|
||||
|
||||
// End of active tab logic
|
||||
|
||||
binding.fabAdd.setOnClickListener {
|
||||
mBarcodeScannerLauncher.launch(
|
||||
Intent(applicationContext, ScanActivity::class.java).apply {
|
||||
putExtras(Bundle().apply {
|
||||
if (selectedTab != 0) {
|
||||
putString(
|
||||
LoyaltyCardEditActivity.BUNDLE_ADDGROUP,
|
||||
groupsTabLayout.getTabAt(selectedTab)!!.text.toString()
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
binding.fabAdd.bringToFront()
|
||||
|
||||
val layoutManager = contentMainBinding.list.layoutManager as GridLayoutManager?
|
||||
if (layoutManager != null) {
|
||||
val settings = Settings(this)
|
||||
layoutManager.setSpanCount(settings.getPreferredColumnCount())
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayCardSetupOptions(menu: Menu, shouldShow: Boolean) {
|
||||
for (id in intArrayOf(R.id.action_search, R.id.action_display_options, R.id.action_sort)) {
|
||||
menu.findItem(id).isVisible = shouldShow
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLoyaltyCardCount() {
|
||||
mLoyaltyCardCount = DBHelper.getLoyaltyCardCount(mDatabase)
|
||||
}
|
||||
|
||||
private fun updateLoyaltyCardList(updateCount: Boolean) {
|
||||
var group: Group? = null
|
||||
if (mGroup != null) {
|
||||
group = mGroup as Group
|
||||
}
|
||||
|
||||
mAdapter.swapCursor(
|
||||
DBHelper.getLoyaltyCardCursor(
|
||||
mDatabase,
|
||||
mFilter,
|
||||
group,
|
||||
mOrder,
|
||||
mOrderDirection,
|
||||
if (mAdapter.showingArchivedCards()) DBHelper.LoyaltyCardArchiveFilter.All else DBHelper.LoyaltyCardArchiveFilter.Unarchived
|
||||
)
|
||||
)
|
||||
|
||||
if (updateCount) {
|
||||
updateLoyaltyCardCount()
|
||||
// Update menu icons if necessary
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
if (mLoyaltyCardCount > 0) {
|
||||
// We want the cardList to be visible regardless of the filtered match count
|
||||
// to ensure that the noMatchingCardsText doesn't end up being shown below
|
||||
// the keyboard
|
||||
contentMainBinding.helpSection.visibility = View.GONE
|
||||
contentMainBinding.noGroupCardsText.visibility = View.GONE
|
||||
|
||||
if (mAdapter.itemCount > 0) {
|
||||
contentMainBinding.list.visibility = View.VISIBLE
|
||||
contentMainBinding.noMatchingCardsText.visibility = View.GONE
|
||||
} else {
|
||||
contentMainBinding.list.visibility = View.GONE
|
||||
if (!mFilter.isEmpty()) {
|
||||
// Actual Empty Search Result
|
||||
contentMainBinding.noMatchingCardsText.visibility = View.VISIBLE
|
||||
contentMainBinding.noGroupCardsText.visibility = View.GONE
|
||||
} else {
|
||||
// Group Tab with no Group Cards
|
||||
contentMainBinding.noMatchingCardsText.visibility = View.GONE
|
||||
contentMainBinding.noGroupCardsText.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
} else {
|
||||
contentMainBinding.list.visibility = View.GONE
|
||||
contentMainBinding.helpSection.visibility = View.VISIBLE
|
||||
|
||||
contentMainBinding.noMatchingCardsText.visibility = View.GONE
|
||||
contentMainBinding.noGroupCardsText.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (mCurrentActionMode != null) {
|
||||
mCurrentActionMode!!.finish()
|
||||
}
|
||||
|
||||
ListWidget().updateAll(mAdapter.mContext)
|
||||
}
|
||||
|
||||
private fun processParseResultList(
|
||||
parseResultList: MutableList<ParseResult?>,
|
||||
group: String?,
|
||||
closeAppOnNoBarcode: Boolean
|
||||
) {
|
||||
require(!parseResultList.isEmpty()) { "parseResultList may not be empty" }
|
||||
|
||||
Utils.makeUserChooseParseResultFromList(
|
||||
this@MainActivity,
|
||||
parseResultList,
|
||||
object : ParseResultListDisambiguatorCallback {
|
||||
override fun onUserChoseParseResult(parseResult: ParseResult) {
|
||||
val intent =
|
||||
Intent(applicationContext, LoyaltyCardEditActivity::class.java)
|
||||
val bundle = parseResult.toLoyaltyCardBundle(this@MainActivity)
|
||||
if (group != null) {
|
||||
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group)
|
||||
}
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
override fun onUserDismissedSelector() {
|
||||
if (closeAppOnNoBarcode) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun onSharedIntent(intent: Intent) {
|
||||
val receivedAction = intent.action
|
||||
val receivedType = intent.type
|
||||
|
||||
if (receivedAction == null || receivedType == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val parseResultList: MutableList<ParseResult?>?
|
||||
|
||||
// Check for shared text
|
||||
if (receivedAction == Intent.ACTION_SEND && receivedType == "text/plain") {
|
||||
val loyaltyCard = LoyaltyCard()
|
||||
loyaltyCard.setCardId(intent.getStringExtra(Intent.EXTRA_TEXT)!!)
|
||||
parseResultList = mutableListOf(ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard))
|
||||
} else {
|
||||
// Parse whatever file was sent, regardless of opening or sharing
|
||||
val data: Uri? = when (receivedAction) {
|
||||
Intent.ACTION_VIEW -> {
|
||||
intent.data
|
||||
}
|
||||
Intent.ACTION_SEND -> {
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM)
|
||||
}
|
||||
else -> {
|
||||
Log.e(TAG, "Wrong action type to parse intent")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (receivedType.startsWith("image/")) {
|
||||
parseResultList = Utils.retrieveBarcodesFromImage(this, data)
|
||||
} else if (receivedType == "application/pdf") {
|
||||
parseResultList = Utils.retrieveBarcodesFromPdf(this, data)
|
||||
} else if (mutableListOf<String?>(
|
||||
"application/vnd.apple.pkpass",
|
||||
"application/vnd-com.apple.pkpass"
|
||||
).contains(receivedType)
|
||||
) {
|
||||
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data)
|
||||
} else if (receivedType == "application/vnd.espass-espass") {
|
||||
// FIXME: espass is not pkpass
|
||||
// However, several users stated in https://github.com/CatimaLoyalty/Android/issues/2197 that the formats are extremely similar to the point they could rename an .espass file to .pkpass and have it imported
|
||||
// So it makes sense to "unofficially" treat it as a PKPASS for now, even though not completely correct
|
||||
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data)
|
||||
} else if (receivedType == "application/vnd.apple.pkpasses") {
|
||||
parseResultList = Utils.retrieveBarcodesFromPkPasses(this, data)
|
||||
} else {
|
||||
Log.e(TAG, "Wrong mime-type")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Give up if we should parse but there is nothing to parse
|
||||
if (parseResultList == null || parseResultList.isEmpty()) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
processParseResultList(parseResultList, null, true)
|
||||
}
|
||||
|
||||
private fun extractIntentFields(intent: Intent) {
|
||||
onSharedIntent(intent)
|
||||
}
|
||||
|
||||
fun updateTabGroups(groupsTabLayout: TabLayout) {
|
||||
val newGroups = DBHelper.getGroups(mDatabase)
|
||||
|
||||
if (newGroups.isEmpty()) {
|
||||
groupsTabLayout.removeAllTabs()
|
||||
groupsTabLayout.visibility = View.GONE
|
||||
return
|
||||
}
|
||||
|
||||
groupsTabLayout.removeAllTabs()
|
||||
groupsTabLayout.addTab(
|
||||
groupsTabLayout.newTab().apply {
|
||||
setText(R.string.all)
|
||||
tag = null
|
||||
},
|
||||
false
|
||||
)
|
||||
|
||||
for (group in newGroups) {
|
||||
groupsTabLayout.addTab(
|
||||
groupsTabLayout.newTab().apply {
|
||||
text = group._id
|
||||
tag = group
|
||||
},
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
groupsTabLayout.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
// Saving currentQuery to finalQuery for user, this will be used to restore search history, happens when user clicks a card from list
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
finalQuery = currentQuery
|
||||
// Putting the query also into outState for later use in onRestoreInstanceState when rotating screen
|
||||
if (mSearchView != null) {
|
||||
outState.putString(STATE_SEARCH_QUERY, finalQuery)
|
||||
}
|
||||
}
|
||||
|
||||
// Restoring instance state when rotation of screen happens with the goal to restore search query for user
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||
super.onRestoreInstanceState(savedInstanceState)
|
||||
finalQuery = savedInstanceState.getString(STATE_SEARCH_QUERY, "")
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(inputMenu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.main_menu, inputMenu)
|
||||
|
||||
displayCardSetupOptions(inputMenu, mLoyaltyCardCount > 0)
|
||||
|
||||
val searchManager = getSystemService(SEARCH_SERVICE) as SearchManager?
|
||||
if (searchManager != null) {
|
||||
val searchMenuItem = inputMenu.findItem(R.id.action_search)
|
||||
mSearchView = searchMenuItem.actionView as SearchView?
|
||||
mSearchView!!.setSearchableInfo(searchManager.getSearchableInfo(componentName))
|
||||
mSearchView!!.setSubmitButtonEnabled(false)
|
||||
mSearchView!!.setOnCloseListener {
|
||||
invalidateOptionsMenu()
|
||||
false
|
||||
}
|
||||
|
||||
/*
|
||||
* On Android 13 and later, pressing Back while the search view is open hides the keyboard
|
||||
* and collapses the search view at the same time.
|
||||
* This brings back the old behavior on Android 12 and lower: pressing Back once
|
||||
* hides the keyboard, press again while keyboard is hidden to collapse the search view.
|
||||
*/
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
searchMenuItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
|
||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||
if (mSearchView!!.hasFocus()) {
|
||||
mSearchView!!.clearFocus()
|
||||
return false
|
||||
}
|
||||
currentQuery = ""
|
||||
mFilter = ""
|
||||
updateLoyaltyCardList(false)
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
mSearchView!!.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String): Boolean {
|
||||
mFilter = newText
|
||||
// New logic to ensure search history after coming back from picked card - user will see the last search query
|
||||
if (newText.isEmpty()) {
|
||||
if (!finalQuery.isEmpty()) {
|
||||
// Setting the query text for user after coming back from picked card from finalQuery
|
||||
mSearchView!!.setQuery(finalQuery, false)
|
||||
} else if (!currentQuery.isEmpty()) {
|
||||
// Else if is needed in case user deletes search - expected behaviour is to show all cards
|
||||
currentQuery = ""
|
||||
mSearchView!!.setQuery(currentQuery, false)
|
||||
}
|
||||
} else {
|
||||
// Setting search query each time user changes the text in search to temporary variable to be used later in finalQuery String which will be used to restore search history
|
||||
currentQuery = newText
|
||||
}
|
||||
val currentTab =
|
||||
groupsTabLayout.getTabAt(groupsTabLayout.selectedTabPosition)
|
||||
mGroup = currentTab?.tag
|
||||
|
||||
updateLoyaltyCardList(false)
|
||||
|
||||
return true
|
||||
}
|
||||
})
|
||||
// Check if we came from a picked card back to search, in that case we want to show the search view with previous search query
|
||||
if (!finalQuery.isEmpty()) {
|
||||
// Expand the search view to show the query
|
||||
searchMenuItem.expandActionView()
|
||||
// Setting the query text to empty String due to behaviour of onQueryTextChange after coming back from picked card - onQueryTextChange is called automatically without users interaction
|
||||
finalQuery = ""
|
||||
mSearchView!!.setQuery(currentQuery, false)
|
||||
}
|
||||
}
|
||||
|
||||
return super.onCreateOptionsMenu(inputMenu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(inputItem: MenuItem): Boolean {
|
||||
when (inputItem.itemId) {
|
||||
android.R.id.home -> {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
R.id.action_display_options -> {
|
||||
mAdapter.showDisplayOptionsDialog()
|
||||
invalidateOptionsMenu()
|
||||
return true
|
||||
}
|
||||
R.id.action_sort -> {
|
||||
val currentIndex = AtomicInteger()
|
||||
val loyaltyCardOrders = listOf<LoyaltyCardOrder?>(*LoyaltyCardOrder.entries.toTypedArray())
|
||||
for (i in loyaltyCardOrders.indices) {
|
||||
if (mOrder == loyaltyCardOrders[i]) {
|
||||
currentIndex.set(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder(this@MainActivity).apply {
|
||||
setTitle(R.string.sort_by)
|
||||
|
||||
val sortingOptionBinding = SortingOptionBinding.inflate(LayoutInflater.from(this@MainActivity), null, false)
|
||||
val customLayout: View = sortingOptionBinding.getRoot()
|
||||
setView(customLayout)
|
||||
|
||||
val showReversed = sortingOptionBinding.checkBoxReverse
|
||||
|
||||
showReversed.isChecked = mOrderDirection == LoyaltyCardOrderDirection.Descending
|
||||
|
||||
setSingleChoiceItems(
|
||||
R.array.sort_types_array,
|
||||
currentIndex.get()
|
||||
) { _: DialogInterface?, which: Int ->
|
||||
currentIndex.set(which)
|
||||
}
|
||||
|
||||
setPositiveButton(
|
||||
R.string.sort
|
||||
) { dialog, _ ->
|
||||
setSort(
|
||||
loyaltyCardOrders[currentIndex.get()]!!,
|
||||
if (showReversed.isChecked) LoyaltyCardOrderDirection.Descending else LoyaltyCardOrderDirection.Ascending
|
||||
)
|
||||
ListWidget().updateAll(this@MainActivity)
|
||||
dialog?.dismiss()
|
||||
}
|
||||
|
||||
setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
}.create().show()
|
||||
|
||||
return true
|
||||
}
|
||||
R.id.action_manage_groups -> {
|
||||
startActivity(
|
||||
Intent(applicationContext, ManageGroupsActivity::class.java)
|
||||
)
|
||||
return true
|
||||
}
|
||||
R.id.action_import_export -> {
|
||||
startActivity(
|
||||
Intent(applicationContext, ImportExportActivity::class.java)
|
||||
)
|
||||
return true
|
||||
}
|
||||
R.id.action_settings -> {
|
||||
mSettingsLauncher.launch(
|
||||
Intent(applicationContext, SettingsActivity::class.java)
|
||||
)
|
||||
return true
|
||||
}
|
||||
R.id.action_about -> {
|
||||
startActivity(
|
||||
Intent(applicationContext, AboutActivity::class.java)
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(inputItem)
|
||||
}
|
||||
|
||||
private fun setSort(order: LoyaltyCardOrder, direction: LoyaltyCardOrderDirection) {
|
||||
// Update values
|
||||
mOrder = order
|
||||
mOrderDirection = direction
|
||||
|
||||
// Store in Shared Preference to restore next app launch
|
||||
applicationContext.getSharedPreferences(
|
||||
getString(R.string.sharedpreference_sort),
|
||||
MODE_PRIVATE
|
||||
).edit {
|
||||
putString(
|
||||
getString(R.string.sharedpreference_sort_order),
|
||||
order.name
|
||||
)
|
||||
putString(
|
||||
getString(R.string.sharedpreference_sort_direction),
|
||||
direction.name
|
||||
)
|
||||
}
|
||||
|
||||
// Update card list
|
||||
updateLoyaltyCardList(false)
|
||||
}
|
||||
|
||||
override fun onRowLongClicked(inputPosition: Int) {
|
||||
enableActionMode(inputPosition)
|
||||
}
|
||||
|
||||
private fun enableActionMode(inputPosition: Int) {
|
||||
if (mCurrentActionMode == null) {
|
||||
mCurrentActionMode = startSupportActionMode(mCurrentActionModeCallback)
|
||||
}
|
||||
toggleSelection(inputPosition)
|
||||
}
|
||||
|
||||
private fun scaleScreen() {
|
||||
val displayMetrics = DisplayMetrics()
|
||||
windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
val screenHeight = displayMetrics.heightPixels
|
||||
val mediumSizePx = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
MEDIUM_SCALE_FACTOR_DIP.toFloat(),
|
||||
getResources().displayMetrics
|
||||
)
|
||||
val shouldScaleSmaller = screenHeight < mediumSizePx
|
||||
|
||||
binding.include.welcomeIcon.visibility = if (shouldScaleSmaller) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
private fun toggleSelection(inputPosition: Int) {
|
||||
mAdapter.toggleSelection(inputPosition)
|
||||
val count = mAdapter.selectedItemCount
|
||||
|
||||
if (count == 0) {
|
||||
mCurrentActionMode!!.finish()
|
||||
} else {
|
||||
mCurrentActionMode!!.title = getResources().getQuantityString(
|
||||
R.plurals.selectedCardCount,
|
||||
count,
|
||||
count
|
||||
)
|
||||
|
||||
val editItem = mCurrentActionMode!!.menu.findItem(R.id.action_edit)
|
||||
val archiveItem = mCurrentActionMode!!.menu.findItem(R.id.action_archive)
|
||||
val unarchiveItem = mCurrentActionMode!!.menu.findItem(R.id.action_unarchive)
|
||||
val starItem = mCurrentActionMode!!.menu.findItem(R.id.action_star)
|
||||
val unstarItem = mCurrentActionMode!!.menu.findItem(R.id.action_unstar)
|
||||
|
||||
var hasStarred = false
|
||||
var hasUnstarred = false
|
||||
var hasArchived = false
|
||||
var hasUnarchived = false
|
||||
|
||||
for (loyaltyCard in mAdapter.getSelectedItems()) {
|
||||
if (loyaltyCard.starStatus == 1) {
|
||||
hasStarred = true
|
||||
} else {
|
||||
hasUnstarred = true
|
||||
}
|
||||
|
||||
if (loyaltyCard.archiveStatus == 1) {
|
||||
hasArchived = true
|
||||
} else {
|
||||
hasUnarchived = true
|
||||
}
|
||||
|
||||
// We have all types, no need to keep checking
|
||||
if (hasStarred && hasUnstarred && hasArchived && hasUnarchived) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
unarchiveItem.isVisible = hasArchived
|
||||
archiveItem.isVisible = hasUnarchived
|
||||
|
||||
if (count == 1) {
|
||||
starItem.isVisible = !hasStarred
|
||||
unstarItem.isVisible = !hasUnstarred
|
||||
editItem.isVisible = true
|
||||
editItem.isEnabled = true
|
||||
} else {
|
||||
starItem.isVisible = hasUnstarred
|
||||
unstarItem.isVisible = hasStarred
|
||||
|
||||
editItem.isVisible = false
|
||||
editItem.isEnabled = false
|
||||
}
|
||||
|
||||
mCurrentActionMode!!.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onRowClicked(inputPosition: Int) {
|
||||
if (mAdapter.selectedItemCount > 0) {
|
||||
enableActionMode(inputPosition)
|
||||
} else {
|
||||
// FIXME
|
||||
//
|
||||
// There is a really nasty edge case that can happen when someone taps a card but right
|
||||
// after it swipes (very small window, hard to reproduce). The cursor gets replaced and
|
||||
// may not have a card at the ID number that is returned from onRowClicked.
|
||||
//
|
||||
// The proper fix, obviously, would involve makes sure an onFling can't happen while a
|
||||
// click is being processed. Sadly, I have not yet found a way to make that possible.
|
||||
val loyaltyCard: LoyaltyCard
|
||||
try {
|
||||
loyaltyCard = mAdapter.getCard(inputPosition)
|
||||
} catch (e: CursorIndexOutOfBoundsException) {
|
||||
Log.w(TAG, "Prevented crash from tap + swipe on ID $inputPosition: $e")
|
||||
return
|
||||
}
|
||||
|
||||
startActivity(
|
||||
Intent(this, LoyaltyCardViewActivity::class.java).apply {
|
||||
action = ""
|
||||
putExtras(Bundle().apply {
|
||||
putInt(LoyaltyCardViewActivity.BUNDLE_ID, loyaltyCard.id)
|
||||
|
||||
val cardList = ArrayList<Int?>()
|
||||
for (i in 0..<mAdapter.itemCount) {
|
||||
cardList.add(mAdapter.getCard(i).id)
|
||||
}
|
||||
|
||||
putIntegerArrayList(LoyaltyCardViewActivity.BUNDLE_CARDLIST, cardList)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "Catima"
|
||||
const val RESTART_ACTIVITY_INTENT: String = "restart_activity_intent"
|
||||
|
||||
private const val MEDIUM_SCALE_FACTOR_DIP = 460
|
||||
const val STATE_SEARCH_QUERY: String = "SEARCH_QUERY"
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import protect.card_locker.databinding.ActivityManageGroupBinding;
|
||||
|
||||
public class ManageGroupActivity extends CatimaAppCompatActivity implements ManageGroupCursorAdapter.CardAdapterListener {
|
||||
private ActivityManageGroupBinding binding;
|
||||
private SQLiteDatabase mDatabase;
|
||||
private ManageGroupCursorAdapter mAdapter;
|
||||
|
||||
private final String SAVE_INSTANCE_ADAPTER_STATE = "adapterState";
|
||||
private final String SAVE_INSTANCE_CURRENT_GROUP_NAME = "currentGroupName";
|
||||
|
||||
protected Group mGroup = null;
|
||||
private RecyclerView mCardList;
|
||||
private TextView noGroupCardsText;
|
||||
private EditText mGroupNameText;
|
||||
|
||||
private boolean mGroupNameNotInUse;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle inputSavedInstanceState) {
|
||||
super.onCreate(inputSavedInstanceState);
|
||||
binding = ActivityManageGroupBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
Utils.applyWindowInsetsAndFabOffset(binding.getRoot(), binding.fabSave);
|
||||
Toolbar toolbar = binding.toolbar;
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
mDatabase = new DBHelper(this).getWritableDatabase();
|
||||
|
||||
noGroupCardsText = binding.include.noGroupCardsText;
|
||||
mCardList = binding.include.list;
|
||||
FloatingActionButton saveButton = binding.fabSave;
|
||||
|
||||
mGroupNameText = binding.editTextGroupName;
|
||||
|
||||
mGroupNameText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
mGroupNameNotInUse = true;
|
||||
mGroupNameText.setError(null);
|
||||
String currentGroupName = mGroupNameText.getText().toString().trim();
|
||||
if (currentGroupName.length() == 0) {
|
||||
mGroupNameText.setError(getResources().getText(R.string.group_name_is_empty));
|
||||
return;
|
||||
}
|
||||
if (!mGroup._id.equals(currentGroupName)) {
|
||||
if (DBHelper.getGroup(mDatabase, currentGroupName) != null) {
|
||||
mGroupNameNotInUse = false;
|
||||
mGroupNameText.setError(getResources().getText(R.string.group_name_already_in_use));
|
||||
} else {
|
||||
mGroupNameNotInUse = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Intent intent = getIntent();
|
||||
String groupId = intent.getStringExtra("group");
|
||||
if (groupId == null) {
|
||||
throw (new IllegalArgumentException("this activity expects a group loaded into it's intent"));
|
||||
}
|
||||
Log.d("groupId", "groupId: " + groupId);
|
||||
mGroup = DBHelper.getGroup(mDatabase, groupId);
|
||||
if (mGroup == null) {
|
||||
throw (new IllegalArgumentException("cannot load group " + groupId + " from database"));
|
||||
}
|
||||
mGroupNameText.setText(mGroup._id);
|
||||
setTitle(getString(R.string.editGroup, mGroup._id));
|
||||
mAdapter = new ManageGroupCursorAdapter(this, null, this, mGroup, null);
|
||||
mCardList.setAdapter(mAdapter);
|
||||
registerForContextMenu(mCardList);
|
||||
|
||||
if (inputSavedInstanceState != null) {
|
||||
mAdapter.importInGroupState(integerArrayToAdapterState(inputSavedInstanceState.getIntegerArrayList(SAVE_INSTANCE_ADAPTER_STATE)));
|
||||
mGroupNameText.setText(inputSavedInstanceState.getString(SAVE_INSTANCE_CURRENT_GROUP_NAME));
|
||||
}
|
||||
|
||||
enableToolbarBackButton();
|
||||
|
||||
saveButton.setOnClickListener(v -> {
|
||||
String currentGroupName = mGroupNameText.getText().toString().trim();
|
||||
if (!currentGroupName.equals(mGroup._id)) {
|
||||
if (currentGroupName.length() == 0) {
|
||||
Toast.makeText(getApplicationContext(), R.string.group_name_is_empty, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (!mGroupNameNotInUse) {
|
||||
Toast.makeText(getApplicationContext(), R.string.group_name_already_in_use, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mAdapter.commitToDatabase();
|
||||
if (!currentGroupName.equals(mGroup._id)) {
|
||||
DBHelper.updateGroup(mDatabase, mGroup._id, currentGroupName);
|
||||
}
|
||||
Toast.makeText(getApplicationContext(), R.string.group_updated, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
});
|
||||
// this setText is here because content_main.xml is reused from main activity
|
||||
noGroupCardsText.setText(getResources().getText(R.string.noGiftCardsGroup));
|
||||
updateLoyaltyCardList();
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
leaveWithoutSaving();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ArrayList<Integer> adapterStateToIntegerArray(HashMap<Integer, Boolean> adapterState) {
|
||||
ArrayList<Integer> ret = new ArrayList<>(adapterState.size() * 2);
|
||||
for (Map.Entry<Integer, Boolean> entry : adapterState.entrySet()) {
|
||||
ret.add(entry.getKey());
|
||||
ret.add(entry.getValue() ? 1 : 0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private HashMap<Integer, Boolean> integerArrayToAdapterState(ArrayList<Integer> in) {
|
||||
HashMap<Integer, Boolean> ret = new HashMap<>();
|
||||
if (in.size() % 2 != 0) {
|
||||
throw (new RuntimeException("failed restoring adapterState from integer array list"));
|
||||
}
|
||||
for (int i = 0; i < in.size(); i += 2) {
|
||||
ret.put(in.get(i), in.get(i + 1) == 1);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu inputMenu) {
|
||||
getMenuInflater().inflate(R.menu.card_details_menu, inputMenu);
|
||||
|
||||
return super.onCreateOptionsMenu(inputMenu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem inputItem) {
|
||||
int id = inputItem.getItemId();
|
||||
|
||||
if (id == R.id.action_display_options) {
|
||||
mAdapter.showDisplayOptionsDialog();
|
||||
invalidateOptionsMenu();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(inputItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
outState.putIntegerArrayList(SAVE_INSTANCE_ADAPTER_STATE, adapterStateToIntegerArray(mAdapter.exportInGroupState()));
|
||||
outState.putString(SAVE_INSTANCE_CURRENT_GROUP_NAME, mGroupNameText.getText().toString());
|
||||
}
|
||||
|
||||
private void updateLoyaltyCardList() {
|
||||
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase));
|
||||
|
||||
if (mAdapter.getItemCount() == 0) {
|
||||
mCardList.setVisibility(View.GONE);
|
||||
noGroupCardsText.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mCardList.setVisibility(View.VISIBLE);
|
||||
noGroupCardsText.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void leaveWithoutSaving() {
|
||||
if (hasChanged()) {
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(ManageGroupActivity.this);
|
||||
builder.setTitle(R.string.leaveWithoutSaveTitle);
|
||||
builder.setMessage(R.string.leaveWithoutSaveConfirmation);
|
||||
builder.setPositiveButton(R.string.confirm, (dialog, which) -> finish());
|
||||
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasChanged() {
|
||||
return mAdapter.hasChanged() || !mGroup._id.equals(mGroupNameText.getText().toString().trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRowLongClicked(int inputPosition) {
|
||||
mAdapter.toggleSelection(inputPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRowClicked(int inputPosition) {
|
||||
mAdapter.toggleSelection(inputPosition);
|
||||
|
||||
}
|
||||
}
|
||||
236
app/src/main/java/protect/card_locker/ManageGroupActivity.kt
Normal file
236
app/src/main/java/protect/card_locker/ManageGroupActivity.kt
Normal file
@@ -0,0 +1,236 @@
|
||||
package protect.card_locker
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import protect.card_locker.LoyaltyCardCursorAdapter.CardAdapterListener
|
||||
import protect.card_locker.databinding.ActivityManageGroupBinding
|
||||
|
||||
class ManageGroupActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
private lateinit var binding: ActivityManageGroupBinding
|
||||
private lateinit var mDatabase: SQLiteDatabase
|
||||
private lateinit var mAdapter: ManageGroupCursorAdapter
|
||||
|
||||
private lateinit var mGroup: Group
|
||||
private lateinit var mCardList: RecyclerView
|
||||
private lateinit var noGroupCardsText: TextView
|
||||
private lateinit var mGroupNameText: EditText
|
||||
|
||||
private var mGroupNameNotInUse = false
|
||||
|
||||
override fun onCreate(inputSavedInstanceState: Bundle?) {
|
||||
super.onCreate(inputSavedInstanceState)
|
||||
binding = ActivityManageGroupBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
Utils.applyWindowInsetsAndFabOffset(binding.root, binding.fabSave)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
|
||||
mDatabase = DBHelper(this).writableDatabase
|
||||
noGroupCardsText = binding.include.noGroupCardsText
|
||||
mCardList = binding.include.list
|
||||
|
||||
mGroupNameText = binding.editTextGroupName
|
||||
mGroupNameText.doAfterTextChanged {
|
||||
mGroupNameNotInUse = true
|
||||
mGroupNameText.error = null
|
||||
val currentGroupName = mGroupNameText.text.trim().toString()
|
||||
if (currentGroupName.isEmpty()) {
|
||||
mGroupNameText.error = getText(R.string.group_name_is_empty)
|
||||
return@doAfterTextChanged
|
||||
}
|
||||
if (mGroup._id != currentGroupName) {
|
||||
if (DBHelper.getGroup(mDatabase, currentGroupName) != null) {
|
||||
mGroupNameNotInUse = false
|
||||
mGroupNameText.error = getText(R.string.group_name_already_in_use)
|
||||
} else {
|
||||
mGroupNameNotInUse = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val groupId = intent.getStringExtra("group")
|
||||
?: throw (IllegalArgumentException("this activity expects a group loaded into it's intent"))
|
||||
Log.d("groupId", "groupId: $groupId")
|
||||
mGroup = DBHelper.getGroup(mDatabase, groupId)
|
||||
?: throw IllegalArgumentException("Cannot load group $groupId from database")
|
||||
mGroupNameText.setText(mGroup._id)
|
||||
setTitle(getString(R.string.editGroup, mGroup._id))
|
||||
mAdapter = ManageGroupCursorAdapter(this, null, this, mGroup, null)
|
||||
mCardList.adapter = mAdapter
|
||||
registerForContextMenu(mCardList)
|
||||
|
||||
if (inputSavedInstanceState != null) {
|
||||
mAdapter.importInGroupState(
|
||||
bundleToAdapterState(
|
||||
adapterStateBundle = inputSavedInstanceState.getBundle(
|
||||
SAVE_INSTANCE_ADAPTER_STATE
|
||||
)
|
||||
)
|
||||
)
|
||||
mGroupNameText.setText(
|
||||
inputSavedInstanceState.getString(
|
||||
SAVE_INSTANCE_CURRENT_GROUP_NAME
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
enableToolbarBackButton()
|
||||
|
||||
binding.fabSave.setOnClickListener { v: View ->
|
||||
val currentGroupName = mGroupNameText.text.trim().toString()
|
||||
if (currentGroupName != mGroup._id) {
|
||||
when {
|
||||
currentGroupName.isEmpty() -> {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.group_name_is_empty,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
!mGroupNameNotInUse -> {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.group_name_already_in_use,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mAdapter.commitToDatabase()
|
||||
if (currentGroupName != mGroup._id) {
|
||||
DBHelper.updateGroup(mDatabase, mGroup._id, currentGroupName)
|
||||
}
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.group_updated,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
finish()
|
||||
}
|
||||
// this setText is here because content_main.xml is reused from main activity
|
||||
noGroupCardsText.text = getText(R.string.noGiftCardsGroup)
|
||||
updateLoyaltyCardList()
|
||||
|
||||
onBackPressedDispatcher.addCallback(
|
||||
owner = this,
|
||||
onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
leaveWithoutSaving()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun adapterStateToBundle(adapterState: HashMap<Int, Boolean>): Bundle {
|
||||
val adapterStateBundle = Bundle().apply {
|
||||
for (entry in adapterState.entries) {
|
||||
putBoolean(entry.key.toString(), entry.value)
|
||||
}
|
||||
}
|
||||
return adapterStateBundle
|
||||
}
|
||||
|
||||
private fun bundleToAdapterState(adapterStateBundle: Bundle?): Map<Int, Boolean> {
|
||||
adapterStateBundle ?: return emptyMap()
|
||||
val adapterStateMap = buildMap {
|
||||
for (key in adapterStateBundle.keySet()) {
|
||||
put(key.toInt(), adapterStateBundle.getBoolean(key))
|
||||
}
|
||||
}
|
||||
return adapterStateMap
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(inputMenu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.card_details_menu, inputMenu)
|
||||
|
||||
return super.onCreateOptionsMenu(inputMenu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(inputItem: MenuItem): Boolean {
|
||||
val id = inputItem.itemId
|
||||
|
||||
if (id == R.id.action_display_options) {
|
||||
mAdapter.showDisplayOptionsDialog()
|
||||
invalidateOptionsMenu()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(inputItem)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
|
||||
outState.putBundle(
|
||||
SAVE_INSTANCE_ADAPTER_STATE,
|
||||
adapterStateToBundle(mAdapter.exportInGroupState())
|
||||
)
|
||||
outState.putString(SAVE_INSTANCE_CURRENT_GROUP_NAME, mGroupNameText.text.toString())
|
||||
}
|
||||
|
||||
private fun updateLoyaltyCardList() {
|
||||
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase))
|
||||
|
||||
if (mAdapter.itemCount == 0) {
|
||||
mCardList.visibility = View.GONE
|
||||
noGroupCardsText.visibility = View.VISIBLE
|
||||
} else {
|
||||
mCardList.visibility = View.VISIBLE
|
||||
noGroupCardsText.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun leaveWithoutSaving() {
|
||||
if (hasChanged()) {
|
||||
MaterialAlertDialogBuilder(this@ManageGroupActivity).apply {
|
||||
setTitle(R.string.leaveWithoutSaveTitle)
|
||||
setMessage(R.string.leaveWithoutSaveConfirmation)
|
||||
setPositiveButton(R.string.confirm) { dialog: DialogInterface, _ ->
|
||||
finish()
|
||||
}
|
||||
setNegativeButton(R.string.cancel) { dialog: DialogInterface, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
}.create().show()
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun hasChanged(): Boolean {
|
||||
return mAdapter.hasChanged() || mGroup._id != mGroupNameText.text.trim().toString()
|
||||
}
|
||||
|
||||
override fun onRowLongClicked(inputPosition: Int) {
|
||||
mAdapter.toggleSelection(inputPosition)
|
||||
}
|
||||
|
||||
override fun onRowClicked(inputPosition: Int) {
|
||||
mAdapter.toggleSelection(inputPosition)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val SAVE_INSTANCE_ADAPTER_STATE = "adapterState"
|
||||
const val SAVE_INSTANCE_CURRENT_GROUP_NAME = "currentGroupName"
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ public class ManageGroupCursorAdapter extends LoyaltyCardCursorAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
public void importInGroupState(HashMap<Integer, Boolean> cardIdInGroupMap) {
|
||||
public void importInGroupState(Map<Integer, Boolean> cardIdInGroupMap) {
|
||||
mInGroupOverlay = new HashMap<>(cardIdInGroupMap);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,247 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import protect.card_locker.databinding.ManageGroupsActivityBinding;
|
||||
|
||||
public class ManageGroupsActivity extends CatimaAppCompatActivity implements GroupCursorAdapter.GroupAdapterListener {
|
||||
private ManageGroupsActivityBinding binding;
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private SQLiteDatabase mDatabase;
|
||||
private TextView mHelpText;
|
||||
private RecyclerView mGroupList;
|
||||
GroupCursorAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = ManageGroupsActivityBinding.inflate(getLayoutInflater());
|
||||
setTitle(R.string.groups);
|
||||
setContentView(binding.getRoot());
|
||||
Utils.applyWindowInsets(binding.getRoot());
|
||||
Toolbar toolbar = binding.toolbar;
|
||||
setSupportActionBar(toolbar);
|
||||
enableToolbarBackButton();
|
||||
|
||||
mDatabase = new DBHelper(this).getWritableDatabase();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
FloatingActionButton addButton = binding.fabAdd;
|
||||
addButton.setOnClickListener(v -> createGroup());
|
||||
addButton.bringToFront();
|
||||
|
||||
mGroupList = binding.include.list;
|
||||
mHelpText = binding.include.helpText;
|
||||
|
||||
// Init group list
|
||||
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
|
||||
mGroupList.setLayoutManager(mLayoutManager);
|
||||
mGroupList.setItemAnimator(new DefaultItemAnimator());
|
||||
|
||||
mAdapter = new GroupCursorAdapter(this, null, this);
|
||||
mGroupList.setAdapter(mAdapter);
|
||||
|
||||
updateGroupList();
|
||||
}
|
||||
|
||||
private void updateGroupList() {
|
||||
mAdapter.swapCursor(DBHelper.getGroupCursor(mDatabase));
|
||||
|
||||
if (DBHelper.getGroupCount(mDatabase) == 0) {
|
||||
mGroupList.setVisibility(View.GONE);
|
||||
mHelpText.setVisibility(View.VISIBLE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
mGroupList.setVisibility(View.VISIBLE);
|
||||
mHelpText.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void invalidateHomescreenActiveTab() {
|
||||
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
|
||||
getString(R.string.sharedpreference_active_tab),
|
||||
Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor activeTabPrefEditor = activeTabPref.edit();
|
||||
activeTabPrefEditor.putInt(getString(R.string.sharedpreference_active_tab), 0);
|
||||
activeTabPrefEditor.apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
|
||||
if (id == android.R.id.home) {
|
||||
finish();
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void createGroup() {
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||
|
||||
// Header
|
||||
builder.setTitle(R.string.enter_group_name);
|
||||
|
||||
// Layout
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
int contentPadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
|
||||
params.leftMargin = contentPadding;
|
||||
params.topMargin = contentPadding / 2;
|
||||
params.rightMargin = contentPadding;
|
||||
|
||||
// EditText with spacing
|
||||
final EditText input = new EditText(this);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
input.setLayoutParams(params);
|
||||
layout.addView(input);
|
||||
|
||||
// Set layout
|
||||
builder.setView(layout);
|
||||
|
||||
// Buttons
|
||||
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
|
||||
DBHelper.insertGroup(mDatabase, input.getText().toString().trim());
|
||||
updateGroupList();
|
||||
});
|
||||
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
// Now that the dialog exists, we can bind something that affects the OK button
|
||||
input.addTextChangedListener(new SimpleTextWatcher() {
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
String groupName = s.toString().trim();
|
||||
|
||||
if (groupName.length() == 0) {
|
||||
input.setError(getString(R.string.group_name_is_empty));
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (DBHelper.getGroup(mDatabase, groupName) != null) {
|
||||
input.setError(getString(R.string.group_name_already_in_use));
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
input.setError(null);
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
|
||||
}
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
|
||||
// Disable button (must be done **after** dialog is shown to prevent crash
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
// Set focus on input field
|
||||
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||
input.requestFocus();
|
||||
}
|
||||
|
||||
private String getGroupName(View view) {
|
||||
TextView groupNameTextView = view.findViewById(R.id.name);
|
||||
return (String) groupNameTextView.getText();
|
||||
}
|
||||
|
||||
private void moveGroup(View view, boolean up) {
|
||||
List<Group> groups = DBHelper.getGroups(mDatabase);
|
||||
final String groupName = getGroupName(view);
|
||||
|
||||
int currentIndex = DBHelper.getGroup(mDatabase, groupName).order;
|
||||
int newIndex;
|
||||
|
||||
// Reinsert group in correct position
|
||||
if (up) {
|
||||
newIndex = currentIndex - 1;
|
||||
} else {
|
||||
newIndex = currentIndex + 1;
|
||||
}
|
||||
|
||||
// Don't try to move out of bounds
|
||||
if (newIndex < 0 || newIndex >= groups.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Group group = groups.remove(currentIndex);
|
||||
groups.add(newIndex, group);
|
||||
|
||||
// Update database
|
||||
DBHelper.reorderGroups(mDatabase, groups);
|
||||
|
||||
// Update UI
|
||||
updateGroupList();
|
||||
|
||||
// Ordering may have changed, so invalidate
|
||||
invalidateHomescreenActiveTab();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoveDownButtonClicked(View view) {
|
||||
moveGroup(view, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoveUpButtonClicked(View view) {
|
||||
moveGroup(view, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEditButtonClicked(View view) {
|
||||
Intent intent = new Intent(this, ManageGroupActivity.class);
|
||||
intent.putExtra("group", getGroupName(view));
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteButtonClicked(View view) {
|
||||
final String groupName = getGroupName(view);
|
||||
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle(R.string.deleteConfirmationGroup);
|
||||
builder.setMessage(groupName);
|
||||
|
||||
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
|
||||
DBHelper.deleteGroup(mDatabase, groupName);
|
||||
updateGroupList();
|
||||
// Delete may change ordering, so invalidate
|
||||
invalidateHomescreenActiveTab();
|
||||
});
|
||||
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
240
app/src/main/java/protect/card_locker/ManageGroupsActivity.kt
Normal file
240
app/src/main/java/protect/card_locker/ManageGroupsActivity.kt
Normal file
@@ -0,0 +1,240 @@
|
||||
package protect.card_locker
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import protect.card_locker.GroupCursorAdapter.GroupAdapterListener
|
||||
import protect.card_locker.databinding.ManageGroupsActivityBinding
|
||||
|
||||
class ManageGroupsActivity : CatimaAppCompatActivity(), GroupAdapterListener {
|
||||
private lateinit var binding: ManageGroupsActivityBinding
|
||||
private lateinit var mDatabase: SQLiteDatabase
|
||||
private lateinit var mHelpText: TextView
|
||||
private lateinit var mGroupList: RecyclerView
|
||||
private lateinit var mAdapter: GroupCursorAdapter
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ManageGroupsActivityBinding.inflate(layoutInflater)
|
||||
setTitle(R.string.groups)
|
||||
setContentView(binding.root)
|
||||
Utils.applyWindowInsets(binding.root)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
enableToolbarBackButton()
|
||||
|
||||
mDatabase = DBHelper(this).writableDatabase
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
with(binding.fabAdd) {
|
||||
setOnClickListener { v: View ->
|
||||
createGroup()
|
||||
}
|
||||
bringToFront()
|
||||
}
|
||||
|
||||
mGroupList = binding.include.list
|
||||
mHelpText = binding.include.helpText
|
||||
|
||||
// Init group list
|
||||
LinearLayoutManager(applicationContext).apply {
|
||||
mGroupList.layoutManager = this
|
||||
}
|
||||
mGroupList.setItemAnimator(DefaultItemAnimator())
|
||||
mAdapter = GroupCursorAdapter(this, null, this)
|
||||
mGroupList.setAdapter(mAdapter)
|
||||
|
||||
updateGroupList()
|
||||
}
|
||||
|
||||
private fun updateGroupList() {
|
||||
mAdapter.swapCursor(DBHelper.getGroupCursor(mDatabase))
|
||||
|
||||
if (DBHelper.getGroupCount(mDatabase) == 0) {
|
||||
mGroupList.visibility = View.GONE
|
||||
mHelpText.visibility = View.VISIBLE
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
mGroupList.visibility = View.VISIBLE
|
||||
mHelpText.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun invalidateHomescreenActiveTab() {
|
||||
val activeTabPref = getSharedPreferences(
|
||||
getString(R.string.sharedpreference_active_tab),
|
||||
MODE_PRIVATE
|
||||
)
|
||||
activeTabPref.edit {
|
||||
putInt(getString(R.string.sharedpreference_active_tab), 0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
finish()
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun createGroup() {
|
||||
val builder: AlertDialog.Builder = MaterialAlertDialogBuilder(this)
|
||||
|
||||
// Header
|
||||
builder.setTitle(R.string.enter_group_name)
|
||||
|
||||
// Layout
|
||||
val layout = LinearLayout(this)
|
||||
layout.orientation = LinearLayout.VERTICAL
|
||||
val params = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
val contentPadding =
|
||||
resources.getDimensionPixelSize(R.dimen.alert_dialog_content_padding)
|
||||
leftMargin = contentPadding
|
||||
topMargin = contentPadding / 2
|
||||
rightMargin = contentPadding
|
||||
}
|
||||
|
||||
// EditText with spacing
|
||||
val input = EditText(this)
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT)
|
||||
input.setLayoutParams(params)
|
||||
layout.addView(input)
|
||||
|
||||
// Set layout
|
||||
builder.setView(layout)
|
||||
|
||||
// Buttons
|
||||
builder.setPositiveButton(getString(R.string.ok)) { dialog: DialogInterface, which: Int ->
|
||||
DBHelper.insertGroup(mDatabase, input.text.trim().toString())
|
||||
updateGroupList()
|
||||
}
|
||||
builder.setNegativeButton(getString(R.string.cancel)) { dialog: DialogInterface, which: Int ->
|
||||
dialog.cancel()
|
||||
}
|
||||
val dialog = builder.create()
|
||||
|
||||
// Now that the dialog exists, we can bind something that affects the OK button
|
||||
input.doOnTextChanged { s: CharSequence?, start: Int, before: Int, count: Int ->
|
||||
val groupName = s?.trim().toString()
|
||||
|
||||
if (groupName.isEmpty()) {
|
||||
input.error = getString(R.string.group_name_is_empty)
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false)
|
||||
return@doOnTextChanged
|
||||
}
|
||||
|
||||
if (DBHelper.getGroup(mDatabase, groupName) != null) {
|
||||
input.error = getString(R.string.group_name_already_in_use)
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false)
|
||||
return@doOnTextChanged
|
||||
}
|
||||
|
||||
input.error = null
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true)
|
||||
}
|
||||
|
||||
dialog.apply {
|
||||
show()
|
||||
// Disable button (must be done **after** dialog is shown to prevent crash
|
||||
getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false)
|
||||
// Set focus on input field
|
||||
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||
}
|
||||
|
||||
input.requestFocus()
|
||||
}
|
||||
|
||||
private fun getGroupName(view: View): String {
|
||||
val groupNameTextView = view.findViewById<TextView>(R.id.name)
|
||||
return groupNameTextView.text.toString()
|
||||
}
|
||||
|
||||
private fun moveGroup(view: View, up: Boolean) {
|
||||
val groups = DBHelper.getGroups(mDatabase)
|
||||
val groupName = getGroupName(view)
|
||||
|
||||
val currentIndex = DBHelper.getGroup(mDatabase, groupName).order
|
||||
|
||||
// Reinsert group in correct position
|
||||
val newIndex: Int = if (up) {
|
||||
currentIndex - 1
|
||||
} else {
|
||||
currentIndex + 1
|
||||
}
|
||||
|
||||
// Don't try to move out of bounds
|
||||
if (newIndex < 0 || newIndex >= groups.size) {
|
||||
return
|
||||
}
|
||||
|
||||
val group = groups.removeAt(currentIndex)
|
||||
groups.add(newIndex, group)
|
||||
|
||||
// Update database
|
||||
DBHelper.reorderGroups(mDatabase, groups)
|
||||
|
||||
// Update UI
|
||||
updateGroupList()
|
||||
|
||||
// Ordering may have changed, so invalidate
|
||||
invalidateHomescreenActiveTab()
|
||||
}
|
||||
|
||||
override fun onMoveDownButtonClicked(view: View) {
|
||||
moveGroup(view, false)
|
||||
}
|
||||
|
||||
override fun onMoveUpButtonClicked(view: View) {
|
||||
moveGroup(view, true)
|
||||
}
|
||||
|
||||
override fun onEditButtonClicked(view: View) {
|
||||
Intent(this, ManageGroupActivity::class.java).apply {
|
||||
putExtra("group", getGroupName(view))
|
||||
startActivity(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDeleteButtonClicked(view: View) {
|
||||
val groupName = getGroupName(view)
|
||||
|
||||
MaterialAlertDialogBuilder(this).apply {
|
||||
setTitle(R.string.deleteConfirmationGroup)
|
||||
setMessage(groupName)
|
||||
|
||||
setPositiveButton(getString(R.string.ok)) { dialog: DialogInterface, which: Int ->
|
||||
DBHelper.deleteGroup(mDatabase, groupName)
|
||||
updateGroupList()
|
||||
// Delete may change ordering, so invalidate
|
||||
invalidateHomescreenActiveTab()
|
||||
}
|
||||
setNegativeButton(getString(R.string.cancel)) { dialog: DialogInterface, which: Int ->
|
||||
dialog.cancel()
|
||||
}
|
||||
}.create().show()
|
||||
}
|
||||
}
|
||||
@@ -1,542 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import static protect.card_locker.BarcodeSelectorActivity.BARCODE_CONTENTS;
|
||||
import static protect.card_locker.BarcodeSelectorActivity.BARCODE_FORMAT;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.InputType;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.SimpleAdapter;
|
||||
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.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.client.android.Intents;
|
||||
import com.journeyapps.barcodescanner.BarcodeCallback;
|
||||
import com.journeyapps.barcodescanner.BarcodeResult;
|
||||
import com.journeyapps.barcodescanner.CaptureManager;
|
||||
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import protect.card_locker.databinding.CustomBarcodeScannerBinding;
|
||||
import protect.card_locker.databinding.ScanActivityBinding;
|
||||
|
||||
/**
|
||||
* Custom Scannner Activity extending from Activity to display a custom layout form scanner view.
|
||||
* <p>
|
||||
* Based on https://github.com/journeyapps/zxing-android-embedded/blob/0fdfbce9fb3285e985bad9971c5f7c0a7a334e7b/sample/src/main/java/example/zxing/CustomScannerActivity.java
|
||||
* originally licensed under Apache 2.0
|
||||
*/
|
||||
public class ScanActivity extends CatimaAppCompatActivity {
|
||||
private ScanActivityBinding binding;
|
||||
private CustomBarcodeScannerBinding customBarcodeScannerBinding;
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private static final int MEDIUM_SCALE_FACTOR_DIP = 460;
|
||||
private static final int COMPAT_SCALE_FACTOR_DIP = 320;
|
||||
|
||||
private static final int PERMISSION_SCAN_ADD_FROM_IMAGE = 100;
|
||||
private static final int PERMISSION_SCAN_ADD_FROM_PDF = 101;
|
||||
private static final int PERMISSION_SCAN_ADD_FROM_PKPASS = 102;
|
||||
|
||||
private CaptureManager capture;
|
||||
private DecoratedBarcodeView barcodeScannerView;
|
||||
|
||||
private String cardId;
|
||||
private String addGroup;
|
||||
private boolean torch = false;
|
||||
|
||||
private ActivityResultLauncher<Intent> manualAddLauncher;
|
||||
// can't use the pre-made contract because that launches the file manager for image type instead of gallery
|
||||
private ActivityResultLauncher<Intent> photoPickerLauncher;
|
||||
private ActivityResultLauncher<Intent> pdfPickerLauncher;
|
||||
private ActivityResultLauncher<Intent> pkpassPickerLauncher;
|
||||
|
||||
static final String STATE_SCANNER_ACTIVE = "scannerActive";
|
||||
private boolean mScannerActive = true;
|
||||
private boolean mHasError = false;
|
||||
|
||||
private void extractIntentFields(Intent intent) {
|
||||
final Bundle b = intent.getExtras();
|
||||
cardId = b != null ? b.getString(LoyaltyCard.BUNDLE_LOYALTY_CARD_CARD_ID) : null;
|
||||
addGroup = b != null ? b.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) : null;
|
||||
Log.d(TAG, "Scan activity: id=" + cardId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = ScanActivityBinding.inflate(getLayoutInflater());
|
||||
customBarcodeScannerBinding = CustomBarcodeScannerBinding.bind(binding.zxingBarcodeScanner);
|
||||
setTitle(R.string.scanCardBarcode);
|
||||
setContentView(binding.getRoot());
|
||||
Utils.applyWindowInsets(binding.getRoot());
|
||||
Toolbar toolbar = binding.toolbar;
|
||||
setSupportActionBar(toolbar);
|
||||
enableToolbarBackButton();
|
||||
|
||||
extractIntentFields(getIntent());
|
||||
|
||||
manualAddLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.SELECT_BARCODE_REQUEST, result.getResultCode(), result.getData()));
|
||||
photoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_IMAGE_FILE, result.getResultCode(), result.getData()));
|
||||
pdfPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_PDF_FILE, result.getResultCode(), result.getData()));
|
||||
pkpassPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_PKPASS_FILE, result.getResultCode(), result.getData()));
|
||||
customBarcodeScannerBinding.fabOtherOptions.setOnClickListener(view -> {
|
||||
setScannerActive(false);
|
||||
|
||||
ArrayList<HashMap<String, Object>> list = new ArrayList<>();
|
||||
String[] texts = new String[]{
|
||||
getString(R.string.addWithoutBarcode),
|
||||
getString(R.string.addManually),
|
||||
getString(R.string.addFromImage),
|
||||
getString(R.string.addFromPdfFile),
|
||||
getString(R.string.addFromPkpass)
|
||||
};
|
||||
Object[] icons = new Object[]{
|
||||
R.drawable.baseline_block_24,
|
||||
R.drawable.ic_edit,
|
||||
R.drawable.baseline_image_24,
|
||||
R.drawable.baseline_picture_as_pdf_24,
|
||||
R.drawable.local_activity_24px
|
||||
};
|
||||
String[] columns = new String[]{"text", "icon"};
|
||||
|
||||
for (int i = 0; i < texts.length; i++) {
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put(columns[0], texts[i]);
|
||||
map.put(columns[1], icons[i]);
|
||||
list.add(map);
|
||||
}
|
||||
|
||||
ListAdapter adapter = new SimpleAdapter(
|
||||
ScanActivity.this,
|
||||
list,
|
||||
R.layout.alertdialog_row_with_icon,
|
||||
columns,
|
||||
new int[]{R.id.textView, R.id.imageView}
|
||||
);
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ScanActivity.this);
|
||||
builder.setTitle(getString(R.string.add_a_card_in_a_different_way));
|
||||
builder.setAdapter(
|
||||
adapter,
|
||||
(dialogInterface, i) -> {
|
||||
switch (i) {
|
||||
case 0:
|
||||
addWithoutBarcode();
|
||||
break;
|
||||
case 1:
|
||||
addManually();
|
||||
break;
|
||||
case 2:
|
||||
addFromImage();
|
||||
break;
|
||||
case 3:
|
||||
addFromPdf();
|
||||
break;
|
||||
case 4:
|
||||
addFromPkPass();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown 'Add a card in a different way' dialog option");
|
||||
}
|
||||
}
|
||||
);
|
||||
builder.setOnCancelListener(dialogInterface -> setScannerActive(true));
|
||||
builder.show();
|
||||
});
|
||||
|
||||
// Configure barcodeScanner
|
||||
barcodeScannerView = binding.zxingBarcodeScanner;
|
||||
Intent barcodeScannerIntent = new Intent();
|
||||
Bundle barcodeScannerIntentBundle = new Bundle();
|
||||
barcodeScannerIntentBundle.putBoolean(DecodeHintType.ALSO_INVERTED.name(), Boolean.TRUE);
|
||||
barcodeScannerIntent.putExtras(barcodeScannerIntentBundle);
|
||||
barcodeScannerView.initializeFromIntent(barcodeScannerIntent);
|
||||
|
||||
// Even though we do the actual decoding with the barcodeScannerView
|
||||
// CaptureManager needs to be running to show the camera and scanning bar
|
||||
capture = new CatimaCaptureManager(this, barcodeScannerView, this::onCaptureManagerError);
|
||||
Intent captureIntent = new Intent();
|
||||
Bundle captureIntentBundle = new Bundle();
|
||||
captureIntentBundle.putBoolean(Intents.Scan.BEEP_ENABLED, false);
|
||||
captureIntent.putExtras(captureIntentBundle);
|
||||
capture.initializeFromIntent(captureIntent, savedInstanceState);
|
||||
|
||||
barcodeScannerView.decodeSingle(new BarcodeCallback() {
|
||||
@Override
|
||||
public void barcodeResult(BarcodeResult result) {
|
||||
LoyaltyCard loyaltyCard = new LoyaltyCard();
|
||||
loyaltyCard.setCardId(result.getText());
|
||||
loyaltyCard.setBarcodeType(CatimaBarcode.fromBarcode(result.getBarcodeFormat()));
|
||||
|
||||
returnResult(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void possibleResultPoints(List<ResultPoint> resultPoints) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (mScannerActive) {
|
||||
capture.onResume();
|
||||
}
|
||||
|
||||
if (!Utils.deviceHasCamera(this)) {
|
||||
showCameraError(getString(R.string.noCameraFoundGuideText), false);
|
||||
} else if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
||||
showCameraPermissionMissingText();
|
||||
} else {
|
||||
hideCameraError();
|
||||
}
|
||||
|
||||
scaleScreen();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
capture.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
capture.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle savedInstanceState) {
|
||||
super.onSaveInstanceState(savedInstanceState);
|
||||
capture.onSaveInstanceState(savedInstanceState);
|
||||
|
||||
savedInstanceState.putBoolean(STATE_SCANNER_ACTIVE, mScannerActive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
|
||||
mScannerActive = savedInstanceState.getBoolean(STATE_SCANNER_ACTIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
|
||||
getMenuInflater().inflate(R.menu.scan_menu, menu);
|
||||
}
|
||||
|
||||
barcodeScannerView.setTorchOff();
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_toggle_flashlight) {
|
||||
if (torch) {
|
||||
torch = false;
|
||||
barcodeScannerView.setTorchOff();
|
||||
item.setTitle(R.string.turn_flashlight_on);
|
||||
item.setIcon(R.drawable.ic_flashlight_off_white_24dp);
|
||||
} else {
|
||||
torch = true;
|
||||
barcodeScannerView.setTorchOn();
|
||||
item.setTitle(R.string.turn_flashlight_off);
|
||||
item.setIcon(R.drawable.ic_flashlight_on_white_24dp);
|
||||
}
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void setScannerActive(boolean isActive) {
|
||||
if (isActive) {
|
||||
barcodeScannerView.resume();
|
||||
} else {
|
||||
barcodeScannerView.pause();
|
||||
}
|
||||
mScannerActive = isActive;
|
||||
}
|
||||
|
||||
private void returnResult(ParseResult parseResult) {
|
||||
Intent result = new Intent();
|
||||
Bundle bundle = parseResult.toLoyaltyCardBundle(ScanActivity.this);
|
||||
if (addGroup != null) {
|
||||
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
|
||||
}
|
||||
result.putExtras(bundle);
|
||||
ScanActivity.this.setResult(RESULT_OK, result);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void handleActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
super.onActivityResult(requestCode, resultCode, intent);
|
||||
|
||||
List<ParseResult> parseResultList = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
|
||||
|
||||
if (parseResultList.isEmpty()) {
|
||||
setScannerActive(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Utils.makeUserChooseParseResultFromList(this, parseResultList, new ParseResultListDisambiguatorCallback() {
|
||||
@Override
|
||||
public void onUserChoseParseResult(ParseResult parseResult) {
|
||||
returnResult(parseResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserDismissedSelector() {
|
||||
setScannerActive(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addWithoutBarcode() {
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||
|
||||
builder.setOnCancelListener(dialogInterface -> setScannerActive(true));
|
||||
|
||||
// Header
|
||||
builder.setTitle(R.string.addWithoutBarcode);
|
||||
|
||||
// Layout
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
int contentPadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
|
||||
params.leftMargin = contentPadding;
|
||||
params.topMargin = contentPadding / 2;
|
||||
params.rightMargin = contentPadding;
|
||||
|
||||
// Description
|
||||
TextView currentTextview = new TextView(this);
|
||||
currentTextview.setText(getString(R.string.enter_card_id));
|
||||
currentTextview.setLayoutParams(params);
|
||||
layout.addView(currentTextview);
|
||||
|
||||
// EditText with spacing
|
||||
final EditText input = new EditText(this);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
input.setLayoutParams(params);
|
||||
layout.addView(input);
|
||||
|
||||
// Set layout
|
||||
builder.setView(layout);
|
||||
|
||||
// Buttons
|
||||
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
|
||||
LoyaltyCard loyaltyCard = new LoyaltyCard();
|
||||
loyaltyCard.setCardId(input.getText().toString());
|
||||
returnResult(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
|
||||
});
|
||||
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
// Now that the dialog exists, we can bind something that affects the OK button
|
||||
input.addTextChangedListener(new SimpleTextWatcher() {
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
if (s.length() == 0) {
|
||||
input.setError(getString(R.string.card_id_must_not_be_empty));
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
} else {
|
||||
input.setError(null);
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
|
||||
// Disable button (must be done **after** dialog is shown to prevent crash
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
// Set focus on input field
|
||||
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||
input.requestFocus();
|
||||
}
|
||||
|
||||
public void addManually() {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ScanActivity.this);
|
||||
builder.setTitle(R.string.add_manually_warning_title);
|
||||
builder.setMessage(R.string.add_manually_warning_message);
|
||||
builder.setPositiveButton(R.string.continue_, (dialog, which) -> {
|
||||
Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class);
|
||||
if (cardId != null) {
|
||||
final Bundle b = new Bundle();
|
||||
b.putString(LoyaltyCard.BUNDLE_LOYALTY_CARD_CARD_ID, cardId);
|
||||
i.putExtras(b);
|
||||
}
|
||||
manualAddLauncher.launch(i);
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel, (dialog, which) -> setScannerActive(true));
|
||||
builder.setOnCancelListener(dialog -> setScannerActive(true));
|
||||
builder.show();
|
||||
}
|
||||
|
||||
public void addFromImage() {
|
||||
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_IMAGE);
|
||||
}
|
||||
|
||||
public void addFromPdf() {
|
||||
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PDF);
|
||||
}
|
||||
|
||||
public void addFromPkPass() {
|
||||
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PKPASS);
|
||||
}
|
||||
|
||||
private void addFromImageOrFileAfterPermission(String mimeType, ActivityResultLauncher<Intent> launcher, int chooserText, int errorMessage) {
|
||||
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
|
||||
photoPickerIntent.setType(mimeType);
|
||||
Intent contentIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
contentIntent.setType(mimeType);
|
||||
|
||||
Intent chooserIntent = Intent.createChooser(photoPickerIntent, getString(chooserText));
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { contentIntent });
|
||||
try {
|
||||
launcher.launch(chooserIntent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
setScannerActive(true);
|
||||
Toast.makeText(getApplicationContext(), errorMessage, Toast.LENGTH_LONG).show();
|
||||
Log.e(TAG, "No activity found to handle intent", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onCaptureManagerError(String errorMessage) {
|
||||
if (mHasError) {
|
||||
// We're already showing an error, ignore this new error
|
||||
return;
|
||||
}
|
||||
|
||||
showCameraError(errorMessage, false);
|
||||
}
|
||||
|
||||
private void showCameraPermissionMissingText() {
|
||||
showCameraError(getString(R.string.noCameraPermissionDirectToSystemSetting), true);
|
||||
}
|
||||
|
||||
private void showCameraError(String message, boolean setOnClick) {
|
||||
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorMessage.setText(message);
|
||||
|
||||
setCameraErrorState(true, setOnClick);
|
||||
}
|
||||
|
||||
private void hideCameraError() {
|
||||
setCameraErrorState(false, false);
|
||||
}
|
||||
|
||||
private void setCameraErrorState(boolean visible, boolean setOnClick) {
|
||||
mHasError = visible;
|
||||
|
||||
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorClickableArea.setOnClickListener(visible && setOnClick ? v -> {
|
||||
navigateToSystemPermissionSetting();
|
||||
} : null);
|
||||
customBarcodeScannerBinding.cardInputContainer.setBackgroundColor(visible ? obtainThemeAttribute(com.google.android.material.R.attr.colorSurface) : Color.TRANSPARENT);
|
||||
customBarcodeScannerBinding.cameraErrorLayout.getRoot().setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void scaleScreen() {
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
|
||||
int screenHeight = displayMetrics.heightPixels;
|
||||
float mediumSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,MEDIUM_SCALE_FACTOR_DIP,getResources().getDisplayMetrics());
|
||||
boolean shouldScaleSmaller = screenHeight < mediumSizePx;
|
||||
|
||||
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorIcon.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
|
||||
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorTitle.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
private int obtainThemeAttribute(int attribute) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
getTheme().resolveAttribute(attribute, typedValue, true);
|
||||
return typedValue.data;
|
||||
}
|
||||
|
||||
private void navigateToSystemPermissionSetting() {
|
||||
Intent permissionIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", getPackageName(), null));
|
||||
permissionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(permissionIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
onMockedRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
if (requestCode == CaptureManager.getCameraPermissionReqCode()) {
|
||||
if (granted) {
|
||||
hideCameraError();
|
||||
} else {
|
||||
showCameraPermissionMissingText();
|
||||
}
|
||||
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE || requestCode == PERMISSION_SCAN_ADD_FROM_PDF || requestCode == PERMISSION_SCAN_ADD_FROM_PKPASS) {
|
||||
if (granted) {
|
||||
if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
|
||||
addFromImageOrFileAfterPermission("image/*", photoPickerLauncher, R.string.addFromImage, R.string.failedLaunchingPhotoPicker);
|
||||
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_PDF) {
|
||||
addFromImageOrFileAfterPermission("application/pdf", pdfPickerLauncher, R.string.addFromPdfFile, R.string.failedLaunchingFileManager);
|
||||
} else {
|
||||
addFromImageOrFileAfterPermission("application/*", pkpassPickerLauncher, R.string.addFromPkpass, R.string.failedLaunchingFileManager);
|
||||
}
|
||||
} else {
|
||||
setScannerActive(true);
|
||||
Toast.makeText(this, R.string.storageReadPermissionRequired, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
599
app/src/main/java/protect/card_locker/ScanActivity.kt
Normal file
599
app/src/main/java/protect/card_locker/ScanActivity.kt
Normal file
@@ -0,0 +1,599 @@
|
||||
package protect.card_locker
|
||||
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.text.InputType
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ListAdapter
|
||||
import android.widget.SimpleAdapter
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.zxing.DecodeHintType
|
||||
import com.google.zxing.ResultPoint
|
||||
import com.journeyapps.barcodescanner.BarcodeCallback
|
||||
import com.journeyapps.barcodescanner.BarcodeResult
|
||||
import com.journeyapps.barcodescanner.CaptureManager
|
||||
import com.journeyapps.barcodescanner.DecoratedBarcodeView
|
||||
import protect.card_locker.databinding.CustomBarcodeScannerBinding
|
||||
import protect.card_locker.databinding.ScanActivityBinding
|
||||
|
||||
/**
|
||||
* Custom Scannner Activity extending from Activity to display a custom layout form scanner view.
|
||||
* <p>
|
||||
* Based on https://github.com/journeyapps/zxing-android-embedded/blob/0fdfbce9fb3285e985bad9971c5f7c0a7a334e7b/sample/src/main/java/example/zxing/CustomScannerActivity.java
|
||||
* originally licensed under Apache 2.0
|
||||
*/
|
||||
class ScanActivity : CatimaAppCompatActivity() {
|
||||
private lateinit var binding: ScanActivityBinding
|
||||
private lateinit var customBarcodeScannerBinding: CustomBarcodeScannerBinding
|
||||
|
||||
companion object {
|
||||
private const val TAG = "Catima"
|
||||
|
||||
private const val MEDIUM_SCALE_FACTOR_DIP = 460
|
||||
private const val COMPAT_SCALE_FACTOR_DIP = 320
|
||||
|
||||
private const val PERMISSION_SCAN_ADD_FROM_IMAGE = 100
|
||||
private const val PERMISSION_SCAN_ADD_FROM_PDF = 101
|
||||
private const val PERMISSION_SCAN_ADD_FROM_PKPASS = 102
|
||||
|
||||
private const val STATE_SCANNER_ACTIVE = "scannerActive"
|
||||
}
|
||||
|
||||
private lateinit var capture: CaptureManager
|
||||
private lateinit var barcodeScannerView: DecoratedBarcodeView
|
||||
private var cardId: String? = null
|
||||
private var addGroup: String? = null
|
||||
private var torch = false
|
||||
|
||||
private lateinit var manualAddLauncher: ActivityResultLauncher<Intent>
|
||||
// can't use the pre-made contract because that launches the file manager for image type instead of gallery
|
||||
private lateinit var photoPickerLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var pdfPickerLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var pkpassPickerLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
private var mScannerActive = true
|
||||
private var mHasError = false
|
||||
|
||||
private fun extractIntentFields(intent: Intent) {
|
||||
val b = intent.extras
|
||||
cardId = b?.getString(LoyaltyCard.BUNDLE_LOYALTY_CARD_CARD_ID)
|
||||
addGroup = b?.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP)
|
||||
Log.d(TAG, "Scan activity: id=$cardId")
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ScanActivityBinding.inflate(layoutInflater)
|
||||
customBarcodeScannerBinding = CustomBarcodeScannerBinding.bind(binding.zxingBarcodeScanner)
|
||||
setTitle(R.string.scanCardBarcode)
|
||||
setContentView(binding.root)
|
||||
Utils.applyWindowInsets(binding.root)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
enableToolbarBackButton()
|
||||
|
||||
extractIntentFields(intent)
|
||||
|
||||
manualAddLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
handleActivityResult(
|
||||
Utils.SELECT_BARCODE_REQUEST,
|
||||
result.resultCode,
|
||||
result.data
|
||||
)
|
||||
}
|
||||
photoPickerLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
handleActivityResult(
|
||||
Utils.BARCODE_IMPORT_FROM_IMAGE_FILE,
|
||||
result.resultCode,
|
||||
result.data
|
||||
)
|
||||
}
|
||||
pdfPickerLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
handleActivityResult(
|
||||
Utils.BARCODE_IMPORT_FROM_PDF_FILE,
|
||||
result.resultCode,
|
||||
result.data
|
||||
)
|
||||
}
|
||||
pkpassPickerLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
handleActivityResult(
|
||||
Utils.BARCODE_IMPORT_FROM_PKPASS_FILE,
|
||||
result.resultCode,
|
||||
result.data
|
||||
)
|
||||
}
|
||||
|
||||
customBarcodeScannerBinding.fabOtherOptions.setOnClickListener {
|
||||
setScannerActive(false)
|
||||
|
||||
val list: ArrayList<HashMap<String, Any>> = arrayListOf()
|
||||
val texts = arrayOf(
|
||||
getString(R.string.addWithoutBarcode),
|
||||
getString(R.string.addManually),
|
||||
getString(R.string.addFromImage),
|
||||
getString(R.string.addFromPdfFile),
|
||||
getString(R.string.addFromPkpass)
|
||||
)
|
||||
val icons = arrayOf(
|
||||
R.drawable.baseline_block_24,
|
||||
R.drawable.ic_edit,
|
||||
R.drawable.baseline_image_24,
|
||||
R.drawable.baseline_picture_as_pdf_24,
|
||||
R.drawable.local_activity_24px
|
||||
)
|
||||
val columns = arrayOf("text", "icon")
|
||||
|
||||
for (i in 0 until texts.size) {
|
||||
val map: HashMap<String, Any> = hashMapOf()
|
||||
map.put(columns[0], texts[i])
|
||||
map.put(columns[1], icons[i])
|
||||
list.add(map)
|
||||
}
|
||||
|
||||
val adapter: ListAdapter = SimpleAdapter(
|
||||
this,
|
||||
list,
|
||||
R.layout.alertdialog_row_with_icon,
|
||||
columns,
|
||||
intArrayOf(R.id.textView, R.id.imageView)
|
||||
)
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(this).apply {
|
||||
setTitle(getString(R.string.add_a_card_in_a_different_way))
|
||||
setAdapter(adapter) { _, i ->
|
||||
when (i) {
|
||||
0 -> addWithoutBarcode()
|
||||
1 -> addManually()
|
||||
2 -> addFromImage()
|
||||
3 -> addFromPdf()
|
||||
4 -> addFromPkPass()
|
||||
else -> throw IllegalArgumentException(
|
||||
"Unknown 'Add a card in a different way' dialog option: $i"
|
||||
)
|
||||
}
|
||||
}
|
||||
setOnCancelListener { _ -> setScannerActive(true) }
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
|
||||
// Configure barcodeScanner
|
||||
barcodeScannerView = binding.zxingBarcodeScanner
|
||||
|
||||
val barcodeScannerIntent = Intent().apply {
|
||||
val barcodeScannerIntentBundle = Bundle().apply {
|
||||
putBoolean(DecodeHintType.ALSO_INVERTED.name, true)
|
||||
}
|
||||
putExtras(barcodeScannerIntentBundle)
|
||||
}
|
||||
barcodeScannerView.initializeFromIntent(barcodeScannerIntent)
|
||||
|
||||
// Even though we do the actual decoding with the barcodeScannerView
|
||||
// CaptureManager needs to be running to show the camera and scanning bar
|
||||
capture = CatimaCaptureManager(this, barcodeScannerView, this::onCaptureManagerError)
|
||||
val captureIntent = Intent().apply {
|
||||
val captureIntentBundle = Bundle().apply {
|
||||
putBoolean(DecodeHintType.ALSO_INVERTED.name, false)
|
||||
}
|
||||
putExtras(captureIntentBundle)
|
||||
}
|
||||
capture.initializeFromIntent(captureIntent, savedInstanceState)
|
||||
|
||||
barcodeScannerView.decodeSingle(object : BarcodeCallback {
|
||||
override fun barcodeResult(result: BarcodeResult) {
|
||||
val loyaltyCard = LoyaltyCard().apply {
|
||||
setCardId(result.text)
|
||||
setBarcodeType(CatimaBarcode.fromBarcode(result.barcodeFormat))
|
||||
}
|
||||
|
||||
returnResult(ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard))
|
||||
}
|
||||
|
||||
override fun possibleResultPoints(resultPoints: List<ResultPoint?>?) {}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
if (mScannerActive) {
|
||||
capture.onResume()
|
||||
}
|
||||
|
||||
if (!Utils.deviceHasCamera(this)) {
|
||||
showCameraError(getString(R.string.noCameraFoundGuideText), false)
|
||||
} else if (ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.CAMERA
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
showCameraPermissionMissingText()
|
||||
} else {
|
||||
hideCameraError()
|
||||
}
|
||||
|
||||
scaleScreen()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
capture.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
capture.onDestroy()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(savedInstanceState: Bundle) {
|
||||
super.onSaveInstanceState(savedInstanceState)
|
||||
capture.onSaveInstanceState(savedInstanceState)
|
||||
|
||||
savedInstanceState.putBoolean(STATE_SCANNER_ACTIVE, mScannerActive)
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||
super.onRestoreInstanceState(savedInstanceState)
|
||||
|
||||
mScannerActive = savedInstanceState.getBoolean(STATE_SCANNER_ACTIVE)
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||
return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
if (packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
|
||||
menuInflater.inflate(R.menu.scan_menu, menu)
|
||||
}
|
||||
|
||||
barcodeScannerView.setTorchOff()
|
||||
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
return true
|
||||
} else if (item.itemId == R.id.action_toggle_flashlight) {
|
||||
if (torch) {
|
||||
torch = false
|
||||
barcodeScannerView.setTorchOff()
|
||||
item.setTitle(R.string.turn_flashlight_on)
|
||||
item.setIcon(R.drawable.ic_flashlight_off_white_24dp)
|
||||
} else {
|
||||
torch = true
|
||||
barcodeScannerView.setTorchOn()
|
||||
item.setTitle(R.string.turn_flashlight_off)
|
||||
item.setIcon(R.drawable.ic_flashlight_on_white_24dp)
|
||||
}
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun setScannerActive(isActive: Boolean) {
|
||||
if (isActive) {
|
||||
barcodeScannerView.resume()
|
||||
} else {
|
||||
barcodeScannerView.pause()
|
||||
}
|
||||
mScannerActive = isActive
|
||||
}
|
||||
|
||||
private fun returnResult(parseResult: ParseResult) {
|
||||
val bundle = parseResult.toLoyaltyCardBundle(this).apply {
|
||||
addGroup?.let { putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, it) }
|
||||
}
|
||||
val result = Intent().apply { putExtras(bundle) }
|
||||
this.setResult(RESULT_OK, result)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun handleActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||
super.onActivityResult(resultCode, resultCode, intent)
|
||||
|
||||
val parseResultList: List<ParseResult> =
|
||||
Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this)
|
||||
|
||||
if (parseResultList.isEmpty()) {
|
||||
setScannerActive(true)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Utils.makeUserChooseParseResultFromList(
|
||||
this,
|
||||
parseResultList,
|
||||
object : ParseResultListDisambiguatorCallback {
|
||||
override fun onUserChoseParseResult(parseResult: ParseResult) {
|
||||
returnResult(parseResult)
|
||||
}
|
||||
|
||||
override fun onUserDismissedSelector() {
|
||||
setScannerActive(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun addWithoutBarcode() {
|
||||
val builder: AlertDialog.Builder = MaterialAlertDialogBuilder(this).apply {
|
||||
setOnCancelListener { dialogInterface -> setScannerActive(true) }
|
||||
// Header
|
||||
setTitle(R.string.addWithoutBarcode)
|
||||
}
|
||||
|
||||
// Layout
|
||||
val layout = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
}
|
||||
val contentPadding = resources.getDimensionPixelSize(R.dimen.alert_dialog_content_padding)
|
||||
val params = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
leftMargin = contentPadding
|
||||
topMargin = contentPadding / 2
|
||||
rightMargin = contentPadding
|
||||
}
|
||||
|
||||
// Description
|
||||
val currentTextview = TextView(this).apply {
|
||||
text = getString(R.string.enter_card_id)
|
||||
layoutParams = params
|
||||
}
|
||||
layout.addView(currentTextview)
|
||||
|
||||
//EditText with spacing
|
||||
val input = EditText(this).apply {
|
||||
inputType = InputType.TYPE_CLASS_TEXT
|
||||
layoutParams = params
|
||||
}
|
||||
layout.addView(input)
|
||||
|
||||
// Set layout
|
||||
builder.setView(layout).apply {
|
||||
|
||||
setPositiveButton(getString(R.string.ok)) { _, _ ->
|
||||
val loyaltyCard = LoyaltyCard()
|
||||
loyaltyCard.cardId = input.text.toString()
|
||||
returnResult(ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard))
|
||||
}
|
||||
setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
|
||||
dialog.cancel()
|
||||
}
|
||||
}
|
||||
val dialog: AlertDialog = builder.create()
|
||||
|
||||
// Now that the dialog exists, we can bind something that affects the OK button
|
||||
input.doOnTextChanged { text, _, _, _ ->
|
||||
if (text.isNullOrEmpty()) {
|
||||
input.error = getString(R.string.card_id_must_not_be_empty)
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
} else {
|
||||
input.error = null
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
|
||||
// Disable button (must be done **after** dialog is shown to prevent crash
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
// Set focus on input field
|
||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||
input.requestFocus()
|
||||
}
|
||||
|
||||
fun addManually() {
|
||||
val builder = MaterialAlertDialogBuilder(this).apply {
|
||||
setTitle(R.string.add_manually_warning_title)
|
||||
setMessage(R.string.add_manually_warning_message)
|
||||
setPositiveButton(R.string.continue_) { _, _ ->
|
||||
val i = Intent(applicationContext, BarcodeSelectorActivity::class.java)
|
||||
if (cardId != null) {
|
||||
val b = Bundle()
|
||||
b.putString(LoyaltyCard.BUNDLE_LOYALTY_CARD_CARD_ID, cardId)
|
||||
i.putExtras(b)
|
||||
}
|
||||
manualAddLauncher.launch(i)
|
||||
}
|
||||
setNegativeButton(R.string.cancel) { _, _ -> setScannerActive(true) }
|
||||
setOnCancelListener { _ -> setScannerActive(true) }
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
|
||||
fun addFromImage() {
|
||||
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_IMAGE)
|
||||
}
|
||||
|
||||
fun addFromPdf() {
|
||||
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PDF)
|
||||
}
|
||||
|
||||
fun addFromPkPass() {
|
||||
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PKPASS)
|
||||
}
|
||||
|
||||
private fun addFromImageOrFileAfterPermission(
|
||||
mimeType: String,
|
||||
launcher: ActivityResultLauncher<Intent>,
|
||||
chooserText: Int,
|
||||
errorMessage: Int
|
||||
) {
|
||||
val photoPickerIntent = Intent(Intent.ACTION_PICK)
|
||||
photoPickerIntent.type = mimeType
|
||||
val contentIntent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
contentIntent.type = mimeType
|
||||
|
||||
val chooserIntent = Intent.createChooser(photoPickerIntent, getString(chooserText))
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(contentIntent))
|
||||
try {
|
||||
launcher.launch(chooserIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
setScannerActive(true)
|
||||
Toast.makeText(applicationContext, errorMessage, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, "No activity found to handle intent", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun onCaptureManagerError(errorMessage: String) {
|
||||
if (mHasError) {
|
||||
// We're already showing an error, ignore this new error
|
||||
return
|
||||
}
|
||||
|
||||
showCameraError(errorMessage, false)
|
||||
}
|
||||
|
||||
private fun showCameraPermissionMissingText() {
|
||||
showCameraError(getString(R.string.noCameraPermissionDirectToSystemSetting), true)
|
||||
}
|
||||
|
||||
private fun showCameraError(message: String, setOnClick: Boolean) {
|
||||
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorMessage.text = message
|
||||
|
||||
setCameraErrorState(true, setOnClick)
|
||||
}
|
||||
|
||||
private fun hideCameraError() {
|
||||
setCameraErrorState(false, false)
|
||||
}
|
||||
|
||||
private fun setCameraErrorState(visible: Boolean, setOnClick: Boolean) {
|
||||
mHasError = visible
|
||||
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorClickableArea.setOnClickListener(
|
||||
if (visible && setOnClick) { _ -> navigateToSystemPermissionSetting() }
|
||||
else null
|
||||
)
|
||||
customBarcodeScannerBinding.cardInputContainer.setBackgroundColor(
|
||||
if (visible) obtainThemeAttribute(com.google.android.material.R.attr.colorSurface)
|
||||
else Color.TRANSPARENT
|
||||
)
|
||||
customBarcodeScannerBinding.cameraErrorLayout.root.visibility =
|
||||
if (visible) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun scaleScreen() {
|
||||
val displayMetrics = DisplayMetrics()
|
||||
windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
val screenHeight: Int = displayMetrics.heightPixels
|
||||
val mediumSizePx: Float = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
MEDIUM_SCALE_FACTOR_DIP.toFloat(),
|
||||
resources.displayMetrics
|
||||
)
|
||||
val shouldScaleSmaller = screenHeight < mediumSizePx
|
||||
|
||||
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorIcon.visibility =
|
||||
if (shouldScaleSmaller) View.GONE else View.VISIBLE
|
||||
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorTitle.visibility =
|
||||
if (shouldScaleSmaller) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
private fun obtainThemeAttribute(attribute: Int): Int {
|
||||
val typedValue = TypedValue()
|
||||
theme.resolveAttribute(attribute, typedValue, true)
|
||||
return typedValue.data
|
||||
}
|
||||
|
||||
private fun navigateToSystemPermissionSetting() {
|
||||
val permissionIntent = Intent(
|
||||
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
||||
Uri.fromParts("package", getPackageName(), null)
|
||||
)
|
||||
permissionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(permissionIntent)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String?>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
|
||||
onMockedRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
override fun onMockedRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String?>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
val granted =
|
||||
grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
if (requestCode == CaptureManager.getCameraPermissionReqCode()) {
|
||||
if (granted) {
|
||||
hideCameraError()
|
||||
} else {
|
||||
showCameraPermissionMissingText()
|
||||
}
|
||||
} else if (requestCode in listOf(
|
||||
PERMISSION_SCAN_ADD_FROM_IMAGE,
|
||||
PERMISSION_SCAN_ADD_FROM_PDF,
|
||||
PERMISSION_SCAN_ADD_FROM_PKPASS
|
||||
)
|
||||
) {
|
||||
if (granted) {
|
||||
if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
|
||||
addFromImageOrFileAfterPermission(
|
||||
"image/*",
|
||||
photoPickerLauncher,
|
||||
R.string.addFromImage,
|
||||
R.string.failedLaunchingPhotoPicker
|
||||
)
|
||||
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_PDF) {
|
||||
addFromImageOrFileAfterPermission(
|
||||
"application/pdf",
|
||||
pdfPickerLauncher,
|
||||
R.string.addFromPdfFile,
|
||||
R.string.failedLaunchingFileManager
|
||||
)
|
||||
} else {
|
||||
addFromImageOrFileAfterPermission(
|
||||
"application/*",
|
||||
pkpassPickerLauncher,
|
||||
R.string.addFromPkpass,
|
||||
R.string.failedLaunchingFileManager
|
||||
)
|
||||
}
|
||||
} else {
|
||||
setScannerActive(true)
|
||||
Toast.makeText(this, R.string.storageReadPermissionRequired, Toast.LENGTH_LONG)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.view.WindowInsetsControllerCompat;
|
||||
|
||||
import com.google.android.material.color.MaterialColors;
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
import com.yalantis.ucrop.UCropActivity;
|
||||
|
||||
public class UCropWrapper extends UCropActivity {
|
||||
public static final String UCROP_TOOLBAR_TYPEFACE_STYLE = "ucop_toolbar_typeface_style";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Utils.applyWindowInsets(findViewById(android.R.id.content));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
boolean darkMode = Utils.isDarkModeEnabled(this);
|
||||
Window window = getWindow();
|
||||
// setup status bar to look like the rest of the app
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
if (window != null) {
|
||||
View decorView = window.getDecorView();
|
||||
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(window, decorView);
|
||||
wic.setAppearanceLightStatusBars(!darkMode);
|
||||
}
|
||||
} else {
|
||||
// icons are always white back then
|
||||
if (window != null && !darkMode) {
|
||||
window.setStatusBarColor(ColorUtils.compositeColors(Color.argb(127, 0, 0, 0), window.getStatusBarColor()));
|
||||
}
|
||||
}
|
||||
|
||||
// find and check views that we wish to color modify
|
||||
// for when we update ucrop or switch to another cropper
|
||||
View check = findViewById(com.yalantis.ucrop.R.id.wrapper_controls);
|
||||
if (check instanceof FrameLayout) {
|
||||
FrameLayout controls = (FrameLayout) check;
|
||||
check = findViewById(com.yalantis.ucrop.R.id.wrapper_states);
|
||||
if (check instanceof LinearLayout) {
|
||||
LinearLayout states = (LinearLayout) check;
|
||||
for (int i = 0; i < controls.getChildCount(); i++) {
|
||||
check = controls.getChildAt(i);
|
||||
if (check instanceof AppCompatImageView) {
|
||||
AppCompatImageView controlsBackgroundImage = (AppCompatImageView) check;
|
||||
// everything gathered and are as expected, now perform color patching
|
||||
Utils.patchColors(this);
|
||||
int colorSurface = MaterialColors.getColor(this, com.google.android.material.R.attr.colorSurface, ContextCompat.getColor(this, R.color.md_theme_light_surface));
|
||||
int colorOnSurface = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSurface, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
|
||||
|
||||
Drawable controlsBackgroundImageDrawable = controlsBackgroundImage.getBackground();
|
||||
controlsBackgroundImageDrawable.mutate();
|
||||
controlsBackgroundImageDrawable.setTint(darkMode ? colorOnSurface : colorSurface);
|
||||
controlsBackgroundImage.setBackgroundDrawable(controlsBackgroundImageDrawable);
|
||||
|
||||
states.setBackgroundColor(darkMode ? colorSurface : colorOnSurface);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// change toolbar font
|
||||
check = findViewById(com.yalantis.ucrop.R.id.toolbar_title);
|
||||
if (check instanceof MaterialTextView) {
|
||||
MaterialTextView toolbarTextview = (MaterialTextView) check;
|
||||
Intent intent = getIntent();
|
||||
int style = intent.getIntExtra(UCROP_TOOLBAR_TYPEFACE_STYLE, -1);
|
||||
if (style != -1) {
|
||||
toolbarTextview.setTypeface(Typeface.defaultFromStyle(style));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
122
app/src/main/java/protect/card_locker/UCropWrapper.kt
Normal file
122
app/src/main/java/protect/card_locker/UCropWrapper.kt
Normal file
@@ -0,0 +1,122 @@
|
||||
package protect.card_locker
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.core.view.children
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import com.yalantis.ucrop.UCropActivity
|
||||
|
||||
class UCropWrapper : UCropActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
Utils.applyWindowInsets(findViewById(android.R.id.content))
|
||||
}
|
||||
|
||||
override fun onPostCreate(savedInstanceState: Bundle?) {
|
||||
super.onPostCreate(savedInstanceState)
|
||||
val darkMode = Utils.isDarkModeEnabled(this)
|
||||
// setup status bar to look like the rest of the app
|
||||
setupStatusBar(darkMode)
|
||||
// find and check views that we wish to color modify
|
||||
// for when we update ucrop or switch to another cropper
|
||||
checkViews(darkMode)
|
||||
// change toolbar font
|
||||
changeToolbarFont()
|
||||
}
|
||||
|
||||
private fun setupStatusBar(darkMode: Boolean) {
|
||||
if (window == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
val decorView = window.decorView
|
||||
val wic = WindowInsetsControllerCompat(window, decorView)
|
||||
wic.isAppearanceLightStatusBars = !darkMode
|
||||
} else if (!darkMode) {
|
||||
window.statusBarColor = ColorUtils.compositeColors(
|
||||
Color.argb(127, 0, 0, 0),
|
||||
window.statusBarColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkViews(darkMode: Boolean) {
|
||||
var view = findViewById<View?>(com.yalantis.ucrop.R.id.wrapper_controls)
|
||||
if (view !is FrameLayout) {
|
||||
return
|
||||
}
|
||||
|
||||
val controls = view
|
||||
view = findViewById(com.yalantis.ucrop.R.id.wrapper_states)
|
||||
if (view !is LinearLayout) {
|
||||
return
|
||||
}
|
||||
val states = view
|
||||
controls.children.firstOrNull { it is AppCompatImageView }?.let {
|
||||
// everything gathered and are as expected, now perform color patching
|
||||
Utils.patchColors(this)
|
||||
val colorSurface = MaterialColors.getColor(
|
||||
this,
|
||||
com.google.android.material.R.attr.colorSurface,
|
||||
ContextCompat.getColor(
|
||||
this,
|
||||
R.color.md_theme_light_surface
|
||||
)
|
||||
)
|
||||
val colorOnSurface = MaterialColors.getColor(
|
||||
this,
|
||||
com.google.android.material.R.attr.colorOnSurface,
|
||||
ContextCompat.getColor(
|
||||
this,
|
||||
R.color.md_theme_light_onSurface
|
||||
)
|
||||
)
|
||||
|
||||
val controlsBackgroundImageDrawable = it.background
|
||||
controlsBackgroundImageDrawable.mutate()
|
||||
controlsBackgroundImageDrawable.setTint(
|
||||
if (darkMode) {
|
||||
colorOnSurface
|
||||
} else {
|
||||
colorSurface
|
||||
}
|
||||
)
|
||||
it.background = controlsBackgroundImageDrawable
|
||||
states.setBackgroundColor(
|
||||
if (darkMode) {
|
||||
colorSurface
|
||||
} else {
|
||||
colorOnSurface
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeToolbarFont() {
|
||||
val toolbar = findViewById<View?>(com.yalantis.ucrop.R.id.toolbar_title)
|
||||
if (toolbar !is MaterialTextView) {
|
||||
return
|
||||
}
|
||||
|
||||
val style = intent.getIntExtra(UCROP_TOOLBAR_TYPEFACE_STYLE, -1)
|
||||
if (style != -1) {
|
||||
toolbar.setTypeface(Typeface.defaultFromStyle(style))
|
||||
}
|
||||
}
|
||||
|
||||
internal companion object {
|
||||
const val UCROP_TOOLBAR_TYPEFACE_STYLE: String = "ucop_toolbar_typeface_style"
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ public class Utils {
|
||||
int pixelSize = context.getResources().getDimensionPixelSize(R.dimen.tileLetterImageSize);
|
||||
|
||||
if (backgroundColor == null) {
|
||||
backgroundColor = LetterBitmap.getDefaultColor(context, store);
|
||||
backgroundColor = LetterBitmap.Companion.getDefaultColor(context, store);
|
||||
}
|
||||
|
||||
return new LetterBitmap(context, store, store,
|
||||
@@ -1129,7 +1129,7 @@ public class Utils {
|
||||
}
|
||||
|
||||
public static int getHeaderColor(Context context, LoyaltyCard loyaltyCard) {
|
||||
return loyaltyCard.headerColor != null ? loyaltyCard.headerColor : LetterBitmap.getDefaultColor(context, loyaltyCard.store);
|
||||
return loyaltyCard.headerColor != null ? loyaltyCard.headerColor : LetterBitmap.Companion.getDefaultColor(context, loyaltyCard.store);
|
||||
}
|
||||
|
||||
public static String checksum(InputStream input) throws IOException {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package protect.card_locker.async;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public interface CompatCallable<T> extends Callable<T> {
|
||||
void onPostExecute(Object result);
|
||||
|
||||
void onPreExecute();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package protect.card_locker.async
|
||||
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
interface CompatCallable<T> : Callable<T?> {
|
||||
fun onPostExecute(result: Any?)
|
||||
|
||||
fun onPreExecute()
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
public enum DataFormat {
|
||||
Catima,
|
||||
Fidme,
|
||||
VoucherVault;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package protect.card_locker.importexport
|
||||
|
||||
enum class DataFormat {
|
||||
Catima,
|
||||
Fidme,
|
||||
VoucherVault
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Interface for a class which can export the contents of the database
|
||||
* in a given format.
|
||||
*/
|
||||
public interface Exporter {
|
||||
/**
|
||||
* Export the database to the output stream in a given format.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
void exportData(Context context, SQLiteDatabase database, OutputStream output, char[] password) throws IOException, InterruptedException;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package protect.card_locker.importexport
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* Interface for a class which can export the contents of the database
|
||||
* in a given format.
|
||||
*/
|
||||
interface Exporter {
|
||||
/**
|
||||
* Export the database to the output stream in a given format.
|
||||
*
|
||||
* @throws IOException, InterruptedException
|
||||
*/
|
||||
@Throws(IOException::class, InterruptedException::class)
|
||||
fun exportData(
|
||||
context: Context,
|
||||
database: SQLiteDatabase,
|
||||
output: OutputStream,
|
||||
password: CharArray
|
||||
)
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
public enum ImportExportResultType {
|
||||
Success,
|
||||
GenericFailure,
|
||||
BadPassword;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package protect.card_locker.importexport
|
||||
|
||||
enum class ImportExportResultType {
|
||||
Success,
|
||||
GenericFailure,
|
||||
BadPassword
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
|
||||
import protect.card_locker.FormatException;
|
||||
|
||||
/**
|
||||
* Interface for a class which can import the contents of a stream
|
||||
* into the database.
|
||||
*/
|
||||
public interface Importer {
|
||||
/**
|
||||
* Import data from the input stream in a given format into
|
||||
* the database.
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws FormatException
|
||||
*/
|
||||
void importData(Context context, SQLiteDatabase database, File inputFile, char[] password) throws IOException, FormatException, InterruptedException, JSONException, ParseException;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package protect.card_locker.importexport
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import org.json.JSONException
|
||||
import protect.card_locker.FormatException
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.text.ParseException
|
||||
|
||||
/**
|
||||
* Interface for a class which can import the contents of a stream
|
||||
* into the database.
|
||||
*/
|
||||
interface Importer {
|
||||
/**
|
||||
* Import data from the input stream in a given format into
|
||||
* the database.
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws FormatException
|
||||
* @throws InterruptedException
|
||||
* @throws JSONException
|
||||
* @throws ParseException
|
||||
*/
|
||||
@Throws(
|
||||
IOException::class,
|
||||
FormatException::class,
|
||||
InterruptedException::class,
|
||||
JSONException::class,
|
||||
ParseException::class
|
||||
)
|
||||
fun importData(
|
||||
context: Context,
|
||||
database: SQLiteDatabase,
|
||||
inputFile: File,
|
||||
password: CharArray
|
||||
)
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
package protect.card_locker.preferences;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import com.google.android.material.color.DynamicColors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import protect.card_locker.BuildConfig;
|
||||
import protect.card_locker.CatimaAppCompatActivity;
|
||||
import protect.card_locker.MainActivity;
|
||||
import protect.card_locker.R;
|
||||
import protect.card_locker.Utils;
|
||||
import protect.card_locker.databinding.SettingsActivityBinding;
|
||||
|
||||
public class SettingsActivity extends CatimaAppCompatActivity {
|
||||
|
||||
private SettingsActivityBinding binding;
|
||||
private final static String RELOAD_MAIN_STATE = "mReloadMain";
|
||||
private SettingsFragment fragment;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = SettingsActivityBinding.inflate(getLayoutInflater());
|
||||
setTitle(R.string.settings);
|
||||
setContentView(binding.getRoot());
|
||||
Utils.applyWindowInsets(binding.getRoot());
|
||||
Toolbar toolbar = binding.toolbar;
|
||||
setSupportActionBar(toolbar);
|
||||
enableToolbarBackButton();
|
||||
|
||||
// Display the fragment as the main content.
|
||||
fragment = new SettingsFragment();
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.settings_container, fragment)
|
||||
.commit();
|
||||
|
||||
// restore reload main state
|
||||
if (savedInstanceState != null) {
|
||||
fragment.mReloadMain = savedInstanceState.getBoolean(RELOAD_MAIN_STATE);
|
||||
}
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
finishSettingsActivity();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(RELOAD_MAIN_STATE, fragment.mReloadMain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
|
||||
if (id == android.R.id.home) {
|
||||
finishSettingsActivity();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void finishSettingsActivity() {
|
||||
if (fragment.mReloadMain) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(MainActivity.RESTART_ACTIVITY_INTENT, true);
|
||||
setResult(Activity.RESULT_OK, intent);
|
||||
} else {
|
||||
setResult(Activity.RESULT_OK);
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragmentCompat {
|
||||
private static final String DIALOG_FRAGMENT_TAG = "SettingsFragment";
|
||||
|
||||
public boolean mReloadMain;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
// Show pretty names and summaries
|
||||
ListPreference themePreference = findPreference(getResources().getString(R.string.settings_key_theme));
|
||||
assert themePreference != null;
|
||||
themePreference.setOnPreferenceChangeListener((preference, o) -> {
|
||||
if (o.toString().equals(getResources().getString(R.string.settings_key_light_theme))) {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||
} else if (o.toString().equals(getResources().getString(R.string.settings_key_dark_theme))) {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||
} else {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
ListPreference themeColorPreference = findPreference(getResources().getString(R.string.setting_key_theme_color));
|
||||
assert themeColorPreference != null;
|
||||
themeColorPreference.setOnPreferenceChangeListener((preference, o) -> {
|
||||
refreshActivity(true);
|
||||
return true;
|
||||
});
|
||||
if (!DynamicColors.isDynamicColorAvailable()) {
|
||||
themeColorPreference.setEntryValues(R.array.color_values_no_dynamic);
|
||||
themeColorPreference.setEntries(R.array.color_value_strings_no_dynamic);
|
||||
}
|
||||
|
||||
Preference oledDarkPreference = findPreference(getResources().getString(R.string.settings_key_oled_dark));
|
||||
assert oledDarkPreference != null;
|
||||
oledDarkPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
refreshActivity(true);
|
||||
return true;
|
||||
});
|
||||
|
||||
ListPreference localePreference = findPreference(getResources().getString(R.string.settings_key_locale));
|
||||
assert localePreference != null;
|
||||
CharSequence[] entryValues = localePreference.getEntryValues();
|
||||
List<CharSequence> entries = new ArrayList<>();
|
||||
for (CharSequence entry : entryValues) {
|
||||
if (entry.length() == 0) {
|
||||
entries.add(getResources().getString(R.string.settings_system_locale));
|
||||
} else {
|
||||
Locale entryLocale = Utils.stringToLocale(entry.toString());
|
||||
entries.add(entryLocale.getDisplayName(entryLocale));
|
||||
}
|
||||
}
|
||||
localePreference.setEntries(entries.toArray(new CharSequence[entryValues.length]));
|
||||
// Make locale picker preference in sync with system settings
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
Locale sysLocale = AppCompatDelegate.getApplicationLocales().get(0);
|
||||
if (sysLocale == null) {
|
||||
// Corresponds to "System"
|
||||
localePreference.setValue("");
|
||||
} else {
|
||||
// Need to set preference's value to one of localePreference.getEntryValues() to match the locale.
|
||||
// Locale.toLanguageTag() theoretically should be one of the values in localePreference.getEntryValues()...
|
||||
// But it doesn't work for some locales. so trying something more heavyweight.
|
||||
|
||||
// Obtain all locales supported by the app.
|
||||
List<Locale> appLocales = Arrays.stream(localePreference.getEntryValues())
|
||||
.map(Objects::toString)
|
||||
.map(Utils::stringToLocale)
|
||||
.collect(Collectors.toList());
|
||||
// Get the app locale that best matches the system one
|
||||
Locale bestMatchLocale = Utils.getBestMatchLocale(appLocales, sysLocale);
|
||||
// Get its index in supported locales
|
||||
int index = appLocales.indexOf(bestMatchLocale);
|
||||
// Set preference value to entry value at that index
|
||||
localePreference.setValue(localePreference.getEntryValues()[index].toString());
|
||||
}
|
||||
}
|
||||
|
||||
localePreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
// See corresponding comment in Utils.updateBaseContextLocale for Android 6- notes
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
refreshActivity(true);
|
||||
return true;
|
||||
}
|
||||
String newLocale = (String) newValue;
|
||||
// If newLocale is empty, that means "System" was selected
|
||||
AppCompatDelegate.setApplicationLocales(newLocale.isEmpty() ? LocaleListCompat.getEmptyLocaleList() : LocaleListCompat.create(Utils.stringToLocale(newLocale)));
|
||||
return true;
|
||||
});
|
||||
|
||||
// Disable content provider on SDK < 23 since dangerous permissions
|
||||
// are granted at install-time
|
||||
Preference contentProviderReadPreference = findPreference(getResources().getString(R.string.settings_key_allow_content_provider_read));
|
||||
assert contentProviderReadPreference != null;
|
||||
contentProviderReadPreference.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
|
||||
|
||||
// Hide crash reporter settings on builds it's not enabled on
|
||||
Preference crashReporterPreference = findPreference("acra.enable");
|
||||
assert crashReporterPreference != null;
|
||||
crashReporterPreference.setVisible(BuildConfig.useAcraCrashReporter);
|
||||
}
|
||||
|
||||
private void refreshActivity(boolean reloadMain) {
|
||||
mReloadMain = reloadMain || mReloadMain;
|
||||
Activity activity = getActivity();
|
||||
if (activity != null) {
|
||||
activity.recreate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package protect.card_locker.preferences
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import protect.card_locker.BuildConfig
|
||||
import protect.card_locker.CatimaAppCompatActivity
|
||||
import protect.card_locker.MainActivity
|
||||
import protect.card_locker.R
|
||||
import protect.card_locker.Utils
|
||||
import protect.card_locker.databinding.SettingsActivityBinding
|
||||
|
||||
class SettingsActivity : CatimaAppCompatActivity() {
|
||||
|
||||
private lateinit var binding: SettingsActivityBinding
|
||||
private lateinit var fragment: SettingsFragment
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = SettingsActivityBinding.inflate(layoutInflater)
|
||||
setTitle(R.string.settings)
|
||||
setContentView(binding.root)
|
||||
Utils.applyWindowInsets(binding.root)
|
||||
val toolbar = binding.toolbar
|
||||
setSupportActionBar(toolbar)
|
||||
enableToolbarBackButton()
|
||||
|
||||
// Display the fragment as the main content.
|
||||
fragment = SettingsFragment()
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.settings_container, fragment)
|
||||
.commit()
|
||||
|
||||
// restore reload main state
|
||||
if (savedInstanceState != null) {
|
||||
fragment.mReloadMain = savedInstanceState.getBoolean(RELOAD_MAIN_STATE)
|
||||
}
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
finishSettingsActivity()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putBoolean(RELOAD_MAIN_STATE, fragment.mReloadMain)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
val id = item.itemId
|
||||
|
||||
if (id == android.R.id.home) {
|
||||
finishSettingsActivity()
|
||||
return true
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun finishSettingsActivity() {
|
||||
if (fragment.mReloadMain) {
|
||||
val intent = Intent()
|
||||
intent.putExtra(MainActivity.RESTART_ACTIVITY_INTENT, true)
|
||||
setResult(RESULT_OK, intent)
|
||||
} else {
|
||||
setResult(RESULT_OK)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
var mReloadMain: Boolean = false
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.preferences)
|
||||
|
||||
// Show pretty names and summaries
|
||||
val themePreference = findPreference<ListPreference>(getString(R.string.settings_key_theme))
|
||||
themePreference!!.setOnPreferenceChangeListener { _, o ->
|
||||
when (o.toString()) {
|
||||
getString(R.string.settings_key_light_theme) -> {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
||||
}
|
||||
getString(R.string.settings_key_dark_theme) -> {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||
}
|
||||
else -> {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
val themeColorPreference = findPreference<ListPreference>(getString(R.string.setting_key_theme_color))
|
||||
themeColorPreference!!.setOnPreferenceChangeListener { _, _ ->
|
||||
refreshActivity(true)
|
||||
true
|
||||
}
|
||||
if (!DynamicColors.isDynamicColorAvailable()) {
|
||||
themeColorPreference.setEntryValues(R.array.color_values_no_dynamic)
|
||||
themeColorPreference.setEntries(R.array.color_value_strings_no_dynamic)
|
||||
}
|
||||
|
||||
val oledDarkPreference = findPreference<Preference>(getString(R.string.settings_key_oled_dark))
|
||||
oledDarkPreference!!.setOnPreferenceChangeListener { _, _ ->
|
||||
refreshActivity(true)
|
||||
true
|
||||
}
|
||||
|
||||
val localePreference =
|
||||
findPreference<ListPreference>(getString(R.string.settings_key_locale))!!
|
||||
localePreference.let {
|
||||
val entryValues = it.entryValues
|
||||
val entries = entryValues.map { entry ->
|
||||
if (entry.isEmpty()) {
|
||||
getString(R.string.settings_system_locale)
|
||||
} else {
|
||||
val entryLocale = Utils.stringToLocale(entry.toString())
|
||||
entryLocale.getDisplayName(entryLocale)
|
||||
}
|
||||
}
|
||||
it.entries = entries.toTypedArray()
|
||||
|
||||
// Make locale picker preference in sync with system settings
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val sysLocale = AppCompatDelegate.getApplicationLocales()[0]
|
||||
if (sysLocale == null) {
|
||||
// Corresponds to "System"
|
||||
it.value = ""
|
||||
} else {
|
||||
// Need to set preference's value to one of localePreference.getEntryValues() to match the locale.
|
||||
// Locale.toLanguageTag() theoretically should be one of the values in localePreference.getEntryValues()...
|
||||
// But it doesn't work for some locales. so trying something more heavyweight.
|
||||
|
||||
// Obtain all locales supported by the app.
|
||||
val appLocales = entryValues.map { entry -> Utils.stringToLocale(entry.toString()) }
|
||||
// Get the app locale that best matches the system one
|
||||
val bestMatchLocale = Utils.getBestMatchLocale(appLocales, sysLocale)
|
||||
// Get its index in supported locales
|
||||
val index = appLocales.indexOf(bestMatchLocale)
|
||||
// Set preference value to entry value at that index
|
||||
it.value = entryValues[index].toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
localePreference.setOnPreferenceChangeListener { _, newValue ->
|
||||
// See corresponding comment in Utils.updateBaseContextLocale for Android 6- notes
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
refreshActivity(true)
|
||||
return@setOnPreferenceChangeListener true
|
||||
}
|
||||
val newLocale = newValue as String
|
||||
// If newLocale is empty, that means "System" was selected
|
||||
AppCompatDelegate.setApplicationLocales(if (newLocale.isEmpty()) LocaleListCompat.getEmptyLocaleList() else LocaleListCompat.create(Utils.stringToLocale(newLocale)))
|
||||
true
|
||||
}
|
||||
|
||||
// Disable content provider on SDK < 23 since dangerous permissions
|
||||
// are granted at install-time
|
||||
val contentProviderReadPreference = findPreference<Preference>(getString(R.string.settings_key_allow_content_provider_read))
|
||||
contentProviderReadPreference!!.isVisible =
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
|
||||
// Hide crash reporter settings on builds it's not enabled on
|
||||
val crashReporterPreference = findPreference<Preference>("acra.enable")
|
||||
crashReporterPreference!!.isVisible = BuildConfig.useAcraCrashReporter
|
||||
}
|
||||
|
||||
private fun refreshActivity(reloadMain: Boolean) {
|
||||
mReloadMain = reloadMain || mReloadMain
|
||||
activity?.recreate()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val RELOAD_MAIN_STATE = "mReloadMain"
|
||||
}
|
||||
}
|
||||
@@ -276,6 +276,24 @@
|
||||
android:paddingTop="@dimen/inputPadding"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- Currency -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/balanceCurrencyView"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:hint="@string/currency"
|
||||
android:labelFor="@+id/balanceCurrencyField">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/balanceCurrencyField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- Balance -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/balanceView"
|
||||
@@ -294,24 +312,6 @@
|
||||
android:digits="0123456789,." />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- Currency -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/balanceCurrencyView"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:hint="@string/currency"
|
||||
android:labelFor="@+id/balanceCurrencyField">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/balanceCurrencyField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Valid from -->
|
||||
|
||||
@@ -7,83 +7,93 @@ Heimen Stoffels
|
||||
Oğuz Ersen
|
||||
FC (Fay) Stegerman
|
||||
StoyanDimitrov
|
||||
大王叫我来巡山
|
||||
SlavekB
|
||||
Katharine Chui
|
||||
大王叫我来巡山
|
||||
B o d o
|
||||
mondstern
|
||||
IllusiveMan196
|
||||
B o d o
|
||||
Altonss
|
||||
Silvério Santos
|
||||
Michael Moroni
|
||||
Altonss
|
||||
Edgars Andersons
|
||||
Eric
|
||||
Joel A
|
||||
Michael Moroni
|
||||
Liner Seven
|
||||
Priit Jõerüüt
|
||||
Eric
|
||||
Максим Горпиніч
|
||||
GitSpoon
|
||||
GM
|
||||
Fjuro
|
||||
laralem
|
||||
Petr Novák
|
||||
Taco
|
||||
GitSpoon
|
||||
nadiafekihahmed
|
||||
pfaffenrodt
|
||||
Fjuro
|
||||
Aayush Gupta
|
||||
Scrambled777
|
||||
josé m
|
||||
ikanakova
|
||||
Giovanni Donisi
|
||||
HudobniVolk
|
||||
Jiri Grönroos
|
||||
Nyatsuki
|
||||
Warder
|
||||
Kachelkaiser
|
||||
Giovanni Donisi
|
||||
Milo Ivir
|
||||
HudobniVolk
|
||||
Горпиніч Максим Олександрович
|
||||
Vasilis
|
||||
Kachelkaiser
|
||||
Jiri Grönroos
|
||||
Warder
|
||||
Samantaz Fox
|
||||
Balázs Meskó
|
||||
Cliff Heraldo
|
||||
Sergio Paredes
|
||||
Ankit Tiwari
|
||||
Arno-github
|
||||
Feike Donia
|
||||
109247019824
|
||||
Jose Delvani
|
||||
mdvhimself
|
||||
Milan Šalka
|
||||
Robin
|
||||
தமிழ்நேரம்
|
||||
damjang
|
||||
Govindgopalyadav
|
||||
Skrripy
|
||||
huuhaa
|
||||
தமிழ்நேரம்
|
||||
waffshappen
|
||||
Marnick L'Eau
|
||||
Горпиніч Максим Олександрович
|
||||
ngocanhtve
|
||||
aradxxx
|
||||
StellarSand
|
||||
Quentin PAGÈS
|
||||
Projjal Moitra
|
||||
109247019824
|
||||
e-michalak
|
||||
JungHee Lee
|
||||
hajertabbane
|
||||
inavleb
|
||||
Ziad OUALHADJ
|
||||
Robin Liu
|
||||
Ricky Tigg
|
||||
Renko
|
||||
Aliaksandr Trush
|
||||
Denis Shilin
|
||||
Renko
|
||||
Ricky Tigg
|
||||
Robin Liu
|
||||
しいたけ
|
||||
Alexander Ivanov
|
||||
Miha Frangež
|
||||
stavpup
|
||||
mrestivill
|
||||
ehrt74
|
||||
delvani
|
||||
Virginie
|
||||
Tim Trek
|
||||
Peter Dave Hello
|
||||
Michael Gangolf
|
||||
rudy3
|
||||
Kim Seohyun
|
||||
Govind S Nair
|
||||
Freddo espresso
|
||||
Augustin LAVILLE
|
||||
arshbeerSingh
|
||||
MisterCosta96
|
||||
Aliaksandr Trush
|
||||
arshbeerSingh
|
||||
Augustin LAVILLE
|
||||
Traductor
|
||||
Freddo espresso
|
||||
Gideon
|
||||
vasudev-cell
|
||||
Kim Seohyun
|
||||
rudy3
|
||||
Michael Gangolf
|
||||
PRATHAMESH BHAGAT
|
||||
|
||||
11
app/src/main/res/values-af/strings.xml
Normal file
11
app/src/main/res/values-af/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="action_search">Soek</string>
|
||||
<string name="action_add">Voeg by</string>
|
||||
<string name="save">Stoor</string>
|
||||
<plurals name="selectedCardCount">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> geselekteer</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> geselekteer</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -71,7 +71,7 @@
|
||||
<string name="privacy_policy">سياسة الخصوصية</string>
|
||||
<string name="accept">قبول</string>
|
||||
<string name="importCatima">الاستيراد من Catima</string>
|
||||
<string name="importCatimaMessage">حدّد ملفك <i>catima.zip</i> تصدير من Catima للاستيراد. \nإنشئها من قائمة الاستيراد / التصدير لتطبيق Catima آخر بالضغط على تصدير هناك أولاً.</string>
|
||||
<string name="importCatimaMessage">حدّد ملفك تصدير من Catima للاستيراد.\nإنشئها من قائمة الاستيراد / التصدير لتطبيق Catima آخر بالضغط على تصدير .</string>
|
||||
<string name="importFidme">الاستيراد من FidMe</string>
|
||||
<string name="importFidmeMessage">حدّد ملفك <i>fidme-export-request-xxxxxx.zip</i> تصدير من FidMe للاستيراد، ثم حدد أنواع الباركود يدويًا بعد ذلك. \nإنشئها من ملف تعريف FidMe الخاص بك عن طريق اختيار حماية البيانات ثم الضغط على استخراج بياناتي أولاً.</string>
|
||||
<string name="importVoucherVault">الاستيراد من Voucher Vault</string>
|
||||
|
||||
@@ -306,4 +306,14 @@
|
||||
<string name="generic_error_please_retry">На жаль, нешта пайшло не так, паспрабуйце яшчэ раз...</string>
|
||||
<string name="setBarcodeWidth">Задаць шырыню штрыхкода</string>
|
||||
<string name="app_license">Свабоднае копілефт праграмнае забеспячэнне, ліцэнзаванае паводле GPLv3+</string>
|
||||
<string name="cardWithNumber">Карта <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Карта <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Калі ласка, не паварочвайце прыладу, бо гэта адменіць дзеянне</string>
|
||||
<string name="acra_explain_crash">Калі магчыма, дадайце больш падрабязную інфармацыю пра тое, што вы тут рабілі:</string>
|
||||
<string name="acra_crash_email_subject">Справаздача аб збоі <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Запытваць дазвол на адпраўку справаздач аб збоях</string>
|
||||
<string name="pref_enable_acra_summary">Калі гэта ўключана, вам будзе прапанавана паведаміць пра збой, калі ён адбудзецца. Справаздачы аб збоях ніколі не адпраўляюцца аўтаматычна.</string>
|
||||
<string name="card_list_widget_name">Спіс карт</string>
|
||||
<string name="card_list_widget_empty">Пасля таго, як вы дадасце некалькі картак лаяльнасці ў Catima, яны з\'явяцца тут. Калі ў вас ёсць карты, пераканайцеся, што яны не ўсе заархіваваны.</string>
|
||||
<string name="acra_catima_has_crashed">Прабачце, але ў праграме <xliff:g id="app_name">%s</xliff:g> адбыўся збой. Калі ласка, дапамажыце нам выправіць гэту праблему, даслаўшы нам справаздачу аб памылцы.</string>
|
||||
</resources>
|
||||
|
||||
@@ -299,4 +299,13 @@
|
||||
<string name="card_list_widget_empty">Когато добавите карти в Catima те ще се покажат тук. Ако имате карти уверете се, че са извън архива.</string>
|
||||
<string name="cardWithNumber">Карта <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Карта <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Не завъртайте устройството, защото това ще прекъсне действието</string>
|
||||
<string name="acra_catima_has_crashed">За съжаление <xliff:g id="app_name">%s</xliff:g> се срина. Помогнете ни да оправим проблема като ни изпратите доклад за грешката.</string>
|
||||
<string name="acra_crash_email_subject">Доклад за срив на <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Питане преди изпращане на доклад за срив</string>
|
||||
<string name="pref_enable_acra_summary">Когато е отметнато, при срив ще ви бъде предложено да докладвате за него. Докладите никога не се изпращат автоматично.</string>
|
||||
<string name="acra_explain_crash">Ако е възможно добавете подробности за вашите действия:</string>
|
||||
<string name="copy_value">Копиране на стойността</string>
|
||||
<string name="copied_to_clipboard">Копирано</string>
|
||||
<string name="nothing_to_copy">Няма стойност</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
<string name="delete">Elimina</string>
|
||||
<string name="confirm">Confirma</string>
|
||||
<string name="ok">D\'acord</string>
|
||||
<string name="importExport">Importa/Exporta</string>
|
||||
<string name="importExport">Importa/exporta</string>
|
||||
<string name="exportName">Exporta</string>
|
||||
<string name="action_search">Cerca</string>
|
||||
<string name="deleteTitle">Elimina la targeta</string>
|
||||
<string name="welcome">Benvingut a Catima</string>
|
||||
<string name="noGiftCards">Cliqueu el botó + més per afegir una targeta, o importeu-ne des del ⋮ menú.</string>
|
||||
<string name="noGiftCards">Fes clic al botó + per afegir una targeta, o importa des del menú ⋮</string>
|
||||
<string name="photos">Fotos</string>
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="moveDown">Baixar abaix</string>
|
||||
@@ -24,10 +24,10 @@
|
||||
<string name="on_google_play">al Google Play</string>
|
||||
<string name="settings_locale">Idioma</string>
|
||||
<string name="field_must_not_be_empty">El camp no pot estar buit</string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Copyright © 2019–<xliff:g>%d</xliff:g> Sylvia van Os i contribuïdors</string>
|
||||
<string name="app_copyright_short">Copyright © Sylvia van Os i contribuïdors</string>
|
||||
<string name="app_license">Software lliure Copyleft, licència GPLv3+</string>
|
||||
<string name="app_resources">Recursos lliures de tercers: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Copyright © 2019–<xliff:g>%d</xliff:g> Sylvia van Os i col·laboradors</string>
|
||||
<string name="app_copyright_short">Copyright © Sylvia van Os i col·laboradors</string>
|
||||
<string name="app_license">Programari lliure Copyleft, licència GPLv3+</string>
|
||||
<string name="app_resources">Recursos de tercers: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="thumbnailDescription">Miniatura</string>
|
||||
<string name="starImage">Estrella de preferides</string>
|
||||
<string name="settings">Configuració</string>
|
||||
@@ -55,13 +55,13 @@
|
||||
<string name="add_manually_warning_title">Recomenem escanejar</string>
|
||||
<string name="add_manually_warning_message">En algunes targetes el valor imprès en la targeta no correspon amb el codi registrat en el codi de barres. Per això, introduint manualment el codi pot no funcionar en alguns casos. Recomanem sempre que sigui possible escanejar la targeta amb la càmera. Vol igualment continuar la edició manual?</string>
|
||||
<string name="continue_">Continuar</string>
|
||||
<string name="exportOptionExplanation">La informació serà escrita al lloc de la seva elecció.</string>
|
||||
<string name="exportOptionExplanation">La informació serà escrita al lloc de la seva elecció</string>
|
||||
<string name="importOptionFilesystemTitle">Importar desde el sistema de fitxers</string>
|
||||
<string name="importOptionFilesystemButton">Desde el sistema de fitxers</string>
|
||||
<string name="selectBarcodeTitle">Sel•lecciona el Codi de Barres</string>
|
||||
<string name="selectBarcodeTitle">Selecciona el codi de barres</string>
|
||||
<string name="importSuccessful">Dades importades correctament</string>
|
||||
<string name="exportSuccessful">Dades exportades correctament</string>
|
||||
<string name="failedOpeningFileManager">Instala un gestor de fitxers.</string>
|
||||
<string name="failedOpeningFileManager">No s\'ha pogut obrir el gestor de fitxers</string>
|
||||
<string name="showMoreInfo">Mostrar informació</string>
|
||||
<string name="version_history">Històric de versions</string>
|
||||
<string name="sort_by">Ordenar per</string>
|
||||
@@ -72,7 +72,7 @@
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> seleccionats</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> seleccionats</item>
|
||||
</plurals>
|
||||
<string name="importOptionFilesystemExplanation">Escull un fitxer especific del sistema de fitxers.</string>
|
||||
<string name="importOptionFilesystemExplanation">Escull un fitxer especific del sistema de fitxers</string>
|
||||
<string name="no">No</string>
|
||||
<string name="settings_pink_theme">Rosa</string>
|
||||
<string name="sort">Ordenar</string>
|
||||
@@ -96,8 +96,8 @@
|
||||
</plurals>
|
||||
<string name="importCancelled">Importació anulada</string>
|
||||
<string name="exportCancelled">Exportació cancelada</string>
|
||||
<string name="noGiftCardsGroup">Crea algunes targetes, asigna-les en un grup aquí.</string>
|
||||
<string name="noMatchingGiftCards">Sense resultats. Prova a canviar la teva cerca.</string>
|
||||
<string name="noGiftCardsGroup">Crea algunes targetes i després asigna-les en al grup aquí</string>
|
||||
<string name="noMatchingGiftCards">No hi ha resultats; prova de modificar la cerca.</string>
|
||||
<string name="storeName">Nom</string>
|
||||
<string name="note">Nota</string>
|
||||
<string name="cardId">Id. de la Targeta</string>
|
||||
@@ -166,13 +166,13 @@
|
||||
<string name="deleteConfirmation">Vols eliminar de forma permanent aquesta targeta?</string>
|
||||
<string name="share">Compartir</string>
|
||||
<string name="sendLabel">Enviar…</string>
|
||||
<string name="editCardTitle">Editar Targeta</string>
|
||||
<string name="addCardTitle">Afegir Targeta</string>
|
||||
<string name="scanCardBarcode">Escanejar Codi de Barres</string>
|
||||
<string name="cardShortcut">Drecera a la Targeta</string>
|
||||
<string name="editCardTitle">Editar targeta</string>
|
||||
<string name="addCardTitle">Afegir targeta</string>
|
||||
<string name="scanCardBarcode">Escanejar codi de barres</string>
|
||||
<string name="cardShortcut">Drecera a la targeta</string>
|
||||
<string name="noCardsMessage">Afegeix primer una targeta</string>
|
||||
<string name="noCardExistsError">No s\'ha pogut trobar aquesta targeta</string>
|
||||
<string name="failedParsingImportUriError">No s\'ha pogut analitzar la URI d\'importació</string>
|
||||
<string name="failedParsingImportUriError">No s\'ha pogut analitzar l\'URI d\'importació</string>
|
||||
<string name="openFrontImageInGalleryApp">Obrir la imatge frontal a l\'app de galeria</string>
|
||||
<string name="settings_use_volume_keys_navigation_summary">Utilitza els botons de volum per canviar la targeta que es mostra</string>
|
||||
<string name="updateBarcodeQuestionText">Ha canviat el valor ID. Vol actualitzar també el codi de barres per uter utilitzar el mateix valor?</string>
|
||||
@@ -180,7 +180,7 @@
|
||||
<string name="starred">Preferides</string>
|
||||
<string name="deleteConfirmationGroup">Vols eliminar aquest grup?</string>
|
||||
<string name="removeImage">Eliminar imatge</string>
|
||||
<string name="app_libraries">Llibreries lliures de tercers: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Llibreries de tercers: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="settings_display_barcode_max_brightness">Màxima iluminació</string>
|
||||
<string name="settings_brown_theme">Marró</string>
|
||||
<string name="manually_enter_barcode_instructions">Introdueixi el ID de la targeta manualment i trii un codi de barres que s\'assembli al de la seva targeta.</string>
|
||||
@@ -227,7 +227,7 @@
|
||||
<string name="addFromPkpass">Seleccioni el fitxer Passbook (.pkpass)</string>
|
||||
<string name="unsupportedFile">Aquest fitxer no està soportat</string>
|
||||
<string name="settings_use_volume_keys_navigation">Canviar les targetes al prèmer els botons de volum</string>
|
||||
<string name="noGroups">Clica el botó + per afegir grups per categoritzar.</string>
|
||||
<string name="noGroups">Feu clic al botó + més per aferir grups pre categoritzar</string>
|
||||
<string name="noGroupCards">Aquest grup està buit</string>
|
||||
<string name="group_name_already_in_use">Ja existeix un grup amb aquest nom</string>
|
||||
<string name="group_updated">Grup actualitzat</string>
|
||||
@@ -238,4 +238,43 @@
|
||||
<string name="settings_system_locale">Idioma del sistema</string>
|
||||
<string name="settings_catima_theme">Catima</string>
|
||||
<string name="spend">Gastar</string>
|
||||
<string name="importExportHelp">Fer una còpia de seguretat de les dades permet moure-les a un altre dispositiu</string>
|
||||
<string name="importSuccessfulTitle">Importat</string>
|
||||
<string name="importFailedTitle">La importació ha fallat</string>
|
||||
<string name="importFailed">No s\'ha pogut realitzar la importació</string>
|
||||
<string name="exportSuccessfulTitle">Exportat</string>
|
||||
<string name="exportFailedTitle">L\'exportació ha fallat</string>
|
||||
<string name="exportFailed">No s\'ha pogut realitzar l\'exportació</string>
|
||||
<string name="importing">Important…</string>
|
||||
<string name="exporting">Exportant…</string>
|
||||
<string name="storageReadPermissionRequired">Cal permís per llegir l\'emmagatzematge per a aquesta acció…</string>
|
||||
<string name="cameraPermissionRequired">Cal permís per accedir a la càmera per a aquesta acció…</string>
|
||||
<string name="permissionReadCardsLabel">Legeix targetes Catima</string>
|
||||
<string name="permissionReadCardsDescription">llegeix les teves targetes Catima i tots els seus detalls, incloses notes i imatges</string>
|
||||
<string name="cameraPermissionDeniedTitle">No s\'ha pogut accedir a la càmera</string>
|
||||
<string name="noCameraPermissionDirectToSystemSetting">Per escanejar codis de barres, Catima necessitarà accés a la teva càmera. Toca aquí per canviar la configuració dels permisos.</string>
|
||||
<string name="about">Sobre</string>
|
||||
<string name="app_copyright_old">Clauer basat en na Loyalty Card Keychain\ncopyright © 2016–2020 Branden Archer</string>
|
||||
<string name="addManually">Introduïu el codi de barres manualment</string>
|
||||
<string name="addFromImage">Seleccioneu una imatge de la galeria</string>
|
||||
<string name="groupsList">Grups: <xliff:g>%s</xliff:g></string>
|
||||
<string name="editGroup">Editeu el grup: <xliff:g>%s</xliff:g></string>
|
||||
<string name="expiryStateSentence">Caduca el: <xliff:g>%s</xliff:g></string>
|
||||
<string name="expiryStateSentenceExpired">Caducat el: <xliff:g>%s</xliff:g></string>
|
||||
<plurals name="balancePoints">
|
||||
<item quantity="one"><xliff:g>%s</xliff:g> punt</item>
|
||||
<item quantity="many"><xliff:g>%s</xliff:g> punts</item>
|
||||
<item quantity="other"/>
|
||||
</plurals>
|
||||
<string name="balanceSentence">Saldo: <xliff:g>%s</xliff:g></string>
|
||||
<string name="card">Targeta</string>
|
||||
<string name="editBarcode">Editeu el codi de barres</string>
|
||||
<string name="expiryDate">Data de caducitat</string>
|
||||
<string name="never">Mai</string>
|
||||
<string name="chooseExpiryDate">Trieu la data de caducitat</string>
|
||||
<string name="moveBarcodeToTopOfScreen">Moveu el codi de barres a la part superior de la pantalla</string>
|
||||
<string name="noBarcodeFound">No s\'ha trobat cap codi de barres</string>
|
||||
<string name="errorReadingImage">No s\'ha pogut llegir la imatge</string>
|
||||
<string name="balance">Saldo</string>
|
||||
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
|
||||
</resources>
|
||||
|
||||
@@ -305,4 +305,13 @@
|
||||
<string name="card_list_widget_empty">Karty přidané do aplikace Catima se zobrazí zde. Pokud máte karty, ujistěte se, že nejsou všechny archivovány.</string>
|
||||
<string name="cardWithNumber">Karta <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Karta <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Neotáčejte prosím zařízení, protože tím zrušíte akci</string>
|
||||
<string name="acra_catima_has_crashed">Omlouváme se, aplikace <xliff:g id="app_name">%s</xliff:g> havarovala. Pomozte nám prosím s opravou tohoto problému odesláním hlášení o chybě.</string>
|
||||
<string name="acra_explain_crash">Pokud je to možné, přidejte prosím další podrobnosti o tom, co jste tu dělali:</string>
|
||||
<string name="acra_crash_email_subject">Hlášení o pádu <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Ptát se na odesílání hlášení o pádech</string>
|
||||
<string name="pref_enable_acra_summary">Pokud je povoleno, budete při pádu aplikace dotázáni na jeho nahlášení. Hlášení nejsou nikdy odesílána automaticky.</string>
|
||||
<string name="copy_value">Kopírovat hodnotu</string>
|
||||
<string name="copied_to_clipboard">Zkopírováno do schránky</string>
|
||||
<string name="nothing_to_copy">Nenalezena žádná hodnota</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
<string name="scanCardBarcode">Scan stregkode</string>
|
||||
<string name="addCardTitle">Tilføj kort</string>
|
||||
<string name="editCardTitle">Rediger kort</string>
|
||||
<string name="sendLabel">Afsend…</string>
|
||||
<string name="share">Aktie</string>
|
||||
<string name="sendLabel">Send…</string>
|
||||
<string name="share">Del</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="deleteConfirmation">Slete dette kort permanent\?</string>
|
||||
<string name="deleteConfirmation">Slet dette kort permanent?</string>
|
||||
<plurals name="deleteCardsTitle">
|
||||
<item quantity="one">Streichen <xliff:g>%d</xliff:g> kort</item>
|
||||
<item quantity="other">Streichen <xliff:g>%d</xliff:g> korts</item>
|
||||
<item quantity="one">Slet <xliff:g>%d</xliff:g> kort</item>
|
||||
<item quantity="other">Slet <xliff:g>%d</xliff:g> korts</item>
|
||||
</plurals>
|
||||
<string name="deleteTitle">Karte streichen</string>
|
||||
<string name="deleteTitle">Slet kort</string>
|
||||
<string name="confirm">Bekræft</string>
|
||||
<string name="delete">Slet</string>
|
||||
<string name="edit">Rediger</string>
|
||||
@@ -34,7 +34,7 @@
|
||||
<string name="action_search">Søg</string>
|
||||
<string name="importExport">Import/eksport</string>
|
||||
<string name="exportName">Eksport</string>
|
||||
<string name="importExportHelp">Sikkerhedskopiering af dit data, giver dig mulighed for at flytte dem til en anden enhed.</string>
|
||||
<string name="importExportHelp">Sikkerhedskopiering af dine data, giver dig mulighed for at flytte dem til en anden enhed.</string>
|
||||
<string name="importSuccessfulTitle">Importeret</string>
|
||||
<string name="importFailedTitle">Import mislykkedes</string>
|
||||
<string name="importFailed">Kunne ikke udføre importering</string>
|
||||
@@ -54,12 +54,12 @@
|
||||
\ncopyright © 2016-2020 Branden Archer.</string>
|
||||
<string name="about">Om</string>
|
||||
<string name="noCardsMessage">Tilføj først et kort</string>
|
||||
<string name="cardShortcut">Kort genvej</string>
|
||||
<string name="cardShortcut">Genvej til kort</string>
|
||||
<string name="importOptionFilesystemButton">Fra filsystemet</string>
|
||||
<string name="importOptionFilesystemExplanation">Vælg en bestemt fil fra filsystemet.</string>
|
||||
<string name="importOptionFilesystemTitle">Import fra filsystem</string>
|
||||
<string name="exportOptionExplanation">Dataene skrives til en placering efter eget valg.</string>
|
||||
<string name="failedParsingImportUriError">Kunne ikke analysere import-URI\'en</string>
|
||||
<string name="failedParsingImportUriError">Kunne ikke importere URI\'en</string>
|
||||
<string name="noCardExistsError">Kunne ikke finde det kort</string>
|
||||
<string name="deleteConfirmationGroup">Slet gruppe\?</string>
|
||||
<string name="all">Alle</string>
|
||||
@@ -79,16 +79,16 @@
|
||||
<string name="moveDown">Bevæger sig nedad</string>
|
||||
<string name="leaveWithoutSaveTitle">Afslut</string>
|
||||
<string name="addManually">Indtast stregkoden manuelt</string>
|
||||
<string name="noGiftCardsGroup">Opret kort og tildel dem gupper her.</string>
|
||||
<string name="noGiftCardsGroup">Opret kort og tildel dem grupper her.</string>
|
||||
<plurals name="deleteCardsConfirmation">
|
||||
<item quantity="one">Slet dette <xliff:g>%d</xliff:g> kort permanent\?</item>
|
||||
<item quantity="other">Slet disse <xliff:g>%d</xliff:g> kort permanent\?</item>
|
||||
</plurals>
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="cameraPermissionRequired">Behov for kamera adgang krævet for denne funktion…</string>
|
||||
<string name="storageReadPermissionRequired">Behov for lager adgang krævet for denne funktion…</string>
|
||||
<string name="cameraPermissionRequired">Behov for kamera adgang er krævet for denne funktion…</string>
|
||||
<string name="storageReadPermissionRequired">Behov for lager adgang er krævet for denne funktion…</string>
|
||||
<string name="permissionReadCardsLabel">Læs Catima Kort</string>
|
||||
<string name="permissionReadCardsDescription">læs dine Catima kort og alle deres detaljer, også noter og billeder</string>
|
||||
<string name="permissionReadCardsDescription">læs dit Catima kort og alle kortets detaljer, også noter og billeder</string>
|
||||
<string name="cameraPermissionDeniedTitle">Kunne ikke få adgang til kamera</string>
|
||||
<string name="noCameraPermissionDirectToSystemSetting">For at scanne stregkoder, har Catima behov for at få adgang til dit kamera. Klik her for at ændre dine tilladelser i indstillinger.</string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Copyright © 2019–<xliff:g>%d</xliff:g> Sylvia van Os og hjælpere</string>
|
||||
@@ -144,4 +144,4 @@
|
||||
<item quantity="one"><xliff:g>%s</xliff:g> point</item>
|
||||
<item quantity="other"><xliff:g>%s</xliff:g> point</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -299,4 +299,13 @@
|
||||
<string name="card_list_widget_name">Kartenliste</string>
|
||||
<string name="cardWithNumberAndLocale">Karte <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="cardWithNumber">Karte <xliff:g>%d</xliff:g></string>
|
||||
<string name="pref_enable_acra_summary">Wenn aktiviert, wirst du bei einem Absturz gebeten diesen zu melden. Absturzberichte werden niemals automatisch gesendet.</string>
|
||||
<string name="pref_enable_acra">Bitte um die Übermittlung von Absturzberichten</string>
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> Absturzbericht</string>
|
||||
<string name="acra_explain_crash">Wenn möglich, bitte übermittle mehr Details zu dem, was du hier getan hast:</string>
|
||||
<string name="acra_catima_has_crashed">Es tut uns leid, aber <xliff:g id="app_name">%s</xliff:g> ist abgestürzt. Bitte hilf uns diesen Fehler zu beheben und übermittle uns einen Absturzbericht.</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Bitte drehe nicht das Gerät, weil sonst die Aktion abbricht</string>
|
||||
<string name="copy_value">Kopiere Betrag</string>
|
||||
<string name="copied_to_clipboard">In die Zwischenablage kopiert</string>
|
||||
<string name="nothing_to_copy">Keinen Betrag gefunden</string>
|
||||
</resources>
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
<string name="sameAsCardId">Όπως ο κωδικός</string>
|
||||
<string name="exportPassword">Προσθέστε έναν κωδικό για προστασία της εξαγωγής (προαιρετικά)</string>
|
||||
<string name="exportPasswordHint">Εισαγωγή κωδικού</string>
|
||||
<string name="failedGeneratingShareURL">Δεν ήταν δυνατή η δημιουργία κοινοποιούμενου URL.</string>
|
||||
<string name="failedGeneratingShareURL">Δεν ήταν δυνατή η δημιουργία κοινοποιούμενου URL</string>
|
||||
<string name="turn_flashlight_on">Ενεργοποίηση φακού</string>
|
||||
<string name="turn_flashlight_off">Απενεργοποίηση φακού</string>
|
||||
<string name="settings_locale">Γλώσσα</string>
|
||||
@@ -299,4 +299,13 @@
|
||||
<string name="card_list_widget_name">Λίστα καρτών</string>
|
||||
<string name="cardWithNumber">Κάρτα <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Κάρτα <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Μην περιστρέφετε τη συσκευή, καθώς αυτό θα ακυρώσει την ενέργεια</string>
|
||||
<string name="acra_catima_has_crashed">Λυπούμαστε, αλλά το <xliff:g id="app_name">%s</xliff:g> παρουσίασε σφάλμα. Βοηθήστε μας να διορθώσουμε αυτό το πρόβλημα, στέλνοντάς μας μια αναφορά σφάλματος.</string>
|
||||
<string name="acra_explain_crash">Αν είναι δυνατόν, προσθέστε περισσότερες λεπτομέρειες σχετικά με το τι κάνατε εδώ:</string>
|
||||
<string name="acra_crash_email_subject">Αναφορά σφάλματος <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Ερώτηση για αποστολή αναφορών σφαλμάτων</string>
|
||||
<string name="pref_enable_acra_summary">Όταν είναι ενεργοποιημένη, θα σας ζητηθεί να αναφέρετε ένα σφάλμα όταν συμβεί. Οι αναφορές σφάλματος δεν αποστέλλονται ποτέ αυτόματα.</string>
|
||||
<string name="copy_value">Αντιγραφή τιμής</string>
|
||||
<string name="copied_to_clipboard">Αντιγράφηκε στο πρόχειρο</string>
|
||||
<string name="nothing_to_copy">Δεν βρέθηκε τιμή</string>
|
||||
</resources>
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<string name="intent_import_card_from_url_share_text">Mi deziras dividi karto kun vi</string>
|
||||
<string name="exportSuccessful">Datumoj eksportitaj</string>
|
||||
<string name="noGroupCards">Ĉi tiu grupo estas malplena</string>
|
||||
<string name="noGiftCards">Klavu la \"+\" butonon por aldoni karton, aŭ importu el la menuo \" ⋮\".</string>
|
||||
<string name="noGiftCards">Klavu la \"+\" butonon por aldoni karton, aŭ importu el la menuo \" ⋮\"</string>
|
||||
<plurals name="selectedCardCount">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> elektita</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> elektitaj</item>
|
||||
|
||||
@@ -232,7 +232,7 @@
|
||||
<string name="height">Alto</string>
|
||||
<string name="switchToFrontImage">Cambiar a imagen frontal</string>
|
||||
<string name="openFrontImageInGalleryApp">Abrir imagen frontal en la aplicación de la galería</string>
|
||||
<string name="openBackImageInGalleryApp">Abrir imagen trasera en la aplicación de la galería</string>
|
||||
<string name="openBackImageInGalleryApp">Abrir imagen trasera en la aplicación de visor de imagen</string>
|
||||
<string name="setBarcodeHeight">Ajustar la altura del código de barras</string>
|
||||
<string name="donate">Donar</string>
|
||||
<string name="switchToBarcode">Cambiar a código de barras</string>
|
||||
@@ -268,7 +268,7 @@
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="continue_">Continuar</string>
|
||||
<string name="add_manually_warning_title">Se recomienda escanear</string>
|
||||
<string name="add_manually_warning_message">En algunas tiendas, el valor del código de barras difiere del número escrito en la tarjeta. Por este motivo, es posible que la introducción manual del código de barras no siempre funcione. Se recomienda encarecidamente escanear el código de barras con la cámara. ¿Aún desea continuar?</string>
|
||||
<string name="add_manually_warning_message">En algunas tarjetas, el valor del código de barras difiere del número escrito en la tarjeta. Por este motivo, introducir manualmente puede que no siempre funcione. Se recomienda analizar el código de barras con su cámara en su lugar. ¿Aún desea continuar?</string>
|
||||
<string name="spend">Gastar</string>
|
||||
<string name="receive">Recibió</string>
|
||||
<string name="amountParsingFailed">Importe incorrecto</string>
|
||||
@@ -305,4 +305,10 @@
|
||||
<string name="card_list_widget_empty">Después de añadir algunas tarjetas de fidelidad en Catima, aparecerán aquí. Si tienes tarjetas, asegúrate de que no estén archivadas.</string>
|
||||
<string name="cardWithNumber">Tarjeta <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Tarjeta <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Por favor, no rote el dispositivo, ya que esto cancelará la acción</string>
|
||||
<string name="acra_catima_has_crashed">Lo sentimos, pero <xliff:g id="app_name">%s</xliff:g> ha fallado. Por favor, ayúdenos a resolver esta incidencia enviándonos un reporte del error.</string>
|
||||
<string name="acra_explain_crash">Si es posible, por favor añada más detalles sobre lo que estaba haciendo aquí:</string>
|
||||
<string name="acra_crash_email_subject">Reporte del fallo <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Solicitar envío de reportes de fallos</string>
|
||||
<string name="pref_enable_acra_summary">Cuando está activado, se le pedirá que informe sobre un fallo cuando ocurra. Los informes de fallo nunca se envían automáticamente.</string>
|
||||
</resources>
|
||||
|
||||
@@ -299,4 +299,13 @@
|
||||
<string name="card_list_widget_empty">Kui lisad Catimasse kliendikaarte, siis saavad nad olema nähtavad siin. Kui sul on kaardid lisatud, siis palun kontrolli, et nad kõik poleks arhiveeritud.</string>
|
||||
<string name="cardWithNumber">Kaart: <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Kaart: <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Palun ära pööra nutiseadet - see katkestab tegevuse</string>
|
||||
<string name="acra_catima_has_crashed">Vabandus, aga <xliff:g id="app_name">%s</xliff:g> on kokku jooksnud. Kui saadad meile veakirjelduse, siis aitad seda viga parandada.</string>
|
||||
<string name="acra_explain_crash">Kui vähegi võimalik, siis palun kirjelda, mida sa antud hetkel tegid:</string>
|
||||
<string name="acra_crash_email_subject">Kokkujooksmise aruanne: <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Küsi luba kokkujooksmiste aruannete saatmiseks</string>
|
||||
<string name="pref_enable_acra_summary">Kui eelistus on kasutusel, siis rakendus küsib sinult luba veateate saatmiseks. Seda ei tehta iialgi automaatselt.</string>
|
||||
<string name="copy_value">Kopeeri väärtus</string>
|
||||
<string name="copied_to_clipboard">Kopeeritud lõikelauale</string>
|
||||
<string name="nothing_to_copy">Ühtegi väärtust ei leidu</string>
|
||||
</resources>
|
||||
|
||||
@@ -15,4 +15,16 @@
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> napili</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> ang napili</item>
|
||||
</plurals>
|
||||
<string name="star">Sa card viewing, ang text ay naka-display lamang tuwing naka-long press ang star icon</string>
|
||||
<string name="cancel">I-kansela</string>
|
||||
<string name="save">I-save</string>
|
||||
<string name="edit">I-edit</string>
|
||||
<string name="delete">I-delete</string>
|
||||
<string name="confirm">I-confirm</string>
|
||||
<string name="share">I-share</string>
|
||||
<string name="sendLabel">I-send…</string>
|
||||
<string name="editCardTitle">I-edit ang card</string>
|
||||
<string name="noCardsMessage">Mag-add ng card muna</string>
|
||||
<string name="noCardExistsError">Hindi mahanap ang card</string>
|
||||
<string name="exportName">I-export</string>
|
||||
</resources>
|
||||
|
||||
@@ -305,4 +305,13 @@
|
||||
<string name="card_list_widget_empty">Après avoir ajouter des cartes de fidélité dans Catima, elles apparaîtront ici. Si vous avez des cartes, assurez-vous qu\'elles ne soient pas archivées.</string>
|
||||
<string name="cardWithNumber">Carte <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Carte <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Merci de ne pas tourner l\'écran, car cela annulera l\'action</string>
|
||||
<string name="acra_catima_has_crashed">Nous sommes désolé, <xliff:g id="app_name">%s</xliff:g> a planté. Merci de nous aider à corriger ce souci en nous envoyant un rapport d\'erreur.</string>
|
||||
<string name="acra_explain_crash">Si possible, merci d\'ajouter plus de détails sur ce que vous étiez en train de faire :</string>
|
||||
<string name="acra_crash_email_subject">Rapport de plantage de <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Demander pour envoyer des rapports de plantage</string>
|
||||
<string name="pref_enable_acra_summary">Quand activé, il vous sera demandé d\'envoyer un rapport de plantage en cas de plantage. Les rapports de plantage ne sont jamais envoyés automatiquement.</string>
|
||||
<string name="copy_value">Copier la valeur</string>
|
||||
<string name="copied_to_clipboard">Copié dans le presse-papier</string>
|
||||
<string name="nothing_to_copy">Aucune valeur trouvée</string>
|
||||
</resources>
|
||||
|
||||
3
app/src/main/res/values-fy/strings.xml
Normal file
3
app/src/main/res/values-fy/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
</resources>
|
||||
@@ -298,4 +298,13 @@
|
||||
<string name="card_list_widget_empty">Aquí aparecerán as tarxetas fidelidade cando as engadas a Catima. Se tes tarxetas mira que non estean arquivadas.</string>
|
||||
<string name="cardWithNumber">Tarxeta <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Tarxeta <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Por favor non rotes o dispositivo, porque isto cancelará a acción</string>
|
||||
<string name="acra_catima_has_crashed">Lamentámolo, pero <xliff:g id="app_name">%s</xliff:g> fallou. Axúdanos a resolver a incidencia enviando un informe co erro.</string>
|
||||
<string name="acra_explain_crash">Se é posible engade algún detalle máis como o que estabas a facer:</string>
|
||||
<string name="acra_crash_email_subject">Informe do fallo de <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Solicitar informar sobre os fallos</string>
|
||||
<string name="pref_enable_acra_summary">Se está activo, váiseche pedir informar sobre os fallos cando acontezan. Os informes nunca se envían automaticamente.</string>
|
||||
<string name="copy_value">Copiar valor</string>
|
||||
<string name="copied_to_clipboard">Copiado ao portapapeis</string>
|
||||
<string name="nothing_to_copy">Non hai ningún valor</string>
|
||||
</resources>
|
||||
|
||||
@@ -298,4 +298,13 @@
|
||||
<string name="card_list_widget_empty">कैटिमा में कुछ लॉयल्टी कार्ड जोड़ने के बाद, वे यहाँ दिखाई देंगे। अगर आपके पास कार्ड हैं, तो सुनिश्चित करें कि वे सभी संग्रहित न हों।</string>
|
||||
<string name="cardWithNumber">कार्ड <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">कार्ड <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">कृपया डिवाइस को घुमाएँ नहीं, क्योंकि इससे कार्रवाई रद्द हो जाएगी</string>
|
||||
<string name="acra_catima_has_crashed">हमें खेद है, लेकिन <xliff:g id="app_name">%s</xliff:g> क्रैश हो गया है। कृपया हमें एक त्रुटि रिपोर्ट भेजकर इस समस्या को ठीक करने में हमारी सहायता करें।</string>
|
||||
<string name="acra_explain_crash">यदि संभव हो तो कृपया यहां आप क्या कर रहे थे, इसके बारे में अधिक विवरण जोड़ें:</string>
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> क्रैश रिपोर्ट</string>
|
||||
<string name="pref_enable_acra">दुर्घटना रिपोर्ट भेजने के लिए कहें</string>
|
||||
<string name="pref_enable_acra_summary">सक्षम होने पर, क्रैश होने पर आपको रिपोर्ट करने के लिए कहा जाएगा। क्रैश रिपोर्ट कभी भी स्वचालित रूप से नहीं भेजी जाती हैं।</string>
|
||||
<string name="copy_value">मान कॉपी करें</string>
|
||||
<string name="copied_to_clipboard">क्लिपबोर्ड पर कॉपी किया गया</string>
|
||||
<string name="nothing_to_copy">कोई मूल्य नहीं मिला</string>
|
||||
</resources>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<string name="sendLabel">Pošalji …</string>
|
||||
<string name="editCardTitle">Uredi karticu</string>
|
||||
<string name="addCardTitle">Dodaj karticu</string>
|
||||
<string name="scanCardBarcode">Snimi crtični kod kartice</string>
|
||||
<string name="scanCardBarcode">Snimi crtični kod</string>
|
||||
<string name="cardShortcut">Prečac kartice</string>
|
||||
<string name="noCardsMessage">Najprije dodaj karticu</string>
|
||||
<string name="noBarcode">Nema crtičnog koda</string>
|
||||
@@ -24,21 +24,21 @@
|
||||
<string name="cardId">ID kartice</string>
|
||||
<string name="barcodeType">Vrsta crtičnog koda</string>
|
||||
<string name="cancel">Odustani</string>
|
||||
<string name="noGiftCards">Pritisni gumb + plus za dodavanje kartice ili uvezi putem izbornika ⋮.</string>
|
||||
<string name="noGiftCards">Pritisni gumb + plus za dodavanje kartice ili uvezi putem izbornika ⋮</string>
|
||||
<string name="noCardExistsError">Nije bilo moguće pronaći tu karticu</string>
|
||||
<string name="failedParsingImportUriError">Nije bilo moguće obraditi URI uvoza</string>
|
||||
<string name="importExport">Uvoz/Izvoz</string>
|
||||
<string name="importExport">Uvoz/izvoz</string>
|
||||
<string name="exportName">Izvoz</string>
|
||||
<string name="importExportHelp">Spremanje sigurnosnih kopija tvojih podataka omogućuje premještanje podataka na jedan drugi uređaj.</string>
|
||||
<string name="importExportHelp">Spremanje sigurnosnih kopija tvojih podataka omogućuje premještanje podataka na jedan drugi uređaj</string>
|
||||
<string name="importSuccessfulTitle">Uvezeno</string>
|
||||
<string name="importFailedTitle">Neuspio uvoz</string>
|
||||
<string name="importFailed">Nije bilo moguće izvršiti uvoz</string>
|
||||
<string name="exportSuccessfulTitle">Izvezeno</string>
|
||||
<string name="about">Informacije</string>
|
||||
<string name="exportOptionExplanation">Podaci će se zapisati u željeno mjesto.</string>
|
||||
<string name="exportOptionExplanation">Podaci će se zapisati na mjesto po tvom izboru</string>
|
||||
<string name="exportFailedTitle">Neuspio izvoz</string>
|
||||
<string name="exporting">Izvoz …</string>
|
||||
<string name="importOptionFilesystemExplanation">Odaberi određenu datoteku iz datotečnog sustava.</string>
|
||||
<string name="importOptionFilesystemExplanation">Odaberi određenu datoteku iz datotečnog sustava</string>
|
||||
<string name="settings">Postavke</string>
|
||||
<string name="settings_dark_theme">Tamna</string>
|
||||
<string name="exportFailed">Nije bilo moguće izvršiti izvoz</string>
|
||||
@@ -60,16 +60,16 @@
|
||||
<string name="importSuccessful">Podaci su uvezeni</string>
|
||||
<string name="enter_group_name">Upiši ime grupe</string>
|
||||
<string name="groups">Grupe</string>
|
||||
<string name="noGroups">Pritisni gumb + plus za dodavanje grupe za kategoriziranje.</string>
|
||||
<string name="noGroups">Pritisni gumb + plus za dodavanje grupe za kategoriziranje</string>
|
||||
<string name="noGroupCards">Ova je grupa prazna</string>
|
||||
<string name="addFromImage">Odaberi sliku iz galerije</string>
|
||||
<string name="deleteConfirmationGroup">Izbrisati grupu\?</string>
|
||||
<string name="failedOpeningFileManager">Najprije instaliraj upravljač datoteka.</string>
|
||||
<string name="failedOpeningFileManager">Neuspjelo otvaranje upravljača datoteka</string>
|
||||
<string name="moveUp">Pomakni prema gore</string>
|
||||
<string name="leaveWithoutSaveTitle">Zatvori aplikaciju</string>
|
||||
<string name="card">Kartica</string>
|
||||
<string name="leaveWithoutSaveConfirmation">Zatvoriti aplikaciju bez spremanja\?</string>
|
||||
<string name="noGiftCardsGroup">Stvori neke kartice, a zatim ih ovdje dodijeli grupi.</string>
|
||||
<string name="noGiftCardsGroup">Stvori neke kartice, a zatim ih ovdje dodijeli grupi</string>
|
||||
<plurals name="groupCardCount">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> kartica</item>
|
||||
<item quantity="few"><xliff:g>%d</xliff:g> kartice</item>
|
||||
@@ -83,8 +83,7 @@
|
||||
<string name="accept">Prihvati</string>
|
||||
<string name="importCatima">Uvezi iz Catima</string>
|
||||
<string name="importFidme">Uvezi iz FidMe</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Odaberi tvoju iz LoyaltyCardKeychain izvezenu <i>LoyaltyCardKeychain.csv</i> datoteku koju želiš uvesti.
|
||||
\nStvori je putem izbornika „Uvoz/Izvoz” u aplikaciji Loyalty Card Keychain i tamo pritisni „Izvoz”.</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Odaberi tvoj izvoz iz LoyaltyCardKeychain za uvoz. \nStvori je putem izbornika „Uvoz/Izvoz” u aplikaciji Loyalty Card Keychain pritiskom na „Izvoz”.</string>
|
||||
<string name="updateBarcodeQuestionText">Promijenio/la si ID. Želiš li također aktualizirati crtični kod da koristi istu vrijednost\?</string>
|
||||
<string name="importCards">Uvezi kartice</string>
|
||||
<string name="selectColor">Odaberi boju</string>
|
||||
@@ -97,7 +96,7 @@
|
||||
<string name="frontImageDescription">Prednja slika</string>
|
||||
<string name="exportPasswordHint">Upiši lozinku</string>
|
||||
<string name="turn_flashlight_on">Uključi bljeskalicu</string>
|
||||
<string name="failedGeneratingShareURL">Nije bilo moguće generirati URL za dijeljenje. Prijavi ovaj problem.</string>
|
||||
<string name="failedGeneratingShareURL">Nije bilo moguće generirati URL za dijeljenje</string>
|
||||
<string name="turn_flashlight_off">Isključi bljeskalicu</string>
|
||||
<string name="settings_locale">Jezik</string>
|
||||
<string name="settings_magenta_theme">Magenta</string>
|
||||
@@ -118,10 +117,10 @@
|
||||
<string name="archive">Arhiviraj</string>
|
||||
<string name="archived">Kartica je arhivirana</string>
|
||||
<string name="unarchived">Kartica je uklonjena iz arhive</string>
|
||||
<string name="failedLaunchingPhotoPicker">Nije bilo moguće pronaći podržanu aplikaciju galerije</string>
|
||||
<string name="failedLaunchingPhotoPicker">Nije bilo moguće pronaći podržani birač slika</string>
|
||||
<string name="cameraPermissionDeniedTitle">Nije bilo moguće pristupiti kameri</string>
|
||||
<string name="noCameraPermissionDirectToSystemSetting">Za snimanje crtičnih kodova Catima treba pristup tvojoj kameri. Dodirni ovdje za mijenjanje postavki dozvola.</string>
|
||||
<string name="app_libraries">Slobodne biblioteke trećih strana: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Biblioteke trećih strana: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="selectBarcodeTitle">Odaberi crtični kod</string>
|
||||
<string name="group_edit">Uredi grupu</string>
|
||||
<string name="group_name_already_in_use">Ime grupe se već koristi</string>
|
||||
@@ -129,14 +128,13 @@
|
||||
<string name="balance">Saldo</string>
|
||||
<string name="chooseImportType">Uvezi podatke iz</string>
|
||||
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
|
||||
<string name="importCatimaMessage">Odaberi tvoju iz Catima izvezenu <i>catima.zip</i> datoteku koju želiš uvesti.
|
||||
\nStvori je putem izbornika „Uvoz/Izvoz” jedne druge Catima aplikacije pritiskom na „Izvoz”.</string>
|
||||
<string name="importCatimaMessage">Odaberi tvoj izvoz iz Catima za uvoz. \nStvori je putem izbornika „Uvoz/Izvoz” jedne druge Catima aplikacije pritiskom na „Izvoz”.</string>
|
||||
<string name="height">Visina</string>
|
||||
<string name="switchToFrontImage">Prebaci na prednju sliku</string>
|
||||
<string name="switchToBackImage">Prebaci na stražnju sliku</string>
|
||||
<string name="switchToBarcode">Prebaci na crtični kod</string>
|
||||
<string name="openFrontImageInGalleryApp">Otvori prednju sliku u aplikaciji galerije</string>
|
||||
<string name="openBackImageInGalleryApp">Otvori stražnju sliku u aplikaciji galerije</string>
|
||||
<string name="openFrontImageInGalleryApp">Otvori prednju sliku u aplikaciji prikazivača slika</string>
|
||||
<string name="openBackImageInGalleryApp">Otvori stražnju sliku u aplikaciji prikazivača slika</string>
|
||||
<string name="setBarcodeHeight">Postavi visinu crtičnog koda</string>
|
||||
<plurals name="selectedCardCount">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> odabrana</item>
|
||||
@@ -162,10 +160,8 @@
|
||||
<string name="cameraPermissionRequired">Za ovu radnju je potrebna dozvola za pristup kameri …</string>
|
||||
<string name="app_license">Copylefted libre softver, GPLv3+ licenca</string>
|
||||
<string name="balanceSentence">Saldo: <xliff:g>%s</xliff:g></string>
|
||||
<string name="importFidmeMessage">Odaberi tvoju iz FidMe izvezenu <i>idme-export-request-xxxxxx.zip</i> datoteku koju želiš uvesti i ručno odaberi vste crtičnog koda nakon toga.
|
||||
\nStvori je putem tvog FidMe profila biranjem „Zaštita podataka” a zatim pritisni „Dekomprimiraj moje podatke”.</string>
|
||||
<string name="importVoucherVaultMessage">Odaberi tvoju iz Voucher Vault izvezenu <i>vouchervault.json</i> datoteku koju želiš uvesti.
|
||||
\nStvori je u aplikaciji Voucher Vault i tamo pritisni „Izvoz”.</string>
|
||||
<string name="importFidmeMessage">Odaberi tvoj izvoz iz FidMe za uvoz i ručno odaberi vste crtičnog koda nakon toga. \nStvori ga putem tvog FidMe profila biranjem „Zaštita podataka” a zatim pritisni „Dekomprimiraj moje podatke”.</string>
|
||||
<string name="importVoucherVaultMessage">Odaberi tvoj izvoz iz Voucher Vault za uvoz. \nStvori ga u aplikaciji Voucher Vault pritiskom na „Izvoz”.</string>
|
||||
<string name="settings_pink_theme">Ružičasta</string>
|
||||
<string name="settings_blue_theme">Plava</string>
|
||||
<string name="failedToRetrieveImageFile">Neuspjelo dohvaćanje slikovne datoteke</string>
|
||||
@@ -196,7 +192,7 @@
|
||||
</plurals>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Autorska prava © 2019. – <xliff:g>%d.</xliff:g> Sylvia van Os i doprinositelji</string>
|
||||
<string name="debug_version_fmt">Verzija: <xliff:g id="version">%s</xliff:g></string>
|
||||
<string name="app_resources">Slobodni resursi trećih strana: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Resursi trećih strana: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="group_name_is_empty">Ime grupe ne smije biti prazno</string>
|
||||
<string name="group_updated">Grupa je aktualizirana</string>
|
||||
<string name="all">Sve</string>
|
||||
@@ -205,7 +201,7 @@
|
||||
<string name="expiryStateSentenceExpired">Istekla: <xliff:g>%s</xliff:g></string>
|
||||
<string name="chooseExpiryDate">Odaberi datum isteka</string>
|
||||
<string name="moveBarcodeToTopOfScreen">Premjesti crtični kod na vrh ekrana</string>
|
||||
<string name="errorReadingImage">Nije bilo moguće učitati sliku</string>
|
||||
<string name="errorReadingImage">Nije bilo moguće čitati sliku</string>
|
||||
<string name="currency">Valuta</string>
|
||||
<string name="points">Bodovi</string>
|
||||
<string name="privacy_policy">Politika privatnosti</string>
|
||||
@@ -232,7 +228,7 @@
|
||||
<string name="app_contributors">Doprinositelji: <xliff:g id="app_contributors">%s</xliff:g></string>
|
||||
<string name="showMoreInfo">Prikaži informacije</string>
|
||||
<string name="sort_by_name">Ime</string>
|
||||
<string name="sort_by_most_recently_used">Nedavno korišteno</string>
|
||||
<string name="sort_by_most_recently_used">Zadnje korišteno</string>
|
||||
<string name="reverse">… u obrnutom redoslijedu</string>
|
||||
<string name="shortcutSelectCard">Odaberi karticu</string>
|
||||
<string name="previousCard">Prethodna</string>
|
||||
@@ -271,10 +267,10 @@
|
||||
<string name="settings_keep_screen_on_summary">Deaktivira isključivanje ekrana tijekom prikaza kartice</string>
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="continue_">Nastavi</string>
|
||||
<string name="add_manually_warning_message">Za neke trgovine se vrijednost crtičnog koda razlikuje od broja na kartici. Zbog toga ručno upisivanje crtičnog koda možda neće uvijek funkcionirati. Preporučuje se snimanje crtičnog koda pomoću kamere. Želiš li svejedno nastaviti?</string>
|
||||
<string name="add_manually_warning_message">Za neke kartice se vrijednost crtičnog koda razlikuje od broja na kartici. Zbog toga ručno upisivanje crtičnog koda možda neće uvijek funkcionirati. Preporučuje se snimanje crtičnog koda pomoću kamere. Želiš li svejedno nastaviti?</string>
|
||||
<string name="add_manually_warning_title">Preporučuje se snimanje</string>
|
||||
<string name="addFromPdfFile">Odaberi PDF datoteku</string>
|
||||
<string name="errorReadingFile">Nije bilo moguće pročitati datoteku</string>
|
||||
<string name="errorReadingFile">Nije bilo moguće čitati datoteku</string>
|
||||
<string name="failedLaunchingFileManager">Nije bilo moguće pronaći podržani upravljač datoteka</string>
|
||||
<string name="multipleBarcodesFoundPleaseChooseOne">Koji od pronađenih crtičnih kodova želiš koristiti?</string>
|
||||
<string name="pageWithNumber">Stranica <xliff:g>%d</xliff:g></string>
|
||||
@@ -298,14 +294,21 @@
|
||||
<string name="settings_column_count_4">4</string>
|
||||
<string name="settings_column_count_5">5</string>
|
||||
<string name="settings_column_count_7">7</string>
|
||||
<string name="generic_error_please_retry">Žao nam je, nešto nije u redu, pokušaj ponovo …</string>
|
||||
<string name="generic_error_please_retry">Dogodila se greška</string>
|
||||
<string name="addFromPkpass">Odaberi jednu Passbook datoteku (.pkpass / .pkpasses)</string>
|
||||
<string name="unsupportedFile">Ova datoteka nije podržana</string>
|
||||
<string name="settings_use_volume_keys_navigation_summary">Pomoću gumba za glasnoću promijeni koja se kartica prikazuje</string>
|
||||
<string name="settings_use_volume_keys_navigation">Mijenjaj kartice pomoću gumba za glasnoću</string>
|
||||
<string name="width">Širina</string>
|
||||
<string name="card_list_widget_name">Popis kartica</string>
|
||||
<string name="setBarcodeWidth">Postavi širinu barkoda</string>
|
||||
<string name="setBarcodeWidth">Postavi širinu crtičnog koda</string>
|
||||
<string name="cardWithNumber">Kartica <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Kartica <xliff:g>%d</xliff:g> (%s)</string>
|
||||
<string name="cardWithNumberAndLocale">Kartica <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="card_list_widget_empty">Nakon što dodaš neke kartice vjernosti u Catima, one će se pojaviti ovdje. Ako već imaš kartice, provjeri da nisu sve arhivirane.</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Ne okreći uređaj jer će to prekinuti radnju</string>
|
||||
<string name="acra_catima_has_crashed">Žao nam je, ali aplikacija <xliff:g id="app_name">%s</xliff:g> je prekinula rad. Pomogni riješiti ovaj problem slanjem izvještaja o grešci.</string>
|
||||
<string name="acra_explain_crash">Po mogućnosti dodaj više detalja o tvojim radnjama:</string>
|
||||
<string name="acra_crash_email_subject">Izvještaj o prekidu rada aplikacije <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Pitaj da li poslati izvještaj o prekidu rada aplikacije</string>
|
||||
<string name="pref_enable_acra_summary">Kada je uključeno, zamolit ćemo te da prijaviš prekid rada aplikacije kada se dogodi. Izvještaji o prekidu rada se nikada ne šalju automatski.</string>
|
||||
</resources>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<string name="license">Lisensi</string>
|
||||
<string name="settings">Pengaturan</string>
|
||||
<string name="settings_system_theme">Sistem</string>
|
||||
<string name="selectBarcodeTitle">Pilih Barcode</string>
|
||||
<string name="selectBarcodeTitle">Pilih barcode</string>
|
||||
<string name="deleteConfirmation">Hapus kartu ini secara permanen?</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="share">Bagikan</string>
|
||||
@@ -27,7 +27,7 @@
|
||||
<string name="addCardTitle">Tambah Kartu</string>
|
||||
<string name="scanCardBarcode">Pindai Barcode</string>
|
||||
<string name="cancel">Batalkan</string>
|
||||
<string name="importExport">Impor/Ekspor</string>
|
||||
<string name="importExport">Impor/ekspor</string>
|
||||
<string name="settings_theme">Tema</string>
|
||||
<string name="all">Semua</string>
|
||||
<string name="leaveWithoutSaveTitle">Keluar</string>
|
||||
@@ -46,10 +46,10 @@
|
||||
<string name="setBarcodeId">Tentukan nilai barcode</string>
|
||||
<string name="photos">Foto</string>
|
||||
<string name="setFrontImage">Atur gambar bagian depan</string>
|
||||
<string name="report_error">Lapor Kesalahan</string>
|
||||
<string name="report_error">Laporkan kesalahan</string>
|
||||
<string name="rate_this_app">Beri nilai pada aplikasi ini</string>
|
||||
<string name="sort_by_expiry">Masa berlaku</string>
|
||||
<string name="sort_by_most_recently_used">Paling banyak digunakan</string>
|
||||
<string name="sort_by_most_recently_used">Yang paling baru digunakan</string>
|
||||
<string name="settings_catima_theme">Catima</string>
|
||||
<string name="settings_pink_theme">Merah Muda</string>
|
||||
<string name="settings_blue_theme">Biru</string>
|
||||
@@ -87,9 +87,9 @@
|
||||
<string name="exportFailed">Tidak dapat mengekspor</string>
|
||||
<string name="importing">Sedang mengimpor…</string>
|
||||
<string name="exporting">Sedang mengekspor…</string>
|
||||
<string name="exportOptionExplanation">Data akan ditulis ke lokasi pilihan Anda.</string>
|
||||
<string name="exportOptionExplanation">Data akan ditulis ke lokasi yang Anda pilih</string>
|
||||
<string name="importOptionFilesystemTitle">Impor dari pengelola file bawaan</string>
|
||||
<string name="importOptionFilesystemExplanation">Pilih file dari pengelola file bawaan.</string>
|
||||
<string name="importOptionFilesystemExplanation">Pilih berkas tertentu dari sistem berkas</string>
|
||||
<string name="importOptionFilesystemButton">Dari pengelola file bawaan</string>
|
||||
<string name="about">Tentang</string>
|
||||
<string name="app_copyright_fmt">Hak Cipta © 2019–<xliff:g>%d</xliff:g> Sylvia van Os dan para kontributor</string>
|
||||
@@ -98,8 +98,8 @@
|
||||
<string name="app_license">Perangkat lunak bebas copyleft, berlisensi GPLv3+</string>
|
||||
<string name="about_title_fmt">Tentang <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="debug_version_fmt">Versi: <xliff:g id="version">%s</xliff:g></string>
|
||||
<string name="app_libraries">Pustaka pihak ketiga gratis: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Sumber daya pihak ketiga gratis: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Perpustakaan pihak ketiga: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Sumber daya pihak ketiga: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="thumbnailDescription">Gambar tampilan</string>
|
||||
<string name="starImage">Favorit</string>
|
||||
<string name="settings_light_theme">Terang</string>
|
||||
@@ -112,10 +112,10 @@
|
||||
<string name="exportSuccessful">Data terekspor</string>
|
||||
<string name="enter_group_name">Masukan nama grup</string>
|
||||
<string name="groups">Grup</string>
|
||||
<string name="noGroups">Klik pada tombol tambah + untuk menambahkan grup untuk pengkategorian.</string>
|
||||
<string name="noGroups">Klik tombol + untuk menambahkan grup untuk pengelompokan</string>
|
||||
<string name="noGroupCards">Grup ini kosong</string>
|
||||
<string name="deleteConfirmationGroup">Hapus grup?</string>
|
||||
<string name="failedOpeningFileManager">Pasang aplikasi pengelola berkas terlebih dahulu.</string>
|
||||
<string name="failedOpeningFileManager">Gagal membuka pengelola berkas</string>
|
||||
<string name="moveUp">Pindah ke atas</string>
|
||||
<string name="moveDown">Pindah ke bawah</string>
|
||||
<string name="leaveWithoutSaveConfirmation">Keluar tanpa menyimpan?</string>
|
||||
@@ -132,36 +132,32 @@
|
||||
<string name="points">Poin</string>
|
||||
<string name="app_loyalty_card_keychain">Gantungan kunci kartu kesetiaan</string>
|
||||
<string name="privacy_policy">Kebijakan Privasi</string>
|
||||
<string name="importCatimaMessage">Pilih ekspor <i>catima.zip</i> Anda dari Catima untuk diimpor.
|
||||
\nBuat dari menu Impor/Ekspor aplikasi Catima lain dengan menekan Ekspor di sana terlebih dahulu.</string>
|
||||
<string name="importFidmeMessage">Pilih ekspor <i>fidme-export-request-xxxxxx.zip</i> Anda dari FidMe untuk diimpor, dan pilih jenis barcode secara manual setelahnya.
|
||||
\nBuat dari profil FidMe Anda dengan memilih Perlindungan Data lalu tekan Ekstrak data saya terlebih dahulu.</string>
|
||||
<string name="importCatimaMessage">Pilih ekspor Anda dari Catima untuk diimpor.\nBuatlah dari menu Impor/Ekspor aplikasi Catima lainnya dengan menekan Ekspor.</string>
|
||||
<string name="importFidmeMessage">Pilih ekspor Anda dari FidMe untuk diimpor, lalu pilih jenis barcode secara manual setelahnya.\nBuatlah dari profil FidMe Anda dengan memilih Data Protection, lalu tekan Extract my data.</string>
|
||||
<string name="importLoyaltyCardKeychain">Impor dari Loyalty Card Keychain</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Pilih ekspor <i>LoyaltyCardKeychain.csv</i> Anda dari Loyalty Card Keychain untuk diimpor.
|
||||
\nBuat dari menu Import/Export di Loyalty Card Keychain dengan menekan Export terlebih dahulu.</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Pilih ekspor Anda dari Loyalty Card Keychain untuk diimpor.\nBuatlah dari menu Impor/Ekspor di Loyalty Card Keychain dengan menekan Ekspor.</string>
|
||||
<string name="importVoucherVault">Impor dari Voucher Vault</string>
|
||||
<string name="importVoucherVaultMessage">Pilih ekspor <i>vouchervault.json</i> Anda dari Voucher Vault untuk diimpor.
|
||||
\nBuat dengan menekan Ekspor di Voucher Vault terlebih dahulu.</string>
|
||||
<string name="importVoucherVaultMessage">Pilih ekspor Anda dari Voucher Vault untuk diimpor.\nBuatlah dengan menekan tombol Ekspor di Voucher Vault.</string>
|
||||
<string name="unsupportedBarcodeType">Jenis barcode ini belum dapat ditampilkan. Ini mungkin didukung di versi aplikasi yang lebih baru.</string>
|
||||
<string name="wrongValueForBarcodeType">Nilai tidak berlaku untuk jenis barcode yang dipilih</string>
|
||||
<string name="wrongValueForBarcodeType">Nilai tersebut tidak valid untuk jenis barcode yang dipilih</string>
|
||||
<string name="frontImageDescription">Gambar depan</string>
|
||||
<string name="backImageDescription">Gambar belakang</string>
|
||||
<string name="updateBarcodeQuestionTitle">Perbarui barcode?</string>
|
||||
<string name="updateBarcodeQuestionText">Anda mengubah ID. Apakah Anda juga ingin memperbarui barcode untuk menggunakan nilai yang sama\?</string>
|
||||
<string name="passwordRequired">Silahkan masukan kata sandi</string>
|
||||
<string name="passwordRequired">Masukkan kata sandi</string>
|
||||
<string name="exportPassword">Tetapkan kata sandi untuk melindungi ekspor anda (opsional)</string>
|
||||
<string name="failedGeneratingShareURL">Tidak dapat membuat alamat berbagi. Mohon laporkan ini.</string>
|
||||
<string name="failedGeneratingShareURL">Tidak dapat menghasilkan URL yang dapat dibagikan</string>
|
||||
<string name="app_contributors">Pengembangan dibantu oleh: <xliff:g id="app_contributors">%s</xliff:g></string>
|
||||
<string name="reverse">…dalam urutan terbalik</string>
|
||||
<string name="version_history">Riwayat Versi</string>
|
||||
<string name="version_history">Riwayat versi</string>
|
||||
<string name="help_translate_this_app">Bantu terjemahkan aplikasi ini</string>
|
||||
<string name="source_repository">Sumber Repositori</string>
|
||||
<string name="source_repository">Repositori sumber</string>
|
||||
<string name="on_github">di GitHub</string>
|
||||
<string name="and_data_usage">dan penggunaan data</string>
|
||||
<string name="on_google_play">di Google Play</string>
|
||||
<string name="cardShortcut">Pintasan Kartu</string>
|
||||
<string name="barcodeImageDescriptionWithType">Gambar <xliff:g>%s</xliff:g> barcode</string>
|
||||
<string name="importExportHelp">Mencadangkan data anda akan memungkinkan memindahkannya ke perangkat lain.</string>
|
||||
<string name="importExportHelp">Membackup data Anda memungkinkan Anda memindahkan data tersebut ke perangkat lain</string>
|
||||
<plurals name="selectedCardCount">
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> kartu dipilih</item>
|
||||
</plurals>
|
||||
@@ -174,7 +170,7 @@
|
||||
<plurals name="deleteCardsTitle">
|
||||
<item quantity="other">Hapus <xliff:g>%d</xliff:g> kartu</item>
|
||||
</plurals>
|
||||
<string name="editGroup">Menyunting Grup: <xliff:g>%s</xliff:g></string>
|
||||
<string name="editGroup">Kelompok pengeditan: <xliff:g>%s</xliff:g></string>
|
||||
<string name="selectColor">Pilih warna</string>
|
||||
<string name="noGiftCardsGroup">Buat beberapa kartu, kemudian masukkan mereka ke grup di sini</string>
|
||||
<string name="group_name_already_in_use">Nama grup telah dipakai</string>
|
||||
@@ -189,7 +185,7 @@
|
||||
<string name="translate_platform">di Weblate</string>
|
||||
<string name="welcome">Selamat datang di Catima</string>
|
||||
<string name="failedToOpenUrl">Install browser web terlebih dahulu</string>
|
||||
<string name="failedLaunchingPhotoPicker">Tidak dapat menemukan aplikasi galeri yang didukung</string>
|
||||
<string name="failedLaunchingPhotoPicker">Tidak dapat menemukan pemilih gambar yang didukung</string>
|
||||
<string name="previousCard">Sebelumnya</string>
|
||||
<string name="nextCard">Berikutnya</string>
|
||||
<plurals name="balancePoints">
|
||||
@@ -225,8 +221,8 @@
|
||||
<string name="switchToFrontImage">Ubah ke depan gambar</string>
|
||||
<string name="switchToBackImage">Ubah ke belakang gambar</string>
|
||||
<string name="switchToBarcode">Ubah ke kode batang</string>
|
||||
<string name="openFrontImageInGalleryApp">Buka gambar didepan di galeri app</string>
|
||||
<string name="openBackImageInGalleryApp">Buka gambar dibelakang di galeri app</string>
|
||||
<string name="openFrontImageInGalleryApp">Buka gambar depan di aplikasi penampil gambar</string>
|
||||
<string name="openBackImageInGalleryApp">Buka gambar di aplikasi penampil gambar</string>
|
||||
<string name="setBarcodeHeight">Atur tinggi kode batang</string>
|
||||
<string name="donate">Donasi</string>
|
||||
<string name="show_validity">Tunjukkan validitas</string>
|
||||
@@ -234,7 +230,7 @@
|
||||
<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>
|
||||
<string name="permissionReadCardsLabel">Baca Kartu Catima</string>
|
||||
<string name="permissionReadCardsLabel">Bacalah 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>
|
||||
@@ -260,15 +256,15 @@
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="add_manually_warning_title">Pemindaian sangat dianjurkan</string>
|
||||
<string name="continue_">Lanjut</string>
|
||||
<string name="failedLaunchingFileManager">Tidak dapat menemukan pengelola file yang didukung</string>
|
||||
<string name="errorReadingFile">Tidak dapat membaca file</string>
|
||||
<string name="failedLaunchingFileManager">Tidak dapat menemukan pengelola berkas yang didukung</string>
|
||||
<string name="errorReadingFile">Tidak dapat membaca berkas</string>
|
||||
<string name="addFromPdfFile">Pilih file PDF</string>
|
||||
<string name="multipleBarcodesFoundPleaseChooseOne">Barcode mana yang ingin Anda gunakan?</string>
|
||||
<string name="pageWithNumber">Halaman <xliff:g>%d</xliff:g></string>
|
||||
<string name="spend">Dibelanjakan</string>
|
||||
<string name="receive">Terima</string>
|
||||
<string name="amountParsingFailed">Jumlah tidak valid</string>
|
||||
<string name="add_manually_warning_message">Untuk beberapa toko, nilai barcode berbeda dengan nomor yang tertulis di kartu. Oleh karena itu, memasukkan barcode secara manual mungkin tidak selalu berhasil. Sangat disarankan untuk memindai barcode dengan kamera anda. Apakah anda masih ingin melanjutkan?</string>
|
||||
<string name="add_manually_warning_message">Untuk beberapa kartu, nilai barcode berbeda dengan angka yang tertulis di kartu. Karena itu, memasukkan barcode secara manual mungkin tidak selalu berhasil. Disarankan untuk memindai barcode menggunakan kamera Anda. Apakah Anda tetap ingin melanjutkan?</string>
|
||||
<string name="noCameraFoundGuideText">Perangkat Anda sepertinya tidak memiliki kamera. Jika iya, coba mulai ulang perangkat. Jika tidak, gunakan tombol Opsi lainnya di bawah untuk menambahkan barcode dengan cara lain.</string>
|
||||
<string name="importCancelled">Impor dibatalkan</string>
|
||||
<string name="exportCancelled">Ekspor dibatalkan</string>
|
||||
@@ -289,10 +285,18 @@
|
||||
<string name="settings_column_count_5">5</string>
|
||||
<string name="addFromPkpass">Pilih file Buku Tabungan (.pkpass / .pkpasses)</string>
|
||||
<string name="unsupportedFile">File ini tidak didukung</string>
|
||||
<string name="generic_error_please_retry">Maaf, terjadi kesalahan, silakan coba lagi...</string>
|
||||
<string name="sort_by_valid_from">Berlaku dari</string>
|
||||
<string name="generic_error_please_retry">Terjadi kesalahan</string>
|
||||
<string name="sort_by_valid_from">Berlaku mulai</string>
|
||||
<string name="width">Lebar</string>
|
||||
<string name="card_list_widget_name">Daftar kartu</string>
|
||||
<string name="setBarcodeWidth">Atur Lebar Barcode</string>
|
||||
<string name="setBarcodeWidth">Atur lebar barcode</string>
|
||||
<string name="card_list_widget_empty">Setelah Anda menambahkan beberapa kartu loyalitas di Catima, kartu tersebut akan muncul di sini. Jika Anda memiliki kartu sebelumnya, pastikan tidak semuanya diarsipkan.</string>
|
||||
<string name="cardWithNumber">Kartu <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Kartu <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Jangan memutar perangkat, karena hal ini akan membatalkan tindakan</string>
|
||||
<string name="acra_catima_has_crashed">Kami mohon maaf, tetapi <xliff:g id="app_name">%s</xliff:g> telah mengalami crash. Tolong bantu kami memperbaiki masalah ini dengan mengirimkan laporan kesalahan kepada kami.</string>
|
||||
<string name="acra_explain_crash">Jika memungkinkan, tolong tambahkan detail lebih lanjut tentang apa yang Anda lakukan di sini:</string>
|
||||
<string name="acra_crash_email_subject">Laporan crash <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Minta untuk mengirimkan laporan crash</string>
|
||||
<string name="pref_enable_acra_summary">Ketika diaktifkan, Anda akan diminta untuk melaporkan crash saat terjadi. Laporan crash tidak pernah dikirim secara otomatis.</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="action_search">Cerca</string>
|
||||
<string name="action_add">Aggiungi</string>
|
||||
<string name="noGiftCards">Premi il pulsante + per aggiungere una carta oppure importala dal menù</string>
|
||||
<string name="noGiftCards">Premi il pulsante + per aggiungere una carta oppure importala dal menù ⋮</string>
|
||||
<string name="noMatchingGiftCards">Nessun risultato. Prova a cambiare la tua ricerca.</string>
|
||||
<string name="storeName">Nome</string>
|
||||
<string name="note">Note</string>
|
||||
@@ -17,14 +17,14 @@
|
||||
<string name="sendLabel">Invia…</string>
|
||||
<string name="editCardTitle">Modifica carta</string>
|
||||
<string name="addCardTitle">Aggiungi carta</string>
|
||||
<string name="scanCardBarcode">Scansiona il codice</string>
|
||||
<string name="scanCardBarcode">Scansiona codice a barre</string>
|
||||
<string name="cardShortcut">Scorciatoia per la carta</string>
|
||||
<string name="noCardsMessage">Aggiungi prima una carta</string>
|
||||
<string name="noCardExistsError">Impossibile trovare quella carta</string>
|
||||
<string name="failedParsingImportUriError">Impossibile analizzare l\'URI di importazione</string>
|
||||
<string name="importExport">Importa/Esporta</string>
|
||||
<string name="importExport">Importa/esporta</string>
|
||||
<string name="exportName">Esporta</string>
|
||||
<string name="importExportHelp">Il backup dei dati permette di spostarli su un altro dispositivo.</string>
|
||||
<string name="importExportHelp">Il backup dei dati permette di spostarli su un altro dispositivo</string>
|
||||
<string name="importSuccessfulTitle">Importato</string>
|
||||
<string name="importFailedTitle">Importazione fallita</string>
|
||||
<string name="importFailed">Impossibile eseguire l\'importazione</string>
|
||||
@@ -183,7 +183,7 @@
|
||||
<string name="report_error">Segnala un errore</string>
|
||||
<string name="editGroup">Modifica del gruppo: <xliff:g>%s</xliff:g></string>
|
||||
<string name="group_name_is_empty">Il nome del gruppo non deve essere vuoto</string>
|
||||
<string name="noGiftCardsGroup">Crea alcune carte e poi assegnale al gruppo qui.</string>
|
||||
<string name="noGiftCardsGroup">Crea alcune carte e poi assegnale al gruppo qui</string>
|
||||
<string name="group_edit">Modifica il gruppo</string>
|
||||
<string name="group_name_already_in_use">Il nome del gruppo è già in uso</string>
|
||||
<string name="group_updated">Gruppo aggiornato</string>
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="wrongValueForBarcodeType">選択したバーコード形式ではこの番号は使用できません</string>
|
||||
<string name="unsupportedBarcodeType">このバーコード形式は表示できません。将来のアップデートにより対応するかもしれません。</string>
|
||||
<string name="setBarcodeId">バーコード番号を設定</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">インポートするにはLoyalty Card Keychainでエクスポートした <i>LoyaltyCardKeychain.csv</i>ファイルを選択してください。
|
||||
\nファイルがない場合、 Loyalty Card Keychainアプリからファイルをエクスポートしてください。</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Loyalty Card Keychainからエクスポートしたデータを選択してインポートしてください。\nデータはLoyalty Card Keychainのインポート/エクスポートメニューからエクスポートを押して作成してください。</string>
|
||||
<string name="importLoyaltyCardKeychain">Loyalty Card Keychainからインポート</string>
|
||||
<string name="importFidmeMessage">インポートするにはFindMeでエクスポートした <i>fidme-export-request-xxxxxx.zip</i>ファイルを選択してください。そのあと手動でバーコード形式を選択してください。
|
||||
\nファイルがない場合、FidMeでファイルを作成してください。</string>
|
||||
<string name="importFidmeMessage">FidMeからエクスポートしたデータを選択してインポートしたうえで、手動でバーコードの種類を選択してください。\nFidMeプロフィールから作成するには データ保護 を選択して データを抽出 を押してください。</string>
|
||||
<string name="importFidme">FidMeからインポート</string>
|
||||
<string name="importCatimaMessage">インポートするにはCatimaでエクスポートした<i>Catima.zip</i>ファイルを選択してください。
|
||||
\nファイルがない場合、他のCatimaアプリでファイルをエクスポートしてください。</string>
|
||||
<string name="importCatimaMessage">インポートするにはCatimaからエクスポートしたファイルを選択してください。\nファイルは別なCatimaアプリのインポート/エクスポートメニューからエクスポートを押して作成できます。</string>
|
||||
<string name="importCatima">Catimaからインポート</string>
|
||||
<string name="accept">承認</string>
|
||||
<string name="privacy_policy">プライバシーポリシー</string>
|
||||
@@ -40,15 +37,15 @@
|
||||
</plurals>
|
||||
<string name="moveDown">下に移動</string>
|
||||
<string name="moveUp">上に移動</string>
|
||||
<string name="failedOpeningFileManager">ファイルマネージャーをインストールしてください。</string>
|
||||
<string name="failedOpeningFileManager">ファイルマネージャーを開けません</string>
|
||||
<string name="deleteConfirmationGroup">グループを削除しますか?</string>
|
||||
<string name="all">すべて</string>
|
||||
<string name="noGroups">+ボタンを押してグループを追加してください。</string>
|
||||
<string name="noGroups">+ ボタンを押してグループを追加してください</string>
|
||||
<string name="groups">グループ</string>
|
||||
<string name="enter_group_name">グループ名を入力</string>
|
||||
<string name="exportSuccessful">データがエクスポートされました</string>
|
||||
<string name="importSuccessful">データがインポートされました</string>
|
||||
<string name="intent_import_card_from_url_share_text">カード共有をしましょう</string>
|
||||
<string name="intent_import_card_from_url_share_text">カードを共有しましょう</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card">バーコード表示中に画面をロックしない</string>
|
||||
<string name="settings_keep_screen_on">バーコード表示中に画面を点けたままにする</string>
|
||||
<string name="settings_display_barcode_max_brightness">画面を明るくする</string>
|
||||
@@ -59,17 +56,17 @@
|
||||
<string name="settings">設定</string>
|
||||
<string name="starImage">お気に入りのスター</string>
|
||||
<string name="thumbnailDescription">サムネイル</string>
|
||||
<string name="selectBarcodeTitle">バーコード選択</string>
|
||||
<string name="app_libraries">Libre third-party libraries: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="selectBarcodeTitle">バーコードを選択</string>
|
||||
<string name="app_libraries">サードパーティーのライブラリ: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="debug_version_fmt">バージョン: <xliff:g id="version">%s</xliff:g></string>
|
||||
<string name="about_title_fmt"><xliff:g id="app_name">%s</xliff:g> について</string>
|
||||
<string name="app_license">Copylefted libre software, licensed GPLv3+</string>
|
||||
<string name="app_resources">Libre third-party resources: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_license">GPLv3+ライセンスによる、コピーレフトされた自由ソフトウェア</string>
|
||||
<string name="app_resources">サードパーティーのリソース: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="about">このアプリについて</string>
|
||||
<string name="importOptionFilesystemButton">ファイルを選択</string>
|
||||
<string name="importOptionFilesystemExplanation">ストレージからファイルを選択してください。</string>
|
||||
<string name="importOptionFilesystemExplanation">ストレージからファイルを選択してください</string>
|
||||
<string name="importOptionFilesystemTitle">ストレージからインポート</string>
|
||||
<string name="exportOptionExplanation">選択した場所にデータを出力します。</string>
|
||||
<string name="exportOptionExplanation">選択した場所にデータを出力します</string>
|
||||
<string name="exporting">エクスポート中…</string>
|
||||
<string name="importing">インポート中…</string>
|
||||
<string name="exportFailed">カードをエクスポートできませんでした</string>
|
||||
@@ -77,13 +74,12 @@
|
||||
<string name="exportSuccessfulTitle">エクスポートしました</string>
|
||||
<string name="sameAsCardId">カード番号に合わせる</string>
|
||||
<string name="barcodeId">バーコード番号</string>
|
||||
<string name="importVoucherVaultMessage">Voucher Vaultでエクスポートした <i>vouchervault.json</i>ファイルを選択してください。
|
||||
\nファイルがない場合、Voucher Vaultでファイルをエクスポートしてください。</string>
|
||||
<string name="importVoucherVaultMessage">Voucher Vaultでエクスポートしてからインポートしてください。\nエクスポートはVoucher Vaultでエクスポートを押して作成してください。</string>
|
||||
<string name="importVoucherVault">Voucher Vaultからインポート</string>
|
||||
<string name="importFailed">カードをインポートできません</string>
|
||||
<string name="importFailedTitle">インポートに失敗しました</string>
|
||||
<string name="importSuccessfulTitle">インポートしました</string>
|
||||
<string name="importExportHelp">データをバックアップすると、他のデバイスにカードを移すことができます。</string>
|
||||
<string name="importExportHelp">データをバックアップする事で他のデバイスにカードを移行できます</string>
|
||||
<string name="exportName">エクスポート</string>
|
||||
<string name="importExport">インポート/エクスポート</string>
|
||||
<string name="failedParsingImportUriError">インポートURIを解析できません</string>
|
||||
@@ -91,8 +87,8 @@
|
||||
<string name="noCardsMessage">カードを追加</string>
|
||||
<string name="cardShortcut">カードのショートカット</string>
|
||||
<string name="scanCardBarcode">バーコードをスキャン</string>
|
||||
<string name="addCardTitle">カードの追加</string>
|
||||
<string name="editCardTitle">カードの編集</string>
|
||||
<string name="addCardTitle">カードを追加</string>
|
||||
<string name="editCardTitle">カードを編集</string>
|
||||
<string name="sendLabel">送信先を選択…</string>
|
||||
<string name="share">共有</string>
|
||||
<string name="ok">確定</string>
|
||||
@@ -109,25 +105,25 @@
|
||||
<string name="note">メモ</string>
|
||||
<string name="storeName">名前</string>
|
||||
<string name="noMatchingGiftCards">該当なし</string>
|
||||
<string name="noGiftCards">+ボタンからカードを新規追加、⋮メニューからカードをインポートすることができます。</string>
|
||||
<string name="noGiftCards">+ ボタンからカードを新規追加、⋮ メニューからカードをインポート出来ます</string>
|
||||
<string name="action_add">追加</string>
|
||||
<string name="action_search">検索</string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">カードを共有しましょう</string>
|
||||
<string name="turn_flashlight_off">ライトをオフにする</string>
|
||||
<string name="turn_flashlight_on">ライトをオンにする</string>
|
||||
<string name="failedGeneratingShareURL">共有URLの生成を生成できませんでした。バグを報告してください。</string>
|
||||
<string name="passwordRequired">パスワードを入力してください</string>
|
||||
<string name="turn_flashlight_off">ライトを消灯する</string>
|
||||
<string name="turn_flashlight_on">ライトを点灯する</string>
|
||||
<string name="failedGeneratingShareURL">共有可能なURLを作成できませんでした</string>
|
||||
<string name="passwordRequired">パスワードを入力</string>
|
||||
<string name="no">いいえ</string>
|
||||
<string name="yes">はい</string>
|
||||
<string name="updateBarcodeQuestionText">カード番号を変更しました。バーコード番号も同じ値に変更しますか?</string>
|
||||
<string name="updateBarcodeQuestionTitle">バーコードの番号を変更しますか?</string>
|
||||
<string name="takePhoto">写真を撮影する</string>
|
||||
<string name="removeImage">画像を削除</string>
|
||||
<string name="setBackImage">裏面の画像を設定</string>
|
||||
<string name="setFrontImage">表面の画像を設定</string>
|
||||
<string name="setBackImage">背面の画像を設定</string>
|
||||
<string name="setFrontImage">前面の画像を設定</string>
|
||||
<string name="photos">画像</string>
|
||||
<string name="backImageDescription">裏</string>
|
||||
<string name="frontImageDescription">表</string>
|
||||
<string name="backImageDescription">背面</string>
|
||||
<string name="frontImageDescription">前面</string>
|
||||
<plurals name="selectedCardCount">
|
||||
<item quantity="other">選択済み: <xliff:g>%d</xliff:g> 枚</item>
|
||||
</plurals>
|
||||
@@ -155,7 +151,7 @@
|
||||
<string name="noGroupCards">このグループにはカードがありません</string>
|
||||
<string name="sort_by">並び替え</string>
|
||||
<string name="sort_by_expiry">期限</string>
|
||||
<string name="sort_by_most_recently_used">最近使用したカード</string>
|
||||
<string name="sort_by_most_recently_used">最近使ったカード</string>
|
||||
<string name="sort_by_name">名前</string>
|
||||
<string name="sort">並び替え</string>
|
||||
<string name="rate_this_app">このアプリを評価する</string>
|
||||
@@ -168,7 +164,7 @@
|
||||
<string name="help_translate_this_app">翻訳を手伝う</string>
|
||||
<string name="license">ライセンス</string>
|
||||
<string name="on_google_play">Google Play</string>
|
||||
<string name="report_error">問題を報告する</string>
|
||||
<string name="report_error">問題を報告</string>
|
||||
<string name="reverse">逆順</string>
|
||||
<string name="and_data_usage">データの扱いなど</string>
|
||||
<string name="group_updated">グループを更新しました</string>
|
||||
@@ -186,11 +182,11 @@
|
||||
<string name="chooseValidFromDate">有効期限を選択</string>
|
||||
<string name="anyDate">無期限</string>
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">仕事をするためにいくつかのスキャナーが必要</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">一部のスキャナを動かすのに必要です</string>
|
||||
<string name="storageReadPermissionRequired">このアクションのためにストレージの読み取り権限を許可…</string>
|
||||
<string name="cameraPermissionDeniedTitle">カメラへアクセスできません</string>
|
||||
<string name="cameraPermissionRequired">このアクションのためにカメラへのアクセス権限の許可…</string>
|
||||
<string name="noGiftCardsGroup">いくつかのカードを作って、それらをこのグループにアサインします。</string>
|
||||
<string name="noGiftCardsGroup">幾つかカードを作り、それらをこのグループに紐づけます</string>
|
||||
<string name="noCameraPermissionDirectToSystemSetting">バーコードをスキャンするためには、Catimaはカメラへのアクセスを必要とします。ここをタップして権限設定の変更をお願いします。</string>
|
||||
<string name="importCards">カードをインポート</string>
|
||||
<string name="show_balance">残高を表示</string>
|
||||
@@ -201,7 +197,7 @@
|
||||
<string name="welcome">Catimaへようこそ</string>
|
||||
<string name="show_name_below_image_thumbnail">画像サムネイルの下に名前を表示</string>
|
||||
<string name="settings_keep_screen_on_summary">画面の自動消灯を無効化します</string>
|
||||
<string name="settings_category_title_cards">カード</string>
|
||||
<string name="settings_category_title_cards">カードビュー</string>
|
||||
<string name="settings_category_title_general">一般</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">画面のロックを無効化します</string>
|
||||
<string name="action_display_options">表示の設定</string>
|
||||
@@ -234,4 +230,75 @@
|
||||
<string name="permissionReadCardsDescription">Catimaカードと、ノートや画像を含むすべての詳細を読み取る</string>
|
||||
<string name="settings_use_volume_keys_navigation_summary">ボリュームボタンを使ってどのカードを表示するかを変更する</string>
|
||||
<string name="unsupportedFile">このファイルはサポートされていません</string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">著作権 © 2019–<xliff:g>%d</xliff:g> Sylvia van Os と貢献者一同</string>
|
||||
<string name="app_copyright_short">著作権 © Sylvia van Os と貢献者一同</string>
|
||||
<string name="app_copyright_old">Loyalty Card Keychain が基になりました\n著作権 © 2016–2020 Branden Archer</string>
|
||||
<string name="settings_allow_content_provider_read_summary">アプリは継続したアクセス許可を要求します</string>
|
||||
<string name="settings_use_volume_keys_navigation">音量ボタンでカードを切り替え</string>
|
||||
<plurals name="balancePoints">
|
||||
<item quantity="other"><xliff:g>%s</xliff:g> ポイント</item>
|
||||
</plurals>
|
||||
<string name="balanceParsingFailed">残高が無効です</string>
|
||||
<string name="showMoreInfo">情報を確認</string>
|
||||
<string name="updateBalance">残高を更新</string>
|
||||
<string name="failedToRetrieveImageFile">画像ファイルを取得できませんでした</string>
|
||||
<string name="barcodeLongPressMessage">ギャラリーアプリは画像のみ開けます</string>
|
||||
<string name="sort_by_valid_from">有効期限</string>
|
||||
<string name="starred">スター付き</string>
|
||||
<string name="include_if_asking_support">サポートを依頼する場合、以下の情報を含めて下さい:</string>
|
||||
<string name="failedLaunchingPhotoPicker">サポートされている画像ピッカーが見つかりませんでした</string>
|
||||
<plurals name="groupCardCountWithArchived">
|
||||
<item quantity="other"><xliff:g>%1$d</xliff:g> のカード (<xliff:g id="archivedCount">%2$d</xliff:g> アーカイブ済み)</item>
|
||||
</plurals>
|
||||
<string name="updateBalanceTitle">どれくらい収入・支出がありましたか?</string>
|
||||
<string name="updateBalanceHint">金額を入力</string>
|
||||
<string name="currentBalanceSentence">現在の残高: <xliff:g>%s</xliff:g></string>
|
||||
<string name="newBalanceSentence">新規残高: <xliff:g>%s</xliff:g></string>
|
||||
<string name="validFromSentence">有効期限: <xliff:g>%s</xliff:g></string>
|
||||
<string name="height">高さ</string>
|
||||
<string name="switchToFrontImage">前面画像へ切り替え</string>
|
||||
<string name="switchToBackImage">背面画像へ切り替え</string>
|
||||
<string name="switchToBarcode">バーコードへ切り替え</string>
|
||||
<string name="openFrontImageInGalleryApp">前面画像を画像ビューワーアプリで開く</string>
|
||||
<string name="openBackImageInGalleryApp">背面画像を画像ビューワーアプリで開く</string>
|
||||
<string name="setBarcodeHeight">バーコードの高さを設定</string>
|
||||
<string name="icon_header_click_text">サムネイルを長押しして編集</string>
|
||||
<string name="settings_category_title_cards_overview">カードの概要</string>
|
||||
<string name="settings_column_count_portrait">縦向きモードの列</string>
|
||||
<string name="settings_column_count_landscape">横向きモードの列</string>
|
||||
<string name="settings_automatic_column_count">自動</string>
|
||||
<string name="view_online">オンラインで閲覧</string>
|
||||
<string name="enter_card_id">カード記載のID番号かテキストを入力してください</string>
|
||||
<string name="card_id_must_not_be_empty">カードIDは空っぽに出来ません</string>
|
||||
<string name="field_must_not_be_empty">この欄は入力必須です</string>
|
||||
<string name="manually_enter_barcode_instructions">カードに記載のID番号かテキストを入力してからカード上のバーコードかそれに類似のものを押してください。</string>
|
||||
<string name="add_manually_warning_title">スキャンするのをお勧めします</string>
|
||||
<string name="add_manually_warning_message">一部のカードでは、バーコードの値がカード券面記載の番号と異なります。そのために、バーコードを手動入力しても正しく機能しない場合があります。代わりにカメラでバーコードをスキャンすることをお勧めしています。それでも続けますか?</string>
|
||||
<string name="spend">支出</string>
|
||||
<string name="receive">収入</string>
|
||||
<string name="amountParsingFailed">無効な金額です</string>
|
||||
<string name="errorReadingFile">ファイルを読み取れませんでした</string>
|
||||
<string name="failedLaunchingFileManager">サポートされているファイルマネージャーが見つかりませんでした</string>
|
||||
<string name="multipleBarcodesFoundPleaseChooseOne">発見できたバーコードのどれを使いますか?</string>
|
||||
<string name="pageWithNumber"><xliff:g>%d</xliff:g> ページ</string>
|
||||
<string name="noCameraFoundGuideText">お使いのデバイスにカメラが搭載されていないようです。搭載されている場合には、デバイスを再起動してみてください。搭載されていなければ、下にあるその他のオプションボタンから別の方法でバーコードを追加してください。</string>
|
||||
<string name="useFrontImage">前面画像を利用</string>
|
||||
<string name="useBackImage">背面画像を利用</string>
|
||||
<string name="addFromPkpass">Passbook 形式のファイルを選択 (.pkpass / .pkpasses)</string>
|
||||
<string name="generic_error_please_retry">エラーが発生しました</string>
|
||||
<string name="width">幅</string>
|
||||
<string name="card_list_widget_name">カード一覧</string>
|
||||
<string name="setBarcodeWidth">バーコードの幅を設定</string>
|
||||
<string name="card_list_widget_empty">Catimaで幾つかポイントカードを追加すると、ここに表示されます。カードをお持ちの場合、全てアーカイブがされていないことをご確認ください。</string>
|
||||
<string name="cardWithNumber">カード <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">カード <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">動作がキャンセルされるため、デバイスを回転させないようにしてください</string>
|
||||
<string name="acra_catima_has_crashed">申し訳ありません、<xliff:g id="app_name">%s</xliff:g> がクラッシュしました。エラーレポートを送信し問題解決にご協力ください。</string>
|
||||
<string name="acra_explain_crash">できれば、何をしようとしてそうなったのか、より詳細な情報を追加ねがいます:</string>
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> クラッシュレポート</string>
|
||||
<string name="pref_enable_acra">クラッシュレポートを送信する</string>
|
||||
<string name="pref_enable_acra_summary">有効にすると、クラッシュ発生時に報告するかを確認されます。クラッシュレポートが自動送信されることはありません。</string>
|
||||
<string name="copy_value">値をコピー</string>
|
||||
<string name="copied_to_clipboard">クリップボードへコピー</string>
|
||||
<string name="nothing_to_copy">値が見つかりません</string>
|
||||
</resources>
|
||||
|
||||
@@ -76,16 +76,16 @@
|
||||
<string name="leaveWithoutSaveConfirmation">Iziet nesaglabājot\?</string>
|
||||
<string name="addFromImage">Atlasīt attēlu no galerijas</string>
|
||||
<string name="card">Karte</string>
|
||||
<string name="expiryDate">Derīguma termiņš</string>
|
||||
<string name="expiryDate">Derīguma beigu datums</string>
|
||||
<string name="never">Nekad</string>
|
||||
<string name="chooseExpiryDate">Izvēlēties beigu datumu</string>
|
||||
<string name="failedToRetrieveImageFile">Neizdevās iegūt attēla datni</string>
|
||||
<string name="barcodeLongPressMessage">Galerijas lietotnē var atvērt tikai attēlus</string>
|
||||
<string name="sort_by_expiry">Derīguma termiņš</string>
|
||||
<string name="sort_by_expiry">Derīgums</string>
|
||||
<string name="reverse">...apgrieztā secībā</string>
|
||||
<string name="credits">Pateicības</string>
|
||||
<string name="shortcutSelectCard">Atlasīt karti</string>
|
||||
<string name="duplicateCard">Dublēt</string>
|
||||
<string name="duplicateCard">Pavairot</string>
|
||||
<string name="archive">Arhivēt</string>
|
||||
<string name="translate_platform">Weblate</string>
|
||||
<string name="starred">Izlase</string>
|
||||
@@ -96,7 +96,7 @@
|
||||
<item quantity="other">Neatgriezeniski dzēst šīs <xliff:g>%d</xliff:g> kartes\?</item>
|
||||
</plurals>
|
||||
<string name="about_title_fmt">Par <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="expiryStateSentenceExpired">Derīguma termiņš beidzās: <xliff:g>%s</xliff:g></string>
|
||||
<string name="expiryStateSentenceExpired">Derīgums beidzās: <xliff:g>%s</xliff:g></string>
|
||||
<string name="selectColor">Atlasīt krāsu</string>
|
||||
<string name="settings_catima_theme">Catima</string>
|
||||
<string name="settings_pink_theme">Rozā</string>
|
||||
@@ -129,7 +129,7 @@
|
||||
<string name="source_repository">Pirmkoda glabātava</string>
|
||||
<string name="rate_this_app">Novērtēt šo lietotni</string>
|
||||
<string name="noGiftCardsGroup">Izveido kādas kartes, tad šeit pievieno tās kopai</string>
|
||||
<string name="options">Parametri</string>
|
||||
<string name="options">Iespējas</string>
|
||||
<plurals name="groupCardCount">
|
||||
<item quantity="zero"><xliff:g>%d</xliff:g> kartes</item>
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> karte</item>
|
||||
@@ -165,7 +165,7 @@
|
||||
<string name="group_updated">Kopa atjaunināta</string>
|
||||
<string name="addManually">Pašrocīgi ievadīt svītrkodu</string>
|
||||
<string name="groupsList">Kopas: <xliff:g>%s</xliff:g></string>
|
||||
<string name="expiryStateSentence">Derīguma termiņš: <xliff:g>%s</xliff:g></string>
|
||||
<string name="expiryStateSentence">Derīgums beigsies: <xliff:g>%s</xliff:g></string>
|
||||
<string name="balanceSentence">Atlikums: <xliff:g>%s</xliff:g></string>
|
||||
<string name="editBarcode">Labot svītrkodu</string>
|
||||
<string name="importCatima">Ievietot no Catima</string>
|
||||
@@ -182,10 +182,10 @@
|
||||
<string name="intent_import_card_from_url_share_multiple_text">Vēlos ar Tevi kopīgot dažas kartes</string>
|
||||
<string name="frontImageDescription">Priekšpuses attēls</string>
|
||||
<string name="backImageDescription">Aizmugures attēls</string>
|
||||
<string name="photos">Foto</string>
|
||||
<string name="photos">Fotoattēli</string>
|
||||
<string name="setFrontImage">Iestatīt priekšpuses attēlu</string>
|
||||
<string name="setBackImage">Iestatīt aizmugures attēlu</string>
|
||||
<string name="takePhoto">Fotografēt</string>
|
||||
<string name="takePhoto">Uzņemt attēlu</string>
|
||||
<string name="passwordRequired">Jāievada parole</string>
|
||||
<string name="exportPassword">Iestatīt paroli, lai aizsargātu savu izguves datni (pēc izvēles)</string>
|
||||
<string name="turn_flashlight_on">Ieslēgt zibspuldzi</string>
|
||||
@@ -305,4 +305,13 @@
|
||||
<string name="card_list_widget_empty">Pēc klienta karšu pievienošanas Catima tās parādīsies šeit. Ja Tev ir kartes, jāpārliecinās, ka tās visas nav arhivētas.</string>
|
||||
<string name="cardWithNumber">Karte <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Karte <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Lūgums nepagriezt ierīci, jo tas atcels darbību</string>
|
||||
<string name="acra_catima_has_crashed">Mēs atvainojamies, bet <xliff:g id="app_name">%s</xliff:g> avarēja. Lūgums palīdzēt mums novērst šo nepilnību, nosūtot mums ziņojumu par kļūdu.</string>
|
||||
<string name="acra_explain_crash">Ja iespējams, lūgums pievienot vairāk informācijas, par to, ko darīji:</string>
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> avārijas ziņojums</string>
|
||||
<string name="pref_enable_acra">Vaicāt, lai nosūtītu ziņojumus par avārijām</string>
|
||||
<string name="pref_enable_acra_summary">Kad iespējots, tiks vaicāts ziņot par avāriju, kad tā notiek. Ziņojumi par avārijām nekad netiks automātiski nosūtīti.</string>
|
||||
<string name="copy_value">Ievietot vērtību starpliktuvē</string>
|
||||
<string name="copied_to_clipboard">Ievietots starpliktuvē</string>
|
||||
<string name="nothing_to_copy">Nav atrasta vērtība</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="action_search">Zoeken</string>
|
||||
<string name="action_add">Toevoegen</string>
|
||||
<string name="noGiftCards">Druk op de plusknop (‘+’) om een kaart toe te voegen of importeer kaarten via het ⋮-menu</string>
|
||||
<string name="noMatchingGiftCards">Geen zoekresultaten - probeer een andere zoekopdracht.</string>
|
||||
<string name="noGiftCards">Druk op de + plusknop om een kaart toe te voegen of importeer kaarten via het ⋮-menu</string>
|
||||
<string name="noMatchingGiftCards">Geen zoekresultaten – probeer een andere zoekopdracht.</string>
|
||||
<string name="storeName">Naam</string>
|
||||
<string name="note">Aantekening</string>
|
||||
<string name="note">Notitie</string>
|
||||
<string name="cardId">Kaartnummer</string>
|
||||
<string name="barcodeType">Soort barcode</string>
|
||||
<string name="cancel">Annuleren</string>
|
||||
@@ -299,4 +299,10 @@
|
||||
<string name="card_list_widget_empty">Zodra er kaarten in Catima toegevoegd zijn worden deze hier getoond. Heb je al kaarten? Controleer dan of deze niet gearchiveerd zijn.</string>
|
||||
<string name="cardWithNumber">Kaart <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Kaart <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Draai niet je telefoon, dit annuleert de actie</string>
|
||||
<string name="acra_catima_has_crashed">Sorry, <xliff:g id="app_name">%s</xliff:g> is gecrasht. Je kunt ons helpen dit op te lossen door een foutrapport te sturen.</string>
|
||||
<string name="acra_explain_crash">Voeg als het kan wat meer details toe over wat je aan het doen was:</string>
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> foutrapport</string>
|
||||
<string name="pref_enable_acra">Vraag om foutrapporten te versturen</string>
|
||||
<string name="pref_enable_acra_summary">Als dit aanstaat, zal je gevraagd worden om foutrapporten te sturen als de app crasht. Dit zal nooit automatisch gebeuren.</string>
|
||||
</resources>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<string name="importing">Importowanie…</string>
|
||||
<string name="exporting">Eksportowanie…</string>
|
||||
<string name="importOptionFilesystemTitle">Importuj z systemu plików</string>
|
||||
<string name="importOptionFilesystemExplanation">Wybierz określony plik z systemu plików.</string>
|
||||
<string name="importOptionFilesystemExplanation">Wybierz określony plik z systemu plików</string>
|
||||
<string name="importOptionFilesystemButton">Z systemu plików</string>
|
||||
<string name="about">O aplikacji</string>
|
||||
<string name="app_license">Wolne oprogramowanie typu copyleft, na licencji GPLv3+</string>
|
||||
@@ -174,8 +174,8 @@
|
||||
<string name="sort_by">Sortuj według</string>
|
||||
<string name="credits">Podziękowania</string>
|
||||
<string name="help_translate_this_app">Pomóż przetłumaczyć tę aplikację</string>
|
||||
<string name="source_repository">Repozytorium Źródłowe</string>
|
||||
<string name="report_error">Zgłoś Błąd</string>
|
||||
<string name="source_repository">Repozytorium źródłowe</string>
|
||||
<string name="report_error">Zgłoś błąd</string>
|
||||
<string name="setIcon">Ustaw miniaturę</string>
|
||||
<string name="on_github">na GitHub\'ie</string>
|
||||
<string name="selectColor">Wybierz kolor</string>
|
||||
@@ -243,10 +243,10 @@
|
||||
<string name="switchToFrontImage">Przełącz na obraz z przodu</string>
|
||||
<string name="switchToBackImage">Przełącz na obraz z tyłu</string>
|
||||
<string name="switchToBarcode">Przełącz na kod kreskowy</string>
|
||||
<string name="openFrontImageInGalleryApp">Otwórz obraz z przodu w aplikacji galeria</string>
|
||||
<string name="openFrontImageInGalleryApp">Otwórz obraz z przodu w galerii</string>
|
||||
<string name="setBarcodeHeight">Ustaw wysokość kodu kreskowego</string>
|
||||
<string name="donate">Darowizna</string>
|
||||
<string name="openBackImageInGalleryApp">Otwórz obraz z powrotem w aplikacji galerii</string>
|
||||
<string name="openBackImageInGalleryApp">Otwórz obraz z powrotem w galerii</string>
|
||||
<string name="icon_header_click_text">Przytrzymaj, aby edytować miniaturę</string>
|
||||
<string name="show_name_below_image_thumbnail">Pokaż nazwę pod miniaturką zdjęcia</string>
|
||||
<string name="show_balance">Pokaż balans</string>
|
||||
@@ -315,4 +315,9 @@
|
||||
<string name="card_list_widget_name">Lista kart</string>
|
||||
<string name="cardWithNumber">Karta <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Karta <xliff:g>%d</xliff:g> (%s)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Proszę nie obracać urządzenia, gdyż anuluje to obecne zadanie</string>
|
||||
<string name="acra_explain_crash">Jeśli możliwe, dodaj więcej szczegółów na temat co robiłeś/aś tutaj:</string>
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> raport błędu</string>
|
||||
<string name="pref_enable_acra">Zapytaj o wysłanie raportu błędu</string>
|
||||
<string name="pref_enable_acra_summary">Kiedy zaznaczone, będziesz proszony/a o zgłoszenie raportu błędu, gdyby zaistniał. Raporty błędu nigdy nie są wysyłane automatycznie.</string>
|
||||
</resources>
|
||||
|
||||
@@ -305,4 +305,13 @@
|
||||
<string name="card_list_widget_empty">Depois que você adicionar alguns cartões de fidelidade no Catima, eles aparecerão aqui. Se você tiver cartões, verifique se eles não estão todos arquivados.</string>
|
||||
<string name="cardWithNumber">Cartão <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Cartão <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Não gire o dispositivo, pois isso cancelará a ação</string>
|
||||
<string name="acra_catima_has_crashed">Lamentamos, mas o <xliff:g id="app_name">%s</xliff:g> travou. Ajude-nos a corrigir esse problema enviando um relatório de erro.</string>
|
||||
<string name="acra_explain_crash">Se possível, acrescente mais detalhes sobre o que você estava fazendo aqui:</string>
|
||||
<string name="acra_crash_email_subject">Relatório de falha em <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Solicitar o envio de relatórios de falhas</string>
|
||||
<string name="pref_enable_acra_summary">Quando ativado, você será solicitado a relatar uma falha quando isto ocorrer. Os relatórios de falhas nunca são enviados automaticamente.</string>
|
||||
<string name="copy_value">Copiar valor</string>
|
||||
<string name="copied_to_clipboard">Copiado para a área de transferência</string>
|
||||
<string name="nothing_to_copy">Nenhum valor encontrado</string>
|
||||
</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">Adicionar</string>
|
||||
<string name="importOptionFilesystemExplanation">Escolha um ficheiro específico a partir do sistema de ficheiros.</string>
|
||||
<string name="importOptionFilesystemExplanation">Escolha um ficheiro específico do sistema de ficheiros</string>
|
||||
<string name="action_search">Pesquisa</string>
|
||||
<string name="star">Adicionar aos favoritos</string>
|
||||
<string name="noMatchingGiftCards">Sem resultados. Tente alterar a sua pesquisa.</string>
|
||||
@@ -11,7 +11,7 @@
|
||||
<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="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="unstar">Retirar dos favoritos</string>
|
||||
<string name="importOptionFilesystemButton">Do sistema de ficheiros</string>
|
||||
@@ -36,10 +36,10 @@
|
||||
<string name="noCardsMessage">Adicione um cartão primeiro</string>
|
||||
<string name="noCardExistsError">Não foi possível encontrar esse cartão</string>
|
||||
<string name="failedParsingImportUriError">Não foi possível analisar o URI de importação</string>
|
||||
<string name="importExport">Importar / Exportar</string>
|
||||
<string name="importExport">Importar/exportar</string>
|
||||
<string name="exportName">Exportar</string>
|
||||
<string name="importSuccessful">Dados importados</string>
|
||||
<string name="noGroups">Clique no botão + para adicionar grupos para categorização.</string>
|
||||
<string name="noGroups">Clique no botão + para adicionar grupos para a categorização</string>
|
||||
<string name="noGroupCards">Este grupo está vazio</string>
|
||||
<string name="intent_import_card_from_url_share_text">Quero partilhar um cartão</string>
|
||||
<string name="settings_display_barcode_max_brightness">Iluminar o ecrã</string>
|
||||
@@ -58,11 +58,11 @@
|
||||
<string name="selectBarcodeTitle">Selecionar código de barras</string>
|
||||
<string name="thumbnailDescription">Miniatura</string>
|
||||
<string name="starImage">Favorito</string>
|
||||
<string name="failedOpeningFileManager">Instalar primeiro um gestor de ficheiros.</string>
|
||||
<string name="failedOpeningFileManager">Falha ao abrir o gestor de ficheiros</string>
|
||||
<string name="moveUp">Subir</string>
|
||||
<string name="moveDown">Descer</string>
|
||||
<string name="leaveWithoutSaveTitle">Sair</string>
|
||||
<string name="importExportHelp">A cópia de segurança dos seus dados permite-lhe movê-los para outro dispositivo.</string>
|
||||
<string name="importExportHelp">A cópia de segurança dos seus dados permite-lhe movê-los para outro dispositivo</string>
|
||||
<string name="importSuccessfulTitle">Importado</string>
|
||||
<string name="importFailedTitle">A importação falhou</string>
|
||||
<string name="importFailed">Não foi possível importar</string>
|
||||
@@ -76,18 +76,17 @@
|
||||
<string name="chooseImportType">Importar dados de</string>
|
||||
<string name="card">Cartão</string>
|
||||
<string name="expiryStateSentence">Expiram: <xliff:g>%s</xliff:g></string>
|
||||
<string name="app_resources">Recursos livres de terceiros: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Bibliotecas livres de terceiros: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Recursos de terceiros: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Bibliotecas de terceiros: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="takePhoto">Tirar uma fotografia</string>
|
||||
<string name="yes">Sim</string>
|
||||
<string name="exportPassword">Defina uma palavra-passe para proteger a exportação (opcional)</string>
|
||||
<string name="exportPasswordHint">Digite a palavra-passe</string>
|
||||
<string name="setBarcodeId">Definir o valor do código de barras</string>
|
||||
<string name="sameAsCardId">Igual ao identificador</string>
|
||||
<string name="importFidmeMessage">Selecione a exportação <i>fidme-export-request-xxxxxx.zip</i> do FidMe para importar e depois selecione os tipos de código de barras manualmente.
|
||||
\nPrimeiro crie a exportação no seu perfil do FidMe escolhendo a opção \"Proteção de dados\" e em seguida pressionando \"Extrair os meus dados\".</string>
|
||||
<string name="importFidmeMessage">Selecione o seu ficheiro exportado do FidMe a importae e depois selecione manualmente os tipos de código de barras. \nCrie-o no seu perfil do FidMe a escolher a opção \"Proteção de dados\" e depois pressionar \"Extrair os meus dados\".</string>
|
||||
<string name="barcodeId">Valor do código de barras</string>
|
||||
<string name="wrongValueForBarcodeType">O valor não é válido para o tipo de código de barras selecionado</string>
|
||||
<string name="wrongValueForBarcodeType">O valor é inválido para o tipo de código de barras selecionado</string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">Quero partilhar alguns cartões</string>
|
||||
<string name="removeImage">Remover imagem</string>
|
||||
<string name="backImageDescription">Imagem de trás</string>
|
||||
@@ -130,19 +129,16 @@
|
||||
<string name="privacy_policy">Política de privacidade</string>
|
||||
<string name="accept">Aceitar</string>
|
||||
<string name="importCatima">Importar do Catima</string>
|
||||
<string name="importCatimaMessage">Selecione a exportação <i>catima.zip</i> do Catima a importar.
|
||||
\nPrimeiro crie a exportação no menu \"Importar / exportar\" de outra aplicação Catima pressionando \"Exportar\" nesse menu.</string>
|
||||
<string name="importCatimaMessage">Selecione a exportação <i>catima.zip</i> do Catima a importar. \nPrimeiro crie a exportação no menu \"Importar / exportar\" de outra aplicação Catima pressionando \"Exportar\" nesse menu.</string>
|
||||
<string name="importFidme">Importar do FidMe</string>
|
||||
<string name="importLoyaltyCardKeychain">Importar do Loyalty Card Keychain</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Selecione a exportação <i>LoyaltyCardKeychain.csv</i> do Loyalty Card Keychain para importar.
|
||||
\nPrimeiro crie a exportação no menu \"Importar / exportar\" no Loyalty Card Keychain pressionando \"Exportar\".</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Selecione a exportação do Loyalty Card Keychain a importar. \nCrie a exportação no menu \"Importar/exportar\" no Loyalty Card Keychain a pressionar \"Exportar\".</string>
|
||||
<string name="importVoucherVault">Importar do Voucher Vault</string>
|
||||
<string name="importVoucherVaultMessage">Selecione a exportação <i>vouchervault.json</i> do Voucher Vault para importar.
|
||||
\nCrie-a primeiro pressionando a opção \"Exportar\" no Voucher Vault.</string>
|
||||
<string name="importVoucherVaultMessage">Selecione a exportação do Voucher Vault a importar. \nCrie-a a pressionar a opção Exportar no Voucher Vault.</string>
|
||||
<string name="unsupportedBarcodeType">Este tipo de código de barras ainda não pode ser mostrado. Pode vir a ser suportado numa versão posterior da aplicação.</string>
|
||||
<string name="setFrontImage">Definir imagem frontal</string>
|
||||
<string name="setBackImage">Definir imagem de trás</string>
|
||||
<string name="failedGeneratingShareURL">Não foi possível gerar um URL partilhável. Por favor reporte isto aos programadores.</string>
|
||||
<string name="failedGeneratingShareURL">Não foi possível gerar um URL partilhável</string>
|
||||
<string name="turn_flashlight_on">Ligar lanterna</string>
|
||||
<string name="turn_flashlight_off">Desligar lanterna</string>
|
||||
<string name="settings_locale">Idioma</string>
|
||||
@@ -156,7 +152,7 @@
|
||||
<string name="app_contributors">Tornado possível por: <xliff:g id="app_contributors">%s</xliff:g></string>
|
||||
<string name="sort">Ordenar</string>
|
||||
<string name="sort_by_name">Nome</string>
|
||||
<string name="sort_by_most_recently_used">Mais usados recentemente</string>
|
||||
<string name="sort_by_most_recently_used">Mais recentemente utilizado</string>
|
||||
<string name="sort_by_expiry">Validade</string>
|
||||
<string name="reverse">…na ordem inversa</string>
|
||||
<string name="sort_by">Ordenar por</string>
|
||||
@@ -169,7 +165,7 @@
|
||||
<string name="and_data_usage">e utilização de dados</string>
|
||||
<string name="rate_this_app">Avalie esta aplicação</string>
|
||||
<string name="on_google_play">no Google Play</string>
|
||||
<string name="exportOptionExplanation">Os dados serão guardados num local à sua escolha.</string>
|
||||
<string name="exportOptionExplanation">Os dados serão guardados num local à sua escolha</string>
|
||||
<plurals name="deleteCardsTitle">
|
||||
<item quantity="one">Eliminar <xliff:g>%d</xliff:g> cartão</item>
|
||||
<item quantity="many">Eliminar <xliff:g>%d</xliff:g> cartões</item>
|
||||
@@ -187,7 +183,7 @@
|
||||
<string name="group_name_is_empty">O nome do grupo não pode ser vazio</string>
|
||||
<string name="group_updated">Grupo atualizado</string>
|
||||
<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="noGiftCardsGroup">Crie alguns cartões e atribua-os depois ao grupo aqui</string>
|
||||
<string name="selectColor">Selecionar cor</string>
|
||||
<string name="setIcon">Definir miniatura</string>
|
||||
<string name="shortcutSelectCard">Selecione um cartão</string>
|
||||
@@ -212,7 +208,7 @@
|
||||
<item quantity="many"><xliff:g>%1$d</xliff:g> cartões (<xliff:g id="archivedCount">%2$d</xliff:g> arquivados)</item>
|
||||
<item quantity="other"><xliff:g>%1$d</xliff:g> cartões (<xliff:g id="archivedCount">%2$d</xliff:g> arquivados)</item>
|
||||
</plurals>
|
||||
<string name="failedLaunchingPhotoPicker">Não foi encontrada nenhuma aplicação de galeria de imagens</string>
|
||||
<string name="failedLaunchingPhotoPicker">Não foi possível encontrar um seletor de imagens compatível</string>
|
||||
<string name="nextCard">Próximo</string>
|
||||
<string name="previousCard">Anterior</string>
|
||||
<string name="failedToOpenUrl">Instale primeiro um navegador de Internet</string>
|
||||
@@ -237,13 +233,13 @@
|
||||
<string name="height">Altura</string>
|
||||
<string name="switchToBackImage">Mudar para a imagem de trás</string>
|
||||
<string name="switchToBarcode">Mudar para o código de barras</string>
|
||||
<string name="openFrontImageInGalleryApp">Abrir a imagem frontal na aplicação da galeria</string>
|
||||
<string name="openBackImageInGalleryApp">Abrir a imagem traseira na aplicação da galeria</string>
|
||||
<string name="openFrontImageInGalleryApp">Abrir imagem frontal na app visualizadora de imagens</string>
|
||||
<string name="openBackImageInGalleryApp">Abrir imagem traseira na app visualizadora de imagens</string>
|
||||
<string name="setBarcodeHeight">Definir altura do código de barras</string>
|
||||
<string name="donate">Doar</string>
|
||||
<string name="show_validity">Mostrar validade</string>
|
||||
<string name="show_balance">Mostrar saldo</string>
|
||||
<string name="permissionReadCardsLabel">Ler Cartões Catima</string>
|
||||
<string name="permissionReadCardsLabel">Ler cartões Catima</string>
|
||||
<string name="permissionReadCardsDescription">leia seus cartões do Catima 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>
|
||||
@@ -272,7 +268,7 @@
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="continue_">Continuar</string>
|
||||
<string name="add_manually_warning_title">Recomenda-se a digitalização</string>
|
||||
<string name="add_manually_warning_message">Em algumas lojas, o valor do código de barras é diferente do número escrito no cartão. Por este motivo, a introdução manual de um código de barras pode nem sempre funcionar. Recomenda-se vivamente que, em vez disso, digitalize o código de barras com a sua câmara. Ainda quer continuar?</string>
|
||||
<string name="add_manually_warning_message">Em alguns cartões, o valor do código de barras é diferente do número escrito no cartão. Por este motivo, a introdução manual de um código de barras pode nem sempre funcionar. Recomenda-se que, em vez disso, digitalizar o código de barras com a sua câmara. Ainda quer continuar?</string>
|
||||
<string name="spend">Gastar</string>
|
||||
<string name="receive">Receber</string>
|
||||
<string name="amountParsingFailed">Montante inválido</string>
|
||||
@@ -280,7 +276,7 @@
|
||||
<string name="errorReadingFile">Não foi possível ler o ficheiro</string>
|
||||
<string name="multipleBarcodesFoundPleaseChooseOne">Qual dos códigos de barras encontrados pretende utilizar?</string>
|
||||
<string name="pageWithNumber">Página <xliff:g>%d</xliff:g></string>
|
||||
<string name="failedLaunchingFileManager">Não foi possível encontrar um gestor de ficheiros suportado</string>
|
||||
<string name="failedLaunchingFileManager">Não foi possível encontrar um gestor de ficheiros apoiado</string>
|
||||
<string name="noCameraFoundGuideText">O seu dispositivo não parece ter uma câmara. Se tiver, tente reiniciar o dispositivo. Caso contrário, utilize o botão \"Mais opções\" abaixo para adicionar um código de barras de outra maneira.</string>
|
||||
<string name="importCancelled">Importação cancelada</string>
|
||||
<string name="exportCancelled">Exportação cancelada</string>
|
||||
@@ -301,12 +297,18 @@
|
||||
<string name="settings_column_count_7">7</string>
|
||||
<string name="addFromPkpass">Selecionar um ficheiro Passbook (.pkpass / .pkpasses)</string>
|
||||
<string name="unsupportedFile">Este ficheiro não é suportado</string>
|
||||
<string name="generic_error_please_retry">Lamento, ocorreu um erro, tente novamente...</string>
|
||||
<string name="generic_error_please_retry">Ocorreu um erro</string>
|
||||
<string name="sort_by_valid_from">Válido a partir de</string>
|
||||
<string name="width">Largura</string>
|
||||
<string name="setBarcodeWidth">Definir a largura do código de barras</string>
|
||||
<string name="card_list_widget_name">Lista de cartões</string>
|
||||
<string name="card_list_widget_empty">Após adicionar cartões de fidelidade em Catima, eles aparecerão aqui. Se tem cartões, certifique-se de que não estão todos arquivados.</string>
|
||||
<string name="cardWithNumber">Cartão <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Cartão <xliff:g>%d</xliff:g> (%s)</string>
|
||||
<string name="cardWithNumberAndLocale">Cartão <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Não gire o dispositivo, pois cancelará a ação</string>
|
||||
<string name="acra_catima_has_crashed">Lamentamos, mas o <xliff:g id="app_name">%s</xliff:g> travou. Ajude-nos a corrigir este problema a enviar um relatório de erro.</string>
|
||||
<string name="acra_explain_crash">Se possível, acrescente detalhes sobre o que fazia aqui:</string>
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> relatório de travamento</string>
|
||||
<string name="pref_enable_acra">Solicitar o envio de relatórios de falhas</string>
|
||||
<string name="pref_enable_acra_summary">Quando ativado, relatar uma falha será solicitado quando isto ocorrer. Os relatórios de falhas nunca são enviados automaticamente.</string>
|
||||
</resources>
|
||||
|
||||
@@ -303,5 +303,11 @@
|
||||
<string name="card_list_widget_name">Lista de cartões</string>
|
||||
<string name="card_list_widget_empty">Depois que você adicionar alguns cartões de fidelidade no Catima, eles aparecerão aqui. Se você tiver cartões, verifique se eles não estão todos arquivados.</string>
|
||||
<string name="cardWithNumber">Cartão <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Cartão <xliff:g>%d</xliff:g> (%s)</string>
|
||||
<string name="cardWithNumberAndLocale">Cartão <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Por favor, não gire o dispositivo, pois cancelará a ação</string>
|
||||
<string name="acra_catima_has_crashed">Lamentamos, mas o <xliff:g id="app_name">%s</xliff:g> travou. Ajude-nos a corrigir este problema a enviar um relatório de erro.</string>
|
||||
<string name="acra_explain_crash">Se possível, acrescente detalhes sobre o que fazia aqui:</string>
|
||||
<string name="acra_crash_email_subject">Relatório de falha em <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Solicitar o envio de relatórios de falhas</string>
|
||||
<string name="pref_enable_acra_summary">Quando ativado, relatar uma falha será solicitado quando isto ocorrer. Os relatórios de falhas nunca são enviados automaticamente.</string>
|
||||
</resources>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<string name="note">Notă</string>
|
||||
<string name="storeName">Numele</string>
|
||||
<string name="noMatchingGiftCards">Nu au fost găsite rezultate. Încercați să schimbați termenii de căutare.</string>
|
||||
<string name="noGiftCards">Faceți clic pe butonul + plus pentru a adăuga o carte sau importați mai întâi una din meniul ⋮.</string>
|
||||
<string name="noGiftCards">Faceți clic pe butonul + plus pentru a adăuga o carte sau mai întâi importați una din meniu.</string>
|
||||
<string name="action_add">Adăugați</string>
|
||||
<string name="action_search">Căutare</string>
|
||||
<string name="sendLabel">Trimiteți…</string>
|
||||
@@ -88,7 +88,7 @@
|
||||
<string name="passwordRequired">Vă rugăm, introduceți parola</string>
|
||||
<string name="unsupportedBarcodeType">Acest tip de cod de bare nu poate fi afișat. Este posibil ca acesta să se poată afișa într-o versiune mai nouă a aplicației.</string>
|
||||
<string name="photos">Imagini</string>
|
||||
<string name="noGiftCardsGroup">Adăugați câteva carduri, iar apoi atribuiți-le grupului aici.</string>
|
||||
<string name="noGiftCardsGroup">Creați câteva carduri, iar apoi atribuiți-le grupului de aici.</string>
|
||||
<string name="importCatima">Importați din Catima</string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">Aș dori să partajez niște carduri cu tine</string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Drepturi de autor © 2019–<xliff:g>%d</xliff:g> Sylvia van Os și contribuitorii</string>
|
||||
|
||||
@@ -311,4 +311,13 @@
|
||||
<string name="card_list_widget_empty">После добавления карт лояльности в Catima, они появятся здесь. Если у вас есть карты, убедитесь, что они не архивированы.</string>
|
||||
<string name="cardWithNumber">Карта <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Карта <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Не поворачивайте устройство, так как это отменит действие</string>
|
||||
<string name="acra_catima_has_crashed">К сожалению, в <xliff:g id="app_name">%s</xliff:g> произошёл сбой. Помогите исправить проблему, отправив нам отчёт об ошибке.</string>
|
||||
<string name="acra_explain_crash">Если возможно, добавьте больше информации о том, что произошло:</string>
|
||||
<string name="acra_crash_email_subject">Отчёт об ошибке в <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Запрашивать отправку отчётов об ошибках</string>
|
||||
<string name="pref_enable_acra_summary">Если включено, то в случае сбоя вам будет предложено отправить отчёт о нём. Отчёты никогда не отправляются автоматически.</string>
|
||||
<string name="copy_value">Скопировать значение</string>
|
||||
<string name="copied_to_clipboard">Скопировано в буфер обмена</string>
|
||||
<string name="nothing_to_copy">Значение не найдено</string>
|
||||
</resources>
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
<string name="cardShortcut">Skratka karty</string>
|
||||
<string name="noCardsMessage">Najprv pridajte kartu</string>
|
||||
<string name="noCardExistsError">Nepodarilo sa nájsť túto kartu</string>
|
||||
<string name="importExport">Import/Export</string>
|
||||
<string name="importExport">Import/export</string>
|
||||
<string name="exportName">Export</string>
|
||||
<string name="importExportHelp">Zálohovanie vašich údajov umožňuje ich presun na iné zariadenie.</string>
|
||||
<string name="importExportHelp">Zálohovanie vašich údajov umožňuje ich presun na iné zariadenie</string>
|
||||
<string name="importSuccessfulTitle">Úspešne importované</string>
|
||||
<string name="importFailedTitle">Import zlyhal</string>
|
||||
<string name="importFailed">Nemožno vykonať import</string>
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="importing">Importujem…</string>
|
||||
<string name="exporting">Exportujem…</string>
|
||||
<string name="importOptionFilesystemTitle">Import zo súborového systému</string>
|
||||
<string name="importOptionFilesystemExplanation">Vyberte súbor zo súborového systému.</string>
|
||||
<string name="importOptionFilesystemExplanation">Vyberte uložený súbor</string>
|
||||
<string name="importOptionFilesystemButton">Zo súborového systému</string>
|
||||
<string name="about">O aplikácii</string>
|
||||
<string name="app_license">Slobodný softvér s copyleft licenciou GPLv3+</string>
|
||||
@@ -62,11 +62,11 @@
|
||||
<string name="leaveWithoutSaveTitle">Ukončiť</string>
|
||||
<string name="moveDown">Pohyb smerom nadol</string>
|
||||
<string name="moveUp">Pohyb smerom nahor</string>
|
||||
<string name="failedOpeningFileManager">Najprv nainštalujte správcu súborov.</string>
|
||||
<string name="failedOpeningFileManager">Nepodarilo sa otvoriť správcu súborov</string>
|
||||
<string name="deleteConfirmationGroup">Vymazať skupinu\?</string>
|
||||
<string name="all">Všetky</string>
|
||||
<string name="noGroupCards">Táto skupina je prázdna</string>
|
||||
<string name="noGroups">Kliknutím na tlačidlo + plus pridáte skupiny na kategorizáciu.</string>
|
||||
<string name="noGroups">Kliknutím na tlačidlo + (plus) pridáte skupiny na kategorizáciu</string>
|
||||
<string name="groups">Skupiny</string>
|
||||
<string name="enter_group_name">Zadajte názov skupiny</string>
|
||||
<string name="exportSuccessful">Údaje exportované</string>
|
||||
@@ -80,7 +80,7 @@
|
||||
<string name="settings_system_theme">Podľa nastavení systému</string>
|
||||
<string name="settings_theme">Téma</string>
|
||||
<string name="starImage">Obľúbená hviezda</string>
|
||||
<string name="exportOptionExplanation">Údaje sa zapíšu na vami zvolené miesto.</string>
|
||||
<string name="exportOptionExplanation">Údaje budú uložené na vami zvolené miesto</string>
|
||||
<string name="failedParsingImportUriError">Nepodarilo sa analyzovať import URI</string>
|
||||
<string name="share">Zdieľať</string>
|
||||
<string name="barcodeImageDescriptionWithType">Obrázok čiarového kódu <xliff:g>%s</xliff:g></string>
|
||||
@@ -115,10 +115,9 @@
|
||||
<string name="balanceSentence">Zostatok: <xliff:g>%s</xliff:g></string>
|
||||
<string name="importCatima">Import z aplikácie Catima</string>
|
||||
<string name="settings_theme_color">Farba témy</string>
|
||||
<string name="app_libraries">Slobodné knižnice tretích strán: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Slobodné zdroje tretích strán: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="importCatimaMessage">Vyberte svoj <i>catima.zip</i> export z aplikácie Catima, ktorý chcete importovať.
|
||||
\nVytvorte ho z ponuky Import/Export inej aplikácie Catima tak, že stlačíte tlačidlo Exportovať.</string>
|
||||
<string name="app_libraries">Knižnice tretích strán: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Zdroje tretích strán: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="importCatimaMessage">Vyberte svoj export z aplikácie Catima, ktorý chcete importovať. \nVytvorte ho z ponuky Import/export inej aplikácie Catima tak, že stlačíte tlačidlo Exportovať.</string>
|
||||
<string name="accept">Prijať</string>
|
||||
<string name="importLoyaltyCardKeychain">Import z aplikácie Loyalty Card Keychain</string>
|
||||
<string name="importFidme">Import z aplikácie FidMe</string>
|
||||
@@ -154,7 +153,7 @@
|
||||
<string name="rate_this_app">Ohodnoťte túto aplikáciu</string>
|
||||
<string name="exportPassword">Nastavte heslo na ochranu exportu (voliteľné)</string>
|
||||
<string name="exportPasswordHint">Zadajte heslo</string>
|
||||
<string name="failedGeneratingShareURL">Nepodarilo sa vygenerovať zdieľateľnú adresu URL. Nahláste to, prosím.</string>
|
||||
<string name="failedGeneratingShareURL">Nepodarilo sa vygenerovať zdieľateľnú adresu URL</string>
|
||||
<string name="turn_flashlight_off">Vypnúť svetlo</string>
|
||||
<string name="settings_locale">Jazyk</string>
|
||||
<string name="settings_system_locale">Systém</string>
|
||||
@@ -179,7 +178,7 @@
|
||||
<string name="updateBarcodeQuestionTitle">Aktualizovať hodnotu čiarového kódu\?</string>
|
||||
<string name="updateBarcodeQuestionText">Zmenili ste ID. Chcete aktualizovať aj čiarový kód, aby používal rovnakú hodnotu\?</string>
|
||||
<string name="no">Nie</string>
|
||||
<string name="passwordRequired">Zadajte prosím heslo</string>
|
||||
<string name="passwordRequired">Zadajte heslo</string>
|
||||
<string name="noGiftCardsGroup">Zatiaľ nemáte žiadne vernostné karty. Keď nejaké pridáte, môžete ich priradiť ku skupine tu</string>
|
||||
<string name="noCameraPermissionDirectToSystemSetting">Na skenovanie čiarových kódov potrebuje Catima prístup k fotoaparátu. Ťuknite sem a zmeňte nastavenia oprávnení.</string>
|
||||
<string name="importCards">Importovať karty</string>
|
||||
@@ -211,10 +210,8 @@
|
||||
<string name="chooseValidFromDate">Zvoliť dátum platné od</string>
|
||||
<string name="validFromSentence">Platnosť od: <xliff:g>%s</xliff:g></string>
|
||||
<string name="cameraPermissionRequired">Pre túto akciu je potrebné oprávnenie na prístup k fotoaparátu…</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Vyberte svoj export <i>LoyaltyCardKeychain.csv</i> z Kľúčenky vernostných kariet, ktorý chcete importovať.
|
||||
\nVytvorte ho z ponuky Import/Export v aplikácii Loyalty Card Keychain tak, že tam najprv stlačíte tlačidlo Exportovať.</string>
|
||||
<string name="importVoucherVaultMessage">Vyberte svoj <i>vouchervault.json</i> export z Trezoru poukážok pre import.
|
||||
\nNajprv ho vytvorte stlačením tlačidla Export v aplikácii Voucher Vault.</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Vyberte svoj export z aplikácie Loyalty Card Keychain, ktorý chcete importovať. \nVytvorte ho z ponuky Import/Export v aplikácii Loyalty Card Keychain tak, že tam stlačíte tlačidlo Exportovať.</string>
|
||||
<string name="importVoucherVaultMessage">Vyberte svoj export z aplikácie Voucher Vault pre import.\nNajprv ho vytvorte stlačením tlačidla Export v aplikácii Voucher Vault.</string>
|
||||
<string name="shortcutSelectCard">Vybrať kartu</string>
|
||||
<string name="include_if_asking_support">Ak chcete požiadať o podporu, uveďte nasledujúce informácie:</string>
|
||||
<plurals name="groupCardCountWithArchived">
|
||||
@@ -225,13 +222,12 @@
|
||||
<string name="barcodeLongPressMessage">V aplikácii galéria je možné otvoriť iba obrázky</string>
|
||||
<string name="cameraPermissionDeniedTitle">Nepodarilo sa získať prístup k fotoaparátu</string>
|
||||
<string name="storageReadPermissionRequired">Pre túto akciu je potrebné oprávnenie na čítanie úložiska…</string>
|
||||
<string name="importFidmeMessage">Vyberte svoj <i>fidme-export-request-xxxxxx.zip</i> export zo služby FidMe pre import a potom vyberte typy čiarových kódov ručne.
|
||||
\nVytvorte ho z profilu FidMe tak, že najprv vyberiete položku Ochrana údajov a potom stlačíte tlačidlo Extrahovať moje údaje.</string>
|
||||
<string name="importFidmeMessage">Vyberte svoj export zo služby FidMe pre import a potom vyberte typy čiarových kódov ručne.\nVytvorte ho z profilu FidMe tak, že vyberiete položku Ochrana údajov a potom stlačíte tlačidlo Extrahovať moje údaje.</string>
|
||||
<string name="currentBalanceSentence">Aktuálny zostatok: <xliff:g>%s</xliff:g></string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">Chcem sa s vami zdielať karty</string>
|
||||
<string name="app_contributors">Podporili: <xliff:g id="app_contributors">%s</xliff:g></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>
|
||||
<string name="failedLaunchingPhotoPicker">Nepodarilo sa nájsť podporovanú aplikáciu pre výber obrázkov</string>
|
||||
<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>
|
||||
@@ -239,13 +235,13 @@
|
||||
<string name="settings_keep_screen_on_summary">Ponechať obrazovku aktívnu počas prezerania karty</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Pre zaistenie čitateľnosti pre niektoré skenery</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 zadné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="openBackImageInGalleryApp">Otvorenie zadného obrázka v prehliadači obrázkov</string>
|
||||
<string name="openFrontImageInGalleryApp">Otvorenie predného obrázka v prehliadači obrázkov</string>
|
||||
<string name="setBarcodeHeight">Nastavenie výšky čiarového kódu</string>
|
||||
<string name="show_balance">Ukážte zostatok</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="permissionReadCardsLabel">Čítanie kariet Catima</string>
|
||||
<string name="permissionReadCardsDescription">čítať svoje Catima karty a všetky jeho podrobnosti, vrátane poznámky a obrázkov</string>
|
||||
<string name="switchToBackImage">Prepnutie na zadný obrázok</string>
|
||||
<string name="height">Výška</string>
|
||||
@@ -275,7 +271,7 @@
|
||||
<string name="receive">Prijaté</string>
|
||||
<string name="amountParsingFailed">Neplatná hodnota</string>
|
||||
<string name="add_manually_warning_title">Skenovanie je odporúčané</string>
|
||||
<string name="add_manually_warning_message">V niektorých obchodoch sa hodnota čiarového kódu líši od čísla uvedeného na karte. Z tohto dôvodu nemusí manuálne zadanie čiarového kódu vždy fungovať. Dôrazne odporúčame naskenovať čiarový kód pomocou fotoaparátu. Chcete napriek tomu pokračovať?</string>
|
||||
<string name="add_manually_warning_message">Pri niektorých kartách sa hodnota čiarového kódu líši od čísla uvedeného na karte. Z tohto dôvodu nemusí manuálne zadanie čiarového kódu vždy fungovať. Odporúčame naskenovať čiarový kód pomocou fotoaparátu. Chcete napriek tomu pokračovať?</string>
|
||||
<string name="addFromPdfFile">Vyberte súbor PDF</string>
|
||||
<string name="errorReadingFile">Súbor sa nepodarilo prečítať</string>
|
||||
<string name="failedLaunchingFileManager">Nepodarilo sa nájsť podporovaného správcu súborov</string>
|
||||
@@ -286,7 +282,7 @@
|
||||
<string name="exportCancelled">Export zrušený</string>
|
||||
<string name="useFrontImage">Použiť obrázok prednej strany</string>
|
||||
<string name="useBackImage">Použiť obrázok zadnej strany</string>
|
||||
<string name="generic_error_please_retry">Prepáčte, niečo sa pokazilo, skúste to znova...</string>
|
||||
<string name="generic_error_please_retry">Niečo sa pokazilo</string>
|
||||
<string name="settings_category_title_cards_overview">Prehľad kariet</string>
|
||||
<string name="width">Šírka</string>
|
||||
<string name="setBarcodeWidth">Nastaviť šírku čiarového kódu</string>
|
||||
@@ -308,5 +304,14 @@
|
||||
<string name="card_list_widget_name">Zoznam kariet</string>
|
||||
<string name="card_list_widget_empty">Po pridaní vernostných kariet do Catima sa zobrazia tu. Ak máte karty, uistite sa, že nie sú všetky archivované.</string>
|
||||
<string name="cardWithNumber">Karta <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Karta <xliff:g>%d</xliff:g> (%s)</string>
|
||||
<string name="cardWithNumberAndLocale">Karta <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Neotáčajte zariadenie, inak akciu prerušíte</string>
|
||||
<string name="acra_catima_has_crashed">Ospravedlňujeme sa, aplikácia <xliff:g id="app_name">%s</xliff:g> spadla. Pomôžte nám tento problém opraviť zaslaním hlásenia o chybe.</string>
|
||||
<string name="acra_explain_crash">Ak je to možné, uveďte viac podrobností o tom, čo ste práve robili:</string>
|
||||
<string name="acra_crash_email_subject">Hlásenie o páde aplikácie <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Poskytovať možnosť zasielať hlásenia o chybách</string>
|
||||
<string name="pref_enable_acra_summary">Keď túto možnosť zapnete, pri páde aplikácie vás požiadame o zaslanie hlásenia o chybe. Tie sa nikdy nezasielajú automaticky.</string>
|
||||
<string name="copy_value">Kopírovať hodnotu</string>
|
||||
<string name="copied_to_clipboard">Skopírované do schránky</string>
|
||||
<string name="nothing_to_copy">Nenašla sa žiadna hodnota</string>
|
||||
</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">Dodaj</string>
|
||||
<string name="noGiftCards">Pritisni gumb + za dodajanje nove kartice ali gumb ⋮ v meniju za uvoz.</string>
|
||||
<string name="noGiftCards">Pritisni gumb + za dodajanje nove kartice ali gumb ⋮ v meniju za uvoz</string>
|
||||
<string name="storeName">Ime</string>
|
||||
<string name="note">Opomba</string>
|
||||
<string name="cardId">Št. kartice</string>
|
||||
@@ -18,9 +18,9 @@
|
||||
<string name="cardShortcut">Bližnjica do kartice</string>
|
||||
<string name="noCardsMessage">Najprej dodaj kartico</string>
|
||||
<string name="noCardExistsError">Te kartice ni bilo mogoče najti</string>
|
||||
<string name="importExport">Uvozi/Izvozi</string>
|
||||
<string name="importExport">Uvozi/izvozi</string>
|
||||
<string name="exportName">Izvozi</string>
|
||||
<string name="importExportHelp">Varnostna kopija podatkovne baze omogoča prenos na drugo napravo.</string>
|
||||
<string name="importExportHelp">Varnostna kopija podatkovne baze omogoča prenos na drugo napravo</string>
|
||||
<string name="importSuccessfulTitle">Uvoz je bil uspešen</string>
|
||||
<string name="importFailedTitle">Uvoz ni uspel</string>
|
||||
<string name="importFailed">Napaka pri uvozu</string>
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="importing">Uvažanje …</string>
|
||||
<string name="exporting">Izvažanje …</string>
|
||||
<string name="importOptionFilesystemTitle">Uvozi iz datotečnega sistema</string>
|
||||
<string name="importOptionFilesystemExplanation">Izberi specifično datoteko iz datotečnega sistema.</string>
|
||||
<string name="importOptionFilesystemExplanation">Izberi specifično datoteko iz datotečnega sistema</string>
|
||||
<string name="importOptionFilesystemButton">Iz datotečnega sistema</string>
|
||||
<string name="about">Več o aplikaciji</string>
|
||||
<string name="app_license">Prosta programska oprema s copyleftom, licenca GPL3+</string>
|
||||
@@ -49,11 +49,11 @@
|
||||
<string name="leaveWithoutSaveTitle">Izhod</string>
|
||||
<string name="moveDown">Premikanje navzdol</string>
|
||||
<string name="moveUp">Premik navzgor</string>
|
||||
<string name="failedOpeningFileManager">Najprej namesti upravitelja datotek.</string>
|
||||
<string name="failedOpeningFileManager">Ni mogoče odpreti upravitelja datotek</string>
|
||||
<string name="deleteConfirmationGroup">Brisanje skupine\?</string>
|
||||
<string name="all">Vse</string>
|
||||
<string name="noGroupCards">Ta skupina je prazna</string>
|
||||
<string name="noGroups">Pritisni gumb +, če želiš dodati skupine za kategorizacijo.</string>
|
||||
<string name="noGroups">Pritisni gumb +, če želiš dodati skupine za kategorizacijo</string>
|
||||
<string name="groups">Skupine</string>
|
||||
<string name="enter_group_name">Vnesi ime skupine</string>
|
||||
<string name="exportSuccessful">Podatkovna baza izvožena</string>
|
||||
@@ -67,7 +67,7 @@
|
||||
<string name="settings_theme">Tema</string>
|
||||
<string name="starImage">Zvezdica za priljubljene</string>
|
||||
<string name="app_copyright_old">Na podlagi aplikacije Loyalty Card Keychain \navtorske pravice © 2016-2020 Branden Archer</string>
|
||||
<string name="exportOptionExplanation">Podatki bodo zapisani na izbrano mesto.</string>
|
||||
<string name="exportOptionExplanation">Podatki bodo zapisani na izbrano mesto</string>
|
||||
<string name="failedParsingImportUriError">Ni bilo mogoče razčleniti URI uvoza</string>
|
||||
<string name="share">Deli</string>
|
||||
<string name="unstar">Odstrani iz priljubljenih</string>
|
||||
@@ -171,7 +171,7 @@
|
||||
<string name="unarchive">Odpakiraj arhiv</string>
|
||||
<string name="archived">Kartica arhivirana</string>
|
||||
<string name="unarchived">Kartica ni arhivirana</string>
|
||||
<string name="failedLaunchingPhotoPicker">Ni mogoče najti podprte aplikacije za gledanje slik</string>
|
||||
<string name="failedLaunchingPhotoPicker">Ni mogoče najti podprte aplikacije za slike</string>
|
||||
<string name="previousCard">Prejšnja</string>
|
||||
<string name="nextCard">Naslednja</string>
|
||||
<string name="updateBalanceTitle">Koliko si porabil ali prejel?</string>
|
||||
@@ -180,18 +180,18 @@
|
||||
<string name="group_name_is_empty">Ime skupine ne sme biti prazno</string>
|
||||
<string name="group_updated">Skupina posodobljena</string>
|
||||
<string name="groupsList">Skupine: <xliff:g>%s</xliff:g></string>
|
||||
<string name="app_libraries">Proste knjižnice tretjih oseb: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Prosti viri tretjih oseb: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Knjižnice tretjih oseb: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Viri tretjih oseb: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="expiryStateSentence">Poteče: <xliff:g>%s</xliff:g></string>
|
||||
<string name="expiryStateSentenceExpired">Poteklo: <xliff:g>%s</xliff:g></string>
|
||||
<string name="expiryDate">Datum poteka veljavnosti</string>
|
||||
<string name="chooseExpiryDate">Izberi datum poteka veljavnosti</string>
|
||||
<string name="moveBarcodeToTopOfScreen">Premakni črtno kodo na vrh zaslona</string>
|
||||
<string name="importCatimaMessage">Izberi svoj obstoječ Catima <i>catima.zip</i> izvoz podatkov za uvoz v aplikacijo. \nNajprej izvozi podatke v meniju \"Uvozi/Izvozi\" v drugi aplikaciji Catima s pritiskom na Izvozi.</string>
|
||||
<string name="importVoucherVaultMessage">Izberi svoj <i>vouchervault.json</i> Voucher Vault izvoz podatkov za uvoz. \nIzvoz podatkov dobiš s pritiskom na gumb »Export« v Voucher Vault first.</string>
|
||||
<string name="importCatimaMessage">Izberi svoj obstoječ izvoz podatkov za uvoz v aplikacijo. \nNajprej izvozi podatke v meniju Uvozi/izvozi v drugi aplikaciji Catima s pritiskom na Izvozi.</string>
|
||||
<string name="importVoucherVaultMessage">Izberi svoj Voucher Vault izvoz podatkov za uvoz. \nIzvoz podatkov dobiš s pritiskom na gumb »Export« v Voucher Vault.</string>
|
||||
<string name="failedToOpenUrl">Prvo namesti spletni brskalnik</string>
|
||||
<string name="welcome">Pozdravljen v Catimi</string>
|
||||
<string name="noGiftCardsGroup">Ustvari kartice in jih dodeli tej skupini.</string>
|
||||
<string name="noGiftCardsGroup">Ustvari kartice in jih dodeli tej skupini</string>
|
||||
<plurals name="deleteCardsTitle">
|
||||
<item quantity="one">Izbriši <xliff:g>%d</xliff:g> kartico</item>
|
||||
<item quantity="two">Izbriši <xliff:g>%d</xliff:g> kartici</item>
|
||||
@@ -213,9 +213,9 @@
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> kartic</item>
|
||||
</plurals>
|
||||
<string name="editGroup">Urejanje skupine: <xliff:g>%s</xliff:g></string>
|
||||
<string name="importFidmeMessage">Izberi svoj <i>fidme-export-request-xxxxxx.zip</i> FidMe izvoz podatkov za uvoz in naknadno ročno izberi tipe črtnih kod. \nFidMe izvoz podatkov naredi v svojem FidMe profilu z izbiro »Data Protection« in nato s pritiskom na gumb »Extract my data first«.</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Izberi svoj <i>LoyaltyCardKeychain.csv</i> Kartice zvestobe izvoz podatkov za uvoz. \nKartice zvestobe izvoz podatkov naredi s pritiskom na gumb »Import/Export« v meniju s pritiskom najprej na gumb »Export«.</string>
|
||||
<string name="failedGeneratingShareURL">URL-ja za skupno rabo ni bilo mogoče ustvariti. Prosim prijavi napako.</string>
|
||||
<string name="importFidmeMessage">Izberi svoj izvoz iz FindMe za uvoz in naknadno ročno izberi tipe črtnih kod. \nFidMe izvoz podatkov naredi v svojem FidMe profilu z izbiro »Data Protection« in nato s pritiskom na gumb »Extract my data«.</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Izberi svoj izvoz iz Kartice zvestobe za uvoz. \nKartice zvestobe izvoz podatkov naredi s pritiskom na gumb »Uvoz/izvoz« v meniju s pritiskom na gumb »Export«.</string>
|
||||
<string name="failedGeneratingShareURL">URL-ja za skupno rabo ni bilo mogoče ustvariti</string>
|
||||
<string name="settings_oled_dark">Čisto črno ozadje za temno temo</string>
|
||||
<string name="selectColor">Izberi barvo</string>
|
||||
<string name="settings_catima_theme">Catima</string>
|
||||
@@ -276,7 +276,7 @@
|
||||
<string name="field_must_not_be_empty">Polje ne sme biti prazno</string>
|
||||
<string name="manually_enter_barcode_instructions">Vnesi identifikacijsko številko ali besedilo na kartici in pritisni črtno kodo, ki je podobna tisti na kartici.</string>
|
||||
<string name="add_manually_warning_title">Priporočljivo je skeniranje</string>
|
||||
<string name="add_manually_warning_message">V nekaterih trgovinah se vrednost črtne kode razlikuje od številke, napisane na kartici. Zaradi tega ročno vnašanje črtne kode morda ne bo vedno delovalo. Priporočamo, da črtno kodo raje skeniraš s kamero. Želiš nadaljevati?</string>
|
||||
<string name="add_manually_warning_message">V nekatere kartice se vrednost črtne kode razlikuje od številke, napisane na kartici. Zaradi tega ročno vnašanje črtne kode morda ne bo vedno delovalo. Priporočamo, da črtno kodo raje skeniraš s kamero. Želiš nadaljevati?</string>
|
||||
<string name="continue_">Nadaljuj</string>
|
||||
<string name="spend">Porabi</string>
|
||||
<string name="receive">Prejmi</string>
|
||||
@@ -296,16 +296,27 @@
|
||||
<string name="switchToFrontImage">Preklopi na prednjo sliko</string>
|
||||
<string name="switchToBackImage">Preklopi na zadnjo sliko</string>
|
||||
<string name="switchToBarcode">Preklopi na črtno kodo</string>
|
||||
<string name="openFrontImageInGalleryApp">Odpri sprednjo sliko v galeriji</string>
|
||||
<string name="openBackImageInGalleryApp">Odpri zadnjo sliko v galeriji</string>
|
||||
<string name="openFrontImageInGalleryApp">Odpri sprednjo sliko v aplikaciji za slike</string>
|
||||
<string name="openBackImageInGalleryApp">Odpri zadnjo sliko v aplikaciji za slike</string>
|
||||
<string name="setBarcodeHeight">Nastavi višino črtne kode</string>
|
||||
<string name="useFrontImage">Uporabi prednjo sliko</string>
|
||||
<string name="useBackImage">Uporabi zadnjo sliko</string>
|
||||
<string name="addFromPkpass">Izberi Passbook datoteko (.pkpass)</string>
|
||||
<string name="addFromPkpass">Izberi Passbook datoteko (.pkpass / .pkpasses)</string>
|
||||
<string name="unsupportedFile">Ta datoteka ni podprta</string>
|
||||
<string name="generic_error_please_retry">Žal se je pojavila napaka, poskusi znova …</string>
|
||||
<string name="generic_error_please_retry">Prišlo je do napake</string>
|
||||
<string name="width">Širina</string>
|
||||
<string name="card_list_widget_name">Seznam kartic</string>
|
||||
<string name="setBarcodeWidth">Nastavi širino črtne kode</string>
|
||||
<string name="card_list_widget_empty">Ko v Catimi dodaš nekaj kartic zvestobe, se bodo te prikazale tukaj. Če imaš kartice, se prepričaj, da niso vse arhivirane.</string>
|
||||
<string name="cardWithNumber">Kartica <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Kartica <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Naprave ne obračaj, saj bo to prekinilo postopek</string>
|
||||
<string name="acra_catima_has_crashed">Žal nam je, vendar se je aplikacija <xliff:g id="app_name">%s</xliff:g> sesula. Pomagaj nam odpraviti to težavo tako, da nam pošlješ poročilo o napaki.</string>
|
||||
<string name="acra_explain_crash">Če je mogoče, dodaj več podrobnosti o tem, kaj si tukaj počel/a:</string>
|
||||
<string name="acra_crash_email_subject">Poročilo o sesutju aplikacije <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Prosi za pošiljanje poročila o sesutju</string>
|
||||
<string name="pref_enable_acra_summary">Ko je ta možnost omogočena, boš ob pojavu sesutja pozvan, da prijaviš napako. Poročilo o sesutju se nikoli ne pošilja samodejno.</string>
|
||||
<string name="copy_value">Kopiraj vrednost</string>
|
||||
<string name="copied_to_clipboard">Kopirano v odložišče</string>
|
||||
<string name="nothing_to_copy">Nobena vrednost ni najdena</string>
|
||||
</resources>
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> valt</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> valda</item>
|
||||
</plurals>
|
||||
<string name="app_loyalty_card_keychain">Nyckelring för bonuskort</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Välj den exporterade från Loyalty Card Keychain som du vill importera.\nSkapa den från Import/Export-menyn i Loyalty Card Keychain genom att trycka på Exportera.</string>
|
||||
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Välj det exporterade från Loyalty Card Keychain som du vill importera.\nSkapa det från Import/Export-menyn i Loyalty Card Keychain genom att trycka på Exportera.</string>
|
||||
<string name="importVoucherVaultMessage">Välj den exporterade från Voucher Vault som du vill importera. \nSkapa den genom att trycka på Exportera i Voucher Vault.</string>
|
||||
<string name="enter_group_name">Ange gruppnamn</string>
|
||||
<string name="groups">Grupper</string>
|
||||
@@ -24,7 +24,7 @@
|
||||
</plurals>
|
||||
<string name="all">Alla</string>
|
||||
<string name="deleteConfirmationGroup">Ta bort grupp\?</string>
|
||||
<string name="failedOpeningFileManager">Installera en filhanterare först.</string>
|
||||
<string name="failedOpeningFileManager">Kunde ej öppna en filhanterare</string>
|
||||
<string name="moveUp">Flytta uppåt</string>
|
||||
<string name="moveDown">Flytta nedåt</string>
|
||||
<string name="leaveWithoutSaveTitle">Avsluta</string>
|
||||
@@ -192,7 +192,7 @@
|
||||
</plurals>
|
||||
<string name="include_if_asking_support">Om du vill be om hjälp, inkludera då följande information:</string>
|
||||
<string name="settings_oled_dark">Helsvart bakgrund för mörkt tema</string>
|
||||
<string name="failedLaunchingPhotoPicker">Kunde inte hitta kompatibelt bildprogram</string>
|
||||
<string name="failedLaunchingPhotoPicker">Kunde ej hitta kompatibel bildväljare</string>
|
||||
<string name="unarchive">Ta tillbaks från arkiv</string>
|
||||
<string name="archived">Kort arkiverat</string>
|
||||
<string name="duplicateCard">Kopiera</string>
|
||||
@@ -215,14 +215,14 @@
|
||||
<string name="storageReadPermissionRequired">Tillstånd att läsa lagring behövs för denna åtgärd…</string>
|
||||
<string name="currentBalanceSentence">Nuvarande balans: <xliff:g>%s</xliff:g></string>
|
||||
<string name="validFromDate">Giltig från</string>
|
||||
<string name="cameraPermissionRequired">Tillstånd att komma åt kameran krävs för denna åtgärd…</string>
|
||||
<string name="cameraPermissionRequired">Behörighet att komma åt kameran krävs för denna åtgärd…</string>
|
||||
<string name="updateBalance">Uppdatera balans</string>
|
||||
<string name="failedToRetrieveImageFile">Misslyckades att hämta bildfil</string>
|
||||
<string name="barcodeLongPressMessage">Endast bilder kan öppnas i galleri app</string>
|
||||
<string name="barcodeLongPressMessage">Endast bilder kan öppnas i galleriappen</string>
|
||||
<string name="updateBalanceTitle">Hur mycket spenderade du eller fick du?</string>
|
||||
<string name="updateBalanceHint">Ange summa</string>
|
||||
<string name="newBalanceSentence">Ny balans: <xliff:g>%s</xliff:g></string>
|
||||
<string name="openFrontImageInGalleryApp">Öppna bilden på framsidan i galleri-appen</string>
|
||||
<string name="openFrontImageInGalleryApp">Öppna bilden på framsidan i bildvisningsappen</string>
|
||||
<string name="show_name_below_image_thumbnail">Visa namnet nedanför bildens miniatyr</string>
|
||||
<string name="show_validity">Visa giltighet</string>
|
||||
<string name="view_online">Visa på internet</string>
|
||||
@@ -230,7 +230,7 @@
|
||||
<string name="settings_category_title_general">Generellt</string>
|
||||
<string name="switchToBarcode">Byt till streckkod</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Stänger av skärmlåset medans kort visas</string>
|
||||
<string name="permissionReadCardsDescription">Se dina kort med alla dess detaljer, inklusive anteckningar och bilder</string>
|
||||
<string name="permissionReadCardsDescription">läsa dina Catima-kort med alla dess detaljer, inklusive anteckningar och bilder</string>
|
||||
<string name="action_display_options">Visningsalternativ</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Nödvändigt för att en del skannrar ska fungera</string>
|
||||
<string name="settings_oled_dark_summary">Reducerar batterianvändning på OLED-skärmar</string>
|
||||
@@ -242,10 +242,10 @@
|
||||
<string name="switchToFrontImage">Byt till bilden på framsidan</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Appar måste fortfarande begära åtkomst för att få tillgång</string>
|
||||
<string name="setBarcodeHeight">Ställ in streckkodens höjd</string>
|
||||
<string name="openBackImageInGalleryApp">Öppna bilden på baksidan i galleri-appen</string>
|
||||
<string name="openBackImageInGalleryApp">Öppna bilden på baksidan i bildvisningsappen</string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Kopieringsskydd © 2019–<xliff:g>%d</xliff:g> Sylvia van Os och medverkande</string>
|
||||
<string name="settings_allow_content_provider_read_title">Tillåt andra appar att komma åt min data</string>
|
||||
<string name="permissionReadCardsLabel">Läs Catima-kort</string>
|
||||
<string name="permissionReadCardsLabel">Läsa Catima-kort</string>
|
||||
<string name="donate">Donera</string>
|
||||
<string name="show_archived_cards">Visa arkiverade kort</string>
|
||||
<string name="settings_category_title_privacy">Sekretess</string>
|
||||
@@ -296,4 +296,16 @@
|
||||
<string name="generic_error_please_retry">Ett fel uppstod</string>
|
||||
<string name="cardWithNumber">Kort <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Kort <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="setBarcodeWidth">Ange streckkodsbredd</string>
|
||||
<string name="card_list_widget_empty">Bonuskort som du lägger till i Catima kommer att dyka upp här. Om du redan har kort, kontrollera att de alla inte är arkiverade.</string>
|
||||
<string name="add_manually_warning_message">För vissa kort skiljer sig streckkodsvärdet från numret som är skrivet på kortet. På grund av detta kan det ibland ej vara möjligt att ange en streckkod manuellt. Det rekommenderas att istället skanna streckkoden med din kamera. Vill du ändå fortsätta?</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Var vänlig rotera ej enheten, då detta kommer att avbryta åtgärden</string>
|
||||
<string name="acra_catima_has_crashed">Vi beklagar, men <xliff:g id="app_name">%s</xliff:g> har kraschat. Vänligen hjälp oss att lösa detta problem genom att skicka en felrapport till oss.</string>
|
||||
<string name="acra_crash_email_subject">Kraschrapport för <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Be om att skicka kraschrapporter</string>
|
||||
<string name="pref_enable_acra_summary">När det är aktiverat kommer du att bli ombedd att rapportera en krasch när den inträffar. Kraschrapporter skickas aldrig automatiskt.</string>
|
||||
<string name="acra_explain_crash">Om möjligt, var vänlig lägg till fler detaljer om vad du höll på med här:</string>
|
||||
<string name="copy_value">Kopiera värde</string>
|
||||
<string name="copied_to_clipboard">Kopierade till urklipp</string>
|
||||
<string name="nothing_to_copy">Inget värde hittades</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> தேர்ந்தெடுக்கப்பட்டது</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> தேர்ந்தெடுக்கப்பட்டன</item>
|
||||
</plurals>
|
||||
<string name="noGiftCards">ஒரு அட்டையைச் சேர்க்க + பிளச் பொத்தானைக் சொடுக்கு செய்க அல்லது ⋮ மெனுவிலிருந்து இறக்குமதி செய்யுங்கள்.</string>
|
||||
<string name="noGiftCards">ஒரு அட்டையைச் சேர்க்க + சேர் பொத்தானைக் சொடுக்கு செய்க அல்லது ⋮ பட்டியலிருந்து இறக்குமதி செய்</string>
|
||||
<string name="noGiftCardsGroup">சில அட்டைகளை உருவாக்கி, பின்னர் அவற்றை இங்கே குழுவிற்கு ஒதுக்குங்கள்.</string>
|
||||
<string name="storeName">பெயர்</string>
|
||||
<string name="note">குறிப்பு</string>
|
||||
@@ -22,11 +22,11 @@
|
||||
<string name="share">பங்கு</string>
|
||||
<string name="sendLabel">அனுப்பு…</string>
|
||||
<string name="editCardTitle">அட்டையைத் திருத்து</string>
|
||||
<string name="addCardTitle">அட்டை சேர்க்கவும்</string>
|
||||
<string name="scanCardBarcode">ச்கேன் பார்கோடு</string>
|
||||
<string name="addCardTitle">அட்டை சேர்</string>
|
||||
<string name="scanCardBarcode">பட்டைகோடு வருடு</string>
|
||||
<string name="cardShortcut">அட்டை குறுக்குவழி</string>
|
||||
<string name="noCardExistsError">அந்த அட்டையை கண்டுபிடிக்க முடியவில்லை</string>
|
||||
<string name="failedParsingImportUriError">இறக்குமதி யூரியை அலச முடியவில்லை</string>
|
||||
<string name="noCardExistsError">அந்த அட்டையைக் கண்டுபிடிக்க முடியவில்லை</string>
|
||||
<string name="failedParsingImportUriError">இறக்குமதி முகவரியை அலச முடியவில்லை</string>
|
||||
<string name="importExport">இறக்குமதி/ஏற்றுமதி</string>
|
||||
<string name="exportName">ஏற்றுமதி</string>
|
||||
<string name="importFailedTitle">இறக்குமதி தோல்வியடைந்தது</string>
|
||||
@@ -49,7 +49,7 @@
|
||||
<string name="group_updated">குழு புதுப்பிக்கப்பட்டது</string>
|
||||
<string name="all">அனைத்தும்</string>
|
||||
<string name="deleteConfirmationGroup">குழுவை நீக்கவா?</string>
|
||||
<string name="failedOpeningFileManager">முதலில் கோப்பு மேலாளரை நிறுவவும்.</string>
|
||||
<string name="failedOpeningFileManager">கோப்பு மேலாளரைத் திறக்கமுடியவில்லை</string>
|
||||
<string name="leaveWithoutSaveTitle">வெளியேறு</string>
|
||||
<string name="leaveWithoutSaveConfirmation">சேமிக்காமல் விடலாமா?</string>
|
||||
<string name="addManually">பார்கோடு கைமுறையாக உள்ளிடவும்</string>
|
||||
@@ -89,21 +89,21 @@
|
||||
<string name="cancel">ரத்துசெய்</string>
|
||||
<string name="save">சேமி</string>
|
||||
<string name="noCardsMessage">முதலில் ஒரு அட்டையைச் சேர்க்கவும்</string>
|
||||
<string name="importExportHelp">உங்கள் தரவை காப்புப் பிரதி எடுப்பது அதை மற்றொரு சாதனத்திற்கு நகர்த்த அனுமதிக்கிறது.</string>
|
||||
<string name="importExportHelp">உங்கள் தரவைக் காப்புப் பிரதி எடுப்பது அதை மற்றொரு சாதனத்திற்கு நகர்த்த இசைகிறது</string>
|
||||
<string name="importSuccessfulTitle">இறக்குமதி செய்யப்பட்டது</string>
|
||||
<string name="permissionReadCardsLabel">கேட்டிமா அட்டைகளைப் படி</string>
|
||||
<string name="permissionReadCardsLabel">பூனையம்மா அட்டைகளைப் படி</string>
|
||||
<string name="permissionReadCardsDescription">உங்கள் கேட்டிமா அட்டைகள் மற்றும் குறிப்புகள் மற்றும் படங்கள் உட்பட அதன் அனைத்து விவரங்களையும் படி</string>
|
||||
<string name="exportOptionExplanation">தரவு உங்கள் விருப்பப்படி இடத்திற்கு எழுதப்படும்.</string>
|
||||
<string name="exportOptionExplanation">தரவு உங்கள் விருப்பப்படி இடத்திற்கு எழுதப்படும்</string>
|
||||
<string name="importOptionFilesystemTitle">கோப்பு முறைமையிலிருந்து இறக்குமதி</string>
|
||||
<string name="importOptionFilesystemExplanation">கோப்பு முறைமையிலிருந்து ஒரு குறிப்பிட்ட கோப்பைத் தேர்வுசெய்க.</string>
|
||||
<string name="importOptionFilesystemExplanation">கோப்பு முறைமையிலிருந்து ஒரு குறிப்பிட்ட கோப்பைத் தேர்வுசெய்க</string>
|
||||
<string name="importOptionFilesystemButton">கோப்பு முறைமையிலிருந்து</string>
|
||||
<string name="cameraPermissionDeniedTitle">கேமராவை அணுக முடியவில்லை</string>
|
||||
<string name="cameraPermissionDeniedTitle">கேமராவை இயக்க முடியவில்லை</string>
|
||||
<string name="noCameraPermissionDirectToSystemSetting">பார்கோடுகளை ஸ்கேன் செய்ய, கேட்டிமாவிற்கு உங்கள் கேமராவுக்கு அணுகல் தேவைப்படும். உங்கள் இசைவு அமைப்புகளை மாற்ற இங்கே தட்டவும்.</string>
|
||||
<string name="about">பற்றி</string>
|
||||
<string name="app_copyright_short">பதிப்புரிமை © சில்வியா வான் ஓஎச் மற்றும் பங்களிப்பாளர்கள்</string>
|
||||
<string name="app_copyright_old">விசுவாச அட்டை கீச்சின் அடிப்படையில்\n பதிப்புரிமை © 2016–2020 பிராண்டன் ஆர்ச்சர்</string>
|
||||
<string name="app_license">நகலெடுக்கப்பட்ட லிப்ரே மென்பொருள், உரிமம் பெற்ற GPLV3+</string>
|
||||
<string name="selectBarcodeTitle">பார்கோடு தேர்ந்தெடுக்கவும்</string>
|
||||
<string name="selectBarcodeTitle">பட்டைகோடு தேர்ந்தெடு</string>
|
||||
<string name="thumbnailDescription">சிறுபடம்</string>
|
||||
<string name="starImage">பிடித்த விண்மீன்</string>
|
||||
<string name="settings">அமைப்புகள்</string>
|
||||
@@ -120,7 +120,7 @@
|
||||
<string name="settings_allow_content_provider_read_summary">பயன்பாடுகள் இன்னும் அணுகல் வழங்க இசைவு கோர வேண்டும்</string>
|
||||
<string name="settings_use_volume_keys_navigation">தொகுதி பொத்தான்களைப் பயன்படுத்தி அட்டைகளை மாற்றவும்</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">அட்டையைப் பார்க்கும்போது திரை லாக் முடக்குகிறது</string>
|
||||
<string name="noGroups">வகைப்படுத்தலுக்கான குழுக்களைச் சேர்க்க + பிளச் பொத்தானைக் சொடுக்கு செய்க.</string>
|
||||
<string name="noGroups">வகைப்படுத்தலுக்கான குழுக்களைச் சேர்க்க + சேர் பொத்தானைக் சொடுக்குக</string>
|
||||
<string name="settings_use_volume_keys_navigation_summary">எந்த அட்டை காட்டப்படும் என்பதை மாற்ற தொகுதி பொத்தான்களைப் பயன்படுத்தவும்</string>
|
||||
<string name="noGroupCards">இந்த குழு காலியாக உள்ளது</string>
|
||||
<string name="moveUp">மேல்நோக்கி நகர்த்தவும்</string>
|
||||
@@ -138,9 +138,9 @@
|
||||
<string name="privacy_policy">தனியுரிமைக் கொள்கை</string>
|
||||
<string name="accept">ஏற்றுக்கொள்</string>
|
||||
<string name="importCatima">கேட்டிமாவில் இருந்து இறக்குமதி செய்</string>
|
||||
<string name="importCatimaMessage">நீங்கள் கேட்டிமாவில் இருந்து ஏற்றுமதி செய்த <i> catima.zip </i> தேர்ந்துஎடுத்து இறக்குமதி செய்யுங்கள்.\nமுதலில் மற்றொரு கேட்டிமா செயலியில் இறக்குமதி/ஏற்றுமதி மெனுவிலிருந்து ஏற்றுமதியை தேர்ந்துஎடுத்து இக்கோப்பை உருவாக்கவும்.</string>
|
||||
<string name="importCatimaMessage">நீங்கள் பூனையம்மாவிலிருந்து ஏற்றுமதியை தேர்ந்துஎடுத்து இறக்குமதி செய்.\nமற்றொரு பூனையம்மா செயலியில் இறக்குமதி/ஏற்றுமதி பட்டியலிலிருந்து ஏற்றுமதியை தேர்ந்துஎடுத்து இக்கோப்பை உருவாக்கு.</string>
|
||||
<string name="importLoyaltyCardKeychain">விசுவாச அட்டை கீச்சினிலிருந்து இறக்குமதி செய்யுங்கள்</string>
|
||||
<string name="importFidmeMessage">உங்கள் <i> fidme-export-request-xxxxxx.zip </i> இறக்குமதி செய்ய FIDME இலிருந்து ஏற்றுமதி செய்து, பின்னர் பார்கோடு வகைகளை கைமுறையாகத் தேர்ந்தெடுக்கவும்.\n தரவு பாதுகாப்பைத் தேர்ந்தெடுப்பதன் மூலம் உங்கள் FIDME சுயவிவரத்திலிருந்து அதை உருவாக்கவும், பின்னர் எனது தரவைப் பிரித்தெடுக்கவும் அழுத்தவும்.</string>
|
||||
<string name="importFidmeMessage">"உங்கள் இறக்குமதியை FIDME இலிருந்து ஏற்றுமதி செய்து, பின்னர் பட்டைகோடு வகைகளைக் கைமுறையாகத் தேர்ந்தெடு.\n தரவு பாதுகாப்பைத் தேர்ந்தெடுப்பதன் மூலம் உங்கள் FIDME தன்விவரத்திலிருந்து அதை உருவாக்கவும், பின்னர் எனது தரவைப் பிரித்தெடு அழுத்தவும்."</string>
|
||||
<string name="importVoucherVault">வவுச்சர் பெட்டகத்திலிருந்து இறக்குமதி</string>
|
||||
<string name="sameAsCardId">ஐடி அதே</string>
|
||||
<string name="setBarcodeId">பார்கோடு மதிப்பை அமைக்கவும்</string>
|
||||
@@ -281,7 +281,7 @@
|
||||
<string name="app_contributors">வழங்கியவர்: <xliff:g id="app_contributors">%s</xliff:g></string>
|
||||
<string name="about_title_fmt">படம் <xliff:g>%s</xliff:g> பட்டைகுறியீடு</string>
|
||||
<string name="barcodeImageDescriptionWithType">படம் <xliff:g>%s</xliff:g> பட்டை குறியீடு</string>
|
||||
<string name="app_libraries">விடுதலை மூன்றாம் தரப்பு நூலகங்கள்: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">மூன்றாம் தரப்பு நூலகங்கள்: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="expiryStateSentence">காலாவதியாகிறது: <xliff:g>%s</xliff:g></string>
|
||||
<string name="balanceSentence">இருப்பு: <xliff:g>%s</xliff:g></string>
|
||||
<plurals name="groupCardCountWithArchived">
|
||||
@@ -289,11 +289,19 @@
|
||||
<item quantity="other"><xliff:g>%1$d</xliff:g> அட்டைகள் (<xliff:g id="archivedCount">%2$d</xliff:g> காப்பகப்படுத்தப்பட்டது)</item>
|
||||
</plurals>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">பதிப்புரிமை © 2019–<xliff:g>%d</xliff:g> சில்வியா வான் ஓஎச் மற்றும் பங்களிப்பாளர்கள்</string>
|
||||
<string name="app_resources">விடுதலை மூன்றாம் தரப்பு வளங்கள்: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_resources">மூன்றாம் தரப்பு வளங்கள்: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="groupsList">குழுக்கள்: <xliff:g>%s</xliff:g></string>
|
||||
<string name="sort_by_valid_from">இருந்து செல்லுபடியாகும்</string>
|
||||
<string name="setBarcodeWidth">பட்டைகுறி அகலம் அமை</string>
|
||||
<string name="width">அகலம்</string>
|
||||
<string name="card_list_widget_name">அட்டை பட்டியல்</string>
|
||||
<string name="card_list_widget_empty">கேட்டிமாவில் நீங்கள் விசுவாச அட்டைகளை சேர்த்த பிறகு, அவை இங்கு தோன்றும். அட்டைகள் காப்பகப்படுத்த படவில்லை என உறுதி செய்க.</string>
|
||||
<string name="cardWithNumber">அட்டை<xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">அட்டை<xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">தயவுசெய்து சாதனத்தை சுழற்றாதீர்கள், இது செயலை ரத்து செய்யும்.</string>
|
||||
<string name="acra_catima_has_crashed">மன்னிக்கவும், <xliff:g id="app_name">%s</xliff:g> செயலி செயலிழந்துள்ளது. தயவுசெய்து பிழை அறிக்கையை அனுப்பி, இந்த பிரச்சினையை சரிசெய்ய எங்களுக்கு உதவுங்கள்.</string>
|
||||
<string name="acra_explain_crash">சாத்தியமானால், நீங்கள் இங்கே என்ன செய்தீர்கள் என்பதைப் பற்றிய மேலும் விவரங்களைச் சேர்க்கவும்:</string>
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> செயலிழப்பு அறிக்கை</string>
|
||||
<string name="pref_enable_acra">செயலிழப்பு அறிக்கைகளை அனுப்ப வேண்டுமா</string>
|
||||
<string name="pref_enable_acra_summary">இது இயக்கப்பட்டால், செயலி செயலிழக்கும் போது அதைப் பற்றிய அறிக்கையை அனுப்பும்படி உங்களிடம் கேட்கப்படும். செயலிழப்பு அறிக்கைகள் ஒருபோதும் தானாக அனுப்பப்படமாட்டாது.</string>
|
||||
</resources>
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<string name="settings_locale">Dil</string>
|
||||
<string name="turn_flashlight_off">El fenerini kapat</string>
|
||||
<string name="turn_flashlight_on">El fenerini aç</string>
|
||||
<string name="failedGeneratingShareURL">Paylaşılabilir URL oluşturulamadı. Lütfen bunu bildirin.</string>
|
||||
<string name="passwordRequired">Lütfen parolayı girin</string>
|
||||
<string name="failedGeneratingShareURL">Paylaşılabilir URL oluşturulamadı</string>
|
||||
<string name="passwordRequired">Parolayı girin</string>
|
||||
<string name="no">Hayır</string>
|
||||
<string name="yes">Evet</string>
|
||||
<string name="updateBarcodeQuestionText">Numarayı değiştirdiniz. Aynı değeri kullanmak için barkodu da güncellemek ister misiniz\?</string>
|
||||
@@ -33,20 +33,16 @@
|
||||
<string name="setBarcodeId">Barkod değerini ayarla</string>
|
||||
<string name="sameAsCardId">Numarayla aynı</string>
|
||||
<string name="barcodeId">Barkod değeri</string>
|
||||
<string name="importVoucherVaultMessage">İçe aktarmak için Voucher Vault\'tan dışa aktardığınız <i>vouchervault.json</i> dosyasını seçin.
|
||||
\nÖnce Voucher Vault\'ta \"Dışa aktar\" düğmesine basarak bir tane oluşturun.</string>
|
||||
<string name="importVoucherVaultMessage">İçe aktarmak için Voucher Vault\'tan dışa aktardığınız dosyayı seçin.\nVoucher Vault\'ta \"Dışa aktar\" düğmesine basarak bir tane oluşturun.</string>
|
||||
<string name="importVoucherVault">Voucher Vault\'tan içe aktar</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">İçe aktarmak için Loyalty Card Keychain\'den dışa aktardığınız <i>LoyaltyCardKeychain.csv</i> dosyasını seçin.
|
||||
\nLoyalty Card Keychain uygulamasının İçe/Dışa aktar menüsündeki \"Dışa aktar\" düğmesine basarak bir tane oluşturun.</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">İçe aktarmak için Loyalty Card Keychain\'den dışa aktardığınız dosyayı seçin.\nLoyalty Card Keychain uygulamasının İçe/Dışa aktar menüsündeki \"Dışa aktar\" düğmesine basarak bir tane oluşturun.</string>
|
||||
<string name="importLoyaltyCardKeychain">Loyalty Card Keychain\'den içe aktar</string>
|
||||
<string name="importFidmeMessage">FidMe\'den içe aktarmak için dışa aktardığınız <i>fidme-export-request-xxxxxx.zip</i> dosyasını seçin ve ardından barkod türlerini elle seçin.
|
||||
\nFidMe profilinizden Veri Koruma seçeneğini seçip ardından \"Verilerimi çıkar\" düğmesine basarak bir tane oluşturun.</string>
|
||||
<string name="importFidmeMessage">FidMe\'den içe aktarmak için dışa aktardığınız dosyayı seçin ve ardından barkod türlerini elle seçin.\nFidMe profilinizden Veri Koruma seçeneğini seçip ardından \"Verilerimi çıkar\" düğmesine basarak bir tane oluşturun.</string>
|
||||
<string name="importFidme">FidMe\'den içe aktar</string>
|
||||
<string name="importCatimaMessage">İçe aktarmak için Catima\'dan dışa aktardığınız <i>catima.zip</i> dosyasını seçin.
|
||||
\nBaşka bir Catima uygulamasının İçe/Dışa aktar menüsündeki \"Dışa aktar\" düğmesine basarak bir tane oluşturun.</string>
|
||||
<string name="importCatimaMessage">İçe aktarmak için Catima\'dan dışa aktardığınız dosyayı seçin. \nBaşka bir Catima uygulamasının İçe/dışa aktar menüsündeki dışa aktar düğmesine basarak bir tane oluşturun.</string>
|
||||
<string name="importCatima">Catima\'dan içe aktar</string>
|
||||
<string name="accept">Kabul et</string>
|
||||
<string name="privacy_policy">Gizlilik Politikası</string>
|
||||
<string name="privacy_policy">Gizlilik politikası</string>
|
||||
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
|
||||
<string name="chooseImportType">Verileri şuradan içe aktar</string>
|
||||
<string name="points">Puan</string>
|
||||
@@ -70,7 +66,7 @@
|
||||
<string name="leaveWithoutSaveTitle">Çıkış</string>
|
||||
<string name="moveDown">Aşağı git</string>
|
||||
<string name="moveUp">Yukarı git</string>
|
||||
<string name="failedOpeningFileManager">Önce bir dosya yöneticisi kurun.</string>
|
||||
<string name="failedOpeningFileManager">Dosya yöneticisi açılamadı</string>
|
||||
<string name="deleteConfirmationGroup">Grup silinsin mi\?</string>
|
||||
<string name="all">Tümü</string>
|
||||
<plurals name="groupCardCount">
|
||||
@@ -78,7 +74,7 @@
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> kart</item>
|
||||
</plurals>
|
||||
<string name="noGroupCards">Bu grup boş</string>
|
||||
<string name="noGroups">Kategorilere ayırmak üzere gruplar eklemek için + artı düğmesine tıklayın.</string>
|
||||
<string name="noGroups">Kategorilere ayırmak üzere gruplar eklemek için + artı düğmesine tıklayın</string>
|
||||
<string name="groups">Gruplar</string>
|
||||
<string name="enter_group_name">Grup adını girin</string>
|
||||
<string name="exportSuccessful">Veriler dışa aktarıldı</string>
|
||||
@@ -94,9 +90,9 @@
|
||||
<string name="settings">Ayarlar</string>
|
||||
<string name="starImage">Sık kullanılan yıldız</string>
|
||||
<string name="thumbnailDescription">Küçük resim</string>
|
||||
<string name="selectBarcodeTitle">Barkod Seç</string>
|
||||
<string name="app_resources">Özgür üçüncü taraf kaynakları: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Özgür üçüncü taraf kütüphaneleri: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="selectBarcodeTitle">Barkod seç</string>
|
||||
<string name="app_resources">Üçüncü taraf kaynakları: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Üçüncü taraf kütüphaneler: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="debug_version_fmt">Sürüm: <xliff:g id="version">%s</xliff:g></string>
|
||||
<string name="about_title_fmt"><xliff:g id="app_name">%s</xliff:g> hakkında</string>
|
||||
<string name="app_license">GPLv3+ altında lisanslanan copyleft özgür yazılım</string>
|
||||
@@ -105,9 +101,9 @@
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Telif Hakkı © 2019–<xliff:g>%d</xliff:g> Sylvia van Os ve katkıda bulunanlar</string>
|
||||
<string name="about">Hakkında</string>
|
||||
<string name="importOptionFilesystemButton">Dosya sisteminden</string>
|
||||
<string name="importOptionFilesystemExplanation">Dosya sisteminden belirli bir dosya seçin.</string>
|
||||
<string name="importOptionFilesystemExplanation">Dosya sisteminden belirli bir dosya seçin</string>
|
||||
<string name="importOptionFilesystemTitle">Dosya sisteminden içe aktar</string>
|
||||
<string name="exportOptionExplanation">Veriler seçtiğiniz bir konuma yazılacak.</string>
|
||||
<string name="exportOptionExplanation">Veriler seçtiğiniz bir konuma yazılacak</string>
|
||||
<string name="exporting">Dışa aktarılıyor…</string>
|
||||
<string name="importing">İçe aktarılıyor…</string>
|
||||
<string name="exportFailed">Dışa aktarma gerçekleştirilemedi</string>
|
||||
@@ -116,18 +112,18 @@
|
||||
<string name="importFailed">İçe aktarma gerçekleştirilemedi</string>
|
||||
<string name="importFailedTitle">İçe aktarılamadı</string>
|
||||
<string name="importSuccessfulTitle">İçe aktarıldı</string>
|
||||
<string name="importExportHelp">Verilerinizi yedeklemek, onları başka bir aygıta taşımanıza olanak tanır.</string>
|
||||
<string name="importExportHelp">Verilerinizi yedeklemek, onları başka bir aygıta taşımanıza olanak tanır</string>
|
||||
<string name="exportName">Dışa aktar</string>
|
||||
<string name="importExport">İçe/Dışa aktar</string>
|
||||
<string name="importExport">İçe/dışa aktar</string>
|
||||
<string name="failedParsingImportUriError">İçe aktarma URI\'si ayrıştırılamadı</string>
|
||||
<string name="noCardExistsError">Bu kart bulunamadı</string>
|
||||
<string name="barcodeImageDescriptionWithType"><xliff:g>%s</xliff:g> barkod görüntüsü</string>
|
||||
<string name="cardId">Kart numarası</string>
|
||||
<string name="noCardsMessage">Önce bir kart ekleyin</string>
|
||||
<string name="cardShortcut">Kart Kısayolu</string>
|
||||
<string name="scanCardBarcode">Barkod Tara</string>
|
||||
<string name="addCardTitle">Kart Ekle</string>
|
||||
<string name="editCardTitle">Kartı Düzenle</string>
|
||||
<string name="cardShortcut">Kart kısayolu</string>
|
||||
<string name="scanCardBarcode">Barkod tara</string>
|
||||
<string name="addCardTitle">Kart ekle</string>
|
||||
<string name="editCardTitle">Kartı düzenle</string>
|
||||
<string name="sendLabel">Gönder…</string>
|
||||
<string name="share">Paylaş</string>
|
||||
<string name="ok">Tamam</string>
|
||||
@@ -163,24 +159,24 @@
|
||||
<string name="sort_by">Sıralama ölçütü</string>
|
||||
<string name="reverse">…ters sırada</string>
|
||||
<string name="sort_by_expiry">Son kullanma tarihi</string>
|
||||
<string name="sort_by_most_recently_used">En Son Kullanılan</string>
|
||||
<string name="sort_by_most_recently_used">En son kullanılan</string>
|
||||
<string name="sort_by_name">Ad</string>
|
||||
<string name="sort">Sırala</string>
|
||||
<string name="report_error">Hata Bildir</string>
|
||||
<string name="report_error">Hata bildir</string>
|
||||
<string name="on_google_play">Google Play\'de</string>
|
||||
<string name="rate_this_app">Bu uygulamayı değerlendir</string>
|
||||
<string name="and_data_usage">ve veri kullanımı</string>
|
||||
<string name="on_github">GitHub\'da</string>
|
||||
<string name="source_repository">Kaynak Deposu</string>
|
||||
<string name="source_repository">Kaynak deposu</string>
|
||||
<string name="license">Lisans</string>
|
||||
<string name="help_translate_this_app">Bu uygulamayı çevirmeye yardımcı olun</string>
|
||||
<string name="credits">Emeği Geçenler</string>
|
||||
<string name="version_history">Sürüm Geçmişi</string>
|
||||
<string name="version_history">Sürüm geçmişi</string>
|
||||
<string name="exportPassword">Dışa aktarmanızı korumak için bir parola belirleyin (isteğe bağlı)</string>
|
||||
<string name="exportPasswordHint">Parola girin</string>
|
||||
<string name="noGiftCardsGroup">Bazı kartlar oluşturun ve ardından bunları buradaki gruba atayın</string>
|
||||
<string name="group_name_already_in_use">Grup adı zaten kullanılıyor</string>
|
||||
<string name="editGroup">Grup Düzenleniyor: <xliff:g>%s</xliff:g></string>
|
||||
<string name="editGroup">Grup düzenleniyor: <xliff:g>%s</xliff:g></string>
|
||||
<string name="group_edit">Grubu Düzenle</string>
|
||||
<string name="group_name_is_empty">Grup adı boş olamaz</string>
|
||||
<string name="group_updated">Grup güncellendi</string>
|
||||
@@ -202,7 +198,7 @@
|
||||
<string name="archived">Kart arşivlendi</string>
|
||||
<string name="unarchived">Kart arşivden çıkarıldı</string>
|
||||
<string name="archive">Arşivle</string>
|
||||
<string name="failedLaunchingPhotoPicker">Desteklenen bir galeri uygulaması bulunamadı</string>
|
||||
<string name="failedLaunchingPhotoPicker">Desteklenen bir resim seçici bulunamadı</string>
|
||||
<plurals name="groupCardCountWithArchived">
|
||||
<item quantity="one"><xliff:g>%1$d</xliff:g> kart (<xliff:g id="archivedCount">%2$d</xliff:g> tane arşivlendi)</item>
|
||||
<item quantity="other"><xliff:g>%1$d</xliff:g> kart (<xliff:g id="archivedCount">%2$d</xliff:g> tane arşivlendi)</item>
|
||||
@@ -233,8 +229,8 @@
|
||||
<string name="donate">Bağış yap</string>
|
||||
<string name="switchToFrontImage">Ön resme geç</string>
|
||||
<string name="setBarcodeHeight">Barkod yüksekliğini ayarla</string>
|
||||
<string name="openFrontImageInGalleryApp">Ön resmi galeri uygulamasında aç</string>
|
||||
<string name="openBackImageInGalleryApp">Arka resmi galeri uygulamasında aç</string>
|
||||
<string name="openFrontImageInGalleryApp">Ön resmi resim görüntüleyici uygulamasında aç</string>
|
||||
<string name="openBackImageInGalleryApp">Arka resmi resim görüntüleyici uygulamasında aç</string>
|
||||
<string name="icon_header_click_text">Küçük resmi düzenlemek için uzun basın</string>
|
||||
<string name="show_name_below_image_thumbnail">Küçük resmin altında adı göster</string>
|
||||
<string name="show_note">Notu göster</string>
|
||||
@@ -266,7 +262,7 @@
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="continue_">Devam et</string>
|
||||
<string name="add_manually_warning_title">Tarama yapılması tavsiye edilir</string>
|
||||
<string name="add_manually_warning_message">Bazı mağazalarda barkod değeri kartta yazan sayıdan farklıdır. Bu nedenle, bir barkodu elle girmek her zaman işe yaramayabilir. Bunun yerine barkodu kameranızla taramanız şiddetle tavsiye edilir. Yine de devam etmek istiyor musunuz?</string>
|
||||
<string name="add_manually_warning_message">Bazı kartta barkod değeri kartta yazan sayıdan farklıdır. Bu nedenle, bir barkodu elle girmek her zaman işe yaramayabilir. Bunun yerine barkodu kameranızla taramanız tavsiye edilir. Yine de devam etmek istiyor musunuz?</string>
|
||||
<string name="spend">Harca</string>
|
||||
<string name="receive">Al</string>
|
||||
<string name="amountParsingFailed">Geçersiz miktar</string>
|
||||
@@ -294,13 +290,19 @@
|
||||
<string name="settings_automatic_column_count">Otomatik</string>
|
||||
<string name="settings_column_count_portrait">Portre modundaki sutunlar</string>
|
||||
<string name="unsupportedFile">Bu dosya desteklenmiyor</string>
|
||||
<string name="generic_error_please_retry">Üzgünüz, bir şeyler ters gitti, lütfen tekrar deneyin...</string>
|
||||
<string name="generic_error_please_retry">Bir hata oluştu</string>
|
||||
<string name="addFromPkpass">Bir Passbook dosyası seçin (.pkpass)</string>
|
||||
<string name="sort_by_valid_from">İtibaren Geçerli</string>
|
||||
<string name="sort_by_valid_from">İtibaren geçerli</string>
|
||||
<string name="width">Genişlik</string>
|
||||
<string name="setBarcodeWidth">Barkod Genişliğini Ayarla</string>
|
||||
<string name="setBarcodeWidth">Barkod genişliğini ayarla</string>
|
||||
<string name="card_list_widget_name">Kart listesi</string>
|
||||
<string name="card_list_widget_empty">Catima\'ya sadakat kartları eklediğinizde, burada gözükecekler. Eğer kartlarınız varsa, arşivlemediğinizden emin olun.</string>
|
||||
<string name="cardWithNumber">Kart <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Kart <xliff:g>%d</xliff:g> (%s)</string>
|
||||
<string name="cardWithNumberAndLocale">Kart <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Lütfen cihazı döndürme, bu işlemi iptal edecektir</string>
|
||||
<string name="acra_catima_has_crashed">Üzgünüz, fakat <xliff:g id="app_name">%s</xliff:g> çöktü. Lütfen bunu çözmemize yardım etmek için bize bir hata raporu gönderin.</string>
|
||||
<string name="acra_explain_crash">Mümkünse lütfen ne yaptığınızla ilgili daha fazla detay ekleyin:</string>
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> çökme raporu</string>
|
||||
<string name="pref_enable_acra_summary">Etkinleştirildiğinde, bir çökmeyi şikayet etmeniz istenecektir. Çökme raporları hiç bir zaman otomatik olarak gönderilmez.</string>
|
||||
<string name="pref_enable_acra">Çökme bildirimlerini göndermeyi iste</string>
|
||||
</resources>
|
||||
|
||||
@@ -311,4 +311,13 @@
|
||||
<string name="card_list_widget_empty">Після того, як ви додасте кілька карток лояльності в Catima, вони з’являться тут. Якщо у вас є картки, переконайтеся, що вони не всі заархівовані.</string>
|
||||
<string name="cardWithNumber">Картка <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Картка <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Будь ласка, не повертайте пристрій, оскільки це скасує дію</string>
|
||||
<string name="acra_catima_has_crashed">Вибачте, але <xliff:g id="app_name">%s</xliff:g> аварійно завершив роботу. Будь ласка, допоможіть нам вирішити цю проблему, надіславши нам звіт про помилку.</string>
|
||||
<string name="acra_explain_crash">Якщо можливо, додайте, будь ласка, більше деталей про те, що ви тут робили:</string>
|
||||
<string name="acra_crash_email_subject">Звіт про збій <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Запит на надсилання звітів про збої</string>
|
||||
<string name="pref_enable_acra_summary">Якщо цю функцію ввімкнено, вам буде запропоновано повідомити про збій, коли він станеться. Звіти про збої ніколи не надсилаються автоматично.</string>
|
||||
<string name="copy_value">Копіювати значення</string>
|
||||
<string name="copied_to_clipboard">Скопійовано в буфер обміну</string>
|
||||
<string name="nothing_to_copy">Значення не знайдено</string>
|
||||
</resources>
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
<string name="updateBalance">Cập nhật số dư</string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Bản quyền © 2019–<xliff:g>%d</xliff:g> Sylvia van Os và các cộng sự</string>
|
||||
<string name="sort_by_most_recently_used">Sửa dụng gần đây nhất</string>
|
||||
<string name="noGiftCards">Bấm nút dấu cộng + để thêm thẻ, hoặc nhập dữ liệu từ menu ⋮.</string>
|
||||
<string name="noGiftCards">Bấm nút dấu cộng + để thêm thẻ, hoặc nhập dữ liệu từ ⋮ menu</string>
|
||||
<string name="settings_theme_color">Chủ đề màu</string>
|
||||
<string name="importVoucherVault">Nhập dữ liệu từ Voucher Vault</string>
|
||||
<string name="barcodeId">Giá trị mã vạch</string>
|
||||
|
||||
@@ -293,4 +293,13 @@
|
||||
<string name="card_list_widget_empty">在 Catima 中添加了一些会员卡后,它们会出现在这里。如果你有卡片,确保不是所有都已归档。</string>
|
||||
<string name="cardWithNumber">卡<xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">卡 <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">请不要旋转设备,这样做会取消操作</string>
|
||||
<string name="acra_catima_has_crashed">抱歉,但 <xliff:g id="app_name">%s</xliff:g>崩溃了。请发送错误报告帮助修复这个问题。</string>
|
||||
<string name="acra_explain_crash">如有可能,请添加崩溃发生时你在进行什么操作的更多细节:</string>
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> 崩溃报告</string>
|
||||
<string name="pref_enable_acra">请求发送崩溃报告</string>
|
||||
<string name="pref_enable_acra_summary">开启后,会在发生崩溃时请求报告崩溃。应用永远不会自动发送崩溃报告。</string>
|
||||
<string name="copy_value">复制值</string>
|
||||
<string name="copied_to_clipboard">已复制到剪贴板</string>
|
||||
<string name="nothing_to_copy">没找到值</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="action_search">搜尋</string>
|
||||
<string name="action_add">新增</string>
|
||||
<string name="noGiftCards">點選 + 按鈕以新增卡片,或從 ⋮ 選單中匯入。</string>
|
||||
<string name="noGiftCards">點選 + 按鈕以新增卡片,或從 ⋮ 選單中匯入</string>
|
||||
<string name="noMatchingGiftCards">找不到相關結果。試試其他關鍵字。</string>
|
||||
<string name="storeName">名稱</string>
|
||||
<string name="note">註記</string>
|
||||
@@ -40,7 +40,7 @@
|
||||
<string name="deleteConfirmationGroup">刪除此群組?</string>
|
||||
<string name="deleteTitle">刪除卡片</string>
|
||||
<string name="editBarcode">編輯條碼</string>
|
||||
<string name="editCardTitle">編輯圖片</string>
|
||||
<string name="editCardTitle">編輯卡片</string>
|
||||
<string name="enter_group_name">輸入群組名稱</string>
|
||||
<string name="errorReadingImage">無法讀取此圖片</string>
|
||||
<string name="expiryDate">逾期日期</string>
|
||||
@@ -50,12 +50,12 @@
|
||||
<string name="exportFailedTitle">匯出失敗</string>
|
||||
<string name="exporting">匯出中…</string>
|
||||
<string name="exportName">匯出</string>
|
||||
<string name="exportOptionExplanation">資料將寫至您所選的位置。</string>
|
||||
<string name="exportOptionExplanation">資料將寫至您所選的位置</string>
|
||||
<string name="exportPassword">透過密碼保護您的匯出檔(選用)</string>
|
||||
<string name="exportPasswordHint">輸入密碼</string>
|
||||
<string name="exportSuccessful">已匯出資料</string>
|
||||
<string name="exportSuccessfulTitle">已匯出</string>
|
||||
<string name="failedOpeningFileManager">請先安裝檔案管理員。</string>
|
||||
<string name="failedOpeningFileManager">無法開啟檔案管理員</string>
|
||||
<string name="failedParsingImportUriError">無法讀取匯入 URI</string>
|
||||
<string name="frontImageDescription">正面圖片</string>
|
||||
<string name="groups">群組</string>
|
||||
@@ -143,36 +143,33 @@
|
||||
<string name="ok">OK</string>
|
||||
<string name="sendLabel">送出…</string>
|
||||
<string name="scanCardBarcode">掃描條碼</string>
|
||||
<string name="importExportHelp">備份您的資料以將其轉移至其他裝置中。</string>
|
||||
<string name="importExportHelp">備份您的資料以將其轉移至其他裝置中</string>
|
||||
<string name="importOptionFilesystemTitle">自檔案系統中匯入</string>
|
||||
<string name="importOptionFilesystemExplanation">自檔案系統中選取檔案。</string>
|
||||
<string name="importOptionFilesystemExplanation">自檔案系統中選取檔案</string>
|
||||
<string name="importOptionFilesystemButton">自檔案系統</string>
|
||||
<string name="app_copyright_fmt">著作權所有 © 2019–<xliff:g>%d</xliff:g> Sylvia van Os 與其他貢獻者</string>
|
||||
<string name="importVoucherVault">自 Voucher Vault 中匯入</string>
|
||||
<string name="importVoucherVaultMessage">選取您自 Voucher Vault 匯出的 <i>vouchervault.json</i> 檔案以進行匯入。
|
||||
\n請您先透過 Voucher Vault 進行匯出。</string>
|
||||
<string name="importVoucherVaultMessage">請選取您從 Voucher Vault 匯出的檔案以進行匯入。\n您可以在 Voucher Vault 中按下「匯出」來建立此檔案。</string>
|
||||
<string name="importLoyaltyCardKeychain">自 Loyalty Card Keychain 中匯入</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">選取您自 Loyalty Card Keychain <i>LoyaltyCardKeychain.csv</i> 檔案以進行匯入。
|
||||
\n請您先透過 Loyalty Card Keychain 的匯入/匯出選單進行匯出。</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">請選取您從 Loyalty Card Keychain 匯出的檔案以進行匯入。\n您可以在 Loyalty Card Keychain 的「匯入/匯出」選單中按下「匯出」來建立此檔案。</string>
|
||||
<string name="importFidme">自 FidMe 匯入</string>
|
||||
<string name="importFidmeMessage">選取您自 FidMe 匯出的<i>fidme-export-request-xxxxxx.zip</i> 檔案以進行匯入,並手動選擇條碼種類。
|
||||
\n請您先透過您的 FidMe 個人檔案選取『Data Protection』,並選擇『Extract my data』。</string>
|
||||
<string name="importFidmeMessage">請選取您從 FidMe 匯出的檔案以進行匯入,並在之後手動選擇條碼類型。\n您可以在 FidMe 的個人檔案中選擇「Data Protection」,然後按下「Extract my data」來建立此檔案。</string>
|
||||
<string name="importCatima">自卡提碼匯入</string>
|
||||
<string name="importCatimaMessage">選取您自卡提碼匯出的 <i>catima.zip</i> 檔案以進行匯入。 \n您可透過其他裝置的卡提碼程式中的匯入/匯出選單進行匯出。</string>
|
||||
<string name="importCatimaMessage">請選取您從 Catima 匯出檔案以進行匯入。\n您可以在另一台裝置的 Catima 應用程式中,透過「匯入/匯出」選單按下「匯出」來建立此檔案。</string>
|
||||
<string name="points">點</string>
|
||||
<string name="app_libraries">第三方自由函式庫:<xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">第三方自由資源:<xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">第三方程式庫:<xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">第三方資源:<xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="selectBarcodeTitle">選擇條碼</string>
|
||||
<string name="noGroups">請點選 + 加號按鈕新增群組。</string>
|
||||
<string name="noGroups">請點選 + 加號按鈕新增群組</string>
|
||||
<string name="moveBarcodeToTopOfScreen">將條碼移至螢幕上方</string>
|
||||
<string name="app_loyalty_card_keychain">萬用卡片錢包</string>
|
||||
<string name="unsupportedBarcodeType">尚支援此條碼種類,但未來版本的應用程式可能會支援此條碼種類。</string>
|
||||
<string name="wrongValueForBarcodeType">條碼內容不適用於此條碼種類</string>
|
||||
<string name="backImageDescription">背面圖片</string>
|
||||
<string name="updateBarcodeQuestionText">您已更新了 ID,是否要更新條碼內容以匹配此 ID?</string>
|
||||
<string name="failedGeneratingShareURL">無法建立可分享的 URL,請回報此錯誤。</string>
|
||||
<string name="failedGeneratingShareURL">無法建立可分享的 URL</string>
|
||||
<string name="starImage">收藏標示</string>
|
||||
<string name="noGiftCardsGroup">建立一些卡片,然後將它們分配到這個群組中。</string>
|
||||
<string name="noGiftCardsGroup">建立一些卡片,然後將它們分配到這個群組中</string>
|
||||
<string name="showMoreInfo">顯示資訊</string>
|
||||
<string name="shortcutSelectCard">選擇卡片</string>
|
||||
<string name="starred">已收藏</string>
|
||||
@@ -201,7 +198,7 @@
|
||||
<item quantity="other"><xliff:g>%1$d</xliff:g> 張卡片 (<xliff:g id="archivedCount">%2$d</xliff:g> 張已封存)</item>
|
||||
</plurals>
|
||||
<string name="failedToOpenUrl">先安裝網頁瀏覽器</string>
|
||||
<string name="failedLaunchingPhotoPicker">無法找到支援的圖庫應用程式</string>
|
||||
<string name="failedLaunchingPhotoPicker">無法找到支援的圖片選取器</string>
|
||||
<string name="previousCard">上一張</string>
|
||||
<string name="nextCard">下一張</string>
|
||||
<string name="welcome">歡迎使用卡提碼</string>
|
||||
@@ -220,7 +217,7 @@
|
||||
<string name="currentBalanceSentence">餘額:<xliff:g>%s</xliff:g></string>
|
||||
<string name="newBalanceSentence">新的餘額:<xliff:g>%s</xliff:g></string>
|
||||
<string name="switchToBarcode">選擇條碼</string>
|
||||
<string name="openFrontImageInGalleryApp">以圖庫軟件開啟正面圖片</string>
|
||||
<string name="openFrontImageInGalleryApp">在圖片檢視應用程式中開啟正面圖片</string>
|
||||
<string name="show_note">顯示備註</string>
|
||||
<string name="show_balance">顯示餘額</string>
|
||||
<string name="show_validity">顯示有效性</string>
|
||||
@@ -229,7 +226,7 @@
|
||||
<string name="height">高</string>
|
||||
<string name="donate">捐款</string>
|
||||
<string name="icon_header_click_text">長按以編輯縮圖</string>
|
||||
<string name="openBackImageInGalleryApp">以圖庫軟體開啟背面圖片</string>
|
||||
<string name="openBackImageInGalleryApp">在圖片檢視應用程式中開啟背面圖片</string>
|
||||
<string name="show_name_below_image_thumbnail">在縮圖下方顯示名稱</string>
|
||||
<string name="setBarcodeHeight">設定條碼高度</string>
|
||||
<string name="app_copyright_short">著作權所有© Sylvia van Os與其他貢獻者</string>
|
||||
@@ -265,7 +262,7 @@
|
||||
<string name="continue_">繼續</string>
|
||||
<string name="multipleBarcodesFoundPleaseChooseOne">你想要使用哪個找到的條碼?</string>
|
||||
<string name="pageWithNumber">第 <xliff:g>%d</xliff:g> 頁</string>
|
||||
<string name="add_manually_warning_message">對於某些商店,條碼值與卡片上寫的數字並不相同。因此,手動輸入條碼可能並不總是有效。強烈建議使用相機掃描條碼。你還想繼續嗎?</string>
|
||||
<string name="add_manually_warning_message">有些卡片上的條碼數值與卡面上印的號碼可能不同,因此手動輸入條碼可能無法正常運作。建議您改用相機掃描條碼。您仍要繼續嗎?</string>
|
||||
<string name="spend">花費</string>
|
||||
<string name="noCameraFoundGuideText">您的裝置似乎沒有相機鏡頭。如果實際上有相機鏡頭,請嘗試重新啟動此裝置,否則請點選下方的「更多」按鈕,以其它方式新增條碼。</string>
|
||||
<string name="exportCancelled">已取消匯出</string>
|
||||
@@ -285,12 +282,23 @@
|
||||
<string name="settings_category_title_cards_overview">卡片概覽</string>
|
||||
<string name="settings_column_count_portrait">縱向模式下的列數</string>
|
||||
<string name="settings_column_count_landscape">横向模式下的列數</string>
|
||||
<string name="addFromPkpass">選擇 Passbook 檔案 (.pkpass)</string>
|
||||
<string name="addFromPkpass">選擇 Passbook 檔案 (.pkpass / .pkpasses)</string>
|
||||
<string name="unsupportedFile">不支援此檔案</string>
|
||||
<string name="generic_error_please_retry">抱歉,似乎出了點錯誤,請您再試一次...</string>
|
||||
<string name="generic_error_please_retry">發生錯誤</string>
|
||||
<string name="sort_by_valid_from">有效期限開始日</string>
|
||||
<string name="card_list_widget_empty">加入卡提碼的卡片會在這顯示。若您已加入卡片,請確認卡片是否被歸檔。</string>
|
||||
<string name="width">寬</string>
|
||||
<string name="card_list_widget_name">卡片清單</string>
|
||||
<string name="setBarcodeWidth">設定條碼寬度</string>
|
||||
<string name="cardWithNumber">卡片 <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">卡片 <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">請勿旋轉裝置,否則此操作將被取消</string>
|
||||
<string name="acra_catima_has_crashed">很抱歉,<xliff:g id="app_name">%s</xliff:g> 已發生當機。請協助我們修正此問題,並傳送錯誤報告給我們。</string>
|
||||
<string name="acra_explain_crash">如果可以的話,請在此提供您當時的操作詳細資訊:</string>
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> 當機報告</string>
|
||||
<string name="pref_enable_acra">詢問是否傳送當機報告</string>
|
||||
<string name="pref_enable_acra_summary">啟用後,當發生當機時系統會詢問您是否要回報。當機報告絕不會自動傳送。</string>
|
||||
<string name="copy_value">複製值</string>
|
||||
<string name="copied_to_clipboard">已複製到剪貼簿</string>
|
||||
<string name="nothing_to_copy">未找到值</string>
|
||||
</resources>
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
-->
|
||||
<string-array name="locale_values">
|
||||
<item />
|
||||
<!-- <item>af</item> -->
|
||||
<item>ar</item>
|
||||
<!-- <item>ast</item> -->
|
||||
<item>be</item>
|
||||
@@ -121,6 +122,7 @@
|
||||
<item>fi</item>
|
||||
<!-- <item>fil</item> -->
|
||||
<item>fr</item>
|
||||
<!-- <item>fy</item> -->
|
||||
<item>gl</item>
|
||||
<item>he-rIL</item>
|
||||
<item>hi</item>
|
||||
|
||||
@@ -358,4 +358,7 @@
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> crash report</string>
|
||||
<string name="pref_enable_acra">Ask to send crash reports</string>
|
||||
<string name="pref_enable_acra_summary">When enabled, you will be asked to report a crash when it happens. Crash reports are never sent automatically.</string>
|
||||
<string name="copy_value">Copy value</string>
|
||||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||
<string name="nothing_to_copy">No value found</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.view.View;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ImportExportActivityTest {
|
||||
private void registerIntentHandler(String handler) {
|
||||
// Add something that will 'handle' the given intent type
|
||||
PackageManager packageManager = RuntimeEnvironment.application.getPackageManager();
|
||||
|
||||
ResolveInfo info = new ResolveInfo();
|
||||
info.isDefault = true;
|
||||
|
||||
ApplicationInfo applicationInfo = new ApplicationInfo();
|
||||
applicationInfo.packageName = "does.not.matter";
|
||||
info.activityInfo = new ActivityInfo();
|
||||
info.activityInfo.applicationInfo = applicationInfo;
|
||||
info.activityInfo.name = "DoesNotMatter";
|
||||
info.activityInfo.exported = true;
|
||||
|
||||
Intent intent = new Intent(handler);
|
||||
|
||||
if (handler.equals(Intent.ACTION_GET_CONTENT)) {
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
}
|
||||
|
||||
shadowOf(packageManager).addResolveInfoForIntent(intent, info);
|
||||
}
|
||||
|
||||
private void checkVisibility(Activity activity, int state, int divider, int title, int message, int button) {
|
||||
View dividerView = activity.findViewById(divider);
|
||||
View titleView = activity.findViewById(title);
|
||||
View messageView = activity.findViewById(message);
|
||||
View buttonView = activity.findViewById(button);
|
||||
|
||||
assertEquals(state, dividerView.getVisibility());
|
||||
assertEquals(state, titleView.getVisibility());
|
||||
assertEquals(state, messageView.getVisibility());
|
||||
assertEquals(state, buttonView.getVisibility());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllOptionsAvailable() {
|
||||
registerIntentHandler(Intent.ACTION_PICK);
|
||||
registerIntentHandler(Intent.ACTION_GET_CONTENT);
|
||||
|
||||
Activity activity = Robolectric.setupActivity(ImportExportActivity.class);
|
||||
|
||||
checkVisibility(activity, View.VISIBLE, R.id.dividerImportFilesystem,
|
||||
R.id.importOptionFilesystemTitle, R.id.importOptionFilesystemExplanation,
|
||||
R.id.importOptionFilesystemButton);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package protect.card_locker
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.view.View
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class ImportExportActivityTest {
|
||||
|
||||
private fun registerIntentHandler(handler: String) {
|
||||
// Add something that will 'handle' the given intent type
|
||||
val packageManager = RuntimeEnvironment.application.packageManager
|
||||
|
||||
val info = ResolveInfo().apply {
|
||||
isDefault = true
|
||||
activityInfo = ActivityInfo().apply {
|
||||
applicationInfo = ApplicationInfo().apply {
|
||||
packageName = "does.not.matter"
|
||||
}
|
||||
name = "DoesNotMatter"
|
||||
exported = true
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent(handler)
|
||||
|
||||
if (handler == Intent.ACTION_GET_CONTENT) {
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
intent.type = "*/*"
|
||||
}
|
||||
|
||||
shadowOf(packageManager).addResolveInfoForIntent(intent, info)
|
||||
}
|
||||
|
||||
private fun checkVisibility(
|
||||
activity: Activity,
|
||||
state: Int,
|
||||
divider: Int,
|
||||
title: Int,
|
||||
message: Int,
|
||||
button: Int
|
||||
) {
|
||||
val dividerView = activity.findViewById<View>(divider)
|
||||
val titleView = activity.findViewById<View>(title)
|
||||
val messageView = activity.findViewById<View>(message)
|
||||
val buttonView = activity.findViewById<View>(button)
|
||||
|
||||
assertEquals(state, dividerView.visibility)
|
||||
assertEquals(state, titleView.visibility)
|
||||
assertEquals(state, messageView.visibility)
|
||||
assertEquals(state, buttonView.visibility)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAllOptionsAvailable() {
|
||||
registerIntentHandler(Intent.ACTION_PICK)
|
||||
registerIntentHandler(Intent.ACTION_GET_CONTENT)
|
||||
|
||||
val activity = Robolectric.setupActivity(ImportExportActivity::class.java)
|
||||
|
||||
checkVisibility(
|
||||
activity, View.VISIBLE, R.id.dividerImportFilesystem,
|
||||
R.id.importOptionFilesystemTitle, R.id.importOptionFilesystemExplanation,
|
||||
R.id.importOptionFilesystemButton
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
plugins {
|
||||
id("com.android.application") version "8.13.0" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
|
||||
alias(libs.plugins.com.android.application) apply false
|
||||
alias(libs.plugins.org.jetbrains.kotlin.android) apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
||||
@@ -54,7 +54,7 @@ Supported barcodes:
|
||||
|
||||
# Moving data from other apps
|
||||
|
||||
Within the app you can import cards and codes from files, Catima, FidMe, Loyalty Card Keychain, Voucher Vault, and Stocard.
|
||||
Within the app you can import cards and codes from files, Catima, FidMe, Loyalty Card Keychain and Voucher Vault.
|
||||
For FidMe you need to select the barcode type for each entry afterwards.
|
||||
|
||||
# Building
|
||||
|
||||
2
fastlane/metadata/android/ca/changelogs/102.txt
Normal file
2
fastlane/metadata/android/ca/changelogs/102.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Diverses correccions menors
|
||||
- S'ha corregit un error en utilitzar la traducció al noruec
|
||||
2
fastlane/metadata/android/ca/changelogs/103.txt
Normal file
2
fastlane/metadata/android/ca/changelogs/103.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Corregeix la selecció d'idioma manual que no s'aplica a tot arreu
|
||||
- Corregeix el bloqueig a la vista d'edició en la configuració regional sense regió
|
||||
2
fastlane/metadata/android/ca/changelogs/104.txt
Normal file
2
fastlane/metadata/android/ca/changelogs/104.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Desa l'estat de l'expansió dels detalls de la targeta
|
||||
- Correccions menors de la interfície d'usuari
|
||||
2
fastlane/metadata/android/ca/changelogs/105.txt
Normal file
2
fastlane/metadata/android/ca/changelogs/105.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Correcció d'un bloc gris que apareix en un valor no vàlid per al codi de barres
|
||||
- Correccions d'importació de Stocard
|
||||
1
fastlane/metadata/android/ca/changelogs/106.txt
Normal file
1
fastlane/metadata/android/ca/changelogs/106.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Corregeix algunes seqüències de caràcters que es mostraven com un sol caràcter
|
||||
1
fastlane/metadata/android/ca/changelogs/107.txt
Normal file
1
fastlane/metadata/android/ca/changelogs/107.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Correccions d'importació de Stocard
|
||||
5
fastlane/metadata/android/ca/changelogs/108.txt
Normal file
5
fastlane/metadata/android/ca/changelogs/108.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
- Afegeix la funció de duplicació de targetes
|
||||
- No permet triar la caducitat abans de 1970 (de totes maneres, mai van funcionar)
|
||||
- Afegeix compatibilitat amb l'arxivament de targetes
|
||||
- Mou l'eliminació de l'edició a la vista
|
||||
- Elimina la icona de bloqueig de rotació en favor d'una nova configuració de bloqueig de rotació
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user