Compare commits

..

665 Commits

Author SHA1 Message Date
Sylvia van Os
47bdd5209d Adjust text sizing 2025-12-20 12:45:11 +01:00
Sylvia van Os
1f6db32155 Fix build issues 2025-12-17 21:30:24 +01:00
Sylvia van Os
705b80a32c Fix Gradle setup 2025-12-17 21:19:54 +01:00
LooKeR
91bb808d19 test: Fix configuration of compose tests 2025-12-17 21:13:56 +01:00
LooKeR
497669b3f8 test: Add more comprehensive tests for about screen 2025-12-17 21:13:31 +01:00
LooKeR
3fb77bd607 style: Format AboutActivity.kt 2025-12-17 21:13:31 +01:00
LooKeR
cc2fe5b52b refactor: Best practise apply theme as high as possible for most cases 2025-12-17 21:13:31 +01:00
LooKeR
abb01eed66 refactor: Best practise to make previews private to reduce pollution 2025-12-17 21:13:31 +01:00
LooKeR
a1469aa550 refactor: Make showRateOnGooglePlay default to app/build.gradle.kts/defaultConfig value 2025-12-17 21:13:31 +01:00
LooKeR
f8deeee32c refactor: Move compose tests to unit tests 2025-12-17 21:13:31 +01:00
LooKeR
021fd13ea6 refactor: Add defaults for AboutScreenContent 2025-12-17 21:12:35 +01:00
LooKeR
3f753e8f51 test: Add basic test for compose about screen 2025-12-17 21:12:35 +01:00
LooKeR
2c1210e8cd test: Add test tags for compose components 2025-12-17 21:12:35 +01:00
Sylvia van Os
10193ffd85 WIP 2025-12-17 21:11:40 +01:00
Sylvia van Os
17d3e9b3d0 Merge pull request #2876 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-17 20:34:32 +01:00
امیرضا
de47b9e774 Translated using Weblate (Persian)
Currently translated at 83.9% (278 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fa/
2025-12-17 20:01:10 +01:00
امیرضا
acfa8d9fe2 Translated using Weblate (Persian)
Currently translated at 27.2% (42 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fa/
2025-12-17 20:01:08 +01:00
Sylvia van Os
930246e6c5 Merge pull request #2874 from CatimaLoyalty/create-pull-request/patch-1765747610
Update Fastlane changelogs
2025-12-14 22:28:01 +01:00
TheLastProject
c6b8272448 Update Fastlane changelogs 2025-12-14 21:26:50 +00:00
Sylvia van Os
24b832a217 Update CHANGELOG 2025-12-14 22:26:37 +01:00
Charalampos Kardaris
3acf002f95 [Fix] Issue #2812: Show duplicate action in long press menu (#2873)
Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
2025-12-14 22:25:23 +01:00
Sylvia van Os
16f9b3f6b1 Merge pull request #2872 from CatimaLoyalty/create-pull-request/patch-1765686940
Update contributors
2025-12-14 10:51:09 +01:00
TheLastProject
3c38c7cc25 Update contributors 2025-12-14 04:35:39 +00:00
Methum Menthusa
efbc930125 Merge pull request #2868 from methum-m/dependency-cooldown
Add 7 day dependency cooldown
2025-12-12 15:26:02 +01:00
Sylvia van Os
90b326e6b9 Merge pull request #2870 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-12 11:34:18 +01:00
Francisco Serrador
d3e7fe212d Translated using Weblate (Spanish)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2025-12-12 11:00:29 +01:00
Sylvia van Os
803d83f8e1 Merge pull request #2869 from CatimaLoyalty/dependabot/gradle/com.android.application-8.13.2
Bump com.android.application from 8.13.1 to 8.13.2
2025-12-12 08:25:25 +01:00
dependabot[bot]
59b060fbc0 Bump com.android.application from 8.13.1 to 8.13.2
Bumps com.android.application from 8.13.1 to 8.13.2.

---
updated-dependencies:
- dependency-name: com.android.application
  dependency-version: 8.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-12 02:02:59 +00:00
Sylvia van Os
6d64bd4cdf Fix release staps 2025-12-08 18:45:13 +01:00
Sylvia van Os
ada4850f65 Merge branch 'main' of github.com:CatimaLoyalty/Android 2025-12-08 18:40:48 +01:00
Sylvia van Os
479fce68d5 Release Catima 2.40.0 2025-12-08 18:32:40 +01:00
Sylvia van Os
2c0b49d7f8 Merge pull request #2865 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-08 18:31:45 +01:00
Hosted Weblate
e534eebc4d Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/
2025-12-08 18:03:39 +01:00
Yasin Tanış
db16676cc4 Translated using Weblate (Turkish)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/tr/
2025-12-08 18:03:38 +01:00
Gideon
0f1e5b858b Translated using Weblate (Dutch)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2025-12-08 18:03:38 +01:00
Yasin Tanış
a39d2e46e1 Translated using Weblate (Turkish)
Currently translated at 66.8% (103 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/tr/
2025-12-08 18:03:37 +01:00
Sylvia van Os
4370cd2383 Merge pull request #2864 from CatimaLoyalty/dependabot/github_actions/peter-evans/create-pull-request-7.0.11
Bump peter-evans/create-pull-request from 7.0.9 to 7.0.11
2025-12-08 10:15:25 +01:00
dependabot[bot]
0c4ef730e0 Bump peter-evans/create-pull-request from 7.0.9 to 7.0.11
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.9 to 7.0.11.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v7.0.9...v7.0.11)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-version: 7.0.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 02:04:42 +00:00
Sylvia van Os
ee909e0047 Merge pull request #2863 from CatimaLoyalty/create-pull-request/patch-1765081875
Update contributors
2025-12-07 11:11:45 +01:00
TheLastProject
6eee4a25f3 Update contributors 2025-12-07 04:31:15 +00:00
Sylvia van Os
ffa99231c6 Merge pull request #2862 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-06 20:58:41 +01:00
Hosted Weblate
cbcd74f735 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/
2025-12-06 19:03:25 +00:00
Richard Varga
4f46a3c8ab Translated using Weblate (Slovak)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sk/
2025-12-06 19:03:24 +00:00
Ati
5cec75c4c7 Translated using Weblate (Slovak)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sk/
2025-12-06 19:03:24 +00:00
Patrik
38d3731027 Translated using Weblate (Slovak)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sk/
2025-12-06 19:03:23 +00:00
Richard Varga
2a9f911a39 Translated using Weblate (Slovak)
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sk/
2025-12-06 19:03:22 +00:00
Sylvia van Os
c762fcf6cc Merge pull request #2861 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-05 22:17:50 +01:00
VKing9
59db6642c3 Translated using Weblate (Hindi)
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/hi/
2025-12-05 16:01:04 +01:00
Diego Menezes
eb5168ef83 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_BR/
2025-12-05 16:01:03 +01:00
VKing9
7221ea64c8 Translated using Weblate (Hindi)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/hi/
2025-12-05 16:01:03 +01:00
Marko Zakrajsek
af17bde7c5 Translated using Weblate (Slovenian)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sl/
2025-12-05 16:01:02 +01:00
Sylvia van Os
67e2abde8b Merge pull request #2860 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-04 15:26:38 +01:00
Liner Seven
f28a9e7ba3 Translated using Weblate (Japanese)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2025-12-04 15:04:26 +01:00
Joel A
96f01b6a2c Translated using Weblate (Swedish)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sv/
2025-12-04 15:04:26 +01:00
B o d o
56bc429c4b Translated using Weblate (German)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-12-04 15:04:25 +01:00
Fjuro
163da4b021 Translated using Weblate (Czech)
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2025-12-04 15:04:24 +01:00
Sylvain Pichon
6d9c168125 Translated using Weblate (French)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2025-12-04 15:04:24 +01:00
solokot
ccaef1adc5 Translated using Weblate (Russian)
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2025-12-04 15:04:23 +01:00
4ipset
f73222597c Translated using Weblate (Russian)
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2025-12-04 15:04:22 +01:00
大王叫我来巡山
1714606744 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2025-12-04 15:04:22 +01:00
Максим Горпиніч
ac3ef7fb36 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/uk/
2025-12-04 15:04:21 +01:00
Fjuro
9417814268 Translated using Weblate (Czech)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/cs/
2025-12-04 15:04:20 +01:00
Vasilis K.
72737074fb Translated using Weblate (Greek)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2025-12-04 15:04:20 +01:00
Максим Горпиніч
e05386620b Translated using Weblate (Ukrainian)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/uk/
2025-12-04 15:04:19 +01:00
jack son
7cfe1ad833 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hant/
2025-12-04 15:04:18 +01:00
Edgars Andersons
00b975a140 Translated using Weblate (Latvian)
Currently translated at 12.3% (19 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lv/
2025-12-04 15:04:18 +01:00
Liner Seven
424e57e41a Translated using Weblate (Japanese)
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-12-04 15:04:17 +01:00
B o d o
cdb169c4e0 Translated using Weblate (German)
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2025-12-04 15:04:16 +01:00
109247019824
17aa18397e Translated using Weblate (Bulgarian)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bg/
2025-12-04 15:04:15 +01:00
4ipset
bb72eefc7f Translated using Weblate (Russian)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2025-12-04 15:04:15 +01:00
Joel A
a007ed6a8f Translated using Weblate (Swedish)
Currently translated at 6.4% (10 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sv/
2025-12-04 15:04:14 +01:00
jack son
2219e86576 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hant/
2025-12-04 15:04:13 +01:00
大王叫我来巡山
7f90e06ac5 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hans/
2025-12-04 15:04:13 +01:00
Edgars Andersons
5dd089c976 Translated using Weblate (Latvian)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/lv/
2025-12-04 15:04:12 +01:00
Sylvain Pichon
ef9aacd609 Translated using Weblate (French)
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2025-12-04 15:04:11 +01:00
Priit Jõerüüt
fa19960c5e Translated using Weblate (Estonian)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/et/
2025-12-04 15:04:11 +01:00
josé m.
a5428b80ff Translated using Weblate (Galician)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/gl/
2025-12-04 15:04:10 +01:00
Sylvia van Os
56512101c2 Merge pull request #2859 from CatimaLoyalty/dependabot/gradle/androidx.exifinterface-exifinterface-1.4.2
Bump androidx.exifinterface:exifinterface from 1.4.1 to 1.4.2
2025-12-04 07:22:02 +01:00
dependabot[bot]
2bc8312511 Bump androidx.exifinterface:exifinterface from 1.4.1 to 1.4.2
Bumps androidx.exifinterface:exifinterface from 1.4.1 to 1.4.2.

---
updated-dependencies:
- dependency-name: androidx.exifinterface:exifinterface
  dependency-version: 1.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-04 02:03:23 +00:00
ProgramminCat
43ccf9b48e Convert MainActivity to Kotlin (#2830)
Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
2025-12-02 20:54:33 +01:00
Sylvia van Os
e1a4ed6634 Merge pull request #2858 from CatimaLoyalty/dependabot/github_actions/actions/setup-python-6.1.0
Bump actions/setup-python from 6.0.0 to 6.1.0
2025-12-02 20:15:08 +01:00
Sylvia van Os
272e249d5e Merge pull request #2843 from CatimaLoyalty/gradlew-update-9.2.1
Update Gradle Wrapper from 9.2.0 to 9.2.1
2025-12-02 20:14:21 +01:00
dependabot[bot]
5636653c16 Bump actions/setup-python from 6.0.0 to 6.1.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6.0.0 to 6.1.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v6.0.0...v6.1.0)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 02:16:46 +00:00
Sylvia van Os
06b1b32ce7 Merge pull request #2857 from CatimaLoyalty/create-pull-request/patch-1764477253
Update contributors
2025-11-30 11:09:05 +01:00
TheLastProject
202936b21f Update contributors 2025-11-30 04:34:13 +00:00
Sylvia van Os
122f7f64b7 Merge pull request #2856 from CatimaLoyalty/create-pull-request/patch-1764415067
Update Fastlane changelogs
2025-11-29 12:21:44 +01:00
TheLastProject
fbb913862a Update Fastlane changelogs 2025-11-29 11:17:46 +00:00
Sylvia van Os
61892f9f22 Update CHANGELOG 2025-11-29 12:17:34 +01:00
Sylvia van Os
166056fedd Merge pull request #2855 from CatimaLoyalty/fix/2847
Swap currency and balance fields to reduce chance of accidental conversions
2025-11-29 12:16:56 +01:00
Sylvia van Os
e03c883a9c Swap currency and balance fields to reduce chance of accidental conversions
This swaps the currency and balance fields to reduce the risk of users
entering a decimal value (1,23) first and having to changed to 1 due to
the default currency (Points) having no decimals.

The changes in the LoyaltyCardEditActivity are purely cosmetic, just a
swap of function order to more closely stick to the order in the XML
layout file
2025-11-29 11:59:31 +01:00
Sylvia van Os
1b4559fa3c Merge pull request #2854 from CatimaLoyalty/create-pull-request/patch-1764269098
Update Fastlane changelogs
2025-11-27 19:46:16 +01:00
TheLastProject
ae8487f8d9 Update Fastlane changelogs 2025-11-27 18:44:57 +00:00
Sylvia van Os
922b517d37 Update CHANGELOG 2025-11-27 19:44:46 +01:00
Aditya Varma
5a0d99fc80 Copy card ID to clipboard from menu or long press (#2789)
Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
2025-11-27 19:43:26 +01:00
Sylvia van Os
c89a759c8b Merge pull request #2852 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-27 08:27:49 +01:00
Liner Seven
4c81fdcefb Translated using Weblate (Japanese)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2025-11-27 05:05:54 +00:00
Marbino Timatim Jr.
7a35b7f598 Translated using Weblate (Filipino)
Currently translated at 19.5% (64 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fil/
2025-11-27 05:05:53 +00:00
Sylvia van Os
e1108c08ac Merge pull request #2851 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-25 16:36:52 +01:00
Hosted Weblate
6ab943f776 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/
2025-11-25 15:51:14 +01:00
Sylvia van Os
aac3570431 Translated using Weblate (Hebrew (Israel))
Currently translated at 24.0% (79 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/he_IL/
2025-11-25 15:51:13 +01:00
דוד משה המבורגר
3abf287c67 Translated using Weblate (Hebrew (Israel))
Currently translated at 24.3% (80 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/he_IL/
2025-11-25 13:51:27 +00:00
Edgars Andersons
cb26d23b02 Translated using Weblate (Latvian)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/lv/
2025-11-25 13:51:26 +00:00
Maria Vacari
9afcb0d7c6 Translated using Weblate (Romanian)
Currently translated at 85.0% (279 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ro/
2025-11-25 13:51:25 +00:00
Sylvia van Os
acc50c4fda Merge pull request #2849 from CatimaLoyalty/dependabot/github_actions/peter-evans/create-pull-request-7.0.9
Bump peter-evans/create-pull-request from 7.0.8 to 7.0.9
2025-11-24 07:15:20 +01:00
Sylvia van Os
91fab12da6 Merge pull request #2848 from CatimaLoyalty/dependabot/github_actions/actions/checkout-6
Bump actions/checkout from 5 to 6
2025-11-24 07:14:47 +01:00
dependabot[bot]
e094b969ee Bump peter-evans/create-pull-request from 7.0.8 to 7.0.9
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.8 to 7.0.9.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v7.0.8...v7.0.9)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-version: 7.0.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 02:05:16 +00:00
dependabot[bot]
3103d3a9cf Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 02:05:11 +00:00
Sylvia van Os
c82b255eaa Merge pull request #2845 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-18 10:13:36 +01:00
Nam Nguyen Thanh
9ee4f0da9b Translated using Weblate (Vietnamese)
Currently translated at 27.4% (42 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/vi/
2025-11-18 09:52:06 +01:00
Sylvia van Os
3b5e6ac450 Merge pull request #2844 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-18 09:26:57 +01:00
Nam Nguyen Thanh
7ab270f323 Translated using Weblate (Vietnamese)
Currently translated at 16.9% (26 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/vi/
2025-11-18 07:52:27 +00:00
Nam Nguyen Thanh
6f9b4739c8 Translated using Weblate (Vietnamese)
Currently translated at 16.3% (25 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/vi/
2025-11-18 07:52:26 +00:00
gradle-update-robot
e654a657a0 Update Gradle Wrapper from 9.2.0 to 9.2.1
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2025-11-18 00:59:39 +00:00
Sylvia van Os
ae6567a784 Merge pull request #2841 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-16 17:19:24 +01:00
Dao Duy Tin
be12707ab1 Translated using Weblate (Vietnamese)
Currently translated at 84.4% (277 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/vi/
2025-11-16 15:51:51 +00:00
Sylvia van Os
e8d40ec679 Merge pull request #2839 from CatimaLoyalty/create-pull-request/patch-1763267079
Update contributors
2025-11-16 10:14:34 +01:00
TheLastProject
e9c40c88a9 Update contributors 2025-11-16 04:24:38 +00:00
Sylvia van Os
82bb9b2817 Merge pull request #2838 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-15 00:12:13 +01:00
Francisco Serrador
976dd6f80b Translated using Weblate (Spanish)
Currently translated at 66.0% (101 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/es/
2025-11-14 23:52:36 +01:00
Sylvia van Os
2ac36fa415 Merge pull request #2836 from CatimaLoyalty/create-pull-request/patch-1762978593
Update feature graphic
2025-11-12 23:07:39 +01:00
TheLastProject
19399dd17f Update feature graphic 2025-11-12 20:16:33 +00:00
Sylvia van Os
acae8c1a0d Merge pull request #2835 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-12 21:14:35 +01:00
Mohammad Alhasan
64073e210e Translated using Weblate (Arabic)
Currently translated at 96.0% (315 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ar/
2025-11-12 19:51:23 +00:00
Francisco Serrador
3adbf61be7 Translated using Weblate (Spanish)
Currently translated at 63.3% (97 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/es/
2025-11-12 17:52:18 +00:00
Francisco Serrador
f0df4622eb Translated using Weblate (Spanish)
Currently translated at 45.7% (70 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/es/
2025-11-12 17:52:01 +01:00
Francisco Serrador
81c0f284f6 Translated using Weblate (Spanish)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2025-11-12 17:51:59 +01:00
Adrián Gelmotto Ruiz
fce60ca712 Translated using Weblate (Spanish)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2025-11-12 17:51:58 +01:00
Sylvia van Os
2be4d1cd2b Merge pull request #2834 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-12 09:11:26 +01:00
Alì Mortacci
72b19a8272 Translated using Weblate (Italian)
Currently translated at 83.6% (128 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/it/
2025-11-12 08:52:04 +01:00
Sylvia van Os
9f9d404632 Merge pull request #2833 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-12 08:25:25 +01:00
Sylvia van Os
157d1ecc49 Merge pull request #2832 from CatimaLoyalty/dependabot/gradle/com.google.zxing-core-3.5.4
Bump com.google.zxing:core from 3.5.3 to 3.5.4
2025-11-12 08:20:51 +01:00
Alì Mortacci
54b21167ec Translated using Weblate (Italian)
Currently translated at 85.9% (282 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2025-11-12 07:52:57 +01:00
Alì Mortacci
34a125008f Translated using Weblate (Italian)
Currently translated at 81.6% (125 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/it/
2025-11-12 07:52:56 +01:00
Alì Mortacci
1776f8fd90 Translated using Weblate (Italian)
Currently translated at 85.3% (280 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2025-11-12 07:52:55 +01:00
dependabot[bot]
55269f748f Bump com.google.zxing:core from 3.5.3 to 3.5.4
Bumps [com.google.zxing:core](https://github.com/zxing/zxing) from 3.5.3 to 3.5.4.
- [Release notes](https://github.com/zxing/zxing/releases)
- [Changelog](https://github.com/zxing/zxing/blob/master/CHANGES)
- [Commits](https://github.com/zxing/zxing/compare/zxing-3.5.3...zxing-3.5.4)

---
updated-dependencies:
- dependency-name: com.google.zxing:core
  dependency-version: 3.5.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-12 02:03:17 +00:00
Sylvia van Os
6e42d0dcd9 Merge pull request #2831 from CatimaLoyalty/dependabot/gradle/com.android.application-8.13.1
Bump com.android.application from 8.13.0 to 8.13.1
2025-11-11 08:12:29 +01:00
dependabot[bot]
48113eba18 Bump com.android.application from 8.13.0 to 8.13.1
Bumps com.android.application from 8.13.0 to 8.13.1.

---
updated-dependencies:
- dependency-name: com.android.application
  dependency-version: 8.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-11 02:03:16 +00:00
Sylvia van Os
41c8bb1815 Merge pull request #2829 from CatimaLoyalty/create-pull-request/patch-1762662107
Update contributors
2025-11-09 09:17:16 +01:00
Sylvia van Os
eab4f9e123 Merge pull request #2828 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-09 09:16:31 +01:00
TheLastProject
05f4cfc07b Update contributors 2025-11-09 04:21:46 +00:00
asdasd gfsdfdfg
b07b09e703 Translated using Weblate (Polish)
Currently translated at 94.7% (145 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2025-11-08 21:51:56 +01:00
Sylvia van Os
42e69916f0 Merge pull request #2827 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-08 17:13:41 +01:00
Aliaksandr Truš
254c9fee14 Translated using Weblate (Belarusian)
Currently translated at 86.8% (285 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/be/
2025-11-08 11:51:37 +01:00
Sylvia van Os
66e568aa06 Merge pull request #2825 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-07 07:56:05 +01:00
Joel A
1f38110e94 Translated using Weblate (Swedish)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sv/
2025-11-07 03:51:21 +01:00
Joel A
e92a98a956 Translated using Weblate (Swedish)
Currently translated at 99.3% (326 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sv/
2025-11-07 01:51:38 +00:00
Rishab Mamgai
875e90e940 Basic (incomplete) app_name consistency check script
Co-authored-by: ProgramminCat <72707293+ProgramminCat@users.noreply.github.com>
Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
2025-11-06 20:02:03 +01:00
Sylvia van Os
5ebd23a88a Merge pull request #2824 from CatimaLoyalty/dependabot/gradle/androidx.core-core-splashscreen-1.2.0
Bump androidx.core:core-splashscreen from 1.0.1 to 1.2.0
2025-11-06 06:53:52 +01:00
Sylvia van Os
6559857ba2 Merge pull request #2823 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-06 06:52:25 +01:00
dependabot[bot]
684b3e9836 Bump androidx.core:core-splashscreen from 1.0.1 to 1.2.0
Bumps androidx.core:core-splashscreen from 1.0.1 to 1.2.0.

---
updated-dependencies:
- dependency-name: androidx.core:core-splashscreen
  dependency-version: 1.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-06 02:03:18 +00:00
Joel A
d4b4599496 Translated using Weblate (Swedish)
Currently translated at 5.2% (8 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sv/
2025-11-05 23:51:21 +00:00
Joel A
decd0a104d Translated using Weblate (Swedish)
Currently translated at 97.5% (320 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sv/
2025-11-05 23:51:41 +01:00
Sylvia van Os
177f8e43e2 Merge pull request #2822 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-05 22:27:47 +01:00
ssantos
75234a6cd4 Translated using Weblate (Portuguese)
Currently translated at 100.0% (153 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt/
2025-11-05 18:52:34 +00:00
ssantos
86b5c2998e Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (153 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt_PT/
2025-11-05 18:52:33 +00:00
Sylvia van Os
0fee650cee Merge pull request #2820 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-05 17:18:13 +01:00
Francisco Serrador
61ee8b5910 Translated using Weblate (Spanish)
Currently translated at 99.6% (327 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2025-11-05 11:51:45 +01:00
Sylvia van Os
dae66a63f1 Remove Stocard import reference from README
Stocard importer was removed a while ago
2025-11-04 22:43:36 +01:00
Sylvia van Os
934d7ba041 Release Catima 2.39.2 2025-11-04 18:28:37 +01:00
Sylvia van Os
32b4dd73aa Merge pull request #2818 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-04 16:07:16 +01:00
ezn24
e558e7f6e4 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (153 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hant/
2025-11-04 14:52:04 +01:00
Sylvia van Os
20cc714b52 Merge pull request #2817 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-03 22:06:54 +01:00
Odoamar
abf26c92e4 Translated using Weblate (Polish)
Currently translated at 94.7% (145 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2025-11-03 19:51:45 +00:00
Sylvia van Os
89bdbe1544 Merge pull request #2816 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-03 16:34:33 +01:00
Fjuro
afe930a29f Translated using Weblate (Czech)
Currently translated at 100.0% (153 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2025-11-03 14:51:36 +00:00
Edgars Andersons
fc28ad0088 Translated using Weblate (Latvian)
Currently translated at 11.7% (18 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lv/
2025-11-03 08:51:41 +00:00
Kachelkaiser
58b1c67abf Translated using Weblate (German)
Currently translated at 100.0% (153 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2025-11-03 08:51:35 +00:00
solokot
ba563023c1 Translated using Weblate (Russian)
Currently translated at 100.0% (153 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2025-11-03 08:51:34 +01:00
Максим Горпиніч
efe9487a44 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (153 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/uk/
2025-11-03 06:51:38 +00:00
Sylvain Pichon
2140066c4d Translated using Weblate (French)
Currently translated at 100.0% (153 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2025-11-03 05:51:36 +01:00
Liner Seven
32a70f10c0 Translated using Weblate (Japanese)
Currently translated at 100.0% (153 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-11-03 02:51:49 +00:00
Arif Budiman
c5ef9f4b1d Translated using Weblate (Indonesian)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/id/
2025-11-03 00:51:39 +00:00
Krisna A. Prayoga
91a3548613 Translated using Weblate (Indonesian)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/id/
2025-11-03 00:51:34 +00:00
Irham Mustofa
1f370b86dd Translated using Weblate (Indonesian)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/id/
2025-11-03 00:51:33 +00:00
大王叫我来巡山
a93ddcb76e Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (153 of 153 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2025-11-02 23:51:42 +00:00
தமிழ்நேரம்
69aa985318 Translated using Weblate (Tamil)
Currently translated at 93.9% (308 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ta/
2025-11-02 20:51:59 +01:00
Sylvia van Os
2855cf4569 Merge pull request #2815 from CatimaLoyalty/create-pull-request/patch-1762096307
Update Fastlane changelogs
2025-11-02 17:00:28 +01:00
TheLastProject
01878a5ced Update Fastlane changelogs 2025-11-02 15:11:46 +00:00
Sylvia van Os
bfb68e4118 Update CHANGELOG 2025-11-02 16:11:35 +01:00
Sylvia van Os
709fffcfe5 Merge pull request #2814 from CatimaLoyalty/create-pull-request/patch-1762057266
Update contributors
2025-11-02 08:27:39 +01:00
TheLastProject
5c0474b38e Update contributors 2025-11-02 04:21:05 +00:00
Sylvia van Os
986ae4f0cb Merge pull request #2783 from u7683648/refactor/convert-letterbitmap-to-kotlin
refactor: Convert LetterBitmap to Kotlin
2025-10-31 17:25:00 +01:00
Sylvia van Os
04937e8839 Merge pull request #2809 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-31 08:34:31 +01:00
norm
071062a939 Translated using Weblate (Spanish)
Currently translated at 44.7% (68 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/es/
2025-10-31 04:03:15 +00:00
norm
058dc6c79b Translated using Weblate (Spanish)
Currently translated at 44.0% (67 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/es/
2025-10-31 02:03:52 +00:00
Xinqi Li
13a78dadb4 convert LetterBitmap to Kotlin 2025-10-31 12:11:58 +11:00
Sylvia van Os
bf12bc4f9d Merge pull request #2803 from CatimaLoyalty/fix/tamilFeatureGraphicTweak
Tweak Tamil feature graphic sizing more
2025-10-30 20:48:10 +01:00
Sylvia van Os
7986428149 Merge pull request #2808 from CatimaLoyalty/create-pull-request/patch-1761849375
Update feature graphic
2025-10-30 19:44:12 +01:00
TheLastProject
e17cbd5c9a Update feature graphic 2025-10-30 18:36:15 +00:00
Sylvia van Os
ebfb105fe4 Tweak Tamil feature graphic sizing more 2025-10-30 19:26:35 +01:00
Sylvia van Os
05a06eea27 Merge pull request #2800 from CatimaLoyalty/gradlew-update-9.2.0
Update Gradle Wrapper from 9.1.0 to 9.2.0
2025-10-30 17:13:22 +01:00
gradle-update-robot
663d7f3354 Update Gradle Wrapper from 9.1.0 to 9.2.0
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2025-10-30 01:00:21 +00:00
Sylvia van Os
dbe5b88b52 Merge pull request #2798 from CatimaLoyalty/create-pull-request/patch-1761761200
Update feature graphic
2025-10-29 19:29:46 +01:00
Sylvia van Os
c839fffadb Merge pull request #2797 from CatimaLoyalty/fix/tamil_feature_graphic
Lower Tamil font size
2025-10-29 19:28:23 +01:00
TheLastProject
8edfe53b45 Update feature graphic 2025-10-29 18:06:39 +00:00
Sylvia van Os
0153fc54f1 Lower Tamil font size
This should make the feature graphic fit properly
2025-10-29 19:02:05 +01:00
Sylvia van Os
f6b0af153f Merge pull request #2779 from adammazechen/refactor/scanactivity_java_to_kt
Refactor ScanActivity.java to kotlin
2025-10-29 18:42:42 +01:00
Sylvia van Os
cfefce1baf Minor cleanups 2025-10-29 18:27:15 +01:00
Zechen Ma
ff1683d5b4 refactor: convert ScanActivity.java to Kotlin.
bug: fix indentation and several bugs, convert more code blocks into apply blocks.
2025-10-29 08:35:13 +11:00
Sylvia van Os
e181a866f7 Merge pull request #2794 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-28 20:31:36 +01:00
Hosted Weblate
1571d5766c Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/
2025-10-28 18:06:30 +01:00
ssantos
1a4582adae Translated using Weblate (Portuguese)
Currently translated at 100.0% (152 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt/
2025-10-28 18:04:47 +01:00
ssantos
88335b970f Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_PT/
2025-10-28 18:03:56 +01:00
Anonymous
cac2dffb6c Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_PT/
2025-10-28 18:03:55 +01:00
ssantos
8a868e17bc Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (152 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt_PT/
2025-10-28 18:03:54 +01:00
ssantos
1d05e96690 Translated using Weblate (Portuguese)
Currently translated at 98.6% (150 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt/
2025-10-28 16:03:54 +00:00
ssantos
1d315d530f Translated using Weblate (Portuguese (Portugal))
Currently translated at 98.0% (149 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt_PT/
2025-10-28 16:03:32 +00:00
Sylvia van Os
597fefa9c9 Translated using Weblate (Tamil)
Currently translated at 3.9% (6 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ta/
2025-10-27 19:04:17 +01:00
Rajasree2004
764834bbae Translated using Weblate (Tamil)
Currently translated at 3.9% (6 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ta/
2025-10-27 19:04:16 +01:00
Rajasree2004
582cfb4cf0 Translated using Weblate (Tamil)
Currently translated at 3.9% (6 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ta/
2025-10-27 19:04:15 +01:00
Rajasree2004
998fb16a03 Translated using Weblate (Tamil)
Currently translated at 3.9% (6 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ta/
2025-10-27 19:04:14 +01:00
Rajasree2004
40dd95f9c2 Translated using Weblate (Tamil)
Currently translated at 87.1% (286 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ta/
2025-10-27 19:04:13 +01:00
Sylvia van Os
a27a6733e8 Merge pull request #2795 from CatimaLoyalty/dependabot/github_actions/actions/upload-artifact-5.0.0
Bump actions/upload-artifact from 4.6.2 to 5.0.0
2025-10-27 06:58:57 +01:00
dependabot[bot]
c76de152fc Bump actions/upload-artifact from 4.6.2 to 5.0.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 5.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.6.2...v5.0.0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 02:06:32 +00:00
Sylvia van Os
d0d75a4f50 Merge pull request #2793 from CatimaLoyalty/create-pull-request/patch-1761452387
Update contributors
2025-10-26 12:33:09 +01:00
TheLastProject
df42111f83 Update contributors 2025-10-26 04:19:46 +00:00
Sylvia van Os
da52a3685f Merge pull request #2792 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-25 10:46:40 +02:00
Igor W
bd85711e7f Translated using Weblate (Polish)
Currently translated at 92.0% (302 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2025-10-25 08:03:10 +00:00
Sylvia van Os
a02bf3e05c Merge pull request #2791 from CatimaLoyalty/dependabot/gradle/org.jetbrains.kotlin.android-2.2.21
Bump org.jetbrains.kotlin.android from 2.2.20 to 2.2.21
2025-10-24 08:17:00 +02:00
dependabot[bot]
a397199834 Bump org.jetbrains.kotlin.android from 2.2.20 to 2.2.21
Bumps [org.jetbrains.kotlin.android](https://github.com/JetBrains/kotlin) from 2.2.20 to 2.2.21.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v2.2.21/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.2.20...v2.2.21)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin.android
  dependency-version: 2.2.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-24 02:04:27 +00:00
Zechen Ma
27c16c2faf Rename .java to .kt 2025-10-24 06:20:23 +11:00
Sylvia van Os
131004494b Merge pull request #2790 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-22 23:21:55 +02:00
Feike Donia
777bde7b5e Translated using Weblate (Catalan)
Currently translated at 83.2% (273 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ca/
2025-10-22 21:02:56 +00:00
Feike Donia
3ba8f36108 Translated using Weblate (Afrikaans)
Currently translated at 14.3% (47 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/af/
2025-10-22 20:03:29 +00:00
Feike Donia
773a0fa6d4 Translated using Weblate (Catalan)
Currently translated at 83.2% (273 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ca/
2025-10-22 20:03:28 +00:00
Feike Donia
e88a537aec Translated using Weblate (Italian)
Currently translated at 85.0% (279 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2025-10-22 20:03:27 +00:00
Sylvia van Os
6ac60f9546 Merge pull request #2788 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-22 19:26:44 +02:00
Feike Donia
6fd6379ef3 Translated using Weblate (Afrikaans)
Currently translated at 14.0% (46 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/af/
2025-10-22 19:03:03 +02:00
Feike Donia
2a4949a505 Translated using Weblate (Dutch)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2025-10-22 19:03:02 +02:00
Sylvia van Os
386a24305d Merge pull request #2787 from CatimaLoyalty/create-pull-request/patch-1761152124
Update locales
2025-10-22 19:01:48 +02:00
TheLastProject
45c4b89a4d Update locales 2025-10-22 16:55:24 +00:00
Sylvia van Os
73ea525d8b Merge pull request #2786 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-22 18:55:06 +02:00
Sylvia van Os
2ab267f601 Merge pull request #2782 from aradxxx/ucropwrapper_to_kt
Convert UcropWrapper to kotlin
2025-10-22 18:47:20 +02:00
Feike Donia
ae54e91382 Added translation using Weblate (Frisian) 2025-10-22 18:39:39 +02:00
Feike Donia
45c082fba9 Added translation using Weblate (Afrikaans) 2025-10-22 16:39:08 +00:00
Feike Donia
aeedd9c3ac Translated using Weblate (Catalan)
Currently translated at 84.1% (276 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ca/
2025-10-22 16:39:07 +00:00
Sylvia van Os
e76e4f42f2 Merge pull request #2785 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-22 18:27:04 +02:00
Feike Donia
f68a1f1c86 Translated using Weblate (Catalan)
Currently translated at 78.9% (259 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ca/
2025-10-22 18:02:58 +02:00
Edgars Andersons
45a07d361c Translated using Weblate (Latvian)
Currently translated at 11.1% (17 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lv/
2025-10-22 16:03:19 +02:00
Xinqi Li
7f4937552d Rename .java to .kt 2025-10-22 11:22:12 +11:00
Sylvia van Os
b786fd60b4 Merge pull request #2777 from aradxxx/managegroupactivity_codestyle_fixes
ManageGroupActivity.kt small codestyle fixes
2025-10-21 22:44:14 +02:00
aradxxx
66646758a8 Convert UCropWrapper to kotlin 2025-10-21 22:08:15 +04:00
aradxxx
ece309fbde Rename .java to .kt 2025-10-21 21:40:34 +04:00
aradxxx
99c472330f ManageGroupActivity.kt small codestyle fixes 2025-10-20 23:50:51 +04:00
Sylvia van Os
246d5b5e4c Merge pull request #2781 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-20 21:31:45 +02:00
Damjan Gerl
8a8b243012 Translated using Weblate (Slovenian)
Currently translated at 38.1% (58 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sl/
2025-10-20 19:04:55 +02:00
ssantos
4612473e62 Translated using Weblate (Portuguese)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt/
2025-10-20 04:56:55 +00:00
ssantos
ab94e05e91 Translated using Weblate (Portuguese (Portugal))
Currently translated at 97.5% (320 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_PT/
2025-10-20 04:56:53 +00:00
Anonymous
d2ecad5c3f Translated using Weblate (Portuguese (Portugal))
Currently translated at 97.5% (320 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_PT/
2025-10-20 04:56:52 +00:00
Kerso
a4c9d5a345 Translated using Weblate (Polish)
Currently translated at 90.8% (298 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2025-10-20 04:56:51 +00:00
Sylvia van Os
5b30a11da3 Merge pull request #2763 from aradxxx/managegroupsactivity_to_kotlin
Convert ManageGroupsActivity to Kotlin
2025-10-19 14:01:53 +02:00
Gonzalo Aparicio
bda159a343 Migrate dependency management to Gradle Version Catalog (libs.versions.toml) (#2727)
Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
2025-10-19 13:57:09 +02:00
Sylvia van Os
f473d31f13 Merge pull request #2780 from CatimaLoyalty/create-pull-request/patch-1760847620
Update contributors
2025-10-19 12:12:40 +02:00
TheLastProject
1afe181085 Update contributors 2025-10-19 04:20:20 +00:00
Sylvia van Os
647b7185df Merge pull request #2776 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-16 18:37:42 +02:00
Liner Seven
f301726a02 Translated using Weblate (Japanese)
Currently translated at 100.0% (152 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-16 08:08:48 +00:00
Liner Seven
819be647b5 Translated using Weblate (Japanese)
Currently translated at 90.1% (137 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-16 09:13:26 +02:00
Liner Seven
6215972732 Translated using Weblate (Japanese)
Currently translated at 88.8% (135 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-16 07:11:46 +00:00
Liner Seven
90e406c30e Translated using Weblate (Japanese)
Currently translated at 87.5% (133 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-16 07:08:31 +02:00
Liner Seven
d7a5a47393 Translated using Weblate (Japanese)
Currently translated at 80.2% (122 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-16 04:09:06 +02:00
Liner Seven
8bacd4d1f5 Translated using Weblate (Japanese)
Currently translated at 80.2% (122 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-16 02:08:30 +00:00
Liner Seven
a4d9ef0cb1 Translated using Weblate (Japanese)
Currently translated at 76.3% (116 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-16 02:08:44 +02:00
aradxxx
8bed9c753b Convert ManageGroupsActivity.java to Kotlin 2025-10-15 22:58:45 +04:00
aradxxx
47e598ede1 Rename .java to .kt 2025-10-15 21:40:27 +04:00
Damjan Gerl
7edd41b08f Translated using Weblate (Slovenian)
Currently translated at 31.5% (48 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sl/
2025-10-15 17:07:43 +00:00
Sylvia van Os
a00dd69005 Merge pull request #2775 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-15 18:34:04 +02:00
Damjan Gerl
201c2b5964 Translated using Weblate (Slovenian)
Currently translated at 30.2% (46 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sl/
2025-10-15 16:11:18 +00:00
Hosted Weblate
5329a69e4d Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/
2025-10-15 16:11:17 +00:00
Damjan Gerl
7ab0ffa0a3 Translated using Weblate (Slovenian)
Currently translated at 28.9% (44 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sl/
2025-10-15 16:11:16 +00:00
Damjan Gerl
33471e91be Translated using Weblate (Slovenian)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sl/
2025-10-15 16:11:15 +00:00
Sylvia van Os
129bffe4b7 Merge pull request #2774 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-15 17:10:34 +02:00
mrestivill
3f3a9ac807 Translated using Weblate (Catalan)
Currently translated at 40.7% (62 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ca/
2025-10-15 14:38:11 +00:00
mrestivill
c702efbd1e Translated using Weblate (Catalan)
Currently translated at 78.0% (256 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ca/
2025-10-15 14:38:09 +00:00
Sylvia van Os
088098edad Merge pull request #2773 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-15 08:40:14 +02:00
Liner Seven
1e3e3c0e2e Translated using Weblate (Japanese)
Currently translated at 67.1% (102 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-15 01:07:45 +00:00
Liner Seven
d9781e207c Translated using Weblate (Japanese)
Currently translated at 50.6% (77 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-15 02:08:16 +02:00
Liner Seven
4a83c21d0d Translated using Weblate (Japanese)
Currently translated at 50.0% (76 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-15 02:08:15 +02:00
Sylvia van Os
5d592e253b Merge pull request #2772 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-14 19:50:25 +02:00
Liner Seven
820091b8fa Translated using Weblate (Japanese)
Currently translated at 47.3% (72 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-14 09:08:15 +02:00
Sylvia van Os
40c5eab3c5 Merge pull request #2771 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-14 08:20:39 +02:00
Liner Seven
c133fcf08a Translated using Weblate (Japanese)
Currently translated at 42.7% (65 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-14 08:07:58 +02:00
Sylvia van Os
8094b7cc47 Merge pull request #2760 from aradxxx/manage_group_activity_to_kt
Convert ManageGroupActivity to Kotlin
2025-10-13 23:18:36 +02:00
Sylvia van Os
abd8716b56 Minor cleanups 2025-10-13 23:01:36 +02:00
Sylvia van Os
cecad8351e Merge pull request #2770 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-13 18:54:31 +02:00
ssantos
4d1af69ed8 Translated using Weblate (Portuguese)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt/
2025-10-13 16:08:01 +00:00
ssantos
f468c06801 Translated using Weblate (Portuguese)
Currently translated at 98.0% (149 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt/
2025-10-13 16:08:00 +00:00
aradxxx
a0ef9b8d1b Convert ManageGroupActivity.java to Kotlin 2025-10-13 15:04:29 +04:00
Sylvia van Os
27f1f6f179 Merge pull request #2769 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-13 12:24:33 +02:00
jezoswiec
6ea1120517 Translated using Weblate (Polish)
Currently translated at 90.5% (297 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2025-10-13 08:07:41 +02:00
Sylvia van Os
2f7c44cbbe Merge pull request #2766 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-12 09:45:02 +02:00
Sylvia van Os
dd866a0f2b Merge pull request #2767 from CatimaLoyalty/create-pull-request/patch-1760242739
Update contributors
2025-10-12 09:41:42 +02:00
TheLastProject
889d1beab4 Update contributors 2025-10-12 04:18:59 +00:00
Oğuz Ersen
357052ee42 Translated using Weblate (Turkish)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/tr/
2025-10-11 17:08:54 +02:00
Sylvia van Os
19eda065ba Merge pull request #2765 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-11 09:22:58 +02:00
Liner Seven
5279c5c3b2 Translated using Weblate (Japanese)
Currently translated at 40.7% (62 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-11 09:07:37 +02:00
Liner Seven
17be4e739f Translated using Weblate (Japanese)
Currently translated at 36.8% (56 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-11 06:07:41 +00:00
Liner Seven
f2dd2e4d7e Translated using Weblate (Japanese)
Currently translated at 30.2% (46 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-11 03:07:35 +02:00
Liner Seven
7c6ce077c1 Translated using Weblate (Japanese)
Currently translated at 29.6% (45 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-11 02:07:45 +02:00
Sylvia van Os
45bf552eff Merge pull request #2755 from amlwin/main
Convert ImportExportActivity to Kotlin
2025-10-09 23:35:08 +02:00
Sylvia van Os
633d412b52 Merge pull request #2762 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-09 12:29:17 +02:00
Adrián Gelmotto Ruiz
54b8fb2d78 Translated using Weblate (Spanish)
Currently translated at 99.0% (325 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2025-10-09 08:08:01 +00:00
Sylvia van Os
443e9f110b Merge pull request #2761 from CatimaLoyalty/docs/LLM
Explain why LLM contributions are discouraged
2025-10-09 01:11:21 +02:00
Sylvia van Os
ac80bed084 Explain why LLM contributions are discouraged 2025-10-08 21:24:19 +02:00
aradxxx
802717c7a4 Rename .java to .kt 2025-10-08 21:16:30 +04:00
Sylvia van Os
68b931f3b5 Merge pull request #2753 from CatimaLoyalty/dependabot/github_actions/gradle/actions-5
Bump gradle/actions from 4 to 5
2025-10-07 15:15:07 +02:00
Sylvia van Os
d4a4067754 Merge pull request #2758 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-07 10:35:50 +02:00
Hosted Weblate
ca18cfd6d1 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/
2025-10-07 06:05:02 +00:00
ezn24
18d80d2a4a Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (152 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hant/
2025-10-07 06:05:01 +00:00
ezn24
ba4b9e4234 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hant/
2025-10-07 06:05:00 +00:00
Anonymous
b87d531069 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hant/
2025-10-07 06:05:00 +00:00
Sylvia van Os
5cbb2505e3 Merge pull request #2757 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-07 06:46:15 +02:00
Liner Seven
e500a13c7e Translated using Weblate (Japanese)
Currently translated at 26.9% (41 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-07 00:02:32 +00:00
Sylvia van Os
a4e9333c6e Merge pull request #2756 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-06 21:23:21 +02:00
ssantos
9dbe39e1a4 Translated using Weblate (Portuguese (Portugal))
Currently translated at 96.3% (316 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_PT/
2025-10-06 21:02:40 +02:00
Sylvia van Os
13c78eaee5 Merge pull request #2754 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-06 20:19:08 +02:00
Liner Seven
ef0e36b8be Translated using Weblate (Japanese)
Currently translated at 21.7% (33 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-06 07:02:06 +02:00
Liner Seven
a1351563c1 Translated using Weblate (Japanese)
Currently translated at 21.0% (32 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-06 06:02:07 +02:00
amlwin
303b40e572 Convert ImportExportActivity to Kotlin
Refactored `ImportExportActivity` and its corresponding test class from Java to Kotlin. The new implementation uses modern Kotlin idioms and syntax while preserving the original functionality.
2025-10-06 11:34:45 +08:00
amlwin
622ea37554 Rename .java to .kt 2025-10-06 11:34:45 +08:00
Liner Seven
8a80d16f11 Translated using Weblate (Japanese)
Currently translated at 16.4% (25 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-10-06 05:02:27 +02:00
dependabot[bot]
4ea515c342 Bump gradle/actions from 4 to 5
Bumps [gradle/actions](https://github.com/gradle/actions) from 4 to 5.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v4...v5)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-06 02:06:05 +00:00
Aung Myo Lwin
ce3dbaf902 Convert SettingsActivity to Kotlin (#2744)
* Rename .java to .kt

* Convert SettingsActivity to Kotlin

Refactored the `SettingsActivity` and its inner `SettingsFragment` from Java to Kotlin, adopting modern Kotlin idioms and syntax. The functionality remains unchanged.

* Address PR comment: by removing null safety with non-null asserted call operator

* Apply Android Studio suggested fixup

---------

Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
2025-10-05 21:13:17 +02:00
Sylvia van Os
a429b858e2 Merge pull request #2752 from CatimaLoyalty/create-pull-request/patch-1759637964
Update contributors
2025-10-05 09:04:54 +02:00
TheLastProject
d8d228aa67 Update contributors 2025-10-05 04:19:24 +00:00
Sylvia van Os
b31785a705 Merge pull request #2749 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-04 15:25:28 +02:00
B o d o
48b5e9f775 Translated using Weblate (Portuguese)
Currently translated at 98.1% (322 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt/
2025-10-04 15:02:32 +02:00
B o d o
150ef5982a Translated using Weblate (Esperanto)
Currently translated at 78.6% (258 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/eo/
2025-10-04 15:02:31 +02:00
Sylvia van Os
f91b94d100 Dep/bump ucrop (#2748)
* Bump com.github.yalantis:ucrop from 2.2.10 to 2.2.11

Bumps [com.github.yalantis:ucrop](https://github.com/Yalantis/uCrop) from 2.2.10 to 2.2.11.
- [Release notes](https://github.com/Yalantis/uCrop/releases)
- [Commits](https://github.com/Yalantis/uCrop/compare/2.2.10...2.2.11)

---
updated-dependencies:
- dependency-name: com.github.yalantis:ucrop
  dependency-version: 2.2.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix build

While testing on Android 15, no difference was found in the status bar
colour on Android 15 with or without the setting

* Use non-native release

* Apply autogenerated ProGuard missing rules

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-04 14:11:42 +02:00
Sylvia van Os
6f25cc416f Merge pull request #2746 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-03 21:53:12 +02:00
Svend Bøgelund
8358e982f9 Translated using Weblate (Danish)
Currently translated at 46.9% (154 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/da/
2025-10-03 16:02:26 +02:00
Claus Kruse
637fdeebe6 Translated using Weblate (Danish)
Currently translated at 46.9% (154 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/da/
2025-10-03 16:02:25 +02:00
Sylvia van Os
96cf5274b1 Merge pull request #2743 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-02 16:26:15 +02:00
JorgeS15
1df5772857 Translated using Weblate (Portuguese (Portugal))
Currently translated at 85.3% (280 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_PT/
2025-10-02 10:02:14 +00:00
Fjuro
44690dae55 Translated using Weblate (Czech)
Currently translated at 100.0% (152 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2025-10-02 10:02:28 +02:00
Liner Seven
ff46db7ac2 Translated using Weblate (Japanese)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2025-10-02 10:02:06 +02:00
Liner Seven
8f03595683 Translated using Weblate (Japanese)
Currently translated at 97.8% (321 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2025-10-02 09:02:14 +02:00
Liner Seven
ac7494d08d Translated using Weblate (Japanese)
Currently translated at 97.5% (320 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2025-10-02 07:02:06 +00:00
Nyatsuki
e6ae0dab30 Translated using Weblate (Japanese)
Currently translated at 97.5% (320 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2025-10-02 07:02:02 +00:00
Liner Seven
bc7da41da4 Translated using Weblate (Japanese)
Currently translated at 79.2% (260 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2025-10-02 06:02:29 +00:00
Nyatsuki
2fc5216cf1 Translated using Weblate (Japanese)
Currently translated at 79.2% (260 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2025-10-02 06:02:28 +00:00
Liner Seven
8a792481b6 Translated using Weblate (Japanese)
Currently translated at 78.9% (259 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2025-10-02 06:02:27 +00:00
Nyatsuki
53e4e6b675 Translated using Weblate (Japanese)
Currently translated at 78.9% (259 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2025-10-02 06:02:27 +00:00
Sylvia van Os
f06d338c5a Merge pull request #2741 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-01 22:41:29 +02:00
Milo Ivir
fef65bd5d2 Translated using Weblate (Croatian)
Currently translated at 1.9% (3 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/hr/
2025-10-01 20:26:17 +00:00
Milo Ivir
b830040639 Translated using Weblate (Croatian)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/hr/
2025-10-01 20:26:17 +00:00
Sylvia van Os
2662178bef Merge pull request #2739 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-01 20:39:51 +02:00
Milo Ivir
2a15ba9fe4 Translated using Weblate (Croatian)
Currently translated at 85.0% (279 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/hr/
2025-10-01 20:02:34 +02:00
Sylvia van Os
f777491dcd Release Catima 2.39.1 2025-10-01 17:54:19 +02:00
Sylvia van Os
81445a21ff Merge pull request #2737 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-01 17:53:39 +02:00
Edgars Andersons
ff410542fb Translated using Weblate (Latvian)
Currently translated at 11.1% (17 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lv/
2025-10-01 11:02:18 +00:00
B o d o
343e10f433 Translated using Weblate (German)
Currently translated at 100.0% (152 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2025-10-01 12:02:18 +02:00
Максим Горпиніч
8023372a03 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (152 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/uk/
2025-10-01 08:03:02 +02:00
solokot
cd999f2346 Translated using Weblate (Russian)
Currently translated at 100.0% (152 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2025-10-01 08:02:39 +02:00
Sylvain Pichon
4272d48fbf Translated using Weblate (French)
Currently translated at 100.0% (152 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2025-10-01 08:02:18 +02:00
大王叫我来巡山
ae40737b75 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (152 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2025-10-01 05:02:20 +00:00
大王叫我来巡山
4fe55be866 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (152 of 152 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2025-10-01 05:02:38 +02:00
Sylvia van Os
c5c4cf615f Merge pull request #2736 from CatimaLoyalty/create-pull-request/patch-1759268827
Update Fastlane changelogs
2025-09-30 23:47:35 +02:00
TheLastProject
44e542ed5a Update Fastlane changelogs 2025-09-30 21:47:07 +00:00
Sylvia van Os
bc2be8d33c Update CHANGELOG 2025-09-30 23:46:54 +02:00
Sylvia van Os
73ed0edab7 Merge pull request #2734 from CatimaLoyalty/fix/crash_missing_header_color
Fix crash on missing header colors
2025-09-30 23:44:07 +02:00
Sylvia van Os
a34a091cdb Fix crash on missing header colors
An off-by-one error caused invalid colour info to sometimes be generated
if no header colour was set. Under normal conditions, a header colour
should always be set, but due to some bugs in the past in some cases
they may not be set.

Sadly this does change the card colours for cards which are not properly
initialized. But that's better than a crash at least.
2025-09-30 23:27:11 +02:00
Sylvia van Os
4f3d162d7a Merge pull request #2732 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-30 21:31:33 +02:00
Eren Ekşi
99605d7d18 Translated using Weblate (Turkish)
Currently translated at 99.6% (327 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/tr/
2025-09-30 21:02:25 +02:00
Sylvia van Os
fa152510a6 Merge pull request #2730 from CatimaLoyalty/fix/privacy_policy_acra
Document ACRA usage in privacy policy
2025-09-30 19:24:56 +02:00
Sylvia van Os
ddc868894e Document ACRA usage in privacy policy 2025-09-30 19:13:18 +02:00
Sylvia van Os
0a65fb607a Release Catima 2.39.0 2025-09-30 18:39:26 +02:00
Sylvia van Os
921c76459c Merge pull request #2728 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-30 18:38:16 +02:00
大王叫我来巡山
abf1ad61d6 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2025-09-30 16:03:13 +02:00
Sylvia van Os
fbcc2ef4fe Merge pull request #2724 from CatimaLoyalty/dependabot/gradle/acraVersion-5.13.1
Bump acraVersion from 5.12.0 to 5.13.1
2025-09-29 20:10:39 +02:00
Sylvia van Os
699e7ce489 Merge pull request #2725 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-29 18:31:15 +02:00
Gideon
a45588abee Translated using Weblate (Dutch)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2025-09-29 17:01:59 +02:00
ikanakova
44d5095101 Translated using Weblate (Czech)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2025-09-29 14:02:42 +00:00
solokot
b0b6de9a7d Translated using Weblate (Russian)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2025-09-29 11:02:22 +00:00
Edgars Andersons
6b13e83146 Translated using Weblate (Latvian)
Currently translated at 10.5% (16 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lv/
2025-09-29 11:02:12 +02:00
rainy_sunset
cbac67728e Translated using Weblate (Japanese)
Currently translated at 5.2% (8 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2025-09-29 11:02:04 +02:00
Sylvain Pichon
400f4d20c1 Translated using Weblate (French)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2025-09-29 08:02:54 +02:00
dependabot[bot]
3288b4602a Bump acraVersion from 5.12.0 to 5.13.1
Bumps `acraVersion` from 5.12.0 to 5.13.1.

Updates `ch.acra:acra-mail` from 5.12.0 to 5.13.1
- [Release notes](https://github.com/ACRA/acra/releases)
- [Commits](https://github.com/ACRA/acra/compare/acra-5.12.0...acra-5.13.1)

Updates `ch.acra:acra-dialog` from 5.12.0 to 5.13.1
- [Release notes](https://github.com/ACRA/acra/releases)
- [Commits](https://github.com/ACRA/acra/compare/acra-5.12.0...acra-5.13.1)

---
updated-dependencies:
- dependency-name: ch.acra:acra-mail
  dependency-version: 5.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: ch.acra:acra-dialog
  dependency-version: 5.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-29 02:04:16 +00:00
Sylvia van Os
74dec728ad Merge pull request #2723 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-28 23:20:35 +02:00
Максим Горпиніч
aa72663440 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/uk/
2025-09-28 21:03:01 +00:00
B o d o
f2fa6ed96d Translated using Weblate (German)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2025-09-28 21:03:00 +00:00
Sylvia van Os
9b8e78a264 Merge pull request #2722 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-28 22:23:12 +02:00
Fjuro
0e442beed5 Translated using Weblate (Czech)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2025-09-28 20:02:46 +00:00
Fjuro
ff1d38d159 Translated using Weblate (Czech)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/cs/
2025-09-28 20:02:45 +00:00
Sylvia van Os
5f8c8048e6 Merge pull request #2721 from CatimaLoyalty/create-pull-request/patch-1759033153
Update contributors
2025-09-28 08:57:52 +02:00
TheLastProject
dc6d951241 Update contributors 2025-09-28 04:19:13 +00:00
Sylvia van Os
9037ae0d53 Merge pull request #2719 from CatimaLoyalty/create-pull-request/patch-1758969988
Update Fastlane changelogs
2025-09-27 12:46:47 +02:00
TheLastProject
83e7aa61fa Update Fastlane changelogs 2025-09-27 10:46:27 +00:00
Sylvia van Os
11030b1e6a Update CHANGELOG 2025-09-27 12:46:16 +02:00
Sylvia van Os
2f37e2a9c7 Merge pull request #2713 from vijay2909/fix/remove-image-crash
fix: Crash after removing image while viewing image
2025-09-27 12:45:37 +02:00
Sylvia van Os
fcf891647c Merge pull request #2718 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-27 12:03:09 +02:00
Edgars Andersons
8465131d41 Translated using Weblate (Latvian)
Currently translated at 10.5% (16 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lv/
2025-09-27 09:02:26 +00:00
Edgars Andersons
a33656d43b Translated using Weblate (Latvian)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/lv/
2025-09-27 09:02:25 +00:00
Sylvia van Os
52397ab340 Merge pull request #2717 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-27 10:29:19 +02:00
109247019824
930a730252 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bg/
2025-09-27 09:02:24 +02:00
Sylvia van Os
37a707ba1d Merge pull request #2715 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-27 00:19:24 +02:00
109247019824
d9e4f58687 Translated using Weblate (Bulgarian)
Currently translated at 98.7% (324 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bg/
2025-09-27 00:02:20 +02:00
Sylvia van Os
98bbca85b2 Merge pull request #2714 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-26 21:29:05 +02:00
solokot
0f9aac76e2 Translated using Weblate (Russian)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2025-09-26 19:02:44 +00:00
solokot
7c933f888c Translated using Weblate (Russian)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2025-09-26 19:02:43 +00:00
hritikRitss
9ae02ddb15 fix: reset mainImageIndex if it exceeds available image count 2025-09-26 23:47:37 +05:30
Sylvia van Os
8333dd0d0c Update used libraries (#2712) 2025-09-26 19:15:19 +02:00
Sylvia van Os
c21159c571 Update used libraries 2025-09-26 19:03:31 +02:00
PRATHAMESH BHAGAT
81db39d4e1 Migrate Enums and Interfaces to kotlin (#2710)
Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
2025-09-26 18:57:10 +02:00
Sylvia van Os
3e77ab6845 Merge pull request #2711 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-26 18:29:09 +02:00
Sylvain Pichon
562ae9cd56 Translated using Weblate (French)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2025-09-26 16:02:48 +00:00
Sylvain Pichon
cb321ffdb8 Translated using Weblate (French)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2025-09-26 16:02:48 +00:00
Sylvia van Os
a8d654b8d5 Merge pull request #2709 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-26 18:02:23 +02:00
Priit Jõerüüt
b2806cd000 Translated using Weblate (Estonian)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/et/
2025-09-26 16:02:09 +02:00
Vasilis K
d4166f681d Translated using Weblate (Greek)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2025-09-26 13:02:00 +00:00
B o d o
19829be16a Translated using Weblate (German)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2025-09-26 09:02:03 +02:00
B o d o
666ee288c3 Translated using Weblate (German)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-09-26 09:01:59 +02:00
josé m
2a8b5f983f Translated using Weblate (Galician)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/gl/
2025-09-26 07:02:01 +02:00
Максим Горпиніч
adf8ae9878 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/uk/
2025-09-26 06:02:21 +02:00
Максим Горпиніч
7a6bee4a13 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/uk/
2025-09-26 06:02:00 +02:00
大王叫我来巡山
4a05031e42 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2025-09-26 04:02:20 +02:00
大王叫我来巡山
8c86cc3c1a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hans/
2025-09-26 04:02:02 +02:00
delvani
5205011610 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (328 of 328 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_BR/
2025-09-26 00:02:10 +00:00
Bai
f689cb6a8e Translated using Weblate (Turkish)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/tr/
2025-09-25 23:02:43 +00:00
Oğuz Ersen
412215603e Translated using Weblate (Turkish)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/tr/
2025-09-25 23:02:42 +00:00
Sylvia van Os
97c34f8ae6 Merge pull request #2708 from CatimaLoyalty/create-pull-request/patch-1758830206
Update Fastlane changelogs
2025-09-25 22:09:41 +02:00
TheLastProject
6563bc1b70 Update Fastlane changelogs 2025-09-25 19:56:46 +00:00
Sylvia van Os
205bda34ae Update CHANGELOG 2025-09-25 21:55:45 +02:00
Sylvia van Os
663f68fff9 Merge pull request #2107 from CatimaLoyalty/experiment/acra
Set up ACRA with email crash alerts
2025-09-25 21:46:41 +02:00
Sylvia van Os
dc9f4fafde Set up ACRA with email crash alerts for foss build 2025-09-25 19:14:39 +02:00
Sylvia van Os
6651aff962 Merge pull request #2611 from CatimaLoyalty/dependabot/gradle/androidx.core-core-ktx-1.17.0
Bump androidx.core:core-ktx from 1.16.0 to 1.17.0
2025-09-25 19:04:40 +02:00
dependabot[bot]
55a6dfafed Bump androidx.core:core-ktx from 1.16.0 to 1.17.0
Bumps androidx.core:core-ktx from 1.16.0 to 1.17.0.

---
updated-dependencies:
- dependency-name: androidx.core:core-ktx
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-25 16:03:28 +00:00
Sylvia van Os
a6bf282db5 Merge pull request #2706 from CatimaLoyalty/create-pull-request/patch-1758733079
Update Fastlane changelogs
2025-09-24 18:58:23 +02:00
TheLastProject
63bd0edb10 Update Fastlane changelogs 2025-09-24 16:57:58 +00:00
Sylvia van Os
9f2adf4691 Update CHANGELOG 2025-09-24 18:57:47 +02:00
Sylvia van Os
d36b95d275 Merge pull request #2697 from CatimaLoyalty/target/android16
Target Android 16
2025-09-24 18:56:31 +02:00
Sylvia van Os
c7fdd63d7d Add some FIXMEs 2025-09-24 18:31:22 +02:00
Sylvia van Os
412077e2ab Remove rotation hack and add warning to not rotate to import/export
This is suboptimal, but rotation support is being removed by Google for
over 600dp screen sizes
2025-09-24 18:31:22 +02:00
Sylvia van Os
0f1005c193 Remove rotation support
With the release of targetSdk 36, the Android platform will no longer
always apply rotation. To lower the chance of user confusion, this
support is being removed completely.

I know some of you really love this, but I can't do anything about this.
This is Google's decision and my opinion doesn't matter.
2025-09-24 18:31:19 +02:00
Sylvia van Os
7353414ae6 Bump targetSdk to 36 2025-09-24 18:30:44 +02:00
Sylvia van Os
894a3dd44f Merge pull request #2705 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-24 17:28:09 +02:00
Sylvia van Os
4b86f3e822 Translated using Weblate (Hindi)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/hi/
2025-09-24 13:02:32 +00:00
B o d o
b6077e1dd6 Translated using Weblate (Swedish)
Currently translated at 97.9% (327 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sv/
2025-09-24 13:02:08 +00:00
B o d o
267efb8905 Translated using Weblate (Swedish)
Currently translated at 93.1% (311 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sv/
2025-09-24 12:03:02 +00:00
Joel A
9412a70517 Translated using Weblate (Swedish)
Currently translated at 93.1% (311 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sv/
2025-09-24 12:03:01 +00:00
Govind Gopal Yadav
de754acf92 Translated using Weblate (Hindi)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/hi/
2025-09-24 12:03:01 +00:00
Govind Gopal Yadav
fce7bf9cd0 Translated using Weblate (Hindi)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/hi/
2025-09-24 12:03:00 +00:00
Govind Gopal Yadav
6f335d44a0 Translated using Weblate (Hindi)
Currently translated at 98.6% (148 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/hi/
2025-09-24 12:02:59 +00:00
Hosted Weblate
2fa6af7de9 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/
2025-09-24 12:02:58 +00:00
Govind Gopal Yadav
4b9dfc9e0c Translated using Weblate (Hindi)
Currently translated at 97.3% (146 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/hi/
2025-09-24 12:02:58 +00:00
Govind Gopal Yadav
3ead1ab079 Translated using Weblate (Hindi)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/hi/
2025-09-24 12:02:57 +00:00
Py- Droid
ff5ffed8aa Translated using Weblate (Arabic)
Currently translated at 97.3% (325 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ar/
2025-09-24 12:02:55 +00:00
PRATHAMESH BHAGAT
d047c38bc2 Migrating ThirdPartyInfo class to kotlin (#2699)
* Migrating ThirdPartyINfo class to kotlin

* Removing null allowance in strings
2025-09-23 18:30:00 +02:00
Sylvia van Os
52b62b1075 Merge pull request #2701 from CatimaLoyalty/gradlew-update-9.1.0
Update Gradle Wrapper from 9.0.0 to 9.1.0
2025-09-22 22:36:33 +02:00
Sylvia van Os
ad384af7e4 Merge pull request #2704 from CatimaLoyalty/create-pull-request/patch-1758428392
Update contributors
2025-09-21 10:59:55 +02:00
TheLastProject
06b3ec09be Update contributors 2025-09-21 04:19:52 +00:00
Sylvia van Os
c803c5be8b Merge pull request #2703 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-20 23:14:02 +02:00
Fjuro
f120a00a75 Translated using Weblate (Czech)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/cs/
2025-09-20 21:02:35 +00:00
Sylvia van Os
99a244ae2e Merge pull request #2702 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-20 17:18:40 +02:00
Sylvain Pichon
713af1aeaa Translated using Weblate (French)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2025-09-19 04:02:24 +00:00
gradle-update-robot
1cc7ac3f4d Update Gradle Wrapper from 9.0.0 to 9.1.0
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2025-09-19 00:56:32 +00:00
Sylvia van Os
dfee86186a Merge pull request #2700 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-18 20:10:50 +02:00
Priit Jõerüüt
d82574136a Translated using Weblate (Estonian)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/et/
2025-09-18 19:02:28 +02:00
delvani
766f953e65 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_BR/
2025-09-18 19:02:27 +02:00
Sylvia van Os
593a4098a5 Merge pull request #2698 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-18 17:24:22 +02:00
hellooo
ecfc29feb9 Translated using Weblate (Romanian)
Currently translated at 86.2% (288 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ro/
2025-09-18 14:02:01 +02:00
Edgars Andersons
84ce92cebc Translated using Weblate (Latvian)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/lv/
2025-09-18 10:02:04 +00:00
109247019824
489a2eb037 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bg/
2025-09-18 11:02:06 +02:00
B o d o
e48e0233bb Translated using Weblate (Spanish)
Currently translated at 99.1% (331 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2025-09-18 11:02:02 +02:00
B o d o
b67471df92 Translated using Weblate (Spanish (Argentina))
Currently translated at 70.9% (237 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es_AR/
2025-09-18 11:02:00 +02:00
大王叫我来巡山
be727462ee Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hans/
2025-09-18 10:02:00 +02:00
josé m
ed72b41527 Translated using Weblate (Galician)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/gl/
2025-09-18 06:02:10 +00:00
solokot
309980836a Translated using Weblate (Russian)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2025-09-18 06:02:01 +00:00
Yasin Tanış
fb3ab11700 Translated using Weblate (Turkish)
Currently translated at 87.7% (293 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/tr/
2025-09-18 01:02:23 +02:00
Sylvia van Os
ccbcc8b736 Merge pull request #2696 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-17 23:32:46 +02:00
Bert Aarts
7fcfcf7bdb Translated using Weblate (Dutch)
Currently translated at 66.0% (99 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2025-09-17 21:02:44 +00:00
Bert Aarts
f7231a3ac5 Translated using Weblate (Dutch)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2025-09-17 21:02:43 +00:00
Kachelkaiser
c4ae77123d Translated using Weblate (German)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-09-17 21:02:43 +00:00
Sylvia van Os
81c6874aff Merge pull request #2694 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-17 21:21:19 +02:00
Максим Горпиніч
de05560297 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/uk/
2025-09-17 21:02:29 +02:00
HostujePlik.PL
816a3b2f2c Translated using Weblate (Polish)
Currently translated at 91.0% (304 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2025-09-17 21:02:29 +02:00
Vasilis K
c8af3fe35e Translated using Weblate (Greek)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2025-09-17 21:02:29 +02:00
Sylvia van Os
f3448d06c1 Merge pull request #2693 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-17 20:19:54 +02:00
HostujePlik.PL
3442cf3a35 Translated using Weblate (Polish)
Currently translated at 91.0% (304 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2025-09-17 18:02:30 +00:00
PRATHAMESH BHAGAT
03115c0de1 Removing unnecessary lines in workflows files (#2692) 2025-09-17 18:55:37 +02:00
Sylvia van Os
9f5f7f0dd8 Merge pull request #2676 from CatimaLoyalty/gradlew-update-9.0.0
Update Gradle Wrapper from 8.14 to 9.0.0
2025-09-16 19:39:01 +02:00
PRATHAMESH BHAGAT
4901a6b183 Merge pull request #2687 from PrathameshBhagat/main
Making Github Actions file more readable
2025-09-16 18:24:32 +02:00
Sylvia van Os
f2372c40aa Merge pull request #2683 from CatimaLoyalty/fix/missingPositionalFormatting
Fix missing positional formatting
2025-09-16 18:22:51 +02:00
Sylvia van Os
3f1c0695b8 Merge pull request #2691 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-16 17:12:52 +02:00
Gideon
fd9991ceb1 Translated using Weblate (Dutch)
Currently translated at 63.3% (95 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2025-09-16 14:02:23 +02:00
Gideon
2628a9856b Translated using Weblate (Dutch)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2025-09-16 14:02:00 +02:00
Edgars Andersons
c1f3fae50b Translated using Weblate (Latvian)
Currently translated at 10.0% (15 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lv/
2025-09-16 07:02:30 +00:00
Edgars Andersons
2d5cd25696 Translated using Weblate (Latvian)
Currently translated at 94.0% (314 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/lv/
2025-09-16 07:02:29 +00:00
Sylvia van Os
dc8b7d6ae4 Merge pull request #2690 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-16 08:18:43 +02:00
josé m
a2211947cd Translated using Weblate (Galician)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/gl/
2025-09-16 08:02:28 +02:00
Sylvia van Os
db2617e2d4 Merge pull request #2689 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-15 22:58:24 +02:00
Giuseppe Piscitelli
412f48d801 Translated using Weblate (Italian)
Currently translated at 86.8% (290 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2025-09-15 20:02:01 +00:00
Hosted Weblate
bd7bf7ddbe Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/
2025-09-15 18:04:26 +00:00
Gideon
e7a99ad7b6 Translated using Weblate (Dutch)
Currently translated at 62.6% (94 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2025-09-15 18:04:26 +00:00
Gideon
8fce04cf45 Translated using Weblate (Dutch)
Currently translated at 99.4% (332 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2025-09-15 18:04:25 +00:00
Heimen Stoffels
62f2545f9e Translated using Weblate (Dutch)
Currently translated at 99.4% (332 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2025-09-15 18:04:24 +00:00
Sylvia van Os
d22e0bd5e5 Merge pull request #2686 from CatimaLoyalty/dependabot/github_actions/actions/checkout-5
Bump actions/checkout from 4 to 5
2025-09-15 19:23:00 +02:00
Sylvia van Os
61d206f318 Merge pull request #2685 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-15 18:55:23 +02:00
Álvaro Palacios
7bfef398c3 Translated using Weblate (Spanish)
Currently translated at 99.1% (331 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2025-09-15 14:02:01 +02:00
Vasilis K
2311acc15e Translated using Weblate (Greek)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2025-09-15 10:02:03 +02:00
Deleted User
14787cc520 Translated using Weblate (Greek)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2025-09-15 10:02:00 +02:00
Sylwester Cyba
7b57b603aa Translated using Weblate (Polish)
Currently translated at 89.2% (298 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2025-09-15 09:02:03 +02:00
Warder
185f6d1a5f Translated using Weblate (Slovak)
Currently translated at 89.8% (300 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sk/
2025-09-15 07:02:01 +02:00
dependabot[bot]
791cf7224e Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-15 02:05:54 +00:00
Vasilis K
8cab9a4204 Translated using Weblate (Greek)
Currently translated at 94.6% (316 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2025-09-14 21:02:22 +00:00
Deleted User
42a8efd5e5 Translated using Weblate (Greek)
Currently translated at 94.6% (316 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2025-09-14 21:02:21 +00:00
Sylvia van Os
85e171ae4a Merge pull request #2684 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-14 19:02:21 +02:00
Sylvia van Os
18d5438c38 Translated using Weblate (Portuguese)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt/
2025-09-14 14:02:26 +02:00
Sylvia van Os
0f311d1901 Fix missing positional formatting 2025-09-14 13:46:50 +02:00
Sylvia van Os
5f33679ddd Merge pull request #2681 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-14 13:27:43 +02:00
Sylvia van Os
ce0fab5b38 Merge pull request #2682 from CatimaLoyalty/create-pull-request/patch-1757823583
Update contributors
2025-09-14 11:22:11 +02:00
B o d o
d6ba49add5 Translated using Weblate (Spanish)
Currently translated at 98.5% (329 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2025-09-14 09:02:05 +00:00
J. Lavoie
2dcb561882 Translated using Weblate (Spanish)
Currently translated at 98.5% (329 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2025-09-14 09:02:03 +00:00
B o d o
25153d98e7 Translated using Weblate (Spanish (Argentina))
Currently translated at 70.3% (235 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es_AR/
2025-09-14 09:02:02 +00:00
Brian Bentancour
ee90044a59 Translated using Weblate (Spanish (Argentina))
Currently translated at 70.3% (235 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es_AR/
2025-09-14 09:02:01 +00:00
Viviana Y
4a0a23a8f7 Translated using Weblate (Spanish (Argentina))
Currently translated at 70.3% (235 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es_AR/
2025-09-14 09:01:59 +00:00
TheLastProject
09d5b68baf Update contributors 2025-09-14 04:19:42 +00:00
Sylvain Pichon
402a5ef044 Translated using Weblate (French)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2025-09-13 23:02:01 +02:00
Naga
31fd642295 Translated using Weblate (French)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2025-09-13 23:02:00 +02:00
Thomas Bertels
68dc947c4c Translated using Weblate (French)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2025-09-13 23:01:59 +02:00
Максим Горпиніч
4daa00111b Translated using Weblate (Ukrainian)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/uk/
2025-09-13 21:02:29 +02:00
Максим Горпиніч
b6e00fa096 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/uk/
2025-09-13 21:02:03 +02:00
IllusiveMan196
3cf5910e62 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/uk/
2025-09-13 21:02:00 +02:00
delvani
681d0744d4 Translated using Weblate (Portuguese)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt/
2025-09-13 19:02:23 +02:00
Sylvia van Os
c19d6dd2f2 Merge pull request #2680 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-13 18:56:16 +02:00
delvani
35b81437f8 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_BR/
2025-09-13 18:44:43 +02:00
Bruno Fernandes
f1403e6fce Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_BR/
2025-09-13 18:44:43 +02:00
Sylvain Pichon
7f12530b8e Translated using Weblate (French)
Currently translated at 94.3% (315 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2025-09-13 18:44:42 +02:00
Thomas Bertels
850209c00b Translated using Weblate (French)
Currently translated at 94.3% (315 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2025-09-13 18:44:42 +02:00
Sylvia van Os
72e7b8127a Merge pull request #2679 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-13 17:17:37 +02:00
Nataniel Dika Kurniawan
db2808fa94 Translated using Weblate (Indonesian)
Currently translated at 89.2% (298 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/id/
2025-09-13 16:03:11 +02:00
Fjuro
d9c3509bbc Translated using Weblate (Czech)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2025-09-13 16:03:10 +02:00
Sylvain Pichon
231b17d955 Translated using Weblate (French)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2025-09-13 16:03:10 +02:00
Sylvain Pichon
310a1266a5 Translated using Weblate (French)
Currently translated at 94.0% (314 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2025-09-13 16:03:09 +02:00
J. Lavoie
407e7293af Translated using Weblate (French)
Currently translated at 94.0% (314 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2025-09-13 16:03:08 +02:00
Fjuro
577ab84020 Translated using Weblate (Czech)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/cs/
2025-09-13 16:03:08 +02:00
Slávek Banko
650cf559ba Translated using Weblate (Czech)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/cs/
2025-09-13 16:03:07 +02:00
Jan Novotny
b3b53cd25a Translated using Weblate (Czech)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/cs/
2025-09-13 16:03:07 +02:00
Sylvia van Os
160456d21c Merge pull request #2678 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-13 13:23:23 +02:00
B o d o
37b5ed2c24 Translated using Weblate (German)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-09-13 13:02:02 +02:00
B o d o
5c410d4817 Translated using Weblate (German)
Currently translated at 93.4% (312 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-09-13 10:02:41 +00:00
B o d o
940ba85e3d Translated using Weblate (German)
Currently translated at 93.1% (311 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-09-13 10:02:40 +00:00
B o d o
92508bdd2b Translated using Weblate (German)
Currently translated at 92.8% (310 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-09-13 10:02:39 +00:00
J. Lavoie
235cbce123 Translated using Weblate (German)
Currently translated at 92.8% (310 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-09-13 10:02:38 +00:00
Sylvia van Os
060cfa75bf Merge pull request #2677 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-13 11:14:08 +02:00
B o d o
329770576e Translated using Weblate (German)
Currently translated at 88.0% (294 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-09-13 11:02:22 +02:00
B o d o
4881212adb Translated using Weblate (German)
Currently translated at 87.7% (293 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-09-13 11:02:21 +02:00
Sylvia van Os
5b7ed3f4e5 Merge pull request #2675 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-13 10:50:50 +02:00
solokot
bdd6bc3923 Translated using Weblate (Russian)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2025-09-13 08:02:29 +00:00
solokot
1d5d5419dc Translated using Weblate (Russian)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2025-09-13 08:02:01 +00:00
josé m
196051dc82 Translated using Weblate (Galician)
Currently translated at 96.1% (321 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/gl/
2025-09-13 06:02:01 +02:00
gradle-update-robot
4962701224 Update Gradle Wrapper from 8.14 to 9.0.0
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2025-09-13 00:51:38 +00:00
大王叫我来巡山
a74c801977 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2025-09-13 02:02:36 +02:00
大王叫我来巡山
409f35719d Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hans/
2025-09-13 02:02:11 +02:00
Deleted User
f761ae6c3e Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hans/
2025-09-13 02:02:09 +02:00
109247019824
d56f1eb2a3 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bg/
2025-09-12 23:02:23 +02:00
Sylvia van Os
23907a558d Merge pull request #2674 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-12 22:31:21 +02:00
B o d o
6b77aff18f Translated using Weblate (German)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2025-09-12 22:03:09 +02:00
Sylvia van Os
b66383e9ab Release Catima 2.38.0 2025-09-12 20:58:31 +02:00
PRATHAMESH BHAGAT
863316d7b4 Merge pull request #2662 from PrathameshBhagat/main
Replace Gradle update checker
2025-09-12 20:48:47 +02:00
Sylvia van Os
33d5632b6d Merge pull request #2672 from CatimaLoyalty/create-pull-request/patch-1757607265
Update Fastlane changelogs
2025-09-11 19:57:20 +02:00
TheLastProject
67d8ae2d90 Update Fastlane changelogs 2025-09-11 16:14:24 +00:00
Sylvia van Os
c933b76a8c Update CHANGELOG 2025-09-11 18:14:12 +02:00
Sylvia van Os
9d12123f71 Merge pull request #2670 from CatimaLoyalty/fix/widgetIconCrash
Workaround widget crash by disabling images on Android 12L and below
2025-09-11 18:13:18 +02:00
Sylvia van Os
beff5e0aa4 Merge pull request #2671 from CatimaLoyalty/dependabot/gradle/org.jetbrains.kotlin.android-2.2.20
Bump org.jetbrains.kotlin.android from 2.2.10 to 2.2.20
2025-09-11 08:16:55 +02:00
dependabot[bot]
d61da3e499 Bump org.jetbrains.kotlin.android from 2.2.10 to 2.2.20
Bumps [org.jetbrains.kotlin.android](https://github.com/JetBrains/kotlin) from 2.2.10 to 2.2.20.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.2.10...v2.2.20)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin.android
  dependency-version: 2.2.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-11 02:04:30 +00:00
Sylvia van Os
8419122193 Fix wrong SDK_INT 2025-09-10 23:24:36 +02:00
Sylvia van Os
20559d1506 Workaround widget crash by disabling images on Android 12L and below 2025-09-10 21:49:32 +02:00
Sylvia van Os
5e3de19e7b Merge pull request #2669 from CatimaLoyalty/fix/permissionIcon
Use monochrome icon for permission dialog
2025-09-10 20:38:14 +02:00
Sylvia van Os
2463599ba2 Use monochrome icon for permission dialog 2025-09-10 20:24:54 +02:00
jwkwshjsjsj
ea90d26a0a Minor string updates (#2638)
Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
2025-09-10 20:12:43 +02:00
Sylvia van Os
8339de2596 Merge pull request #2668 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-10 17:21:03 +02:00
Hasan Berat Kaylan
e283d3abd8 Translated using Weblate (Turkish)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/tr/
2025-09-10 14:02:02 +00:00
Fjuro
b8980e3708 Translated using Weblate (Czech)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2025-09-10 13:02:45 +00:00
Edgars Andersons
2d306a2046 Translated using Weblate (Latvian)
Currently translated at 10.0% (15 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lv/
2025-09-10 12:02:10 +02:00
solokot
fe79e03fb3 Translated using Weblate (Russian)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2025-09-10 09:02:26 +00:00
B o d o
e938c29601 Translated using Weblate (German)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2025-09-10 10:05:25 +02:00
B o d o
45d76468dc Translated using Weblate (German)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-09-10 10:05:24 +02:00
Sylvia van Os
d8aab4f956 Merge pull request #2667 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-10 08:21:43 +02:00
Sylvain Pichon
7084420781 Translated using Weblate (French)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2025-09-10 08:04:06 +02:00
大王叫我来巡山
b4b90cdf48 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2025-09-10 07:02:53 +02:00
Максим Горпиніч
e899d902f7 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/uk/
2025-09-10 07:02:35 +02:00
Milo Ivir
e04fead496 Translated using Weblate (Croatian)
Currently translated at 99.7% (333 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/hr/
2025-09-09 23:02:00 +00:00
Kachelkaiser
4d5c4bfc6e Translated using Weblate (German)
Currently translated at 99.3% (149 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2025-09-09 22:02:50 +00:00
Sylvia van Os
6779693213 Merge pull request #2666 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-09 23:32:13 +02:00
Kachelkaiser
f2db558eaf Translated using Weblate (German)
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-09-09 21:02:06 +00:00
Sylvia van Os
cecf0bf1bc Merge pull request #2665 from CatimaLoyalty/create-pull-request/patch-1757437148
Update Fastlane changelogs
2025-09-09 19:38:32 +02:00
TheLastProject
9110d3cc17 Update Fastlane changelogs 2025-09-09 16:59:07 +00:00
Sylvia van Os
032380e872 Update CHANGELOG 2025-09-09 18:58:55 +02:00
Sylvia van Os
53ea1741c0 Merge pull request #2657 from CatimaLoyalty/fix/removeStocardImport
Remove Stocard importer
2025-09-09 18:58:12 +02:00
Sylvia van Os
51e75f61ec Merge pull request #2664 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-09 17:02:29 +02:00
Dika
a590e6dca1 Translated using Weblate (Indonesian)
Currently translated at 99.4% (334 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/id/
2025-09-09 12:02:09 +02:00
Sylvia van Os
e25128947c Merge pull request #2663 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-09 08:19:44 +02:00
josé m
da6c81595c Translated using Weblate (Galician)
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/gl/
2025-09-09 04:02:07 +00:00
Sylvia van Os
1fc5a1e04a Remove Stocard importer
Stocard no longer exists as a company and these import files are no
longer generated
2025-09-08 19:55:10 +02:00
Sylvia van Os
faa5946c15 Merge pull request #2661 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-08 19:39:59 +02:00
Edgars Andersons
d35911724c Translated using Weblate (Latvian)
Currently translated at 10.0% (15 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lv/
2025-09-08 11:02:12 +02:00
Edgars Andersons
2acd9d87f4 Translated using Weblate (Latvian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/lv/
2025-09-08 08:02:09 +00:00
Sylvia van Os
43c2e3e78a Merge pull request #2660 from CatimaLoyalty/dependabot/github_actions/actions/setup-python-6.0.0
Bump actions/setup-python from 5.6.0 to 6.0.0
2025-09-08 06:57:47 +02:00
dependabot[bot]
1ea3dc77f0 Bump actions/setup-python from 5.6.0 to 6.0.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.6.0 to 6.0.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5.6.0...v6.0.0)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 02:06:00 +00:00
Sylvia van Os
eeb27dc169 Merge pull request #2659 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-07 22:14:15 +02:00
Fjuro
7cc09e4a0d Translated using Weblate (Czech)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2025-09-07 20:02:28 +00:00
Sylvia van Os
8ca0bcd97c Merge pull request #2658 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-07 21:20:38 +02:00
Fjuro
8bfdef6f9c Translated using Weblate (Czech)
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/cs/
2025-09-07 19:02:15 +00:00
Sylvia van Os
e7155a55bd Merge pull request #2656 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-07 15:00:24 +02:00
ssantos
0405a96710 Translated using Weblate (Portuguese)
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt/
2025-09-07 12:04:47 +00:00
Priit Jõerüüt
a4a988393c Translated using Weblate (Estonian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/et/
2025-09-07 12:04:47 +00:00
delvani
155d732ec7 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_BR/
2025-09-07 12:04:46 +00:00
ssantos
c07eefd48f Translated using Weblate (Portuguese)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt/
2025-09-07 12:04:45 +00:00
ssantos
edf4a67590 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_PT/
2025-09-07 12:04:45 +00:00
ssantos
442072641a Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt_PT/
2025-09-07 12:04:44 +00:00
109247019824
7719ece810 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bg/
2025-09-07 12:04:44 +00:00
大王叫我来巡山
6de0473582 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2025-09-07 12:04:43 +00:00
大王叫我来巡山
4da1d3d1c3 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hans/
2025-09-07 12:04:42 +00:00
Warder
4a70c1f6c9 Translated using Weblate (Slovak)
Currently translated at 98.6% (148 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sk/
2025-09-07 12:04:42 +00:00
Максим Горпиніч
873d7e3cd1 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/uk/
2025-09-07 12:04:41 +00:00
solokot
fce8f6cdb9 Translated using Weblate (Russian)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2025-09-07 12:04:41 +00:00
B o d o
207781fa58 Translated using Weblate (German)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2025-09-07 12:04:40 +00:00
Sylvain Pichon
3aae958a1b Translated using Weblate (French)
Currently translated at 100.0% (150 of 150 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2025-09-07 12:04:39 +00:00
Максим Горпиніч
65b699564e Translated using Weblate (Ukrainian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/uk/
2025-09-07 12:04:39 +00:00
Warder
acf7314f6c Translated using Weblate (Slovak)
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sk/
2025-09-07 12:04:38 +00:00
solokot
8b394cc644 Translated using Weblate (Russian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2025-09-07 12:04:37 +00:00
mateale1234
a1385be797 Translated using Weblate (Polish)
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2025-09-07 12:04:37 +00:00
Sylvain Pichon
cf9249b97e Translated using Weblate (French)
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2025-09-07 12:04:36 +00:00
Vasilis K
91f953915d Translated using Weblate (Greek)
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2025-09-07 12:04:35 +00:00
B o d o
f6263e6cf5 Translated using Weblate (German)
Currently translated at 100.0% (336 of 336 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-09-07 12:04:35 +00:00
Sylvia van Os
e48ff4d6a3 Merge pull request #2644 from CatimaLoyalty/dependabot/gradle/org.robolectric-robolectric-4.16
Bump org.robolectric:robolectric from 4.15.1 to 4.16
2025-09-06 13:51:22 +02:00
dependabot[bot]
a93ee35c9b Bump org.robolectric:robolectric from 4.15.1 to 4.16
Bumps [org.robolectric:robolectric](https://github.com/robolectric/robolectric) from 4.15.1 to 4.16.
- [Release notes](https://github.com/robolectric/robolectric/releases)
- [Commits](https://github.com/robolectric/robolectric/compare/robolectric-4.15.1...robolectric-4.16)

---
updated-dependencies:
- dependency-name: org.robolectric:robolectric
  dependency-version: '4.16'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-06 11:33:33 +00:00
Sylvia van Os
6b561d00f3 Merge pull request #2655 from CatimaLoyalty/create-pull-request/patch-1757156668
Update Fastlane changelogs
2025-09-06 13:04:55 +02:00
TheLastProject
ffd9b9c097 Update Fastlane changelogs 2025-09-06 11:04:28 +00:00
Sylvia van Os
4b4d7f537d Update CHANGELOG 2025-09-06 13:04:16 +02:00
Sylvia van Os
58bad96b2e Merge pull request #2654 from CatimaLoyalty/feature/pkpasses
Add support for .pkpasses
2025-09-06 13:03:20 +02:00
Sylvia van Os
67701840bb Add support for .pkpasses 2025-09-06 12:49:50 +02:00
Sylvia van Os
d936209b0e Merge pull request #2652 from CatimaLoyalty/dependabot/gradle/com.google.android.material-material-1.13.0
Bump com.google.android.material:material from 1.12.0 to 1.13.0
2025-09-04 20:01:46 +02:00
dependabot[bot]
14f7116aad Bump com.google.android.material:material from 1.12.0 to 1.13.0
Bumps [com.google.android.material:material](https://github.com/material-components/material-components-android) from 1.12.0 to 1.13.0.
- [Release notes](https://github.com/material-components/material-components-android/releases)
- [Commits](https://github.com/material-components/material-components-android/compare/1.12.0...1.13.0)

---
updated-dependencies:
- dependency-name: com.google.android.material:material
  dependency-version: 1.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-04 10:17:41 +00:00
Sylvia van Os
132844f6ce Merge pull request #2651 from CatimaLoyalty/dependabot/gradle/com.android.application-8.13.0
Bump com.android.application from 8.12.2 to 8.13.0
2025-09-03 23:56:18 +02:00
dependabot[bot]
a50789a7e9 Bump com.android.application from 8.12.2 to 8.13.0
Bumps com.android.application from 8.12.2 to 8.13.0.

---
updated-dependencies:
- dependency-name: com.android.application
  dependency-version: 8.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-03 21:21:21 +00:00
Sylvia van Os
513e3d97f6 Merge pull request #2650 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-09-01 19:27:01 +02:00
Niko
58f1944268 Translated using Weblate (Serbian)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sr/
2025-08-31 21:02:26 +02:00
Damjan Gerl
aa75c22328 Translated using Weblate (Slovenian)
Currently translated at 26.8% (40 of 149 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sl/
2025-08-31 21:02:26 +02:00
Damjan Gerl
3b72ada8d0 Translated using Weblate (Slovenian)
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sl/
2025-08-31 21:02:25 +02:00
Sylvia van Os
a65131bdf6 Merge pull request #2648 from CatimaLoyalty/create-pull-request/patch-1756613947
Update contributors
2025-08-31 11:08:03 +02:00
TheLastProject
b9622d3da9 Update contributors 2025-08-31 04:19:06 +00:00
Sylvia van Os
234bb86d7e Merge pull request #2646 from CatimaLoyalty/dependabot/gradle/com.android.application-8.12.2
Bump com.android.application from 8.12.1 to 8.12.2
2025-08-29 08:25:23 +02:00
dependabot[bot]
b6243a1f2f Bump com.android.application from 8.12.1 to 8.12.2
Bumps com.android.application from 8.12.1 to 8.12.2.

---
updated-dependencies:
- dependency-name: com.android.application
  dependency-version: 8.12.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-29 02:03:33 +00:00
Sylvia van Os
5f01eef75a Merge pull request #2643 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-08-26 18:10:17 +02:00
ssantos
9c43752134 Translated using Weblate (Portuguese)
Currently translated at 100.0% (149 of 149 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt/
2025-08-26 02:03:51 +00:00
ssantos
e7d965576f Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (149 of 149 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt_PT/
2025-08-26 02:03:50 +00:00
大王叫我来巡山
2111357c7d Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (149 of 149 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2025-08-26 02:03:50 +00:00
Sylvia van Os
37c7f88f82 Merge pull request #2641 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-08-25 07:21:18 +02:00
Robin Syl
15b466c9a6 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1 of 1 strings)

Translation: Catima/Android (Debug)
Translate-URL: https://hosted.weblate.org/projects/catima/android-debug/zh_Hant/
2025-08-24 23:03:06 +02:00
Robin Syl
42a0e5abd8 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 85.9% (128 of 149 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hant/
2025-08-24 23:03:05 +02:00
Robin Syl
feb217f1ba Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (334 of 334 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hant/
2025-08-24 23:03:04 +02:00
Sylvia van Os
c5e56ca27d Merge pull request #2633 from CatimaLoyalty/create-pull-request/patch-1756009535
Update contributors
2025-08-24 09:18:28 +02:00
TheLastProject
6ccfdaef83 Update contributors 2025-08-24 04:25:35 +00:00
Sylvia van Os
081c0b8507 Merge pull request #2632 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-08-23 19:21:20 +02:00
Fjuro
351ee7caed Translated using Weblate (Czech)
Currently translated at 100.0% (149 of 149 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2025-08-23 17:04:17 +00:00
Максим Горпиніч
7fb3c73877 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (149 of 149 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/uk/
2025-08-23 17:04:16 +00:00
solokot
b1102fbcc0 Translated using Weblate (Russian)
Currently translated at 100.0% (149 of 149 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2025-08-23 17:04:15 +00:00
B o d o
b9a5032b15 Translated using Weblate (German)
Currently translated at 100.0% (149 of 149 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2025-08-23 17:04:15 +00:00
Sylvain Pichon
d6e52e17ee Translated using Weblate (French)
Currently translated at 100.0% (149 of 149 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2025-08-23 17:04:14 +00:00
Sylvia van Os
fc25082a9a Release Catima 2.37.0 2025-08-22 18:18:42 +02:00
Sylvia van Os
7625d783f3 Merge pull request #2630 from CatimaLoyalty/create-pull-request/patch-1755877734
Update feature graphic
2025-08-22 18:15:19 +02:00
TheLastProject
f61c9adf61 Update feature graphic 2025-08-22 15:48:53 +00:00
Sylvia van Os
cba8d637f5 Merge pull request #2628 from il-Luca/main
Fix for minor issues with feature graphic generation
2025-08-22 17:46:35 +02:00
Sylvia van Os
79853e597b Add Aeven-Dev as logo contributor 2025-08-22 17:38:09 +02:00
647 changed files with 7264 additions and 13843 deletions

View File

@@ -9,10 +9,15 @@ updates:
- mavenCentral
schedule:
interval: "daily"
cooldown:
default-days: 7
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7
# Workaround for https://github.com/dependabot/dependabot-core/issues/6888
registries:

View File

@@ -32,10 +32,8 @@ jobs:
matrix:
flavor: [Foss, Gplay]
steps:
- uses: actions/checkout@v5.0.0
- name: Fail on bad translations
run: if grep -ri "&lt;xliff" app/src/main/res/values*/strings.xml; then echo "Invalidly escaped translations found"; exit 1; fi
- uses: gradle/actions/wrapper-validation@v4
- uses: actions/checkout@v6
- uses: gradle/actions/wrapper-validation@v5
- name: set up OpenJDK 21
run: |
sudo apt-get update
@@ -66,7 +64,7 @@ jobs:
script: ./gradlew connected${{ matrix.flavor }}DebugAndroidTest
- name: Archive test results
if: always()
uses: actions/upload-artifact@v4.6.2
uses: actions/upload-artifact@v5.0.0
with:
name: test-results-flavor${{ matrix.flavor }}
path: app/build/reports

View File

@@ -1,4 +1,5 @@
name: Convert CHANGELOG to Fastlane
on:
workflow_dispatch:
push:
@@ -6,20 +7,11 @@ on:
- main
paths:
- 'CHANGELOG.md'
permissions:
actions: none
checks: none
contents: write
deployments: none
discussions: none
id-token: none
issues: none
packages: none
pages: none
pull-requests: write
repository-projects: none
security-events: none
statuses: none
jobs:
convert_changelog_to_fastlane:
runs-on: ubuntu-latest
@@ -27,15 +19,15 @@ jobs:
steps:
- name: Checkout repo
id: checkout
uses: actions/checkout@v5.0.0
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.1.0
with:
python-version: '3.x'
- name: Run converter script
run: python .scripts/changelog_to_fastlane.py
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7.0.8
uses: peter-evans/create-pull-request@v7.0.11
with:
title: "Update Fastlane changelogs"
commit-message: "Update Fastlane changelogs"

View File

@@ -1,22 +1,14 @@
name: Write contributors to file
on:
workflow_dispatch:
schedule:
- cron: '3 4 * * 0'
permissions:
actions: none
checks: none
contents: write
deployments: none
discussions: none
id-token: none
issues: none
packages: none
pages: none
pull-requests: write
repository-projects: none
security-events: none
statuses: none
jobs:
contributors_to_file:
runs-on: ubuntu-latest
@@ -25,7 +17,7 @@ jobs:
steps:
- name: Checkout repo
id: checkout
uses: actions/checkout@v5.0.0
uses: actions/checkout@v6
- name: Update contributors
id: update_contributors
uses: TheLastProject/contributors-to-file-action@v3.2.0
@@ -33,7 +25,7 @@ jobs:
file_in_repo: app/src/main/res/raw/contributors.txt
min_commit_count: 5
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7.0.8
uses: peter-evans/create-pull-request@v7.0.11
with:
title: "Update contributors"
commit-message: "Update contributors"

View File

@@ -1,4 +1,5 @@
name: Generate feature graphic
on:
workflow_dispatch:
push:
@@ -7,25 +8,16 @@ on:
paths:
- 'fastlane/**/title.txt'
- '.scripts/generate_feature_graphic/**'
permissions:
actions: none
checks: none
contents: write
deployments: none
discussions: none
id-token: none
issues: none
packages: none
pages: none
pull-requests: write
repository-projects: none
security-events: none
statuses: none
jobs:
generate-feature-graphic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5.0.0
- uses: actions/checkout@v6
- name: Install requirements
run: |
sudo apt-get update
@@ -39,7 +31,7 @@ jobs:
- name: Generate featureGraphic.png for each language
run: .scripts/generate_feature_graphic/generate_feature_graphic.sh
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7.0.8
uses: peter-evans/create-pull-request@v7.0.11
with:
title: "Update feature graphic"
commit-message: "Update feature graphic"

34
.github/workflows/i18n-check.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: i18n check
on:
workflow_dispatch:
push:
branches:
- main
- staging
- trying
pull_request:
branches:
- main
permissions:
actions: none
checks: none
contents: read
deployments: none
discussions: none
id-token: none
issues: none
packages: none
pages: none
pull-requests: none
repository-projects: none
security-events: none
statuses: none
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Fail on bad translations
run: if grep -ri "&lt;xliff" app/src/main/res/values*/strings.xml; then echo "Invalidly escaped translations found"; exit 1; fi
- name: Check app_name consistency
run: bash .scripts/check_app_name.sh

View File

@@ -0,0 +1,19 @@
name: Update Gradle Wrapper
on:
schedule:
- cron: "0 0 * * *"
permissions:
contents: write
pull-requests: write
jobs:
update-gradle-wrapper:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Update Gradle Wrapper
uses: gradle-update/update-gradle-wrapper-action@v2

View File

@@ -1,4 +1,5 @@
name: Update locales
on:
workflow_dispatch:
push:
@@ -7,31 +8,22 @@ on:
paths:
- app/src/main/res/values-*/strings.xml
- app/src/main/res/values/settings.xml
permissions:
actions: none
checks: none
contents: write
deployments: none
discussions: none
id-token: none
issues: none
packages: none
pages: none
pull-requests: write
repository-projects: none
security-events: none
statuses: none
jobs:
update-locales:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5.0.0
- uses: actions/checkout@v6
- name: Add new locales
run: .scripts/new-locales.py
- name: Update locales
run: .scripts/locales.py
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7.0.8
uses: peter-evans/create-pull-request@v7.0.11
with:
title: "Update locales"
commit-message: "Update locales"

View File

@@ -0,0 +1,71 @@
#!/bin/bash
set -e
shopt -s lastpipe # Run last command in a pipeline in the current shell.
# Colors
LIGHTCYAN='\033[1;36m'
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Vars
SUCCESS=1
CANONICAL_TITLE="Catima"
ALLOWLIST=("ar" "bn" "fa" "fa-IR" "he-IL" "hi" "hi-IN" "kn" "kn-IN" "ml" "mr" "ta" "ta-IN" "zh-rTW" "zh-TW") # TODO: Link values and fastlane with different codes together
function get_lang() {
LANG_DIRNAME=$(dirname "$FILE" | xargs basename)
LANG=${LANG_DIRNAME#values-} # Fetch lang name
LANG=${LANG#values} # Handle "app/src/main/res/values"
LANG=${LANG:-en} # Default to en
}
# FIXME: This function should use its own variables and return a success/fail status, instead of working on global variables
function check() {
# FIXME: This allows inconsistency between values and fastlane if the app name is not Catima
# When the app name is not Catima, it should still check if title.txt and strings.xml use the same app name (or start)
if echo "${ALLOWLIST[*]}" | grep -w -q "${LANG}" || [[ -z ${APP_NAME} ]]; then
return 0
fi
if [[ ${FILE} == *"title.txt" ]]; then
if [[ ! ${APP_NAME} =~ ^${CANONICAL_TITLE} ]]; then
echo -e "${RED}Error: ${LIGHTCYAN}title in $FILE ($LANG) is ${RED}'$APP_NAME'${LIGHTCYAN}, expected to start with ${GREEN}'$CANONICAL_TITLE'. ${NC}"
SUCCESS=0
fi
else
if [[ ${APP_NAME} != "${CANONICAL_TITLE}" ]]; then
echo -e "${RED}Error: ${LIGHTCYAN}app_name in $FILE ($LANG) is ${RED}'$APP_NAME'${LIGHTCYAN}, expected ${GREEN}'$CANONICAL_TITLE'. ${NC}"
SUCCESS=0
fi
fi
}
# FIXME: This checks all title.txt and strings.xml files separately, but it needs to check if the title.txt and strings.xml match for a language as well
echo -e "${LIGHTCYAN}Checking title.txt's. ${NC}"
find fastlane/metadata/android/* -maxdepth 1 -type f -name "title.txt" | while read -r FILE; do
APP_NAME=$(head -n 1 "$FILE")
get_lang
check
done
echo -e "${LIGHTCYAN}Checking string.xml's. ${NC}"
find app/src/main/res/values* -maxdepth 1 -type f -name "strings.xml" | while read -r FILE; do
# FIXME: This only checks app_name, but there are more strings with Catima inside it
# It should check the original English text for all strings that contain Catima and ensure they use the correct app_name for consistency
APP_NAME=$(grep -oP '<string name="app_name">\K[^<]+' "$FILE" | head -n1)
get_lang
check
done
if [[ $SUCCESS -eq 1 ]]; then
echo -e "\n${GREEN}Success!! All app_name values match the canonical title. ${NC}"
else
echo -e "\n${RED}Unsuccessful!! Some app_name values did not match the canonical titles. ${NC}"
exit 1
fi

View File

@@ -1,44 +0,0 @@
#!/usr/bin/python3
import csv
import json
import msgpack
MSGPACK = "bootstrapdata.msgpack"
OUTFILE = "stocard_stores.csv"
def load(fh):
data = []
for r in msgpack.Unpacker(fh, raw=False):
if r["collection"] == "/loyalty-card-providers/":
d = json.loads(r["data"])
data.append([r["resource_id"], d["name"], d["default_barcode_format"]])
return data
def save(data, output_file=OUTFILE):
with open(output_file, "w") as fh:
writer = csv.writer(fh, lineterminator="\n")
writer.writerow(["_id", "name", "barcodeFormat"])
for row in data:
writer.writerow(row)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
epilog=f"INPUT_FILE must be a .msgpack or .apk and defaults to {MSGPACK}; "
f"OUTPUT_FILE defaults to {OUTFILE}")
parser.add_argument("input_file", metavar="INPUT_FILE", nargs="?", default=MSGPACK)
parser.add_argument("output_file", metavar="OUTPUT_FILE", nargs="?", default=OUTFILE)
args = parser.parse_args()
if args.input_file.lower().endswith(".apk"):
import zipfile
with zipfile.ZipFile(args.input_file) as zf:
with zf.open(f"assets/{MSGPACK}") as fh:
data = load(fh)
else:
with open(args.input_file, "rb") as fh:
data = load(fh)
save(data, args.output_file)

View File

@@ -42,6 +42,7 @@ for lang in "$script_location/../../fastlane/metadata/android/"*; do
ja-JP) sed -i "s/Lexend/Noto Sans CJK JP/" featureGraphic.svg ;;
kn-IN) sed -i -e 's/font-size="150"/font-size="125"/' -e 's/\(<tspan x="469" \)y="270"/\1y="240"/' -e "s/Lobster/Noto Sans Kannada/" -e "s/Lexend/Noto Sans Kannada/" featureGraphic.svg ;;
ko) sed -i "s/Lexend/Noto Sans CJK KR/" featureGraphic.svg ;;
ta-IN) sed -i -e 's/font-size="150"/font-size="125"/' -e 's/\(<tspan x="469" \)y="270"/\1y="240"/' featureGraphic.svg ;;
zh-CN) sed -i "s/Lexend/Noto Sans CJK SC/" featureGraphic.svg ;;
zh-TW) sed -i -e "s/Lobster/Noto Sans CJK TC/" -e "s/Lexend/Noto Sans CJK TC/" featureGraphic.svg ;;
*) ;;

View File

@@ -1,6 +1,36 @@
# Changelog
## Unreleased - 151
## Unreleased - 157
- Add duplicate option to main screen and reorder options slightly
## v2.40.0 - 156 (2025-12-08)
- Copy card ID to clipboard from view dialog or long press
- Swap balance and currency fields to hopefully reduce unintended rounding
## v2.39.2 - 155 (2025-11-04)
- Preparations for future improvements (rewrote many classes to Kotlin)
## v2.39.1 - 154 (2025-10-01)
- Fix possible crash that could occur for cards missing colour information in the database
## v2.39.0 - 153 (2025-09-30)
- Target Android 16
- Fix possible crash after removing image from card
- Remove "Screen orientation" feature (Google removed the ability for apps to control screen rotation when targeting Android 16)
- Add crash reporter to FOSS build (not used in Google Play version, only in other app stores)
## v2.38.0 - 152 (2025-09-12)
- Add support for .pkpasses files
- Remove Stocard importer (Stocard no longer exists)
- Temporarily disable widget images below Android 12L (workaround for a crash issue)
## v2.37.0 - 151 (2025-08-22)
- New redesign of the Catima logo
- Translation updates

View File

@@ -23,6 +23,30 @@ for good reason.
## Code Changes
Note: submitting LLM ("AI") generated code is strongly discouraged, as such
code is often (subtly) incorrect or overcomplicated (for example: unnecessarily
pulling in extra libraries for functionality already covered by existing
libraries). It also often makes unrelated changes that increase the risk of
introducing new issues and complicates reviewing. Even when it doesn't do any
of the before mentioned things, it will often not fit the coding style and flow
of existing code, requiring excessive refactoring.
While we cannot ever control or be sure if LLMs were used to generate the
submitted code, it is your responsibility to ensure that whatever code you
submit is correct and fits within the design of existing code. It is never
acceptable to defend a change by stating a LLM suggested it.
This is a personal plea more than anything: please understand that writing code
is the easy part. The hard part is making sure the code fits the design of the
rest of the application and is maintainable. Reviewing is a very time-consuming
task for this reason. Please do not use LLMs to quickly generate a "fix" and
moving the cost of labor to me as a reviewer. If you do use LLMs to generate
part of your code, please be open about this, explain what was generated how
and how you confirmed and refactored the code to fit the project and minimized
risk.
Please never submit LLM-generated code as-is.
### Test Your Code
There are four possible tests you can run to verify your code. The first

View File

@@ -1,5 +1,5 @@
**Last updated**
August 30 2023
September 30 2025
# Privacy Policy
Catima does not collect or transmit any personal information.
@@ -11,6 +11,12 @@ To ensure correct app functionality, we require access to the following:
Catima offers a feature to share cards with other users. All the relevant data is in the generated shareable URLs and never transmitted to our servers. When viewed through catima.app, the data in the URL is rendered using client-side Javascript to further ensure no data is ever transmitted to us.
## Crash reporting privacy
In the FOSS version of Catima (the version used on IzzyOnDroid, F-Droid and GitHub), the open source crash reporter ACRA is used for crash reporting. When a crash is detected, Catima will ask the user if they are willing to report the crash. If they choose to do so, the user's mail client is opened so they can review the data that would be sent. Crash reporting data is only sent when the user explicitly chooses to do so, it is **never** sent automatically. Crash reporting data is only used to solve crashes and no (potentially) sensitive information is ever shared. Users who do not want to be asked to report crashes can disable the "Ask to send crash reports" setting in Catima settings.
For the Google Play version of Catima, crash reporting is [managed by Google](https://support.google.com/googleplay/android-developer/answer/9859174?hl=en). Users can opt in or out of crash reporting through the Google app under the "Usage and diagnostics" setting.
# Changes
This Privacy Policy may be updated from time to time for any reason. We will notify you of any changes to our Privacy Policy by posting the new Privacy Policy to https://catima.app/privacy-policy/. A snapshot of the Privacy Policy is available within the Catima app, though it may be outdated. When the Privacy Policy on the website and in the app differ, the website should be considered leading. You are advised to consult the Privacy Policy regularly for any changes, as continued use is deemed approval of all changes.

View File

@@ -1,8 +1,9 @@
import com.android.build.gradle.internal.tasks.factory.dependsOn
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
alias(libs.plugins.com.android.application)
alias(libs.plugins.org.jetbrains.kotlin.android)
alias(libs.plugins.org.jetbrains.kotlin.plugin.compose)
}
kotlin {
@@ -11,14 +12,14 @@ kotlin {
android {
namespace = "protect.card_locker"
compileSdk = 35
compileSdk = 36
defaultConfig {
applicationId = "me.hackerchick.catima"
minSdk = 21
targetSdk = 35
versionCode = 150
versionName = "2.36.0"
targetSdk = 36
versionCode = 156
versionName = "2.40.0"
vectorDrawables.useSupportLibrary = true
multiDexEnabled = true
@@ -29,6 +30,7 @@ android {
buildConfigField("boolean", "showDonate", "true")
buildConfigField("boolean", "showRateOnGooglePlay", "false")
buildConfigField("boolean", "useAcraCrashReporter", "true")
}
buildTypes {
@@ -46,6 +48,7 @@ android {
buildFeatures {
buildConfig = true
compose = true
viewBinding = true
}
@@ -61,6 +64,9 @@ android {
// Google doesn't allow donation links
buildConfigField("boolean", "showDonate", "false")
buildConfigField("boolean", "showRateOnGooglePlay", "true")
// Google Play already sends crashes to the Google Play Console
buildConfigField("boolean", "useAcraCrashReporter", "false")
}
}
@@ -99,48 +105,59 @@ android {
lintConfig = file("lint.xml")
}
kotlinOptions {
jvmTarget = "21"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
jvmTarget = "17"
}
}
dependencies {
// AndroidX
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
implementation("androidx.core:core-ktx:1.16.0")
implementation("androidx.core:core-remoteviews:1.1.0")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.exifinterface:exifinterface:1.4.1")
implementation("androidx.palette:palette:1.0.0")
implementation("androidx.preference:preference:1.2.1")
implementation("com.google.android.material:material:1.12.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
implementation(libs.androidx.appcompat.appcompat)
implementation(libs.androidx.constraintlayout.constraintlayout)
implementation(libs.androidx.core.core.ktx)
implementation(libs.androidx.core.core.remoteviews)
implementation(libs.androidx.core.core.splashscreen)
implementation(libs.androidx.exifinterface.exifinterface)
implementation(libs.androidx.palette.palette)
implementation(libs.androidx.preference.preference)
implementation(libs.com.google.android.material.material)
coreLibraryDesugaring(libs.com.android.tools.desugar.jdk.libs)
// Compose
implementation(libs.androidx.activity.activity.compose)
val composeBom = platform(libs.androidx.compose.compose.bom)
implementation(composeBom)
implementation(libs.androidx.compose.foundation.foundation)
implementation(libs.androidx.compose.material3.material3)
implementation(libs.androidx.compose.material.material.icons.extended)
implementation(libs.androidx.compose.ui.ui.tooling.preview.android)
debugImplementation(libs.androidx.compose.ui.ui.test.manifest)
androidTestImplementation(composeBom)
androidTestImplementation(libs.androidx.compose.ui.ui.test.junit4)
// Third-party
implementation("com.journeyapps:zxing-android-embedded:4.3.0@aar")
implementation("com.github.yalantis:ucrop:2.2.10")
implementation("com.google.zxing:core:3.5.3")
implementation("org.apache.commons:commons-csv:1.9.0")
implementation("com.jaredrummler:colorpicker:1.1.0")
implementation("net.lingala.zip4j:zip4j:2.11.5")
implementation(libs.com.journeyapps.zxing.android.embedded)
implementation(libs.com.github.yalantis.ucrop)
implementation(libs.com.google.zxing.core)
implementation(libs.org.apache.commons.commons.csv)
implementation(libs.com.jaredrummler.colorpicker)
implementation(libs.net.lingala.zip4j.zip4j)
// Crash reporting
implementation(libs.bundles.acra)
// Testing
val androidXTestVersion = "1.7.0"
val junitVersion = "4.13.2"
testImplementation("androidx.test:core:$androidXTestVersion")
testImplementation("junit:junit:$junitVersion")
testImplementation("org.robolectric:robolectric:4.15.1")
testImplementation(libs.androidx.test.core)
testImplementation(libs.junit.junit)
testImplementation(libs.org.robolectric.robolectric)
androidTestImplementation("androidx.test:core:$androidXTestVersion")
androidTestImplementation("junit:junit:$junitVersion")
androidTestImplementation("androidx.test.ext:junit:1.3.0")
androidTestImplementation("androidx.test:runner:$androidXTestVersion")
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
androidTestImplementation(libs.bundles.androidx.test)
androidTestImplementation(libs.junit.junit)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.test.rules)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.androidx.test.uiautomator.uiautomator)
androidTestImplementation(libs.androidx.test.espresso.espresso.core)
}
tasks.register("copyRawResFiles", Copy::class) {

View File

@@ -21,4 +21,19 @@
-keepattributes SourceFile,LineNumberTable
# This keep the class and method names the same, for debugging stack traces
-dontobfuscate
-dontobfuscate
# Required for uCrop 2.2.11
# This is generated automatically by the Android Gradle plugin.
-dontwarn javax.annotation.processing.AbstractProcessor
-dontwarn javax.annotation.processing.SupportedOptions
-dontwarn okhttp3.Call
-dontwarn okhttp3.Dispatcher
-dontwarn okhttp3.OkHttpClient
-dontwarn okhttp3.Request$Builder
-dontwarn okhttp3.Request
-dontwarn okhttp3.Response
-dontwarn okhttp3.ResponseBody
-dontwarn okio.BufferedSource
-dontwarn okio.Okio
-dontwarn okio.Sink

View File

@@ -0,0 +1,89 @@
package protect.card_locker
import android.app.Instrumentation
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.runComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import protect.card_locker.compose.theme.CatimaTheme
@OptIn(ExperimentalTestApi::class)
@RunWith(AndroidJUnit4::class)
class AboutActivityTest {
@get:Rule
private val rule: ComposeContentTestRule = createComposeRule()
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val content: AboutContent = AboutContent(instrumentation.targetContext)
@Test
fun testInitialState(): Unit = runComposeUiTest {
setContent {
AboutScreenContent(content = content)
}
onNodeWithTag("topbar_catima").assertIsDisplayed()
onNodeWithTag("card_version_history").assertIsDisplayed()
onNodeWithText(content.versionHistory).assertIsDisplayed()
onNodeWithTag("card_credits").assertIsDisplayed()
onNodeWithText(content.copyrightShort).assertIsDisplayed()
onNodeWithTag("card_translate").assertIsDisplayed()
onNodeWithTag("card_license").assertIsDisplayed()
// We might be off the screen so start scrolling
onNodeWithTag("card_source_github").performScrollTo().assertIsDisplayed()
onNodeWithTag("card_privacy_policy").performScrollTo().assertIsDisplayed()
onNodeWithTag("card_donate").performScrollTo().assertIsDisplayed()
// Dont scroll to this, since its not displayed
onNodeWithTag("card_rate_google").assertIsNotDisplayed()
onNodeWithTag("card_report_error").performScrollTo().assertIsDisplayed()
}
@Test
fun testDonateAndGoogleCardVisible(): Unit = runComposeUiTest {
setContent {
CatimaTheme {
AboutScreenContent(
content = content,
showDonate = true,
showRateOnGooglePlay = true,
)
}
}
onNodeWithTag("card_donate").performScrollTo().assertIsDisplayed()
onNodeWithTag("card_rate_google").performScrollTo().assertIsDisplayed()
}
@Test
fun testDonateAndGoogleCardHidden(): Unit = runComposeUiTest {
setContent {
CatimaTheme {
AboutScreenContent(
content = content,
showDonate = false,
showRateOnGooglePlay = false,
)
}
}
onNodeWithTag("card_privacy_policy").performScrollTo().assertIsDisplayed()
onNodeWithTag("card_donate").assertIsNotDisplayed()
onNodeWithTag("card_rate_google").assertIsNotDisplayed()
onNodeWithTag("card_report_error").performScrollTo().assertIsDisplayed()
}
}

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Catima 除錯版</string>
</resources>
<string name="app_name">卡提碼除錯版</string>
</resources>

View File

@@ -4,7 +4,7 @@
<permission
android:description="@string/permissionReadCardsDescription"
android:icon="@drawable/ic_launcher_foreground"
android:icon="@drawable/ic_launcher_monochrome"
android:label="@string/permissionReadCardsLabel"
android:name="${applicationId}.READ_CARDS"
android:protectionLevel="dangerous" />
@@ -65,6 +65,7 @@
<data android:mimeType="application/vnd.apple.pkpass" />
<data android:mimeType="application/vnd-com.apple.pkpass" />
<data android:mimeType="application/vnd.espass-espass" />
<data android:mimeType="application/vnd.apple.pkpasses" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
@@ -76,6 +77,7 @@
<data android:mimeType="application/vnd.apple.pkpass" />
<data android:mimeType="application/vnd-com.apple.pkpass" />
<data android:mimeType="application/vnd.espass-espass" />
<data android:mimeType="application/vnd.apple.pkpasses" />
</intent-filter>
</activity>
<activity
@@ -142,12 +144,11 @@
android:name=".preferences.SettingsActivity"
android:label="@string/settings"
android:theme="@style/AppTheme.NoActionBar" />
<!-- FIXME: locked screenOrientation is a workaround for https://github.com/CatimaLoyalty/Android/issues/1715, remove when https://github.com/CatimaLoyalty/Android/issues/513 is fixed -->
<!-- FIXME: ImportExportActivity cancels import on rotation -->
<activity
android:name=".ImportExportActivity"
android:label="@string/importExport"
android:exported="true"
android:screenOrientation="locked"
android:theme="@style/AppTheme.NoActionBar">
<!-- ZIP Intent Filter -->

View File

@@ -1,149 +1,167 @@
package protect.card_locker
import android.os.Bundle
import android.text.Spanned
import android.view.MenuItem
import android.view.View
import android.widget.ScrollView
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.fromHtml
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import protect.card_locker.compose.CatimaAboutSection
import protect.card_locker.compose.CatimaTopAppBar
import protect.card_locker.compose.theme.CatimaTheme
import androidx.annotation.StringRes
import androidx.core.view.isVisible
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import protect.card_locker.databinding.AboutActivityBinding
class AboutActivity : CatimaAppCompatActivity() {
private companion object {
private const val TAG = "Catima"
}
private lateinit var binding: AboutActivityBinding
class AboutActivity : ComponentActivity() {
private lateinit var content: AboutContent
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = AboutActivityBinding.inflate(layoutInflater)
content = AboutContent(this)
title = content.pageTitle
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
enableToolbarBackButton()
binding.apply {
creditsSub.text = content.copyrightShort
versionHistorySub.text = content.versionHistory
versionHistory.tag = "https://catima.app/changelog/"
translate.tag = "https://hosted.weblate.org/engage/catima/"
license.tag = "https://github.com/CatimaLoyalty/Android/blob/main/LICENSE"
repo.tag = "https://github.com/CatimaLoyalty/Android/"
privacy.tag = "https://catima.app/privacy-policy/"
reportError.tag = "https://github.com/CatimaLoyalty/Android/issues"
rate.tag = "https://play.google.com/store/apps/details?id=me.hackerchick.catima"
donate.tag = "https://catima.app/donate"
// Hide Google Play rate button if not on Google Play
rate.isVisible = BuildConfig.showRateOnGooglePlay
// Hide donate button on Google Play (Google Play doesn't allow donation links)
donate.isVisible = BuildConfig.showDonate
}
bindClickListeners()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
finish()
true
setContent {
CatimaTheme {
AboutScreenContent(
content = content,
showDonate = BuildConfig.showDonate,
showRateOnGooglePlay = BuildConfig.showRateOnGooglePlay,
onBackPressedDispatcher = onBackPressedDispatcher
)
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onDestroy() {
super.onDestroy()
content.destroy()
clearClickListeners()
}
private fun bindClickListeners() {
binding.apply {
versionHistory.setOnClickListener { showHistory(it) }
translate.setOnClickListener { openExternalBrowser(it) }
license.setOnClickListener { showLicense(it) }
repo.setOnClickListener { openExternalBrowser(it) }
privacy.setOnClickListener { showPrivacy(it) }
reportError.setOnClickListener { openExternalBrowser(it) }
rate.setOnClickListener { openExternalBrowser(it) }
donate.setOnClickListener { openExternalBrowser(it) }
credits.setOnClickListener { showCredits() }
}
}
private fun clearClickListeners() {
binding.apply {
versionHistory.setOnClickListener(null)
translate.setOnClickListener(null)
license.setOnClickListener(null)
repo.setOnClickListener(null)
privacy.setOnClickListener(null)
reportError.setOnClickListener(null)
rate.setOnClickListener(null)
donate.setOnClickListener(null)
credits.setOnClickListener(null)
}
}
private fun showCredits() {
showHTML(R.string.credits, content.contributorInfo, null)
}
private fun showHistory(view: View) {
showHTML(R.string.version_history, content.historyInfo, view)
}
private fun showLicense(view: View) {
showHTML(R.string.license, content.licenseInfo, view)
}
private fun showPrivacy(view: View) {
showHTML(R.string.privacy_policy, content.privacyInfo, view)
}
private fun showHTML(@StringRes title: Int, text: Spanned, view: View?) {
val dialogContentPadding = resources.getDimensionPixelSize(R.dimen.alert_dialog_content_padding)
val textView = TextView(this).apply {
setText(text)
Utils.makeTextViewLinksClickable(this, text)
}
val scrollView = ScrollView(this).apply {
addView(textView)
setPadding(dialogContentPadding, dialogContentPadding / 2, dialogContentPadding, 0)
}
MaterialAlertDialogBuilder(this).apply {
setTitle(title)
setView(scrollView)
setPositiveButton(R.string.ok, null)
// Add View online button if an URL is linked to this view
view?.tag?.let {
setNeutralButton(R.string.view_online) { _, _ -> openExternalBrowser(view) }
}
show()
}
}
private fun openExternalBrowser(view: View) {
val tag = view.tag
if (tag is String && tag.startsWith("https://")) {
OpenWebLinkHandler().openBrowser(this, tag)
}
}
}
@Composable
fun AboutScreenContent(
content: AboutContent,
showDonate: Boolean = true,
showRateOnGooglePlay: Boolean = false,
onBackPressedDispatcher: OnBackPressedDispatcher? = null,
) {
Scaffold(
topBar = { CatimaTopAppBar(content.pageTitle.toString(), onBackPressedDispatcher) }
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.verticalScroll(rememberScrollState())
) {
CatimaAboutSection(
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
)
)
)
)
CatimaAboutSection(
stringResource(R.string.credits),
content.copyrightShort,
modifier = Modifier.testTag("card_credits"),
onClickDialogText = AnnotatedString.fromHtml(
htmlString = content.contributorInfoHtml,
linkStyles = TextLinkStyles(
style = SpanStyle(
textDecoration = TextDecoration.Underline,
color = MaterialTheme.colorScheme.primary
)
)
)
)
CatimaAboutSection(
stringResource(R.string.help_translate_this_app),
stringResource(R.string.translate_platform),
modifier = Modifier.testTag("card_translate"),
onClickUrl = "https://hosted.weblate.org/engage/catima/"
)
CatimaAboutSection(
stringResource(R.string.license),
stringResource(R.string.app_license),
modifier = Modifier.testTag("card_license"),
onClickUrl = "https://github.com/CatimaLoyalty/Android/blob/main/LICENSE",
onClickDialogText = AnnotatedString.fromHtml(
htmlString = content.licenseHtml,
linkStyles = TextLinkStyles(
style = SpanStyle(
textDecoration = TextDecoration.Underline,
color = MaterialTheme.colorScheme.primary
)
)
)
)
CatimaAboutSection(
stringResource(R.string.source_repository),
stringResource(R.string.on_github),
modifier = Modifier.testTag("card_source_github"),
onClickUrl = "https://github.com/CatimaLoyalty/Android/"
)
CatimaAboutSection(
stringResource(R.string.privacy_policy),
stringResource(R.string.and_data_usage),
modifier = Modifier.testTag("card_privacy_policy"),
onClickUrl = "https://catima.app/privacy-policy/",
onClickDialogText = AnnotatedString.fromHtml(
htmlString = content.privacyHtml,
linkStyles = TextLinkStyles(
style = SpanStyle(
textDecoration = TextDecoration.Underline,
color = MaterialTheme.colorScheme.primary
)
)
)
)
if (showDonate) {
CatimaAboutSection(
stringResource(R.string.donate),
"",
modifier = Modifier.testTag("card_donate"),
onClickUrl = "https://catima.app/donate"
)
}
if (showRateOnGooglePlay) {
CatimaAboutSection(
stringResource(R.string.rate_this_app),
stringResource(R.string.on_google_play),
modifier = Modifier.testTag("card_rate_google"),
onClickUrl = "https://play.google.com/store/apps/details?id=me.hackerchick.catima"
)
}
CatimaAboutSection(
stringResource(R.string.report_error),
stringResource(R.string.on_github),
modifier = Modifier.testTag("card_report_error"),
onClickUrl = "https://github.com/CatimaLoyalty/Android/issues"
)
}
}
}
@Preview
@Composable
private fun AboutActivityPreview() {
AboutScreenContent(AboutContent(LocalContext.current))
}

View File

@@ -3,11 +3,8 @@ package protect.card_locker;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.text.Spanned;
import android.util.Log;
import androidx.core.text.HtmlCompat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
@@ -55,7 +52,7 @@ public class AboutContent {
return context.getString(R.string.app_copyright_short);
}
public String getContributors() {
public String getContributorsHtml() {
String contributors;
try {
contributors = "<br/>" + Utils.readTextFile(context, R.raw.contributors);
@@ -65,7 +62,7 @@ public class AboutContent {
return contributors.replace("\n", "<br />");
}
public String getHistory() {
public String getHistoryHtml() {
String versionHistory;
try {
versionHistory = Utils.readTextFile(context, R.raw.changelog)
@@ -77,7 +74,7 @@ public class AboutContent {
.replace("\n", "<br />");
}
public String getLicense() {
public String getLicenseHtml() {
try {
return Utils.readTextFile(context, R.raw.license);
} catch (IOException ignored) {
@@ -85,7 +82,7 @@ public class AboutContent {
}
}
public String getPrivacy() {
public String getPrivacyHtml() {
String privacyPolicy;
try {
privacyPolicy = Utils.readTextFile(context, R.raw.privacy)
@@ -97,11 +94,11 @@ public class AboutContent {
.replace("\n", "<br />");
}
public String getThirdPartyLibraries() {
public String getThirdPartyLibrariesHtml() {
final List<ThirdPartyInfo> usedLibraries = new ArrayList<>();
usedLibraries.add(new ThirdPartyInfo("ACRA", "https://github.com/ACRA/acra", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("Color Picker", "https://github.com/jaredrummler/ColorPicker", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("Commons CSV", "https://commons.apache.org/proper/commons-csv/", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("NumberPickerPreference", "https://github.com/invissvenska/NumberPickerPreference", "GNU LGPL 3.0"));
usedLibraries.add(new ThirdPartyInfo("uCrop", "https://github.com/Yalantis/uCrop", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("Zip4j", "https://github.com/srikanth-lingala/zip4j", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("ZXing", "https://github.com/zxing/zxing", "Apache 2.0"));
@@ -116,7 +113,7 @@ public class AboutContent {
return result.toString();
}
public String getUsedThirdPartyAssets() {
public String getUsedThirdPartyAssetsHtml() {
final List<ThirdPartyInfo> usedAssets = new ArrayList<>();
usedAssets.add(new ThirdPartyInfo("Android icons", "https://fonts.google.com/icons?selected=Material+Icons", "Apache 2.0"));
@@ -129,31 +126,19 @@ public class AboutContent {
return result.toString();
}
public Spanned getContributorInfo() {
public String getContributorInfoHtml() {
StringBuilder contributorInfo = new StringBuilder();
contributorInfo.append(getCopyright());
contributorInfo.append("<br/><br/>");
contributorInfo.append(context.getString(R.string.app_copyright_old));
contributorInfo.append("<br/><br/>");
contributorInfo.append(String.format(context.getString(R.string.app_contributors), getContributors()));
contributorInfo.append(String.format(context.getString(R.string.app_contributors), getContributorsHtml()));
contributorInfo.append("<br/><br/>");
contributorInfo.append(String.format(context.getString(R.string.app_libraries), getThirdPartyLibraries()));
contributorInfo.append(String.format(context.getString(R.string.app_libraries), getThirdPartyLibrariesHtml()));
contributorInfo.append("<br/><br/>");
contributorInfo.append(String.format(context.getString(R.string.app_resources), getUsedThirdPartyAssets()));
contributorInfo.append(String.format(context.getString(R.string.app_resources), getUsedThirdPartyAssetsHtml()));
return HtmlCompat.fromHtml(contributorInfo.toString(), HtmlCompat.FROM_HTML_MODE_COMPACT);
}
public Spanned getHistoryInfo() {
return HtmlCompat.fromHtml(getHistory(), HtmlCompat.FROM_HTML_MODE_COMPACT);
}
public Spanned getLicenseInfo() {
return HtmlCompat.fromHtml(getLicense(), HtmlCompat.FROM_HTML_MODE_LEGACY);
}
public Spanned getPrivacyInfo() {
return HtmlCompat.fromHtml(getPrivacy(), HtmlCompat.FROM_HTML_MODE_COMPACT);
return contributorInfo.toString();
}
public String getVersionHistory() {

View File

@@ -1,5 +0,0 @@
package protect.card_locker;
public interface BarcodeImageWriterResultCallback {
void onBarcodeImageWriterResult(boolean success);
}

View File

@@ -0,0 +1,5 @@
package protect.card_locker
interface BarcodeImageWriterResultCallback {
fun onBarcodeImageWriterResult(success: Boolean)
}

View File

@@ -1,402 +0,0 @@
package protect.card_locker;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.InputType;
import android.util.Log;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.databinding.ImportExportActivityBinding;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult;
import protect.card_locker.importexport.ImportExportResultType;
public class ImportExportActivity extends CatimaAppCompatActivity {
private ImportExportActivityBinding binding;
private static final String TAG = "Catima";
private ImportExportTask importExporter;
private String importAlertTitle;
private String importAlertMessage;
private DataFormat importDataFormat;
private String exportPassword;
private ActivityResultLauncher<Intent> fileCreateLauncher;
private ActivityResultLauncher<String> fileOpenLauncher;
final private TaskHandler mTasks = new TaskHandler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ImportExportActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.importExport);
setContentView(binding.getRoot());
Utils.applyWindowInsets(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
enableToolbarBackButton();
Intent fileIntent = getIntent();
if (fileIntent != null && fileIntent.getType() != null) {
chooseImportType(fileIntent.getData());
}
// would use ActivityResultContracts.CreateDocument() but mime type cannot be set
fileCreateLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
Intent intent = result.getData();
if (intent == null) {
Log.e(TAG, "Activity returned NULL data");
return;
}
Uri uri = intent.getData();
if (uri == null) {
Log.e(TAG, "Activity returned NULL uri");
return;
}
// Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files
// FIXME: This is still suboptimal, because showing that the export started is delayed until the network request finishes
new Thread() {
@Override
public void run() {
try {
OutputStream writer = getContentResolver().openOutputStream(uri);
Log.d(TAG, "Starting file export with: " + result);
startExport(writer, uri, exportPassword.toCharArray(), true);
} catch (IOException e) {
Log.e(TAG, "Failed to export file: " + result, e);
onExportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, result.toString()), uri);
}
}
}.start();
});
fileOpenLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), result -> {
if (result == null) {
Log.e(TAG, "Activity returned NULL data");
return;
}
openFileForImport(result, null);
});
// Check that there is a file manager available
final Intent intentCreateDocumentAction = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intentCreateDocumentAction.addCategory(Intent.CATEGORY_OPENABLE);
intentCreateDocumentAction.setType("application/zip");
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "catima.zip");
Button exportButton = binding.exportButton;
exportButton.setOnClickListener(v -> {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(ImportExportActivity.this);
builder.setTitle(R.string.exportPassword);
FrameLayout container = new FrameLayout(ImportExportActivity.this);
final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this);
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(50, 10, 50, 0);
textInputLayout.setLayoutParams(params);
final EditText input = new EditText(ImportExportActivity.this);
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
input.setHint(R.string.exportPasswordHint);
textInputLayout.addView(input);
container.addView(textInputLayout);
builder.setView(container);
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
exportPassword = input.getText().toString();
try {
fileCreateLauncher.launch(intentCreateDocumentAction);
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
});
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
builder.show();
});
// Check that there is a file manager available
Button importFilesystem = binding.importOptionFilesystemButton;
importFilesystem.setOnClickListener(v -> chooseImportType(null));
// FIXME: The importer/exporter is currently quite broken
// To prevent the screen from turning off during import/export and some devices killing Catima as it's no longer foregrounded, force the screen to stay on here
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
private void openFileForImport(Uri uri, char[] password) {
// Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files
// FIXME: This is still suboptimal, because showing that the import started is delayed until the network request finishes
new Thread() {
@Override
public void run() {
try {
InputStream reader = getContentResolver().openInputStream(uri);
Log.d(TAG, "Starting file import with: " + uri);
startImport(reader, uri, importDataFormat, password, true);
} catch (IOException e) {
Log.e(TAG, "Failed to import file: " + uri, e);
onImportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, e.toString()), uri, importDataFormat);
}
}
}.start();
}
private void chooseImportType(@Nullable Uri fileData) {
List<CharSequence> betaImportOptions = new ArrayList<>();
betaImportOptions.add("Fidme");
betaImportOptions.add("Stocard");
List<CharSequence> importOptions = new ArrayList<>();
for (String importOption : getResources().getStringArray(R.array.import_types_array)) {
if (betaImportOptions.contains(importOption)) {
importOption = importOption + " (BETA)";
}
importOptions.add(importOption);
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.chooseImportType)
.setItems(importOptions.toArray(new CharSequence[importOptions.size()]), (dialog, which) -> {
switch (which) {
// Catima
case 0:
importAlertTitle = getString(R.string.importCatima);
importAlertMessage = getString(R.string.importCatimaMessage);
importDataFormat = DataFormat.Catima;
break;
// Fidme
case 1:
importAlertTitle = getString(R.string.importFidme);
importAlertMessage = getString(R.string.importFidmeMessage);
importDataFormat = DataFormat.Fidme;
break;
// Loyalty Card Keychain
case 2:
importAlertTitle = getString(R.string.importLoyaltyCardKeychain);
importAlertMessage = getString(R.string.importLoyaltyCardKeychainMessage);
importDataFormat = DataFormat.Catima;
break;
// Stocard
case 3:
importAlertTitle = getString(R.string.importStocard);
importAlertMessage = getString(R.string.importStocardMessage);
importDataFormat = DataFormat.Stocard;
break;
// Voucher Vault
case 4:
importAlertTitle = getString(R.string.importVoucherVault);
importAlertMessage = getString(R.string.importVoucherVaultMessage);
importDataFormat = DataFormat.VoucherVault;
break;
default:
throw new IllegalArgumentException("Unknown DataFormat");
}
if (fileData != null) {
openFileForImport(fileData, null);
return;
}
new MaterialAlertDialogBuilder(this)
.setTitle(importAlertTitle)
.setMessage(importAlertMessage)
.setPositiveButton(R.string.ok, (dialog1, which1) -> {
try {
fileOpenLauncher.launch("*/*");
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
});
builder.show();
}
private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password, final boolean closeWhenDone) {
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
@Override
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
onImportComplete(result, targetUri, dataFormat);
if (closeWhenDone) {
try {
target.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
dataFormat, target, password, listener);
mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter);
}
private void startExport(final OutputStream target, final Uri targetUri, char[] password, final boolean closeWhenDone) {
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
@Override
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
onExportComplete(result, targetUri);
if (closeWhenDone) {
try {
target.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
DataFormat.Catima, target, password, listener);
mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter);
}
@Override
protected void onDestroy() {
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
super.onDestroy();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
private void retryWithPassword(DataFormat dataFormat, Uri uri) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.passwordRequired);
FrameLayout container = new FrameLayout(ImportExportActivity.this);
final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this);
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(50, 10, 50, 0);
textInputLayout.setLayoutParams(params);
final EditText input = new EditText(ImportExportActivity.this);
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
input.setHint(R.string.exportPasswordHint);
textInputLayout.addView(input);
container.addView(textInputLayout);
builder.setView(container);
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
openFileForImport(uri, input.getText().toString().toCharArray());
});
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
builder.show();
}
private String buildResultDialogMessage(ImportExportResult result, boolean isImport) {
int messageId;
if (result.resultType() == ImportExportResultType.Success) {
messageId = isImport ? R.string.importSuccessful : R.string.exportSuccessful;
} else {
messageId = isImport ? R.string.importFailed : R.string.exportFailed;
}
StringBuilder messageBuilder = new StringBuilder(getResources().getString(messageId));
if (result.developerDetails() != null) {
messageBuilder.append("\n\n");
messageBuilder.append(getResources().getString(R.string.include_if_asking_support));
messageBuilder.append("\n\n");
messageBuilder.append(result.developerDetails());
}
return messageBuilder.toString();
}
private void onImportComplete(ImportExportResult result, Uri path, DataFormat dataFormat) {
ImportExportResultType resultType = result.resultType();
if (resultType == ImportExportResultType.BadPassword) {
retryWithPassword(dataFormat, path);
return;
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.importSuccessfulTitle : R.string.importFailedTitle);
builder.setMessage(buildResultDialogMessage(result, true));
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
builder.create().show();
}
private void onExportComplete(ImportExportResult result, final Uri path) {
ImportExportResultType resultType = result.resultType();
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.exportSuccessfulTitle : R.string.exportFailedTitle);
builder.setMessage(buildResultDialogMessage(result, false));
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
if (resultType == ImportExportResultType.Success) {
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel);
builder.setPositiveButton(sendLabel, (dialog, which) -> {
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, path);
sendIntent.setType("text/csv");
// set flag to give temporary permission to external app to use the FileProvider
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
sendLabel));
dialog.dismiss();
});
}
builder.create().show();
}
}

View File

@@ -0,0 +1,416 @@
package protect.card_locker
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.InputType
import android.util.Log
import android.view.MenuItem
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.Button
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.Toolbar
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputLayout
import protect.card_locker.async.TaskHandler
import protect.card_locker.databinding.ImportExportActivityBinding
import protect.card_locker.importexport.DataFormat
import protect.card_locker.importexport.ImportExportResult
import protect.card_locker.importexport.ImportExportResultType
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
class ImportExportActivity : CatimaAppCompatActivity() {
private lateinit var binding: ImportExportActivityBinding
private var importExporter: ImportExportTask? = null
private var importAlertTitle: String? = null
private var importAlertMessage: String? = null
private var importDataFormat: DataFormat? = null
private var exportPassword: String? = null
private lateinit var fileCreateLauncher: ActivityResultLauncher<Intent>
private lateinit var fileOpenLauncher: ActivityResultLauncher<String>
private val mTasks = TaskHandler()
companion object {
private const val TAG = "Catima"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ImportExportActivityBinding.inflate(layoutInflater)
setTitle(R.string.importExport)
setContentView(binding.root)
Utils.applyWindowInsets(binding.root)
val toolbar: Toolbar = binding.toolbar
setSupportActionBar(toolbar)
enableToolbarBackButton()
val fileIntent = intent
if (fileIntent?.type != null) {
chooseImportType(fileIntent.data)
}
// would use ActivityResultContracts.CreateDocument() but mime type cannot be set
fileCreateLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val intent = result.data
if (intent == null) {
Log.e(TAG, "Activity returned NULL data")
return@registerForActivityResult
}
val uri = intent.data
if (uri == null) {
Log.e(TAG, "Activity returned NULL uri")
return@registerForActivityResult
}
// Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files
// FIXME: This is still suboptimal, because showing that the export started is delayed until the network request finishes
Thread {
try {
val writer = contentResolver.openOutputStream(uri)
Log.d(TAG, "Starting file export with: $result")
startExport(writer, uri, exportPassword?.toCharArray(), true)
} catch (e: IOException) {
Log.e(TAG, "Failed to export file: $result", e)
onExportComplete(
ImportExportResult(
ImportExportResultType.GenericFailure,
result.toString()
), uri
)
}
}.start()
}
fileOpenLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) { result ->
if (result == null) {
Log.e(TAG, "Activity returned NULL data")
return@registerForActivityResult
}
openFileForImport(result, null)
}
// Check that there is a file manager available
val intentCreateDocumentAction = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/zip"
putExtra(Intent.EXTRA_TITLE, "catima.zip")
}
val exportButton: Button = binding.exportButton
exportButton.setOnClickListener {
val builder = MaterialAlertDialogBuilder(this@ImportExportActivity)
builder.setTitle(R.string.exportPassword)
val container = FrameLayout(this@ImportExportActivity)
val textInputLayout = TextInputLayout(this@ImportExportActivity).apply {
endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
setMargins(50, 10, 50, 0)
}
}
val input = EditText(this@ImportExportActivity).apply {
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
setHint(R.string.exportPasswordHint)
}
textInputLayout.addView(input)
container.addView(textInputLayout)
builder.setView(container)
builder.setPositiveButton(R.string.ok) { _, _ ->
exportPassword = input.text.toString()
try {
fileCreateLauncher.launch(intentCreateDocumentAction)
} catch (e: ActivityNotFoundException) {
Toast.makeText(
applicationContext,
R.string.failedOpeningFileManager,
Toast.LENGTH_LONG
).show()
Log.e(TAG, "No activity found to handle intent", e)
}
}
builder.setNegativeButton(R.string.cancel) { dialogInterface, _ -> dialogInterface.cancel() }
builder.show()
}
// Check that there is a file manager available
val importFilesystem: Button = binding.importOptionFilesystemButton
importFilesystem.setOnClickListener { chooseImportType(null) }
// FIXME: The importer/exporter is currently quite broken
// To prevent the screen from turning off during import/export and some devices killing Catima as it's no longer foregrounded, force the screen to stay on here
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
private fun openFileForImport(uri: Uri, password: CharArray?) {
// Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files
// FIXME: This is still suboptimal, because showing that the import started is delayed until the network request finishes
Thread {
try {
val reader = contentResolver.openInputStream(uri)
Log.d(TAG, "Starting file import with: $uri")
startImport(reader, uri, importDataFormat, password, true)
} catch (e: IOException) {
Log.e(TAG, "Failed to import file: $uri", e)
onImportComplete(
ImportExportResult(
ImportExportResultType.GenericFailure,
e.toString()
), uri, importDataFormat
)
}
}.start()
}
private fun chooseImportType(fileData: Uri?) {
val betaImportOptions = mutableListOf<CharSequence>()
betaImportOptions.add("Fidme")
val importOptions = mutableListOf<CharSequence>()
for (importOption in resources.getStringArray(R.array.import_types_array)) {
var option = importOption
if (betaImportOptions.contains(importOption)) {
option = "$importOption (BETA)"
}
importOptions.add(option)
}
val builder = MaterialAlertDialogBuilder(this)
builder.setTitle(R.string.chooseImportType)
.setItems(importOptions.toTypedArray()) { _, which ->
when (which) {
// Catima
0 -> {
importAlertTitle = getString(R.string.importCatima)
importAlertMessage = getString(R.string.importCatimaMessage)
importDataFormat = DataFormat.Catima
}
// Fidme
1 -> {
importAlertTitle = getString(R.string.importFidme)
importAlertMessage = getString(R.string.importFidmeMessage)
importDataFormat = DataFormat.Fidme
}
// Loyalty Card Keychain
2 -> {
importAlertTitle = getString(R.string.importLoyaltyCardKeychain)
importAlertMessage = getString(R.string.importLoyaltyCardKeychainMessage)
importDataFormat = DataFormat.Catima
}
// Voucher Vault
3 -> {
importAlertTitle = getString(R.string.importVoucherVault)
importAlertMessage = getString(R.string.importVoucherVaultMessage)
importDataFormat = DataFormat.VoucherVault
}
else -> throw IllegalArgumentException("Unknown DataFormat")
}
if (fileData != null) {
openFileForImport(fileData, null)
return@setItems
}
MaterialAlertDialogBuilder(this)
.setTitle(importAlertTitle)
.setMessage(importAlertMessage)
.setPositiveButton(R.string.ok) { _, _ ->
try {
fileOpenLauncher.launch("*/*")
} catch (e: ActivityNotFoundException) {
Toast.makeText(
applicationContext,
R.string.failedOpeningFileManager,
Toast.LENGTH_LONG
).show()
Log.e(TAG, "No activity found to handle intent", e)
}
}
.setNegativeButton(R.string.cancel, null)
.show()
}
builder.show()
}
private fun startImport(
target: InputStream?,
targetUri: Uri,
dataFormat: DataFormat?,
password: CharArray?,
closeWhenDone: Boolean
) {
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false)
val listener = ImportExportTask.TaskCompleteListener { result, dataFormat ->
onImportComplete(result, targetUri, dataFormat)
if (closeWhenDone) {
try {
target?.close()
} catch (ioException: IOException) {
ioException.printStackTrace()
}
}
}
importExporter = ImportExportTask(
this@ImportExportActivity,
dataFormat, target, password, listener
)
mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter)
}
private fun startExport(
target: OutputStream?,
targetUri: Uri,
password: CharArray?,
closeWhenDone: Boolean
) {
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false)
val listener = ImportExportTask.TaskCompleteListener { result, dataFormat ->
onExportComplete(result, targetUri)
if (closeWhenDone) {
try {
target?.close()
} catch (ioException: IOException) {
ioException.printStackTrace()
}
}
}
importExporter = ImportExportTask(
this@ImportExportActivity,
DataFormat.Catima, target, password, listener
)
mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter)
}
override fun onDestroy() {
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false)
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false)
super.onDestroy()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == android.R.id.home) {
finish()
return true
}
return super.onOptionsItemSelected(item)
}
private fun retryWithPassword(dataFormat: DataFormat, uri: Uri) {
val builder = MaterialAlertDialogBuilder(this)
builder.setTitle(R.string.passwordRequired)
val container = FrameLayout(this@ImportExportActivity)
val textInputLayout = TextInputLayout(this@ImportExportActivity).apply {
endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
setMargins(50, 10, 50, 0)
}
}
val input = EditText(this@ImportExportActivity).apply {
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
setHint(R.string.exportPasswordHint)
}
textInputLayout.addView(input)
container.addView(textInputLayout)
builder.setView(container)
builder.setPositiveButton(R.string.ok) { _, _ ->
openFileForImport(uri, input.text.toString().toCharArray())
}
builder.setNegativeButton(R.string.cancel) { dialogInterface, _ -> dialogInterface.cancel() }
builder.show()
}
private fun buildResultDialogMessage(result: ImportExportResult, isImport: Boolean): String {
val messageId = if (result.resultType() == ImportExportResultType.Success) {
if (isImport) R.string.importSuccessful else R.string.exportSuccessful
} else {
if (isImport) R.string.importFailed else R.string.exportFailed
}
val messageBuilder = StringBuilder(resources.getString(messageId))
if (result.developerDetails() != null) {
messageBuilder.append("\n\n")
messageBuilder.append(resources.getString(R.string.include_if_asking_support))
messageBuilder.append("\n\n")
messageBuilder.append(result.developerDetails())
}
return messageBuilder.toString()
}
private fun onImportComplete(result: ImportExportResult, path: Uri, dataFormat: DataFormat?) {
val resultType = result.resultType()
if (resultType == ImportExportResultType.BadPassword) {
retryWithPassword(dataFormat!!, path)
return
}
val builder = MaterialAlertDialogBuilder(this)
builder.setTitle(if (resultType == ImportExportResultType.Success) R.string.importSuccessfulTitle else R.string.importFailedTitle)
builder.setMessage(buildResultDialogMessage(result, true))
builder.setNeutralButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
builder.create().show()
}
private fun onExportComplete(result: ImportExportResult, path: Uri) {
val resultType = result.resultType()
val builder = MaterialAlertDialogBuilder(this)
builder.setTitle(if (resultType == ImportExportResultType.Success) R.string.exportSuccessfulTitle else R.string.exportFailedTitle)
builder.setMessage(buildResultDialogMessage(result, false))
builder.setNeutralButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
if (resultType == ImportExportResultType.Success) {
val sendLabel = this@ImportExportActivity.resources.getText(R.string.sendLabel)
builder.setPositiveButton(sendLabel) { dialog, _ ->
val sendIntent = Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_STREAM, path)
type = "text/csv"
// set flag to give temporary permission to external app to use the FileProvider
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}
this@ImportExportActivity.startActivity(Intent.createChooser(sendIntent, sendLabel))
dialog.dismiss()
}
}
builder.create().show()
}
}

View File

@@ -1,12 +1,17 @@
package protect.card_locker;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import android.widget.Toast;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.IOException;
import java.io.InputStream;
@@ -32,7 +37,7 @@ public class ImportExportTask implements CompatCallable<ImportExportResult> {
private char[] password;
private TaskCompleteListener listener;
private ProgressDialog progress;
private AlertDialog progress;
/**
* Constructor which will setup a task for exporting to the given file
@@ -88,12 +93,36 @@ public class ImportExportTask implements CompatCallable<ImportExportResult> {
}
public void onPreExecute() {
progress = new ProgressDialog(activity);
progress.setTitle(doImport ? R.string.importing : R.string.exporting);
MaterialAlertDialogBuilder progressDialogBuilder = new MaterialAlertDialogBuilder(activity);
progressDialogBuilder.setCancelable(false); // Don't cancel if user taps next to dialog
progressDialogBuilder.setTitle(doImport ? R.string.importing : R.string.exporting);
progress.setOnCancelListener(dialog -> cancel());
progress.setOnDismissListener(dialog -> cancel());
// Create components
TextView progressDialogTextView = new TextView(activity);
progressDialogTextView.setText(R.string.pleaseDoNotRotateTheDevice); // FIXME: Instead of telling the user to not rotate, rotation should not cancel the import
ProgressBar progressDialogProgressBar = new ProgressBar(activity);
progressDialogProgressBar.setIndeterminate(true);
// Create LinearLayout (to put the components below each other)
LinearLayout progressDialogLayout = new LinearLayout(activity);
progressDialogLayout.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams progressDialogLayoutParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
);
int contentPadding = activity.getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
progressDialogLayoutParams.setMargins(contentPadding, contentPadding / 2, contentPadding, 0);
// Put components in layout
progressDialogLayout.addView(progressDialogTextView, progressDialogLayoutParams);
progressDialogLayout.addView(progressDialogProgressBar, progressDialogLayoutParams);
// Create and show dialog
progressDialogBuilder.setView(progressDialogLayout);
progressDialogBuilder.setNeutralButton(R.string.cancel, (dialogInterface, i) -> cancel());
progressDialogBuilder.setOnCancelListener(dialogInterface -> cancel());
progressDialogBuilder.setOnDismissListener(dialogInterface -> cancel());
progress = progressDialogBuilder.create();
progress.show();
}

View File

@@ -1,145 +0,0 @@
package protect.card_locker;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.util.Log;
import androidx.core.graphics.PaintCompat;
/**
* Original from https://github.com/andOTP/andOTP/blob/master/app/src/main/java/org/shadowice/flocke/andotp/Utilities/LetterBitmap.java
* which was originally from http://stackoverflow.com/questions/23122088/colored-boxed-with-letters-a-la-gmail
* Used to create a {@link Bitmap} that contains a letter used in the English
* alphabet or digit, if there is no letter or digit available, a default image
* is shown instead.
*/
class LetterBitmap {
/**
* The number of available tile colors
*/
private static final int NUM_OF_TILE_COLORS = 8;
/**
* The letter bitmap
*/
private final Bitmap mBitmap;
/**
* The background color of the letter bitmap
*/
private final Integer mColor;
/**
* Constructor for <code>LetterTileProvider</code>
*
* @param context The {@link Context} to use
* @param displayName The name used to create the letter for the tile
* @param key The key used to generate the background color for the tile
* @param tileLetterFontSize The font size used to display the letter
* @param width The desired width of the tile
* @param height The desired height of the tile
* @param backgroundColor (optional) color to use for background.
* @param textColor (optional) color to use for text.
*/
public LetterBitmap(Context context, String displayName, String key, int tileLetterFontSize,
int width, int height, Integer backgroundColor, Integer textColor) {
TextPaint paint = new TextPaint();
if (textColor != null) {
paint.setColor(textColor);
} else {
paint.setColor(Color.WHITE);
}
paint.setTextAlign(Paint.Align.CENTER);
paint.setAntiAlias(true);
paint.setTextSize(tileLetterFontSize);
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
if (backgroundColor == null) {
mColor = getDefaultColor(context, key);
} else {
mColor = backgroundColor;
}
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
String firstChar = displayName.substring(0, 1).toUpperCase();
int firstCharEnd = 2;
while (firstCharEnd <= displayName.length()) {
// Test for the longest render-able string
// But ignore containing only a-Z0-9 to not render things like ffi as a single character
String test = displayName.substring(0, firstCharEnd);
if (!isAlphabetical(test) && PaintCompat.hasGlyph(paint, test)) {
firstChar = test;
}
firstCharEnd++;
}
Log.d("LetterBitmap", "using sequence " + firstChar + " to render first char which has length " + firstChar.length());
final Canvas c = new Canvas();
c.setBitmap(mBitmap);
c.drawColor(mColor);
Rect bounds = new Rect();
paint.getTextBounds(firstChar, 0, firstChar.length(), bounds);
c.drawText(firstChar,
0, firstChar.length(),
width / 2.0f, (height - (bounds.bottom + bounds.top)) / 2.0f
, paint);
}
/**
* @return A {@link Bitmap} that contains a letter used in the English
* alphabet or digit, if there is no letter or digit available, a
* default image is shown instead
*/
public Bitmap getLetterTile() {
return mBitmap;
}
/**
* @return background color used for letter title.
*/
public int getBackgroundColor() {
return mColor;
}
/**
* @param key The key used to generate the tile color
* @return A new or previously chosen color for <code>key</code> used as the
* tile background color
*/
private static int pickColor(String key, TypedArray colors) {
// String.hashCode() is not supposed to change across java versions, so
// this should guarantee the same key always maps to the same color
final int color = Math.abs(key.hashCode()) % NUM_OF_TILE_COLORS;
return colors.getColor(color, Color.BLACK);
}
private static boolean isAlphabetical(String string) {
return string.matches("[a-zA-Z0-9]*");
}
/**
* Determine the color which the letter tile will use if no default
* color is provided.
*/
public static int getDefaultColor(Context context, String key) {
final Resources res = context.getResources();
TypedArray colors = res.obtainTypedArray(R.array.letter_tile_colors);
int color = pickColor(key, colors);
colors.recycle();
return color;
}
}

View File

@@ -0,0 +1,136 @@
package protect.card_locker
import android.content.Context
import android.content.res.TypedArray
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.Typeface
import android.text.TextPaint
import android.util.Log
import androidx.core.graphics.PaintCompat
import java.util.Locale
import kotlin.math.abs
/**
* Original from https://github.com/andOTP/andOTP/blob/master/app/src/main/java/org/shadowice/flocke/andotp/Utilities/LetterBitmap.java
* which was originally from http://stackoverflow.com/questions/23122088/colored-boxed-with-letters-a-la-gmail
* Used to create a {@link Bitmap} that contains a letter used in the English
* alphabet or digit, if there is no letter or digit available, a default image
* is shown instead.
*
* @constructor Constructor for <code>LetterTileProvider</code>
* @param context The {@link Context} to use
* @param displayName The name used to create the letter for the tile
* @param key The key used to generate the background color for the tile
* @param tileLetterFontSize The font size used to display the letter
* @param width The desired width of the tile
* @param height The desired height of the tile
* @param backgroundColor (optional) color to use for background.
* @param textColor (optional) color to use for text.
*/
class LetterBitmap(
context: Context, displayName: String, key: String, tileLetterFontSize: Int,
width: Int, height: Int, backgroundColor: Int?, textColor: Int?
) {
/**
* A {@link Bitmap} that contains a letter used in the English
* alphabet or digit, if there is no letter or digit available, a
* default image is shown instead
*/
private val letterTile: Bitmap
/**
* The background color of the letter bitmap
*/
private val mColor: Int
init {
val paint = TextPaint().apply {
color = textColor ?: Color.WHITE
textAlign = Paint.Align.CENTER
isAntiAlias = true
textSize = tileLetterFontSize.toFloat()
typeface = Typeface.defaultFromStyle(Typeface.BOLD)
}
mColor = backgroundColor ?: getDefaultColor(context, key)
this.letterTile = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
var firstChar = displayName.substring(0, 1).uppercase(Locale.getDefault())
var firstCharEnd = 2
while (firstCharEnd <= displayName.length) {
// Test for the longest render-able string
// But ignore containing only a-Z0-9 to not render things like ffi as a single character
val test = displayName.substring(0, firstCharEnd)
if (!isAlphabetical(test) && PaintCompat.hasGlyph(paint, test)) {
firstChar = test
}
firstCharEnd++
}
Log.d(
"LetterBitmap",
"using sequence $firstChar to render first char which has length ${firstChar.length}"
)
Canvas().apply {
setBitmap(this@LetterBitmap.letterTile)
drawColor(mColor)
val bounds = Rect()
paint.getTextBounds(firstChar, 0, firstChar.length, bounds)
drawText(
firstChar,
0, firstChar.length,
width / 2.0f, (height - (bounds.bottom + bounds.top)) / 2.0f,
paint
)
}
}
val backgroundColor: Int
/**
* @return background color used for letter title.
*/
get() = mColor
fun getLetterTile(): Bitmap {
return letterTile
}
companion object {
/**
* @param key The key used to generate the tile color
* @return A new or previously chosen color for `key` used as the
* tile background color
*/
private fun pickColor(key: String, colors: TypedArray): Int {
// String.hashCode() is not supposed to change across java versions, so
// this should guarantee the same key always maps to the same color
val color = abs(key.hashCode()) % colors.length()
return colors.getColor(color, Color.BLACK)
}
private fun isAlphabetical(string: String): Boolean {
return string.matches("[a-zA-Z0-9]*".toRegex())
}
/**
* Determine the color which the letter tile will use if no default
* color is provided.
*/
fun getDefaultColor(context: Context, key: String): Int {
val res = context.resources
val colors = res.obtainTypedArray(R.array.letter_tile_colors)
val color: Int = pickColor(key, colors)
colors.recycle()
return color
}
}
}

View File

@@ -101,7 +101,8 @@ class ListWidget : AppWidgetProvider() {
setInt(R.id.item_container_foreground, "setBackgroundColor", headerColor)
val icon = loyaltyCard.getImageThumbnail(context)
// setImageViewIcon is not supported on Android 5, so force Android 5 down the text path
if (icon != null && Build.VERSION.SDK_INT >= 23) {
// FIXME: The icon flow causes a crash up to Android 12L, so SDK_INT is forced up from 23 to 33
if (icon != null && Build.VERSION.SDK_INT >= 32) {
setInt(R.id.item_container_foreground, "setBackgroundColor", foreground)
setImageViewIcon(R.id.item_image, Icon.createWithBitmap(icon))
setViewVisibility(R.id.item_text, View.INVISIBLE)

View File

@@ -123,8 +123,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
ChipGroup groupsChips;
AutoCompleteTextView validFromField;
AutoCompleteTextView expiryField;
EditText balanceField;
AutoCompleteTextView balanceCurrencyField;
EditText balanceField;
TextView cardIdFieldView;
AutoCompleteTextView barcodeIdField;
AutoCompleteTextView barcodeTypeField;
@@ -148,9 +148,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
boolean onRestoring = false;
AlertDialog confirmExitDialog = null;
boolean validBalance = true;
HashMap<String, Currency> currencies = new HashMap<>();
HashMap<String, String> currencySymbols = new HashMap<>();
boolean validBalance = true;
ActivityResultLauncher<Uri> mPhotoTakerLauncher;
ActivityResultLauncher<Intent> mPhotoPickerLauncher;
@@ -193,14 +193,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
viewModel.setHasChanged(true);
}
protected void setLoyaltyCardBalance(@NonNull BigDecimal balance) {
viewModel.getLoyaltyCard().setBalance(balance);
protected void setLoyaltyCardBalanceType(@Nullable Currency balanceType) {
viewModel.getLoyaltyCard().setBalanceType(balanceType);
viewModel.setHasChanged(true);
}
protected void setLoyaltyCardBalanceType(@Nullable Currency balanceType) {
viewModel.getLoyaltyCard().setBalanceType(balanceType);
protected void setLoyaltyCardBalance(@NonNull BigDecimal balance) {
viewModel.getLoyaltyCard().setBalance(balance);
viewModel.setHasChanged(true);
}
@@ -329,8 +329,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
groupsChips = binding.groupChips;
validFromField = binding.validFromField;
expiryField = binding.expiryField;
balanceField = binding.balanceField;
balanceCurrencyField = binding.balanceCurrencyField;
balanceField = binding.balanceField;
cardIdFieldView = binding.cardIdView;
barcodeIdField = binding.barcodeIdField;
barcodeTypeField = binding.barcodeTypeField;
@@ -373,33 +373,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
setMaterialDatePickerResultListener();
balanceField.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus && !onResuming && !onRestoring) {
if (balanceField.getText().toString().isEmpty()) {
setLoyaltyCardBalance(BigDecimal.valueOf(0));
}
balanceField.setText(Utils.formatBalanceWithoutCurrencySymbol(viewModel.getLoyaltyCard().balance, viewModel.getLoyaltyCard().balanceType));
}
});
balanceField.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (onResuming || onRestoring) return;
try {
BigDecimal balance = Utils.parseBalance(s.toString(), viewModel.getLoyaltyCard().balanceType);
setLoyaltyCardBalance(balance);
balanceField.setError(null);
validBalance = true;
} catch (ParseException e) {
e.printStackTrace();
balanceField.setError(getString(R.string.balanceParsingFailed));
validBalance = false;
}
}
});
balanceCurrencyField.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
@@ -452,6 +425,33 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
});
balanceField.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus && !onResuming && !onRestoring) {
if (balanceField.getText().toString().isEmpty()) {
setLoyaltyCardBalance(BigDecimal.valueOf(0));
}
balanceField.setText(Utils.formatBalanceWithoutCurrencySymbol(viewModel.getLoyaltyCard().balance, viewModel.getLoyaltyCard().balanceType));
}
});
balanceField.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (onResuming || onRestoring) return;
try {
BigDecimal balance = Utils.parseBalance(s.toString(), viewModel.getLoyaltyCard().balanceType);
setLoyaltyCardBalance(balance);
balanceField.setError(null);
validBalance = true;
} catch (ParseException e) {
e.printStackTrace();
balanceField.setError(getString(R.string.balanceParsingFailed));
validBalance = false;
}
}
});
cardIdFieldView.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -719,7 +719,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
int colorOnSurface = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSurface, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
int colorBackground = MaterialColors.getColor(this, android.R.attr.colorBackground, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
mCropperOptions.setToolbarColor(colorSurface);
mCropperOptions.setStatusBarColor(colorSurface);
mCropperOptions.setToolbarWidgetColor(colorOnSurface);
mCropperOptions.setRootViewBackgroundColor(colorBackground);
// set tool tip to be the darker of primary color

View File

@@ -4,6 +4,12 @@ import android.app.Application;
import androidx.appcompat.app.AppCompatDelegate;
import org.acra.ACRA;
import org.acra.config.CoreConfigurationBuilder;
import org.acra.config.DialogConfigurationBuilder;
import org.acra.config.MailSenderConfigurationBuilder;
import org.acra.data.StringFormat;
import protect.card_locker.preferences.Settings;
public class LoyaltyCardLockerApplication extends Application {
@@ -12,6 +18,27 @@ public class LoyaltyCardLockerApplication extends Application {
public void onCreate() {
super.onCreate();
// Initialize crash reporter (if enabled)
if (BuildConfig.useAcraCrashReporter) {
ACRA.init(this, new CoreConfigurationBuilder()
//core configuration:
.withBuildConfigClass(BuildConfig.class)
.withReportFormat(StringFormat.KEY_VALUE_LIST)
.withPluginConfigurations(
new DialogConfigurationBuilder()
.withText(String.format(getString(R.string.acra_catima_has_crashed), getString(R.string.app_name)))
.withCommentPrompt(getString(R.string.acra_explain_crash))
.withResTheme(R.style.AppTheme)
.build(),
new MailSenderConfigurationBuilder()
.withMailTo("acra-crash@catima.app")
.withSubject(String.format(getString(R.string.acra_crash_email_subject), getString(R.string.app_name)))
.build()
)
);
}
// Set theme
Settings settings = new Settings(this);
AppCompatDelegate.setDefaultNightMode(settings.getTheme());
}

View File

@@ -1,8 +1,9 @@
package protect.card_locker;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.database.sqlite.SQLiteDatabase;
@@ -262,19 +263,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
settings = new Settings(this);
String cardOrientation = settings.getCardViewOrientation();
if (cardOrientation.equals(getString(R.string.settings_key_follow_sensor_orientation))) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
} else if (cardOrientation.equals(getString(R.string.settings_key_lock_on_opening_orientation))) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
} else if (cardOrientation.equals(getString(R.string.settings_key_portrait_orientation))) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} else if (cardOrientation.equals(getString(R.string.settings_key_landscape_orientation))) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
if (savedInstanceState != null) {
mainImageIndex = savedInstanceState.getInt(STATE_IMAGEINDEX);
isFullscreen = savedInstanceState.getBoolean(STATE_FULLSCREEN);
@@ -717,10 +705,22 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(LoyaltyCardViewActivity.this);
builder.setTitle(R.string.cardId);
builder.setView(cardIdView);
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> dialogInterface.dismiss());
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss());
builder.setNeutralButton(R.string.copy_value, (dialog, which) -> {
copyCardIdToClipboard();
});
AlertDialog dialog = builder.create();
dialog.show();
});
binding.mainImageDescription.setOnLongClickListener(view -> {
if (mainImageIndex != 0) {
// Don't copy to clipboard, we're showing something else
return false;
}
copyCardIdToClipboard();
return true;
});
int backgroundHeaderColor = Utils.getHeaderColor(this, loyaltyCard);
@@ -1098,6 +1098,12 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
private void setMainImagePreviousNextButtons() {
// Ensure the main image index is valid. After a card update, some images (front/back/barcode)
// may have been removed, so the index should not exceed the number of available images.
if(mainImageIndex > imageTypes.size() - 1){
mainImageIndex = 0;
}
if (imageTypes.size() < 2) {
binding.mainLeftButton.setVisibility(View.INVISIBLE);
binding.mainRightButton.setVisibility(View.INVISIBLE);
@@ -1254,4 +1260,20 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
);
}
}
private void copyCardIdToClipboard() {
// Take the value thats already displayed to the user
String value = loyaltyCard.cardId;
if (value == null || value.isEmpty()) {
Toast.makeText(this, R.string.nothing_to_copy, Toast.LENGTH_SHORT).show();
return;
}
ClipboardManager cm = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(getString(R.string.cardId), value);
cm.setPrimaryClip(clip);
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
}
}

View File

@@ -1,880 +0,0 @@
package protect.card_locker;
import android.app.Activity;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.CursorIndexOutOfBoundsException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.SearchView;
import androidx.core.splashscreen.SplashScreen;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import protect.card_locker.databinding.ContentMainBinding;
import protect.card_locker.databinding.MainActivityBinding;
import protect.card_locker.databinding.SortingOptionBinding;
import protect.card_locker.preferences.Settings;
import protect.card_locker.preferences.SettingsActivity;
public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
private MainActivityBinding binding;
private ContentMainBinding contentMainBinding;
private static final String TAG = "Catima";
public static final String RESTART_ACTIVITY_INTENT = "restart_activity_intent";
private static final int MEDIUM_SCALE_FACTOR_DIP = 460;
static final String STATE_SEARCH_QUERY = "SEARCH_QUERY";
private SQLiteDatabase mDatabase;
private LoyaltyCardCursorAdapter mAdapter;
private ActionMode mCurrentActionMode;
private SearchView mSearchView;
private int mLoyaltyCardCount = 0;
protected String mFilter = "";
private String currentQuery = "";
private String finalQuery = "";
protected Object mGroup = null;
protected DBHelper.LoyaltyCardOrder mOrder = DBHelper.LoyaltyCardOrder.Alpha;
protected DBHelper.LoyaltyCardOrderDirection mOrderDirection = DBHelper.LoyaltyCardOrderDirection.Ascending;
protected int selectedTab = 0;
private RecyclerView mCardList;
private View mHelpSection;
private View mNoMatchingCardsText;
private View mNoGroupCardsText;
private TabLayout groupsTabLayout;
private Runnable mUpdateLoyaltyCardListRunnable;
private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
private ActivityResultLauncher<Intent> mSettingsLauncher;
private ActionMode.Callback mCurrentActionModeCallback = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode inputMode, Menu inputMenu) {
inputMode.getMenuInflater().inflate(R.menu.card_longclick_menu, inputMenu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode inputMode, Menu inputMenu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode inputMode, MenuItem inputItem) {
if (inputItem.getItemId() == R.id.action_share) {
final ImportURIHelper importURIHelper = new ImportURIHelper(MainActivity.this);
try {
importURIHelper.startShareIntent(mAdapter.getSelectedItems());
} catch (UnsupportedEncodingException e) {
Toast.makeText(MainActivity.this, R.string.failedGeneratingShareURL, Toast.LENGTH_LONG).show();
e.printStackTrace();
}
inputMode.finish();
return true;
} else if (inputItem.getItemId() == R.id.action_edit) {
if (mAdapter.getSelectedItemCount() != 1) {
throw new IllegalArgumentException("Cannot edit more than 1 card at a time");
}
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle bundle = new Bundle();
bundle.putInt(LoyaltyCardEditActivity.BUNDLE_ID, mAdapter.getSelectedItems().get(0).id);
bundle.putBoolean(LoyaltyCardEditActivity.BUNDLE_UPDATE, true);
intent.putExtras(bundle);
startActivity(intent);
inputMode.finish();
return true;
} else if (inputItem.getItemId() == R.id.action_delete) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(MainActivity.this);
// The following may seem weird, but it is necessary to give translators enough flexibility.
// For example, in Russian, Android's plural quantity "one" actually refers to "any number ending on 1 but not ending in 11".
// So while in English the extra non-plural form seems unnecessary duplication, it is necessary to give translators enough flexibility.
// In here, we use the plain string when meaning exactly 1, and otherwise use the plural forms
if (mAdapter.getSelectedItemCount() == 1) {
builder.setTitle(R.string.deleteTitle);
builder.setMessage(R.string.deleteConfirmation);
} else {
builder.setTitle(getResources().getQuantityString(R.plurals.deleteCardsTitle, mAdapter.getSelectedItemCount(), mAdapter.getSelectedItemCount()));
builder.setMessage(getResources().getQuantityString(R.plurals.deleteCardsConfirmation, mAdapter.getSelectedItemCount(), mAdapter.getSelectedItemCount()));
}
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Deleting card: " + loyaltyCard.id);
DBHelper.deleteLoyaltyCard(mDatabase, MainActivity.this, loyaltyCard.id);
ShortcutHelper.removeShortcut(MainActivity.this, loyaltyCard.id);
}
TabLayout.Tab tab = groupsTabLayout.getTabAt(selectedTab);
mGroup = tab != null ? tab.getTag() : null;
updateLoyaltyCardList(true);
dialog.dismiss();
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
AlertDialog dialog = builder.create();
dialog.show();
return true;
} else if (inputItem.getItemId() == R.id.action_archive) {
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Archiving card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 1);
ShortcutHelper.removeShortcut(MainActivity.this, loyaltyCard.id);
updateLoyaltyCardList(false);
inputMode.finish();
invalidateOptionsMenu();
}
return true;
} else if (inputItem.getItemId() == R.id.action_unarchive) {
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Unarchiving card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 0);
updateLoyaltyCardList(false);
inputMode.finish();
invalidateOptionsMenu();
}
return true;
} else if (inputItem.getItemId() == R.id.action_star) {
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Starring card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 1);
updateLoyaltyCardList(false);
inputMode.finish();
}
return true;
} else if (inputItem.getItemId() == R.id.action_unstar) {
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Unstarring card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 0);
updateLoyaltyCardList(false);
inputMode.finish();
}
return true;
}
return false;
}
@Override
public void onDestroyActionMode(ActionMode inputMode) {
mAdapter.clearSelections();
mCurrentActionMode = null;
}
};
@Override
protected void onCreate(Bundle inputSavedInstanceState) {
SplashScreen.installSplashScreen(this);
super.onCreate(inputSavedInstanceState);
// Delete old cache files
// These could be temporary images for the cropper, temporary images in LoyaltyCard toBundle/writeParcel/ etc.
new Thread(() -> {
long twentyFourHoursAgo = System.currentTimeMillis() - (1000 * 60 * 60 * 24);
File[] tempFiles = getCacheDir().listFiles();
if (tempFiles == null) {
Log.e(TAG, "getCacheDir().listFiles() somehow returned null, this should never happen... Skipping cache cleanup...");
return;
}
for (File file : tempFiles) {
if (file.lastModified() < twentyFourHoursAgo) {
if (!file.delete()) {
Log.w(TAG, "Failed to delete cache file " + file.getPath());
}
};
}
}).start();
// We should extract the share intent after we called the super.onCreate as it may need to spawn a dialog window and the app needs to be initialized to not crash
extractIntentFields(getIntent());
binding = MainActivityBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
Utils.applyWindowInsets(binding.getRoot());
setSupportActionBar(binding.toolbar);
groupsTabLayout = binding.groups;
contentMainBinding = ContentMainBinding.bind(binding.include.getRoot());
mDatabase = new DBHelper(this).getWritableDatabase();
mUpdateLoyaltyCardListRunnable = () -> {
updateLoyaltyCardList(false);
};
groupsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
selectedTab = tab.getPosition();
Log.d("onTabSelected", "Tab Position " + tab.getPosition());
mGroup = tab.getTag();
updateLoyaltyCardList(false);
// Store active tab in Shared Preference to restore next app launch
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
Context.MODE_PRIVATE);
SharedPreferences.Editor activeTabPrefEditor = activeTabPref.edit();
activeTabPrefEditor.putInt(getString(R.string.sharedpreference_active_tab), tab.getPosition());
activeTabPrefEditor.apply();
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
mHelpSection = contentMainBinding.helpSection;
mNoMatchingCardsText = contentMainBinding.noMatchingCardsText;
mNoGroupCardsText = contentMainBinding.noGroupCardsText;
mCardList = contentMainBinding.list;
mAdapter = new LoyaltyCardCursorAdapter(this, null, this, mUpdateLoyaltyCardListRunnable);
mCardList.setAdapter(mAdapter);
registerForContextMenu(mCardList);
mBarcodeScannerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
// Exit early if the user cancelled the scan (pressed back/home)
if (result.getResultCode() != RESULT_OK) {
return;
}
Intent editIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
editIntent.putExtras(result.getData().getExtras());
startActivity(editIntent);
});
mSettingsLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent intent = result.getData();
if (intent != null && intent.getBooleanExtra(RESTART_ACTIVITY_INTENT, false)) {
recreate();
}
}
});
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (mSearchView != null && !mSearchView.isIconified()) {
mSearchView.setIconified(true);
} else {
finish();
}
}
});
}
@Override
protected void onResume() {
super.onResume();
if (mCurrentActionMode != null) {
mAdapter.clearSelections();
mCurrentActionMode.finish();
}
if (mSearchView != null && !mSearchView.isIconified()) {
mFilter = mSearchView.getQuery().toString();
}
// Start of active tab logic
updateTabGroups(groupsTabLayout);
// Restore selected tab from Shared Preference
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
Context.MODE_PRIVATE);
selectedTab = activeTabPref.getInt(getString(R.string.sharedpreference_active_tab), 0);
// Restore sort preferences from Shared Preferences
mOrder = Utils.getLoyaltyCardOrder(this);
mOrderDirection = Utils.getLoyaltyCardOrderDirection(this);
mGroup = null;
if (groupsTabLayout.getTabCount() != 0) {
TabLayout.Tab tab = groupsTabLayout.getTabAt(selectedTab);
if (tab == null) {
tab = groupsTabLayout.getTabAt(0);
}
groupsTabLayout.selectTab(tab);
assert tab != null;
mGroup = tab.getTag();
} else {
scaleScreen();
}
updateLoyaltyCardList(true);
// End of active tab logic
FloatingActionButton addButton = binding.fabAdd;
addButton.setOnClickListener(v -> {
Intent intent = new Intent(getApplicationContext(), ScanActivity.class);
Bundle bundle = new Bundle();
if (selectedTab != 0) {
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, groupsTabLayout.getTabAt(selectedTab).getText().toString());
}
intent.putExtras(bundle);
mBarcodeScannerLauncher.launch(intent);
});
addButton.bringToFront();
var layoutManager = (GridLayoutManager) mCardList.getLayoutManager();
if (layoutManager != null) {
var settings = new Settings(this);
layoutManager.setSpanCount(settings.getPreferredColumnCount());
}
}
private void displayCardSetupOptions(Menu menu, boolean shouldShow) {
for (int id : new int[]{R.id.action_search, R.id.action_display_options, R.id.action_sort}) {
menu.findItem(id).setVisible(shouldShow);
}
}
private void updateLoyaltyCardCount() {
mLoyaltyCardCount = DBHelper.getLoyaltyCardCount(mDatabase);
}
private void updateLoyaltyCardList(boolean updateCount) {
Group group = null;
if (mGroup != null) {
group = (Group) mGroup;
}
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase, mFilter, group, mOrder, mOrderDirection, mAdapter.showingArchivedCards() ? DBHelper.LoyaltyCardArchiveFilter.All : DBHelper.LoyaltyCardArchiveFilter.Unarchived));
if (updateCount) {
updateLoyaltyCardCount();
// Update menu icons if necessary
invalidateOptionsMenu();
}
if (mLoyaltyCardCount > 0) {
// We want the cardList to be visible regardless of the filtered match count
// to ensure that the noMatchingCardsText doesn't end up being shown below
// the keyboard
mHelpSection.setVisibility(View.GONE);
mNoGroupCardsText.setVisibility(View.GONE);
if (mAdapter.getItemCount() > 0) {
mCardList.setVisibility(View.VISIBLE);
mNoMatchingCardsText.setVisibility(View.GONE);
} else {
mCardList.setVisibility(View.GONE);
if (!mFilter.isEmpty()) {
// Actual Empty Search Result
mNoMatchingCardsText.setVisibility(View.VISIBLE);
mNoGroupCardsText.setVisibility(View.GONE);
} else {
// Group Tab with no Group Cards
mNoMatchingCardsText.setVisibility(View.GONE);
mNoGroupCardsText.setVisibility(View.VISIBLE);
}
}
} else {
mCardList.setVisibility(View.GONE);
mHelpSection.setVisibility(View.VISIBLE);
mNoMatchingCardsText.setVisibility(View.GONE);
mNoGroupCardsText.setVisibility(View.GONE);
}
if (mCurrentActionMode != null) {
mCurrentActionMode.finish();
}
new ListWidget().updateAll(mAdapter.mContext);
}
private void processParseResultList(List<ParseResult> parseResultList, String group, boolean closeAppOnNoBarcode) {
if (parseResultList.isEmpty()) {
throw new IllegalArgumentException("parseResultList may not be empty");
}
Utils.makeUserChooseParseResultFromList(MainActivity.this, parseResultList, new ParseResultListDisambiguatorCallback() {
@Override
public void onUserChoseParseResult(ParseResult parseResult) {
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle bundle = parseResult.toLoyaltyCardBundle(MainActivity.this);
if (group != null) {
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
}
intent.putExtras(bundle);
startActivity(intent);
}
@Override
public void onUserDismissedSelector() {
if (closeAppOnNoBarcode) {
finish();
}
}
});
}
private void onSharedIntent(Intent intent) {
String receivedAction = intent.getAction();
String receivedType = intent.getType();
if (receivedAction == null || receivedType == null) {
return;
}
List<ParseResult> parseResultList;
// Check for shared text
if (receivedAction.equals(Intent.ACTION_SEND) && receivedType.equals("text/plain")) {
LoyaltyCard loyaltyCard = new LoyaltyCard();
loyaltyCard.setCardId(intent.getStringExtra(Intent.EXTRA_TEXT));
parseResultList = Collections.singletonList(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
} else {
// Parse whatever file was sent, regardless of opening or sharing
Uri data;
if (receivedAction.equals(Intent.ACTION_VIEW)) {
data = intent.getData();
} else if (receivedAction.equals(Intent.ACTION_SEND)) {
data = intent.getParcelableExtra(Intent.EXTRA_STREAM);
} else {
Log.e(TAG, "Wrong action type to parse intent");
return;
}
if (receivedType.startsWith("image/")) {
parseResultList = Utils.retrieveBarcodesFromImage(this, data);
} else if (receivedType.equals("application/pdf")) {
parseResultList = Utils.retrieveBarcodesFromPdf(this, data);
} else if (Arrays.asList("application/vnd.apple.pkpass", "application/vnd-com.apple.pkpass").contains(receivedType)) {
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data);
} else if (receivedType.equals("application/vnd.espass-espass")) {
// FIXME: espass is not pkpass
// However, several users stated in https://github.com/CatimaLoyalty/Android/issues/2197 that the formats are extremely similar to the point they could rename an .espass file to .pkpass and have it imported
// So it makes sense to "unofficially" treat it as a PKPASS for now, even though not completely correct
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data);
} else {
Log.e(TAG, "Wrong mime-type");
return;
}
}
// Give up if we should parse but there is nothing to parse
if (parseResultList == null || parseResultList.isEmpty()) {
finish();
return;
}
processParseResultList(parseResultList, null, true);
}
private void extractIntentFields(Intent intent) {
onSharedIntent(intent);
}
public void updateTabGroups(TabLayout groupsTabLayout) {
List<Group> newGroups = DBHelper.getGroups(mDatabase);
if (newGroups.size() == 0) {
groupsTabLayout.removeAllTabs();
groupsTabLayout.setVisibility(View.GONE);
return;
}
groupsTabLayout.removeAllTabs();
TabLayout.Tab allTab = groupsTabLayout.newTab();
allTab.setText(R.string.all);
allTab.setTag(null);
groupsTabLayout.addTab(allTab, false);
for (Group group : newGroups) {
TabLayout.Tab tab = groupsTabLayout.newTab();
tab.setText(group._id);
tab.setTag(group);
groupsTabLayout.addTab(tab, false);
}
groupsTabLayout.setVisibility(View.VISIBLE);
}
@Override
// Saving currentQuery to finalQuery for user, this will be used to restore search history, happens when user clicks a card from list
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
finalQuery = currentQuery;
// Putting the query also into outState for later use in onRestoreInstanceState when rotating screen
if (mSearchView != null) {
outState.putString(STATE_SEARCH_QUERY, finalQuery);
}
}
@Override
// Restoring instance state when rotation of screen happens with the goal to restore search query for user
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
finalQuery = savedInstanceState.getString(STATE_SEARCH_QUERY, "");
}
@Override
public boolean onCreateOptionsMenu(Menu inputMenu) {
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
displayCardSetupOptions(inputMenu, mLoyaltyCardCount > 0);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
if (searchManager != null) {
MenuItem searchMenuItem = inputMenu.findItem(R.id.action_search);
mSearchView = (SearchView) searchMenuItem.getActionView();
mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
mSearchView.setSubmitButtonEnabled(false);
mSearchView.setOnCloseListener(() -> {
invalidateOptionsMenu();
return false;
});
/*
* On Android 13 and later, pressing Back while the search view is open hides the keyboard
* and collapses the search view at the same time.
* This brings back the old behavior on Android 12 and lower: pressing Back once
* hides the keyboard, press again while keyboard is hidden to collapse the search view.
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
searchMenuItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(@NonNull MenuItem item) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(@NonNull MenuItem item) {
if (mSearchView.hasFocus()) {
mSearchView.clearFocus();
return false;
}
currentQuery = "";
mFilter = "";
updateLoyaltyCardList(false);
return true;
}
});
}
mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
mFilter = newText;
// New logic to ensure search history after coming back from picked card - user will see the last search query
if (newText.isEmpty()) {
if(!finalQuery.isEmpty()){
// Setting the query text for user after coming back from picked card from finalQuery
mSearchView.setQuery(finalQuery, false);
}
else if(!currentQuery.isEmpty()){
// Else if is needed in case user deletes search - expected behaviour is to show all cards
currentQuery = "";
mSearchView.setQuery(currentQuery, false);
}
} else {
// Setting search query each time user changes the text in search to temporary variable to be used later in finalQuery String which will be used to restore search history
currentQuery = newText;
}
TabLayout.Tab currentTab = groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition());
mGroup = currentTab != null ? currentTab.getTag() : null;
updateLoyaltyCardList(false);
return true;
}
});
// Check if we came from a picked card back to search, in that case we want to show the search view with previous search query
if(!finalQuery.isEmpty()){
// Expand the search view to show the query
searchMenuItem.expandActionView();
// Setting the query text to empty String due to behaviour of onQueryTextChange after coming back from picked card - onQueryTextChange is called automatically without users interaction
finalQuery = "";
mSearchView.setQuery(currentQuery, false);
}
}
return super.onCreateOptionsMenu(inputMenu);
}
@Override
public boolean onOptionsItemSelected(MenuItem inputItem) {
int id = inputItem.getItemId();
if (id == android.R.id.home) {
getOnBackPressedDispatcher().onBackPressed();
}
if (id == R.id.action_display_options) {
mAdapter.showDisplayOptionsDialog();
invalidateOptionsMenu();
return true;
}
if (id == R.id.action_sort) {
AtomicInteger currentIndex = new AtomicInteger();
List<DBHelper.LoyaltyCardOrder> loyaltyCardOrders = Arrays.asList(DBHelper.LoyaltyCardOrder.values());
for (int i = 0; i < loyaltyCardOrders.size(); i++) {
if (mOrder == loyaltyCardOrders.get(i)) {
currentIndex.set(i);
break;
}
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(MainActivity.this);
builder.setTitle(R.string.sort_by);
SortingOptionBinding sortingOptionBinding = SortingOptionBinding
.inflate(LayoutInflater.from(MainActivity.this), null, false);
final View customLayout = sortingOptionBinding.getRoot();
builder.setView(customLayout);
CheckBox showReversed = sortingOptionBinding.checkBoxReverse;
showReversed.setChecked(mOrderDirection == DBHelper.LoyaltyCardOrderDirection.Descending);
builder.setSingleChoiceItems(R.array.sort_types_array, currentIndex.get(), (dialog, which) -> currentIndex.set(which));
builder.setPositiveButton(R.string.sort, (dialog, which) -> {
setSort(
loyaltyCardOrders.get(currentIndex.get()),
showReversed.isChecked() ? DBHelper.LoyaltyCardOrderDirection.Descending : DBHelper.LoyaltyCardOrderDirection.Ascending
);
new ListWidget().updateAll(this);
dialog.dismiss();
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
AlertDialog dialog = builder.create();
dialog.show();
return true;
}
if (id == R.id.action_manage_groups) {
Intent i = new Intent(getApplicationContext(), ManageGroupsActivity.class);
startActivity(i);
return true;
}
if (id == R.id.action_import_export) {
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
startActivity(i);
return true;
}
if (id == R.id.action_settings) {
Intent i = new Intent(getApplicationContext(), SettingsActivity.class);
mSettingsLauncher.launch(i);
return true;
}
if (id == R.id.action_about) {
Intent i = new Intent(getApplicationContext(), AboutActivity.class);
startActivity(i);
return true;
}
return super.onOptionsItemSelected(inputItem);
}
private void setSort(DBHelper.LoyaltyCardOrder order, DBHelper.LoyaltyCardOrderDirection direction) {
// Update values
mOrder = order;
mOrderDirection = direction;
// Store in Shared Preference to restore next app launch
SharedPreferences sortPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_sort),
Context.MODE_PRIVATE);
SharedPreferences.Editor sortPrefEditor = sortPref.edit();
sortPrefEditor.putString(getString(R.string.sharedpreference_sort_order), order.name());
sortPrefEditor.putString(getString(R.string.sharedpreference_sort_direction), direction.name());
sortPrefEditor.apply();
// Update card list
updateLoyaltyCardList(false);
}
@Override
public void onRowLongClicked(int inputPosition) {
enableActionMode(inputPosition);
}
private void enableActionMode(int inputPosition) {
if (mCurrentActionMode == null) {
mCurrentActionMode = startSupportActionMode(mCurrentActionModeCallback);
}
toggleSelection(inputPosition);
}
private void scaleScreen() {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int screenHeight = displayMetrics.heightPixels;
float mediumSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,MEDIUM_SCALE_FACTOR_DIP,getResources().getDisplayMetrics());
boolean shouldScaleSmaller = screenHeight < mediumSizePx;
binding.include.welcomeIcon.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
}
private void toggleSelection(int inputPosition) {
mAdapter.toggleSelection(inputPosition);
int count = mAdapter.getSelectedItemCount();
if (count == 0) {
mCurrentActionMode.finish();
} else {
mCurrentActionMode.setTitle(getResources().getQuantityString(R.plurals.selectedCardCount, count, count));
MenuItem editItem = mCurrentActionMode.getMenu().findItem(R.id.action_edit);
MenuItem archiveItem = mCurrentActionMode.getMenu().findItem(R.id.action_archive);
MenuItem unarchiveItem = mCurrentActionMode.getMenu().findItem(R.id.action_unarchive);
MenuItem starItem = mCurrentActionMode.getMenu().findItem(R.id.action_star);
MenuItem unstarItem = mCurrentActionMode.getMenu().findItem(R.id.action_unstar);
boolean hasStarred = false;
boolean hasUnstarred = false;
boolean hasArchived = false;
boolean hasUnarchived = false;
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
if (loyaltyCard.starStatus == 1) {
hasStarred = true;
} else {
hasUnstarred = true;
}
if (loyaltyCard.archiveStatus == 1) {
hasArchived = true;
} else {
hasUnarchived = true;
}
// We have all types, no need to keep checking
if (hasStarred && hasUnstarred && hasArchived && hasUnarchived) {
break;
}
}
unarchiveItem.setVisible(hasArchived);
archiveItem.setVisible(hasUnarchived);
if (count == 1) {
starItem.setVisible(!hasStarred);
unstarItem.setVisible(!hasUnstarred);
editItem.setVisible(true);
editItem.setEnabled(true);
} else {
starItem.setVisible(hasUnstarred);
unstarItem.setVisible(hasStarred);
editItem.setVisible(false);
editItem.setEnabled(false);
}
mCurrentActionMode.invalidate();
}
}
@Override
public void onRowClicked(int inputPosition) {
if (mAdapter.getSelectedItemCount() > 0) {
enableActionMode(inputPosition);
} else {
// FIXME
//
// There is a really nasty edge case that can happen when someone taps a card but right
// after it swipes (very small window, hard to reproduce). The cursor gets replaced and
// may not have a card at the ID number that is returned from onRowClicked.
//
// The proper fix, obviously, would involve makes sure an onFling can't happen while a
// click is being processed. Sadly, I have not yet found a way to make that possible.
LoyaltyCard loyaltyCard;
try {
loyaltyCard = mAdapter.getCard(inputPosition);
} catch (CursorIndexOutOfBoundsException e) {
Log.w(TAG, "Prevented crash from tap + swipe on ID " + inputPosition + ": " + e);
return;
}
Intent intent = new Intent(this, LoyaltyCardViewActivity.class);
intent.setAction("");
final Bundle b = new Bundle();
b.putInt(LoyaltyCardViewActivity.BUNDLE_ID, loyaltyCard.id);
ArrayList<Integer> cardList = new ArrayList<>();
for (int i = 0; i < mAdapter.getItemCount(); i++) {
cardList.add(mAdapter.getCard(i).id);
}
b.putIntegerArrayList(LoyaltyCardViewActivity.BUNDLE_CARDLIST, cardList);
intent.putExtras(b);
startActivity(intent);
}
}
}

View File

@@ -0,0 +1,945 @@
package protect.card_locker
import android.app.SearchManager
import android.content.DialogInterface
import android.content.Intent
import android.database.CursorIndexOutOfBoundsException
import android.database.sqlite.SQLiteDatabase
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import protect.card_locker.DBHelper.LoyaltyCardOrder
import protect.card_locker.DBHelper.LoyaltyCardOrderDirection
import protect.card_locker.LoyaltyCardCursorAdapter.CardAdapterListener
import protect.card_locker.databinding.ContentMainBinding
import protect.card_locker.databinding.MainActivityBinding
import protect.card_locker.databinding.SortingOptionBinding
import protect.card_locker.preferences.Settings
import protect.card_locker.preferences.SettingsActivity
import java.io.UnsupportedEncodingException
import java.util.concurrent.atomic.AtomicInteger
import androidx.core.content.edit
class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
private lateinit var binding: MainActivityBinding
private lateinit var contentMainBinding: ContentMainBinding
private lateinit var mDatabase: SQLiteDatabase
private lateinit var mAdapter: LoyaltyCardCursorAdapter
private var mCurrentActionMode: ActionMode? = null
private var mSearchView: SearchView? = null
private var mLoyaltyCardCount = 0
@JvmField
var mFilter: String = ""
private var currentQuery = ""
private var finalQuery = ""
private var mGroup: Any? = null
private var mOrder: LoyaltyCardOrder = LoyaltyCardOrder.Alpha
private var mOrderDirection: LoyaltyCardOrderDirection = LoyaltyCardOrderDirection.Ascending
private var selectedTab: Int = 0
private lateinit var groupsTabLayout: TabLayout
private lateinit var mUpdateLoyaltyCardListRunnable: Runnable
private lateinit var mBarcodeScannerLauncher: ActivityResultLauncher<Intent>
private lateinit var mSettingsLauncher: ActivityResultLauncher<Intent>
private val mCurrentActionModeCallback: ActionMode.Callback = object : ActionMode.Callback {
override fun onCreateActionMode(inputMode: ActionMode, inputMenu: Menu?): Boolean {
inputMode.menuInflater.inflate(R.menu.card_longclick_menu, inputMenu)
return true
}
override fun onPrepareActionMode(inputMode: ActionMode?, inputMenu: Menu?): Boolean {
return false
}
override fun onActionItemClicked(inputMode: ActionMode, inputItem: MenuItem): Boolean {
when (inputItem.itemId) {
R.id.action_share -> {
try {
ImportURIHelper(this@MainActivity).startShareIntent(mAdapter.getSelectedItems())
} catch (e: UnsupportedEncodingException) {
Toast.makeText(
this@MainActivity,
R.string.failedGeneratingShareURL,
Toast.LENGTH_LONG
).show()
e.printStackTrace()
}
inputMode.finish()
return true
}
R.id.action_edit -> {
require(mAdapter.selectedItemCount == 1) { "Cannot edit more than 1 card at a time" }
startActivity(
Intent(applicationContext, LoyaltyCardEditActivity::class.java).apply {
putExtras(Bundle().apply {
putInt(
LoyaltyCardEditActivity.BUNDLE_ID,
mAdapter.getSelectedItems()[0].id
)
putBoolean(LoyaltyCardEditActivity.BUNDLE_UPDATE, true)
})
}
)
inputMode.finish()
return true
}
R.id.action_duplicate -> {
require(mAdapter.selectedItemCount == 1) { "Cannot duplicate more than 1 card at a time" }
startActivity(
Intent(applicationContext, LoyaltyCardEditActivity::class.java).apply {
putExtras(Bundle().apply {
putInt(
LoyaltyCardEditActivity.BUNDLE_ID,
mAdapter.getSelectedItems()[0].id
)
putBoolean(LoyaltyCardEditActivity.BUNDLE_DUPLICATE_ID, true)
})
}
)
inputMode.finish()
return true
}
R.id.action_delete -> {
MaterialAlertDialogBuilder(this@MainActivity).apply {
// The following may seem weird, but it is necessary to give translators enough flexibility.
// For example, in Russian, Android's plural quantity "one" actually refers to "any number ending on 1 but not ending in 11".
// So while in English the extra non-plural form seems unnecessary duplication, it is necessary to give translators enough flexibility.
// In here, we use the plain string when meaning exactly 1, and otherwise use the plural forms
if (mAdapter.selectedItemCount == 1) {
setTitle(R.string.deleteTitle)
setMessage(R.string.deleteConfirmation)
} else {
setTitle(
getResources().getQuantityString(
R.plurals.deleteCardsTitle,
mAdapter.selectedItemCount,
mAdapter.selectedItemCount
)
)
setMessage(
getResources().getQuantityString(
R.plurals.deleteCardsConfirmation,
mAdapter.selectedItemCount,
mAdapter.selectedItemCount
)
)
}
setPositiveButton(
R.string.confirm
) { dialog, _ ->
for (loyaltyCard in mAdapter.getSelectedItems()) {
Log.d(TAG, "Deleting card: " + loyaltyCard.id)
DBHelper.deleteLoyaltyCard(mDatabase, this@MainActivity, loyaltyCard.id)
ShortcutHelper.removeShortcut(this@MainActivity, loyaltyCard.id)
}
val tab = groupsTabLayout.getTabAt(selectedTab)
mGroup = tab?.tag
updateLoyaltyCardList(true)
dialog.dismiss()
}
setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
}.create().show()
return true
}
R.id.action_archive -> {
for (loyaltyCard in mAdapter.getSelectedItems()) {
Log.d(TAG, "Archiving card: " + loyaltyCard.id)
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 1)
ShortcutHelper.removeShortcut(this@MainActivity, loyaltyCard.id)
updateLoyaltyCardList(false)
inputMode.finish()
invalidateOptionsMenu()
}
return true
}
R.id.action_unarchive -> {
for (loyaltyCard in mAdapter.getSelectedItems()) {
Log.d(TAG, "Unarchiving card: " + loyaltyCard.id)
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 0)
updateLoyaltyCardList(false)
inputMode.finish()
invalidateOptionsMenu()
}
return true
}
R.id.action_star -> {
for (loyaltyCard in mAdapter.getSelectedItems()) {
Log.d(TAG, "Starring card: " + loyaltyCard.id)
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 1)
updateLoyaltyCardList(false)
inputMode.finish()
}
return true
}
R.id.action_unstar -> {
for (loyaltyCard in mAdapter.getSelectedItems()) {
Log.d(TAG, "Unstarring card: " + loyaltyCard.id)
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 0)
updateLoyaltyCardList(false)
inputMode.finish()
}
return true
}
}
return false
}
override fun onDestroyActionMode(inputMode: ActionMode?) {
mAdapter.clearSelections()
mCurrentActionMode = null
}
}
override fun onCreate(inputSavedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(inputSavedInstanceState)
// Delete old cache files
// These could be temporary images for the cropper, temporary images in LoyaltyCard toBundle/writeParcel/ etc.
Thread {
val twentyFourHoursAgo = System.currentTimeMillis() - (1000 * 60 * 60 * 24)
val tempFiles = cacheDir.listFiles()
if (tempFiles == null) {
Log.e(
TAG,
"getCacheDir().listFiles() somehow returned null, this should never happen... Skipping cache cleanup..."
)
return@Thread
}
for (file in tempFiles) {
if (file.lastModified() < twentyFourHoursAgo) {
if (!file.delete()) {
Log.w(TAG, "Failed to delete cache file " + file.path)
}
}
}
}.start()
// We should extract the share intent after we called the super.onCreate as it may need to spawn a dialog window and the app needs to be initialized to not crash
extractIntentFields(intent)
binding = MainActivityBinding.inflate(layoutInflater)
setContentView(binding.getRoot())
Utils.applyWindowInsets(binding.getRoot())
setSupportActionBar(binding.toolbar)
groupsTabLayout = binding.groups
contentMainBinding = ContentMainBinding.bind(binding.include.getRoot())
mDatabase = DBHelper(this).writableDatabase
mUpdateLoyaltyCardListRunnable = Runnable {
updateLoyaltyCardList(false)
}
groupsTabLayout.addOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
selectedTab = tab.position
Log.d("onTabSelected", "Tab Position " + tab.position)
mGroup = tab.tag
updateLoyaltyCardList(false)
// Store active tab in Shared Preference to restore next app launch
applicationContext.getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
MODE_PRIVATE
).edit {
putInt(
getString(R.string.sharedpreference_active_tab),
tab.position
)
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
mAdapter = LoyaltyCardCursorAdapter(this, null, this, mUpdateLoyaltyCardListRunnable)
contentMainBinding.list.setAdapter(mAdapter)
registerForContextMenu(contentMainBinding.list)
mBarcodeScannerLauncher = registerForActivityResult(
StartActivityForResult(),
ActivityResultCallback registerForActivityResult@{ result: ActivityResult? ->
// Exit early if the user cancelled the scan (pressed back/home)
if (result == null || result.resultCode != RESULT_OK) {
return@registerForActivityResult
}
startActivity(
Intent(applicationContext, LoyaltyCardEditActivity::class.java).apply {
putExtras(result.data!!.extras!!)
}
)
})
mSettingsLauncher = registerForActivityResult(
StartActivityForResult()
) { result: ActivityResult? ->
if (result?.resultCode == RESULT_OK) {
val intent = result.data
if (intent != null && intent.getBooleanExtra(RESTART_ACTIVITY_INTENT, false)) {
recreate()
}
}
}
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (mSearchView != null && !mSearchView!!.isIconified) {
mSearchView!!.isIconified = true
} else {
finish()
}
}
})
}
override fun onResume() {
super.onResume()
if (mCurrentActionMode != null) {
mAdapter.clearSelections()
mCurrentActionMode!!.finish()
}
if (mSearchView != null && !mSearchView!!.isIconified) {
mFilter = mSearchView!!.query.toString()
}
// Start of active tab logic
updateTabGroups(groupsTabLayout)
// Restore selected tab from Shared Preference
selectedTab = applicationContext.getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
MODE_PRIVATE
).getInt(getString(R.string.sharedpreference_active_tab), 0)
// Restore sort preferences from Shared Preferences
mOrder = Utils.getLoyaltyCardOrder(this)
mOrderDirection = Utils.getLoyaltyCardOrderDirection(this)
mGroup = null
if (groupsTabLayout.tabCount != 0) {
var tab = groupsTabLayout.getTabAt(selectedTab)
if (tab == null) {
tab = groupsTabLayout.getTabAt(0)
}
groupsTabLayout.selectTab(tab)
checkNotNull(tab)
mGroup = tab.tag
} else {
scaleScreen()
}
updateLoyaltyCardList(true)
// End of active tab logic
binding.fabAdd.setOnClickListener {
mBarcodeScannerLauncher.launch(
Intent(applicationContext, ScanActivity::class.java).apply {
putExtras(Bundle().apply {
if (selectedTab != 0) {
putString(
LoyaltyCardEditActivity.BUNDLE_ADDGROUP,
groupsTabLayout.getTabAt(selectedTab)!!.text.toString()
)
}
})
}
)
}
binding.fabAdd.bringToFront()
val layoutManager = contentMainBinding.list.layoutManager as GridLayoutManager?
if (layoutManager != null) {
val settings = Settings(this)
layoutManager.setSpanCount(settings.getPreferredColumnCount())
}
}
private fun displayCardSetupOptions(menu: Menu, shouldShow: Boolean) {
for (id in intArrayOf(R.id.action_search, R.id.action_display_options, R.id.action_sort)) {
menu.findItem(id).isVisible = shouldShow
}
}
private fun updateLoyaltyCardCount() {
mLoyaltyCardCount = DBHelper.getLoyaltyCardCount(mDatabase)
}
private fun updateLoyaltyCardList(updateCount: Boolean) {
var group: Group? = null
if (mGroup != null) {
group = mGroup as Group
}
mAdapter.swapCursor(
DBHelper.getLoyaltyCardCursor(
mDatabase,
mFilter,
group,
mOrder,
mOrderDirection,
if (mAdapter.showingArchivedCards()) DBHelper.LoyaltyCardArchiveFilter.All else DBHelper.LoyaltyCardArchiveFilter.Unarchived
)
)
if (updateCount) {
updateLoyaltyCardCount()
// Update menu icons if necessary
invalidateOptionsMenu()
}
if (mLoyaltyCardCount > 0) {
// We want the cardList to be visible regardless of the filtered match count
// to ensure that the noMatchingCardsText doesn't end up being shown below
// the keyboard
contentMainBinding.helpSection.visibility = View.GONE
contentMainBinding.noGroupCardsText.visibility = View.GONE
if (mAdapter.itemCount > 0) {
contentMainBinding.list.visibility = View.VISIBLE
contentMainBinding.noMatchingCardsText.visibility = View.GONE
} else {
contentMainBinding.list.visibility = View.GONE
if (!mFilter.isEmpty()) {
// Actual Empty Search Result
contentMainBinding.noMatchingCardsText.visibility = View.VISIBLE
contentMainBinding.noGroupCardsText.visibility = View.GONE
} else {
// Group Tab with no Group Cards
contentMainBinding.noMatchingCardsText.visibility = View.GONE
contentMainBinding.noGroupCardsText.visibility = View.VISIBLE
}
}
} else {
contentMainBinding.list.visibility = View.GONE
contentMainBinding.helpSection.visibility = View.VISIBLE
contentMainBinding.noMatchingCardsText.visibility = View.GONE
contentMainBinding.noGroupCardsText.visibility = View.GONE
}
if (mCurrentActionMode != null) {
mCurrentActionMode!!.finish()
}
ListWidget().updateAll(mAdapter.mContext)
}
private fun processParseResultList(
parseResultList: MutableList<ParseResult?>,
group: String?,
closeAppOnNoBarcode: Boolean
) {
require(!parseResultList.isEmpty()) { "parseResultList may not be empty" }
Utils.makeUserChooseParseResultFromList(
this@MainActivity,
parseResultList,
object : ParseResultListDisambiguatorCallback {
override fun onUserChoseParseResult(parseResult: ParseResult) {
val intent =
Intent(applicationContext, LoyaltyCardEditActivity::class.java)
val bundle = parseResult.toLoyaltyCardBundle(this@MainActivity)
if (group != null) {
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group)
}
intent.putExtras(bundle)
startActivity(intent)
}
override fun onUserDismissedSelector() {
if (closeAppOnNoBarcode) {
finish()
}
}
})
}
private fun onSharedIntent(intent: Intent) {
val receivedAction = intent.action
val receivedType = intent.type
if (receivedAction == null || receivedType == null) {
return
}
val parseResultList: MutableList<ParseResult?>?
// Check for shared text
if (receivedAction == Intent.ACTION_SEND && receivedType == "text/plain") {
val loyaltyCard = LoyaltyCard()
loyaltyCard.setCardId(intent.getStringExtra(Intent.EXTRA_TEXT)!!)
parseResultList = mutableListOf(ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard))
} else {
// Parse whatever file was sent, regardless of opening or sharing
val data: Uri? = when (receivedAction) {
Intent.ACTION_VIEW -> {
intent.data
}
Intent.ACTION_SEND -> {
intent.getParcelableExtra(Intent.EXTRA_STREAM)
}
else -> {
Log.e(TAG, "Wrong action type to parse intent")
return
}
}
if (receivedType.startsWith("image/")) {
parseResultList = Utils.retrieveBarcodesFromImage(this, data)
} else if (receivedType == "application/pdf") {
parseResultList = Utils.retrieveBarcodesFromPdf(this, data)
} else if (mutableListOf<String?>(
"application/vnd.apple.pkpass",
"application/vnd-com.apple.pkpass"
).contains(receivedType)
) {
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data)
} else if (receivedType == "application/vnd.espass-espass") {
// FIXME: espass is not pkpass
// However, several users stated in https://github.com/CatimaLoyalty/Android/issues/2197 that the formats are extremely similar to the point they could rename an .espass file to .pkpass and have it imported
// So it makes sense to "unofficially" treat it as a PKPASS for now, even though not completely correct
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data)
} else if (receivedType == "application/vnd.apple.pkpasses") {
parseResultList = Utils.retrieveBarcodesFromPkPasses(this, data)
} else {
Log.e(TAG, "Wrong mime-type")
return
}
}
// Give up if we should parse but there is nothing to parse
if (parseResultList == null || parseResultList.isEmpty()) {
finish()
return
}
processParseResultList(parseResultList, null, true)
}
private fun extractIntentFields(intent: Intent) {
onSharedIntent(intent)
}
fun updateTabGroups(groupsTabLayout: TabLayout) {
val newGroups = DBHelper.getGroups(mDatabase)
if (newGroups.isEmpty()) {
groupsTabLayout.removeAllTabs()
groupsTabLayout.visibility = View.GONE
return
}
groupsTabLayout.removeAllTabs()
groupsTabLayout.addTab(
groupsTabLayout.newTab().apply {
setText(R.string.all)
tag = null
},
false
)
for (group in newGroups) {
groupsTabLayout.addTab(
groupsTabLayout.newTab().apply {
text = group._id
tag = group
},
false
)
}
groupsTabLayout.visibility = View.VISIBLE
}
// Saving currentQuery to finalQuery for user, this will be used to restore search history, happens when user clicks a card from list
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
finalQuery = currentQuery
// Putting the query also into outState for later use in onRestoreInstanceState when rotating screen
if (mSearchView != null) {
outState.putString(STATE_SEARCH_QUERY, finalQuery)
}
}
// Restoring instance state when rotation of screen happens with the goal to restore search query for user
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
finalQuery = savedInstanceState.getString(STATE_SEARCH_QUERY, "")
}
override fun onCreateOptionsMenu(inputMenu: Menu): Boolean {
menuInflater.inflate(R.menu.main_menu, inputMenu)
displayCardSetupOptions(inputMenu, mLoyaltyCardCount > 0)
val searchManager = getSystemService(SEARCH_SERVICE) as SearchManager?
if (searchManager != null) {
val searchMenuItem = inputMenu.findItem(R.id.action_search)
mSearchView = searchMenuItem.actionView as SearchView?
mSearchView!!.setSearchableInfo(searchManager.getSearchableInfo(componentName))
mSearchView!!.setSubmitButtonEnabled(false)
mSearchView!!.setOnCloseListener {
invalidateOptionsMenu()
false
}
/*
* On Android 13 and later, pressing Back while the search view is open hides the keyboard
* and collapses the search view at the same time.
* This brings back the old behavior on Android 12 and lower: pressing Back once
* hides the keyboard, press again while keyboard is hidden to collapse the search view.
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
searchMenuItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
return true
}
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
if (mSearchView!!.hasFocus()) {
mSearchView!!.clearFocus()
return false
}
currentQuery = ""
mFilter = ""
updateLoyaltyCardList(false)
return true
}
})
}
mSearchView!!.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String): Boolean {
mFilter = newText
// New logic to ensure search history after coming back from picked card - user will see the last search query
if (newText.isEmpty()) {
if (!finalQuery.isEmpty()) {
// Setting the query text for user after coming back from picked card from finalQuery
mSearchView!!.setQuery(finalQuery, false)
} else if (!currentQuery.isEmpty()) {
// Else if is needed in case user deletes search - expected behaviour is to show all cards
currentQuery = ""
mSearchView!!.setQuery(currentQuery, false)
}
} else {
// Setting search query each time user changes the text in search to temporary variable to be used later in finalQuery String which will be used to restore search history
currentQuery = newText
}
val currentTab =
groupsTabLayout.getTabAt(groupsTabLayout.selectedTabPosition)
mGroup = currentTab?.tag
updateLoyaltyCardList(false)
return true
}
})
// Check if we came from a picked card back to search, in that case we want to show the search view with previous search query
if (!finalQuery.isEmpty()) {
// Expand the search view to show the query
searchMenuItem.expandActionView()
// Setting the query text to empty String due to behaviour of onQueryTextChange after coming back from picked card - onQueryTextChange is called automatically without users interaction
finalQuery = ""
mSearchView!!.setQuery(currentQuery, false)
}
}
return super.onCreateOptionsMenu(inputMenu)
}
override fun onOptionsItemSelected(inputItem: MenuItem): Boolean {
when (inputItem.itemId) {
android.R.id.home -> {
onBackPressedDispatcher.onBackPressed()
}
R.id.action_display_options -> {
mAdapter.showDisplayOptionsDialog()
invalidateOptionsMenu()
return true
}
R.id.action_sort -> {
val currentIndex = AtomicInteger()
val loyaltyCardOrders = listOf<LoyaltyCardOrder?>(*LoyaltyCardOrder.entries.toTypedArray())
for (i in loyaltyCardOrders.indices) {
if (mOrder == loyaltyCardOrders[i]) {
currentIndex.set(i)
break
}
}
MaterialAlertDialogBuilder(this@MainActivity).apply {
setTitle(R.string.sort_by)
val sortingOptionBinding = SortingOptionBinding.inflate(LayoutInflater.from(this@MainActivity), null, false)
val customLayout: View = sortingOptionBinding.getRoot()
setView(customLayout)
val showReversed = sortingOptionBinding.checkBoxReverse
showReversed.isChecked = mOrderDirection == LoyaltyCardOrderDirection.Descending
setSingleChoiceItems(
R.array.sort_types_array,
currentIndex.get()
) { _: DialogInterface?, which: Int ->
currentIndex.set(which)
}
setPositiveButton(
R.string.sort
) { dialog, _ ->
setSort(
loyaltyCardOrders[currentIndex.get()]!!,
if (showReversed.isChecked) LoyaltyCardOrderDirection.Descending else LoyaltyCardOrderDirection.Ascending
)
ListWidget().updateAll(this@MainActivity)
dialog?.dismiss()
}
setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
}.create().show()
return true
}
R.id.action_manage_groups -> {
startActivity(
Intent(applicationContext, ManageGroupsActivity::class.java)
)
return true
}
R.id.action_import_export -> {
startActivity(
Intent(applicationContext, ImportExportActivity::class.java)
)
return true
}
R.id.action_settings -> {
mSettingsLauncher.launch(
Intent(applicationContext, SettingsActivity::class.java)
)
return true
}
R.id.action_about -> {
startActivity(
Intent(applicationContext, AboutActivity::class.java)
)
return true
}
}
return super.onOptionsItemSelected(inputItem)
}
private fun setSort(order: LoyaltyCardOrder, direction: LoyaltyCardOrderDirection) {
// Update values
mOrder = order
mOrderDirection = direction
// Store in Shared Preference to restore next app launch
applicationContext.getSharedPreferences(
getString(R.string.sharedpreference_sort),
MODE_PRIVATE
).edit {
putString(
getString(R.string.sharedpreference_sort_order),
order.name
)
putString(
getString(R.string.sharedpreference_sort_direction),
direction.name
)
}
// Update card list
updateLoyaltyCardList(false)
}
override fun onRowLongClicked(inputPosition: Int) {
enableActionMode(inputPosition)
}
private fun enableActionMode(inputPosition: Int) {
if (mCurrentActionMode == null) {
mCurrentActionMode = startSupportActionMode(mCurrentActionModeCallback)
}
toggleSelection(inputPosition)
}
private fun scaleScreen() {
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
val screenHeight = displayMetrics.heightPixels
val mediumSizePx = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
MEDIUM_SCALE_FACTOR_DIP.toFloat(),
getResources().displayMetrics
)
val shouldScaleSmaller = screenHeight < mediumSizePx
binding.include.welcomeIcon.visibility = if (shouldScaleSmaller) View.GONE else View.VISIBLE
}
private fun toggleSelection(inputPosition: Int) {
mAdapter.toggleSelection(inputPosition)
val count = mAdapter.selectedItemCount
if (count == 0) {
mCurrentActionMode!!.finish()
} else {
mCurrentActionMode!!.title = getResources().getQuantityString(
R.plurals.selectedCardCount,
count,
count
)
val editItem = mCurrentActionMode!!.menu.findItem(R.id.action_edit)
val duplicateItem = mCurrentActionMode!!.menu.findItem(R.id.action_duplicate)
val archiveItem = mCurrentActionMode!!.menu.findItem(R.id.action_archive)
val unarchiveItem = mCurrentActionMode!!.menu.findItem(R.id.action_unarchive)
val starItem = mCurrentActionMode!!.menu.findItem(R.id.action_star)
val unstarItem = mCurrentActionMode!!.menu.findItem(R.id.action_unstar)
var hasStarred = false
var hasUnstarred = false
var hasArchived = false
var hasUnarchived = false
for (loyaltyCard in mAdapter.getSelectedItems()) {
if (loyaltyCard.starStatus == 1) {
hasStarred = true
} else {
hasUnstarred = true
}
if (loyaltyCard.archiveStatus == 1) {
hasArchived = true
} else {
hasUnarchived = true
}
// We have all types, no need to keep checking
if (hasStarred && hasUnstarred && hasArchived && hasUnarchived) {
break
}
}
unarchiveItem.isVisible = hasArchived
archiveItem.isVisible = hasUnarchived
if (count == 1) {
starItem.isVisible = !hasStarred
unstarItem.isVisible = !hasUnstarred
editItem.isVisible = true
editItem.isEnabled = true
duplicateItem.isVisible = true
duplicateItem.isEnabled = true
} else {
starItem.isVisible = hasUnstarred
unstarItem.isVisible = hasStarred
editItem.isVisible = false
editItem.isEnabled = false
duplicateItem.isVisible = false
duplicateItem.isEnabled = false
}
mCurrentActionMode!!.invalidate()
}
}
override fun onRowClicked(inputPosition: Int) {
if (mAdapter.selectedItemCount > 0) {
enableActionMode(inputPosition)
} else {
// FIXME
//
// There is a really nasty edge case that can happen when someone taps a card but right
// after it swipes (very small window, hard to reproduce). The cursor gets replaced and
// may not have a card at the ID number that is returned from onRowClicked.
//
// The proper fix, obviously, would involve makes sure an onFling can't happen while a
// click is being processed. Sadly, I have not yet found a way to make that possible.
val loyaltyCard: LoyaltyCard
try {
loyaltyCard = mAdapter.getCard(inputPosition)
} catch (e: CursorIndexOutOfBoundsException) {
Log.w(TAG, "Prevented crash from tap + swipe on ID $inputPosition: $e")
return
}
startActivity(
Intent(this, LoyaltyCardViewActivity::class.java).apply {
action = ""
putExtras(Bundle().apply {
putInt(LoyaltyCardViewActivity.BUNDLE_ID, loyaltyCard.id)
val cardList = ArrayList<Int?>()
for (i in 0..<mAdapter.itemCount) {
cardList.add(mAdapter.getCard(i).id)
}
putIntegerArrayList(LoyaltyCardViewActivity.BUNDLE_CARDLIST, cardList)
})
}
)
}
}
companion object {
private const val TAG = "Catima"
const val RESTART_ACTIVITY_INTENT: String = "restart_activity_intent"
private const val MEDIUM_SCALE_FACTOR_DIP = 460
const val STATE_SEARCH_QUERY: String = "SEARCH_QUERY"
}
}

View File

@@ -1,242 +0,0 @@
package protect.card_locker;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import protect.card_locker.databinding.ActivityManageGroupBinding;
public class ManageGroupActivity extends CatimaAppCompatActivity implements ManageGroupCursorAdapter.CardAdapterListener {
private ActivityManageGroupBinding binding;
private SQLiteDatabase mDatabase;
private ManageGroupCursorAdapter mAdapter;
private final String SAVE_INSTANCE_ADAPTER_STATE = "adapterState";
private final String SAVE_INSTANCE_CURRENT_GROUP_NAME = "currentGroupName";
protected Group mGroup = null;
private RecyclerView mCardList;
private TextView noGroupCardsText;
private EditText mGroupNameText;
private boolean mGroupNameNotInUse;
@Override
protected void onCreate(Bundle inputSavedInstanceState) {
super.onCreate(inputSavedInstanceState);
binding = ActivityManageGroupBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
Utils.applyWindowInsetsAndFabOffset(binding.getRoot(), binding.fabSave);
Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
mDatabase = new DBHelper(this).getWritableDatabase();
noGroupCardsText = binding.include.noGroupCardsText;
mCardList = binding.include.list;
FloatingActionButton saveButton = binding.fabSave;
mGroupNameText = binding.editTextGroupName;
mGroupNameText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
mGroupNameNotInUse = true;
mGroupNameText.setError(null);
String currentGroupName = mGroupNameText.getText().toString().trim();
if (currentGroupName.length() == 0) {
mGroupNameText.setError(getResources().getText(R.string.group_name_is_empty));
return;
}
if (!mGroup._id.equals(currentGroupName)) {
if (DBHelper.getGroup(mDatabase, currentGroupName) != null) {
mGroupNameNotInUse = false;
mGroupNameText.setError(getResources().getText(R.string.group_name_already_in_use));
} else {
mGroupNameNotInUse = true;
}
}
}
});
Intent intent = getIntent();
String groupId = intent.getStringExtra("group");
if (groupId == null) {
throw (new IllegalArgumentException("this activity expects a group loaded into it's intent"));
}
Log.d("groupId", "groupId: " + groupId);
mGroup = DBHelper.getGroup(mDatabase, groupId);
if (mGroup == null) {
throw (new IllegalArgumentException("cannot load group " + groupId + " from database"));
}
mGroupNameText.setText(mGroup._id);
setTitle(getString(R.string.editGroup, mGroup._id));
mAdapter = new ManageGroupCursorAdapter(this, null, this, mGroup, null);
mCardList.setAdapter(mAdapter);
registerForContextMenu(mCardList);
if (inputSavedInstanceState != null) {
mAdapter.importInGroupState(integerArrayToAdapterState(inputSavedInstanceState.getIntegerArrayList(SAVE_INSTANCE_ADAPTER_STATE)));
mGroupNameText.setText(inputSavedInstanceState.getString(SAVE_INSTANCE_CURRENT_GROUP_NAME));
}
enableToolbarBackButton();
saveButton.setOnClickListener(v -> {
String currentGroupName = mGroupNameText.getText().toString().trim();
if (!currentGroupName.equals(mGroup._id)) {
if (currentGroupName.length() == 0) {
Toast.makeText(getApplicationContext(), R.string.group_name_is_empty, Toast.LENGTH_SHORT).show();
return;
}
if (!mGroupNameNotInUse) {
Toast.makeText(getApplicationContext(), R.string.group_name_already_in_use, Toast.LENGTH_SHORT).show();
return;
}
}
mAdapter.commitToDatabase();
if (!currentGroupName.equals(mGroup._id)) {
DBHelper.updateGroup(mDatabase, mGroup._id, currentGroupName);
}
Toast.makeText(getApplicationContext(), R.string.group_updated, Toast.LENGTH_SHORT).show();
finish();
});
// this setText is here because content_main.xml is reused from main activity
noGroupCardsText.setText(getResources().getText(R.string.noGiftCardsGroup));
updateLoyaltyCardList();
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
leaveWithoutSaving();
}
});
}
private ArrayList<Integer> adapterStateToIntegerArray(HashMap<Integer, Boolean> adapterState) {
ArrayList<Integer> ret = new ArrayList<>(adapterState.size() * 2);
for (Map.Entry<Integer, Boolean> entry : adapterState.entrySet()) {
ret.add(entry.getKey());
ret.add(entry.getValue() ? 1 : 0);
}
return ret;
}
private HashMap<Integer, Boolean> integerArrayToAdapterState(ArrayList<Integer> in) {
HashMap<Integer, Boolean> ret = new HashMap<>();
if (in.size() % 2 != 0) {
throw (new RuntimeException("failed restoring adapterState from integer array list"));
}
for (int i = 0; i < in.size(); i += 2) {
ret.put(in.get(i), in.get(i + 1) == 1);
}
return ret;
}
@Override
public boolean onCreateOptionsMenu(Menu inputMenu) {
getMenuInflater().inflate(R.menu.card_details_menu, inputMenu);
return super.onCreateOptionsMenu(inputMenu);
}
@Override
public boolean onOptionsItemSelected(MenuItem inputItem) {
int id = inputItem.getItemId();
if (id == R.id.action_display_options) {
mAdapter.showDisplayOptionsDialog();
invalidateOptionsMenu();
return true;
}
return super.onOptionsItemSelected(inputItem);
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putIntegerArrayList(SAVE_INSTANCE_ADAPTER_STATE, adapterStateToIntegerArray(mAdapter.exportInGroupState()));
outState.putString(SAVE_INSTANCE_CURRENT_GROUP_NAME, mGroupNameText.getText().toString());
}
private void updateLoyaltyCardList() {
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase));
if (mAdapter.getItemCount() == 0) {
mCardList.setVisibility(View.GONE);
noGroupCardsText.setVisibility(View.VISIBLE);
} else {
mCardList.setVisibility(View.VISIBLE);
noGroupCardsText.setVisibility(View.GONE);
}
}
private void leaveWithoutSaving() {
if (hasChanged()) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(ManageGroupActivity.this);
builder.setTitle(R.string.leaveWithoutSaveTitle);
builder.setMessage(R.string.leaveWithoutSaveConfirmation);
builder.setPositiveButton(R.string.confirm, (dialog, which) -> finish());
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
AlertDialog dialog = builder.create();
dialog.show();
} else {
finish();
}
}
@Override
public boolean onSupportNavigateUp() {
getOnBackPressedDispatcher().onBackPressed();
return true;
}
private boolean hasChanged() {
return mAdapter.hasChanged() || !mGroup._id.equals(mGroupNameText.getText().toString().trim());
}
@Override
public void onRowLongClicked(int inputPosition) {
mAdapter.toggleSelection(inputPosition);
}
@Override
public void onRowClicked(int inputPosition) {
mAdapter.toggleSelection(inputPosition);
}
}

View File

@@ -0,0 +1,236 @@
package protect.card_locker
import android.content.DialogInterface
import android.database.sqlite.SQLiteDatabase
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.core.widget.doAfterTextChanged
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import protect.card_locker.LoyaltyCardCursorAdapter.CardAdapterListener
import protect.card_locker.databinding.ActivityManageGroupBinding
class ManageGroupActivity : CatimaAppCompatActivity(), CardAdapterListener {
private lateinit var binding: ActivityManageGroupBinding
private lateinit var mDatabase: SQLiteDatabase
private lateinit var mAdapter: ManageGroupCursorAdapter
private lateinit var mGroup: Group
private lateinit var mCardList: RecyclerView
private lateinit var noGroupCardsText: TextView
private lateinit var mGroupNameText: EditText
private var mGroupNameNotInUse = false
override fun onCreate(inputSavedInstanceState: Bundle?) {
super.onCreate(inputSavedInstanceState)
binding = ActivityManageGroupBinding.inflate(layoutInflater)
setContentView(binding.root)
Utils.applyWindowInsetsAndFabOffset(binding.root, binding.fabSave)
setSupportActionBar(binding.toolbar)
mDatabase = DBHelper(this).writableDatabase
noGroupCardsText = binding.include.noGroupCardsText
mCardList = binding.include.list
mGroupNameText = binding.editTextGroupName
mGroupNameText.doAfterTextChanged {
mGroupNameNotInUse = true
mGroupNameText.error = null
val currentGroupName = mGroupNameText.text.trim().toString()
if (currentGroupName.isEmpty()) {
mGroupNameText.error = getText(R.string.group_name_is_empty)
return@doAfterTextChanged
}
if (mGroup._id != currentGroupName) {
if (DBHelper.getGroup(mDatabase, currentGroupName) != null) {
mGroupNameNotInUse = false
mGroupNameText.error = getText(R.string.group_name_already_in_use)
} else {
mGroupNameNotInUse = true
}
}
}
val groupId = intent.getStringExtra("group")
?: throw (IllegalArgumentException("this activity expects a group loaded into it's intent"))
Log.d("groupId", "groupId: $groupId")
mGroup = DBHelper.getGroup(mDatabase, groupId)
?: throw IllegalArgumentException("Cannot load group $groupId from database")
mGroupNameText.setText(mGroup._id)
setTitle(getString(R.string.editGroup, mGroup._id))
mAdapter = ManageGroupCursorAdapter(this, null, this, mGroup, null)
mCardList.adapter = mAdapter
registerForContextMenu(mCardList)
if (inputSavedInstanceState != null) {
mAdapter.importInGroupState(
bundleToAdapterState(
adapterStateBundle = inputSavedInstanceState.getBundle(
SAVE_INSTANCE_ADAPTER_STATE
)
)
)
mGroupNameText.setText(
inputSavedInstanceState.getString(
SAVE_INSTANCE_CURRENT_GROUP_NAME
)
)
}
enableToolbarBackButton()
binding.fabSave.setOnClickListener { v: View ->
val currentGroupName = mGroupNameText.text.trim().toString()
if (currentGroupName != mGroup._id) {
when {
currentGroupName.isEmpty() -> {
Toast.makeText(
applicationContext,
R.string.group_name_is_empty,
Toast.LENGTH_SHORT
).show()
return@setOnClickListener
}
!mGroupNameNotInUse -> {
Toast.makeText(
applicationContext,
R.string.group_name_already_in_use,
Toast.LENGTH_SHORT
).show()
return@setOnClickListener
}
}
}
mAdapter.commitToDatabase()
if (currentGroupName != mGroup._id) {
DBHelper.updateGroup(mDatabase, mGroup._id, currentGroupName)
}
Toast.makeText(
applicationContext,
R.string.group_updated,
Toast.LENGTH_SHORT
).show()
finish()
}
// this setText is here because content_main.xml is reused from main activity
noGroupCardsText.text = getText(R.string.noGiftCardsGroup)
updateLoyaltyCardList()
onBackPressedDispatcher.addCallback(
owner = this,
onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
leaveWithoutSaving()
}
})
}
private fun adapterStateToBundle(adapterState: HashMap<Int, Boolean>): Bundle {
val adapterStateBundle = Bundle().apply {
for (entry in adapterState.entries) {
putBoolean(entry.key.toString(), entry.value)
}
}
return adapterStateBundle
}
private fun bundleToAdapterState(adapterStateBundle: Bundle?): Map<Int, Boolean> {
adapterStateBundle ?: return emptyMap()
val adapterStateMap = buildMap {
for (key in adapterStateBundle.keySet()) {
put(key.toInt(), adapterStateBundle.getBoolean(key))
}
}
return adapterStateMap
}
override fun onCreateOptionsMenu(inputMenu: Menu): Boolean {
menuInflater.inflate(R.menu.card_details_menu, inputMenu)
return super.onCreateOptionsMenu(inputMenu)
}
override fun onOptionsItemSelected(inputItem: MenuItem): Boolean {
val id = inputItem.itemId
if (id == R.id.action_display_options) {
mAdapter.showDisplayOptionsDialog()
invalidateOptionsMenu()
return true
}
return super.onOptionsItemSelected(inputItem)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBundle(
SAVE_INSTANCE_ADAPTER_STATE,
adapterStateToBundle(mAdapter.exportInGroupState())
)
outState.putString(SAVE_INSTANCE_CURRENT_GROUP_NAME, mGroupNameText.text.toString())
}
private fun updateLoyaltyCardList() {
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase))
if (mAdapter.itemCount == 0) {
mCardList.visibility = View.GONE
noGroupCardsText.visibility = View.VISIBLE
} else {
mCardList.visibility = View.VISIBLE
noGroupCardsText.visibility = View.GONE
}
}
private fun leaveWithoutSaving() {
if (hasChanged()) {
MaterialAlertDialogBuilder(this@ManageGroupActivity).apply {
setTitle(R.string.leaveWithoutSaveTitle)
setMessage(R.string.leaveWithoutSaveConfirmation)
setPositiveButton(R.string.confirm) { dialog: DialogInterface, _ ->
finish()
}
setNegativeButton(R.string.cancel) { dialog: DialogInterface, _ ->
dialog.dismiss()
}
}.create().show()
} else {
finish()
}
}
override fun onSupportNavigateUp(): Boolean {
onBackPressedDispatcher.onBackPressed()
return true
}
private fun hasChanged(): Boolean {
return mAdapter.hasChanged() || mGroup._id != mGroupNameText.text.trim().toString()
}
override fun onRowLongClicked(inputPosition: Int) {
mAdapter.toggleSelection(inputPosition)
}
override fun onRowClicked(inputPosition: Int) {
mAdapter.toggleSelection(inputPosition)
}
private companion object {
const val SAVE_INSTANCE_ADAPTER_STATE = "adapterState"
const val SAVE_INSTANCE_CURRENT_GROUP_NAME = "currentGroupName"
}
}

View File

@@ -99,7 +99,7 @@ public class ManageGroupCursorAdapter extends LoyaltyCardCursorAdapter {
}
}
public void importInGroupState(HashMap<Integer, Boolean> cardIdInGroupMap) {
public void importInGroupState(Map<Integer, Boolean> cardIdInGroupMap) {
mInGroupOverlay = new HashMap<>(cardIdInGroupMap);
}

View File

@@ -1,247 +0,0 @@
package protect.card_locker;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.text.InputType;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.List;
import protect.card_locker.databinding.ManageGroupsActivityBinding;
public class ManageGroupsActivity extends CatimaAppCompatActivity implements GroupCursorAdapter.GroupAdapterListener {
private ManageGroupsActivityBinding binding;
private static final String TAG = "Catima";
private SQLiteDatabase mDatabase;
private TextView mHelpText;
private RecyclerView mGroupList;
GroupCursorAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ManageGroupsActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.groups);
setContentView(binding.getRoot());
Utils.applyWindowInsets(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
enableToolbarBackButton();
mDatabase = new DBHelper(this).getWritableDatabase();
}
@Override
protected void onResume() {
super.onResume();
FloatingActionButton addButton = binding.fabAdd;
addButton.setOnClickListener(v -> createGroup());
addButton.bringToFront();
mGroupList = binding.include.list;
mHelpText = binding.include.helpText;
// Init group list
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
mGroupList.setLayoutManager(mLayoutManager);
mGroupList.setItemAnimator(new DefaultItemAnimator());
mAdapter = new GroupCursorAdapter(this, null, this);
mGroupList.setAdapter(mAdapter);
updateGroupList();
}
private void updateGroupList() {
mAdapter.swapCursor(DBHelper.getGroupCursor(mDatabase));
if (DBHelper.getGroupCount(mDatabase) == 0) {
mGroupList.setVisibility(View.GONE);
mHelpText.setVisibility(View.VISIBLE);
return;
}
mGroupList.setVisibility(View.VISIBLE);
mHelpText.setVisibility(View.GONE);
}
private void invalidateHomescreenActiveTab() {
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
Context.MODE_PRIVATE);
SharedPreferences.Editor activeTabPrefEditor = activeTabPref.edit();
activeTabPrefEditor.putInt(getString(R.string.sharedpreference_active_tab), 0);
activeTabPrefEditor.apply();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
finish();
}
return super.onOptionsItemSelected(item);
}
private void createGroup() {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
// Header
builder.setTitle(R.string.enter_group_name);
// Layout
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
int contentPadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
params.leftMargin = contentPadding;
params.topMargin = contentPadding / 2;
params.rightMargin = contentPadding;
// EditText with spacing
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setLayoutParams(params);
layout.addView(input);
// Set layout
builder.setView(layout);
// Buttons
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
DBHelper.insertGroup(mDatabase, input.getText().toString().trim());
updateGroupList();
});
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
AlertDialog dialog = builder.create();
// Now that the dialog exists, we can bind something that affects the OK button
input.addTextChangedListener(new SimpleTextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
String groupName = s.toString().trim();
if (groupName.length() == 0) {
input.setError(getString(R.string.group_name_is_empty));
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
return;
}
if (DBHelper.getGroup(mDatabase, groupName) != null) {
input.setError(getString(R.string.group_name_already_in_use));
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
return;
}
input.setError(null);
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
}
});
dialog.show();
// Disable button (must be done **after** dialog is shown to prevent crash
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
// Set focus on input field
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
input.requestFocus();
}
private String getGroupName(View view) {
TextView groupNameTextView = view.findViewById(R.id.name);
return (String) groupNameTextView.getText();
}
private void moveGroup(View view, boolean up) {
List<Group> groups = DBHelper.getGroups(mDatabase);
final String groupName = getGroupName(view);
int currentIndex = DBHelper.getGroup(mDatabase, groupName).order;
int newIndex;
// Reinsert group in correct position
if (up) {
newIndex = currentIndex - 1;
} else {
newIndex = currentIndex + 1;
}
// Don't try to move out of bounds
if (newIndex < 0 || newIndex >= groups.size()) {
return;
}
Group group = groups.remove(currentIndex);
groups.add(newIndex, group);
// Update database
DBHelper.reorderGroups(mDatabase, groups);
// Update UI
updateGroupList();
// Ordering may have changed, so invalidate
invalidateHomescreenActiveTab();
}
@Override
public void onMoveDownButtonClicked(View view) {
moveGroup(view, false);
}
@Override
public void onMoveUpButtonClicked(View view) {
moveGroup(view, true);
}
@Override
public void onEditButtonClicked(View view) {
Intent intent = new Intent(this, ManageGroupActivity.class);
intent.putExtra("group", getGroupName(view));
startActivity(intent);
}
@Override
public void onDeleteButtonClicked(View view) {
final String groupName = getGroupName(view);
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.deleteConfirmationGroup);
builder.setMessage(groupName);
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
DBHelper.deleteGroup(mDatabase, groupName);
updateGroupList();
// Delete may change ordering, so invalidate
invalidateHomescreenActiveTab();
});
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
AlertDialog dialog = builder.create();
dialog.show();
}
}

View File

@@ -0,0 +1,240 @@
package protect.card_locker
import android.content.DialogInterface
import android.content.Intent
import android.database.sqlite.SQLiteDatabase
import android.os.Bundle
import android.text.InputType
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.content.edit
import androidx.core.widget.doOnTextChanged
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import protect.card_locker.GroupCursorAdapter.GroupAdapterListener
import protect.card_locker.databinding.ManageGroupsActivityBinding
class ManageGroupsActivity : CatimaAppCompatActivity(), GroupAdapterListener {
private lateinit var binding: ManageGroupsActivityBinding
private lateinit var mDatabase: SQLiteDatabase
private lateinit var mHelpText: TextView
private lateinit var mGroupList: RecyclerView
private lateinit var mAdapter: GroupCursorAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ManageGroupsActivityBinding.inflate(layoutInflater)
setTitle(R.string.groups)
setContentView(binding.root)
Utils.applyWindowInsets(binding.root)
setSupportActionBar(binding.toolbar)
enableToolbarBackButton()
mDatabase = DBHelper(this).writableDatabase
}
override fun onResume() {
super.onResume()
with(binding.fabAdd) {
setOnClickListener { v: View ->
createGroup()
}
bringToFront()
}
mGroupList = binding.include.list
mHelpText = binding.include.helpText
// Init group list
LinearLayoutManager(applicationContext).apply {
mGroupList.layoutManager = this
}
mGroupList.setItemAnimator(DefaultItemAnimator())
mAdapter = GroupCursorAdapter(this, null, this)
mGroupList.setAdapter(mAdapter)
updateGroupList()
}
private fun updateGroupList() {
mAdapter.swapCursor(DBHelper.getGroupCursor(mDatabase))
if (DBHelper.getGroupCount(mDatabase) == 0) {
mGroupList.visibility = View.GONE
mHelpText.visibility = View.VISIBLE
return
}
mGroupList.visibility = View.VISIBLE
mHelpText.visibility = View.GONE
}
private fun invalidateHomescreenActiveTab() {
val activeTabPref = getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
MODE_PRIVATE
)
activeTabPref.edit {
putInt(getString(R.string.sharedpreference_active_tab), 0)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
finish()
}
return super.onOptionsItemSelected(item)
}
private fun createGroup() {
val builder: AlertDialog.Builder = MaterialAlertDialogBuilder(this)
// Header
builder.setTitle(R.string.enter_group_name)
// Layout
val layout = LinearLayout(this)
layout.orientation = LinearLayout.VERTICAL
val params = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
val contentPadding =
resources.getDimensionPixelSize(R.dimen.alert_dialog_content_padding)
leftMargin = contentPadding
topMargin = contentPadding / 2
rightMargin = contentPadding
}
// EditText with spacing
val input = EditText(this)
input.setInputType(InputType.TYPE_CLASS_TEXT)
input.setLayoutParams(params)
layout.addView(input)
// Set layout
builder.setView(layout)
// Buttons
builder.setPositiveButton(getString(R.string.ok)) { dialog: DialogInterface, which: Int ->
DBHelper.insertGroup(mDatabase, input.text.trim().toString())
updateGroupList()
}
builder.setNegativeButton(getString(R.string.cancel)) { dialog: DialogInterface, which: Int ->
dialog.cancel()
}
val dialog = builder.create()
// Now that the dialog exists, we can bind something that affects the OK button
input.doOnTextChanged { s: CharSequence?, start: Int, before: Int, count: Int ->
val groupName = s?.trim().toString()
if (groupName.isEmpty()) {
input.error = getString(R.string.group_name_is_empty)
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false)
return@doOnTextChanged
}
if (DBHelper.getGroup(mDatabase, groupName) != null) {
input.error = getString(R.string.group_name_already_in_use)
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false)
return@doOnTextChanged
}
input.error = null
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true)
}
dialog.apply {
show()
// Disable button (must be done **after** dialog is shown to prevent crash
getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false)
// Set focus on input field
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
}
input.requestFocus()
}
private fun getGroupName(view: View): String {
val groupNameTextView = view.findViewById<TextView>(R.id.name)
return groupNameTextView.text.toString()
}
private fun moveGroup(view: View, up: Boolean) {
val groups = DBHelper.getGroups(mDatabase)
val groupName = getGroupName(view)
val currentIndex = DBHelper.getGroup(mDatabase, groupName).order
// Reinsert group in correct position
val newIndex: Int = if (up) {
currentIndex - 1
} else {
currentIndex + 1
}
// Don't try to move out of bounds
if (newIndex < 0 || newIndex >= groups.size) {
return
}
val group = groups.removeAt(currentIndex)
groups.add(newIndex, group)
// Update database
DBHelper.reorderGroups(mDatabase, groups)
// Update UI
updateGroupList()
// Ordering may have changed, so invalidate
invalidateHomescreenActiveTab()
}
override fun onMoveDownButtonClicked(view: View) {
moveGroup(view, false)
}
override fun onMoveUpButtonClicked(view: View) {
moveGroup(view, true)
}
override fun onEditButtonClicked(view: View) {
Intent(this, ManageGroupActivity::class.java).apply {
putExtra("group", getGroupName(view))
startActivity(this)
}
}
override fun onDeleteButtonClicked(view: View) {
val groupName = getGroupName(view)
MaterialAlertDialogBuilder(this).apply {
setTitle(R.string.deleteConfirmationGroup)
setMessage(groupName)
setPositiveButton(getString(R.string.ok)) { dialog: DialogInterface, which: Int ->
DBHelper.deleteGroup(mDatabase, groupName)
updateGroupList()
// Delete may change ordering, so invalidate
invalidateHomescreenActiveTab()
}
setNegativeButton(getString(R.string.cancel)) { dialog: DialogInterface, which: Int ->
dialog.cancel()
}
}.create().show()
}
}

View File

@@ -1,18 +1,17 @@
package protect.card_locker;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class OpenWebLinkHandler {
private static final String TAG = "Catima";
public void openBrowser(AppCompatActivity activity, String url) {
public void openBrowser(Activity activity, String url) {
if (url == null) {
return;
}

View File

@@ -0,0 +1,73 @@
package protect.card_locker
import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.core.net.toUri
import net.lingala.zip4j.io.inputstream.ZipInputStream
import net.lingala.zip4j.model.LocalFileHeader
import java.io.FileNotFoundException
import java.io.IOException
class PkpassesParser(context: Context, uri: Uri?) {
private var mContext = context
private val pkPassParsers: ArrayList<PkpassParser> = ArrayList()
init {
mContext = context
Log.i(TAG, "Received Pkpasses file")
if (uri == null) {
Log.e(TAG, "Uri did not contain any data")
throw IOException(context.getString(R.string.errorReadingFile))
}
try {
mContext.contentResolver.openInputStream(uri).use { inputStream ->
ZipInputStream(inputStream).use { zipInputStream ->
var localFileHeader: LocalFileHeader?
while (true) {
// Retrieve the next file
localFileHeader = zipInputStream.nextEntry
// If no next file, exit loop
if (localFileHeader == null) {
break
}
// Ignore directories
if (localFileHeader.isDirectory) continue
// Ignore non-pkpass files
if (!localFileHeader.fileName.endsWith(".pkpass")) continue
// Extract .pkpass (.zip) inside .pkpasses to cache directory
val tempFileName = "pkpassparser_" + System.currentTimeMillis() + "_" + localFileHeader.fileName
val tempFile = Utils.copyToTempFile(mContext, zipInputStream, tempFileName)
// Parse temporary file
pkPassParsers.add(
PkpassParser(mContext, tempFile.toUri())
)
// Delete temporary file
tempFile.delete()
}
}
}
} catch (e: FileNotFoundException) {
throw IOException(mContext.getString(R.string.errorReadingFile))
} catch (e: Exception) {
throw e
}
}
fun getPkpassParsers(): ArrayList<PkpassParser> {
return pkPassParsers
}
companion object {
private const val TAG = "Catima"
}
}

View File

@@ -1,542 +0,0 @@
package protect.card_locker;
import static protect.card_locker.BarcodeSelectorActivity.BARCODE_CONTENTS;
import static protect.card_locker.BarcodeSelectorActivity.BARCODE_FORMAT;
import android.Manifest;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.text.InputType;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.zxing.DecodeHintType;
import com.google.zxing.ResultPoint;
import com.google.zxing.client.android.Intents;
import com.journeyapps.barcodescanner.BarcodeCallback;
import com.journeyapps.barcodescanner.BarcodeResult;
import com.journeyapps.barcodescanner.CaptureManager;
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import protect.card_locker.databinding.CustomBarcodeScannerBinding;
import protect.card_locker.databinding.ScanActivityBinding;
/**
* Custom Scannner Activity extending from Activity to display a custom layout form scanner view.
* <p>
* Based on https://github.com/journeyapps/zxing-android-embedded/blob/0fdfbce9fb3285e985bad9971c5f7c0a7a334e7b/sample/src/main/java/example/zxing/CustomScannerActivity.java
* originally licensed under Apache 2.0
*/
public class ScanActivity extends CatimaAppCompatActivity {
private ScanActivityBinding binding;
private CustomBarcodeScannerBinding customBarcodeScannerBinding;
private static final String TAG = "Catima";
private static final int MEDIUM_SCALE_FACTOR_DIP = 460;
private static final int COMPAT_SCALE_FACTOR_DIP = 320;
private static final int PERMISSION_SCAN_ADD_FROM_IMAGE = 100;
private static final int PERMISSION_SCAN_ADD_FROM_PDF = 101;
private static final int PERMISSION_SCAN_ADD_FROM_PKPASS = 102;
private CaptureManager capture;
private DecoratedBarcodeView barcodeScannerView;
private String cardId;
private String addGroup;
private boolean torch = false;
private ActivityResultLauncher<Intent> manualAddLauncher;
// can't use the pre-made contract because that launches the file manager for image type instead of gallery
private ActivityResultLauncher<Intent> photoPickerLauncher;
private ActivityResultLauncher<Intent> pdfPickerLauncher;
private ActivityResultLauncher<Intent> pkpassPickerLauncher;
static final String STATE_SCANNER_ACTIVE = "scannerActive";
private boolean mScannerActive = true;
private boolean mHasError = false;
private void extractIntentFields(Intent intent) {
final Bundle b = intent.getExtras();
cardId = b != null ? b.getString(LoyaltyCard.BUNDLE_LOYALTY_CARD_CARD_ID) : null;
addGroup = b != null ? b.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) : null;
Log.d(TAG, "Scan activity: id=" + cardId);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ScanActivityBinding.inflate(getLayoutInflater());
customBarcodeScannerBinding = CustomBarcodeScannerBinding.bind(binding.zxingBarcodeScanner);
setTitle(R.string.scanCardBarcode);
setContentView(binding.getRoot());
Utils.applyWindowInsets(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
enableToolbarBackButton();
extractIntentFields(getIntent());
manualAddLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.SELECT_BARCODE_REQUEST, result.getResultCode(), result.getData()));
photoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_IMAGE_FILE, result.getResultCode(), result.getData()));
pdfPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_PDF_FILE, result.getResultCode(), result.getData()));
pkpassPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_PKPASS_FILE, result.getResultCode(), result.getData()));
customBarcodeScannerBinding.fabOtherOptions.setOnClickListener(view -> {
setScannerActive(false);
ArrayList<HashMap<String, Object>> list = new ArrayList<>();
String[] texts = new String[]{
getString(R.string.addWithoutBarcode),
getString(R.string.addManually),
getString(R.string.addFromImage),
getString(R.string.addFromPdfFile),
getString(R.string.addFromPkpass)
};
Object[] icons = new Object[]{
R.drawable.baseline_block_24,
R.drawable.ic_edit,
R.drawable.baseline_image_24,
R.drawable.baseline_picture_as_pdf_24,
R.drawable.local_activity_24px
};
String[] columns = new String[]{"text", "icon"};
for (int i = 0; i < texts.length; i++) {
HashMap<String, Object> map = new HashMap<>();
map.put(columns[0], texts[i]);
map.put(columns[1], icons[i]);
list.add(map);
}
ListAdapter adapter = new SimpleAdapter(
ScanActivity.this,
list,
R.layout.alertdialog_row_with_icon,
columns,
new int[]{R.id.textView, R.id.imageView}
);
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ScanActivity.this);
builder.setTitle(getString(R.string.add_a_card_in_a_different_way));
builder.setAdapter(
adapter,
(dialogInterface, i) -> {
switch (i) {
case 0:
addWithoutBarcode();
break;
case 1:
addManually();
break;
case 2:
addFromImage();
break;
case 3:
addFromPdf();
break;
case 4:
addFromPkPass();
break;
default:
throw new IllegalArgumentException("Unknown 'Add a card in a different way' dialog option");
}
}
);
builder.setOnCancelListener(dialogInterface -> setScannerActive(true));
builder.show();
});
// Configure barcodeScanner
barcodeScannerView = binding.zxingBarcodeScanner;
Intent barcodeScannerIntent = new Intent();
Bundle barcodeScannerIntentBundle = new Bundle();
barcodeScannerIntentBundle.putBoolean(DecodeHintType.ALSO_INVERTED.name(), Boolean.TRUE);
barcodeScannerIntent.putExtras(barcodeScannerIntentBundle);
barcodeScannerView.initializeFromIntent(barcodeScannerIntent);
// Even though we do the actual decoding with the barcodeScannerView
// CaptureManager needs to be running to show the camera and scanning bar
capture = new CatimaCaptureManager(this, barcodeScannerView, this::onCaptureManagerError);
Intent captureIntent = new Intent();
Bundle captureIntentBundle = new Bundle();
captureIntentBundle.putBoolean(Intents.Scan.BEEP_ENABLED, false);
captureIntent.putExtras(captureIntentBundle);
capture.initializeFromIntent(captureIntent, savedInstanceState);
barcodeScannerView.decodeSingle(new BarcodeCallback() {
@Override
public void barcodeResult(BarcodeResult result) {
LoyaltyCard loyaltyCard = new LoyaltyCard();
loyaltyCard.setCardId(result.getText());
loyaltyCard.setBarcodeType(CatimaBarcode.fromBarcode(result.getBarcodeFormat()));
returnResult(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
}
@Override
public void possibleResultPoints(List<ResultPoint> resultPoints) {
}
});
}
@Override
protected void onResume() {
super.onResume();
if (mScannerActive) {
capture.onResume();
}
if (!Utils.deviceHasCamera(this)) {
showCameraError(getString(R.string.noCameraFoundGuideText), false);
} else if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
showCameraPermissionMissingText();
} else {
hideCameraError();
}
scaleScreen();
}
@Override
protected void onPause() {
super.onPause();
capture.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
capture.onDestroy();
}
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
capture.onSaveInstanceState(savedInstanceState);
savedInstanceState.putBoolean(STATE_SCANNER_ACTIVE, mScannerActive);
}
@Override
public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mScannerActive = savedInstanceState.getBoolean(STATE_SCANNER_ACTIVE);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
getMenuInflater().inflate(R.menu.scan_menu, menu);
}
barcodeScannerView.setTorchOff();
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
setResult(Activity.RESULT_CANCELED);
finish();
return true;
} else if (item.getItemId() == R.id.action_toggle_flashlight) {
if (torch) {
torch = false;
barcodeScannerView.setTorchOff();
item.setTitle(R.string.turn_flashlight_on);
item.setIcon(R.drawable.ic_flashlight_off_white_24dp);
} else {
torch = true;
barcodeScannerView.setTorchOn();
item.setTitle(R.string.turn_flashlight_off);
item.setIcon(R.drawable.ic_flashlight_on_white_24dp);
}
}
return super.onOptionsItemSelected(item);
}
private void setScannerActive(boolean isActive) {
if (isActive) {
barcodeScannerView.resume();
} else {
barcodeScannerView.pause();
}
mScannerActive = isActive;
}
private void returnResult(ParseResult parseResult) {
Intent result = new Intent();
Bundle bundle = parseResult.toLoyaltyCardBundle(ScanActivity.this);
if (addGroup != null) {
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
}
result.putExtras(bundle);
ScanActivity.this.setResult(RESULT_OK, result);
finish();
}
private void handleActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
List<ParseResult> parseResultList = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
if (parseResultList.isEmpty()) {
setScannerActive(true);
return;
}
Utils.makeUserChooseParseResultFromList(this, parseResultList, new ParseResultListDisambiguatorCallback() {
@Override
public void onUserChoseParseResult(ParseResult parseResult) {
returnResult(parseResult);
}
@Override
public void onUserDismissedSelector() {
setScannerActive(true);
}
});
}
private void addWithoutBarcode() {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setOnCancelListener(dialogInterface -> setScannerActive(true));
// Header
builder.setTitle(R.string.addWithoutBarcode);
// Layout
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
int contentPadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
params.leftMargin = contentPadding;
params.topMargin = contentPadding / 2;
params.rightMargin = contentPadding;
// Description
TextView currentTextview = new TextView(this);
currentTextview.setText(getString(R.string.enter_card_id));
currentTextview.setLayoutParams(params);
layout.addView(currentTextview);
// EditText with spacing
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setLayoutParams(params);
layout.addView(input);
// Set layout
builder.setView(layout);
// Buttons
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
LoyaltyCard loyaltyCard = new LoyaltyCard();
loyaltyCard.setCardId(input.getText().toString());
returnResult(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
});
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
AlertDialog dialog = builder.create();
// Now that the dialog exists, we can bind something that affects the OK button
input.addTextChangedListener(new SimpleTextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.length() == 0) {
input.setError(getString(R.string.card_id_must_not_be_empty));
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
} else {
input.setError(null);
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
}
}
});
dialog.show();
// Disable button (must be done **after** dialog is shown to prevent crash
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
// Set focus on input field
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
input.requestFocus();
}
public void addManually() {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ScanActivity.this);
builder.setTitle(R.string.add_manually_warning_title);
builder.setMessage(R.string.add_manually_warning_message);
builder.setPositiveButton(R.string.continue_, (dialog, which) -> {
Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class);
if (cardId != null) {
final Bundle b = new Bundle();
b.putString(LoyaltyCard.BUNDLE_LOYALTY_CARD_CARD_ID, cardId);
i.putExtras(b);
}
manualAddLauncher.launch(i);
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> setScannerActive(true));
builder.setOnCancelListener(dialog -> setScannerActive(true));
builder.show();
}
public void addFromImage() {
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_IMAGE);
}
public void addFromPdf() {
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PDF);
}
public void addFromPkPass() {
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PKPASS);
}
private void addFromImageOrFileAfterPermission(String mimeType, ActivityResultLauncher<Intent> launcher, int chooserText, int errorMessage) {
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType(mimeType);
Intent contentIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentIntent.setType(mimeType);
Intent chooserIntent = Intent.createChooser(photoPickerIntent, getString(chooserText));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { contentIntent });
try {
launcher.launch(chooserIntent);
} catch (ActivityNotFoundException e) {
setScannerActive(true);
Toast.makeText(getApplicationContext(), errorMessage, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
}
public void onCaptureManagerError(String errorMessage) {
if (mHasError) {
// We're already showing an error, ignore this new error
return;
}
showCameraError(errorMessage, false);
}
private void showCameraPermissionMissingText() {
showCameraError(getString(R.string.noCameraPermissionDirectToSystemSetting), true);
}
private void showCameraError(String message, boolean setOnClick) {
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorMessage.setText(message);
setCameraErrorState(true, setOnClick);
}
private void hideCameraError() {
setCameraErrorState(false, false);
}
private void setCameraErrorState(boolean visible, boolean setOnClick) {
mHasError = visible;
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorClickableArea.setOnClickListener(visible && setOnClick ? v -> {
navigateToSystemPermissionSetting();
} : null);
customBarcodeScannerBinding.cardInputContainer.setBackgroundColor(visible ? obtainThemeAttribute(com.google.android.material.R.attr.colorSurface) : Color.TRANSPARENT);
customBarcodeScannerBinding.cameraErrorLayout.getRoot().setVisibility(visible ? View.VISIBLE : View.GONE);
}
private void scaleScreen() {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int screenHeight = displayMetrics.heightPixels;
float mediumSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,MEDIUM_SCALE_FACTOR_DIP,getResources().getDisplayMetrics());
boolean shouldScaleSmaller = screenHeight < mediumSizePx;
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorIcon.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorTitle.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
}
private int obtainThemeAttribute(int attribute) {
TypedValue typedValue = new TypedValue();
getTheme().resolveAttribute(attribute, typedValue, true);
return typedValue.data;
}
private void navigateToSystemPermissionSetting() {
Intent permissionIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", getPackageName(), null));
permissionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(permissionIntent);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
onMockedRequestPermissionsResult(requestCode, permissions, grantResults);
}
public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
if (requestCode == CaptureManager.getCameraPermissionReqCode()) {
if (granted) {
hideCameraError();
} else {
showCameraPermissionMissingText();
}
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE || requestCode == PERMISSION_SCAN_ADD_FROM_PDF || requestCode == PERMISSION_SCAN_ADD_FROM_PKPASS) {
if (granted) {
if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
addFromImageOrFileAfterPermission("image/*", photoPickerLauncher, R.string.addFromImage, R.string.failedLaunchingPhotoPicker);
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_PDF) {
addFromImageOrFileAfterPermission("application/pdf", pdfPickerLauncher, R.string.addFromPdfFile, R.string.failedLaunchingFileManager);
} else {
addFromImageOrFileAfterPermission("application/*", pkpassPickerLauncher, R.string.addFromPkpass, R.string.failedLaunchingFileManager);
}
} else {
setScannerActive(true);
Toast.makeText(this, R.string.storageReadPermissionRequired, Toast.LENGTH_LONG).show();
}
}
}
}

View File

@@ -0,0 +1,599 @@
package protect.card_locker
import android.Manifest
import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.text.InputType
import android.util.DisplayMetrics
import android.util.Log
import android.util.TypedValue
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.ListAdapter
import android.widget.SimpleAdapter
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.widget.doOnTextChanged
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.zxing.DecodeHintType
import com.google.zxing.ResultPoint
import com.journeyapps.barcodescanner.BarcodeCallback
import com.journeyapps.barcodescanner.BarcodeResult
import com.journeyapps.barcodescanner.CaptureManager
import com.journeyapps.barcodescanner.DecoratedBarcodeView
import protect.card_locker.databinding.CustomBarcodeScannerBinding
import protect.card_locker.databinding.ScanActivityBinding
/**
* Custom Scannner Activity extending from Activity to display a custom layout form scanner view.
* <p>
* Based on https://github.com/journeyapps/zxing-android-embedded/blob/0fdfbce9fb3285e985bad9971c5f7c0a7a334e7b/sample/src/main/java/example/zxing/CustomScannerActivity.java
* originally licensed under Apache 2.0
*/
class ScanActivity : CatimaAppCompatActivity() {
private lateinit var binding: ScanActivityBinding
private lateinit var customBarcodeScannerBinding: CustomBarcodeScannerBinding
companion object {
private const val TAG = "Catima"
private const val MEDIUM_SCALE_FACTOR_DIP = 460
private const val COMPAT_SCALE_FACTOR_DIP = 320
private const val PERMISSION_SCAN_ADD_FROM_IMAGE = 100
private const val PERMISSION_SCAN_ADD_FROM_PDF = 101
private const val PERMISSION_SCAN_ADD_FROM_PKPASS = 102
private const val STATE_SCANNER_ACTIVE = "scannerActive"
}
private lateinit var capture: CaptureManager
private lateinit var barcodeScannerView: DecoratedBarcodeView
private var cardId: String? = null
private var addGroup: String? = null
private var torch = false
private lateinit var manualAddLauncher: ActivityResultLauncher<Intent>
// can't use the pre-made contract because that launches the file manager for image type instead of gallery
private lateinit var photoPickerLauncher: ActivityResultLauncher<Intent>
private lateinit var pdfPickerLauncher: ActivityResultLauncher<Intent>
private lateinit var pkpassPickerLauncher: ActivityResultLauncher<Intent>
private var mScannerActive = true
private var mHasError = false
private fun extractIntentFields(intent: Intent) {
val b = intent.extras
cardId = b?.getString(LoyaltyCard.BUNDLE_LOYALTY_CARD_CARD_ID)
addGroup = b?.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP)
Log.d(TAG, "Scan activity: id=$cardId")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ScanActivityBinding.inflate(layoutInflater)
customBarcodeScannerBinding = CustomBarcodeScannerBinding.bind(binding.zxingBarcodeScanner)
setTitle(R.string.scanCardBarcode)
setContentView(binding.root)
Utils.applyWindowInsets(binding.root)
setSupportActionBar(binding.toolbar)
enableToolbarBackButton()
extractIntentFields(intent)
manualAddLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
handleActivityResult(
Utils.SELECT_BARCODE_REQUEST,
result.resultCode,
result.data
)
}
photoPickerLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
handleActivityResult(
Utils.BARCODE_IMPORT_FROM_IMAGE_FILE,
result.resultCode,
result.data
)
}
pdfPickerLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
handleActivityResult(
Utils.BARCODE_IMPORT_FROM_PDF_FILE,
result.resultCode,
result.data
)
}
pkpassPickerLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
handleActivityResult(
Utils.BARCODE_IMPORT_FROM_PKPASS_FILE,
result.resultCode,
result.data
)
}
customBarcodeScannerBinding.fabOtherOptions.setOnClickListener {
setScannerActive(false)
val list: ArrayList<HashMap<String, Any>> = arrayListOf()
val texts = arrayOf(
getString(R.string.addWithoutBarcode),
getString(R.string.addManually),
getString(R.string.addFromImage),
getString(R.string.addFromPdfFile),
getString(R.string.addFromPkpass)
)
val icons = arrayOf(
R.drawable.baseline_block_24,
R.drawable.ic_edit,
R.drawable.baseline_image_24,
R.drawable.baseline_picture_as_pdf_24,
R.drawable.local_activity_24px
)
val columns = arrayOf("text", "icon")
for (i in 0 until texts.size) {
val map: HashMap<String, Any> = hashMapOf()
map.put(columns[0], texts[i])
map.put(columns[1], icons[i])
list.add(map)
}
val adapter: ListAdapter = SimpleAdapter(
this,
list,
R.layout.alertdialog_row_with_icon,
columns,
intArrayOf(R.id.textView, R.id.imageView)
)
val builder = MaterialAlertDialogBuilder(this).apply {
setTitle(getString(R.string.add_a_card_in_a_different_way))
setAdapter(adapter) { _, i ->
when (i) {
0 -> addWithoutBarcode()
1 -> addManually()
2 -> addFromImage()
3 -> addFromPdf()
4 -> addFromPkPass()
else -> throw IllegalArgumentException(
"Unknown 'Add a card in a different way' dialog option: $i"
)
}
}
setOnCancelListener { _ -> setScannerActive(true) }
}
builder.show()
}
// Configure barcodeScanner
barcodeScannerView = binding.zxingBarcodeScanner
val barcodeScannerIntent = Intent().apply {
val barcodeScannerIntentBundle = Bundle().apply {
putBoolean(DecodeHintType.ALSO_INVERTED.name, true)
}
putExtras(barcodeScannerIntentBundle)
}
barcodeScannerView.initializeFromIntent(barcodeScannerIntent)
// Even though we do the actual decoding with the barcodeScannerView
// CaptureManager needs to be running to show the camera and scanning bar
capture = CatimaCaptureManager(this, barcodeScannerView, this::onCaptureManagerError)
val captureIntent = Intent().apply {
val captureIntentBundle = Bundle().apply {
putBoolean(DecodeHintType.ALSO_INVERTED.name, false)
}
putExtras(captureIntentBundle)
}
capture.initializeFromIntent(captureIntent, savedInstanceState)
barcodeScannerView.decodeSingle(object : BarcodeCallback {
override fun barcodeResult(result: BarcodeResult) {
val loyaltyCard = LoyaltyCard().apply {
setCardId(result.text)
setBarcodeType(CatimaBarcode.fromBarcode(result.barcodeFormat))
}
returnResult(ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard))
}
override fun possibleResultPoints(resultPoints: List<ResultPoint?>?) {}
})
}
override fun onResume() {
super.onResume()
if (mScannerActive) {
capture.onResume()
}
if (!Utils.deviceHasCamera(this)) {
showCameraError(getString(R.string.noCameraFoundGuideText), false)
} else if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED
) {
showCameraPermissionMissingText()
} else {
hideCameraError()
}
scaleScreen()
}
override fun onPause() {
super.onPause()
capture.onPause()
}
override fun onDestroy() {
super.onDestroy()
capture.onDestroy()
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
capture.onSaveInstanceState(savedInstanceState)
savedInstanceState.putBoolean(STATE_SCANNER_ACTIVE, mScannerActive)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
mScannerActive = savedInstanceState.getBoolean(STATE_SCANNER_ACTIVE)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
if (packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
menuInflater.inflate(R.menu.scan_menu, menu)
}
barcodeScannerView.setTorchOff()
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
setResult(RESULT_CANCELED)
finish()
return true
} else if (item.itemId == R.id.action_toggle_flashlight) {
if (torch) {
torch = false
barcodeScannerView.setTorchOff()
item.setTitle(R.string.turn_flashlight_on)
item.setIcon(R.drawable.ic_flashlight_off_white_24dp)
} else {
torch = true
barcodeScannerView.setTorchOn()
item.setTitle(R.string.turn_flashlight_off)
item.setIcon(R.drawable.ic_flashlight_on_white_24dp)
}
}
return super.onOptionsItemSelected(item)
}
private fun setScannerActive(isActive: Boolean) {
if (isActive) {
barcodeScannerView.resume()
} else {
barcodeScannerView.pause()
}
mScannerActive = isActive
}
private fun returnResult(parseResult: ParseResult) {
val bundle = parseResult.toLoyaltyCardBundle(this).apply {
addGroup?.let { putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, it) }
}
val result = Intent().apply { putExtras(bundle) }
this.setResult(RESULT_OK, result)
finish()
}
private fun handleActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
super.onActivityResult(resultCode, resultCode, intent)
val parseResultList: List<ParseResult> =
Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this)
if (parseResultList.isEmpty()) {
setScannerActive(true)
return
}
Utils.makeUserChooseParseResultFromList(
this,
parseResultList,
object : ParseResultListDisambiguatorCallback {
override fun onUserChoseParseResult(parseResult: ParseResult) {
returnResult(parseResult)
}
override fun onUserDismissedSelector() {
setScannerActive(true)
}
})
}
private fun addWithoutBarcode() {
val builder: AlertDialog.Builder = MaterialAlertDialogBuilder(this).apply {
setOnCancelListener { dialogInterface -> setScannerActive(true) }
// Header
setTitle(R.string.addWithoutBarcode)
}
// Layout
val layout = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
}
val contentPadding = resources.getDimensionPixelSize(R.dimen.alert_dialog_content_padding)
val params = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
leftMargin = contentPadding
topMargin = contentPadding / 2
rightMargin = contentPadding
}
// Description
val currentTextview = TextView(this).apply {
text = getString(R.string.enter_card_id)
layoutParams = params
}
layout.addView(currentTextview)
//EditText with spacing
val input = EditText(this).apply {
inputType = InputType.TYPE_CLASS_TEXT
layoutParams = params
}
layout.addView(input)
// Set layout
builder.setView(layout).apply {
setPositiveButton(getString(R.string.ok)) { _, _ ->
val loyaltyCard = LoyaltyCard()
loyaltyCard.cardId = input.text.toString()
returnResult(ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard))
}
setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.cancel()
}
}
val dialog: AlertDialog = builder.create()
// Now that the dialog exists, we can bind something that affects the OK button
input.doOnTextChanged { text, _, _, _ ->
if (text.isNullOrEmpty()) {
input.error = getString(R.string.card_id_must_not_be_empty)
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
} else {
input.error = null
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true
}
}
dialog.show()
// Disable button (must be done **after** dialog is shown to prevent crash
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
// Set focus on input field
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
input.requestFocus()
}
fun addManually() {
val builder = MaterialAlertDialogBuilder(this).apply {
setTitle(R.string.add_manually_warning_title)
setMessage(R.string.add_manually_warning_message)
setPositiveButton(R.string.continue_) { _, _ ->
val i = Intent(applicationContext, BarcodeSelectorActivity::class.java)
if (cardId != null) {
val b = Bundle()
b.putString(LoyaltyCard.BUNDLE_LOYALTY_CARD_CARD_ID, cardId)
i.putExtras(b)
}
manualAddLauncher.launch(i)
}
setNegativeButton(R.string.cancel) { _, _ -> setScannerActive(true) }
setOnCancelListener { _ -> setScannerActive(true) }
}
builder.show()
}
fun addFromImage() {
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_IMAGE)
}
fun addFromPdf() {
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PDF)
}
fun addFromPkPass() {
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PKPASS)
}
private fun addFromImageOrFileAfterPermission(
mimeType: String,
launcher: ActivityResultLauncher<Intent>,
chooserText: Int,
errorMessage: Int
) {
val photoPickerIntent = Intent(Intent.ACTION_PICK)
photoPickerIntent.type = mimeType
val contentIntent = Intent(Intent.ACTION_GET_CONTENT)
contentIntent.type = mimeType
val chooserIntent = Intent.createChooser(photoPickerIntent, getString(chooserText))
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(contentIntent))
try {
launcher.launch(chooserIntent)
} catch (e: ActivityNotFoundException) {
setScannerActive(true)
Toast.makeText(applicationContext, errorMessage, Toast.LENGTH_LONG).show()
Log.e(TAG, "No activity found to handle intent", e)
}
}
fun onCaptureManagerError(errorMessage: String) {
if (mHasError) {
// We're already showing an error, ignore this new error
return
}
showCameraError(errorMessage, false)
}
private fun showCameraPermissionMissingText() {
showCameraError(getString(R.string.noCameraPermissionDirectToSystemSetting), true)
}
private fun showCameraError(message: String, setOnClick: Boolean) {
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorMessage.text = message
setCameraErrorState(true, setOnClick)
}
private fun hideCameraError() {
setCameraErrorState(false, false)
}
private fun setCameraErrorState(visible: Boolean, setOnClick: Boolean) {
mHasError = visible
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorClickableArea.setOnClickListener(
if (visible && setOnClick) { _ -> navigateToSystemPermissionSetting() }
else null
)
customBarcodeScannerBinding.cardInputContainer.setBackgroundColor(
if (visible) obtainThemeAttribute(com.google.android.material.R.attr.colorSurface)
else Color.TRANSPARENT
)
customBarcodeScannerBinding.cameraErrorLayout.root.visibility =
if (visible) View.VISIBLE else View.GONE
}
private fun scaleScreen() {
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
val screenHeight: Int = displayMetrics.heightPixels
val mediumSizePx: Float = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
MEDIUM_SCALE_FACTOR_DIP.toFloat(),
resources.displayMetrics
)
val shouldScaleSmaller = screenHeight < mediumSizePx
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorIcon.visibility =
if (shouldScaleSmaller) View.GONE else View.VISIBLE
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorTitle.visibility =
if (shouldScaleSmaller) View.GONE else View.VISIBLE
}
private fun obtainThemeAttribute(attribute: Int): Int {
val typedValue = TypedValue()
theme.resolveAttribute(attribute, typedValue, true)
return typedValue.data
}
private fun navigateToSystemPermissionSetting() {
val permissionIntent = Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", getPackageName(), null)
)
permissionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(permissionIntent)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
onMockedRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun onMockedRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
val granted =
grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
if (requestCode == CaptureManager.getCameraPermissionReqCode()) {
if (granted) {
hideCameraError()
} else {
showCameraPermissionMissingText()
}
} else if (requestCode in listOf(
PERMISSION_SCAN_ADD_FROM_IMAGE,
PERMISSION_SCAN_ADD_FROM_PDF,
PERMISSION_SCAN_ADD_FROM_PKPASS
)
) {
if (granted) {
if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
addFromImageOrFileAfterPermission(
"image/*",
photoPickerLauncher,
R.string.addFromImage,
R.string.failedLaunchingPhotoPicker
)
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_PDF) {
addFromImageOrFileAfterPermission(
"application/pdf",
pdfPickerLauncher,
R.string.addFromPdfFile,
R.string.failedLaunchingFileManager
)
} else {
addFromImageOrFileAfterPermission(
"application/*",
pkpassPickerLauncher,
R.string.addFromPkpass,
R.string.failedLaunchingFileManager
)
}
} else {
setScannerActive(true)
Toast.makeText(this, R.string.storageReadPermissionRequired, Toast.LENGTH_LONG)
.show()
}
}
}
}

View File

@@ -1,29 +0,0 @@
package protect.card_locker;
public class ThirdPartyInfo {
private final String mName;
private final String mUrl;
private final String mLicense;
public ThirdPartyInfo(String name, String url, String license) {
mName = name;
mUrl = url;
mLicense = license;
}
public String name() {
return mName;
}
public String url() {
return mUrl;
}
public String license() {
return mLicense;
}
public String toHtml() {
return String.format("<a href=\"%s\">%s</a> (%s)", url(), name(), license());
}
}

View File

@@ -0,0 +1,23 @@
package protect.card_locker
class ThirdPartyInfo(
private val mName: String,
private val mUrl: String,
private val mLicense: String
) {
fun name(): String {
return mName
}
fun url(): String {
return mUrl
}
fun license(): String {
return mLicense
}
fun toHtml(): String {
return String.format("<a href=\"%s\">%s</a> (%s)", url(), name(), license())
}
}

View File

@@ -1,93 +0,0 @@
package protect.card_locker;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import androidx.core.view.WindowInsetsControllerCompat;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.textview.MaterialTextView;
import com.yalantis.ucrop.UCropActivity;
public class UCropWrapper extends UCropActivity {
public static final String UCROP_TOOLBAR_TYPEFACE_STYLE = "ucop_toolbar_typeface_style";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Utils.applyWindowInsets(findViewById(android.R.id.content));
}
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
boolean darkMode = Utils.isDarkModeEnabled(this);
Window window = getWindow();
// setup status bar to look like the rest of the app
if (Build.VERSION.SDK_INT >= 23) {
if (window != null) {
View decorView = window.getDecorView();
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(window, decorView);
wic.setAppearanceLightStatusBars(!darkMode);
}
} else {
// icons are always white back then
if (window != null && !darkMode) {
window.setStatusBarColor(ColorUtils.compositeColors(Color.argb(127, 0, 0, 0), window.getStatusBarColor()));
}
}
// find and check views that we wish to color modify
// for when we update ucrop or switch to another cropper
View check = findViewById(com.yalantis.ucrop.R.id.wrapper_controls);
if (check instanceof FrameLayout) {
FrameLayout controls = (FrameLayout) check;
check = findViewById(com.yalantis.ucrop.R.id.wrapper_states);
if (check instanceof LinearLayout) {
LinearLayout states = (LinearLayout) check;
for (int i = 0; i < controls.getChildCount(); i++) {
check = controls.getChildAt(i);
if (check instanceof AppCompatImageView) {
AppCompatImageView controlsBackgroundImage = (AppCompatImageView) check;
// everything gathered and are as expected, now perform color patching
Utils.patchColors(this);
int colorSurface = MaterialColors.getColor(this, com.google.android.material.R.attr.colorSurface, ContextCompat.getColor(this, R.color.md_theme_light_surface));
int colorOnSurface = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSurface, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
Drawable controlsBackgroundImageDrawable = controlsBackgroundImage.getBackground();
controlsBackgroundImageDrawable.mutate();
controlsBackgroundImageDrawable.setTint(darkMode ? colorOnSurface : colorSurface);
controlsBackgroundImage.setBackgroundDrawable(controlsBackgroundImageDrawable);
states.setBackgroundColor(darkMode ? colorSurface : colorOnSurface);
break;
}
}
}
}
// change toolbar font
check = findViewById(com.yalantis.ucrop.R.id.toolbar_title);
if (check instanceof MaterialTextView) {
MaterialTextView toolbarTextview = (MaterialTextView) check;
Intent intent = getIntent();
int style = intent.getIntExtra(UCROP_TOOLBAR_TYPEFACE_STYLE, -1);
if (style != -1) {
toolbarTextview.setTypeface(Typeface.defaultFromStyle(style));
}
}
}
}

View File

@@ -0,0 +1,122 @@
package protect.card_locker
import android.graphics.Color
import android.graphics.Typeface
import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.children
import com.google.android.material.color.MaterialColors
import com.google.android.material.textview.MaterialTextView
import com.yalantis.ucrop.UCropActivity
class UCropWrapper : UCropActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Utils.applyWindowInsets(findViewById(android.R.id.content))
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
val darkMode = Utils.isDarkModeEnabled(this)
// setup status bar to look like the rest of the app
setupStatusBar(darkMode)
// find and check views that we wish to color modify
// for when we update ucrop or switch to another cropper
checkViews(darkMode)
// change toolbar font
changeToolbarFont()
}
private fun setupStatusBar(darkMode: Boolean) {
if (window == null) {
return
}
if (Build.VERSION.SDK_INT >= 23) {
val decorView = window.decorView
val wic = WindowInsetsControllerCompat(window, decorView)
wic.isAppearanceLightStatusBars = !darkMode
} else if (!darkMode) {
window.statusBarColor = ColorUtils.compositeColors(
Color.argb(127, 0, 0, 0),
window.statusBarColor
)
}
}
private fun checkViews(darkMode: Boolean) {
var view = findViewById<View?>(com.yalantis.ucrop.R.id.wrapper_controls)
if (view !is FrameLayout) {
return
}
val controls = view
view = findViewById(com.yalantis.ucrop.R.id.wrapper_states)
if (view !is LinearLayout) {
return
}
val states = view
controls.children.firstOrNull { it is AppCompatImageView }?.let {
// everything gathered and are as expected, now perform color patching
Utils.patchColors(this)
val colorSurface = MaterialColors.getColor(
this,
com.google.android.material.R.attr.colorSurface,
ContextCompat.getColor(
this,
R.color.md_theme_light_surface
)
)
val colorOnSurface = MaterialColors.getColor(
this,
com.google.android.material.R.attr.colorOnSurface,
ContextCompat.getColor(
this,
R.color.md_theme_light_onSurface
)
)
val controlsBackgroundImageDrawable = it.background
controlsBackgroundImageDrawable.mutate()
controlsBackgroundImageDrawable.setTint(
if (darkMode) {
colorOnSurface
} else {
colorSurface
}
)
it.background = controlsBackgroundImageDrawable
states.setBackgroundColor(
if (darkMode) {
colorSurface
} else {
colorOnSurface
}
)
}
}
private fun changeToolbarFont() {
val toolbar = findViewById<View?>(com.yalantis.ucrop.R.id.toolbar_title)
if (toolbar !is MaterialTextView) {
return
}
val style = intent.getIntExtra(UCROP_TOOLBAR_TYPEFACE_STYLE, -1)
if (style != -1) {
toolbar.setTypeface(Typeface.defaultFromStyle(style))
}
}
internal companion object {
const val UCROP_TOOLBAR_TYPEFACE_STYLE: String = "ucop_toolbar_typeface_style"
}
}

View File

@@ -87,10 +87,10 @@ import java.util.Currency;
import java.util.Date;
import java.util.EnumMap;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -143,7 +143,7 @@ public class Utils {
int pixelSize = context.getResources().getDimensionPixelSize(R.dimen.tileLetterImageSize);
if (backgroundColor == null) {
backgroundColor = LetterBitmap.getDefaultColor(context, store);
backgroundColor = LetterBitmap.Companion.getDefaultColor(context, store);
}
return new LetterBitmap(context, store, store,
@@ -228,6 +228,58 @@ public class Utils {
return parseResultList;
}
static public List<ParseResult> retrieveBarcodesFromPkPasses(Context context, Uri uri) {
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<>();
}
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<>();
}
List<ParseResult> parseResultList = new ArrayList<>();
int i = 0;
for (PkpassParser pkpassParser : pkpassesParser.getPkpassParsers()) {
ParseResult parseResult;
List<String> locales = pkpassParser.listLocales();
if (locales.isEmpty()) {
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<>();
}
parseResult.setNote(String.format(context.getString(R.string.cardWithNumber), i+1));
parseResultList.add(parseResult);
} else {
for (String locale : locales) {
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<>();
}
parseResult.setNote(String.format(context.getString(R.string.cardWithNumberAndLocale), i+1, locale));
parseResultList.add(parseResult);
}
}
i++;
}
return parseResultList;
}
static public List<ParseResult> retrieveBarcodesFromPdf(Context context, Uri uri) {
Log.i(TAG, "Received PDF file with possible barcode");
if (uri == null) {
@@ -319,7 +371,19 @@ public class Utils {
}
if (requestCode == Utils.BARCODE_IMPORT_FROM_PKPASS_FILE) {
return retrieveBarcodesFromPkPass(context, intent.getData());
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<>();
}
if (Objects.equals(context.getContentResolver().getType(intentData), "application/vnd.apple.pkpasses")) {
return retrieveBarcodesFromPkPasses(context, intentData);
}
return retrieveBarcodesFromPkPass(context, intentData);
}
if (requestCode == Utils.BARCODE_SCAN || requestCode == Utils.SELECT_BARCODE_REQUEST) {
@@ -850,7 +914,7 @@ public class Utils {
public static File copyToTempFile(Context context, InputStream input, String name) throws IOException {
File file = createTempFile(context, name);
try (input; FileOutputStream out = new FileOutputStream(file)) {
try (FileOutputStream out = new FileOutputStream(file)) {
byte[] buf = new byte[4096];
int len;
while ((len = input.read(buf)) != -1) {
@@ -1065,7 +1129,7 @@ public class Utils {
}
public static int getHeaderColor(Context context, LoyaltyCard loyaltyCard) {
return loyaltyCard.headerColor != null ? loyaltyCard.headerColor : LetterBitmap.getDefaultColor(context, loyaltyCard.store);
return loyaltyCard.headerColor != null ? loyaltyCard.headerColor : LetterBitmap.Companion.getDefaultColor(context, loyaltyCard.store);
}
public static String checksum(InputStream input) throws IOException {

View File

@@ -1,9 +0,0 @@
package protect.card_locker.async;
import java.util.concurrent.Callable;
public interface CompatCallable<T> extends Callable<T> {
void onPostExecute(Object result);
void onPreExecute();
}

View File

@@ -0,0 +1,9 @@
package protect.card_locker.async
import java.util.concurrent.Callable
interface CompatCallable<T> : Callable<T?> {
fun onPostExecute(result: Any?)
fun onPreExecute()
}

View File

@@ -0,0 +1,97 @@
package protect.card_locker.compose
import androidx.activity.compose.LocalActivity
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp
import protect.card_locker.OpenWebLinkHandler
import protect.card_locker.R
@Composable
fun CatimaAboutSection(
title: String,
message: String,
modifier: Modifier = Modifier,
onClickUrl: String? = null,
onClickDialogText: AnnotatedString? = null,
) {
val activity = LocalActivity.current
val openDialog = remember { mutableStateOf(false) }
Row(
modifier = modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
.clickable {
if (onClickDialogText != null) {
openDialog.value = true
} else if (onClickUrl != null) {
OpenWebLinkHandler().openBrowser(activity, onClickUrl)
}
}
) {
Column(modifier = Modifier.weight(1F)) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium
)
Text(text = message)
}
Text(modifier = Modifier.align(Alignment.CenterVertically),
text = ">",
style = MaterialTheme.typography.bodyMedium
)
}
if (openDialog.value && onClickDialogText != null) {
AlertDialog(
icon = {},
title = {
Text(text = title)
},
text = {
Text(
text = onClickDialogText,
modifier = Modifier.verticalScroll(rememberScrollState())
)
},
onDismissRequest = {
openDialog.value = false
},
confirmButton = {
TextButton(
onClick = {
openDialog.value = false
}
) {
Text(stringResource(R.string.ok))
}
},
dismissButton = {
if (onClickUrl != null) {
TextButton(
onClick = {
OpenWebLinkHandler().openBrowser(activity, onClickUrl)
}
) {
Text(stringResource(R.string.view_online))
}
}
}
)
}
}

View File

@@ -0,0 +1,34 @@
package protect.card_locker.compose
import androidx.activity.OnBackPressedDispatcher
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import protect.card_locker.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CatimaTopAppBar(title: String, onBackPressedDispatcher: OnBackPressedDispatcher?) {
TopAppBar(
modifier = Modifier.testTag("topbar_catima"),
title = { Text(text = title) },
navigationIcon = {
if (onBackPressedDispatcher != null) {
IconButton(onClick = { onBackPressedDispatcher.onBackPressed() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.back)
)
}
}
}
)
}

View File

@@ -0,0 +1,46 @@
package protect.card_locker.compose.theme
import android.os.Build
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import protect.card_locker.R
import protect.card_locker.preferences.Settings
@Composable
fun CatimaTheme(content: @Composable () -> Unit) {
val context = LocalContext.current
val settings = Settings(context)
val isDynamicColorSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val lightTheme = if (isDynamicColorSupported) {
dynamicLightColorScheme(context)
} else {
lightColorScheme(primary = colorResource(id = R.color.md_theme_light_primary))
}
val darkTheme = if (isDynamicColorSupported) {
dynamicDarkColorScheme(context)
} else {
darkColorScheme(primary = colorResource(id = R.color.md_theme_dark_primary))
}
val colorScheme = when (settings.theme) {
AppCompatDelegate.MODE_NIGHT_NO -> lightTheme
AppCompatDelegate.MODE_NIGHT_YES -> darkTheme
else -> if (isSystemInDarkTheme()) darkTheme else lightTheme
}
MaterialTheme(
colorScheme = colorScheme,
content = content
)
}

View File

@@ -1,8 +0,0 @@
package protect.card_locker.importexport;
public enum DataFormat {
Catima,
Fidme,
Stocard,
VoucherVault;
}

View File

@@ -0,0 +1,7 @@
package protect.card_locker.importexport
enum class DataFormat {
Catima,
Fidme,
VoucherVault
}

View File

@@ -1,20 +0,0 @@
package protect.card_locker.importexport;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import java.io.IOException;
import java.io.OutputStream;
/**
* Interface for a class which can export the contents of the database
* in a given format.
*/
public interface Exporter {
/**
* Export the database to the output stream in a given format.
*
* @throws IOException
*/
void exportData(Context context, SQLiteDatabase database, OutputStream output, char[] password) throws IOException, InterruptedException;
}

View File

@@ -0,0 +1,25 @@
package protect.card_locker.importexport
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import java.io.IOException
import java.io.OutputStream
/**
* Interface for a class which can export the contents of the database
* in a given format.
*/
interface Exporter {
/**
* Export the database to the output stream in a given format.
*
* @throws IOException, InterruptedException
*/
@Throws(IOException::class, InterruptedException::class)
fun exportData(
context: Context,
database: SQLiteDatabase,
output: OutputStream,
password: CharArray
)
}

View File

@@ -1,7 +0,0 @@
package protect.card_locker.importexport;
public enum ImportExportResultType {
Success,
GenericFailure,
BadPassword;
}

View File

@@ -0,0 +1,7 @@
package protect.card_locker.importexport
enum class ImportExportResultType {
Success,
GenericFailure,
BadPassword
}

View File

@@ -1,27 +0,0 @@
package protect.card_locker.importexport;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import org.json.JSONException;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import protect.card_locker.FormatException;
/**
* Interface for a class which can import the contents of a stream
* into the database.
*/
public interface Importer {
/**
* Import data from the input stream in a given format into
* the database.
*
* @throws IOException
* @throws FormatException
*/
void importData(Context context, SQLiteDatabase database, File inputFile, char[] password) throws IOException, FormatException, InterruptedException, JSONException, ParseException;
}

View File

@@ -0,0 +1,39 @@
package protect.card_locker.importexport
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import org.json.JSONException
import protect.card_locker.FormatException
import java.io.File
import java.io.IOException
import java.text.ParseException
/**
* Interface for a class which can import the contents of a stream
* into the database.
*/
interface Importer {
/**
* Import data from the input stream in a given format into
* the database.
*
* @throws IOException
* @throws FormatException
* @throws InterruptedException
* @throws JSONException
* @throws ParseException
*/
@Throws(
IOException::class,
FormatException::class,
InterruptedException::class,
JSONException::class,
ParseException::class
)
fun importData(
context: Context,
database: SQLiteDatabase,
inputFile: File,
password: CharArray
)
}

View File

@@ -37,9 +37,6 @@ public class MultiFormatImporter {
case Fidme:
importer = new FidmeImporter();
break;
case Stocard:
importer = new StocardImporter();
break;
case VoucherVault:
importer = new VoucherVaultImporter();
break;

View File

@@ -1,428 +0,0 @@
package protect.card_locker.importexport;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.zxing.BarcodeFormat;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.io.inputstream.ZipInputStream;
import net.lingala.zip4j.model.FileHeader;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
import protect.card_locker.ImageLocationType;
import protect.card_locker.LoyaltyCard;
import protect.card_locker.R;
import protect.card_locker.Utils;
import protect.card_locker.ZipUtils;
/**
* Class for importing a database from CSV (Comma Separate Values)
* formatted data.
* <p>
* The database's loyalty cards are expected to appear in the CSV data.
* A header is expected for the each table showing the names of the columns.
*/
public class StocardImporter implements Importer {
public static class StocardProvider {
public String name = null;
public String barcodeFormat = null;
public Bitmap logo = null;
}
public static class StocardRecord {
public String providerId = null;
public String store = null;
public String label = null;
public String note = null;
public String cardId = null;
public String barcodeType = null;
public Long lastUsed = null;
public Bitmap frontImage = null;
public Bitmap backImage = null;
@NonNull
@Override
public String toString() {
return String.format(
"StocardRecord{%n providerId=%s,%n store=%s,%n label=%s,%n note=%s,%n cardId=%s,%n"
+ " barcodeType=%s,%n lastUsed=%s,%n frontImage=%s,%n backImage=%s%n}",
this.providerId,
this.store,
this.label,
this.note,
this.cardId,
this.barcodeType,
this.lastUsed,
this.frontImage,
this.backImage
);
}
}
public static class ZIPData {
public final Map<String, StocardRecord> cards;
public final Map<String, StocardProvider> providers;
ZIPData(final Map<String, StocardRecord> cards, final Map<String, StocardProvider> providers) {
this.cards = cards;
this.providers = providers;
}
}
public static class ImportedData {
public final List<LoyaltyCard> cards;
public final Map<Integer, Map<ImageLocationType, Bitmap>> images;
ImportedData(final List<LoyaltyCard> cards, final Map<Integer, Map<ImageLocationType, Bitmap>> images) {
this.cards = cards;
this.images = images;
}
}
public static final String PROVIDER_PREFIX = "/loyalty-card-providers/";
private static final String TAG = "Catima";
public void importData(Context context, SQLiteDatabase database, File inputFile, char[] password) throws IOException, FormatException, JSONException, ParseException {
ZIPData zipData = new ZIPData(new HashMap<>(), new HashMap<>());
final CSVParser parser = new CSVParser(new InputStreamReader(context.getResources().openRawResource(R.raw.stocard_stores), StandardCharsets.UTF_8), CSVFormat.RFC4180.builder().setHeader().build());
try {
for (CSVRecord record : parser) {
StocardProvider provider = new StocardProvider();
provider.name = record.get("name").trim();
provider.barcodeFormat = record.get("barcodeFormat").trim();
zipData.providers.put(record.get("_id").trim(), provider);
}
parser.close();
} catch (IllegalArgumentException | IllegalStateException e) {
throw new FormatException("Issue parsing CSV data", e);
}
ZipFile zipFile = new ZipFile(inputFile, password);
zipData = importZIP(zipFile, zipData);
zipFile.close();
if (zipData.cards.keySet().size() == 0) {
throw new FormatException("Couldn't find any loyalty cards in this Stocard export.");
}
ImportedData importedData = importLoyaltyCardHashMap(context, zipData);
saveAndDeduplicate(context, database, importedData);
}
public ZIPData importZIP(ZipFile zipFile, final ZIPData zipData) throws IOException, FormatException, JSONException {
Map<String, StocardRecord> cards = zipData.cards;
Map<String, StocardProvider> providers = zipData.providers;
String[] customProvidersBaseName = null;
String[] cardBaseName = null;
String customProviderId = "";
String cardName = "";
for (FileHeader fileHeader : zipFile.getFileHeaders()) {
String fileName = fileHeader.getFileName();
String[] nameParts = fileName.split("/");
if (nameParts.length < 2) {
continue;
}
String userId = nameParts[1];
ZipInputStream zipInputStream = zipFile.getInputStream(fileHeader);
if (customProvidersBaseName == null) {
// FIXME: can we use the points-account/statement/content.json balance info somehow?
/*
Known files:
extracts/<user-UUID>/users/<user-UUID>/
analytics-properties/content.json
devices/<device-UUID>/
analytics-properties/content.json
content.json
ip-location-wifi/content.json
enabled-regions/<UUID>/content.json
loyalty-card-custom-providers/<provider-UUID>/content.json - custom providers
loyalty-cards/<card-UUID>/
card-linked-coupons/accounts/default/
content.json
user-coupons/<UUID>/content.json
content.json - card itself
images/back.png - back image (legacy)
images/back/back.jpg - back image
images/back/content.json
images/front.png - front image (legacy)
images/front/content.json
images/front/front.jpg - front image
notes/default/content.json - note
points-account/
content.json
statement/content.json
usages/<UUID>/content.json - timestamps
usage-statistics/content.json - timestamps
reward-program-balances/<UUID>/content.json
*/
customProvidersBaseName = new String[]{
"extracts",
userId,
"users",
userId,
"loyalty-card-custom-providers"
};
cardBaseName = new String[]{
"extracts",
userId,
"users",
userId,
"loyalty-cards"
};
}
if (startsWith(nameParts, customProvidersBaseName, 1)) {
// Extract providerId
customProviderId = nameParts[customProvidersBaseName.length];
StocardProvider provider = providers.get(customProviderId);
if (provider == null) {
provider = new StocardProvider();
providers.put(customProviderId, provider);
}
// Name file
if (fileName.endsWith(customProviderId + "/content.json")) {
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
provider.name = jsonObject.getString("name");
} else if (fileName.endsWith("logo.png")) {
provider.logo = ZipUtils.readImage(zipInputStream);
} else if (!fileName.endsWith("/")) {
Log.d(TAG, "Unknown or unused loyalty-card-custom-providers file " + fileName + ", skipping...");
}
} else if (startsWith(nameParts, cardBaseName, 1)) {
// Extract cardName
cardName = nameParts[cardBaseName.length];
StocardRecord record = cards.get(cardName);
if (record == null) {
record = new StocardRecord();
cards.put(cardName, record);
}
// This is the card itself
if (fileName.endsWith(cardName + "/content.json")) {
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
record.cardId = jsonObject.getString("input_id");
if (jsonObject.has("input_provider_name")) {
record.store = jsonObject.getString("input_provider_name");
}
if (jsonObject.has("label")) {
String label = jsonObject.getString("label");
if (!label.isBlank()) {
record.label = label;
}
}
// Provider ID can be either custom or not, extract whatever version is relevant
String customProviderPrefix = "/users/" + userId + "/loyalty-card-custom-providers/";
String providerId = jsonObject
.getJSONObject("input_provider_reference")
.getString("identifier");
if (providerId.startsWith(customProviderPrefix)) {
providerId = providerId.substring(customProviderPrefix.length());
} else if (providerId.startsWith(PROVIDER_PREFIX)) {
providerId = providerId.substring(PROVIDER_PREFIX.length());
} else {
throw new FormatException("Unsupported provider ID format: " + providerId);
}
record.providerId = providerId;
if (jsonObject.has("input_barcode_format")) {
record.barcodeType = jsonObject.getString("input_barcode_format");
}
} else if (fileName.endsWith("notes/default/content.json")) {
record.note = ZipUtils.readJSON(zipInputStream).getString("content");
} else if (fileName.endsWith("usage-statistics/content.json")) {
JSONArray usages = ZipUtils.readJSON(zipInputStream).getJSONArray("usages");
for (int i = 0; i < usages.length(); i++) {
JSONObject lastUsedObject = usages.getJSONObject(i);
String lastUsedString = lastUsedObject.getJSONObject("time").getString("value");
long timeStamp = Instant.parse(lastUsedString).getEpochSecond();
if (record.lastUsed == null || timeStamp > record.lastUsed) {
record.lastUsed = timeStamp;
}
}
} else if (fileName.matches(".*/usages/[^/]+/content.json")) {
JSONObject lastUsedObject = ZipUtils.readJSON(zipInputStream);
String lastUsedString = lastUsedObject.getJSONObject("time").getString("value");
long timeStamp = Instant.parse(lastUsedString).getEpochSecond();
if (record.lastUsed == null || timeStamp > record.lastUsed) {
record.lastUsed = timeStamp;
}
} else if (fileName.endsWith("/images/front.png") || fileName.endsWith("/images/front/front.jpg")) {
record.frontImage = ZipUtils.readImage(zipInputStream);
} else if (fileName.endsWith("/images/back.png") || fileName.endsWith("/images/back/back.jpg")) {
record.backImage = ZipUtils.readImage(zipInputStream);
} else if (!fileName.endsWith("/")) {
Log.d(TAG, "Unknown or unused loyalty-cards file " + fileName + ", skipping...");
}
} else if (!fileName.endsWith("/")) {
Log.d(TAG, "Unknown or unused file " + fileName + ", skipping...");
}
zipInputStream.close();
}
return new ZIPData(cards, providers);
}
public ImportedData importLoyaltyCardHashMap(Context context, final ZIPData zipData) throws FormatException {
ImportedData importedData = new ImportedData(new ArrayList<>(), new HashMap<>());
int tempID = 0;
List<String> cardKeys = new ArrayList<>(zipData.cards.keySet());
Collections.sort(cardKeys);
for (String key : cardKeys) {
StocardRecord record = zipData.cards.get(key);
if (record.providerId == null) {
Log.d(TAG, "Missing providerId for card " + record + ", ignoring...");
continue;
}
if (record.cardId == null) {
throw new FormatException("No card ID listed, but is required");
}
StocardProvider provider = zipData.providers.get(record.providerId);
// Read store from card, if not available (old export), fall back to providerData
String store = record.store != null ? record.store : provider != null ? provider.name : record.providerId;
String note = record.note != null ? record.note : "";
String barcodeTypeString = record.barcodeType != null ? record.barcodeType : provider != null ? provider.barcodeFormat : null;
if (record.label != null && !record.label.equals(store) && !record.label.equals(note)) {
note = note.isEmpty() ? record.label : note + "\n" + record.label;
}
CatimaBarcode barcodeType = null;
if (barcodeTypeString != null && !barcodeTypeString.isEmpty()) {
if (barcodeTypeString.equals("RSS_DATABAR_EXPANDED")) {
barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.RSS_EXPANDED);
} else if (barcodeTypeString.equals("GS1_128")) {
barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.CODE_128);
} else {
barcodeType = CatimaBarcode.fromName(barcodeTypeString);
}
}
int headerColor = Utils.getRandomHeaderColor(context);
if (provider != null && provider.logo != null) {
headerColor = Utils.getHeaderColorFromImage(provider.logo, headerColor);
}
long lastUsed = record.lastUsed != null ? record.lastUsed : Utils.getUnixTime();
LoyaltyCard card = new LoyaltyCard(
tempID,
store,
note,
null,
null,
BigDecimal.valueOf(0),
null,
record.cardId,
null,
barcodeType,
headerColor,
0,
lastUsed,
DBHelper.DEFAULT_ZOOM_LEVEL,
DBHelper.DEFAULT_ZOOM_LEVEL_WIDTH,
0,
null,
null,
null,
null,
null,
null
);
importedData.cards.add(card);
Map<ImageLocationType, Bitmap> images = new HashMap<>();
if (provider != null && provider.logo != null) {
images.put(ImageLocationType.icon, provider.logo);
}
if (record.frontImage != null) {
images.put(ImageLocationType.front, record.frontImage);
}
if (record.backImage != null) {
images.put(ImageLocationType.back, record.backImage);
}
importedData.images.put(tempID, images);
tempID++;
}
return importedData;
}
public void saveAndDeduplicate(Context context, SQLiteDatabase database, final ImportedData data) throws IOException {
// This format does not have IDs that can cause conflicts
// Proper deduplication for all formats will be implemented later
for (LoyaltyCard card : data.cards) {
// card.id is temporary and only used to index the images Map
long id = DBHelper.insertLoyaltyCard(database, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType,
card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
for (Map.Entry<ImageLocationType, Bitmap> entry : data.images.get(card.id).entrySet()) {
Utils.saveCardImage(context, entry.getValue(), (int) id, entry.getKey());
}
}
}
private boolean startsWith(String[] full, String[] start, int minExtraLength) {
if (full.length - minExtraLength < start.length) {
return false;
}
for (int i = 0; i < start.length; i++) {
if (!start[i].contentEquals(full[i])) {
return false;
}
}
return true;
}
}

View File

@@ -74,10 +74,6 @@ public class Settings {
return getBoolean(R.string.settings_key_display_barcode_max_brightness, true);
}
public String getCardViewOrientation() {
return getString(R.string.settings_key_card_orientation, getResString(R.string.settings_key_follow_system_orientation));
}
public boolean getKeepScreenOn() {
return getBoolean(R.string.settings_key_keep_screen_on, true);
}

View File

@@ -1,206 +0,0 @@
package protect.card_locker.preferences;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
import androidx.core.os.LocaleListCompat;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import com.google.android.material.color.DynamicColors;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.stream.Collectors;
import protect.card_locker.CatimaAppCompatActivity;
import protect.card_locker.MainActivity;
import protect.card_locker.R;
import protect.card_locker.Utils;
import protect.card_locker.databinding.SettingsActivityBinding;
public class SettingsActivity extends CatimaAppCompatActivity {
private SettingsActivityBinding binding;
private final static String RELOAD_MAIN_STATE = "mReloadMain";
private SettingsFragment fragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = SettingsActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.settings);
setContentView(binding.getRoot());
Utils.applyWindowInsets(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
enableToolbarBackButton();
// Display the fragment as the main content.
fragment = new SettingsFragment();
getSupportFragmentManager().beginTransaction()
.replace(R.id.settings_container, fragment)
.commit();
// restore reload main state
if (savedInstanceState != null) {
fragment.mReloadMain = savedInstanceState.getBoolean(RELOAD_MAIN_STATE);
}
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
finishSettingsActivity();
}
});
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(RELOAD_MAIN_STATE, fragment.mReloadMain);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
finishSettingsActivity();
return true;
}
return super.onOptionsItemSelected(item);
}
private void finishSettingsActivity() {
if (fragment.mReloadMain) {
Intent intent = new Intent();
intent.putExtra(MainActivity.RESTART_ACTIVITY_INTENT, true);
setResult(Activity.RESULT_OK, intent);
} else {
setResult(Activity.RESULT_OK);
}
finish();
}
public static class SettingsFragment extends PreferenceFragmentCompat {
private static final String DIALOG_FRAGMENT_TAG = "SettingsFragment";
public boolean mReloadMain;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
// Show pretty names and summaries
ListPreference themePreference = findPreference(getResources().getString(R.string.settings_key_theme));
assert themePreference != null;
themePreference.setOnPreferenceChangeListener((preference, o) -> {
if (o.toString().equals(getResources().getString(R.string.settings_key_light_theme))) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
} else if (o.toString().equals(getResources().getString(R.string.settings_key_dark_theme))) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
}
return true;
});
ListPreference themeColorPreference = findPreference(getResources().getString(R.string.setting_key_theme_color));
assert themeColorPreference != null;
themeColorPreference.setOnPreferenceChangeListener((preference, o) -> {
refreshActivity(true);
return true;
});
if (!DynamicColors.isDynamicColorAvailable()) {
themeColorPreference.setEntryValues(R.array.color_values_no_dynamic);
themeColorPreference.setEntries(R.array.color_value_strings_no_dynamic);
}
Preference oledDarkPreference = findPreference(getResources().getString(R.string.settings_key_oled_dark));
assert oledDarkPreference != null;
oledDarkPreference.setOnPreferenceChangeListener((preference, newValue) -> {
refreshActivity(true);
return true;
});
ListPreference localePreference = findPreference(getResources().getString(R.string.settings_key_locale));
assert localePreference != null;
CharSequence[] entryValues = localePreference.getEntryValues();
List<CharSequence> entries = new ArrayList<>();
for (CharSequence entry : entryValues) {
if (entry.length() == 0) {
entries.add(getResources().getString(R.string.settings_system_locale));
} else {
Locale entryLocale = Utils.stringToLocale(entry.toString());
entries.add(entryLocale.getDisplayName(entryLocale));
}
}
localePreference.setEntries(entries.toArray(new CharSequence[entryValues.length]));
// Make locale picker preference in sync with system settings
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Locale sysLocale = AppCompatDelegate.getApplicationLocales().get(0);
if (sysLocale == null) {
// Corresponds to "System"
localePreference.setValue("");
} else {
// Need to set preference's value to one of localePreference.getEntryValues() to match the locale.
// Locale.toLanguageTag() theoretically should be one of the values in localePreference.getEntryValues()...
// But it doesn't work for some locales. so trying something more heavyweight.
// Obtain all locales supported by the app.
List<Locale> appLocales = Arrays.stream(localePreference.getEntryValues())
.map(Objects::toString)
.map(Utils::stringToLocale)
.collect(Collectors.toList());
// Get the app locale that best matches the system one
Locale bestMatchLocale = Utils.getBestMatchLocale(appLocales, sysLocale);
// Get its index in supported locales
int index = appLocales.indexOf(bestMatchLocale);
// Set preference value to entry value at that index
localePreference.setValue(localePreference.getEntryValues()[index].toString());
}
}
localePreference.setOnPreferenceChangeListener((preference, newValue) -> {
// See corresponding comment in Utils.updateBaseContextLocale for Android 6- notes
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
refreshActivity(true);
return true;
}
String newLocale = (String) newValue;
// If newLocale is empty, that means "System" was selected
AppCompatDelegate.setApplicationLocales(newLocale.isEmpty() ? LocaleListCompat.getEmptyLocaleList() : LocaleListCompat.create(Utils.stringToLocale(newLocale)));
return true;
});
// Disable content provider on SDK < 23 since dangerous permissions
// are granted at install-time
Preference contentProviderReadPreference = findPreference(getResources().getString(R.string.settings_key_allow_content_provider_read));
assert contentProviderReadPreference != null;
contentProviderReadPreference.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
}
private void refreshActivity(boolean reloadMain) {
mReloadMain = reloadMain || mReloadMain;
Activity activity = getActivity();
if (activity != null) {
activity.recreate();
}
}
}
}

View File

@@ -0,0 +1,191 @@
package protect.card_locker.preferences
import android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.MenuItem
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.color.DynamicColors
import protect.card_locker.BuildConfig
import protect.card_locker.CatimaAppCompatActivity
import protect.card_locker.MainActivity
import protect.card_locker.R
import protect.card_locker.Utils
import protect.card_locker.databinding.SettingsActivityBinding
class SettingsActivity : CatimaAppCompatActivity() {
private lateinit var binding: SettingsActivityBinding
private lateinit var fragment: SettingsFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = SettingsActivityBinding.inflate(layoutInflater)
setTitle(R.string.settings)
setContentView(binding.root)
Utils.applyWindowInsets(binding.root)
val toolbar = binding.toolbar
setSupportActionBar(toolbar)
enableToolbarBackButton()
// Display the fragment as the main content.
fragment = SettingsFragment()
supportFragmentManager.beginTransaction()
.replace(R.id.settings_container, fragment)
.commit()
// restore reload main state
if (savedInstanceState != null) {
fragment.mReloadMain = savedInstanceState.getBoolean(RELOAD_MAIN_STATE)
}
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
finishSettingsActivity()
}
})
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(RELOAD_MAIN_STATE, fragment.mReloadMain)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == android.R.id.home) {
finishSettingsActivity()
return true
}
return super.onOptionsItemSelected(item)
}
private fun finishSettingsActivity() {
if (fragment.mReloadMain) {
val intent = Intent()
intent.putExtra(MainActivity.RESTART_ACTIVITY_INTENT, true)
setResult(RESULT_OK, intent)
} else {
setResult(RESULT_OK)
}
finish()
}
class SettingsFragment : PreferenceFragmentCompat() {
var mReloadMain: Boolean = false
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences)
// Show pretty names and summaries
val themePreference = findPreference<ListPreference>(getString(R.string.settings_key_theme))
themePreference!!.setOnPreferenceChangeListener { _, o ->
when (o.toString()) {
getString(R.string.settings_key_light_theme) -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
getString(R.string.settings_key_dark_theme) -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
else -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
}
true
}
val themeColorPreference = findPreference<ListPreference>(getString(R.string.setting_key_theme_color))
themeColorPreference!!.setOnPreferenceChangeListener { _, _ ->
refreshActivity(true)
true
}
if (!DynamicColors.isDynamicColorAvailable()) {
themeColorPreference.setEntryValues(R.array.color_values_no_dynamic)
themeColorPreference.setEntries(R.array.color_value_strings_no_dynamic)
}
val oledDarkPreference = findPreference<Preference>(getString(R.string.settings_key_oled_dark))
oledDarkPreference!!.setOnPreferenceChangeListener { _, _ ->
refreshActivity(true)
true
}
val localePreference =
findPreference<ListPreference>(getString(R.string.settings_key_locale))!!
localePreference.let {
val entryValues = it.entryValues
val entries = entryValues.map { entry ->
if (entry.isEmpty()) {
getString(R.string.settings_system_locale)
} else {
val entryLocale = Utils.stringToLocale(entry.toString())
entryLocale.getDisplayName(entryLocale)
}
}
it.entries = entries.toTypedArray()
// Make locale picker preference in sync with system settings
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val sysLocale = AppCompatDelegate.getApplicationLocales()[0]
if (sysLocale == null) {
// Corresponds to "System"
it.value = ""
} else {
// Need to set preference's value to one of localePreference.getEntryValues() to match the locale.
// Locale.toLanguageTag() theoretically should be one of the values in localePreference.getEntryValues()...
// But it doesn't work for some locales. so trying something more heavyweight.
// Obtain all locales supported by the app.
val appLocales = entryValues.map { entry -> Utils.stringToLocale(entry.toString()) }
// Get the app locale that best matches the system one
val bestMatchLocale = Utils.getBestMatchLocale(appLocales, sysLocale)
// Get its index in supported locales
val index = appLocales.indexOf(bestMatchLocale)
// Set preference value to entry value at that index
it.value = entryValues[index].toString()
}
}
}
localePreference.setOnPreferenceChangeListener { _, newValue ->
// See corresponding comment in Utils.updateBaseContextLocale for Android 6- notes
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
refreshActivity(true)
return@setOnPreferenceChangeListener true
}
val newLocale = newValue as String
// If newLocale is empty, that means "System" was selected
AppCompatDelegate.setApplicationLocales(if (newLocale.isEmpty()) LocaleListCompat.getEmptyLocaleList() else LocaleListCompat.create(Utils.stringToLocale(newLocale)))
true
}
// Disable content provider on SDK < 23 since dangerous permissions
// are granted at install-time
val contentProviderReadPreference = findPreference<Preference>(getString(R.string.settings_key_allow_content_provider_read))
contentProviderReadPreference!!.isVisible =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
// Hide crash reporter settings on builds it's not enabled on
val crashReporterPreference = findPreference<Preference>("acra.enable")
crashReporterPreference!!.isVisible = BuildConfig.useAcraCrashReporter
}
private fun refreshActivity(reloadMain: Boolean) {
mReloadMain = reloadMain || mReloadMain
activity?.recreate()
}
}
companion object {
private const val RELOAD_MAIN_STATE = "mReloadMain"
}
}

View File

@@ -1,421 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="protect.card_locker.MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
style="?attr/toolbarStyle" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
android:paddingVertical="8dp"
android:clipToPadding="false">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/version_history"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
android:background="?android:selectableItemBackground">
<TextView
android:id="@+id/version_history_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:text="@string/version_history"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/version_history_sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/version_history_main" />
<TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:fontFamily="sans-serif-medium"
android:text="@string/arrow"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/credits"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
android:background="?android:selectableItemBackground">
<TextView
android:id="@+id/credits_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:text="@string/credits"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/credits_sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/credits_main" />
<TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:fontFamily="sans-serif-medium"
android:text="@string/arrow"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/translate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
android:background="?android:selectableItemBackground">
<TextView
android:id="@+id/translate_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:text="@string/help_translate_this_app"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/translate_sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:text="@string/translate_platform"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/translate_main"/>
<TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:fontFamily="sans-serif-medium"
android:text="@string/arrow"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/license"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
android:background="?android:selectableItemBackground">
<TextView
android:id="@+id/license_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:text="@string/license"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/license_sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:text="@string/app_license"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/license_main"/>
<TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:fontFamily="sans-serif-medium"
android:text="@string/arrow"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/repo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
android:background="?android:selectableItemBackground">
<TextView
android:id="@+id/repo_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:text="@string/source_repository"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/repo_sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:text="@string/on_github"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/repo_main" />
<TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:fontFamily="sans-serif-medium"
android:text="@string/arrow"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/privacy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
android:background="?android:selectableItemBackground">
<TextView
android:id="@+id/privacy_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:text="@string/privacy_policy"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/privacy_sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:text="@string/and_data_usage"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/privacy_main" />
<TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:fontFamily="sans-serif-medium"
android:text="@string/arrow"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/donate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
android:background="?android:selectableItemBackground">
<TextView
android:id="@+id/donate_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:text="@string/donate"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:fontFamily="sans-serif-medium"
android:text="@string/arrow"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/rate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
android:background="?android:selectableItemBackground">
<TextView
android:id="@+id/rate_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:text="@string/rate_this_app"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/rate_sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:text="@string/on_google_play"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rate_main" />
<TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:fontFamily="sans-serif-medium"
android:text="@string/arrow"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/report_error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
android:background="?android:selectableItemBackground">
<TextView
android:id="@+id/report_error_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:text="@string/report_error"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/report_error_sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/report_error_main"
app:layout_constraintStart_toStartOf="parent"
android:paddingStart="2dp"
android:paddingEnd="30dp"
android:textSize="16sp"
android:text="@string/on_github" />
<TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:fontFamily="sans-serif-medium"
android:text="@string/arrow"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -276,6 +276,24 @@
android:paddingTop="@dimen/inputPadding"
android:orientation="horizontal">
<!-- Currency -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/balanceCurrencyView"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:hint="@string/currency"
android:labelFor="@+id/balanceCurrencyField">
<AutoCompleteTextView
android:id="@+id/balanceCurrencyField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"/>
</com.google.android.material.textfield.TextInputLayout>
<!-- Balance -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/balanceView"
@@ -294,24 +312,6 @@
android:digits="0123456789,." />
</com.google.android.material.textfield.TextInputLayout>
<!-- Currency -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/balanceCurrencyView"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:hint="@string/currency"
android:labelFor="@+id/balanceCurrencyField">
<AutoCompleteTextView
android:id="@+id/balanceCurrencyField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<!-- Valid from -->

View File

@@ -40,10 +40,16 @@
android:titleCondensed="@string/unarchive"
app:showAsAction="never"/>
<item
android:id="@+id/action_duplicate"
android:title="@string/duplicateCard"
android:titleCondensed="@string/duplicateCard"
app:showAsAction="never" />
<item
android:id="@+id/action_delete"
android:icon="@drawable/ic_delete_white_24dp"
android:title="@string/delete"
android:titleCondensed="@string/delete"
app:showAsAction="never"/>
</menu>
</menu>

View File

@@ -21,25 +21,25 @@
app:showAsAction="always">
<menu>
<item
android:id="@+id/action_archive"
android:title="@string/archive"
app:showAsAction="never"/>
<item
android:id="@+id/action_unarchive"
android:title="@string/unarchive"
app:showAsAction="never"/>
<item
android:id="@+id/action_duplicate"
android:title="@string/duplicateCard"
app:showAsAction="never" />
<item
android:id="@+id/action_archive"
android:title="@string/archive"
app:showAsAction="never"/>
<item
android:id="@+id/action_unarchive"
android:title="@string/unarchive"
app:showAsAction="never"/>
<item
android:id="@+id/action_delete"
android:title="@string/delete"
app:showAsAction="never"/>
</menu>
</item>

View File

@@ -7,80 +7,93 @@ Heimen Stoffels
Oğuz Ersen
FC (Fay) Stegerman
StoyanDimitrov
Katharine Chui
大王叫我来巡山
SlavekB
Katharine Chui
B o d o
mondstern
IllusiveMan196
大王叫我来巡山
Altonss
B o d o
Michael Moroni
Eric
Joel A
Silvério Santos
Максим Горпиніч
GM
Altonss
Edgars Andersons
Joel A
Michael Moroni
Liner Seven
Priit Jõerüüt
Eric
Максим Горпиніч
GitSpoon
GM
Fjuro
laralem
Petr Novák
Edgars Andersons
Taco
nadiafekihahmed
pfaffenrodt
Aayush Gupta
Scrambled777
ikanakova
Giovanni Donisi
HudobniVolk
Jiri Grönroos
Nyatsuki
josé m
ikanakova
Nyatsuki
Giovanni Donisi
Milo Ivir
HudobniVolk
Горпиніч Максим Олександрович
Vasilis
Kachelkaiser
Jiri Grönroos
Warder
Samantaz Fox
Balázs Meskó
Milo Ivir
Fjuro
Cliff Heraldo
Sergio Paredes
Ankit Tiwari
109247019824
Feike Donia
Arno-github
Warder
Kachelkaiser
Jose Delvani
mdvhimself
Milan Šalka
GitSpoon
Robin
mdvhimself
தமிழ்நேரம்
damjang
Govindgopalyadav
Skrripy
Vasilis
huuhaa
தமிழ் நேரம்
waffshappen
Marnick L'Eau
ngocanhtve
aradxxx
StellarSand
Quentin PAGÈS
Projjal Moitra
Robin
e-michalak
JungHee Lee
hajertabbane
inavleb
Ziad OUALHADJ
Robin Liu
Ricky Tigg
Renko
Aliaksandr Trush
Denis Shilin
Traductor
Gideon
Renko
Ricky Tigg
しいたけ
Alexander Ivanov
Miha Frangež
stavpup
mrestivill
ehrt74
Virginie
Tim Trek
Peter Dave Hello
Michael Gangolf
rudy3
Kim Seohyun
Govind S Nair
Freddo espresso
Augustin LAVILLE
arshbeerSingh
MisterCosta96
Aliaksandr Trush
arshbeerSingh
Augustin LAVILLE
Freddo espresso
vasudev-cell
Kim Seohyun
rudy3
Michael Gangolf
PRATHAMESH BHAGAT
Peter Dave Hello

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Catima</string>
<string name="action_search">Soek</string>
<string name="action_add">Voeg by</string>
<string name="save">Stoor</string>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> geselekteer</item>
<item quantity="other"><xliff:g>%d</xliff:g> geselekteer</item>
</plurals>
</resources>

View File

@@ -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>
@@ -29,7 +29,7 @@
<string name="noCardExistsError">لا يمكن العثور على هذه البطاقة</string>
<string name="failedParsingImportUriError">لا يمكن تحليل الرابط المستورد</string>
<string name="importExport">استيراد/تصدير</string>
<string name="importExportHelp">دعم بياناتك يسمح بنقلها إلى جهاز آخر.</string>
<string name="importExportHelp">انشاء نسخة احتياطية من بياناتك يسمح بنقلها إلى جهاز آخر.</string>
<string name="importFailed">تعذر إجراء الاستيراد</string>
<string name="exportSuccessfulTitle">متصدر</string>
<string name="exportFailedTitle">فشل التصدير</string>
@@ -40,16 +40,13 @@
<string name="app_copyright_old">بناء على Loyalty Card Keychain
\nحقوق النشر © 2016-2020 Branden Archer</string>
<string name="app_license">البرمجيات الحرة متروكة الحقوق, ترخيص +GPLv3</string>
<string name="app_libraries">مكتبات الطرف الثالث الحرة: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_libraries">مكتبات الطرف الثالث : <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">اختار الباركود</string>
<string name="thumbnailDescription">صورة مصغرة</string>
<string name="starImage">نجم مفضل</string>
<string name="settings">الإعدادات</string>
<string name="settings_light_theme">فاتحة</string>
<string name="settings_dark_theme">داكنة</string>
<string name="settings_card_orientation">اتجاه الشاشة</string>
<string name="settings_portrait_orientation">الوضع الرأسي</string>
<string name="settings_landscape_orientation">الوضع الأفقي</string>
<string name="settings_theme">مظهر</string>
<string name="settings_display_barcode_max_brightness">شاشة ساطعة</string>
<string name="importSuccessful">تم استيراد البيانات</string>
@@ -63,7 +60,7 @@
<string name="group_updated">تم تحديث المجموعة</string>
<string name="all">الكل</string>
<string name="deleteConfirmationGroup">هل تريد حذف المجموعة؟</string>
<string name="failedOpeningFileManager">ثبِّت مدير الملفات أولاً.</string>
<string name="failedOpeningFileManager">فشل فتح مدير الملفات</string>
<string name="moveUp">تحرك لأعلى</string>
<string name="addFromImage">حدد صورة من المعرض</string>
<string name="balance">الرصيد</string>
@@ -74,11 +71,9 @@
<string name="privacy_policy">سياسة الخصوصية</string>
<string name="accept">قبول</string>
<string name="importCatima">الاستيراد من Catima</string>
<string name="importCatimaMessage">حدّد ملفك <i>catima.zip</i> تصدير من Catima للاستيراد. \nإنشئها من قائمة الاستيراد / التصدير لتطبيق Catima آخر بالضغط على تصدير هناك أولاً.</string>
<string name="importCatimaMessage">حدّد ملفك تصدير من Catima للاستيراد.\nإنشئها من قائمة الاستيراد / التصدير لتطبيق Catima آخر بالضغط على تصدير .</string>
<string name="importFidme">الاستيراد من FidMe</string>
<string name="importFidmeMessage">حدّد ملفك <i>fidme-export-request-xxxxxx.zip</i> تصدير من FidMe للاستيراد، ثم حدد أنواع الباركود يدويًا بعد ذلك. \nإنشئها من ملف تعريف FidMe الخاص بك عن طريق اختيار حماية البيانات ثم الضغط على استخراج بياناتي أولاً.</string>
<string name="importStocardMessage">حدد ملفك <i>***.zip</i> تصدير من Stocard للاستيراد.
\nاحصل عليه عن طريق إرسال بريد إلكتروني إلى support@stocardapp.com لطلب تصدير بياناتك.</string>
<string name="importVoucherVault">الاستيراد من Voucher Vault</string>
<string name="importVoucherVaultMessage">حدّد ملفك <i>vouchervault.json</i> تصدير من Voucher Vault للاستيراد. \nإنشئها بالضغط على تصدير في Voucher Vault أولاً.</string>
<string name="barcodeId">قيمة الباركود</string>
@@ -183,16 +178,14 @@
<string name="about_title_fmt">حول <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">نسخة: <xliff:g id="version">%s</xliff:g></string>
<string name="settings_system_theme">النظام</string>
<string name="settings_lock_on_opening_orientation">قفل على الاتجاه عند فتح البطاقة</string>
<string name="app_resources">موارد الطرف الثالث الحرة: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="settings_follow_system_orientation">نظام المتابعة</string>
<string name="groups">مجموعات</string>
<string name="settings_keep_screen_on">حافظ على الشاشة قيد التشغيل</string>
<string name="intent_import_card_from_url_share_text">اريد مشاركة بطاقة معك</string>
<string name="groupsList">مجموعات: <xliff:g>%s</xliff:g></string>
<string name="settings_disable_lockscreen_while_viewing_card">منع قفل الشاشة</string>
<string name="leaveWithoutSaveTitle">خروج</string>
<string name="editGroup">تعديل المجموعه: <xliff:g>%s</xliff:g></string>
<string name="editGroup">تعديل المجموعة: <xliff:g>%s</xliff:g></string>
<plurals name="groupCardCount">
<item quantity="zero"><xliff:g>%d</xliff:g> بطاقة</item>
<item quantity="one"><xliff:g>%d</xliff:g> بطاقة</item>
@@ -229,8 +222,7 @@
<string name="sort_by_expiry">انقضاء</string>
<string name="importLoyaltyCardKeychain">الاستيراد من Loyalty Card Keychain</string>
<string name="importLoyaltyCardKeychainMessage">حدّد ملفك <i>LoyaltyCardKeychain.csv</i> التصدير من Loyalty Card Keychain للاستيراد. \nإنشئها من قائمة الاستيراد / التصدير في Loyalty Card Keychain بالضغط على تصدير هناك أولاً.</string>
<string name="importStocard">الاستيراد من Stocard</string>
<string name="failedGeneratingShareURL">تعذر إنشاء عنوان URL قابل للمشاركة. الرجاء الإبلاغ عن هذا.</string>
<string name="failedGeneratingShareURL">تعذر إنشاء عنوان URL قابل للمشاركة</string>
<string name="help_translate_this_app">ساعد في ترجمة هذا التطبيق</string>
<string name="on_google_play">على Google Play</string>
<string name="settings_theme_color">لون المظهر</string>
@@ -292,7 +284,6 @@
<string name="addWithoutBarcode">إضافة بدون باركود</string>
<string name="field_must_not_be_empty">يجب ألا يكون الحقل فارغا</string>
<string name="app_name">كاتيما</string>
<string name="settings_follow_sensor_orientation">التدوير دائمًا ( تجاهل إعدادات النظام)</string>
<string name="add_manually_warning_title">الفحص موصى به</string>
<string name="continue_">استمر</string>
<string name="spend">انفق</string>
@@ -311,7 +302,7 @@
<string name="useBackImage">استخدم صورة خلفية</string>
<string name="addFromPkpass">اختر ملف الدفتر (.pkpass)</string>
<string name="unsupportedFile">هذا الملف غير مدعوم</string>
<string name="generic_error_please_retry">نعتذر، حدث خطأ ما، حاول مرة أخرى...</string>
<string name="generic_error_please_retry">حدث خطأ ما</string>
<string name="settings_use_volume_keys_navigation">بدّل البطاقات باستخدام أزرار الصوت</string>
<string name="settings_use_volume_keys_navigation_summary">بدّل البطاقات الظاهرة باستخدام أزرار الصوت</string>
<string name="settings_category_title_cards_overview">نظرة عامة على البطاقات</string>
@@ -328,4 +319,7 @@
<string name="sort_by_valid_from">صالح من</string>
<string name="width">العرض</string>
<string name="setBarcodeWidth">تعيين عرض الرمز الشريطي \"باركود\"</string>
<string name="card_list_widget_name">قائمة البطاقات</string>
<string name="cardWithNumber">البطاقة <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">البطاقة <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
</resources>

View File

@@ -84,11 +84,6 @@
<string name="settings_system_theme">Сістэмная</string>
<string name="settings_light_theme">Светлая</string>
<string name="settings_dark_theme">Цёмная</string>
<string name="settings_card_orientation">Арыентацыя экрана</string>
<string name="settings_follow_sensor_orientation">Заўсёды паварочваць (ігнаруе налады сістэмы)</string>
<string name="settings_portrait_orientation">Партрэтная</string>
<string name="settings_landscape_orientation">Альбомная</string>
<string name="settings_lock_on_opening_orientation">Зафіксаваць арыентацыю, якая выкарыстоўваецца пры адкрыцці карты</string>
<string name="settings_keep_screen_on_summary">Адключае тайм-аўт экрана падчас прагляду карты</string>
<string name="settings_disable_lockscreen_while_viewing_card">Прадухіляць блакіроўку экрана</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">Адключае блакіроўку экрана падчас прагляду карты</string>
@@ -139,7 +134,6 @@
<string name="importCatima">Імпарт з Catima</string>
<string name="importFidme">Імпарт з FidMe</string>
<string name="importLoyaltyCardKeychain">Імпарт з Loyalty Card Keychain</string>
<string name="importStocard">Імпарт з Stocard</string>
<string name="importVoucherVault">Імпарт з Voucher Vault</string>
<string name="barcodeId">Значэнне штрыхкода</string>
<string name="importVoucherVaultMessage">Каб імпартаваць, выберыце файл <i>vouchervault.json</i> з Voucher Vault. \nСтварыце яго, націснуўшы Экспарт у Voucher Vault .</string>
@@ -268,7 +262,6 @@
<string name="addFromImage">Выбраць малюнак з галерэі</string>
<string name="settings_keep_screen_on">Трымаць экран уключаным</string>
<string name="app_resources">Бясплатныя староннія рэсурсы: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="settings_follow_system_orientation">Як у сістэме</string>
<string name="leaveWithoutSaveTitle">Выйсці</string>
<string name="settings_allow_content_provider_read_title">Дазволіць іншым праграмам доступ да маіх даных</string>
<string name="settings_display_barcode_max_brightness">Павялічваць яркасць экрану</string>
@@ -277,7 +270,6 @@
<string name="editBarcode">Рэдагаваць штрыхкод</string>
<string name="leaveWithoutSaveConfirmation">Выйсці без захавання?</string>
<string name="importLoyaltyCardKeychainMessage">Каб імпартаваць, выберыце файл <i>LoyaltyCardKeychain.csv</i> з Loyalty Card Keychain. \nСтварыце яго з меню «Імпарт/Экспарт» у Loyalty Card Keychain, спачатку націснуўшы там «Экспарт».</string>
<string name="importStocardMessage">Каб імпартаваць, выберыце файл <i>***.zip</i> з Stocard. \nАтрымайце яго па электроннай пошце support@stocardapp.com з запытам на экспарт вашых даных.</string>
<string name="frontImageDescription">Пярэдні відарыс</string>
<string name="groupsList">Групы: <xliff:g>%s</xliff:g></string>
<string name="switchToBackImage">Пераключыцца на задні відарыс</string>
@@ -314,4 +306,14 @@
<string name="generic_error_please_retry">На жаль, нешта пайшло не так, паспрабуйце яшчэ раз...</string>
<string name="setBarcodeWidth">Задаць шырыню штрыхкода</string>
<string name="app_license">Свабоднае копілефт праграмнае забеспячэнне, ліцэнзаванае паводле GPLv3+</string>
<string name="cardWithNumber">Карта <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Карта <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Калі ласка, не паварочвайце прыладу, бо гэта адменіць дзеянне</string>
<string name="acra_explain_crash">Калі магчыма, дадайце больш падрабязную інфармацыю пра тое, што вы тут рабілі:</string>
<string name="acra_crash_email_subject">Справаздача аб збоі <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Запытваць дазвол на адпраўку справаздач аб збоях</string>
<string name="pref_enable_acra_summary">Калі гэта ўключана, вам будзе прапанавана паведаміць пра збой, калі ён адбудзецца. Справаздачы аб збоях ніколі не адпраўляюцца аўтаматычна.</string>
<string name="card_list_widget_name">Спіс карт</string>
<string name="card_list_widget_empty">Пасля таго, як вы дадасце некалькі картак лаяльнасці ў Catima, яны з\'явяцца тут. Калі ў вас ёсць карты, пераканайцеся, што яны не ўсе заархіваваны.</string>
<string name="acra_catima_has_crashed">Прабачце, але ў праграме <xliff:g id="app_name">%s</xliff:g> адбыўся збой. Калі ласка, дапамажыце нам выправіць гэту праблему, даслаўшы нам справаздачу аб памылцы.</string>
</resources>

View File

@@ -16,13 +16,13 @@
<string name="note">Бележка</string>
<string name="storeName">Наименование</string>
<string name="noMatchingGiftCards">Няма резултати. Променете критериите за търсене.</string>
<string name="noGiftCards">Докоснете бутона +, за да добавите карта или внесете от менюто ⋮.</string>
<string name="noGiftCards">Докоснете бутона +, за да добавите карта или внесете от менюто ⋮</string>
<string name="all">Всички</string>
<plurals name="groupCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> карта</item>
<item quantity="other"><xliff:g>%d</xliff:g> карти</item>
</plurals>
<string name="failedOpeningFileManager">Инсталирайте приложение за управление на файлове.</string>
<string name="failedOpeningFileManager">Грешка при отваряне управление на файлове</string>
<string name="app_license">Свободен софтуер с авторски права, лицензиран под GPLv3+</string>
<string name="frontImageDescription">Снимка на предната страна</string>
<string name="backImageDescription">Снимка на задната страна</string>
@@ -45,10 +45,9 @@
<string name="sameAsCardId">Като номера</string>
<string name="barcodeId">Стойност на щрихкода</string>
<string name="importLoyaltyCardKeychain">Внасяне от Loyalty Card Keychain</string>
<string name="importFidmeMessage">Изберете файла <i>fidme-export-request-xxxxxx.zip</i>, предварително изнесен от FidMe и ръчно изберете вида на щрихкодовете.
\nСъздайте такъв файл от Data Protection в менюто на профила във FidMe и изберете „Extract my data“.</string>
<string name="importFidmeMessage">Изберете предварително изнесен файл от FidMe, който да внесете и ръчно изберете вида на щрихкодовете.\nСъздайте такъв файл от Data Protection в менюто на профила във FidMe и изберете „Extract my data“.</string>
<string name="importFidme">Внасяне от FidMe</string>
<string name="exportOptionExplanation">Данните ще бъдат запазени на място по ваш избор.</string>
<string name="exportOptionExplanation">Данните ще бъдат запазени на място по ваш избор</string>
<string name="accept">Приемане</string>
<string name="privacy_policy">Политика за личните данни</string>
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
@@ -70,7 +69,7 @@
<string name="expiryStateSentence">Валидност до: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentenceExpired">Изтекла: <xliff:g>%s</xliff:g></string>
<string name="balanceSentence">Наличност: <xliff:g>%s</xliff:g></string>
<string name="noGroups">Докоснете бутона +, за да добавите списък.</string>
<string name="noGroups">Докоснете бутона +, за да добавите списък</string>
<string name="groups">Списъци</string>
<string name="enter_group_name">Въведете име на списъка</string>
<string name="intent_import_card_from_url_share_text">Искам да споделя тази карта с вас</string>
@@ -93,22 +92,21 @@
<string name="importFailedTitle">Грешка при внасяне</string>
<string name="exportSuccessfulTitle">Резултат от изнасяне</string>
<string name="importSuccessfulTitle">Резултат от внасяне</string>
<string name="importExportHelp">Резервните копия на данните ви дават възможност да ги премествате на друго устройство.</string>
<string name="importExportHelp">Резервните копия на данните дават възможност да ги премествате на друго устройство</string>
<string name="exportName">Изнасяне</string>
<string name="importExport">Внасяне/изнасяне</string>
<string name="sendLabel">Изпращане…</string>
<string name="scanCardBarcode">Снемане на щрихкод</string>
<string name="editCardTitle">Редактиране на карта</string>
<string name="editCardTitle">Променяне на карта</string>
<string name="share">Споделя</string>
<string name="ok">Добре</string>
<string name="importSuccessful">Данните са внесени</string>
<string name="chooseImportType">Внасяне на данни на</string>
<string name="importCatimaMessage">Изберете файла <i>catima.zip</i>, предварително изнесен от Catima.
\nСъздайте такъв файл от меню Внасяне/изнасяне от друго устройство с Catima като изберете Изнасяне.</string>
<string name="importCatimaMessage">Изберете предварително изнесен файл от Catima, който да внесете.\nСъздайте такъв файл от меню Внасяне/изнасяне от друго устройство с Catima като изберете Изнасяне.</string>
<string name="importOptionFilesystemButton">Избиране от файлова система</string>
<string name="importOptionFilesystemExplanation">Изберете определен файл от файловата система.</string>
<string name="app_resources">Свободни ресурси: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Свободни библиотеки: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="importOptionFilesystemExplanation">Изберете определен файл от файловата система</string>
<string name="app_resources">Ресурси: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Библиотеки: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="debug_version_fmt">Издание: <xliff:g id="version">%s</xliff:g></string>
<string name="about_title_fmt">Относно <xliff:g id="app_name">%s</xliff:g></string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Всички права запазени © 2019<xliff:g>%d</xliff:g> Силвия ван Ос и сътрудници</string>
@@ -129,16 +127,11 @@
<string name="addManually">Ръчно въвеждане</string>
<string name="leaveWithoutSaveConfirmation">Оставяте промените незапазени\?</string>
<string name="unsupportedBarcodeType">Щрихкод от този вид не може да бъде показан. Може да бъде поддържан в следващо издание.</string>
<string name="importStocard">Внасяне от Stocard</string>
<string name="importVoucherVault">Внасяне от Voucher Vault</string>
<string name="importVoucherVaultMessage">Изберете файла <i>vouchervault.json</i>, предварително изнесен от Voucher Vault.
\nСъздайте такъв файл от меню „Export“ във Voucher Vault.</string>
<string name="importStocardMessage">Изберете файла <i>***.zip</i>, предварително изнесен от Stocard.
\nПолучете го като изпратите писмо на support@stocardapp.com с искане за изнасяне вашите данни.</string>
<string name="importLoyaltyCardKeychainMessage">Изберете файла <i>LoyaltyCardKeychain.csv</i>, предварително изнесен от Loyalty Card Keychain.
\nСъздайте такъв файл от меню Внасяне/изнасяне от друго устройство с Loyalty Card Keychain като изберете Изнасяне.</string>
<string name="failedParsingImportUriError">Препратката не може да бъде анализирана за внасяне</string>
<string name="failedGeneratingShareURL">Не може да бъде генериран адрес за споделяне. Изпратете доклад за дефект.</string>
<string name="importVoucherVaultMessage">Изберете предварително изнесен файл от Voucher Vault, който да внесете.\nСъздайте такъв файл от меню „Export“ във Voucher Vault.</string>
<string name="importLoyaltyCardKeychainMessage">Изберете предварително изнесен файл от Loyalty Card Keychain, който да внесете.\nСъздайте такъв файл от меню Внасяне/изнасяне от друго устройство с Loyalty Card Keychain като изберете Изнасяне.</string>
<string name="failedParsingImportUriError">Адресът за внасяне не може да бъде анализиран</string>
<string name="failedGeneratingShareURL">Грешка при създаване на адрес, който да споделите</string>
<string name="deleteTitle">Премахване на карта</string>
<plurals name="deleteCardsTitle">
<item quantity="one">Изтриване на <xliff:g>%d</xliff:g> карта</item>
@@ -188,7 +181,7 @@
<string name="selectColor">Избиране на цвят</string>
<string name="group_name_is_empty">Името на списъка не трябва да е празно</string>
<string name="group_edit">Редактиране на списък</string>
<string name="noGiftCardsGroup">Създайте карти и ги зачислите към списък от тук.</string>
<string name="noGiftCardsGroup">Създайте карти и ги зачислете към списък от тук</string>
<string name="translate_platform">в Weblate</string>
<string name="shortcutSelectCard">Избор на карта</string>
<string name="starred">Със звезда</string>
@@ -200,17 +193,12 @@
</plurals>
<string name="settings_oled_dark">Черен фон за тъмната тема</string>
<string name="include_if_asking_support">Ако искате да потърсите поддръжка, включете следната информация:</string>
<string name="settings_card_orientation">Завъртане на екрана</string>
<string name="settings_follow_system_orientation">Според системата</string>
<string name="settings_portrait_orientation">Портрет</string>
<string name="settings_landscape_orientation">Пейзаж</string>
<string name="settings_lock_on_opening_orientation">Като при отваряне на картата</string>
<string name="duplicateCard">Дублиране</string>
<string name="archive">Архивиране</string>
<string name="unarchive">Изваждане от архива</string>
<string name="archived">Картата е архивирана</string>
<string name="unarchived">Карта е извадена от архива</string>
<string name="failedLaunchingPhotoPicker">Не е намерено поддържано приложение за галерия</string>
<string name="failedLaunchingPhotoPicker">Не е намерено поддържано приложение за избор на изображение</string>
<plurals name="groupCardCountWithArchived">
<item quantity="one"><xliff:g>%1$d</xliff:g> карта (<xliff:g id="archivedCount">%2$d</xliff:g> архивирана)</item>
<item quantity="other"><xliff:g>%1$d</xliff:g> карти (<xliff:g id="archivedCount">%2$d</xliff:g> архивирани)</item>
@@ -239,8 +227,8 @@
<string name="switchToFrontImage">Показване на предната страна</string>
<string name="switchToBackImage">Показване на задната страна</string>
<string name="switchToBarcode">Показване на щрихкода</string>
<string name="openFrontImageInGalleryApp">Отваряне на изображението на предната страна в приложението галерия</string>
<string name="openBackImageInGalleryApp">Отваряне на изображението на задната страна в приложението галерия</string>
<string name="openFrontImageInGalleryApp">Отваряне на изображението на предната страна в приложение за преглед за изображения</string>
<string name="openBackImageInGalleryApp">Отваряне на изображението на задната страна в приложение за преглед за изображения</string>
<string name="setBarcodeHeight">Задаване на височина на щрихкода</string>
<string name="donate">Даряване</string>
<string name="icon_header_click_text">Задръжте, за да промените миниатюрата</string>
@@ -272,10 +260,9 @@
<string name="addWithoutBarcode">Добавяне на карта без щрихкод</string>
<string name="field_must_not_be_empty">Полето не трябва да е празно</string>
<string name="app_name">Catima</string>
<string name="settings_follow_sensor_orientation">Винаги да се завърта (пренебрегва системната настройка)</string>
<string name="continue_">Продължаване</string>
<string name="add_manually_warning_title">Препоръчително е да сканирате</string>
<string name="add_manually_warning_message">Стойностите от щрихкода и отбелязаните на картата числа в някои случаи се различават. По тази причина е при ръчно въвеждане картата може да не работи. Силно препоръчително е да сканирате щрихкода с камерата. Желаете ли да продължите въпреки това?</string>
<string name="add_manually_warning_message">Стойностите от щрихкода и отбелязаните на картата числа в някои случаи се различават. По тази причина при ръчно въвеждане картата може да не работи. Препоръчително е да сканирате щрихкода с камерата. Желаете ли да продължите въпреки това?</string>
<string name="amountParsingFailed">Неприемлива сума</string>
<string name="spend">Похарчено</string>
<string name="receive">Получено</string>
@@ -302,12 +289,23 @@
<string name="settings_column_count_landscape">Колони в пейзажен изглед</string>
<string name="settings_column_count_portrait">Колони в портретен изглед</string>
<string name="settings_category_title_cards_overview">Списък с карти</string>
<string name="generic_error_please_retry">Съжаляваме, нещо се обърка, опитайте отново…</string>
<string name="addFromPkpass">Изберете файл на Passbook (.pkpass)</string>
<string name="generic_error_please_retry">Възникна грешка</string>
<string name="addFromPkpass">Изберете файл на Passbook (.pkpass / pkpasses)</string>
<string name="unsupportedFile">Този вид файлове не се поддържат</string>
<string name="sort_by_valid_from">Начало валидност</string>
<string name="width">Ширина</string>
<string name="setBarcodeWidth">Задаване ширина на щрих кода</string>
<string name="setBarcodeWidth">Задаване ширина на щрихкода</string>
<string name="card_list_widget_name">Списък с карти</string>
<string name="card_list_widget_empty">Когато добавите карти в Catima те ще се покажат тук. Ако имате карти уверете се, че са извън архива.</string>
<string name="cardWithNumber">Карта <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Карта <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Не завъртайте устройството, защото това ще прекъсне действието</string>
<string name="acra_catima_has_crashed">За съжаление <xliff:g id="app_name">%s</xliff:g> се срина. Помогнете ни да оправим проблема като ни изпратите доклад за грешката.</string>
<string name="acra_crash_email_subject">Доклад за срив на <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Питане преди изпращане на доклад за срив</string>
<string name="pref_enable_acra_summary">Когато е отметнато, при срив ще ви бъде предложено да докладвате за него. Докладите никога не се изпращат автоматично.</string>
<string name="acra_explain_crash">Ако е възможно добавете подробности за вашите действия:</string>
<string name="copy_value">Копиране на стойността</string>
<string name="copied_to_clipboard">Копирано</string>
<string name="nothing_to_copy">Няма стойност</string>
</resources>

View File

@@ -26,7 +26,6 @@
<string name="starImage">তারা ছবি</string>
<string name="importCatima">ক্যাতিনা আগম</string>
<string name="importLoyaltyCardKeychain">আমদানি লয়্যালটি কার্ড কীচেন</string>
<string name="importStocard">স্টো কার্ড আমদানি করুন</string>
<string name="importVoucherVault">আমদানি ভাউচার ভল্ট</string>
<string name="barcodeId">বারকোড আইডি</string>
<string name="sameAsCardId">আইডি আর এটা এক</string>
@@ -111,9 +110,6 @@
<string name="about_title_fmt"><xliff:g id="app_name">%s</xliff:g>টির সম্পর্কে</string>
<string name="app_resources">মুক্ত সম্পদ যেগুলি আমার নয়: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="thumbnailDescription">থাম্বনেইল</string>
<string name="settings_card_orientation">বারকোড অভিমুখ</string>
<string name="settings_follow_system_orientation">সিস্টেমের অনুসারে</string>
<string name="settings_portrait_orientation">প্রতিকৃতি</string>
<string name="barcodeImageDescriptionWithType">ছবি <xliff:g>%s</xliff:g> বারকোড</string>
<string name="exportName">রপ্তানি</string>
<string name="failedParsingImportUriError">আমদানির URI-টি বোঝা যাচ্ছে না</string>
@@ -139,8 +135,6 @@
<string name="selectBarcodeTitle">বারকোড নির্বাচন করুন</string>
<string name="settings">সেটিংস</string>
<string name="settings_dark_theme">অন্ধকার</string>
<string name="settings_landscape_orientation">অনুভূমিক</string>
<string name="settings_lock_on_opening_orientation">কার্ড খোলার সময় যে অভিমুখ থাকে সেটিতে লক করে দেবেন</string>
<string name="group_name_already_in_use">গ্রুপটির নাম আগে একবার ব্যবহার করে ফেলেছেন</string>
<string name="group_edit">গ্রুপ সম্পাদনা করুন</string>
<string name="group_updated">গ্রুপটি আপডেট করা হল</string>
@@ -203,8 +197,6 @@
\nআপনার FidMe প্রোফাইল থেকে ডেটা সুরক্ষা নির্বাচন করে এবং তারপর প্রথমে আমার ডেটা বের করুন টিপে এটি তৈরি করুন।</string>
<string name="importCatimaMessage">ক্যাটিমা থেকে আমদানি করতে আপনার <i>catima.zip</i> রপ্তানি নির্বাচন করুন।
\nঅন্য Catima অ্যাপের আমদানি/রপ্তানি মেনু থেকে প্রথমে সেখানে রপ্তানি টিপে এটি তৈরি করুন।</string>
<string name="importStocardMessage">আমদানি করতে Stocard থেকে আপনার <i>***.zip</i> এক্সপোর্ট নির্বাচন করুন।
\nআপনার ডেটা রপ্তানির জন্য জিজ্ঞাসা করে support@stocardapp.com ই-মেইল করে এটি পান।</string>
<string name="importVoucherVaultMessage">আমদানি করতে ভাউচার ভল্ট থেকে আপনার <i>vouchervault.json</i> এক্সপোর্ট নির্বাচন করুন।
\nপ্রথমে ভাউচার ভল্টে এক্সপোর্ট টিপে এটি তৈরি করুন।</string>
<string name="settings_oled_dark">অন্ধকার থিমের জন্য খাঁটি কালো পটভূমি</string>

View File

@@ -73,6 +73,5 @@
<string name="permissionReadCardsLabel">কাটিমা কার্ডস পড়ুন</string>
<string name="storageReadPermissionRequired">এই কাজটির জন্য ফোনের স্টোরেজ দেখার অনুমতি লাগবে…</string>
<string name="exportFailedTitle">রপ্তানি ব্যর্থ</string>
<string name="settings_card_orientation">বারকোড অভিমুখ (ওরিয়েন্টেশন)</string>
<string name="app_name">ক্যাটিমা</string>
</resources>

View File

@@ -26,7 +26,6 @@
<string name="starImage">Omiljena zvijezda</string>
<string name="importCatima">Uvezi iz Catima</string>
<string name="importLoyaltyCardKeychain">Uvezi iz Loyalty Card Keychain</string>
<string name="importStocard">Uvezi iz Stokarda</string>
<string name="importVoucherVault">Uvezi iz trezora vaučer</string>
<string name="barcodeId">Barcode vrijednost</string>
<string name="sameAsCardId">Isto kao i kartica</string>

View File

@@ -7,12 +7,12 @@
<string name="delete">Elimina</string>
<string name="confirm">Confirma</string>
<string name="ok">D\'acord</string>
<string name="importExport">Importa/Exporta</string>
<string name="importExport">Importa/exporta</string>
<string name="exportName">Exporta</string>
<string name="action_search">Cerca</string>
<string name="deleteTitle">Elimina la targeta</string>
<string name="welcome">Benvingut a Catima</string>
<string name="noGiftCards">Cliqueu el botó + més per afegir una targeta, o importeu-ne des del menú.</string>
<string name="noGiftCards">Fes clic al botó + per afegir una targeta, o importa des del menú</string>
<string name="photos">Fotos</string>
<string name="app_name">Catima</string>
<string name="moveDown">Baixar abaix</string>
@@ -24,10 +24,10 @@
<string name="on_google_play">al Google Play</string>
<string name="settings_locale">Idioma</string>
<string name="field_must_not_be_empty">El camp no pot estar buit</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Copyright © 2019<xliff:g>%d</xliff:g> Sylvia van Os i contribuïdors</string>
<string name="app_copyright_short">Copyright © Sylvia van Os i contribuïdors</string>
<string name="app_license">Software lliure Copyleft, licència GPLv3+</string>
<string name="app_resources">Recursos lliures de tercers: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Copyright © 2019<xliff:g>%d</xliff:g> Sylvia van Os i col·laboradors</string>
<string name="app_copyright_short">Copyright © Sylvia van Os i col·laboradors</string>
<string name="app_license">Programari lliure Copyleft, licència GPLv3+</string>
<string name="app_resources">Recursos de tercers: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="thumbnailDescription">Miniatura</string>
<string name="starImage">Estrella de preferides</string>
<string name="settings">Configuració</string>
@@ -35,7 +35,6 @@
<string name="settings_light_theme">Tema clar</string>
<string name="settings_system_theme">Tema de sistema</string>
<string name="settings_dark_theme">Tema Fosc</string>
<string name="settings_card_orientation">Orientació de la pantalla</string>
<string name="settings_allow_content_provider_read_title">Permet altres apps a accedir a les meves dades</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">Desactiva el bloqueix la pantalla mentre es visualitza la targeta</string>
<string name="settings_allow_content_provider_read_summary">Les aplicacions han de seguir demanant permís per tenir-hi accés</string>
@@ -56,13 +55,13 @@
<string name="add_manually_warning_title">Recomenem escanejar</string>
<string name="add_manually_warning_message">En algunes targetes el valor imprès en la targeta no correspon amb el codi registrat en el codi de barres. Per això, introduint manualment el codi pot no funcionar en alguns casos. Recomanem sempre que sigui possible escanejar la targeta amb la càmera. Vol igualment continuar la edició manual?</string>
<string name="continue_">Continuar</string>
<string name="exportOptionExplanation">La informació serà escrita al lloc de la seva elecció.</string>
<string name="exportOptionExplanation">La informació serà escrita al lloc de la seva elecció</string>
<string name="importOptionFilesystemTitle">Importar desde el sistema de fitxers</string>
<string name="importOptionFilesystemButton">Desde el sistema de fitxers</string>
<string name="selectBarcodeTitle">Sel•lecciona el Codi de Barres</string>
<string name="selectBarcodeTitle">Selecciona el codi de barres</string>
<string name="importSuccessful">Dades importades correctament</string>
<string name="exportSuccessful">Dades exportades correctament</string>
<string name="failedOpeningFileManager">Instala un gestor de fitxers.</string>
<string name="failedOpeningFileManager">No s\'ha pogut obrir el gestor de fitxers</string>
<string name="showMoreInfo">Mostrar informació</string>
<string name="version_history">Històric de versions</string>
<string name="sort_by">Ordenar per</string>
@@ -73,7 +72,7 @@
<item quantity="many"><xliff:g>%d</xliff:g> seleccionats</item>
<item quantity="other"><xliff:g>%d</xliff:g> seleccionats</item>
</plurals>
<string name="importOptionFilesystemExplanation">Escull un fitxer especific del sistema de fitxers.</string>
<string name="importOptionFilesystemExplanation">Escull un fitxer especific del sistema de fitxers</string>
<string name="no">No</string>
<string name="settings_pink_theme">Rosa</string>
<string name="sort">Ordenar</string>
@@ -97,22 +96,19 @@
</plurals>
<string name="importCancelled">Importació anulada</string>
<string name="exportCancelled">Exportació cancelada</string>
<string name="noGiftCardsGroup">Crea algunes targetes, asigna-les en un grup aquí.</string>
<string name="noMatchingGiftCards">Sense resultats. Prova a canviar la teva cerca.</string>
<string name="noGiftCardsGroup">Crea algunes targetes i després asigna-les en al grup aquí</string>
<string name="noMatchingGiftCards">No hi ha resultats; prova de modificar la cerca.</string>
<string name="storeName">Nom</string>
<string name="note">Nota</string>
<string name="cardId">Id. de la Targeta</string>
<string name="barcodeType">Tipus de codi de barres</string>
<string name="noBarcode">Sense codi de barres</string>
<string name="settings_portrait_orientation">Vertical</string>
<string name="yes">Si</string>
<string name="addFromPdfFile">Seleccioni un PDF</string>
<string name="errorReadingFile">No s\'ha pogut llegir el fitxer</string>
<string name="failedLaunchingFileManager">No s\'ha pogut trobar un gestor de fitxers compatible</string>
<string name="multipleBarcodesFoundPleaseChooseOne">Quin dels següents codis de barres prefereix utilitzar?</string>
<string name="pageWithNumber">Pàgina <xliff:g>%d</xliff:g></string>
<string name="settings_follow_system_orientation">Seguir el sistema</string>
<string name="settings_landscape_orientation">Horitzontal</string>
<string name="intent_import_card_from_url_share_text">Vull compartir una targeta amb tu</string>
<string name="takePhoto">Fer una foto</string>
<string name="help_translate_this_app">Ajuda a traduïr aquesta app</string>
@@ -134,7 +130,6 @@
<string name="barcodeImageDescriptionWithType">Codi de barres <xliff:g>%s</xliff:g></string>
<string name="about_title_fmt">Sobre <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versió: <xliff:g id="version">%s</xliff:g></string>
<string name="settings_follow_sensor_orientation">Sempre rota (ignora la configuració de sistema)</string>
<string name="settings_display_barcode_max_brightness_summary">Alguns escàners ho necesiten</string>
<string name="settings_keep_screen_on">Mantenir la pantalla encesa</string>
<string name="settings_keep_screen_on_summary">Desactiva el bloqueix de la pantalla mentre mostra una targeta</string>
@@ -171,22 +166,21 @@
<string name="deleteConfirmation">Vols eliminar de forma permanent aquesta targeta?</string>
<string name="share">Compartir</string>
<string name="sendLabel">Enviar…</string>
<string name="editCardTitle">Editar Targeta</string>
<string name="addCardTitle">Afegir Targeta</string>
<string name="scanCardBarcode">Escanejar Codi de Barres</string>
<string name="cardShortcut">Drecera a la Targeta</string>
<string name="editCardTitle">Editar targeta</string>
<string name="addCardTitle">Afegir targeta</string>
<string name="scanCardBarcode">Escanejar codi de barres</string>
<string name="cardShortcut">Drecera a la targeta</string>
<string name="noCardsMessage">Afegeix primer una targeta</string>
<string name="noCardExistsError">No s\'ha pogut trobar aquesta targeta</string>
<string name="failedParsingImportUriError">No s\'ha pogut analitzar la URI d\'importació</string>
<string name="failedParsingImportUriError">No s\'ha pogut analitzar l\'URI d\'importació</string>
<string name="openFrontImageInGalleryApp">Obrir la imatge frontal a l\'app de galeria</string>
<string name="settings_lock_on_opening_orientation">En obrir la targeta, bloquejar la orientació de la pantalla</string>
<string name="settings_use_volume_keys_navigation_summary">Utilitza els botons de volum per canviar la targeta que es mostra</string>
<string name="updateBarcodeQuestionText">Ha canviat el valor ID. Vol actualitzar també el codi de barres per uter utilitzar el mateix valor?</string>
<string name="settings_sky_blue_theme">Blau fluix</string>
<string name="starred">Preferides</string>
<string name="deleteConfirmationGroup">Vols eliminar aquest grup?</string>
<string name="removeImage">Eliminar imatge</string>
<string name="app_libraries">Llibreries lliures de tercers: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_libraries">Llibreries de tercers: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="settings_display_barcode_max_brightness">Màxima iluminació</string>
<string name="settings_brown_theme">Marró</string>
<string name="manually_enter_barcode_instructions">Introdueixi el ID de la targeta manualment i trii un codi de barres que s\'assembli al de la seva targeta.</string>
@@ -233,7 +227,7 @@
<string name="addFromPkpass">Seleccioni el fitxer Passbook (.pkpass)</string>
<string name="unsupportedFile">Aquest fitxer no està soportat</string>
<string name="settings_use_volume_keys_navigation">Canviar les targetes al prèmer els botons de volum</string>
<string name="noGroups">Clica el botó + per afegir grups per categoritzar.</string>
<string name="noGroups">Feu clic al botó + més per aferir grups pre categoritzar</string>
<string name="noGroupCards">Aquest grup està buit</string>
<string name="group_name_already_in_use">Ja existeix un grup amb aquest nom</string>
<string name="group_updated">Grup actualitzat</string>
@@ -244,4 +238,43 @@
<string name="settings_system_locale">Idioma del sistema</string>
<string name="settings_catima_theme">Catima</string>
<string name="spend">Gastar</string>
<string name="importExportHelp">Fer una còpia de seguretat de les dades permet moure-les a un altre dispositiu</string>
<string name="importSuccessfulTitle">Importat</string>
<string name="importFailedTitle">La importació ha fallat</string>
<string name="importFailed">No s\'ha pogut realitzar la importació</string>
<string name="exportSuccessfulTitle">Exportat</string>
<string name="exportFailedTitle">L\'exportació ha fallat</string>
<string name="exportFailed">No s\'ha pogut realitzar l\'exportació</string>
<string name="importing">Important…</string>
<string name="exporting">Exportant…</string>
<string name="storageReadPermissionRequired">Cal permís per llegir l\'emmagatzematge per a aquesta acció…</string>
<string name="cameraPermissionRequired">Cal permís per accedir a la càmera per a aquesta acció…</string>
<string name="permissionReadCardsLabel">Legeix targetes Catima</string>
<string name="permissionReadCardsDescription">llegeix les teves targetes Catima i tots els seus detalls, incloses notes i imatges</string>
<string name="cameraPermissionDeniedTitle">No s\'ha pogut accedir a la càmera</string>
<string name="noCameraPermissionDirectToSystemSetting">Per escanejar codis de barres, Catima necessitarà accés a la teva càmera. Toca aquí per canviar la configuració dels permisos.</string>
<string name="about">Sobre</string>
<string name="app_copyright_old">Clauer basat en na Loyalty Card Keychain\ncopyright © 20162020 Branden Archer</string>
<string name="addManually">Introduïu el codi de barres manualment</string>
<string name="addFromImage">Seleccioneu una imatge de la galeria</string>
<string name="groupsList">Grups: <xliff:g>%s</xliff:g></string>
<string name="editGroup">Editeu el grup: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentence">Caduca el: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentenceExpired">Caducat el: <xliff:g>%s</xliff:g></string>
<plurals name="balancePoints">
<item quantity="one"><xliff:g>%s</xliff:g> punt</item>
<item quantity="many"><xliff:g>%s</xliff:g> punts</item>
<item quantity="other"/>
</plurals>
<string name="balanceSentence">Saldo: <xliff:g>%s</xliff:g></string>
<string name="card">Targeta</string>
<string name="editBarcode">Editeu el codi de barres</string>
<string name="expiryDate">Data de caducitat</string>
<string name="never">Mai</string>
<string name="chooseExpiryDate">Trieu la data de caducitat</string>
<string name="moveBarcodeToTopOfScreen">Moveu el codi de barres a la part superior de la pantalla</string>
<string name="noBarcodeFound">No s\'ha trobat cap codi de barres</string>
<string name="errorReadingImage">No s\'ha pogut llegir la imatge</string>
<string name="balance">Saldo</string>
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
</resources>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_add">Přidat</string>
<string name="noGiftCards">Klepněte na tlačítko plus (+) pro přidání karty nebo naimportujete karty z nabídky (⋮).</string>
<string name="noGiftCards">Klepněte na tlačítko plus (+) pro přidání karty nebo naimportujete karty z nabídky (⋮)</string>
<string name="storeName">Název</string>
<string name="note">Poznámka</string>
<string name="cardId">ID karty</string>
@@ -12,12 +12,12 @@
<string name="confirm">Potvrdit</string>
<string name="ok">OK</string>
<string name="sendLabel">Odeslat…</string>
<string name="editCardTitle">Editovat kartu</string>
<string name="editCardTitle">Upravit kartu</string>
<string name="addCardTitle">Přidat kartu</string>
<string name="scanCardBarcode">Naskenovat čárový kód</string>
<string name="importExport">Import/Export</string>
<string name="importExport">Import/export</string>
<string name="exportName">Export</string>
<string name="importExportHelp">Zálohování dat vám umožní přesunout je do jiného zařízení.</string>
<string name="importExportHelp">Zálohování dat vám umožní přesunout je do jiného zařízení</string>
<string name="importSuccessfulTitle">Importováno</string>
<string name="importFailedTitle">Import selhal</string>
<string name="importFailed">Import nelze provést</string>
@@ -27,7 +27,7 @@
<string name="importing">Importuji…</string>
<string name="exporting">Exportuji…</string>
<string name="importOptionFilesystemTitle">Import z úložiště</string>
<string name="importOptionFilesystemExplanation">Vyberte konkrétní soubor v úložišti.</string>
<string name="importOptionFilesystemExplanation">Vyberte konkrétní soubor v úložišti</string>
<string name="importOptionFilesystemButton">Z úložiště</string>
<string name="about">O aplikaci</string>
<string name="app_license">Copyleftovaný svobodný software s licencí GPLv3+</string>
@@ -41,13 +41,13 @@
<string name="never">Nikdy</string>
<string name="expiryDate">Vypršení platnosti</string>
<string name="editBarcode">Upravit čárový kód</string>
<string name="app_resources">Svobodné zdroje třetích stran: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Svobodné knihovny třetích stran: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Zdroje třetích stran: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Knihovny třetích stran: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_copyright_old">Založeno na Loyalty Card Keychain
\ncopyright © 20162020 Branden Archer</string>
<string name="exportOptionExplanation">Data budou zapsána na místo podle vašeho výběru.</string>
<string name="failedParsingImportUriError">Nelze zpracovat URI importu</string>
<string name="noCardExistsError">Tuto kartu nelze najít</string>
<string name="exportOptionExplanation">Data budou zapsána na místo podle vašeho výběru</string>
<string name="failedParsingImportUriError">Nepodařilo se zpracovat URI importu</string>
<string name="noCardExistsError">Kartu se nepodařilo najít</string>
<string name="noCardsMessage">Nejprve přidejte kartu</string>
<string name="cardShortcut">Zástupce karty</string>
<string name="share">Sdílet</string>
@@ -96,8 +96,8 @@
<string name="settings_locale">Jazyk</string>
<string name="turn_flashlight_off">Vypnout světlo</string>
<string name="turn_flashlight_on">Zapnout světlo</string>
<string name="failedGeneratingShareURL">Nepodařilo se vygenerovat adresu URL pro sdílení. Nahlaste to prosím.</string>
<string name="passwordRequired">Zadejte prosím heslo</string>
<string name="failedGeneratingShareURL">Nepodařilo se vygenerovat adresu URL pro sdílení</string>
<string name="passwordRequired">Zadejte heslo</string>
<string name="no">Ne</string>
<string name="yes">Ano</string>
<string name="updateBarcodeQuestionText">Změnili jste ID. Chcete také aktualizovat čárový kód, aby používal stejnou hodnotu\?</string>
@@ -115,36 +115,29 @@
<string name="barcodeId">Hodnota čárového kódu</string>
<string name="setBarcodeId">Nastavení hodnoty čárového kódu</string>
<string name="sameAsCardId">Stejné jako ID</string>
<string name="importVoucherVaultMessage">Vyberte k importu svůj <i>vouchervault.json</i> exportovaný z Voucher Vault.
\nVytvoříte jej tak, že nejprve stisknete tlačítko Exportovat v aplikaci Voucher Vault.</string>
<string name="importVoucherVaultMessage">Vyberte soubor exportu z aplikace Voucher Vault, který chcete importovat.\nVytvořte jej z nabídky stisknutím tlačítka Export v aplikaci Voucher Vault.</string>
<string name="importVoucherVault">Import z Voucher Vault</string>
<string name="importStocardMessage">Vyberte k importu svůj <i>***.zip</i> exportovaný z aplikace Stocard.
\nZískejte ji zasláním e-mailu na adresu support@stocardapp.com s žádostí o export vašich dat.</string>
<string name="importStocard">Import ze Stocard</string>
<string name="importLoyaltyCardKeychainMessage">Vyberte k importu <i>LoyaltyCardKeychain.csv</i> exportovaný z Loyalty Card Keychain.
\nVytvoříte jej z nabídky Import/Export v Loyalty Card Keychain tak, že tam nejprve stisknete tlačítko Exportovat.</string>
<string name="importLoyaltyCardKeychainMessage">Vyberte soubor exportu z aplikace Loyalty Card Keychain, který chcete importovat.\nVytvořte jej z nabídky Import/export jiné aplikace Loyalty Card Keychain klepnutím na Export.</string>
<string name="importLoyaltyCardKeychain">Import z Loyalty Card Keychain</string>
<string name="importFidmeMessage">Vyberte k importu svůj <i>fidme-export-request-xxxxxx.zip</i> exportovaný z FidMe a poté vyberte typy čárových kódů ručně.
\nVytvoříte jej ze svého profilu FidMe tak, že nejprve zvolíte možnost Ochrana dat a poté stisknete tlačítko Extrahovat moje data.</string>
<string name="importFidmeMessage">Vyberte soubor exportu z aplikace FidMe, který chcete importovat, a poté ručně vyberte typy čárových kódů.\nVytvořte jej z aplikace FidMe vybráním položky Data Protection a stisknutím tlačítka Extract my data.</string>
<string name="importFidme">Import z FidMe</string>
<string name="importCatimaMessage">Vyberte <i>catima.zip</i> exportovaný z aplikace Catima, který chcete importovat.
\nVytvoříte jej z nabídky Import/Export jiné aplikace Catima tak, že v ní nejprve stisknete tlačítko Exportovat.</string>
<string name="importCatimaMessage">Vyberte soubor exportu z aplikace Catima, který chcete importovat.\nVytvořte jej z nabídky Import/export jiné aplikace Catima klepnutím na Export.</string>
<string name="importCatima">Import z Catima</string>
<string name="accept">Přijmout</string>
<string name="privacy_policy">Zásady soukromí</string>
<string name="privacy_policy">Ochrana soukromí</string>
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
<string name="chooseImportType">Importovat data z</string>
<string name="points">Body</string>
<string name="currency">Měna</string>
<string name="balance">Zůstatek</string>
<string name="errorReadingImage">Obrázek se nepodařilo přečíst</string>
<string name="errorReadingImage">Nepodařilo se přečíst obrázek</string>
<string name="noBarcodeFound">Čárový kód nenalezen</string>
<string name="groupsList">Skupiny: <xliff:g>%s</xliff:g></string>
<string name="addFromImage">Vybrat obrázek z galerie</string>
<string name="addManually">Zadat čárový kód ručně</string>
<string name="leaveWithoutSaveConfirmation">Ukončit bez uložení\?</string>
<string name="leaveWithoutSaveTitle">Ukončit</string>
<string name="failedOpeningFileManager">Nejprve si nainstalujte správce souborů.</string>
<string name="failedOpeningFileManager">Nepodařilo se otevřít správce souborů</string>
<string name="deleteConfirmationGroup">Smazat skupinu\?</string>
<string name="all">Všechny</string>
<plurals name="groupCardCount">
@@ -152,7 +145,7 @@
<item quantity="few"><xliff:g>%d</xliff:g> karty</item>
<item quantity="other"><xliff:g>%d</xliff:g> karet</item>
</plurals>
<string name="noGroups">Kliknutím na tlačítko + plus přidejte skupiny pro kategorizaci.</string>
<string name="noGroups">Klepnutím na tlačítko plus (+) přidejte skupiny pro kategorizaci</string>
<string name="groups">Skupiny</string>
<string name="enter_group_name">Zadejte název skupiny</string>
<string name="exportSuccessful">Data exportována</string>
@@ -177,7 +170,7 @@
<string name="and_data_usage">a využití dat</string>
<string name="credits">Zásluhy</string>
<string name="on_github">na GitHubu</string>
<string name="source_repository">Úložiště zdrojů</string>
<string name="source_repository">Zdrojový repozitář</string>
<string name="license">Licence</string>
<string name="help_translate_this_app">Pomozte s překladem této aplikace</string>
<string name="report_error">Nahlásit chybu</string>
@@ -191,7 +184,7 @@
<string name="group_name_is_empty">Název skupiny nesmí být prázdný</string>
<string name="group_updated">Skupina aktualizována</string>
<string name="editGroup">Úprava skupiny: <xliff:g>%s</xliff:g></string>
<string name="noGiftCardsGroup">Zatím nemáte žádné věrnostní karty. Jakmile nějaké přidáte, můžete je zde přiřadit do skupiny.</string>
<string name="noGiftCardsGroup">Vytvořte si karty a poté je zde přiřaďte do skupiny</string>
<string name="shortcutSelectCard">Vybrat kartu</string>
<string name="translate_platform">na Weblate</string>
<string name="showMoreInfo">Zobrazit podrobnosti</string>
@@ -204,17 +197,12 @@
</plurals>
<string name="settings_oled_dark">Čistě černé pozadí pro tmavý motiv</string>
<string name="include_if_asking_support">Pokud chcete požádat o podporu, uveďte následující informace:</string>
<string name="settings_follow_system_orientation">Podle orientace systému</string>
<string name="settings_portrait_orientation">Na výšku</string>
<string name="settings_lock_on_opening_orientation">Ponechat orientaci jako při otevření karty</string>
<string name="archive">Archivovat</string>
<string name="unarchive">Vrátit z archivu</string>
<string name="unarchived">Karta vrácena z archivu</string>
<string name="settings_card_orientation">Orientace obrazovky</string>
<string name="settings_landscape_orientation">Na šířku</string>
<string name="duplicateCard">Duplikovat</string>
<string name="archived">Karta archivována</string>
<string name="failedLaunchingPhotoPicker">Nepodařilo se najít podporovanou aplikaci galerie</string>
<string name="failedLaunchingPhotoPicker">Nepodařilo se najít podporovaný nástroj pro výběr obrázků</string>
<plurals name="groupCardCountWithArchived">
<item quantity="one"><xliff:g>%1$d</xliff:g> karta (<xliff:g id="archivedCount">%2$d</xliff:g> archivovaná)</item>
<item quantity="few"><xliff:g>%1$d</xliff:g> karty (<xliff:g id="archivedCount">%2$d</xliff:g> archivované)</item>
@@ -226,7 +214,7 @@
<string name="welcome">Vítejte v Catima</string>
<string name="barcodeLongPressMessage">V aplikaci pro galerii mohou být otevírány pouze obrázky</string>
<string name="failedToRetrieveImageFile">Nepodařilo se získat soubor obrázku</string>
<string name="cameraPermissionDeniedTitle">Nelze získat přístup k fotoaparátu</string>
<string name="cameraPermissionDeniedTitle">Nepodařilo se získat přístup k fotoaparátu</string>
<string name="importCards">Importovat karty</string>
<string name="updateBalance">Aktualizovat zůstatek</string>
<string name="currentBalanceSentence">Současný zůstatek: <xliff:g>%s</xliff:g></string>
@@ -244,8 +232,8 @@
<string name="switchToFrontImage">Přepnout na přední obrázek</string>
<string name="switchToBackImage">Přepnout na zadní obrázek</string>
<string name="switchToBarcode">Přepnout na čárový kód</string>
<string name="openFrontImageInGalleryApp">Otevřít přední obrázek v galerii</string>
<string name="openBackImageInGalleryApp">Otevřít zadní obrázek v galerii</string>
<string name="openFrontImageInGalleryApp">Otevřít přední obrázek v aplikaci prohlížeče obrázků</string>
<string name="openBackImageInGalleryApp">Otevřít zadní obrázek v aplikaci prohlížeče obrázků</string>
<string name="setBarcodeHeight">Nastavit výšku čárového kódu</string>
<string name="donate">Přispět</string>
<string name="icon_header_click_text">Dlouhým stisknutím miniaturu upravíte</string>
@@ -278,18 +266,17 @@
<string name="addWithoutBarcode">Přidat kartu bez čárového kódu</string>
<string name="field_must_not_be_empty">Položka nesmí být prázdná</string>
<string name="app_name">Catima</string>
<string name="settings_follow_sensor_orientation">Vždy otáčet (ignoruje nastavení systému)</string>
<string name="continue_">Pokračovat</string>
<string name="add_manually_warning_title">Doporučuje se skenování</string>
<string name="add_manually_warning_message">V některých obchodech se hodnota čárového kódu liší od čísla napsaného na kartě. Z tohoto důvodu nemusí ruční zadání čárového kódu vždy fungovat. Důrazně doporučujeme místo toho naskenovat čárový kód pomocí fotoaparátu. Chcete přesto pokračovat?</string>
<string name="add_manually_warning_message">U některých karet se hodnota čárového kódu liší od čísla napsaného na kartě. Z tohoto důvodu nemusí ruční zadání čárového kódu vždy fungovat. Doporučujeme místo toho naskenovat čárový kód pomocí fotoaparátu. Chcete přesto pokračovat?</string>
<string name="spend">Utratit</string>
<string name="receive">Obdržet</string>
<string name="amountParsingFailed">Neplatné množství</string>
<string name="addFromPdfFile">Vybrat soubor PDF</string>
<string name="errorReadingFile">Soubor nelze přečíst</string>
<string name="errorReadingFile">Soubor se nepodařilo přečíst</string>
<string name="pageWithNumber">Stránka <xliff:g>%d</xliff:g></string>
<string name="multipleBarcodesFoundPleaseChooseOne">Který z nalezených čárových kódů chcete použít?</string>
<string name="failedLaunchingFileManager">Nelze nalézt podporovaný správce souborů</string>
<string name="failedLaunchingFileManager">Nepodařilo se nat podporovaného správce souborů</string>
<string name="noCameraFoundGuideText">Zdá se, že vaše zařízení nemá fotoaparát. Pokud ano, zkuste zařízení restartovat. V opačném případě použijte tlačítko Další možnosti a přidejte čárový kód jiným způsobem.</string>
<string name="importCancelled">Import zrušen</string>
<string name="exportCancelled">Export zrušen</string>
@@ -297,10 +284,10 @@
<string name="useFrontImage">Použít přední obrázek</string>
<string name="settings_use_volume_keys_navigation_summary">Pomocí tlačítek hlasitosti můžete změnit, která karta se zobrazí</string>
<string name="settings_use_volume_keys_navigation">Přepínat karty pomocí tlačítek hlasitosti</string>
<string name="generic_error_please_retry">Je nám líto, něco se pokazilo, zkuste to prosím znovu...</string>
<string name="generic_error_please_retry">Došlo k chybě</string>
<string name="settings_column_count_portrait">Sloupce v režimu na výšku</string>
<string name="settings_automatic_column_count">Automatický</string>
<string name="addFromPkpass">Vyberte soubor Passbook (.pkpass)</string>
<string name="addFromPkpass">Vyberte soubor Passbook (.pkpass / .pkpasses)</string>
<string name="unsupportedFile">Tento soubor není podporován</string>
<string name="settings_category_title_cards_overview">Přehled karet</string>
<string name="settings_column_count_landscape">Sloupce v režimu na šířku</string>
@@ -316,4 +303,15 @@
<string name="width">Šířka</string>
<string name="card_list_widget_name">Seznam karet</string>
<string name="card_list_widget_empty">Karty přidané do aplikace Catima se zobrazí zde. Pokud máte karty, ujistěte se, že nejsou všechny archivovány.</string>
<string name="cardWithNumber">Karta <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Karta <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Neotáčejte prosím zařízení, protože tím zrušíte akci</string>
<string name="acra_catima_has_crashed">Omlouváme se, aplikace <xliff:g id="app_name">%s</xliff:g> havarovala. Pomozte nám prosím s opravou tohoto problému odesláním hlášení o chybě.</string>
<string name="acra_explain_crash">Pokud je to možné, přidejte prosím další podrobnosti o tom, co jste tu dělali:</string>
<string name="acra_crash_email_subject">Hlášení o pádu <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Ptát se na odesílání hlášení o pádech</string>
<string name="pref_enable_acra_summary">Pokud je povoleno, budete při pádu aplikace dotázáni na jeho nahlášení. Hlášení nejsou nikdy odesílána automaticky.</string>
<string name="copy_value">Kopírovat hodnotu</string>
<string name="copied_to_clipboard">Zkopírováno do schránky</string>
<string name="nothing_to_copy">Nenalezena žádná hodnota</string>
</resources>

View File

@@ -3,15 +3,15 @@
<string name="scanCardBarcode">Scan stregkode</string>
<string name="addCardTitle">Tilføj kort</string>
<string name="editCardTitle">Rediger kort</string>
<string name="sendLabel">Afsend…</string>
<string name="share">Aktie</string>
<string name="sendLabel">Send…</string>
<string name="share">Del</string>
<string name="ok">OK</string>
<string name="deleteConfirmation">Slete dette kort permanent\?</string>
<string name="deleteConfirmation">Slet dette kort permanent?</string>
<plurals name="deleteCardsTitle">
<item quantity="one">Streichen <xliff:g>%d</xliff:g> kort</item>
<item quantity="other">Streichen <xliff:g>%d</xliff:g> korts</item>
<item quantity="one">Slet <xliff:g>%d</xliff:g> kort</item>
<item quantity="other">Slet <xliff:g>%d</xliff:g> korts</item>
</plurals>
<string name="deleteTitle">Karte streichen</string>
<string name="deleteTitle">Slet kort</string>
<string name="confirm">Bekræft</string>
<string name="delete">Slet</string>
<string name="edit">Rediger</string>
@@ -34,7 +34,7 @@
<string name="action_search">Søg</string>
<string name="importExport">Import/eksport</string>
<string name="exportName">Eksport</string>
<string name="importExportHelp">Sikkerhedskopiering af dit data, giver dig mulighed for at flytte dem til en anden enhed.</string>
<string name="importExportHelp">Sikkerhedskopiering af dine data, giver dig mulighed for at flytte dem til en anden enhed.</string>
<string name="importSuccessfulTitle">Importeret</string>
<string name="importFailedTitle">Import mislykkedes</string>
<string name="importFailed">Kunne ikke udføre importering</string>
@@ -54,12 +54,12 @@
\ncopyright © 2016-2020 Branden Archer.</string>
<string name="about">Om</string>
<string name="noCardsMessage">Tilføj først et kort</string>
<string name="cardShortcut">Kort genvej</string>
<string name="cardShortcut">Genvej til kort</string>
<string name="importOptionFilesystemButton">Fra filsystemet</string>
<string name="importOptionFilesystemExplanation">Vælg en bestemt fil fra filsystemet.</string>
<string name="importOptionFilesystemTitle">Import fra filsystem</string>
<string name="exportOptionExplanation">Dataene skrives til en placering efter eget valg.</string>
<string name="failedParsingImportUriError">Kunne ikke analysere import-URI\'en</string>
<string name="failedParsingImportUriError">Kunne ikke importere URI\'en</string>
<string name="noCardExistsError">Kunne ikke finde det kort</string>
<string name="deleteConfirmationGroup">Slet gruppe\?</string>
<string name="all">Alle</string>
@@ -79,16 +79,16 @@
<string name="moveDown">Bevæger sig nedad</string>
<string name="leaveWithoutSaveTitle">Afslut</string>
<string name="addManually">Indtast stregkoden manuelt</string>
<string name="noGiftCardsGroup">Opret kort og tildel dem gupper her.</string>
<string name="noGiftCardsGroup">Opret kort og tildel dem grupper her.</string>
<plurals name="deleteCardsConfirmation">
<item quantity="one">Slet dette <xliff:g>%d</xliff:g> kort permanent\?</item>
<item quantity="other">Slet disse <xliff:g>%d</xliff:g> kort permanent\?</item>
</plurals>
<string name="app_name">Catima</string>
<string name="cameraPermissionRequired">Behov for kamera adgang krævet for denne funktion…</string>
<string name="storageReadPermissionRequired">Behov for lager adgang krævet for denne funktion…</string>
<string name="cameraPermissionRequired">Behov for kamera adgang er krævet for denne funktion…</string>
<string name="storageReadPermissionRequired">Behov for lager adgang er krævet for denne funktion…</string>
<string name="permissionReadCardsLabel">Læs Catima Kort</string>
<string name="permissionReadCardsDescription">læs dine Catima kort og alle deres detaljer, også noter og billeder</string>
<string name="permissionReadCardsDescription">læs dit Catima kort og alle kortets detaljer, også noter og billeder</string>
<string name="cameraPermissionDeniedTitle">Kunne ikke få adgang til kamera</string>
<string name="noCameraPermissionDirectToSystemSetting">For at scanne stregkoder, har Catima behov for at få adgang til dit kamera. Klik her for at ændre dine tilladelser i indstillinger.</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Copyright © 2019<xliff:g>%d</xliff:g> Sylvia van Os og hjælpere</string>
@@ -100,10 +100,6 @@
<string name="group_name_already_in_use">Gruppenavn allerede i brug</string>
<string name="editGroup">Redigerer Gruppe: <xliff:g>%s</xliff:g></string>
<string name="importFidme">Importer fra FidMe</string>
<string name="settings_card_orientation">Skærm orientation</string>
<string name="settings_follow_system_orientation">Følg system</string>
<string name="settings_portrait_orientation">Portræt</string>
<string name="settings_landscape_orientation">Landskab</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">Deaktiver låseskærm når et kort er åbent</string>
<string name="groupsList">Grupper: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentence">Udløber: <xliff:g>%s</xliff:g></string>
@@ -114,7 +110,6 @@
<string name="never">Aldrig</string>
<string name="chooseExpiryDate">Vælg udløbsdato</string>
<string name="balance">Balance</string>
<string name="importStocard">Importer fra Stocard</string>
<string name="balanceSentence">Balance: <xliff:g>%s</xliff:g></string>
<string name="group_name_is_empty">Gruppenavn må ikke være tom</string>
<string name="group_updated">Gruppe opdateret</string>
@@ -134,10 +129,8 @@
<string name="setBarcodeId">Vælg stregkode værdi</string>
<string name="sameAsCardId">Samme som ID</string>
<string name="settings_system_theme">System</string>
<string name="settings_lock_on_opening_orientation">Lås til orientation når kort åbnes</string>
<string name="settings_keep_screen_on_summary">Deaktiver skærm tids slukning når et kort er åbent</string>
<string name="group_edit">Rediger gruppe</string>
<string name="settings_follow_sensor_orientation">Altid roter (ignorer system indstillinger)</string>
<string name="chooseImportType">Importer data fra</string>
<string name="importVoucherVault">Importer fra Voucher Vault</string>
<string name="settings_use_volume_keys_navigation">Skift kort ved brug af lydstyrke knapperne</string>
@@ -151,4 +144,4 @@
<item quantity="one"><xliff:g>%s</xliff:g> point</item>
<item quantity="other"><xliff:g>%s</xliff:g> point</item>
</plurals>
</resources>
</resources>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_search">Suchen</string>
<string name="action_search">Suche</string>
<string name="action_add">Hinzufügen</string>
<string name="noGiftCards">Füge eine Karte mit + hinzu oder importiere welche über das ⋮ Menü.</string>
<string name="noGiftCards">Füge eine Karte mit + hinzu oder importiere welche über das ⋮ Menü</string>
<string name="noMatchingGiftCards">Keine Ergebnisse. Versuche, deine Suche zu ändern.</string>
<string name="storeName">Name</string>
<string name="note">Notiz</string>
@@ -24,7 +24,7 @@
<string name="noCardExistsError">Konnte die Karte nicht finden</string>
<string name="importExport">Import/Export</string>
<string name="exportName">Export</string>
<string name="importExportHelp">Wenn du deine Daten sicherst, kannst du sie auf ein anderes Gerät übertragen.</string>
<string name="importExportHelp">Wenn du deine Daten sicherst, kannst du sie auf ein anderes Gerät übertragen</string>
<string name="importSuccessfulTitle">Importiert</string>
<string name="importFailedTitle">Import fehlgeschlagen</string>
<string name="importFailed">Import konnte nicht durchgeführt werden</string>
@@ -34,7 +34,7 @@
<string name="importing">Importiere…</string>
<string name="exporting">Exportiere…</string>
<string name="importOptionFilesystemTitle">Aus Dateisystem importieren</string>
<string name="importOptionFilesystemExplanation">Wähle eine bestimmte Datei aus dem Dateisystem aus.</string>
<string name="importOptionFilesystemExplanation">Wähle eine bestimmte Datei aus dem Dateisystem aus</string>
<string name="importOptionFilesystemButton">vom Dateisystem</string>
<string name="about">Über</string>
<string name="app_license">Freie Software, lizensiert unter der GPLv3+</string>
@@ -53,20 +53,20 @@
<string name="settings_theme">Farbschema</string>
<string name="app_copyright_old">Basierend auf Loyalty Card Keychain
\nCopyright © 2016-2020 Branden Archer</string>
<string name="exportOptionExplanation">Die Daten werden an einen Ort deiner Wahl geschrieben.</string>
<string name="exportOptionExplanation">Die Daten werden an einen Ort deiner Wahl geschrieben</string>
<string name="failedParsingImportUriError">Die Import-URI konnte nicht verarbeitet werden</string>
<string name="share">Teilen</string>
<string name="barcodeType">Barcodetyp</string>
<string name="starImage">Favoritenstern</string>
<string name="deleteConfirmationGroup">Gruppe löschen?</string>
<string name="all">Alle</string>
<string name="noGroups">Klicke auf das Pluszeichen +, um eine Gruppe hinzuzufügen.</string>
<string name="noGroups">Klicke auf das Pluszeichen +, um eine Gruppe hinzuzufügen</string>
<string name="noGroupCards">Diese Gruppe ist leer</string>
<string name="groups">Gruppen</string>
<string name="enter_group_name">Gruppennamen eingeben</string>
<string name="leaveWithoutSaveConfirmation">Beenden ohne zu speichern\?</string>
<string name="leaveWithoutSaveTitle">Beenden</string>
<string name="failedOpeningFileManager">Installiere zuerst einen Dateimanager.</string>
<string name="failedOpeningFileManager">Dateimanager konnte nicht geöffnet werden</string>
<string name="noBarcode">Kein Barcode</string>
<string name="addManually">Barcode manuell eingeben</string>
<string name="moveDown">Nach unten verschieben</string>
@@ -94,14 +94,13 @@
<string name="settings_keep_screen_on">Bildschirm aktiv lassen</string>
<string name="accept">Annehmen</string>
<string name="privacy_policy">Datenschutzrichtlinie</string>
<string name="importVoucherVaultMessage">Wähle deinen <i>vouchervault.json</i>-Export aus Voucher Vault zum Importieren aus. \nErstelle ihn, indem du zuerst auf Export in Voucher Vault drückst.</string>
<string name="importVoucherVaultMessage">Wähle deinen Export aus Voucher Vault zum Importieren aus.\nErstelle ihn, indem du Export in Voucher Vault drückst.</string>
<string name="importVoucherVault">Aus Voucher Vault importieren</string>
<string name="importLoyaltyCardKeychainMessage">Wählen du deinen <i>LoyaltyCardKeychain.csv</i>-Export aus Loyalty Card Keychain zum Importieren aus.
\nErstelle ihn über das Menü Import/Export in Loyalty Card Keychain, indem du dort zuerst auf Export drückst.</string>
<string name="importLoyaltyCardKeychainMessage">Wähle deinen Export vom „Loyalty Card Keychain“ zum Importieren aus.\nErstelle ihn über das Import/Export Menü in Loyalty Card Keychain, indem du dort auf Export drückst.</string>
<string name="importLoyaltyCardKeychain">Aus Loyalty Card Keychain importieren</string>
<string name="importFidmeMessage">Wähle deinen <i>fidme-export-request-xxxxxx.zip</i>-Export aus FidMe zum Importieren aus und wähle anschließend die Barcodetypen manuell aus. \nOder erstelle ihn aus deinem FidMe-Profil, indem du Datenschutz wählst und dann zuerst auf Meine Daten extrahieren drückst.</string>
<string name="importFidmeMessage">Wähle deinen „FidMe-Export“ zum Importieren und anschließend manuell die Barcodetypen aus.\nErstelle ihn aus deinem FidMe-Profil, indem du Datenschutz wählst und dann auf Meine Daten extrahieren drückst.</string>
<string name="importFidme">Aus FidMe importieren</string>
<string name="importCatimaMessage">Wähle deinen „<i>catima.zip</i>-Export“ von Catima zum Importieren aus.\nErstelle ihn zuerst aus dem Import/Export-Menü einer anderen Catima-Anwendung, indem du dort Export drückst.</string>
<string name="importCatimaMessage">Wähle deinen „Catima-Export“ zum Importieren aus.\nErstelle diesen durch das Drücken auf Export im Import/Export-Menü in einer anderen Catima-Anwendung.</string>
<string name="importCatima">Aus Catima importieren</string>
<string name="setBarcodeId">Barcodewert festlegen</string>
<string name="sameAsCardId">Entspricht Kartennummer</string>
@@ -110,9 +109,9 @@
<string name="noBarcodeFound">Keinen Barcode erkannt</string>
<string name="addFromImage">Bild aus der Galerie wählen</string>
<string name="unsupportedBarcodeType">Dieser Barcodetyp kann noch nicht angezeigt werden. Wir hoffen das Format in einer zukünftigen Version zu unterstützen.</string>
<string name="wrongValueForBarcodeType">Der Wert ist für den gewählten Barcodetyp leider nicht gültig</string>
<string name="app_resources">Freie Ressourcen von Drittanbietern: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Freie Bibliotheken von Drittanbietern: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="wrongValueForBarcodeType">Der Wert ist ungültig für den gewählten Barcodetyp</string>
<string name="app_resources">Ressourcen von Drittanbietern: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Bibliotheken von Drittanbietern: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="intent_import_card_from_url_share_multiple_text">Ich möchte diese Karten mit dir teilen</string>
<string name="no">Nein</string>
<string name="yes">Ja</string>
@@ -125,12 +124,10 @@
<string name="photos">Fotos</string>
<string name="frontImageDescription">Vorderseite</string>
<string name="backImageDescription">Rückseite</string>
<string name="passwordRequired">Bitte gib das Passwort ein</string>
<string name="importStocardMessage">Wähle deinen <i>***.zip</i>-Export aus Stocard zum Importieren aus. \nDu erhälst ihn, indem du eine E-Mail an support@stocardapp.com sendest und um einen Export deiner Daten bittest.</string>
<string name="importStocard">Von Stocard importieren</string>
<string name="passwordRequired">Gib das Passwort ein</string>
<string name="turn_flashlight_off">Blitzlicht ausschalten</string>
<string name="turn_flashlight_on">Blitzlicht einschalten</string>
<string name="failedGeneratingShareURL">URL konnte nicht erstellt werden. Bitte melde das an uns.</string>
<string name="failedGeneratingShareURL">Teilbare URL konnte nicht erstellt werden</string>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> ausgewählt</item>
<item quantity="other"><xliff:g>%d</xliff:g> ausgewählt</item>
@@ -179,9 +176,9 @@
<string name="group_name_already_in_use">Der Gruppenname wird bereits verwendet</string>
<string name="group_name_is_empty">Gruppenname darf nicht leer sein</string>
<string name="group_updated">Gruppe aktualisiert</string>
<string name="editGroup">Gruppe wird bearbeitet: <xliff:g>%s</xliff:g></string>
<string name="editGroup">Gruppe bearbeiten: <xliff:g>%s</xliff:g></string>
<string name="group_edit">Gruppe bearbeiten</string>
<string name="noGiftCardsGroup">Erstelle einige Karten und ordne sie dann hier der Gruppe zu.</string>
<string name="noGiftCardsGroup">Erstelle einige Karten und ordne sie dann hier der Gruppe zu</string>
<string name="setIcon">Vorschaubild festlegen</string>
<string name="selectColor">Farbe auswählen</string>
<string name="translate_platform">auf Weblate</string>
@@ -195,21 +192,16 @@
</plurals>
<string name="settings_oled_dark">Komplett schwarzer Hintergrund im dunklen Design</string>
<string name="include_if_asking_support">Wenn Du Unterstützung haben möchtest, gib bitte folgende Informationen an:</string>
<string name="settings_follow_system_orientation">System folgen</string>
<string name="settings_landscape_orientation">Querformat</string>
<string name="settings_portrait_orientation">Hochformat</string>
<string name="duplicateCard">Duplizieren</string>
<string name="unarchive">Aus dem Archiv wiederherstellen</string>
<string name="settings_card_orientation">Bildschirm-Ausrichtung</string>
<string name="unarchived">Karte aus dem Archiv wiederhergestellt</string>
<string name="archive">Archivieren</string>
<string name="archived">Karte archiviert</string>
<string name="settings_lock_on_opening_orientation">Kartenausrichtung nach dem Öffnen beibehalten</string>
<plurals name="groupCardCountWithArchived">
<item quantity="one"><xliff:g>%1$d</xliff:g> Karte (<xliff:g id="archivedCount">%2$d</xliff:g> archiviert)</item>
<item quantity="other"><xliff:g>%1$d</xliff:g> Karten (<xliff:g id="archivedCount">%2$d</xliff:g> archiviert)</item>
</plurals>
<string name="failedLaunchingPhotoPicker">Es konnte keine unterstützte Galerie-App gefunden werden</string>
<string name="failedLaunchingPhotoPicker">Keine unterstützte App zur Bildauswahl gefunden</string>
<string name="previousCard">Vorherige</string>
<string name="nextCard">Nächste</string>
<string name="failedToOpenUrl">Bitte installiere zuerst einen Webbrowser</string>
@@ -217,7 +209,7 @@
<string name="barcodeLongPressMessage">In der Galerie können nur Bilder geöffnet werden</string>
<string name="failedToRetrieveImageFile">Bilddatei konnte nicht abgerufen werden</string>
<string name="updateBalanceTitle">Wie viel hast du ausgegeben oder erhalten?</string>
<string name="cameraPermissionDeniedTitle">Kein Zugriff auf die Kamera möglich</string>
<string name="cameraPermissionDeniedTitle">Kein Zugriff auf die Kamera</string>
<string name="noCameraPermissionDirectToSystemSetting">Um Barcodes zu scannen, benötigt Catima Zugriff auf deine Kamera. Tippe hier, um deine Berechtigungseinstellungen zu ändern.</string>
<string name="updateBalanceHint">Betrag eingeben</string>
<string name="importCards">Karten importieren</string>
@@ -232,8 +224,8 @@
<string name="anyDate">Beliebiges Datum</string>
<string name="icon_header_click_text">Zum Bearbeiten des Vorschaubildes lang drücken</string>
<string name="switchToBarcode">Zum Barcode wechseln</string>
<string name="openFrontImageInGalleryApp">Vorderseite in Galerie öffnen</string>
<string name="openBackImageInGalleryApp">Rückseite in Galerie öffnen</string>
<string name="openFrontImageInGalleryApp">Vorderseite in Bildbetrachter öffnen</string>
<string name="openBackImageInGalleryApp">Rückseite in Bildbetrachter öffnen</string>
<string name="height">Höhe</string>
<string name="switchToFrontImage">Zur Vorderseite wechseln</string>
<string name="switchToBackImage">Zur Rückseite wechseln</string>
@@ -268,10 +260,9 @@
<string name="field_must_not_be_empty">Feld darf nicht leer sein</string>
<string name="manually_enter_barcode_instructions">Trage die Kartenummer oder Text deiner Karte ein und drücke auf den Barcode, der wie der auf deiner Karte aussieht.</string>
<string name="app_name">Catima</string>
<string name="settings_follow_sensor_orientation">Immer drehen (ignoriert Systemeinstellungen)</string>
<string name="continue_">Fortfahren</string>
<string name="add_manually_warning_title">Scannen empfohlen</string>
<string name="add_manually_warning_message">In einigen Geschäften weicht der Wert des Barcodes von dem auf der Karte angegebenen Wert ab. Aus diesem Grund funktioniert die manuelle Eingabe des Barcodes in einigen Fällen nicht. Es wird dringend empfohlen, den Barcode mit einer Kamera zu scannen. Möchtest du dennoch fortfahren?</string>
<string name="add_manually_warning_message">In einigen Geschäften weicht der Wert des Barcodes von dem auf der Karte angegebenen Wert ab. Aus diesem Grund funktioniert die manuelle Eingabe des Barcodes in einigen Fällen nicht. Es wird empfohlen, stattdessen den Barcode mit deiner Kamera zu scannen. Möchtest du dennoch fortfahren?</string>
<string name="spend">Zahlen</string>
<string name="receive">Erhalten</string>
<string name="amountParsingFailed">Ungültiger Betrag</string>
@@ -298,12 +289,23 @@
<string name="settings_column_count_4">4</string>
<string name="settings_column_count_5">5</string>
<string name="settings_column_count_6">6</string>
<string name="generic_error_please_retry">Entschuldigung, da ist etwas schief gelaufen, versuchen Sie es noch einmal ...</string>
<string name="generic_error_please_retry">Ein Fehler ist aufgetreten</string>
<string name="unsupportedFile">Diese Datei wird nicht unterstützt</string>
<string name="addFromPkpass">Passbook-Datei (.pkpass) auswählen</string>
<string name="addFromPkpass">Eine Passbook-Datei (.pkpass / .pkpasses) auswählen</string>
<string name="sort_by_valid_from">Gültig ab</string>
<string name="width">Breite</string>
<string name="setBarcodeWidth">Barcodebreite einstellen</string>
<string name="card_list_widget_empty">Nachdem du einige Treuekarten in Catima hinzugefügt hast, werden sie hier angezeigt. Wenn du Karten hast, stelle sicher, dass sie nicht alle archiviert sind.</string>
<string name="card_list_widget_empty">Nachdem du einige Treuekarten in Catima hinzugefügt hast, werden sie hier angezeigt. Wenn du Karten hast, stelle sicher, dass diese nicht alle archiviert sind.</string>
<string name="card_list_widget_name">Kartenliste</string>
<string name="cardWithNumberAndLocale">Karte <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="cardWithNumber">Karte <xliff:g>%d</xliff:g></string>
<string name="pref_enable_acra_summary">Wenn aktiviert, wirst du bei einem Absturz gebeten diesen zu melden. Absturzberichte werden niemals automatisch gesendet.</string>
<string name="pref_enable_acra">Bitte um die Übermittlung von Absturzberichten</string>
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> Absturzbericht</string>
<string name="acra_explain_crash">Wenn möglich, bitte übermittle mehr Details zu dem, was du hier getan hast:</string>
<string name="acra_catima_has_crashed">Es tut uns leid, aber <xliff:g id="app_name">%s</xliff:g> ist abgestürzt. Bitte hilf uns diesen Fehler zu beheben und übermittle uns einen Absturzbericht.</string>
<string name="pleaseDoNotRotateTheDevice">Bitte drehe nicht das Gerät, weil sonst die Aktion abbricht</string>
<string name="copy_value">Kopiere Betrag</string>
<string name="copied_to_clipboard">In die Zwischenablage kopiert</string>
<string name="nothing_to_copy">Keinen Betrag gefunden</string>
</resources>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_add">Προσθήκη</string>
<string name="noGiftCards">Κάντε κλικ στο + κουμπί για να προσθέσετε μία κάρτα ή προσθέστε από το ⋮ μενού.</string>
<string name="noGiftCards">Κάντε κλικ στο + κουμπί για να προσθέσετε μία κάρτα ή προσθέστε από το ⋮ μενού</string>
<string name="storeName">Όνομα</string>
<string name="note">Σημείωση</string>
<string name="cardId">Κωδικός κάρτας</string>
@@ -12,15 +12,15 @@
<string name="confirm">Επιβεβαίωση</string>
<string name="ok">Εντάξει</string>
<string name="sendLabel">Αποστολή…</string>
<string name="editCardTitle">Επεξεργασία Κάρτας</string>
<string name="addCardTitle">Προσθήκη Κάρτας</string>
<string name="scanCardBarcode">Σαρώστε τον γραμμωτό κώδικα</string>
<string name="cardShortcut">Συντόμευση Κάρτας</string>
<string name="editCardTitle">Επεξεργασία κάρτας</string>
<string name="addCardTitle">Προσθήκη κάρτας</string>
<string name="scanCardBarcode">Σάρωση γραμμωτού κώδικα</string>
<string name="cardShortcut">Συντόμευση κάρτας</string>
<string name="noCardsMessage">Προσθέστε μία κάρτα πρώτα</string>
<string name="noCardExistsError">Δεν ήταν δυνατό να εντοπιστεί η κάρτα</string>
<string name="importExport">Εισαγωγή/Εξαγωγή</string>
<string name="exportName">Εξαγωγή</string>
<string name="importExportHelp">Τα αντίγραφα ασφαλείας σας επιτρέπουν να τα εισάγετε σε άλλη συσκευή.</string>
<string name="importExportHelp">Τα αντίγραφα ασφαλείας σας επιτρέπουν να τα εισάγετε σε άλλη συσκευή</string>
<string name="importSuccessfulTitle">Εισήχθησαν</string>
<string name="importFailedTitle">Εισαγωγή ανεπιτυχής</string>
<string name="importFailed">Δεν ήταν δυνατή η εισαγωγή</string>
@@ -30,7 +30,7 @@
<string name="importing">Γίνεται εισαγωγή του…</string>
<string name="exporting">Γίνεται εξαγωγή του…</string>
<string name="importOptionFilesystemTitle">Εισαγωγή από το σύστημα αρχείων</string>
<string name="importOptionFilesystemExplanation">Επιλέξτε ένα συγκεκριμένο αρχείο από το σύστημα αρχείων.</string>
<string name="importOptionFilesystemExplanation">Επιλέξτε ένα συγκεκριμένο αρχείο από το σύστημα αρχείων</string>
<string name="importOptionFilesystemButton">Από το σύστημα αρχείων</string>
<string name="about">Σχετικά</string>
<string name="app_license">Άδεια χρήσης υπό GPLv3+</string>
@@ -50,7 +50,7 @@
<item quantity="one"><xliff:g>%d</xliff:g> επιλέγχθηκε</item>
<item quantity="other"><xliff:g>%d</xliff:g> επιλέγχθηκαν</item>
</plurals>
<string name="noGiftCardsGroup">Δημιούργησε κάρτες και βάλτες σε αυτή την ομάδα.</string>
<string name="noGiftCardsGroup">Δημιούργησε κάρτες και βάλτες σε αυτή την ομάδα</string>
<string name="addManually">Εισαγωγή γραμμωτού κώδικα με μη αυτόματο τρόπο</string>
<string name="never">Ποτέ</string>
<string name="share">Κοινοποίηση</string>
@@ -58,7 +58,7 @@
<item quantity="one"><xliff:g>%s</xliff:g> πόντος</item>
<item quantity="other"><xliff:g>%s</xliff:g> πόντοι</item>
</plurals>
<string name="exportOptionExplanation">Τα δεδομένα θα μεταφερθούν σε τοποθεσία της επιλογής σας.</string>
<string name="exportOptionExplanation">Τα δεδομένα θα μεταφερθούν σε τοποθεσία της επιλογής σας</string>
<string name="settings_theme">Θέμα</string>
<string name="groupsList">Ομάδες: <xliff:g>%s</xliff:g></string>
<string name="barcodeId">Τιμή γραμμωτού κώδικα</string>
@@ -81,14 +81,13 @@
<string name="source_repository">Αποθήκη κώδικα</string>
<string name="on_github">στο GitHub</string>
<string name="on_google_play">στο Google Play</string>
<string name="report_error">Αναφορά Σφάλματος</string>
<string name="report_error">Αναφορά σφάλματος</string>
<string name="starred">Αγαπημένα</string>
<string name="translate_platform">στο Weblate</string>
<string name="importLoyaltyCardKeychain">Εισαγωγή από Loyalty Card Keychain</string>
<string name="importLoyaltyCardKeychainMessage">Επιλέξτε την <i>LoyaltyCardKeychain.csv</i> εξαγωγή από το Loyalty Card Keychain για εισαγωγή.
\nΔημιουργήστε το από το μενού Εισαγωγής/Εξαγωγής στο Loyalty Card Keychain επιλέγοντας Εξαγωγή.</string>
<string name="importLoyaltyCardKeychainMessage">Επιλέξτε την εξαγωγή σας από το Loyalty Card Keychain για εισαγωγή. \nΔημιουργήστε το από το μενού Εισαγωγής/Εξαγωγής στο Loyalty Card Keychain επιλέγοντας Εξαγωγή.</string>
<string name="importFidme">Εισαγωγή από FidMe</string>
<string name="importFidmeMessage">Επιλέξτε την <i>fidme-export-request-xxxxxx.zip</i> εξαγωγή από το FidMe για εισαγωγή και επιλέξτε χειροκίνητα τους τύπους γραμμωτού κώδικα μετέπειτα.\nΔημιουργήστε το από το FidMe προφίλ επιλέγοντας Προστασία Δεδομένων και πατώντας Εξαγωγή δεδομένων πρώτα.</string>
<string name="importFidmeMessage">Επιλέξτε την εξαγωγή σας από το FidMe για εισαγωγή και επιλέξτε χειροκίνητα τους τύπους γραμμωτού κώδικα.\nΔημιουργήστε το από το FidMe προφίλ επιλέγοντας Προστασία Δεδομένων και πατώντας Εξαγωγή δεδομένων.</string>
<string name="setBarcodeId">Επιλέξτε τιμή γραμμωτού κώδικα</string>
<string name="wrongValueForBarcodeType">Η τιμή δεν είναι έγκυρη για τον επιλεγμένο γραμμωτό κώδικα</string>
<string name="setBackImage">Επιλογή οπίσθιας εικόνας</string>
@@ -106,7 +105,7 @@
<item quantity="one">Διαγραφή <xliff:g>%d</xliff:g> κάρτας</item>
<item quantity="other">Διαγραφή <xliff:g>%d</xliff:g> καρτών</item>
</plurals>
<string name="errorReadingImage">Δεν ήταν δυνατό να διαβαστεί η εικόνα</string>
<string name="errorReadingImage">Δεν ήταν δυνατή η ανάγνωση της εικόνας</string>
<string name="currency">Νόμισμα</string>
<string name="privacy_policy">Πολιτική απορρήτου</string>
<string name="chooseImportType">Εισαγωγή δεδομένων από</string>
@@ -115,31 +114,27 @@
<item quantity="one"><xliff:g>%1$d</xliff:g> κάρτα ( <xliff:g id="archivedCount">%2$d</xliff:g> αρχειοθετήθηκε)</item>
<item quantity="other"><xliff:g>%1$d</xliff:g> κάρτες ( <xliff:g id="archivedCount">%2$d</xliff:g> αρχειοθετήθηκαν)</item>
</plurals>
<string name="importCatimaMessage">Επιλέξτε την <i>catima.zip</i> εξαγωγή από το Catima για εισαγωγή
\nΔημιουργήστε το από το μενού Εισαγωγής/Εξαγωγής μιας άλλης εφαρμογής Catima κάνοντας εξαγωγή εκεί πρώτα.</string>
<string name="importStocardMessage">Επιλέξτε την <i>***.zip</i> εξαγωγή από το Stocard για εισαγωγή.
\nΠάρτε το στέλνοντας email στο: support@stocardapp.com ζητώντας μια εξαγωγή αρχείων των δεδομένων σας.</string>
<string name="importCatimaMessage">Επιλέξτε την εξαγωγή σας από το Catima για εισαγωγή.\nΔημιουργήστε την από το μενού Εισαγωγή/Εξαγωγή μιας άλλης εφαρμογής Catima πατώντας Εξαγωγή.</string>
<string name="intent_import_card_from_url_share_multiple_text">Θέλω να μοιραστώ μερικές κάρτες μαζί σου</string>
<string name="editGroup">Επεξεργασία Ομάδας: <xliff:g>%s</xliff:g></string>
<string name="editGroup">Επεξεργασία ομάδας: <xliff:g>%s</xliff:g></string>
<string name="setFrontImage">Επιλογή εμπρόσθιας εικόνας</string>
<string name="importVoucherVaultMessage">Επιλέξτε την <i>vouchervault.json</i> εξαγωγή από το Voucher Vault για εισαγωγή.
\nΔημιουργήστε το επιλέγοντας Εξαγωγή στο Voucher Vault.</string>
<string name="importVoucherVaultMessage">Επιλέξτε την εξαγωγή σας από το Voucher Vault για εισαγωγή. \nΔημιουργήστε το επιλέγοντας Εξαγωγή στο Voucher Vault.</string>
<string name="unsupportedBarcodeType">Ο τύπος γραμμωτού κώδικα δεν μπορεί να εμφανιστεί ακόμα. Μπορεί να υποστηρίζεται σε μια μελλοντική έκδοση της εφαρμογής.</string>
<string name="frontImageDescription">Εμπρόσθια</string>
<string name="photos">Φωτογραφίες</string>
<string name="backImageDescription">Οπίσθια</string>
<string name="updateBarcodeQuestionTitle">Ενημέρωση τιμής γραμμωτού κώδικα;</string>
<string name="passwordRequired">Παρακαλώ εισάγετε τον κωδικό</string>
<string name="sort_by_most_recently_used">Χρήση</string>
<string name="passwordRequired">Εισάγετε τον κωδικό</string>
<string name="sort_by_most_recently_used">Πρόσφατα χρησιμοποιημένα</string>
<string name="shortcutSelectCard">Επιλέξτε μία κάρτα</string>
<string name="barcodeImageDescriptionWithType">Εικόνα <xliff:g>%s</xliff:g> γραμμωτού κώδικα</string>
<string name="app_libraries">Ελεύθερες βιβλιοθήκες τρίτων: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_libraries">Βιβλιοθήκες τρίτων: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="license">Άδεια</string>
<string name="include_if_asking_support">Αν θέλετε να ζητήσετε υποστήριξη, συμπεριλάβετε τις ακόλουθες πληροφορίες:</string>
<string name="importSuccessful">Δεδομένα εισήχθησαν</string>
<string name="moveUp">Προχώρα πάνω</string>
<string name="barcodeType">Τύπος γραμμωτού κώδικα</string>
<string name="app_resources">Ελεύθερες πηγές τρίτων: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_resources">Πηγές τρίτων: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectColor">Επιλογή χρώματος</string>
<string name="setIcon">Ορισμός εικονιδίου</string>
<string name="settings_sky_blue_theme">Γαλάζιο</string>
@@ -153,7 +148,7 @@
<string name="points">Πόντοι</string>
<string name="exportSuccessful">Δεδομένα εξήχθησαν</string>
<string name="settings_disable_lockscreen_while_viewing_card">Αποτροπή κλειδώματος οθόνης</string>
<string name="failedLaunchingPhotoPicker">Δεν βρέθηκε υποστηριζόμενη εφαρμογή συλλογής</string>
<string name="failedLaunchingPhotoPicker">Δεν βρέθηκε υποστηριζόμενος επιλογέας εικόνων</string>
<string name="noBarcode">Χωρίς γραμμωτό κώδικα</string>
<string name="starImage">Αγαπημένο αστέρι</string>
<string name="balanceSentence">Υπόλοιπο: <xliff:g>%s</xliff:g></string>
@@ -164,20 +159,15 @@
</plurals>
<string name="app_copyright_old">Βασισμένο στο Loyalty Card Keychain
\nπνευματικά δικαιώματα © 2016-2020 Branden Archer</string>
<string name="settings_follow_system_orientation">Ακολούθηση συστήματος</string>
<string name="settings_card_orientation">Προσανατολισμός οθόνης</string>
<string name="settings_portrait_orientation">Πορτραίτο</string>
<string name="settings_landscape_orientation">Οριζόντια</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Πνευματικά δικαιώματα © 2019-<xliff:g>%d</xliff:g> Sylvia van Os</string>
<string name="settings_lock_on_opening_orientation">Κλείδωμα τρέχοντος προσανατολισμού όταν ανοίγει μία κάρτα</string>
<string name="intent_import_card_from_url_share_text">Θέλω να μοιραστώ μία κάρτα μαζί σου</string>
<string name="enter_group_name">Εισάγετε όνομα ομάδας</string>
<string name="groups">Ομάδες</string>
<string name="noGroups">Κάντε κλικ στο + κουμπί ώστε να προσθέσετε ομάδες για κατηγοριοποίηση.</string>
<string name="noGroups">Κάντε κλικ στο + κουμπί ώστε να προσθέσετε ομάδες για κατηγοριοποίηση</string>
<string name="group_name_already_in_use">Αυτό το όνομα υπάρχει ήδη</string>
<string name="group_updated">Η ομάδα ενημερώθηκε</string>
<string name="all">Όλες</string>
<string name="failedOpeningFileManager">Εγκαταστήστε έναν διαχειριστή αρχείων πρώτα.</string>
<string name="failedOpeningFileManager">Αποτυχία εκκίνησης διαχειριστή αρχείων</string>
<string name="leaveWithoutSaveConfirmation">Έξοδος χωρίς αποθήκευση;</string>
<string name="expiryStateSentenceExpired">Έληξε: <xliff:g>%s</xliff:g></string>
<string name="card">Κάρτα</string>
@@ -187,12 +177,11 @@
<string name="noBarcodeFound">Δεν βρέθηκε γραμμωτός κώδικα</string>
<string name="balance">Υπόλοιπο</string>
<string name="importCatima">Εισαγωγή από Catima</string>
<string name="importStocard">Εισαγωγή από Stocard</string>
<string name="importVoucherVault">Εισαγωγή από Voucher Vault</string>
<string name="sameAsCardId">Όπως ο κωδικός</string>
<string name="exportPassword">Προσθέστε έναν κωδικό για προστασία της εξαγωγής (προαιρετικά)</string>
<string name="exportPasswordHint">Εισαγωγή κωδικού</string>
<string name="failedGeneratingShareURL">Δεν ήταν δυνατή η δημιουργία κοινοποιούμενου URL. Παρακαλώ αναφέρετε το.</string>
<string name="failedGeneratingShareURL">Δεν ήταν δυνατή η δημιουργία κοινοποιούμενου URL</string>
<string name="turn_flashlight_on">Ενεργοποίηση φακού</string>
<string name="turn_flashlight_off">Απενεργοποίηση φακού</string>
<string name="settings_locale">Γλώσσα</string>
@@ -245,7 +234,6 @@
<string name="icon_header_click_text">Πατήστε παρατεταμένα για επεξεργασία του εικονιδίου</string>
<string name="openFrontImageInGalleryApp">Ανοίξτε την εμπρόσθια εικόνα στη συλλογή εικόνων</string>
<string name="storageReadPermissionRequired">Δικαίωμα ανάγνωσης του χώρου αποθήκευσης απαραίτητο για αυτήν την ενέργεια…</string>
<string name="settings_follow_sensor_orientation">Πάντα σε περιστροφή (αγνοεί τις ρυθμίσεις του συστήματος)</string>
<string name="validFromDate">Ισχύει από</string>
<string name="anyDate">Οποιαδήποτε ημερομηνία</string>
<string name="chooseValidFromDate">Επιλέξτε έγκυρη ημερομηνία από</string>
@@ -256,7 +244,7 @@
<string name="continue_">Συνέχεια</string>
<string name="settings_category_title_privacy">Απόρρητο</string>
<string name="addFromPdfFile">Επιλογή αρχείου PDF</string>
<string name="add_manually_warning_message">Για ορισμένα καταστήματα, ο γραμμωτός κώδικας διαφέρει από τον αριθμό που αναγράφεται πάνω στην κάρτα. Εξαιτίας αυτού, η εισαγωγή γραμμωτού κώδικα χειροκίνητα ενδέχεται να μην λειτουργεί πάντα. Προτείνεται να σκανάρετε τον γραμμωτό κώδικα με χρήση της κάμερας. Επιθυμείτε να συνεχίσετε ;</string>
<string name="add_manually_warning_message">Για ορισμένες κάρτες, ο γραμμωτός κώδικας διαφέρει από τον αριθμό που αναγράφεται πάνω στην κάρτα. Εξαιτίας αυτού, η εισαγωγή γραμμωτού κώδικα χειροκίνητα ενδέχεται να μην λειτουργεί πάντα. Προτείνεται να σαρώσετε τον γραμμωτό κώδικα με χρήση της κάμερας. Επιθυμείτε να συνεχίσετε;</string>
<string name="amountParsingFailed">Μη έγκυρο ποσό</string>
<string name="show_balance">Προβολή υπολοίπου</string>
<string name="action_display_options">Επιλογές εμφάνισης</string>
@@ -301,12 +289,23 @@
<string name="settings_column_count_2">2</string>
<string name="settings_column_count_6">6</string>
<string name="settings_column_count_7">7</string>
<string name="generic_error_please_retry">Λυπούμαστε, κάτι πήγε στραβά, δοκιμάστε ξανά...</string>
<string name="generic_error_please_retry">Συνέβη ένα σφάλμα</string>
<string name="unsupportedFile">Το αρχείο δεν υποστηρίζεται</string>
<string name="addFromPkpass">Επιλογή αρχείου Passbook (.pkpass)</string>
<string name="sort_by_valid_from">Έναρξη ισχύος</string>
<string name="addFromPkpass">Επιλέξτε αρχείο Passbook (.pkpass / .pkpasses)</string>
<string name="sort_by_valid_from">Έγκυρα από</string>
<string name="setBarcodeWidth">Ορισμός πλάτους γραμμωτού κώδικα</string>
<string name="width">Πλάτος</string>
<string name="card_list_widget_empty">Αφού προσθέσετε μερικές κάρτες επιβράβευσης στο Catima, θα εμφανιστούν εδώ. Εάν έχετε κάρτες, βεβαιωθείτε ότι δεν είναι όλες αρχειοθετημένες.</string>
<string name="card_list_widget_name">Λίστα καρτών</string>
<string name="cardWithNumber">Κάρτα <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Κάρτα <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Μην περιστρέφετε τη συσκευή, καθώς αυτό θα ακυρώσει την ενέργεια</string>
<string name="acra_catima_has_crashed">Λυπούμαστε, αλλά το <xliff:g id="app_name">%s</xliff:g> παρουσίασε σφάλμα. Βοηθήστε μας να διορθώσουμε αυτό το πρόβλημα, στέλνοντάς μας μια αναφορά σφάλματος.</string>
<string name="acra_explain_crash">Αν είναι δυνατόν, προσθέστε περισσότερες λεπτομέρειες σχετικά με το τι κάνατε εδώ:</string>
<string name="acra_crash_email_subject">Αναφορά σφάλματος <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Ερώτηση για αποστολή αναφορών σφαλμάτων</string>
<string name="pref_enable_acra_summary">Όταν είναι ενεργοποιημένη, θα σας ζητηθεί να αναφέρετε ένα σφάλμα όταν συμβεί. Οι αναφορές σφάλματος δεν αποστέλλονται ποτέ αυτόματα.</string>
<string name="copy_value">Αντιγραφή τιμής</string>
<string name="copied_to_clipboard">Αντιγράφηκε στο πρόχειρο</string>
<string name="nothing_to_copy">Δεν βρέθηκε τιμή</string>
</resources>

View File

@@ -74,7 +74,7 @@
<string name="intent_import_card_from_url_share_text">Mi deziras dividi karto kun vi</string>
<string name="exportSuccessful">Datumoj eksportitaj</string>
<string name="noGroupCards">Ĉi tiu grupo estas malplena</string>
<string name="noGiftCards">Klavu la \"+\" butonon por aldoni karton, aŭ importu el la menuo \" ⋮\".</string>
<string name="noGiftCards">Klavu la \"+\" butonon por aldoni karton, aŭ importu el la menuo \" ⋮\"</string>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> elektita</item>
<item quantity="other"><xliff:g>%d</xliff:g> elektitaj</item>
@@ -213,10 +213,6 @@
<string name="cameraPermissionDeniedTitle">Fotilo neatingebla</string>
<string name="noCameraPermissionDirectToSystemSetting">Por skani strikodojn Catima bezonas atingorajton al via fotilo. Klaku ĉi tie por ŝanĝi viajn permesajn agordojn.</string>
<string name="app_copyright_short">Kopirajto © Sylvia van Os kaj kontribuantoj</string>
<string name="settings_card_orientation">Orientiĝo de strikodo</string>
<string name="settings_follow_system_orientation">Laŭ la sistemo</string>
<string name="settings_portrait_orientation">Vertikala</string>
<string name="settings_landscape_orientation">Horizontala</string>
<string name="settings_display_barcode_max_brightness_summary">Bezonata por ke iuj skaniloj funkciu</string>
<string name="unsupportedBarcodeType">Ne eblas montri ĉi tiun strikodspecon. Ĝi eble estos subtenata en posta versio de la apo.</string>
<string name="importVoucherVaultMessage">Elektu la <i>vouchervault.json</i> eksporton de Voucher Vault kiun vi volas importi.
@@ -233,8 +229,6 @@
<string name="settings_pink_theme">Rozkolora</string>
<string name="field_must_not_be_empty">Kampo devas ne esti malplena</string>
<string name="manually_enter_barcode_instructions">Entajpu la identigilon aŭ tekston sur via karto kaj premu la strikodon kiu aspektas kiel tiu sur via karto.</string>
<string name="importStocardMessage">Elektu la <i>***.zip</i> eksoporton de Stocard kiun vi volas importi.
\nAkiru ĝin sendante retpoŝton al support@stocardapp.com petante eksporton de viaj datumoj.</string>
<string name="turn_flashlight_off">Malŝalti poŝlampon</string>
<string name="add_manually_warning_title">Skani estas rekomendata</string>
<string name="continue_">Daŭrigi</string>
@@ -256,8 +250,6 @@
<string name="pageWithNumber">Paĝo <xliff:g>%d</xliff:g></string>
<string name="settings_system_locale">Sistemo</string>
<string name="app_resources">Liberaj triaj risurcoj: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="settings_follow_sensor_orientation">Ĉiam turni (ignori la agordojn de la sistemo)</string>
<string name="settings_lock_on_opening_orientation">Fiksi al la orientiĝo uzata dum malfermado de la karto</string>
<string name="importCatimaMessage">Elektu la <i>catima.zip</i> eksporton kiun vi volas importi.
\nKreu ĝin unue en la importi/eksporti menuo en alia Catima apo elektante \'eksporti\' tie.</string>
<string name="importFidme">Importi el FidMe</string>
@@ -272,7 +264,6 @@
<string name="updateBarcodeQuestionText">Vi ŝanĝis la identigon. Ĉu vi volas ankaŭ ĝisdatigi la strikodon por uzi la saman valoron?</string>
<string name="importLoyaltyCardKeychainMessage">Elektu la <i>LoyaltyCardKeychain.csv</i> eksporton de Loyalty Card Keychain kiun vi volas importi.
\nKreu ĝin unue de la \"Importi/eksporti\" menuo en Loyalty Card Keychain elektante \"eksporti\" tie.</string>
<string name="importStocard">Importi de Stocard</string>
<string name="importVoucherVault">Importi el Voucher Vault</string>
<string name="turn_flashlight_on">Enŝalti poŝlampon</string>
<string name="settings_locale">Lingvo</string>

View File

@@ -12,13 +12,13 @@
<string name="exportFailed">No se pudo exportar</string>
<string name="noBarcode">Sin código de barra</string>
<string name="edit">Editar</string>
<string name="noGiftCards">Pulsá el botón + para agregar una tarjeta de regalo, o importá una desde el menú.</string>
<string name="noGiftCardsGroup">Crea tarjetas de regalo, y asignales un grupo.</string>
<string name="noGiftCards">Pulsá el botón + para agregar una tarjeta de regalo, o importá una desde el menú</string>
<string name="noGiftCardsGroup">Crea tarjetas de regalo, y asignales un grupo</string>
<string name="note">Nota</string>
<string name="unstar">Borrar de favoritos</string>
<string name="importExport">Importar/Exportar</string>
<string name="exportName">Exportar</string>
<string name="importExportHelp">Crear una copia de seguridad de sus datos, permite moverlos hacia otro dispositivo.</string>
<string name="importExportHelp">Crear una copia de seguridad de sus datos, permite moverlos hacia otro dispositivo</string>
<string name="importing">Importando…</string>
<string name="exporting">Exportando…</string>
<string name="save">Guardar</string>
@@ -44,12 +44,12 @@
<plurals name="deleteCardsTitle">
<item quantity="one">Borrar <xliff:g>%d</xliff:g> tajeta</item>
<item quantity="many">Borrar <xliff:g>%d</xliff:g> tarjetas</item>
<item quantity="other"></item>
<item quantity="other"/>
</plurals>
<plurals name="deleteCardsConfirmation">
<item quantity="one">¿Borrar esta<xliff:g>%d</xliff:g> tarjeta de forma permanente\?</item>
<item quantity="many">Borrar estas <xliff:g>%d</xliff:g> tarjetas de forma permanente\?</item>
<item quantity="other"></item>
<item quantity="other"/>
</plurals>
<string name="failedOpeningFileManager">Primero instale un administrador de archivos.</string>
<string name="intent_import_card_from_url_share_multiple_text">Quiero compartirte algunas tarjetas</string>
@@ -58,18 +58,15 @@
<string name="about_title_fmt">Acerca de <xliff:g id="app_name">%s</xliff:g></string>
<string name="editBarcode">Editar código de barras</string>
<string name="removeImage">Remover imágen</string>
<string name="settings_portrait_orientation">Vertical</string>
<string name="takePhoto">Tomar una foto</string>
<string name="cameraPermissionDeniedTitle">No se pudo acceder a la cámara</string>
<string name="wrongValueForBarcodeType">El valor no es válido para el tipo de código de barras seleccionado</string>
<string name="expiryDate">Fecha de vencimiento</string>
<string name="importStocard">Importar desde Stocard</string>
<string name="currency">Moneda</string>
<string name="group_edit">Editar grupo</string>
<string name="debug_version_fmt">Versión: <xliff:g id="version">%s</xliff:g></string>
<string name="backImageDescription">Imágen dorsal</string>
<string name="noCameraPermissionDirectToSystemSetting">Para escanear códigos de barra, Catima necesitará acceso a la cámara. Presione aquí para cambiar la configuración de sus permisos.</string>
<string name="settings_lock_on_opening_orientation">Bloquear a la orientación utilizada al abrir la tarjeta</string>
<string name="app_loyalty_card_keychain">Cartera para Tarjetas de Fidelización</string>
<string name="importOptionFilesystemTitle">Importar desde su sistema de archivos</string>
<string name="leaveWithoutSaveTitle">Salir</string>
@@ -81,11 +78,10 @@
<string name="settings_dark_theme">Oscuro</string>
<string name="importFidme">Importar desde FidMe</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">Deshabilita el bloqueo de pantalla mientras se ve una tarjeta</string>
<string name="exportOptionExplanation">Los datos serán escritos a una ubicación de tu elección.</string>
<string name="exportOptionExplanation">Los datos serán escritos a una ubicación de tu elección</string>
<string name="app_copyright_old">Basado en Loyalty Card Keychain
\ncopyright © 20162020 Branden Archer</string>
<string name="importVoucherVaultMessage">Seleccione su <i>vouchervault.json</i> exportado desde Voucher Vault para importarlo.
\nPrimero créelo presionando la opción Exportar en Voucher Vault.</string>
<string name="importVoucherVaultMessage">Seleccione su exportado desde Voucher Vault para importarlo.\nCréelo presionando la opción Exportar en Voucher Vault.</string>
<string name="chooseImportType">Importar datos desde</string>
<string name="frontImageDescription">Imágen frontal</string>
<string name="settings_system_theme">Sistema</string>
@@ -103,11 +99,10 @@
<string name="settings_keep_screen_on">Mantener la pantalla encendida</string>
<string name="setBarcodeId">Establecer valor del código de barras</string>
<string name="importCatima">Importar desde Catima</string>
<string name="settings_follow_system_orientation">Seguir el sistema</string>
<string name="intent_import_card_from_url_share_text">Quiero compartirte una tarjeta</string>
<string name="addFromImage">Seleccione una imágen desde la galería</string>
<string name="app_copyright_short">Copyright © Sylvia van Os y colaboradores</string>
<string name="importOptionFilesystemExplanation">Elija un archivo desde su sistema de archivos.</string>
<string name="importOptionFilesystemExplanation">Elija un archivo desde su sistema de archivos</string>
<string name="exportSuccessful">Datos exportados</string>
<string name="settings_allow_content_provider_read_summary">Las aplicaciones aún tendrán que pedir permiso para obtener acceso</string>
<string name="editGroup">Edición de grupo: <xliff:g>%s</xliff:g></string>
@@ -116,11 +111,8 @@
<string name="about">Acerca de</string>
<string name="sameAsCardId">Igual que el código</string>
<string name="importOptionFilesystemButton">Desde el sistema de archivos</string>
<string name="settings_landscape_orientation">Horizontal</string>
<string name="privacy_policy">Política de Privacidad</string>
<string name="enter_group_name">Ingrese el nombre del grupo</string>
<string name="importStocardMessage">Seleccione su <i>***.zip</i> exportado desde Stocard para importarlo.
\nObténgalo mandando un correo electrónico a support@stocardapp.com preguntando por una copia de tus datos.</string>
<string name="addManually">Ingresar el código de barras manualmente</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Copyright © 2019<xliff:g>%d</xliff:g> Sylvia van Os y colaboradores</string>
<string name="importVoucherVault">Importar desde Voucher Vault</string>
@@ -131,8 +123,7 @@
<string name="balance">Balance</string>
<string name="cameraPermissionRequired">Se necesita permiso para acceder a la cámara para realizar esta acción…</string>
<string name="settings_allow_content_provider_read_title">Permitir que otras aplicaciones accedan a mis datos</string>
<string name="importLoyaltyCardKeychainMessage">Seleccione su <i>LoyaltyCardKeychain.csv</i> exportado desde Loyalty Card Keychain para importarlo.
\nPrimero créelo desde el menu Importar/Exportar de Loyalty Card Keychain al presionar la opción Exportar.</string>
<string name="importLoyaltyCardKeychainMessage">Seleccione su exportado desde Loyalty Card Keychain para importarlo.\nCréelo desde el menu Importar/Exportar de Loyalty Card Keychain al presionar la opción Exportar.</string>
<string name="settings_light_theme">Claro</string>
<string name="moveDown">Mover hacia abajo</string>
<string name="importLoyaltyCardKeychain">Importar desde Loyalty Card Keychain</string>
@@ -150,30 +141,27 @@
<string name="settings">Configuración</string>
<string name="selectBarcodeTitle">Seleccione el código de barras</string>
<string name="importSuccessful">Datos importados</string>
<string name="settings_card_orientation">Orientación del código de barras</string>
<string name="app_libraries">Librerías externas libres: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Recursos externos libres: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Librerías externas: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Recursos externos: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_name">Catima</string>
<string name="accept">Aceptar</string>
<plurals name="groupCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> tarjeta</item>
<item quantity="many"><xliff:g>%d</xliff:g> tarjetas</item>
<item quantity="other"></item>
<item quantity="other"/>
</plurals>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> seleccionado</item>
<item quantity="many"><xliff:g>%d</xliff:g> seleccionados</item>
<item quantity="other"></item>
<item quantity="other"/>
</plurals>
<string name="importCatimaMessage">Seleccione su <i>catima.zip</i> exportado desde Catima para importarlo.
\nPrimero créelo desde el menu para Importar/Exportar de otra aplicación de Catima presionando la opción Exportar.</string>
<string name="importCatimaMessage">Seleccione su exportado desde Catima para importarlo.\nCréelo desde el menu para Importar/Exportar de otra aplicación de Catima presionando la opción Exportar.</string>
<plurals name="balancePoints">
<item quantity="one"><xliff:g>%s</xliff:g> punto</item>
<item quantity="many"><xliff:g>%s</xliff:g> puntos</item>
<item quantity="other"></item>
<item quantity="other"/>
</plurals>
<string name="importFidmeMessage">Seleccione su <i>fidme-export-request-xxxxxx.zip</i> exportado de FidMe para importarlo, y a continuación seleccione manualmente los tipos de código de barras.
\nPrimero créelo desde su perfil de FidMe al elegir la opción Protección de Datos y presionando Extraer mis datos.</string>
<string name="importFidmeMessage">Seleccione su exportado de FidMe para importarlo, y a continuación seleccione manualmente los tipos de código de barras.\nCréelo desde su perfil de FidMe al elegir la opción Protección de Datos y presionando Extraer mis datos.</string>
<string name="updateBarcodeQuestionTitle">¿Actualizar el valor del código de barras\?</string>
<string name="settings_keep_screen_on_summary">Deshabilita el tiempo de espera de la pantalla mientras se ve una tarjeta</string>
<string name="thumbnailDescription">Miniatura</string>
@@ -204,17 +192,18 @@
<string name="settings_blue_theme">Azul</string>
<string name="app_contributors">Hecho posible por: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="barcodeLongPressMessage">Solo se puede abrir imágenes en la aplicación de galería</string>
<string name="settings_follow_sensor_orientation">Siempre rotar (ignora configuración del sistema)</string>
<string name="yes">Si</string>
<string name="no">No</string>
<string name="passwordRequired">Por favor ingresa la contraseña</string>
<string name="failedGeneratingShareURL">No se pudo generar URL compartible. Por favor reporte esto.</string>
<string name="passwordRequired">Ingresa la contraseña</string>
<string name="failedGeneratingShareURL">No se pudo generar URL compartible</string>
<string name="sort_by_name">Nombre</string>
<string name="sort_by">Ordenar por</string>
<string name="reverse">en orden inverso</string>
<string name="sort_by_most_recently_used">Más Recientemente Usado</string>
<string name="sort_by_most_recently_used">Más recientemente usado</string>
<string name="settings_use_volume_keys_navigation">Cambiar tarjetas usando los botones de volumen</string>
<string name="sort_by_valid_from">Válido Desde</string>
<string name="sort_by_expiry">Vencimiento</string>
<string name="settings_use_volume_keys_navigation_summary">Usá los botones de volumen para cambiar la tarjeta que se muestra</string>
<string name="cardWithNumberAndLocale">Tarjeta <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="cardWithNumber">Tarjeta <xliff:g>%d</xliff:g></string>
</resources>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_add">Añadir</string>
<string name="noGiftCards">Haz clic en el botón + para añadir una tarjeta, o importa desde el menú ⋮.</string>
<string name="noGiftCards">Haz clic en el botón + para añadir una tarjeta, o importa desde el menú ⋮</string>
<string name="storeName">Nombre</string>
<string name="note">Nota</string>
<string name="cardId">ID de tarjeta</string>
@@ -20,7 +20,7 @@
<string name="noCardExistsError">No se ha podido encontrar esa tarjeta</string>
<string name="importExport">Importar/Exportar</string>
<string name="exportName">Exportar</string>
<string name="importExportHelp">Respaldar tus datos permite trasladarlos a otro dispositivo.</string>
<string name="importExportHelp">Respaldar tus datos permite trasladarlos a otro dispositivo</string>
<string name="importSuccessfulTitle">Importado</string>
<string name="importFailedTitle">Falló la importación</string>
<string name="importFailed">No se ha podido realizar la importación</string>
@@ -30,7 +30,7 @@
<string name="importing">Importando…</string>
<string name="exporting">Exportando…</string>
<string name="importOptionFilesystemTitle">Importar desde el sistema de archivos</string>
<string name="importOptionFilesystemExplanation">Elegir un archivo concreto del sistema de archivos.</string>
<string name="importOptionFilesystemExplanation">Elegir un archivo concreto del sistema de archivos</string>
<string name="importOptionFilesystemButton">Desde el sistema de archivos</string>
<string name="about">Información</string>
<string name="app_license">Programa libre con «copyleft», disponible en virtud de la licencia GPLv3+</string>
@@ -47,13 +47,13 @@
<string name="settings_theme">Tema</string>
<string name="app_copyright_old">Basado en Loyalty Card Keychain
\nderechos de autor © 2016-2020 de Branden Archer</string>
<string name="exportOptionExplanation">Los datos se guardarán en la ubicación que elija.</string>
<string name="exportOptionExplanation">Los datos se guardarán en la ubicación que elija</string>
<string name="failedParsingImportUriError">No se pudo procesar el URI de importación</string>
<string name="share">Compartir</string>
<string name="barcodeType">Tipo de código de barras</string>
<string name="noMatchingGiftCards">Sin resultados. Intente cambiar su búsqueda.</string>
<string name="action_search">Buscar</string>
<string name="noGroups">Pulse en el botón + para añadir grupos de categorización.</string>
<string name="noGroups">Pulse en el botón + para añadir grupos de categorización</string>
<string name="starImage">Estrella favorita</string>
<string name="thumbnailDescription">Miniatura</string>
<string name="selectBarcodeTitle">Seleccionar el código de barras</string>
@@ -67,7 +67,7 @@
<string name="leaveWithoutSaveTitle">Salir</string>
<string name="moveDown">Bajar</string>
<string name="moveUp">Subir</string>
<string name="failedOpeningFileManager">Instale un gestor de archivos primero.</string>
<string name="failedOpeningFileManager">No se puedo abrir un gestor de archivos</string>
<string name="deleteConfirmationGroup">¿Quiere eliminar el grupo\?</string>
<string name="all">Todo</string>
<string name="star">Añadir a favoritos</string>
@@ -86,20 +86,14 @@
<string name="expiryStateSentenceExpired">Expirado: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentence">Expira: <xliff:g>%s</xliff:g></string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Derechos de autor © 2019-<xliff:g>%d</xliff:g> Sylvia van Os y colaboradores</string>
<string name="app_resources">Recursos de terceros libres: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Bibliotecas de terceros libres: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="importCatimaMessage">Seleccione su <i>catima.zip</i> exportado desde Catima para importarlo.
\nCréalo primero desde el menú Importar/Exportar de otra app de Catima al presionar Exportar desde allí.</string>
<string name="importFidmeMessage">Seleccione su <i>fidme-export-request-xxxxxx.zip</i> exportado desde FidMe para importar, y luego escoja los tipos de códigos de barras manualmente.
\nCréalo primero desde tu perfil de FidMe eligiendo Protección de datos y pulsa Extraer mis datos.</string>
<string name="importLoyaltyCardKeychainMessage">Seleccione su <i>LoyaltyCardKeychain.csv</i> exportado desde Loyalty Card Keychain para importarlo.
\nCréalo primero desde el menú Importar/Exportar en Loyalty Card Keychain pulsando Exportar desde allí.</string>
<string name="importStocardMessage">Seleccione su exportación <i>*.zip</i> de Stocard para importarla.
\nConsígalo enviando un correo electrónico a support@stocardapp.com solicitando una exportación de sus datos.</string>
<string name="importVoucherVaultMessage">Seleccione su <i>vouchervault.json</i> exportado desde Voucher Vault para importarlo.
\nCréalo pulsando primero Exportar en Voucher Vault.</string>
<string name="failedGeneratingShareURL">No se ha podido generar una URL compartible. Por favor, informe de ello.</string>
<string name="passwordRequired">Por favor, introduzca la contraseña</string>
<string name="app_resources">Recursos de terceros: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Bibliotecas de terceros: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="importCatimaMessage">Seleccione su exportado desde Catima para importarlo.\nCréalo primero desde el menú Importar/Exportar de otra app de Catima al presionar Exportar desde allí.</string>
<string name="importFidmeMessage">Seleccione su exportado desde FidMe para importar, y luego escoja los tipos de códigos de barras manualmente.\nCréalo desde tu perfil de FidMe eligiendo Protección de datos y pulsa Extraer mis datos.</string>
<string name="importLoyaltyCardKeychainMessage">Seleccione su exportado desde Loyalty Card Keychain para importarlo.\nCréalo desde el menú Importar/Exportar en Loyalty Card Keychain pulsando Exportar desde allí.</string>
<string name="importVoucherVaultMessage">Seleccione su exportado desde Voucher Vault para importarlo.\nCréalo pulsando Exportar en Voucher Vault.</string>
<string name="failedGeneratingShareURL">No se ha podido generar una URL compartible</string>
<string name="passwordRequired">Introduzca la contraseña</string>
<string name="updateBarcodeQuestionText">Ha cambiado el código. ¿Desea actualizar también el código de barras para usar el mismo valor\?</string>
<string name="intent_import_card_from_url_share_multiple_text">Quiero compartirte algunas tarjetas</string>
<string name="setBackImage">Establecer imagen anversa</string>
@@ -120,7 +114,6 @@
<string name="sameAsCardId">Igual que el código</string>
<string name="barcodeId">Valor de código de barra</string>
<string name="importVoucherVault">Importar desde Voucher Vault</string>
<string name="importStocard">Importar desde Stocard</string>
<string name="importLoyaltyCardKeychain">Importar desde Loyalty Card Keychain</string>
<string name="importFidme">Importar desde FidMe</string>
<string name="importCatima">Importar desde Catima</string>
@@ -166,20 +159,15 @@
<string name="settings_system_locale">Sistema</string>
<string name="settings_locale">Idioma</string>
<string name="noGroupCards">Este grupo está vacío</string>
<string name="settings_landscape_orientation">Horizontal</string>
<plurals name="balancePoints">
<item quantity="one"><xliff:g>%s</xliff:g> punto</item>
<item quantity="many"><xliff:g>%s</xliff:g> puntos</item>
<item quantity="other"><xliff:g>%s</xliff:g> puntos</item>
</plurals>
<string name="barcodeImageDescriptionWithType">Imagen <xliff:g>%s</xliff:g> código de barras</string>
<string name="settings_card_orientation">Orientación de pantalla</string>
<string name="settings_portrait_orientation">Formato vertical</string>
<string name="group_edit">Editar grupo</string>
<string name="group_updated">Grupo actualizado</string>
<string name="noGiftCardsGroup">Crea algunas tarjetas y luego asígnelas al grupo aquí.</string>
<string name="settings_follow_system_orientation">Segue el sistema</string>
<string name="settings_lock_on_opening_orientation">Bloqueo a la orientación utilizada al abrir la tarjeta</string>
<string name="noGiftCardsGroup">Crea algunas tarjetas y luego asígnelas al grupo aquí</string>
<string name="sort_by_most_recently_used">Lo más usado recientemente</string>
<string name="sort_by_expiry">Caducidad</string>
<string name="version_history">Historial de versiones</string>
@@ -218,7 +206,7 @@
<string name="archived">Tarjeta archivada</string>
<string name="unarchived">Tarjeta desarchivada</string>
<string name="exportPassword">Establezca una contraseña para proteger su exportación (opcional)</string>
<string name="failedLaunchingPhotoPicker">No se ha encontró una aplicación de galería compatible</string>
<string name="failedLaunchingPhotoPicker">No se encontró una aplicación de galería compatible</string>
<plurals name="groupCardCountWithArchived">
<item quantity="one"><xliff:g>%1$d</xliff:g> tarjeta (archivada)<xliff:g id="archivedCount">%2$d</xliff:g></item>
<item quantity="many"><xliff:g>%1$d</xliff:g> tarjetas (archivadas)<xliff:g id="archivedCount">%2$d</xliff:g></item>
@@ -244,7 +232,7 @@
<string name="height">Alto</string>
<string name="switchToFrontImage">Cambiar a imagen frontal</string>
<string name="openFrontImageInGalleryApp">Abrir imagen frontal en la aplicación de la galería</string>
<string name="openBackImageInGalleryApp">Abrir imagen trasera en la aplicación de la galería</string>
<string name="openBackImageInGalleryApp">Abrir imagen trasera en la aplicación de visor de imagen</string>
<string name="setBarcodeHeight">Ajustar la altura del código de barras</string>
<string name="donate">Donar</string>
<string name="switchToBarcode">Cambiar a código de barras</string>
@@ -278,10 +266,9 @@
<string name="addWithoutBarcode">Añadir una tarjeta sin código de barras</string>
<string name="field_must_not_be_empty">Este campo no debe estar vacío</string>
<string name="app_name">Catima</string>
<string name="settings_follow_sensor_orientation">Girar siempre (ignora la configuración del sistema)</string>
<string name="continue_">Continuar</string>
<string name="add_manually_warning_title">Se recomienda escanear</string>
<string name="add_manually_warning_message">En algunas tiendas, el valor del código de barras difiere del número escrito en la tarjeta. Por este motivo, es posible que la introducción manual del código de barras no siempre funcione. Se recomienda encarecidamente escanear el código de barras con la cámara. ¿Aún desea continuar?</string>
<string name="add_manually_warning_message">En algunas tarjetas, el valor del código de barras difiere del número escrito en la tarjeta. Por este motivo, introducir manualmente puede que no siempre funcione. Se recomienda analizar el código de barras con su cámara en su lugar. ¿Aún desea continuar?</string>
<string name="spend">Gastar</string>
<string name="receive">Recibió</string>
<string name="amountParsingFailed">Importe incorrecto</string>
@@ -308,12 +295,23 @@
<string name="settings_column_count_5">5</string>
<string name="settings_column_count_6">6</string>
<string name="settings_column_count_7">7</string>
<string name="generic_error_please_retry">Lo sentimos, algo salió mal, por favor inténtelo de nuevo...</string>
<string name="generic_error_please_retry">Algo salió mal</string>
<string name="unsupportedFile">Este archivo no es compatible</string>
<string name="addFromPkpass">Seleccione un archivo Passbook (.pkpass)</string>
<string name="addFromPkpass">Seleccione un archivo Passbook (.pkpass / .pkpasses)</string>
<string name="sort_by_valid_from">Válido desde</string>
<string name="setBarcodeWidth">Establecer el ancho del código de barras</string>
<string name="width">Ancho</string>
<string name="card_list_widget_name">Lista de tarjetas</string>
<string name="card_list_widget_empty">Después de añadir algunas tarjetas de fidelidad en Catima, aparecerán aquí. Si tienes tarjetas, asegúrate de que no estén archivadas.</string>
<string name="cardWithNumber">Tarjeta <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Tarjeta <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Por favor, no rote el dispositivo, ya que esto cancelará la acción</string>
<string name="acra_catima_has_crashed">Lo sentimos, pero <xliff:g id="app_name">%s</xliff:g> ha fallado. Por favor, ayúdenos a resolver esta incidencia enviándonos un reporte del error.</string>
<string name="acra_explain_crash">Si es posible, por favor añada más detalles sobre lo que estaba haciendo aquí:</string>
<string name="acra_crash_email_subject">Reporte del fallo <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Solicitar envío de reportes de fallos</string>
<string name="pref_enable_acra_summary">Cuando está activado, se le pedirá que informe sobre un fallo cuando ocurra. Los informes de fallo nunca se envían automáticamente.</string>
<string name="copy_value">Copia valor</string>
<string name="copied_to_clipboard">Copiado al portapapeles</string>
<string name="nothing_to_copy">Ningún valor encontrado</string>
</resources>

View File

@@ -5,7 +5,7 @@
<item quantity="one"><xliff:g>%d</xliff:g> valitud</item>
<item quantity="other"><xliff:g>%d</xliff:g> valitud</item>
</plurals>
<string name="noGiftCardsGroup">Lisa mõned kaardid ja siis jaga nad gruppidesse.</string>
<string name="noGiftCardsGroup">Lisa mõned kaardid ja siis jaga nad gruppidesse</string>
<string name="noMatchingGiftCards">Tulemusi pole. Palun proovi muuta otsingut.</string>
<string name="storeName">Nimi</string>
<string name="note">Märkus</string>
@@ -32,7 +32,7 @@
<string name="addCardTitle">Lisa kaart</string>
<string name="scanCardBarcode">Skaneeri triipkoodi</string>
<string name="app_name">Catima</string>
<string name="noGiftCards">Kaardi lisamiseks klõpsi + pluss nuppu või impordi ⋮ikooniga menüüst.</string>
<string name="noGiftCards">Kaardi lisamiseks klõpsi + pluss nuppu või impordi ⋮ikooniga menüüst</string>
<string name="action_search">Otsi</string>
<string name="unstar">Eemalda lemmikute hulgast</string>
<string name="cancel">Katkesta</string>
@@ -46,9 +46,6 @@
<string name="settings_dark_theme">Tume kujundus</string>
<string name="thumbnailDescription">Pisipilt</string>
<string name="settings_theme">Kujundus</string>
<string name="settings_card_orientation">Ekraanipaigutuse suund</string>
<string name="settings_follow_sensor_orientation">Alati pööra (eira süsteemset paigutust)</string>
<string name="settings_landscape_orientation">Rõhtvaade</string>
<string name="settings_display_barcode_max_brightness">Tee ekraan eredamaks</string>
<string name="app_license">Copyleft-tüüpi autoriõiguste alusel loodud avatud lähtekoodiga tarkvara, mis on avaldatud GPLv3+ all</string>
<string name="settings_keep_screen_on">Hoia ekraan sisselülitatuna</string>
@@ -56,10 +53,10 @@
<string name="noCardsMessage">Esmalt lisa kaart</string>
<string name="barcodeImageDescriptionWithType">Kaardi <xliff:g>%s</xliff:g> tiipkood</string>
<string name="noCardExistsError">Seda kaarti ei leidu</string>
<string name="failedParsingImportUriError">Impordi aadressi töötlemine ei õnnestunud</string>
<string name="failedParsingImportUriError">Importimise aadressi töötlemine ei õnnestunud</string>
<string name="importExport">Import/eksport</string>
<string name="exportName">Ekspordi</string>
<string name="importExportHelp">Andmete varundamine võimaldab sul neid tõsta mõnda teise seadmesse.</string>
<string name="importExportHelp">Andmete varundamine võimaldab sul neid tõsta mõnda teise seadmesse</string>
<string name="importSuccessfulTitle">Imporditud</string>
<string name="importFailedTitle">Import ei õnnestunud</string>
<string name="exportSuccessfulTitle">Eksporditud</string>
@@ -74,26 +71,23 @@
<string name="permissionReadCardsDescription">loe kõiki oma Catima kaarte koos nende üksikasjadega, sealhulgas märkuste ja piltidega</string>
<string name="cameraPermissionDeniedTitle">Puudub ligipääs kaamerale</string>
<string name="noCameraPermissionDirectToSystemSetting">Triipkoodide skaneerimiseks vajab Catima õigust asutada kaamerat. Õiguste andmiseks klõpsi siin.</string>
<string name="exportOptionExplanation">Andmed salvestame sinu valitud asukohta.</string>
<string name="exportOptionExplanation">Andmed salvestame sinu valitud asukohta</string>
<string name="importOptionFilesystemTitle">Impordi failisüsteemist</string>
<string name="importOptionFilesystemExplanation">Vali vajalik impordifail failisüsteemist.</string>
<string name="importOptionFilesystemExplanation">Vali vajalik impordifail failisüsteemist</string>
<string name="importOptionFilesystemButton">Vali failisüsteemist</string>
<string name="about">Rakenduse teave</string>
<string name="app_copyright_short">Autoriõigused © Sylvia van Os ja kaasautorid</string>
<string name="about_title_fmt">Teave <xliff:g id="app_name">%s</xliff:g> kohta</string>
<string name="debug_version_fmt">Versioon: <xliff:g id="version">%s</xliff:g></string>
<string name="app_libraries">Kolmandate osapoolte avatud lähtekoodiga teegid: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Kolmandate osapoolte avatud lähtekoodiga materjalid: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_libraries">Kolmandate osapoolte teegid: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Kolmandate osapoolte materjalid: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="starImage">Lemmikut märkiv täht</string>
<string name="settings">Seadistused</string>
<string name="settings_system_theme">Süsteemi kujundus</string>
<string name="settings_follow_system_orientation">Järgi süsteemset paigutust</string>
<string name="settings_portrait_orientation">Püstvaade</string>
<string name="settings_lock_on_opening_orientation">Kaardivaate avamisel lukusta paigutus</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="noGroups">Kui soovid sarnaseid kaarte omavahel liigitada siis + pluss nupuga lisa kaardigruppe.</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>
<plurals name="groupCardCountWithArchived">
@@ -117,10 +111,10 @@
<string name="expiryDate">Aegumise kuupäev</string>
<string name="never">Mitte kunagi</string>
<string name="showMoreInfo">Näita teavet</string>
<string name="importLoyaltyCardKeychainMessage">Importimiseks vali oma <i>LoyaltyCardKeychain.csv</i> Loyalty Card Keychaini ekspordifail. \nSellise faili saad teha rakendusest Loyalty Card Keychain valides menüüst Import/Eksport valiku Eksport.</string>
<string name="importLoyaltyCardKeychainMessage">Importimiseks vali Loyalty Card Keychaini ekspordifail. \nSellise faili saad teha rakendusest Loyalty Card Keychain valides menüüst Import/Eksport valiku Eksport.</string>
<string name="unsupportedBarcodeType">Sellist triipkoodi tüüpi pole veel võimalik kuvada, aga mõnes hilisemas rakenduse versioonis võib see võimalik olla.</string>
<string name="wrongValueForBarcodeType">Väärtus ei sobi selle triipkoodi tüübiga</string>
<string name="passwordRequired">Palun sisesta salasõna</string>
<string name="passwordRequired">Sisesta salasõna</string>
<string name="updateBarcodeQuestionTitle">Kas uuendame triipkoodi väärtust?</string>
<string name="yes">Jah</string>
<string name="no">Ei</string>
@@ -163,9 +157,9 @@
<string name="group_updated">Kaardigrupp on uuendatud</string>
<string name="deleteConfirmationGroup">Kas kustutame grupi?</string>
<string name="all">Kõik</string>
<string name="failedOpeningFileManager">Esmalt paigalda failihaldur.</string>
<string name="failedOpeningFileManager">Failihalduri avamine ei õnnestunud</string>
<string name="intent_import_card_from_url_share_text">Ma soovin sinuga jagada ühte oma kliendikaarti</string>
<string name="editGroup">Muudame gruppi: <xliff:g>%s</xliff:g></string>
<string name="editGroup">Grupp on muutmisel: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentence">Aegub: <xliff:g>%s</xliff:g></string>
<string name="moveBarcodeToTopOfScreen">Tõsta triipkood ekraani ülaossa</string>
<string name="noBarcodeFound">Ühtegi triipkoodi ei leidunud</string>
@@ -178,15 +172,13 @@
<string name="privacy_policy">Andmekaitsepõhimõtted</string>
<string name="accept">Nõustu</string>
<string name="importCatima">Impordi Catima varukoopiast</string>
<string name="importCatimaMessage">Importimiseks vali varem tehtud <i>catima.zip</i> Catima ekspordifail. \nSellise faili saad luua mõnes teises seadmes olevast Catima rakendusest Import/Eksport menüüst valikust Eksport.</string>
<string name="importCatimaMessage">Importimiseks vali varem tehtud Catima ekspordifail. \nSellise faili saad luua mõnes teises seadmes olevast Catima rakendusest Import/Eksport menüüst valikust Eksport.</string>
<string name="importFidme">Impordi FidMe varukoopiast</string>
<string name="importFidmeMessage">Importimiseks vali oma <i>fidme-export-request-xxxxxx.zip</i>, mille oled FidMe rakendusest eksportinud. Peale importi määra triipkoodi tüübid käsistsi. \nSellise faili loomiseks vali oma FidMe profiilist eelistuse Andmekaitse-Paki lahti.</string>
<string name="importFidmeMessage">Importimiseks vali fail, mille oled FidMe rakendusest eksportinud. Peale importi määra triipkoodi tüübid käsistsi. \nSellise faili loomiseks vali oma FidMe profiilist eelistuse Andmekaitse-Paki lahti.</string>
<string name="importLoyaltyCardKeychain">Impordi rakendusest Loyalty Card Keychain</string>
<string name="importStocard">Impordi Stocardist</string>
<string name="importStocardMessage">Importimiseks vali oma <i>***.zip</i> Stocardi ekspordifail. \nSellise faili saad saates kirja aadressile support@stocardapp.com ning küsides oma andmeid.</string>
<string name="chooseImportType">Importimise valikud</string>
<string name="importVoucherVault">Impordi rakendusest Voucher Vault</string>
<string name="importVoucherVaultMessage">Importimiseks vali oma <i>vouchervault.json</i> Voucher Vaulti ekspordifail. \nSellise faili saad teha rakenduses Voucher Vault menüüvalikust Eksport.</string>
<string name="importVoucherVaultMessage">Importimiseks vali oma Voucher Vaulti ekspordifail. \nSellise faili saad teha rakenduses Voucher Vault menüüvalikust Eksport.</string>
<string name="barcodeId">Triipkoodi väärtus</string>
<string name="sameAsCardId">Sama, kui ID</string>
<string name="setBarcodeId">Sisesta triipkoodi väärtus</string>
@@ -200,7 +192,7 @@
<string name="updateBarcodeQuestionText">Sa muutsid ID väärtust? Kas sa soovid ka triipkoodiväärtuse vastavalt uuendada?</string>
<string name="exportPassword">Sinu eksporditavate andmete turvamiseks palun sisesta salasõna (kui soovid seda)</string>
<string name="exportPasswordHint">Sisesta salasõna</string>
<string name="failedGeneratingShareURL">Jagatava võrguaadressi loomine ei õnnestunud. Palun anna sellest meile teada.</string>
<string name="failedGeneratingShareURL">Jagatava võrguaadressi loomine ei õnnestunud</string>
<string name="turn_flashlight_on">Lülita taskulamp sisse</string>
<string name="turn_flashlight_off">Lülita taskulamp välja</string>
<string name="settings_locale">Keel</string>
@@ -234,7 +226,7 @@
<string name="unarchive">Eemalda arhiivist</string>
<string name="archived">Kaart on arhiveeritud</string>
<string name="unarchived">Kaart on arhiivist eemaldatud</string>
<string name="failedLaunchingPhotoPicker">Ei õnnestunud leida toetatud galeriirakendust</string>
<string name="failedLaunchingPhotoPicker">Ei õnnestunud leida toetatud pildivalijat</string>
<string name="previousCard">Eelmine</string>
<string name="nextCard">Järgmine</string>
<string name="failedToOpenUrl">Esmalt paigalda veebibrauser</string>
@@ -247,8 +239,8 @@
<string name="switchToFrontImage">Vaata esikülje pilti</string>
<string name="switchToBackImage">Vaata tagakülje pilti</string>
<string name="switchToBarcode">Vaata triipkoodi</string>
<string name="openFrontImageInGalleryApp">Ava esikülje pilt galeriirakenduses</string>
<string name="openBackImageInGalleryApp">Ava tagakülje pilt galeriirakenduses</string>
<string name="openFrontImageInGalleryApp">Ava esikülje pilt pildivalijas</string>
<string name="openBackImageInGalleryApp">Ava tagakülje pilt pildivalijas</string>
<string name="setBarcodeHeight">Määra triipkoodi kõrgus</string>
<string name="donate">Toeta rahaliselt</string>
<string name="icon_header_click_text">Pisipildi muutmiseks vajuta pikalt</string>
@@ -271,7 +263,7 @@
<string name="field_must_not_be_empty">Väli ei tohi olla tühi</string>
<string name="manually_enter_barcode_instructions">Sisesta sinu kaardil kuvatav tunnusnumber või -tekst ja klõpsi triipkoodi, millelaadset kuvatakse kaardil.</string>
<string name="add_manually_warning_title">Soovitame, et skaneerid triipkoodi</string>
<string name="add_manually_warning_message">Mõnede poodide ja äride puhul triipkoodi väärtus erineb kaardile kirjutatud numbrist. Seetõttu ei pruugi triipkoodi käsitsi lisamine alati toimida. Me tungivalt soovitame, et pigem skaneerid triipkoodi kaameraga. Kas sa siiski soovid jätkata?</string>
<string name="add_manually_warning_message">Mõnede poodide ja äride puhul triipkoodi väärtus erineb kaardile kirjutatud numbrist. Seetõttu ei pruugi triipkoodi käsitsi lisamine alati toimida. Me soovitame, et pigem skaneerid triipkoodi kaameraga. Kas sa siiski soovid jätkata?</string>
<string name="continue_">Jätka</string>
<string name="spend">Kuluta</string>
<string name="receive">Võta vastu</string>
@@ -297,12 +289,23 @@
<string name="settings_column_count_6">6</string>
<string name="settings_column_count_4">4</string>
<string name="settings_column_count_7">7</string>
<string name="generic_error_please_retry">Vabandust, midagi läks nüüd viltu, palun proovi uuesti...</string>
<string name="generic_error_please_retry">Tekkis viga</string>
<string name="unsupportedFile">See fail pole toetatud</string>
<string name="addFromPkpass">Vali Passbooki fail (.pkpass)</string>
<string name="addFromPkpass">Vali Passbooki fail (.pkpass / .pkpasses)</string>
<string name="sort_by_valid_from">Kehtib alates</string>
<string name="setBarcodeWidth">Määratle triipkoodi laius</string>
<string name="width">Laius</string>
<string name="card_list_widget_name">Kaartide loend</string>
<string name="card_list_widget_empty">Kui lisad Catimasse kliendikaarte, siis saavad nad olema nähtavad siin. Kui sul on kaardid lisatud, siis palun kontrolli, et nad kõik poleks arhiveeritud.</string>
<string name="cardWithNumber">Kaart: <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Kaart: <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Palun ära pööra nutiseadet - see katkestab tegevuse</string>
<string name="acra_catima_has_crashed">Vabandus, aga <xliff:g id="app_name">%s</xliff:g> on kokku jooksnud. Kui saadad meile veakirjelduse, siis aitad seda viga parandada.</string>
<string name="acra_explain_crash">Kui vähegi võimalik, siis palun kirjelda, mida sa antud hetkel tegid:</string>
<string name="acra_crash_email_subject">Kokkujooksmise aruanne: <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Küsi luba kokkujooksmiste aruannete saatmiseks</string>
<string name="pref_enable_acra_summary">Kui eelistus on kasutusel, siis rakendus küsib sinult luba veateate saatmiseks. Seda ei tehta iialgi automaatselt.</string>
<string name="copy_value">Kopeeri väärtus</string>
<string name="copied_to_clipboard">Kopeeritud lõikelauale</string>
<string name="nothing_to_copy">Ühtegi väärtust ei leidu</string>
</resources>

View File

@@ -36,7 +36,7 @@
<string name="scanCardBarcode">اسکن بارکد</string>
<string name="cardShortcut">میان‌بر کارت</string>
<string name="noCardsMessage">ابتدا یک کارت بیافزایید</string>
<string name="noCardExistsError">کارت پیدا نشد</string>
<string name="noCardExistsError">آن کارت پیدا نشد</string>
<string name="importFailedTitle">ایمپورت ناموفق بود</string>
<string name="importFailed">نمیتوان ایمپورت کرد</string>
<string name="exportSuccessfulTitle">خروجی گرفته شده</string>
@@ -68,7 +68,7 @@
<string name="permissionReadCardsDescription">کارت های کاتیما و تمام جزئیاتشان از جمله یادداشت‌ها و عکس‌ها را بخوانید</string>
<string name="cameraPermissionDeniedTitle">نمیتوان به دوربین دسترسی پیدا کرد</string>
<string name="noCameraPermissionDirectToSystemSetting">برای اسکن بارکد ها، کاتیما نیاز دارد به دوربین شما دسترسی داشته باشد. اینجا بزنید تا تنظیمات دسترسی خود را تغییر دهید.</string>
<string name="importExport">ایمپورت/خروجی گرفتن</string>
<string name="importExport">واردات/صادرات</string>
<string name="settings_category_title_privacy">حریم شخصی</string>
<string name="settings_category_title_general">عمومی</string>
<string name="settings_category_title_cards">نمایش کارت</string>
@@ -90,9 +90,6 @@
<string name="settings_theme">تم</string>
<string name="settings_system_theme">سیستم</string>
<string name="settings_dark_theme">تیره</string>
<string name="settings_card_orientation">جهت صفحه نمایش</string>
<string name="settings_follow_sensor_orientation">همیشه قابل چرخش باشد (بدون در نظر گرفتن تنظیمات سیستم)</string>
<string name="settings_portrait_orientation">عمودی</string>
<string name="settings_keep_screen_on">روشن نگه داشتن صفحه نمایش</string>
<string name="settings_keep_screen_on_summary">غیرفعال سازی مهلت صفحه نمایش هنگام مشاهده کارت</string>
<string name="settings_disable_lockscreen_while_viewing_card">جلوگیری از قفل شدن صفحه</string>
@@ -100,12 +97,10 @@
<string name="settings_allow_content_provider_read_summary">برنامه ها باید برای گرفتن مجوز درخواست کنند</string>
<string name="importSuccessful">داده وارد شد</string>
<string name="thumbnailDescription">تصویر کوچک</string>
<string name="settings_landscape_orientation">افقی</string>
<string name="settings_light_theme">روشن</string>
<string name="settings_display_barcode_max_brightness_summary">برای کارکرد برخی اسکنر ها ضروری است</string>
<string name="settings_display_barcode_max_brightness">روشنایی صفحه</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">جلوگیری از قفل شدن صفحه هنگام مشاهده کارت</string>
<string name="settings_follow_system_orientation">پیروی از سیستم</string>
<string name="intent_import_card_from_url_share_text">میخواهم یک کارت را با تو به اشتراک بگذارم</string>
<string name="settings_use_volume_keys_navigation">جابجایی میان کارت ها با استفاده از کلید های صدا</string>
<string name="settings_use_volume_keys_navigation_summary">از کلید های صدا برای تغیر کارت نمایشی استفاده کنید</string>
@@ -120,11 +115,10 @@
<string name="noGroups">روی دکمه + اضافه کلیک کنید تا گروه‌هایی برای دسته‌بندی اضافه کنید.</string>
<string name="editGroup">ویرایش گروه: <xliff:g>%s</xliff:g></string>
<string name="importCatimaMessage">فایل <i>catima.zip</i> خروجی خود را از Catima برای وارد کردن انتخاب کنید.\nآن را از منوی وارد/صادر کردن در یک اپلیکیشن دیگر Catima با فشردن دکمه صادرکردن ابتدا ایجاد کنید.</string>
<string name="importStocard">واردات از Stocard</string>
<string name="unsupportedBarcodeType">این نوع بارکد هنوز نمی‌تواند نمایش داده شود. ممکن است در نسخه آینده برنامه پشتیبانی شود.</string>
<plurals name="balancePoints">
<item quantity="one"><xliff:g>%s</xliff:g> امتیاز</item>
<item quantity="other"><xliff:g>%s</xliff:g> امتیاز</item>
<item quantity="one"><xliff:g>%s</xliff:g> نقطه</item>
<item quantity="other"><xliff:g>%s</xliff:g> نقطه</item>
</plurals>
<string name="importFidmeMessage">فایل خروجی <i>fidme-export-request-xxxxxx.zip</i> خود را از FidMe برای وارد کردن انتخاب کنید، و سپس نوع بارکدها را به صورت دستی مشخص کنید.\nآن را از پروفایل FidMe خود با انتخاب گزینه حفاظت از داده و سپس فشار دادن گزینه استخراج داده من ابتدا ایجاد کنید.</string>
<string name="leaveWithoutSaveTitle">خروج</string>
@@ -136,7 +130,6 @@
<string name="importLoyaltyCardKeychain">وارد کردن از جاکلیدی کارت وفاداری</string>
<string name="importLoyaltyCardKeychainMessage">فایل خروجی <i>LoyaltyCardKeychain.csv</i> خود را از جاسوئیچی کارت وفاداری برای وارد کردن انتخاب کنید.\nآن را از منوی وارد/صادر‌کردن در جاسوئیچی کارت وفاداری با فشردن دکمه صادر‌کردن ابتدا ایجاد کنید.</string>
<string name="app_resources">منابع آزاد از طرف شخص ثالث: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="settings_lock_on_opening_orientation">قفل به جهت استفاده شده در هنگام بازکردن کارت</string>
<plurals name="groupCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> کارت</item>
<item quantity="other"><xliff:g>%d</xliff:g> کارت</item>
@@ -171,7 +164,6 @@
<string name="accept">قبول</string>
<string name="importCatima">واردات از کاتیما</string>
<string name="importFidme">واردات از FidMe</string>
<string name="importStocardMessage">فایل خروجی <i>***.zip</i> خود را از Stocard برای وارد کردن انتخاب کنید.\nاین فایل را با ارسال ایمیل به آدرس support@stocardapp.com و درخواست خروجی داده‌های خود دریافت کنید.</string>
<string name="importVoucherVault">واردات از صندوق کوپن</string>
<string name="importVoucherVaultMessage">فایل خروجی <i>vouchervault.json</i> خود را از صندوق کوپن برای وارد کردن انتخاب کنید.\nآن را ابتدا با فشردن دکمه صادرکردن در صندوق کوپن ایجاد کنید.</string>
<string name="barcodeId">مقدار بارکد</string>
@@ -302,4 +294,5 @@
<string name="spend">خرج کردن</string>
<string name="addFromPkpass">یک فایل دفترچه حساب (.pkpass) انتخاب کنید</string>
<string name="noCameraFoundGuideText">به نظر نمی‌رسد دستگاه شما دوربین داشته باشد. اگر دارد، دستگاه را مجدداً راه‌اندازی کنید. در غیر این صورت، از دکمه گزینه‌های بیشتر در زیر برای افزودن بارکد به روش دیگری استفاده کنید.</string>
<string name="card_list_widget_empty">بعد از اینکه چند کارت وفاداری در کاتیما اضافه کردید، آنها اینجا ظاهر می‌شوند. اگر کارت دارید، مطمئن شوید که همه آنها بایگانی نشده‌اند.</string>
</resources>

View File

@@ -142,9 +142,6 @@
<item quantity="one"><xliff:g>%d</xliff:g> valittu</item>
<item quantity="other"><xliff:g>%d</xliff:g> valitut</item>
</plurals>
<string name="importStocard">Tuo Stocardista</string>
<string name="importStocardMessage">Valitse tuotava <i>***.zip</i>-vienti Stocardista.
\nHanki se lähettämällä sähköpostia osoitteeseen support@stocardapp.com ja pyytämällä tietojesi vientiä.</string>
<string name="passwordRequired">Ole hyvä ja syötä salasana</string>
<string name="failedGeneratingShareURL">Jaettavaa URL-osoitetta ei voitu luoda. Ilmoita tästä.</string>
<string name="turn_flashlight_on">Käytä taskulamppua</string>
@@ -200,14 +197,10 @@
<string name="rate_this_app">Arvostele tämä sovellus</string>
<string name="noGiftCardsGroup">Lisää kortteja ja lisää ne ryhmään täällä.</string>
<string name="barcodeImageDescriptionWithType">Kuva <xliff:g>%s</xliff:g> viivakoodi</string>
<string name="settings_follow_system_orientation">Seuraa järjestelmää</string>
<string name="settings_portrait_orientation">Pysty</string>
<string name="settings_landscape_orientation">Vaaka</string>
<string name="unarchived">Kortti on poistettu arkistosta</string>
<string name="unarchive">Poista arkistosta</string>
<string name="archived">Kortti arkistoitu</string>
<string name="failedLaunchingPhotoPicker">Tuettua galleriasovellusta ei löytynyt</string>
<string name="settings_card_orientation">Näytön suunta</string>
<plurals name="groupCardCountWithArchived">
<item quantity="one"><xliff:g>%1$d</xliff:g> kortti (<xliff:g id="archivedCount">%2$d</xliff:g> arkistoitu)</item>
<item quantity="other"><xliff:g>%1$d</xliff:g> korttia (<xliff:g id="archivedCount">%2$d</xliff:g> arkistoitu)</item>
@@ -226,7 +219,6 @@
<string name="currentBalanceSentence">Nykyinen saldo: <xliff:g>%s</xliff:g></string>
<string name="newBalanceSentence">Uusi saldo: <xliff:g>%s</xliff:g></string>
<string name="cameraPermissionDeniedTitle">Ei pääsyä kameraan</string>
<string name="settings_lock_on_opening_orientation">Lukitse suunta, kun korttia avataan</string>
<string name="noCameraPermissionDirectToSystemSetting">Viivakoodien lukeminen vaatii, että Catimalla on käyttöoikeus kameraan. Napauta tästä muuttaaksesi oikeuksia.</string>
<string name="updateBalance">Päivitä saldo</string>
<string name="cameraPermissionRequired">Tämä toiminto vaatii oikeuden käyttää kameraa…</string>
@@ -272,7 +264,6 @@
<string name="app_name">Catima</string>
<string name="balanceParsingFailed">Virheellinen saldo</string>
<string name="view_online">Näytä verkossa</string>
<string name="settings_follow_sensor_orientation">Kierrä aina (ohittaa järjestelmän asetukset)</string>
<string name="continue_">Jatka</string>
<string name="add_manually_warning_title">Skannausta suositellaan</string>
<string name="spend">Kuluta</string>

View File

@@ -15,4 +15,16 @@
<item quantity="one"><xliff:g>%d</xliff:g> napili</item>
<item quantity="other"><xliff:g>%d</xliff:g> ang napili</item>
</plurals>
<string name="star">Sa card viewing, ang text ay naka-display lamang tuwing naka-long press ang star icon</string>
<string name="cancel">I-kansela</string>
<string name="save">I-save</string>
<string name="edit">I-edit</string>
<string name="delete">I-delete</string>
<string name="confirm">I-confirm</string>
<string name="share">I-share</string>
<string name="sendLabel">I-send…</string>
<string name="editCardTitle">I-edit ang card</string>
<string name="noCardsMessage">Mag-add ng card muna</string>
<string name="noCardExistsError">Hindi mahanap ang card</string>
<string name="exportName">I-export</string>
</resources>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_add">Ajouter</string>
<string name="noGiftCards">Cliquez sur le bouton + plus pour ajouter une carte, ou importez les depuis le menu ⋮.</string>
<string name="noGiftCards">Cliquez sur le bouton + plus pour ajouter une carte, ou importez les depuis le menu ⋮</string>
<string name="storeName">Nom</string>
<string name="note">Notes</string>
<string name="cardId">Numéro</string>
@@ -18,9 +18,9 @@
<string name="cardShortcut">Raccourci de carte</string>
<string name="noCardsMessage">Ajoutez dabord une carte</string>
<string name="noCardExistsError">Impossible de trouver cette carte</string>
<string name="importExport">Importer/Exporter</string>
<string name="importExport">Importer/exporter</string>
<string name="exportName">Exporter</string>
<string name="importExportHelp">La sauvegarde de vos données permet de les déplacer sur un autre appareil.</string>
<string name="importExportHelp">La sauvegarde de vos données permet de les déplacer sur un autre appareil</string>
<string name="importSuccessfulTitle">Importé</string>
<string name="importFailedTitle">Échec de limport</string>
<string name="importFailed">Impossible deffectuer limportation</string>
@@ -30,7 +30,7 @@
<string name="importing">Import …</string>
<string name="exporting">Export …</string>
<string name="importOptionFilesystemTitle">Importer depuis le système de fichiers</string>
<string name="importOptionFilesystemExplanation">Choisissez le fichier à importer.</string>
<string name="importOptionFilesystemExplanation">Choisissez le fichier à importer depuis le système de fichiers</string>
<string name="importOptionFilesystemButton">Système de fichiers</string>
<string name="about">À propos</string>
<string name="app_license">Logiciel libre à copyleft, sous licence GPLv3+</string>
@@ -49,7 +49,7 @@
<string name="settings_theme">Thème</string>
<string name="app_copyright_old">Basé sur Loyalty Card Keychain
\ncopyright © 2016-2020 Branden Archer</string>
<string name="exportOptionExplanation">Les données seront exportées vers lemplacement de votre choix.</string>
<string name="exportOptionExplanation">Les données seront exportées vers lemplacement de votre choix</string>
<string name="failedParsingImportUriError">Impossible danalyser lURI dimportation</string>
<string name="share">Partager</string>
<string name="barcodeType">Type de code-barres</string>
@@ -60,13 +60,13 @@
<string name="starImage">Étoile favorite</string>
<string name="deleteConfirmationGroup">Supprimer le groupe \?</string>
<string name="all">Tous</string>
<string name="noGroups">Cliquez sur le bouton + pour ajouter des groupes à catégoriser.</string>
<string name="noGroups">Cliquez sur le bouton + pour ajouter des groupes à catégoriser</string>
<string name="groups">Groupes</string>
<string name="enter_group_name">Entrez le nom du groupe</string>
<string name="noBarcode">Aucun code-barres</string>
<string name="leaveWithoutSaveConfirmation">Quitter sans enregistrer \?</string>
<string name="leaveWithoutSaveTitle">Quitter</string>
<string name="failedOpeningFileManager">Installez dabord un gestionnaire de fichiers.</string>
<string name="failedOpeningFileManager">Échec de l\'ouverture du gestionnaire de fichiers</string>
<string name="addManually">Entrez le code-barres manuellement</string>
<string name="moveDown">Descendre</string>
<string name="moveUp">Monter</string>
@@ -94,17 +94,13 @@
<string name="expiryStateSentence">Expire le: <xliff:g>%s</xliff:g></string>
<string name="settings_disable_lockscreen_while_viewing_card">Empêcher le verrouillage de lécran</string>
<string name="settings_keep_screen_on">Garder lécran allumé</string>
<string name="importVoucherVaultMessage">Sélectionnez votre exportation <i>vouchervault.json</i> de Voucher Vault à importer.
\nCréez-la en appuyant dabord sur Exporter dans Voucher Vault.</string>
<string name="importVoucherVaultMessage">Sélectionnez votre exportation de Voucher Vault à importer. \nCréez-la en appuyant sur Exporter dans Voucher Vault.</string>
<string name="importVoucherVault">Importer depuis Voucher Vault</string>
<string name="importLoyaltyCardKeychainMessage">Sélectionnez votre exportation <i>LoyaltyCardKeychain.csv</i> à partir de Loyalty Card Keychain pour limporter.
\nCréez-la à partir du menu Importer/Exporter du Loyalty Card Keychain en appuyant dabord sur Exporter.</string>
<string name="importLoyaltyCardKeychainMessage">Sélectionnez votre exportation à partir de Loyalty Card Keychain à importer.\nCréez-la à partir du menu Importer/Exporter du Loyalty Card Keychain en appuyant sur Exporter.</string>
<string name="importLoyaltyCardKeychain">Importer depuis Loyalty Card Keychain</string>
<string name="importFidmeMessage">Sélectionnez votre exportation <i>fidme-export-request-xxxxxx.zip</i> de FidMe pour limporter, puis sélectionnez manuellement les types de codes-barres.
\nCréez-la à partir de votre profil FidMe en choisissant Protection des données, puis en cliquant sur Extraire mes données dabord.</string>
<string name="importFidmeMessage">Sélectionnez votre exportation de FidMe pour limporter, puis sélectionnez manuellement les types de codes-barres. \nCréez-la à partir de votre profil FidMe en choisissant Protection des données, puis en cliquant sur Extraire mes données.</string>
<string name="importFidme">Importer depuis FidMe</string>
<string name="importCatimaMessage">Sélectionnez votre exportation <i>catima.zip</i> depuis Catima à importer.
\nCréez-la à partir du menu Importer/Exporter dune autre application Catima en appuyant dabord sur Exporter.</string>
<string name="importCatimaMessage">Sélectionnez votre exportation <i>catima.zip</i> depuis Catima à importer.\nCréez-la à partir du menu Importer/Exporter dune autre application Catima en appuyant sur Exporter.</string>
<string name="importCatima">Importer depuis Catima</string>
<string name="addFromImage">Sélectionner dans la galerie</string>
<string name="errorReadingImage">Impossible de lire limage</string>
@@ -114,8 +110,8 @@
<string name="barcodeId">Valeur du code-barres</string>
<string name="unsupportedBarcodeType">Ce type de code-barres ne peut pas encore être affiché. Il sera peut-être pris en charge dans une version ultérieure de lapplication.</string>
<string name="wrongValueForBarcodeType">La valeur nest pas valide pour le type de code-barres sélectionné</string>
<string name="app_resources">Ressources tierces libres : <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Bibliothèques tierces libres : <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Ressources tierces : <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Bibliothèques tierces : <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="intent_import_card_from_url_share_multiple_text">Je veux partager des cartes avec vous</string>
<string name="updateBarcodeQuestionText">Vous avez changé lidentifiant. Voulez-vous également mettre à jour le code-barres pour utiliser la même valeur \?</string>
<string name="no">Non</string>
@@ -128,13 +124,10 @@
<string name="photos">Photos</string>
<string name="backImageDescription">Image du verso</string>
<string name="frontImageDescription">Image du recto</string>
<string name="passwordRequired">Veuillez entrer le mot de passe</string>
<string name="importStocardMessage">Sélectionnez votre exportation <i>***.zip</i> de Stocard pour limporter.
\nVous pouvez lobtenir en envoyant un courriel à support@stocardapp.com pour demander une exportation de vos données.</string>
<string name="importStocard">Importer depuis Stocard</string>
<string name="passwordRequired">Renseigner le mot de passe</string>
<string name="turn_flashlight_off">Éteindre la lampe de poche</string>
<string name="turn_flashlight_on">Allumer la lampe de poche</string>
<string name="failedGeneratingShareURL">Impossible de générer une URL partageable. Veuillez signaler ceci.</string>
<string name="failedGeneratingShareURL">Impossible de générer une URL partageable</string>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> sélectionnée</item>
<item quantity="many"><xliff:g>%d</xliff:g> sélectionnées</item>
@@ -185,7 +178,7 @@
<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="noGiftCardsGroup">Créez des cartes, puis affectez-les au groupe ici.</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>
<string name="group_updated">Groupe mis à jour</string>
@@ -204,17 +197,12 @@
</plurals>
<string name="settings_oled_dark">Fond noir pour le thème sombre</string>
<string name="include_if_asking_support">Si vous voulez demander de l\'aide, incluez les informations suivantes :</string>
<string name="settings_card_orientation">Orientation de l\'écran</string>
<string name="settings_follow_system_orientation">Suivre le système</string>
<string name="settings_portrait_orientation">Portrait</string>
<string name="settings_landscape_orientation">Paysage</string>
<string name="settings_lock_on_opening_orientation">Garder l\'orientation utilisée pour ouvrir la carte</string>
<string name="duplicateCard">Dupliquer</string>
<string name="archive">Archiver</string>
<string name="unarchive">Désarchiver</string>
<string name="archived">Carte archivée</string>
<string name="unarchived">Carte désarchivée</string>
<string name="failedLaunchingPhotoPicker">Impossible de trouver une application de galerie prise en charge</string>
<string name="failedLaunchingPhotoPicker">Impossible de trouver une application de selection d\'image prise en charge</string>
<plurals name="groupCardCountWithArchived">
<item quantity="one"><xliff:g>%1$d</xliff:g> carte (<xliff:g id="archivedCount">%2$d</xliff:g> archivée)</item>
<item quantity="many"><xliff:g>%1$d</xliff:g> cartes (<xliff:g id="archivedCount">%2$d</xliff:g> archivées)</item>
@@ -244,8 +232,8 @@
<string name="switchToFrontImage">Passer à l\'image avant</string>
<string name="switchToBackImage">Passer à l\'image arrière</string>
<string name="switchToBarcode">Passer au code barre</string>
<string name="openFrontImageInGalleryApp">Ouvrir l\'image avant dans l\'application galerie</string>
<string name="openBackImageInGalleryApp">Ouvrir l\'image arrière dans l\'application galerie</string>
<string name="openFrontImageInGalleryApp">Ouvrir l\'image du recto dans l\'application de visualisation des images</string>
<string name="openBackImageInGalleryApp">Ouvrir l\'image du verso dans l\'application de visualisation des images</string>
<string name="setBarcodeHeight">Définir la hauteur du code-barres</string>
<string name="donate">Faire un don</string>
<string name="icon_header_click_text">Appuyez longuement pour modifier la vignette</string>
@@ -278,10 +266,9 @@
<string name="addWithoutBarcode">Ajouter une carte sans code-barres</string>
<string name="field_must_not_be_empty">Le champ ne peut pas être vide</string>
<string name="app_name">Catima</string>
<string name="settings_follow_sensor_orientation">Toujours pivoter (ignore les paramètres du système)</string>
<string name="add_manually_warning_title">Scan recommandé</string>
<string name="continue_">Continuer</string>
<string name="add_manually_warning_message">Pour certains magasins, la valeur du code-barres diffère du numéro inscrit sur la carte. Pour cette raison, la saisie manuelle dun code-barres peut ne pas toujours fonctionner. Il est fortement recommandé de scanner le code-barres avec votre appareil photo. Voulez-vous toujours continuer?</string>
<string name="add_manually_warning_message">Pour certaines cartes, la valeur du code-barres diffère du numéro inscrit sur la carte. Pour cette raison, la saisie manuelle dun code-barres peut ne pas toujours fonctionner. Il est recommandé de scanner le code-barres avec votre appareil photo. Voulez-vous toujours continuer?</string>
<string name="spend">Dépenser</string>
<string name="receive">Reçevoir</string>
<string name="amountParsingFailed">Montant Invalide</string>
@@ -308,12 +295,23 @@
<string name="settings_automatic_column_count">Automatique</string>
<string name="settings_column_count_6">6</string>
<string name="settings_column_count_7">7</string>
<string name="addFromPkpass">Sélectionner un fichier Cartes / Passbook (.pkpass)</string>
<string name="addFromPkpass">Sélectionner un fichier Cartes / Passbook (.pkpass / .pkpasses)</string>
<string name="unsupportedFile">Ce fichier n\'est pas supporté</string>
<string name="generic_error_please_retry">Désolé, un problème est survenu, veuillez réessayer...</string>
<string name="generic_error_please_retry">Une erreur est survenue</string>
<string name="sort_by_valid_from">Valide à partir du</string>
<string name="width">Largeur</string>
<string name="setBarcodeWidth">Définir la largeur du code-barres</string>
<string name="card_list_widget_name">Liste des cartes</string>
<string name="card_list_widget_empty">Après avoir ajouter des cartes de fidélité dans Catima, elles apparaîtront ici. Si vous avez des cartes, assurez-vous qu\'elles ne soient pas archivées.</string>
<string name="cardWithNumber">Carte <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Carte <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Merci de ne pas tourner l\'écran, car cela annulera l\'action</string>
<string name="acra_catima_has_crashed">Nous sommes désolé, <xliff:g id="app_name">%s</xliff:g> a planté. Merci de nous aider à corriger ce souci en nous envoyant un rapport d\'erreur.</string>
<string name="acra_explain_crash">Si possible, merci d\'ajouter plus de détails sur ce que vous étiez en train de faire :</string>
<string name="acra_crash_email_subject">Rapport de plantage de <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Demander pour envoyer des rapports de plantage</string>
<string name="pref_enable_acra_summary">Quand activé, il vous sera demandé d\'envoyer un rapport de plantage en cas de plantage. Les rapports de plantage ne sont jamais envoyés automatiquement.</string>
<string name="copy_value">Copier la valeur</string>
<string name="copied_to_clipboard">Copié dans le presse-papier</string>
<string name="nothing_to_copy">Aucune valeur trouvée</string>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -2,8 +2,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">Catima</string>
<string name="action_add">Engadir</string>
<string name="noGiftCards">Preme no botón + para engadir unha tarxeta ou desde o menú ⋮.</string>
<string name="noGiftCardsGroup">Crear tarxetas e despois engádeas aquí ao grupo.</string>
<string name="noGiftCards">Preme no botón + para engadir unha tarxeta ou desde o menú ⋮</string>
<string name="noGiftCardsGroup">Crea tarxetas e despois engádeas aquí ao grupo</string>
<string name="noMatchingGiftCards">Sen resultados. Intenta cambiar a busca.</string>
<string name="storeName">Nome</string>
<string name="barcodeType">Tipo de código de barras</string>
@@ -22,16 +22,16 @@
<string name="ok">OK</string>
<string name="share">Compartir</string>
<string name="sendLabel">Enviar…</string>
<string name="editCardTitle">Editar Tarxeta</string>
<string name="addCardTitle">Engadir Tarxeta</string>
<string name="scanCardBarcode">Escanear Código de Barras</string>
<string name="cardShortcut">Atallo á Tarxeta</string>
<string name="editCardTitle">Editar tarxeta</string>
<string name="addCardTitle">Engadir tarxeta</string>
<string name="scanCardBarcode">Escanear código de barras</string>
<string name="cardShortcut">Atallo á tarxeta</string>
<string name="noCardsMessage">Primeiro engade a tarxeta</string>
<string name="barcodeImageDescriptionWithType">Imaxe do código de barras de <xliff:g>%s</xliff:g></string>
<string name="failedParsingImportUriError">Non se puido procesar o URI de importación</string>
<string name="importExport">Importar/Exportar</string>
<string name="exportName">Exportar</string>
<string name="importExportHelp">Facendo copia de apoio dos datos podes movelos a outro dispositivo.</string>
<string name="importExportHelp">Ao facer copia de apoio dos datos podes movelos a outro dispositivo</string>
<string name="importSuccessfulTitle">Importados</string>
<string name="importFailedTitle">Fallou a importación</string>
<string name="exportSuccessfulTitle">Exportado</string>
@@ -42,11 +42,11 @@
<string name="exporting">Exportando…</string>
<string name="storageReadPermissionRequired">O preciso o permiso de lectura para realizar esta acción…</string>
<string name="cameraPermissionRequired">O preciso o acceso á cámara para realizar esta acción…</string>
<string name="permissionReadCardsLabel">Ler Tarxetas Catima</string>
<string name="permissionReadCardsLabel">Ler tarxetas Catima</string>
<string name="permissionReadCardsDescription">ler as tarxetas Catima e os seus detalles, incluíndo notas e imaxes</string>
<string name="cameraPermissionDeniedTitle">Non puido acceder á cámara</string>
<string name="noCameraPermissionDirectToSystemSetting">Catima precisa acceso á cámara para escanear códigos de barras. Toca aquí para cambiar os axustes do permiso.</string>
<string name="exportOptionExplanation">Os datos vanse escribir na localización que elixas.</string>
<string name="exportOptionExplanation">Os datos vanse escribir na localización que elixas</string>
<string name="importOptionFilesystemTitle">Importar desde o sistema de ficheiros</string>
<string name="about">Sobre</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Copyright © 2019<xliff:g>%d</xliff:g> Sylvia van Os e colaboradoras</string>
@@ -55,15 +55,11 @@
<string name="app_license">Software Libre con Copyleft, licenza GPLv3+</string>
<string name="about_title_fmt">Sobre <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versión: <xliff:g id="version">%s</xliff:g></string>
<string name="app_libraries">Bibliotecas libres alleas: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Recursos alleos libres: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Elixir Código de barras</string>
<string name="app_libraries">Bibliotecas alleas: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Recursos alleos: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Elixir código de barras</string>
<string name="settings">Axustes</string>
<string name="settings_theme">Decorado</string>
<string name="settings_follow_sensor_orientation">Rotar sempre (ignora o axuste do sistema)</string>
<string name="settings_portrait_orientation">Retrato</string>
<string name="settings_landscape_orientation">Paisaxe</string>
<string name="settings_lock_on_opening_orientation">Fixar a orientación ao abrir a tarxeta</string>
<string name="settings_display_barcode_max_brightness">Brillo da pantalla</string>
<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>
@@ -78,7 +74,7 @@
<string name="enter_group_name">Escribe o nome do grupo</string>
<string name="groups">Grupos</string>
<string name="group_edit">Editar Grupo</string>
<string name="noGroups">Preme no botón + para engadir grupos por categorías.</string>
<string name="noGroups">Preme no botón + para engadir grupos por categorías</string>
<string name="noGroupCards">O grupo está baleiro</string>
<plurals name="groupCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> tarxeta</item>
@@ -93,7 +89,7 @@
<string name="addManually">Escribir manualmente o código</string>
<string name="addFromImage">Elixe unha imaxe desde a galería</string>
<string name="groupsList">Grupos: <xliff:g>%s</xliff:g></string>
<string name="editGroup">Editando o Grupo: <xliff:g>%s</xliff:g></string>
<string name="editGroup">Editando o grupo: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentence">Caducidade: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentenceExpired">Caducou: <xliff:g>%s</xliff:g></string>
<string name="balanceSentence">Saldo: <xliff:g>%s</xliff:g></string>
@@ -115,18 +111,16 @@
<string name="balanceParsingFailed">Saldo non válido</string>
<string name="chooseImportType">Importar datos desde</string>
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
<string name="privacy_policy">Política de Privacidade</string>
<string name="privacy_policy">Directiva de privacidade</string>
<string name="accept">Aceptar</string>
<string name="importCatima">Importar desde Catima</string>
<string name="importCatimaMessage">Selecciona o ficheiro de exportación <i>catima.zip</i> para importalo.\nCréao no menú Importar/Exportar noutra app Catima premendo en Exportar.</string>
<string name="importCatimaMessage">Selecciona o ficheiro de exportación para importalo.\nCréao no menú Importar/Exportar noutra app Catima premendo en Exportar.</string>
<string name="importFidme">Importar desde FidMe</string>
<string name="importFidmeMessage">Elixe o ficheiro exportado <i>fidme-export-request-xxxxxx.zip</i> desde FidMe, e despois elixe manualmente o tipo de código de barras.\nCréao no teu perfil FidMe en Protección de Datos e despois premendo en Extraer os meus datos.</string>
<string name="importFidmeMessage">Elixe o ficheiro exportado desde FidMe, e despois elixe manualmente o tipo de código de barras.\nCréao no teu perfil FidMe en Protección de Datos e despois premendo en Extraer os meus datos.</string>
<string name="importLoyaltyCardKeychain">Importar desde Loyalty Card Keychain</string>
<string name="importLoyaltyCardKeychainMessage">Elixe o ficheiro de exportación <i>LoyaltyCardKeychain.csv</i> desde Loyalty Card Keychain \nCréao no menú Importar/Exportar en Loyalty Card Keychain premendo en Exportar.</string>
<string name="importStocard">Importar desde Stocard</string>
<string name="importStocardMessage">Elixe o ficheiro <i>***.zip</i> desde Stocard. \nPodes obtelo escribindo un correo a support@stocardapp.com e pedindo a exportación dos datos.</string>
<string name="importLoyaltyCardKeychainMessage">Elixe o ficheiro de exportación desde Loyalty Card Keychain \nCréao no menú Importar/Exportar en Loyalty Card Keychain premendo en Exportar.</string>
<string name="importVoucherVault">Importar desde Voucher Vault</string>
<string name="importVoucherVaultMessage">Selecciona o ficheiro de exportación <i>vouchervault.json</i> desde Voucher Vault. \nCréao premendo en Exportar en Voucher Vault.</string>
<string name="importVoucherVaultMessage">Selecciona o ficheiro de exportación desde Voucher Vault.\nCréao premendo en Exportar en Voucher Vault.</string>
<string name="barcodeId">Valor do código de barras</string>
<string name="sameAsCardId">Igual que o ID</string>
<string name="setBarcodeId">Establecer valor do código de barras</string>
@@ -144,7 +138,7 @@
<string name="updateBarcodeQuestionText">Cambiaches o ID. Queres cambiar tamén o código de barras para que use o mesmo valor?</string>
<string name="yes">Si</string>
<string name="no">Non</string>
<string name="failedGeneratingShareURL">Non se puido crear un URL para compartir. Informa sobre o fallo.</string>
<string name="failedGeneratingShareURL">Non se puido crear un URL para compartir</string>
<string name="turn_flashlight_on">Acender o flash</string>
<string name="turn_flashlight_off">Apagar o flash</string>
<string name="settings_oled_dark">Fondo en negro puro para o decorado escuro</string>
@@ -208,8 +202,8 @@
<string name="height">Altura</string>
<string name="switchToFrontImage">Cambiar á imaxe frontal</string>
<string name="switchToBackImage">Cambiar á imaxe posterior</string>
<string name="openFrontImageInGalleryApp">Abrir imaxe frontal na app de galería</string>
<string name="openBackImageInGalleryApp">Abrir imaxe posterior na app de galería</string>
<string name="openFrontImageInGalleryApp">Abrir imaxe frontal no visor de imaxes</string>
<string name="openBackImageInGalleryApp">Abrir imaxe posterior no visor de imaxes</string>
<string name="setBarcodeHeight">Establecer altura do código de barras</string>
<string name="icon_header_click_text">Pulsación longa para editar a miniatura</string>
<string name="show_name_below_image_thumbnail">Mostrar nome debaixo da miniatura</string>
@@ -234,7 +228,7 @@
<string name="field_must_not_be_empty">O campo non pode quedar baleiro</string>
<string name="manually_enter_barcode_instructions">Escribe o número ID ou texto na túa tarxeta e preme no código de barras que se pareza ao da túa tarxeta.</string>
<string name="add_manually_warning_title">Recomendamos escanear</string>
<string name="add_manually_warning_message">Nalgunhas tendas o código de barras é diferente ao número escrito na tarxeta. Debido a isto escribir o código de barras manualmente non sempre funciona. Recomendamos que escanees o código de barras coa cámara. Queres continuar igualmente?</string>
<string name="add_manually_warning_message">Nalgunhas tarxetas o código de barras é diferente ao número escrito na tarxeta. Debido a isto escribir o código de barras manualmente non sempre funciona. Recomendamos que escanees o código de barras coa cámara. Queres continuar igualmente?</string>
<string name="continue_">Continuar</string>
<string name="spend">Gastar</string>
<string name="receive">Recibir</string>
@@ -263,26 +257,24 @@
<string name="noCardExistsError">Non atopamos esa tarxeta</string>
<string name="settings_light_theme">Claro</string>
<string name="importOptionFilesystemButton">Desde sistema de ficheiros</string>
<string name="importOptionFilesystemExplanation">Elixe un ficheiro concreto no sistema de ficheiros.</string>
<string name="importOptionFilesystemExplanation">Elixe un ficheiro concreto no sistema de ficheiros</string>
<string name="thumbnailDescription">Miniatura</string>
<string name="starImage">Estrela de favorita</string>
<string name="settings_system_theme">Sistema</string>
<string name="settings_follow_system_orientation">Seguir ao sistema</string>
<string name="settings_display_barcode_max_brightness_summary">Preciso para que algúns escáneres funcionen</string>
<string name="settings_keep_screen_on">Manter pantalla acendida</string>
<string name="settings_dark_theme">Escuro</string>
<string name="settings_card_orientation">Orientación da pantalla</string>
<string name="group_updated">Grupo actualizado</string>
<string name="all">Todo</string>
<string name="deleteConfirmationGroup">Eliminar grupo?</string>
<string name="failedOpeningFileManager">Primeiro instala un xestor de ficheiros.</string>
<string name="failedOpeningFileManager">Non puido abrir un xestor de ficheiros</string>
<string name="settings_locale">Idioma</string>
<string name="settings_blue_theme">Azul</string>
<string name="passwordRequired">Escribe o contrasinal</string>
<string name="exportPassword">Establece un contrasinal para protexer a exportación (optativo)</string>
<string name="exportPasswordHint">Escribe o contrasinal</string>
<string name="setIcon">Establecer miniatura</string>
<string name="failedLaunchingPhotoPicker">Non se atopa unha app de galería compatible</string>
<string name="failedLaunchingPhotoPicker">Non se atopa un selector de imaxes compatible</string>
<string name="importCards">Importar tarxetas</string>
<string name="validFromDate">Válida desde</string>
<string name="switchToBarcode">Cambiar ao código de barras</string>
@@ -297,11 +289,22 @@
<string name="addFromPdfFile">Elixe un ficheiro PDF</string>
<string name="errorReadingFile">Non se puido ler o ficheiro</string>
<string name="unsupportedFile">Este ficheiro non é compatible</string>
<string name="addFromPkpass">Selecciona un ficheiro Passbook (.pkpass)</string>
<string name="generic_error_please_retry">Sentímolo, pero algo fallou, inténtao outra vez…</string>
<string name="addFromPkpass">Selecciona un ficheiro Passbook (.pkpass / .pkpasses)</string>
<string name="generic_error_please_retry">Houbo un fallo</string>
<string name="sort_by_valid_from">Válido desde</string>
<string name="width">Anchura</string>
<string name="setBarcodeWidth">Establecer anchura do código de barras</string>
<string name="card_list_widget_name">Lista de tarxetas</string>
<string name="card_list_widget_empty">Aquí aparecerán as tarxetas fidelidade cando as engadas a Catima. Se tes tarxetas mira que non estean arquivadas.</string>
<string name="cardWithNumber">Tarxeta <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Tarxeta <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Por favor non rotes o dispositivo, porque isto cancelará a acción</string>
<string name="acra_catima_has_crashed">Lamentámolo, pero <xliff:g id="app_name">%s</xliff:g> fallou. Axúdanos a resolver a incidencia enviando un informe co erro.</string>
<string name="acra_explain_crash">Se é posible engade algún detalle máis como o que estabas a facer:</string>
<string name="acra_crash_email_subject">Informe do fallo de <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Solicitar informar sobre os fallos</string>
<string name="pref_enable_acra_summary">Se está activo, váiseche pedir informar sobre os fallos cando acontezan. Os informes nunca se envían automaticamente.</string>
<string name="copy_value">Copiar valor</string>
<string name="copied_to_clipboard">Copiado ao portapapeis</string>
<string name="nothing_to_copy">Non hai ningún valor</string>
</resources>

View File

@@ -25,7 +25,7 @@
<string name="importSuccessfulTitle">आयात हुआ</string>
<string name="importFailed">आयात नहीं हो सका</string>
<string name="action_search">खोज</string>
<string name="noGiftCardsGroup">कुछ कार्ड बनाएँ, और फिर उन्हें यहाँ समूह करें</string>
<string name="noGiftCardsGroup">कुछ कार्ड बनाएँ, और फिर उन्हें यहाँ समूह करें</string>
<string name="noMatchingGiftCards">कोई परिणाम नहीं। अपनी खोज बदलने का प्रयास करें।</string>
<string name="deleteTitle">कार्ड हटाएं</string>
<plurals name="deleteCardsTitle">
@@ -33,8 +33,8 @@
<item quantity="other"><xliff:g>%d</xliff:g> इन कार्डों को हटाएं</item>
</plurals>
<string name="importFailedTitle">आयात विफल</string>
<string name="exportOptionExplanation">डाटा आपके पसंद के स्थान पर लिखा जाएगा</string>
<string name="importOptionFilesystemExplanation">फाईल सिस्टम से एक विशिष्ट फाईल चुनें</string>
<string name="exportOptionExplanation">डाटा आपके पसंद के स्थान पर लिखा जाएगा</string>
<string name="importOptionFilesystemExplanation">फाईल सिस्टम से एक विशिष्ट फाईल चुनें</string>
<string name="app_copyright_old">लोयलटी कार्ड कीचैंन पर आधारित\nकॉपीराइट © 20162020 ब्रांडन आर्चर</string>
<string name="action_add">जोड़ें</string>
<string name="edit">संपादित करें</string>
@@ -53,10 +53,6 @@
<string name="settings_dark_theme">गाढ़ा (काला)</string>
<string name="settings">सेटिंग्स</string>
<string name="settings_system_theme">सिस्टम</string>
<string name="settings_card_orientation">स्क्रीन अभिमुखता</string>
<string name="settings_landscape_orientation">क्षैतिज (लैंडस्केप)</string>
<string name="settings_follow_system_orientation">सिस्टम का पालन करें</string>
<string name="settings_portrait_orientation">लंबवत (पोट्रैट)</string>
<string name="settings_display_barcode_max_brightness">स्क्रीन की चमक बढ़ाएं</string>
<string name="settings_keep_screen_on">स्क्रीन को चालू रखें</string>
<string name="cameraPermissionDeniedTitle">कैमरे की अनुमति नहीं मिली</string>
@@ -66,15 +62,14 @@
<string name="noBarcode">बारकोड नहीं है</string>
<string name="scanCardBarcode">बारकोड स्कैन करें</string>
<string name="cardShortcut">कार्ड का सरल उपाय (शॉर्टकट)</string>
<string name="noGiftCards">कार्ड जोड़ने के लिए + प्लस बटन पर क्लिक करें, या ⋮ मेनू से आयात करें</string>
<string name="importExportHelp">आपके डाटा को बैकअप करना उसे दूसरे डिवाइस में भेजना संभव कर देता है</string>
<string name="noGiftCards">कार्ड जोड़ने के लिए + प्लस बटन पर क्लिक करें, या ⋮ मेनू से आयात करें</string>
<string name="importExportHelp">आपके डाटा को बैकअप करना उसे दूसरे डिवाइस में भेजना संभव कर देता है</string>
<string name="barcodeImageDescriptionWithType"><xliff:g>%s</xliff:g> का बारकोड</string>
<string name="settings_disable_lockscreen_while_viewing_card">स्क्रीन को लॉक होने से रोकें</string>
<string name="settings_lock_on_opening_orientation">कार्ड खोलते समय प्रयुक्त अभिमुख अवस्था को प्रतिबंधित करें</string>
<string name="intent_import_card_from_url_share_text">मैं तुम्हें एक कार्ड भेजना चाहता हूँ</string>
<string name="selectBarcodeTitle">बारकोड चुनें</string>
<string name="thumbnailDescription">छोटा चित्र</string>
<string name="noGroups">+ दबा कर समूहों को वर्गीकरण के लिए चुनें</string>
<string name="noGroups">+ दबा कर समूहों को वर्गीकरण के लिए चुनें</string>
<string name="sameAsCardId">आई डी से निरंतर</string>
<string name="intent_import_card_from_url_share_multiple_text">में तुम्हें कुछ कार्ड्स भेजना चाहता हूँ</string>
<string name="importSuccessful">डाटा आयात किया गया</string>
@@ -106,7 +101,7 @@
<item quantity="one"><xliff:g>%s</xliff:g> पॉइंट</item>
<item quantity="other"><xliff:g>%s</xliff:g> पॉइंट्स</item>
</plurals>
<string name="importCatimaMessage">आयात करने के लिए, <i>catima.zip</i> फाइल को चुने जो की Catima से निर्यात किया गया था. \nदूसरे Catima प के आयात/निर्यात मेनू से निर्यात बटन दबाकर, पहले catima.zip फाइल को बनाये</string>
<string name="importCatimaMessage">आयात करने के लिए Catima से अपना निर्यात चुनें।\nइसे किसी अन्य Catima प के आयात/निर्यात मेनू से \"निर्यात\" दबाकर बनाएँ</string>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> चयनितहुए</item>
<item quantity="other"><xliff:g>%d</xliff:g> चयनित किए गए</item>
@@ -118,25 +113,24 @@
<item quantity="other"><xliff:g>%d</xliff:g> कार्डो</item>
</plurals>
<string name="group_updated">समूह अपडेट किया गया</string>
<string name="failedOpeningFileManager">पहले एक फाइल मैनेजर इनस्टॉल करें.</string>
<string name="failedOpeningFileManager">ाइल मैनेजर खोलने में विफल</string>
<string name="leaveWithoutSaveConfirmation">बिना सुरक्षित (सेव)किये छोड़े?</string>
<string name="addManually">बारकोड को मैन्युअल रूप से दर्ज करें</string>
<string name="editGroup">समूह संपादन: <xliff:g>%s</xliff:g></string>
<string name="points">पॉइंट्स</string>
<string name="chooseImportType">यहाँ से डेटा का आयात करें</string>
<string name="privacy_policy">निजता नीति</string>
<string name="privacy_policy">गोपनीयता नीति</string>
<string name="importCatima">कैटिमा से आयात करें</string>
<string name="app_license">कॉपीलेफ्ट लिबर सॉफ्टवेयर, GPLv3+ लाइसेंस प्राप्त</string>
<string name="about_title_fmt"><xliff:g id="app_name">%s</xliff:g> के बारे में</string>
<string name="debug_version_fmt">संस्करण: <xliff:g id="version">%s</xliff:g></string>
<string name="cameraPermissionRequired">इस क्रिया के लिए कैमरा की अनुमति चाहिए…</string>
<string name="permissionReadCardsLabel">टीमा कार्ड्स पढ़ें</string>
<string name="permissionReadCardsLabel">टीमा कार्ड्स पढ़ें</string>
<string name="storageReadPermissionRequired">इस क्रिया के लिए स्टोरेज पढ़ने की अनुमति आवश्यक है…</string>
<string name="permissionReadCardsDescription">अपने केटीमा कार्ड और सभी विवरण, सहित नोट्स और छवियों को पढ़ें</string>
<string name="balanceParsingFailed">अमान्य शेष राशि</string>
<string name="takePhoto">एक फोटो खींचें</string>
<string name="wrongValueForBarcodeType">चयनित बारकोड प्रकार के लिए मान्य नहीं है</string>
<string name="importStocard">स्टोकार्ड (Stocard) से आयात करें</string>
<string name="app_loyalty_card_keychain">लॉयल्टी कार्ड कीचेन</string>
<string name="no">नहीं</string>
<string name="importFidme">Fidme से आयात करें</string>
@@ -155,9 +149,8 @@
<string name="balanceSentence">शेष राशि: <xliff:g>%s</xliff:g></string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">कार्ड देखते समय स्क्रीन का लॉक हो जाना बंद करें</string>
<string name="expiryStateSentenceExpired">समय अवधि समाप्त: <xliff:g>%s</xliff:g></string>
<string name="importStocardMessage">आयात करने के लिए स्टोकार्ड (स्टोकार्ड) से अपना <i>***.zip</i> निर्यात चुनें।\nअपने डेटा के निर्यात के लिए support@stocardapp.com पर ई-मेल करके इसे प्राप्त करें।</string>
<string name="app_libraries">लिब्रे तृतीय-पक्ष लाइब्रेरी: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">लिब्रे तृतीय-पक्ष संसाधन: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">तृतीय-पक्ष लाइब्रेरी: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">तृतीय-पक्ष संसाधन: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="settings_keep_screen_on_summary">कार्ड देखते समय स्क्रीन टाइमआउट बंद करें</string>
<string name="removeImage">छवि हटाएं</string>
<string name="setBackImage">पीछे की छवि सेट करें</string>
@@ -190,7 +183,7 @@
<string name="settings_catima_theme">कैटिमा</string>
<string name="options">विकल्प</string>
<string name="settings_magenta_theme">मैजेंटा</string>
<string name="failedGeneratingShareURL">साझा करने योग्य URL जनरेट नहीं किया जा सकता. कृपया इसकी रिपोर्ट करें</string>
<string name="failedGeneratingShareURL">साझा करने योग्य URL जनरेट नहीं किया जा सकता. कृपया इसकी रिपोर्ट करें</string>
<string name="sort_by_most_recently_used">सबसे हाल ही में उपयोग किया गया</string>
<string name="settings_theme_color">थीम का रंग</string>
<string name="settings_sky_blue_theme">आसमानी नीला (हल्का नीला)</string>
@@ -216,9 +209,8 @@
<string name="app_contributors">इनके द्वारा संभव बनाया गया: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="sort">क्रमबद्ध करें</string>
<string name="show_note">नोट दिखाएँ</string>
<string name="settings_follow_sensor_orientation">हमेशा घुमाएँ (सिस्टम सेटिंग्स को अनदेखा करें)</string>
<string name="importFidmeMessage">आयात करने के लिए FidMe से अपना <i>fidme-export-request-xxxxxx.zip</i> निर्यात चुनें, और बाद में मैन्युअल रूप से बारकोड प्रकार चुनें।\nडेटा सुरक्षा चुनकर और फिर पहले मेरा डेटा निकालें दबाकर इसे अपनी FidMe प्रोफ़ाइल से बनाए</string>
<string name="importLoyaltyCardKeychainMessage">आयात करने के लिए लॉयल्टी कार्ड कीचेन से अपना <i>LoyaltyCardKeychen.csv</i> निर्यात चुनें।\nपहले वहां एक्सपोर्ट दबाकर लॉयल्टी कार्ड कीचेन में आयात/निर्यात मेनू से इसे बनाएं।</string>
<string name="importFidmeMessage">आयात करने के लिए FidMe से अपना निर्यात चुनें, और उसके बाद मैन्युअल रूप से बारकोड प्रकार चुनें।\nडेटा सुरक्षा चुनकर और फिर मेरा डेटा निकालें दबाकर इसे अपनी FidMe प्रोफ़ाइल से बनाएँ।</string>
<string name="importLoyaltyCardKeychainMessage">आयात करने के लिए Loyalty Card Keychain से अपना निर्यात चुनें।\nइसे लॉयल्टी कार्ड कीचेन में आयात/निर्यात मेनू से निर्यात बटन दबाकर बनाए</string>
<string name="updateBarcodeQuestionText">आपने आईडी बदल दी. क्या आप समान मान का उपयोग करने के लिए बारकोड को भी अपडेट करना चाहते हैं?</string>
<string name="exportPassword">अपने निर्यात की सुरक्षा के लिए एक पासवर्ड सेट करें (वैकल्पिक)</string>
<string name="turn_flashlight_off">टॉर्च बंद करें</string>
@@ -226,7 +218,7 @@
<string name="showMoreInfo">जानकारी दिखाएँ</string>
<string name="updateBalance">शेष राशि अद्यतन (अपडेट) करें</string>
<string name="failedToRetrieveImageFile">छवि फ़ाइल पुनः प्राप्त करने में विफल</string>
<string name="version_history">संस्करण इतिहास</string>
<string name="version_history">वर्जन तिहास</string>
<string name="archive">पुरालेख/संग्रहित (Archive)</string>
<string name="archived">कार्ड संग्रहीत (Archived)</string>
<string name="barcodeLongPressMessage">गैलरी ऐप में केवल छवियां ही खोली जा सकती हैं</string>
@@ -246,10 +238,10 @@
<string name="manually_enter_barcode_instructions">अपने कार्ड पर आईडी नंबर या टेक्स्ट दर्ज करें और अपने कार्ड पर मौजूद बारकोड की तरह दिखने वाले बारकोड को दबाएं।</string>
<string name="welcome">कैटिमा में आपका स्वागत है</string>
<string name="previousCard">पिछला</string>
<string name="failedLaunchingPhotoPicker">कोई समर्थित गैलरी ऐप नहीं मिल सका</string>
<string name="failedLaunchingPhotoPicker">समर्थित छवि चयनकर्ता नहीं मिल सका</string>
<string name="reverse">...उल्टे क्रम में</string>
<string name="height">ऊँचाई</string>
<string name="importVoucherVaultMessage">आयात करने के लिए वाउचर वॉल्ट से अपना <i>vouchervault.json</i> निर्यात चुनें।\nसबसे पहले वाउचर वॉल्ट में एक्सपोर्ट दबाकर इसे बनाएं।</string>
<string name="importVoucherVaultMessage">आयात करने के लिए वाउचर वॉल्ट से अपना निर्यात चुनें।\nवाउचर वॉल्ट में एक्सपोर्ट दबाकर इसे बनाएं।</string>
<string name="turn_flashlight_on">टॉर्च चालू करें</string>
<string name="sort_by_name">नाम</string>
<string name="credits">आभार</string>
@@ -262,8 +254,8 @@
<string name="switchToFrontImage">सामने वाली छवि पर स्विच करें</string>
<string name="switchToBackImage">पिछली छवि पर स्विच करें</string>
<string name="switchToBarcode">बारकोड पर स्विच करें</string>
<string name="openFrontImageInGalleryApp">गैलरी ऐप में सामने वाली छवि खोलें</string>
<string name="openBackImageInGalleryApp">गैलरी ऐप में पिछली छवि खोलें</string>
<string name="openFrontImageInGalleryApp">इमेज दर्शक सामने वाली इमेज खोलें</string>
<string name="openBackImageInGalleryApp">पिछली इमेज खोलें</string>
<string name="setBarcodeHeight">बारकोड ऊंचाई सेट करें</string>
<string name="icon_header_click_text">छोटी छवि (थंबनेल) संपादित करने के लिए देर तक दबाएँ</string>
<string name="enter_card_id">अपने कार्ड पर आईडी नंबर या टेक्स्ट दर्ज करें</string>
@@ -277,7 +269,7 @@
<string name="pageWithNumber">पेज <xliff:g>%d</xliff:g></string>
<string name="addFromPdfFile">एक PDF फाइल चुनें</string>
<string name="errorReadingFile">फाइल को पढ़ा नहीं जा सका</string>
<string name="failedLaunchingFileManager">समर्थित फाइल प्रबंधक नहीं मिल सका</string>
<string name="failedLaunchingFileManager">समर्थित फाइल मैनेजर नहीं मिल सका</string>
<string name="noCameraFoundGuideText">ऐसा लगता है कि आपके डिवाइस में कैमरा नहीं है। अगर है, तो डिवाइस को रीबूट करने का प्रयास करें। अन्यथा, किसी अन्य तरीके से बारकोड जोड़ने के लिए नीचे दिए गए अधिक विकल्प बटन का उपयोग करें।</string>
<string name="importCancelled">आयात रद्द</string>
<string name="exportCancelled">निर्यात रद्द</string>
@@ -285,11 +277,11 @@
<string name="useBackImage">पीछे की छवि का उपयोग करें</string>
<string name="width">चौड़ाई</string>
<string name="setBarcodeWidth">बारकोड की चौड़ाई सेट करें</string>
<string name="generic_error_please_retry">क्षमा करें, कुछ ग़लत हो गया, कृपया पुनः प्रयास करें..।</string>
<string name="generic_error_please_retry">क्षमा करें, कुछ ग़लत हो गया</string>
<string name="settings_use_volume_keys_navigation_summary">कौन सा कार्ड प्रदर्शित किया जाए, यह बदलने के लिए वॉल्यूम बटन का उपयोग करें</string>
<string name="sort_by_valid_from">मान्य तिथि के अनुसार</string>
<string name="settings_use_volume_keys_navigation">वॉल्यूम से कार्ड बदलें</string>
<string name="addFromPkpass">पासबुक फ़ाइल चुनें (.pkpass)</string>
<string name="addFromPkpass">पासबुक फ़ाइल चुनें (.pkpass/.pkpasses)</string>
<string name="unsupportedFile">यह फ़ाइल समर्थित नहीं है</string>
<string name="settings_category_title_cards_overview">कार्ड अवलोकन</string>
<string name="settings_column_count_2">2</string>
@@ -304,4 +296,15 @@
<string name="settings_column_count_7">7</string>
<string name="card_list_widget_name">कार्ड सूची</string>
<string name="card_list_widget_empty">कैटिमा में कुछ लॉयल्टी कार्ड जोड़ने के बाद, वे यहाँ दिखाई देंगे। अगर आपके पास कार्ड हैं, तो सुनिश्चित करें कि वे सभी संग्रहित न हों।</string>
<string name="cardWithNumber">कार्ड <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">कार्ड <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">कृपया डिवाइस को घुमाएँ नहीं, क्योंकि इससे कार्रवाई रद्द हो जाएगी</string>
<string name="acra_catima_has_crashed">हमें खेद है, लेकिन <xliff:g id="app_name">%s</xliff:g> क्रैश हो गया है। कृपया हमें एक त्रुटि रिपोर्ट भेजकर इस समस्या को ठीक करने में हमारी सहायता करें।</string>
<string name="acra_explain_crash">यदि संभव हो तो कृपया यहां आप क्या कर रहे थे, इसके बारे में अधिक विवरण जोड़ें:</string>
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> क्रैश रिपोर्ट</string>
<string name="pref_enable_acra">दुर्घटना रिपोर्ट भेजने के लिए कहें</string>
<string name="pref_enable_acra_summary">सक्षम होने पर, क्रैश होने पर आपको रिपोर्ट करने के लिए कहा जाएगा। क्रैश रिपोर्ट कभी भी स्वचालित रूप से नहीं भेजी जाती हैं।</string>
<string name="copy_value">मान कॉपी करें</string>
<string name="copied_to_clipboard">क्लिपबोर्ड पर कॉपी किया गया</string>
<string name="nothing_to_copy">कोई मूल्य नहीं मिला</string>
</resources>

View File

@@ -12,7 +12,7 @@
<string name="sendLabel">Pošalji …</string>
<string name="editCardTitle">Uredi karticu</string>
<string name="addCardTitle">Dodaj karticu</string>
<string name="scanCardBarcode">Snimi crtični kod kartice</string>
<string name="scanCardBarcode">Snimi crtični kod</string>
<string name="cardShortcut">Prečac kartice</string>
<string name="noCardsMessage">Najprije dodaj karticu</string>
<string name="noBarcode">Nema crtičnog koda</string>
@@ -24,21 +24,21 @@
<string name="cardId">ID kartice</string>
<string name="barcodeType">Vrsta crtičnog koda</string>
<string name="cancel">Odustani</string>
<string name="noGiftCards">Pritisni gumb + plus za dodavanje kartice ili uvezi putem izbornika ⋮.</string>
<string name="noGiftCards">Pritisni gumb + plus za dodavanje kartice ili uvezi putem izbornika ⋮</string>
<string name="noCardExistsError">Nije bilo moguće pronaći tu karticu</string>
<string name="failedParsingImportUriError">Nije bilo moguće obraditi URI uvoza</string>
<string name="importExport">Uvoz/Izvoz</string>
<string name="importExport">Uvoz/izvoz</string>
<string name="exportName">Izvoz</string>
<string name="importExportHelp">Spremanje sigurnosnih kopija tvojih podataka omogućuje premještanje podataka na jedan drugi uređaj.</string>
<string name="importExportHelp">Spremanje sigurnosnih kopija tvojih podataka omogućuje premještanje podataka na jedan drugi uređaj</string>
<string name="importSuccessfulTitle">Uvezeno</string>
<string name="importFailedTitle">Neuspio uvoz</string>
<string name="importFailed">Nije bilo moguće izvršiti uvoz</string>
<string name="exportSuccessfulTitle">Izvezeno</string>
<string name="about">Informacije</string>
<string name="exportOptionExplanation">Podaci će se zapisati u željeno mjesto.</string>
<string name="exportOptionExplanation">Podaci će se zapisati na mjesto po tvom izboru</string>
<string name="exportFailedTitle">Neuspio izvoz</string>
<string name="exporting">Izvoz …</string>
<string name="importOptionFilesystemExplanation">Odaberi određenu datoteku iz datotečnog sustava.</string>
<string name="importOptionFilesystemExplanation">Odaberi određenu datoteku iz datotečnog sustava</string>
<string name="settings">Postavke</string>
<string name="settings_dark_theme">Tamna</string>
<string name="exportFailed">Nije bilo moguće izvršiti izvoz</string>
@@ -60,23 +60,21 @@
<string name="importSuccessful">Podaci su uvezeni</string>
<string name="enter_group_name">Upiši ime grupe</string>
<string name="groups">Grupe</string>
<string name="noGroups">Pritisni gumb + plus za dodavanje grupe za kategoriziranje.</string>
<string name="noGroups">Pritisni gumb + plus za dodavanje grupe za kategoriziranje</string>
<string name="noGroupCards">Ova je grupa prazna</string>
<string name="addFromImage">Odaberi sliku iz galerije</string>
<string name="deleteConfirmationGroup">Izbrisati grupu\?</string>
<string name="failedOpeningFileManager">Najprije instaliraj upravljač datoteka.</string>
<string name="failedOpeningFileManager">Neuspjelo otvaranje upravljača datoteka</string>
<string name="moveUp">Pomakni prema gore</string>
<string name="leaveWithoutSaveTitle">Zatvori aplikaciju</string>
<string name="card">Kartica</string>
<string name="leaveWithoutSaveConfirmation">Zatvoriti aplikaciju bez spremanja\?</string>
<string name="noGiftCardsGroup">Stvori neke kartice, a zatim ih ovdje dodijeli grupi.</string>
<string name="noGiftCardsGroup">Stvori neke kartice, a zatim ih ovdje dodijeli grupi</string>
<plurals name="groupCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> kartica</item>
<item quantity="few"><xliff:g>%d</xliff:g> kartice</item>
<item quantity="other"><xliff:g>%d</xliff:g> kartica</item>
</plurals>
<string name="importStocardMessage">Odaberi tvoju iz Stocard izvezenu <i>***.zip</i> datoteku koju želiš uvesti.
\nPošalji e-mail na support@stocardapp.com s molbom za izvoz tvojih podataka.</string>
<string name="translate_platform">na platformi Weblate</string>
<string name="editGroup">Uređivanje grupe: <xliff:g>%s</xliff:g></string>
<string name="editBarcode">Uredi crtični kod</string>
@@ -85,9 +83,7 @@
<string name="accept">Prihvati</string>
<string name="importCatima">Uvezi iz Catima</string>
<string name="importFidme">Uvezi iz FidMe</string>
<string name="importStocard">Uvezi iz Stocard</string>
<string name="importLoyaltyCardKeychainMessage">Odaberi tvoju iz LoyaltyCardKeychain izvezenu <i>LoyaltyCardKeychain.csv</i> datoteku koju želiš uvesti.
\nStvori je putem izbornika „Uvoz/Izvoz” u aplikaciji Loyalty Card Keychain i tamo pritisni „Izvoz”.</string>
<string name="importLoyaltyCardKeychainMessage">Odaberi tvoj izvoz iz LoyaltyCardKeychain za uvoz. \nStvori je putem izbornika „Uvoz/Izvoz” u aplikaciji Loyalty Card Keychain pritiskom na „Izvoz”.</string>
<string name="updateBarcodeQuestionText">Promijenio/la si ID. Želiš li također aktualizirati crtični kod da koristi istu vrijednost\?</string>
<string name="importCards">Uvezi kartice</string>
<string name="selectColor">Odaberi boju</string>
@@ -100,7 +96,7 @@
<string name="frontImageDescription">Prednja slika</string>
<string name="exportPasswordHint">Upiši lozinku</string>
<string name="turn_flashlight_on">Uključi bljeskalicu</string>
<string name="failedGeneratingShareURL">Nije bilo moguće generirati URL za dijeljenje. Prijavi ovaj problem.</string>
<string name="failedGeneratingShareURL">Nije bilo moguće generirati URL za dijeljenje</string>
<string name="turn_flashlight_off">Isključi bljeskalicu</string>
<string name="settings_locale">Jezik</string>
<string name="settings_magenta_theme">Magenta</string>
@@ -121,27 +117,24 @@
<string name="archive">Arhiviraj</string>
<string name="archived">Kartica je arhivirana</string>
<string name="unarchived">Kartica je uklonjena iz arhive</string>
<string name="failedLaunchingPhotoPicker">Nije bilo moguće pronaći podržanu aplikaciju galerije</string>
<string name="failedLaunchingPhotoPicker">Nije bilo moguće pronaći podržani birač slika</string>
<string name="cameraPermissionDeniedTitle">Nije bilo moguće pristupiti kameri</string>
<string name="noCameraPermissionDirectToSystemSetting">Za snimanje crtičnih kodova Catima treba pristup tvojoj kameri. Dodirni ovdje za mijenjanje postavki dozvola.</string>
<string name="app_libraries">Slobodne biblioteke trećih strana: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_libraries">Biblioteke trećih strana: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Odaberi crtični kod</string>
<string name="settings_portrait_orientation">Uspravno</string>
<string name="settings_lock_on_opening_orientation">Odredi orijentaciju koja se koristi prilikom otvaranja kartice</string>
<string name="group_edit">Uredi grupu</string>
<string name="group_name_already_in_use">Ime grupe se već koristi</string>
<string name="noBarcodeFound">Nijedan crtični kod nije pronađen</string>
<string name="balance">Saldo</string>
<string name="chooseImportType">Uvezi podatke iz</string>
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
<string name="importCatimaMessage">Odaberi tvoju iz Catima izvezenu <i>catima.zip</i> datoteku koju želiš uvesti.
\nStvori je putem izbornika „Uvoz/Izvoz” jedne druge Catima aplikacije pritiskom na „Izvoz”.</string>
<string name="importCatimaMessage">Odaberi tvoj izvoz iz Catima za uvoz. \nStvori je putem izbornika „Uvoz/Izvoz” jedne druge Catima aplikacije pritiskom na „Izvoz”.</string>
<string name="height">Visina</string>
<string name="switchToFrontImage">Prebaci na prednju sliku</string>
<string name="switchToBackImage">Prebaci na stražnju sliku</string>
<string name="switchToBarcode">Prebaci na crtični kod</string>
<string name="openFrontImageInGalleryApp">Otvori prednju sliku u aplikaciji galerije</string>
<string name="openBackImageInGalleryApp">Otvori stražnju sliku u aplikaciji galerije</string>
<string name="openFrontImageInGalleryApp">Otvori prednju sliku u aplikaciji prikazivača slika</string>
<string name="openBackImageInGalleryApp">Otvori stražnju sliku u aplikaciji prikazivača slika</string>
<string name="setBarcodeHeight">Postavi visinu crtičnog koda</string>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> odabrana</item>
@@ -166,13 +159,9 @@
<string name="storageReadPermissionRequired">Za ovu radnju je potrebna dozvola za čitanje spremljenih podataka …</string>
<string name="cameraPermissionRequired">Za ovu radnju je potrebna dozvola za pristup kameri …</string>
<string name="app_license">Copylefted libre softver, GPLv3+ licenca</string>
<string name="settings_card_orientation">Orijentacija ekrana</string>
<string name="settings_follow_system_orientation">Slijedi sustav</string>
<string name="balanceSentence">Saldo: <xliff:g>%s</xliff:g></string>
<string name="importFidmeMessage">Odaberi tvoju iz FidMe izvezenu <i>idme-export-request-xxxxxx.zip</i> datoteku koju želiš uvesti i ručno odaberi vste crtičnog koda nakon toga.
\nStvori je putem tvog FidMe profila biranjem „Zaštita podataka” a zatim pritisni „Dekomprimiraj moje podatke”.</string>
<string name="importVoucherVaultMessage">Odaberi tvoju iz Voucher Vault izvezenu <i>vouchervault.json</i> datoteku koju želiš uvesti.
\nStvori je u aplikaciji Voucher Vault i tamo pritisni „Izvoz”.</string>
<string name="importFidmeMessage">Odaberi tvoj izvoz iz FidMe za uvoz i ručno odaberi vste crtičnog koda nakon toga. \nStvori ga putem tvog FidMe profila biranjem „Zaštita podataka” a zatim pritisni „Dekomprimiraj moje podatke”.</string>
<string name="importVoucherVaultMessage">Odaberi tvoj izvoz iz Voucher Vault za uvoz. \nStvori ga u aplikaciji Voucher Vault pritiskom na „Izvoz”.</string>
<string name="settings_pink_theme">Ružičasta</string>
<string name="settings_blue_theme">Plava</string>
<string name="failedToRetrieveImageFile">Neuspjelo dohvaćanje slikovne datoteke</string>
@@ -203,8 +192,7 @@
</plurals>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Autorska prava © 2019. <xliff:g>%d.</xliff:g> Sylvia van Os i doprinositelji</string>
<string name="debug_version_fmt">Verzija: <xliff:g id="version">%s</xliff:g></string>
<string name="app_resources">Slobodni resursi trećih strana: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="settings_landscape_orientation">Ležeće</string>
<string name="app_resources">Resursi trećih strana: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="group_name_is_empty">Ime grupe ne smije biti prazno</string>
<string name="group_updated">Grupa je aktualizirana</string>
<string name="all">Sve</string>
@@ -213,7 +201,7 @@
<string name="expiryStateSentenceExpired">Istekla: <xliff:g>%s</xliff:g></string>
<string name="chooseExpiryDate">Odaberi datum isteka</string>
<string name="moveBarcodeToTopOfScreen">Premjesti crtični kod na vrh ekrana</string>
<string name="errorReadingImage">Nije bilo moguće učitati sliku</string>
<string name="errorReadingImage">Nije bilo moguće čitati sliku</string>
<string name="currency">Valuta</string>
<string name="points">Bodovi</string>
<string name="privacy_policy">Politika privatnosti</string>
@@ -240,7 +228,7 @@
<string name="app_contributors">Doprinositelji: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="showMoreInfo">Prikaži informacije</string>
<string name="sort_by_name">Ime</string>
<string name="sort_by_most_recently_used">Nedavno korišteno</string>
<string name="sort_by_most_recently_used">Zadnje korišteno</string>
<string name="reverse">… u obrnutom redoslijedu</string>
<string name="shortcutSelectCard">Odaberi karticu</string>
<string name="previousCard">Prethodna</string>
@@ -278,12 +266,11 @@
<string name="settings_category_title_privacy">Privatnost</string>
<string name="settings_keep_screen_on_summary">Deaktivira isključivanje ekrana tijekom prikaza kartice</string>
<string name="app_name">Catima</string>
<string name="settings_follow_sensor_orientation">Uvijek okreni (zanemaruje postavke sustava)</string>
<string name="continue_">Nastavi</string>
<string name="add_manually_warning_message">Za neke trgovine se vrijednost crtičnog koda razlikuje od broja na kartici. Zbog toga ručno upisivanje crtičnog koda možda neće uvijek funkcionirati. Preporučuje se snimanje crtičnog koda pomoću kamere. Želiš li svejedno nastaviti?</string>
<string name="add_manually_warning_message">Za neke kartice se vrijednost crtičnog koda razlikuje od broja na kartici. Zbog toga ručno upisivanje crtičnog koda možda neće uvijek funkcionirati. Preporučuje se snimanje crtičnog koda pomoću kamere. Želiš li svejedno nastaviti?</string>
<string name="add_manually_warning_title">Preporučuje se snimanje</string>
<string name="addFromPdfFile">Odaberi PDF datoteku</string>
<string name="errorReadingFile">Nije bilo moguće pročitati datoteku</string>
<string name="errorReadingFile">Nije bilo moguće čitati datoteku</string>
<string name="failedLaunchingFileManager">Nije bilo moguće pronaći podržani upravljač datoteka</string>
<string name="multipleBarcodesFoundPleaseChooseOne">Koji od pronađenih crtičnih kodova želiš koristiti?</string>
<string name="pageWithNumber">Stranica <xliff:g>%d</xliff:g></string>
@@ -307,9 +294,21 @@
<string name="settings_column_count_4">4</string>
<string name="settings_column_count_5">5</string>
<string name="settings_column_count_7">7</string>
<string name="generic_error_please_retry">Žao nam je, nešto nije u redu, pokušaj ponovo …</string>
<string name="addFromPkpass">Odaberi jednu Passbook datoteku (.pkpass)</string>
<string name="generic_error_please_retry">Dogodila se greška</string>
<string name="addFromPkpass">Odaberi jednu Passbook datoteku (.pkpass / .pkpasses)</string>
<string name="unsupportedFile">Ova datoteka nije podržana</string>
<string name="settings_use_volume_keys_navigation_summary">Pomoću gumba za glasnoću promijeni koja se kartica prikazuje</string>
<string name="settings_use_volume_keys_navigation">Mijenjaj kartice pomoću gumba za glasnoću</string>
<string name="width">Širina</string>
<string name="card_list_widget_name">Popis kartica</string>
<string name="setBarcodeWidth">Postavi širinu crtičnog koda</string>
<string name="cardWithNumber">Kartica <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Kartica <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="card_list_widget_empty">Nakon što dodaš neke kartice vjernosti u Catima, one će se pojaviti ovdje. Ako već imaš kartice, provjeri da nisu sve arhivirane.</string>
<string name="pleaseDoNotRotateTheDevice">Ne okreći uređaj jer će to prekinuti radnju</string>
<string name="acra_catima_has_crashed">Žao nam je, ali aplikacija <xliff:g id="app_name">%s</xliff:g> je prekinula rad. Pomogni riješiti ovaj problem slanjem izvještaja o grešci.</string>
<string name="acra_explain_crash">Po mogućnosti dodaj više detalja o tvojim radnjama:</string>
<string name="acra_crash_email_subject">Izvještaj o prekidu rada aplikacije <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Pitaj da li poslati izvještaj o prekidu rada aplikacije</string>
<string name="pref_enable_acra_summary">Kada je uključeno, zamolit ćemo te da prijaviš prekid rada aplikacije kada se dogodi. Izvještaji o prekidu rada se nikada ne šalju automatski.</string>
</resources>

View File

@@ -50,8 +50,6 @@
<string name="deleteTitle">Kártya törlése</string>
<string name="barcodeImageDescriptionWithType">A(z) <xliff:g>%s</xliff:g> vonalkód képe</string>
<string name="noCardExistsError">A kártya nem található</string>
<string name="importStocardMessage">Válassza ki az importálandó <i>***.zip</i> Stocard export fájlt.
\nAz adatainak exportálását a support@stocardapp.com címre írt levélben kérheti.</string>
<string name="importVoucherVault">Importálás a Voucher Vaultból</string>
<string name="wrongValueForBarcodeType">Ez az érték meg megfelelő a kiválasztott vonalkódtípushoz</string>
<string name="settings_green_theme">Zöld</string>
@@ -125,7 +123,6 @@
<string name="importLoyaltyCardKeychain">Importálás a Loyalty Card Keychainből</string>
<string name="importLoyaltyCardKeychainMessage">Válassza ki a Loyalty Card Keychainből importálandó <i>LoyaltyCardKeychain.csv</i> export fájlt.
\nEzt hozza létre a Loyalty Card Keychainben az Importálás/exportálás menüben, az Exportálás megnyomásával.</string>
<string name="importStocard">Importálás a Stocardból</string>
<string name="importVoucherVaultMessage">Válassza ki az importálandó <i>vouchervault.json</i> Voucher Vault export fájlt.
\nEzt hozza létre a Voucher Vaultban az Exportálás megnyomásával.</string>
<string name="barcodeId">Vonalkód értéke</string>
@@ -197,7 +194,6 @@
\nLétrehozhatja az Importálás/exportálás menüből az Exportálást megnyomva egy másik Catima alkalmazásban.</string>
<string name="importFidmeMessage">Válassza ki a FidMeből exportált <i>fidme-export-request-xxxxxx.zip</i> fájl majd importálja be, és utána válassza a kézi vonalkódbeírást.
\nEzt hozza létre a FidMe-profiljában az Adatvédelem rész választásával, majd a Saját adatok kinyerése megnyomásával.</string>
<string name="settings_card_orientation">Képernyő tájolása</string>
<plurals name="groupCardCountWithArchived">
<item quantity="one"><xliff:g>%1$d</xliff:g> kártya (<xliff:g id="archivedCount">%2$d</xliff:g> archiválva)</item>
<item quantity="other"><xliff:g>%1$d</xliff:g> kártya (<xliff:g id="archivedCount">%2$d</xliff:g> archiválva)</item>
@@ -206,10 +202,6 @@
<string name="failedLaunchingPhotoPicker">Nem található támogatott galéria alkalmazás</string>
<string name="previousCard">Előző</string>
<string name="nextCard">Következő</string>
<string name="settings_portrait_orientation">Álló</string>
<string name="settings_follow_system_orientation">Rendszer követése</string>
<string name="settings_lock_on_opening_orientation">A használt tájolás zárolása a kártya megnyitásakor</string>
<string name="settings_landscape_orientation">Fekvő</string>
<string name="settings_oled_dark">Teljesen fekete háttér a sötét témánál</string>
<string name="include_if_asking_support">Ha támogatás akar kérni, ossza meg az alábbi információkat:</string>
<string name="archive">Archiválás</string>
@@ -272,7 +264,6 @@
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Copyright © 2019<xliff:g>%d</xliff:g> Sylvia van Os és közreműködők</string>
<string name="show_archived_cards">Archivált kártyák megjelenítése</string>
<string name="app_name">Catima</string>
<string name="settings_follow_sensor_orientation">Mindig forgassa (figyelmen kívül hagyja a rendszerbeállításokat)</string>
<string name="amountParsingFailed">Hibás érték</string>
<string name="add_manually_warning_title">Szkennelés ajánlott</string>
<string name="add_manually_warning_message">Egyes boltoknál a kártyán levő számsor különbözik a vonalkódtól. Emiatt a manuális szám beírás nem minden esetben fog működni. Erősen ajánlott inkább a vonalkód szkennelése kamerával. Biztosan folytatja?</string>

View File

@@ -19,7 +19,7 @@
<string name="license">Lisensi</string>
<string name="settings">Pengaturan</string>
<string name="settings_system_theme">Sistem</string>
<string name="selectBarcodeTitle">Pilih Barcode</string>
<string name="selectBarcodeTitle">Pilih barcode</string>
<string name="deleteConfirmation">Hapus kartu ini secara permanen?</string>
<string name="ok">OK</string>
<string name="share">Bagikan</string>
@@ -27,7 +27,7 @@
<string name="addCardTitle">Tambah Kartu</string>
<string name="scanCardBarcode">Pindai Barcode</string>
<string name="cancel">Batalkan</string>
<string name="importExport">Impor/Ekspor</string>
<string name="importExport">Impor/ekspor</string>
<string name="settings_theme">Tema</string>
<string name="all">Semua</string>
<string name="leaveWithoutSaveTitle">Keluar</string>
@@ -46,10 +46,10 @@
<string name="setBarcodeId">Tentukan nilai barcode</string>
<string name="photos">Foto</string>
<string name="setFrontImage">Atur gambar bagian depan</string>
<string name="report_error">Lapor Kesalahan</string>
<string name="report_error">Laporkan kesalahan</string>
<string name="rate_this_app">Beri nilai pada aplikasi ini</string>
<string name="sort_by_expiry">Masa berlaku</string>
<string name="sort_by_most_recently_used">Paling banyak digunakan</string>
<string name="sort_by_most_recently_used">Yang paling baru digunakan</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_pink_theme">Merah Muda</string>
<string name="settings_blue_theme">Biru</string>
@@ -70,7 +70,7 @@
<string name="removeImage">Hapus gambar</string>
<string name="setBackImage">Atur gambar bagian belakang</string>
<string name="intent_import_card_from_url_share_multiple_text">Saya ingin berbagi kartu dengan Anda</string>
<string name="noGiftCards">Tap tanda tombol plus ( + ) untuk menambahkan kartu, atau mengimpor nya melalui menu ( ⋮ ).</string>
<string name="noGiftCards">Tap tanda tombol plus ( + ) untuk menambahkan kartu, atau mengimpor nya melalui menu ( ⋮ )</string>
<string name="noMatchingGiftCards">Tidak menemukan apapun. Cobalah untuk mengubah pencarian anda.</string>
<string name="noBarcode">Bukan barcode</string>
<string name="confirm">Konfirmasi</string>
@@ -87,9 +87,9 @@
<string name="exportFailed">Tidak dapat mengekspor</string>
<string name="importing">Sedang mengimpor…</string>
<string name="exporting">Sedang mengekspor…</string>
<string name="exportOptionExplanation">Data akan ditulis ke lokasi pilihan Anda.</string>
<string name="exportOptionExplanation">Data akan ditulis ke lokasi yang Anda pilih</string>
<string name="importOptionFilesystemTitle">Impor dari pengelola file bawaan</string>
<string name="importOptionFilesystemExplanation">Pilih file dari pengelola file bawaan.</string>
<string name="importOptionFilesystemExplanation">Pilih berkas tertentu dari sistem berkas</string>
<string name="importOptionFilesystemButton">Dari pengelola file bawaan</string>
<string name="about">Tentang</string>
<string name="app_copyright_fmt">Hak Cipta © 2019<xliff:g>%d</xliff:g> Sylvia van Os dan para kontributor</string>
@@ -98,8 +98,8 @@
<string name="app_license">Perangkat lunak bebas copyleft, berlisensi GPLv3+</string>
<string name="about_title_fmt">Tentang <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versi: <xliff:g id="version">%s</xliff:g></string>
<string name="app_libraries">Pustaka pihak ketiga gratis: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Sumber daya pihak ketiga gratis: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Perpustakaan pihak ketiga: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Sumber daya pihak ketiga: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="thumbnailDescription">Gambar tampilan</string>
<string name="starImage">Favorit</string>
<string name="settings_light_theme">Terang</string>
@@ -112,10 +112,10 @@
<string name="exportSuccessful">Data terekspor</string>
<string name="enter_group_name">Masukan nama grup</string>
<string name="groups">Grup</string>
<string name="noGroups">Klik pada tombol tambah + untuk menambahkan grup untuk pengkategorian.</string>
<string name="noGroups">Klik tombol + untuk menambahkan grup untuk pengelompokan</string>
<string name="noGroupCards">Grup ini kosong</string>
<string name="deleteConfirmationGroup">Hapus grup?</string>
<string name="failedOpeningFileManager">Pasang aplikasi pengelola berkas terlebih dahulu.</string>
<string name="failedOpeningFileManager">Gagal membuka pengelola berkas</string>
<string name="moveUp">Pindah ke atas</string>
<string name="moveDown">Pindah ke bawah</string>
<string name="leaveWithoutSaveConfirmation">Keluar tanpa menyimpan?</string>
@@ -132,39 +132,32 @@
<string name="points">Poin</string>
<string name="app_loyalty_card_keychain">Gantungan kunci kartu kesetiaan</string>
<string name="privacy_policy">Kebijakan Privasi</string>
<string name="importCatimaMessage">Pilih ekspor <i>catima.zip</i> Anda dari Catima untuk diimpor.
\nBuat dari menu Impor/Ekspor aplikasi Catima lain dengan menekan Ekspor di sana terlebih dahulu.</string>
<string name="importFidmeMessage">Pilih ekspor <i>fidme-export-request-xxxxxx.zip</i> Anda dari FidMe untuk diimpor, dan pilih jenis barcode secara manual setelahnya.
\nBuat dari profil FidMe Anda dengan memilih Perlindungan Data lalu tekan Ekstrak data saya terlebih dahulu.</string>
<string name="importCatimaMessage">Pilih ekspor Anda dari Catima untuk diimpor.\nBuatlah dari menu Impor/Ekspor aplikasi Catima lainnya dengan menekan Ekspor.</string>
<string name="importFidmeMessage">Pilih ekspor Anda dari FidMe untuk diimpor, lalu pilih jenis barcode secara manual setelahnya.\nBuatlah dari profil FidMe Anda dengan memilih Data Protection, lalu tekan Extract my data.</string>
<string name="importLoyaltyCardKeychain">Impor dari Loyalty Card Keychain</string>
<string name="importLoyaltyCardKeychainMessage">Pilih ekspor <i>LoyaltyCardKeychain.csv</i> Anda dari Loyalty Card Keychain untuk diimpor.
\nBuat dari menu Import/Export di Loyalty Card Keychain dengan menekan Export terlebih dahulu.</string>
<string name="importStocard">Impor dari Stocard</string>
<string name="importStocardMessage">Pilih ekspor <i>***.zip</i> Anda dari Stocard untuk diimpor.
\nDapatkan dengan mengirim email ke support@stocardapp.com untuk meminta ekspor data Anda.</string>
<string name="importLoyaltyCardKeychainMessage">Pilih ekspor Anda dari Loyalty Card Keychain untuk diimpor.\nBuatlah dari menu Impor/Ekspor di Loyalty Card Keychain dengan menekan Ekspor.</string>
<string name="importVoucherVault">Impor dari Voucher Vault</string>
<string name="importVoucherVaultMessage">Pilih ekspor <i>vouchervault.json</i> Anda dari Voucher Vault untuk diimpor.
\nBuat dengan menekan Ekspor di Voucher Vault terlebih dahulu.</string>
<string name="importVoucherVaultMessage">Pilih ekspor Anda dari Voucher Vault untuk diimpor.\nBuatlah dengan menekan tombol Ekspor di Voucher Vault.</string>
<string name="unsupportedBarcodeType">Jenis barcode ini belum dapat ditampilkan. Ini mungkin didukung di versi aplikasi yang lebih baru.</string>
<string name="wrongValueForBarcodeType">Nilai tidak berlaku untuk jenis barcode yang dipilih</string>
<string name="wrongValueForBarcodeType">Nilai tersebut tidak valid untuk jenis barcode yang dipilih</string>
<string name="frontImageDescription">Gambar depan</string>
<string name="backImageDescription">Gambar belakang</string>
<string name="updateBarcodeQuestionTitle">Perbarui barcode?</string>
<string name="updateBarcodeQuestionText">Anda mengubah ID. Apakah Anda juga ingin memperbarui barcode untuk menggunakan nilai yang sama\?</string>
<string name="passwordRequired">Silahkan masukan kata sandi</string>
<string name="passwordRequired">Masukkan kata sandi</string>
<string name="exportPassword">Tetapkan kata sandi untuk melindungi ekspor anda (opsional)</string>
<string name="failedGeneratingShareURL">Tidak dapat membuat alamat berbagi. Mohon laporkan ini.</string>
<string name="failedGeneratingShareURL">Tidak dapat menghasilkan URL yang dapat dibagikan</string>
<string name="app_contributors">Pengembangan dibantu oleh: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="reverse">…dalam urutan terbalik</string>
<string name="version_history">Riwayat Versi</string>
<string name="version_history">Riwayat versi</string>
<string name="help_translate_this_app">Bantu terjemahkan aplikasi ini</string>
<string name="source_repository">Sumber Repositori</string>
<string name="source_repository">Repositori sumber</string>
<string name="on_github">di GitHub</string>
<string name="and_data_usage">dan penggunaan data</string>
<string name="on_google_play">di Google Play</string>
<string name="cardShortcut">Pintasan Kartu</string>
<string name="barcodeImageDescriptionWithType">Gambar <xliff:g>%s</xliff:g> barcode</string>
<string name="importExportHelp">Mencadangkan data anda akan memungkinkan memindahkannya ke perangkat lain.</string>
<string name="importExportHelp">Membackup data Anda memungkinkan Anda memindahkan data tersebut ke perangkat lain</string>
<plurals name="selectedCardCount">
<item quantity="other"><xliff:g>%d</xliff:g> kartu dipilih</item>
</plurals>
@@ -177,9 +170,9 @@
<plurals name="deleteCardsTitle">
<item quantity="other">Hapus <xliff:g>%d</xliff:g> kartu</item>
</plurals>
<string name="editGroup">Menyunting Grup: <xliff:g>%s</xliff:g></string>
<string name="editGroup">Kelompok pengeditan: <xliff:g>%s</xliff:g></string>
<string name="selectColor">Pilih warna</string>
<string name="noGiftCardsGroup">Buat beberapa kartu, kemudian masukkan mereka ke grup di sini.</string>
<string name="noGiftCardsGroup">Buat beberapa kartu, kemudian masukkan mereka ke grup di sini</string>
<string name="group_name_already_in_use">Nama grup telah dipakai</string>
<string name="setIcon">Atur thumbnail</string>
<string name="settings_oled_dark">Latar belakang gelap gulita untuk tema gelap</string>
@@ -192,14 +185,9 @@
<string name="translate_platform">di Weblate</string>
<string name="welcome">Selamat datang di Catima</string>
<string name="failedToOpenUrl">Install browser web terlebih dahulu</string>
<string name="failedLaunchingPhotoPicker">Tidak dapat menemukan aplikasi galeri yang didukung</string>
<string name="failedLaunchingPhotoPicker">Tidak dapat menemukan pemilih gambar yang didukung</string>
<string name="previousCard">Sebelumnya</string>
<string name="nextCard">Berikutnya</string>
<string name="settings_card_orientation">Orientasi layar</string>
<string name="settings_follow_system_orientation">Ikuti sistem</string>
<string name="settings_portrait_orientation">Potret</string>
<string name="settings_landscape_orientation">Lanskap</string>
<string name="settings_lock_on_opening_orientation">Kunci ke orientasi yang digunakan saat membuka kartu</string>
<plurals name="balancePoints">
<item quantity="other"><xliff:g>%s</xliff:g> poin</item>
</plurals>
@@ -233,8 +221,8 @@
<string name="switchToFrontImage">Ubah ke depan gambar</string>
<string name="switchToBackImage">Ubah ke belakang gambar</string>
<string name="switchToBarcode">Ubah ke kode batang</string>
<string name="openFrontImageInGalleryApp">Buka gambar didepan di galeri app</string>
<string name="openBackImageInGalleryApp">Buka gambar dibelakang di galeri app</string>
<string name="openFrontImageInGalleryApp">Buka gambar depan di aplikasi penampil gambar</string>
<string name="openBackImageInGalleryApp">Buka gambar di aplikasi penampil gambar</string>
<string name="setBarcodeHeight">Atur tinggi kode batang</string>
<string name="donate">Donasi</string>
<string name="show_validity">Tunjukkan validitas</string>
@@ -242,7 +230,7 @@
<string name="icon_header_click_text">Tekan lama untuk mengedit thumbnail</string>
<string name="show_name_below_image_thumbnail">Tampilkan nama di bawah thumbnail gambar</string>
<string name="show_note">Tampilkan catatan</string>
<string name="permissionReadCardsLabel">Baca Kartu Catima</string>
<string name="permissionReadCardsLabel">Bacalah kartu Catima</string>
<string name="permissionReadCardsDescription">baca kartu Anda dan semua detailnya, termasuk catatan dan gambar</string>
<string name="settings_allow_content_provider_read_title">Izinkan aplikasi lain mengakses data saya</string>
<string name="settings_allow_content_provider_read_summary">Aplikasi masih harus meminta izin untuk diberikan akses</string>
@@ -266,18 +254,17 @@
<string name="addWithoutBarcode">Tambah kartu tanpa barcode</string>
<string name="field_must_not_be_empty">Isian tidak boleh kosong</string>
<string name="app_name">Catima</string>
<string name="settings_follow_sensor_orientation">Selalu rotasi (abaikan pengaturan sistem)</string>
<string name="add_manually_warning_title">Pemindaian sangat dianjurkan</string>
<string name="continue_">Lanjut</string>
<string name="failedLaunchingFileManager">Tidak dapat menemukan pengelola file yang didukung</string>
<string name="errorReadingFile">Tidak dapat membaca file</string>
<string name="failedLaunchingFileManager">Tidak dapat menemukan pengelola berkas yang didukung</string>
<string name="errorReadingFile">Tidak dapat membaca berkas</string>
<string name="addFromPdfFile">Pilih file PDF</string>
<string name="multipleBarcodesFoundPleaseChooseOne">Barcode mana yang ingin Anda gunakan?</string>
<string name="pageWithNumber">Halaman <xliff:g>%d</xliff:g></string>
<string name="spend">Dibelanjakan</string>
<string name="receive">Terima</string>
<string name="amountParsingFailed">Jumlah tidak valid</string>
<string name="add_manually_warning_message">Untuk beberapa toko, nilai barcode berbeda dengan nomor yang tertulis di kartu. Oleh karena itu, memasukkan barcode secara manual mungkin tidak selalu berhasil. Sangat disarankan untuk memindai barcode dengan kamera anda. Apakah anda masih ingin melanjutkan?</string>
<string name="add_manually_warning_message">Untuk beberapa kartu, nilai barcode berbeda dengan angka yang tertulis di kartu. Karena itu, memasukkan barcode secara manual mungkin tidak selalu berhasil. Disarankan untuk memindai barcode menggunakan kamera Anda. Apakah Anda tetap ingin melanjutkan?</string>
<string name="noCameraFoundGuideText">Perangkat Anda sepertinya tidak memiliki kamera. Jika iya, coba mulai ulang perangkat. Jika tidak, gunakan tombol Opsi lainnya di bawah untuk menambahkan barcode dengan cara lain.</string>
<string name="importCancelled">Impor dibatalkan</string>
<string name="exportCancelled">Ekspor dibatalkan</string>
@@ -296,12 +283,20 @@
<string name="settings_column_count_1">1</string>
<string name="settings_column_count_4">4</string>
<string name="settings_column_count_5">5</string>
<string name="addFromPkpass">Pilih file Buku Tabungan (.pkpass)</string>
<string name="addFromPkpass">Pilih file Buku Tabungan (.pkpass / .pkpasses)</string>
<string name="unsupportedFile">File ini tidak didukung</string>
<string name="generic_error_please_retry">Maaf, terjadi kesalahan, silakan coba lagi...</string>
<string name="sort_by_valid_from">Berlaku dari</string>
<string name="generic_error_please_retry">Terjadi kesalahan</string>
<string name="sort_by_valid_from">Berlaku mulai</string>
<string name="width">Lebar</string>
<string name="card_list_widget_name">Daftar kartu</string>
<string name="setBarcodeWidth">Atur Lebar Barcode</string>
<string name="setBarcodeWidth">Atur lebar barcode</string>
<string name="card_list_widget_empty">Setelah Anda menambahkan beberapa kartu loyalitas di Catima, kartu tersebut akan muncul di sini. Jika Anda memiliki kartu sebelumnya, pastikan tidak semuanya diarsipkan.</string>
<string name="cardWithNumber">Kartu <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Kartu <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Jangan memutar perangkat, karena hal ini akan membatalkan tindakan</string>
<string name="acra_catima_has_crashed">Kami mohon maaf, tetapi <xliff:g id="app_name">%s</xliff:g> telah mengalami crash. Tolong bantu kami memperbaiki masalah ini dengan mengirimkan laporan kesalahan kepada kami.</string>
<string name="acra_explain_crash">Jika memungkinkan, tolong tambahkan detail lebih lanjut tentang apa yang Anda lakukan di sini:</string>
<string name="acra_crash_email_subject">Laporan crash <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Minta untuk mengirimkan laporan crash</string>
<string name="pref_enable_acra_summary">Ketika diaktifkan, Anda akan diminta untuk melaporkan crash saat terjadi. Laporan crash tidak pernah dikirim secara otomatis.</string>
</resources>

View File

@@ -2,7 +2,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_search">Cerca</string>
<string name="action_add">Aggiungi</string>
<string name="noGiftCards">Premi il pulsante + per aggiungere una carta oppure importala dal menù ⋮.</string>
<string name="noGiftCards">Premi il pulsante + per aggiungere una carta oppure importala dal menù ⋮</string>
<string name="noMatchingGiftCards">Nessun risultato. Prova a cambiare la tua ricerca.</string>
<string name="storeName">Nome</string>
<string name="note">Note</string>
@@ -17,14 +17,14 @@
<string name="sendLabel">Invia…</string>
<string name="editCardTitle">Modifica carta</string>
<string name="addCardTitle">Aggiungi carta</string>
<string name="scanCardBarcode">Scansiona il codice</string>
<string name="scanCardBarcode">Scansiona codice a barre</string>
<string name="cardShortcut">Scorciatoia per la carta</string>
<string name="noCardsMessage">Aggiungi prima una carta</string>
<string name="noCardExistsError">Impossibile trovare quella carta</string>
<string name="failedParsingImportUriError">Impossibile analizzare l\'URI di importazione</string>
<string name="importExport">Importa/Esporta</string>
<string name="importExport">Importa/esporta</string>
<string name="exportName">Esporta</string>
<string name="importExportHelp">Il backup dei dati permette di spostarli su un altro dispositivo.</string>
<string name="importExportHelp">Il backup dei dati permette di spostarli su un altro dispositivo</string>
<string name="importSuccessfulTitle">Importato</string>
<string name="importFailedTitle">Importazione fallita</string>
<string name="importFailed">Impossibile eseguire l\'importazione</string>
@@ -129,9 +129,6 @@
<string name="backImageDescription">Immagine posteriore</string>
<string name="frontImageDescription">Immagine frontale</string>
<string name="passwordRequired">Si prega di inserire la password</string>
<string name="importStocardMessage">Seleziona il tuo file di esportazione <i>***.zip</i> da Stocard per importarlo.
\nOttienilo inviando un\'e-mail a support@stocardapp.com chiedendo un\'esportazione dei tuoi dati.</string>
<string name="importStocard">Importa da Stocard</string>
<string name="turn_flashlight_off">Spegni la torcia</string>
<string name="turn_flashlight_on">Accendi la torcia</string>
<string name="failedGeneratingShareURL">Impossibile generare un URL condivisibile. Si prega di segnalarlo.</string>
@@ -186,7 +183,7 @@
<string name="report_error">Segnala un errore</string>
<string name="editGroup">Modifica del gruppo: <xliff:g>%s</xliff:g></string>
<string name="group_name_is_empty">Il nome del gruppo non deve essere vuoto</string>
<string name="noGiftCardsGroup">Crea alcune carte e poi assegnale al gruppo qui.</string>
<string name="noGiftCardsGroup">Crea alcune carte e poi assegnale al gruppo qui</string>
<string name="group_edit">Modifica il gruppo</string>
<string name="group_name_already_in_use">Il nome del gruppo è già in uso</string>
<string name="group_updated">Gruppo aggiornato</string>
@@ -204,16 +201,11 @@
</plurals>
<string name="settings_oled_dark">Sfondo nero puro per il tema scuro</string>
<string name="include_if_asking_support">Se vuoi richiedere supporto, includi le seguenti informazioni:</string>
<string name="settings_card_orientation">Orientamento dello schermo</string>
<string name="settings_follow_system_orientation">Segui il sistema</string>
<string name="duplicateCard">Duplica</string>
<string name="archive">Archivia</string>
<string name="unarchive">Disarchivia</string>
<string name="unarchived">Carta non archiviata</string>
<string name="archived">Carta archiviata</string>
<string name="settings_portrait_orientation">Verticale</string>
<string name="settings_landscape_orientation">Orizzontale</string>
<string name="settings_lock_on_opening_orientation">Blocca sull\'orientamento utilizzato all\'apertura della carta</string>
<string name="failedLaunchingPhotoPicker">Impossibile trovare un\'app galleria supportata</string>
<plurals name="groupCardCountWithArchived">
<item quantity="one"><xliff:g>%1$d</xliff:g> carta (<xliff:g id="archivedCount">%2$d</xliff:g> archiviata)</item>
@@ -278,7 +270,6 @@
<string name="addWithoutBarcode">Aggiungere una carta senza codice a barre</string>
<string name="field_must_not_be_empty">Il campo non deve essere vuoto</string>
<string name="app_name">Catima</string>
<string name="settings_follow_sensor_orientation">Ruota sempre (ignora le impostazioni di sistema)</string>
<string name="add_manually_warning_title">Consigliata scansione</string>
<string name="continue_">Successivo</string>
<string name="add_manually_warning_message">In alcuni negozi, il valore del codice a barre differisce dal numero scritto sulla carta. Per questo motivo, l\'inserimento manuale del codice a barre potrebbe non funzionare sempre. Si consiglia di scansionare il codice a barre con la fotocamera. Vuoi continuare lo stesso?</string>

View File

@@ -1,16 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="wrongValueForBarcodeType">選択したバーコード形式ではこの番号は使用できません</string>
<string name="unsupportedBarcodeType">このバーコード形式は表示できません。将来のアップデートにより対応するかもしれません。</string>
<string name="setBarcodeId">バーコード番号を設定</string>
<string name="importLoyaltyCardKeychainMessage">インポートするにはLoyalty Card Keychainエクスポートした <i>LoyaltyCardKeychain.csv</i>ファイルを選択してください。
\nファイルがない場合、 Loyalty Card Keychainアプリからファイルをエクスポートしてください。</string>
<string name="importLoyaltyCardKeychainMessage">Loyalty Card Keychainからエクスポートしたデータを選択してインポートしてください。\nデータはLoyalty Card Keychainのインポート/エクスポートメニューからエクスポートを押して作成してください。</string>
<string name="importLoyaltyCardKeychain">Loyalty Card Keychainからインポート</string>
<string name="importFidmeMessage">インポートするにはFindMeエクスポートした <i>fidme-export-request-xxxxxx.zip</i>ファイルを選択してください。そのあと手動でバーコード形式を選択してください。
\nファイルがない場合、FidMeでファイルを作成してください。</string>
<string name="importFidmeMessage">FidMeからエクスポートしたデータを選択してインポートしたうえで、手動でバーコードの種類を選択してください。\nFidMeプロフィールから作成するには データ保護 を選択して データを抽出 を押してください。</string>
<string name="importFidme">FidMeからインポート</string>
<string name="importCatimaMessage">インポートするにはCatimaエクスポートした<i>Catima.zip</i>ファイルを選択してください。
\nファイルがない場合、他のCatimaアプリでファイルをエクスポートしてください。</string>
<string name="importCatimaMessage">インポートするにはCatimaからエクスポートしたファイルを選択してください。\nファイルは別なCatimaアプリのインポートエクスポートメニューからエクスポートを押して作成できます。</string>
<string name="importCatima">Catimaからインポート</string>
<string name="accept">承認</string>
<string name="privacy_policy">プライバシーポリシー</string>
@@ -40,15 +37,15 @@
</plurals>
<string name="moveDown">下に移動</string>
<string name="moveUp">上に移動</string>
<string name="failedOpeningFileManager">ファイルマネージャーをインストールしてください。</string>
<string name="failedOpeningFileManager">ファイルマネージャーを開けません</string>
<string name="deleteConfirmationGroup">グループを削除しますか?</string>
<string name="all">すべて</string>
<string name="noGroups">+ボタンを押してグループを追加してください</string>
<string name="noGroups">+ ボタンを押してグループを追加してください</string>
<string name="groups">グループ</string>
<string name="enter_group_name">グループ名を入力</string>
<string name="exportSuccessful">データがエクスポートされました</string>
<string name="importSuccessful">データがインポートされました</string>
<string name="intent_import_card_from_url_share_text">カード共有しましょう</string>
<string name="intent_import_card_from_url_share_text">カード共有しましょう</string>
<string name="settings_disable_lockscreen_while_viewing_card">バーコード表示中に画面をロックしない</string>
<string name="settings_keep_screen_on">バーコード表示中に画面を点けたままにする</string>
<string name="settings_display_barcode_max_brightness">画面を明るくする</string>
@@ -59,17 +56,17 @@
<string name="settings">設定</string>
<string name="starImage">お気に入りのスター</string>
<string name="thumbnailDescription">サムネイル</string>
<string name="selectBarcodeTitle">バーコード選択</string>
<string name="app_libraries">Libre third-party libraries: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">バーコード選択</string>
<string name="app_libraries">サードパーティーのライブラリ: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="debug_version_fmt">バージョン: <xliff:g id="version">%s</xliff:g></string>
<string name="about_title_fmt"><xliff:g id="app_name">%s</xliff:g> について</string>
<string name="app_license">Copylefted libre software, licensed GPLv3+</string>
<string name="app_resources">Libre third-party resources: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_license">GPLv3+ライセンスによる、コピーレフトされた自由ソフトウェア</string>
<string name="app_resources">サードパーティーのリソース: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="about">このアプリについて</string>
<string name="importOptionFilesystemButton">ファイルを選択</string>
<string name="importOptionFilesystemExplanation">ストレージからファイルを選択してください</string>
<string name="importOptionFilesystemExplanation">ストレージからファイルを選択してください</string>
<string name="importOptionFilesystemTitle">ストレージからインポート</string>
<string name="exportOptionExplanation">選択した場所にデータを出力します</string>
<string name="exportOptionExplanation">選択した場所にデータを出力します</string>
<string name="exporting">エクスポート中…</string>
<string name="importing">インポート中…</string>
<string name="exportFailed">カードをエクスポートできませんでした</string>
@@ -77,13 +74,12 @@
<string name="exportSuccessfulTitle">エクスポートしました</string>
<string name="sameAsCardId">カード番号に合わせる</string>
<string name="barcodeId">バーコード番号</string>
<string name="importVoucherVaultMessage">Voucher Vaultでエクスポートし<i>vouchervault.json</i>ファイルを選択してください。
\nファイルがない場合、Voucher Vaultでファイルをエクスポートしてください。</string>
<string name="importVoucherVaultMessage">Voucher Vaultでエクスポートしてからインポートしてください。\nエクスポートはVoucher Vaultでエクスポートを押して作成してください。</string>
<string name="importVoucherVault">Voucher Vaultからインポート</string>
<string name="importFailed">カードをインポートできません</string>
<string name="importFailedTitle">インポートに失敗しました</string>
<string name="importSuccessfulTitle">インポートしました</string>
<string name="importExportHelp">データをバックアップすると、他のデバイスにカードを移すことができます</string>
<string name="importExportHelp">データをバックアップする事で他のデバイスにカードを移できます</string>
<string name="exportName">エクスポート</string>
<string name="importExport">インポート/エクスポート</string>
<string name="failedParsingImportUriError">インポートURIを解析できません</string>
@@ -91,8 +87,8 @@
<string name="noCardsMessage">カードを追加</string>
<string name="cardShortcut">カードのショートカット</string>
<string name="scanCardBarcode">バーコードをスキャン</string>
<string name="addCardTitle">カード追加</string>
<string name="editCardTitle">カード編集</string>
<string name="addCardTitle">カード追加</string>
<string name="editCardTitle">カード編集</string>
<string name="sendLabel">送信先を選択…</string>
<string name="share">共有</string>
<string name="ok">確定</string>
@@ -109,28 +105,25 @@
<string name="note">メモ</string>
<string name="storeName">名前</string>
<string name="noMatchingGiftCards">該当なし</string>
<string name="noGiftCards">+ボタンからカードを新規追加、⋮メニューからカードをインポートすることができます</string>
<string name="noGiftCards">+ ボタンからカードを新規追加、⋮ メニューからカードをインポート出来ます</string>
<string name="action_add">追加</string>
<string name="action_search">検索</string>
<string name="intent_import_card_from_url_share_multiple_text">カードを共有しましょう</string>
<string name="turn_flashlight_off">ライトをオフにする</string>
<string name="turn_flashlight_on">ライトをオンにする</string>
<string name="failedGeneratingShareURL">共有URLの生成を生成できませんでした。バグを報告してください。</string>
<string name="passwordRequired">パスワードを入力してください</string>
<string name="turn_flashlight_off">ライトを消灯する</string>
<string name="turn_flashlight_on">ライトを点灯する</string>
<string name="failedGeneratingShareURL">共有可能なURLを作成できませんでした</string>
<string name="passwordRequired">パスワードを入力</string>
<string name="no">いいえ</string>
<string name="yes">はい</string>
<string name="updateBarcodeQuestionText">カード番号を変更しました。バーコード番号も同じ値に変更しますか?</string>
<string name="updateBarcodeQuestionTitle">バーコードの番号を変更しますか?</string>
<string name="takePhoto">写真を撮影する</string>
<string name="removeImage">画像を削除</string>
<string name="setBackImage">面の画像を設定</string>
<string name="setFrontImage">面の画像を設定</string>
<string name="setBackImage">面の画像を設定</string>
<string name="setFrontImage">面の画像を設定</string>
<string name="photos">画像</string>
<string name="backImageDescription"></string>
<string name="frontImageDescription"></string>
<string name="importStocardMessage">Stocardでエクスポートした<i>***.zip</i>ファイルを選択してください。
\nファイルがない場合、e-mailing support@stocardapp.comにデータのエクスポートを要求してください。</string>
<string name="importStocard">Stocardからインポート</string>
<string name="backImageDescription">背面</string>
<string name="frontImageDescription">前面</string>
<plurals name="selectedCardCount">
<item quantity="other">選択済み: <xliff:g>%d</xliff:g></item>
</plurals>
@@ -158,7 +151,7 @@
<string name="noGroupCards">このグループにはカードがありません</string>
<string name="sort_by">並び替え</string>
<string name="sort_by_expiry">期限</string>
<string name="sort_by_most_recently_used">最近使用したカード</string>
<string name="sort_by_most_recently_used">最近使たカード</string>
<string name="sort_by_name">名前</string>
<string name="sort">並び替え</string>
<string name="rate_this_app">このアプリを評価する</string>
@@ -171,7 +164,7 @@
<string name="help_translate_this_app">翻訳を手伝う</string>
<string name="license">ライセンス</string>
<string name="on_google_play">Google Play</string>
<string name="report_error">問題を報告する</string>
<string name="report_error">問題を報告</string>
<string name="reverse">逆順</string>
<string name="and_data_usage">データの扱いなど</string>
<string name="group_updated">グループを更新しました</string>
@@ -189,16 +182,11 @@
<string name="chooseValidFromDate">有効期限を選択</string>
<string name="anyDate">無期限</string>
<string name="app_name">Catima</string>
<string name="settings_card_orientation">画面の向き</string>
<string name="settings_display_barcode_max_brightness_summary">仕事をするためにいくつかのスキャナーが必要</string>
<string name="settings_follow_system_orientation">システムに従う</string>
<string name="settings_display_barcode_max_brightness_summary">一部のスキャナを動かすのに必要です</string>
<string name="storageReadPermissionRequired">このアクションのためにストレージの読み取り権限を許可…</string>
<string name="cameraPermissionDeniedTitle">カメラへアクセスできません</string>
<string name="settings_follow_sensor_orientation">自動回転 (システムを無視)</string>
<string name="cameraPermissionRequired">このアクションのためにカメラへのアクセス権限の許可…</string>
<string name="settings_landscape_orientation"></string>
<string name="settings_portrait_orientation"></string>
<string name="noGiftCardsGroup">いくつかのカードを作って、それらをこのグループにアサインします。</string>
<string name="noGiftCardsGroup">幾つかカードを作り、それらをこのグループに紐づけます</string>
<string name="noCameraPermissionDirectToSystemSetting">バーコードをスキャンするためには、Catimaはカメラへのアクセスを必要とします。ここをタップして権限設定の変更をお願いします。</string>
<string name="importCards">カードをインポート</string>
<string name="show_balance">残高を表示</string>
@@ -209,7 +197,7 @@
<string name="welcome">Catimaへようこそ</string>
<string name="show_name_below_image_thumbnail">画像サムネイルの下に名前を表示</string>
<string name="settings_keep_screen_on_summary">画面の自動消灯を無効化します</string>
<string name="settings_category_title_cards">カード</string>
<string name="settings_category_title_cards">カードビュー</string>
<string name="settings_category_title_general">一般</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">画面のロックを無効化します</string>
<string name="action_display_options">表示の設定</string>
@@ -222,7 +210,6 @@
<string name="failedToOpenUrl">ブラウザーをインストールしてください</string>
<string name="previousCard">前へ</string>
<string name="nextCard">次へ</string>
<string name="settings_lock_on_opening_orientation">カードを開いた時の向きに固定</string>
<string name="settings_oled_dark">ダークテーマで黒い背景を使用する</string>
<string name="settings_oled_dark_summary">有機ELディスプレイでの電池の使用量を削減します</string>
<string name="action_more_options">オプション</string>
@@ -243,4 +230,75 @@
<string name="permissionReadCardsDescription">Catimaカードと、ートや画像を含むすべての詳細を読み取る</string>
<string name="settings_use_volume_keys_navigation_summary">ボリュームボタンを使ってどのカードを表示するかを変更する</string>
<string name="unsupportedFile">このファイルはサポートされていません</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">著作権 © 2019<xliff:g>%d</xliff:g> Sylvia van Os と貢献者一同</string>
<string name="app_copyright_short">著作権 © Sylvia van Os と貢献者一同</string>
<string name="app_copyright_old">Loyalty Card Keychain が基になりました\n著作権 © 20162020 Branden Archer</string>
<string name="settings_allow_content_provider_read_summary">アプリは継続したアクセス許可を要求します</string>
<string name="settings_use_volume_keys_navigation">音量ボタンでカードを切り替え</string>
<plurals name="balancePoints">
<item quantity="other"><xliff:g>%s</xliff:g> ポイント</item>
</plurals>
<string name="balanceParsingFailed">残高が無効です</string>
<string name="showMoreInfo">情報を確認</string>
<string name="updateBalance">残高を更新</string>
<string name="failedToRetrieveImageFile">画像ファイルを取得できませんでした</string>
<string name="barcodeLongPressMessage">ギャラリーアプリは画像のみ開けます</string>
<string name="sort_by_valid_from">有効期限</string>
<string name="starred">スター付き</string>
<string name="include_if_asking_support">サポートを依頼する場合、以下の情報を含めて下さい:</string>
<string name="failedLaunchingPhotoPicker">サポートされている画像ピッカーが見つかりませんでした</string>
<plurals name="groupCardCountWithArchived">
<item quantity="other"><xliff:g>%1$d</xliff:g> のカード (<xliff:g id="archivedCount">%2$d</xliff:g> アーカイブ済み)</item>
</plurals>
<string name="updateBalanceTitle">どれくらい収入・支出がありましたか?</string>
<string name="updateBalanceHint">金額を入力</string>
<string name="currentBalanceSentence">現在の残高: <xliff:g>%s</xliff:g></string>
<string name="newBalanceSentence">新規残高: <xliff:g>%s</xliff:g></string>
<string name="validFromSentence">有効期限: <xliff:g>%s</xliff:g></string>
<string name="height">高さ</string>
<string name="switchToFrontImage">前面画像へ切り替え</string>
<string name="switchToBackImage">背面画像へ切り替え</string>
<string name="switchToBarcode">バーコードへ切り替え</string>
<string name="openFrontImageInGalleryApp">前面画像を画像ビューワーアプリで開く</string>
<string name="openBackImageInGalleryApp">背面画像を画像ビューワーアプリで開く</string>
<string name="setBarcodeHeight">バーコードの高さを設定</string>
<string name="icon_header_click_text">サムネイルを長押しして編集</string>
<string name="settings_category_title_cards_overview">カードの概要</string>
<string name="settings_column_count_portrait">縦向きモードの列</string>
<string name="settings_column_count_landscape">横向きモードの列</string>
<string name="settings_automatic_column_count">自動</string>
<string name="view_online">オンラインで閲覧</string>
<string name="enter_card_id">カード記載のID番号かテキストを入力してください</string>
<string name="card_id_must_not_be_empty">カードIDは空っぽに出来ません</string>
<string name="field_must_not_be_empty">この欄は入力必須です</string>
<string name="manually_enter_barcode_instructions">カードに記載のID番号かテキストを入力してからカード上のバーコードかそれに類似のものを押してください。</string>
<string name="add_manually_warning_title">スキャンするのをお勧めします</string>
<string name="add_manually_warning_message">一部のカードでは、バーコードの値がカード券面記載の番号と異なります。そのために、バーコードを手動入力しても正しく機能しない場合があります。代わりにカメラでバーコードをスキャンすることをお勧めしています。それでも続けますか?</string>
<string name="spend">支出</string>
<string name="receive">収入</string>
<string name="amountParsingFailed">無効な金額です</string>
<string name="errorReadingFile">ファイルを読み取れませんでした</string>
<string name="failedLaunchingFileManager">サポートされているファイルマネージャーが見つかりませんでした</string>
<string name="multipleBarcodesFoundPleaseChooseOne">発見できたバーコードのどれを使いますか?</string>
<string name="pageWithNumber"><xliff:g>%d</xliff:g> ページ</string>
<string name="noCameraFoundGuideText">お使いのデバイスにカメラが搭載されていないようです。搭載されている場合には、デバイスを再起動してみてください。搭載されていなければ、下にあるその他のオプションボタンから別の方法でバーコードを追加してください。</string>
<string name="useFrontImage">前面画像を利用</string>
<string name="useBackImage">背面画像を利用</string>
<string name="addFromPkpass">Passbook 形式のファイルを選択 (.pkpass / .pkpasses)</string>
<string name="generic_error_please_retry">エラーが発生しました</string>
<string name="width"></string>
<string name="card_list_widget_name">カード一覧</string>
<string name="setBarcodeWidth">バーコードの幅を設定</string>
<string name="card_list_widget_empty">Catimaで幾つかポイントカードを追加すると、ここに表示されます。カードをお持ちの場合、全てアーカイブがされていないことをご確認ください。</string>
<string name="cardWithNumber">カード <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">カード <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">動作がキャンセルされるため、デバイスを回転させないようにしてください</string>
<string name="acra_catima_has_crashed">申し訳ありません、<xliff:g id="app_name">%s</xliff:g> がクラッシュしました。エラーレポートを送信し問題解決にご協力ください。</string>
<string name="acra_explain_crash">できれば、何をしようとしてそうなったのか、より詳細な情報を追加ねがいます:</string>
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> クラッシュレポート</string>
<string name="pref_enable_acra">クラッシュレポートを送信する</string>
<string name="pref_enable_acra_summary">有効にすると、クラッシュ発生時に報告するかを確認されます。クラッシュレポートが自動送信されることはありません。</string>
<string name="copy_value">値をコピー</string>
<string name="copied_to_clipboard">クリップボードへコピー</string>
<string name="nothing_to_copy">値が見つかりません</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More