Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e68dbe255f | ||
|
|
3fffc83c08 | ||
|
|
03e1f5c672 | ||
|
|
40295f7f37 | ||
|
|
bbf00e1d90 | ||
|
|
61637599e3 | ||
|
|
b748f2e002 | ||
|
|
f3424b716d | ||
|
|
3a413127a5 | ||
|
|
352086887d | ||
|
|
2e3770706c | ||
|
|
92abf69236 | ||
|
|
c9e6606524 | ||
|
|
9e58c9da44 | ||
|
|
b99204c55b | ||
|
|
15035fe38e | ||
|
|
5d58fae618 | ||
|
|
363fce3064 | ||
|
|
868f12f072 | ||
|
|
3332cf732e | ||
|
|
abec7ea821 | ||
|
|
555f3dff46 | ||
|
|
59e14a516f | ||
|
|
6fc798dfc6 | ||
|
|
b9462df24a | ||
|
|
9344df33d7 | ||
|
|
fbec74ae54 | ||
|
|
d493733abd | ||
|
|
dbae33492d | ||
|
|
2b8b48afdc | ||
|
|
3bb8b94c6d | ||
|
|
3b0f3b3a5b | ||
|
|
2885357b80 | ||
|
|
d08bbb5e3c | ||
|
|
1da01b846a | ||
|
|
1f2870d3d8 | ||
|
|
a0a2f061bd | ||
|
|
c70b3ec172 | ||
|
|
a3ad6a8568 | ||
|
|
1a9d162370 | ||
|
|
950d19ce8c | ||
|
|
b531081806 | ||
|
|
b46313a112 | ||
|
|
b54fc85fdb | ||
|
|
b71a2a0bbf | ||
|
|
8ba44eeba5 | ||
|
|
8ef9f39fb7 | ||
|
|
86543c8fb9 | ||
|
|
3c8bae3acc | ||
|
|
0a19db7c7e | ||
|
|
7b55f593d0 | ||
|
|
ced6b08e6c | ||
|
|
23589e88df | ||
|
|
4bc38a1162 | ||
|
|
69ce67cda4 | ||
|
|
ce76901d6e | ||
|
|
fa4b89d3a9 | ||
|
|
3bac76db8f | ||
|
|
f93e7d857a | ||
|
|
1b894e5043 | ||
|
|
08508952fc | ||
|
|
e7159b7980 | ||
|
|
223b5a04db | ||
|
|
b7aa25a5e3 | ||
|
|
1d9fbd6633 | ||
|
|
4be04fb057 | ||
|
|
efdb68cb90 | ||
|
|
1f61b00ed5 | ||
|
|
7afa0399d4 | ||
|
|
efe441b0bd | ||
|
|
59efd0892f | ||
|
|
1cee23baad | ||
|
|
d13519a75f | ||
|
|
5a12769f22 | ||
|
|
14ed18c4d0 | ||
|
|
59e2083511 | ||
|
|
a9daca77a2 | ||
|
|
8b67797e71 | ||
|
|
4195c2b75e | ||
|
|
5e3bb55ba5 | ||
|
|
0a0c378f56 | ||
|
|
2e4fb34dbe | ||
|
|
2df681a5b8 | ||
|
|
7c11508b12 | ||
|
|
54bbe1e99a | ||
|
|
24bd17eb9c | ||
|
|
33653fab99 | ||
|
|
9efb246133 | ||
|
|
e807e1f4ff | ||
|
|
d1d8eaacae | ||
|
|
4030cd0284 | ||
|
|
80ec71d9d9 | ||
|
|
2cd3b4a131 | ||
|
|
e90ab64194 | ||
|
|
0656767d07 | ||
|
|
ab4685b725 | ||
|
|
42beafa255 | ||
|
|
3df40a8808 | ||
|
|
eefe52320a | ||
|
|
96cf6c04be | ||
|
|
f397692f3b | ||
|
|
ec17b3a8d9 | ||
|
|
03ba942423 | ||
|
|
67d812b291 | ||
|
|
e9ca16ffd3 | ||
|
|
66f96b64e2 | ||
|
|
f165ae525a | ||
|
|
dfc27cf4fe | ||
|
|
c6bf7101c3 | ||
|
|
a1a97aea9c | ||
|
|
a338eb6e1b | ||
|
|
74b248f565 | ||
|
|
dae297a41f | ||
|
|
944fa35f83 | ||
|
|
d4196e4029 | ||
|
|
62866c93db | ||
|
|
db03cdcb3e | ||
|
|
0c28c3e5f7 | ||
|
|
bbe39f3d6a | ||
|
|
4e976caaaf | ||
|
|
ff804f9352 | ||
|
|
a3805c48d3 | ||
|
|
8eb2dc61d7 | ||
|
|
5b7bb3acc4 | ||
|
|
c001e317ec | ||
|
|
4eb02446aa | ||
|
|
84e092fce4 | ||
|
|
b84730877b | ||
|
|
bad9e0d9d8 | ||
|
|
10ea218b34 | ||
|
|
59f52608db | ||
|
|
239fc9754d | ||
|
|
f520534890 | ||
|
|
d91a7dd4a1 | ||
|
|
b3573891bf | ||
|
|
fb75cf91c3 | ||
|
|
15022515bf | ||
|
|
19c277b5b7 | ||
|
|
0af89b9516 | ||
|
|
0450bc5e9b | ||
|
|
2ca2840c3c | ||
|
|
192d516acd | ||
|
|
d636097845 | ||
|
|
e5b1c209de | ||
|
|
61ddf10df5 | ||
|
|
96533db864 | ||
|
|
2f84e53257 | ||
|
|
bb760c5db4 | ||
|
|
078b708c8a | ||
|
|
edb14da576 | ||
|
|
a321edde1c | ||
|
|
449a89a6f3 | ||
|
|
246605e3ca | ||
|
|
2ddc36bada | ||
|
|
1e512bac09 | ||
|
|
60ec5d2520 | ||
|
|
a790762071 | ||
|
|
07709873c9 | ||
|
|
fa06b40a97 | ||
|
|
537a66ad5d | ||
|
|
0b618e57a9 | ||
|
|
28c6afa9ba | ||
|
|
0ccc29bfc6 | ||
|
|
85ea4c0000 | ||
|
|
9c21e1f3c1 | ||
|
|
1ba2a44dde | ||
|
|
3a2d125bb2 | ||
|
|
de8d2f78c2 | ||
|
|
e55d6f3189 | ||
|
|
ad5449488f | ||
|
|
5d9caa27fe | ||
|
|
22193b1872 | ||
|
|
306e7b5201 | ||
|
|
0c8104af96 | ||
|
|
3b06f7a105 | ||
|
|
0869623c1d | ||
|
|
bfa79a6f68 | ||
|
|
cda842485f | ||
|
|
db35306a26 | ||
|
|
2363861a62 | ||
|
|
2960585250 | ||
|
|
ff830ee50d | ||
|
|
3c917da56a | ||
|
|
6ea1d9ec73 | ||
|
|
5ebd7cbb63 | ||
|
|
0d91444ee2 | ||
|
|
9459c78e7c | ||
|
|
b326a7148d | ||
|
|
5ae850dbe8 | ||
|
|
2ef9c9c68e | ||
|
|
613bd000d2 | ||
|
|
f7ea5bcb7b | ||
|
|
b8b5b8ae9b | ||
|
|
e6085a64be | ||
|
|
e77acf9b1e | ||
|
|
dc4299d315 | ||
|
|
1c8d2ba19e | ||
|
|
d46455c021 | ||
|
|
abd4c24138 | ||
|
|
1e922ae074 | ||
|
|
335059a681 | ||
|
|
a530991119 | ||
|
|
45130b5f85 | ||
|
|
70fd411640 | ||
|
|
4d47cd4a4a | ||
|
|
bb694ca9cb | ||
|
|
7ede8424d0 | ||
|
|
1167cf51b5 | ||
|
|
13d2c81c5c | ||
|
|
102a34a3f3 | ||
|
|
0c490f2bdb | ||
|
|
8f69f9cef9 | ||
|
|
00e65a39de | ||
|
|
8698c6099c | ||
|
|
da3fbdb401 | ||
|
|
b28052c46c | ||
|
|
b438a7f03d | ||
|
|
c144035d95 | ||
|
|
7658c3980a | ||
|
|
c11297a733 | ||
|
|
c89e60adf6 | ||
|
|
fa33f61183 | ||
|
|
f3e57d816c | ||
|
|
bf2e1eface | ||
|
|
a69fff9b53 | ||
|
|
9fc0038bd5 | ||
|
|
ffd8dd5633 | ||
|
|
c3947b0f6a | ||
|
|
84b17b17fe | ||
|
|
3956a1123e | ||
|
|
f04185f6fd | ||
|
|
10d188ee40 | ||
|
|
63e2d3740f | ||
|
|
672173437f | ||
|
|
2c851158f5 | ||
|
|
367733676a | ||
|
|
538cd2078b | ||
|
|
731833d9be | ||
|
|
7ebd71283f | ||
|
|
5eb1942ad2 | ||
|
|
999222f3a4 | ||
|
|
e82d726898 | ||
|
|
0f48c75512 | ||
|
|
183d3ea666 | ||
|
|
4e4e9123f2 | ||
|
|
c09566d38a | ||
|
|
750ac6f2ec | ||
|
|
1ed78fa680 | ||
|
|
48a26cf1f7 | ||
|
|
bc6a63a1f0 | ||
|
|
45f360904f | ||
|
|
bdf39a1dd7 | ||
|
|
abff198d01 | ||
|
|
6721ecf113 | ||
|
|
3899c71317 | ||
|
|
b8e4ec9c9f | ||
|
|
92cb120dd0 | ||
|
|
7a8f9c38eb | ||
|
|
dc7498e505 | ||
|
|
02e925f77a | ||
|
|
7dc2a479b6 | ||
|
|
1913d629e7 | ||
|
|
59a750f41c | ||
|
|
46dfafe0b2 | ||
|
|
1faab05032 | ||
|
|
c895f0db56 | ||
|
|
c622df96f0 | ||
|
|
30ac13ec73 | ||
|
|
efbe809982 | ||
|
|
92df5dd36a | ||
|
|
ebdf13678a | ||
|
|
0a0d46033c | ||
|
|
7f72565e24 | ||
|
|
cfcf1e2fcb | ||
|
|
faf2f7a38c | ||
|
|
ace7e5abdd | ||
|
|
f50e547980 | ||
|
|
c05a303568 | ||
|
|
4fcd41d3cc | ||
|
|
3e6c4c1b13 | ||
|
|
55d356ea40 | ||
|
|
a752421efe | ||
|
|
aa4b41612f | ||
|
|
f576228c2d | ||
|
|
c93add3b83 | ||
|
|
117446cebb | ||
|
|
4bee5ec6c8 | ||
|
|
92776c9e8d | ||
|
|
eea3fb5c54 | ||
|
|
dbd0727af2 | ||
|
|
a4cf49754a | ||
|
|
587c5991f1 | ||
|
|
10234ffcef | ||
|
|
2a2710e391 | ||
|
|
f1b5423d94 | ||
|
|
1cb916d16a | ||
|
|
6396120fb0 | ||
|
|
2fe5157519 | ||
|
|
4ce480defb | ||
|
|
3ea5d16f19 | ||
|
|
31fc58576c | ||
|
|
9573799eba | ||
|
|
df94e94b31 | ||
|
|
98b561b186 | ||
|
|
8ee469343c | ||
|
|
e54d129577 | ||
|
|
a7a3237f4f | ||
|
|
4b17c3963f | ||
|
|
93925a03b9 | ||
|
|
a74707ee82 |
No files matched your search
@@ -4,11 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- staging
|
||||
- trying
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
permissions:
|
||||
actions: none
|
||||
checks: none
|
||||
@@ -30,45 +26,60 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
flavor: [Foss, Gplay]
|
||||
flavor: [fdroidLegacy, foss, gplay]
|
||||
steps:
|
||||
- uses: actions/checkout@v7.0.0
|
||||
- uses: gradle/actions/wrapper-validation@v6.2.0
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Ensure sentence-cased flavor variables
|
||||
run: |
|
||||
flavor="$(echo ${{ matrix.flavor }})"
|
||||
echo "sentenceCasedFlavor=${flavor^}" >> "$GITHUB_ENV"
|
||||
- uses: gradle/actions/wrapper-validation@v6.3.0
|
||||
- name: set up OpenJDK 25
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y openjdk-25-jdk-headless
|
||||
sudo update-alternatives --auto java
|
||||
- name: Build
|
||||
run: ./gradlew assemble${{ matrix.flavor }}Release
|
||||
run: ./gradlew assemble${sentenceCasedFlavor}Release
|
||||
- name: Check for non-free libraries
|
||||
run: |
|
||||
wget -q https://codeberg.org/Katastima/apkscanner/releases/download/v0.0.7/apk-scanner_v0.0.7.jar
|
||||
java -jar apk-scanner_v0.0.7.jar scan-apk "$(echo 'app/build/outputs/apk/${{ matrix.flavor }}/release/app-${{ matrix.flavor }}-release-unsigned.apk' | tr '[:upper:]' '[:lower:]')"
|
||||
# Get Katastima APK scanner
|
||||
wget -q https://codeberg.org/Katastima/apkscanner/releases/download/v0.0.9/apk-scanner_v0.0.9.jar
|
||||
# Print pretty result
|
||||
java -jar apk-scanner_v0.0.9.jar scan-apk "$(echo 'app/build/outputs/apk/${{ matrix.flavor }}/release/app-${{ matrix.flavor }}-release-unsigned.apk')"
|
||||
# Check for offending libraries
|
||||
# Exit code 4 = no result, which is what we want
|
||||
set +e
|
||||
java -jar apk-scanner_v0.0.9.jar scan-apk --json=yes "$(echo 'app/build/outputs/apk/${{ matrix.flavor }}/release/app-${{ matrix.flavor }}-release-unsigned.apk')" | jq --exit-status '.libraryCheckResult.offendingLibraries[] | select((.libraryId == "/org/acra" and .license == "Apache-2.0")|not)'
|
||||
if [ "$?" -ne 4 ]; then
|
||||
echo "Bad libraries found!"
|
||||
exit 1
|
||||
fi
|
||||
set -e
|
||||
- name: Check lint
|
||||
run: ./gradlew lint${{ matrix.flavor }}Release
|
||||
run: ./gradlew lint${sentenceCasedFlavor}Release
|
||||
- name: Run unit tests
|
||||
run: timeout 5m ./gradlew test${{ matrix.flavor }}DebugUnitTest || { ./gradlew --stop && timeout 5m ./gradlew test${{ matrix.flavor }}DebugUnitTest; }
|
||||
run: timeout 5m ./gradlew test${sentenceCasedFlavor}DebugUnitTest || { ./gradlew --stop && timeout 5m ./gradlew test${sentenceCasedFlavor}DebugUnitTest; }
|
||||
- name: Enable KVM
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
- name: Run instrumented tests (API 23)
|
||||
uses: ReactiveCircus/android-emulator-runner@v2.37.0
|
||||
uses: ReactiveCircus/android-emulator-runner@v2.38.0
|
||||
with:
|
||||
api-level: 23
|
||||
arch: x86_64
|
||||
script: ./gradlew connected${{ matrix.flavor }}DebugAndroidTest
|
||||
script: ./gradlew connected${sentenceCasedFlavor}DebugAndroidTest
|
||||
- name: Run instrumented tests (API 35)
|
||||
uses: ReactiveCircus/android-emulator-runner@v2.37.0
|
||||
uses: ReactiveCircus/android-emulator-runner@v2.38.0
|
||||
with:
|
||||
api-level: 35
|
||||
arch: x86_64
|
||||
script: ./gradlew connected${{ matrix.flavor }}DebugAndroidTest
|
||||
script: ./gradlew connected${sentenceCasedFlavor}DebugAndroidTest
|
||||
- name: Archive test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: test-results-flavor${{ matrix.flavor }}
|
||||
name: test-results-flavor-${{ matrix.flavor }}
|
||||
path: app/build/reports
|
||||
@@ -19,9 +19,9 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
id: checkout
|
||||
uses: actions/checkout@v7.0.0
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6.3.0
|
||||
uses: actions/setup-python@v7.0.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Run converter script
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
id: checkout
|
||||
uses: actions/checkout@v7.0.0
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Update contributors
|
||||
id: update_contributors
|
||||
uses: TheLastProject/contributors-to-file-action@v3.2.0
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
generate-feature-graphic:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7.0.0
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Install requirements
|
||||
run: |
|
||||
sudo apt-get update
|
||||
|
||||
@@ -4,11 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- staging
|
||||
- trying
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
permissions:
|
||||
actions: none
|
||||
checks: none
|
||||
@@ -27,7 +23,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7.0.0
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- 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
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
update-locales:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7.0.0
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Add new locales
|
||||
run: .scripts/new-locales.py
|
||||
- name: Update locales
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Android Wear CI
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
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
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-25-openjdk-amd64
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- uses: gradle/actions/wrapper-validation@v6.3.0
|
||||
- name: set up OpenJDK 25
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y openjdk-25-jdk-headless
|
||||
sudo update-alternatives --auto java
|
||||
- name: Build
|
||||
run: ./gradlew :wear:assembleRelease
|
||||
- name: Check for non-free libraries
|
||||
run: |
|
||||
# Get Katastima APK scanner
|
||||
wget -q https://codeberg.org/Katastima/apkscanner/releases/download/v0.0.9/apk-scanner_v0.0.9.jar
|
||||
# Print pretty result
|
||||
java -jar apk-scanner_v0.0.9.jar scan-apk "wear/build/outputs/apk/release/wear-release-unsigned.apk"
|
||||
# Check for offending libraries
|
||||
# Exit code 4 = no result, which is what we want
|
||||
set +e
|
||||
java -jar apk-scanner_v0.0.9.jar scan-apk --json=yes "wear/build/outputs/apk/release/wear-release-unsigned.apk" | jq --exit-status '.libraryCheckResult.offendingLibraries[]'
|
||||
if [ "$?" -ne 4 ]; then
|
||||
echo "Bad libraries found!"
|
||||
exit 1
|
||||
fi
|
||||
set -e
|
||||
- name: Check lint
|
||||
run: ./gradlew :wear:lintRelease
|
||||
@@ -19,6 +19,8 @@
|
||||
/app/*.log
|
||||
/app/build
|
||||
/app/release
|
||||
/wear/build
|
||||
/wear/release
|
||||
/.idea/*
|
||||
!/.idea/icon.svg
|
||||
# Bundle
|
||||
@@ -28,3 +30,4 @@
|
||||
|
||||
# Catima-specific
|
||||
SHA256SUMS
|
||||
/shared/build
|
||||
@@ -15,7 +15,7 @@ with open('CHANGELOG.md') as changelog:
|
||||
changelogs[version_code] = text
|
||||
|
||||
text = []
|
||||
match = re.match("## \S* - (\d*).*", line)
|
||||
match = re.match("## .+ - (\d*).*", line)
|
||||
if not match:
|
||||
raise ValueError(f"Invalid version line: {line}")
|
||||
version_code = match.group(1)
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased (Android) - 1003
|
||||
|
||||
- Fix list widget not updating when opening a card from it
|
||||
- Fix opening pkpass/pdf files from network locations sometimes failing
|
||||
|
||||
## v1.0.1 (Wear OS) - 201 (2026-08-28)
|
||||
|
||||
- Add scroll bar to card list
|
||||
- Add splash screen
|
||||
- Fix some padding issues
|
||||
- Remove barcode value from card view
|
||||
|
||||
## v2.45.0 (Android) - 1002 (2026-08-12)
|
||||
|
||||
- Add a Bluetooth server for Wear OS companion app support
|
||||
- Remove embedded CHANGELOG
|
||||
|
||||
## v1.0.0 (Wear OS) - 200 (2026-08-12)
|
||||
|
||||
- Initial release of Wear OS companion app
|
||||
|
||||
## v2.44.0 - 168 (2026-07-28)
|
||||
|
||||
- Add option to open image in internal image viewer in gallery
|
||||
|
||||
## v2.43.0 - 167 (2026-07-12)
|
||||
|
||||
- Add support for showing the card ID in the card list
|
||||
|
||||
@@ -8,8 +8,8 @@ GEM
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1249.0)
|
||||
aws-sdk-core (3.247.0)
|
||||
aws-partitions (1.1269.0)
|
||||
aws-sdk-core (3.254.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
@@ -17,11 +17,11 @@ GEM
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.125.0)
|
||||
aws-sdk-core (~> 3, >= 3.247.0)
|
||||
aws-sdk-kms (1.130.0)
|
||||
aws-sdk-core (~> 3, >= 3.254.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.222.0)
|
||||
aws-sdk-core (~> 3, >= 3.247.0)
|
||||
aws-sdk-s3 (1.227.0)
|
||||
aws-sdk-core (~> 3, >= 3.254.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.12.1)
|
||||
@@ -42,8 +42,9 @@ GEM
|
||||
domain_name (0.6.20240107)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.112.0)
|
||||
faraday (1.10.5)
|
||||
excon (1.5.0)
|
||||
logger
|
||||
faraday (1.10.6)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
@@ -72,22 +73,22 @@ GEM
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.4.1)
|
||||
fastlane (2.234.0)
|
||||
fastlane (2.237.0)
|
||||
CFPropertyList (>= 2.3, < 5.0.0)
|
||||
abbrev (~> 0.1)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
addressable (>= 2.9.0, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.197)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
base64 (~> 0.2)
|
||||
benchmark (>= 0.1.0)
|
||||
bundler (>= 1.17.3, < 5.0.0)
|
||||
bundler (>= 2.4.0, < 5.0.0)
|
||||
colored (~> 1.2)
|
||||
commander (~> 4.6)
|
||||
csv (~> 3.3)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
excon (>= 0.71.0, < 2.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
@@ -96,14 +97,15 @@ GEM
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-env (>= 1.6.0, <= 2.1.1)
|
||||
google-cloud-env (>= 1.6.0, < 2.3.0)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
jwt (>= 2.10.3, < 4)
|
||||
logger (>= 1.6, < 2.0)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multi_json (~> 1.12)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
mutex_m (~> 0.3)
|
||||
naturally (~> 2.2)
|
||||
@@ -124,7 +126,7 @@ GEM
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-sirp (1.1.0)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.100.0)
|
||||
google-apis-androidpublisher_v3 (0.104.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-core (0.18.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
@@ -134,19 +136,20 @@ GEM
|
||||
mutex_m
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
google-apis-iamcredentials_v1 (0.27.0)
|
||||
google-apis-iamcredentials_v1 (0.28.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.17.0)
|
||||
google-apis-playcustomapp_v1 (0.18.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-storage_v1 (0.62.0)
|
||||
google-apis-storage_v1 (0.64.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-cloud-core (1.8.0)
|
||||
google-cloud-core (1.9.0)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (2.1.1)
|
||||
google-cloud-env (2.2.2)
|
||||
base64 (~> 0.2)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-errors (1.6.0)
|
||||
google-cloud-storage (1.60.0)
|
||||
google-cloud-errors (1.7.0)
|
||||
google-cloud-storage (1.62.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-core (>= 0.18, < 2)
|
||||
@@ -155,12 +158,14 @@ GEM
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (~> 1.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.11.2)
|
||||
google-logging-utils (0.2.0)
|
||||
googleauth (1.17.1)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-env (~> 2.1)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
google-cloud-env (~> 2.2)
|
||||
google-logging-utils (~> 0.1)
|
||||
jwt (>= 1.4, < 4.0)
|
||||
os (>= 0.9, < 2.0)
|
||||
pstore (~> 0.1)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.8)
|
||||
@@ -168,8 +173,8 @@ GEM
|
||||
httpclient (2.9.0)
|
||||
mutex_m
|
||||
jmespath (1.6.2)
|
||||
json (2.19.5)
|
||||
jwt (2.10.2)
|
||||
json (2.21.1)
|
||||
jwt (3.2.0)
|
||||
base64
|
||||
logger (1.7.0)
|
||||
mini_magick (4.13.2)
|
||||
@@ -179,28 +184,28 @@ GEM
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.4.0)
|
||||
naturally (2.3.0)
|
||||
nkf (0.2.0)
|
||||
nkf (0.3.0)
|
||||
optparse (0.8.1)
|
||||
os (1.1.4)
|
||||
ostruct (0.6.3)
|
||||
plist (3.7.2)
|
||||
pstore (0.2.1)
|
||||
public_suffix (7.0.5)
|
||||
rake (13.4.2)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.4.1)
|
||||
retriable (3.8.0)
|
||||
rexml (3.4.4)
|
||||
rouge (3.28.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.4.1)
|
||||
security (0.1.5)
|
||||
signet (0.21.0)
|
||||
signet (0.22.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 4.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
@@ -215,12 +220,14 @@ GEM
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.27.0)
|
||||
xcodeproj (1.28.1)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
base64
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.4.0)
|
||||
nkf
|
||||
rexml (>= 3.3.6, < 4.0)
|
||||
xcpretty (0.4.1)
|
||||
rouge (~> 3.28.0)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
**Last updated**
|
||||
April 29 2026
|
||||
August 7 2026
|
||||
|
||||
# Privacy Policy
|
||||
Catima does not collect or transmit any personal information.
|
||||
|
||||
To ensure correct app functionality, we require access to the following:
|
||||
|
||||
- Bluetooth: When using Catima together with the Wear OS companion app, Bluetooth access is needed to be able to communicate to Wear OS. This may show up as "find, connect to, and determine the relative position of nearby devices" because Android considers Bluetooth as part of location, but location information is never collected or used. The app can still be used when Bluetooth permission is denied, but you will not be able to use the Wear OS companion app.
|
||||
- Camera: We need access to your camera to be able to scan barcodes. The app can still be used when camera access is denied, but you will have to manually type the barcode information.
|
||||
- NFC: We need NFC access to temporarily disable NFC while showing a barcode to prevent NFC payment apps from triggering (such as when using a check-in gates which support both barcodes and NFC payments). This functionality can be disabled in settings.
|
||||
- Storage (Android 5 and 6 only): We need access to your device storage to create or import backups. The app can still be used when storage access is denied, but you will not be able to create or import backups.
|
||||
|
||||
@@ -19,8 +19,8 @@ android {
|
||||
applicationId = "me.hackerchick.catima"
|
||||
minSdk = 23
|
||||
targetSdk = 36
|
||||
versionCode = 167
|
||||
versionName = "2.43.0"
|
||||
versionCode = 1002
|
||||
versionName = "2.45.0"
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
multiDexEnabled = true
|
||||
@@ -59,8 +59,14 @@ android {
|
||||
dimension = "type"
|
||||
isDefault = true
|
||||
}
|
||||
create("fdroidLegacy") {
|
||||
dimension = "type"
|
||||
versionNameSuffix = " (F-Droid)"
|
||||
versionCode = defaultConfig.versionCode!! - 1
|
||||
}
|
||||
create("gplay") {
|
||||
dimension = "type"
|
||||
versionNameSuffix = " (Google)"
|
||||
|
||||
// Google doesn't allow donation links
|
||||
buildConfigField("boolean", "showDonate", "false")
|
||||
@@ -113,6 +119,8 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":shared"))
|
||||
|
||||
// AndroidX
|
||||
implementation(libs.androidx.appcompat.appcompat)
|
||||
implementation(libs.androidx.constraintlayout.constraintlayout)
|
||||
@@ -165,7 +173,6 @@ dependencies {
|
||||
|
||||
tasks.register("copyRawResFiles", Copy::class) {
|
||||
from(
|
||||
layout.projectDirectory.file("../CHANGELOG.md"),
|
||||
layout.projectDirectory.file("../PRIVACY.md")
|
||||
)
|
||||
into(layout.projectDirectory.dir("src/main/res/raw"))
|
||||
@@ -173,7 +180,7 @@ tasks.register("copyRawResFiles", Copy::class) {
|
||||
}.also {
|
||||
tasks.preBuild.dependsOn(it)
|
||||
tasks.getByName<Delete>("clean") {
|
||||
val filesNamesToDelete = listOf("CHANGELOG", "PRIVACY")
|
||||
val filesNamesToDelete = listOf("PRIVACY")
|
||||
filesNamesToDelete.forEach { fileName ->
|
||||
delete(layout.projectDirectory.file("src/main/res/raw/${fileName.lowercase()}.md"))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Ladění</string>
|
||||
<string name="app_name">Catima (ladicí verze)</string>
|
||||
</resources>
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
<string name="app_name">Catima silumine ja veaotsing</string>
|
||||
</resources>
|
||||
@@ -13,6 +13,12 @@
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="23" />
|
||||
|
||||
<uses-feature
|
||||
@@ -226,5 +232,26 @@
|
||||
<action android:name="android.service.controls.ControlsProviderService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".wearos.BluetoothServerService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="connectedDevice" />
|
||||
|
||||
<activity
|
||||
android:name=".wearos.BluetoothPairingActivity"
|
||||
android:exported="false"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/AppTheme.Transparent" />
|
||||
|
||||
<receiver
|
||||
android:name=".wearos.BluetoothPairingReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="protect.card_locker.wearos.action.ALLOW_DEVICE" />
|
||||
<action android:name="protect.card_locker.wearos.action.BLOCK_DEVICE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -69,16 +69,7 @@ fun AboutScreenContent(
|
||||
stringResource(R.string.version_history),
|
||||
content.versionHistory,
|
||||
modifier = Modifier.testTag("card_version_history"),
|
||||
onClickUrl = "https://catima.app/changelog/",
|
||||
onClickDialogText = AnnotatedString.fromHtml(
|
||||
htmlString = content.historyHtml,
|
||||
linkStyles = TextLinkStyles(
|
||||
style = SpanStyle(
|
||||
textDecoration = TextDecoration.Underline,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
)
|
||||
)
|
||||
onClickUrl = "https://catima.app/changelog/"
|
||||
)
|
||||
CatimaAboutSection(
|
||||
stringResource(R.string.credits),
|
||||
|
||||
@@ -62,18 +62,6 @@ public class AboutContent {
|
||||
return contributors.replace("\n", "<br />");
|
||||
}
|
||||
|
||||
public String getHistoryHtml() {
|
||||
String versionHistory;
|
||||
try {
|
||||
versionHistory = Utils.readTextFile(context, R.raw.changelog)
|
||||
.replace("# Changelog\n\n", "");
|
||||
} catch (IOException ignored) {
|
||||
return "";
|
||||
}
|
||||
return Utils.linkify(Utils.basicMDToHTML(versionHistory))
|
||||
.replace("\n", "<br />");
|
||||
}
|
||||
|
||||
public String getLicenseHtml() {
|
||||
try {
|
||||
return Utils.readTextFile(context, R.raw.license);
|
||||
|
||||
@@ -14,6 +14,7 @@ import android.widget.RemoteViews
|
||||
import androidx.core.widget.RemoteViewsCompat
|
||||
import protect.card_locker.DBHelper.LoyaltyCardArchiveFilter
|
||||
import protect.card_locker.cardview.LoyaltyCardViewActivity
|
||||
import protect.card_locker.shared.ForegroundColorHelper
|
||||
|
||||
class ListWidget : AppWidgetProvider() {
|
||||
fun updateAll(context: Context) {
|
||||
@@ -100,7 +101,7 @@ class ListWidget : AppWidgetProvider() {
|
||||
// Note: Android 5 will not use bitmaps
|
||||
val remoteViews = RemoteViews(context.packageName, R.layout.list_widget_item).apply {
|
||||
val headerColor = Utils.getHeaderColor(context, loyaltyCard)
|
||||
val foreground = if (Utils.needsDarkForeground(headerColor)) Color.BLACK else Color.WHITE
|
||||
val foreground = if (ForegroundColorHelper.needsDarkForeground(headerColor)) Color.BLACK else Color.WHITE
|
||||
setInt(R.id.item_container_foreground, "setBackgroundColor", headerColor)
|
||||
val icon = loyaltyCard.getImageThumbnail(context)
|
||||
// FIXME: The icon flow causes a crash up to Android 12L, so force anything below 33 down this path
|
||||
|
||||
@@ -91,6 +91,7 @@ import java.util.concurrent.Callable;
|
||||
import protect.card_locker.async.TaskHandler;
|
||||
import protect.card_locker.databinding.LayoutChipChoiceBinding;
|
||||
import protect.card_locker.databinding.LoyaltyCardEditActivityBinding;
|
||||
import protect.card_locker.shared.ForegroundColorHelper;
|
||||
import protect.card_locker.viewmodels.LoyaltyCardEditActivityViewModel;
|
||||
|
||||
public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements BarcodeImageWriterResultCallback, ColorPickerDialogListener {
|
||||
@@ -896,8 +897,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
Integer headerColor = viewModel.getLoyaltyCard().headerColor;
|
||||
if (headerColor != null) {
|
||||
thumbnail.setOnClickListener(new ChooseCardImage());
|
||||
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
|
||||
thumbnailEditIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
thumbnailEditIcon.setColorFilter(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
|
||||
}
|
||||
|
||||
onResuming = false;
|
||||
@@ -919,18 +920,18 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
setLoyaltyCardHeaderColor(headerColor);
|
||||
|
||||
thumbnail.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
thumbnail.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
|
||||
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
|
||||
thumbnailEditIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
thumbnailEditIcon.setColorFilter(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
|
||||
} else {
|
||||
generateIcon(storeFieldEdit.getText().toString().trim());
|
||||
|
||||
Integer headerColor = viewModel.getLoyaltyCard().headerColor;
|
||||
|
||||
if (headerColor != null) {
|
||||
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
|
||||
thumbnailEditIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
thumbnailEditIcon.setColorFilter(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
import org.acra.ACRA;
|
||||
import org.acra.config.CoreConfigurationBuilder;
|
||||
import org.acra.config.DialogConfigurationBuilder;
|
||||
@@ -11,6 +11,7 @@ import org.acra.config.MailSenderConfigurationBuilder;
|
||||
import org.acra.data.StringFormat;
|
||||
|
||||
import protect.card_locker.preferences.Settings;
|
||||
import protect.card_locker.wearos.WearSyncServiceManager;
|
||||
|
||||
public class LoyaltyCardLockerApplication extends Application {
|
||||
|
||||
@@ -41,5 +42,10 @@ public class LoyaltyCardLockerApplication extends Application {
|
||||
// Set theme
|
||||
Settings settings = new Settings(this);
|
||||
AppCompatDelegate.setDefaultNightMode(settings.getTheme());
|
||||
|
||||
// Start Bluetooth server for Wear OS companion if enabled.
|
||||
// The service checks BLUETOOTH_CONNECT itself and stops if the permission is missing.
|
||||
// The permission is requested from the launcher Activity when the UI resumes.
|
||||
WearSyncServiceManager.INSTANCE.synchronize(this, null);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,9 @@ 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.content.edit
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
@@ -37,9 +39,12 @@ 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 protect.card_locker.wearos.WearSyncPermissionRequester
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import androidx.core.content.edit
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
private lateinit var binding: MainActivityBinding
|
||||
@@ -61,6 +66,7 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
private lateinit var mUpdateLoyaltyCardListRunnable: Runnable
|
||||
private lateinit var mBarcodeScannerLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var mSettingsLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var mWearSyncPermissionRequester: WearSyncPermissionRequester
|
||||
|
||||
private val mCurrentActionModeCallback: ActionMode.Callback = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(inputMode: ActionMode, inputMenu: Menu?): Boolean {
|
||||
@@ -318,6 +324,8 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
}
|
||||
}
|
||||
|
||||
mWearSyncPermissionRequester = WearSyncPermissionRequester(this, this)
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (mSearchView != null && !mSearchView!!.isIconified) {
|
||||
@@ -394,6 +402,8 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
val settings = Settings(this)
|
||||
layoutManager.setSpanCount(settings.getPreferredColumnCount())
|
||||
}
|
||||
|
||||
mWearSyncPermissionRequester.synchronize()
|
||||
}
|
||||
|
||||
private fun displayCardSetupOptions(menu: Menu, shouldShow: Boolean) {
|
||||
@@ -467,11 +477,7 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
ShortcutHelper.updateShortcuts(mAdapter.mContext)
|
||||
}
|
||||
|
||||
private fun processParseResultList(
|
||||
parseResultList: MutableList<ParseResult?>,
|
||||
group: String?,
|
||||
closeAppOnNoBarcode: Boolean
|
||||
) {
|
||||
private fun processParseResultList(parseResultList: MutableList<ParseResult?>) {
|
||||
require(!parseResultList.isEmpty()) { "parseResultList may not be empty" }
|
||||
|
||||
Utils.makeUserChooseParseResultFromList(
|
||||
@@ -482,17 +488,12 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
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()
|
||||
}
|
||||
finish()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -505,13 +506,11 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
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))
|
||||
processParseResultList(mutableListOf(ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard)))
|
||||
} else {
|
||||
// Parse whatever file was sent, regardless of opening or sharing
|
||||
val data: Uri? = when (receivedAction) {
|
||||
@@ -527,36 +526,54 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
importFile(data, receivedType)
|
||||
}
|
||||
}
|
||||
|
||||
private fun importFile(data: Uri?, receivedType: String) {
|
||||
lifecycleScope.launch {
|
||||
val parseResultList: MutableList<ParseResult?> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
when {
|
||||
receivedType.startsWith("image/") ->
|
||||
Utils.retrieveBarcodesFromImage(this@MainActivity, data)
|
||||
|
||||
receivedType == "application/pdf" ->
|
||||
Utils.retrieveBarcodesFromPdf(this@MainActivity, data)
|
||||
|
||||
receivedType == "application/vnd.apple.pkpass" ||
|
||||
receivedType == "application/vnd-com.apple.pkpass" ->
|
||||
Utils.retrieveBarcodesFromPkPass(this@MainActivity, data)
|
||||
// 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
|
||||
receivedType == "application/vnd.espass-espass" ->
|
||||
Utils.retrieveBarcodesFromPkPass(this@MainActivity, data)
|
||||
|
||||
receivedType == "application/vnd.apple.pkpasses" ->
|
||||
Utils.retrieveBarcodesFromPkPasses(this@MainActivity, data)
|
||||
|
||||
else -> {
|
||||
Log.e(TAG, "Wrong mime-type")
|
||||
return@withContext null
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Parsing failed: $e");
|
||||
e.printStackTrace();
|
||||
Utils.showToast(this@MainActivity, R.string.errorReadingFile, Toast.LENGTH_LONG);
|
||||
return@withContext null;
|
||||
}
|
||||
} ?: return@launch
|
||||
|
||||
if (parseResultList.isEmpty()) {
|
||||
Utils.showToast(this@MainActivity, R.string.noBarcodeFound, Toast.LENGTH_LONG);
|
||||
finish()
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
||||
// Give up if we should parse but there is nothing to parse
|
||||
if (parseResultList == null || parseResultList.isEmpty()) {
|
||||
finish()
|
||||
return
|
||||
processParseResultList(parseResultList)
|
||||
}
|
||||
|
||||
processParseResultList(parseResultList, null, true)
|
||||
}
|
||||
|
||||
private fun extractIntentFields(intent: Intent) {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package protect.card_locker
|
||||
|
||||
object NotificationInfo {
|
||||
object WearBluetooth {
|
||||
const val NOTIFICATION_ID = 1001
|
||||
const val CHANNEL_ID = "catima_wear_bt"
|
||||
const val PAIRING_CHANNEL_ID = "catima_wear_bt_pairing"
|
||||
const val CRITICAL_ERROR_NOTIFICATION_ID = 1002
|
||||
const val CRITICAL_ERROR_CHANNEL_ID = "catima_wear_bt_critical_error"
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,8 @@ class PkpassesParser(context: Context, uri: Uri?) {
|
||||
throw IOException(context.getString(R.string.errorReadingFile))
|
||||
}
|
||||
|
||||
var hasPkpassFiles = false
|
||||
|
||||
try {
|
||||
mContext.contentResolver.openInputStream(uri).use { inputStream ->
|
||||
ZipInputStream(inputStream).use { zipInputStream ->
|
||||
@@ -42,6 +44,9 @@ class PkpassesParser(context: Context, uri: Uri?) {
|
||||
// Ignore non-pkpass files
|
||||
if (!localFileHeader.fileName.endsWith(".pkpass")) continue
|
||||
|
||||
// Mark as found
|
||||
hasPkpassFiles = true
|
||||
|
||||
// Extract .pkpass (.zip) inside .pkpasses to cache directory
|
||||
val tempFileName = "pkpassparser_" + System.currentTimeMillis() + "_" + localFileHeader.fileName
|
||||
val tempFile = Utils.copyToTempFile(mContext, zipInputStream, tempFileName)
|
||||
@@ -61,6 +66,11 @@ class PkpassesParser(context: Context, uri: Uri?) {
|
||||
} catch (e: Exception) {
|
||||
throw e
|
||||
}
|
||||
|
||||
if (!hasPkpassFiles) {
|
||||
Log.d(TAG, "No pkpass files found")
|
||||
throw IOException(mContext.getString(R.string.errorReadingFile))
|
||||
}
|
||||
}
|
||||
|
||||
fun getPkpassParsers(): ArrayList<PkpassParser> {
|
||||
|
||||
@@ -30,6 +30,7 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.zxing.DecodeHintType
|
||||
import com.google.zxing.ResultPoint
|
||||
@@ -37,6 +38,9 @@ import com.journeyapps.barcodescanner.BarcodeCallback
|
||||
import com.journeyapps.barcodescanner.BarcodeResult
|
||||
import com.journeyapps.barcodescanner.CaptureManager
|
||||
import com.journeyapps.barcodescanner.DecoratedBarcodeView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import protect.card_locker.databinding.CustomBarcodeScannerBinding
|
||||
import protect.card_locker.databinding.ScanActivityBinding
|
||||
|
||||
@@ -322,15 +326,29 @@ class ScanActivity : CatimaAppCompatActivity() {
|
||||
private fun handleActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||
super.onActivityResult(resultCode, resultCode, intent)
|
||||
|
||||
val parseResultList: List<ParseResult> =
|
||||
Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this)
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val parseResultList =
|
||||
Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this@ScanActivity)
|
||||
|
||||
if (parseResultList.isEmpty()) {
|
||||
setScannerActive(true)
|
||||
return
|
||||
withContext(Dispatchers.Main) {
|
||||
if (parseResultList == null) {
|
||||
Utils.showToast(this@ScanActivity, R.string.errorReadingFile, Toast.LENGTH_LONG)
|
||||
setScannerActive(true)
|
||||
return@withContext
|
||||
}
|
||||
|
||||
if (parseResultList.isEmpty()) {
|
||||
Utils.showToast(this@ScanActivity, R.string.noBarcodeFound, Toast.LENGTH_LONG)
|
||||
setScannerActive(true)
|
||||
return@withContext
|
||||
}
|
||||
|
||||
processParseResultList(parseResultList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun processParseResultList(parseResultList: List<ParseResult>) {
|
||||
Utils.makeUserChooseParseResultFromList(
|
||||
this,
|
||||
parseResultList,
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import protect.card_locker.cardview.LoyaltyCardViewActivity;
|
||||
import protect.card_locker.shared.ForegroundColorHelper;
|
||||
|
||||
public class ShortcutHelper {
|
||||
/**
|
||||
@@ -102,7 +103,7 @@ public class ShortcutHelper {
|
||||
if (iconBitmap == null) {
|
||||
iconBitmap = Utils.generateIcon(context, loyaltyCard, true).getLetterTile();
|
||||
} else {
|
||||
iconBitmap = createAdaptiveBitmap(iconBitmap, Utils.needsDarkForeground(Utils.getHeaderColor(context, loyaltyCard)) ? Color.BLACK : Color.WHITE);
|
||||
iconBitmap = createAdaptiveBitmap(iconBitmap, ForegroundColorHelper.Companion.needsDarkForeground(Utils.getHeaderColor(context, loyaltyCard)) ? Color.BLACK : Color.WHITE);
|
||||
}
|
||||
|
||||
IconCompat icon = IconCompat.createWithAdaptiveBitmap(iconBitmap);
|
||||
|
||||
@@ -19,6 +19,8 @@ import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.Layout;
|
||||
@@ -39,7 +41,6 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RawRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
@@ -95,6 +96,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import protect.card_locker.preferences.Settings;
|
||||
import protect.card_locker.shared.ForegroundColorHelper;
|
||||
|
||||
public class Utils {
|
||||
private static final String TAG = "Catima";
|
||||
@@ -115,11 +117,19 @@ public class Utils {
|
||||
|
||||
public static final String CARD_IMAGE_FILENAME_REGEX = "^(card_)(\\d+)(_(?:front|back|icon)\\.png)$";
|
||||
|
||||
static final double LUMINANCE_MIDPOINT = 0.5;
|
||||
|
||||
static final int BITMAP_SIZE_SMALL = 512;
|
||||
static final int BITMAP_SIZE_BIG = 1600;
|
||||
|
||||
// Displays the toast immediately on the main UI loop, or asks Android to display it there otherwise.
|
||||
static public void showToast(Context context, int message, int duration) {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
Toast.makeText(context, message, duration).show();
|
||||
} else {
|
||||
new Handler(Looper.getMainLooper()).post(
|
||||
() -> Toast.makeText(context, message, duration).show());
|
||||
}
|
||||
}
|
||||
|
||||
static public LetterBitmap generateIcon(Context context, LoyaltyCard loyaltyCard, boolean forShortcut) {
|
||||
return generateIcon(context, loyaltyCard.store, loyaltyCard.headerColor, forShortcut);
|
||||
}
|
||||
@@ -147,57 +157,38 @@ public class Utils {
|
||||
}
|
||||
|
||||
return new LetterBitmap(context, store, store,
|
||||
tileLetterFontSize, pixelSize, pixelSize, backgroundColor, needsDarkForeground(backgroundColor) ? Color.BLACK : Color.WHITE);
|
||||
tileLetterFontSize, pixelSize, pixelSize, backgroundColor, ForegroundColorHelper.Companion.needsDarkForeground(backgroundColor) ? Color.BLACK : Color.WHITE);
|
||||
}
|
||||
|
||||
static public boolean needsDarkForeground(Integer backgroundColor) {
|
||||
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
|
||||
}
|
||||
|
||||
static public List<ParseResult> retrieveBarcodesFromImage(Context context, Uri uri) {
|
||||
static public List<ParseResult> retrieveBarcodesFromImage(Context context, Uri uri) throws FileNotFoundException {
|
||||
Log.i(TAG, "Received image file with possible barcode");
|
||||
|
||||
if (uri == null) {
|
||||
Log.e(TAG, "Uri did not contain any data");
|
||||
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
throw new FileNotFoundException("Uri did not contain any data");
|
||||
}
|
||||
|
||||
Bitmap bitmap;
|
||||
try {
|
||||
bitmap = retrieveImageFromUri(context, uri);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error getting data from image file");
|
||||
e.printStackTrace();
|
||||
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
throw new IllegalArgumentException("Error reading image file", e);
|
||||
}
|
||||
|
||||
List<ParseResult> barcodesFromBitmap = getBarcodesFromBitmap(bitmap);
|
||||
|
||||
if (barcodesFromBitmap.isEmpty()) {
|
||||
Log.i(TAG, "No barcode found in image file");
|
||||
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
return barcodesFromBitmap;
|
||||
// Returns barcodes or an empty list if nothing found
|
||||
return getBarcodesFromBitmap(bitmap);
|
||||
}
|
||||
|
||||
static public List<ParseResult> retrieveBarcodesFromPkPass(Context context, Uri uri) {
|
||||
static public List<ParseResult> retrieveBarcodesFromPkPass(Context context, Uri uri) throws FileNotFoundException {
|
||||
Log.i(TAG, "Received Pkpass file with possible barcode");
|
||||
if (uri == null) {
|
||||
Log.e(TAG, "Pkpass did not contain any data");
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
throw new FileNotFoundException("Uri did not contain any data");
|
||||
}
|
||||
|
||||
PkpassParser pkpassParser;
|
||||
try {
|
||||
pkpassParser = new PkpassParser(context, uri);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error reading pkpass file", e);
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
throw new IllegalArgumentException("Error reading pkpass file", e);
|
||||
}
|
||||
|
||||
List<String> locales = pkpassParser.listLocales();
|
||||
@@ -205,9 +196,7 @@ public class Utils {
|
||||
try {
|
||||
return Collections.singletonList(new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(null)));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error calling toLoyaltyCard on pkpass file", e);
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
throw new IllegalArgumentException("Error calling toLoyaltyCard on pkpass file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,9 +206,7 @@ public class Utils {
|
||||
try {
|
||||
parseResult = new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(locale));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error calling toLoyaltyCard on pkpass file", e);
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
throw new IllegalArgumentException("Error calling toLoyaltyCard on pkpass file", e);
|
||||
}
|
||||
parseResult.setNote(locale);
|
||||
parseResultList.add(parseResult);
|
||||
@@ -228,21 +215,17 @@ public class Utils {
|
||||
return parseResultList;
|
||||
}
|
||||
|
||||
static public List<ParseResult> retrieveBarcodesFromPkPasses(Context context, Uri uri) {
|
||||
static public List<ParseResult> retrieveBarcodesFromPkPasses(Context context, Uri uri) throws FileNotFoundException {
|
||||
Log.i(TAG, "Received Pkpasses file with possible barcode");
|
||||
if (uri == null) {
|
||||
Log.e(TAG, "Pkpasses did not contain any data");
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
throw new FileNotFoundException("Uri did not contain any data");
|
||||
}
|
||||
|
||||
PkpassesParser pkpassesParser;
|
||||
try {
|
||||
pkpassesParser = new PkpassesParser(context, uri);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error reading pkpasses file", e);
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
throw new IllegalArgumentException("Error reading pkpasses file", e);
|
||||
}
|
||||
|
||||
List<ParseResult> parseResultList = new ArrayList<>();
|
||||
@@ -254,9 +237,7 @@ public class Utils {
|
||||
try {
|
||||
parseResult = new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(null));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error calling toLoyaltyCard on pkpass file", e);
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
throw new IllegalArgumentException("Error calling toLoyaltyCard on pkpass file", e);
|
||||
}
|
||||
parseResult.setNote(String.format(context.getString(R.string.cardWithNumber), i+1));
|
||||
parseResultList.add(parseResult);
|
||||
@@ -265,9 +246,7 @@ public class Utils {
|
||||
try {
|
||||
parseResult = new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(locale));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error calling toLoyaltyCard on pkpass file", e);
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
throw new IllegalArgumentException("Error calling toLoyaltyCard on pkpass file", e);
|
||||
}
|
||||
parseResult.setNote(String.format(context.getString(R.string.cardWithNumberAndLocale), i+1, locale));
|
||||
parseResultList.add(parseResult);
|
||||
@@ -280,12 +259,10 @@ public class Utils {
|
||||
return parseResultList;
|
||||
}
|
||||
|
||||
static public List<ParseResult> retrieveBarcodesFromPdf(Context context, Uri uri) {
|
||||
static public List<ParseResult> retrieveBarcodesFromPdf(Context context, Uri uri) throws FileNotFoundException {
|
||||
Log.i(TAG, "Received PDF file with possible barcode");
|
||||
if (uri == null) {
|
||||
Log.e(TAG, "Uri did not contain any data");
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
throw new FileNotFoundException("Uri did not contain any data");
|
||||
}
|
||||
|
||||
ParcelFileDescriptor parcelFileDescriptor = null;
|
||||
@@ -320,8 +297,7 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error reading PDF file", e);
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
throw new IllegalArgumentException("Error reading PDF file", e);
|
||||
} finally {
|
||||
// Resource handling
|
||||
if (renderer != null) {
|
||||
@@ -336,10 +312,7 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
if (barcodesFromPdfPages.isEmpty()) {
|
||||
Log.i(TAG, "No barcode found in pdf file");
|
||||
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
// Returns barcodes or an empty list if nothing found
|
||||
return barcodesFromPdfPages;
|
||||
}
|
||||
|
||||
@@ -354,6 +327,7 @@ public class Utils {
|
||||
* @param context
|
||||
* @return List<ParseResult>
|
||||
*/
|
||||
@Nullable
|
||||
static public List<ParseResult> parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) {
|
||||
String contents;
|
||||
String format;
|
||||
@@ -362,28 +336,35 @@ public class Utils {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) {
|
||||
return retrieveBarcodesFromImage(context, intent.getData());
|
||||
}
|
||||
|
||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_PDF_FILE) {
|
||||
return retrieveBarcodesFromPdf(context, intent.getData());
|
||||
}
|
||||
|
||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_PKPASS_FILE) {
|
||||
Uri intentData = intent.getData();
|
||||
|
||||
if (intentData == null) {
|
||||
Log.e(TAG, "Uri did not contain any data");
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
try {
|
||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) {
|
||||
return retrieveBarcodesFromImage(context, intent.getData());
|
||||
}
|
||||
|
||||
if (Objects.equals(context.getContentResolver().getType(intentData), "application/vnd.apple.pkpasses")) {
|
||||
return retrieveBarcodesFromPkPasses(context, intentData);
|
||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_PDF_FILE) {
|
||||
return retrieveBarcodesFromPdf(context, intent.getData());
|
||||
}
|
||||
|
||||
return retrieveBarcodesFromPkPass(context, intentData);
|
||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_PKPASS_FILE) {
|
||||
Uri intentData = intent != null ? intent.getData() : null;
|
||||
|
||||
if (intentData == null) {
|
||||
throw new FileNotFoundException("Uri did not contain any data");
|
||||
}
|
||||
|
||||
// Mime type info is not always available. So we're going to first guess it's a pkpass file and if that fails, try pkpasses
|
||||
try {
|
||||
return retrieveBarcodesFromPkPass(context, intentData);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error reading pkpass file, attempting as pkpasses file", e);
|
||||
return retrieveBarcodesFromPkPasses(context, intentData);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Return null instead of an empty list to differentiate between error or no result
|
||||
Log.e(TAG, "Parsing failed: " + e);
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (requestCode == Utils.BARCODE_SCAN || requestCode == Utils.SELECT_BARCODE_REQUEST) {
|
||||
@@ -1070,7 +1051,7 @@ public class Utils {
|
||||
|
||||
if (icon != null) {
|
||||
// Use header colour to decide if this image will need a white or black background
|
||||
backgroundOrIcon.setBackgroundColor(needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
backgroundOrIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
|
||||
// Ensure correct cropping style
|
||||
backgroundOrIcon.setScaleType(Utils.getRecommendedScaleTypeForThumbnailImage(icon));
|
||||
@@ -1100,7 +1081,7 @@ public class Utils {
|
||||
// Actually set the text and colour
|
||||
textWhenNoImage.setVisibility(View.VISIBLE);
|
||||
textWhenNoImage.setText(loyaltyCard.store);
|
||||
textWhenNoImage.setTextColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
textWhenNoImage.setTextColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
}
|
||||
|
||||
return headerColor;
|
||||
|
||||
@@ -2,6 +2,7 @@ package protect.card_locker.cardimageview
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
@@ -17,8 +18,15 @@ import androidx.compose.foundation.gestures.transformable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
@@ -32,9 +40,11 @@ import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.core.content.FileProvider
|
||||
import protect.card_locker.CatimaComponentActivity
|
||||
import protect.card_locker.ImageLocationType
|
||||
import protect.card_locker.R
|
||||
@@ -68,6 +78,8 @@ class LoyaltyCardImageViewActivity : CatimaComponentActivity() {
|
||||
CatimaTheme {
|
||||
LoyaltyCardImageViewScreen(
|
||||
bitmap = bitmap,
|
||||
imageLocationType = imageLocationType,
|
||||
cardId = loyaltyCardId,
|
||||
contentDescriptionRes = imageLocationType.descriptionRes(),
|
||||
onBackPressedDispatcher = onBackPressedDispatcher
|
||||
)
|
||||
@@ -124,9 +136,12 @@ class LoyaltyCardImageViewActivity : CatimaComponentActivity() {
|
||||
@Composable
|
||||
fun LoyaltyCardImageViewScreen(
|
||||
bitmap: Bitmap,
|
||||
imageLocationType: ImageLocationType,
|
||||
cardId: Int,
|
||||
@StringRes contentDescriptionRes: Int,
|
||||
onBackPressedDispatcher: OnBackPressedDispatcher,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var scale by remember { mutableFloatStateOf(1F) }
|
||||
var offset by remember { mutableStateOf(Offset.Zero) }
|
||||
var size by remember { mutableStateOf(IntSize.Zero) }
|
||||
@@ -144,7 +159,24 @@ fun LoyaltyCardImageViewScreen(
|
||||
topBar = {
|
||||
CatimaTopAppBar(
|
||||
title = stringResource(contentDescriptionRes),
|
||||
onBackPressedDispatcher = onBackPressedDispatcher
|
||||
onBackPressedDispatcher = onBackPressedDispatcher,
|
||||
actions = {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
IconButton(onClick = { expanded = !expanded }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.MoreVert,
|
||||
contentDescription = stringResource(R.string.action_more_options)
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false }
|
||||
) {
|
||||
DropdownMenuItem(onClick = { openInImageGallery(context, cardId, imageLocationType) },
|
||||
text = { Text(stringResource(R.string.open_in_gallery)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
@@ -196,4 +228,13 @@ private fun Offset.clampTo(size: IntSize, scale: Float): Offset {
|
||||
)
|
||||
}
|
||||
|
||||
private const val MAX_IMAGE_SCALE = 5F
|
||||
private fun openInImageGallery(context: Context, cardId: Int, imageLocationType: ImageLocationType){
|
||||
val imagePath = Utils.getCardImageFileName(cardId, imageLocationType)
|
||||
val viewInGalleryIntent = Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(FileProvider.getUriForFile(context, context.packageName, context.getFileStreamPath(imagePath)), "image/*")
|
||||
flags = FLAG_GRANT_READ_URI_PERMISSION
|
||||
}
|
||||
context.startActivity(viewInGalleryIntent)
|
||||
}
|
||||
|
||||
private const val MAX_IMAGE_SCALE = 5F
|
||||
@@ -54,6 +54,7 @@ import protect.card_locker.cardimageview.LoyaltyCardImageViewActivity;
|
||||
import protect.card_locker.databinding.LoyaltyCardViewLayoutBinding;
|
||||
import protect.card_locker.preferences.Settings;
|
||||
import protect.card_locker.preferences.SettingsActivity;
|
||||
import protect.card_locker.shared.ForegroundColorHelper;
|
||||
|
||||
public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements BarcodeImageWriterResultCallback {
|
||||
private LoyaltyCardViewLayoutBinding binding;
|
||||
@@ -95,6 +96,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
private long initTime = System.currentTimeMillis();
|
||||
|
||||
private final ListWidget listWidget = new ListWidget();
|
||||
|
||||
private enum AdjacentCardDirection {
|
||||
PREVIOUS,
|
||||
NEXT
|
||||
@@ -453,6 +456,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
invalidateOptionsMenu();
|
||||
|
||||
ShortcutHelper.updateShortcuts(this);
|
||||
listWidget.updateAll(LoyaltyCardViewActivity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -598,20 +602,20 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
// Set bottomAppBar and system navigation bar color
|
||||
binding.bottomAppBar.setBackgroundColor(darkenedColor);
|
||||
Utils.setNavigationBarColor(null, window, darkenedColor, Utils.needsDarkForeground(darkenedColor));
|
||||
Utils.setNavigationBarColor(null, window, darkenedColor, ForegroundColorHelper.Companion.needsDarkForeground(darkenedColor));
|
||||
|
||||
int complementaryColor = Utils.getComplementaryColor(darkenedColor);
|
||||
binding.fabEdit.setBackgroundTintList(ColorStateList.valueOf(complementaryColor));
|
||||
Drawable editButtonIcon = binding.fabEdit.getDrawable();
|
||||
editButtonIcon.mutate();
|
||||
editButtonIcon.setTint(Utils.needsDarkForeground(complementaryColor) ? Color.BLACK : Color.WHITE);
|
||||
editButtonIcon.setTint(ForegroundColorHelper.Companion.needsDarkForeground(complementaryColor) ? Color.BLACK : Color.WHITE);
|
||||
binding.fabEdit.setImageDrawable(editButtonIcon);
|
||||
|
||||
Bitmap icon = loyaltyCard.getImageThumbnail(this);
|
||||
Utils.setIconOrTextWithBackground(this, loyaltyCard, icon, binding.iconImage, binding.iconText, 1);
|
||||
|
||||
// If the background is very bright, we should use dark icons
|
||||
backgroundNeedsDarkIcons = Utils.needsDarkForeground(backgroundHeaderColor);
|
||||
backgroundNeedsDarkIcons = ForegroundColorHelper.Companion.needsDarkForeground(backgroundHeaderColor);
|
||||
|
||||
fixBottomAppBarImageButtonColor(binding.bottomAppBarInfoButton);
|
||||
fixBottomAppBarImageButtonColor(binding.bottomAppBarPreviousButton);
|
||||
@@ -774,7 +778,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
} else if (id == R.id.action_star_unstar) {
|
||||
DBHelper.updateLoyaltyCardStarStatus(database, loyaltyCardId, loyaltyCard.starStatus == 0 ? 1 : 0);
|
||||
|
||||
new ListWidget().updateAll(LoyaltyCardViewActivity.this);
|
||||
listWidget.updateAll(LoyaltyCardViewActivity.this);
|
||||
|
||||
// Re-init loyaltyCard with new data from DB
|
||||
onResume();
|
||||
@@ -785,7 +789,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
DBHelper.updateLoyaltyCardArchiveStatus(database, loyaltyCardId, 1);
|
||||
Toast.makeText(LoyaltyCardViewActivity.this, R.string.archived, Toast.LENGTH_LONG).show();
|
||||
|
||||
new ListWidget().updateAll(LoyaltyCardViewActivity.this);
|
||||
listWidget.updateAll(LoyaltyCardViewActivity.this);
|
||||
|
||||
// Re-init loyaltyCard with new data from DB
|
||||
onResume();
|
||||
@@ -810,7 +814,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
DBHelper.deleteLoyaltyCard(database, LoyaltyCardViewActivity.this, loyaltyCardId);
|
||||
|
||||
new ListWidget().updateAll(LoyaltyCardViewActivity.this);
|
||||
listWidget.updateAll(LoyaltyCardViewActivity.this);
|
||||
|
||||
finish();
|
||||
dialog.dismiss();
|
||||
|
||||
@@ -3,6 +3,7 @@ package protect.card_locker.compose
|
||||
import androidx.activity.OnBackPressedDispatcher
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -22,7 +23,9 @@ import protect.card_locker.preferences.Settings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CatimaTopAppBar(title: String, onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
||||
fun CatimaTopAppBar(title: String,
|
||||
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
||||
actions : @Composable RowScope.() -> Unit = {}) {
|
||||
// Use pure black in OLED theme
|
||||
val context = LocalContext.current
|
||||
val settings = Settings(context)
|
||||
@@ -51,6 +54,7 @@ fun CatimaTopAppBar(title: String, onBackPressedDispatcher: OnBackPressedDispatc
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
actions = actions
|
||||
)
|
||||
}
|
||||
@@ -112,4 +112,8 @@ public class Settings {
|
||||
public boolean useVolumeKeysForNavigation() {
|
||||
return getBoolean(R.string.settings_key_use_volume_keys_navigation, false);
|
||||
}
|
||||
|
||||
public boolean getWearSyncEnabled() {
|
||||
return getBoolean(R.string.settings_key_wear_sync, false);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,42 @@
|
||||
package protect.card_locker.preferences
|
||||
|
||||
import android.app.Activity
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.MenuItem
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
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
|
||||
import protect.card_locker.shared.BluetoothPermissionHelper
|
||||
import protect.card_locker.shared.WearBluetoothSecurity
|
||||
import protect.card_locker.wearos.BluetoothPairingNotificationManager
|
||||
import protect.card_locker.wearos.WearSyncPermissionRequester
|
||||
|
||||
class SettingsActivity : CatimaAppCompatActivity() {
|
||||
|
||||
@@ -81,11 +101,62 @@ class SettingsActivity : CatimaAppCompatActivity() {
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
var mReloadMain: Boolean = false
|
||||
|
||||
private lateinit var wearSyncPermissionRequester: WearSyncPermissionRequester
|
||||
private var currentDevicesDialog: AlertDialog? = null
|
||||
private var currentDevicesDialogIsBlocked: Boolean = false
|
||||
private val devicesChangedReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != BluetoothPairingNotificationManager.ACTION_DEVICES_CHANGED) {
|
||||
return
|
||||
}
|
||||
findPreference<Preference>(getString(R.string.settings_key_wear_sync_devices))?.let { updateWearSyncAllowedDevicesSummary(it) }
|
||||
findPreference<Preference>(getString(R.string.settings_key_wear_sync_blocked_devices))?.let { updateWearSyncBlockedDevicesSummary(it) }
|
||||
if (currentDevicesDialog?.isShowing != true) {
|
||||
return
|
||||
}
|
||||
val isBlocked = currentDevicesDialogIsBlocked
|
||||
currentDevicesDialog?.dismiss()
|
||||
if (isBlocked) {
|
||||
showWearSyncBlockedDevicesDialog()
|
||||
} else {
|
||||
showWearSyncAllowedDevicesDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
wearSyncPermissionRequester.synchronize()
|
||||
findPreference<Preference>(getString(R.string.settings_key_wear_sync_devices))?.let { updateWearSyncAllowedDevicesSummary(it) }
|
||||
findPreference<Preference>(getString(R.string.settings_key_wear_sync_blocked_devices))?.let { updateWearSyncBlockedDevicesSummary(it) }
|
||||
ContextCompat.registerReceiver(
|
||||
requireContext(),
|
||||
devicesChangedReceiver,
|
||||
IntentFilter(BluetoothPairingNotificationManager.ACTION_DEVICES_CHANGED),
|
||||
ContextCompat.RECEIVER_NOT_EXPORTED
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
requireContext().unregisterReceiver(devicesChangedReceiver)
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
wearSyncPermissionRequester = WearSyncPermissionRequester(this, requireContext())
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.preferences)
|
||||
|
||||
// Show pretty names and summaries
|
||||
setupThemePreference()
|
||||
setupOledDarkPreference()
|
||||
setupLocalePreference()
|
||||
setupCrashReporterPreference()
|
||||
setupWearSyncPreference()
|
||||
setupWearSyncDevicePreferences()
|
||||
}
|
||||
|
||||
private fun setupThemePreference() {
|
||||
val themePreference = findPreference<ListPreference>(getString(R.string.settings_key_theme))
|
||||
themePreference!!.setOnPreferenceChangeListener { _, o ->
|
||||
when (o.toString()) {
|
||||
@@ -101,13 +172,17 @@ class SettingsActivity : CatimaAppCompatActivity() {
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupOledDarkPreference() {
|
||||
val oledDarkPreference = findPreference<Preference>(getString(R.string.settings_key_oled_dark))
|
||||
oledDarkPreference!!.setOnPreferenceChangeListener { _, _ ->
|
||||
refreshActivity(true)
|
||||
refreshActivity()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupLocalePreference() {
|
||||
val localePreference =
|
||||
findPreference<ListPreference>(getString(R.string.settings_key_locale))!!
|
||||
localePreference.let {
|
||||
@@ -148,7 +223,7 @@ class SettingsActivity : CatimaAppCompatActivity() {
|
||||
localePreference.setOnPreferenceChangeListener { _, newValue ->
|
||||
// See corresponding comment in Utils.updateBaseContextLocale for Android 6- notes
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
refreshActivity(true)
|
||||
refreshActivity()
|
||||
return@setOnPreferenceChangeListener true
|
||||
}
|
||||
val newLocale = newValue as String
|
||||
@@ -156,14 +231,205 @@ class SettingsActivity : CatimaAppCompatActivity() {
|
||||
AppCompatDelegate.setApplicationLocales(if (newLocale.isEmpty()) LocaleListCompat.getEmptyLocaleList() else LocaleListCompat.create(Utils.stringToLocale(newLocale)))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCrashReporterPreference() {
|
||||
// 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
|
||||
private fun setupWearSyncPreference() {
|
||||
val wearSyncPreference = findPreference<SwitchPreferenceCompat>(getString(R.string.settings_key_wear_sync))
|
||||
wearSyncPreference!!.setOnPreferenceChangeListener { preference, newValue ->
|
||||
val enabled = newValue as Boolean
|
||||
if (enabled) {
|
||||
wearSyncPermissionRequester.onWearSyncChanged(true) { granted ->
|
||||
if (granted) {
|
||||
(preference as? SwitchPreferenceCompat)?.isChecked = true
|
||||
} else {
|
||||
showWearSyncPermissionDeniedSnackbar()
|
||||
}
|
||||
}
|
||||
false
|
||||
} else {
|
||||
wearSyncPermissionRequester.onWearSyncChanged(false)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWearSyncDevicePreferences() {
|
||||
val allowedPreference = findPreference<Preference>(getString(R.string.settings_key_wear_sync_devices))
|
||||
allowedPreference!!.setOnPreferenceClickListener {
|
||||
showWearSyncAllowedDevicesDialog()
|
||||
true
|
||||
}
|
||||
updateWearSyncAllowedDevicesSummary(allowedPreference)
|
||||
|
||||
val blockedPreference = findPreference<Preference>(getString(R.string.settings_key_wear_sync_blocked_devices))
|
||||
blockedPreference!!.setOnPreferenceClickListener {
|
||||
showWearSyncBlockedDevicesDialog()
|
||||
true
|
||||
}
|
||||
updateWearSyncBlockedDevicesSummary(blockedPreference)
|
||||
}
|
||||
|
||||
private fun updateWearSyncAllowedDevicesSummary(preference: Preference) {
|
||||
val count = WearBluetoothSecurity.listTrustedDevices(requireContext()).size
|
||||
preference.summary = if (count == 0) {
|
||||
getString(R.string.settings_wear_sync_no_devices)
|
||||
} else {
|
||||
resources.getQuantityString(R.plurals.settings_wear_sync_devices_summary_count, count, count)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateWearSyncBlockedDevicesSummary(preference: Preference) {
|
||||
val count = WearBluetoothSecurity.listBlockedDevices(requireContext()).size
|
||||
preference.summary = if (count == 0) {
|
||||
getString(R.string.settings_wear_sync_no_blocked_devices)
|
||||
} else {
|
||||
resources.getQuantityString(R.plurals.settings_wear_sync_blocked_devices_summary_count, count, count)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun showWearSyncDeviceListDialog(
|
||||
titleRes: Int,
|
||||
noDevicesRes: Int,
|
||||
messageRes: Int,
|
||||
addresses: Set<String>,
|
||||
isBlocked: Boolean,
|
||||
onRemove: (String, String) -> Unit
|
||||
) {
|
||||
val context = requireContext()
|
||||
if (!BluetoothPermissionHelper.isBluetoothConnectGranted(context)) {
|
||||
showWearSyncPermissionDeniedSnackbar()
|
||||
return
|
||||
}
|
||||
val knownAddresses = addresses.toList()
|
||||
currentDevicesDialog?.dismiss()
|
||||
currentDevicesDialogIsBlocked = isBlocked
|
||||
if (knownAddresses.isEmpty()) {
|
||||
currentDevicesDialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(titleRes)
|
||||
.setMessage(noDevicesRes)
|
||||
.setOnDismissListener {
|
||||
currentDevicesDialog = null
|
||||
currentDevicesDialogIsBlocked = false
|
||||
}
|
||||
.setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||
.show()
|
||||
return
|
||||
}
|
||||
|
||||
val adapter = context.getSystemService(BLUETOOTH_SERVICE) as? BluetoothManager
|
||||
val bondedDevices = try {
|
||||
adapter?.adapter?.bondedDevices ?: emptySet()
|
||||
} catch (_: SecurityException) {
|
||||
emptySet<BluetoothDevice>()
|
||||
}
|
||||
val deviceMap = bondedDevices.associateBy { it.address }
|
||||
|
||||
val content = View.inflate(context, R.layout.wear_sync_device_list, null)
|
||||
content.findViewById<TextView>(R.id.wear_sync_device_list_message).text = getString(messageRes)
|
||||
val container = content.findViewById<LinearLayout>(R.id.wear_sync_device_list_container)
|
||||
|
||||
knownAddresses.forEach { address ->
|
||||
val name = deviceName(deviceMap[address], address)
|
||||
val itemView = LayoutInflater.from(context).inflate(R.layout.wear_sync_device_list_item, container, false) as TextView
|
||||
itemView.text = getString(R.string.settings_wear_sync_device_name, name, address)
|
||||
itemView.setOnClickListener { onRemove(address, name) }
|
||||
container.addView(itemView)
|
||||
}
|
||||
|
||||
currentDevicesDialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(titleRes)
|
||||
.setView(content)
|
||||
.setOnDismissListener {
|
||||
currentDevicesDialog = null
|
||||
currentDevicesDialogIsBlocked = false
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.dismiss() }
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun showWearSyncAllowedDevicesDialog() {
|
||||
showWearSyncDeviceListDialog(
|
||||
R.string.settings_wear_sync_devices,
|
||||
R.string.settings_wear_sync_no_devices,
|
||||
R.string.settings_wear_sync_tap_to_remove,
|
||||
WearBluetoothSecurity.listTrustedDevices(requireContext()),
|
||||
isBlocked = false
|
||||
) { address, name -> confirmRemoveAllowedDevice(address, name) }
|
||||
}
|
||||
|
||||
private fun showWearSyncBlockedDevicesDialog() {
|
||||
showWearSyncDeviceListDialog(
|
||||
R.string.settings_wear_sync_blocked_devices,
|
||||
R.string.settings_wear_sync_no_blocked_devices,
|
||||
R.string.settings_wear_sync_tap_to_remove,
|
||||
WearBluetoothSecurity.listBlockedDevices(requireContext()),
|
||||
isBlocked = true
|
||||
) { address, name -> confirmRemoveBlockedDevice(address, name) }
|
||||
}
|
||||
|
||||
private fun confirmRemoveAllowedDevice(address: String, name: String) {
|
||||
confirmRemoveDevice(
|
||||
address,
|
||||
name,
|
||||
R.string.settings_wear_sync_unpair_message,
|
||||
onRemove = { WearBluetoothSecurity.untrustDevice(requireContext(), address) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun confirmRemoveBlockedDevice(address: String, name: String) {
|
||||
confirmRemoveDevice(
|
||||
address,
|
||||
name,
|
||||
R.string.settings_wear_sync_unblock_message,
|
||||
onRemove = { WearBluetoothSecurity.unblockDevice(requireContext(), address) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun confirmRemoveDevice(address: String, name: String, messageRes: Int, onRemove: () -> Unit) {
|
||||
val context = requireContext()
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.settings_wear_sync_unpair_title)
|
||||
.setMessage(getString(messageRes, name))
|
||||
.setPositiveButton(R.string.settings_wear_sync_unpair) { dialog, _ ->
|
||||
onRemove()
|
||||
BluetoothPairingNotificationManager.cancel(context, address)
|
||||
BluetoothPairingNotificationManager.notifyDevicesChanged(context)
|
||||
findPreference<Preference>(getString(R.string.settings_key_wear_sync_devices))?.let { updateWearSyncAllowedDevicesSummary(it) }
|
||||
findPreference<Preference>(getString(R.string.settings_key_wear_sync_blocked_devices))?.let { updateWearSyncBlockedDevicesSummary(it) }
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.dismiss() }
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun deviceName(device: BluetoothDevice?, fallback: String): String {
|
||||
return try { device?.name } catch (_: SecurityException) { null } ?: fallback
|
||||
}
|
||||
|
||||
private fun showWearSyncPermissionDeniedSnackbar() {
|
||||
Snackbar.make(
|
||||
requireView(),
|
||||
R.string.wear_sync_permission_required,
|
||||
Snackbar.LENGTH_LONG
|
||||
).setAction(R.string.open_settings) {
|
||||
startActivity(
|
||||
Intent(
|
||||
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
||||
Uri.fromParts("package", requireContext().packageName, null)
|
||||
)
|
||||
)
|
||||
}.show()
|
||||
}
|
||||
|
||||
private fun refreshActivity() {
|
||||
mReloadMain = true
|
||||
activity?.recreate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package protect.card_locker.wearos
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import protect.card_locker.R
|
||||
import protect.card_locker.shared.WearBluetoothSecurity
|
||||
|
||||
class BluetoothPairingActivity : AppCompatActivity() {
|
||||
|
||||
private var currentDialog: AlertDialog? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
showPairingDialog(intent)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
showPairingDialog(intent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
currentDialog?.dismiss()
|
||||
currentDialog = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun showPairingDialog(intent: Intent) {
|
||||
val address = intent.getStringExtra(BluetoothPairingConstants.DEVICE_ADDRESS) ?: return finish()
|
||||
val deviceName = intent.getStringExtra(BluetoothPairingConstants.DEVICE_NAME) ?: address
|
||||
val token = intent.getStringExtra(BluetoothPairingConstants.DEVICE_TOKEN)
|
||||
val isMismatch = intent.getBooleanExtra(BluetoothPairingConstants.IS_MISMATCH, false)
|
||||
|
||||
val titleRes = if (isMismatch) {
|
||||
R.string.wear_bt_pairing_mismatch_title
|
||||
} else {
|
||||
R.string.wear_bt_pairing_title
|
||||
}
|
||||
val messageRes = if (isMismatch) {
|
||||
R.string.wear_bt_pairing_mismatch_message
|
||||
} else {
|
||||
R.string.wear_bt_pairing_message
|
||||
}
|
||||
|
||||
currentDialog?.dismiss()
|
||||
currentDialog = MaterialAlertDialogBuilder(this)
|
||||
.setTitle(getString(titleRes, deviceName))
|
||||
.setMessage(getString(messageRes, deviceName))
|
||||
.setPositiveButton(R.string.wear_bt_pairing_allow) { _, _ ->
|
||||
WearBluetoothSecurity.trustDevice(this, address, token)
|
||||
BluetoothPairingNotificationManager.updateResultNotification(this, address, deviceName, true)
|
||||
BluetoothPairingNotificationManager.notifyDevicesChanged(this)
|
||||
finish()
|
||||
}
|
||||
.setNegativeButton(R.string.wear_bt_pairing_block) { _, _ ->
|
||||
WearBluetoothSecurity.blockDevice(this, address)
|
||||
BluetoothPairingNotificationManager.updateResultNotification(this, address, deviceName, false)
|
||||
BluetoothPairingNotificationManager.notifyDevicesChanged(this)
|
||||
finish()
|
||||
}
|
||||
.setOnCancelListener { finish() }
|
||||
.setCancelable(true)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package protect.card_locker.wearos
|
||||
|
||||
object BluetoothPairingConstants {
|
||||
const val DEVICE_ADDRESS = "device_address"
|
||||
const val DEVICE_NAME = "device_name"
|
||||
const val DEVICE_TOKEN = "device_token"
|
||||
const val IS_MISMATCH = "is_mismatch"
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package protect.card_locker.wearos
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import protect.card_locker.NotificationInfo
|
||||
import protect.card_locker.R
|
||||
|
||||
object BluetoothPairingNotificationManager {
|
||||
|
||||
const val ACTION_DEVICES_CHANGED = "protect.card_locker.wearos.ACTION_DEVICES_CHANGED"
|
||||
|
||||
fun notifyDevicesChanged(context: Context) {
|
||||
context.sendBroadcast(Intent(ACTION_DEVICES_CHANGED).apply { setPackage(context.packageName) })
|
||||
}
|
||||
|
||||
fun showAuthorizationNotification(
|
||||
context: Context,
|
||||
deviceName: String,
|
||||
address: String,
|
||||
token: String,
|
||||
isMismatch: Boolean = false
|
||||
) {
|
||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
NotificationInfo.WearBluetooth.PAIRING_CHANNEL_ID,
|
||||
context.getString(R.string.wear_bt_pairing_channel_name),
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
)
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
val titleRes = if (isMismatch) {
|
||||
R.string.wear_bt_pairing_mismatch_title
|
||||
} else {
|
||||
R.string.wear_bt_pairing_title
|
||||
}
|
||||
val messageRes = if (isMismatch) {
|
||||
R.string.wear_bt_pairing_mismatch_message
|
||||
} else {
|
||||
R.string.wear_bt_pairing_message
|
||||
}
|
||||
|
||||
val allowIntent = Intent(context, BluetoothPairingReceiver::class.java).apply {
|
||||
action = BluetoothPairingReceiver.ACTION_ALLOW
|
||||
putExtra(BluetoothPairingConstants.DEVICE_ADDRESS, address)
|
||||
putExtra(BluetoothPairingConstants.DEVICE_NAME, deviceName)
|
||||
putExtra(BluetoothPairingConstants.DEVICE_TOKEN, token)
|
||||
}
|
||||
val blockIntent = Intent(context, BluetoothPairingReceiver::class.java).apply {
|
||||
action = BluetoothPairingReceiver.ACTION_BLOCK
|
||||
putExtra(BluetoothPairingConstants.DEVICE_ADDRESS, address)
|
||||
putExtra(BluetoothPairingConstants.DEVICE_NAME, deviceName)
|
||||
putExtra(BluetoothPairingConstants.IS_MISMATCH, isMismatch)
|
||||
}
|
||||
|
||||
val allowPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
address.hashCode(),
|
||||
allowIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val blockPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
address.hashCode() + 1,
|
||||
blockIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val contentIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
address.hashCode() + 2,
|
||||
Intent(context, BluetoothPairingActivity::class.java).apply {
|
||||
putExtra(BluetoothPairingConstants.DEVICE_ADDRESS, address)
|
||||
putExtra(BluetoothPairingConstants.DEVICE_NAME, deviceName)
|
||||
putExtra(BluetoothPairingConstants.DEVICE_TOKEN, token)
|
||||
putExtra(BluetoothPairingConstants.IS_MISMATCH, isMismatch)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val notification = NotificationCompat.Builder(context, NotificationInfo.WearBluetooth.PAIRING_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification_catima)
|
||||
.setContentTitle(context.getString(titleRes, deviceName))
|
||||
.setContentText(context.getString(messageRes, deviceName))
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(context.getString(messageRes, deviceName)))
|
||||
.addAction(0, context.getString(R.string.wear_bt_pairing_allow), allowPendingIntent)
|
||||
.addAction(0, context.getString(R.string.wear_bt_pairing_block), blockPendingIntent)
|
||||
.setContentIntent(contentIntent)
|
||||
.setAutoCancel(false)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_STATUS)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(notificationId(address), notification)
|
||||
}
|
||||
|
||||
fun cancel(context: Context, address: String) {
|
||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.cancel(notificationId(address))
|
||||
}
|
||||
|
||||
fun updateResultNotification(context: Context, address: String, deviceName: String, allowed: Boolean) {
|
||||
cancel(context, address)
|
||||
val message = if (allowed) {
|
||||
context.getString(R.string.wear_bt_pairing_allowed, deviceName)
|
||||
} else {
|
||||
context.getString(R.string.wear_bt_pairing_blocked, deviceName)
|
||||
}
|
||||
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
fun notificationId(address: String): Int = address.hashCode()
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package protect.card_locker.wearos
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import protect.card_locker.shared.WearBluetoothSecurity
|
||||
|
||||
class BluetoothPairingReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val address = intent.getStringExtra(BluetoothPairingConstants.DEVICE_ADDRESS) ?: return
|
||||
val deviceName = intent.getStringExtra(BluetoothPairingConstants.DEVICE_NAME) ?: address
|
||||
val token = intent.getStringExtra(BluetoothPairingConstants.DEVICE_TOKEN)
|
||||
|
||||
when (intent.action) {
|
||||
ACTION_ALLOW -> {
|
||||
WearBluetoothSecurity.trustDevice(context, address, token)
|
||||
BluetoothPairingNotificationManager.updateResultNotification(context, address, deviceName, true)
|
||||
BluetoothPairingNotificationManager.notifyDevicesChanged(context)
|
||||
}
|
||||
ACTION_BLOCK -> {
|
||||
WearBluetoothSecurity.blockDevice(context, address)
|
||||
BluetoothPairingNotificationManager.updateResultNotification(context, address, deviceName, false)
|
||||
BluetoothPairingNotificationManager.notifyDevicesChanged(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ACTION_ALLOW = "protect.card_locker.wearos.action.ALLOW_DEVICE"
|
||||
const val ACTION_BLOCK = "protect.card_locker.wearos.action.BLOCK_DEVICE"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
package protect.card_locker.wearos
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.BluetoothServerSocket
|
||||
import android.bluetooth.BluetoothSocket
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import protect.card_locker.DBHelper
|
||||
import protect.card_locker.NotificationInfo
|
||||
import protect.card_locker.R
|
||||
import protect.card_locker.Utils
|
||||
import protect.card_locker.shared.BluetoothPermissionHelper
|
||||
import protect.card_locker.shared.WearBluetoothProtocol
|
||||
import protect.card_locker.shared.WearBluetoothSecurity
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.io.OutputStreamWriter
|
||||
import java.io.PrintWriter
|
||||
|
||||
class BluetoothServerService : Service() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "CatimaBtServer"
|
||||
private const val NOTIFICATION_ID = NotificationInfo.WearBluetooth.NOTIFICATION_ID
|
||||
private const val CHANNEL_ID = NotificationInfo.WearBluetooth.CHANNEL_ID
|
||||
}
|
||||
|
||||
private var serverThread: AcceptThread? = null
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (!BluetoothPermissionHelper.isBluetoothConnectGranted(this)) {
|
||||
Log.w(TAG, "BLUETOOTH_CONNECT permission not granted, stopping")
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
val adapter = (getSystemService(BLUETOOTH_SERVICE) as? BluetoothManager)?.adapter
|
||||
if (adapter == null || !adapter.isEnabled) {
|
||||
Log.w(TAG, "Bluetooth not available or disabled")
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
// Avoid tearing down a working accept socket every time Settings is resumed.
|
||||
if (serverThread?.isAlive == true) {
|
||||
Log.d(TAG, "Bluetooth server already listening")
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
startForegroundWithNotification()
|
||||
serverThread?.cancel()
|
||||
serverThread = AcceptThread(adapter).also { it.start() }
|
||||
Log.d(TAG, "Bluetooth server started")
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
private fun startForegroundWithNotification() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
getString(R.string.wear_bt_channel_name),
|
||||
NotificationManager.IMPORTANCE_MIN
|
||||
).apply { setShowBadge(false) }
|
||||
getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
|
||||
}
|
||||
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle(getString(R.string.wear_bt_notification_title))
|
||||
.setSmallIcon(R.drawable.ic_notification_sync)
|
||||
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||
.setSilent(true)
|
||||
.build()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(NOTIFICATION_ID, notification,
|
||||
android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE)
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
serverThread?.cancel()
|
||||
serverThread = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
private inner class AcceptThread(adapter: BluetoothAdapter) : Thread() {
|
||||
private var serverSocket: BluetoothServerSocket? = null
|
||||
@Volatile private var running = true
|
||||
|
||||
init {
|
||||
try {
|
||||
if (BluetoothPermissionHelper.isBluetoothConnectGranted(this@BluetoothServerService)) {
|
||||
serverSocket = adapter.listenUsingRfcommWithServiceRecord(
|
||||
WearBluetoothProtocol.BT_SERVICE_NAME,
|
||||
WearBluetoothProtocol.BT_SERVICE_UUID
|
||||
)
|
||||
} else {
|
||||
Log.w(TAG, "BLUETOOTH_CONNECT permission missing, cannot open server socket")
|
||||
running = false
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "BLUETOOTH_CONNECT permission missing, cannot create server socket", e)
|
||||
running = false
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to create server socket", e)
|
||||
running = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
Log.d(TAG, "Listening for Bluetooth connections")
|
||||
while (running) {
|
||||
val socket: BluetoothSocket = try {
|
||||
serverSocket?.accept() ?: break
|
||||
} catch (e: Exception) {
|
||||
if (running) Log.e(TAG, "Accept failed", e)
|
||||
break
|
||||
}
|
||||
// Handle each client on its own thread so a slow/stuck client
|
||||
// cannot block the accept loop.
|
||||
Thread { handleConnection(socket) }.start()
|
||||
}
|
||||
Log.d(TAG, "Accept loop ended")
|
||||
if (running) {
|
||||
Log.w(TAG, "Accept loop exited unexpectedly, restarting service")
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleConnection(socket: BluetoothSocket) {
|
||||
val address = try { socket.remoteDevice.address } catch (_: SecurityException) { null } ?: return
|
||||
val deviceName = try { socket.remoteDevice.name } catch (_: SecurityException) { address }
|
||||
Log.d(TAG, "Connected to $deviceName ($address)")
|
||||
try {
|
||||
val reader = BufferedReader(InputStreamReader(socket.inputStream, "UTF-8"))
|
||||
val writer = PrintWriter(OutputStreamWriter(socket.outputStream, "UTF-8"), false)
|
||||
val firstLine = reader.readLine()?.trim() ?: return
|
||||
|
||||
if (firstLine == WearBluetoothProtocol.BT_CMD_VERSIONS) {
|
||||
val versions = JSONArray().put("${WearBluetoothProtocol.PROTOCOL_VERSION}.${WearBluetoothProtocol.PROTOCOL_MINOR_VERSION}").toString()
|
||||
writer.println(versions)
|
||||
writer.flush()
|
||||
Log.d(TAG, "Sent supported versions to $deviceName: $versions")
|
||||
return
|
||||
}
|
||||
|
||||
var commandLine: String? = firstLine
|
||||
while (commandLine != null) {
|
||||
val command = commandLine
|
||||
Log.d(TAG, "Received command: $command from $deviceName")
|
||||
|
||||
if (!command.startsWith(WearBluetoothProtocol.BT_CMD_VERSION_PREFIX)) {
|
||||
notAuthorized(writer)
|
||||
Log.w(TAG, "Unsupported command from $deviceName: $command")
|
||||
break
|
||||
}
|
||||
|
||||
val tokenLine = reader.readLine()?.trim()
|
||||
if (tokenLine == null || !tokenLine.startsWith(WearBluetoothProtocol.BT_CMD_TOKEN_PREFIX)) {
|
||||
notAuthorized(writer)
|
||||
Log.w(TAG, "Malformed token line from $deviceName: $tokenLine")
|
||||
break
|
||||
}
|
||||
val token = tokenLine.removePrefix(WearBluetoothProtocol.BT_CMD_TOKEN_PREFIX)
|
||||
|
||||
val isTrusted = WearBluetoothSecurity.isDeviceTrusted(this@BluetoothServerService, address)
|
||||
val storedToken = WearBluetoothSecurity.getDeviceToken(this@BluetoothServerService, address)
|
||||
val tokenMatches = storedToken != null && storedToken == token
|
||||
|
||||
when {
|
||||
WearBluetoothSecurity.isDeviceBlocked(this@BluetoothServerService, address) -> {
|
||||
notAuthorized(writer)
|
||||
Log.w(TAG, "Rejected blocked device $deviceName")
|
||||
break
|
||||
}
|
||||
isTrusted && tokenMatches -> {
|
||||
if (!serveCommand(writer, deviceName, command)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
isTrusted && !tokenMatches -> {
|
||||
BluetoothPairingNotificationManager.showAuthorizationNotification(
|
||||
this@BluetoothServerService,
|
||||
deviceName,
|
||||
address,
|
||||
token,
|
||||
isMismatch = true
|
||||
)
|
||||
notAuthorized(writer)
|
||||
Log.w(TAG, "Token mismatch for $deviceName")
|
||||
break
|
||||
}
|
||||
else -> {
|
||||
BluetoothPairingNotificationManager.showAuthorizationNotification(
|
||||
this@BluetoothServerService,
|
||||
deviceName,
|
||||
address,
|
||||
token,
|
||||
isMismatch = false
|
||||
)
|
||||
notAuthorized(writer)
|
||||
Log.d(TAG, "Requested authorization for $deviceName")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
commandLine = reader.readLine()?.trim()
|
||||
}
|
||||
} catch (_: IOException) {
|
||||
// Client closed the socket; this is a normal end-of-stream after
|
||||
// the watch has received the pages it needs.
|
||||
Log.d(TAG, "Connection closed by $deviceName")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error handling connection from $deviceName", e)
|
||||
} finally {
|
||||
try { socket.close() } catch (_: Exception) { }
|
||||
}
|
||||
}
|
||||
|
||||
private fun notAuthorized(writer: PrintWriter) {
|
||||
writer.println(WearBluetoothProtocol.BT_RESPONSE_NOT_AUTHORIZED)
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
private fun serveCommand(
|
||||
writer: PrintWriter,
|
||||
deviceName: String,
|
||||
command: String
|
||||
): Boolean {
|
||||
when {
|
||||
command.startsWith(WearBluetoothProtocol.BT_CMD_CARDS_PAGE_PREFIX) -> {
|
||||
val pageIndex = command.removePrefix(WearBluetoothProtocol.BT_CMD_CARDS_PAGE_PREFIX).toIntOrNull()
|
||||
if (pageIndex == null || pageIndex < 0) {
|
||||
Log.w(TAG, "Invalid page index from $deviceName: $command")
|
||||
notAuthorized(writer)
|
||||
return false
|
||||
}
|
||||
val json = buildCardsPageJson(pageIndex)
|
||||
writer.println(json)
|
||||
writer.flush()
|
||||
Log.d(TAG, "Sent page $pageIndex to $deviceName")
|
||||
return true
|
||||
}
|
||||
else -> {
|
||||
Log.w(TAG, "Unsupported command from $deviceName: $command")
|
||||
notAuthorized(writer)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildCardsPageJson(pageIndex: Int): String {
|
||||
val dbHelper = DBHelper(this@BluetoothServerService)
|
||||
val db = dbHelper.readableDatabase
|
||||
val order = Utils.getLoyaltyCardOrder(this@BluetoothServerService)
|
||||
val orderDirection = Utils.getLoyaltyCardOrderDirection(this@BluetoothServerService)
|
||||
val cursor = DBHelper.getLoyaltyCardCursor(
|
||||
db, "", null, order, orderDirection,
|
||||
DBHelper.LoyaltyCardArchiveFilter.Unarchived
|
||||
)
|
||||
val totalCards = cursor.count
|
||||
val totalPages = if (totalCards == 0) 1 else (totalCards + WearBluetoothProtocol.PAGE_SIZE - 1) / WearBluetoothProtocol.PAGE_SIZE
|
||||
val offset = pageIndex * WearBluetoothProtocol.PAGE_SIZE
|
||||
val array = JSONArray()
|
||||
cursor.use {
|
||||
if (it.move(offset + 1)) {
|
||||
val idIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID)
|
||||
val storeIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE)
|
||||
val cardIdIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID)
|
||||
val barcodeIdIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_ID)
|
||||
val barcodeTypeIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE)
|
||||
val headerColorIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR)
|
||||
var count = 0
|
||||
do {
|
||||
array.put(JSONObject().apply {
|
||||
put("id", it.getInt(idIdx))
|
||||
put("store", it.getString(storeIdx) ?: "")
|
||||
put("cardId", it.getString(cardIdIdx) ?: "")
|
||||
put("barcodeId", if (it.isNull(barcodeIdIdx)) JSONObject.NULL else it.getString(barcodeIdIdx))
|
||||
put("barcodeType", if (it.isNull(barcodeTypeIdx)) JSONObject.NULL else it.getString(barcodeTypeIdx))
|
||||
put("headerColor", if (it.isNull(headerColorIdx)) JSONObject.NULL else it.getInt(headerColorIdx))
|
||||
})
|
||||
count++
|
||||
} while (count < WearBluetoothProtocol.PAGE_SIZE && it.moveToNext())
|
||||
}
|
||||
}
|
||||
return JSONObject().apply {
|
||||
put("page", pageIndex)
|
||||
put("totalPages", totalPages)
|
||||
put("cards", array)
|
||||
}.toString()
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
running = false
|
||||
try { serverSocket?.close() } catch (_: Exception) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package protect.card_locker.wearos
|
||||
|
||||
import android.Manifest
|
||||
import android.app.ForegroundServiceStartNotAllowedException
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.activity.result.ActivityResultCaller
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import protect.card_locker.NotificationInfo
|
||||
import protect.card_locker.R
|
||||
import protect.card_locker.preferences.Settings
|
||||
import protect.card_locker.shared.BluetoothPermissionHelper
|
||||
|
||||
class WearSyncPermissionRequester(
|
||||
caller: ActivityResultCaller,
|
||||
context: Context
|
||||
) {
|
||||
private val context = context.applicationContext
|
||||
private var pendingResultCallback: ((Boolean) -> Unit)? = null
|
||||
|
||||
private val permissionLauncher = caller.registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { results ->
|
||||
val allGranted = results.all { it.value }
|
||||
pendingResultCallback?.invoke(allGranted)
|
||||
WearSyncServiceManager.onPermissionResult(this.context, allGranted)
|
||||
pendingResultCallback = null
|
||||
}
|
||||
|
||||
fun synchronize() {
|
||||
WearSyncServiceManager.synchronize(context) {
|
||||
permissionLauncher.launch(getRequiredPermissions())
|
||||
}
|
||||
}
|
||||
|
||||
fun onWearSyncChanged(enabled: Boolean, onPermissionResult: ((Boolean) -> Unit)? = null) {
|
||||
val applied = WearSyncServiceManager.onWearSyncChanged(context, enabled) {
|
||||
pendingResultCallback = onPermissionResult
|
||||
permissionLauncher.launch(getRequiredPermissions())
|
||||
}
|
||||
if (applied) {
|
||||
onPermissionResult?.invoke(true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRequiredPermissions(): Array<String> = buildList {
|
||||
if (BluetoothPermissionHelper.isBluetoothConnectRequired()) {
|
||||
add(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
}
|
||||
if (BluetoothPermissionHelper.isPostNotificationsRequired()) {
|
||||
add(Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
object WearSyncServiceManager {
|
||||
private const val TAG = "CatimaWearSyncServiceManager"
|
||||
|
||||
fun synchronize(context: Context, requestPermission: (() -> Unit)? = null) {
|
||||
if (!Settings(context).wearSyncEnabled) {
|
||||
stop(context)
|
||||
} else if (arePermissionsGranted(context)) {
|
||||
start(context)
|
||||
} else if (requestPermission != null) {
|
||||
requestPermission()
|
||||
} else {
|
||||
stop(context)
|
||||
}
|
||||
}
|
||||
|
||||
fun onWearSyncChanged(
|
||||
context: Context,
|
||||
enabled: Boolean,
|
||||
requestPermission: () -> Unit
|
||||
): Boolean {
|
||||
return if (enabled) {
|
||||
if (arePermissionsGranted(context)) {
|
||||
start(context)
|
||||
true
|
||||
} else {
|
||||
requestPermission()
|
||||
false
|
||||
}
|
||||
} else {
|
||||
stop(context)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun arePermissionsGranted(context: Context): Boolean {
|
||||
return BluetoothPermissionHelper.isBluetoothConnectGranted(context) &&
|
||||
BluetoothPermissionHelper.isPostNotificationsGranted(context)
|
||||
}
|
||||
|
||||
fun onPermissionResult(context: Context, granted: Boolean) {
|
||||
if (granted && Settings(context).wearSyncEnabled) {
|
||||
start(context)
|
||||
} else {
|
||||
stop(context)
|
||||
if (!granted) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
putBoolean(context.getString(R.string.settings_key_wear_sync), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun start(context: Context) {
|
||||
// Exception can only throw on API level 31 and up
|
||||
if (Build.VERSION.SDK_INT >= 31) {
|
||||
try {
|
||||
ContextCompat.startForegroundService(
|
||||
context,
|
||||
Intent(context, BluetoothServerService::class.java)
|
||||
)
|
||||
} catch (e: ForegroundServiceStartNotAllowedException) {
|
||||
// We naively assume we have enough permissions to show a notification as the service should never try to start
|
||||
// before the user gave notification access anyway
|
||||
Log.d(TAG, "Foreground service failed to launch: $e")
|
||||
val channel = NotificationChannel(
|
||||
NotificationInfo.WearBluetooth.CRITICAL_ERROR_CHANNEL_ID,
|
||||
context.getString(R.string.wear_bt_critical_error_channel_name),
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply { setShowBadge(false) }
|
||||
context.getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
|
||||
val notification = NotificationCompat.Builder(context, NotificationInfo.WearBluetooth.CRITICAL_ERROR_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification_error)
|
||||
.setContentTitle(context.getString(R.string.wear_bt_failed_foreground_notification_title))
|
||||
.setContentText(e.message)
|
||||
.build()
|
||||
with(NotificationManagerCompat.from(context)) {
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return@with
|
||||
}
|
||||
notify(
|
||||
NotificationInfo.WearBluetooth.CRITICAL_ERROR_NOTIFICATION_ID,
|
||||
notification
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ContextCompat.startForegroundService(
|
||||
context,
|
||||
Intent(context, BluetoothServerService::class.java)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stop(context: Context) {
|
||||
context.stopService(Intent(context, BluetoothServerService::class.java))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
~ Copyright (C) 2026 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="#FFFFFF">
|
||||
<group android:scaleX="1.104"
|
||||
android:scaleY="1.104"
|
||||
android:translateX="-49.92"
|
||||
android:translateY="-49.92">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M508.5,668.5Q520,657 520,640Q520,623 508.5,611.5Q497,600 480,600Q463,600 451.5,611.5Q440,623 440,640Q440,657 451.5,668.5Q463,680 480,680Q497,680 508.5,668.5ZM440,520L520,520L520,280L440,280L440,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
~ Copyright (C) 2026 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="#FFFFFF">
|
||||
<group android:scaleX="0.92"
|
||||
android:scaleY="0.92"
|
||||
android:translateX="38.4"
|
||||
android:translateY="38.4">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M160,800L160,720L270,720L254,706Q202,660 181,601Q160,542 160,482Q160,371 226.5,284.5Q293,198 400,170L400,254Q328,280 284,342.5Q240,405 240,482Q240,527 257,569.5Q274,612 310,648L320,658L320,560L400,560L400,800L160,800ZM560,790L560,706Q632,680 676,617.5Q720,555 720,478Q720,433 703,390.5Q686,348 650,312L640,302L640,400L560,400L560,160L800,160L800,240L690,240L706,254Q755,303 777.5,360.5Q800,418 800,478Q800,589 733.5,675.5Q667,762 560,790Z"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
After Width: | Height: | Size: 790 B |
|
After Width: | Height: | Size: 752 B |
|
After Width: | Height: | Size: 499 B |
|
After Width: | Height: | Size: 509 B |
|
After Width: | Height: | Size: 478 B |
|
After Width: | Height: | Size: 346 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 919 B |
|
After Width: | Height: | Size: 656 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 918 B |
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/wear_sync_device_list_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wear_sync_device_list_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="?attr/dialogPreferredPadding"
|
||||
android:paddingEnd="?attr/dialogPreferredPadding"
|
||||
style="?attr/materialAlertDialogBodyTextStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/listPreferredItemHeightSmall"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingLeft"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingRight"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:ellipsize="marquee"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
style="?attr/materialAlertDialogBodyTextStyle" />
|
||||
@@ -1,65 +1,68 @@
|
||||
Sylvia van Os
|
||||
Branden Archer
|
||||
J. Lavoie
|
||||
solokot
|
||||
J. Lavoie
|
||||
Allan Nordhøy
|
||||
Heimen Stoffels
|
||||
Oğuz Ersen
|
||||
B o d o
|
||||
Heimen Stoffels
|
||||
FC (Fay) Stegerman
|
||||
大王叫我来巡山
|
||||
B o d o
|
||||
StoyanDimitrov
|
||||
SlavekB
|
||||
Katharine Chui / Katie
|
||||
Silvério Santos
|
||||
mondstern
|
||||
IllusiveMan196
|
||||
Максим Горпиніч
|
||||
Fjuro
|
||||
Edgars Andersons
|
||||
Liner Seven
|
||||
Altonss
|
||||
Fjuro
|
||||
Joel A
|
||||
Максим Горпиніч
|
||||
GitSpoon
|
||||
Michael Moroni
|
||||
Priit Jõerüüt
|
||||
Joel A
|
||||
Altonss
|
||||
Michael Moroni
|
||||
Eric
|
||||
GM
|
||||
laralem
|
||||
Petr Novák
|
||||
laralem
|
||||
Vasilis
|
||||
Taco
|
||||
Milo Ivir
|
||||
nadiafekihahmed
|
||||
pfaffenrodt
|
||||
Vasilis
|
||||
Aayush Gupta
|
||||
damjang
|
||||
Scrambled777
|
||||
josé m
|
||||
Kachelkaiser
|
||||
ikanakova
|
||||
Nyatsuki
|
||||
Giovanni Donisi
|
||||
Kachelkaiser
|
||||
Jiri Grönroos
|
||||
HudobniVolk
|
||||
Giovanni Donisi
|
||||
109247019824
|
||||
HudobniVolk
|
||||
Warder
|
||||
Balázs Meskó
|
||||
Traductor
|
||||
AMIR-G98
|
||||
Samantaz Fox
|
||||
mdvhimself
|
||||
AMIR-G98
|
||||
NooB9496
|
||||
Cliff Heraldo
|
||||
Sergio Paredes
|
||||
Ankit Tiwari
|
||||
Feike
|
||||
Arno-github
|
||||
Feike
|
||||
Jose Delvani
|
||||
Milan Šalka
|
||||
damjang
|
||||
Robin
|
||||
தமிழ்நேரம்
|
||||
huuhaa
|
||||
Watchman89
|
||||
Skrripy
|
||||
Hosted Weblate user 54392
|
||||
Govindgopalyadav
|
||||
Gideon
|
||||
waffshappen
|
||||
@@ -67,6 +70,7 @@ Marnick L'Eau
|
||||
JungHee Lee
|
||||
pacavarn
|
||||
ngocanhtve
|
||||
josé m.
|
||||
aradxxx
|
||||
StellarSand
|
||||
Quentin PAGÈS
|
||||
@@ -88,19 +92,22 @@ stavpup
|
||||
mrestivill
|
||||
ezn24
|
||||
ehrt74
|
||||
dukezas
|
||||
Virginie
|
||||
Tim Trek
|
||||
Thu-Trang Pham
|
||||
Alì Mortacci
|
||||
Andre Costa
|
||||
Arif Budiman
|
||||
arshbeerSingh
|
||||
Augustin LAVILLE
|
||||
Freddo espresso
|
||||
Peter Dave Hello
|
||||
PRATHAMESH BHAGAT
|
||||
Michael Gangolf
|
||||
rudy3
|
||||
echo r"0xX4H" | rev
|
||||
Kim Seohyun
|
||||
Govind S Nair
|
||||
Kim Seohyun
|
||||
Libre
|
||||
echo r"0xX4H" | rev
|
||||
rudy3
|
||||
Michael Gangolf
|
||||
PRATHAMESH BHAGAT
|
||||
Peter Dave Hello
|
||||
Thomas Bertels
|
||||
@@ -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">بحث</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>
|
||||
@@ -16,7 +16,7 @@
|
||||
<string name="edit">تعديل</string>
|
||||
<string name="delete">حذف</string>
|
||||
<string name="confirm">تأكيد</string>
|
||||
<string name="deleteConfirmation">حذف هذه البطاقة نهائيًّا؟</string>
|
||||
<string name="deleteConfirmation">هل ترغب في حذف هذه البطاقة نهائيًّا؟</string>
|
||||
<string name="ok">حسنًا</string>
|
||||
<string name="share">مشاركة</string>
|
||||
<string name="sendLabel">إرسال…</string>
|
||||
@@ -161,9 +161,9 @@
|
||||
<item quantity="zero">حذف هذه <xliff:g>%d</xliff:g> البطاقة نهائيًا؟</item>
|
||||
<item quantity="one">حذف هذه <xliff:g>%d</xliff:g> البطاقة نهائيًا؟</item>
|
||||
<item quantity="two">حذف هذه <xliff:g>%d</xliff:g> البطاقتين نهائيًا؟</item>
|
||||
<item quantity="few">حذف هذه <xliff:g>%d</xliff:g> البطاقات نهائيًا؟</item>
|
||||
<item quantity="many">حذف هذه <xliff:g>%d</xliff:g> البطاقات نهائيًا؟</item>
|
||||
<item quantity="other">حذف هذه <xliff:g>%d</xliff:g> البطاقات نهائيًا؟</item>
|
||||
<item quantity="few">هل ترغب في حذف هذه البطاقات الـ <xliff:g>%d</xliff:g> نهائيًّا؟</item>
|
||||
<item quantity="many">هل ترغب في حذف هذه البطاقات الـ <xliff:g>%d</xliff:g> نهائيًّا؟</item>
|
||||
<item quantity="other">هل ترغب في حذف هذه البطاقات الـ <xliff:g>%d</xliff:g> نهائيًا؟</item>
|
||||
</plurals>
|
||||
<string name="importOptionFilesystemTitle">الاستيراد من نظام الملفات</string>
|
||||
<string name="importOptionFilesystemExplanation">اختر ملفًا محددًا من نظام الملفات</string>
|
||||
@@ -255,9 +255,9 @@
|
||||
<string name="show_note">إظهار الملاحظة</string>
|
||||
<string name="switchToBackImage">التبديل إلى الصورة الخلفية</string>
|
||||
<string name="switchToFrontImage">التبديل إلى الصورة الأمامية</string>
|
||||
<string name="settings_allow_content_provider_read_summary">سيظل يتعين على التطبيقات طلب الإذن لمنحها حق الوصول</string>
|
||||
<string name="settings_allow_content_provider_read_summary">يسمح لتطبيق Gadgetbridge بمزامنة بطاقاتك مع الساعات المدعومة</string>
|
||||
<string name="setBarcodeHeight">ضبط ارتفاع الباركود</string>
|
||||
<string name="settings_allow_content_provider_read_title">السماح للتطبيقات الأخرى بالوصول إلى بياناتي</string>
|
||||
<string name="settings_allow_content_provider_read_title">المزامنة مع Gadgetbridge</string>
|
||||
<string name="donate">تبرّع</string>
|
||||
<string name="show_archived_cards">عرض البطاقات المؤرشفة</string>
|
||||
<string name="settings_category_title_privacy">خصوصية</string>
|
||||
@@ -327,4 +327,47 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC موقوف</string>
|
||||
<string name="change_settings">تغير الاعدادات</string>
|
||||
<string name="nfc_block_system_error">فشل في ايقاف NFC</string>
|
||||
<string name="sort_by_most_recently_added">تمت إضافته مؤخرًا</string>
|
||||
<string name="zoomFrontImage">تكبير الصورة الأمامية</string>
|
||||
<string name="zoomBackImage">تكبير الصورة للخلف</string>
|
||||
<string name="show_card_id">عرض رقم ID للبطاقة</string>
|
||||
<string name="settings_wear_sync">المزامنة مع Wear OS</string>
|
||||
<string name="settings_wear_sync_summary">يسمح التطبيق لـ Wear OS بقراءة بطاقاتك عبر البلوتوث</string>
|
||||
<string name="wear_sync_permission_required">كاتيما تحتاج الأذونات التي رفضتها للمزامنة مع ساعة Wear OS الذكية الخاصة بك</string>
|
||||
<string name="settings_wear_sync_devices">أجهزة Wear OS المسموح بها</string>
|
||||
<string name="settings_wear_sync_devices_summary">إدارة الأجهزة المسموح لها بالمزامنة عبر البلوتوث</string>
|
||||
<string name="settings_wear_sync_no_devices">لا توجد أجهزة مسموحة</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="zero"><xliff:g>%d</xliff:g> جهاز مسموح</item>
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> جهاز مسموح</item>
|
||||
<item quantity="two"><xliff:g>%d</xliff:g> جهازان مسموحان</item>
|
||||
<item quantity="few"><xliff:g>%d</xliff:g> أجهزة مسموحة</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> أجهزة مسموحة</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> جهاز مسموح</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">أجهزة Wear OS المحجوبة</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">إدارة أجهزة Wear OS المحجوبة</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">لا توجد أجهزة محجوبة</string>
|
||||
<string name="open_in_gallery">افتح في معرض الصور</string>
|
||||
<string name="settings_wear_sync_unpair_title">هل تريد إزالة الجهاز؟</string>
|
||||
<string name="settings_wear_sync_unpair_message">هل تريد إزالة <xliff:g>%1$s</xliff:g> من كاتيما؟ لن تكون قادرًا بعد الآن على مزامنة بطاقاتك.</string>
|
||||
<string name="settings_wear_sync_unblock_message">هل تريد إزالة <xliff:g>%1$s</xliff:g> من القائمة المحظورة؟ سيتمكن من طلب الوصول مرة أخرى.</string>
|
||||
<string name="settings_wear_sync_unpair">إزالة</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">اضغط على جهاز لإزالته.</string>
|
||||
<string name="wear_bt_pairing_channel_name">إقران جهاز Wear OS</string>
|
||||
<string name="wear_bt_pairing_title">جهاز Wear OS يريد المزامنة</string>
|
||||
<string name="wear_bt_pairing_message">هل تريد السماح لـ <xliff:g>%1$s</xliff:g> بالوصول إلى بطاقاتك؟</string>
|
||||
<string name="wear_bt_pairing_allow">سماح</string>
|
||||
<string name="wear_bt_pairing_block">حظر</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> مسموح له بالمزامنة.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> تم حظره.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">طلب تفويض جديد من <xliff:g>%1$s</xliff:g></string>
|
||||
<string name="wear_bt_pairing_mismatch_message">تمت الموافقة على هذا الجهاز سابقًا، لكنه يعرض الآن رمز هوية مختلف. يحدث هذا عندما يتم إعادة تثبيت تطبيق Wear OS أو مسح بياناته. كما يمكن أن يشير إلى محاولة انتحال عنوان MAC عبر البلوتوث.\n\nاقبل فقط إذا قمت مؤخرًا بإعادة تثبيت تطبيق كاتيما Wear OS على هذه الساعة.</string>
|
||||
<string name="open_settings">فتح الإعدادات</string>
|
||||
<string name="wear_bt_channel_name">رفيق Wear OS</string>
|
||||
<string name="wear_bt_notification_title">مزامنة كاتيما لـ Wear OS نشطة</string>
|
||||
<string name="settings_category_title_smartwatch">الساعة الذكية مدعومة</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">فشل في بدء خدمة مزامنة Wear OS</string>
|
||||
<string name="wear_bt_critical_error_channel_name">خطأ كبير في مزامنة Wear OS</string>
|
||||
</resources>
|
||||
@@ -226,10 +226,10 @@
|
||||
<string name="show_name_below_image_thumbnail">Наименование</string>
|
||||
<string name="permissionReadCardsLabel">Четене на карти на Catima</string>
|
||||
<string name="permissionReadCardsDescription">четене на картите на Catima с всички подробности, включително бележки и изображения</string>
|
||||
<string name="settings_allow_content_provider_read_title">Разрешаване на достъп на други приложения до данните</string>
|
||||
<string name="settings_allow_content_provider_read_title">Синхронизиране чрез Gadgetbridge</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Необходимо за работата на някои скенери</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Без заключване на екрана при преглед на карта</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Приложенията ще искат разрешение, за да получат достъп</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Разрешава на приложението Gadgetbridge да синхронизира картите с поддържаните часовници</string>
|
||||
<string name="settings_oled_dark_summary">Намалява разхода на батерия от OLED дисплеи</string>
|
||||
<string name="settings_category_title_cards">Преглед на карта</string>
|
||||
<string name="settings_category_title_general">Общи</string>
|
||||
@@ -303,4 +303,11 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC е на пауза</string>
|
||||
<string name="change_settings">Настройки</string>
|
||||
<string name="nfc_block_system_error">Грешка при спиране на NFC</string>
|
||||
<string name="sort_by_most_recently_added">Поредност на добавяне</string>
|
||||
<string name="zoomFrontImage">Увеличаване на предното изображение</string>
|
||||
<string name="zoomBackImage">Увеличаване на задното изображение</string>
|
||||
<string name="show_card_id">Показване номера на картата</string>
|
||||
<string name="open_in_gallery">Отваряне в галерията</string>
|
||||
<string name="settings_wear_sync">Синхронизиране с Wear OS</string>
|
||||
<string name="settings_wear_sync_summary">Разрешава на приложението за Wear OS да чете картите чрез Bluetooth</string>
|
||||
</resources>
|
||||
@@ -3,13 +3,13 @@
|
||||
<string name="save">সংরক্ষণ</string>
|
||||
<string name="cancel">বাতিল</string>
|
||||
<string name="unstar">তারা মুক্ত</string>
|
||||
<string name="star">তারা</string>
|
||||
<string name="barcodeType">বারকোড ধরন</string>
|
||||
<string name="note">বিঃদ্রঃ</string>
|
||||
<string name="storeName">দোকানের নাম</string>
|
||||
<string name="star">পছন্দের তালিকায় যোগ করুন</string>
|
||||
<string name="barcodeType">বারকোডের ধরন</string>
|
||||
<string name="note">উল্লেখ্য</string>
|
||||
<string name="storeName">নাম</string>
|
||||
<string name="noMatchingGiftCards">কোনো ফলাফল পাওয়া যায়নি। অনুসন্ধানের বাক্যটি বদলে দেখুন।</string>
|
||||
<string name="noGiftCards">প্লাস বোতামটি টিপে একটি কার্ড যোগ করুন বা ⋮ মেনু থেকে কিছু নিয়ে আসুন।</string>
|
||||
<string name="action_add">কর্ম যোগ</string>
|
||||
<string name="action_add">যোগ করুন</string>
|
||||
<string name="all">সব</string>
|
||||
<string name="noGroupCards">এই গ্রুপটি খালি</string>
|
||||
<string name="noGroups">+ যোগ বোতামটি টিপে গ্রুপ যোগ করুন যাতে তাদের শ্রেণীকরণ করা যায়।</string>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<string name="action_search">Cerca</string>
|
||||
<string name="deleteTitle">Elimina la targeta</string>
|
||||
<string name="welcome">Benvingut a Catima</string>
|
||||
<string name="noGiftCards">Fes clic al botó + per afegir una targeta, o importa des del menú ⋮</string>
|
||||
<string name="noGiftCards">Fes clic al botó mes + 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>
|
||||
@@ -91,7 +91,7 @@
|
||||
<string name="importCancelled">Importació anulada</string>
|
||||
<string name="exportCancelled">Exportació cancelada</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="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>
|
||||
@@ -153,7 +153,7 @@
|
||||
<string name="enter_card_id">Introdueixi el nombre ID o text de la seva targeta</string>
|
||||
<string name="card_id_must_not_be_empty">ID de la Targeta no pot estar buit</string>
|
||||
<string name="add_a_card_in_a_different_way">Afegeix una targeta de forma diferent</string>
|
||||
<string name="unstar">Treure entre les preferides</string>
|
||||
<string name="unstar">Treure de les preferides</string>
|
||||
<string name="star">Afegir a les preferides</string>
|
||||
<string name="cancel">Cancel•la</string>
|
||||
<string name="deleteConfirmation">Vols eliminar de forma permanent aquesta targeta?</string>
|
||||
|
||||
@@ -230,9 +230,9 @@
|
||||
<string name="show_validity">Zobrazit platnost</string>
|
||||
<string name="show_balance">Zobrazit zůstatek</string>
|
||||
<string name="permissionReadCardsDescription">číst vaše karty Catima a všechny jejich podrobnosti, včetně poznámek a obrázků</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Aplikace budou i tak muset požádat o povolení k poskytnutí přístupu</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Umožňuje aplikaci Gadgetbridge synchronizovat vaše karty do podporovaných chytrých hodinek</string>
|
||||
<string name="permissionReadCardsLabel">Číst karty Catima</string>
|
||||
<string name="settings_allow_content_provider_read_title">Umožnit ostatním aplikacím přístup k mým datům</string>
|
||||
<string name="settings_allow_content_provider_read_title">Synchronizace s Gadgetbridge</string>
|
||||
<string name="settings_keep_screen_on_summary">Při prohlížení karty vypnout časovač zhasnutí obrazovky</string>
|
||||
<string name="settings_oled_dark_summary">Snižuje používání baterie na displejích OLED</string>
|
||||
<string name="settings_category_title_privacy">Soukromí</string>
|
||||
@@ -309,4 +309,51 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC je pozastaveno</string>
|
||||
<string name="change_settings">Změnit nastavení</string>
|
||||
<string name="nfc_block_system_error">Nepodařilo se pozastavit NFC</string>
|
||||
<string name="sort_by_most_recently_added">Nedávno přidáno</string>
|
||||
<string name="zoomFrontImage">Přiblížit přední obrázek</string>
|
||||
<string name="zoomBackImage">Přiblížit zadní obrázek</string>
|
||||
<string name="show_card_id">Zobrazit ID karty</string>
|
||||
<string name="open_in_gallery">Otevřít v galerii</string>
|
||||
<string name="settings_wear_sync">Synchronizovat s Wear OS</string>
|
||||
<string name="settings_wear_sync_summary">Umožňuje doprovodné aplikaci Wear OS číst vaše karty skrze Bluetooth</string>
|
||||
<string name="wear_sync_permission_required">Catima potřebuje oprávnění, které jste zamítli, k synchronizaci s vašimi chytrými hodinkami Wear OS</string>
|
||||
<string name="settings_wear_sync_devices">Povolená zařízení Wear OS</string>
|
||||
<string name="settings_wear_sync_devices_summary">Správa zařízení, u kterých je povolena synchronizace skrze Bluetooth</string>
|
||||
<string name="settings_wear_sync_no_devices">Žádná povolená zařízení</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> povolené zařízení</item>
|
||||
<item quantity="few"><xliff:g>%d</xliff:g> povolená zařízení</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> povolených zařízení</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> povolených zařízení</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Zablokovaná zařízení Wear OS</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Spravovat zablokovaná zařízení Wear OS</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Žádná zablokovaná zařízení</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> zablokované zařízení</item>
|
||||
<item quantity="few"><xliff:g>%d</xliff:g> zablokovaná zařízení</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> zablokovaných zařízení</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> zablokovaných zařízení</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">Odstranit zařízení?</string>
|
||||
<string name="settings_wear_sync_unpair_message">Odstranit zařízení <xliff:g>%1$s</xliff:g> z aplikace Catima? Již nebude moci synchronizovat vaše karty.</string>
|
||||
<string name="settings_wear_sync_unblock_message">Odstranit zařízení <xliff:g>%1$s</xliff:g> ze seznamu zablokovaných? Bude moci znovu žádat o přístup.</string>
|
||||
<string name="settings_wear_sync_unpair">Odstranit</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Klepněte na zařízení pro jeho odstranění.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Párování zařízení s Wear OS</string>
|
||||
<string name="wear_bt_pairing_title">Zařízení s Wear OS požaduje synchronizaci</string>
|
||||
<string name="wear_bt_pairing_message">Povolit zařízení <xliff:g>%1$s</xliff:g> přístup k vašim kartám?</string>
|
||||
<string name="wear_bt_pairing_allow">Povolit</string>
|
||||
<string name="wear_bt_pairing_block">Zablokovat</string>
|
||||
<string name="wear_bt_pairing_allowed">Zařízení <xliff:g>%1$s</xliff:g> má nyní povolenou synchronizaci.</string>
|
||||
<string name="wear_bt_pairing_blocked">Zařízení <xliff:g>%1$s</xliff:g> bylo zablokováno.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Nový požadavek na autorizaci ze zařízení <xliff:g>%1$s</xliff:g></string>
|
||||
<string name="wear_bt_pairing_mismatch_message">Toto zařízení bylo v minulosti autorizováno, nyní se ale prezentuje jiným tokenem identity. K této situaci může dojít při reinstalaci aplikace Wear OS nebo vymazání jejích dat. Může to ale také značit pokus o maskování adresy Bluetooth MAC.\n\nPřijměte pouze, pokud jste na těchto hodinkách nedávno přeinstalovali aplikaci Catima pro Wear OS.</string>
|
||||
<string name="open_settings">Otevřít nastavení</string>
|
||||
<string name="wear_bt_channel_name">Doprovoná aplikace na Wear OS</string>
|
||||
<string name="wear_bt_notification_title">Synchronizace s Catima Wear OS je aktivní</string>
|
||||
<string name="settings_category_title_smartwatch">Podpora chytrých hodinek</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Nepodařilo se spustit synchronizační službu Wear OS</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Kritická chyba synchronizace s Wear OS</string>
|
||||
</resources>
|
||||
@@ -223,10 +223,10 @@
|
||||
<string name="show_balance">Betrag anzeigen</string>
|
||||
<string name="show_validity">Gültigkeitsdauer anzeigen</string>
|
||||
<string name="show_name_below_image_thumbnail">Namen unter dem Vorschaubild anzeigen</string>
|
||||
<string name="settings_allow_content_provider_read_title">Anderen Apps den Zugriff auf meine Daten gestatten</string>
|
||||
<string name="settings_allow_content_provider_read_title">Synchronisation mit Gadgetbrigde</string>
|
||||
<string name="permissionReadCardsLabel">Catima-Karten lesen</string>
|
||||
<string name="permissionReadCardsDescription">Lesen deiner Catima Karten mit allen Details, einschließlich Notizen und Bildern</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Apps müssen weiterhin eine Genehmigung beantragen, um Zugriff zu erhalten</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Ermöglicht der Gadgetbridge-App, deine Karten mit unterstützten Uhren zu synchronisieren</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Erforderlich für das Funktionieren einiger Scanner</string>
|
||||
<string name="settings_keep_screen_on_summary">Deaktiviert die Bildschirmzeitüberschreitung beim Anzeigen einer Karte</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Deaktiviert die Bildschirmsperre während der Anzeige einer Karte</string>
|
||||
@@ -303,4 +303,47 @@
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Deaktiviert vorübergehend NFC, sodass keine NFC-Zahlungen ausgelöst werden können, während eine Karte angezeigt wird</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">NFC vorübergehend deaktivieren</string>
|
||||
<string name="nfc_block_system_error">Pausieren von NFC ist fehlgeschlagen</string>
|
||||
<string name="show_card_id">Karten-ID anzeigen</string>
|
||||
<string name="zoomBackImage">Rückseite vergrößern</string>
|
||||
<string name="zoomFrontImage">Vorderseite vergrößern</string>
|
||||
<string name="sort_by_most_recently_added">Zuletzt hinzugefügt</string>
|
||||
<string name="open_in_gallery">In Bildergalerie öffnen</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Kritischer Wear OS Synchronisierungsfehler</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Synchronisierungsdienst zu Wear OS konnte nicht gestartet werden</string>
|
||||
<string name="settings_category_title_smartwatch">Smartwatch Unterstützung</string>
|
||||
<string name="wear_bt_notification_title">Catima Wear OS Synchronisierung ist aktiv</string>
|
||||
<string name="wear_bt_channel_name">Wear OS Begleit-App</string>
|
||||
<string name="open_settings">Einstellungen öffnen</string>
|
||||
<string name="wear_bt_pairing_mismatch_message">Dieses Gerät wurde zuvor autorisiert, besitzt nun aber einen anderen Identitäts-Token. Dies kann vorkommen, wenn die Daten der Wear OS App gelöscht wurden oder die App erneut installiert wurde. Es kann ebenso auf den Versuch einer Bluetooth MAC Manipulation hindeuten.\n\nAkzeptiere die Autorisierung nur, wenn die Catima Wear OS App kürzlich neu auf dieser Uhr installiert wurde.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Neue Anfrage zur Autorisation von <xliff:g>%1$s</xliff:g></string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> wurde blockiert.</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> darf jetzt synchronisieren.</string>
|
||||
<string name="wear_bt_pairing_block">Blockiert</string>
|
||||
<string name="wear_bt_pairing_allow">Zugelassen</string>
|
||||
<string name="wear_bt_pairing_message">Erlaube <xliff:g>%1$s</xliff:g> den Zugang zu deinen Karten?</string>
|
||||
<string name="wear_bt_pairing_title">Wear OS Gerät möchte synchronisieren</string>
|
||||
<string name="wear_bt_pairing_channel_name">Wear OS Gerät koppeln</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Tippe auf ein Gerät, um es zu entfernen.</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_unpair">Entfernen</string>
|
||||
<string name="settings_wear_sync_unblock_message">Entferne <xliff:g>%1$s</xliff:g> von der Blockierungsliste? Eine erneute Anfrage zum Zugriff ist dann möglich.</string>
|
||||
<string name="settings_wear_sync_unpair_message">Entferne <xliff:g>%1$s</xliff:g> von Catima? Es wird dann nicht mehr möglich sein Karten zu synchronisieren.</string>
|
||||
<string name="settings_wear_sync_unpair_title">Entferne Gerät?</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> Gerät blockiert</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> Geräte blockiert</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Keine blockierten Geräte</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Blockierte Wear OS Geräte verwalten</string>
|
||||
<string name="settings_wear_sync_blocked_devices">Blockierte Wear OS Geräte</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> Gerät zugelassen</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> Geräte zugelassen</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_no_devices">Keine zugelassenen Geräte</string>
|
||||
<string name="settings_wear_sync_devices_summary">Verwalte die Geräte, die per Bluetooth synchronisieren dürfen</string>
|
||||
<string name="settings_wear_sync_devices">Zugelassene Wear OS Geräte</string>
|
||||
<string name="wear_sync_permission_required">Catima benötigt die von dir verweigerte Erlaubnis, um mit deiner Wear OS Smartwatch zu synchronisieren</string>
|
||||
<string name="settings_wear_sync_summary">Erlaube der Wear OS Begleit-App die Karten per Bluetooth auszulesen</string>
|
||||
<string name="settings_wear_sync">Synchronisiere mit Wear OS</string>
|
||||
</resources>
|
||||
@@ -210,7 +210,7 @@
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Απενεργοποιεί το κλείδωμα οθόνης κατά την προβολή μιας κάρτας</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Απαραίτητο για να δουλέψει σε κάποιους σαρωτές</string>
|
||||
<string name="cameraPermissionRequired">Δικαίωμα πρόσβασης στην κάμερα απαραίτητο γι\' αυτή την ενέργεια…</string>
|
||||
<string name="settings_allow_content_provider_read_title">Να επιτρέπεται σε άλλες εφαρμογές να έχουν πρόσβαση στα δεδομένα μου</string>
|
||||
<string name="settings_allow_content_provider_read_title">Συγχρονισμός με Gadgetbridge</string>
|
||||
<string name="app_copyright_short">Πνευματικά δικαιώματα © Sylvia van Os και συνεργάτες</string>
|
||||
<string name="height">Ύψος</string>
|
||||
<string name="switchToFrontImage">Μετάβαση στην εμπρόσθια εικόνα</string>
|
||||
@@ -255,7 +255,7 @@
|
||||
<string name="show_validity">Εμφάνιση εγκυρότητας</string>
|
||||
<string name="add_a_card_in_a_different_way">Προσθέστε μια κάρτα με διαφορετικό τρόπο</string>
|
||||
<string name="card_id_must_not_be_empty">Ο κωδικός αριθμός της κάρτας δεν πρέπει να είναι κενός</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Οι εφαρμογές θα πρέπει ωστόσο να ζητήσουν άδεια για να τους δοθεί πρόσβαση</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Επιτρέπει στην εφαρμογή Gadgetbridge να συγχρονίζει τις κάρτες σας με τα υποστηριζόμενα ρολόγια</string>
|
||||
<string name="field_must_not_be_empty">Το πεδίο δεν πρέπει να είναι κενό</string>
|
||||
<string name="manually_enter_barcode_instructions">Εισάγετε τον κωδικό αριθμό ή το κείμενο στην κάρτα σας και πατήστε τον γραμμωτό κώδικα που μοιάζει με αυτόν της κάρτας σας.</string>
|
||||
<string name="add_manually_warning_title">Συνιστάται η σάρωση</string>
|
||||
@@ -303,4 +303,47 @@
|
||||
<string name="nfc_blocked_while_viewing_card">Το NFC είναι σε παύση</string>
|
||||
<string name="change_settings">Αλλαγή ρυθμίσεων</string>
|
||||
<string name="nfc_block_system_error">Αποτυχία παύσης NFC</string>
|
||||
<string name="sort_by_most_recently_added">Πρόσφατα προστεθέντα</string>
|
||||
<string name="zoomFrontImage">Μεγέθυνση μπροστινής εικόνας</string>
|
||||
<string name="zoomBackImage">Μεγέθυνση πίσω εικόνας</string>
|
||||
<string name="show_card_id">Εμφάνιση ταυτότητας κάρτας</string>
|
||||
<string name="open_in_gallery">Άνοιγμα στη συλλογή εικόνων</string>
|
||||
<string name="settings_wear_sync">Συγχρονισμός με Wear OS</string>
|
||||
<string name="settings_wear_sync_summary">Επιτρέπει στην συνοδευτική εφαρμογή Wear OS να διαβάζει τις κάρτες σας μέσω Bluetooth</string>
|
||||
<string name="wear_sync_permission_required">Η Catima χρειάζεται τα δικαιώματα που απορρίψατε για να συγχρονιστεί με το Wear OS smartwatch σας</string>
|
||||
<string name="settings_wear_sync_devices">Επιτρεπόμενες συσκευές Wear OS</string>
|
||||
<string name="settings_wear_sync_devices_summary">Διαχείριση συσκευών που επιτρέπεται να συγχρονίζονται μέσω Bluetooth</string>
|
||||
<string name="settings_wear_sync_no_devices">Δεν επιτρέπονται συσκευές</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> συσκευή επιτρέπεται</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> συσκευές επιτρέπονται</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Αποκλεισμένες συσκευές Wear OS</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Διαχείριση αποκλεισμένων συσκευών Wear OS</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Δεν υπάρχουν αποκλεισμένες συσκευές</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> συσκευή αποκλεισμένη</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> συσκευές αποκλεισμένες</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">Αφαίρεση συσκευής;</string>
|
||||
<string name="settings_wear_sync_unpair_message">Να καταργηθεί η <xliff:g>%1$s</xliff:g> από το Catima; Δεν θα είναι πλέον δυνατός ο συγχρονισμός των καρτών σας.</string>
|
||||
<string name="settings_wear_sync_unblock_message">Να καταργηθεί η <xliff:g>%1$s</xliff:g> από τη λίστα αποκλεισμένων; Θα μπορεί να ζητήσει ξανά πρόσβαση.</string>
|
||||
<string name="settings_wear_sync_unpair">Αφαίρεση</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Αγγίξτε μια συσκευή για να την καταργήσετε.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Σύζευξη συσκευών Wear OS</string>
|
||||
<string name="wear_bt_pairing_title">Η συσκευή Wear OS θέλει συγχρονισμό</string>
|
||||
<string name="wear_bt_pairing_message">Να επιτραπεί στη <xliff:g>%1$s</xliff:g> η πρόσβαση στις κάρτες σας;</string>
|
||||
<string name="wear_bt_pairing_allow">Να επιτραπεί</string>
|
||||
<string name="wear_bt_pairing_block">Φραγή</string>
|
||||
<string name="wear_bt_pairing_allowed">Η <xliff:g>%1$s</xliff:g> έχει πλέον δικαίωμα συγχρονισμού.</string>
|
||||
<string name="wear_bt_pairing_blocked">Η <xliff:g>%1$s</xliff:g> έχει αποκλειστεί.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Νέο αίτημα εξουσιοδότησης από <xliff:g>%1$s</xliff:g></string>
|
||||
<string name="wear_bt_pairing_mismatch_message">Αυτή η συσκευή είχε προηγουμένως εξουσιοδοτηθεί, αλλά τώρα παρουσιάζει διαφορετικό διακριτικό ταυτότητας. Αυτό συμβαίνει όταν η εφαρμογή Wear OS επανεγκαθίσταται ή τα δεδομένα της διαγράφονται. Μπορεί επίσης να υποδεικνύει μια προσπάθεια πλαστογράφησης MAC Bluetooth.\n\nΑποδεχθείτε μόνο εάν επανεγκαταστήσατε πρόσφατα την εφαρμογή Catima Wear OS σε αυτό το ρολόι.</string>
|
||||
<string name="open_settings">Άνοιγμα ρυθμίσεων</string>
|
||||
<string name="wear_bt_channel_name">Συνοδευτική εφαρμογή Wear OS</string>
|
||||
<string name="wear_bt_notification_title">Ο συγχρονισμός Wear OS του Catima είναι ενεργός</string>
|
||||
<string name="settings_category_title_smartwatch">Συγχρονισμός smartwatch</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Αποτυχία εκκίνησης της υπηρεσίας συγχρονισμού Wear OS</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Σοβαρό σφάλμα συγχρονισμού Wear OS</string>
|
||||
</resources>
|
||||
@@ -232,10 +232,10 @@
|
||||
<string name="show_balance">Mostrar el saldo</string>
|
||||
<string name="permissionReadCardsLabel">Leer Tarjetas Catima</string>
|
||||
<string name="permissionReadCardsDescription">lea sus tarjetas Catima y todos sus detalles, incluyendo notas e imágenes</string>
|
||||
<string name="settings_allow_content_provider_read_title">Permite a otras aplicaciones acceder a mis datos</string>
|
||||
<string name="settings_allow_content_provider_read_title">Sincronizar con Gadgetbridge</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Necesario para que funcione en algunos escáneres</string>
|
||||
<string name="settings_keep_screen_on_summary">Deshabilita el tiempo de espera de la pantalla mientras se ve una tarjeta</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Las aplicaciones todavía tendrán que solicitar permiso para conseguir acceso</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Permite a la app Gadgetbridge sincronizar tus tarjetas con los relojes soportados</string>
|
||||
<string name="settings_oled_dark_summary">Reduce el uso de batería en pantallas OLED</string>
|
||||
<string name="settings_category_title_cards">Vista de tarjeta</string>
|
||||
<string name="settings_category_title_general">General</string>
|
||||
@@ -309,4 +309,49 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC está pausado</string>
|
||||
<string name="change_settings">Cambiar ajustes</string>
|
||||
<string name="nfc_block_system_error">Incorrecto al pausar NFC</string>
|
||||
<string name="sort_by_most_recently_added">Más recientemente añadido</string>
|
||||
<string name="zoomFrontImage">Imagen frontal de zoom</string>
|
||||
<string name="zoomBackImage">Imagen trasera de zoom</string>
|
||||
<string name="show_card_id">Mostrar ID de tarjeta</string>
|
||||
<string name="settings_wear_sync">Sincronizar con Wear OS</string>
|
||||
<string name="settings_wear_sync_summary">Permite a la app de acompañamiento de Wear OS leer tus tarjetas a través de Bluetooth</string>
|
||||
<string name="wear_sync_permission_required">Catima necesita los permisos que has denegado para sincronizarse con tu reloj inteligente Wear OS</string>
|
||||
<string name="settings_wear_sync_devices">Dispositivos Wear OS permitidos</string>
|
||||
<string name="settings_wear_sync_devices_summary">Gestiona los dispositivos con permiso para sincronizarse por Bluetooth</string>
|
||||
<string name="settings_wear_sync_no_devices">No hay dispositivos permitidos</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> dispositivo permitido</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> dispositivos permitidos</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> dispositivos permitidos</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Dispositivos Wear OS bloqueados</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Gestiona los dispositivos Wear OS bloqueados</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">No hay dispositivos bloqueados</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> dispositivo bloqueado</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> dispositivos bloqueados</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> dispositivos bloqueados</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">¿Eliminar dispositivo?</string>
|
||||
<string name="settings_wear_sync_unpair_message">¿Eliminar <xliff:g>%1$s</xliff:g> de Catima? No podrá seguir sincronizándose con tus tarjetas.</string>
|
||||
<string name="settings_wear_sync_unblock_message">¿Eliminar <xliff:g>%1$s</xliff:g> de la lista de bloqueos? Podrá volver a solicitar acceso de nuevo.</string>
|
||||
<string name="settings_wear_sync_unpair">Eliminar</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Toca un dispositivo para eliminarlo.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Emparejamiento de dispositivos Wear OS</string>
|
||||
<string name="wear_bt_pairing_title">Un dispositivo Wear OS solicita sincronización</string>
|
||||
<string name="wear_bt_pairing_message">¿Conceder a <xliff:g>%1$s</xliff:g> acceso a tus tarjetas?</string>
|
||||
<string name="wear_bt_pairing_allow">Permitir</string>
|
||||
<string name="wear_bt_pairing_block">Bloquear</string>
|
||||
<string name="wear_bt_pairing_allowed">Ahora <xliff:g>%1$s</xliff:g> tiene permitido sincronizarse.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> ha sido bloqueado.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Nueva petición de autorización desde <xliff:g>%1$s</xliff:g></string>
|
||||
<string name="wear_bt_pairing_mismatch_message">Este dispositivo había sido autorizado previamente pero ahora emite un identificador diferente. Esto ocurre cuando la app de Wear OS se reinstala o se borran sus datos. También puede indicar un intento de suplantación MAC por Bluetooth.\n\nAcepta solo si has reinstalado recientemente la app de Catima para Wear OS en este reloj.</string>
|
||||
<string name="open_settings">Abrir ajustes</string>
|
||||
<string name="open_in_gallery">Abrir en galería de imágenes</string>
|
||||
<string name="wear_bt_channel_name">Aplicación de acompañamiento de Wear OS</string>
|
||||
<string name="wear_bt_notification_title">Sincronización activa con Catima Wear OS</string>
|
||||
<string name="settings_category_title_smartwatch">Soporte para reloj inteligente</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Fallo al iniciar el servicio de sincronización de Wear OS</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Error crítico de sincronización con Wear OS</string>
|
||||
</resources>
|
||||
@@ -86,7 +86,7 @@
|
||||
<string name="settings_system_theme">Süsteemi kujundus</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">See on vajalik mõnede skännerite toimimiseks</string>
|
||||
<string name="expiryStateSentenceExpired">Aegus: <xliff:g>%s</xliff:g></string>
|
||||
<string name="settings_allow_content_provider_read_summary">Selle valiku sisselülitamisel peavad muud rakendused lisaks küsima õigust vaadata kaartide andmeid</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Selle valiku sisselülitamisel lubad Gadgetbridge\'i abil sünkroonida kaarte toetatud nutikelladesse</string>
|
||||
<string name="noGroups">Kui soovid sarnaseid kaarte omavahel liigitada, siis + pluss nupuga lisa kaardigruppe</string>
|
||||
<string name="group_name_is_empty">Kaardigrupi nimi ei saa jääda tühjaks</string>
|
||||
<string name="groupsList">Grupid: <xliff:g>%s</xliff:g></string>
|
||||
@@ -139,7 +139,7 @@
|
||||
<string name="settings_keep_screen_on_summary">Lülitab kaardi vaatamise ajaks välja ekraani energiahalduse</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card">Keela ekraanilukustus</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Lülitab kaardi vaatamise ajaks välja ekraani lukustamise</string>
|
||||
<string name="settings_allow_content_provider_read_title">Luba teistel rakendustel minu andmeid lugeda</string>
|
||||
<string name="settings_allow_content_provider_read_title">Sünkrooni Gadgetbridge\'iga</string>
|
||||
<string name="importSuccessful">Andmed on imporditud</string>
|
||||
<string name="exportSuccessful">Andmed on eksporditud</string>
|
||||
<string name="enter_group_name">Sisesta kaardigrupi nimi</string>
|
||||
@@ -303,4 +303,47 @@
|
||||
<string name="change_settings">Muuda seadistusi</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Vältimaks, et lähiväljaside (NFC) abil toimiv makseliides käivitub, lülitatakse kaardi vaatamisel lähiväljaside (NFC) välja</string>
|
||||
<string name="nfc_block_system_error">Lähiväljaside (NFC) peatamine ei õnnestunud</string>
|
||||
<string name="sort_by_most_recently_added">Viimasena lisatud</string>
|
||||
<string name="zoomFrontImage">Suumi esikaane pilti</string>
|
||||
<string name="zoomBackImage">Suumi tagakaane pilti</string>
|
||||
<string name="show_card_id">Näita kaardi tunnust</string>
|
||||
<string name="open_in_gallery">Ava pildigaleriis</string>
|
||||
<string name="settings_wear_sync">Sünkrooni Wear OS-iga</string>
|
||||
<string name="settings_wear_sync_devices">Lubatud Wear OS-i seadmed</string>
|
||||
<string name="settings_wear_sync_devices_summary">Halda seadmeid, mis võivad Bluetoothi abil kaarte sünkroonida</string>
|
||||
<string name="settings_wear_sync_summary">Võimaldab Wear OS-i kaasrakendusel lugeda sinu kaarte Bluetoothi abil</string>
|
||||
<string name="wear_sync_permission_required">Catima sinu Wear OS-i põhise nutikellaga sünkroonimiseks sinu luba</string>
|
||||
<string name="settings_wear_sync_no_devices">Ühtegi lubatud seadet pole</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> lubatud seade</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> lubatud seadet</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Blokeeritud Wear OS-i seadmed</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Halda blokeeritud Wear OS-i seadmeid</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Ühtegi blokeeritud seadet pole</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> blokeeritud seade</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> blokeeritud seadet</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">Kas eemaldad seadme?</string>
|
||||
<string name="settings_wear_sync_unpair_message">Kas eemaldad <xliff:g>%1$s</xliff:g> seadme Catimast? Peale seda pole enam sinu kaarte võimalik sinna sünkroonida.</string>
|
||||
<string name="settings_wear_sync_unblock_message">Kas eemaldad <xliff:g>%1$s</xliff:g> seadme blokeeritud seadmete loendist? Peale seda saab ta uuesti küsida ligipääsuõigusi.</string>
|
||||
<string name="settings_wear_sync_unpair">Eemalda</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Seadme eemaldamiseks klõpsa temal.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Paardumine Wear OS-i seadmega</string>
|
||||
<string name="wear_bt_pairing_title">Wear OS-i seade soovib sünkroonida</string>
|
||||
<string name="wear_bt_pairing_message">Kas lubad <xliff:g>%1$s</xliff:g> seadmele ligipääsu sinu kaartidele?</string>
|
||||
<string name="wear_bt_pairing_allow">Luba</string>
|
||||
<string name="wear_bt_pairing_block">Blokeeri</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> võib nüüd sünkroonida.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> on nüüd blokeeritud.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Uus autentimispäring <xliff:g>%1$s</xliff:g> seadmest</string>
|
||||
<string name="open_settings">Ava seadistused</string>
|
||||
<string name="wear_bt_channel_name">Wear OS-i kaaslasrakendus</string>
|
||||
<string name="wear_bt_notification_title">Catima Wear OS-i sünkroonimine on aktiivne</string>
|
||||
<string name="settings_category_title_smartwatch">Nutikellade tugi</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Wear OS-i sünkroonimisteenuse käivitamine ei õnnestunud</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Kriitiline viga Wear OS-iga sünkroonimisel</string>
|
||||
<string name="wear_bt_pairing_mismatch_message">See seade oli varem autoriseeritud, kuid esitab nüüd päringutele teise tunnusloa. See juhtub siis, kui Wear OS-i rakendus on uuesti paigaldatud või selle andmed on kustutatud. See võib viidata ka Bluetoothi MAC-aadressi võltsimise katsele.\n\nNõustu sellega ainult juhul, kui oled hiljuti sellele kellale Catima Wear OS-i rakenduse uuesti paigaldanud.</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
</resources>
|
||||
@@ -287,4 +287,9 @@
|
||||
<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="settings_disable_nfc_while_viewing_card">توقف NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">NFC را متوقف میکند تا هنگام مشاهده کارت، پرداختهای NFC فعال نشوند.</string>
|
||||
<string name="settings_wear_sync_blocked_devices">دستگاههای Wear OS مسدودشده</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">مدیریت دستگاههای Wear OS مسدودشده</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">هیچ دستگاه مسدودشدهای وجود ندارد</string>
|
||||
</resources>
|
||||
@@ -299,4 +299,14 @@
|
||||
<string name="back">Takaisin</string>
|
||||
<string name="acra_explain_crash">Jos mahdollista lisää tietoja toiminnastasi täällä:</string>
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> pysähtymisraportti</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">Keskeytä NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Keskeyttää NFC:n, jotta NFC-maksut eivät aktivoituisi korttia katsoessa</string>
|
||||
<string name="sort_by_most_recently_added">Viimeksi lisätty</string>
|
||||
<string name="zoomFrontImage">Suurenna etukuvaa</string>
|
||||
<string name="zoomBackImage">Suurenna takakuvaa</string>
|
||||
<string name="show_card_id">Näytä kortin tunniste</string>
|
||||
<string name="nfc_blocked_while_viewing_card">NFC on keskeytetty</string>
|
||||
<string name="change_settings">Muuta asetuksia</string>
|
||||
<string name="nfc_block_system_error">NFC:n keskeyttäminen epäonnistui</string>
|
||||
<string name="open_in_gallery">Avaa kuvagalleriassa</string>
|
||||
</resources>
|
||||
@@ -168,7 +168,7 @@
|
||||
<string name="version_history">Historique des versions</string>
|
||||
<string name="exportPassword">Définissez un mot de passe pour protéger vos exportations (facultatif)</string>
|
||||
<string name="exportPasswordHint">Entrez le mot de passe</string>
|
||||
<string name="editGroup">Modification du groupe : <xliff:g> %s </xliff:g></string>
|
||||
<string name="editGroup">Modification du groupe : <xliff:g>%s</xliff:g></string>
|
||||
<string name="noGiftCardsGroup">Créez des cartes, puis affectez-les au groupe ici</string>
|
||||
<string name="group_edit">Modifier le groupe</string>
|
||||
<string name="group_name_already_in_use">Nom de groupe déjà utilisé</string>
|
||||
@@ -231,8 +231,8 @@
|
||||
<string name="show_balance">Afficher le solde</string>
|
||||
<string name="permissionReadCardsLabel">Lire les cartes Catima</string>
|
||||
<string name="permissionReadCardsDescription">lisez vos cartes Catima et tous leurs détails, y compris les notes et les images</string>
|
||||
<string name="settings_allow_content_provider_read_title">Autoriser d\'autres applications à accéder à mes données</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Les applications devront toujours demander une autorisation pour obtenir l\'accès</string>
|
||||
<string name="settings_allow_content_provider_read_title">Synchroniser avec Gadgetbridge</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Permet à l\'appli Gadgetbridge de synchroniser vos cartes vers des montres prises en charge</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Nécessaire au fonctionnement de certains scanneurs</string>
|
||||
<string name="settings_keep_screen_on_summary">Désactive la temporisation de l\'écran lors de la visualisation d\'une carte</string>
|
||||
<string name="settings_oled_dark_summary">Réduit l\'utilisation de la batterie sur les écrans OLED</string>
|
||||
@@ -309,4 +309,49 @@
|
||||
<string name="nfc_blocked_while_viewing_card">Le NFC est en pause</string>
|
||||
<string name="change_settings">Modifier les paramètres</string>
|
||||
<string name="nfc_block_system_error">Erreur lors de la mise en pause du NFC</string>
|
||||
<string name="sort_by_most_recently_added">Récemment ajoutés</string>
|
||||
<string name="zoomFrontImage">Grossir l\'image avant</string>
|
||||
<string name="zoomBackImage">Grossir l\'image arrière</string>
|
||||
<string name="show_card_id">Afficher l\'ID de carte</string>
|
||||
<string name="open_in_gallery">Ouvrir dans la galerie d\'images</string>
|
||||
<string name="settings_wear_sync">Synchroniser avec Wear OS</string>
|
||||
<string name="settings_wear_sync_summary">Permet à l\'appli compagnon Wear OS de lire vos cartes par Bluetooth</string>
|
||||
<string name="wear_sync_permission_required">Catima a besoin de l\'autorisation que vous avez refusée pour se synchroniser avec votre montre connectée Wear OS</string>
|
||||
<string name="settings_wear_sync_devices">Appareils Wear OS autorisés</string>
|
||||
<string name="settings_wear_sync_devices_summary">Gérer les appareils autorisés à se synchroniser par Bluetooth</string>
|
||||
<string name="settings_wear_sync_no_devices">Aucun appareil autorisé</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> appareil autorisé</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> appareils autorisés</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> appareils autorisés</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Appareils Wear OS bloqués</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Gérer les appareils Wear OS bloqués</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Aucun appareil bloqué</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> appareil bloqué</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> appareils bloqués</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> appareils bloqués</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">Supprimer l\'appareil ?</string>
|
||||
<string name="settings_wear_sync_unpair_message">Retirer <xliff:g>%1$s</xliff:g> de Catima ? Il ne pourra plus synchroniser de cartes.</string>
|
||||
<string name="settings_wear_sync_unblock_message">Retirer <xliff:g>%1$s</xliff:g> de la liste des appareils bloqués ? Il pourra demander l\'accès à nouveau.</string>
|
||||
<string name="settings_wear_sync_unpair">Retirer</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Appuyer sur un appareil pour le supprimer.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Appairage d\'appareil Wear OS</string>
|
||||
<string name="wear_bt_pairing_title">L\'appareil Wear OS souhaite se synchroniser</string>
|
||||
<string name="wear_bt_pairing_message">Autoriser <xliff:g>%1$s</xliff:g> à accéder à vos cartes ?</string>
|
||||
<string name="wear_bt_pairing_allow">Autoriser</string>
|
||||
<string name="wear_bt_pairing_block">Bloquer</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> est maintenant autorisé à se synchroniser.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> a été bloqué.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Nouvelle demande d\'autorisation de <xliff:g>%1$s</xliff:g></string>
|
||||
<string name="open_settings">Ouvrir les paramètres</string>
|
||||
<string name="wear_bt_channel_name">Compagnon Wear OS</string>
|
||||
<string name="wear_bt_notification_title">Synchronisation Catima Wear OS active</string>
|
||||
<string name="settings_category_title_smartwatch">Prise en charge des montres connectées</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Erreur au démarrage du service de synchronisation Wear OS</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Erreur critique de synchronisation Wear OS</string>
|
||||
<string name="wear_bt_pairing_mismatch_message">Cet appareil était autorisé précédemment mais affiche maintenant un jeton d\'identité différent. Cela arrive lorsque l\'appli Wear OS est réinstallée ou ses données sont supprimées. Cela peut aussi indiquer une tentative d\'usurpation d\'adresse MAC du Bluetooth.\n\nAcceptez uniquement si vous avez récemment réinstallée l\'appli Catima Wear OS sur cette montre.</string>
|
||||
</resources>
|
||||
@@ -64,8 +64,8 @@
|
||||
<string name="settings_keep_screen_on_summary">Evita que se apague a pantalla cando se ve unha tarxeta</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card">Evitar bloqueo da pantalla</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Desactiva o bloqueo da pantalla cando se ve unha tarxeta</string>
|
||||
<string name="settings_allow_content_provider_read_title">Permitir a outras apps acceder aos meus datos</string>
|
||||
<string name="settings_allow_content_provider_read_summary">As app terán que solicitar igualmente o permiso para acceder aos datos</string>
|
||||
<string name="settings_allow_content_provider_read_title">Sincronizar con Gadgetbridge</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Permite á app Gadgetbridge sincronizar as túas tarxetas cos reloxos compatibles</string>
|
||||
<string name="settings_use_volume_keys_navigation">Cambiar de tarxeta cos botóns de volume</string>
|
||||
<string name="settings_use_volume_keys_navigation_summary">Usar os botóns de volume para cambiar a tarxeta que se mostra</string>
|
||||
<string name="intent_import_card_from_url_share_text">Quero compartir unha tarxeta contigo</string>
|
||||
@@ -302,4 +302,47 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC está en pausa</string>
|
||||
<string name="change_settings">Cambiar axustes</string>
|
||||
<string name="nfc_block_system_error">Non se detivo NFC</string>
|
||||
<string name="sort_by_most_recently_added">Máis recentes</string>
|
||||
<string name="zoomFrontImage">Zoom na imaxe frontal</string>
|
||||
<string name="zoomBackImage">Zoom na imaxe posterior</string>
|
||||
<string name="show_card_id">Mostrar ID da tarxeta</string>
|
||||
<string name="open_in_gallery">Abrir na galería de imaxes</string>
|
||||
<string name="settings_wear_sync">Sincronizar con Wear OS</string>
|
||||
<string name="settings_wear_sync_summary">Permite que a app asocidada de Wear OS lea as túas tarxetas utilizando Bluetooth</string>
|
||||
<string name="wear_sync_permission_required">Catima precisa os permisos que non concedeches para sincronizar co teu reloxo Wear OS</string>
|
||||
<string name="settings_wear_sync_devices">Dispositivos Wear OS permitidos</string>
|
||||
<string name="settings_wear_sync_devices_summary">Xestiona os dispositivos con permiso para sincronizar usando Bluetooth</string>
|
||||
<string name="settings_wear_sync_no_devices">Sen dispositivos permitidos</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> dispositivo permitido</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> dispositivos permitidos</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Dispositivos Wear OS bloqueados</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Xestiona os dispositivos Wear OS bloquedos</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Sen dispositivos bloqueados</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> dispositivo bloqueado</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> dispositivos bloqueados</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">Retirar dispositivo?</string>
|
||||
<string name="settings_wear_sync_unpair_message">Retirar <xliff:g>%1$s</xliff:g> de Catima? Non poderá seguir sincronizándose coas túas tarxetas.</string>
|
||||
<string name="settings_wear_sync_unblock_message">Retirar <xliff:g>%1$s</xliff:g> da lista de bloqueados? Poderá volver a solicitar o acceso.</string>
|
||||
<string name="settings_wear_sync_unpair">Retirar</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Toca nun dispositivo para retiralo.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Emparellando dispositivo Wear OS</string>
|
||||
<string name="wear_bt_pairing_title">O dispositivo Wear OS quere sincronizar</string>
|
||||
<string name="wear_bt_pairing_message">Permitirlle a <xliff:g>%1$s</xliff:g> que acceda ás tarxetas?</string>
|
||||
<string name="wear_bt_pairing_allow">Permitir</string>
|
||||
<string name="wear_bt_pairing_block">Bloquear</string>
|
||||
<string name="wear_bt_pairing_allowed">Agora <xliff:g>%1$s</xliff:g> ten permiso para sincronizar.</string>
|
||||
<string name="wear_bt_pairing_blocked">Bloqueaches a <xliff:g>%1$s</xliff:g>.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Nova solicitude de autorización desde <xliff:g>%1$s</xliff:g></string>
|
||||
<string name="wear_bt_pairing_mismatch_message">O dispositivo tiña permiso anteriormente pero agora presenta un token de identidade diferente. Isto acontece cando se reinstala a app Wear OS ou se limparon os datos. Tamén pode indicar un intento de suplantación utilizando a MAC de Bluetooth.\n\nSó acepta se reinstalaches recentemente a app Catima Wear OS neste reloxo.</string>
|
||||
<string name="open_settings">Abrir os axustes</string>
|
||||
<string name="wear_bt_channel_name">Aplicación Wear OS</string>
|
||||
<string name="wear_bt_notification_title">Sincronización activa Wear OS para Catima</string>
|
||||
<string name="settings_category_title_smartwatch">Compatibilidade con reloxos</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Fallou o inicio do servizo de sincronización con Wear OS</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Erro crítico ao sincronizar con Wear OS</string>
|
||||
</resources>
|
||||
@@ -220,8 +220,8 @@
|
||||
<string name="show_note">Tampilkan catatan</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>
|
||||
<string name="settings_allow_content_provider_read_title">Sinkronkan dengan Gadgetbridge</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Memungkinkan aplikasi Gadgetbridge menyinkronkan kartu Anda ke jam tangan yang didukung</string>
|
||||
<string name="settings_keep_screen_on_summary">Menonaktifkan batas waktu layar saat melihat kartu</string>
|
||||
<string name="settings_oled_dark_summary">Mengurangi penggunaan baterai pada layar OLED</string>
|
||||
<string name="settings_category_title_general">Umum</string>
|
||||
@@ -297,4 +297,45 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC dijeda</string>
|
||||
<string name="change_settings">Ubah pengaturan</string>
|
||||
<string name="nfc_block_system_error">Gagal menjeda NFC</string>
|
||||
<string name="show_card_id">Tampilkan ID Kartu</string>
|
||||
<string name="sort_by_most_recently_added">Terakhir ditambahkan</string>
|
||||
<string name="zoomFrontImage">Perbesar gambar depan</string>
|
||||
<string name="zoomBackImage">Perbesar gambar belakang</string>
|
||||
<string name="settings_wear_sync">Sinkronkan dengan Wear OS</string>
|
||||
<string name="settings_wear_sync_summary">Memungkinkan aplikasi pendamping Wear OS membaca kartu Anda melalui Bluetooth</string>
|
||||
<string name="wear_sync_permission_required">Catima memerlukan izin yang Anda tolak untuk menyinkronkan dengan smartwatch Wear OS Anda</string>
|
||||
<string name="settings_wear_sync_devices">Perangkat Wear OS yang diizinkan</string>
|
||||
<string name="settings_wear_sync_devices_summary">Kelola perangkat yang diizinkan untuk sinkronisasi melalui Bluetooth</string>
|
||||
<string name="settings_wear_sync_no_devices">Tidak ada perangkat yang diizinkan</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> perangkat diizinkan</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Perangkat Wear OS yang diblokir</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Kelola perangkat Wear OS yang diblokir</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Tidak ada perangkat yang diblokir</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> perangkat diblokir</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">Hapus perangkat?</string>
|
||||
<string name="settings_wear_sync_unpair_message">Hapus <xliff:g>%1$s</xliff:g> dari Catima? Perangkat ini tidak akan dapat menyinkronkan kartu Anda lagi.</string>
|
||||
<string name="settings_wear_sync_unblock_message">Hapus <xliff:g>%1$s</xliff:g> dari daftar yang diblokir? Perangkat ini akan dapat meminta akses lagi.</string>
|
||||
<string name="settings_wear_sync_unpair">Hapus</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Ketuk perangkat untuk menghapusnya.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Pemasangan perangkat Wear OS</string>
|
||||
<string name="wear_bt_pairing_title">Perangkat Wear OS ingin menyinkronkan</string>
|
||||
<string name="wear_bt_pairing_message">Izinkan <xliff:g>%1$s</xliff:g> mengakses kartu Anda?</string>
|
||||
<string name="wear_bt_pairing_allow">Izinkan</string>
|
||||
<string name="wear_bt_pairing_block">Blokir</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> kini diizinkan untuk menyinkronkan.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> telah diblokir.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Permintaan otorisasi baru dari <xliff:g>%1$s</xliff:g></string>
|
||||
<string name="wear_bt_pairing_mismatch_message">Perangkat ini sebelumnya diotorisasi tetapi kini menampilkan token identitas yang berbeda. Hal ini terjadi ketika aplikasi Wear OS diinstal ulang atau datanya dihapus. Hal ini juga dapat mengindikasikan upaya spoofing MAC Bluetooth.\n\nTerima hanya jika Anda baru-baru ini menginstal ulang aplikasi Catima Wear OS di jam tangan ini.</string>
|
||||
<string name="open_settings">Buka pengaturan</string>
|
||||
<string name="open_in_gallery">Buka di galeri gambar</string>
|
||||
<string name="wear_bt_channel_name">Pendamping Wear OS</string>
|
||||
<string name="wear_bt_notification_title">Sinkronisasi Catima Wear OS aktif</string>
|
||||
<string name="settings_category_title_smartwatch">Dukungan smartwatch</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Gagal memulai layanan sinkronisasi Wear OS</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Kesalahan kritis sinkronisasi Wear OS</string>
|
||||
</resources>
|
||||
@@ -309,4 +309,10 @@
|
||||
<string name="nfc_blocked_while_viewing_card">La funzione NFC è disattivata</string>
|
||||
<string name="change_settings">Modifica impostazioni</string>
|
||||
<string name="nfc_block_system_error">Impossibile mettere in pausa l\'NFC</string>
|
||||
<string name="sort_by_most_recently_added">Aggiunte più di recente</string>
|
||||
<string name="show_card_id">Mostra ID carta</string>
|
||||
<string name="zoomFrontImage">Zoom immagine fronte</string>
|
||||
<string name="zoomBackImage">Zoom immagine retro</string>
|
||||
<string name="open_in_gallery">Apri nella galleria immagini</string>
|
||||
<string name="open_settings">Apri le impostazioni</string>
|
||||
</resources>
|
||||
@@ -217,14 +217,14 @@
|
||||
<string name="settings_column_count_4">4</string>
|
||||
<string name="settings_category_title_privacy">プライバシー</string>
|
||||
<string name="exportCancelled">輸出がキャンセルされた</string>
|
||||
<string name="settings_allow_content_provider_read_title">他のアプリが自分のデータにアクセスすることを許可する</string>
|
||||
<string name="settings_allow_content_provider_read_title">Gadgetbridgeと同期</string>
|
||||
<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_allow_content_provider_read_summary">Gadgetbridge アプリによる対応スマートウォッチとカードの同期を許可する</string>
|
||||
<string name="settings_use_volume_keys_navigation">音量ボタンでカードを切り替え</string>
|
||||
<plurals name="balancePoints">
|
||||
<item quantity="other"><xliff:g>%s</xliff:g> ポイント</item>
|
||||
@@ -296,4 +296,45 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFCは停止中です</string>
|
||||
<string name="change_settings">設定を変更する</string>
|
||||
<string name="nfc_block_system_error">NFCの一時停止に失敗</string>
|
||||
<string name="sort_by_most_recently_added">最近追加されたもの</string>
|
||||
<string name="zoomFrontImage">券面を拡大</string>
|
||||
<string name="zoomBackImage">背面を拡大</string>
|
||||
<string name="show_card_id">カードIDを表示</string>
|
||||
<string name="open_in_gallery">ギャラリーを開く</string>
|
||||
<string name="settings_wear_sync">Wear OS と同期</string>
|
||||
<string name="settings_wear_sync_summary">Wear OS コンパニオンアプリによる Bluetooth 経由のカード読取を許可する</string>
|
||||
<string name="wear_sync_permission_required">貴方が拒否した権限は Catima が Wear OS スマートウォッチと同期するのに必要です</string>
|
||||
<string name="settings_wear_sync_devices">対応している Wear OS デバイス</string>
|
||||
<string name="settings_wear_sync_devices_summary">Bluetooth 経由での同期が許可されたデバイスの管理</string>
|
||||
<string name="settings_wear_sync_no_devices">デバイスが許可されていません</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> 台のデバイスが許可されています</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">ブロックされた Wear OS デバイス</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">ブロックされた Wear OS デバイスの管理</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">ブロックされているデバイスはありません</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> 台のデバイスがブロックされています</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">デバイスを削除しますか?</string>
|
||||
<string name="settings_wear_sync_unpair_message">Catima から <xliff:g>%1$s</xliff:g> を削除しますか? 削除するとカードの同期は出来なくなります。</string>
|
||||
<string name="settings_wear_sync_unblock_message">ブロックの一覧から <xliff:g>%1$s</xliff:g> を削除しますか? 削除すればアクセスを再要求できるようになります。</string>
|
||||
<string name="settings_wear_sync_unpair">削除</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">タップしてデバイスを削除します。</string>
|
||||
<string name="wear_bt_pairing_channel_name">Wear OS デバイスを接続しました</string>
|
||||
<string name="wear_bt_pairing_title">Wear OS デバイスとの同期を要求</string>
|
||||
<string name="wear_bt_pairing_message"><xliff:g>%1$s</xliff:g> にカードへのアクセスを許可しますか?</string>
|
||||
<string name="wear_bt_pairing_allow">許可</string>
|
||||
<string name="wear_bt_pairing_block">ブロック</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> への同期が許可されました。</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> はブロックされました。</string>
|
||||
<string name="wear_bt_pairing_mismatch_title"><xliff:g>%1$s</xliff:g>からの新たな認証要求</string>
|
||||
<string name="wear_bt_pairing_mismatch_message">当該デバイスは以前に認証済みでしたが異なる識別トークンを現段階で提示しています。これは Wear OS アプリを再インストールした場合もしくはそのデータが消去された場合に発生します。Bluetooth MACアドレスのなりすまし攻撃の試行の可能性もあります。\n\n当該スマートウォッチで最近 Catima Wear OS アプリを再インストールした場合のみ承認して下さい。</string>
|
||||
<string name="open_settings">設定を開く</string>
|
||||
<string name="wear_bt_channel_name">Wear OS コンパニオン</string>
|
||||
<string name="wear_bt_notification_title">Catima は Wear OS と同期中です</string>
|
||||
<string name="settings_category_title_smartwatch">対応するスマートウォッチです</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Wear OSとの同期サービスの開始が失敗しました</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Wear OS 同期の深刻なエラー</string>
|
||||
</resources>
|
||||
@@ -11,10 +11,10 @@
|
||||
<string name="delete">Ištrinti</string>
|
||||
<string name="confirm">Patvirtinti</string>
|
||||
<string name="ok">Gerai</string>
|
||||
<string name="editCardTitle">Redaguoti lojalumo kortelę</string>
|
||||
<string name="addCardTitle">Pridėti lojalumo kortelę</string>
|
||||
<string name="editCardTitle">Redaguoti kortelę</string>
|
||||
<string name="addCardTitle">Pridėti kortelę</string>
|
||||
<string name="scanCardBarcode">Nuskaityti brūkšninį kodą</string>
|
||||
<string name="importExport">Importuoti/Exportuoti</string>
|
||||
<string name="importExport">Importas/Exportas</string>
|
||||
<string name="exportName">Exportuoti</string>
|
||||
<string name="importFailed">Nepavyko importuoti</string>
|
||||
<string name="exportFailed">Nepavyko eksportuoti</string>
|
||||
@@ -43,7 +43,7 @@
|
||||
<string name="app_loyalty_card_keychain">Lojalumo kortelės</string>
|
||||
<string name="moveBarcodeToTopOfScreen">Perkelti brūkšninį kodą į ekrano viršų</string>
|
||||
<string name="settings_display_barcode_max_brightness">Šviesinti ekraną</string>
|
||||
<string name="failedParsingImportUriError">Nepavyko iššifruoti importo URI</string>
|
||||
<string name="failedParsingImportUriError">Nepavyko nuskaityti importo URI</string>
|
||||
<string name="turn_flashlight_off">Išjunkti žibintuvėlį</string>
|
||||
<string name="turn_flashlight_on">Įjunkti žibintuvėlį</string>
|
||||
<string name="failedGeneratingShareURL">Nepavyko sugeneruoti bendrinamo URL</string>
|
||||
@@ -65,11 +65,9 @@
|
||||
<string name="setBarcodeId">Nustatyti brūkšninio kodo reikšmę</string>
|
||||
<string name="sameAsCardId">Tokia pat kaip ID</string>
|
||||
<string name="barcodeId">Brūkšninio kodo reikšmė</string>
|
||||
<string name="importFidmeMessage">Pasirinkite <i>fidme-export-request-xxxxxx.zip</i> eksportą iš FidMe, kurį norite importuoti, ir po to brūkšninių kodų tipus pasirinkite rankiniu būdu.
|
||||
\nSukurkite jį iš savo FidMe profilio, pasirinkę Duomenų apsauga ir pirmiausia paspaudę Išgauti mano duomenis.</string>
|
||||
<string name="importFidmeMessage">Importavimui pasirinkite eksportą iš FidMe ir po to brūkšninių kodų tipus pasirinkite rankiniu būdu. \nSukurkite jį iš savo FidMe profilio pasirinkę \"Duomenų apsauga\" ir po to paspausdami \"Išgauti mano duomenis\".</string>
|
||||
<string name="importFidme">Importuoti iš FidMe</string>
|
||||
<string name="importCatimaMessage">Norėdami importuoti, pasirinkite savo <i> catima.zip </i> eksportavimą iš Catima.
|
||||
\nSukurkite ją iš kitos Catima programos importavimo / eksportavimo meniu, pirmiausia ten paspausdami Eksportuoti.</string>
|
||||
<string name="importCatimaMessage">Norėdami importuoti, pasirinkite savo <i> catima.zip </i> eksportavimą iš Catima. \nSukurkite ją iš kitos Catima programos importavimo/eksportavimo meniu paspausdami \"Eksportuoti\".</string>
|
||||
<string name="importCatima">Importuoti iš Catima</string>
|
||||
<string name="accept">Priimti</string>
|
||||
<string name="privacy_policy">Privatumo politika</string>
|
||||
@@ -117,8 +115,8 @@
|
||||
<string name="settings">Nustatymai</string>
|
||||
<string name="starImage">Mėgstamiausia žvaigždė</string>
|
||||
<string name="thumbnailDescription">Miniatiūra</string>
|
||||
<string name="app_resources">Laisvosios trečiųjų šalių ištekliai: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Laisvosios trečiųjų šalių bibliotekos: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Trečiųjų šalių ištekliai: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Trečiųjų šalių bibliotekos: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_copyright_old">Paremta Loyalty Card Keychain
|
||||
\nautorinės teisės © 2016–2020 Branden Archer</string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Autorinės teisės © 2019–<xliff:g>%d</xliff:g> Sylvia van Os</string>
|
||||
@@ -248,7 +246,7 @@
|
||||
<string name="add_manually_warning_title">Rekomenduojama skenuoti</string>
|
||||
<string name="continue_">Tęsti</string>
|
||||
<string name="add_manually_warning_message">Kai kuriuose parduotuvėse brūkšninio kodo vertė gali skirtis nuo kortelėje nurodyto numerio. Dėlto įvedus brūkšninį kodą rankiniu būdu gali nepavykti. Vietoj to, labai rekomenduojama nuskaityti brūkšninį kodą naudojant kamerą. Ar vis tiek norite tęsti?</string>
|
||||
<string name="permissionReadCardsLabel">Skenuoti Catima Korteles</string>
|
||||
<string name="permissionReadCardsLabel">Skaityti Catima korteles</string>
|
||||
<string name="settings_category_title_cards">Kortelių peržiūra</string>
|
||||
<string name="settings_category_title_general">Bendri</string>
|
||||
<string name="settings_category_title_privacy">Privatumąi</string>
|
||||
@@ -281,4 +279,6 @@
|
||||
<string name="settings_column_count_portrait">Stulpeliai vertikaliame režime</string>
|
||||
<string name="settings_column_count_landscape">Stulpeliai horizontaliame režime</string>
|
||||
<string name="settings_automatic_column_count">Automatiškai</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">NFC pauzė</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Pristabdo NFC, kad peržiūrint kortelę nebūtų suaktyvinti NFC mokėjimai</string>
|
||||
</resources>
|
||||
@@ -220,8 +220,8 @@
|
||||
<string name="add_a_card_in_a_different_way">Pievienot karti citā veidā</string>
|
||||
<string name="add_manually_warning_message">Dažām kartēm svītrkoda vērtība atšķiras no numura, kas ir rakstīts uz tās. Šī iemesla dēļ pašrocīgi ievadīts svītrkods ne vienmēr var darboties. Ir ieteicams nolasīt svītrkodu ar kameru. Vai joprojām turpināt?</string>
|
||||
<string name="noCameraFoundGuideText">Šķiet, ka ierīcei nav kameras. Ja tā tomēr ir, jāmēģina pārsāknēt ierīci. Pretējā gadījumā jāizmanto zemāk esošā poga \"Vairāk iespēju\", lai pievienot svītrkodu citā veidā.</string>
|
||||
<string name="settings_allow_content_provider_read_title">Ļaut citām lietotnēm piekļūt maniem datiem</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Lietotnēm joprojām būs jāpieprasa atļauja, lai nodrošinātu piekļuvi</string>
|
||||
<string name="settings_allow_content_provider_read_title">Vienādot ar Gadgetbridge</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Atļauj Gadgetbridge lietotnei vienādot kartes ar atbalstītajiem pulksteņiem</string>
|
||||
<string name="show_note">Rādīt piezīmi</string>
|
||||
<string name="show_balance">Rādīt atlikumu</string>
|
||||
<string name="settings_category_title_privacy">Privātums</string>
|
||||
@@ -309,4 +309,49 @@
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Aptur NFC, lai novērstu NFC maksājumu izsaukšanu kartes apskatīšanas laikā</string>
|
||||
<string name="nfc_blocked_while_viewing_card">NFC ir apturēts</string>
|
||||
<string name="change_settings">Mainīt iestatījumus</string>
|
||||
<string name="open_in_gallery">Atvērt attēlu galerijā</string>
|
||||
<string name="sort_by_most_recently_added">Visnesenāk pievienotās</string>
|
||||
<string name="zoomFrontImage">Pietuvināt priekšpuses attēlu</string>
|
||||
<string name="zoomBackImage">Pietuvināt aizmugures attēlu</string>
|
||||
<string name="show_card_id">Rādīt kartes Id</string>
|
||||
<string name="settings_wear_sync">Vienādot ar Wear OS</string>
|
||||
<string name="settings_wear_sync_summary">Ļauj Wear OS palīglietotnei lasīt kartes ar Bluetooth starpniecību</string>
|
||||
<string name="wear_sync_permission_required">Catima ir nepieciešamas iepriekš noraidītās atļaujas, lai vienādotu ar Wear OS viedpulksteni</string>
|
||||
<string name="settings_wear_sync_devices">Atļautās Wear OS ierīces</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Būtiska Wear OS vienādošanas kļūda</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Neizdevās palaist Wear OS vienādošanas pakalpojumu</string>
|
||||
<string name="settings_category_title_smartwatch">Viedpulksteņu atbalsts</string>
|
||||
<string name="wear_bt_notification_title">Catima Wear OS vienādošana darbojas</string>
|
||||
<string name="wear_bt_channel_name">Wear OS palīgs</string>
|
||||
<string name="open_settings">Atvērt iestatījumus</string>
|
||||
<string name="wear_bt_pairing_mismatch_message">Šī ierīce iepriekš bija pilnvarota, bet tagad norāda citu identitātes pilnvaru. Tas notiek, kad Wear OS lietotne ir uzstādīta no jauna vai ir notīrīti tās dati. Tas var norādīt arī uz Bluetooth MAC atdarināšanas mēģinājumu.\n\nPieņemt tikai tad, ja šajā pulkstenī Catima Wear OS lietotne tika uzstādīta no jauna.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Jauns pilnvarošanas pieprasījums no <xliff:g>%1$s</xliff:g></string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> tika liegts.</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> tagad ir atļauta vienādošana.</string>
|
||||
<string name="wear_bt_pairing_block">Liegt</string>
|
||||
<string name="wear_bt_pairing_allow">Atļaut</string>
|
||||
<string name="wear_bt_pairing_message">Atļaut <xliff:g>%1$s</xliff:g> piekļūt kartēm?</string>
|
||||
<string name="wear_bt_pairing_title">Wear OS ierīce vēlas vienādot</string>
|
||||
<string name="wear_bt_pairing_channel_name">Wear OS ierīces pārošana</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Piesist ierīces nosaukumam, lai to noņemtu.</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_unpair">Noņemt</string>
|
||||
<string name="settings_wear_sync_unblock_message">Noņemt <xliff:g>%1$s</xliff:g> no liegumu saraksta? Tā atkal varēs pieprasīt piekļuvi.</string>
|
||||
<string name="settings_wear_sync_unpair_message">Noņemt <xliff:g>%1$s</xliff:g> no Catima? Tā vairs nevarēs vienādot kartes.</string>
|
||||
<string name="settings_wear_sync_unpair_title">Noņemt ierīci?</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="zero">Liegtas <xliff:g>%d</xliff:g> ierīces</item>
|
||||
<item quantity="one">Liegta <xliff:g>%d</xliff:g> ierīce</item>
|
||||
<item quantity="other">Liegtas <xliff:g>%d</xliff:g> ierīces</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Nav liegtu ierīču</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Liegto Wear OS ierīču pārvaldīšana</string>
|
||||
<string name="settings_wear_sync_blocked_devices">Liegtās Wear OS ierīces</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="zero">Atļautas <xliff:g>%d</xliff:g> ierīces</item>
|
||||
<item quantity="one">Atļauta <xliff:g>%d</xliff:g> ierīce</item>
|
||||
<item quantity="other">Atļautas <xliff:g>%d</xliff:g> ierīces</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_no_devices">Nav atļautu ierīču</string>
|
||||
<string name="settings_wear_sync_devices_summary">Ierīču, kurām ir atļauts vienādot ar Bluetooth starpniecību, pārvaldīšana</string>
|
||||
</resources>
|
||||
@@ -32,10 +32,10 @@
|
||||
<string name="exportSuccessfulTitle">Exporteren voltooid</string>
|
||||
<string name="exportFailedTitle">Exporteren mislukt</string>
|
||||
<string name="exportFailed">Het exporteren is mislukt</string>
|
||||
<string name="importing">Bezig met importeren…</string>
|
||||
<string name="importing">Importeren…</string>
|
||||
<string name="exporting">Bezig met exporteren…</string>
|
||||
<string name="exportOptionExplanation">De gegevens worden weggeschreven op een locatie naar keuze</string>
|
||||
<string name="importOptionFilesystemTitle">Importeren uit bestandssysteem</string>
|
||||
<string name="importOptionFilesystemTitle">Importeren</string>
|
||||
<string name="importOptionFilesystemExplanation">Kies een specifiek bestand van het bestandssysteem</string>
|
||||
<string name="importOptionFilesystemButton">Van bestandssysteem</string>
|
||||
<string name="about">Over</string>
|
||||
@@ -52,7 +52,7 @@
|
||||
<string name="settings_display_barcode_max_brightness">Scherm oplichten bij tonen van barcode</string>
|
||||
<string name="intent_import_card_from_url_share_text">Ik wil een klantenkaart met je delen</string>
|
||||
<string name="all">Alles</string>
|
||||
<string name="importSuccessful">De gegevens zijn geïmporteerd</string>
|
||||
<string name="importSuccessful">Gegevens geïmporteerd</string>
|
||||
<string name="deleteConfirmationGroup">Groep verwijderen\?</string>
|
||||
<string name="noGroups">Druk op de plusknop (‘+’) om een groep toe te voegen</string>
|
||||
<string name="exportSuccessful">De gegevens zijn geëxporteerd</string>
|
||||
@@ -95,8 +95,8 @@
|
||||
<string name="accept">Accepteren</string>
|
||||
<string name="importVoucherVaultMessage">Kies het te importeren exportbestand. \nGa naar het exportmenu van Voucher Vault om een exportbestand samen te stellen.</string>
|
||||
<string name="importVoucherVault">Importeren uit Voucher Vault</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Kies het te importeren exportbestand. \nGa naar het import-/exportmenu van Klantenkaartkluis om een exportbestand samen te stellen.</string>
|
||||
<string name="importLoyaltyCardKeychain">Importeren uit Klantenkaartkluis</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Kies het te importeren exportbestand. \nGa naar het import-/exportmenu van Loyalty Card Keychain om een exportbestand samen te stellen.</string>
|
||||
<string name="importLoyaltyCardKeychain">Importeren uit Loyalty Card Keychain</string>
|
||||
<string name="importFidmeMessage">Kies het te importeren exportbestand en kies nadien de juiste barcodes. \nGa naar je FidMe-profiel en druk op ‘Gegevensbescherming’ om een exportbestand samen te stellen.</string>
|
||||
<string name="importCatimaMessage">Kies het te importeren exportbestand. \nGa naar het import-/exportmenu van Catima op een ander apparaat om een exportbestand samen te stellen.</string>
|
||||
<string name="importFidme">Importeren uit FidMe</string>
|
||||
@@ -225,9 +225,9 @@
|
||||
<string name="show_note">Aantekening tonen</string>
|
||||
<string name="show_validity">Geldigheid tonen</string>
|
||||
<string name="permissionReadCardsLabel">Catimakaarten uitlezen</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Let op: apps moeten nog steeds om toestemming vragen</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Hiermee kan de Gadgetbridge-app jouw kaarten synchroniseren met ondersteunde horloges</string>
|
||||
<string name="permissionReadCardsDescription">Lees alle Catima-kaarten uit, inclusief alle details, aantekeningen en afbeeldingen</string>
|
||||
<string name="settings_allow_content_provider_read_title">Andere apps toegang geven tot gegevens</string>
|
||||
<string name="settings_allow_content_provider_read_title">Synchroniseren met Gadgetbridge</string>
|
||||
<string name="settings_keep_screen_on_summary">Laat het scherm niet op zwart gaan tijdens het bekijken van een kaart</string>
|
||||
<string name="settings_category_title_privacy">Privacy</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Benodigd voor de werking van sommige scanners</string>
|
||||
@@ -249,7 +249,7 @@
|
||||
<string name="field_must_not_be_empty">Dit veld is vereist</string>
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="add_manually_warning_title">Scannen wordt aangeraden</string>
|
||||
<string name="add_manually_warning_message">Bij sommige kaarten wijkt de barcodewaarde af van het nummer op de kaart. Hierdoor werkt het handmatig invoeren van een barcode niet altijd. Het wordt aangeraden om in plaats daarvan de barcode met je camera te scannen. Wilt u nog steeds doorgaan?</string>
|
||||
<string name="add_manually_warning_message">Bij sommige kaarten wijkt de barcodewaarde af van het nummer op de kaart. Hierdoor werkt het handmatig invoeren van een barcode niet altijd. Het wordt aangeraden om in plaats daarvan de barcode met je camera te scannen. Wil je nog steeds doorgaan?</string>
|
||||
<string name="continue_">Ga door</string>
|
||||
<string name="spend">Geef uit</string>
|
||||
<string name="receive">Ontvang</string>
|
||||
@@ -259,7 +259,7 @@
|
||||
<string name="failedLaunchingFileManager">Geen ondersteunde bestandsbeheerder aangetroffen</string>
|
||||
<string name="multipleBarcodesFoundPleaseChooseOne">Welke van de gevonden barcodes wil je gebruiken?</string>
|
||||
<string name="pageWithNumber">Pagina <xliff:g>%d</xliff:g></string>
|
||||
<string name="noCameraFoundGuideText">Uw apparaat lijkt niet over een camera te beschikken. Als het dat wel doet, probeer uw apparaat dan opnieuw op te starten. Anders gebruikt u de knop \"Meer opties\" beneden om handmatig een streepjescode in te voeren.</string>
|
||||
<string name="noCameraFoundGuideText">Jouw apparaat lijkt niet over een camera te beschikken. Als het dat wel doet, probeer het apparaat dan opnieuw op te starten. Gebruik anders de knop \"Meer opties\" beneden om handmatig een streepjescode in te voeren.</string>
|
||||
<string name="importCancelled">Importeren geannuleerd</string>
|
||||
<string name="exportCancelled">Exporteren geannuleerd</string>
|
||||
<string name="useFrontImage">Gebruik voorzijde van kaart</string>
|
||||
@@ -299,8 +299,51 @@
|
||||
<string name="back">Terug</string>
|
||||
<string name="barcodeEncoding">Barcodecodering</string>
|
||||
<string name="change_settings">Verander instellingen</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">Pauzeer NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">NFC pauzeren</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Pauzeert NFC om te voorkomen dat NFC-betalingen activeren tijdens het bekijken van een kaart</string>
|
||||
<string name="nfc_blocked_while_viewing_card">NFC is gepauzeerd</string>
|
||||
<string name="nfc_block_system_error">Pauzeren van NFC mislukt</string>
|
||||
<string name="nfc_block_system_error">NFC pauzeren is mislukt</string>
|
||||
<string name="sort_by_most_recently_added">Op onlangs toegevoegd</string>
|
||||
<string name="show_card_id">Kaart ID tonen</string>
|
||||
<string name="zoomFrontImage">Voorzijde inzoomen</string>
|
||||
<string name="zoomBackImage">Achterzijde inzoomen</string>
|
||||
<string name="open_in_gallery">Open in afbeeldingsgalerij</string>
|
||||
<string name="settings_wear_sync">Synchroniseren met Wear OS</string>
|
||||
<string name="wear_sync_permission_required">Catima heeft toestemming nodig om te synchroniseren met je Wear OS smartwatch</string>
|
||||
<string name="settings_wear_sync_devices">Toegestane Wear OS-apparaten</string>
|
||||
<string name="settings_wear_sync_devices_summary">Apparaten beheren die via Bluetooth kunnen worden gesynchroniseerd</string>
|
||||
<string name="settings_wear_sync_no_devices">Geen toegestane apparaten</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> apparaat toegestaan</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> apparaten toegestaan</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Geblokkeerde Wear OS-apparaten</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Geblokkeerde Wear OS-apparaten beheren</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Geen geblokkeerde apparaten</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> apparaat geblokkeerd</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> apparaten geblokkeerd</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">Apparaat verwijderen?</string>
|
||||
<string name="settings_wear_sync_unpair_message"><xliff:g>%1$s</xliff:g> van Catima verwijderen? Het zal je kaarten niet meer kunnen synchroniseren.</string>
|
||||
<string name="wear_bt_pairing_message"><xliff:g>%1$s</xliff:g> toestaan om toegang te krijgen tot jouw kaarten?</string>
|
||||
<string name="settings_wear_sync_summary">Stelt de Wear OS companion app in staat om jouw kaarten via Bluetooth te lezen</string>
|
||||
<string name="wear_bt_channel_name">Wear OS companion</string>
|
||||
<string name="settings_wear_sync_unblock_message"><xliff:g>%1$s</xliff:g> van de blokkeringslijst verwijderen? Het zal opnieuw toegang kunnen aanvragen.</string>
|
||||
<string name="settings_wear_sync_unpair">Verwijderen</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Tik op een apparaat om het te verwijderen.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Wear OS-apparaat koppelen</string>
|
||||
<string name="wear_bt_pairing_title">Wear OS-apparaat wil synchroniseren</string>
|
||||
<string name="wear_bt_pairing_allow">Toestaan</string>
|
||||
<string name="wear_bt_pairing_block">Blokkeren</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> mag niet synchroniseren.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> is geblokkeerd.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Nieuw autorisatieverzoek van <xliff:g>%1$s</xliff:g></string>
|
||||
<string name="wear_bt_pairing_mismatch_message">Dit apparaat was eerder geautoriseerd, maar presenteert nu een ander identiteitstoken. Dit gebeurt wanneer de Wear OS-app opnieuw wordt geïnstalleerd of de gegevens ervan worden gewist. Het kan ook duiden op een Bluetooth MAC-spoofingpoging.\n\nAccepteer alleen als je onlangs de Catima Wear OS-app opnieuw op dit horloge hebt geïnstalleerd.</string>
|
||||
<string name="open_settings">Instellingen openen</string>
|
||||
<string name="wear_bt_notification_title">Catima Wear OS synchronisatie is geactiveerd</string>
|
||||
<string name="settings_category_title_smartwatch">Smartwatch-ondersteuning</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Starten van Wear OS-synchronisatieservice is mislukt</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Kritische fout met Wear OS-synchronisatie</string>
|
||||
</resources>
|
||||
@@ -238,11 +238,11 @@
|
||||
<string name="show_note">Pokaż notatkę</string>
|
||||
<string name="permissionReadCardsLabel">Odczytaj karty Catima</string>
|
||||
<string name="permissionReadCardsDescription">odczytywanie swoich kart Catima i ich szczegółów, włącznie z notatkami i obrazkami</string>
|
||||
<string name="settings_allow_content_provider_read_title">Pozwól innym aplikacjom na dostęp do moich danych</string>
|
||||
<string name="settings_allow_content_provider_read_title">Synchronizacja z Gadgetbridge</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Potrzebne aby niektóre skanery działały</string>
|
||||
<string name="settings_keep_screen_on_summary">Wyłącza wygaszanie ekranu kiedy wyświetlana jest karta</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Wyłącza blokadę ekranu kiedy wyświetlana jest karta</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Aplikacje będą wymagały pozwolenia aby otrzymać dostęp do danych</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Umożliwia aplikacji Gadgetbridge synchronizację kart z obsługiwanymi zegarkami</string>
|
||||
<string name="settings_oled_dark_summary">Zmniejsza zużycie baterii na wyświetlaczach OLED</string>
|
||||
<string name="settings_category_title_cards">Widok karty</string>
|
||||
<string name="settings_category_title_general">Ogólne</string>
|
||||
@@ -315,4 +315,51 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC zostało zatrzymane</string>
|
||||
<string name="change_settings">Zmień ustawienia</string>
|
||||
<string name="nfc_block_system_error">Błąd podczas zatrzymania NFC</string>
|
||||
<string name="sort_by_most_recently_added">Najnowsze dodane</string>
|
||||
<string name="zoomFrontImage">Powiększ zdjęcie z przodu</string>
|
||||
<string name="zoomBackImage">Powiększ zdjęcie z tyłu</string>
|
||||
<string name="show_card_id">Pokaż ID karty</string>
|
||||
<string name="open_in_gallery">Otwórz zdjęcie w galerii</string>
|
||||
<string name="settings_wear_sync">Synchronizacja z systemem Wear OS</string>
|
||||
<string name="settings_wear_sync_summary">Umożliwia aplikacji towarzyszącej Wear OS odczytywanie kart za pośrednictwem Bluetooth</string>
|
||||
<string name="wear_sync_permission_required">Aplikacja Catima potrzebuje uprawnień, których nie przyznałeś, aby zsynchronizować się z Twoim smartwatchem z systemem Wear OS</string>
|
||||
<string name="settings_wear_sync_devices">Obsługiwane urządzenia z systemem Wear OS</string>
|
||||
<string name="settings_wear_sync_devices_summary">Zarządzaj urządzeniami, które mogą synchronizować się przez Bluetooth</string>
|
||||
<string name="settings_wear_sync_no_devices">Żadne urządzenia nie są dozwolone</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> urządzenie dozwolone</item>
|
||||
<item quantity="few"><xliff:g>%d</xliff:g> urządzenia dozwolone</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> urządzeń dozwolonych</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> urządzeń dozwolonych</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Zablokowane urządzenia z systemem Wear OS</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Zarządzanie zablokowanymi urządzeniami z systemem Wear OS</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Brak zablokowanych urządzeń</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> urządzenie zablokowane</item>
|
||||
<item quantity="few"><xliff:g>%d</xliff:g> urządzenia zablokowane</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> urządzeń zablokowanych</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> urządzeń zablokowanych</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">Usunąć urządzenie?</string>
|
||||
<string name="settings_wear_sync_unpair_message">Czy chcesz usunąć <xliff:g>%1$s</xliff:g> z aplikacji Catima? W takim przypadku aplikacja nie będzie już mogła synchronizować Twoich kart.</string>
|
||||
<string name="settings_wear_sync_unblock_message">Czy chcesz usunąć <xliff:g>%1$s</xliff:g> z listy zablokowanych? Wówczas będzie mogło ponownie poprosić o dostęp.</string>
|
||||
<string name="settings_wear_sync_unpair">Usuń</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Dotknij urządzenia, aby je usunąć.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Parowanie urządzeń z systemem Wear OS</string>
|
||||
<string name="wear_bt_pairing_title">Urządzenie z systemem Wear OS próbuje się zsynchronizować</string>
|
||||
<string name="wear_bt_pairing_message">Czy zezwolić <xliff:g>%1$s</xliff:g> na dostęp do Twoich kart?</string>
|
||||
<string name="wear_bt_pairing_allow">Zezwól</string>
|
||||
<string name="wear_bt_pairing_block">Zablokuj</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> może teraz zostać zsynchronizowane.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> zostało zablokowane.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Nowy wniosek o autoryzację od <xliff:g>%1$s</xliff:g></string>
|
||||
<string name="wear_bt_pairing_mismatch_message">To urządzenie zostało wcześniej autoryzowane, ale obecnie przedstawia inny token tożsamości. Sytuacja ta ma miejsce po ponownej instalacji aplikacji Wear OS lub wyczyszczeniu jej danych. Może to również wskazywać na próbę sfałszowania adresu MAC przez Bluetooth.\n\nZatwierdź tylko wtedy, gdy niedawno ponownie zainstalowałeś aplikację Catima Wear OS na tym zegarku.</string>
|
||||
<string name="open_settings">Otwórz ustawienia</string>
|
||||
<string name="wear_bt_channel_name">Aplikacja towarzysząca dla Wear OS</string>
|
||||
<string name="wear_bt_notification_title">Synchronizacja Catima Wear OS jest aktywna</string>
|
||||
<string name="settings_category_title_smartwatch">Obsługa smartwatchów</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Nie udało się uruchomić usługi synchronizacji Wear OS</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Krytyczny błąd synchronizacji systemu Wear OS</string>
|
||||
</resources>
|
||||
@@ -218,8 +218,8 @@
|
||||
<string name="intent_import_card_from_url_share_text">Eu quero compartilhar um cartão com você</string>
|
||||
<string name="settings_keep_screen_on">Manter tela ligada</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card">Previnir bloqueio de tela</string>
|
||||
<string name="settings_allow_content_provider_read_title">Permitir que outros aplicativos acessem meus dados</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Aplicativos ainda precisarão pedir permissão para ter acesso concedido</string>
|
||||
<string name="settings_allow_content_provider_read_title">Sincronização com o Gadgetbridge</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Permite que o aplicativo Gadgetbridge sincronize seus cartões com relógios suportados</string>
|
||||
<string name="group_edit">Editar grupo</string>
|
||||
<string name="exportSuccessful">Dados exportados</string>
|
||||
<string name="noGroups">Clique no botão \"+\" para adicionar grupos a serem categorizados</string>
|
||||
@@ -309,4 +309,49 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC está pausado</string>
|
||||
<string name="change_settings">Alterar configurações</string>
|
||||
<string name="nfc_block_system_error">Falha ao pausar o NFC</string>
|
||||
<string name="sort_by_most_recently_added">Adicionados mais recentemente</string>
|
||||
<string name="zoomFrontImage">Ampliar imagem frontal</string>
|
||||
<string name="zoomBackImage">Reduzir o zoom da imagem</string>
|
||||
<string name="show_card_id">Mostrar ID do cartão</string>
|
||||
<string name="open_in_gallery">Abrir na galeria de imagens</string>
|
||||
<string name="settings_wear_sync">Sincronizar com o Wear OS</string>
|
||||
<string name="settings_wear_sync_summary">Permite que o aplicativo complementar do Wear OS leia seus cartões via Bluetooth</string>
|
||||
<string name="wear_sync_permission_required">A Catima precisa das permissões que você negou para sincronizar com seu smartwatch Wear OS</string>
|
||||
<string name="settings_wear_sync_devices">Dispositivos permitidos pelo Wear OS</string>
|
||||
<string name="settings_wear_sync_devices_summary">Gerencie dispositivos autorizados a sincronizar via Bluetooth</string>
|
||||
<string name="settings_wear_sync_no_devices">Dispositivos não permitidos</string>
|
||||
<string name="settings_wear_sync_blocked_devices">Dispositivos Wear OS bloqueados</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Gerencie dispositivos bloqueados do Wear OS</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Nenhum dispositivo bloqueado</string>
|
||||
<string name="settings_wear_sync_unpair_title">Remover o dispositivo?</string>
|
||||
<string name="settings_wear_sync_unpair">Remover</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Toque em um dispositivo para removê-lo.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Pareamento de dispositivos Wear OS</string>
|
||||
<string name="wear_bt_pairing_title">Dispositivo Wear OS quer sincronizar</string>
|
||||
<string name="wear_bt_pairing_allow">Permita</string>
|
||||
<string name="wear_bt_pairing_block">Bloquear</string>
|
||||
<string name="wear_bt_pairing_mismatch_message">Este dispositivo já foi autorizado anteriormente, mas agora apresenta um token de identidade diferente. Isso acontece quando o app Wear OS é reinstalado ou seus dados são apagados. Também pode indicar uma tentativa de falsificação de MAC por Bluetooth.\n\nAceite apenas se você reinstalou recentemente o aplicativo Catima Wear OS neste relógio.</string>
|
||||
<string name="open_settings">Abrir configurações</string>
|
||||
<string name="wear_bt_channel_name">Companheiro Wear OS</string>
|
||||
<string name="wear_bt_notification_title">Sincronização do Catima Wear OS ativa</string>
|
||||
<string name="settings_category_title_smartwatch">Suporte a smartwatch</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Falhou ao iniciar o serviço de sincronização do Wear OS</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Erro crítico de sincronização do Wear OS</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> dispositivo permitido</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> dispositivos permitidos</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> dispositivos permitidos</item>
|
||||
</plurals>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> dispositivo bloqueado</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> dispositivos bloqueados</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> dispositivos bloqueados</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_message">Remover <xliff:g>%1$s</xliff:g> do Catima? Ele não poderá mais sincronizar seus cartões.</string>
|
||||
<string name="settings_wear_sync_unblock_message">Remover <xliff:g>%1$s</xliff:g> da lista de bloqueados? O item poderá solicitar acesso novamente.</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="wear_bt_pairing_message">Permitir que <xliff:g>%1$s</xliff:g> acesse seus cartões?</string>
|
||||
<string name="wear_bt_pairing_allowed">Agora, a sincronização de <xliff:g>%1$s</xliff:g> é permitida.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> foi bloqueado.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Nova solicitação de autorização de <xliff:g>%1$s</xliff:g></string>
|
||||
</resources>
|
||||
@@ -309,4 +309,6 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC está pausado</string>
|
||||
<string name="change_settings">Alterar configurações</string>
|
||||
<string name="nfc_block_system_error">Falha ao pausar o NFC</string>
|
||||
<string name="zoomFrontImage">Ampliar imagem frontal</string>
|
||||
<string name="show_card_id">Mostrar ID do cartão</string>
|
||||
</resources>
|
||||
@@ -308,4 +308,7 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC está pausado</string>
|
||||
<string name="change_settings">Alterar configurações</string>
|
||||
<string name="nfc_block_system_error">Erro ao pausar o NFC</string>
|
||||
<string name="zoomFrontImage">Ampliar imagem frontal</string>
|
||||
<string name="show_card_id">Mostrar ID do cartão</string>
|
||||
<string name="sort_by_most_recently_added">Adicionados mais recentemente</string>
|
||||
</resources>
|
||||
@@ -238,8 +238,8 @@
|
||||
<string name="show_balance">Показывать баланс</string>
|
||||
<string name="permissionReadCardsDescription">чтение ваших карт Catima и всех их данных, включая заметки и изображения</string>
|
||||
<string name="permissionReadCardsLabel">Чтение карт Catima</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Приложениям по-прежнему придётся запрашивать разрешение для получения доступа</string>
|
||||
<string name="settings_allow_content_provider_read_title">Разрешать другим приложениям доступ к моим данным</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Разрешить приложению Gadgetbridge синхронизировать ваши карты с поддерживаемыми часами</string>
|
||||
<string name="settings_allow_content_provider_read_title">Синхронизация с Gadgetbridge</string>
|
||||
<string name="settings_category_title_privacy">Конфиденциальность</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Необходимо для работы некоторых сканеров</string>
|
||||
<string name="settings_keep_screen_on_summary">Отключение функции автоотключения экрана при отображении карты</string>
|
||||
@@ -315,4 +315,51 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC приостановлен</string>
|
||||
<string name="change_settings">Изменить настройки</string>
|
||||
<string name="nfc_block_system_error">Приостановка NFC невозможна</string>
|
||||
<string name="sort_by_most_recently_added">Порядок добавления</string>
|
||||
<string name="show_card_id">Показывать номер карты</string>
|
||||
<string name="zoomFrontImage">Масштабирование лицевого изображения</string>
|
||||
<string name="zoomBackImage">Масштабирование заднего изображения</string>
|
||||
<string name="open_in_gallery">Открыть в галерее</string>
|
||||
<string name="settings_wear_sync">Синхронизация с Wear OS</string>
|
||||
<string name="settings_wear_sync_summary">Разрешить приложению-компаньону Wear OS читать ваши карты по Bluetooth</string>
|
||||
<string name="wear_sync_permission_required">Для синхронизации умными часами Wear OS Catima требуются разрешения, в которых ранее было отказано</string>
|
||||
<string name="settings_wear_sync_devices">Доверенные устройства Wear OS</string>
|
||||
<string name="settings_wear_sync_devices_summary">Управление устройствами, которым разрешена синхронизация по Bluetooth</string>
|
||||
<string name="settings_wear_sync_no_devices">Нет доверенных устройств</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> доверенное устройство</item>
|
||||
<item quantity="few"><xliff:g>%d</xliff:g> доверенных устройства</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> доверенных устройств</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> доверенных устройств</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Заблокированные устройства Wear OS</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Управление заблокированными устройствами Wear OS</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Нет заблокированных устройств</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> заблокированное устройство</item>
|
||||
<item quantity="few"><xliff:g>%d</xliff:g> заблокированных устройства</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> заблокированных устройств</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> заблокированных устройств</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">Удалить устройство?</string>
|
||||
<string name="settings_wear_sync_unpair_message">Удалить <xliff:g>%1$s</xliff:g> из Catima? Он больше не сможет синхронизировать ваши карты.</string>
|
||||
<string name="settings_wear_sync_unblock_message">Удалить устройство <xliff:g>%1$s</xliff:g> из списка заблокированных? Оно снова сможет запросить доступ.</string>
|
||||
<string name="settings_wear_sync_unpair">Удалить</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Нажмите на устройство, чтобы удалить его.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Сопряжение устройства Wear OS</string>
|
||||
<string name="wear_bt_pairing_title">Устройство Wear OS запрашивает синхронизацию</string>
|
||||
<string name="wear_bt_pairing_message">Разрешить устройству <xliff:g>%1$s</xliff:g> доступ к вашим картам?</string>
|
||||
<string name="wear_bt_pairing_allow">Разрешить</string>
|
||||
<string name="wear_bt_pairing_block">Заблокировать</string>
|
||||
<string name="wear_bt_pairing_allowed">Устройству <xliff:g>%1$s</xliff:g> разрешена синхронизация.</string>
|
||||
<string name="wear_bt_pairing_blocked">Устройство <xliff:g>%1$s</xliff:g> заблокировано.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Новый запрос на авторизацию от <xliff:g>%1$s</xliff:g></string>
|
||||
<string name="wear_bt_pairing_mismatch_message">Данное устройство ранее было авторизовано, но теперь предоставляет другой токен идентификации. Это происходит, когда приложение Wear OS переустанавливается или его данные очищаются. Также это может указывать на попытку подделать MAC-адрес Bluetooth.\n\nРазрешайте только в том случае, если вы недавно переустановили приложение Catima Wear OS на этих часах.</string>
|
||||
<string name="open_settings">Открыть настройки</string>
|
||||
<string name="wear_bt_channel_name">Компаньон Wear OS</string>
|
||||
<string name="wear_bt_notification_title">Синхронизация Catima с Wear OS активна</string>
|
||||
<string name="settings_category_title_smartwatch">Поддержка умных часов</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Невозможно запустить службу синхронизации Wear OS</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Критическая ошибка синхронизации Wear OS</string>
|
||||
</resources>
|
||||
@@ -314,4 +314,9 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC je zaustavljen</string>
|
||||
<string name="change_settings">Spremeni nastavitve</string>
|
||||
<string name="nfc_block_system_error">ustavitev NFC-ja ni uspela</string>
|
||||
<string name="sort_by_most_recently_added">Nazadnje dodane</string>
|
||||
<string name="zoomFrontImage">Povečaj sprednjo sliko</string>
|
||||
<string name="zoomBackImage">Povečaj zadnjo sliko</string>
|
||||
<string name="show_card_id">Prikaži ID kartice</string>
|
||||
<string name="open_in_gallery">Odpri v galeriji slik</string>
|
||||
</resources>
|
||||
@@ -62,8 +62,8 @@
|
||||
<string name="selectBarcodeTitle">Odaberi bar-kod</string>
|
||||
<string name="thumbnailDescription">Naslovna fotografija</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card">Spreči zaključavanje ekrana</string>
|
||||
<string name="settings_allow_content_provider_read_title">Dozvoli drugim aplikacijama da pristupe mojim podacima</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Aplikacije će i dalje morati da zatraže dozvolu da bi im se odobrio pristup</string>
|
||||
<string name="settings_allow_content_provider_read_title">Sinhronizuj sa Gadgetbridge-om</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Omogućava aplikaciji Gadgetbridge da sinhronizuje tvoje kartice na podržane satove</string>
|
||||
<string name="intent_import_card_from_url_share_text">Želim da podelim karticu sa tobom</string>
|
||||
<string name="importSuccessful">Podaci uvezeni</string>
|
||||
<string name="exportSuccessful">Podaci izvezeni</string>
|
||||
@@ -309,4 +309,35 @@
|
||||
<string name="nfc_blocked_while_viewing_card">НФЦ је заустављен</string>
|
||||
<string name="change_settings">Измени поставке</string>
|
||||
<string name="nfc_block_system_error">Нисам успео застанути НФЦ</string>
|
||||
<string name="sort_by_most_recently_added">Nedavno dodate</string>
|
||||
<string name="zoomBackImage">Uvećaj zadnju sliku</string>
|
||||
<string name="show_card_id">Prikaži ID kartice</string>
|
||||
<string name="settings_wear_sync">Sinhronizuj sa Wear OS-om</string>
|
||||
<string name="settings_wear_sync_summary">Omogućava Wear OS pratnoj aplikaciji da pročita tvoje kartice preko Bluetootha</string>
|
||||
<string name="wear_sync_permission_required">Catima trebaju dozvole koje si odbio da bi mogla sinhronizovati sa tvojim Wear OS pametnim satom</string>
|
||||
<string name="settings_wear_sync_devices">Dozvoljeni Wear OS uređaji</string>
|
||||
<string name="settings_wear_sync_devices_summary">Upravljaj dozvoljenim uređajima za sinhronizovanje preko Bluetootha</string>
|
||||
<string name="settings_wear_sync_no_devices">Nema dozvoljenih uređaja</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> uređaj dozvoljen</item>
|
||||
<item quantity="few"><xliff:g>%d</xliff:g> uređaja dozvoljena</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> uređaja dozvoljeno</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Blokirani Wear OS uređaji</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Upravljaj blokiranim Wear OS uređajima</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Nema blokiranih uređaja</string>
|
||||
<string name="settings_wear_sync_unpair_title">Obriši uređaj?</string>
|
||||
<string name="settings_wear_sync_unpair">Obriši</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Dodirni uređaj da ga ukloniš.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Wear OS uređaj se uparuje</string>
|
||||
<string name="wear_bt_pairing_title">Wear OS uređaj želi da se sinhronizuje</string>
|
||||
<string name="wear_bt_pairing_allow">Dozvoli</string>
|
||||
<string name="wear_bt_pairing_block">Blokiraj</string>
|
||||
<string name="wear_bt_pairing_mismatch_message">Ovaj uređaj je bio prethodno autorizovan, ali sada prikazuje drugi token identiteta. Ovo se dešava kada se Wear OS aplikacija ponovno instalira ili se njeni podaci obrišu. To može takođe ukazivati na pokušaj MAC spoofinga preko Bluetootha.\n\nPrihvati samo ako si nedavno ponovno instalirao/la Catima Wear OS aplikaciju na ovaj sat.</string>
|
||||
<string name="open_settings">Otvori podešavanja</string>
|
||||
<string name="open_in_gallery">Otvori u galeriji slika</string>
|
||||
<string name="wear_bt_notification_title">Catima Wear OS sinhronizacija aktivna</string>
|
||||
<string name="settings_category_title_smartwatch">Podrška za pametni sat</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Greška pri pokretanju Wear OS servisa sinhronizacije</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Wear OS sinhronizacija - kritična greška</string>
|
||||
</resources>
|
||||
@@ -232,7 +232,7 @@
|
||||
<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="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="settings_allow_content_provider_read_title">Synka med Gadgetbridge</string>
|
||||
<string name="permissionReadCardsLabel">Läsa Catima-kort</string>
|
||||
<string name="donate">Donera</string>
|
||||
<string name="show_archived_cards">Visa arkiverade kort</string>
|
||||
@@ -303,4 +303,31 @@
|
||||
<string name="change_settings">Ändra inställningar</string>
|
||||
<string name="nfc_block_system_error">Lyckades inte pausa NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Pausar NFC för att förhindra att NFC-betalningar genomförs av misstag medan du tittar på ett kort</string>
|
||||
<string name="sort_by_most_recently_added">Senast tillagda</string>
|
||||
<string name="zoomFrontImage">Zooma framsidesbilden</string>
|
||||
<string name="zoomBackImage">Zooma baksidesbilden</string>
|
||||
<string name="show_card_id">Visa kort-ID</string>
|
||||
<string name="open_in_gallery">Öppna i bildgalleriet</string>
|
||||
<string name="settings_wear_sync">Synka med Wear OS</string>
|
||||
<string name="wear_sync_permission_required">Catima behöver få de behörigheter du nekade appen för att kunna synka med din smarta klocka med Wear OS</string>
|
||||
<string name="settings_wear_sync_devices">Tillåtna Wear OS-enheter</string>
|
||||
<string name="settings_wear_sync_devices_summary">Hantera enheter som tillåts synka över Bluetooth</string>
|
||||
<string name="settings_wear_sync_no_devices">Inga tillåtna enheter</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> tillåten enhet</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> tillåtna enheter</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Blockerade Wear OS-enheter</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Hantera blockerade Wear OS-enheter</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Inga blockerade enheter</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> blockerad enhet</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> blockerade enheter</item>
|
||||
</plurals>
|
||||
<string name="wear_bt_pairing_allow">Tillåt</string>
|
||||
<string name="wear_bt_pairing_block">Blockera</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> tillåts nu synka.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> har blockerats.</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Kritiskt fel vid Wear OS-synkning</string>
|
||||
<string name="open_settings">Öppna inställningar</string>
|
||||
</resources>
|
||||
@@ -227,10 +227,10 @@
|
||||
<string name="permissionReadCardsLabel">Catima kartlarını oku</string>
|
||||
<string name="permissionReadCardsDescription">Catima kartlarınızı ve tüm detaylarını, notlar ve resimler de dahil olmak üzere okuyun</string>
|
||||
<string name="settings_keep_screen_on_summary">Ekran zaman aşımını bir kart görüntülerken devre dışı bırakır</string>
|
||||
<string name="settings_allow_content_provider_read_title">Diğer uygulamaların verilerime erişmesine izin ver</string>
|
||||
<string name="settings_allow_content_provider_read_title">Gadgetbridge ile eşzamanla</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Bazı tarayıcıların çalışması için gereklidir</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Ekran kilidini bir kart görüntülerken devre dışı bırakır</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Uygulamaların erişim izni almak için yine de izin istemesi gerekecek</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Gadgetbridge uygulamasının kartlarınızı desteklenen saatlerle eşzamanlamasına izin verir</string>
|
||||
<string name="settings_category_title_general">Genel</string>
|
||||
<string name="settings_category_title_privacy">Gizlilik</string>
|
||||
<string name="settings_oled_dark_summary">OLED ekranlarda güç kullanımını düşürür</string>
|
||||
@@ -303,4 +303,47 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC duraklatıldı</string>
|
||||
<string name="change_settings">Ayarları değiştir</string>
|
||||
<string name="nfc_block_system_error">NFC duraklatılamadı</string>
|
||||
<string name="sort_by_most_recently_added">En son eklenen</string>
|
||||
<string name="zoomFrontImage">Ön görüntüyü yakınlaştır</string>
|
||||
<string name="zoomBackImage">Arka görüntüyü yakınlaştır</string>
|
||||
<string name="show_card_id">Kart numarasını göster</string>
|
||||
<string name="open_in_gallery">Resim galerisinde aç</string>
|
||||
<string name="settings_wear_sync">Wear OS ile eşzamanla</string>
|
||||
<string name="settings_wear_sync_summary">Wear OS yardımcı uygulamasının Bluetooth üzerinden kartlarınızı okumasına izin verir</string>
|
||||
<string name="wear_sync_permission_required">Catima, Wear OS akıllı saatinizle eşzamanlama için reddettiğiniz izinlere ihtiyaç duyuyor</string>
|
||||
<string name="settings_wear_sync_devices">İzin verilen Wear OS aygıtları</string>
|
||||
<string name="settings_wear_sync_devices_summary">Bluetooth üzerinden eşzamanlanmasına izin verilen aygıtları yönetin</string>
|
||||
<string name="settings_wear_sync_no_devices">İzin verilen aygıt yok</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> aygıta izin verildi</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> aygıta izin verildi</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Engellenen Wear OS aygıtları</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Engellenen Wear OS aygıtlarını yönetin</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Engellenen aygıt yok</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> aygıt engellendi</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> aygıt engellendi</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">Aygıt kaldırılsın mı?</string>
|
||||
<string name="settings_wear_sync_unpair_message"><xliff:g>%1$s</xliff:g> Catima\'dan kaldırılsın mı? Artık kartlarınızı eşzamanlayamayacak.</string>
|
||||
<string name="settings_wear_sync_unblock_message"><xliff:g>%1$s</xliff:g> engellenen listesinden kaldırılsın mı? Tekrar erişim isteğinde bulunabilecek.</string>
|
||||
<string name="settings_wear_sync_unpair">Kaldır</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Aygıtı kaldırmak için üzerine dokunun.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Wear OS aygıt eşleştirmesi</string>
|
||||
<string name="wear_bt_pairing_title">Wear OS aygıtı eşzamanlamak istiyor</string>
|
||||
<string name="wear_bt_pairing_message"><xliff:g>%1$s</xliff:g> için kartlarınıza erişim izni verilsin mi?</string>
|
||||
<string name="wear_bt_pairing_allow">İzin ver</string>
|
||||
<string name="wear_bt_pairing_block">Engelle</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> artık eşzamanlayabilir.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> engellendi.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title"><xliff:g>%1$s</xliff:g> için yeni yetkilendirme isteği</string>
|
||||
<string name="wear_bt_pairing_mismatch_message">Bu aygıt daha önce yetkilendirildi, ancak şu anda farklı bir kimlik belirteci sunuyor. Bu durum, Wear OS uygulamasının yeniden kurulması veya verilerinin silinmesi durumunda ortaya çıkar. Ayrıca, bir Bluetooth MAC sahteciliği girişimi olduğunu da gösterebilir.\n\nYalnızca bu saatte Catima Wear OS uygulamasını yakın zamanda yeniden kurduysanız kabul edin.</string>
|
||||
<string name="open_settings">Ayarları aç</string>
|
||||
<string name="wear_bt_channel_name">Wear OS yardımcısı</string>
|
||||
<string name="wear_bt_notification_title">Catima Wear OS eşzamanlaması etkin</string>
|
||||
<string name="settings_category_title_smartwatch">Akıllı saat desteği</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Wear OS eşzamanlama hizmeti başlatılamadı</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Wear OS eşzamanlaması önemli hata</string>
|
||||
</resources>
|
||||
@@ -297,4 +297,9 @@
|
||||
<string name="nfc_blocked_while_viewing_card">Đã tạm dừng NFC</string>
|
||||
<string name="change_settings">Thay đổi cài đặt</string>
|
||||
<string name="nfc_block_system_error">Không thể tạm dừng NFC</string>
|
||||
<string name="sort_by_most_recently_added">Đã thêm gần đây nhất</string>
|
||||
<string name="zoomFrontImage">Phóng ảnh mặt trước</string>
|
||||
<string name="zoomBackImage">Phóng ảnh mặt sau</string>
|
||||
<string name="show_card_id">Hiện ID của thẻ</string>
|
||||
<string name="open_in_gallery">Mở trong thư viện ảnh</string>
|
||||
</resources>
|
||||
@@ -218,8 +218,8 @@
|
||||
<string name="show_name_below_image_thumbnail">在图像缩略图下方显示名称</string>
|
||||
<string name="show_validity">显示有效性</string>
|
||||
<string name="permissionReadCardsLabel">读取 Catima 卡</string>
|
||||
<string name="settings_allow_content_provider_read_summary">应用程序仍然需要请求权限</string>
|
||||
<string name="settings_allow_content_provider_read_title">允许其他应用程序访问我的数据</string>
|
||||
<string name="settings_allow_content_provider_read_summary">允许 Gadgetbridge 应用同步卡片到支持的手表</string>
|
||||
<string name="settings_allow_content_provider_read_title">与 Gadgetbridge 同步</string>
|
||||
<string name="permissionReadCardsDescription">读取你的 Catima 卡片及其所有细节,包括笔记和图像</string>
|
||||
<string name="settings_keep_screen_on_summary">在查看卡片时禁止屏幕超时</string>
|
||||
<string name="settings_oled_dark_summary">减少 OLED 显示屏上的电池使用量</string>
|
||||
@@ -297,4 +297,45 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC 已暂停</string>
|
||||
<string name="change_settings">更改设置</string>
|
||||
<string name="nfc_block_system_error">暂停 NFC 失败</string>
|
||||
<string name="sort_by_most_recently_added">最近添加</string>
|
||||
<string name="zoomFrontImage">缩放正面图</string>
|
||||
<string name="zoomBackImage">缩放反面图</string>
|
||||
<string name="show_card_id">显示卡片 ID</string>
|
||||
<string name="open_in_gallery">在图库中打开</string>
|
||||
<string name="settings_wear_sync">与 WearOS 同步</string>
|
||||
<string name="settings_wear_sync_summary">允许 WearOS 伴侣应用通过蓝牙读取你的卡片</string>
|
||||
<string name="wear_sync_permission_required">Catima 需要你拒绝的权限来和你的 Wear OS 智能手表同步</string>
|
||||
<string name="settings_wear_sync_devices">允许的 Wear OS 设备</string>
|
||||
<string name="settings_wear_sync_devices_summary">管理允许经蓝牙同步的设备</string>
|
||||
<string name="settings_wear_sync_no_devices">没有允许的设备</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="other">允许了 <xliff:g>%d</xliff:g> 台设备</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">阻止的 Wear OS 设备</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">管理阻止的 Wear OS 设备</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">无阻止的设备</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="other">阻止了 <xliff:g>%d</xliff:g> 台设备</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">删除设备?</string>
|
||||
<string name="settings_wear_sync_unpair_message">从 Catima 删除 <xliff:g>%1$s</xliff:g> ?它将无法再和你的卡片同步。</string>
|
||||
<string name="settings_wear_sync_unblock_message">从拦截列表删除 <xliff:g>%1$s</xliff:g> ?它将能再次请求访问。</string>
|
||||
<string name="settings_wear_sync_unpair">删除</string>
|
||||
<string name="wear_bt_pairing_mismatch_message">该设备先前已授权但现在提供了不同的身份令牌。重新安装 Wear OS 应用或应用数据被清除时可能发生这种情况。这种情况还可能表明存在蓝牙 MAC 伪造企图。\n\n除非你最近在这块手表上重新安装过 Catima Wear OS 应用,否则请不要接受。</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">轻触设备删除。</string>
|
||||
<string name="wear_bt_pairing_channel_name">Wear OS 设备配对</string>
|
||||
<string name="wear_bt_pairing_title">Wear OS 设备想同步</string>
|
||||
<string name="wear_bt_pairing_message">允许 <xliff:g>%1$s</xliff:g> 访问你的卡片?</string>
|
||||
<string name="wear_bt_pairing_allow">允许</string>
|
||||
<string name="wear_bt_pairing_block">拦截</string>
|
||||
<string name="wear_bt_pairing_allowed">已允许 <xliff:g>%1$s</xliff:g> 同步。</string>
|
||||
<string name="wear_bt_pairing_blocked">已拦截 <xliff:g>%1$s</xliff:g>。</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">来自 <xliff:g>%1$s</xliff:g> 的新授权请求</string>
|
||||
<string name="open_settings">打开设置</string>
|
||||
<string name="wear_bt_channel_name">Wear OS 伴侣</string>
|
||||
<string name="wear_bt_notification_title">Catima Wear OS 同步活跃</string>
|
||||
<string name="settings_category_title_smartwatch">智能手表支持</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">未能启动 Wear OS 同步服务</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Wear OS 同步严重错误</string>
|
||||
</resources>
|
||||
@@ -222,8 +222,8 @@
|
||||
<string name="app_name">卡提碼</string>
|
||||
<string name="permissionReadCardsDescription">讀取卡提碼卡片及卡片資訊,包含註記與圖片</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">觀看卡片時避免螢幕鎖定</string>
|
||||
<string name="settings_allow_content_provider_read_summary">其他程式仍然需要取得權限</string>
|
||||
<string name="settings_allow_content_provider_read_title">允許其他程式使用我的資料</string>
|
||||
<string name="settings_allow_content_provider_read_summary">允許 Gadgetbridge 應用程式同步卡片到支援的手表</string>
|
||||
<string name="settings_allow_content_provider_read_title">與 Gadgetbridge 同步</string>
|
||||
<string name="settings_keep_screen_on_summary">觀看卡片時避免螢幕關閉</string>
|
||||
<string name="balanceParsingFailed">無效餘額</string>
|
||||
<string name="view_online">網站上顯示</string>
|
||||
@@ -296,4 +296,8 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC 已暫停</string>
|
||||
<string name="change_settings">變更設定</string>
|
||||
<string name="nfc_block_system_error">暫停 NFC 失敗</string>
|
||||
<string name="sort_by_most_recently_added">最近添加</string>
|
||||
<string name="show_card_id">顯示卡片 ID</string>
|
||||
<string name="settings_wear_sync">與 WearOS 同步</string>
|
||||
<string name="settings_wear_sync_devices">允許的 Wear OS 裝置</string>
|
||||
</resources>
|
||||
@@ -104,8 +104,8 @@
|
||||
<string name="settings_disable_nfc_while_viewing_card">Pause NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Pauses NFC to prevent NFC payments from triggering while viewing a card</string>
|
||||
<string name="settings_key_disable_nfc_while_viewing_card" translatable="false">pref_disable_nfc_while_viewing_card</string>
|
||||
<string name="settings_allow_content_provider_read_title">Allow other apps to access my data</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Apps will still have to request permission to be granted access</string>
|
||||
<string name="settings_allow_content_provider_read_title">Sync with Gadgetbridge</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Allows the Gadgetbridge app to sync your cards to supported watches</string>
|
||||
<string name="settings_key_disable_lockscreen_while_viewing_card" translatable="false">pref_disable_lockscreen_while_viewing_card</string>
|
||||
<string name="settings_key_allow_content_provider_read" translatable="false">pref_allow_content_provider_read</string>
|
||||
<string name="settings_key_oled_dark" translatable="false">pref_oled_dark</string>
|
||||
@@ -304,6 +304,42 @@
|
||||
<string name="setting_key_column_count_landscape" translatable="false">pref_column_count_landscape</string>
|
||||
<string name="settings_category_title_general">General</string>
|
||||
<string name="settings_category_title_privacy">Privacy</string>
|
||||
<string name="settings_key_wear_sync" translatable="false">pref_wear_sync</string>
|
||||
<string name="settings_wear_sync">Sync with Wear OS</string>
|
||||
<string name="settings_wear_sync_summary">Allows the Wear OS companion app to read your cards over Bluetooth</string>
|
||||
<string name="wear_sync_permission_required">Catima needs the permissions you denied to sync with your Wear OS smartwatch</string>
|
||||
<string name="settings_key_wear_sync_devices" translatable="false">pref_wear_sync_devices</string>
|
||||
<string name="settings_wear_sync_devices">Allowed Wear OS devices</string>
|
||||
<string name="settings_wear_sync_devices_summary">Manage devices allowed to sync over Bluetooth</string>
|
||||
<string name="settings_wear_sync_no_devices">No allowed devices</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> device allowed</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> devices allowed</item>
|
||||
</plurals>
|
||||
<string name="settings_key_wear_sync_blocked_devices" translatable="false">pref_wear_sync_blocked_devices</string>
|
||||
<string name="settings_wear_sync_blocked_devices">Blocked Wear OS devices</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Manage blocked Wear OS devices</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">No blocked devices</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> device blocked</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> devices blocked</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">Remove device?</string>
|
||||
<string name="settings_wear_sync_unpair_message">Remove <xliff:g>%1$s</xliff:g> from Catima? It will no longer be able to sync your cards.</string>
|
||||
<string name="settings_wear_sync_unblock_message">Remove <xliff:g>%1$s</xliff:g> from the blocked list? It will be able to request access again.</string>
|
||||
<string name="settings_wear_sync_unpair">Remove</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Tap a device to remove it.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Wear OS device pairing</string>
|
||||
<string name="wear_bt_pairing_title">Wear OS device wants to sync</string>
|
||||
<string name="wear_bt_pairing_message">Allow <xliff:g>%1$s</xliff:g> to access your cards?</string>
|
||||
<string name="wear_bt_pairing_allow">Allow</string>
|
||||
<string name="wear_bt_pairing_block">Block</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> is now allowed to sync.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> has been blocked.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">New authorization request from "<xliff:g>%1$s</xliff:g>"</string>
|
||||
<string name="wear_bt_pairing_mismatch_message">This device was previously authorized but is now presenting a different identity token. This happens when the Wear OS app is reinstalled or its data is cleared. It can also indicate a Bluetooth MAC spoofing attempt.\n\nOnly accept if you recently reinstalled the Catima Wear OS app on this watch.</string>
|
||||
<string name="open_settings">Open settings</string>
|
||||
<string name="action_display_options">Display options</string>
|
||||
<string name="show_archived_cards">Show archived cards</string>
|
||||
<string name="view_online">View online</string>
|
||||
@@ -353,4 +389,10 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC is paused</string>
|
||||
<string name="change_settings">Change settings</string>
|
||||
<string name="nfc_block_system_error">Failed to pause NFC</string>
|
||||
<string name="open_in_gallery">Open in image gallery</string>
|
||||
<string name="wear_bt_channel_name">Wear OS companion</string>
|
||||
<string name="wear_bt_notification_title">Catima Wear OS sync active</string>
|
||||
<string name="settings_category_title_smartwatch">Smartwatch support</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Failed to start Wear OS sync service</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Wear OS sync critical error</string>
|
||||
</resources>
|
||||
@@ -57,6 +57,13 @@
|
||||
<item name="postSplashScreenTheme">@style/AppTheme</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Transparent" parent="AppTheme.NoActionBar">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:backgroundDimEnabled">true</item>
|
||||
</style>
|
||||
|
||||
<!-- place holder for dark background night theme overrides -->
|
||||
<style name="DarkBackground" />
|
||||
</resources>
|
||||
@@ -121,7 +121,7 @@
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/settings_category_title_privacy"
|
||||
android:title="@string/settings_category_title_smartwatch"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
@@ -132,6 +132,29 @@
|
||||
android:title="@string/settings_allow_content_provider_read_title"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preference_material_switch"
|
||||
android:defaultValue="false"
|
||||
android:key="@string/settings_key_wear_sync"
|
||||
android:summary="@string/settings_wear_sync_summary"
|
||||
android:title="@string/settings_wear_sync"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<Preference
|
||||
android:key="@string/settings_key_wear_sync_devices"
|
||||
android:title="@string/settings_wear_sync_devices"
|
||||
android:summary="@string/settings_wear_sync_devices_summary"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<Preference
|
||||
android:key="@string/settings_key_wear_sync_blocked_devices"
|
||||
android:title="@string/settings_wear_sync_blocked_devices"
|
||||
android:summary="@string/settings_wear_sync_blocked_devices_summary"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -2,16 +2,21 @@ package protect.card_locker;
|
||||
|
||||
import static android.os.Looper.getMainLooper;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Looper;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -30,17 +35,197 @@ import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.android.controller.ActivityController;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
import org.robolectric.shadows.ShadowActivity;
|
||||
import org.robolectric.shadows.ShadowContentResolver;
|
||||
import org.robolectric.shadows.ShadowToast;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class MainActivityTest {
|
||||
private SharedPreferences prefs;
|
||||
|
||||
@Test
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
public void pkpassSharedIntentReadsOffMainThreadAndProcessesOnMainThread() throws Exception {
|
||||
Uri uri = Uri.parse("content://test/cloud-backed.pkpass");
|
||||
CountDownLatch readStarted = new CountDownLatch(1);
|
||||
CountDownLatch streamClosed = new CountDownLatch(1);
|
||||
AtomicReference<Looper> readLooper = new AtomicReference<>();
|
||||
InputStream pkpass = getClass().getResourceAsStream(
|
||||
"pkpass/DCBLN24/DCBLN24-QLUKT-1-passbook.pkpass");
|
||||
assertNotNull(pkpass);
|
||||
new ShadowContentResolver().registerInputStream(
|
||||
uri, new RecordingInputStream(pkpass, readStarted, streamClosed, readLooper));
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW)
|
||||
.setDataAndType(uri, "application/vnd.apple.pkpass");
|
||||
ActivityController<MainActivity> controller =
|
||||
Robolectric.buildActivity(MainActivity.class, intent).create().start().resume().visible();
|
||||
MainActivity activity = controller.get();
|
||||
|
||||
// Do NOT idle the main looper here: the read runs on a real background
|
||||
// thread, so the latch fires without it, and idling would run the
|
||||
// main-thread continuation and defeat the ordering assertion below.
|
||||
assertTrue(readStarted.await(5, TimeUnit.SECONDS));
|
||||
// The read must not happen on the main looper. This is the property the
|
||||
// fix is about; it is asserted directly rather than inferred from
|
||||
// ordering. Robolectric drains the main looper during .visible(), so by
|
||||
// the time the test regains control the whole import has already run to
|
||||
// completion and there is no observable in-between state to assert on.
|
||||
assertTrue(readLooper.get() != getMainLooper());
|
||||
assertTrue(streamClosed.await(5, TimeUnit.SECONDS));
|
||||
|
||||
waitFor(() -> shadowOf(activity).peekNextStartedActivity() != null);
|
||||
Intent editIntent = shadowOf(activity).getNextStartedActivity();
|
||||
assertEquals(LoyaltyCardEditActivity.class.getName(), editIntent.getComponent().getClassName());
|
||||
assertNotNull(editIntent.getExtras());
|
||||
controller.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
public void destroyingActivityCancelsPendingPkpassResult() throws Exception {
|
||||
Uri uri = Uri.parse("content://test/pending-cloud-backed.pkpass");
|
||||
CountDownLatch readStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseRead = new CountDownLatch(1);
|
||||
CountDownLatch streamClosed = new CountDownLatch(1);
|
||||
InputStream pkpass = getClass().getResourceAsStream(
|
||||
"pkpass/DCBLN24/DCBLN24-QLUKT-1-passbook.pkpass");
|
||||
assertNotNull(pkpass);
|
||||
new ShadowContentResolver().registerInputStream(
|
||||
uri, new BlockingInputStream(pkpass, readStarted, releaseRead, streamClosed));
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW)
|
||||
.setDataAndType(uri, "application/vnd.apple.pkpass");
|
||||
ActivityController<MainActivity> controller =
|
||||
Robolectric.buildActivity(MainActivity.class, intent).create().start().resume();
|
||||
MainActivity activity = controller.get();
|
||||
|
||||
waitFor(() -> readStarted.getCount() == 0);
|
||||
controller.destroy();
|
||||
releaseRead.countDown();
|
||||
assertTrue(streamClosed.await(5, TimeUnit.SECONDS));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
assertNull(shadowOf(activity).peekNextStartedActivity());
|
||||
assertFalse(ShadowToast.showedToast(activity.getString(R.string.errorReadingFile)));
|
||||
}
|
||||
|
||||
private static void waitFor(BooleanSupplier condition) throws Exception {
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
|
||||
while (!condition.getAsBoolean() && System.nanoTime() < deadline) {
|
||||
shadowOf(getMainLooper()).idle();
|
||||
Thread.sleep(10);
|
||||
}
|
||||
shadowOf(getMainLooper()).idle();
|
||||
assertTrue("Timed out waiting for asynchronous activity work", condition.getAsBoolean());
|
||||
}
|
||||
|
||||
private static class RecordingInputStream extends FilterInputStream {
|
||||
private final CountDownLatch readStarted;
|
||||
private final CountDownLatch streamClosed;
|
||||
private final AtomicReference<Looper> readLooper;
|
||||
|
||||
RecordingInputStream(
|
||||
InputStream inputStream,
|
||||
CountDownLatch readStarted,
|
||||
CountDownLatch streamClosed,
|
||||
AtomicReference<Looper> readLooper) {
|
||||
super(inputStream);
|
||||
this.readStarted = readStarted;
|
||||
this.streamClosed = streamClosed;
|
||||
this.readLooper = readLooper;
|
||||
}
|
||||
|
||||
private void recordRead() {
|
||||
readLooper.compareAndSet(null, Looper.myLooper());
|
||||
readStarted.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
recordRead();
|
||||
return super.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int length) throws IOException {
|
||||
recordRead();
|
||||
return super.read(buffer, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
super.close();
|
||||
} finally {
|
||||
streamClosed.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class BlockingInputStream extends FilterInputStream {
|
||||
private final CountDownLatch readStarted;
|
||||
private final CountDownLatch releaseRead;
|
||||
private final CountDownLatch streamClosed;
|
||||
|
||||
BlockingInputStream(
|
||||
InputStream inputStream,
|
||||
CountDownLatch readStarted,
|
||||
CountDownLatch releaseRead,
|
||||
CountDownLatch streamClosed) {
|
||||
super(inputStream);
|
||||
this.readStarted = readStarted;
|
||||
this.releaseRead = releaseRead;
|
||||
this.streamClosed = streamClosed;
|
||||
}
|
||||
|
||||
private void awaitRelease() throws IOException {
|
||||
readStarted.countDown();
|
||||
try {
|
||||
if (!releaseRead.await(5, TimeUnit.SECONDS)) {
|
||||
throw new IOException("Timed out waiting to release provider stream");
|
||||
}
|
||||
} catch (InterruptedException exception) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
awaitRelease();
|
||||
return super.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int length) throws IOException {
|
||||
awaitRelease();
|
||||
return super.read(buffer, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
super.close();
|
||||
} finally {
|
||||
streamClosed.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initiallyNoLoyaltyCards() {
|
||||
Activity activity = Robolectric.setupActivity(MainActivity.class);
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
package protect.card_locker
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Looper
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.android.controller.ActivityController
|
||||
import org.robolectric.annotation.LooperMode
|
||||
import org.robolectric.shadows.ShadowContentResolver
|
||||
import org.robolectric.shadows.ShadowToast
|
||||
import java.io.FilterInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
class ScanActivityTest {
|
||||
@Before
|
||||
fun setUp() {
|
||||
ShadowToast.reset()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pkpassPickerReadsOffMainThreadAndReturnsResultOnMainThread() {
|
||||
val uri = Uri.parse("content://test/picker-cloud-backed.pkpass")
|
||||
val readStarted = CountDownLatch(1)
|
||||
val streamClosed = CountDownLatch(1)
|
||||
val readLooper = AtomicReference<Looper>()
|
||||
val pkpass = javaClass.getResourceAsStream(
|
||||
"pkpass/DCBLN24/DCBLN24-QLUKT-1-passbook.pkpass"
|
||||
)
|
||||
assertNotNull(pkpass)
|
||||
ShadowContentResolver().registerInputStream(
|
||||
uri,
|
||||
RecordingInputStream(pkpass, readStarted, streamClosed, readLooper)
|
||||
)
|
||||
val controller = createActivity()
|
||||
val activity = controller.get()
|
||||
|
||||
handlePkpassResult(activity, Intent().setData(uri))
|
||||
|
||||
assertTrue(readStarted.await(5, TimeUnit.SECONDS))
|
||||
// The read must not happen on the main looper. Asserted directly rather
|
||||
// than inferred from an intermediate result code: waiting on the main
|
||||
// looper drains it, so the flow has already reached its final state by
|
||||
// the time the test regains control.
|
||||
assertTrue(readLooper.get() !== Looper.getMainLooper())
|
||||
assertTrue(streamClosed.await(5, TimeUnit.SECONDS))
|
||||
|
||||
waitFor { shadowOf(activity).resultCode == Activity.RESULT_OK }
|
||||
assertNotNull(shadowOf(activity).resultIntent)
|
||||
assertTrue(activity.isFinishing)
|
||||
controller.destroy()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun providerFailureShowsOneErrorAndReenablesScanner() {
|
||||
val uri = Uri.parse("content://test/unavailable-cloud-backed.pkpass")
|
||||
ShadowContentResolver().registerInputStream(uri, FailingInputStream())
|
||||
val controller = createActivity()
|
||||
val activity = controller.get()
|
||||
setScannerActive(activity, false)
|
||||
val initialToastCount = ShadowToast.shownToastCount()
|
||||
|
||||
handlePkpassResult(activity, Intent().setData(uri))
|
||||
|
||||
waitFor { ShadowToast.shownToastCount() > initialToastCount }
|
||||
assertEquals(initialToastCount + 1, ShadowToast.shownToastCount())
|
||||
assertEquals(activity.getString(R.string.errorReadingFile), ShadowToast.getTextOfLatestToast())
|
||||
assertTrue(isScannerActive(activity))
|
||||
assertFalse(activity.isFinishing)
|
||||
controller.destroy()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun destroyingActivityCancelsPendingPkpassResult() {
|
||||
val uri = Uri.parse("content://test/pending-picker-cloud-backed.pkpass")
|
||||
val readStarted = CountDownLatch(1)
|
||||
val releaseRead = CountDownLatch(1)
|
||||
val streamClosed = CountDownLatch(1)
|
||||
val pkpass = javaClass.getResourceAsStream(
|
||||
"pkpass/DCBLN24/DCBLN24-QLUKT-1-passbook.pkpass"
|
||||
)
|
||||
assertNotNull(pkpass)
|
||||
ShadowContentResolver().registerInputStream(
|
||||
uri,
|
||||
BlockingInputStream(pkpass, readStarted, releaseRead, streamClosed)
|
||||
)
|
||||
val controller = createActivity()
|
||||
val activity = controller.get()
|
||||
|
||||
handlePkpassResult(activity, Intent().setData(uri))
|
||||
waitFor { readStarted.count == 0L }
|
||||
controller.destroy()
|
||||
releaseRead.countDown()
|
||||
assertTrue(streamClosed.await(5, TimeUnit.SECONDS))
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
|
||||
assertEquals(Activity.RESULT_CANCELED, shadowOf(activity).resultCode)
|
||||
assertFalse(ShadowToast.showedToast(activity.getString(R.string.errorReadingFile)))
|
||||
}
|
||||
|
||||
private fun createActivity(): ActivityController<ScanActivity> =
|
||||
Robolectric.buildActivity(ScanActivity::class.java).create().start().resume().visible()
|
||||
|
||||
private fun handlePkpassResult(activity: ScanActivity, intent: Intent) {
|
||||
ScanActivity::class.java.getDeclaredMethod(
|
||||
"handleActivityResult",
|
||||
Int::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType,
|
||||
Intent::class.java
|
||||
).apply { isAccessible = true }.invoke(
|
||||
activity,
|
||||
Utils.BARCODE_IMPORT_FROM_PKPASS_FILE,
|
||||
Activity.RESULT_OK,
|
||||
intent
|
||||
)
|
||||
}
|
||||
|
||||
private fun setScannerActive(activity: ScanActivity, active: Boolean) {
|
||||
ScanActivity::class.java.getDeclaredMethod(
|
||||
"setScannerActive",
|
||||
Boolean::class.javaPrimitiveType
|
||||
).apply { isAccessible = true }.invoke(activity, active)
|
||||
}
|
||||
|
||||
private fun isScannerActive(activity: ScanActivity): Boolean =
|
||||
ScanActivity::class.java.getDeclaredField("mScannerActive")
|
||||
.apply { isAccessible = true }
|
||||
.getBoolean(activity)
|
||||
|
||||
private fun waitFor(condition: () -> Boolean) {
|
||||
val deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5)
|
||||
while (!condition() && System.nanoTime() < deadline) {
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Thread.sleep(10)
|
||||
}
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
assertTrue("Timed out waiting for asynchronous activity work", condition())
|
||||
}
|
||||
|
||||
private class RecordingInputStream(
|
||||
inputStream: InputStream,
|
||||
private val readStarted: CountDownLatch,
|
||||
private val streamClosed: CountDownLatch,
|
||||
private val readLooper: AtomicReference<Looper>
|
||||
) : FilterInputStream(inputStream) {
|
||||
private fun recordRead() {
|
||||
readLooper.compareAndSet(null, Looper.myLooper())
|
||||
readStarted.countDown()
|
||||
}
|
||||
|
||||
override fun read(): Int {
|
||||
recordRead()
|
||||
return super.read()
|
||||
}
|
||||
|
||||
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
|
||||
recordRead()
|
||||
return super.read(buffer, offset, length)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
try {
|
||||
super.close()
|
||||
} finally {
|
||||
streamClosed.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BlockingInputStream(
|
||||
inputStream: InputStream,
|
||||
private val readStarted: CountDownLatch,
|
||||
private val releaseRead: CountDownLatch,
|
||||
private val streamClosed: CountDownLatch
|
||||
) : FilterInputStream(inputStream) {
|
||||
private fun awaitRelease() {
|
||||
readStarted.countDown()
|
||||
try {
|
||||
if (!releaseRead.await(5, TimeUnit.SECONDS)) {
|
||||
throw IOException("Timed out waiting to release provider stream")
|
||||
}
|
||||
} catch (exception: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
throw IOException(exception)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(): Int {
|
||||
awaitRelease()
|
||||
return super.read()
|
||||
}
|
||||
|
||||
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
|
||||
awaitRelease()
|
||||
return super.read(buffer, offset, length)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
try {
|
||||
super.close()
|
||||
} finally {
|
||||
streamClosed.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FailingInputStream : InputStream() {
|
||||
override fun read(): Int = throw IOException("Remote provider unavailable")
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import protect.card_locker.shared.ForegroundColorHelper;
|
||||
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class UtilsTest {
|
||||
@@ -26,7 +28,7 @@ public class UtilsTest {
|
||||
for (int i = 0; i < colors.length(); i++) {
|
||||
// Grab white as fallback so that if the retrieval somehow fails the test is guaranteed to fail because a white background will have black foreground
|
||||
int color = colors.getColor(i, Color.WHITE);
|
||||
assertFalse(Utils.needsDarkForeground(color));
|
||||
assertFalse(ForegroundColorHelper.Companion.needsDarkForeground(color));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package protect.card_locker.wearos
|
||||
|
||||
import android.content.Context
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import protect.card_locker.R
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class WearSyncServiceManagerTest {
|
||||
|
||||
@Test
|
||||
fun onPermissionResult_denied_disablesWearSync() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
prefs.edit().putBoolean(context.getString(R.string.settings_key_wear_sync), true).commit()
|
||||
|
||||
WearSyncServiceManager.onPermissionResult(context, false)
|
||||
|
||||
assertFalse(prefs.getBoolean(context.getString(R.string.settings_key_wear_sync), true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPermissionResult_granted_keepsWearSyncEnabled() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
prefs.edit().putBoolean(context.getString(R.string.settings_key_wear_sync), true).commit()
|
||||
|
||||
WearSyncServiceManager.onPermissionResult(context, true)
|
||||
|
||||
assertTrue(prefs.getBoolean(context.getString(R.string.settings_key_wear_sync), false))
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.com.android.application) apply false
|
||||
alias(libs.plugins.com.android.library) apply false
|
||||
alias(libs.plugins.org.jetbrains.kotlin.android) apply false
|
||||
alias(libs.plugins.org.jetbrains.kotlin.plugin.compose) apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
||||
@@ -5,6 +5,16 @@ IFS=$'\n\t'
|
||||
### build.sh
|
||||
### Builds Catima the same way rbtlog/IzzyOnDroid does for reproducible builds
|
||||
|
||||
if [ -z "${BUILD_TYPE:-}" ]; then
|
||||
echo "BUILD_TYPE is not set, setting to app."
|
||||
export BUILD_TYPE="app"
|
||||
fi
|
||||
|
||||
if [ "${BUILD_TYPE}" != "app" ] && [ "${BUILD_TYPE}" != "wear" ]; then
|
||||
echo "Invalid BUILD_TYPE ${BUILD_TYPE}, must be either app or wear."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${ANDROID_HOME:-}" ]; then
|
||||
echo "ANDROID_HOME is not set, setting to $HOME/Android/Sdk";
|
||||
export ANDROID_HOME=$HOME/Android/Sdk
|
||||
@@ -22,13 +32,17 @@ if [ -z "${JAVA_HOME:-}" ]; then
|
||||
fi
|
||||
|
||||
echo "Starting build"
|
||||
./gradlew clean assembleRelease
|
||||
./gradlew clean ":${BUILD_TYPE}:assembleRelease"
|
||||
|
||||
echo "Build finished (unsigned)"
|
||||
flavourDirs=$(find app/build/outputs/apk/ -mindepth 1 -maxdepth 1 -type d)
|
||||
flavourDirs=$(find "${BUILD_TYPE}/build/outputs/apk/" -mindepth 1 -maxdepth 1 -type d)
|
||||
for flavourDir in $flavourDirs; do
|
||||
flavourName="$(basename "$flavourDir")"
|
||||
echo "Your $flavourName flavour is at $flavourDir/release/app-$flavourName-release-unsigned.apk"
|
||||
if [ "$flavourName" == "release" ]; then
|
||||
echo "Your $flavourName flavour is at $flavourDir/${BUILD_TYPE}-release-unsigned.apk"
|
||||
else
|
||||
echo "Your $flavourName flavour is at $flavourDir/release/${BUILD_TYPE}-$flavourName-release-unsigned.apk"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "${KEYSTORE:-}" ]; then
|
||||
@@ -44,19 +58,33 @@ else
|
||||
for flavourDir in $flavourDirs; do
|
||||
flavourName="$(basename "$flavourDir")"
|
||||
echo "Signing $flavourName flavour..."
|
||||
cp "$flavourDir/release/app-$flavourName-release-unsigned.apk" "$flavourDir/release/app-$flavourName-release.apk"
|
||||
"$ANDROID_HOME/build-tools/$apksigner_version/apksigner" sign -v --ks "$KEYSTORE" --ks-key-alias "$KEYSTORE_ALIAS" "$flavourDir/release/app-$flavourName-release.apk"
|
||||
if [ "$flavourName" == "release" ]; then
|
||||
cp "$flavourDir/${BUILD_TYPE}-release-unsigned.apk" "$flavourDir/${BUILD_TYPE}-release.apk"
|
||||
"$ANDROID_HOME/build-tools/$apksigner_version/apksigner" sign -v --ks "$KEYSTORE" --ks-key-alias "$KEYSTORE_ALIAS" "$flavourDir/${BUILD_TYPE}-release.apk"
|
||||
else
|
||||
cp "$flavourDir/release/${BUILD_TYPE}-$flavourName-release-unsigned.apk" "$flavourDir/release/${BUILD_TYPE}-$flavourName-release.apk"
|
||||
"$ANDROID_HOME/build-tools/$apksigner_version/apksigner" sign -v --ks "$KEYSTORE" --ks-key-alias "$KEYSTORE_ALIAS" "$flavourDir/release/${BUILD_TYPE}-$flavourName-release.apk"
|
||||
fi
|
||||
|
||||
echo "Build finished (signed)"
|
||||
echo "Your $flavourName flavour is at $flavourDir/release/app-$flavourName-release.apk"
|
||||
if [ "$flavourName" == "release" ]; then
|
||||
echo "Your $flavourName flavour is at $flavourDir/${BUILD_TYPE}-release.apk"
|
||||
else
|
||||
echo "Your $flavourName flavour is at $flavourDir/release/${BUILD_TYPE}-$flavourName-release.apk"
|
||||
fi
|
||||
done
|
||||
|
||||
shasumPath="$(pwd)/SHA256SUMS"
|
||||
echo "" > "$shasumPath"
|
||||
|
||||
for flavourDir in $flavourDirs; do
|
||||
pushd "$flavourDir/release/"
|
||||
sha256sum -- *.apk >> "$shasumPath"
|
||||
flavourName="$(basename "$flavourDir")"
|
||||
if [ "$flavourName" == "release" ]; then
|
||||
pushd "$flavourDir"
|
||||
else
|
||||
pushd "$flavourDir/release/"
|
||||
fi
|
||||
sha256sum -- "${BUILD_TYPE}"-*.apk >> "$shasumPath"
|
||||
popd
|
||||
done
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# Bluetooth server (for WearOS support and other Bluetooth devices)
|
||||
|
||||
Catima implements a [Rfcomm Bluetooth server](https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#listenUsingRfcommWithServiceRecord(java.lang.String,%20java.util.UUID)) to allow Bluetooth devices to interact with the cards database. Right now, this only provides read-only functionality.
|
||||
|
||||
The UUID of the Rfcomm Bluetooth server is `e5b4f020-3a7e-4b6d-9f2c-1a8c5d3e7f90`. You should bind to this using [createRfcommSocketToServiceRecord](https://developer.android.com/reference/android/bluetooth/BluetoothDevice#createRfcommSocketToServiceRecord(java.util.UUID)) on Android or, if not Android, whatever is the alternative for your platform. Insecure Rfcomm sockets are not supported.
|
||||
|
||||
Since running the Bluetooth server requires extra permissions (Bluetooth and Notifications) and runs a constant background process it is disabled by default.
|
||||
|
||||
For security, Catima keeps a list of known MAC addresses and tokens and will only allow devices explicitly allowed by the user to interact with the Bluetooth server. This prevents every paired Bluetooth device from being able to access cards without user authentication.
|
||||
|
||||
The Bluetooth server can be controlled with the setting "Sync with Wear OS" under "Smartwatch support". It is disabled by default.
|
||||
|
||||
The Bluetooth server is currently only used by the Catima WearOS companion app. If your app/device uses it, please let us know, so we can update the documentation and contact your project in case of planned breaking changes.
|
||||
|
||||
## API versions
|
||||
|
||||
For each API version, we have a major and a minor version. The rules for versioning are as follows:
|
||||
1. A new minor version may add fields or endpoints, but must never modify or remove existing fields or endpoints.
|
||||
2. A new minor version must not change any authentication systems compared to the previous minor version.
|
||||
3. A device cannot request Catima to use a specific minor version, it will always return data as per the most recently supported minor version.
|
||||
|
||||
The main driver of compatibility is the major version.
|
||||
|
||||
Due to this system, a device only supporting API version 1.0 can talk to Catima supporting API version 1.1 with no issues, as long as it ignores fields it doesn't know. On the other side, a device wanting to use API version 1.1 with Catima on API version 1.0 has to accept certain fields may not be available. Which minor version introduces which additional fields and endpoints is documented in this file.
|
||||
|
||||
### Unversioned
|
||||
|
||||
#### /VERSIONS
|
||||
|
||||
Return type: JSON.
|
||||
|
||||
Returns all supported major versions and the most recent supported minor version.
|
||||
|
||||
Example request:
|
||||
```
|
||||
/VERSIONS
|
||||
```
|
||||
|
||||
Example return value:
|
||||
```
|
||||
["1.0"]
|
||||
```
|
||||
|
||||
In this example case, the phone supports only API version 1. Of API version 1, the most recent minor version supported is 0.
|
||||
|
||||
### V1
|
||||
|
||||
All V1 endpoints are authenticated and must be communicated with by sending 2 lines over an RfConn socket. First the full command, then a device-specific token (recommended: a base64-encoded version of a 32 characters long SecureRandom-generated string).
|
||||
|
||||
The device-specific token must be generated once on the companion device and never changed.
|
||||
|
||||
For example, to call the first page of `/V1/CARDS_REQUEST_PAGE/` you should send:
|
||||
```
|
||||
/V1/CARDS_REQUEST_PAGE/0
|
||||
TOKEN: your_base64_encoded_token_here
|
||||
```
|
||||
|
||||
#### /V1/CARDS_REQUEST_PAGE/{id:int}
|
||||
|
||||
Return type: JSON.
|
||||
|
||||
Request a single page of cards with page and total page details. The page ID is 0-indexed, making 0 the first page. Currently, a page is 10 cards but this may change in the future.
|
||||
|
||||
Each card contains the following fields:
|
||||
|
||||
| Column | Type | Description | Introduced in minor version |
|
||||
|-------------------|-------------------|----------------------------| --------------------------- |
|
||||
| `id` | `int` | Unique card ID | 0 |
|
||||
| `store` | `String` | Card name | 0 |
|
||||
| `cardId` | `String` | Card ID. | 0 |
|
||||
| `barcodeId` | `Nullable String` | Barcode value. If empty, it's the same as the card ID. | 0 |
|
||||
| `barcodeType` | `Nullable String` | The barcode type name, matching [com.google.zxing.BarcodeFormat](https://zxing.github.io/zxing/apidocs/com/google/zxing/BarcodeFormat.html). When null, there is no barcode for this card. | 0 |
|
||||
| `headerColor` | `int` | Header color, matching [Android color int](https://developer.android.com/reference/android/graphics/Color). | 0 |
|
||||
|
||||
Example request:
|
||||
```
|
||||
/V1/CARDS_REQUEST_PAGE/0
|
||||
TOKEN: your_base64_encoded_token_here
|
||||
```
|
||||
|
||||
Example return value:
|
||||
```
|
||||
{
|
||||
"page": 0,
|
||||
"totalPages": 1,
|
||||
"cards": [
|
||||
{
|
||||
"id": 1,
|
||||
"store": "Bookshop",
|
||||
"cardId": "123456",
|
||||
"barcodeId": null,
|
||||
"barcodeType": "QR_CODE",
|
||||
"headerColor": -1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -1,9 +1,13 @@
|
||||
# Content Provider
|
||||
# Content Provider (for Gadgetbridge and other local apps)
|
||||
|
||||
Catima implements a [content provider](https://developer.android.com/guide/topics/providers/content-providers) to allow for external apps to interact with the cards database. Right now, this only provides read-only functionality.
|
||||
|
||||
Since runtime permissions are only available since Android 6.0 (API level 23), the content provider is disabled for older android versions in order to prevent unwanted access to the data.
|
||||
|
||||
The content provider can be controlled with the setting "Sync with Gadgetbridge" under "Smartwatch support". It is enabled by default.
|
||||
|
||||
The content provider is currently only used by Gadgetbridge. If your app uses it, please let us know, so we can update the documentation and contact your project in case of planned breaking changes.
|
||||
|
||||
## Package Names
|
||||
|
||||
There are 3 release channels, with 2 possible package names:
|
||||
@@ -41,23 +45,23 @@ A major version change implies breaking changes (eg. columns being renamed or re
|
||||
|
||||
### /cards
|
||||
|
||||
| Column | Type | Description |
|
||||
|-------------------|----------|----------------------------|
|
||||
| `_id` | `int` | Unique card ID |
|
||||
| `store` | `String` | Card name |
|
||||
| `validfrom` | `long` | Timestamp from which the card is valid (unix epoch millis). |
|
||||
| `expiry` | `long` | Expiration timestamp (unix epoch millis). |
|
||||
| `balance` | `String` | Current balance, as a string-formatted big decimal. |
|
||||
| `balancetype` | `String` | Balance currency code, ISO 4217. |
|
||||
| `note` | `String` | A note. |
|
||||
| `headercolor` | `int` | Header color, in RGBA. |
|
||||
| `cardid` | `String` | Card ID. |
|
||||
| `barcodeid` | `String` | Barcode value. If empty, it's the same as the card ID. |
|
||||
| `barcodetype` | `String` | The barcode type name, matching [com.google.zxing.BarcodeFormat](https://zxing.github.io/zxing/apidocs/com/google/zxing/BarcodeFormat.html). |
|
||||
| `barcodeencoding` | `String` | The barcode encoding used to render the barcode, matching a [StandardCharset](https://developer.android.com/reference/java/nio/charset/StandardCharsets)'s `.name()` output. Only `ISO-8859-1` and `UTF-8` are user selectable. |
|
||||
| `starstatus` | `int` | 1 if starred, 0 if not |
|
||||
| `lastused` | `long` | Timestamp of last card usage (unix epoch millis). |
|
||||
| `archive` | `int` | 1 if archived, 0 if not |
|
||||
| Column | Type | Description |
|
||||
|-------------------|-------------------|----------------------------|
|
||||
| `_id` | `int` | Unique card ID |
|
||||
| `store` | `String` | Card name |
|
||||
| `validfrom` | `long` | Timestamp from which the card is valid (unix epoch millis). |
|
||||
| `expiry` | `long` | Expiration timestamp (unix epoch millis). |
|
||||
| `balance` | `String` | Current balance, as a string-formatted big decimal. |
|
||||
| `balancetype` | `Nullable String` | Balance currency code, ISO 4217. When null, it's a generic "Points" indicator. |
|
||||
| `note` | `String` | A note. |
|
||||
| `headercolor` | `int` | Header color, matching [Android color int](https://developer.android.com/reference/android/graphics/Color). |
|
||||
| `cardid` | `String` | Card ID. |
|
||||
| `barcodeid` | `Nullable String` | Barcode value. If null, it's the same as the card ID. |
|
||||
| `barcodetype` | `Nullable String` | The barcode type name, matching [com.google.zxing.BarcodeFormat](https://zxing.github.io/zxing/apidocs/com/google/zxing/BarcodeFormat.html). When null, there is no barcode for this card. |
|
||||
| `barcodeencoding` | `String` | The barcode encoding used to render the barcode, matching a [StandardCharset](https://developer.android.com/reference/java/nio/charset/StandardCharsets)'s `.name()` output. Only `ISO-8859-1` and `UTF-8` are user selectable. |
|
||||
| `starstatus` | `int` | 1 if starred, 0 if not |
|
||||
| `lastused` | `long` | Timestamp of last card usage (unix epoch millis). |
|
||||
| `archive` | `int` | 1 if archived, 0 if not |
|
||||
|
||||
### /groups
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Catima
|
||||
|
||||
Copylefted libre software (GPLv3+) card management app.
|
||||
Copylefted libre software (GPLv3+) card management app for Android 6.0 and up.
|
||||
|
||||
[](https://github.com/CatimaLoyalty/Android/releases)
|
||||
[](https://github.com/CatimaLoyalty/Android/releases)
|
||||
[&filter=*-wearos)](https://github.com/CatimaLoyalty/Android/releases)
|
||||
[](https://apt.izzysoft.de/fdroid/index/apk/me.hackerchick.catima)
|
||||
[](https://f-droid.org/packages/me.hackerchick.catima/)
|
||||
[](https://play.google.com/store/apps/details?id=me.hackerchick.catima)
|
||||
@@ -18,6 +19,8 @@ Copylefted libre software (GPLv3+) card management app.
|
||||
<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" alt="Get it on IzzyOnDroid" height="90"/></a>
|
||||
<a href="https://f-droid.org/repository/browse/?fdid=me.hackerchick.catima" target="_blank">
|
||||
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="90"/></a>
|
||||
<a href="https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/%7B%22id%22%3A%22me.hackerchick.catima%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2FCatimaLoyalty%2FAndroid%22%2C%22author%22%3A%22CatimaLoyalty%22%2C%22name%22%3A%22Catima%22%2C%22preferredApkIndex%22%3A0%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Afalse%2C%5C%22fallbackToOlderReleases%5C%22%3Atrue%2C%5C%22filterReleaseTitlesByRegEx%5C%22%3A%5C%22%5E((%3F!.*Wear.*).)*%24%5C%22%2C%5C%22filterReleaseNotesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22verifyLatestTag%5C%22%3Afalse%2C%5C%22sortMethodChoice%5C%22%3A%5C%22smartname%5C%22%2C%5C%22useLatestAssetDateAsReleaseDate%5C%22%3Afalse%2C%5C%22releaseTitleAsVersion%5C%22%3Atrue%2C%5C%22trackOnly%5C%22%3Afalse%2C%5C%22versionExtractionRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22matchGroupToUse%5C%22%3A%5C%22%5C%22%2C%5C%22versionDetection%5C%22%3Atrue%2C%5C%22releaseDateAsVersion%5C%22%3Afalse%2C%5C%22useVersionCodeAsOSVersion%5C%22%3Afalse%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22invertAPKFilter%5C%22%3Afalse%2C%5C%22autoApkFilterByArch%5C%22%3Atrue%2C%5C%22minimumUpdateAgeDays%5C%22%3A%5C%22%5C%22%2C%5C%22appName%5C%22%3A%5C%22%5C%22%2C%5C%22appAuthor%5C%22%3A%5C%22Sylvia%20van%20Os%5C%22%2C%5C%22shizukuPretendToBeGooglePlay%5C%22%3Afalse%2C%5C%22allowInsecure%5C%22%3Afalse%2C%5C%22exemptFromBackgroundUpdates%5C%22%3Afalse%2C%5C%22skipUpdateNotifications%5C%22%3Afalse%2C%5C%22about%5C%22%3A%5C%22%5C%22%2C%5C%22refreshBeforeDownload%5C%22%3Afalse%2C%5C%22includeZips%5C%22%3Afalse%2C%5C%22zippedApkFilterRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22includeTarballs%5C%22%3Afalse%2C%5C%22tarballedApkFilterRegEx%5C%22%3A%5C%22%5C%22%7D%22%2C%22overrideSource%22%3Anull%7D" target="_blank">
|
||||
<img src="https://github.com/user-attachments/assets/713d71c5-3dec-4ec4-a3f2-8d28d025a9c6" alt="Get it on Obtainium" height="90"/></a>
|
||||
<a href="https://play.google.com/store/apps/details?id=me.hackerchick.catima" target="_blank">
|
||||
<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="90"/></a>
|
||||
|
||||
@@ -41,8 +44,12 @@ Supported barcodes:
|
||||
- UPC_A
|
||||
- UPC_E
|
||||
|
||||
Works fully offline, supports pkpass ("Add to Apple Wallet") files and select smartwatches through [Gadgetbridge](https://gadgetbridge.org/basics/integrations/cards/) and Wear OS companion app.
|
||||
|
||||
# Screenshots
|
||||
|
||||
## Core app
|
||||
|
||||
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-01.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-01.png)
|
||||
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-02.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-02.png)
|
||||
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-03.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-03.png)
|
||||
@@ -52,6 +59,12 @@ Supported barcodes:
|
||||
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-07.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-07.png)
|
||||
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-08.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-08.png)
|
||||
|
||||
## Wear OS companion app
|
||||
|
||||
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/wearScreenshots/screenshot-01.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/wearScreenshots/screenshot-01.png)
|
||||
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/wearScreenshots/screenshot-02.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/wearScreenshots/screenshot-02.png)
|
||||
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/wearScreenshots/screenshot-03.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/wearScreenshots/screenshot-03.png)
|
||||
|
||||
# Moving data from other apps
|
||||
|
||||
Within the app you can import cards and codes from files, Catima, FidMe, Loyalty Card Keychain and Voucher Vault.
|
||||
|
||||
@@ -1,14 +1,31 @@
|
||||
**This documentation is for maintainers. If you're a user, please ignore it.**
|
||||
|
||||
# General versioning note
|
||||
Android releases should be at least versionCode 1000 and end on an even number (the uneven numbers are used for F-Droid legacy builds). Valid: 1002, 1004, etc.
|
||||
|
||||
WearOS releases should be between 200 and 999 and only have to be bumped by 1. Valid: 200, 201, etc.
|
||||
|
||||
Version codes below 200 are from the pre-WearOS days.
|
||||
|
||||
# When releasing, do the following:
|
||||
1. Press "Commit" and "Push" on Weblate to ensure all translations are up to date
|
||||
2. Merge Weblate pull request
|
||||
3. Make sure to pull the `main` branch locally
|
||||
4. Update `CHANGELOG.md` with the new version name and the release date
|
||||
5. Update `app/build.gradle.kts` with the new `versionCode` and `versionName`
|
||||
6. Create a commit for the new release: `git add CHANGELOG.md app/build.gradle.kts && git commit -m "Release Catima <VERSION>"`
|
||||
7. Build the new .apks: `KEYSTORE=/path/to/keystore KEYSTORE_ALIAS=catima ./build.sh`
|
||||
8. Upload `app/build/outputs/apk/gplay/release/app-gplay-release.apk` to Google Play Open Testing
|
||||
5. Update the relevant build.gradle.kts with the new `versionCode` and `versionName`
|
||||
- For Android releases, this is `app/build.gradle.kts`, make sure to use an even number above 1000
|
||||
- For Wear OS releases, this is `wear/build.gradle.kts`, make sure to use a number between 200 and 1000
|
||||
6. Create a commit for the new release:
|
||||
- For Android releases, `git add CHANGELOG.md app/build.gradle.kts && git commit -m "Release Catima <VERSION>"`
|
||||
- For Wear OS releases, `git add CHANGELOG.md wear/build.gradle.kts && git commit -m "Release Catima Wear OS <VERSION>"`
|
||||
7. Build the new .apks:
|
||||
- For Android releases, `BUILD_TYPE=app KEYSTORE=/path/to/keystore KEYSTORE_ALIAS=catima ./build.sh`
|
||||
- For Wear OS releases, `BUILD_TYPE=wear KEYSTORE=/path/to/keystore KEYSTORE_ALIAS=catima ./build.sh`
|
||||
8. Upload the Google release to Google Play Open Testing
|
||||
- For Android releases, this is `app/build/outputs/apk/gplay/release/app-gplay-release.apk`
|
||||
- For Wear OS releases, this is `wear/build/outputs/apk/release/wear-release.apk` (Wear OS currently does not use flavours)
|
||||
9. Push the version update commit: `git push`
|
||||
10. Create a new release on GitHub and attach the `app/build/outputs/apk/foss/release/app-foss-release.apk` and `SHA256SUMS` files
|
||||
10. Create a new release on GitHub and attach the non-Google APKs and `SHA256SUMS` files
|
||||
- For Android releases, this is `app/build/outputs/apk/foss/release/app-foss-release.apk` and `SHA256SUMS`. Releases are tagged vX.X.X.
|
||||
- For Wear OS releases, this is `wear/build/outputs/apk/release/wear-release.apk` and `SHA256SUMs` (Wear OS currently does not use flavours). Releases are tagged vX.X.X-wearos.
|
||||
11. When pushing the release to Google Play Production, update the metadata there: `bundle exec fastlane supply --version_code <VERSION_CODE>`
|
||||