Compare commits

...
911 Commits
Author SHA1 Message Date
Sylvia van Os 61b582f676 Release Catima 2.42.0 2026-05-06 20:34:41 +02:00
Sylvia van OsandGitHub 4bf6ae897f Merge pull request #3116 from CatimaLoyalty/feature/java_25_21
Java fixes
2026-05-06 20:29:56 +02:00
Sylvia van Os 74b15ce766 Java fixes
Use 25 in build.sh (Fedora 44 doesn't ship with 21 anymore) and target
21 consistently (Android Studio doesn't support 25 yet)
2026-05-06 19:01:35 +02:00
Sylvia van OsandGitHub cea4a5431e Merge pull request #3115 from CatimaLoyalty/dependabot/gradle/gradle-wrapper-9.5.0
Bump gradle-wrapper from 9.4.1 to 9.5.0
2026-05-06 17:05:31 +02:00
dependabot[bot]andGitHub 12e0105b5a Bump gradle-wrapper from 9.4.1 to 9.5.0
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.4.1 to 9.5.0.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.4.1...v9.5.0)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-06 02:53:39 +00:00
Sylvia van OsandGitHub 17be43d0d2 Merge pull request #3114 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-05-05 20:47:27 +02:00
Francisco SerradorandHosted Weblate dd071c912a Translated using Weblate (Spanish)
Currently translated at 94.4% (154 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/es/
2026-05-05 18:47:00 +02:00
The WizardandHosted Weblate 32cdacb4be Translated using Weblate (Arabic)
Currently translated at 99.3% (318 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ar/
2026-05-05 18:46:59 +02:00
Thu Trang PhamandHosted Weblate c1cbebd782 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/vi/
2026-05-05 18:46:58 +02:00
Sylvia van OsandGitHub 2a334445dc Merge pull request #3113 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-05-05 13:27:21 +02:00
PatrikandHosted Weblate 34bba5a636 Translated using Weblate (Czech)
Currently translated at 100.0% (163 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2026-05-05 11:12:23 +00:00
Francisco SerradorandHosted Weblate 3c18ac0d96 Translated using Weblate (Spanish)
Currently translated at 75.4% (123 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/es/
2026-05-05 11:12:22 +00:00
PatrikandHosted Weblate 9b9005087e Translated using Weblate (Slovak)
Currently translated at 100.0% (163 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sk/
2026-05-05 11:12:21 +00:00
The WizardandHosted Weblate 88f0bc4c28 Translated using Weblate (Arabic)
Currently translated at 99.0% (317 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ar/
2026-05-05 11:12:20 +00:00
Sylvia van OsandGitHub c8d368e86f Merge pull request #3112 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-05-04 11:37:04 +02:00
PatrikandHosted Weblate 318d6956fa Translated using Weblate (Slovak)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sk/
2026-05-04 09:10:36 +00:00
OmikorinandHosted Weblate 5443214033 Translated using Weblate (Polish)
Currently translated at 100.0% (163 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2026-05-04 09:10:36 +00:00
NooB9496andHosted Weblate 2bdabbcd4c Translated using Weblate (Polish)
Currently translated at 100.0% (163 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2026-05-04 09:10:35 +00:00
Francisco SerradorandHosted Weblate 38eaa59518 Translated using Weblate (Spanish)
Currently translated at 69.9% (114 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/es/
2026-05-04 09:10:34 +00:00
PatrikandHosted Weblate ed6fa92f51 Translated using Weblate (Slovak)
Currently translated at 95.7% (156 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sk/
2026-05-04 09:10:33 +00:00
Sylvia van OsandGitHub 7f0b4bccb3 Merge pull request #3111 from CatimaLoyalty/create-pull-request/patch-1777789590
Update contributors
2026-05-03 08:48:31 +02:00
TheLastProjectandgithub-actions[bot] f4edfb870b Update contributors 2026-05-03 06:26:30 +00:00
Sylvia van OsandGitHub 9a07256a70 Merge pull request #3110 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-05-02 20:34:58 +02:00
이정희andHosted Weblate b744754949 Translated using Weblate (Korean)
Currently translated at 94.3% (302 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ko/
2026-05-02 18:10:09 +00:00
ssantosandHosted Weblate f08859b377 Translated using Weblate (Portuguese)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt/
2026-05-02 18:10:08 +00:00
ssantosandHosted Weblate fb5475b724 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_PT/
2026-05-02 18:10:07 +00:00
Sylvia van OsandGitHub 798d26ddae Merge pull request #3109 from CatimaLoyalty/fix/nfc_state
Ensure correct NFC state on onResume
2026-05-02 19:02:56 +02:00
Sylvia van Os cd2eb2e0d9 Ensure correct NFC state on onResume
This fixes going to settings from the view activity and disabling NFC,
followed by a press of the back button from not disabling reader mode
2026-05-02 16:14:50 +02:00
NightShiftNexusandGitHub dfa1c29fa0 feat: split complex loyaltyCardViewActivity (#3072) 2026-05-02 16:10:03 +02:00
Sylvia van OsandGitHub 090e85ea1c Merge pull request #3106 from CatimaLoyalty/dependabot/gradle/androidx.compose-compose-bom-2026.04.01
Bump androidx.compose:compose-bom from 2026.03.01 to 2026.04.01
2026-04-30 11:44:47 +02:00
dependabot[bot]andGitHub d1fd7c1eb0 Bump androidx.compose:compose-bom from 2026.03.01 to 2026.04.01
Bumps androidx.compose:compose-bom from 2026.03.01 to 2026.04.01.

---
updated-dependencies:
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2026.04.01
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-30 05:37:45 +00:00
Sylvia van OsandGitHub 775cba5667 Merge pull request #3105 from CatimaLoyalty/dependabot/gradle/kotlin-2.3.21
Bump kotlin from 2.3.20 to 2.3.21
2026-04-30 07:36:40 +02:00
dependabot[bot]andGitHub 6f40bc5b1b Bump kotlin from 2.3.20 to 2.3.21
Bumps `kotlin` from 2.3.20 to 2.3.21.

Updates `org.jetbrains.kotlin.android` from 2.3.20 to 2.3.21
- [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.3.20...v2.3.21)

Updates `org.jetbrains.kotlin.plugin.compose` from 2.3.20 to 2.3.21
- [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.3.20...v2.3.21)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-30 02:56:01 +00:00
Sylvia van OsandGitHub e14fb974b4 Fix last updated year 2026-04-29 21:36:39 +02:00
Sylvia van OsandGitHub dbf29c6b83 Add NFC permission to privacy policy (#3104) 2026-04-29 21:24:06 +02:00
Sylvia van OsandGitHub ad44fd3382 Merge pull request #3103 from CatimaLoyalty/revert-3095-fix/remove_unneeded_nfc_permission
Revert "Remove unneeded NFC permission"
2026-04-29 19:42:47 +02:00
Sylvia van OsandGitHub 61df41d5fe Merge pull request #3102 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-04-29 19:27:38 +02:00
Sylvia van OsandGitHub 942a795c2c Revert "Remove unneeded NFC permission" 2026-04-29 19:18:52 +02:00
Sylvia van OsandHosted Weblate 5f948badd1 Translated using Weblate (Spanish)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2026-04-29 17:10:21 +00:00
Liner SevenandHosted Weblate f07f32b169 Translated using Weblate (Japanese)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2026-04-29 17:10:20 +00:00
Liner SevenandHosted Weblate 905257d039 Translated using Weblate (Japanese)
Currently translated at 100.0% (163 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2026-04-29 17:10:19 +00:00
ezn24andHosted Weblate 4af7500e42 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hant/
2026-04-29 17:10:18 +00:00
Sylvia van OsandGitHub a9a3a0037b Merge pull request #3100 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-04-28 18:28:16 +02:00
Sylvia van OsandHosted Weblate 111d16319b Translated using Weblate (Bulgarian)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bg/
2026-04-28 17:10:27 +02:00
Francisco SerradorandHosted Weblate 269a74da52 Translated using Weblate (Spanish)
Currently translated at 67.4% (110 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/es/
2026-04-28 17:10:26 +02:00
Milo IvirandHosted Weblate 51a094a502 Translated using Weblate (Croatian)
Currently translated at 2.4% (4 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/hr/
2026-04-28 17:10:25 +02:00
Francisco SerradorandHosted Weblate b048c017bf Translated using Weblate (Spanish)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2026-04-28 17:10:24 +02:00
NooB9496andHosted Weblate d725756498 Translated using Weblate (Polish)
Currently translated at 99.3% (162 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2026-04-28 17:10:23 +02:00
Milo IvirandHosted Weblate 65649bbb72 Translated using Weblate (Croatian)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/hr/
2026-04-28 17:10:22 +02:00
Sylvia van OsandGitHub 7e246e9d78 Merge pull request #3098 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-04-27 16:35:37 +02:00
Sylvain PichonandHosted Weblate cdf965f4da Translated using Weblate (French)
Currently translated at 100.0% (163 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2026-04-27 16:11:24 +02:00
FjuroandHosted Weblate b2e19bb89a Translated using Weblate (Czech)
Currently translated at 100.0% (163 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2026-04-27 16:11:24 +02:00
B o d oandHosted Weblate ba6cfb0a55 Translated using Weblate (German)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2026-04-27 16:11:23 +02:00
109247019824andHosted Weblate 8a24dbfdaf Translated using Weblate (Bulgarian)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bg/
2026-04-27 16:11:21 +02:00
Oğuz ErsenandHosted Weblate d84e13e2ef Translated using Weblate (Turkish)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/tr/
2026-04-27 16:11:20 +02:00
大王叫我来巡山andHosted Weblate c4727aa3d8 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hans/
2026-04-27 16:11:18 +02:00
josé m.andHosted Weblate 110b5d4c87 Translated using Weblate (Galician)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/gl/
2026-04-27 16:11:17 +02:00
大王叫我来巡山andHosted Weblate b8c834794b Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (163 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2026-04-27 16:11:16 +02:00
solokotandHosted Weblate bcc1cc412e Translated using Weblate (Russian)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2026-04-27 16:11:16 +02:00
B o d oandHosted Weblate ff96880cc2 Translated using Weblate (German)
Currently translated at 100.0% (163 of 163 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-04-27 16:11:15 +02:00
Sylvain PichonandHosted Weblate 6f4e9662db Translated using Weblate (French)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2026-04-27 16:11:14 +02:00
GideonandHosted Weblate 5da99fc5f0 Translated using Weblate (Dutch)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2026-04-27 16:11:12 +02:00
Priit JõerüütandHosted Weblate ec3fc8737a Translated using Weblate (Estonian)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/et/
2026-04-27 16:11:11 +02:00
Vasilis K.andHosted Weblate 5fd3dcda36 Translated using Weblate (Greek)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2026-04-27 16:11:11 +02:00
FjuroandHosted Weblate 938aaba898 Translated using Weblate (Czech)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/cs/
2026-04-27 16:11:10 +02:00
Giovanni DonisiandHosted Weblate cb7e6c96b0 Translated using Weblate (Italian)
Currently translated at 100.0% (320 of 320 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2026-04-27 16:11:09 +02:00
Sylvia van OsandGitHub 49b128a20c Merge pull request #3096 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-04-26 16:04:06 +02:00
andHosted Weblate 7f683eb920 Translated using Weblate (Korean)
Currently translated at 95.2% (300 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ko/
2026-04-26 13:10:01 +00:00
Sylvia van OsandGitHub 489c89205d Merge pull request #3095 from CatimaLoyalty/fix/remove_unneeded_nfc_permission
Remove unneeded NFC permission
2026-04-25 18:20:55 +02:00
Sylvia van Os 4b0b91de98 Remove unneeded NFC permission 2026-04-25 16:09:39 +02:00
Sylvia van OsandGitHub c54b1ab4a0 Merge pull request #3094 from CatimaLoyalty/create-pull-request/patch-1777125791
Update Fastlane changelogs
2026-04-25 16:03:39 +02:00
TheLastProjectandgithub-actions[bot] 8055a2b651 Update Fastlane changelogs 2026-04-25 14:03:11 +00:00
Sylvia van Os 71ababc4d1 Update CHANGELOG 2026-04-25 16:02:58 +02:00
Sylvia van OsandGitHub 498b885398 Merge pull request #3047 from il-Luca/feature/pause-nfc-while-viewing-card
Add option to pause NFC while viewing a card
2026-04-25 16:02:30 +02:00
Sylvia van Os 774fcfd497 Improve comment clarity 2026-04-25 15:46:23 +02:00
Sylvia van Os 0967e475d5 Pause NFC by default 2026-04-25 15:42:08 +02:00
Sylvia van Os 2bd6fc444e Show snackbar when NFC is paused 2026-04-25 14:07:14 +02:00
Sylvia van OsandGitHub dfa025952b Merge pull request #3093 from CatimaLoyalty/dependabot/github_actions/peter-evans/create-pull-request-8.1.1
Bump peter-evans/create-pull-request from 8.1.0 to 8.1.1
2026-04-23 09:09:12 +02:00
Sylvia van OsandGitHub 30918675bf Merge pull request #3092 from CatimaLoyalty/dependabot/github_actions/actions/upload-artifact-7.0.1
Bump actions/upload-artifact from 7.0.0 to 7.0.1
2026-04-23 09:08:34 +02:00
dependabot[bot]andGitHub 91f6c11821 Bump peter-evans/create-pull-request from 8.1.0 to 8.1.1
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 8.1.0 to 8.1.1.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v8.1.0...v8.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-23 02:53:10 +00:00
dependabot[bot]andGitHub bf16fbe84b Bump actions/upload-artifact from 7.0.0 to 7.0.1
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v7.0.0...v7.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-23 02:53:02 +00:00
Sylvia van OsandGitHub ffb8003245 Merge pull request #3090 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-04-21 21:29:59 +02:00
Ariel IsaharovandHosted Weblate 1645b5f362 Translated using Weblate (Hebrew (Israel))
Currently translated at 23.8% (75 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/he_IL/
2026-04-21 19:09:56 +00:00
Sylvia van OsandGitHub aa03cb5f89 Merge pull request #3089 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-04-20 19:31:59 +02:00
אופק מזרחיandHosted Weblate 1d8025b0cf Translated using Weblate (Hebrew)
Currently translated at 13.0% (41 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/he/
2026-04-20 17:09:59 +00:00
Sylvia van OsandGitHub 3ac8998377 Merge pull request #3088 from CatimaLoyalty/create-pull-request/patch-1776191728
Update Fastlane changelogs
2026-04-14 21:06:34 +02:00
TheLastProjectandgithub-actions[bot] 5160f61523 Update Fastlane changelogs 2026-04-14 18:35:28 +00:00
Sylvia van Os 68c5fa10f4 Update CHANGELOG 2026-04-14 20:35:15 +02:00
Sylvia van OsandGitHub 620f564ae5 Merge pull request #3087 from utafrali/fix/issue-3086-feature-request-timestamp-in-the-export-
Add timestamp to export filename
2026-04-14 20:34:35 +02:00
ugurtafrali b840d481a8 Add timestamp to export filename 2026-04-14 00:31:40 +03:00
Sylvia van OsandGitHub 7872a59a89 Merge pull request #3085 from CatimaLoyalty/create-pull-request/patch-1775973243
Update contributors
2026-04-12 09:19:26 +02:00
TheLastProjectandgithub-actions[bot] 62c7f92563 Update contributors 2026-04-12 05:54:02 +00:00
Sylvia van OsandGitHub 283f237180 Merge pull request #3084 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-04-10 13:18:16 +02:00
Thu Trang PhamandHosted Weblate 6dcd1c43fe Translated using Weblate (Vietnamese)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/vi/
2026-04-10 06:47:10 +02:00
ChaitanyaandHosted Weblate 805bad437c Translated using Weblate (Hindi)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/hi/
2026-04-10 06:47:10 +02:00
Sylvia van OsandGitHub f476fb1cc0 Merge pull request #3082 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-04-08 10:53:11 +02:00
Sylvia van OsandHosted Weblate f35f8b1b29 Translated using Weblate (Bosnian)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bs/
2026-04-07 22:53:57 +00:00
Sylvia van OsandGitHub 061ed7e765 Merge pull request #3081 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-04-07 15:18:02 +02:00
Milo IvirandHosted Weblate fbbfe78828 Translated using Weblate (Croatian)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/hr/
2026-04-06 19:09:57 +00:00
pacavarnandHosted Weblate 557132b4c5 Translated using Weblate (Bosnian)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bs/
2026-04-06 19:09:56 +00:00
Sylvia van OsandGitHub 5bc7a15c15 Merge pull request #3079 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-04-03 21:41:03 +02:00
Sylvia van OsandHosted Weblate 0fa34e3eeb Translated using Weblate (Vietnamese)
Currently translated at 88.2% (278 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/vi/
2026-04-03 21:40:33 +02:00
Thu Trang PhamandHosted Weblate eeef8bd468 Translated using Weblate (Vietnamese)
Currently translated at 88.2% (278 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/vi/
2026-04-03 18:09:58 +02:00
Sylvia van OsandGitHub 6b5eeea3b7 Merge pull request #3077 from CatimaLoyalty/dependabot/gradle/androidx.compose-compose-bom-2026.03.01
Bump androidx.compose:compose-bom from 2026.03.00 to 2026.03.01
2026-04-02 08:31:43 +02:00
dependabot[bot]andGitHub 6e9fd540c5 Bump androidx.compose:compose-bom from 2026.03.00 to 2026.03.01
Bumps androidx.compose:compose-bom from 2026.03.00 to 2026.03.01.

---
updated-dependencies:
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2026.03.01
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-02 05:34:18 +00:00
Sylvia van OsandGitHub c4da8ce9df Merge pull request #3075 from CatimaLoyalty/create-pull-request/patch-1774762990
Update contributors
2026-03-29 10:21:42 +02:00
TheLastProjectandgithub-actions[bot] 5cc50e6e06 Update contributors 2026-03-29 05:43:10 +00:00
Sylvia van OsandGitHub 5088ea7b80 Merge pull request #3074 from CatimaLoyalty/dependabot/gradle/gradle-wrapper-9.4.1
Bump gradle-wrapper from 9.4.0 to 9.4.1
2026-03-27 19:50:16 +01:00
dependabot[bot]andGitHub d7ae175a00 Bump gradle-wrapper from 9.4.0 to 9.4.1
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.4.0 to 9.4.1.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.4.0...v9.4.1)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-27 02:53:17 +00:00
Sylvia van OsandGitHub 60e47f2ffc Update to AGP 9.1.0 (#3071) 2026-03-26 21:00:32 +01:00
Sylvia van OsandGitHub 850c2d3b8a Merge pull request #3070 from CatimaLoyalty/create-pull-request/patch-1774369651
Update Fastlane changelogs
2026-03-24 18:01:11 +01:00
TheLastProjectandgithub-actions[bot] cdddd5eacf Update Fastlane changelogs 2026-03-24 16:27:30 +00:00
Sylvia van Os fd4793db99 Update CHANGELOG 2026-03-24 17:27:18 +01:00
Sylvia van OsandGitHub 9006dbee87 Merge pull request #3069 from tbertels/first_letter_capital
Set store name first letter as capital by default
2026-03-24 17:26:18 +01:00
Thomas Bertels 073923d072 Set store name first letter as capital by default
Asks the keyboard to switch to capital for the first letter of each
word.
2026-03-24 09:45:59 +01:00
Sylvia van OsandGitHub dfb006584e Merge pull request #3066 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-03-23 17:50:32 +01:00
Sylvia van OsandHosted Weblate c00383ce81 Translated using Weblate (Spanish (Argentina))
Currently translated at 87.3% (275 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es_AR/
2026-03-23 16:51:57 +01:00
Sylvia van OsandHosted Weblate ca9d61d955 Translated using Weblate (Finnish)
Currently translated at 99.3% (313 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fi/
2026-03-23 16:51:57 +01:00
LilliandHosted Weblate 37a8549d21 Translated using Weblate (Finnish)
Currently translated at 99.3% (313 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fi/
2026-03-23 12:10:03 +00:00
Agustin RodriguezandHosted Weblate 6c753b5058 Translated using Weblate (Spanish (Argentina))
Currently translated at 87.3% (275 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es_AR/
2026-03-23 12:10:02 +00:00
Sylvia van OsandGitHub 7395ed0c2b Merge pull request #3064 from CatimaLoyalty/dependabot/gradle/androidx.core-core-ktx-1.18.0
Bump androidx.core:core-ktx from 1.17.0 to 1.18.0
2026-03-19 09:38:59 +01:00
dependabot[bot]andGitHub 2b55c2b5e0 Bump androidx.core:core-ktx from 1.17.0 to 1.18.0
Bumps androidx.core:core-ktx from 1.17.0 to 1.18.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-19 06:13:20 +00:00
Sylvia van OsandGitHub 41d9aa488f Merge pull request #3063 from CatimaLoyalty/dependabot/gradle/androidx.compose-compose-bom-2026.03.00
Bump androidx.compose:compose-bom from 2026.02.01 to 2026.03.00
2026-03-19 07:12:19 +01:00
dependabot[bot]andGitHub 3e46d2ad84 Bump androidx.compose:compose-bom from 2026.02.01 to 2026.03.00
Bumps androidx.compose:compose-bom from 2026.02.01 to 2026.03.00.

---
updated-dependencies:
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2026.03.00
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-19 05:51:36 +00:00
Sylvia van OsandGitHub dac990716d Merge pull request #3062 from CatimaLoyalty/dependabot/gradle/androidx.activity-activity-compose-1.13.0
Bump androidx.activity:activity-compose from 1.12.2 to 1.13.0
2026-03-19 06:50:21 +01:00
dependabot[bot]andGitHub ed56e14574 Bump androidx.activity:activity-compose from 1.12.2 to 1.13.0
Bumps androidx.activity:activity-compose from 1.12.2 to 1.13.0.

---
updated-dependencies:
- dependency-name: androidx.activity:activity-compose
  dependency-version: 1.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-19 02:52:32 +00:00
Sylvia van OsandGitHub acf72397b6 Merge pull request #3061 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-03-16 20:58:22 +01:00
EditaNEmilisandHosted Weblate 02d97e440f Translated using Weblate (Lithuanian)
Currently translated at 77.4% (244 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/lt/
2026-03-16 19:10:03 +00:00
Sylvia van OsandGitHub f2e67d3727 Merge pull request #3058 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-03-15 16:52:37 +01:00
Sylvia van OsandGitHub ae11b24c7a Merge pull request #3059 from CatimaLoyalty/create-pull-request/patch-1773552502
Update contributors
2026-03-15 10:47:59 +01:00
TheLastProjectandgithub-actions[bot] 6f2b77fbdc Update contributors 2026-03-15 05:28:21 +00:00
Liner SevenandHosted Weblate 11d4df8c36 Translated using Weblate (Japanese)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2026-03-15 02:10:11 +01:00
Sylvia van OsandGitHub f221b01811 Merge pull request #3056 from CatimaLoyalty/dependabot/gradle/gradle-wrapper-9.4.0
Bump gradle-wrapper from 9.3.1 to 9.4.0
2026-03-13 20:09:52 +01:00
Sylvia van OsandGitHub 236e9e60da Merge pull request #3057 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-03-13 19:38:35 +01:00
Thu Trang PhamandHosted Weblate 40cfb3d111 Translated using Weblate (Vietnamese)
Currently translated at 87.9% (277 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/vi/
2026-03-13 15:11:56 +01:00
Arif BudimanandHosted Weblate 05190e6384 Translated using Weblate (Indonesian)
Currently translated at 95.0% (154 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/id/
2026-03-13 15:11:56 +01:00
dependabot[bot]andGitHub 77ab8ed85b Bump gradle-wrapper from 9.3.1 to 9.4.0
Bumps gradle-wrapper from 9.3.1 to 9.4.0.

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-12 02:53:34 +00:00
Sylvia van OsandGitHub 059cf5e631 Merge pull request #3055 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-03-11 16:23:18 +01:00
claudio.arseniandHosted Weblate b304c7ebe8 Translated using Weblate (Italian)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2026-03-11 14:10:13 +00:00
claudio.arseniandHosted Weblate 9b097d3a42 Translated using Weblate (Italian)
Currently translated at 97.5% (158 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/it/
2026-03-11 14:10:10 +00:00
AhmedAwadandHosted Weblate 4dc2e1f262 Translated using Weblate (Arabic)
Currently translated at 41.9% (68 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ar/
2026-03-11 14:09:56 +00:00
claudio.arseniandHosted Weblate 6a10cdd8cd Translated using Weblate (Italian)
Currently translated at 100.0% (1 of 1 strings)

Translation: Catima/Android (Debug)
Translate-URL: https://hosted.weblate.org/projects/catima/android-debug/it/
2026-03-11 14:09:48 +00:00
Sylvia van OsandGitHub 7febdf13b7 Merge pull request #3054 from CatimaLoyalty/dependabot/gradle/androidx.compose-compose-bom-2026.02.01
Bump androidx.compose:compose-bom from 2026.02.00 to 2026.02.01
2026-03-09 07:04:54 +01:00
dependabot[bot]andGitHub 47b0b678e4 Bump androidx.compose:compose-bom from 2026.02.00 to 2026.02.01
Bumps androidx.compose:compose-bom from 2026.02.00 to 2026.02.01.

---
updated-dependencies:
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2026.02.01
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 02:52:31 +00:00
Sylvia van OsandGitHub 4e21c606ef Merge pull request #3053 from CatimaLoyalty/create-pull-request/patch-1772988807
Update feature graphic
2026-03-08 17:53:53 +01:00
TheLastProjectandgithub-actions[bot] aef143256d Update feature graphic 2026-03-08 16:53:26 +00:00
Sylvia van OsandGitHub 7c76bd91b4 Merge pull request #3052 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-03-08 17:51:50 +01:00
Sylvia van OsandHosted Weblate 2ddce7b69b Translated using Weblate (Serbian)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sr/
2026-03-08 17:51:24 +01:00
Michał KorczakandHosted Weblate 73df916b86 Translated using Weblate (Polish)
Currently translated at 99.3% (161 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2026-03-08 17:10:10 +01:00
Zoran JeremićandHosted Weblate 85285a2005 Translated using Weblate (Serbian)
Currently translated at 3.0% (5 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sr/
2026-03-08 17:10:08 +01:00
Zoran JeremićandHosted Weblate 5a3b90ec12 Translated using Weblate (Serbian)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sr/
2026-03-08 17:10:04 +01:00
LepusOLandHosted Weblate 13bc0b7d46 Translated using Weblate (German)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-03-08 17:10:01 +01:00
Sylvia van OsandGitHub 814e11a153 Merge pull request #3051 from CatimaLoyalty/create-pull-request/patch-1772946485
Update contributors
2026-03-08 10:44:06 +01:00
TheLastProjectandgithub-actions[bot] 262988a0f8 Update contributors 2026-03-08 05:08:05 +00:00
Sylvia van Os 4376bf069c Release Catima 2.41.7 2026-03-06 10:51:42 +01:00
Sylvia van OsandGitHub 995d75c95a Merge pull request #3048 from CatimaLoyalty/dependabot/github_actions/actions/upload-artifact-7.0.0
Bump actions/upload-artifact from 6.0.0 to 7.0.0
2026-03-05 09:16:53 +01:00
dependabot[bot]andGitHub f9773de1d8 Bump actions/upload-artifact from 6.0.0 to 7.0.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6.0.0...v7.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-05 02:52:50 +00:00
Sylvia van OsandGitHub c46cc41bc4 Merge pull request #3046 from CatimaLoyalty/translation_updates
Translation updates
2026-03-04 18:48:40 +01:00
FjuroandSylvia van Os bcef06a1f1 Translated using Weblate (Czech)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2026-03-04 15:31:18 +01:00
B o d oandSylvia van Os 45941784a8 Translated using Weblate (German)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-03-04 15:31:03 +01:00
Sylvain PichonandSylvia van Os d646853c2e Translated using Weblate (French)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2026-03-04 15:30:54 +01:00
大王叫我来巡山andSylvia van Os eedc3ccc69 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2026-03-04 15:30:42 +01:00
c4tb3rtandSylvia van Os 036a9a12cc Translated using Weblate (Dutch)
Currently translated at 66.0% (107 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2026-03-04 15:30:04 +01:00
남현정andSylvia van Os 59d8746cb3 Translated using Weblate (Korean)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ko/
2026-03-04 15:29:29 +01:00
Sylvia van OsandGitHub cf8e266c4c Merge pull request #3041 from CatimaLoyalty/create-pull-request/patch-1772342234
Update contributors
2026-03-01 09:09:33 +01:00
TheLastProjectandgithub-actions[bot] 363c53ede2 Update contributors 2026-03-01 05:17:14 +00:00
Sylvia van OsandGitHub f9175ffd03 Merge pull request #3040 from CatimaLoyalty/create-pull-request/patch-1772283383
Update Fastlane changelogs
2026-02-28 13:57:19 +01:00
TheLastProjectandgithub-actions[bot] 041b2538d3 Update Fastlane changelogs 2026-02-28 12:56:22 +00:00
Sylvia van Os 58daf0993b Update CHANGELOG 2026-02-28 13:56:11 +01:00
Sylvia van OsandGitHub 2be75b28d9 Merge pull request #3039 from CatimaLoyalty/fix/3038
Fix shortcut returning to previous card after rotation
2026-02-28 13:55:51 +01:00
Sylvia van Os 3e9549088a Fix shortcut returning to previous card after rotation 2026-02-28 13:29:04 +01:00
il-Luca 0428a87c71 Add option to pause NFC while viewing a card 2026-02-27 12:37:19 +01:00
Sylvia van OsandGitHub 9121df192d Merge pull request #3036 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-02-26 11:14:34 +01:00
Pjetur G. HjaltasonandHosted Weblate b63d85d341 Translated using Weblate (Icelandic)
Currently translated at 38.4% (121 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/is/
2026-02-26 07:09:51 +01:00
AlexanderandHosted Weblate 423a762cad Translated using Weblate (Icelandic)
Currently translated at 38.4% (121 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/is/
2026-02-26 07:09:46 +01:00
Sean YoungandHosted Weblate 2c68d395f7 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hant/
2026-02-24 18:10:22 +01:00
Belkin FahriandHosted Weblate b1132c901a Translated using Weblate (Bulgarian)
Currently translated at 99.3% (161 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/bg/
2026-02-24 18:10:15 +01:00
E. Ta.andHosted Weblate db1fd83c8b Translated using Weblate (Arabic)
Currently translated at 99.6% (314 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ar/
2026-02-24 18:09:49 +01:00
Arne Martinus LindstadandHosted Weblate 4592ba8d38 Translated using Weblate (Norwegian Bokmål)
Currently translated at 81.2% (256 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2026-02-24 18:09:47 +01:00
E. Ta.andHosted Weblate 05951c0776 Translated using Weblate (Arabic)
Currently translated at 99.6% (314 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ar/
2026-02-23 16:12:24 +01:00
E SaandHosted Weblate 070c010156 Translated using Weblate (Arabic)
Currently translated at 99.6% (314 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ar/
2026-02-23 16:12:23 +01:00
Ngô Quang ĐạiandHosted Weblate 3e0741b6d8 Translated using Weblate (Vietnamese)
Currently translated at 83.4% (263 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/vi/
2026-02-23 16:12:22 +01:00
Michał KorczakandHosted Weblate 8ec0e3348b Translated using Weblate (Polish)
Currently translated at 99.3% (161 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2026-02-23 16:12:21 +01:00
Sylvia van OsandGitHub e81a63c695 Merge pull request #3035 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-02-22 09:22:08 +01:00
Sylvia van OsandGitHub d41976829a Merge pull request #3034 from CatimaLoyalty/create-pull-request/patch-1771737337
Update contributors
2026-02-22 09:20:08 +01:00
Rostyslav SheshenyaandHosted Weblate 09589ec899 Translated using Weblate (Russian)
Currently translated at 95.6% (155 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2026-02-22 07:10:11 +01:00
TheLastProjectandgithub-actions[bot] 9553f1ca74 Update contributors 2026-02-22 05:15:37 +00:00
Sylvia van OsandGitHub 2d91e87ca0 Merge pull request #3032 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-02-19 19:26:57 +01:00
Sylvain PichonandHosted Weblate 1cd09bace7 Translated using Weblate (French)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2026-02-19 14:11:12 +00:00
B o d oandHosted Weblate a896464e4c Translated using Weblate (German)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-02-19 14:11:11 +00:00
Liner SevenandHosted Weblate 052d492f59 Translated using Weblate (Japanese)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2026-02-19 14:11:11 +00:00
Edgars AndersonsandHosted Weblate d0fc8c3f73 Translated using Weblate (Latvian)
Currently translated at 12.3% (20 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lv/
2026-02-19 14:11:10 +00:00
FjuroandHosted Weblate 02322e50ab Translated using Weblate (Czech)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2026-02-19 14:11:09 +00:00
Josefina VellozoandHosted Weblate 029aa6d466 Translated using Weblate (Spanish (Argentina))
Currently translated at 83.8% (264 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es_AR/
2026-02-19 14:11:08 +00:00
大王叫我来巡山andHosted Weblate 9fbbb43ec1 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2026-02-19 14:11:07 +00:00
Sylvia van OsandGitHub 8429539527 Merge pull request #3031 from CatimaLoyalty/dependabot/gradle/androidx.compose-compose-bom-2026.02.00
Bump androidx.compose:compose-bom from 2026.01.01 to 2026.02.00
2026-02-19 09:43:29 +01:00
Sylvia van OsandGitHub 935f878688 Merge pull request #3030 from CatimaLoyalty/dependabot/gradle/net.lingala.zip4j-zip4j-2.11.6
Bump net.lingala.zip4j:zip4j from 2.11.5 to 2.11.6
2026-02-19 09:40:48 +01:00
dependabot[bot]andGitHub 5596d1f8a6 Bump androidx.compose:compose-bom from 2026.01.01 to 2026.02.00
Bumps androidx.compose:compose-bom from 2026.01.01 to 2026.02.00.

---
updated-dependencies:
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2026.02.00
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-19 02:53:44 +00:00
dependabot[bot]andGitHub 00028248c0 Bump net.lingala.zip4j:zip4j from 2.11.5 to 2.11.6
Bumps [net.lingala.zip4j:zip4j](https://github.com/srikanth-lingala/zip4j) from 2.11.5 to 2.11.6.
- [Release notes](https://github.com/srikanth-lingala/zip4j/releases)
- [Commits](https://github.com/srikanth-lingala/zip4j/compare/v2.11.5...v2.11.6)

---
updated-dependencies:
- dependency-name: net.lingala.zip4j:zip4j
  dependency-version: 2.11.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-19 02:52:51 +00:00
Sylvia van OsandGitHub 75feb7b409 Merge pull request #3029 from CatimaLoyalty/create-pull-request/patch-1771423966
Update feature graphic
2026-02-18 15:14:00 +01:00
TheLastProjectandgithub-actions[bot] 5fefe96963 Update feature graphic 2026-02-18 14:12:45 +00:00
Sylvia van OsandGitHub 7572cdb25d Merge pull request #3027 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-02-18 15:11:04 +01:00
Michał KorczakandHosted Weblate 77834809f6 Translated using Weblate (Polish)
Currently translated at 99.3% (161 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2026-02-18 14:11:14 +01:00
Igor WandHosted Weblate b72edac261 Translated using Weblate (Polish)
Currently translated at 99.3% (161 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2026-02-18 14:11:12 +01:00
m45kedandHosted Weblate 4c4623d92d Translated using Weblate (Polish)
Currently translated at 99.3% (161 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2026-02-18 14:11:11 +01:00
GnebandHosted Weblate 162b51f1fa Translated using Weblate (Slovak)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sk/
2026-02-18 14:11:09 +01:00
Sylvia van OsandGitHub f667c4f3ad Merge pull request #3025 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-02-15 12:30:59 +01:00
Sylvia van OsandGitHub b291f6b26c Merge pull request #3026 from CatimaLoyalty/create-pull-request/patch-1771155005
Update Fastlane changelogs
2026-02-15 12:30:32 +01:00
TheLastProjectandgithub-actions[bot] a679acfb6a Update Fastlane changelogs 2026-02-15 11:30:05 +00:00
Sylvia van Os c546294cd9 Update CHANGELOG 2026-02-15 12:29:53 +01:00
Sylvia van OsandGitHub 474c6e2112 Merge pull request #3024 from CatimaLoyalty/fix/3023
Fix crash in volume button card switching with no other cards
2026-02-15 12:25:01 +01:00
ssantosandHosted Weblate dbce80b4fa Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_PT/
2026-02-15 11:09:55 +00:00
Sylvia van Os 4399cdadb0 Fix crash in volume button card switching with no other cards 2026-02-15 11:49:58 +01:00
Sylvia van OsandGitHub 14e7f5bbb8 Merge pull request #3020 from CatimaLoyalty/dependabot/gradle/org.jetbrains.kotlin.plugin.compose-2.3.10
Bump org.jetbrains.kotlin.plugin.compose from 2.3.0 to 2.3.10
2026-02-12 08:47:01 +01:00
dependabot[bot]andGitHub 3440017db8 Bump org.jetbrains.kotlin.plugin.compose from 2.3.0 to 2.3.10
Bumps [org.jetbrains.kotlin.plugin.compose](https://github.com/JetBrains/kotlin) from 2.3.0 to 2.3.10.
- [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.3.0...v2.3.10)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin.plugin.compose
  dependency-version: 2.3.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-12 07:28:44 +00:00
Sylvia van OsandGitHub 1223b2b4eb Merge pull request #3019 from CatimaLoyalty/dependabot/gradle/org.jetbrains.kotlin.android-2.3.10
Bump org.jetbrains.kotlin.android from 2.3.0 to 2.3.10
2026-02-12 08:27:54 +01:00
dependabot[bot]andGitHub 731fbc3083 Bump org.jetbrains.kotlin.android from 2.3.0 to 2.3.10
Bumps [org.jetbrains.kotlin.android](https://github.com/JetBrains/kotlin) from 2.3.0 to 2.3.10.
- [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.3.0...v2.3.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-12 02:54:16 +00:00
Sylvia van OsandGitHub bb9487b35e Merge pull request #3018 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-02-11 14:31:30 +01:00
f4n0andHosted Weblate 43ea2a0f84 Translated using Weblate (Italian)
Currently translated at 97.5% (158 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/it/
2026-02-09 22:10:19 +01:00
Sylvia van OsandGitHub f5606aea09 Merge pull request #3016 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-02-08 23:28:06 +01:00
Sylvia van OsandGitHub 2d10d080a4 Delete gradle wrapper workflow
Dependabot now does this
2026-02-08 23:17:24 +01:00
Sylvia van OsandGitHub e36c007176 Merge pull request #3013 from CatimaLoyalty/dependabot/gradle/gradle-wrapper-9.3.1
Bump gradle-wrapper from 9.3.0 to 9.3.1
2026-02-08 23:16:24 +01:00
Wiesław FijołekandHosted Weblate 60c2784643 Translated using Weblate (Polish)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2026-02-08 10:11:36 +00:00
Wiesław FijołekandHosted Weblate 49d91caafd Translated using Weblate (Polish)
Currently translated at 98.1% (159 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2026-02-08 10:11:35 +00:00
Plamen NazarovandHosted Weblate 6c4e9fc29b Translated using Weblate (Bulgarian)
Currently translated at 99.3% (161 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/bg/
2026-02-08 10:11:33 +00:00
Plamen NazarovandHosted Weblate e49fa835a8 Translated using Weblate (Bulgarian)
Currently translated at 90.7% (147 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/bg/
2026-02-08 10:11:30 +00:00
Sylvia van OsandGitHub ab1e7c623f Merge pull request #3017 from CatimaLoyalty/create-pull-request/patch-1770528158
Update contributors
2026-02-08 10:50:53 +01:00
TheLastProjectandgithub-actions[bot] 725ce14ed6 Update contributors 2026-02-08 05:22:38 +00:00
Sylvia van OsandGitHub a154445723 Merge pull request #3014 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-02-06 10:05:16 +01:00
Plamen NazarovandHosted Weblate b09e49ddae Translated using Weblate (Bulgarian)
Currently translated at 50.0% (81 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/bg/
2026-02-06 06:03:21 +01:00
امیرضاandHosted Weblate f94f39a31c Translated using Weblate (Persian)
Currently translated at 83.1% (262 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fa/
2026-02-06 06:03:19 +01:00
Sean YoungandHosted Weblate a4172acdf2 Translated using Weblate (Japanese)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2026-02-06 06:03:18 +01:00
Sean YoungandHosted Weblate 8823762b16 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hant/
2026-02-06 06:03:18 +01:00
امیرضاandHosted Weblate 5b9f44e73d Translated using Weblate (Persian)
Currently translated at 40.7% (66 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fa/
2026-02-06 06:03:17 +01:00
dependabot[bot]andGitHub af1b5f135b Bump gradle-wrapper from 9.3.0 to 9.3.1
Bumps gradle-wrapper from 9.3.0 to 9.3.1.

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-06 02:53:56 +00:00
Sylvia van OsandGitHub 14399f46cf Merge pull request #3012 from CatimaLoyalty/dependabot/gradle/androidx.compose-compose-bom-2026.01.01
Bump androidx.compose:compose-bom from 2026.01.00 to 2026.01.01
2026-02-05 08:31:04 +01:00
dependabot[bot]andGitHub eccd154046 Bump androidx.compose:compose-bom from 2026.01.00 to 2026.01.01
Bumps androidx.compose:compose-bom from 2026.01.00 to 2026.01.01.

---
updated-dependencies:
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2026.01.01
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-05 02:53:20 +00:00
Sylvia van OsandGitHub e7e789aadb Merge pull request #3010 from CatimaLoyalty/create-pull-request/patch-1770232751
Update feature graphic
2026-02-04 22:07:25 +01:00
TheLastProjectandgithub-actions[bot] 1f171427fc Update feature graphic 2026-02-04 19:19:11 +00:00
Sylvia van OsandGitHub 1cf054f50c Merge pull request #3009 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-02-04 20:17:18 +01:00
Hosted Weblate e824c3830e Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/
2026-02-04 17:05:20 +00:00
LepusOLandHosted Weblate b93b0eaef7 Translated using Weblate (German)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-02-04 17:05:19 +00:00
LepusOLandHosted Weblate be4c4e7455 Translated using Weblate (Hungarian)
Currently translated at 28.3% (46 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/hu/
2026-02-04 17:05:18 +00:00
தமிழ்நேரம்andHosted Weblate 5dbb53f053 Translated using Weblate (Tamil)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ta/
2026-02-04 17:05:18 +00:00
தமிழ்நேரம்andHosted Weblate 550946be5e Translated using Weblate (Tamil)
Currently translated at 92.5% (150 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ta/
2026-02-04 17:05:17 +00:00
Plamen NazarovandHosted Weblate 8819e2dddc Translated using Weblate (Bulgarian)
Currently translated at 41.9% (68 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/bg/
2026-02-04 17:05:13 +00:00
Sylvia van OsandGitHub ae14412385 Merge pull request #3008 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-02-02 10:05:42 +01:00
TC0452andHosted Weblate 276a731f53 Translated using Weblate (Polish)
Currently translated at 99.6% (314 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2026-02-01 10:02:07 +01:00
Jiri GrönroosandHosted Weblate 6873c8bc72 Translated using Weblate (Finnish)
Currently translated at 98.7% (311 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fi/
2026-02-01 10:02:06 +01:00
Mika LatvalaandHosted Weblate 13a3a85916 Translated using Weblate (Finnish)
Currently translated at 98.7% (311 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fi/
2026-02-01 10:02:05 +01:00
Sylvia van OsandGitHub 79c25e5dd4 Merge pull request #3007 from CatimaLoyalty/create-pull-request/patch-1769923341
Update contributors
2026-02-01 06:58:07 +01:00
TheLastProjectandgithub-actions[bot] 05e1710953 Update contributors 2026-02-01 05:22:21 +00:00
Sylvia van OsandGitHub 756698bda6 Merge pull request #3006 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-31 18:46:10 +01:00
I.H.andHosted Weblate 8965acb17c Translated using Weblate (Dutch)
Currently translated at 61.7% (100 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2026-01-30 18:02:06 +00:00
大王叫我来巡山andHosted Weblate 87e6fc3d03 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2026-01-30 18:02:05 +00:00
Jiri GrönroosandHosted Weblate 438959e42b Translated using Weblate (Finnish)
Currently translated at 97.4% (307 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fi/
2026-01-30 18:02:04 +00:00
Sylvia van OsandGitHub c5713c6079 Merge pull request #3003 from CatimaLoyalty/dependabot/github_actions/peter-evans/create-pull-request-8.1.0
Bump peter-evans/create-pull-request from 8.0.0 to 8.1.0
2026-01-29 19:37:21 +01:00
Sylvia van OsandGitHub 2251df5a6f Merge pull request #3004 from CatimaLoyalty/dependabot/github_actions/actions/setup-python-6.2.0
Bump actions/setup-python from 6.1.0 to 6.2.0
2026-01-29 08:52:17 +01:00
dependabot[bot]andGitHub 4474aee7fa Bump actions/setup-python from 6.1.0 to 6.2.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v6.1.0...v6.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-29 02:53:49 +00:00
dependabot[bot]andGitHub 4dcc637065 Bump peter-evans/create-pull-request from 8.0.0 to 8.1.0
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 8.0.0 to 8.1.0.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v8.0.0...v8.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-29 02:53:08 +00:00
Sylvia van OsandGitHub d793eec2e0 Merge pull request #3002 from CatimaLoyalty/dependabot/gradle/org.robolectric-robolectric-4.16.1
Bump org.robolectric:robolectric from 4.16 to 4.16.1
2026-01-28 08:49:43 +01:00
dependabot[bot]andGitHub 3ece1c3636 Bump org.robolectric:robolectric from 4.16 to 4.16.1
Bumps [org.robolectric:robolectric](https://github.com/robolectric/robolectric) from 4.16 to 4.16.1.
- [Release notes](https://github.com/robolectric/robolectric/releases)
- [Commits](https://github.com/robolectric/robolectric/compare/robolectric-4.16...robolectric-4.16.1)

---
updated-dependencies:
- dependency-name: org.robolectric:robolectric
  dependency-version: 4.16.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-28 02:53:33 +00:00
Sylvia van OsandGitHub c135cad669 Merge pull request #3000 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-27 17:44:35 +01:00
Edgars AndersonsandHosted Weblate 3d9e636cc3 Translated using Weblate (Latvian)
Currently translated at 12.3% (20 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lv/
2026-01-27 15:40:59 +00:00
Sylvia van OsandGitHub b64c4966e0 Merge pull request #2998 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-27 08:29:49 +01:00
Sylvain PichonandHosted Weblate 7695fe8659 Translated using Weblate (French)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2026-01-27 04:00:55 +01:00
Sylvia van OsandGitHub ad7e021928 Merge pull request #2997 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-25 12:25:22 +01:00
FjuroandHosted Weblate 73233ad6de Translated using Weblate (Czech)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2026-01-25 12:08:48 +01:00
Sylvia van OsandGitHub 3e96c793b1 Merge pull request #2996 from CatimaLoyalty/create-pull-request/patch-1769335330
Update contributors
2026-01-25 11:11:50 +01:00
TheLastProjectandgithub-actions[bot] 6d10a87efc Update contributors 2026-01-25 10:02:10 +00:00
Sylvia van OsandGitHub 0228b7d0a4 Fix typo 2026-01-25 10:59:02 +01:00
Sylvia van OsandGitHub 798377416d Merge pull request #2995 from CatimaLoyalty/TheLastProject-patch-1
Remove gradle-update-robot from contributors
2026-01-25 10:53:00 +01:00
Sylvia van OsandGitHub 9b64c65816 Merge pull request #2993 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-25 10:31:08 +01:00
Sylvia van OsandGitHub 2030fe4461 Remove gradle-update-robot from contributors 2026-01-25 10:28:54 +01:00
B o d oandHosted Weblate c8f9fb8bac Translated using Weblate (German)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-01-25 08:55:26 +01:00
Liner SevenandHosted Weblate cce698578e Translated using Weblate (Japanese)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2026-01-25 08:55:20 +01:00
Максим ГорпинічandHosted Weblate 2bf8146489 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/uk/
2026-01-25 08:55:10 +01:00
大王叫我来巡山andHosted Weblate 7148477209 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (162 of 162 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2026-01-25 08:55:00 +01:00
大王叫我来巡山andHosted Weblate fe0e68ab37 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (161 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2026-01-25 00:43:30 +00:00
Максим ГорпинічandHosted Weblate 24420db4c4 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (161 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/uk/
2026-01-25 00:43:30 +00:00
Sylvain PichonandHosted Weblate 2a0e067472 Translated using Weblate (French)
Currently translated at 100.0% (161 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2026-01-25 00:43:29 +00:00
ResandHosted Weblate 8126ffc980 Translated using Weblate (Indonesian)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/id/
2026-01-25 00:43:28 +00:00
Sylvia van OsandGitHub 9d530e095a Merge pull request #2992 from CatimaLoyalty/create-pull-request/patch-1769269531
Update Fastlane changelogs
2026-01-24 16:57:52 +01:00
TheLastProjectandgithub-actions[bot] 8dd8ed45b4 Update Fastlane changelogs 2026-01-24 15:45:31 +00:00
Sylvia van OsandGitHub c825d91779 Merge pull request #2989 from CatimaLoyalty/revert-2984-sadness/revertAndroid5RemovalRevert
Revert "Reapply "Temporarily re-add Android 5 support (#2925)""
2026-01-24 16:45:21 +01:00
Sylvia van Os 38a2c3f26b Revert "Reapply "Temporarily re-add Android 5 support (#2925)"" 2026-01-24 16:24:13 +01:00
Sylvia van OsandGitHub 3b8aba29d5 Merge pull request #2990 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-24 08:49:39 +01:00
B o d oandHosted Weblate 3aa77e5022 Translated using Weblate (German)
Currently translated at 100.0% (161 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-01-24 03:23:40 +01:00
FjuroandHosted Weblate 543bacf448 Translated using Weblate (Czech)
Currently translated at 100.0% (161 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2026-01-24 03:23:40 +01:00
Sylvia van Os 4518dadcf6 Release Catima 2.41.6 2026-01-23 19:44:27 +01:00
Sylvia van OsandGitHub f07b7412f5 Merge pull request #2987 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-23 19:42:53 +01:00
Sylvia van OsandHosted Weblate 474dc0e5d6 Translated using Weblate (Czech)
Currently translated at 99.3% (160 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2026-01-23 19:29:05 +01:00
Sylvia van OsandHosted Weblate 202cb91ac3 Translated using Weblate (Bosnian)
Currently translated at 99.6% (314 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bs/
2026-01-23 19:29:02 +01:00
19atlasandHosted Weblate 4e2672d9ae Translated using Weblate (Turkish)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/tr/
2026-01-23 19:25:01 +01:00
pacavarnandHosted Weblate b281bdf392 Translated using Weblate (Bosnian)
Currently translated at 53.4% (86 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/bs/
2026-01-23 19:25:00 +01:00
pacavarnandHosted Weblate 168245c3c1 Translated using Weblate (Bosnian)
Currently translated at 99.6% (314 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bs/
2026-01-23 19:25:00 +01:00
Sylvia van Os 6d1856ceba Remove Android 5 support removal note from translations 2026-01-23 18:49:13 +01:00
Sylvia van OsandGitHub dc58e89a0d Merge pull request #2986 from CatimaLoyalty/create-pull-request/patch-1769190474
Update Fastlane changelogs
2026-01-23 18:48:07 +01:00
TheLastProjectandgithub-actions[bot] 16f4866c38 Update Fastlane changelogs 2026-01-23 17:47:53 +00:00
Sylvia van OsandGitHub 93492828dc Merge pull request #2984 from CatimaLoyalty/sadness/revertAndroid5RemovalRevert
Reapply "Temporarily re-add Android 5 support (#2925)"
2026-01-23 18:47:42 +01:00
Sylvia van OsandGitHub e6fb6fd95b Merge pull request #2985 from CatimaLoyalty/create-pull-request/patch-1769189547
Update Fastlane changelogs
2026-01-23 18:35:14 +01:00
TheLastProjectandgithub-actions[bot] 57b2f308da Update Fastlane changelogs 2026-01-23 17:32:27 +00:00
Sylvia van Os 93d259b1d8 Reapply "Temporarily re-add Android 5 support (#2925)"
This reverts commit 5ac2405834.
2026-01-23 18:32:22 +01:00
Sylvia van Os daf447cbdd Update CHANGELOG 2026-01-23 18:32:12 +01:00
Sylvia van OsandGitHub 9e438e1498 Merge pull request #2983 from davkenn/fix/properly-loading-barcode-adapter
fix: BarcodeSelectorAdapter properly fills adapter with barcode images instead of passing in null
2026-01-23 18:26:43 +01:00
Sylvia van Os ce213356b9 Add FIXME 2026-01-23 18:24:17 +01:00
Sylvia van OsandGitHub f5fa11dcc3 Merge pull request #2982 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-23 18:11:48 +01:00
B o d oandHosted Weblate 841902bcaf Translated using Weblate (German)
Currently translated at 100.0% (161 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-01-23 11:45:32 +01:00
大王叫我来巡山andHosted Weblate 3de83b6745 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (161 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2026-01-23 11:45:29 +01:00
David Kennedy 18ae00f5a4 fix: passes ISO_8859_1 to BarcodeImageWriterTask constructor instead of null 2026-01-22 18:40:56 -07:00
pacavarnandHosted Weblate 8639bf98f4 Translated using Weblate (Bosnian)
Currently translated at 57.4% (181 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bs/
2026-01-23 00:48:39 +00:00
pacavarnandHosted Weblate 543c5f9b23 Translated using Weblate (Bosnian)
Currently translated at 52.1% (84 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/bs/
2026-01-23 00:48:38 +00:00
Sylvia van OsandGitHub 102ba18a05 Merge pull request #2980 from CatimaLoyalty/dependabot/gradle/androidx.compose-compose-bom-2026.01.00
Bump androidx.compose:compose-bom from 2025.11.01 to 2026.01.00
2026-01-22 21:44:31 +01:00
dependabot[bot]andGitHub 54e0ed3f82 Bump androidx.compose:compose-bom from 2025.11.01 to 2026.01.00
Bumps androidx.compose:compose-bom from 2025.11.01 to 2026.01.00.

---
updated-dependencies:
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2026.01.00
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-22 19:38:23 +00:00
Sylvia van OsandGitHub d86b0043ff Merge pull request #2981 from CatimaLoyalty/create-pull-request/patch-1769110652
Update Fastlane changelogs
2026-01-22 20:37:52 +01:00
TheLastProjectandgithub-actions[bot] 1a214e6526 Update Fastlane changelogs 2026-01-22 19:37:32 +00:00
Sylvia van OsandGitHub af7cf16786 Merge pull request #2930 from CatimaLoyalty/revert-2925-temp/revertAndroid5Removal
Revert "Temporarily re-add Android 5 support"
2026-01-22 20:37:20 +01:00
Sylvia van Os 5ac2405834 Revert "Temporarily re-add Android 5 support (#2925)"
This reverts commit 37e6782032.
2026-01-22 20:13:37 +01:00
Sylvia van OsandGitHub 08325b8528 Merge pull request #2979 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-22 20:11:16 +01:00
Mauro F. T.andHosted Weblate 6573c5ea80 Translated using Weblate (Portuguese)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt/
2026-01-22 04:33:37 +01:00
Edgars AndersonsandHosted Weblate 77ca8e7069 Translated using Weblate (Latvian)
Currently translated at 12.4% (20 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lv/
2026-01-22 03:07:39 +01:00
Sylvia van OsandGitHub 3dbd99f052 Merge pull request #2976 from CatimaLoyalty/gradlew-update-9.3.0
Update Gradle Wrapper from 9.2.1 to 9.3.0
2026-01-21 18:45:58 +01:00
Sylvia van OsandGitHub 4d34794957 Merge pull request #2978 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-21 18:41:57 +01:00
Максим ГорпинічandHosted Weblate 8c99189d79 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (161 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/uk/
2026-01-21 15:04:02 +01:00
pacavarnandHosted Weblate 4dae3cb826 Translated using Weblate (Bosnian)
Currently translated at 42.8% (69 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/bs/
2026-01-21 15:04:01 +01:00
B o d oandHosted Weblate 52d4f63418 Translated using Weblate (German)
Currently translated at 100.0% (161 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-01-21 15:04:00 +01:00
FjuroandHosted Weblate 76e3ba2e4a Translated using Weblate (Czech)
Currently translated at 100.0% (161 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2026-01-21 15:03:59 +01:00
pacavarnandHosted Weblate 1bea439fa0 Translated using Weblate (Bosnian)
Currently translated at 42.2% (133 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bs/
2026-01-21 15:03:58 +01:00
Stefan RacicandHosted Weblate 36459d62b6 Translated using Weblate (Bosnian)
Currently translated at 42.2% (133 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bs/
2026-01-21 15:03:57 +01:00
slavkandHosted Weblate 3699a24809 Translated using Weblate (Croatian)
Currently translated at 99.3% (313 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/hr/
2026-01-21 15:03:56 +01:00
大王叫我来巡山andHosted Weblate fc8691470c Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (161 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2026-01-21 15:03:56 +01:00
Liner SevenandHosted Weblate a03be85474 Translated using Weblate (Japanese)
Currently translated at 100.0% (161 of 161 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2026-01-21 15:03:55 +01:00
Sylvia van OsandGitHub 701964bc46 Merge pull request #2977 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-20 16:59:28 +01:00
தமிழ்நேரம்andHosted Weblate 18604ba8bd Translated using Weblate (Tamil)
Currently translated at 92.3% (291 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ta/
2026-01-20 13:02:06 +00:00
Nicolas AignerandHosted Weblate b6b9b7c90e Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_BR/
2026-01-20 13:02:05 +00:00
GideonandHosted Weblate 5aa7217c1b Translated using Weblate (Dutch)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2026-01-20 13:02:04 +00:00
Aleś ŁahviniecandHosted Weblate 7fb5c96322 Translated using Weblate (Belarusian)
Currently translated at 90.4% (285 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/be/
2026-01-20 13:02:04 +00:00
gradle-update-robot 3d9729f992 Update Gradle Wrapper from 9.2.1 to 9.3.0
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2026-01-20 01:04:18 +00:00
Sylvia van OsandGitHub 8c61e32798 Merge pull request #2975 from CatimaLoyalty/TheLastProject-patch-1
Fix gradle wrapper update
2026-01-19 21:47:48 +01:00
Sylvia van OsandGitHub 0bb38e4fce Fix gradle wrapper update
https://github.com/gradle-update/update-gradle-wrapper-action/issues/988
2026-01-19 21:23:11 +01:00
Sylvia van OsandGitHub 9eb9e9df25 Merge pull request #2971 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-19 20:54:13 +01:00
Sylvia van OsandGitHub e8d7857044 Merge pull request #2974 from CatimaLoyalty/create-pull-request/patch-1768852113
Update Fastlane changelogs
2026-01-19 20:49:41 +01:00
TheLastProjectandgithub-actions[bot] 2c782fba62 Update Fastlane changelogs 2026-01-19 19:48:33 +00:00
Sylvia van Os 31cd7f009c Fix typo 2026-01-19 20:48:22 +01:00
Sylvia van Os 068c0307af Update CHANGELOG 2026-01-19 20:37:06 +01:00
Sylvia van OsandGitHub 54c885d1cf Merge pull request #2970 from CatimaLoyalty/feature/2969
Autolink in card ID dialog
2026-01-19 20:35:52 +01:00
scollovatiandHosted Weblate bbb434e2b5 Translated using Weblate (Italian)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2026-01-19 11:38:58 +01:00
tygyhandHosted Weblate bc879f847d Translated using Weblate (Swedish)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sv/
2026-01-19 00:01:58 +00:00
Sylvia van Os 1a474e9d22 Move info text dialog away from deprecated Linkify.ALL 2026-01-18 22:38:44 +01:00
Sylvia van Os cbb2453f6b Detect links in card ID in card ID popup 2026-01-18 22:37:56 +01:00
Sylvia van OsandGitHub e372d23b6e Merge pull request #2968 from CatimaLoyalty/create-pull-request/patch-1768711210
Update contributors
2026-01-18 09:33:30 +01:00
TheLastProjectandgithub-actions[bot] a3507d5975 Update contributors 2026-01-18 04:40:10 +00:00
Sylvia van Os c4f28c12f8 Revert "Update fastlane"
This reverts commit 69939d5957.

The new Fastlane version is broken on my system.
2026-01-17 19:50:59 +01:00
Sylvia van Os 69939d5957 Update fastlane 2026-01-17 18:54:57 +01:00
Sylvia van OsandGitHub c2b0fda80a Merge pull request #2966 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-17 12:48:14 +01:00
josé m.andHosted Weblate b62a0b0f4f Translated using Weblate (Galician)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/gl/
2026-01-17 08:01:55 +01:00
aliandHosted Weblate 5f9376ca63 Translated using Weblate (Arabic)
Currently translated at 99.6% (314 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ar/
2026-01-17 08:01:55 +01:00
Sylvia van Os f1087573dc Release Catima 2.41.5 2026-01-15 22:55:38 +01:00
Sylvia van OsandGitHub 62f188eb72 Merge pull request #2964 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-15 22:54:35 +01:00
Edgars AndersonsandHosted Weblate cdfa3e6779 Translated using Weblate (Latvian)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/lv/
2026-01-15 21:38:02 +00:00
Максим ГорпинічandHosted Weblate f64406f70f Translated using Weblate (Ukrainian)
Currently translated at 100.0% (160 of 160 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/uk/
2026-01-15 21:38:01 +00:00
Sylvia van OsandGitHub cabae400e6 Merge pull request #2963 from CatimaLoyalty/fix/pkpassEncodingCrashSafety
Fix crash risk on pkpass encoding
2026-01-15 20:43:32 +01:00
Sylvia van Os d5b3796ae9 Fix crash risk on pkpass encoding 2026-01-15 18:14:55 +01:00
Sylvia van OsandGitHub 442c7c28d4 Merge pull request #2959 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-14 22:07:58 +01:00
Sylvia van Os e7fd16dd45 Fix broken translations
This string no longer exists in the base translation
2026-01-14 21:52:21 +01:00
Sylvain PichonandHosted Weblate 868d065d5d Translated using Weblate (French)
Currently translated at 100.0% (160 of 160 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2026-01-14 20:55:39 +01:00
Liner SevenandHosted Weblate 185b0ff2df Translated using Weblate (Japanese)
Currently translated at 100.0% (160 of 160 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2026-01-14 17:10:08 +01:00
B o d oandHosted Weblate e60b1cc26a Translated using Weblate (German)
Currently translated at 100.0% (160 of 160 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-01-14 17:10:04 +01:00
Liner SevenandHosted Weblate 5cecad9088 Translated using Weblate (Japanese)
Currently translated at 100.0% (315 of 315 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2026-01-14 17:10:01 +01:00
大王叫我来巡山andHosted Weblate f26799c65c Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (160 of 160 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2026-01-14 17:10:00 +01:00
FjuroandHosted Weblate 6046b48285 Translated using Weblate (Czech)
Currently translated at 100.0% (160 of 160 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2026-01-14 17:09:57 +01:00
Sylvain PichonandHosted Weblate 4bf925f1b1 Translated using Weblate (French)
Currently translated at 100.0% (160 of 160 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2026-01-14 09:56:11 +01:00
Liner SevenandHosted Weblate fa55a8b354 Translated using Weblate (Japanese)
Currently translated at 99.3% (314 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2026-01-14 09:56:10 +01:00
Sylvain PichonandHosted Weblate 38a8f76b88 Translated using Weblate (French)
Currently translated at 100.0% (316 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2026-01-14 09:56:09 +01:00
ezn24andHosted Weblate dc574c0569 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (316 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hant/
2026-01-14 09:56:09 +01:00
solokotandHosted Weblate c52791e029 Translated using Weblate (Russian)
Currently translated at 100.0% (316 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2026-01-14 09:56:08 +01:00
Максим ГорпинічandHosted Weblate 37c31d8a0f Translated using Weblate (Ukrainian)
Currently translated at 100.0% (160 of 160 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/uk/
2026-01-14 09:56:07 +01:00
Максим ГорпинічandHosted Weblate 4e99654d1d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (316 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/uk/
2026-01-14 09:56:06 +01:00
大王叫我来巡山andHosted Weblate a9a84ffdfc Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (160 of 160 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2026-01-14 09:56:06 +01:00
Максим ГорпинічandHosted Weblate 9bb51da3c1 Translated using Weblate (Ukrainian)
Currently translated at 99.6% (315 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/uk/
2026-01-14 09:56:05 +01:00
FjuroandHosted Weblate 3f73224d97 Translated using Weblate (Czech)
Currently translated at 100.0% (160 of 160 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2026-01-14 09:56:04 +01:00
Damjan GerlandHosted Weblate b79c62341e Translated using Weblate (Slovenian)
Currently translated at 100.0% (316 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sl/
2026-01-14 09:56:03 +01:00
B o d oandHosted Weblate 33a47f2ed1 Translated using Weblate (German)
Currently translated at 100.0% (160 of 160 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-01-14 09:56:02 +01:00
大王叫我来巡山andHosted Weblate 3177e91e58 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (316 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hans/
2026-01-14 09:56:02 +01:00
Ahmed Al-MaharbiandHosted Weblate 3ec4ffcab2 Translated using Weblate (Arabic)
Currently translated at 43.2% (67 of 155 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ar/
2026-01-14 09:56:01 +01:00
Ahmed Al-MaharbiandHosted Weblate 2f8449bb04 Translated using Weblate (Arabic)
Currently translated at 98.4% (311 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ar/
2026-01-14 09:56:00 +01:00
Sylvia van OsandGitHub 011f3edcbc Merge pull request #2962 from CatimaLoyalty/create-pull-request/patch-1768338748
Update Fastlane changelogs
2026-01-13 23:17:23 +01:00
TheLastProjectandgithub-actions[bot] abb6762a4f Update Fastlane changelogs 2026-01-13 21:12:27 +00:00
Sylvia van Os 382a28c983 Update CHANGELOG 2026-01-13 22:12:15 +01:00
Sylvia van OsandGitHub c7a8d343b5 Merge pull request #2961 from CatimaLoyalty/feature/2890
Expose barcode encoding in content provider
2026-01-13 22:10:30 +01:00
Sylvia van Os bc21a53d44 Expose barcode encoding in content provider 2026-01-13 21:17:50 +01:00
Sylvia van Os d3bdad56e4 Update docs 2026-01-13 20:14:57 +01:00
Sylvia van OsandGitHub c9a2a4acbb Merge pull request #2960 from CatimaLoyalty/create-pull-request/patch-1768331285
Update Fastlane changelogs
2026-01-13 20:09:59 +01:00
TheLastProjectandgithub-actions[bot] eaf30e6c2d Update Fastlane changelogs 2026-01-13 19:08:04 +00:00
Sylvia van Os 5979160a6a Update CHANGELOG 2026-01-13 20:07:51 +01:00
Sylvia van OsandGitHub 0181fde243 Merge pull request #2957 from CatimaLoyalty/fix/2946
Remove automatic barcode encoding type
2026-01-13 20:06:33 +01:00
Sylvia van Os a5599dc673 Remove automatic barcode encoding type
Migrates all automatic cards to ISO-8859-1 and uses that as default
2026-01-13 19:47:29 +01:00
Sylvia van OsandGitHub 25290ae202 Merge pull request #2958 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-12 13:18:49 +01:00
LukasLKTandHosted Weblate 4d36812cd6 Translated using Weblate (Danish)
Currently translated at 46.2% (146 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/da/
2026-01-12 13:01:54 +01:00
Sylvia van OsandGitHub a9f402cd6d Merge pull request #2956 from CatimaLoyalty/create-pull-request/patch-1768106786
Update contributors
2026-01-11 09:51:24 +01:00
TheLastProjectandgithub-actions[bot] 2798ba7648 Update contributors 2026-01-11 04:46:26 +00:00
Sylvia van OsandGitHub 349558e62e Merge pull request #2955 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-10 14:32:30 +01:00
Hosted Weblate 91ca2a220e Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/
2026-01-10 11:04:49 +01:00
m45kedandHosted Weblate 93104e4b40 Translated using Weblate (Polish)
Currently translated at 98.0% (151 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2026-01-10 11:04:49 +01:00
m45kedandHosted Weblate d8adcdd1e5 Translated using Weblate (Polish)
Currently translated at 100.0% (313 of 313 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2026-01-10 11:04:48 +01:00
Maciej BłędkowskiandHosted Weblate c4b210a8db Translated using Weblate (Polish)
Currently translated at 100.0% (313 of 313 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2026-01-10 11:04:48 +01:00
Sylvia van OsandGitHub d54323fde0 Merge pull request #2954 from CatimaLoyalty/create-pull-request/patch-1767975957
Update Fastlane changelogs
2026-01-09 17:28:06 +01:00
TheLastProjectandgithub-actions[bot] c02be50242 Update Fastlane changelogs 2026-01-09 16:25:57 +00:00
Sylvia van Os a883ce0f43 Update CHANGELOG 2026-01-09 17:25:46 +01:00
Matthias PaulmierandGitHub ace353d71d Fix several bugs related to shortcut handling (#2919)
* Improve ShortcuHelper.updateShortcuts to take all actions into account

* Remove now useless calls to removeShortcut

* Add doc to explain the usage of maxShortcut

* Fix typo in doc of maxShortcuts
2026-01-09 17:24:29 +01:00
Sylvia van OsandGitHub 18dbb24375 Merge pull request #2953 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-09 08:18:47 +01:00
m45kedandHosted Weblate 78daf54716 Translated using Weblate (Polish)
Currently translated at 95.4% (147 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2026-01-09 05:56:05 +01:00
Sylvia van OsandGitHub ee6541ba54 Merge pull request #2952 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-08 16:42:28 +01:00
B o d oandHosted Weblate e4e9fe05e1 Translated using Weblate (German)
Currently translated at 100.0% (155 of 155 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-01-08 11:36:36 +00:00
Sylvia van OsandGitHub 9d4035c94e Merge pull request #2951 from CatimaLoyalty/create-pull-request/patch-1767812819
Update Fastlane changelogs
2026-01-07 20:09:53 +01:00
TheLastProjectandgithub-actions[bot] 293d38bd09 Update Fastlane changelogs 2026-01-07 19:06:59 +00:00
Sylvia van Os 28be05600b Update CHANGELOG 2026-01-07 20:06:46 +01:00
Sylvia van OsandGitHub 03649820ce Merge pull request #2950 from CatimaLoyalty/fix/2918
Set Compose TopAppBar to pure black in OLED theme
2026-01-07 20:05:51 +01:00
Sylvia van Os 93515d2f88 Set Compose TopAppBar to pure black in OLED theme 2026-01-07 17:17:49 +01:00
Sylvia van OsandGitHub d7dc70c0df Merge pull request #2949 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-06 17:50:23 +01:00
Francisco SerradorandHosted Weblate ce0738782a Translated using Weblate (Spanish)
Currently translated at 100.0% (316 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2026-01-06 13:02:02 +00:00
Sylvia van OsandGitHub df738b9c1d Merge pull request #2948 from CatimaLoyalty/create-pull-request/patch-1767646247
Update Fastlane changelogs
2026-01-05 21:52:18 +01:00
TheLastProjectandgithub-actions[bot] 8bf0033d22 Update Fastlane changelogs 2026-01-05 20:50:47 +00:00
Sylvia van Os 01e08e4928 Update CHANGELOG 2026-01-05 21:50:33 +01:00
Sylvia van OsandGitHub 90aee54a3c Merge pull request #2947 from CatimaLoyalty/fix/2945
Fix list widget opening on previous card sometimes
2026-01-05 21:49:50 +01:00
Sylvia van Os b7c42b5c8c Fix list widget opening on previous card sometimes 2026-01-05 20:20:41 +01:00
Sylvia van Os 008b5254c6 Release Catima 2.41.4 2026-01-04 23:17:37 +01:00
Sylvia van OsandGitHub 1b1163a100 Merge pull request #2941 from CatimaLoyalty/create-pull-request/patch-1767564871
Update Fastlane changelogs
2026-01-04 23:14:46 +01:00
TheLastProjectandgithub-actions[bot] 70c14514e6 Update Fastlane changelogs 2026-01-04 22:14:31 +00:00
Sylvia van Os 5830813170 Update CHANGELOG 2026-01-04 23:14:14 +01:00
Sylvia van OsandGitHub a07d4d1b8a Merge pull request #2940 from CatimaLoyalty/fix/disable_automatic_encoding
Disable automatic barcode encoding guessing for now
2026-01-04 23:12:57 +01:00
Sylvia van Os 29c4ccf4d9 Disable automatic barcode encoding guessing for now 2026-01-04 22:57:50 +01:00
Sylvia van Os 417006cfa3 Release Catima 2.41.3 2026-01-04 21:45:51 +01:00
Sylvia van OsandGitHub 907a8578ca Merge pull request #2938 from CatimaLoyalty/create-pull-request/patch-1767559150
Update Fastlane changelogs
2026-01-04 21:39:39 +01:00
TheLastProjectandgithub-actions[bot] 7e63babd47 Update Fastlane changelogs 2026-01-04 20:39:10 +00:00
Sylvia van Os c289bb0af3 Update CHANGELOG 2026-01-04 21:39:00 +01:00
Sylvia van OsandGitHub 80767e7cd6 Merge pull request #2937 from CatimaLoyalty/fix/enforce_iso_on_2.41.3_upgrade
On initial upgrade to 2.41.3, force all cards to ISO-8859-1
2026-01-04 21:38:11 +01:00
Sylvia van Os 90e6dd8738 Fix tests 2026-01-04 21:20:49 +01:00
Sylvia van Os d3de9c65e6 Force ISO-8859-1 when importing old backups
This will not catch one edge case: use exporting a backup in Catima
2.41.0 - 2.41.2, then uninstall Catima, then installing Catima 2.41.3
and then importing the database. But this seems rare enough to be
acceptable.
2026-01-04 21:20:49 +01:00
Sylvia van Os 48993f0486 On initial upgrade, force all automatic cards to ISO-8859-1
This fixes old pkpass files imported before Catima 2.41.0 to be forced
to "Automatic", which may guess UTF-8. New pkpass files will have the
encoding read from the file and newly scanned barcodes will use
Automatic.

This does have the unfortunate side effect of everyone who already
scanned a QR code with UTF-8 data since Catima 2.41.0 to have it forced
to ISO-8859-1, but it will fix Deutschlandtickets imported before 2.41.0
which is a rather large amount of the Catima userbase.
2026-01-04 21:20:44 +01:00
Sylvia van OsandGitHub 918c6fb41b Merge pull request #2936 from CatimaLoyalty/create-pull-request/patch-1767555122
Update Fastlane changelogs
2026-01-04 20:32:26 +01:00
TheLastProjectandgithub-actions[bot] d121841fdf Update Fastlane changelogs 2026-01-04 19:32:02 +00:00
Sylvia van Os 95ace2aa39 Update CHANGELOG 2026-01-04 20:31:51 +01:00
Sylvia van OsandGitHub b8cd52563d Merge pull request #2935 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-04 20:29:37 +01:00
Sylvia van OsandGitHub 70754af167 Merge pull request #2934 from CatimaLoyalty/fix/2932
Never pass ISO-8859-1 as encode hint
2026-01-04 20:29:24 +01:00
Sylvia van Os 80bad7ad56 Never pass ISO-8859-1 as encode hint
This fully restores the old behaviour for pkpass files with ISO-8859-1 as messageEncoding
2026-01-04 20:13:43 +01:00
Edgars AndersonsandHosted Weblate e214d6d69e Translated using Weblate (Latvian)
Currently translated at 100.0% (313 of 313 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/lv/
2026-01-04 20:02:09 +01:00
109247019824andHosted Weblate 0e7509d966 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (316 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bg/
2026-01-04 20:02:08 +01:00
FjuroandHosted Weblate 58a5b16484 Translated using Weblate (Czech)
Currently translated at 100.0% (316 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/cs/
2026-01-04 20:02:07 +01:00
B o d oandHosted Weblate a8df588b33 Translated using Weblate (German)
Currently translated at 100.0% (316 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2026-01-04 20:02:05 +01:00
Sylvia van OsandGitHub ef948f16f1 Merge pull request #2933 from CatimaLoyalty/create-pull-request/patch-1767502013
Update contributors
2026-01-04 09:50:08 +01:00
TheLastProjectandgithub-actions[bot] 11f942771f Update contributors 2026-01-04 04:46:53 +00:00
Sylvia van Os f507e15bfc Release Catima 2.41.2 2026-01-03 21:06:53 +01:00
Sylvia van OsandGitHub 64ebc345b5 Merge pull request #2929 from CatimaLoyalty/create-pull-request/patch-1767466255
Update feature graphic
2026-01-03 19:57:28 +01:00
Sylvia van OsandGitHub f94dbcd66d Merge pull request #2928 from CatimaLoyalty/create-pull-request/patch-1767466175
Update locales
2026-01-03 19:56:19 +01:00
TheLastProjectandgithub-actions[bot] 881303b73d Update feature graphic 2026-01-03 18:50:55 +00:00
TheLastProjectandgithub-actions[bot] 97a2ff7125 Update locales 2026-01-03 18:49:35 +00:00
Sylvia van OsandGitHub 22e968796d Merge pull request #2920 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-01-03 19:49:23 +01:00
Hosted Weblate 0c5f90d4ad Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/
2026-01-03 19:09:57 +01:00
Priit JõerüütandHosted Weblate 0f51e19d44 Translated using Weblate (Estonian)
Currently translated at 100.0% (316 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/et/
2026-01-03 19:09:57 +01:00
Priit JõerüütandHosted Weblate 5c720aa58a Translated using Weblate (Estonian)
Currently translated at 11.6% (18 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/et/
2026-01-03 19:09:57 +01:00
Sylvia van OsandHosted Weblate 19d7ef0669 Translated using Weblate (Persian (Old))
Currently translated at 99.0% (310 of 313 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/peo/
2026-01-03 19:09:57 +01:00
Hosted Weblate 04a22be542 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/
2026-01-03 19:09:57 +01:00
امیرضاandHosted Weblate 6ff2a9b48c Translated using Weblate (Persian)
Currently translated at 42.2% (65 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fa/
2026-01-03 19:09:57 +01:00
Simone DottoandHosted Weblate 19d3b736cb Translated using Weblate (Italian)
Currently translated at 100.0% (313 of 313 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2026-01-03 19:09:57 +01:00
Michael MoroniandHosted Weblate 2bcdf170ad Translated using Weblate (Italian)
Currently translated at 100.0% (313 of 313 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2026-01-03 19:09:57 +01:00
Simone DottoandHosted Weblate 23d79ba536 Translated using Weblate (Italian)
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/it/
2026-01-03 19:09:57 +01:00
Alì MortacciandHosted Weblate fe6de6f309 Translated using Weblate (Italian)
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/it/
2026-01-03 19:09:57 +01:00
امیرضاandHosted Weblate 29251978c4 Translated using Weblate (Persian (Old))
Currently translated at 99.3% (311 of 313 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/peo/
2026-01-03 19:09:57 +01:00
Sylvia van Os a10e8d5aab Fix typo in comment 2026-01-03 17:57:06 +01:00
Sylvia van OsandGitHub 5973761e2e Merge pull request #2926 from CatimaLoyalty/create-pull-request/patch-1767459283
Update Fastlane changelogs
2026-01-03 17:55:06 +01:00
TheLastProjectandgithub-actions[bot] 00f3fb47d5 Update Fastlane changelogs 2026-01-03 16:54:43 +00:00
Sylvia van OsandGitHub 37e6782032 Temporarily re-add Android 5 support (#2925)
* Revert "Bump androidx.activity:activity-compose from 1.10.1 to 1.12.2"

This reverts commit 307f0e12bc.

* Revert "Bump androidx.compose:compose-bom from 2025.11.01 to 2025.12.01"

This reverts commit a0fea79359.

* Revert "Remove Android 5 support"

This reverts commit 23457854b7.

* Update CHANGELOG
2026-01-03 17:54:33 +01:00
Sylvia van OsandGitHub f58bdba273 Merge pull request #2924 from CatimaLoyalty/create-pull-request/patch-1767452719
Update Fastlane changelogs
2026-01-03 16:07:00 +01:00
TheLastProjectandgithub-actions[bot] e02f74032b Update Fastlane changelogs 2026-01-03 15:05:19 +00:00
Sylvia van Os 6213bd4500 Update CHANGELOG 2026-01-03 16:05:07 +01:00
Sylvia van OsandGitHub 7739249364 Merge pull request #2923 from CatimaLoyalty/fix/2921
Only pass encoding hint in automatic if UTF-8
2026-01-03 16:04:12 +01:00
Sylvia van Os 2308deba33 Only pass encoding hint in automatic if UTF-8
If zxing is not explicitly told a barcode is UTF-8, it may render it
incorrectly. Which caused
https://github.com/CatimaLoyalty/Android/issues/2555.

However, when an encode hint is set, it will cause zxing to set an ECI
hint inside the barcode, which some scanners may trip over and cause
scanning failures, leading to
https://github.com/CatimaLoyalty/Android/issues/2921.

This change only passes the encoding in automatic mode if zxing
explicitly guesses it to be UTF-8, and otherwise doesn't pass anything,
to keep the ECI empty. This might need to be expanded for other types
like SJIS, but as nobody ever reported such a bug let's assume it's not
necessary for now.
2026-01-03 15:44:51 +01:00
Sylvia van OsandGitHub 8252f52862 Merge pull request #2922 from CatimaLoyalty/create-pull-request/patch-1767447624
Update Fastlane changelogs
2026-01-03 15:25:34 +01:00
TheLastProjectandgithub-actions[bot] 9b05d46c77 Update Fastlane changelogs 2026-01-03 13:40:24 +00:00
Sylvia van Os a8e9a932cb Update CHANGELOG 2026-01-03 14:40:13 +01:00
Sylvia van OsandGitHub d1858f22cf Merge pull request #2906 from CatimaLoyalty/dependabot/gradle/androidx.activity-activity-compose-1.12.2
Bump androidx.activity:activity-compose from 1.10.1 to 1.12.2
2026-01-01 21:43:29 +01:00
dependabot[bot]andGitHub 307f0e12bc Bump androidx.activity:activity-compose from 1.10.1 to 1.12.2
Bumps androidx.activity:activity-compose from 1.10.1 to 1.12.2.

---
updated-dependencies:
- dependency-name: androidx.activity:activity-compose
  dependency-version: 1.12.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-01 20:21:25 +00:00
Sylvia van OsandGitHub 3caf3ee0a8 Merge pull request #2905 from CatimaLoyalty/dependabot/gradle/androidx.compose-compose-bom-2025.12.01
Bump androidx.compose:compose-bom from 2025.11.01 to 2025.12.01
2026-01-01 21:20:19 +01:00
dependabot[bot]andGitHub a0fea79359 Bump androidx.compose:compose-bom from 2025.11.01 to 2025.12.01
Bumps androidx.compose:compose-bom from 2025.11.01 to 2025.12.01.

---
updated-dependencies:
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.12.01
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-01 19:57:58 +00:00
Sylvia van OsandGitHub b01e6b5928 Merge pull request #2917 from CatimaLoyalty/minSdk23
Remove Android 5 support
2026-01-01 20:47:34 +01:00
Sylvia van Os 23457854b7 Remove Android 5 support
Newer dependencies need at least Android 6, so no use in keeping this
2026-01-01 19:58:19 +01:00
Sylvia van Os 68d5dcd5f3 Release Catima 2.41.1 2025-12-31 17:51:13 +01:00
Sylvia van OsandGitHub d9905665b2 Merge pull request #2914 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-31 17:50:29 +01:00
B o d oandHosted Weblate 58af4bae55 Translated using Weblate (German)
Currently translated at 100.0% (316 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-12-31 17:08:27 +01:00
Sylvia van OsandHosted Weblate 9c5aefd8d1 Translated using Weblate (Spanish (Argentina))
Currently translated at 75.3% (238 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es_AR/
2025-12-31 17:08:27 +01:00
Sylvia van OsandGitHub bca0e07c23 Merge pull request #2913 from CatimaLoyalty/create-pull-request/patch-1767197144
Update Fastlane changelogs
2025-12-31 17:06:54 +01:00
TheLastProjectandgithub-actions[bot] 4676c2c2b3 Update Fastlane changelogs 2025-12-31 16:05:43 +00:00
Sylvia van Os 196542ce5e Update CHANGELOG 2025-12-31 17:05:32 +01:00
Sylvia van OsandGitHub a550bcca07 Merge pull request #2912 from CatimaLoyalty/fix/2911
Fix Compose status and navigation bar theming
2025-12-31 17:03:58 +01:00
Sylvia van Os 17d3d86127 Fix Compose status and navigation bar theming
Especially affects Android 14 and below, but also slightly 15 and up
2025-12-31 12:43:11 +01:00
Sylvia van Os 2c0efcb2c3 Release Catima 2.41.0 2025-12-30 17:47:51 +01:00
Sylvia van OsandGitHub 53f17731b3 Merge pull request #2909 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-30 17:45:31 +01:00
FjuroandHosted Weblate 5fb1ffebc1 Translated using Weblate (Czech)
Currently translated at 100.0% (155 of 155 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2025-12-30 16:10:35 +00:00
Azael857andHosted Weblate 9093eb9693 Translated using Weblate (Spanish (Argentina))
Currently translated at 75.3% (238 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es_AR/
2025-12-30 16:10:35 +00:00
Aliaksandr TrušandHosted Weblate 60ba0bb63e Translated using Weblate (Belarusian)
Currently translated at 84.8% (268 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/be/
2025-12-30 16:10:34 +00:00
Sylvia van OsandGitHub 2215aa84c0 Merge pull request #2908 from CatimaLoyalty/fix/2907
Fix Talkback announcing "greater than arrow"
2025-12-29 17:24:06 +01:00
Sylvia van Os 6d2ac18859 Fix Talkback announcing "greater than arrow" 2025-12-29 17:05:55 +01:00
Sylvia van OsandGitHub 056f346f1f Merge pull request #2903 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-29 15:14:55 +01:00
Sylvia van OsandGitHub 50c0babb24 Merge pull request #2904 from CatimaLoyalty/dependabot/gradle/org.jetbrains.kotlin.plugin.compose-2.3.0
Bump org.jetbrains.kotlin.plugin.compose from 2.1.21 to 2.3.0
2025-12-29 09:24:29 +01:00
dependabot[bot]andGitHub deb47c032b Bump org.jetbrains.kotlin.plugin.compose from 2.1.21 to 2.3.0
Bumps [org.jetbrains.kotlin.plugin.compose](https://github.com/JetBrains/kotlin) from 2.1.21 to 2.3.0.
- [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.1.21...v2.3.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin.plugin.compose
  dependency-version: 2.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 02:05:03 +00:00
B o d oandHosted Weblate 2d3b0b9001 Translated using Weblate (German)
Currently translated at 100.0% (155 of 155 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2025-12-29 00:01:53 +01:00
امیرضاandHosted Weblate 26b076a981 Translated using Weblate (Persian (Old))
Currently translated at 74.4% (233 of 313 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/peo/
2025-12-29 00:01:52 +01:00
Sylvia van OsandHosted Weblate 6b8d58c230 Translated using Weblate (Persian)
Currently translated at 41.5% (64 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fa/
2025-12-29 00:01:52 +01:00
Sylvia van OsandGitHub 54aca0323a Merge pull request #2902 from CatimaLoyalty/TheLastProject-patch-1
Temporarily allow peo
2025-12-28 22:04:55 +01:00
Sylvia van OsandGitHub 5546e904f4 Explain reasoning 2025-12-28 22:01:45 +01:00
Sylvia van OsandGitHub cd48cd0635 Temporarily allow peo
https://github.com/WeblateOrg/weblate/issues/17455
2025-12-28 22:01:06 +01:00
Sylvia van OsandGitHub f8ec8e1dbb Merge pull request #2901 from CatimaLoyalty/create-pull-request/patch-1766898767
Update contributors
2025-12-28 07:12:35 +01:00
TheLastProjectandgithub-actions[bot] 13db4f6dab Update contributors 2025-12-28 05:12:47 +00:00
Sylvia van OsandGitHub cdafc60bf0 Merge pull request #2900 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-27 23:53:44 +01:00
Hosted Weblate d505f7f27b Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/
2025-12-27 23:35:09 +01:00
Vasilis K.andHosted Weblate 6b82952f2a Translated using Weblate (Greek)
Currently translated at 100.0% (316 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2025-12-27 23:35:09 +01:00
KachelkaiserandHosted Weblate b0c1fbf769 Translated using Weblate (German)
Currently translated at 100.0% (316 of 316 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-12-27 23:35:08 +01:00
امیرضاandHosted Weblate 0f55b478c2 Translated using Weblate (Persian)
Currently translated at 41.5% (64 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fa/
2025-12-27 23:35:07 +01:00
Sylvia van OsandHosted Weblate d8cd526545 Translated using Weblate (Persian (Old))
Currently translated at 58.7% (184 of 313 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/peo/
2025-12-27 23:35:06 +01:00
Sylvia van OsandGitHub 0fe4a1a64b Merge pull request #2882 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-27 17:50:40 +01:00
Vasilis K.andHosted Weblate b19183b802 Translated using Weblate (Greek)
Currently translated at 100.0% (333 of 333 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2025-12-27 14:14:16 +01:00
KachelkaiserandHosted Weblate 25f6d3752b Translated using Weblate (German)
Currently translated at 100.0% (333 of 333 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2025-12-27 14:14:16 +01:00
Heeheon RyuandHosted Weblate cd96d11769 Translated using Weblate (Korean)
Currently translated at 87.6% (290 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ko/
2025-12-27 14:14:16 +01:00
ssantosandHosted Weblate 2558210d10 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_PT/
2025-12-27 14:14:16 +01:00
ssantosandHosted Weblate fb584a7c89 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt_PT/
2025-12-27 14:14:16 +01:00
ssantosandHosted Weblate 112beb7edd Translated using Weblate (Portuguese)
Currently translated at 100.0% (331 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt/
2025-12-27 14:14:16 +01:00
ssantosandHosted Weblate fa8891ca34 Translated using Weblate (Portuguese)
Currently translated at 100.0% (154 of 154 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt/
2025-12-27 14:14:16 +01:00
امیرضاandHosted Weblate d9beca97d7 Translated using Weblate (Persian (Old))
Currently translated at 59.5% (197 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/peo/
2025-12-27 14:14:16 +01:00
B o d oandHosted Weblate 143221825d Translated using Weblate (Italian)
Currently translated at 86.1% (285 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2025-12-27 14:14:16 +01:00
FjuroandHosted Weblate d2d3a84b07 Translated using Weblate (Czech)
Currently translated at 100.0% (155 of 155 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2025-12-27 14:14:16 +01:00
Nataniel Dika KurniawanandHosted Weblate 70f18f8aeb Translated using Weblate (Indonesian)
Currently translated at 99.6% (330 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/id/
2025-12-27 14:14:16 +01:00
امیرضاandHosted Weblate 169c3cefc1 Translated using Weblate (Persian (Old))
Currently translated at 29.0% (96 of 331 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/peo/
2025-12-27 14:14:16 +01:00
Sylvia van OsandGitHub a6b8d2c133 Merge pull request #2899 from CatimaLoyalty/create-pull-request/patch-1766838544
Update Fastlane changelogs
2025-12-27 13:30:03 +01:00
TheLastProjectandgithub-actions[bot] 42620cbf50 Update Fastlane changelogs 2025-12-27 12:29:04 +00:00
Sylvia van Os 7897e1cb76 Update CHANGELOG 2025-12-27 13:28:49 +01:00
Sylvia van OsandGitHub ccfcc1103c Merge pull request #2897 from CatimaLoyalty/remove/colorOptions
Remove theme colour support
2025-12-27 13:28:10 +01:00
Sylvia van Os c202c86ce4 Remove theme colour support
This simplifies the codebase and new Compose implementation. If this
feature is really wanted by the community, it could possibly be
reimplemented in a much more flexible way (choosing the exact colour)
after all UIs are migrated to Compose, as Compose can generate a theme
based on just a primary colour.
2025-12-27 13:11:05 +01:00
Sylvia van OsandGitHub c4132d2b6b Merge pull request #2898 from CatimaLoyalty/create-pull-request/patch-1766836891
Update Fastlane changelogs
2025-12-27 13:09:53 +01:00
TheLastProjectandgithub-actions[bot] ecbf970ae3 Update Fastlane changelogs 2025-12-27 12:01:31 +00:00
Sylvia van Os aca497ba2d Update CHANGELOG
Normally I wouldn't mention non-user-facing changes, but it is useful to
have a sense of when the Compose migration really started in the
changelog for easier reference
2025-12-27 13:00:52 +01:00
b06f240755 AboutActivity to Jetpack Compose (#2489)
* WIP

* test: Add test tags for compose components

* test: Add basic test for compose about screen

* refactor: Add defaults for `AboutScreenContent`

* refactor: Move compose tests to unit tests

* refactor: Make `showRateOnGooglePlay` default to `app/build.gradle.kts/defaultConfig` value

* refactor: Best practise to make previews private to reduce pollution

* refactor: Best practise apply theme as high as possible for most cases

* style: Format AboutActivity.kt

* test: Add more comprehensive tests for about screen

* test: Fix configuration of compose tests

* Fix Gradle setup

* Fix build issues

* Adjust text sizing

* Use full black OLED theme in Compose if chosen in settings

---------

Co-authored-by: LooKeR <iamlooker@proton.me>
2025-12-27 13:00:13 +01:00
Sylvia van OsandGitHub 61e26262b0 Merge pull request #2896 from CatimaLoyalty/create-pull-request/patch-1766747742
Update Fastlane changelogs
2025-12-26 12:16:50 +01:00
TheLastProjectandgithub-actions[bot] 6ca0f0e176 Update Fastlane changelogs 2025-12-26 11:15:42 +00:00
Sylvia van Os 7f9ae158ec Update CHANGELOG 2025-12-26 12:15:30 +01:00
Sylvia van OsandGitHub 2b1c7f739d Merge pull request #2895 from CatimaLoyalty/fix/2842
Apply column count setting in group edit activity
2025-12-26 12:14:43 +01:00
Sylvia van Os 5a3f7c17ed Apply column count setting in group edit activity 2025-12-26 12:02:19 +01:00
Sylvia van OsandGitHub 329c2049c3 Merge pull request #2894 from CatimaLoyalty/feature/dev_docs
Migrate dev docs to git repo
2025-12-25 18:05:02 +01:00
Sylvia van Os 2e51bd1ffa Update to include new barcodeencoding field 2025-12-25 18:04:01 +01:00
Sylvia van Os 1a8c6d6e90 Migrate dev docs to git repository
This allows us to deprecate https://github.com/CatimaLoyalty/Docs and
keep everything together
2025-12-25 18:03:58 +01:00
Sylvia van OsandGitHub 64a31b5729 Merge pull request #2893 from CatimaLoyalty/create-pull-request/patch-1766677737
Update Fastlane changelogs
2025-12-25 17:48:21 +01:00
TheLastProjectandgithub-actions[bot] 2dcc94e534 Update Fastlane changelogs 2025-12-25 15:48:56 +00:00
Sylvia van Os 81cbc9f5e9 Update CHANGELOG 2025-12-25 16:48:46 +01:00
Sylvia van OsandGitHub 0d68735b60 Merge pull request #2892 from CatimaLoyalty/fix/reduceMaxImageSize
Reduce max photo attachment size to 1600x1600px
2025-12-25 16:48:14 +01:00
Sylvia van Os bb8591b0ef Reduce max photo attachment size to 1600x1600px
This reduces the file size with no clear quality change
2025-12-25 16:30:03 +01:00
Sylvia van OsandGitHub e513ab4a09 Merge pull request #2891 from CatimaLoyalty/create-pull-request/patch-1766676299
Update Fastlane changelogs
2025-12-25 16:25:13 +01:00
TheLastProjectandgithub-actions[bot] e162fac30a Update Fastlane changelogs 2025-12-25 15:24:58 +00:00
Sylvia van Os 3ae93cc1ad Update CHANGELOG 2025-12-25 16:24:47 +01:00
Sylvia van OsandGitHub 59001d466a Merge pull request #2716 from CatimaLoyalty/feature/barcodeEncoding
Add barcode encoding support
2025-12-25 16:23:19 +01:00
Sylvia van OsandGitHub c8dcdedae0 Merge pull request #2889 from CatimaLoyalty/dependabot/github_actions/actions/upload-artifact-6.0.0
Bump actions/upload-artifact from 5.0.0 to 6.0.0
2025-12-25 16:19:11 +01:00
Sylvia van Os 0c61abf4f0 Add barcode encoding support
- Add new barcodeencoding field to database
- Read barcode encoding from pkpass file
- Add barcodeencoding to import/export
- Add barcodeencoding to share URI
- On default, use zxing's GuessEncoding function in StringUtils (this
  should not use UTF-8 unless needed)
- Allow manually forcing ISO-8859-1 or UTF-8
2025-12-25 16:08:05 +01:00
dependabot[bot]andGitHub 8e5e875fe0 Bump actions/upload-artifact from 5.0.0 to 6.0.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-25 02:02:09 +00:00
Sylvia van OsandGitHub e6d7065461 Merge pull request #2888 from CatimaLoyalty/dependabot/gradle/org.jetbrains.kotlin.android-2.3.0
Bump org.jetbrains.kotlin.android from 2.2.21 to 2.3.0
2025-12-24 08:30:40 +01:00
dependabot[bot]andGitHub 8b87a4612c Bump org.jetbrains.kotlin.android from 2.2.21 to 2.3.0
Bumps [org.jetbrains.kotlin.android](https://github.com/JetBrains/kotlin) from 2.2.21 to 2.3.0.
- [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.21...v2.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-24 02:04:10 +00:00
Sylvia van OsandGitHub e8d7293e2a Merge pull request #2885 from joserebelo/jr-idea-icon
Add .idea project icon
2025-12-21 21:29:02 +01:00
José Rebelo c283feb699 Add .idea project icon 2025-12-21 18:39:12 +00:00
Sylvia van OsandGitHub b895a9bc48 Merge pull request #2884 from CatimaLoyalty/create-pull-request/patch-1766291750
Update contributors
2025-12-21 12:12:49 +01:00
TheLastProjectandgithub-actions[bot] 0d33319ad4 Update contributors 2025-12-21 04:35:50 +00:00
Sylvia van OsandGitHub 2af9d58f06 Merge pull request #2883 from Iamlooker/fix/jvm-configuration
build: Fix conflicting JVM setup
2025-12-20 12:55:58 +01:00
LooKeR 427e9d6482 build: Fix conflicting JVM setup 2025-12-20 13:44:21 +05:30
Sylvia van OsandGitHub e21fd954d3 Merge pull request #2881 from CatimaLoyalty/create-pull-request/patch-1766097757
Update locales
2025-12-19 00:12:25 +01:00
TheLastProjectandgithub-actions[bot] e7f47c7a5a Update locales 2025-12-18 22:42:36 +00:00
Sylvia van OsandGitHub 54c6dac322 Merge pull request #2880 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-18 23:42:24 +01:00
امیرضاandHosted Weblate cf64958562 Added translation using Weblate (Persian (Old)) 2025-12-18 23:02:46 +01:00
Sylvia van OsandGitHub ea31a3b42a Merge pull request #2879 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-18 21:32:08 +01:00
B o d oandHosted Weblate 353dbedbb9 Translated using Weblate (German)
Currently translated at 100.0% (155 of 155 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2025-12-18 21:01:29 +01:00
Sylvia van OsandGitHub a87c446c31 Merge pull request #2877 from CatimaLoyalty/dependabot/github_actions/peter-evans/create-pull-request-8.0.0
Bump peter-evans/create-pull-request from 7.0.11 to 8.0.0
2025-12-18 10:30:48 +01:00
dependabot[bot]andGitHub 85f1d06b02 Bump peter-evans/create-pull-request from 7.0.11 to 8.0.0
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.0.0.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v7.0.11...v8.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-18 02:02:26 +00:00
Sylvia van OsandGitHub 17d3e9b3d0 Merge pull request #2876 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-17 20:34:32 +01:00
امیرضاandHosted Weblate 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
امیرضاandHosted Weblate 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 OsandGitHub 930246e6c5 Merge pull request #2874 from CatimaLoyalty/create-pull-request/patch-1765747610
Update Fastlane changelogs
2025-12-14 22:28:01 +01:00
TheLastProjectandgithub-actions[bot] 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
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 OsandGitHub 16f9b3f6b1 Merge pull request #2872 from CatimaLoyalty/create-pull-request/patch-1765686940
Update contributors
2025-12-14 10:51:09 +01:00
TheLastProjectandgithub-actions[bot] 3c38c7cc25 Update contributors 2025-12-14 04:35:39 +00:00
Methum MenthusaandGitHub 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 OsandGitHub 90b326e6b9 Merge pull request #2870 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-12 11:34:18 +01:00
Francisco SerradorandHosted Weblate 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 OsandGitHub 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]andGitHub 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 OsandGitHub 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ışandHosted Weblate 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
GideonandHosted Weblate 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ışandHosted Weblate 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 OsandGitHub 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]andGitHub 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 OsandGitHub ee909e0047 Merge pull request #2863 from CatimaLoyalty/create-pull-request/patch-1765081875
Update contributors
2025-12-07 11:11:45 +01:00
TheLastProjectandgithub-actions[bot] 6eee4a25f3 Update contributors 2025-12-07 04:31:15 +00:00
Sylvia van OsandGitHub 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 VargaandHosted Weblate 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
AtiandHosted Weblate 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
PatrikandHosted Weblate 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 VargaandHosted Weblate 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 OsandGitHub c762fcf6cc Merge pull request #2861 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-05 22:17:50 +01:00
VKing9andHosted Weblate 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 MenezesandHosted Weblate 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
VKing9andHosted Weblate 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 ZakrajsekandHosted Weblate 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 OsandGitHub 67e2abde8b Merge pull request #2860 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-12-04 15:26:38 +01:00
Liner SevenandHosted Weblate 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 AandHosted Weblate 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 oandHosted Weblate 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
FjuroandHosted Weblate 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 PichonandHosted Weblate 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
solokotandHosted Weblate 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
4ipsetandHosted Weblate 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
大王叫我来巡山andHosted Weblate 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
Максим ГорпинічandHosted Weblate 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
FjuroandHosted Weblate 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.andHosted Weblate 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
Максим ГорпинічandHosted Weblate 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 sonandHosted Weblate 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 AndersonsandHosted Weblate 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 SevenandHosted Weblate 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 oandHosted Weblate 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
109247019824andHosted Weblate 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
4ipsetandHosted Weblate 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 AandHosted Weblate 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 sonandHosted Weblate 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
大王叫我来巡山andHosted Weblate 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 AndersonsandHosted Weblate 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 PichonandHosted Weblate 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üütandHosted Weblate 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.andHosted Weblate 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 OsandGitHub 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]andGitHub 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
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 OsandGitHub 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 OsandGitHub 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]andGitHub 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 OsandGitHub 06b1b32ce7 Merge pull request #2857 from CatimaLoyalty/create-pull-request/patch-1764477253
Update contributors
2025-11-30 11:09:05 +01:00
TheLastProjectandgithub-actions[bot] 202936b21f Update contributors 2025-11-30 04:34:13 +00:00
Sylvia van OsandGitHub 122f7f64b7 Merge pull request #2856 from CatimaLoyalty/create-pull-request/patch-1764415067
Update Fastlane changelogs
2025-11-29 12:21:44 +01:00
TheLastProjectandgithub-actions[bot] 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 OsandGitHub 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 OsandGitHub 1b4559fa3c Merge pull request #2854 from CatimaLoyalty/create-pull-request/patch-1764269098
Update Fastlane changelogs
2025-11-27 19:46:16 +01:00
TheLastProjectandgithub-actions[bot] 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
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 OsandGitHub c89a759c8b Merge pull request #2852 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-27 08:27:49 +01:00
Liner SevenandHosted Weblate 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.andHosted Weblate 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 OsandGitHub 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 OsandHosted Weblate 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
דוד משה המבורגרandHosted Weblate 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 AndersonsandHosted Weblate 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 VacariandHosted Weblate 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 OsandGitHub 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 OsandGitHub 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]andGitHub 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]andGitHub 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 OsandGitHub 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 ThanhandHosted Weblate 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 OsandGitHub 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 ThanhandHosted Weblate 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 ThanhandHosted Weblate 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 OsandGitHub 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 TinandHosted Weblate 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 OsandGitHub e8d40ec679 Merge pull request #2839 from CatimaLoyalty/create-pull-request/patch-1763267079
Update contributors
2025-11-16 10:14:34 +01:00
TheLastProjectandgithub-actions[bot] e9c40c88a9 Update contributors 2025-11-16 04:24:38 +00:00
Sylvia van OsandGitHub 82bb9b2817 Merge pull request #2838 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-15 00:12:13 +01:00
Francisco SerradorandHosted Weblate 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 OsandGitHub 2ac36fa415 Merge pull request #2836 from CatimaLoyalty/create-pull-request/patch-1762978593
Update feature graphic
2025-11-12 23:07:39 +01:00
TheLastProjectandgithub-actions[bot] 19399dd17f Update feature graphic 2025-11-12 20:16:33 +00:00
Sylvia van OsandGitHub acae8c1a0d Merge pull request #2835 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-12 21:14:35 +01:00
Mohammad AlhasanandHosted Weblate 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 SerradorandHosted Weblate 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 SerradorandHosted Weblate 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 SerradorandHosted Weblate 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 RuizandHosted Weblate 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 OsandGitHub 2be4d1cd2b Merge pull request #2834 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-12 09:11:26 +01:00
Alì MortacciandHosted Weblate 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 OsandGitHub 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 OsandGitHub 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ì MortacciandHosted Weblate 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ì MortacciandHosted Weblate 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ì MortacciandHosted Weblate 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]andGitHub 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 OsandGitHub 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]andGitHub 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 OsandGitHub 41c8bb1815 Merge pull request #2829 from CatimaLoyalty/create-pull-request/patch-1762662107
Update contributors
2025-11-09 09:17:16 +01:00
Sylvia van OsandGitHub eab4f9e123 Merge pull request #2828 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-09 09:16:31 +01:00
TheLastProjectandgithub-actions[bot] 05f4cfc07b Update contributors 2025-11-09 04:21:46 +00:00
asdasd gfsdfdfgandHosted Weblate 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 OsandGitHub 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šandHosted Weblate 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 OsandGitHub 66e568aa06 Merge pull request #2825 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-07 07:56:05 +01:00
Joel AandHosted Weblate 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 AandHosted Weblate 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
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 OsandGitHub 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 OsandGitHub 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]andGitHub 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 AandHosted Weblate 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 AandHosted Weblate 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 OsandGitHub 177f8e43e2 Merge pull request #2822 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-05 22:27:47 +01:00
ssantosandHosted Weblate 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
ssantosandHosted Weblate 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 OsandGitHub 0fee650cee Merge pull request #2820 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-05 17:18:13 +01:00
Francisco SerradorandHosted Weblate 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 OsandGitHub 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 OsandGitHub 32b4dd73aa Merge pull request #2818 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-04 16:07:16 +01:00
ezn24andHosted Weblate 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 OsandGitHub 20cc714b52 Merge pull request #2817 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-03 22:06:54 +01:00
OdoamarandHosted Weblate 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 OsandGitHub 89bdbe1544 Merge pull request #2816 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-11-03 16:34:33 +01:00
FjuroandHosted Weblate 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 AndersonsandHosted Weblate 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
KachelkaiserandHosted Weblate 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
solokotandHosted Weblate 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
Максим ГорпинічandHosted Weblate 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 PichonandHosted Weblate 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 SevenandHosted Weblate 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 BudimanandHosted Weblate 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. PrayogaandHosted Weblate 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 MustofaandHosted Weblate 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
大王叫我来巡山andHosted Weblate 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
தமிழ்நேரம்andHosted Weblate 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 OsandGitHub 2855cf4569 Merge pull request #2815 from CatimaLoyalty/create-pull-request/patch-1762096307
Update Fastlane changelogs
2025-11-02 17:00:28 +01:00
TheLastProjectandgithub-actions[bot] 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 OsandGitHub 709fffcfe5 Merge pull request #2814 from CatimaLoyalty/create-pull-request/patch-1762057266
Update contributors
2025-11-02 08:27:39 +01:00
TheLastProjectandgithub-actions[bot] 5c0474b38e Update contributors 2025-11-02 04:21:05 +00:00
Sylvia van OsandGitHub 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 OsandGitHub 04937e8839 Merge pull request #2809 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-31 08:34:31 +01:00
normandHosted Weblate 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
normandHosted Weblate 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 LiandAdrian Carpio 13a78dadb4 convert LetterBitmap to Kotlin 2025-10-31 12:11:58 +11:00
Sylvia van OsandGitHub 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 OsandGitHub 7986428149 Merge pull request #2808 from CatimaLoyalty/create-pull-request/patch-1761849375
Update feature graphic
2025-10-30 19:44:12 +01:00
TheLastProjectandgithub-actions[bot] 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 OsandGitHub 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 OsandGitHub 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 OsandGitHub c839fffadb Merge pull request #2797 from CatimaLoyalty/fix/tamil_feature_graphic
Lower Tamil font size
2025-10-29 19:28:23 +01:00
TheLastProjectandgithub-actions[bot] 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 OsandGitHub 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 OsandGitHub 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
ssantosandHosted Weblate 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
ssantosandHosted Weblate 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
AnonymousandHosted Weblate 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
ssantosandHosted Weblate 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
ssantosandHosted Weblate 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
ssantosandHosted Weblate 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 OsandHosted Weblate 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
Rajasree2004andHosted Weblate 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
Rajasree2004andHosted Weblate 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
Rajasree2004andHosted Weblate 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
Rajasree2004andHosted Weblate 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 OsandGitHub 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]andGitHub 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 OsandGitHub d0d75a4f50 Merge pull request #2793 from CatimaLoyalty/create-pull-request/patch-1761452387
Update contributors
2025-10-26 12:33:09 +01:00
TheLastProjectandgithub-actions[bot] df42111f83 Update contributors 2025-10-26 04:19:46 +00:00
Sylvia van OsandGitHub da52a3685f Merge pull request #2792 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-25 10:46:40 +02:00
Igor WandHosted Weblate 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 OsandGitHub 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]andGitHub 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 OsandGitHub 131004494b Merge pull request #2790 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-22 23:21:55 +02:00
Feike DoniaandHosted Weblate 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 DoniaandHosted Weblate 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 DoniaandHosted Weblate 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 DoniaandHosted Weblate 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 OsandGitHub 6ac60f9546 Merge pull request #2788 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-22 19:26:44 +02:00
Feike DoniaandHosted Weblate 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 DoniaandHosted Weblate 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 OsandGitHub 386a24305d Merge pull request #2787 from CatimaLoyalty/create-pull-request/patch-1761152124
Update locales
2025-10-22 19:01:48 +02:00
TheLastProjectandgithub-actions[bot] 45c4b89a4d Update locales 2025-10-22 16:55:24 +00:00
Sylvia van OsandGitHub 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 OsandGitHub 2ab267f601 Merge pull request #2782 from aradxxx/ucropwrapper_to_kt
Convert UcropWrapper to kotlin
2025-10-22 18:47:20 +02:00
Feike DoniaandHosted Weblate ae54e91382 Added translation using Weblate (Frisian) 2025-10-22 18:39:39 +02:00
Feike DoniaandHosted Weblate 45c082fba9 Added translation using Weblate (Afrikaans) 2025-10-22 16:39:08 +00:00
Feike DoniaandHosted Weblate 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 OsandGitHub e76e4f42f2 Merge pull request #2785 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-22 18:27:04 +02:00
Feike DoniaandHosted Weblate 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 AndersonsandHosted Weblate 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 LiandAdrian Carpio 7f4937552d Rename .java to .kt 2025-10-22 11:22:12 +11:00
Sylvia van OsandGitHub 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 OsandGitHub 246d5b5e4c Merge pull request #2781 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-20 21:31:45 +02:00
Damjan GerlandHosted Weblate 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
ssantosandHosted Weblate 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
ssantosandHosted Weblate 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
AnonymousandHosted Weblate 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
KersoandHosted Weblate 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 OsandGitHub 5b30a11da3 Merge pull request #2763 from aradxxx/managegroupsactivity_to_kotlin
Convert ManageGroupsActivity to Kotlin
2025-10-19 14:01:53 +02:00
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 OsandGitHub f473d31f13 Merge pull request #2780 from CatimaLoyalty/create-pull-request/patch-1760847620
Update contributors
2025-10-19 12:12:40 +02:00
TheLastProjectandgithub-actions[bot] 1afe181085 Update contributors 2025-10-19 04:20:20 +00:00
Sylvia van OsandGitHub 647b7185df Merge pull request #2776 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-16 18:37:42 +02:00
Liner SevenandHosted Weblate 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 SevenandHosted Weblate 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 SevenandHosted Weblate 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 SevenandHosted Weblate 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 SevenandHosted Weblate 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 SevenandHosted Weblate 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 SevenandHosted Weblate 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 GerlandHosted Weblate 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 OsandGitHub a00dd69005 Merge pull request #2775 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-15 18:34:04 +02:00
Damjan GerlandHosted Weblate 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 GerlandHosted Weblate 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 GerlandHosted Weblate 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 OsandGitHub 129bffe4b7 Merge pull request #2774 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-15 17:10:34 +02:00
mrestivillandHosted Weblate 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
mrestivillandHosted Weblate 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 OsandGitHub 088098edad Merge pull request #2773 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-15 08:40:14 +02:00
Liner SevenandHosted Weblate 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 SevenandHosted Weblate 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 SevenandHosted Weblate 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 OsandGitHub 5d592e253b Merge pull request #2772 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-14 19:50:25 +02:00
Liner SevenandHosted Weblate 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 OsandGitHub 40c5eab3c5 Merge pull request #2771 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-14 08:20:39 +02:00
Liner SevenandHosted Weblate 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 OsandGitHub 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 OsandGitHub cecad8351e Merge pull request #2770 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-13 18:54:31 +02:00
ssantosandHosted Weblate 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
ssantosandHosted Weblate 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 OsandGitHub 27f1f6f179 Merge pull request #2769 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-13 12:24:33 +02:00
jezoswiecandHosted Weblate 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 OsandGitHub 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 OsandGitHub dd866a0f2b Merge pull request #2767 from CatimaLoyalty/create-pull-request/patch-1760242739
Update contributors
2025-10-12 09:41:42 +02:00
TheLastProjectandgithub-actions[bot] 889d1beab4 Update contributors 2025-10-12 04:18:59 +00:00
Oğuz ErsenandHosted Weblate 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 OsandGitHub 19eda065ba Merge pull request #2765 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-11 09:22:58 +02:00
Liner SevenandHosted Weblate 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 SevenandHosted Weblate 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 SevenandHosted Weblate 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 SevenandHosted Weblate 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 OsandGitHub 45bf552eff Merge pull request #2755 from amlwin/main
Convert ImportExportActivity to Kotlin
2025-10-09 23:35:08 +02:00
Sylvia van OsandGitHub 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 RuizandHosted Weblate 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 OsandGitHub 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 OsandGitHub 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 OsandGitHub 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
ezn24andHosted Weblate 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
ezn24andHosted Weblate 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
AnonymousandHosted Weblate 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 OsandGitHub 5cbb2505e3 Merge pull request #2757 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-07 06:46:15 +02:00
Liner SevenandHosted Weblate 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 OsandGitHub a4e9333c6e Merge pull request #2756 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-06 21:23:21 +02:00
ssantosandHosted Weblate 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 OsandGitHub 13c78eaee5 Merge pull request #2754 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-06 20:19:08 +02:00
Liner SevenandHosted Weblate 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 SevenandHosted Weblate 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 SevenandHosted Weblate 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]andGitHub 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
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 OsandGitHub a429b858e2 Merge pull request #2752 from CatimaLoyalty/create-pull-request/patch-1759637964
Update contributors
2025-10-05 09:04:54 +02:00
TheLastProjectandgithub-actions[bot] d8d228aa67 Update contributors 2025-10-05 04:19:24 +00:00
Sylvia van OsandGitHub 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 oandHosted Weblate 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 oandHosted Weblate 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 OsGitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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 OsandGitHub 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øgelundandHosted Weblate 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 KruseandHosted Weblate 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 OsandGitHub 96cf5274b1 Merge pull request #2743 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-02 16:26:15 +02:00
JorgeS15andHosted Weblate 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
FjuroandHosted Weblate 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 SevenandHosted Weblate 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 SevenandHosted Weblate 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 SevenandHosted Weblate 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
NyatsukiandHosted Weblate 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 SevenandHosted Weblate 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
NyatsukiandHosted Weblate 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 SevenandHosted Weblate 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
NyatsukiandHosted Weblate 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 OsandGitHub f06d338c5a Merge pull request #2741 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-01 22:41:29 +02:00
Milo IvirandHosted Weblate 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 IvirandHosted Weblate 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 OsandGitHub 2662178bef Merge pull request #2739 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-01 20:39:51 +02:00
Milo IvirandHosted Weblate 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
1168 changed files with 10457 additions and 6671 deletions
+5
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:
+9 -11
View File
@@ -24,7 +24,7 @@ permissions:
security-events: none
statuses: none
env:
JAVA_HOME: /usr/lib/jvm/java-21-openjdk-amd64
JAVA_HOME: /usr/lib/jvm/java-25-openjdk-amd64
jobs:
build:
runs-on: ubuntu-latest
@@ -32,30 +32,28 @@ jobs:
matrix:
flavor: [Foss, Gplay]
steps:
- uses: actions/checkout@v5
- 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
- name: set up OpenJDK 21
- uses: actions/checkout@v6
- uses: gradle/actions/wrapper-validation@v5
- name: set up OpenJDK 25
run: |
sudo apt-get update
sudo apt-get install -y openjdk-21-jdk-headless
sudo apt-get install -y openjdk-25-jdk-headless
sudo update-alternatives --auto java
- name: Build
run: ./gradlew assemble${{ matrix.flavor }}Release
- name: Check lint
run: ./gradlew lint${{ matrix.flavor }}Release
- name: Run unit tests
run: timeout 5m ./gradlew test${{ matrix.flavor }}ReleaseUnitTest || { ./gradlew --stop && timeout 5m ./gradlew test${{ matrix.flavor }}ReleaseUnitTest; }
run: timeout 5m ./gradlew test${{ matrix.flavor }}DebugUnitTest || { ./gradlew --stop && timeout 5m ./gradlew test${{ matrix.flavor }}DebugUnitTest; }
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Run instrumented tests (API 21)
- name: Run instrumented tests (API 23)
uses: ReactiveCircus/android-emulator-runner@v2
with:
api-level: 21
api-level: 23
arch: x86_64
script: ./gradlew connected${{ matrix.flavor }}DebugAndroidTest
- name: Run instrumented tests (API 35)
@@ -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@v7.0.1
with:
name: test-results-flavor${{ matrix.flavor }}
path: app/build/reports
+3 -3
View File
@@ -19,15 +19,15 @@ jobs:
steps:
- name: Checkout repo
id: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v6.2.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@v8.1.1
with:
title: "Update Fastlane changelogs"
commit-message: "Update Fastlane changelogs"
+4 -2
View File
@@ -17,15 +17,17 @@ jobs:
steps:
- name: Checkout repo
id: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Update contributors
id: update_contributors
uses: TheLastProject/contributors-to-file-action@v3.2.0
with:
file_in_repo: app/src/main/res/raw/contributors.txt
min_commit_count: 5
- name: Remove gradle-update-robot from contributors (not a person)
run: "sed -i '/^gradle-update-robot$/d' app/src/main/res/raw/contributors.txt"
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7.0.8
uses: peter-evans/create-pull-request@v8.1.1
with:
title: "Update contributors"
commit-message: "Update contributors"
@@ -17,7 +17,7 @@ jobs:
generate-feature-graphic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install requirements
run: |
sudo apt-get update
@@ -31,7 +31,7 @@ jobs:
- name: Generate featureGraphic.png for each language
run: .scripts/generate_feature_graphic/generate_feature_graphic.sh
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7.0.8
uses: peter-evans/create-pull-request@v8.1.1
with:
title: "Update feature graphic"
commit-message: "Update feature graphic"
+34
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
@@ -1,19 +0,0 @@
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@v5
- name: Update Gradle Wrapper
uses: gradle-update/update-gradle-wrapper-action@v2
+2 -2
View File
@@ -17,13 +17,13 @@ jobs:
update-locales:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Add new locales
run: .scripts/new-locales.py
- name: Update locales
run: .scripts/locales.py
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7.0.8
uses: peter-evans/create-pull-request@v8.1.1
with:
title: "Update locales"
commit-message: "Update locales"
+2 -2
View File
@@ -19,8 +19,8 @@
/app/*.log
/app/build
/app/release
/.idea
/.idea/*
!/.idea/icon.svg
# Bundle
/.bundle/
/vendor/bundle
Generated Symlink
+1
View File
@@ -0,0 +1 @@
../.design/ic_launcher_foreground.svg
+72
View File
@@ -0,0 +1,72 @@
#!/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"
# peo is only temporarily allowed due to https://github.com/WeblateOrg/weblate/issues/17455
ALLOWLIST=("ar" "bn" "fa" "fa-IR" "he-IL" "hi" "hi-IN" "kn" "kn-IN" "ml" "mr" "peo", "ta" "ta-IN" "zh-rTW" "zh-TW") # TODO: Link values and fastlane with different codes together
function get_lang() {
LANG_DIRNAME=$(dirname "$FILE" | xargs basename)
LANG=${LANG_DIRNAME#values-} # Fetch lang name
LANG=${LANG#values} # Handle "app/src/main/res/values"
LANG=${LANG:-en} # Default to en
}
# FIXME: This function should use its own variables and return a success/fail status, instead of working on global variables
function check() {
# FIXME: This allows inconsistency between values and fastlane if the app name is not Catima
# When the app name is not Catima, it should still check if title.txt and strings.xml use the same app name (or start)
if echo "${ALLOWLIST[*]}" | grep -w -q "${LANG}" || [[ -z ${APP_NAME} ]]; then
return 0
fi
if [[ ${FILE} == *"title.txt" ]]; then
if [[ ! ${APP_NAME} =~ ^${CANONICAL_TITLE} ]]; then
echo -e "${RED}Error: ${LIGHTCYAN}title in $FILE ($LANG) is ${RED}'$APP_NAME'${LIGHTCYAN}, expected to start with ${GREEN}'$CANONICAL_TITLE'. ${NC}"
SUCCESS=0
fi
else
if [[ ${APP_NAME} != "${CANONICAL_TITLE}" ]]; then
echo -e "${RED}Error: ${LIGHTCYAN}app_name in $FILE ($LANG) is ${RED}'$APP_NAME'${LIGHTCYAN}, expected ${GREEN}'$CANONICAL_TITLE'. ${NC}"
SUCCESS=0
fi
fi
}
# FIXME: This checks all title.txt and strings.xml files separately, but it needs to check if the title.txt and strings.xml match for a language as well
echo -e "${LIGHTCYAN}Checking title.txt's. ${NC}"
find fastlane/metadata/android/* -maxdepth 1 -type f -name "title.txt" | while read -r FILE; do
APP_NAME=$(head -n 1 "$FILE")
get_lang
check
done
echo -e "${LIGHTCYAN}Checking string.xml's. ${NC}"
find app/src/main/res/values* -maxdepth 1 -type f -name "strings.xml" | while read -r FILE; do
# FIXME: This only checks app_name, but there are more strings with Catima inside it
# It should check the original English text for all strings that contain Catima and ensure they use the correct app_name for consistency
APP_NAME=$(grep -oP '<string name="app_name">\K[^<]+' "$FILE" | head -n1)
get_lang
check
done
if [[ $SUCCESS -eq 1 ]]; then
echo -e "\n${GREEN}Success!! All app_name values match the canonical title. ${NC}"
else
echo -e "\n${RED}Unsuccessful!! Some app_name values did not match the canonical titles. ${NC}"
exit 1
fi
@@ -42,6 +42,7 @@ for lang in "$script_location/../../fastlane/metadata/android/"*; do
ja-JP) sed -i "s/Lexend/Noto Sans CJK JP/" featureGraphic.svg ;;
kn-IN) sed -i -e 's/font-size="150"/font-size="125"/' -e 's/\(<tspan x="469" \)y="270"/\1y="240"/' -e "s/Lobster/Noto Sans Kannada/" -e "s/Lexend/Noto Sans Kannada/" featureGraphic.svg ;;
ko) sed -i "s/Lexend/Noto Sans CJK KR/" featureGraphic.svg ;;
ta-IN) sed -i -e 's/font-size="150"/font-size="125"/' -e 's/\(<tspan x="469" \)y="270"/\1y="240"/' featureGraphic.svg ;;
zh-CN) sed -i "s/Lexend/Noto Sans CJK SC/" featureGraphic.svg ;;
zh-TW) sed -i -e "s/Lobster/Noto Sans CJK TC/" -e "s/Lexend/Noto Sans CJK TC/" featureGraphic.svg ;;
*) ;;
+61
View File
@@ -1,5 +1,66 @@
# Changelog
## v2.42.0 - 165 (2026-05-06)
- Disable NFC while showing a card by default
- Ask keyboard to capitalize each word in store name in edit screen
- Put date in export file name
## v2.41.7 - 164 (2026-03-06)
Android 5.0 and 5.1 are no longer supported starting with this release. If you want to use Catima on these versions, please use version 2.41.6.
- Fix crash when switching cards using volume buttons if only one card is available
- Fix shortcut returning to previous card after rotation
## v2.41.6 - 163 (2026-01-23)
- Auto-detect URLs in card ID and make the clickable in card ID details pop-up
- Fix barcode selector activity showing white squares instead of barcodes
## v2.41.5 - 162 (2026-01-15)
- Fix list widget sometimes opening wrong card
- Fix several bugs with shortcut handling
- Fix About activity not using pure black title bar in OLED mode
- Fully remove automatic barcode encoding detection
- Expose barcode encoding through ContentProvider (used by Gadgetbridge)
## v2.41.4 - 161 (2026-01-04)
- Disable automatic barcode encoding detection for now (breaks too many cards)
## v2.41.3 - 160 (2026-01-04)
- Follow-up for fix in 2.41.2 for cards explicitly set to ISO-8859-1
- Migrate old cards to ISO-8859-1 to fix sudden behaviour differences for existing cards
## v2.41.2 - 159 (2026-01-03)
- Fix change introduced in 2.41.0 that broke support for some scanners for non-UTF-8 barcodes
## v2.41.1 - 158 (2025-12-31)
- Fix status and navigation bar colour in new About activity
## v2.41.0 - 157 (2025-12-30)
- Add support for UTF-8 barcodes
- Add duplicate option to main screen and reorder options slightly
- Fix column count setting not being applied to group card list
- Remove theme colour support
- Reduce max photo size to reduce storage use (only for newly added photos)
- Migrate About activity from Android XML to Jetpack Compose
## 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
+24
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
+2 -1
View File
@@ -1,5 +1,5 @@
**Last updated**
September 30 2025
April 29 2026
# Privacy Policy
Catima does not collect or transmit any personal information.
@@ -7,6 +7,7 @@ Catima does not collect or transmit any personal information.
To ensure correct app functionality, we require access to the following:
- Camera: We need access to your camera to be able to scan barcodes. The app can still be used when camera access is denied, but you will have to manually type the barcode information.
- NFC: We need NFC access to temporarily disable NFC while showing a barcode to prevent NFC payment apps from triggering (such as when using a check-in gates which support both barcodes and NFC payments). This functionality can be disabled in settings.
- Storage (Android 5 and 6 only): We need access to your device storage to create or import backups. The app can still be used when storage access is denied, but you will not be able to create or import backups.
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.
+60 -49
View File
@@ -1,8 +1,10 @@
import com.android.build.gradle.internal.tasks.factory.dependsOn
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
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 {
@@ -15,15 +17,15 @@ android {
defaultConfig {
applicationId = "me.hackerchick.catima"
minSdk = 21
minSdk = 23
targetSdk = 36
versionCode = 154
versionName = "2.39.1"
versionCode = 165
versionName = "2.42.0"
vectorDrawables.useSupportLibrary = true
multiDexEnabled = true
resourceConfigurations += listOf("ar", "be", "bg", "bn", "bn-rIN", "bs", "cs", "da", "de", "el-rGR", "en", "eo", "es", "es-rAR", "et", "fa", "fi", "fr", "gl", "he-rIL", "hi", "hr", "hu", "in-rID", "is", "it", "ja", "ko", "lt", "lv", "nb-rNO", "nl", "oc", "pl", "pt", "pt-rBR", "pt-rPT", "ro-rRO", "ru", "sk", "sl", "sr", "sv", "ta", "tr", "uk", "vi", "zh-rCN", "zh-rTW")
resourceConfigurations += listOf("ar", "be", "bg", "bn", "bn-rIN", "bs", "cs", "da", "de", "el-rGR", "en", "eo", "es", "es-rAR", "et", "fa", "fi", "fr", "gl", "he-rIL", "hi", "hr", "hu", "in-rID", "is", "it", "ja", "ko", "lt", "lv", "nb-rNO", "nl", "oc", "peo", "pl", "pt", "pt-rBR", "pt-rPT", "ro-rRO", "ru", "sk", "sl", "sr", "sv", "ta", "tr", "uk", "vi", "zh-rCN", "zh-rTW")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -36,7 +38,7 @@ android {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android.txt"),
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
@@ -47,6 +49,7 @@ android {
buildFeatures {
buildConfig = true
compose = true
viewBinding = true
}
@@ -74,16 +77,6 @@ android {
}
}
compileOptions {
encoding = "UTF-8"
// Flag to enable support for the new language APIs
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
sourceSets {
getByName("test") {
resources.srcDirs("src/test/res")
@@ -102,10 +95,18 @@ android {
lint {
lintConfig = file("lint.xml")
}
kotlinOptions {
jvmTarget = "21"
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_21
}
}
compileOptions {
encoding = "UTF-8"
// Flag to enable support for the new language APIs
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
@@ -113,43 +114,53 @@ android {
dependencies {
// AndroidX
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
implementation("androidx.core:core-ktx:1.17.0")
implementation("androidx.core:core-remoteviews:1.1.0")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.exifinterface:exifinterface:1.4.1")
implementation("androidx.palette:palette:1.0.0")
implementation("androidx.preference:preference:1.2.1")
implementation("com.google.android.material:material:1.13.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
implementation(libs.androidx.appcompat.appcompat)
implementation(libs.androidx.constraintlayout.constraintlayout)
implementation(libs.androidx.core.core.ktx)
implementation(libs.androidx.core.core.remoteviews)
implementation(libs.androidx.core.core.splashscreen)
implementation(libs.androidx.exifinterface.exifinterface)
implementation(libs.androidx.palette.palette)
implementation(libs.androidx.preference.preference)
implementation(libs.com.google.android.material.material)
coreLibraryDesugaring(libs.com.android.tools.desugar.jdk.libs)
// 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
val acraVersion = "5.13.1"
implementation("ch.acra:acra-mail:$acraVersion")
implementation("ch.acra:acra-dialog:$acraVersion")
implementation(libs.bundles.acra)
// Testing
val androidXTestVersion = "1.7.0"
val junitVersion = "4.13.2"
testImplementation("androidx.test:core:$androidXTestVersion")
testImplementation("junit:junit:$junitVersion")
testImplementation("org.robolectric:robolectric:4.16")
testImplementation(libs.androidx.test.core)
testImplementation(libs.junit.junit)
testImplementation(libs.org.robolectric.robolectric)
androidTestImplementation("androidx.test:core:$androidXTestVersion")
androidTestImplementation("junit:junit:$junitVersion")
androidTestImplementation("androidx.test.ext:junit:1.3.0")
androidTestImplementation("androidx.test:runner:$androidXTestVersion")
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
androidTestImplementation(libs.bundles.androidx.test)
androidTestImplementation(libs.junit.junit)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.test.rules)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.androidx.test.uiautomator.uiautomator)
androidTestImplementation(libs.androidx.test.espresso.espresso.core)
}
tasks.register("copyRawResFiles", Copy::class) {
+16 -1
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
@@ -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()
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Catima Debug</string>
</resources>
<string name="app_name">Debug di Catima</string>
</resources>
+5 -1
View File
@@ -12,8 +12,12 @@
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="23" />
<uses-feature
android:name="android.hardware.nfc"
android:required="false" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
@@ -94,7 +98,7 @@
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustResize"/>
<activity
android:name=".LoyaltyCardViewActivity"
android:name=".cardview.LoyaltyCardViewActivity"
android:exported="true"
android:theme="@style/AppTheme.NoActionBar" />
<activity
@@ -1,149 +1,169 @@
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.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 : CatimaComponentActivity() {
private lateinit var content: AboutContent
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = AboutActivityBinding.inflate(layoutInflater)
fixedEdgeToEdge()
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))
}
@@ -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,7 +94,7 @@ 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"));
@@ -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() {
@@ -4,17 +4,27 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.util.ArrayMap;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.StringUtils;
import java.lang.ref.WeakReference;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import protect.card_locker.async.CompatCallable;
@@ -39,6 +49,7 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
private final WeakReference<TextView> textViewReference;
private String cardId;
private final CatimaBarcode format;
private final Charset encoding;
private final int imageHeight;
private final int imageWidth;
private final int imagePadding;
@@ -46,9 +57,9 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
private final boolean showFallback;
private final BarcodeImageWriterResultCallback callback;
BarcodeImageWriterTask(
public BarcodeImageWriterTask(
Context context, ImageView imageView, String cardIdString,
CatimaBarcode barcodeFormat, TextView textView,
CatimaBarcode barcodeFormat, @NonNull Charset barcodeEncoding, TextView textView,
boolean showFallback, BarcodeImageWriterResultCallback callback, boolean roundCornerPadding, boolean isFullscreen
) {
mContext = context;
@@ -62,6 +73,7 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
cardId = cardIdString;
format = barcodeFormat;
encoding = barcodeEncoding;
int imageViewHeight = imageView.getHeight();
int imageViewWidth = imageView.getWidth();
@@ -172,10 +184,32 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
}
MultiFormatWriter writer = new MultiFormatWriter();
Map<EncodeHintType, Object> encodeHints = new ArrayMap<>();
// We don't want to pass the ISO-8859-1 as an encoding hint as zxing may add this as ECI
// inside the barcode.
//
// Due to many barcode scanners in the wild being badly coded they may trip over ECI
// info existing and fail to scan.
// See:
// - https://github.com/CatimaLoyalty/Android/issues/2921
// - https://github.com/CatimaLoyalty/Android/issues/2932
//
// Just not always passing the encoding hint is slightly hacky, but in 5+ years of Catima
// cards without encode hints have never caused any issues (unless they were UTF-8), yet
// just days after passing ISO-8859-1 as CHARACTER_SET in the encode hints already 2
// scan failures were reported (one for QR, one for Aztec).
if (!Objects.equals(encoding.name(), StandardCharsets.ISO_8859_1.name())) {
Log.d(TAG, "Chosen encoding is not ISO_8859_1, so passing as encoding hint");
encodeHints.put(EncodeHintType.CHARACTER_SET, encoding);
} else {
Log.d(TAG, "Not passing encoding as encoding hint");
}
BitMatrix bitMatrix;
try {
try {
bitMatrix = writer.encode(cardId, format.format(), imageWidth, imageHeight, null);
bitMatrix = writer.encode(cardId, format.format(), imageWidth, imageHeight, encodeHints);
} catch (Exception e) {
// Cast a wider net here and catch any exception, as there are some
// cases where an encoder may fail if the data is invalid for the
@@ -10,6 +10,7 @@ import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import protect.card_locker.async.TaskHandler;
@@ -74,6 +75,9 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue>
return viewHolder.image.getTag() != null && (boolean) viewHolder.image.getTag();
}
// FIXME: The examples in the selector activity are always rendering using ISO-8859-1, even if UTF-8 is the selected type
// This needs some refactoring to properly retrieve the barcode encoding when rendering an active barcode
// This is the case for example when editing an active card, pressing the "Edit barcode" button, then "More options" and then "Enter the barcode manually"
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId, final TextView text) {
final CatimaBarcode format = CatimaBarcode.fromName(formatType);
@@ -92,13 +96,13 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue>
Log.d(TAG, "Generating barcode for type " + formatType);
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, text, true, null, true, false);
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, StandardCharsets.ISO_8859_1, text, true, null, true, false);
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
}
});
} else {
Log.d(TAG, "Generating barcode for type " + formatType);
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, text, true, null, true, false);
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, StandardCharsets.ISO_8859_1, text, true, null, true, false);
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
}
}
@@ -22,6 +22,8 @@ import java.util.List;
import java.util.concurrent.Flow;
import java.util.function.Consumer;
import protect.card_locker.cardview.LoyaltyCardViewActivity;
@RequiresApi(Build.VERSION_CODES.R)
public class CardsOnPowerScreenService extends ControlsProviderService {
@@ -38,15 +38,10 @@ public class CatimaAppCompatActivity extends AppCompatActivity {
Window window = getWindow();
if (window != null) {
boolean darkMode = Utils.isDarkModeEnabled(this);
if (Build.VERSION.SDK_INT >= 23) {
View decorView = window.getDecorView();
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(window, decorView);
wic.setAppearanceLightStatusBars(!darkMode);
window.setStatusBarColor(Color.TRANSPARENT);
} else {
// icons are always white back then
window.setStatusBarColor(darkMode ? Color.TRANSPARENT : Color.argb(127, 0, 0, 0));
}
View decorView = window.getDecorView();
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(window, decorView);
wic.setAppearanceLightStatusBars(!darkMode);
window.setStatusBarColor(Color.TRANSPARENT);
}
// XXX android 9 and below has a nasty rendering bug if the theme was patched earlier
Utils.postPatchColors(this);
@@ -66,7 +61,4 @@ public class CatimaAppCompatActivity extends AppCompatActivity {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
}
}
@@ -0,0 +1,42 @@
package protect.card_locker
import android.graphics.Color
import android.os.Bundle
import android.os.PersistableBundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatDelegate
import protect.card_locker.preferences.Settings
open class CatimaComponentActivity() : ComponentActivity() {
fun fixedEdgeToEdge() {
// Fix edge-to-edge
// When overriding onCreate this does not correctly get applied, which is why it is its own function
// We explicitly need to set the systemBarStyle ourselves, to prevent issues where Android
// for example renders white icons on top of a white statusbar (or black on black)
val settings = Settings(this)
val systemBarStyle = when (settings.theme) {
AppCompatDelegate.MODE_NIGHT_NO ->
SystemBarStyle.light(
scrim = Color.TRANSPARENT,
darkScrim = Color.TRANSPARENT,
)
AppCompatDelegate.MODE_NIGHT_YES ->
SystemBarStyle.dark(
scrim = Color.TRANSPARENT,
)
else ->
SystemBarStyle.auto(
lightScrim = Color.TRANSPARENT,
darkScrim = Color.TRANSPARENT
)
}
enableEdgeToEdge(
statusBarStyle = systemBarStyle,
navigationBarStyle = systemBarStyle
)
}
}
@@ -10,8 +10,12 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.FileNotFoundException;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Currency;
@@ -23,7 +27,7 @@ import java.util.Set;
public class DBHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "Catima.db";
public static final int ORIGINAL_DATABASE_VERSION = 1;
public static final int DATABASE_VERSION = 17;
public static final int DATABASE_VERSION = 20;
// NB: changing these values requires a migration
public static final int DEFAULT_ZOOM_LEVEL = 100;
@@ -49,6 +53,7 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String CARD_ID = "cardid";
public static final String BARCODE_ID = "barcodeid";
public static final String BARCODE_TYPE = "barcodetype";
public static final String BARCODE_ENCODING = "barcodeencoding";
public static final String STAR_STATUS = "starstatus";
public static final String LAST_USED = "lastused";
public static final String ZOOM_LEVEL = "zoomlevel";
@@ -112,6 +117,7 @@ public class DBHelper extends SQLiteOpenHelper {
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
LoyaltyCardDbIds.BARCODE_ID + " TEXT," +
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," +
LoyaltyCardDbIds.BARCODE_ENCODING + " TEXT not null," +
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'," +
LoyaltyCardDbIds.LAST_USED + " INTEGER DEFAULT '0', " +
LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '" + DEFAULT_ZOOM_LEVEL + "', " +
@@ -335,6 +341,160 @@ public class DBHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.ZOOM_LEVEL_WIDTH + " INTEGER DEFAULT '100' ");
}
if (oldVersion < 18 && newVersion >= 18) {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.BARCODE_ENCODING + " TEXT");
}
// On upgrade to 2.41.3, force all existing "Automatic" cards to ISO-8859-1.
// UTF-8 support was only added in 2.42.0, before that, all barcodes were saved without
// any encoding info. As many scanners deal badly with ECI info, automatically guessing
// UTF-8 for old barcodes may break them.
//
// New cards will be saved using either "Automatic" encoding to guess or, for pkpass files,
// whatever encoding is specified.
if (oldVersion < 19 && newVersion >= 19) {
db.execSQL("UPDATE " + LoyaltyCardDbIds.TABLE + " SET " + LoyaltyCardDbIds.BARCODE_ENCODING + " = 'ISO-8859-1' WHERE " + LoyaltyCardDbIds.BARCODE_ENCODING + " IS NULL");
}
if (oldVersion < 20 && newVersion >= 20) {
// SQLite doesn't support modify column
// So we need to create a temp column to make barcode encoding non-nullable
// https://www.sqlite.org/faq.html#q11
db.beginTransaction();
// First migrate all cards to ISO-8859-1
db.execSQL("UPDATE " + LoyaltyCardDbIds.TABLE + " SET " + LoyaltyCardDbIds.BARCODE_ENCODING + " = 'ISO-8859-1' WHERE " + LoyaltyCardDbIds.BARCODE_ENCODING + " IS NULL");
// create temp table
db.execSQL("CREATE TEMPORARY TABLE tmp (" +
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
LoyaltyCardDbIds.STORE + " TEXT not null," +
LoyaltyCardDbIds.NOTE + " TEXT not null," +
LoyaltyCardDbIds.VALID_FROM + " INTEGER," +
LoyaltyCardDbIds.EXPIRY + " INTEGER," +
LoyaltyCardDbIds.BALANCE + " TEXT not null DEFAULT '0'," +
LoyaltyCardDbIds.BALANCE_TYPE + " TEXT," +
LoyaltyCardDbIds.HEADER_COLOR + " INTEGER," +
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
LoyaltyCardDbIds.BARCODE_ID + " TEXT," +
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," +
LoyaltyCardDbIds.BARCODE_ENCODING + " TEXT not null," +
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'," +
LoyaltyCardDbIds.LAST_USED + " INTEGER DEFAULT '0', " +
LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '" + DEFAULT_ZOOM_LEVEL + "', " +
LoyaltyCardDbIds.ZOOM_LEVEL_WIDTH + " INTEGER DEFAULT '" + DEFAULT_ZOOM_LEVEL_WIDTH + "', " +
LoyaltyCardDbIds.ARCHIVE_STATUS + " INTEGER DEFAULT '0' )");
// Insert all cards into temp table
db.execSQL("INSERT INTO tmp (" +
LoyaltyCardDbIds.ID + " ," +
LoyaltyCardDbIds.STORE + " ," +
LoyaltyCardDbIds.NOTE + " ," +
LoyaltyCardDbIds.VALID_FROM + " , " +
LoyaltyCardDbIds.EXPIRY + " ," +
LoyaltyCardDbIds.BALANCE + " ," +
LoyaltyCardDbIds.BALANCE_TYPE + " ," +
LoyaltyCardDbIds.HEADER_COLOR + " ," +
LoyaltyCardDbIds.CARD_ID + " ," +
LoyaltyCardDbIds.BARCODE_ID + " ," +
LoyaltyCardDbIds.BARCODE_TYPE + " ," +
LoyaltyCardDbIds.BARCODE_ENCODING + " ," +
LoyaltyCardDbIds.STAR_STATUS + " ," +
LoyaltyCardDbIds.LAST_USED + " ," +
LoyaltyCardDbIds.ZOOM_LEVEL + " ," +
LoyaltyCardDbIds.ZOOM_LEVEL_WIDTH + " ," +
LoyaltyCardDbIds.ARCHIVE_STATUS + ")" +
" SELECT " +
LoyaltyCardDbIds.ID + " ," +
LoyaltyCardDbIds.STORE + " ," +
LoyaltyCardDbIds.NOTE + " ," +
LoyaltyCardDbIds.VALID_FROM + " ," +
LoyaltyCardDbIds.EXPIRY + " ," +
LoyaltyCardDbIds.BALANCE + " ," +
LoyaltyCardDbIds.BALANCE_TYPE + " ," +
LoyaltyCardDbIds.HEADER_COLOR + " ," +
LoyaltyCardDbIds.CARD_ID + " ," +
LoyaltyCardDbIds.BARCODE_ID + " ," +
LoyaltyCardDbIds.BARCODE_TYPE + " ," +
LoyaltyCardDbIds.BARCODE_ENCODING + " ," +
LoyaltyCardDbIds.STAR_STATUS + " ," +
LoyaltyCardDbIds.LAST_USED + " ," +
LoyaltyCardDbIds.ZOOM_LEVEL + " ," +
LoyaltyCardDbIds.ZOOM_LEVEL_WIDTH + " ," +
LoyaltyCardDbIds.ARCHIVE_STATUS +
" FROM " + LoyaltyCardDbIds.TABLE);
// Drop old table
db.execSQL("DROP TABLE " + LoyaltyCardDbIds.TABLE);
// Create table again
db.execSQL("CREATE TABLE " + LoyaltyCardDbIds.TABLE + "(" +
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
LoyaltyCardDbIds.STORE + " TEXT not null," +
LoyaltyCardDbIds.NOTE + " TEXT not null," +
LoyaltyCardDbIds.VALID_FROM + " INTEGER," +
LoyaltyCardDbIds.EXPIRY + " INTEGER," +
LoyaltyCardDbIds.BALANCE + " TEXT not null DEFAULT '0'," +
LoyaltyCardDbIds.BALANCE_TYPE + " TEXT," +
LoyaltyCardDbIds.HEADER_COLOR + " INTEGER," +
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
LoyaltyCardDbIds.BARCODE_ID + " TEXT," +
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," +
LoyaltyCardDbIds.BARCODE_ENCODING + " TEXT not null," +
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'," +
LoyaltyCardDbIds.LAST_USED + " INTEGER DEFAULT '0', " +
LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '" + DEFAULT_ZOOM_LEVEL + "', " +
LoyaltyCardDbIds.ZOOM_LEVEL_WIDTH + " INTEGER DEFAULT '" + DEFAULT_ZOOM_LEVEL_WIDTH + "', " +
LoyaltyCardDbIds.ARCHIVE_STATUS + " INTEGER DEFAULT '0' )");
// Insert all temp cards into newly created table
db.execSQL("INSERT INTO " + LoyaltyCardDbIds.TABLE + "(" +
LoyaltyCardDbIds.ID + " ," +
LoyaltyCardDbIds.STORE + " ," +
LoyaltyCardDbIds.NOTE + " ," +
LoyaltyCardDbIds.VALID_FROM + " , " +
LoyaltyCardDbIds.EXPIRY + " ," +
LoyaltyCardDbIds.BALANCE + " ," +
LoyaltyCardDbIds.BALANCE_TYPE + " ," +
LoyaltyCardDbIds.HEADER_COLOR + " ," +
LoyaltyCardDbIds.CARD_ID + " ," +
LoyaltyCardDbIds.BARCODE_ID + " ," +
LoyaltyCardDbIds.BARCODE_TYPE + " ," +
LoyaltyCardDbIds.BARCODE_ENCODING + " ," +
LoyaltyCardDbIds.STAR_STATUS + " ," +
LoyaltyCardDbIds.LAST_USED + " ," +
LoyaltyCardDbIds.ZOOM_LEVEL + " ," +
LoyaltyCardDbIds.ZOOM_LEVEL_WIDTH + " ," +
LoyaltyCardDbIds.ARCHIVE_STATUS + ")" +
" SELECT " +
LoyaltyCardDbIds.ID + " ," +
LoyaltyCardDbIds.STORE + " ," +
LoyaltyCardDbIds.NOTE + " ," +
LoyaltyCardDbIds.VALID_FROM + " ," +
LoyaltyCardDbIds.EXPIRY + " ," +
LoyaltyCardDbIds.BALANCE + " ," +
LoyaltyCardDbIds.BALANCE_TYPE + " ," +
LoyaltyCardDbIds.HEADER_COLOR + " ," +
LoyaltyCardDbIds.CARD_ID + " ," +
LoyaltyCardDbIds.BARCODE_ID + " ," +
LoyaltyCardDbIds.BARCODE_TYPE + " ," +
LoyaltyCardDbIds.BARCODE_ENCODING + " ," +
LoyaltyCardDbIds.STAR_STATUS + " ," +
LoyaltyCardDbIds.LAST_USED + " ," +
LoyaltyCardDbIds.ZOOM_LEVEL + " ," +
LoyaltyCardDbIds.ZOOM_LEVEL_WIDTH + " ," +
LoyaltyCardDbIds.ARCHIVE_STATUS +
" FROM tmp");
// Drop tmp table
db.execSQL("DROP TABLE tmp");
db.setTransactionSuccessful();
db.endTransaction();
}
}
public static Set<String> imageFiles(Context context, final SQLiteDatabase database) {
@@ -396,7 +556,8 @@ public class DBHelper extends SQLiteOpenHelper {
public static long insertLoyaltyCard(
final SQLiteDatabase database, final String store, final String note, final Date validFrom,
final Date expiry, final BigDecimal balance, final Currency balanceType, final String cardId,
final String barcodeId, final CatimaBarcode barcodeType, final Integer headerColor,
final String barcodeId, final CatimaBarcode barcodeType, final @NonNull Charset barcodeEncoding,
final Integer headerColor,
final int starStatus, final Long lastUsed, final int archiveStatus) {
database.beginTransaction();
@@ -411,6 +572,7 @@ public class DBHelper extends SQLiteOpenHelper {
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null);
contentValues.put(LoyaltyCardDbIds.BARCODE_ENCODING, barcodeEncoding.name());
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
contentValues.put(LoyaltyCardDbIds.LAST_USED, lastUsed != null ? lastUsed : Utils.getUnixTime());
@@ -430,7 +592,8 @@ public class DBHelper extends SQLiteOpenHelper {
final SQLiteDatabase database, final int id, final String store, final String note,
final Date validFrom, final Date expiry, final BigDecimal balance,
final Currency balanceType, final String cardId, final String barcodeId,
final CatimaBarcode barcodeType, final Integer headerColor, final int starStatus,
final CatimaBarcode barcodeType, final @NonNull Charset barcodeEncoding,
final Integer headerColor, final int starStatus,
final Long lastUsed, final int archiveStatus) {
database.beginTransaction();
@@ -446,6 +609,7 @@ public class DBHelper extends SQLiteOpenHelper {
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null);
contentValues.put(LoyaltyCardDbIds.BARCODE_ENCODING, barcodeEncoding.name());
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
contentValues.put(LoyaltyCardDbIds.LAST_USED, lastUsed != null ? lastUsed : Utils.getUnixTime());
@@ -465,7 +629,8 @@ public class DBHelper extends SQLiteOpenHelper {
SQLiteDatabase database, final int id, final String store, final String note,
final Date validFrom, final Date expiry, final BigDecimal balance,
final Currency balanceType, final String cardId, final String barcodeId,
final CatimaBarcode barcodeType, final Integer headerColor, final int starStatus,
final CatimaBarcode barcodeType, final @NonNull Charset barcodeEncoding,
final Integer headerColor, final int starStatus,
final Long lastUsed, final int archiveStatus) {
database.beginTransaction();
@@ -480,6 +645,7 @@ public class DBHelper extends SQLiteOpenHelper {
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null);
contentValues.put(LoyaltyCardDbIds.BARCODE_ENCODING, barcodeEncoding.name());
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
contentValues.put(LoyaltyCardDbIds.LAST_USED, lastUsed != null ? lastUsed : Utils.getUnixTime());
@@ -1,395 +0,0 @@
package protect.card_locker;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.InputType;
import android.util.Log;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.databinding.ImportExportActivityBinding;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult;
import protect.card_locker.importexport.ImportExportResultType;
public class ImportExportActivity extends CatimaAppCompatActivity {
private ImportExportActivityBinding binding;
private static final String TAG = "Catima";
private ImportExportTask importExporter;
private String importAlertTitle;
private String importAlertMessage;
private DataFormat importDataFormat;
private String exportPassword;
private ActivityResultLauncher<Intent> fileCreateLauncher;
private ActivityResultLauncher<String> fileOpenLauncher;
final private TaskHandler mTasks = new TaskHandler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ImportExportActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.importExport);
setContentView(binding.getRoot());
Utils.applyWindowInsets(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
enableToolbarBackButton();
Intent fileIntent = getIntent();
if (fileIntent != null && fileIntent.getType() != null) {
chooseImportType(fileIntent.getData());
}
// would use ActivityResultContracts.CreateDocument() but mime type cannot be set
fileCreateLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
Intent intent = result.getData();
if (intent == null) {
Log.e(TAG, "Activity returned NULL data");
return;
}
Uri uri = intent.getData();
if (uri == null) {
Log.e(TAG, "Activity returned NULL uri");
return;
}
// Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files
// FIXME: This is still suboptimal, because showing that the export started is delayed until the network request finishes
new Thread() {
@Override
public void run() {
try {
OutputStream writer = getContentResolver().openOutputStream(uri);
Log.d(TAG, "Starting file export with: " + result);
startExport(writer, uri, exportPassword.toCharArray(), true);
} catch (IOException e) {
Log.e(TAG, "Failed to export file: " + result, e);
onExportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, result.toString()), uri);
}
}
}.start();
});
fileOpenLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), result -> {
if (result == null) {
Log.e(TAG, "Activity returned NULL data");
return;
}
openFileForImport(result, null);
});
// Check that there is a file manager available
final Intent intentCreateDocumentAction = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intentCreateDocumentAction.addCategory(Intent.CATEGORY_OPENABLE);
intentCreateDocumentAction.setType("application/zip");
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "catima.zip");
Button exportButton = binding.exportButton;
exportButton.setOnClickListener(v -> {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(ImportExportActivity.this);
builder.setTitle(R.string.exportPassword);
FrameLayout container = new FrameLayout(ImportExportActivity.this);
final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this);
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(50, 10, 50, 0);
textInputLayout.setLayoutParams(params);
final EditText input = new EditText(ImportExportActivity.this);
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
input.setHint(R.string.exportPasswordHint);
textInputLayout.addView(input);
container.addView(textInputLayout);
builder.setView(container);
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
exportPassword = input.getText().toString();
try {
fileCreateLauncher.launch(intentCreateDocumentAction);
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
});
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
builder.show();
});
// Check that there is a file manager available
Button importFilesystem = binding.importOptionFilesystemButton;
importFilesystem.setOnClickListener(v -> chooseImportType(null));
// FIXME: The importer/exporter is currently quite broken
// To prevent the screen from turning off during import/export and some devices killing Catima as it's no longer foregrounded, force the screen to stay on here
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
private void openFileForImport(Uri uri, char[] password) {
// Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files
// FIXME: This is still suboptimal, because showing that the import started is delayed until the network request finishes
new Thread() {
@Override
public void run() {
try {
InputStream reader = getContentResolver().openInputStream(uri);
Log.d(TAG, "Starting file import with: " + uri);
startImport(reader, uri, importDataFormat, password, true);
} catch (IOException e) {
Log.e(TAG, "Failed to import file: " + uri, e);
onImportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, e.toString()), uri, importDataFormat);
}
}
}.start();
}
private void chooseImportType(@Nullable Uri fileData) {
List<CharSequence> betaImportOptions = new ArrayList<>();
betaImportOptions.add("Fidme");
List<CharSequence> importOptions = new ArrayList<>();
for (String importOption : getResources().getStringArray(R.array.import_types_array)) {
if (betaImportOptions.contains(importOption)) {
importOption = importOption + " (BETA)";
}
importOptions.add(importOption);
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.chooseImportType)
.setItems(importOptions.toArray(new CharSequence[importOptions.size()]), (dialog, which) -> {
switch (which) {
// Catima
case 0:
importAlertTitle = getString(R.string.importCatima);
importAlertMessage = getString(R.string.importCatimaMessage);
importDataFormat = DataFormat.Catima;
break;
// Fidme
case 1:
importAlertTitle = getString(R.string.importFidme);
importAlertMessage = getString(R.string.importFidmeMessage);
importDataFormat = DataFormat.Fidme;
break;
// Loyalty Card Keychain
case 2:
importAlertTitle = getString(R.string.importLoyaltyCardKeychain);
importAlertMessage = getString(R.string.importLoyaltyCardKeychainMessage);
importDataFormat = DataFormat.Catima;
break;
// Voucher Vault
case 3:
importAlertTitle = getString(R.string.importVoucherVault);
importAlertMessage = getString(R.string.importVoucherVaultMessage);
importDataFormat = DataFormat.VoucherVault;
break;
default:
throw new IllegalArgumentException("Unknown DataFormat");
}
if (fileData != null) {
openFileForImport(fileData, null);
return;
}
new MaterialAlertDialogBuilder(this)
.setTitle(importAlertTitle)
.setMessage(importAlertMessage)
.setPositiveButton(R.string.ok, (dialog1, which1) -> {
try {
fileOpenLauncher.launch("*/*");
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
});
builder.show();
}
private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password, final boolean closeWhenDone) {
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
@Override
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
onImportComplete(result, targetUri, dataFormat);
if (closeWhenDone) {
try {
target.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
dataFormat, target, password, listener);
mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter);
}
private void startExport(final OutputStream target, final Uri targetUri, char[] password, final boolean closeWhenDone) {
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
@Override
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
onExportComplete(result, targetUri);
if (closeWhenDone) {
try {
target.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
DataFormat.Catima, target, password, listener);
mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter);
}
@Override
protected void onDestroy() {
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
super.onDestroy();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
private void retryWithPassword(DataFormat dataFormat, Uri uri) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.passwordRequired);
FrameLayout container = new FrameLayout(ImportExportActivity.this);
final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this);
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(50, 10, 50, 0);
textInputLayout.setLayoutParams(params);
final EditText input = new EditText(ImportExportActivity.this);
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
input.setHint(R.string.exportPasswordHint);
textInputLayout.addView(input);
container.addView(textInputLayout);
builder.setView(container);
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
openFileForImport(uri, input.getText().toString().toCharArray());
});
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
builder.show();
}
private String buildResultDialogMessage(ImportExportResult result, boolean isImport) {
int messageId;
if (result.resultType() == ImportExportResultType.Success) {
messageId = isImport ? R.string.importSuccessful : R.string.exportSuccessful;
} else {
messageId = isImport ? R.string.importFailed : R.string.exportFailed;
}
StringBuilder messageBuilder = new StringBuilder(getResources().getString(messageId));
if (result.developerDetails() != null) {
messageBuilder.append("\n\n");
messageBuilder.append(getResources().getString(R.string.include_if_asking_support));
messageBuilder.append("\n\n");
messageBuilder.append(result.developerDetails());
}
return messageBuilder.toString();
}
private void onImportComplete(ImportExportResult result, Uri path, DataFormat dataFormat) {
ImportExportResultType resultType = result.resultType();
if (resultType == ImportExportResultType.BadPassword) {
retryWithPassword(dataFormat, path);
return;
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.importSuccessfulTitle : R.string.importFailedTitle);
builder.setMessage(buildResultDialogMessage(result, true));
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
builder.create().show();
}
private void onExportComplete(ImportExportResult result, final Uri path) {
ImportExportResultType resultType = result.resultType();
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.exportSuccessfulTitle : R.string.exportFailedTitle);
builder.setMessage(buildResultDialogMessage(result, false));
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
if (resultType == ImportExportResultType.Success) {
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel);
builder.setPositiveButton(sendLabel, (dialog, which) -> {
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, path);
sendIntent.setType("text/csv");
// set flag to give temporary permission to external app to use the FileProvider
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
sendLabel));
dialog.dismiss();
});
}
builder.create().show();
}
}
@@ -0,0 +1,424 @@
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
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
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"
internal fun getExportFilename(): String {
val sdf = SimpleDateFormat("yyyyMMdd", Locale.US)
return "catima_${sdf.format(Date())}.zip"
}
}
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, getExportFilename())
}
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()
}
}
@@ -4,11 +4,14 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import androidx.annotation.Nullable;
import java.io.InvalidObjectException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Currency;
import java.util.Date;
@@ -25,6 +28,7 @@ public class ImportURIHelper {
private static final String CARD_ID = DBHelper.LoyaltyCardDbIds.CARD_ID;
private static final String BARCODE_ID = DBHelper.LoyaltyCardDbIds.BARCODE_ID;
private static final String BARCODE_TYPE = DBHelper.LoyaltyCardDbIds.BARCODE_TYPE;
private static final String BARCODE_ENCODING = DBHelper.LoyaltyCardDbIds.BARCODE_ENCODING;
private static final String HEADER_COLOR = DBHelper.LoyaltyCardDbIds.HEADER_COLOR;
private final Context context;
@@ -66,6 +70,7 @@ public class ImportURIHelper {
try {
// These values are allowed to be null
CatimaBarcode barcodeType = null;
Charset barcodeEncoding = StandardCharsets.ISO_8859_1;
Date validFrom = null;
Date expiry = null;
BigDecimal balance = new BigDecimal("0");
@@ -103,6 +108,11 @@ public class ImportURIHelper {
barcodeType = CatimaBarcode.fromName(unparsedBarcodeType);
}
String unparsedBarcodeEncoding = kv.get(BARCODE_ENCODING);
if (unparsedBarcodeEncoding != null && !unparsedBarcodeEncoding.equals("")) {
barcodeEncoding = Charset.forName(unparsedBarcodeEncoding);
}
String unparsedBalance = kv.get(BALANCE);
if (unparsedBalance != null && !unparsedBalance.equals("")) {
balance = new BigDecimal(unparsedBalance);
@@ -136,6 +146,7 @@ public class ImportURIHelper {
cardId,
barcodeId,
barcodeType,
barcodeEncoding,
headerColor,
0,
Utils.getUnixTime(),
@@ -195,6 +206,7 @@ public class ImportURIHelper {
if (loyaltyCard.barcodeType != null) {
fragment = appendFragment(fragment, BARCODE_TYPE, loyaltyCard.barcodeType.name());
}
fragment = appendFragment(fragment, BARCODE_ENCODING, loyaltyCard.barcodeEncoding.name());
if (loyaltyCard.headerColor != null) {
fragment = appendFragment(fragment, HEADER_COLOR, loyaltyCard.headerColor.toString());
}
@@ -1,140 +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 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()) % colors.length();
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;
}
}
@@ -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
}
}
}
@@ -13,6 +13,7 @@ import android.view.View
import android.widget.RemoteViews
import androidx.core.widget.RemoteViewsCompat
import protect.card_locker.DBHelper.LoyaltyCardArchiveFilter
import protect.card_locker.cardview.LoyaltyCardViewActivity
class ListWidget : AppWidgetProvider() {
fun updateAll(context: Context) {
@@ -69,7 +70,9 @@ class ListWidget : AppWidgetProvider() {
if (hasCards) {
// If we have cards, create the list
views = RemoteViews(context.packageName, R.layout.list_widget)
val templateIntent = Intent(context, LoyaltyCardViewActivity::class.java)
val templateIntent = Intent(context, LoyaltyCardViewActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent = PendingIntent.getActivity(
context,
0,
@@ -100,8 +103,7 @@ class ListWidget : AppWidgetProvider() {
val foreground = if (Utils.needsDarkForeground(headerColor)) Color.BLACK else Color.WHITE
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
// FIXME: The icon flow causes a crash up to Android 12L, so SDK_INT is forced up from 23 to 33
// FIXME: The icon flow causes a crash up to Android 12L, so force anything below 33 down this path
if (icon != null && Build.VERSION.SDK_INT >= 32) {
setInt(R.id.item_container_foreground, "setBackgroundColor", foreground)
setImageViewIcon(R.id.item_image, Icon.createWithBitmap(icon))
@@ -128,4 +130,4 @@ class ListWidget : AppWidgetProvider() {
return remoteViews
}
}
}
@@ -9,6 +9,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Currency;
import java.util.Date;
import java.util.List;
@@ -30,6 +32,7 @@ public class LoyaltyCard {
public String barcodeId;
@Nullable
public CatimaBarcode barcodeType;
public Charset barcodeEncoding;
@Nullable
public Integer headerColor;
public int starStatus;
@@ -62,6 +65,7 @@ public class LoyaltyCard {
public static final String BUNDLE_LOYALTY_CARD_CARD_ID = "loyaltyCardCardId";
public static final String BUNDLE_LOYALTY_CARD_BARCODE_ID = "loyaltyCardBarcodeId";
public static final String BUNDLE_LOYALTY_CARD_BARCODE_TYPE = "loyaltyCardBarcodeType";
public static final String BUNDLE_LOYALTY_CARD_BARCODE_ENCODING = "loyaltyCardBarcodeEncoding";
public static final String BUNDLE_LOYALTY_CARD_HEADER_COLOR = "loyaltyCardHeaderColor";
public static final String BUNDLE_LOYALTY_CARD_STAR_STATUS = "loyaltyCardStarStatus";
public static final String BUNDLE_LOYALTY_CARD_LAST_USED = "loyaltyCardLastUsed";
@@ -90,6 +94,7 @@ public class LoyaltyCard {
setCardId("");
setBarcodeId(null);
setBarcodeType(null);
setBarcodeEncoding(StandardCharsets.ISO_8859_1);
setHeaderColor(null);
setStarStatus(0);
setLastUsed(Utils.getUnixTime());
@@ -124,7 +129,7 @@ public class LoyaltyCard {
public LoyaltyCard(final int id, final String store, final String note, @Nullable final Date validFrom,
@Nullable final Date expiry, final BigDecimal balance, @Nullable final Currency balanceType,
final String cardId, @Nullable final String barcodeId, @Nullable final CatimaBarcode barcodeType,
@Nullable final Integer headerColor, final int starStatus,
@NonNull final Charset barcodeEncoding, @Nullable final Integer headerColor, final int starStatus,
final long lastUsed, final int zoomLevel, final int zoomLevelWidth, final int archiveStatus,
@Nullable Bitmap imageThumbnail, @Nullable String imageThumbnailPath,
@Nullable Bitmap imageFront, @Nullable String imageFrontPath,
@@ -139,6 +144,7 @@ public class LoyaltyCard {
setCardId(cardId);
setBarcodeId(barcodeId);
setBarcodeType(barcodeType);
setBarcodeEncoding(barcodeEncoding);
setHeaderColor(headerColor);
setStarStatus(starStatus);
setLastUsed(lastUsed);
@@ -244,6 +250,10 @@ public class LoyaltyCard {
this.barcodeType = barcodeType;
}
public void setBarcodeEncoding(@NonNull Charset barcodeEncoding) {
this.barcodeEncoding = barcodeEncoding;
}
public void setHeaderColor(@Nullable Integer headerColor) {
this.headerColor = headerColor;
}
@@ -379,6 +389,11 @@ public class LoyaltyCard {
} else if (requireFull) {
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_BARCODE_TYPE);
}
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_BARCODE_ENCODING)) {
setBarcodeEncoding(Charset.forName(bundle.getString(BUNDLE_LOYALTY_CARD_BARCODE_ENCODING)));
} else if (requireFull) {
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_BARCODE_ENCODING);
}
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_HEADER_COLOR)) {
int tmpHeaderColor = bundle.getInt(BUNDLE_LOYALTY_CARD_HEADER_COLOR);
setHeaderColor(tmpHeaderColor != -1 ? tmpHeaderColor : null);
@@ -462,6 +477,9 @@ public class LoyaltyCard {
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_BARCODE_TYPE)) {
bundle.putString(BUNDLE_LOYALTY_CARD_BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null);
}
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_BARCODE_ENCODING)) {
bundle.putString(BUNDLE_LOYALTY_CARD_BARCODE_ENCODING, barcodeEncoding.name());
}
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_HEADER_COLOR)) {
bundle.putInt(BUNDLE_LOYALTY_CARD_HEADER_COLOR, headerColor != null ? headerColor : -1);
}
@@ -539,6 +557,8 @@ public class LoyaltyCard {
// barcodeType
int barcodeTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
CatimaBarcode barcodeType = !cursor.isNull(barcodeTypeColumn) ? CatimaBarcode.fromName(cursor.getString(barcodeTypeColumn)) : null;
// barcodeEncoding
Charset barcodeEncoding = Charset.forName(cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_ENCODING)));
// headerColor
int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR);
Integer headerColor = !cursor.isNull(headerColorColumn) ? cursor.getInt(headerColorColumn) : null;
@@ -564,6 +584,7 @@ public class LoyaltyCard {
cardId,
barcodeId,
barcodeType,
barcodeEncoding,
headerColor,
starStatus,
lastUsed,
@@ -593,6 +614,7 @@ public class LoyaltyCard {
Utils.equals(a.barcodeId, b.barcodeId) && // nullable String
Utils.equals(a.barcodeType == null ? null : a.barcodeType.format(),
b.barcodeType == null ? null : b.barcodeType.format()) && // nullable CatimaBarcode with no overridden .equals(), so we need to check .format()
a.barcodeEncoding.name().equals(b.barcodeEncoding.name()) && // non-nullable String
Utils.equals(a.headerColor, b.headerColor) && // nullable Integer
a.starStatus == b.starStatus && // non-nullable int
a.archiveStatus == b.archiveStatus && // non-nullable int
@@ -619,7 +641,7 @@ public class LoyaltyCard {
public String toString() {
return String.format(
"LoyaltyCard{%n id=%s,%n store=%s,%n note=%s,%n validFrom=%s,%n expiry=%s,%n"
+ " balance=%s,%n balanceType=%s,%n cardId=%s,%n barcodeId=%s,%n barcodeType=%s,%n"
+ " balance=%s,%n balanceType=%s,%n cardId=%s,%n barcodeId=%s,%n barcodeType=%s,%n barcodeEncoding=%s,%n"
+ " headerColor=%s,%n starStatus=%s,%n lastUsed=%s,%n zoomLevel=%s,%n zoomLevelWidth=%s,%n archiveStatus=%s%n"
+ " imageThumbnail=%s,%n imageThumbnailPath=%s,%n imageFront=%s,%n imageFrontPath=%s,%n imageBack=%s,%n imageBackPath=%s,%n}",
this.id,
@@ -632,6 +654,7 @@ public class LoyaltyCard {
this.cardId,
this.barcodeId,
this.barcodeType != null ? this.barcodeType.format() : null,
this.barcodeEncoding.name(),
this.headerColor,
this.starStatus,
this.lastUsed,
@@ -70,6 +70,8 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
@@ -123,11 +125,12 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
ChipGroup groupsChips;
AutoCompleteTextView validFromField;
AutoCompleteTextView expiryField;
EditText balanceField;
AutoCompleteTextView balanceCurrencyField;
EditText balanceField;
TextView cardIdFieldView;
AutoCompleteTextView barcodeIdField;
AutoCompleteTextView barcodeTypeField;
AutoCompleteTextView barcodeEncodingField;
ImageView barcodeImage;
View barcodeImageLayout;
View barcodeCaptureLayout;
@@ -148,9 +151,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 +196,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);
}
@@ -229,6 +232,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
viewModel.setHasChanged(true);
}
protected void setLoyaltyCardBarcodeEncoding(@NonNull Charset barcodeEncoding) {
viewModel.getLoyaltyCard().setBarcodeEncoding(barcodeEncoding);
generateBarcode();
viewModel.setHasChanged(true);
}
protected void setLoyaltyCardHeaderColor(@Nullable Integer headerColor) {
viewModel.getLoyaltyCard().setHeaderColor(headerColor);
@@ -329,11 +340,12 @@ 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;
barcodeEncodingField = binding.barcodeEncodingField;
barcodeImage = binding.barcode;
barcodeImage.setClipToOutline(true);
barcodeImageLayout = binding.barcodeLayout;
@@ -373,33 +385,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 +437,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) {
@@ -577,6 +589,25 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
});
barcodeEncodingField.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (!s.toString().isEmpty()) {
Log.d(TAG, "Setting barcode encoding to " + s.toString());
setLoyaltyCardBarcodeEncoding(Charset.forName(s.toString()));
}
}
@Override
public void afterTextChanged(Editable s) {
ArrayList<String> barcodeEncodingList = new ArrayList<>();
barcodeEncodingList.add(StandardCharsets.ISO_8859_1.name());
barcodeEncodingList.add(StandardCharsets.UTF_8.name());
ArrayAdapter<String> barcodeEncodingAdapter = new ArrayAdapter<>(LoyaltyCardEditActivity.this, android.R.layout.select_dialog_item, barcodeEncodingList);
barcodeEncodingField.setAdapter(barcodeEncodingAdapter);
}
});
binding.tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
@@ -688,6 +719,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
});
}
@Override
protected void onDestroy() {
if (mDatabase != null && mDatabase.isOpen()) {
mDatabase.close();
}
super.onDestroy();
}
private void selectTab(int index) {
binding.tabs.selectTab(binding.tabs.getTabAt(index));
viewModel.setTabIndex(index);
@@ -719,7 +758,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
@@ -774,6 +812,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
barcodeIdField.setText(barcodeId != null && !barcodeId.isEmpty() ? barcodeId : getString(R.string.sameAsCardId));
CatimaBarcode barcodeType = viewModel.getLoyaltyCard().barcodeType;
barcodeTypeField.setText(barcodeType != null ? barcodeType.prettyName() : getString(R.string.noBarcode));
Charset barcodeEncoding = viewModel.getLoyaltyCard().barcodeEncoding;
barcodeEncodingField.setText(barcodeEncoding.name());
// We set the balance here (with onResuming/onRestoring == true) to prevent formatBalanceCurrencyField() from setting it (via onTextChanged),
// which can cause issues when switching locale because it parses the balance and e.g. the decimal separator may have changed.
@@ -1480,9 +1520,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
// This makes the DBHelper set it to the current date
// So that new and edited card are always on top when sorting by recently used
if (viewModel.getUpdateLoyaltyCard()) {
DBHelper.updateLoyaltyCard(mDatabase, viewModel.getLoyaltyCardId(), viewModel.getLoyaltyCard().store, viewModel.getLoyaltyCard().note, viewModel.getLoyaltyCard().validFrom, viewModel.getLoyaltyCard().expiry, viewModel.getLoyaltyCard().balance, viewModel.getLoyaltyCard().balanceType, viewModel.getLoyaltyCard().cardId, viewModel.getLoyaltyCard().barcodeId, viewModel.getLoyaltyCard().barcodeType, viewModel.getLoyaltyCard().headerColor, viewModel.getLoyaltyCard().starStatus, null, viewModel.getLoyaltyCard().archiveStatus);
DBHelper.updateLoyaltyCard(mDatabase, viewModel.getLoyaltyCardId(), viewModel.getLoyaltyCard().store, viewModel.getLoyaltyCard().note, viewModel.getLoyaltyCard().validFrom, viewModel.getLoyaltyCard().expiry, viewModel.getLoyaltyCard().balance, viewModel.getLoyaltyCard().balanceType, viewModel.getLoyaltyCard().cardId, viewModel.getLoyaltyCard().barcodeId, viewModel.getLoyaltyCard().barcodeType, viewModel.getLoyaltyCard().barcodeEncoding, viewModel.getLoyaltyCard().headerColor, viewModel.getLoyaltyCard().starStatus, null, viewModel.getLoyaltyCard().archiveStatus);
} else {
viewModel.setLoyaltyCardId((int) DBHelper.insertLoyaltyCard(mDatabase, viewModel.getLoyaltyCard().store, viewModel.getLoyaltyCard().note, viewModel.getLoyaltyCard().validFrom, viewModel.getLoyaltyCard().expiry, viewModel.getLoyaltyCard().balance, viewModel.getLoyaltyCard().balanceType, viewModel.getLoyaltyCard().cardId, viewModel.getLoyaltyCard().barcodeId, viewModel.getLoyaltyCard().barcodeType, viewModel.getLoyaltyCard().headerColor, 0, null, 0));
viewModel.setLoyaltyCardId((int) DBHelper.insertLoyaltyCard(mDatabase, viewModel.getLoyaltyCard().store, viewModel.getLoyaltyCard().note, viewModel.getLoyaltyCard().validFrom, viewModel.getLoyaltyCard().expiry, viewModel.getLoyaltyCard().balance, viewModel.getLoyaltyCard().balanceType, viewModel.getLoyaltyCard().cardId, viewModel.getLoyaltyCard().barcodeId, viewModel.getLoyaltyCard().barcodeType, viewModel.getLoyaltyCard().barcodeEncoding, viewModel.getLoyaltyCard().headerColor, 0, null, 0));
}
try {
@@ -1495,7 +1535,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
DBHelper.setLoyaltyCardGroups(mDatabase, viewModel.getLoyaltyCardId(), selectedGroups);
ShortcutHelper.updateShortcuts(this, DBHelper.getLoyaltyCard(this, mDatabase, viewModel.getLoyaltyCardId()));
ShortcutHelper.updateShortcuts(this);
if (viewModel.getDuplicateFromLoyaltyCardId()) {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
@@ -1597,6 +1637,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
String cardIdString = viewModel.getLoyaltyCard().barcodeId != null ? viewModel.getLoyaltyCard().barcodeId : viewModel.getLoyaltyCard().cardId;
CatimaBarcode barcodeFormat = viewModel.getLoyaltyCard().barcodeType;
Charset barcodeEncoding = viewModel.getLoyaltyCard().barcodeEncoding;
if (cardIdString == null || cardIdString.isEmpty() || barcodeFormat == null) {
barcodeImageLayout.setVisibility(View.GONE);
@@ -1616,13 +1657,13 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Log.d(TAG, "ImageView size now known");
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, LoyaltyCardEditActivity.this, true, false);
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, barcodeEncoding, null, false, LoyaltyCardEditActivity.this, true, false);
viewModel.getTaskHandler().executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
}
});
} else {
Log.d(TAG, "ImageView size known known, creating barcode");
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, this, true, false);
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, barcodeEncoding, null, false, this, true, false);
viewModel.getTaskHandler().executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
}
}
@@ -1,882 +0,0 @@
package protect.card_locker;
import android.app.Activity;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.CursorIndexOutOfBoundsException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.SearchView;
import androidx.core.splashscreen.SplashScreen;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import protect.card_locker.databinding.ContentMainBinding;
import protect.card_locker.databinding.MainActivityBinding;
import protect.card_locker.databinding.SortingOptionBinding;
import protect.card_locker.preferences.Settings;
import protect.card_locker.preferences.SettingsActivity;
public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
private MainActivityBinding binding;
private ContentMainBinding contentMainBinding;
private static final String TAG = "Catima";
public static final String RESTART_ACTIVITY_INTENT = "restart_activity_intent";
private static final int MEDIUM_SCALE_FACTOR_DIP = 460;
static final String STATE_SEARCH_QUERY = "SEARCH_QUERY";
private SQLiteDatabase mDatabase;
private LoyaltyCardCursorAdapter mAdapter;
private ActionMode mCurrentActionMode;
private SearchView mSearchView;
private int mLoyaltyCardCount = 0;
protected String mFilter = "";
private String currentQuery = "";
private String finalQuery = "";
protected Object mGroup = null;
protected DBHelper.LoyaltyCardOrder mOrder = DBHelper.LoyaltyCardOrder.Alpha;
protected DBHelper.LoyaltyCardOrderDirection mOrderDirection = DBHelper.LoyaltyCardOrderDirection.Ascending;
protected int selectedTab = 0;
private RecyclerView mCardList;
private View mHelpSection;
private View mNoMatchingCardsText;
private View mNoGroupCardsText;
private TabLayout groupsTabLayout;
private Runnable mUpdateLoyaltyCardListRunnable;
private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
private ActivityResultLauncher<Intent> mSettingsLauncher;
private ActionMode.Callback mCurrentActionModeCallback = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode inputMode, Menu inputMenu) {
inputMode.getMenuInflater().inflate(R.menu.card_longclick_menu, inputMenu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode inputMode, Menu inputMenu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode inputMode, MenuItem inputItem) {
if (inputItem.getItemId() == R.id.action_share) {
final ImportURIHelper importURIHelper = new ImportURIHelper(MainActivity.this);
try {
importURIHelper.startShareIntent(mAdapter.getSelectedItems());
} catch (UnsupportedEncodingException e) {
Toast.makeText(MainActivity.this, R.string.failedGeneratingShareURL, Toast.LENGTH_LONG).show();
e.printStackTrace();
}
inputMode.finish();
return true;
} else if (inputItem.getItemId() == R.id.action_edit) {
if (mAdapter.getSelectedItemCount() != 1) {
throw new IllegalArgumentException("Cannot edit more than 1 card at a time");
}
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle bundle = new Bundle();
bundle.putInt(LoyaltyCardEditActivity.BUNDLE_ID, mAdapter.getSelectedItems().get(0).id);
bundle.putBoolean(LoyaltyCardEditActivity.BUNDLE_UPDATE, true);
intent.putExtras(bundle);
startActivity(intent);
inputMode.finish();
return true;
} else if (inputItem.getItemId() == R.id.action_delete) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(MainActivity.this);
// The following may seem weird, but it is necessary to give translators enough flexibility.
// For example, in Russian, Android's plural quantity "one" actually refers to "any number ending on 1 but not ending in 11".
// So while in English the extra non-plural form seems unnecessary duplication, it is necessary to give translators enough flexibility.
// In here, we use the plain string when meaning exactly 1, and otherwise use the plural forms
if (mAdapter.getSelectedItemCount() == 1) {
builder.setTitle(R.string.deleteTitle);
builder.setMessage(R.string.deleteConfirmation);
} else {
builder.setTitle(getResources().getQuantityString(R.plurals.deleteCardsTitle, mAdapter.getSelectedItemCount(), mAdapter.getSelectedItemCount()));
builder.setMessage(getResources().getQuantityString(R.plurals.deleteCardsConfirmation, mAdapter.getSelectedItemCount(), mAdapter.getSelectedItemCount()));
}
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Deleting card: " + loyaltyCard.id);
DBHelper.deleteLoyaltyCard(mDatabase, MainActivity.this, loyaltyCard.id);
ShortcutHelper.removeShortcut(MainActivity.this, loyaltyCard.id);
}
TabLayout.Tab tab = groupsTabLayout.getTabAt(selectedTab);
mGroup = tab != null ? tab.getTag() : null;
updateLoyaltyCardList(true);
dialog.dismiss();
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
AlertDialog dialog = builder.create();
dialog.show();
return true;
} else if (inputItem.getItemId() == R.id.action_archive) {
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Archiving card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 1);
ShortcutHelper.removeShortcut(MainActivity.this, loyaltyCard.id);
updateLoyaltyCardList(false);
inputMode.finish();
invalidateOptionsMenu();
}
return true;
} else if (inputItem.getItemId() == R.id.action_unarchive) {
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Unarchiving card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 0);
updateLoyaltyCardList(false);
inputMode.finish();
invalidateOptionsMenu();
}
return true;
} else if (inputItem.getItemId() == R.id.action_star) {
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Starring card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 1);
updateLoyaltyCardList(false);
inputMode.finish();
}
return true;
} else if (inputItem.getItemId() == R.id.action_unstar) {
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Unstarring card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 0);
updateLoyaltyCardList(false);
inputMode.finish();
}
return true;
}
return false;
}
@Override
public void onDestroyActionMode(ActionMode inputMode) {
mAdapter.clearSelections();
mCurrentActionMode = null;
}
};
@Override
protected void onCreate(Bundle inputSavedInstanceState) {
SplashScreen.installSplashScreen(this);
super.onCreate(inputSavedInstanceState);
// Delete old cache files
// These could be temporary images for the cropper, temporary images in LoyaltyCard toBundle/writeParcel/ etc.
new Thread(() -> {
long twentyFourHoursAgo = System.currentTimeMillis() - (1000 * 60 * 60 * 24);
File[] tempFiles = getCacheDir().listFiles();
if (tempFiles == null) {
Log.e(TAG, "getCacheDir().listFiles() somehow returned null, this should never happen... Skipping cache cleanup...");
return;
}
for (File file : tempFiles) {
if (file.lastModified() < twentyFourHoursAgo) {
if (!file.delete()) {
Log.w(TAG, "Failed to delete cache file " + file.getPath());
}
};
}
}).start();
// We should extract the share intent after we called the super.onCreate as it may need to spawn a dialog window and the app needs to be initialized to not crash
extractIntentFields(getIntent());
binding = MainActivityBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
Utils.applyWindowInsets(binding.getRoot());
setSupportActionBar(binding.toolbar);
groupsTabLayout = binding.groups;
contentMainBinding = ContentMainBinding.bind(binding.include.getRoot());
mDatabase = new DBHelper(this).getWritableDatabase();
mUpdateLoyaltyCardListRunnable = () -> {
updateLoyaltyCardList(false);
};
groupsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
selectedTab = tab.getPosition();
Log.d("onTabSelected", "Tab Position " + tab.getPosition());
mGroup = tab.getTag();
updateLoyaltyCardList(false);
// Store active tab in Shared Preference to restore next app launch
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
Context.MODE_PRIVATE);
SharedPreferences.Editor activeTabPrefEditor = activeTabPref.edit();
activeTabPrefEditor.putInt(getString(R.string.sharedpreference_active_tab), tab.getPosition());
activeTabPrefEditor.apply();
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
mHelpSection = contentMainBinding.helpSection;
mNoMatchingCardsText = contentMainBinding.noMatchingCardsText;
mNoGroupCardsText = contentMainBinding.noGroupCardsText;
mCardList = contentMainBinding.list;
mAdapter = new LoyaltyCardCursorAdapter(this, null, this, mUpdateLoyaltyCardListRunnable);
mCardList.setAdapter(mAdapter);
registerForContextMenu(mCardList);
mBarcodeScannerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
// Exit early if the user cancelled the scan (pressed back/home)
if (result.getResultCode() != RESULT_OK) {
return;
}
Intent editIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
editIntent.putExtras(result.getData().getExtras());
startActivity(editIntent);
});
mSettingsLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent intent = result.getData();
if (intent != null && intent.getBooleanExtra(RESTART_ACTIVITY_INTENT, false)) {
recreate();
}
}
});
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (mSearchView != null && !mSearchView.isIconified()) {
mSearchView.setIconified(true);
} else {
finish();
}
}
});
}
@Override
protected void onResume() {
super.onResume();
if (mCurrentActionMode != null) {
mAdapter.clearSelections();
mCurrentActionMode.finish();
}
if (mSearchView != null && !mSearchView.isIconified()) {
mFilter = mSearchView.getQuery().toString();
}
// Start of active tab logic
updateTabGroups(groupsTabLayout);
// Restore selected tab from Shared Preference
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
Context.MODE_PRIVATE);
selectedTab = activeTabPref.getInt(getString(R.string.sharedpreference_active_tab), 0);
// Restore sort preferences from Shared Preferences
mOrder = Utils.getLoyaltyCardOrder(this);
mOrderDirection = Utils.getLoyaltyCardOrderDirection(this);
mGroup = null;
if (groupsTabLayout.getTabCount() != 0) {
TabLayout.Tab tab = groupsTabLayout.getTabAt(selectedTab);
if (tab == null) {
tab = groupsTabLayout.getTabAt(0);
}
groupsTabLayout.selectTab(tab);
assert tab != null;
mGroup = tab.getTag();
} else {
scaleScreen();
}
updateLoyaltyCardList(true);
// End of active tab logic
FloatingActionButton addButton = binding.fabAdd;
addButton.setOnClickListener(v -> {
Intent intent = new Intent(getApplicationContext(), ScanActivity.class);
Bundle bundle = new Bundle();
if (selectedTab != 0) {
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, groupsTabLayout.getTabAt(selectedTab).getText().toString());
}
intent.putExtras(bundle);
mBarcodeScannerLauncher.launch(intent);
});
addButton.bringToFront();
var layoutManager = (GridLayoutManager) mCardList.getLayoutManager();
if (layoutManager != null) {
var settings = new Settings(this);
layoutManager.setSpanCount(settings.getPreferredColumnCount());
}
}
private void displayCardSetupOptions(Menu menu, boolean shouldShow) {
for (int id : new int[]{R.id.action_search, R.id.action_display_options, R.id.action_sort}) {
menu.findItem(id).setVisible(shouldShow);
}
}
private void updateLoyaltyCardCount() {
mLoyaltyCardCount = DBHelper.getLoyaltyCardCount(mDatabase);
}
private void updateLoyaltyCardList(boolean updateCount) {
Group group = null;
if (mGroup != null) {
group = (Group) mGroup;
}
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase, mFilter, group, mOrder, mOrderDirection, mAdapter.showingArchivedCards() ? DBHelper.LoyaltyCardArchiveFilter.All : DBHelper.LoyaltyCardArchiveFilter.Unarchived));
if (updateCount) {
updateLoyaltyCardCount();
// Update menu icons if necessary
invalidateOptionsMenu();
}
if (mLoyaltyCardCount > 0) {
// We want the cardList to be visible regardless of the filtered match count
// to ensure that the noMatchingCardsText doesn't end up being shown below
// the keyboard
mHelpSection.setVisibility(View.GONE);
mNoGroupCardsText.setVisibility(View.GONE);
if (mAdapter.getItemCount() > 0) {
mCardList.setVisibility(View.VISIBLE);
mNoMatchingCardsText.setVisibility(View.GONE);
} else {
mCardList.setVisibility(View.GONE);
if (!mFilter.isEmpty()) {
// Actual Empty Search Result
mNoMatchingCardsText.setVisibility(View.VISIBLE);
mNoGroupCardsText.setVisibility(View.GONE);
} else {
// Group Tab with no Group Cards
mNoMatchingCardsText.setVisibility(View.GONE);
mNoGroupCardsText.setVisibility(View.VISIBLE);
}
}
} else {
mCardList.setVisibility(View.GONE);
mHelpSection.setVisibility(View.VISIBLE);
mNoMatchingCardsText.setVisibility(View.GONE);
mNoGroupCardsText.setVisibility(View.GONE);
}
if (mCurrentActionMode != null) {
mCurrentActionMode.finish();
}
new ListWidget().updateAll(mAdapter.mContext);
}
private void processParseResultList(List<ParseResult> parseResultList, String group, boolean closeAppOnNoBarcode) {
if (parseResultList.isEmpty()) {
throw new IllegalArgumentException("parseResultList may not be empty");
}
Utils.makeUserChooseParseResultFromList(MainActivity.this, parseResultList, new ParseResultListDisambiguatorCallback() {
@Override
public void onUserChoseParseResult(ParseResult parseResult) {
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle bundle = parseResult.toLoyaltyCardBundle(MainActivity.this);
if (group != null) {
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
}
intent.putExtras(bundle);
startActivity(intent);
}
@Override
public void onUserDismissedSelector() {
if (closeAppOnNoBarcode) {
finish();
}
}
});
}
private void onSharedIntent(Intent intent) {
String receivedAction = intent.getAction();
String receivedType = intent.getType();
if (receivedAction == null || receivedType == null) {
return;
}
List<ParseResult> parseResultList;
// Check for shared text
if (receivedAction.equals(Intent.ACTION_SEND) && receivedType.equals("text/plain")) {
LoyaltyCard loyaltyCard = new LoyaltyCard();
loyaltyCard.setCardId(intent.getStringExtra(Intent.EXTRA_TEXT));
parseResultList = Collections.singletonList(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
} else {
// Parse whatever file was sent, regardless of opening or sharing
Uri data;
if (receivedAction.equals(Intent.ACTION_VIEW)) {
data = intent.getData();
} else if (receivedAction.equals(Intent.ACTION_SEND)) {
data = intent.getParcelableExtra(Intent.EXTRA_STREAM);
} else {
Log.e(TAG, "Wrong action type to parse intent");
return;
}
if (receivedType.startsWith("image/")) {
parseResultList = Utils.retrieveBarcodesFromImage(this, data);
} else if (receivedType.equals("application/pdf")) {
parseResultList = Utils.retrieveBarcodesFromPdf(this, data);
} else if (Arrays.asList("application/vnd.apple.pkpass", "application/vnd-com.apple.pkpass").contains(receivedType)) {
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data);
} else if (receivedType.equals("application/vnd.espass-espass")) {
// FIXME: espass is not pkpass
// However, several users stated in https://github.com/CatimaLoyalty/Android/issues/2197 that the formats are extremely similar to the point they could rename an .espass file to .pkpass and have it imported
// So it makes sense to "unofficially" treat it as a PKPASS for now, even though not completely correct
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data);
} else if (receivedType.equals("application/vnd.apple.pkpasses")) {
parseResultList = Utils.retrieveBarcodesFromPkPasses(this, data);
} else {
Log.e(TAG, "Wrong mime-type");
return;
}
}
// Give up if we should parse but there is nothing to parse
if (parseResultList == null || parseResultList.isEmpty()) {
finish();
return;
}
processParseResultList(parseResultList, null, true);
}
private void extractIntentFields(Intent intent) {
onSharedIntent(intent);
}
public void updateTabGroups(TabLayout groupsTabLayout) {
List<Group> newGroups = DBHelper.getGroups(mDatabase);
if (newGroups.size() == 0) {
groupsTabLayout.removeAllTabs();
groupsTabLayout.setVisibility(View.GONE);
return;
}
groupsTabLayout.removeAllTabs();
TabLayout.Tab allTab = groupsTabLayout.newTab();
allTab.setText(R.string.all);
allTab.setTag(null);
groupsTabLayout.addTab(allTab, false);
for (Group group : newGroups) {
TabLayout.Tab tab = groupsTabLayout.newTab();
tab.setText(group._id);
tab.setTag(group);
groupsTabLayout.addTab(tab, false);
}
groupsTabLayout.setVisibility(View.VISIBLE);
}
@Override
// Saving currentQuery to finalQuery for user, this will be used to restore search history, happens when user clicks a card from list
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
finalQuery = currentQuery;
// Putting the query also into outState for later use in onRestoreInstanceState when rotating screen
if (mSearchView != null) {
outState.putString(STATE_SEARCH_QUERY, finalQuery);
}
}
@Override
// Restoring instance state when rotation of screen happens with the goal to restore search query for user
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
finalQuery = savedInstanceState.getString(STATE_SEARCH_QUERY, "");
}
@Override
public boolean onCreateOptionsMenu(Menu inputMenu) {
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
displayCardSetupOptions(inputMenu, mLoyaltyCardCount > 0);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
if (searchManager != null) {
MenuItem searchMenuItem = inputMenu.findItem(R.id.action_search);
mSearchView = (SearchView) searchMenuItem.getActionView();
mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
mSearchView.setSubmitButtonEnabled(false);
mSearchView.setOnCloseListener(() -> {
invalidateOptionsMenu();
return false;
});
/*
* On Android 13 and later, pressing Back while the search view is open hides the keyboard
* and collapses the search view at the same time.
* This brings back the old behavior on Android 12 and lower: pressing Back once
* hides the keyboard, press again while keyboard is hidden to collapse the search view.
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
searchMenuItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(@NonNull MenuItem item) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(@NonNull MenuItem item) {
if (mSearchView.hasFocus()) {
mSearchView.clearFocus();
return false;
}
currentQuery = "";
mFilter = "";
updateLoyaltyCardList(false);
return true;
}
});
}
mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
mFilter = newText;
// New logic to ensure search history after coming back from picked card - user will see the last search query
if (newText.isEmpty()) {
if(!finalQuery.isEmpty()){
// Setting the query text for user after coming back from picked card from finalQuery
mSearchView.setQuery(finalQuery, false);
}
else if(!currentQuery.isEmpty()){
// Else if is needed in case user deletes search - expected behaviour is to show all cards
currentQuery = "";
mSearchView.setQuery(currentQuery, false);
}
} else {
// Setting search query each time user changes the text in search to temporary variable to be used later in finalQuery String which will be used to restore search history
currentQuery = newText;
}
TabLayout.Tab currentTab = groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition());
mGroup = currentTab != null ? currentTab.getTag() : null;
updateLoyaltyCardList(false);
return true;
}
});
// Check if we came from a picked card back to search, in that case we want to show the search view with previous search query
if(!finalQuery.isEmpty()){
// Expand the search view to show the query
searchMenuItem.expandActionView();
// Setting the query text to empty String due to behaviour of onQueryTextChange after coming back from picked card - onQueryTextChange is called automatically without users interaction
finalQuery = "";
mSearchView.setQuery(currentQuery, false);
}
}
return super.onCreateOptionsMenu(inputMenu);
}
@Override
public boolean onOptionsItemSelected(MenuItem inputItem) {
int id = inputItem.getItemId();
if (id == android.R.id.home) {
getOnBackPressedDispatcher().onBackPressed();
}
if (id == R.id.action_display_options) {
mAdapter.showDisplayOptionsDialog();
invalidateOptionsMenu();
return true;
}
if (id == R.id.action_sort) {
AtomicInteger currentIndex = new AtomicInteger();
List<DBHelper.LoyaltyCardOrder> loyaltyCardOrders = Arrays.asList(DBHelper.LoyaltyCardOrder.values());
for (int i = 0; i < loyaltyCardOrders.size(); i++) {
if (mOrder == loyaltyCardOrders.get(i)) {
currentIndex.set(i);
break;
}
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(MainActivity.this);
builder.setTitle(R.string.sort_by);
SortingOptionBinding sortingOptionBinding = SortingOptionBinding
.inflate(LayoutInflater.from(MainActivity.this), null, false);
final View customLayout = sortingOptionBinding.getRoot();
builder.setView(customLayout);
CheckBox showReversed = sortingOptionBinding.checkBoxReverse;
showReversed.setChecked(mOrderDirection == DBHelper.LoyaltyCardOrderDirection.Descending);
builder.setSingleChoiceItems(R.array.sort_types_array, currentIndex.get(), (dialog, which) -> currentIndex.set(which));
builder.setPositiveButton(R.string.sort, (dialog, which) -> {
setSort(
loyaltyCardOrders.get(currentIndex.get()),
showReversed.isChecked() ? DBHelper.LoyaltyCardOrderDirection.Descending : DBHelper.LoyaltyCardOrderDirection.Ascending
);
new ListWidget().updateAll(this);
dialog.dismiss();
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
AlertDialog dialog = builder.create();
dialog.show();
return true;
}
if (id == R.id.action_manage_groups) {
Intent i = new Intent(getApplicationContext(), ManageGroupsActivity.class);
startActivity(i);
return true;
}
if (id == R.id.action_import_export) {
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
startActivity(i);
return true;
}
if (id == R.id.action_settings) {
Intent i = new Intent(getApplicationContext(), SettingsActivity.class);
mSettingsLauncher.launch(i);
return true;
}
if (id == R.id.action_about) {
Intent i = new Intent(getApplicationContext(), AboutActivity.class);
startActivity(i);
return true;
}
return super.onOptionsItemSelected(inputItem);
}
private void setSort(DBHelper.LoyaltyCardOrder order, DBHelper.LoyaltyCardOrderDirection direction) {
// Update values
mOrder = order;
mOrderDirection = direction;
// Store in Shared Preference to restore next app launch
SharedPreferences sortPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_sort),
Context.MODE_PRIVATE);
SharedPreferences.Editor sortPrefEditor = sortPref.edit();
sortPrefEditor.putString(getString(R.string.sharedpreference_sort_order), order.name());
sortPrefEditor.putString(getString(R.string.sharedpreference_sort_direction), direction.name());
sortPrefEditor.apply();
// Update card list
updateLoyaltyCardList(false);
}
@Override
public void onRowLongClicked(int inputPosition) {
enableActionMode(inputPosition);
}
private void enableActionMode(int inputPosition) {
if (mCurrentActionMode == null) {
mCurrentActionMode = startSupportActionMode(mCurrentActionModeCallback);
}
toggleSelection(inputPosition);
}
private void scaleScreen() {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int screenHeight = displayMetrics.heightPixels;
float mediumSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,MEDIUM_SCALE_FACTOR_DIP,getResources().getDisplayMetrics());
boolean shouldScaleSmaller = screenHeight < mediumSizePx;
binding.include.welcomeIcon.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
}
private void toggleSelection(int inputPosition) {
mAdapter.toggleSelection(inputPosition);
int count = mAdapter.getSelectedItemCount();
if (count == 0) {
mCurrentActionMode.finish();
} else {
mCurrentActionMode.setTitle(getResources().getQuantityString(R.plurals.selectedCardCount, count, count));
MenuItem editItem = mCurrentActionMode.getMenu().findItem(R.id.action_edit);
MenuItem archiveItem = mCurrentActionMode.getMenu().findItem(R.id.action_archive);
MenuItem unarchiveItem = mCurrentActionMode.getMenu().findItem(R.id.action_unarchive);
MenuItem starItem = mCurrentActionMode.getMenu().findItem(R.id.action_star);
MenuItem unstarItem = mCurrentActionMode.getMenu().findItem(R.id.action_unstar);
boolean hasStarred = false;
boolean hasUnstarred = false;
boolean hasArchived = false;
boolean hasUnarchived = false;
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
if (loyaltyCard.starStatus == 1) {
hasStarred = true;
} else {
hasUnstarred = true;
}
if (loyaltyCard.archiveStatus == 1) {
hasArchived = true;
} else {
hasUnarchived = true;
}
// We have all types, no need to keep checking
if (hasStarred && hasUnstarred && hasArchived && hasUnarchived) {
break;
}
}
unarchiveItem.setVisible(hasArchived);
archiveItem.setVisible(hasUnarchived);
if (count == 1) {
starItem.setVisible(!hasStarred);
unstarItem.setVisible(!hasUnstarred);
editItem.setVisible(true);
editItem.setEnabled(true);
} else {
starItem.setVisible(hasUnstarred);
unstarItem.setVisible(hasStarred);
editItem.setVisible(false);
editItem.setEnabled(false);
}
mCurrentActionMode.invalidate();
}
}
@Override
public void onRowClicked(int inputPosition) {
if (mAdapter.getSelectedItemCount() > 0) {
enableActionMode(inputPosition);
} else {
// FIXME
//
// There is a really nasty edge case that can happen when someone taps a card but right
// after it swipes (very small window, hard to reproduce). The cursor gets replaced and
// may not have a card at the ID number that is returned from onRowClicked.
//
// The proper fix, obviously, would involve makes sure an onFling can't happen while a
// click is being processed. Sadly, I have not yet found a way to make that possible.
LoyaltyCard loyaltyCard;
try {
loyaltyCard = mAdapter.getCard(inputPosition);
} catch (CursorIndexOutOfBoundsException e) {
Log.w(TAG, "Prevented crash from tap + swipe on ID " + inputPosition + ": " + e);
return;
}
Intent intent = new Intent(this, LoyaltyCardViewActivity.class);
intent.setAction("");
final Bundle b = new Bundle();
b.putInt(LoyaltyCardViewActivity.BUNDLE_ID, loyaltyCard.id);
ArrayList<Integer> cardList = new ArrayList<>();
for (int i = 0; i < mAdapter.getItemCount(); i++) {
cardList.add(mAdapter.getCard(i).id);
}
b.putIntegerArrayList(LoyaltyCardViewActivity.BUNDLE_CARDLIST, cardList);
intent.putExtras(b);
startActivity(intent);
}
}
}
@@ -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.cardview.LoyaltyCardViewActivity
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)
}
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)
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()
// Apply column count setting to card list
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)
ShortcutHelper.updateShortcuts(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"
}
}
@@ -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);
}
}
@@ -0,0 +1,246 @@
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.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import protect.card_locker.LoyaltyCardCursorAdapter.CardAdapterListener
import protect.card_locker.databinding.ActivityManageGroupBinding
import protect.card_locker.preferences.Settings
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()
}
}
)
// Apply column count setting to card list
val layoutManager = mCardList.layoutManager as GridLayoutManager?
if (layoutManager != null) {
val settings = Settings(this)
layoutManager.setSpanCount(settings.getPreferredColumnCount())
}
}
private fun adapterStateToBundle(adapterState: HashMap<Int, Boolean>): Bundle {
val adapterStateBundle = Bundle().apply {
for (entry in adapterState.entries) {
putBoolean(entry.key.toString(), entry.value)
}
}
return adapterStateBundle
}
private fun bundleToAdapterState(adapterStateBundle: Bundle?): Map<Int, Boolean> {
adapterStateBundle ?: return emptyMap()
val adapterStateMap = buildMap {
for (key in adapterStateBundle.keySet()) {
put(key.toInt(), adapterStateBundle.getBoolean(key))
}
}
return adapterStateMap
}
override fun onCreateOptionsMenu(inputMenu: Menu): Boolean {
menuInflater.inflate(R.menu.card_details_menu, inputMenu)
return super.onCreateOptionsMenu(inputMenu)
}
override fun onOptionsItemSelected(inputItem: MenuItem): Boolean {
val id = inputItem.itemId
if (id == R.id.action_display_options) {
mAdapter.showDisplayOptionsDialog()
invalidateOptionsMenu()
return true
}
return super.onOptionsItemSelected(inputItem)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBundle(
SAVE_INSTANCE_ADAPTER_STATE,
adapterStateToBundle(mAdapter.exportInGroupState())
)
outState.putString(SAVE_INSTANCE_CURRENT_GROUP_NAME, mGroupNameText.text.toString())
}
private fun updateLoyaltyCardList() {
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase))
if (mAdapter.itemCount == 0) {
mCardList.visibility = View.GONE
noGroupCardsText.visibility = View.VISIBLE
} else {
mCardList.visibility = View.VISIBLE
noGroupCardsText.visibility = View.GONE
}
}
private fun leaveWithoutSaving() {
if (hasChanged()) {
MaterialAlertDialogBuilder(this@ManageGroupActivity).apply {
setTitle(R.string.leaveWithoutSaveTitle)
setMessage(R.string.leaveWithoutSaveConfirmation)
setPositiveButton(R.string.confirm) { dialog: DialogInterface, _ ->
finish()
}
setNegativeButton(R.string.cancel) { dialog: DialogInterface, _ ->
dialog.dismiss()
}
}.create().show()
} else {
finish()
}
}
override fun onSupportNavigateUp(): Boolean {
onBackPressedDispatcher.onBackPressed()
return true
}
private fun hasChanged(): Boolean {
return mAdapter.hasChanged() || mGroup._id != mGroupNameText.text.trim().toString()
}
override fun onRowLongClicked(inputPosition: Int) {
mAdapter.toggleSelection(inputPosition)
}
override fun onRowClicked(inputPosition: Int) {
mAdapter.toggleSelection(inputPosition)
}
private companion object {
const val SAVE_INSTANCE_ADAPTER_STATE = "adapterState"
const val SAVE_INSTANCE_CURRENT_GROUP_NAME = "currentGroupName"
}
}
@@ -99,7 +99,7 @@ public class ManageGroupCursorAdapter extends LoyaltyCardCursorAdapter {
}
}
public void importInGroupState(HashMap<Integer, Boolean> cardIdInGroupMap) {
public void importInGroupState(Map<Integer, Boolean> cardIdInGroupMap) {
mInGroupOverlay = new HashMap<>(cardIdInGroupMap);
}
@@ -1,247 +0,0 @@
package protect.card_locker;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.text.InputType;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.List;
import protect.card_locker.databinding.ManageGroupsActivityBinding;
public class ManageGroupsActivity extends CatimaAppCompatActivity implements GroupCursorAdapter.GroupAdapterListener {
private ManageGroupsActivityBinding binding;
private static final String TAG = "Catima";
private SQLiteDatabase mDatabase;
private TextView mHelpText;
private RecyclerView mGroupList;
GroupCursorAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ManageGroupsActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.groups);
setContentView(binding.getRoot());
Utils.applyWindowInsets(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
enableToolbarBackButton();
mDatabase = new DBHelper(this).getWritableDatabase();
}
@Override
protected void onResume() {
super.onResume();
FloatingActionButton addButton = binding.fabAdd;
addButton.setOnClickListener(v -> createGroup());
addButton.bringToFront();
mGroupList = binding.include.list;
mHelpText = binding.include.helpText;
// Init group list
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
mGroupList.setLayoutManager(mLayoutManager);
mGroupList.setItemAnimator(new DefaultItemAnimator());
mAdapter = new GroupCursorAdapter(this, null, this);
mGroupList.setAdapter(mAdapter);
updateGroupList();
}
private void updateGroupList() {
mAdapter.swapCursor(DBHelper.getGroupCursor(mDatabase));
if (DBHelper.getGroupCount(mDatabase) == 0) {
mGroupList.setVisibility(View.GONE);
mHelpText.setVisibility(View.VISIBLE);
return;
}
mGroupList.setVisibility(View.VISIBLE);
mHelpText.setVisibility(View.GONE);
}
private void invalidateHomescreenActiveTab() {
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
Context.MODE_PRIVATE);
SharedPreferences.Editor activeTabPrefEditor = activeTabPref.edit();
activeTabPrefEditor.putInt(getString(R.string.sharedpreference_active_tab), 0);
activeTabPrefEditor.apply();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
finish();
}
return super.onOptionsItemSelected(item);
}
private void createGroup() {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
// Header
builder.setTitle(R.string.enter_group_name);
// Layout
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
int contentPadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
params.leftMargin = contentPadding;
params.topMargin = contentPadding / 2;
params.rightMargin = contentPadding;
// EditText with spacing
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setLayoutParams(params);
layout.addView(input);
// Set layout
builder.setView(layout);
// Buttons
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
DBHelper.insertGroup(mDatabase, input.getText().toString().trim());
updateGroupList();
});
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
AlertDialog dialog = builder.create();
// Now that the dialog exists, we can bind something that affects the OK button
input.addTextChangedListener(new SimpleTextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
String groupName = s.toString().trim();
if (groupName.length() == 0) {
input.setError(getString(R.string.group_name_is_empty));
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
return;
}
if (DBHelper.getGroup(mDatabase, groupName) != null) {
input.setError(getString(R.string.group_name_already_in_use));
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
return;
}
input.setError(null);
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
}
});
dialog.show();
// Disable button (must be done **after** dialog is shown to prevent crash
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
// Set focus on input field
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
input.requestFocus();
}
private String getGroupName(View view) {
TextView groupNameTextView = view.findViewById(R.id.name);
return (String) groupNameTextView.getText();
}
private void moveGroup(View view, boolean up) {
List<Group> groups = DBHelper.getGroups(mDatabase);
final String groupName = getGroupName(view);
int currentIndex = DBHelper.getGroup(mDatabase, groupName).order;
int newIndex;
// Reinsert group in correct position
if (up) {
newIndex = currentIndex - 1;
} else {
newIndex = currentIndex + 1;
}
// Don't try to move out of bounds
if (newIndex < 0 || newIndex >= groups.size()) {
return;
}
Group group = groups.remove(currentIndex);
groups.add(newIndex, group);
// Update database
DBHelper.reorderGroups(mDatabase, groups);
// Update UI
updateGroupList();
// Ordering may have changed, so invalidate
invalidateHomescreenActiveTab();
}
@Override
public void onMoveDownButtonClicked(View view) {
moveGroup(view, false);
}
@Override
public void onMoveUpButtonClicked(View view) {
moveGroup(view, true);
}
@Override
public void onEditButtonClicked(View view) {
Intent intent = new Intent(this, ManageGroupActivity.class);
intent.putExtra("group", getGroupName(view));
startActivity(intent);
}
@Override
public void onDeleteButtonClicked(View view) {
final String groupName = getGroupName(view);
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.deleteConfirmationGroup);
builder.setMessage(groupName);
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
DBHelper.deleteGroup(mDatabase, groupName);
updateGroupList();
// Delete may change ordering, so invalidate
invalidateHomescreenActiveTab();
});
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
AlertDialog dialog = builder.create();
dialog.show();
}
}
@@ -0,0 +1,240 @@
package protect.card_locker
import android.content.DialogInterface
import android.content.Intent
import android.database.sqlite.SQLiteDatabase
import android.os.Bundle
import android.text.InputType
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.content.edit
import androidx.core.widget.doOnTextChanged
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import protect.card_locker.GroupCursorAdapter.GroupAdapterListener
import protect.card_locker.databinding.ManageGroupsActivityBinding
class ManageGroupsActivity : CatimaAppCompatActivity(), GroupAdapterListener {
private lateinit var binding: ManageGroupsActivityBinding
private lateinit var mDatabase: SQLiteDatabase
private lateinit var mHelpText: TextView
private lateinit var mGroupList: RecyclerView
private lateinit var mAdapter: GroupCursorAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ManageGroupsActivityBinding.inflate(layoutInflater)
setTitle(R.string.groups)
setContentView(binding.root)
Utils.applyWindowInsets(binding.root)
setSupportActionBar(binding.toolbar)
enableToolbarBackButton()
mDatabase = DBHelper(this).writableDatabase
}
override fun onResume() {
super.onResume()
with(binding.fabAdd) {
setOnClickListener { v: View ->
createGroup()
}
bringToFront()
}
mGroupList = binding.include.list
mHelpText = binding.include.helpText
// Init group list
LinearLayoutManager(applicationContext).apply {
mGroupList.layoutManager = this
}
mGroupList.setItemAnimator(DefaultItemAnimator())
mAdapter = GroupCursorAdapter(this, null, this)
mGroupList.setAdapter(mAdapter)
updateGroupList()
}
private fun updateGroupList() {
mAdapter.swapCursor(DBHelper.getGroupCursor(mDatabase))
if (DBHelper.getGroupCount(mDatabase) == 0) {
mGroupList.visibility = View.GONE
mHelpText.visibility = View.VISIBLE
return
}
mGroupList.visibility = View.VISIBLE
mHelpText.visibility = View.GONE
}
private fun invalidateHomescreenActiveTab() {
val activeTabPref = getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
MODE_PRIVATE
)
activeTabPref.edit {
putInt(getString(R.string.sharedpreference_active_tab), 0)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
finish()
}
return super.onOptionsItemSelected(item)
}
private fun createGroup() {
val builder: AlertDialog.Builder = MaterialAlertDialogBuilder(this)
// Header
builder.setTitle(R.string.enter_group_name)
// Layout
val layout = LinearLayout(this)
layout.orientation = LinearLayout.VERTICAL
val params = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
val contentPadding =
resources.getDimensionPixelSize(R.dimen.alert_dialog_content_padding)
leftMargin = contentPadding
topMargin = contentPadding / 2
rightMargin = contentPadding
}
// EditText with spacing
val input = EditText(this)
input.setInputType(InputType.TYPE_CLASS_TEXT)
input.setLayoutParams(params)
layout.addView(input)
// Set layout
builder.setView(layout)
// Buttons
builder.setPositiveButton(getString(R.string.ok)) { dialog: DialogInterface, which: Int ->
DBHelper.insertGroup(mDatabase, input.text.trim().toString())
updateGroupList()
}
builder.setNegativeButton(getString(R.string.cancel)) { dialog: DialogInterface, which: Int ->
dialog.cancel()
}
val dialog = builder.create()
// Now that the dialog exists, we can bind something that affects the OK button
input.doOnTextChanged { s: CharSequence?, start: Int, before: Int, count: Int ->
val groupName = s?.trim().toString()
if (groupName.isEmpty()) {
input.error = getString(R.string.group_name_is_empty)
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false)
return@doOnTextChanged
}
if (DBHelper.getGroup(mDatabase, groupName) != null) {
input.error = getString(R.string.group_name_already_in_use)
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false)
return@doOnTextChanged
}
input.error = null
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true)
}
dialog.apply {
show()
// Disable button (must be done **after** dialog is shown to prevent crash
getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false)
// Set focus on input field
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
}
input.requestFocus()
}
private fun getGroupName(view: View): String {
val groupNameTextView = view.findViewById<TextView>(R.id.name)
return groupNameTextView.text.toString()
}
private fun moveGroup(view: View, up: Boolean) {
val groups = DBHelper.getGroups(mDatabase)
val groupName = getGroupName(view)
val currentIndex = DBHelper.getGroup(mDatabase, groupName).order
// Reinsert group in correct position
val newIndex: Int = if (up) {
currentIndex - 1
} else {
currentIndex + 1
}
// Don't try to move out of bounds
if (newIndex < 0 || newIndex >= groups.size) {
return
}
val group = groups.removeAt(currentIndex)
groups.add(newIndex, group)
// Update database
DBHelper.reorderGroups(mDatabase, groups)
// Update UI
updateGroupList()
// Ordering may have changed, so invalidate
invalidateHomescreenActiveTab()
}
override fun onMoveDownButtonClicked(view: View) {
moveGroup(view, false)
}
override fun onMoveUpButtonClicked(view: View) {
moveGroup(view, true)
}
override fun onEditButtonClicked(view: View) {
Intent(this, ManageGroupActivity::class.java).apply {
putExtra("group", getGroupName(view))
startActivity(this)
}
}
override fun onDeleteButtonClicked(view: View) {
val groupName = getGroupName(view)
MaterialAlertDialogBuilder(this).apply {
setTitle(R.string.deleteConfirmationGroup)
setMessage(groupName)
setPositiveButton(getString(R.string.ok)) { dialog: DialogInterface, which: Int ->
DBHelper.deleteGroup(mDatabase, groupName)
updateGroupList()
// Delete may change ordering, so invalidate
invalidateHomescreenActiveTab()
}
setNegativeButton(getString(R.string.cancel)) { dialog: DialogInterface, which: Int ->
dialog.cancel()
}
}.create().show()
}
}
@@ -1,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;
}
@@ -34,11 +34,6 @@ public class PermissionUtils {
* @return
*/
public static boolean needsCameraPermission(Activity activity) {
// Android only introduced the runtime permission system in Marshmallow (Android 6.0)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false;
}
return ContextCompat.checkSelfPermission(activity, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED;
}
@@ -49,21 +44,14 @@ public class PermissionUtils {
* @param activity
* @param requestCode
*/
public static void requestStorageReadPermission(CatimaAppCompatActivity activity, int requestCode) {
public static void requestStorageReadPermission(Activity activity, int requestCode) {
String[] permissions = new String[]{ android.Manifest.permission.READ_EXTERNAL_STORAGE };
int[] mockedResults = new int[]{ PackageManager.PERMISSION_GRANTED };
if (needsStorageReadPermission(activity)) {
ActivityCompat.requestPermissions(activity, permissions, requestCode);
} else {
// FIXME: This points to onMockedRequestPermissionResult instead of to
// onRequestPermissionResult because onRequestPermissionResult was only introduced in
// Android 6.0 (SDK 23) and we and to support Android 5.0 (SDK 21) too.
//
// When minSdk becomes 23, this should point to onRequestPermissionResult directly and
// the activity input variable should be changed from CatimaAppCompatActivity to
// Activity.
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
activity.onRequestPermissionsResult(requestCode, permissions, mockedResults);
}
}
@@ -74,21 +62,14 @@ public class PermissionUtils {
* @param activity
* @param requestCode
*/
public static void requestCameraPermission(CatimaAppCompatActivity activity, int requestCode) {
public static void requestCameraPermission(Activity activity, int requestCode) {
String[] permissions = new String[]{ Manifest.permission.CAMERA };
int[] mockedResults = new int[]{ PackageManager.PERMISSION_GRANTED };
if (needsCameraPermission(activity)) {
ActivityCompat.requestPermissions(activity, permissions, requestCode);
} else {
// FIXME: This points to onMockedRequestPermissionResult instead of to
// onRequestPermissionResult because onRequestPermissionResult was only introduced in
// Android 6.0 (SDK 23) and we and to support Android 5.0 (SDK 21) too.
//
// When minSdk becomes 23, this should point to onRequestPermissionResult directly and
// the activity input variable should be changed from CatimaAppCompatActivity to
// Activity.
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
activity.onRequestPermissionsResult(requestCode, permissions, mockedResults);
}
}
}
@@ -14,6 +14,8 @@ import org.json.JSONObject
import java.io.FileNotFoundException
import java.io.IOException
import java.math.BigDecimal
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.text.DateFormat
import java.text.ParseException
import java.time.ZonedDateTime
@@ -40,6 +42,7 @@ class PkpassParser(context: Context, uri: Uri?) {
private var cardId: String = context.getString(R.string.noBarcode)
private var barcodeId: String? = null
private var barcodeType: CatimaBarcode? = null
private var barcodeEncoding: Charset = StandardCharsets.ISO_8859_1
private var headerColor: Int? = null
private val starStatus = 0
private val lastUsed: Long = 0
@@ -134,6 +137,7 @@ class PkpassParser(context: Context, uri: Uri?) {
cardId,
barcodeId,
barcodeType,
barcodeEncoding,
headerColor,
starStatus,
lastUsed,
@@ -342,7 +346,6 @@ class PkpassParser(context: Context, uri: Uri?) {
else -> throw IllegalArgumentException("No valid barcode type")
}
// FIXME: We probably need to do something with the messageEncoding field
try {
cardId = barcodeInfo.getString("altText")
barcodeId = barcodeInfo.getString("message")
@@ -351,6 +354,12 @@ class PkpassParser(context: Context, uri: Uri?) {
barcodeId = null
}
try {
barcodeEncoding = Charset.forName(barcodeInfo.getString("messageEncoding"))
} catch (ignored: Exception) {
// If encoding is not defined, null or otherwise invalid, stick to the default
}
// Don't set barcodeId if it's the same as cardId
if (cardId == barcodeId) {
barcodeId = null
@@ -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();
}
}
}
}
@@ -0,0 +1,591 @@
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)
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()
}
}
}
}
@@ -2,31 +2,35 @@ package protect.card_locker;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Bundle;
import androidx.annotation.VisibleForTesting;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.ColorUtils;
import androidx.core.graphics.drawable.IconCompat;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
class ShortcutHelper {
// Android documentation says that no more than 5 shortcuts
// are supported. However, that may be too many, as not all
// launcher will show all 5. Instead, the number is limited
// to 3 here, so that the most recent shortcut has a good
// chance of being shown.
private static final int MAX_SHORTCUTS = 3;
import protect.card_locker.cardview.LoyaltyCardViewActivity;
public class ShortcutHelper {
/**
* This variable controls the maximum number of shortcuts available.
* It is made public only to make testing easier and should not be
* manually modified. We use -1 here as a default value to check if
* the value has been set either manually by the test scenario or
* automatically in the `updateShortcuts` function.
* Its actual value will be set based on the maximum amount of shortcuts
* declared by the launcher via `getMaxShortcutCountPerActivity`.
*/
@VisibleForTesting
public static int maxShortcuts = -1;
// https://developer.android.com/reference/android/graphics/drawable/AdaptiveIconDrawable.html
private static final int ADAPTIVE_BITMAP_SCALE = 1;
@@ -35,86 +39,45 @@ class ShortcutHelper {
private static final int ADAPTIVE_BITMAP_IMAGE_SIZE = ADAPTIVE_BITMAP_VISIBLE_SIZE + 5 * ADAPTIVE_BITMAP_SCALE;
/**
* Add a card to the app shortcuts, and maintain a list of the most
* recently used cards. If there is already a shortcut for the card,
* the card is marked as the most recently used card. If adding this
* card exceeds the max number of shortcuts, then the least recently
* used card shortcut is discarded.
* Update the dynamic shortcut list with the most recently viewed cards
* based on the lastUsed field. Archived cards are excluded from the shortcuts
* list. The list keeps at most maxShortcuts number of elements.
*/
static void updateShortcuts(Context context, LoyaltyCard card) {
if (card.archiveStatus == 1) {
// Don't add archived card to menu
return;
public static void updateShortcuts(Context context) {
if (maxShortcuts == -1) {
maxShortcuts = ShortcutManagerCompat.getMaxShortcutCountPerActivity(context);
}
LinkedList<ShortcutInfoCompat> list = new LinkedList<>(ShortcutManagerCompat.getDynamicShortcuts(context));
SQLiteDatabase database = new DBHelper(context).getReadableDatabase();
String shortcutId = Integer.toString(card.id);
// Sort the shortcuts by rank, so working with the relative order will be easier.
// This sorts so that the lowest rank is first.
Collections.sort(list, Comparator.comparingInt(ShortcutInfoCompat::getRank));
Integer foundIndex = null;
for (int index = 0; index < list.size(); index++) {
if (list.get(index).getId().equals(shortcutId)) {
// Found the item already
foundIndex = index;
break;
}
}
if (foundIndex != null) {
// If the item is already found, then the list needs to be
// reordered, so that the selected item now has the lowest
// rank, thus letting it survive longer.
ShortcutInfoCompat found = list.remove(foundIndex.intValue());
list.addFirst(found);
} else {
// The item is new to the list. We add it and trim the list later.
ShortcutInfoCompat shortcut = createShortcutBuilder(context, card).build();
list.addFirst(shortcut);
}
LinkedList<ShortcutInfoCompat> finalList = new LinkedList<>();
int rank = 0;
DBHelper dbHelper = new DBHelper(context);
SQLiteDatabase database = dbHelper.getReadableDatabase();
try (Cursor loyaltyCardCursor = DBHelper.getLoyaltyCardCursor(
database,
"",
null,
DBHelper.LoyaltyCardOrder.LastUsed,
DBHelper.LoyaltyCardOrderDirection.Ascending,
DBHelper.LoyaltyCardArchiveFilter.Unarchived
)) {
int rank = 0;
// The ranks are now updated; the order in the list is the rank.
for (int index = 0; index < list.size(); index++) {
ShortcutInfoCompat prevShortcut = list.get(index);
while (rank < maxShortcuts && loyaltyCardCursor.moveToNext()) {
int id = loyaltyCardCursor.getInt(loyaltyCardCursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(context, database, id);
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(context, database, Integer.parseInt(prevShortcut.getId()));
// skip outdated cards that no longer exist
if (loyaltyCard != null) {
ShortcutInfoCompat updatedShortcut = createShortcutBuilder(context, loyaltyCard)
.setRank(rank)
.build();
finalList.addLast(updatedShortcut);
rank++;
// trim the list
if (rank >= MAX_SHORTCUTS) {
break;
}
}
} finally {
dbHelper.close();
}
ShortcutManagerCompat.setDynamicShortcuts(context, finalList);
}
/**
* Remove the given card id from the app shortcuts, if such a
* shortcut exists.
*/
static void removeShortcut(Context context, int cardId) {
ShortcutManagerCompat.removeDynamicShortcuts(context, Collections.singletonList(Integer.toString(cardId)));
}
static @NotNull
Bitmap createAdaptiveBitmap(@NotNull Bitmap in, int paddingColor) {
Bitmap ret = Bitmap.createBitmap(ADAPTIVE_BITMAP_SIZE, ADAPTIVE_BITMAP_SIZE, Bitmap.Config.ARGB_8888);
@@ -130,7 +93,7 @@ class ShortcutHelper {
intent.setAction(Intent.ACTION_MAIN);
// Prevent instances of the view activity from piling up; if one exists let this
// one replace it.
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
final Bundle bundle = new Bundle();
bundle.putInt(LoyaltyCardViewActivity.BUNDLE_ID, loyaltyCard.id);
intent.putExtras(bundle);
@@ -150,4 +113,4 @@ class ShortcutHelper {
.setIntent(intent)
.setIcon(icon);
}
}
}
@@ -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));
}
}
}
}
@@ -0,0 +1,115 @@
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
}
val decorView = window.decorView
val wic = WindowInsetsControllerCompat(window, decorView)
wic.isAppearanceLightStatusBars = !darkMode
}
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"
}
}
@@ -118,7 +118,7 @@ public class Utils {
static final double LUMINANCE_MIDPOINT = 0.5;
static final int BITMAP_SIZE_SMALL = 512;
static final int BITMAP_SIZE_BIG = 2048;
static final int BITMAP_SIZE_BIG = 1600;
static public LetterBitmap generateIcon(Context context, LoyaltyCard loyaltyCard, boolean forShortcut) {
return generateIcon(context, loyaltyCard.store, loyaltyCard.headerColor, forShortcut);
@@ -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,
@@ -963,31 +963,9 @@ public class Utils {
// replace colors in the current theme
public static void patchColors(AppCompatActivity activity) {
Settings settings = new Settings(activity);
String color = settings.getColor();
Resources.Theme theme = activity.getTheme();
Resources resources = activity.getResources();
if (color.equals(resources.getString(R.string.settings_key_pink_theme))) {
theme.applyStyle(R.style.pink, true);
} else if (color.equals(resources.getString(R.string.settings_key_magenta_theme))) {
theme.applyStyle(R.style.magenta, true);
} else if (color.equals(resources.getString(R.string.settings_key_violet_theme))) {
theme.applyStyle(R.style.violet, true);
} else if (color.equals(resources.getString(R.string.settings_key_blue_theme))) {
theme.applyStyle(R.style.blue, true);
} else if (color.equals(resources.getString(R.string.settings_key_sky_blue_theme))) {
theme.applyStyle(R.style.skyblue, true);
} else if (color.equals(resources.getString(R.string.settings_key_green_theme))) {
theme.applyStyle(R.style.green, true);
} else if (color.equals(resources.getString(R.string.settings_key_brown_theme))) {
theme.applyStyle(R.style.brown, true);
} else if (color.equals(resources.getString(R.string.settings_key_catima_theme))) {
// catima theme is AppTheme itself, no dynamic colors nor applyStyle
} else {
// final catch all in case of invalid theme value from older versions
// also handles R.string.settings_key_system_theme
DynamicColors.applyToActivityIfAvailable(activity);
}
DynamicColors.applyToActivityIfAvailable(activity);
if (isDarkModeEnabled(activity) && settings.getOledDark()) {
theme.applyStyle(R.style.DarkBackground, true);
@@ -1129,7 +1107,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 {
@@ -0,0 +1,103 @@
package protect.card_locker.cardview;
import java.util.ArrayList;
import java.util.List;
final class LoyaltyCardImageNavigator {
private final List<LoyaltyCardImageType> imageTypes;
private int currentIndex;
LoyaltyCardImageNavigator(List<LoyaltyCardImageType> imageTypes, int currentIndex) {
this.imageTypes = new ArrayList<>(imageTypes);
this.currentIndex = clampIndex(currentIndex);
}
LoyaltyCardImageType getCurrent() {
if (isEmpty()) {
return LoyaltyCardImageType.NONE;
}
return imageTypes.get(currentIndex);
}
boolean isEmpty() {
return imageTypes.isEmpty();
}
int size() {
return imageTypes.size();
}
boolean remove(LoyaltyCardImageType type) {
int removedIndex = imageTypes.indexOf(type);
if (removedIndex == -1) {
return false;
}
imageTypes.remove(removedIndex);
currentIndex = clampIndex(currentIndex);
return true;
}
boolean canGoPrevious() {
return currentIndex > 0;
}
boolean canGoNext() {
return currentIndex < imageTypes.size() - 1;
}
boolean movePrevious() {
if (!canGoPrevious()) {
return false;
}
currentIndex--;
return true;
}
boolean moveNext(boolean overflow) {
if (isEmpty()) {
return false;
}
if (canGoNext()) {
currentIndex++;
return true;
}
if (overflow) {
currentIndex = 0;
return true;
}
return false;
}
int getCurrentIndex() {
return currentIndex;
}
LoyaltyCardImageType peekNext(boolean overflow) {
if (isEmpty()) {
return LoyaltyCardImageType.NONE;
}
if (canGoNext()) {
return imageTypes.get(currentIndex + 1);
}
return overflow ? imageTypes.get(0) : getCurrent();
}
private int clampIndex(int index) {
if (imageTypes.isEmpty()) {
return 0;
}
return Math.max(0, Math.min(index, imageTypes.size() - 1));
}
}
enum LoyaltyCardImageType {
NONE,
ICON,
BARCODE,
IMAGE_FRONT,
IMAGE_BACK
}
@@ -0,0 +1,183 @@
package protect.card_locker.cardview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.color.MaterialColors;
import java.nio.charset.Charset;
import protect.card_locker.*;
import protect.card_locker.async.TaskHandler;
final class LoyaltyCardMainImageRenderer {
private final Context context;
private final TaskHandler tasks = new TaskHandler();
private final BarcodeImageWriterResultCallback barcodeCallback;
LoyaltyCardMainImageRenderer(
Context context,
BarcodeImageWriterResultCallback barcodeCallback
) {
this.context = context;
this.barcodeCallback = barcodeCallback;
}
void renderCurrent(
LoyaltyCardImageType imageType,
Bitmap frontImageBitmap,
Bitmap backImageBitmap,
CatimaBarcode format,
Charset barcodeEncoding,
String cardIdString,
String barcodeIdString,
ImageView barcodeRenderTarget,
TextView mainImageDescription,
MaterialCardView mainCardView,
boolean isFullscreen,
boolean waitForResize
) {
if (imageType == LoyaltyCardImageType.NONE) {
// With no renderable media left, show the raw card ID instead of an empty card area.
barcodeRenderTarget.setVisibility(View.GONE);
mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
mainImageDescription.setTextColor(
MaterialColors.getColor(
mainImageDescription,
com.google.android.material.R.attr.colorOnSurfaceVariant
)
);
mainImageDescription.setText(cardIdString);
return;
}
if (imageType == LoyaltyCardImageType.BARCODE) {
barcodeRenderTarget.setBackgroundColor(Color.WHITE);
mainCardView.setCardBackgroundColor(Color.WHITE);
mainImageDescription.setTextColor(context.getResources().getColor(R.color.md_theme_light_onSurfaceVariant));
if (waitForResize) {
redrawBarcodeAfterResize(
barcodeRenderTarget,
barcodeIdString,
cardIdString,
format,
barcodeEncoding,
!isFullscreen,
isFullscreen
);
} else {
drawBarcode(
barcodeRenderTarget,
barcodeIdString,
cardIdString,
format,
barcodeEncoding,
!isFullscreen,
isFullscreen
);
}
mainImageDescription.setText(cardIdString);
barcodeRenderTarget.setContentDescription(
context.getString(R.string.barcodeImageDescriptionWithType, format.prettyName())
);
} else if (imageType == LoyaltyCardImageType.IMAGE_FRONT) {
barcodeRenderTarget.setImageBitmap(frontImageBitmap);
barcodeRenderTarget.setBackgroundColor(Color.TRANSPARENT);
mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
mainImageDescription.setTextColor(
MaterialColors.getColor(
mainImageDescription,
com.google.android.material.R.attr.colorOnSurfaceVariant
)
);
mainImageDescription.setText(context.getString(R.string.frontImageDescription));
barcodeRenderTarget.setContentDescription(context.getString(R.string.frontImageDescription));
} else if (imageType == LoyaltyCardImageType.IMAGE_BACK) {
barcodeRenderTarget.setImageBitmap(backImageBitmap);
barcodeRenderTarget.setBackgroundColor(Color.TRANSPARENT);
mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
mainImageDescription.setTextColor(
MaterialColors.getColor(
mainImageDescription,
com.google.android.material.R.attr.colorOnSurfaceVariant
)
);
mainImageDescription.setText(context.getString(R.string.backImageDescription));
barcodeRenderTarget.setContentDescription(context.getString(R.string.backImageDescription));
} else {
throw new IllegalArgumentException("Unknown image type: " + imageType);
}
barcodeRenderTarget.setVisibility(View.VISIBLE);
}
private void redrawBarcodeAfterResize(
ImageView barcodeRenderTarget,
String barcodeIdString,
String cardIdString,
CatimaBarcode format,
Charset barcodeEncoding,
boolean addPadding,
boolean isFullscreen
) {
if (format == null) {
return;
}
// Barcode dimensions depend on the final ImageView size, so wait for layout before rendering.
barcodeRenderTarget.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
barcodeRenderTarget.getViewTreeObserver().removeOnGlobalLayoutListener(this);
drawBarcode(
barcodeRenderTarget,
barcodeIdString,
cardIdString,
format,
barcodeEncoding,
addPadding,
isFullscreen
);
}
});
}
private void drawBarcode(
ImageView barcodeRenderTarget,
String barcodeIdString,
String cardIdString,
CatimaBarcode format,
Charset barcodeEncoding,
boolean addPadding,
boolean isFullscreen
) {
// Barcodes are regenerated eagerly because zoom/fullscreen changes affect the output bitmap.
tasks.flushTaskList(TaskHandler.TYPE.BARCODE, true, false, false);
if (format == null) {
return;
}
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(
context.getApplicationContext(),
barcodeRenderTarget,
barcodeIdString != null ? barcodeIdString : cardIdString,
format,
barcodeEncoding,
null,
false,
barcodeCallback,
addPadding,
isFullscreen
);
tasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
}
}
@@ -0,0 +1,246 @@
package protect.card_locker.cardview;
import android.content.Context;
import android.graphics.Color;
import android.text.InputType;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.DigitsKeyListener;
import android.text.style.ForegroundColorSpan;
import android.text.util.Linkify;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputEditText;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.Predicate;
import protect.card_locker.*;
final class LoyaltyCardViewDialogs {
interface BalanceUpdateListener {
void onBalanceUpdated(BigDecimal newBalance);
}
void showInfoDialog(Context context, LoyaltyCard loyaltyCard, List<Group> loyaltyCardGroups) {
AlertDialog.Builder infoDialog = new MaterialAlertDialogBuilder(context);
int dialogContentPadding = context.getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
infoDialog.setTitle(loyaltyCard.store);
TextView infoTextview = new TextView(context);
infoTextview.setPadding(
dialogContentPadding,
dialogContentPadding / 2,
dialogContentPadding,
0
);
infoTextview.setAutoLinkMask(Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS | Linkify.WEB_URLS);
infoTextview.setTextIsSelectable(true);
SpannableStringBuilder infoText = new SpannableStringBuilder();
if (!loyaltyCard.note.isEmpty()) {
infoText.append(loyaltyCard.note);
}
if (!loyaltyCardGroups.isEmpty()) {
List<String> groupNames = new ArrayList<>();
for (Group group : loyaltyCardGroups) {
groupNames.add(group._id);
}
padSpannableString(infoText);
infoText.append(context.getString(R.string.groupsList, TextUtils.join(", ", groupNames)));
}
if (hasBalance(loyaltyCard)) {
padSpannableString(infoText);
infoText.append(context.getString(
R.string.balanceSentence,
Utils.formatBalance(context, loyaltyCard.balance, loyaltyCard.balanceType)
));
}
appendDateInfo(
context,
infoText,
loyaltyCard.validFrom,
Utils::isNotYetValid,
R.string.validFromSentence,
R.string.validFromSentence
);
appendDateInfo(
context,
infoText,
loyaltyCard.expiry,
Utils::hasExpired,
R.string.expiryStateSentenceExpired,
R.string.expiryStateSentence
);
infoTextview.setText(infoText);
infoDialog.setView(infoTextview);
infoDialog.setPositiveButton(R.string.ok, (dialogInterface, i) -> dialogInterface.dismiss());
infoDialog.create().show();
}
void showBalanceUpdateDialog(
Context context,
LoyaltyCard loyaltyCard,
BalanceUpdateListener balanceUpdateListener
) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context);
builder.setTitle(R.string.updateBalanceTitle);
FrameLayout container = new FrameLayout(context);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
int contentPadding = context.getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
params.leftMargin = contentPadding;
params.topMargin = contentPadding / 2;
params.rightMargin = contentPadding;
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
TextView currentTextview = new TextView(context);
currentTextview.setText(context.getString(
R.string.currentBalanceSentence,
Utils.formatBalance(context, loyaltyCard.balance, loyaltyCard.balanceType)
));
layout.addView(currentTextview);
final TextInputEditText input = new TextInputEditText(context);
input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
input.setKeyListener(DigitsKeyListener.getInstance("0123456789,."));
input.setHint(R.string.updateBalanceHint);
layout.addView(input);
layout.setLayoutParams(params);
container.addView(layout);
builder.setView(container);
builder.setPositiveButton(R.string.spend, (dialogInterface, i) -> {
try {
BigDecimal balanceChange = Utils.parseBalance(input.getText().toString(), loyaltyCard.balanceType);
BigDecimal newBalance = loyaltyCard.balance.subtract(balanceChange).max(new BigDecimal(0));
balanceUpdateListener.onBalanceUpdated(newBalance);
Toast.makeText(
context,
context.getString(
R.string.newBalanceSentence,
Utils.formatBalance(context, newBalance, loyaltyCard.balanceType)
),
Toast.LENGTH_LONG
).show();
} catch (ParseException e) {
Toast.makeText(context, R.string.amountParsingFailed, Toast.LENGTH_LONG).show();
}
});
builder.setNegativeButton(R.string.receive, (dialogInterface, i) -> {
try {
BigDecimal balanceChange = Utils.parseBalance(input.getText().toString(), loyaltyCard.balanceType);
BigDecimal newBalance = loyaltyCard.balance.add(balanceChange);
balanceUpdateListener.onBalanceUpdated(newBalance);
Toast.makeText(
context,
context.getString(
R.string.newBalanceSentence,
Utils.formatBalance(context, newBalance, loyaltyCard.balanceType)
),
Toast.LENGTH_LONG
).show();
} catch (ParseException e) {
Toast.makeText(context, R.string.amountParsingFailed, Toast.LENGTH_LONG).show();
}
});
builder.setNeutralButton(context.getString(R.string.cancel), (dialog, which) -> dialog.cancel());
AlertDialog dialog = builder.create();
// Button state depends on the parsed input, so listeners must be bound after the dialog exists.
input.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
BigDecimal balanceChange;
try {
balanceChange = Utils.parseBalance(s.toString(), loyaltyCard.balanceType);
} catch (ParseException e) {
input.setError(context.getString(R.string.amountParsingFailed));
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false);
return;
}
input.setError(null);
boolean hasNonZeroValue = !balanceChange.equals(new BigDecimal(0));
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(hasNonZeroValue);
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(hasNonZeroValue);
}
});
dialog.show();
// Touching dialog buttons before show() can crash because they are not created yet.
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false);
if (dialog.getWindow() != null) {
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
// Focus immediately so the keyboard opens on the amount field.
input.requestFocus();
}
private boolean hasBalance(LoyaltyCard loyaltyCard) {
return !loyaltyCard.balance.equals(new BigDecimal(0));
}
private SpannableStringBuilder padSpannableString(SpannableStringBuilder spannableStringBuilder) {
if (spannableStringBuilder.length() > 0) {
spannableStringBuilder.append("\n\n");
}
return spannableStringBuilder;
}
private void appendDateInfo(
Context context,
SpannableStringBuilder infoText,
Date date,
Predicate<Date> dateCheck,
@StringRes int dateCheckTrueString,
@StringRes int dateCheckFalseString
) {
if (date == null) {
return;
}
String formattedDate = DateFormat.getDateInstance(DateFormat.LONG).format(date);
padSpannableString(infoText);
if (dateCheck.test(date)) {
int start = infoText.length();
infoText.append(context.getString(dateCheckTrueString, formattedDate));
infoText.setSpan(new ForegroundColorSpan(Color.RED), start, infoText.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
} else {
infoText.append(context.getString(dateCheckFalseString, formattedDate));
}
}
}
@@ -0,0 +1,100 @@
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.semantics.hideFromAccessibility
import androidx.compose.ui.semantics.semantics
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)
}
}
.semantics(mergeDescendants = true) {}
) {
Column(modifier = Modifier.weight(1F)) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium
)
Text(text = message)
}
Text(modifier = Modifier.align(Alignment.CenterVertically).semantics() { hideFromAccessibility() },
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))
}
}
}
)
}
}
@@ -0,0 +1,56 @@
package protect.card_locker.compose
import androidx.activity.OnBackPressedDispatcher
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.isSystemInDarkTheme
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.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import protect.card_locker.R
import protect.card_locker.preferences.Settings
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CatimaTopAppBar(title: String, onBackPressedDispatcher: OnBackPressedDispatcher?) {
// Use pure black in OLED theme
val context = LocalContext.current
val settings = Settings(context)
val isDarkMode = when (settings.theme) {
AppCompatDelegate.MODE_NIGHT_NO -> false
AppCompatDelegate.MODE_NIGHT_YES -> true
else -> isSystemInDarkTheme()
}
val appBarColors = if (isDarkMode && settings.oledDark) {
TopAppBarDefaults.topAppBarColors().copy(containerColor = Color.Black)
} else {
TopAppBarDefaults.topAppBarColors()
}
TopAppBar(
modifier = Modifier.testTag("topbar_catima"),
title = { Text(text = title) },
colors = appBarColors,
navigationIcon = {
if (onBackPressedDispatcher != null) {
IconButton(onClick = { onBackPressedDispatcher.onBackPressed() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.back)
)
}
}
}
)
}
@@ -0,0 +1,51 @@
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.graphics.Color
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))
}
var darkTheme = if (isDynamicColorSupported) {
dynamicDarkColorScheme(context)
} else {
darkColorScheme(primary = colorResource(id = R.color.md_theme_dark_primary))
}
if (settings.oledDark) {
darkTheme = darkTheme.copy(background = Color.Black)
}
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
)
}
@@ -32,7 +32,7 @@ public class CardsContentProvider extends ContentProvider {
public static final String MAJOR_COLUMN = "major";
public static final String MINOR_COLUMN = "minor";
public static final int MAJOR = 1;
public static final int MINOR = 0;
public static final int MINOR = 1;
}
private static final int URI_VERSION = 0;
@@ -52,6 +52,7 @@ public class CardsContentProvider extends ContentProvider {
LoyaltyCardDbIds.CARD_ID,
LoyaltyCardDbIds.BARCODE_ID,
LoyaltyCardDbIds.BARCODE_TYPE,
LoyaltyCardDbIds.BARCODE_ENCODING,
LoyaltyCardDbIds.STAR_STATUS,
LoyaltyCardDbIds.LAST_USED,
LoyaltyCardDbIds.ARCHIVE_STATUS,
@@ -76,13 +77,6 @@ public class CardsContentProvider extends ContentProvider {
@Nullable final String selection,
@Nullable final String[] selectionArgs,
@Nullable final String sortOrder) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Disable the content provider on SDK < 23 since it grants dangerous
// permissions at install-time
Log.w(TAG, "Content provider read is only available for SDK >= 23");
return null;
}
final Settings settings = new Settings(getContext());
if (!settings.getAllowContentProviderRead()) {
Log.w(TAG, "Content provider read is disabled");
@@ -134,6 +134,7 @@ public class CatimaExporter implements Exporter {
DBHelper.LoyaltyCardDbIds.CARD_ID,
DBHelper.LoyaltyCardDbIds.BARCODE_ID,
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
DBHelper.LoyaltyCardDbIds.BARCODE_ENCODING,
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
DBHelper.LoyaltyCardDbIds.STAR_STATUS,
DBHelper.LoyaltyCardDbIds.LAST_USED,
@@ -154,6 +155,7 @@ public class CatimaExporter implements Exporter {
card.cardId,
card.barcodeId,
card.barcodeType != null ? card.barcodeType.name() : "",
card.barcodeEncoding.name(),
card.headerColor,
card.starStatus,
card.lastUsed,
@@ -20,6 +20,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Currency;
@@ -127,10 +128,10 @@ public class CatimaImporter implements Importer {
LoyaltyCard existing = DBHelper.getLoyaltyCard(context, database, card.id);
if (existing == null) {
DBHelper.insertLoyaltyCard(database, card.id, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType,
card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
card.cardId, card.barcodeId, card.barcodeType, card.barcodeEncoding, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
} else if (!isDuplicate(context, existing, card, existingImages, imageChecksums)) {
long newId = DBHelper.insertLoyaltyCard(database, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType,
card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
card.cardId, card.barcodeId, card.barcodeType, card.barcodeEncoding, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
idMap.put(card.id, (int) newId);
}
}
@@ -458,6 +459,15 @@ public class CatimaImporter implements Importer {
barcodeType = CatimaBarcode.fromName(unparsedBarcodeType);
}
// Barcode encoding information is only supported since Catima 2.41.0, so old exports will lack this field
// Database migration 19 forcing ISO-8859-1 on all cards, so we should treat imports from old Catima versions the same and force ISO-8859-1 there too
// This limits the risk of breaking cards when importing an old database backup
Charset barcodeEncoding = StandardCharsets.ISO_8859_1;
String unparsedBarcodeEncoding = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BARCODE_ENCODING, record, "");
if (!unparsedBarcodeEncoding.isEmpty()) {
barcodeEncoding = Charset.forName(unparsedBarcodeEncoding);
}
Integer headerColor = null;
try {
headerColor = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record);
@@ -501,6 +511,7 @@ public class CatimaImporter implements Importer {
cardId,
barcodeId,
barcodeType,
barcodeEncoding,
headerColor,
starStatus,
lastUsed,
@@ -160,6 +160,7 @@ public class FidmeImporter implements Importer {
cardId,
null,
barcodeType,
StandardCharsets.ISO_8859_1,
headerColor,
starStatus,
Utils.getUnixTime(),
@@ -181,7 +182,7 @@ public class FidmeImporter implements Importer {
for (LoyaltyCard card : data.cards) {
// Do not use card.id which is set to -1
DBHelper.insertLoyaltyCard(database, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType,
card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
card.cardId, card.barcodeId, card.barcodeType, card.barcodeEncoding, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
}
}
}
@@ -162,6 +162,7 @@ public class VoucherVaultImporter implements Importer {
cardId,
null,
barcodeType,
StandardCharsets.ISO_8859_1,
headerColor,
0,
Utils.getUnixTime(),
@@ -186,7 +187,7 @@ public class VoucherVaultImporter implements Importer {
for (LoyaltyCard card : data.cards) {
// Do not use card.id which is set to -1
DBHelper.insertLoyaltyCard(database, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType,
card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
card.cardId, card.barcodeId, card.barcodeType, card.barcodeEncoding, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
}
}
}
@@ -82,6 +82,10 @@ public class Settings {
return getBoolean(R.string.settings_key_disable_lockscreen_while_viewing_card, true);
}
public boolean getDisableNfcWhileViewingCard() {
return getBoolean(R.string.settings_key_disable_nfc_while_viewing_card, true);
}
public boolean getAllowContentProviderRead() {
return getBoolean(R.string.settings_key_allow_content_provider_read, true);
}
@@ -90,10 +94,6 @@ public class Settings {
return getBoolean(R.string.settings_key_oled_dark, false);
}
public String getColor() {
return getString(R.string.setting_key_theme_color, mContext.getResources().getString(R.string.settings_key_system_theme));
}
public int getPreferredColumnCount() {
var defaultSymbol = mContext.getResources().getString(R.string.settings_key_automatic_column_count);
var defaultColumnCount = mContext.getResources().getInteger(R.integer.main_view_card_columns);
@@ -1,212 +0,0 @@
package protect.card_locker.preferences;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
import androidx.core.os.LocaleListCompat;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import com.google.android.material.color.DynamicColors;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.stream.Collectors;
import protect.card_locker.BuildConfig;
import protect.card_locker.CatimaAppCompatActivity;
import protect.card_locker.MainActivity;
import protect.card_locker.R;
import protect.card_locker.Utils;
import protect.card_locker.databinding.SettingsActivityBinding;
public class SettingsActivity extends CatimaAppCompatActivity {
private SettingsActivityBinding binding;
private final static String RELOAD_MAIN_STATE = "mReloadMain";
private SettingsFragment fragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = SettingsActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.settings);
setContentView(binding.getRoot());
Utils.applyWindowInsets(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
enableToolbarBackButton();
// Display the fragment as the main content.
fragment = new SettingsFragment();
getSupportFragmentManager().beginTransaction()
.replace(R.id.settings_container, fragment)
.commit();
// restore reload main state
if (savedInstanceState != null) {
fragment.mReloadMain = savedInstanceState.getBoolean(RELOAD_MAIN_STATE);
}
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
finishSettingsActivity();
}
});
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(RELOAD_MAIN_STATE, fragment.mReloadMain);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
finishSettingsActivity();
return true;
}
return super.onOptionsItemSelected(item);
}
private void finishSettingsActivity() {
if (fragment.mReloadMain) {
Intent intent = new Intent();
intent.putExtra(MainActivity.RESTART_ACTIVITY_INTENT, true);
setResult(Activity.RESULT_OK, intent);
} else {
setResult(Activity.RESULT_OK);
}
finish();
}
public static class SettingsFragment extends PreferenceFragmentCompat {
private static final String DIALOG_FRAGMENT_TAG = "SettingsFragment";
public boolean mReloadMain;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
// Show pretty names and summaries
ListPreference themePreference = findPreference(getResources().getString(R.string.settings_key_theme));
assert themePreference != null;
themePreference.setOnPreferenceChangeListener((preference, o) -> {
if (o.toString().equals(getResources().getString(R.string.settings_key_light_theme))) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
} else if (o.toString().equals(getResources().getString(R.string.settings_key_dark_theme))) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
}
return true;
});
ListPreference themeColorPreference = findPreference(getResources().getString(R.string.setting_key_theme_color));
assert themeColorPreference != null;
themeColorPreference.setOnPreferenceChangeListener((preference, o) -> {
refreshActivity(true);
return true;
});
if (!DynamicColors.isDynamicColorAvailable()) {
themeColorPreference.setEntryValues(R.array.color_values_no_dynamic);
themeColorPreference.setEntries(R.array.color_value_strings_no_dynamic);
}
Preference oledDarkPreference = findPreference(getResources().getString(R.string.settings_key_oled_dark));
assert oledDarkPreference != null;
oledDarkPreference.setOnPreferenceChangeListener((preference, newValue) -> {
refreshActivity(true);
return true;
});
ListPreference localePreference = findPreference(getResources().getString(R.string.settings_key_locale));
assert localePreference != null;
CharSequence[] entryValues = localePreference.getEntryValues();
List<CharSequence> entries = new ArrayList<>();
for (CharSequence entry : entryValues) {
if (entry.length() == 0) {
entries.add(getResources().getString(R.string.settings_system_locale));
} else {
Locale entryLocale = Utils.stringToLocale(entry.toString());
entries.add(entryLocale.getDisplayName(entryLocale));
}
}
localePreference.setEntries(entries.toArray(new CharSequence[entryValues.length]));
// Make locale picker preference in sync with system settings
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Locale sysLocale = AppCompatDelegate.getApplicationLocales().get(0);
if (sysLocale == null) {
// Corresponds to "System"
localePreference.setValue("");
} else {
// Need to set preference's value to one of localePreference.getEntryValues() to match the locale.
// Locale.toLanguageTag() theoretically should be one of the values in localePreference.getEntryValues()...
// But it doesn't work for some locales. so trying something more heavyweight.
// Obtain all locales supported by the app.
List<Locale> appLocales = Arrays.stream(localePreference.getEntryValues())
.map(Objects::toString)
.map(Utils::stringToLocale)
.collect(Collectors.toList());
// Get the app locale that best matches the system one
Locale bestMatchLocale = Utils.getBestMatchLocale(appLocales, sysLocale);
// Get its index in supported locales
int index = appLocales.indexOf(bestMatchLocale);
// Set preference value to entry value at that index
localePreference.setValue(localePreference.getEntryValues()[index].toString());
}
}
localePreference.setOnPreferenceChangeListener((preference, newValue) -> {
// See corresponding comment in Utils.updateBaseContextLocale for Android 6- notes
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
refreshActivity(true);
return true;
}
String newLocale = (String) newValue;
// If newLocale is empty, that means "System" was selected
AppCompatDelegate.setApplicationLocales(newLocale.isEmpty() ? LocaleListCompat.getEmptyLocaleList() : LocaleListCompat.create(Utils.stringToLocale(newLocale)));
return true;
});
// Disable content provider on SDK < 23 since dangerous permissions
// are granted at install-time
Preference contentProviderReadPreference = findPreference(getResources().getString(R.string.settings_key_allow_content_provider_read));
assert contentProviderReadPreference != null;
contentProviderReadPreference.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
// Hide crash reporter settings on builds it's not enabled on
Preference crashReporterPreference = findPreference("acra.enable");
assert crashReporterPreference != null;
crashReporterPreference.setVisible(BuildConfig.useAcraCrashReporter);
}
private void refreshActivity(boolean reloadMain) {
mReloadMain = reloadMain || mReloadMain;
Activity activity = getActivity();
if (activity != null) {
activity.recreate();
}
}
}
}
@@ -0,0 +1,174 @@
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 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 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
}
// 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"
}
}
-421
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>
@@ -109,7 +109,8 @@
android:id="@+id/storeNameEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true" />
android:singleLine="true"
android:inputType="textCapWords" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
@@ -191,6 +192,32 @@
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<!-- Barcode encoding -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/inputPadding"
android:paddingTop="@dimen/inputPadding"
android:orientation="horizontal">
<!-- Barcode type -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/barcodeEncodingView"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:hint="@string/barcodeEncoding"
android:labelFor="@+id/barcodeEncodingField">
<AutoCompleteTextView
android:id="@+id/barcodeEncodingField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<!-- Barcode -->
<LinearLayout android:orientation="horizontal"
android:layout_marginTop="10.0dp"
@@ -276,6 +303,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 +339,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 -->
@@ -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>
+9 -9
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>
+59 -46
View File
@@ -6,86 +6,99 @@ Allan Nordhøy
Heimen Stoffels
Oğuz Ersen
FC (Fay) Stegerman
StoyanDimitrov
SlavekB
Katharine Chui
大王叫我来巡山
B o d o
StoyanDimitrov
SlavekB
Katharine Chui / Katie
mondstern
IllusiveMan196
Altonss
Silvério Santos
Michael Moroni
Edgars Andersons
Joel A
Eric
Priit Jõerüüt
Максим Горпиніч
Edgars Andersons
Liner Seven
Altonss
Fjuro
Joel A
GitSpoon
Michael Moroni
Priit Jõerüüt
Eric
GM
Petr Novák
laralem
GitSpoon
Taco
Milo Ivir
nadiafekihahmed
pfaffenrodt
Fjuro
Aayush Gupta
Scrambled777
josé m
Giovanni Donisi
ikanakova
HudobniVolk
Jiri Grönroos
Nyatsuki
Vasilis
Warder
ikanakova
Nyatsuki
Giovanni Donisi
Kachelkaiser
Milo Ivir
Samantaz Fox
Balázs Meskó
Arno-github
Ankit Tiwari
Cliff Heraldo
Sergio Paredes
Jose Delvani
Jiri Grönroos
HudobniVolk
Warder
109247019824
Samantaz Fox
AMIR-G98
Balázs Meskó
Sergio Paredes
Cliff Heraldo
Ankit Tiwari
Arno-github
Feike
Jose Delvani
damjang
Traductor
mdvhimself
Milan Šalka
Горпиніч Максим Олександрович
Robin
தமிழ்நேரம்
huuhaa
Skrripy
Govindgopalyadav
Projjal Moitra
Quentin PAGÈS
StellarSand
ngocanhtve
Marnick L'Eau
Gideon
waffshappen
Marnick L'Eau
JungHee Lee
pacavarn
ngocanhtve
aradxxx
StellarSand
Quentin PAGÈS
Projjal Moitra
Aliaksandr Trush
e-michalak
hajertabbane
delvani
Ziad OUALHADJ
Robin Liu
Ricky Tigg
Renko
inavleb
Denis Shilin
Renko
Ricky Tigg
Robin Liu
Ziad OUALHADJ
しいたけ
Alexander Ivanov
Miha Frangež
stavpup
mrestivill
ezn24
ehrt74
Virginie
Tim Trek
Peter Dave Hello
PRATHAMESH BHAGAT
Michael Gangolf
rudy3
Kim Seohyun
Govind S Nair
Freddo espresso
Augustin LAVILLE
Alì Mortacci
Andre Costa
arshbeerSingh
MisterCosta96
Aliaksandr Trush
Augustin LAVILLE
Freddo espresso
Govind S Nair
Kim Seohyun
echo r"0xX4H" | rev
rudy3
Michael Gangolf
PRATHAMESH BHAGAT
Peter Dave Hello
Thomas Bertels
+11
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>
+64 -59
View File
@@ -1,40 +1,40 @@
<?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">بحث</string>
<string name="action_add">أضف</string>
<string name="noGiftCards">اضغط على زر الإضافة + لإضافة بطاقة، أو استورد من القائمة خلال</string>
<string name="noMatchingGiftCards">لا نتائج. حاول تغيير كلمات البحث.</string>
<string name="action_add">إضافة</string>
<string name="noGiftCards">اضغط على زر + لإضافة بطاقة، أو استورد من خلال قائمة ⋮</string>
<string name="noMatchingGiftCards">لا توجد نتائج. حاول تغيير بحثك.</string>
<string name="storeName">اسم</string>
<string name="note">مذكرة</string>
<string name="note">ملاحظة</string>
<string name="cardId">معرّف البطاقة</string>
<string name="barcodeType">نوع الباركود</string>
<string name="noBarcode">لا يوجد باركود</string>
<string name="star">أضف الى المفضلة</string>
<string name="unstar">حذف من المفضلة</string>
<string name="star">إضافة إلى المفضّلة</string>
<string name="unstar">حذف من المفضّلة</string>
<string name="cancel">إلغاء</string>
<string name="save">حفظ</string>
<string name="edit">تعديل</string>
<string name="delete">مسح</string>
<string name="delete">حذف</string>
<string name="confirm">تأكيد</string>
<string name="deleteConfirmation">مسح هذة البطاقة نهائيا؟</string>
<string name="ok">حسنا</string>
<string name="share">شارك</string>
<string name="sendLabel">ارسل…</string>
<string name="editCardTitle">عدل البطاقة</string>
<string name="addCardTitle">اضف بطاقة</string>
<string name="scanCardBarcode">مسح باركود</string>
<string name="deleteConfirmation">حذف هذه البطاقة نهائيًا؟</string>
<string name="ok">حسنًا</string>
<string name="share">مشاركة</string>
<string name="sendLabel">إرسال…</string>
<string name="editCardTitle">تعديل البطاقة</string>
<string name="addCardTitle">إضافة بطاقة</string>
<string name="scanCardBarcode">مسح الباركود</string>
<string name="cardShortcut">اختصار البطاقة</string>
<string name="noCardsMessage">اضف بطاقة أولا</string>
<string name="noCardsMessage">أضف بطاقة أولًا</string>
<string name="barcodeImageDescriptionWithType">صورة <xliff:g>%s</xliff:g> الباركود</string>
<string name="noCardExistsError">لا يمكن العثور على هذه البطاقة</string>
<string name="failedParsingImportUriError">لا يمكن تحليل الرابط المستورد</string>
<string name="noCardExistsError">لم يتمّ العثور على تلك البطاقة</string>
<string name="failedParsingImportUriError">لا يمكن تحليل عنوان URL المستورد</string>
<string name="importExport">استيراد/تصدير</string>
<string name="importExportHelp">انشاء نسخة احتياطية من بياناتك يسمح بنقلها إلى جهاز آخر.</string>
<string name="importFailed">تعذر إجراء الاستيراد</string>
<string name="exportSuccessfulTitle">متصدر</string>
<string name="importExportHelp">تتيح لك عملية النسخ الاحتياطي لبياناتك نقلها إلى جهاز آخر</string>
<string name="importFailed">تعذّر إجراء عملية الاستيراد</string>
<string name="exportSuccessfulTitle">تمّ التصدير</string>
<string name="exportFailedTitle">فشل التصدير</string>
<string name="exportFailed">لا يمكن عمل التصدير</string>
<string name="exportOptionExplanation">ستتم كتابة البيانات في الموقع الذي تختاره.</string>
<string name="exportFailed">تعذّر إجراء عملية التصدير</string>
<string name="exportOptionExplanation">ستكون البيانات مكتوبة في المسار الذي تختاره</string>
<string name="importOptionFilesystemButton">من نظام الملفات</string>
<string name="about">حول</string>
<string name="app_copyright_old">بناء على Loyalty Card Keychain
@@ -53,7 +53,7 @@
<string name="exportSuccessful">تم تصدير البيانات</string>
<string name="enter_group_name">أدخل اسم المجموعة</string>
<string name="group_edit">تعديل المجموعة</string>
<string name="noGroups">اضغط زر + لإضافة مجموعات للتصنيف.</string>
<string name="noGroups">اضغط زر + لإضافة مجموعات للتصنيف</string>
<string name="noGroupCards">هذه المجموعة فارغة</string>
<string name="group_name_already_in_use">اسم المجموعة قيد الاستخدام بالفعل</string>
<string name="group_name_is_empty">لا يمكن أن يكون اسم المجموعة فارغًا</string>
@@ -71,11 +71,11 @@
<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="importFidmeMessage">حدّد للتصدير من FidMe للاستيراد، ثم حدد أنواع الباركود يدويًا بعد ذلك. \nقم بإنشاءها من ملفك التعريفي في FidMe عن طريق اختيار \"حماية البيانات\" ثم النقر على \"استخراج بياناتي\".</string>
<string name="importVoucherVault">الاستيراد من Voucher Vault</string>
<string name="importVoucherVaultMessage">حدّد ملفك <i>vouchervault.json</i> تصدير من Voucher Vault للاستيراد. \nإنشئها بالضغط على تصدير في Voucher Vault أولاً.</string>
<string name="importVoucherVaultMessage">حدّد ملفك <i>vouchervault.json</i> تصدير من Voucher Vault للاستيراد.\nإنشئها بالضغط على تصدير في Voucher Vault.</string>
<string name="barcodeId">قيمة الباركود</string>
<string name="sameAsCardId">نفس بطاقة الهوية</string>
<string name="setBarcodeId">عيّن قيمة الباركود</string>
@@ -101,14 +101,6 @@
<string name="settings_locale">لغة</string>
<string name="settings_system_locale">النظام</string>
<string name="setIcon">تعيين الصورة المصغرة</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_pink_theme">زهري</string>
<string name="settings_magenta_theme">أرجواني</string>
<string name="settings_violet_theme">البنفسجي</string>
<string name="settings_blue_theme">أزرق</string>
<string name="settings_sky_blue_theme">أزرق سماوي</string>
<string name="settings_green_theme">أخضر</string>
<string name="settings_brown_theme">بني</string>
<string name="app_contributors">أصبح ممكنًا بواسطة: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="sort">فرز</string>
<string name="showMoreInfo">اظهر المعلومات</string>
@@ -130,8 +122,8 @@
<string name="archived">تمت أرشفة البطاقة</string>
<string name="unarchived">البطاقة غير مؤرشفة</string>
<string name="failedLaunchingPhotoPicker">تعذر العثور على تطبيق معرض مدعوم</string>
<string name="noGiftCardsGroup">أنشئ بعض البطاقات، ثم عيّنهم لهذه المجموعة.</string>
<string name="deleteTitle">مسح بطاقة</string>
<string name="noGiftCardsGroup">أنشئ بعض البطاقات، ثم عيّنهم لهذه المجموعة</string>
<string name="deleteTitle">حذف البطاقة</string>
<plurals name="selectedCardCount">
<item quantity="zero"><xliff:g>%d</xliff:g> محدد</item>
<item quantity="one"><xliff:g>%d</xliff:g> محددة</item>
@@ -140,11 +132,11 @@
<item quantity="many"><xliff:g>%d</xliff:g> محدد</item>
<item quantity="other"><xliff:g>%d</xliff:g> محدد</item>
</plurals>
<string name="importing">جار الاستيراد…</string>
<string name="importing">جارٍ الاستيراد…</string>
<string name="exportName">تصدير</string>
<string name="exporting">جار التصدير…</string>
<string name="importSuccessfulTitle">مستورد</string>
<string name="importFailedTitle">فشل بالاستيراد</string>
<string name="importSuccessfulTitle">تمّ الاستيراد</string>
<string name="importFailedTitle">فشل الاستيراد</string>
<string name="sort_by_most_recently_used">الأكثر أستعمالا مؤخرا</string>
<string name="license">رخصة</string>
<string name="sort_by_name">اسم</string>
@@ -158,23 +150,23 @@
<item quantity="other"><xliff:g>%1$d</xliff:g> بطاقات ( <xliff:g id="archivedCount">%2$d</xliff:g> مؤرشفة)</item>
</plurals>
<plurals name="deleteCardsTitle">
<item quantity="zero">مسح <xliff:g>%d</xliff:g> بطاقة</item>
<item quantity="one">مسح <xliff:g>%d</xliff:g> بطاقة</item>
<item quantity="two">مسح <xliff:g>%d</xliff:g> بطاقتين</item>
<item quantity="few">مسح <xliff:g>%d</xliff:g> بطائق</item>
<item quantity="many">مسح <xliff:g>%d</xliff:g> بطاقة</item>
<item quantity="other">مسح <xliff:g>%d</xliff:g> بطاقة</item>
<item quantity="zero">حذف <xliff:g>%d</xliff:g> بطاقة</item>
<item quantity="one">حذف <xliff:g>%d</xliff:g> بطاقة</item>
<item quantity="two">حذف <xliff:g>%d</xliff:g> بطاقتَين</item>
<item quantity="few">حذف <xliff:g>%d</xliff:g> من البطاقات</item>
<item quantity="many">حذف <xliff:g>%d</xliff:g> من البطاقات</item>
<item quantity="other">حذف <xliff:g>%d</xliff:g> من البطاقات</item>
</plurals>
<plurals name="deleteCardsConfirmation">
<item quantity="zero">مسح هذه <xliff:g>%d</xliff:g> البطاقة نهائيا؟</item>
<item quantity="one">مسح هذه <xliff:g>%d</xliff:g> البطاقة نهائيا؟</item>
<item quantity="two">مسح هذه <xliff:g>%d</xliff:g> البطاقتين نهائيا؟</item>
<item quantity="few">مسح هذه <xliff:g>%d</xliff:g> البطاقات نهائيا؟</item>
<item quantity="many">مسح هذه <xliff:g>%d</xliff:g> البطاقات نهائيا؟</item>
<item quantity="other">مسح هذه <xliff:g>%d</xliff:g> البطاقات نهائيا؟</item>
<item quantity="zero">حذف هذه <xliff:g>%d</xliff:g> البطاقة نهائيًا؟</item>
<item quantity="one">حذف هذه <xliff:g>%d</xliff:g> البطاقة نهائيًا؟</item>
<item quantity="two">حذف هذه <xliff:g>%d</xliff:g> البطاقتين نهائيًا؟</item>
<item quantity="few">حذف هذه <xliff:g>%d</xliff:g> البطاقات نهائيًا؟</item>
<item quantity="many">حذف هذه <xliff:g>%d</xliff:g> البطاقات نهائيًا؟</item>
<item quantity="other">حذف هذه <xliff:g>%d</xliff:g> البطاقات نهائيًا؟</item>
</plurals>
<string name="importOptionFilesystemTitle">الاستيراد من نظام الملفات</string>
<string name="importOptionFilesystemExplanation">اختر ملفًا محددًا من نظام الملفات.</string>
<string name="importOptionFilesystemExplanation">اختر ملفًا محددًا من نظام الملفات</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="settings_system_theme">النظام</string>
@@ -221,11 +213,10 @@
<string name="report_error">الإبلاغ عن خطأ</string>
<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="importLoyaltyCardKeychainMessage">حدد تصديرك من سلسلة مفاتيح بطاقة الولاء للتصدير.\nانشئها من قائمة استيراد/تصدير في سلسلة مفاتيح بطاقة الولاء عبر ضغط زر تصدير.</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>
<string name="previousCard">السابق</string>
<string name="nextCard">التالي</string>
<string name="failedToRetrieveImageFile">فشل في استخراج ملف الصورة</string>
@@ -237,17 +228,17 @@
<string name="importCards">استيراد البطاقات</string>
<string name="newBalanceSentence">الرصيد الجديد: <xliff:g>%s</xliff:g></string>
<string name="cameraPermissionDeniedTitle">تعذر الوصول إلى الكاميرا</string>
<string name="noCameraPermissionDirectToSystemSetting">لمسح الباركود، ستحتاج Catima إلى الوصول إلى الكاميرا. اضغط هنا لتغيير إعدادات الأذونات.</string>
<string name="noCameraPermissionDirectToSystemSetting">لمسح الباركود، ستحتاج كاتيما إلى الوصول إلى الكاميرا. اضغط هنا لتغيير إعدادات الأذونات.</string>
<string name="updateBalance">تحديث الرصيد</string>
<string name="updateBalanceHint">أدخل المبلغ</string>
<string name="storageReadPermissionRequired">الصلاحيه للوصل للتخزين مطلوبة لهذا الاجراء…</string>
<string name="storageReadPermissionRequired">الصلاحيه للوصول للتخزين مطلوبة لهذا الاجراء…</string>
<string name="validFromDate">عربيه</string>
<string name="cameraPermissionRequired">إذن للوصول إلى الكاميرا اللازمة لهذا الإجراء…</string>
<string name="anyDate">أي تاريخ</string>
<string name="chooseValidFromDate">اختر صالح من التاريخ</string>
<string name="validFromSentence">صالح من:<xliff:g>%s</xliff:g></string>
<string name="height">الطول</string>
<string name="permissionReadCardsDescription">اقرأ بطاقتك مع جميع التفاصيل، بما فيه الملاحضات والصور</string>
<string name="permissionReadCardsDescription">اقرأ بطاقتك مع جميع التفاصيل، بما فيه الملاحظات والصور</string>
<string name="settings_display_barcode_max_brightness_summary">ظروري لعمل بعض الماسحات الضوئية</string>
<string name="permissionReadCardsLabel">اقرأ بطاقات كاتيما</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">حقوق النشر © 2019<xliff:g>%d</xliff:g> Sylvia van Os والمساهمون</string>
@@ -289,7 +280,7 @@
<string name="spend">انفق</string>
<string name="receive">استلم</string>
<string name="amountParsingFailed">كمية غير صحيحة</string>
<string name="add_manually_warning_message">في بعض المتاجر قيمة الباركود تختلف عن الرقم الموجود على البطاقة. لهذا السبب إدخال الباركود يدوياً لن ينجح دائماً. من المستحسن فحص الباركود بكاميرا بدلا من ذالك. هل انت مُصِر على الاستكمال؟</string>
<string name="add_manually_warning_message">بالنسبة لبعض البطاقات، تختلف قيمة الرمز الشريطي عن الرقم المكتوب عليها. لذا، قد لا ينجح إدخال الرمز الشريطي يدويًا دائمًا. يُنصح بمسح الرمز الشريطي باستخدام الكاميرا. هل ما زلت ترغب في المتابعة؟</string>
<string name="addFromPdfFile">تحديد ملف PDF</string>
<string name="errorReadingFile">لا يمكن قراءة الملف</string>
<string name="failedLaunchingFileManager">لم يتم العثور على مدير ملفات مدعوم</string>
@@ -300,7 +291,7 @@
<string name="exportCancelled">تم الغاء الاستخراج</string>
<string name="useFrontImage">استخدام صورة أمامية</string>
<string name="useBackImage">استخدم صورة خلفية</string>
<string name="addFromPkpass">اختر ملف الدفتر (.pkpass)</string>
<string name="addFromPkpass">اختر ملف الدفتر الحساب [Passbook] (.pkpass. / pkpasses)</string>
<string name="unsupportedFile">هذا الملف غير مدعوم</string>
<string name="generic_error_please_retry">حدث خطأ ما</string>
<string name="settings_use_volume_keys_navigation">بدّل البطاقات باستخدام أزرار الصوت</string>
@@ -322,4 +313,18 @@
<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="card_list_widget_empty">بعد أن تضيف بطاقات الولاء في كاتيما، ستظهر هنا. وإذا كنت تملك البطاقات، تأكد من عدم وجودهن جميعا في الأرشفة.</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="copy_value">قيمة النسخ</string>
<string name="copied_to_clipboard">تم النسخ إلى الحافظة</string>
<string name="nothing_to_copy">لم يتم العثور على قيمة</string>
<string name="barcodeEncoding">طريقة ترميز الرمز الشريطي \"الباركود\"</string>
<string name="back">رجوع</string>
<string name="pref_enable_acra">إخطار بإرسال تقارير الأخطاء</string>
<string name="pref_enable_acra_summary">عند التفعيل, سيتم سؤالك للابلاغ بالاعطال عندما تحدث. لا ولن يتم الابلاغ بالاعطال تلفائيا.</string>
<string name="settings_disable_nfc_while_viewing_card">وقف NFC</string>
<string name="settings_disable_nfc_while_viewing_card_summary">وقف NFC لمنع NFC من العمل أثناء عرض بطاقة</string>
</resources>
+18 -12
View File
@@ -10,7 +10,7 @@
<string name="action_add">Дадаць</string>
<string name="app_name">Catima</string>
<string name="action_search">Пошук</string>
<string name="noGiftCards">Націсніце кнопку + плюс, каб дадаць картку, або імпартуйце праз меню ⋮.</string>
<string name="noGiftCards">Націсніце кнопку + плюс, каб дадаць картку, або імпартуйце праз меню ⋮</string>
<string name="storeName">Назва</string>
<string name="star">Дадаць у абраныя</string>
<string name="unstar">Выдаліць з абраных</string>
@@ -37,7 +37,7 @@
<string name="storageReadPermissionRequired">Для гэтага дзеяння патрабуецца дазвол на чытанне сховішча…</string>
<string name="cameraPermissionRequired">Для гэтага дзеяння патрабуецца дазвол на доступ да камеры…</string>
<string name="permissionReadCardsLabel">Чытаць карты Catima</string>
<string name="exportOptionExplanation">Даныя будуць запісаны ў выбранае вамі месца.</string>
<string name="exportOptionExplanation">Даныя будуць запісаны ў выбранае вамі месца</string>
<string name="importOptionFilesystemExplanation">Выберыце пэўны файл з файлавай сістэмы.</string>
<string name="importOptionFilesystemButton">З файлавай сістэмы</string>
<string name="about">Аб праграме</string>
@@ -65,7 +65,7 @@
<string name="importExport">Імпарт/Экспарт</string>
<string name="importOptionFilesystemTitle">Імпарт з файлавай сістэмы</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Аўтарскае права © 2019–<xliff:g>%d</xliff:g> Sylvia van Os і суаўтары</string>
<string name="importExportHelp">Рэзервовае капіяванне даных дазваляе перанесці іх на іншую прыладу.</string>
<string name="importExportHelp">Рэзервовае капіяванне даных дазваляе перанесці іх на іншую прыладу</string>
<string name="failedParsingImportUriError">Не атрымалася разабраць URI імпарту</string>
<string name="exportFailedTitle">Не ўдалося экспартаваць</string>
<string name="exportFailed">Немагчыма выканаць экспарт</string>
@@ -158,9 +158,6 @@
<string name="settings_oled_dark">Чысты чорны фон для цёмнай тэмы</string>
<string name="selectColor">Выбраць колер</string>
<string name="setIcon">Задаць мініяцюру</string>
<string name="settings_theme_color">Колер тэмы</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_magenta_theme">Пурпурны</string>
<string name="app_contributors">Стала магчымым дзякуючы: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="sort">Сартаваць</string>
<string name="showMoreInfo">Паказаць інфармацыю</string>
@@ -275,8 +272,6 @@
<string name="switchToBackImage">Пераключыцца на задні відарыс</string>
<string name="importFidmeMessage">Каб імпартаваць, выберыце файл <i>fidme-export-request-xxxxxx.zip</i> з FidMe, а потым уручную выберыце тыпы штрыхкодаў. \nСтварыце яго з вашага профілю FidMe, выбраўшы \"Абарону даных\", а затым націснуўшы \"Выняць мае даныя\".</string>
<string name="importCatimaMessage">Каб імпартаваць, выберыце файл <i>catima.zip</i> з Catima. \nСтварыце яго з меню \"Імпарт/Экспарт\" іншай праграмы Catima, спачатку націснуўшы там \"Экспарт\".</string>
<string name="settings_sky_blue_theme">Нябесна-блакітны</string>
<string name="settings_brown_theme">Карычневы</string>
<string name="switchToBarcode">Пераключыцца на штрыхкод</string>
<string name="settings_locale">Мова</string>
<plurals name="groupCardCountWithArchived">
@@ -288,12 +283,8 @@
<string name="unarchived">Карта разархівавана</string>
<string name="updateBarcodeQuestionText">Вы змянілі ID. Вы хочаце таксама абнавіць штрыхкод, каб выкарыстоўваць тое ж значэнне?</string>
<string name="no">Не</string>
<string name="settings_pink_theme">Ружовы</string>
<string name="settings_system_locale">Сістэма</string>
<string name="settings_violet_theme">Фіялетавы</string>
<string name="multipleBarcodesFoundPleaseChooseOne">Які са знойдзеных штрыхкодаў вы хочаце выкарыстоўваць?</string>
<string name="settings_blue_theme">Сіні</string>
<string name="settings_green_theme">Зялёны</string>
<string name="report_error">Паведаміць пра памылку</string>
<string name="failedLaunchingPhotoPicker">Не атрымалася знайсці праграму для галерэі, якая падтрымліваецца</string>
<string name="unsupportedFile">Гэты файл не падтрымліваецца</string>
@@ -306,4 +297,19 @@
<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>
<string name="back">Назад</string>
<string name="copy_value">Скапіяваць значэнне</string>
<string name="copied_to_clipboard">Скапіявана ў буфер абмену</string>
<string name="nothing_to_copy">Значэнне не знойдзенае</string>
<string name="barcodeEncoding">Кадыяванне штрых-кодаў</string>
</resources>
+9 -9
View File
@@ -143,15 +143,6 @@
<item quantity="other">Желаете ли тези <xliff:g>%d</xliff:g> карти да бъдат премахнати\?</item>
</plurals>
<string name="app_contributors">Осъществено от: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="settings_brown_theme">Кафяво</string>
<string name="settings_green_theme">Зелено</string>
<string name="settings_sky_blue_theme">Небесносиньо</string>
<string name="settings_blue_theme">Синьо</string>
<string name="settings_violet_theme">Виолетово</string>
<string name="settings_magenta_theme">Цикламено</string>
<string name="settings_pink_theme">Розово</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_theme_color">Цвят на темата</string>
<string name="settings_system_locale">Система</string>
<string name="settings_locale">Език</string>
<string name="noGroupCards">Групата е празна</string>
@@ -305,4 +296,13 @@
<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>
<string name="back">Назад</string>
<string name="barcodeEncoding">Кодиране на щрихкода</string>
<string name="settings_disable_nfc_while_viewing_card">Пауза на NFC</string>
<string name="settings_disable_nfc_while_viewing_card_summary">Спира NFC, за да бъдат предотвратени безконтактни плащания при преглед на карта</string>
<string name="nfc_blocked_while_viewing_card">NFC е на пауза</string>
<string name="change_settings">Настройки</string>
</resources>
@@ -50,15 +50,6 @@
<string name="turn_flashlight_off">টর্চলাইট বন্ধ করুন</string>
<string name="settings_locale">লোকেল</string>
<string name="settings_system_locale">সিস্টেম লোকেল</string>
<string name="settings_theme_color">থিম রঙ</string>
<string name="settings_catima_theme">কটিমা থিম</string>
<string name="settings_pink_theme">গোলাপী থিম</string>
<string name="settings_magenta_theme">ম্যাজেন্টা থিম</string>
<string name="settings_violet_theme">ভায়োলেট থিম</string>
<string name="settings_blue_theme">নীল থিম</string>
<string name="settings_sky_blue_theme">আকাশী নীল থিম</string>
<string name="settings_green_theme">সবুজ থিম</string>
<string name="settings_brown_theme">বাদামী থিম</string>
<string name="sort">সাজান</string>
<string name="sort_by_name">নামের দ্বারা সাজান</string>
<string name="sort_by_most_recently_used">সর্বাধিক সম্প্রতি ব্যবহৃত দ্বারা সাজান</string>
-5
View File
@@ -2,10 +2,6 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="settings_locale">ভাষা</string>
<string name="action_search">খুঁজুন</string>
<string name="settings_pink_theme">গুলাপি</string>
<string name="settings_blue_theme">নীল</string>
<string name="settings_green_theme">সবুজ</string>
<string name="settings_brown_theme">বাদামি</string>
<string name="save">সংরক্ষণ</string>
<string name="cardId">কার্ড আইডি</string>
<string name="barcodeType">বারকোডের ধরন</string>
@@ -19,7 +15,6 @@
<string name="all">সকল</string>
<string name="never">কখনো না</string>
<string name="currency">মুদ্রা</string>
<string name="settings_violet_theme">বেগুনি</string>
<string name="no">না</string>
<string name="nextCard">পরবর্তী</string>
<string name="action_add">যুক্ত করুন</string>
+239 -34
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="save">Snimi</string>
<string name="cancel">Odustani</string>
<string name="unstar">Ukloni iz omiljenih</string>
@@ -8,15 +8,15 @@
<string name="note">Zabilješka</string>
<string name="storeName">Ime</string>
<string name="noMatchingGiftCards">Nema rezultata. Pokušaj promijeniti pretragu.</string>
<string name="noGiftCards">Kliknite + Plus dugme da dodate kartu ili uvezete nešto iz menija prvo.</string>
<string name="noGiftCards">Kliknite na dugme + plus da biste dodali karticu ili je uvezite iz menija</string>
<string name="action_add">Dodaj</string>
<string name="all">Sve</string>
<string name="noGroupCards">Ova grupa ne sadrži nikakve karte</string>
<string name="noGroups">Kliknite + Plus dugme da dodate grupe za kategorizaciju prvo.</string>
<string name="noGroupCards">Ova grupa je prazna</string>
<string name="noGroups">Kliknite na dugme + plus da biste dodali grupe za kategorizaciju</string>
<string name="groups">Grupe</string>
<string name="enter_group_name">Unesite ime grupe</string>
<string name="exportSuccessful">Podaci o kartama izvožen</string>
<string name="importSuccessful">Uvezeni podaci o kartama</string>
<string name="exportSuccessful">Izvezeni podaci</string>
<string name="importSuccessful">Uvezeni podaci</string>
<string name="intent_import_card_from_url_share_text">Želim podijeliti čestitku s tobom</string>
<string name="settings_disable_lockscreen_while_viewing_card">Spriječi zaključavanje ekrana</string>
<string name="settings_keep_screen_on">Zadrži ekran</string>
@@ -28,47 +28,38 @@
<string name="importLoyaltyCardKeychain">Uvezi iz Loyalty Card Keychain</string>
<string name="importVoucherVault">Uvezi iz trezora vaučer</string>
<string name="barcodeId">Barcode vrijednost</string>
<string name="sameAsCardId">Isto kao i kartica</string>
<string name="sameAsCardId">Isto kao i ID</string>
<string name="setBarcodeId">Postavi vrijednost za bar kod</string>
<string name="unsupportedBarcodeType">Ovaj bar kod još nije prikazan. Ona može biti podržana u kasnijoj verziji app.</string>
<string name="wrongValueForBarcodeType">Izabrana vrijednost nije izvršna</string>
<string name="wrongValueForBarcodeType">Vrijednost nije važeća za odabrani tip barkoda</string>
<string name="intent_import_card_from_url_share_multiple_text">Želim podijeliti karte s tobom</string>
<string name="frontImageDescription">Slika kartice</string>
<string name="backImageDescription">Slika pozadine kartice</string>
<string name="frontImageDescription">Prednja slika</string>
<string name="backImageDescription">Stražnja slika</string>
<string name="photos">Slike</string>
<string name="setFrontImage">Postavi naslovnu sliku</string>
<string name="setBackImage">Vrati sliku</string>
<string name="removeImage">Ukloni sliku</string>
<string name="takePhoto">Slikaj</string>
<string name="updateBarcodeQuestionTitle">Ažurirati bar kod vrijednost\?</string>
<string name="updateBarcodeQuestionText">Promijenio si auto. Da li želite ažurirati bar kod koristiti istu vrijednost\?</string>
<string name="updateBarcodeQuestionText">Promijenili ste ID. Želite li ažurirati i barkod da koristi istu vrijednost?</string>
<string name="yes">Da</string>
<string name="no">Ne.</string>
<string name="passwordRequired">Upišite šifru</string>
<string name="failedGeneratingShareURL">Ne mogu generisati oštriji URL. Molim vas, prijavite ovo.</string>
<string name="passwordRequired">Unesite lozinku</string>
<string name="failedGeneratingShareURL">Nije moguće generirati URL za dijeljenje</string>
<string name="turn_flashlight_on">Upali lampu</string>
<string name="turn_flashlight_off">Ugasi lampu</string>
<string name="settings_locale">Jezik</string>
<string name="settings_system_locale">Sistem</string>
<string name="settings_theme_color">Boja teme</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_pink_theme">Ružičasto</string>
<string name="settings_magenta_theme">Ljubičasto</string>
<string name="settings_violet_theme">Ljubičasto</string>
<string name="settings_blue_theme">Plavo</string>
<string name="settings_sky_blue_theme">Nebo plavo</string>
<string name="settings_green_theme">Zeleno</string>
<string name="settings_brown_theme">Braun</string>
<string name="sort">Poništi sortiranje</string>
<string name="sort_by_name">Ime</string>
<string name="sort_by_most_recently_used">Nedavno Korišten</string>
<string name="sort_by_most_recently_used">Najnovije korišteno</string>
<string name="sort_by_expiry">Sajam</string>
<string name="reverse">Rikverc</string>
<string name="reverse">...obrnutim redoslijedom</string>
<string name="sort_by">Sortiraj</string>
<string name="noCardExistsError">Nisam mogao pronaći tu karticu</string>
<string name="noCardExistsError">Nisam mogao/la pronaći tu karticu</string>
<string name="noCardsMessage">Dodaj prvo kartu</string>
<string name="addCardTitle">Dodaj Kartu</string>
<string name="editCardTitle">Izmijeni Karticu</string>
<string name="addCardTitle">Dodaj karticu</string>
<string name="editCardTitle">Uredi karticu</string>
<string name="sendLabel">Pošalji…</string>
<string name="share">Podijeli</string>
<string name="deleteConfirmation">Izbriši trajno ovu karticu\?</string>
@@ -81,25 +72,239 @@
<string name="exportFailed">Nisam uspio izvršiti izvoz</string>
<string name="app_copyright_old">Bazirano na Loyalty Card privjesku
\ncopyright © 2016-2020 Branden Archer</string>
<string name="noGiftCardsGroup">Kreirajte kartice, i dodajte ih ovdje u grupu.</string>
<string name="importExportHelp">Backupovanje vaših podataka omogućava njihov prenos na drugi uređaj.</string>
<string name="noGiftCardsGroup">Napravite nekoliko kartica, a zatim ih dodijelite grupi ovdje</string>
<string name="importExportHelp">Pravljenje sigurnosne kopije podataka omogućava njihovo premještanje na drugi uređaj</string>
<string name="importSuccessfulTitle">Uvezeno</string>
<string name="exportFailedTitle">Izvoz neuspješan</string>
<string name="scanCardBarcode">Skeniraj barkod</string>
<string name="cardShortcut">Prečica kartice</string>
<string name="failedParsingImportUriError">Nisam uspio parsirati URI za uvoz</string>
<string name="importExport">Uvoz/Izvoz</string>
<string name="failedParsingImportUriError">Nemogu analizirati URI za uvoz</string>
<string name="importExport">Uvoz/izvoz</string>
<string name="exportName">Izvoz</string>
<string name="importFailedTitle">Uvoz neuspješan</string>
<string name="importFailed">Nisam uspio odraditi uvoz</string>
<string name="exportSuccessfulTitle">Izvezeno</string>
<string name="importing">Uvozim…</string>
<string name="exporting">Izvozim…</string>
<string name="exportOptionExplanation">Ovi podaci će biti zapisani na lokaciju po Voj želji.</string>
<string name="exportOptionExplanation">Podaci će biti zapisani na lokaciju po vem izboru</string>
<string name="importOptionFilesystemTitle">Uvoz iz file sistema</string>
<string name="importOptionFilesystemExplanation">Izaberite specifični file iz file sistema.</string>
<string name="importOptionFilesystemExplanation">Odaberite određenu datoteku iz datotečnog sistema</string>
<string name="importOptionFilesystemButton">Iz file sistema</string>
<string name="about">O</string>
<string name="noBarcode">Nema barkoda</string>
<string name="deleteTitle">Obriši karticu</string>
</resources>
<string name="app_name">Catima</string>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> odabran</item>
<item quantity="few"><xliff:g>%d</xliff:g> odabranih</item>
<item quantity="other"><xliff:g>%d</xliff:g> odabrano</item>
</plurals>
<string name="cameraPermissionRequired">Za ovu radnju potrebna je dozvola za pristup kameri…</string>
<plurals name="deleteCardsTitle">
<item quantity="one">Obriši <xliff:g>%d</xliff:g> karticu</item>
<item quantity="few">Obriši <xliff:g>%d</xliff:g> kartice</item>
<item quantity="other">Obriši <xliff:g>%d</xliff:g> kartica</item>
</plurals>
<plurals name="deleteCardsConfirmation">
<item quantity="one">Trajno izbrisati ovu <xliff:g>%d</xliff:g> karticu?</item>
<item quantity="few">Trajno izbrisati ove <xliff:g>%d</xliff:g> kartice?</item>
<item quantity="other">Trajno izbrisati ovih <xliff:g>%d</xliff:g> kartica?</item>
</plurals>
<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>
<plurals name="balancePoints">
<item quantity="one"><xliff:g>%s</xliff:g> poen</item>
<item quantity="few"><xliff:g>%s</xliff:g> poena</item>
<item quantity="other"><xliff:g>%s</xliff:g> poena</item>
</plurals>
<plurals name="groupCardCountWithArchived">
<item quantity="one"><xliff:g>%1$d</xliff:g> kartica (<xliff:g id="archivedCount">%2$d</xliff:g> archived)</item>
<item quantity="few"><xliff:g>%1$d</xliff:g> kartice (<xliff:g id="archivedCount">%2$d</xliff:g> archived)</item>
<item quantity="other"><xliff:g>%1$d</xliff:g> kartica (<xliff:g id="archivedCount">%2$d</xliff:g> archived)</item>
</plurals>
<string name="barcodeImageDescriptionWithType">Slika <xliff:g>%s</xliff:g> barkoda</string>
<string name="storageReadPermissionRequired">Za ovu radnju potrebna je dozvola za čitanje pohrane…</string>
<string name="permissionReadCardsLabel">Čitajte Catima kartice</string>
<string name="permissionReadCardsDescription">da pročita vaše Catima kartice i sve njihove detalje, uključujući bilješke i slike</string>
<string name="cameraPermissionDeniedTitle">Nije bilo moguće pristupiti kameri</string>
<string name="noCameraPermissionDirectToSystemSetting">Za skeniranje barkodova, Catima će trebati pristup vašoj kameri. Dodirnite ovdje da promijenite postavke dozvola.</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Autorska prava © 2019<xliff:g>%d</xliff:g> Sylvia van Os i saradnici</string>
<string name="app_copyright_short">Autorska prava © Sylvia van Os i saradnici</string>
<string name="app_license">Slobodni softver zaštićen autorskim pravom, licenciran pod GPLv3+</string>
<string name="about_title_fmt">O aplikaciji <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Verzija: <xliff:g id="version">%s</xliff:g></string>
<string name="app_libraries">Biblioteke trećih strana: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Resursi trećih strana: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Odaberite barkod</string>
<string name="thumbnailDescription">Sličica</string>
<string name="settings">Postavke</string>
<string name="failedOpeningFileManager">Nije moguće otvoriti upravitelj datoteka</string>
<string name="moveUp">Pomakni se prema gore</string>
<string name="moveDown">Pomakni se prema dolje</string>
<string name="leaveWithoutSaveTitle">Izlaz</string>
<string name="leaveWithoutSaveConfirmation">Napustiti bez spremanja?</string>
<string name="addManually">Ručno unesite barkod</string>
<string name="addFromImage">Odaberite sliku iz galerije</string>
<string name="groupsList">Grupe: <xliff:g>%s</xliff:g></string>
<string name="editGroup">Grupa za uređivanje: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentence">Ističe: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentenceExpired">Isteklo: <xliff:g>%s</xliff:g></string>
<string name="balanceSentence">Stanje: <xliff:g>%s</xliff:g></string>
<string name="card">Kartica</string>
<string name="editBarcode">Uredi barkod</string>
<string name="expiryDate">Datum isteka</string>
<string name="never">Nikad</string>
<string name="chooseExpiryDate">Odaberite datum isteka</string>
<string name="moveBarcodeToTopOfScreen">Pomaknite barkod na vrh ekrana</string>
<string name="noBarcodeFound">Nije pronađen barkod</string>
<string name="errorReadingImage">Nisam mogao/la pročitati sliku</string>
<string name="balance">Stanje</string>
<string name="currency">Valuta</string>
<string name="points">Bodovi</string>
<string name="balanceParsingFailed">Nevažeći saldo</string>
<string name="chooseImportType">Uvoz podataka iz</string>
<string name="app_loyalty_card_keychain">Privjesak za ključeve s karticom vjernosti</string>
<string name="privacy_policy">Politika privatnosti</string>
<string name="accept">Prihvati</string>
<string name="importFidme">Uvoz iz FidMe-a</string>
<string name="importFidmeMessage">Odaberite izvoz iz FidMe-a za uvoz i nakon toga ručno odaberite vrste barkodova. \nKreirajte ga iz svog FidMe profila odabirom Zaštita podataka, a zatim pritiskom na Izdvoji moje podatke.</string>
<string name="importLoyaltyCardKeychainMessage">Odaberite svoj izvoz iz Privjeska za ključeve s karticom vjernosti za uvoz.\nKreirajte ga iz menija Uvoz/Izvoz u Privjesku za ključeve s karticom vjernosti pritiskom na Izvoz.</string>
<string name="importVoucherVaultMessage">Odaberite izvoz iz Voucher Vaulta za uvoz. \nKreirajte ga pritiskom na Izvoz u Voucher Vaultu.</string>
<string name="switchToBarcode">Prebaci na barkod</string>
<string name="openFrontImageInGalleryApp">Otvorite prednju sliku u aplikaciji za pregled slika</string>
<string name="switchToBackImage">Prebaci na sliku iz pozadine</string>
<string name="switchToFrontImage">Prebaci na prednju sliku</string>
<string name="height">Visina</string>
<string name="validFromSentence">Važi od: <xliff:g>%s</xliff:g></string>
<string name="chooseValidFromDate">Odaberite datum važenja od</string>
<string name="anyDate">Bilo koji datum</string>
<string name="validFromDate">Vrijedi od</string>
<string name="newBalanceSentence">Novo stanje: <xliff:g>%s</xliff:g></string>
<string name="currentBalanceSentence">Trenutno stanje: <xliff:g>%s</xliff:g></string>
<string name="updateBalanceHint">Unesite iznos</string>
<string name="updateBalanceTitle">Koliko ste potrošili ili primili?</string>
<string name="importCards">Uvezi kartice</string>
<string name="welcome">Dobrodošli u Catima</string>
<string name="failedToOpenUrl">Prvo instalirajte web preglednik</string>
<string name="nextCard">Sljedeće</string>
<string name="previousCard">Prethodno</string>
<string name="failedLaunchingPhotoPicker">Nemogu pronaći podržani alat za odabir slika</string>
<string name="unarchived">Kartica je vraćena iz arhive</string>
<string name="archived">Kartica je arhivirana</string>
<string name="unarchive">Vrati iz arhive</string>
<string name="archive">Arhiva</string>
<string name="duplicateCard">Duplikat</string>
<string name="include_if_asking_support">Ako želite zatražiti podršku, uključite sljedeće informacije:</string>
<string name="starred">Označeno zvjezdicom</string>
<string name="options">Opcije</string>
<string name="shortcutSelectCard">Odaberite karticu</string>
<string name="translate_platform">na Weblateu</string>
<string name="report_error">Prijavi grešku</string>
<string name="on_google_play">na Google Play</string>
<string name="rate_this_app">Ocijenite ovu aplikaciju</string>
<string name="and_data_usage">i korištenje podataka</string>
<string name="on_github">na GitHubu</string>
<string name="source_repository">Izvorni repozitorij</string>
<string name="license">Licenca</string>
<string name="help_translate_this_app">Pomozite u prevođenju ove aplikacije</string>
<string name="credits">Zasluge</string>
<string name="version_history">Historija verzija</string>
<string name="sort_by_valid_from">Vrijedi od</string>
<string name="barcodeLongPressMessage">U aplikaciji galerija mogu se otvoriti samo slike</string>
<string name="failedToRetrieveImageFile">Neuspjevam preuzimanje slikovne datoteke</string>
<string name="updateBalance">Ažuriraj stanje</string>
<string name="showMoreInfo">Prikaži informacije</string>
<string name="app_contributors">Omogućeno zahvaljujući: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="setIcon">Postavi sličicu</string>
<string name="selectColor">Odaberite boju</string>
<string name="settings_oled_dark_summary">Smanjuje potrošnju baterije na OLED ekranima</string>
<string name="settings_oled_dark">Čisto crna pozadina za tamnu temu</string>
<string name="exportPasswordHint">Unesite lozinku</string>
<string name="exportPassword">Postavite lozinku za zaštitu izvoza (opcionalno)</string>
<string name="back">Nazad</string>
<string name="barcodeEncoding">Kodiranje barkoda</string>
<string name="nothing_to_copy">Nije pronađena vrijednost</string>
<string name="settings_dark_theme">Tamno</string>
<string name="settings_display_barcode_max_brightness">Posvijetli ekran</string>
<string name="settings_display_barcode_max_brightness_summary">Neophodno za rad nekih skenera</string>
<string name="settings_keep_screen_on_summary">Onemogućava istek vremena ekrana tokom pregleda kartice</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">Onemogućava zaključavanje ekrana tokom pregleda kartice</string>
<string name="settings_allow_content_provider_read_title">Dozvoli drugim aplikacijama pristup mojim podacima</string>
<string name="settings_allow_content_provider_read_summary">Aplikacije će i dalje morati tražiti dozvolu za pristup</string>
<string name="settings_use_volume_keys_navigation">Prebacivanje kartica pomoću tipki za jačinu zvuka</string>
<string name="settings_use_volume_keys_navigation_summary">Koristite tipke za jačinu zvuka da promijenite koja se kartica prikazuje</string>
<string name="group_edit">Uredi grupu</string>
<string name="group_name_already_in_use">Naziv grupe se već koristi</string>
<string name="group_name_is_empty">Naziv grupe ne smije biti prazan</string>
<string name="group_updated">Grupa ažurirana</string>
<string name="deleteConfirmationGroup">Izbrisati grupu?</string>
<string name="openBackImageInGalleryApp">Otvori sliku pozadi u aplikaciji za pregled slika</string>
<string name="setBarcodeHeight">Postavi visinu barkoda</string>
<string name="donate">Donirajte</string>
<string name="icon_header_click_text">Dugo pritisnite za uređivanje sličice</string>
<string name="show_name_below_image_thumbnail">Prikaži ime ispod sličice slike</string>
<string name="show_note">Prikaži bilješku</string>
<string name="show_balance">Prikaži stanje</string>
<string name="show_validity">Prikaži validnost</string>
<string name="settings_category_title_cards">Prikaz kartice</string>
<string name="settings_category_title_cards_overview">Pregled kartica</string>
<string name="settings_column_count_portrait">Kolone u portretnom načinu rada</string>
<string name="settings_column_count_landscape">Kolone u pejzažnom načinu rada</string>
<string name="settings_automatic_column_count">Automatski</string>
<string name="settings_column_count_1">1</string>
<string name="settings_column_count_2">2</string>
<string name="settings_column_count_3">3</string>
<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="settings_column_count_7">7</string>
<string name="settings_category_title_general">Općenito</string>
<string name="settings_category_title_privacy">Privatnost</string>
<string name="action_display_options">Opcije prikaza</string>
<string name="show_archived_cards">Prikaži arhivirane kartice</string>
<string name="view_online">Pogledajte online</string>
<string name="action_more_options">Više opcija</string>
<string name="addWithoutBarcode">Dodajte karticu bez barkoda</string>
<string name="enter_card_id">Unesite ID broj ili tekst sa vaše kartice</string>
<string name="card_id_must_not_be_empty">ID kartice ne smije biti prazan</string>
<string name="add_a_card_in_a_different_way">Dodajte karticu na drugačiji način</string>
<string name="field_must_not_be_empty">Polje ne smije biti prazno</string>
<string name="manually_enter_barcode_instructions">Unesite ID broj ili tekst sa vaše kartice i pritisnite barkod koji izgleda kao onaj na vašoj kartici.</string>
<string name="add_manually_warning_title">Preporučuje se skeniranje</string>
<string name="add_manually_warning_message">Za neke kartice, vrijednost barkoda se razlikuje od broja napisanog na kartici. Zbog toga, ručni unos barkoda možda neće uvijek funkcionirati. Preporučuje se skeniranje barkoda kamerom. Želite li i dalje nastaviti?</string>
<string name="continue_">Nastavi</string>
<string name="spend">Potroši</string>
<string name="receive">Primi</string>
<string name="amountParsingFailed">Nevažeći iznos</string>
<string name="addFromPdfFile">Odaberite PDF datoteku</string>
<string name="errorReadingFile">Nisam mogao pročitati datoteku</string>
<string name="failedLaunchingFileManager">Nije pronađen podržani upravitelj datoteka</string>
<string name="multipleBarcodesFoundPleaseChooseOne">Koji od pronađenih barkodova želite koristiti?</string>
<string name="pageWithNumber">Stranica <xliff:g>%d</xliff:g></string>
<string name="noCameraFoundGuideText">Izgleda da vaš uređaj nema kameru. Ako je ima, pokušajte ponovo pokrenuti uređaj. U suprotnom, koristite dugme Više opcija ispod da biste dodali barkod na drugi način.</string>
<string name="importCancelled">Uvoz otkazan</string>
<string name="exportCancelled">Izvoz otkazan</string>
<string name="useFrontImage">Koristi prednju sliku</string>
<string name="useBackImage">Koristi sliku sa zadnje strane</string>
<string name="addFromPkpass">Odaberite Passbook datoteku (.pkpass / .pkpasses)</string>
<string name="unsupportedFile">Ova datoteka nije podržana</string>
<string name="generic_error_please_retry">Došlo je do greške</string>
<string name="width">Širina</string>
<string name="card_list_widget_name">Lista kartica</string>
<string name="setBarcodeWidth">Postavi širinu barkoda</string>
<string name="card_list_widget_empty">Nakon što dodate neke kartice vjernosti u Catima, one će se pojaviti ovdje. Ako imate kartice, provjerite da nisu sve arhivirane.</string>
<string name="cardWithNumber">Kartica <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Kartica <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Molimo vas da ne rotirate 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> se srušila. Molimo vas da nam pomognete da riješimo ovaj problem slanjem izvještaja o grešci.</string>
<string name="acra_explain_crash">Ako je moguće, molimo vas da dodate više detalja o tome šta ste ovdje radili:</string>
<string name="acra_crash_email_subject">Izvještaj o padu aplikacije <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Zatraži slanje izvještaja o padu programa</string>
<string name="pref_enable_acra_summary">Kada je omogućeno, bit ćete upitani da prijavite pad sistema kada se dogodi. Izvještaji o padu sistema se nikada ne šalju automatski.</string>
<string name="copy_value">Kopiraj vrijednost</string>
<string name="copied_to_clipboard">Kopirano u međuspremnik</string>
<string name="importCatimaMessage">Odaberite izvoz iz Catima za uvoz. \nKreirajte ga iz menija Uvoz/izvoz druge Catima aplikacije pritiskom na Izvoz.</string>
</resources>
+58 -28
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>
@@ -42,10 +42,6 @@
<string name="leaveWithoutSaveConfirmation">Vols sortir sense grabar?</string>
<string name="passwordRequired">Introdueixi el password</string>
<string name="turn_flashlight_on">Encendre el llum flash</string>
<string name="settings_magenta_theme">Magenta</string>
<string name="settings_violet_theme">Violeta</string>
<string name="settings_blue_theme">Blau</string>
<string name="settings_green_theme">Verd</string>
<string name="translate_platform">a la Pàgina Web</string>
<string name="report_error">Informar un Error</string>
<string name="archived">Targeta arxivada</string>
@@ -55,13 +51,13 @@
<string name="add_manually_warning_title">Recomenem escanejar</string>
<string name="add_manually_warning_message">En algunes targetes el valor imprès en la targeta no correspon amb el codi registrat en el codi de barres. Per això, introduint manualment el codi pot no funcionar en alguns casos. Recomanem sempre que sigui possible escanejar la targeta amb la càmera. Vol igualment continuar la edició manual?</string>
<string name="continue_">Continuar</string>
<string name="exportOptionExplanation">La informació serà escrita al lloc de la seva elecció.</string>
<string name="exportOptionExplanation">La informació serà escrita al lloc de la seva elecció</string>
<string name="importOptionFilesystemTitle">Importar desde el sistema de fitxers</string>
<string name="importOptionFilesystemButton">Desde el sistema de fitxers</string>
<string name="selectBarcodeTitle">Sel•lecciona el Codi de Barres</string>
<string name="selectBarcodeTitle">Selecciona el codi de barres</string>
<string name="importSuccessful">Dades importades correctament</string>
<string name="exportSuccessful">Dades exportades correctament</string>
<string name="failedOpeningFileManager">Instala un gestor de fitxers.</string>
<string name="failedOpeningFileManager">No s\'ha pogut obrir el gestor de fitxers</string>
<string name="showMoreInfo">Mostrar informació</string>
<string name="version_history">Històric de versions</string>
<string name="sort_by">Ordenar per</string>
@@ -72,9 +68,8 @@
<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>
<string name="failedToRetrieveImageFile">Ha fallat l\'obtenció del fitxer d\'imatge</string>
<string name="barcodeLongPressMessage">Les imatges només es poden obrir desde la app galeria</string>
@@ -96,8 +91,8 @@
</plurals>
<string name="importCancelled">Importació anulada</string>
<string name="exportCancelled">Exportació cancelada</string>
<string name="noGiftCardsGroup">Crea algunes targetes, asigna-les en un grup aquí.</string>
<string name="noMatchingGiftCards">Sense resultats. Prova a canviar la teva cerca.</string>
<string name="noGiftCardsGroup">Crea algunes targetes i després asigna-les en al grup aquí</string>
<string name="noMatchingGiftCards">No hi ha resultats; prova de modificar la cerca.</string>
<string name="storeName">Nom</string>
<string name="note">Nota</string>
<string name="cardId">Id. de la Targeta</string>
@@ -146,7 +141,6 @@
<string name="settings_oled_dark">Negre pur en el tema fosc</string>
<string name="selectColor">Sel•leccioni el color</string>
<string name="setIcon">Setegi la miniatura</string>
<string name="settings_theme_color">Color del tema</string>
<string name="app_contributors">Fet possible per: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="updateBalance">Actualitzar el balanç</string>
<string name="sort_by_name">Nom</string>
@@ -166,23 +160,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_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>
<string name="rate_this_app">Valora aquesta app</string>
<string name="exportPasswordHint">Introdueixi el password</string>
@@ -227,7 +219,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>
@@ -236,6 +228,44 @@
<string name="turn_flashlight_off">Apagar el llum Flash</string>
<string name="settings_oled_dark_summary">Redueix l\'ús de la bateria en pantalles OLED</string>
<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>
+9 -9
View File
@@ -83,15 +83,6 @@
<string name="expiryStateSentence">Platí do: <xliff:g>%s</xliff:g></string>
<string name="moveDown">Přesunout dolů</string>
<string name="moveUp">Přesunout nahoru</string>
<string name="settings_brown_theme">Hnědá</string>
<string name="settings_green_theme">Zelená</string>
<string name="settings_sky_blue_theme">Azurová</string>
<string name="settings_blue_theme">Modrá</string>
<string name="settings_violet_theme">Fialová</string>
<string name="settings_magenta_theme">Purpurová</string>
<string name="settings_pink_theme">Růžová</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_theme_color">Barva motivu</string>
<string name="settings_system_locale">Podle systému</string>
<string name="settings_locale">Jazyk</string>
<string name="turn_flashlight_off">Vypnout světlo</string>
@@ -311,4 +302,13 @@
<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>
<string name="barcodeEncoding">Kódování čárového kódu</string>
<string name="back">Zpět</string>
<string name="settings_disable_nfc_while_viewing_card">Pozastavit NFC</string>
<string name="settings_disable_nfc_while_viewing_card_summary">Pozastaví NFC pro zabránění spuštění NFC plateb při zobrazování karty</string>
<string name="nfc_blocked_while_viewing_card">NFC je pozastaveno</string>
<string name="change_settings">Změnit nastavení</string>
</resources>
+15 -15
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>
@@ -25,7 +25,7 @@
<string name="note">Bemærk</string>
<string name="storeName">Navn</string>
<string name="noMatchingGiftCards">Ingen resultater. Prøv at ændre din søgning.</string>
<string name="noGiftCards">Klik på + plus-knappen for at tilføje et kort, eller importer kort fra ⋮ menuen.</string>
<string name="noGiftCards">Klik på + plus-knappen for at tilføje et kort, eller importer kort fra ⋮ menuen</string>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> valgt kort</item>
<item quantity="other"><xliff:g>%d</xliff:g> valgte kort</item>
@@ -34,7 +34,7 @@
<string name="action_search">Søg</string>
<string name="importExport">Import/eksport</string>
<string name="exportName">Eksport</string>
<string name="importExportHelp">Sikkerhedskopiering af dit data, giver dig mulighed for at flytte dem til en anden enhed.</string>
<string name="importExportHelp">Sikkerhedskopiering af dine data, giver dig mulighed for at flytte dem til en anden enhed.</string>
<string name="importSuccessfulTitle">Importeret</string>
<string name="importFailedTitle">Import mislykkedes</string>
<string name="importFailed">Kunne ikke udføre importering</string>
@@ -54,12 +54,12 @@
\ncopyright © 2016-2020 Branden Archer.</string>
<string name="about">Om</string>
<string name="noCardsMessage">Tilføj først et kort</string>
<string name="cardShortcut">Kort genvej</string>
<string name="cardShortcut">Genvej til kort</string>
<string name="importOptionFilesystemButton">Fra filsystemet</string>
<string name="importOptionFilesystemExplanation">Vælg en bestemt fil fra filsystemet.</string>
<string name="importOptionFilesystemTitle">Import fra filsystem</string>
<string name="exportOptionExplanation">Dataene skrives til en placering efter eget valg.</string>
<string name="failedParsingImportUriError">Kunne ikke analysere import-URI\'en</string>
<string name="failedParsingImportUriError">Kunne ikke importere URI\'en</string>
<string name="noCardExistsError">Kunne ikke finde det kort</string>
<string name="deleteConfirmationGroup">Slet gruppe\?</string>
<string name="all">Alle</string>
@@ -79,16 +79,16 @@
<string name="moveDown">Bevæger sig nedad</string>
<string name="leaveWithoutSaveTitle">Afslut</string>
<string name="addManually">Indtast stregkoden manuelt</string>
<string name="noGiftCardsGroup">Opret kort og tildel dem gupper her.</string>
<string name="noGiftCardsGroup">Opret kort og tildel dem grupper her.</string>
<plurals name="deleteCardsConfirmation">
<item quantity="one">Slet dette <xliff:g>%d</xliff:g> kort permanent\?</item>
<item quantity="other">Slet disse <xliff:g>%d</xliff:g> kort permanent\?</item>
</plurals>
<string name="app_name">Catima</string>
<string name="cameraPermissionRequired">Behov for kamera adgang krævet for denne funktion…</string>
<string name="storageReadPermissionRequired">Behov for lager adgang krævet for denne funktion…</string>
<string name="cameraPermissionRequired">Behov for kamera adgang er krævet for denne funktion…</string>
<string name="storageReadPermissionRequired">Behov for lager adgang er krævet for denne funktion…</string>
<string name="permissionReadCardsLabel">Læs Catima Kort</string>
<string name="permissionReadCardsDescription">læs dine Catima kort og alle deres detaljer, også noter og billeder</string>
<string name="permissionReadCardsDescription">læs dit Catima kort og alle kortets detaljer, også noter og billeder</string>
<string name="cameraPermissionDeniedTitle">Kunne ikke få adgang til kamera</string>
<string name="noCameraPermissionDirectToSystemSetting">For at scanne stregkoder, har Catima behov for at få adgang til dit kamera. Klik her for at ændre dine tilladelser i indstillinger.</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Copyright © 2019<xliff:g>%d</xliff:g> Sylvia van Os og hjælpere</string>
@@ -144,4 +144,4 @@
<item quantity="one"><xliff:g>%s</xliff:g> point</item>
<item quantity="other"><xliff:g>%s</xliff:g> point</item>
</plurals>
</resources>
</resources>
+12 -12
View File
@@ -33,8 +33,8 @@
<string name="exportFailed">Export konnte nicht durchgeführt werden</string>
<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="importOptionFilesystemTitle">Import vom Dateisystem</string>
<string name="importOptionFilesystemExplanation">Wähle eine bestimmte Datei aus dem Dateisystem</string>
<string name="importOptionFilesystemButton">vom Dateisystem</string>
<string name="about">Über</string>
<string name="app_license">Freie Software, lizensiert unter der GPLv3+</string>
@@ -100,7 +100,7 @@
<string name="importLoyaltyCardKeychain">Aus Loyalty Card Keychain importieren</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 „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="importCatimaMessage">Wähle deinen „Catima-Export“ zum Importieren aus.\nExportiere diesen zuvor im Import/Export-Menü einer anderen Catima-Anwendung.</string>
<string name="importCatima">Aus Catima importieren</string>
<string name="setBarcodeId">Barcodewert festlegen</string>
<string name="sameAsCardId">Entspricht Kartennummer</string>
@@ -144,15 +144,6 @@
</plurals>
<string name="settings_system_locale">System</string>
<string name="settings_locale">Sprache</string>
<string name="settings_brown_theme">Braun</string>
<string name="settings_green_theme">Grün</string>
<string name="settings_sky_blue_theme">Himmelblau</string>
<string name="settings_blue_theme">Blau</string>
<string name="settings_violet_theme">Violett</string>
<string name="settings_magenta_theme">Magenta</string>
<string name="settings_pink_theme">Rosa</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_theme_color">Designfarbe</string>
<string name="app_contributors">Ermöglicht durch: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="barcodeImageDescriptionWithType">Bild <xliff:g>%s</xliff:g> Barcode</string>
<string name="sort_by">Sortieren nach</string>
@@ -305,4 +296,13 @@
<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>
<string name="barcodeEncoding">Barcode-Kodierung</string>
<string name="back">Zurück</string>
<string name="change_settings">Einstellungen ändern</string>
<string name="nfc_blocked_while_viewing_card">NFC ist vorübergehend deaktiviert</string>
<string name="settings_disable_nfc_while_viewing_card_summary">Deaktiviert vorübergehend NFC, sodass keine NFC-Zahlungen ausgelöst werden können, während eine Karte angezeigt wird</string>
<string name="settings_disable_nfc_while_viewing_card">NFC vorübergehend deaktivieren</string>
</resources>
+9 -9
View File
@@ -137,9 +137,6 @@
<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>
<string name="settings_green_theme">Πράσινο</string>
<string name="settings_brown_theme">Καφέ</string>
<string name="sort_by_expiry">Λήξη</string>
<plurals name="groupCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> κάρτα</item>
@@ -187,12 +184,6 @@
<string name="settings_locale">Γλώσσα</string>
<string name="settings_oled_dark">Απόλυτο μαύρο φόντο για το μαύρο θέμα</string>
<string name="settings_system_locale">Σύστημα</string>
<string name="settings_theme_color">Χρώμα θέματος</string>
<string name="settings_catima_theme">Κάτιμα</string>
<string name="settings_pink_theme">Ροζ</string>
<string name="settings_magenta_theme">Φούξια</string>
<string name="settings_violet_theme">Βιολετί</string>
<string name="settings_blue_theme">Μπλε</string>
<string name="app_contributors">Δημιουργήθηκε από: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="showMoreInfo">Εμφάνιση πληροφοριών</string>
<string name="sort_by_name">Όνομα</string>
@@ -305,4 +296,13 @@
<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>
<string name="barcodeEncoding">Κωδικοποίηση γραμμωτού κώδικα</string>
<string name="back">Πίσω</string>
<string name="settings_disable_nfc_while_viewing_card">Παύση NFC</string>
<string name="settings_disable_nfc_while_viewing_card_summary">Παύση του NFC για την αποτροπή ενεργοποίησης πληρωμών NFC κατά την προβολή μιας κάρτας</string>
<string name="nfc_blocked_while_viewing_card">Το NFC είναι σε παύση</string>
<string name="change_settings">Αλλαγή ρυθμίσεων</string>
</resources>
+1 -10
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>
@@ -101,7 +101,6 @@
<string name="balance">Saldo</string>
<string name="moveBarcodeToTopOfScreen">Movi la strekodon al la supro de la ekrano</string>
<string name="errorReadingImage">Ne eblis legi bildon</string>
<string name="settings_brown_theme">Bruna</string>
<string name="showMoreInfo">Montri informojn</string>
<string name="on_github">sur GitHub</string>
<string name="archive">Enarkivigi</string>
@@ -127,7 +126,6 @@
<string name="validFromDate">Valida ekde</string>
<string name="accept">Akcepti</string>
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
<string name="settings_sky_blue_theme">Ĉielblua</string>
<string name="unarchive">Elarkivigi</string>
<string name="switchToBarcode">Ŝanĝi al strikodo</string>
<string name="currentBalanceSentence">Nuna saldo: <xliff:g>%s</xliff:g></string>
@@ -166,7 +164,6 @@
<string name="balanceParsingFailed">Nevalida saldo</string>
<string name="chooseImportType">Importi datumojn de</string>
<string name="importCatima">Importi el Catima</string>
<string name="settings_green_theme">Verda</string>
<string name="updateBalance">Ĝisdatigi saldon</string>
<string name="barcodeLongPressMessage">Nur bildoj povas esti malfermitaj en la galeria apo</string>
<string name="sort_by_name">Nomo</string>
@@ -225,8 +222,6 @@
<string name="settings_oled_dark_summary">Malpligrandigas baterian uzadon sur OLED-ekranoj</string>
<string name="selectColor">Elekti koloron</string>
<string name="setIcon">Starigi bildeton</string>
<string name="settings_catima_theme">Catima</string>
<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="turn_flashlight_off">Malŝalti poŝlampon</string>
@@ -241,7 +236,6 @@
<string name="wrongValueForBarcodeType">La valoro ne validas por la elektita tipo de strikodo</string>
<string name="importCancelled">Importado nuligita</string>
<string name="exportCancelled">Eksportado nuligita</string>
<string name="settings_theme_color">Koloro de la temo</string>
<string name="app_libraries">Liberaj triaj bibliotekoj: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="addFromPdfFile">Elekti PDF-dosieron</string>
<string name="failedLaunchingFileManager">Subtenata dosiermastrumilo ne trovebla</string>
@@ -267,9 +261,6 @@
<string name="importVoucherVault">Importi el Voucher Vault</string>
<string name="turn_flashlight_on">Enŝalti poŝlampon</string>
<string name="settings_locale">Lingvo</string>
<string name="settings_magenta_theme">Maĝenta</string>
<string name="settings_violet_theme">Viola</string>
<string name="settings_blue_theme">Blua</string>
<string name="enter_card_id">Entajpu la identigilon aŭ tekston sur via karto</string>
<string name="card_id_must_not_be_empty">Identigilo devas ne esti malplena</string>
<string name="add_a_card_in_a_different_way">Aldoni karton alimaniere</string>
+68 -11
View File
@@ -51,7 +51,7 @@
<item quantity="many">Borrar estas <xliff:g>%d</xliff:g> tarjetas de forma permanente\?</item>
<item quantity="other"/>
</plurals>
<string name="failedOpeningFileManager">Primero instale un administrador de archivos.</string>
<string name="failedOpeningFileManager">No se pudo abrir el explorador de archivos</string>
<string name="intent_import_card_from_url_share_multiple_text">Quiero compartirte algunas tarjetas</string>
<string name="points">Puntos</string>
<string name="balanceParsingFailed">Balance inválido</string>
@@ -169,11 +169,7 @@
<string name="updateBarcodeQuestionText">Has cambiado el ID. ¿Quieres actualizar también el código de barras para usar el mismo valor?</string>
<string name="settings_locale">Idioma</string>
<string name="settings_system_locale">Sistema</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_pink_theme">Rosa</string>
<string name="exportPassword">Configura una contraseña para proteger tu exportación (opcional)</string>
<string name="settings_sky_blue_theme">Celeste</string>
<string name="settings_green_theme">Verde</string>
<string name="exportPasswordHint">Ingresar contraseña</string>
<string name="setIcon">Establecer miniatura</string>
<string name="showMoreInfo">Mostrar información</string>
@@ -184,12 +180,7 @@
<string name="settings_oled_dark_summary">Reduce uso de batería en pantallas OLED</string>
<string name="settings_oled_dark">Fondo negro puro para tema oscuro</string>
<string name="selectColor">Seleccionar color</string>
<string name="settings_theme_color">Color del tema</string>
<string name="settings_magenta_theme">Magenta</string>
<string name="settings_violet_theme">Violeta</string>
<string name="settings_brown_theme">Marrón</string>
<string name="sort">Ordenar</string>
<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="yes">Si</string>
@@ -201,9 +192,75 @@
<string name="reverse">en orden inverso</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_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>
<string name="version_history">Historial de versiones</string>
<string name="credits">Créditos</string>
<string name="help_translate_this_app">Ayúdanos a traducir esta app</string>
<string name="license">Licencia</string>
<string name="on_github">en GitHub</string>
<string name="rate_this_app">Califica esta app</string>
<string name="on_google_play">en Google Play</string>
<string name="report_error">Reportar error</string>
<string name="translate_platform">en Weblate</string>
<string name="shortcutSelectCard">Selecciona una tarjeta</string>
<string name="options">Opciones</string>
<string name="duplicateCard">Duplicar</string>
<string name="archive">Archivo</string>
<string name="archived">Tarjeta archivada</string>
<string name="failedLaunchingPhotoPicker">No se pudo encontrar un selector de imagen soportado</string>
<string name="previousCard">Anterior</string>
<string name="nextCard">Siguiente</string>
<string name="failedToOpenUrl">Primero instala un navegador web</string>
<string name="welcome">Bienvenido/a a Catima</string>
<string name="importCards">Importar tarjetas</string>
<string name="updateBalanceHint">Ingrese monto</string>
<string name="validFromDate">Valido desde</string>
<string name="height">Altura</string>
<string name="donate">Donar</string>
<string name="icon_header_click_text">Manten presionado para editar la miniatura</string>
<string name="show_note">Mostrar nota</string>
<string name="back">Atrás</string>
<string name="source_repository">Fuente de repositorio</string>
<string name="and_data_usage">y uso de datos</string>
<string name="include_if_asking_support">Si queres solicitar soporte, incluya la siguiente información:</string>
<string name="starred">Favoritos</string>
<string name="unarchive">Desarchivar</string>
<string name="unarchived">Carta desarchivada</string>
<string name="updateBalanceTitle">¿Cuánto gastó o ganó?</string>
<string name="anyDate">Cualquier fecha</string>
<string name="chooseValidFromDate">Elija fecha válida desde</string>
<string name="switchToFrontImage">Cambiar a imagen frontal</string>
<string name="switchToBackImage">Cambiar a imagen trasera</string>
<string name="switchToBarcode">Cambiar a código de barras</string>
<string name="openFrontImageInGalleryApp">Abrir imagen frontal en aplicación para ver imágenes</string>
<string name="openBackImageInGalleryApp">Abrir imagen trasera en galería de fotos</string>
<string name="setBarcodeHeight">Definir altura de código de barras</string>
<string name="show_name_below_image_thumbnail">Mostrar nombre de la imagen bajo su miniatura</string>
<string name="show_balance">Mostrar saldo</string>
<string name="show_validity">Mostrar validez</string>
<string name="settings_category_title_cards">Ver tarjeta</string>
<string name="settings_category_title_cards_overview">Ver todas las tarjetas</string>
<string name="settings_column_count_portrait">Columna en modo retrato</string>
<string name="settings_column_count_landscape">Columna en modo horizontal</string>
<string name="settings_automatic_column_count">Automático</string>
<string name="settings_column_count_1">1</string>
<string name="settings_column_count_2">2</string>
<string name="settings_column_count_3">3</string>
<string name="settings_column_count_4">4</string>
<string name="settings_column_count_5">5</string>
<string name="currentBalanceSentence">Balance actual: <xliff:g>%s</xliff:g></string>
<string name="newBalanceSentence">Nuevo balance: <xliff:g>%s</xliff:g></string>
<string name="validFromSentence">Válido desde <xliff:g>%s</xliff:g></string>
<string name="settings_column_count_6">6</string>
<string name="settings_column_count_7">7</string>
<string name="settings_category_title_general">General</string>
<string name="settings_category_title_privacy">Privacidad</string>
<string name="action_display_options">Mostrar Opciones</string>
<string name="show_archived_cards">Mostrar tarjetas archivadas</string>
<string name="view_online">Ver Online</string>
<string name="action_more_options">Más Opciones</string>
</resources>
+19 -13
View File
@@ -10,7 +10,7 @@
<string name="edit">Editar</string>
<string name="delete">Eliminar</string>
<string name="confirm">Confirmar</string>
<string name="ok">De acuerdo</string>
<string name="ok">Aceptar</string>
<string name="sendLabel">Enviar…</string>
<string name="editCardTitle">Editar tarjeta</string>
<string name="addCardTitle">Añadir tarjeta</string>
@@ -134,7 +134,7 @@
<item quantity="many"><xliff:g>%d</xliff:g> seleccionadas</item>
<item quantity="other"><xliff:g>%d</xliff:g> seleccionadas</item>
</plurals>
<string name="deleteTitle">Eliminar la tarjeta</string>
<string name="deleteTitle">Eliminar tarjeta</string>
<string name="deleteConfirmation">¿Quiere eliminar permanentemente esta tarjeta\?</string>
<plurals name="deleteCardsConfirmation">
<item quantity="one">¿Borrar esta tarjeta <xliff:g>%d</xliff:g> permanentemente\?</item>
@@ -147,15 +147,6 @@
<item quantity="other">Borrar <xliff:g>%d</xliff:g> tarjetas</item>
</plurals>
<string name="app_contributors">Hecho posible por: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="settings_brown_theme">Marrón</string>
<string name="settings_green_theme">Verde</string>
<string name="settings_sky_blue_theme">Azul cielo</string>
<string name="settings_blue_theme">Azul</string>
<string name="settings_violet_theme">Violeta</string>
<string name="settings_magenta_theme">Magenta</string>
<string name="settings_pink_theme">Rosa</string>
<string name="settings_theme_color">Color del tema</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_system_locale">Sistema</string>
<string name="settings_locale">Idioma</string>
<string name="noGroupCards">Este grupo está vacío</string>
@@ -232,7 +223,7 @@
<string name="height">Alto</string>
<string name="switchToFrontImage">Cambiar a imagen frontal</string>
<string name="openFrontImageInGalleryApp">Abrir imagen frontal en la aplicación de la galería</string>
<string name="openBackImageInGalleryApp">Abrir imagen trasera en la aplicación de la galería</string>
<string name="openBackImageInGalleryApp">Abrir imagen trasera en la aplicación de visor de imagen</string>
<string name="setBarcodeHeight">Ajustar la altura del código de barras</string>
<string name="donate">Donar</string>
<string name="switchToBarcode">Cambiar a código de barras</string>
@@ -268,7 +259,7 @@
<string name="app_name">Catima</string>
<string name="continue_">Continuar</string>
<string name="add_manually_warning_title">Se recomienda escanear</string>
<string name="add_manually_warning_message">En algunas tiendas, el valor del código de barras difiere del número escrito en la tarjeta. Por este motivo, es posible que la introducción manual del código de barras no siempre funcione. Se recomienda encarecidamente escanear el código de barras con la cámara. ¿Aún desea continuar?</string>
<string name="add_manually_warning_message">En algunas tarjetas, el valor del código de barras difiere del número escrito en la tarjeta. Por este motivo, introducir manualmente puede que no siempre funcione. Se recomienda analizar el código de barras con su cámara en su lugar. ¿Aún desea continuar?</string>
<string name="spend">Gastar</string>
<string name="receive">Recibió</string>
<string name="amountParsingFailed">Importe incorrecto</string>
@@ -305,4 +296,19 @@
<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>
<string name="barcodeEncoding">Codificación de barra de código</string>
<string name="back">Atrás</string>
<string name="settings_disable_nfc_while_viewing_card">Pausar NFC</string>
<string name="settings_disable_nfc_while_viewing_card_summary">Pausa NFC para prevenir abonaciones NFC desde disparador mientras vea una tarjeta</string>
<string name="nfc_blocked_while_viewing_card">NFC está pausado</string>
<string name="change_settings">Cambiar ajustes</string>
</resources>
+9 -9
View File
@@ -118,8 +118,6 @@
<string name="updateBarcodeQuestionTitle">Kas uuendame triipkoodi väärtust?</string>
<string name="yes">Jah</string>
<string name="no">Ei</string>
<string name="settings_theme_color">Kujunduse värv</string>
<string name="settings_pink_theme">Roosa</string>
<string name="barcodeLongPressMessage">Galeriirakenduses saad avada vaid pilte</string>
<string name="sort_by_most_recently_used">Viimati kasutatud</string>
<string name="sort_by_expiry">Aegumine</string>
@@ -201,13 +199,6 @@
<string name="settings_system_locale">Süsteemi keel</string>
<string name="selectColor">Vali värv</string>
<string name="setIcon">Lisa pisipilt</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_magenta_theme">Fuksiapunane</string>
<string name="settings_violet_theme">Punakassinine</string>
<string name="settings_blue_theme">Sinine</string>
<string name="settings_sky_blue_theme">Taevasinine</string>
<string name="settings_green_theme">Roheline</string>
<string name="settings_brown_theme">Pruun</string>
<string name="app_contributors">Seda rakendust on aidanud teha: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="sort">Järjesta</string>
<string name="updateBalance">Uuenda maksejääki</string>
@@ -305,4 +296,13 @@
<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>
<string name="barcodeEncoding">Triipkoodi kodeerimine</string>
<string name="back">Tagasi</string>
<string name="settings_disable_nfc_while_viewing_card">Peata lähiväljaside (NFC)</string>
<string name="nfc_blocked_while_viewing_card">Lähiväljaside (NFC) on peatatud</string>
<string name="change_settings">Muuda seadistusi</string>
<string name="settings_disable_nfc_while_viewing_card_summary">Vältimaks, et lähiväljaside (NFC) abil toimiv makseliides käivitub, lülitatakse kaardi vaatamisel lähiväljaside (NFC) välja</string>
</resources>
+7 -15
View File
@@ -3,8 +3,8 @@
<string name="app_name">کاتیما</string>
<string name="action_search">جستجو</string>
<string name="action_add">افزودن</string>
<string name="noGiftCards">برای افزودن کارت دکمه + پلاس را بزنید, یا از منو ایمپورت کنید.</string>
<string name="noGiftCardsGroup">تعدادی کارت ایجاد کنید، و سپس آنها را اینجا به گروه بیافزایید.</string>
<string name="noGiftCards">برای افزودن کارت، روی دکمه + کلیک کنید یا از منوی ⋮ کارت‌ها را وارد کنید.</string>
<string name="noGiftCardsGroup">چند کارت ایجاد کنید و سپس آنها را در اینجا به این گروه اختصاص دهید.</string>
<string name="noMatchingGiftCards">بدون نتیجه. سعی کنید جستجویتان را تغییر دهید.</string>
<string name="storeName">نام</string>
<string name="note">یادداشت</string>
@@ -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>
@@ -117,8 +117,8 @@
<string name="importCatimaMessage">فایل <i>catima.zip</i> خروجی خود را از Catima برای وارد کردن انتخاب کنید.\nآن را از منوی وارد/صادر کردن در یک اپلیکیشن دیگر Catima با فشردن دکمه صادرکردن ابتدا ایجاد کنید.</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>
@@ -180,7 +180,6 @@
<string name="height">ارتفاع</string>
<string name="add_manually_warning_message">برای برخی از فروشگاه‌ها، مقدار بارکد با عدد نوشته شده روی کارت متفاوت است. به همین دلیل، وارد کردن دستی بارکد ممکن است همیشه کار نکند. اکیداً توصیه می‌شود که به جای آن، بارکد را با دوربین خود اسکن کنید. آیا هنوز می‌خواهید ادامه دهید؟</string>
<string name="generic_error_please_retry">ببخشید، مشکلی پیش آمده، لطفا دوباره امتحان کنید...</string>
<string name="settings_magenta_theme">سرخابی</string>
<string name="welcome">یه کتیما خوش آمدید</string>
<string name="chooseValidFromDate">مقداری درست از تاریخ برگزینید</string>
<string name="intent_import_card_from_url_share_multiple_text">می‌خواهم چند کارت به شما بدهم</string>
@@ -198,15 +197,8 @@
<string name="settings_oled_dark">پس‌زمینه‌ی یک‌دست سیاه برای حالت تاریک</string>
<string name="settings_oled_dark_summary">استفاده‌ی باتری را برای نمایشگرهای OLED کاهش می‌دهد</string>
<string name="setIcon">قالب پیش‌نمایه را بگمارید</string>
<string name="settings_theme_color">رنگ زمینه</string>
<string name="settings_catima_theme">کتیما</string>
<string name="settings_system_locale">سیستم</string>
<string name="selectColor">رنگ را برگزینید</string>
<string name="settings_violet_theme">بنفش</string>
<string name="settings_blue_theme">آبی</string>
<string name="settings_sky_blue_theme">آبی آسمانی</string>
<string name="settings_green_theme">سبز</string>
<string name="settings_brown_theme">قهوه‌ای</string>
<string name="app_contributors">با کمک او ممکن شد: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="sort">مرتب‌کردن</string>
<string name="showMoreInfo">نمایش اطلاعات</string>
@@ -220,7 +212,6 @@
<string name="passwordRequired">لطفا گذرواژه را وارد کنید</string>
<string name="previousCard">پیشین</string>
<string name="turn_flashlight_off">چراغ‌قوه را خاموش کنید</string>
<string name="settings_pink_theme">صورتی</string>
<string name="updateBalance">به‌روزرسانی موجودی</string>
<string name="barcodeLongPressMessage">تنها عکس می‌تواند در گالری برنامه باز شود</string>
<string name="sort_by_name">نام</string>
@@ -294,4 +285,5 @@
<string name="spend">خرج کردن</string>
<string name="addFromPkpass">یک فایل دفترچه حساب (.pkpass) انتخاب کنید</string>
<string name="noCameraFoundGuideText">به نظر نمی‌رسد دستگاه شما دوربین داشته باشد. اگر دارد، دستگاه را مجدداً راه‌اندازی کنید. در غیر این صورت، از دکمه گزینه‌های بیشتر در زیر برای افزودن بارکد به روش دیگری استفاده کنید.</string>
<string name="card_list_widget_empty">بعد از اینکه چند کارت وفاداری در کاتیما اضافه کردید، آنها اینجا ظاهر می‌شوند. اگر کارت دارید، مطمئن شوید که همه آنها بایگانی نشده‌اند.</string>
</resources>
+34 -33
View File
@@ -10,17 +10,14 @@
<string name="setBarcodeId">Aseta viivakoodin arvo</string>
<string name="sameAsCardId">Sama kuin ID-tunnus</string>
<string name="barcodeId">Viivakoodin arvo</string>
<string name="importVoucherVaultMessage">Valitse tuotava <i>vouchervault.json</i>-vienti Voucher Vaultista.
\nLuo se painamalla ensin Vie Voucher Vaultissa.</string>
<string name="importVoucherVaultMessage">Valitse vienti Voucher Vaultista tuotavaksi. \nLuo se painamalla \"Export\" Voucher Vaultissa.</string>
<string name="importVoucherVault">Tuo Voucher Vault -varmuuskopiotiedostosta</string>
<string name="importLoyaltyCardKeychainMessage">Valitse tuotava <i>LoyaltyCardKeychain.csv</i>-vientitiedostosi Loyalty Card Keychainista.
\nTai luo se Loyalty Card Keychainin Import/Export-valikosta painamalla ensin Export (Vie).</string>
<string name="importLoyaltyCardKeychain">Tuo Loyalty Card Keychain varmuuskopiotiedostosta</string>
<string name="importFidmeMessage">Valitse tuotava <i>fidme-export-request-xxxxxxxx.zip</i>-vientitiedostosi FidMe:stä ja valitse viivakoodityypit manuaalisesti sen jälkeen.
\nLuo se FidMe-profiilistasi valitsemalla Tietosuoja ja painamalla ensin Extract my data.</string>
<string name="importFidmeMessage">Valitse vienti FidMe:stä tuotavaksi ja valitse viivakoodityypit manuaalisesti sen jälkeen. \nLuo se FidMe-profiilistasi valitsemalla \"Data Protection\" ja painamalla \"Extract my data\".</string>
<string name="importFidme">Tuo FidMe-varmuuskopiotiedostosta</string>
<string name="importCatimaMessage">Valitse tuotava <i>catima.zip</i>-vientitiedostosi Catimasta.
\nLuo se Catima-sovelluksen Tuo/vie-valikosta painamalla siellä ensin Vie.</string>
<string name="importCatimaMessage">Valitse tuotava vientitiedosto Catimasta. \nLuo se Catima-sovelluksen Tuo/vie-valikosta painamalla siellä ensin Vie.</string>
<string name="importCatima">Tuo Catima varmuuskopiotiedostosta</string>
<string name="accept">Hyväksy</string>
<string name="privacy_policy">Tietosuojakäytäntö</string>
@@ -47,14 +44,14 @@
<string name="leaveWithoutSaveTitle">Poistu</string>
<string name="moveDown">Siirrä alaspäin</string>
<string name="moveUp">Siirrä ylöspäin</string>
<string name="failedOpeningFileManager">Asenna ensin tiedostonhallintaohjelma.</string>
<string name="failedOpeningFileManager">Tiedostonhallintaohjelman avaaminen epäonnistui</string>
<string name="deleteConfirmationGroup">Poistetaanko ryhmä\?</string>
<string name="all">Kaikki</string>
<plurals name="groupCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> kortti</item>
<item quantity="other"><xliff:g>%d</xliff:g> korttia</item>
</plurals>
<string name="noGroups">Napsauta plus-painiketta (+) lisätäksesi ryhmät luokittelua varten.</string>
<string name="noGroups">Napsauta plus-painiketta (+) lisätäksesi ryhmät luokittelua varten</string>
<string name="groups">Ryhmät</string>
<string name="enter_group_name">Anna ryhmän nimi</string>
<string name="exportSuccessful">Tiedot viety</string>
@@ -71,8 +68,8 @@
<string name="starImage">Suosikkitähti</string>
<string name="thumbnailDescription">Pienoiskuva</string>
<string name="selectBarcodeTitle">Valitse viivakoodi</string>
<string name="app_resources">Vapaat kolmannen osapuolen resurssit: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Vapaat kolmannen osapuolen kirjastot: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Kolmannen osapuolen resurssit: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Kolmannen osapuolen kirjastot: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="debug_version_fmt">Versio: <xliff:g id="version">%s</xliff:g></string>
<string name="about_title_fmt">Tietoja <xliff:g id="app_name">%s</xliff:g></string>
<string name="app_license">Copyleft (käyttäjänoikeus) - vapaa ohjelmisto, lisenssi GPLv3+</string>
@@ -81,9 +78,9 @@
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Tekijänoikeus © 2019<xliff:g>%d</xliff:g> Sylvia van Os ja avustajat</string>
<string name="about">Tietoja</string>
<string name="importOptionFilesystemButton">Tiedostojärjestelmästä</string>
<string name="importOptionFilesystemExplanation">Valitse tietty tiedosto tiedostojärjestelmästä.</string>
<string name="importOptionFilesystemExplanation">Valitse tietty tiedosto tiedostojärjestelmästä</string>
<string name="importOptionFilesystemTitle">Tuo tiedostojärjestelmästä</string>
<string name="exportOptionExplanation">Tiedot kirjoitetaan valitsemaasi sijaintiin.</string>
<string name="exportOptionExplanation">Tiedot kirjoitetaan valitsemaasi sijaintiin</string>
<string name="exporting">Viedään…</string>
<string name="importing">Tuodaan…</string>
<string name="exportFailed">Vienti epäonnistui</string>
@@ -92,10 +89,10 @@
<string name="importFailed">Tuonti epäonnistui</string>
<string name="importFailedTitle">Tuonti epäonnistui</string>
<string name="importSuccessfulTitle">Tuonti valmis</string>
<string name="importExportHelp">Tietojesi varmuuskopiointi mahdollistaa niiden siirtämisen toiseen laitteeseen.</string>
<string name="importExportHelp">Tietojesi varmuuskopiointi mahdollistaa niiden siirtämisen toiseen laitteeseen</string>
<string name="exportName">Vie</string>
<string name="importExport">Tuo/vie</string>
<string name="failedParsingImportUriError">Tuonnin URI: n jäsentäminen epäonnistui</string>
<string name="failedParsingImportUriError">Tuonnin URI:n jäsentäminen epäonnistui</string>
<string name="noCardExistsError">Tätä korttia ei löytynyt</string>
<string name="noCardsMessage">Lisää ensin kortti</string>
<string name="cardShortcut">Kortin pikakuvake</string>
@@ -118,7 +115,7 @@
<string name="note">Lisätieto</string>
<string name="storeName">Nimi</string>
<string name="noMatchingGiftCards">Ei hakutuloksia, kokeile toisella hakutermillä.</string>
<string name="noGiftCards">Lisää kortti napsauttamalla plus-painiketta (+), tai mene ⋮-valikkoon tuodaksesi.</string>
<string name="noGiftCards">Lisää kortti napsauttamalla plus-painiketta (+), tai mene ⋮-valikkoon tuodaksesi</string>
<string name="action_add">Lisää</string>
<string name="action_search">Hae</string>
<string name="takePhoto">Ota valokuva</string>
@@ -142,20 +139,11 @@
<item quantity="one"><xliff:g>%d</xliff:g> valittu</item>
<item quantity="other"><xliff:g>%d</xliff:g> valitut</item>
</plurals>
<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="passwordRequired">Syötä salasana</string>
<string name="failedGeneratingShareURL">Jaettavaa URL-osoitetta ei voitu luoda</string>
<string name="turn_flashlight_on">Käytä taskulamppua</string>
<string name="turn_flashlight_off">Sammuta salamavalo</string>
<string name="app_contributors">Mahdollistanut: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="settings_brown_theme">Ruskea</string>
<string name="settings_green_theme">Vihreä</string>
<string name="settings_sky_blue_theme">Taivaansininen</string>
<string name="settings_blue_theme">Siniset</string>
<string name="settings_violet_theme">Violetti</string>
<string name="settings_magenta_theme">Magenta</string>
<string name="settings_pink_theme">Pinkki</string>
<string name="settings_theme_color">Teeman väri</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_system_locale">Järjestelmä</string>
<string name="settings_locale">Kieli</string>
<string name="noGroupCards">Tämä ryhmä on tyhjä</string>
@@ -182,7 +170,7 @@
<string name="on_github">GitHubissa</string>
<string name="and_data_usage">ja datan käyttö</string>
<string name="on_google_play">Google Playssa</string>
<string name="report_error">Ilmoita virheestä</string>
<string name="report_error">Ilmoita ongelmasta</string>
<string name="shortcutSelectCard">Valitse kortti</string>
<string name="starred">Tähdellä merkityt</string>
<string name="options">Vaihtoehdot</string>
@@ -195,12 +183,12 @@
<string name="translate_platform">Weblatessa</string>
<string name="selectColor">Valitse väri</string>
<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="noGiftCardsGroup">Lisää kortteja ja lisää ne ryhmään täällä</string>
<string name="barcodeImageDescriptionWithType">Kuva <xliff:g>%s</xliff:g> viivakoodi</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="failedLaunchingPhotoPicker">Tuettua kuvavalitsinta ei löytynyt</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>
@@ -235,14 +223,14 @@
<string name="height">Korkeus</string>
<string name="switchToFrontImage">Vaihda etukuvaan</string>
<string name="switchToBarcode">Vaihda viivakoodiin</string>
<string name="openFrontImageInGalleryApp">Avaa etukuva galleriasovelluksessa</string>
<string name="openFrontImageInGalleryApp">Avaa etukuva kuvankatselusovelluksessa</string>
<string name="settings_display_barcode_max_brightness_summary">Välttämätön, jotta jotkin skannerit toimivat</string>
<string name="settings_keep_screen_on_summary">Poistaa näytön aikakatkaisun korttia katsellessa</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">Poistaa näyttölukituksen käytöstä korttia katsellessa</string>
<string name="settings_allow_content_provider_read_title">Salli muiden sovellusten käyttää omaa dataa</string>
<string name="settings_oled_dark_summary">Vähentää akun käyttöä OLED-näytöillä</string>
<string name="switchToBackImage">Vaihda takakuvaan</string>
<string name="openBackImageInGalleryApp">Avaa takakuva galleriasovelluksessa</string>
<string name="openBackImageInGalleryApp">Avaa takakuva kuvankatselusovelluksessa</string>
<string name="setBarcodeHeight">Aseta viivakoodin korkeus</string>
<string name="icon_header_click_text">Pitkä painallus pikkukuvan muokkaamiseksi</string>
<string name="show_name_below_image_thumbnail">Näytä nimi pikkukuvan alapuolella</string>
@@ -293,12 +281,25 @@
<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="addFromPkpass">Valitse Passbook-tiedosto (.pkpass)</string>
<string name="addFromPkpass">Valitse Passbook-tiedosto (.pkpass / .pkpasses)</string>
<string name="unsupportedFile">Tämä tiedosto ei ole tuettu</string>
<string name="generic_error_please_retry">Pahoittelut, jokin meni pieleen. Yritä uudelleen...</string>
<string name="generic_error_please_retry">Tapahtui virhe</string>
<string name="sort_by_valid_from">Voimassa alkaen</string>
<string name="width">Leveys</string>
<string name="setBarcodeWidth">Aseta viivakoodin leveys</string>
<string name="card_list_widget_name">Korttiluettelo</string>
<string name="card_list_widget_empty">Kun olet lisännyt kanta-asiakaskortteja Catimaan, ne näkyvät täällä. Jos sinulla on kortteja, varmista, etteivät ne kaikki ole arkistoituja.</string>
<string name="cardWithNumber">Kortti <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Kortti <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Älä kierrä laitetta, koska se peruu toiminnon</string>
<string name="acra_catima_has_crashed">Valitettavasti <xliff:g id="app_name">%s</xliff:g> kaatui. Auta korjaamaan ongelma lähettämällä virheraportti.</string>
<string name="pref_enable_acra">Kysy kaatumisraporttien lähettämisestä</string>
<string name="pref_enable_acra_summary">Kun käytössä, sinulta kysytään lupa lähettää raportti kaatumisen yhteydessä. Kaatumisraportteja ei koskaan lähetetä automaattisesti.</string>
<string name="copy_value">Kopioi arvo</string>
<string name="copied_to_clipboard">Kopioitu leikepöydälle</string>
<string name="nothing_to_copy">Arvoa ei löytynyt</string>
<string name="barcodeEncoding">Viivakoodin koodaus</string>
<string name="back">Takaisin</string>
<string name="acra_explain_crash">Jos mahdollista lisää tietoja toiminnastasi täällä:</string>
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> pysähtymisraportti</string>
</resources>
+12
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>
+9 -9
View File
@@ -147,15 +147,6 @@
</plurals>
<string name="settings_system_locale">Système</string>
<string name="settings_locale">Langue</string>
<string name="settings_brown_theme">Marron</string>
<string name="settings_green_theme">Vert</string>
<string name="settings_sky_blue_theme">Bleu ciel</string>
<string name="settings_blue_theme">Bleu</string>
<string name="settings_violet_theme">Violet</string>
<string name="settings_magenta_theme">Magenta</string>
<string name="settings_pink_theme">Rose</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_theme_color">Couleur du thème</string>
<string name="app_contributors">Rendu possible par : <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="noGroupCards">Ce groupe est vide</string>
<string name="barcodeImageDescriptionWithType">Image <xliff:g>%s</xliff:g> code-barres</string>
@@ -311,4 +302,13 @@
<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>
<string name="barcodeEncoding">Encodage du code-barres</string>
<string name="back">Retour</string>
<string name="settings_disable_nfc_while_viewing_card">Mettre en pause le NFC</string>
<string name="settings_disable_nfc_while_viewing_card_summary">Mettre en pause le NFC pour éviter le déclenchement de paiements NFC lors de la consultation d\'une carte</string>
<string name="nfc_blocked_while_viewing_card">Le NFC est en pause</string>
<string name="change_settings">Modifier les paramètres</string>
</resources>
+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>
+9 -9
View File
@@ -145,14 +145,6 @@
<string name="settings_oled_dark_summary">Diminúe o uso da batería nas pantallas OLED</string>
<string name="settings_system_locale">Sistema</string>
<string name="selectColor">Elixir cor</string>
<string name="settings_theme_color">Cor do decorado</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_pink_theme">Rosa</string>
<string name="settings_magenta_theme">Maxenta</string>
<string name="settings_violet_theme">Violeta</string>
<string name="settings_sky_blue_theme">Azul celeste</string>
<string name="settings_green_theme">Verde</string>
<string name="settings_brown_theme">Marrón</string>
<string name="app_contributors">Creada grazas a: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="sort">Orde</string>
<string name="showMoreInfo">Ver info</string>
@@ -269,7 +261,6 @@
<string name="deleteConfirmationGroup">Eliminar grupo?</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>
@@ -304,4 +295,13 @@
<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>
<string name="barcodeEncoding">Códificación do código de barras</string>
<string name="back">Volver</string>
<string name="settings_disable_nfc_while_viewing_card">NFC en pausa</string>
<string name="settings_disable_nfc_while_viewing_card_summary">Detén o NFC para evitar a activación de pago con NFC cando se mira unha tarxeta</string>
<string name="nfc_blocked_while_viewing_card">NFC está en pausa</string>
<string name="change_settings">Cambiar axustes</string>
</resources>
+18 -3
View File
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_add">הוספה</string>
<string name="cardId">מזהה כרטיס</string>
<string name="cancel">ביטול</string>
<string name="save">שמור</string>
<string name="ok">אישור</string>
<string name="action_search">חיפוש</string>
<string name="noGiftCards">לחץ על לחצן ה + להוספת כרטיס, או ייבא מספר כרטיסים באמצעות התפריט ⋮.</string>
<string name="noGiftCards">לחץ על כפתור הפלוס (+) כדי להוסיף כרטיס, או ייבא כרטיס מהתפריט ⋮.</string>
<string name="share">שיתוף</string>
<string name="addCardTitle">הוספת כרטיס</string>
<string name="star">הוספה למועדפים</string>
@@ -16,7 +16,7 @@
<string name="noBarcode">אין ברקוד</string>
<string name="edit">עריכה</string>
<string name="exportFailed">לא ניתן לייצא</string>
<string name="noGiftCardsGroup">צור מספר כרטיסים, ואז שייך אותם לקבוצה פה.</string>
<string name="noGiftCardsGroup">צור מספר כרטיסים, ולאחר מכן שייך אותם לקבוצה.</string>
<string name="failedParsingImportUriError">נתיב לא נמצא</string>
<string name="noMatchingGiftCards">אין תוצאות. נסה חיפוש אחר.</string>
<string name="storeName">שם חנות</string>
@@ -51,4 +51,19 @@
<string name="multipleBarcodesFoundPleaseChooseOne">באיזה מהברקודים שנמצאו אתה רוצה להשתמש?</string>
<string name="useFrontImage">השתמש בתמונה קדמית</string>
<string name="useBackImage">השתמש בתמונה אחורית</string>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> נבחר</item>
<item quantity="two"><xliff:g>%d</xliff:g> נבחרו</item>
<item quantity="other"><xliff:g>%d</xliff:g> נבחרו</item>
</plurals>
<plurals name="deleteCardsTitle">
<item quantity="one">הסר <xliff:g>%d</xliff:g> כרטיסייה</item>
<item quantity="two">הסר <xliff:g>%d</xliff:g> כרטיסיות</item>
<item quantity="other">הסר <xliff:g>%d</xliff:g> כרטיסיות</item>
</plurals>
<plurals name="deleteCardsConfirmation">
<item quantity="one">להסיר את <xliff:g>%d</xliff:g> הכרטיסייה לצמיתות?</item>
<item quantity="two">להסיר את <xliff:g>%d</xliff:g> הכרטיסיות לצמיתות?</item>
<item quantity="other">להסיר את <xliff:g>%d</xliff:g> הכרטיסיות לצמיתות?</item>
</plurals>
</resources>
+11 -9
View File
@@ -170,28 +170,19 @@
<string name="action_more_options">अधिक विकल्प</string>
<string name="frontImageDescription">सामने की छवि</string>
<string name="anyDate">कोई दिन</string>
<string name="settings_green_theme">हरा</string>
<string name="settings_pink_theme">गुलाबी</string>
<string name="action_display_options">प्रदर्शन विकल्प</string>
<string name="settings_category_title_cards">कार्ड</string>
<string name="addWithoutBarcode">बिना बारकोड वाला कार्ड जोड़ें</string>
<string name="on_google_play">गूगल प्ले पर</string>
<string name="report_error">गलती की रिपोर्ट करें</string>
<string name="passwordRequired">कृपया पासवर्ड दर्ज करें</string>
<string name="settings_brown_theme">भूरा</string>
<string name="field_must_not_be_empty">फ़ील्ड खाली नहीं होनी चाहिए</string>
<string name="settings_catima_theme">कैटिमा</string>
<string name="options">विकल्प</string>
<string name="settings_magenta_theme">मैजेंटा</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>
<string name="updateBalanceHint">राशि डालें</string>
<string name="on_github">गिटहब पर</string>
<string name="donate">दान करें /भेंट दें</string>
<string name="settings_violet_theme">बैंगनी</string>
<string name="settings_blue_theme">नीला</string>
<string name="shortcutSelectCard">एक कार्ड चुनें</string>
<string name="settings_category_title_privacy">गोपनीयता</string>
<string name="show_balance">शेष राशि दिखाएं</string>
@@ -298,4 +289,15 @@
<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>
<string name="barcodeEncoding">बारकोड एन्कोडिंग</string>
<string name="back">वापस</string>
</resources>
+42 -39
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,16 +60,16 @@
<string name="importSuccessful">Podaci su uvezeni</string>
<string name="enter_group_name">Upiši ime grupe</string>
<string name="groups">Grupe</string>
<string name="noGroups">Pritisni gumb + plus za dodavanje grupe za kategoriziranje.</string>
<string name="noGroups">Pritisni gumb + plus za dodavanje grupe za kategoriziranje</string>
<string name="noGroupCards">Ova je grupa prazna</string>
<string name="addFromImage">Odaberi sliku iz galerije</string>
<string name="deleteConfirmationGroup">Izbrisati grupu\?</string>
<string name="failedOpeningFileManager">Najprije instaliraj upravljač datoteka.</string>
<string name="failedOpeningFileManager">Neuspjelo otvaranje upravljača datoteka</string>
<string name="moveUp">Pomakni prema gore</string>
<string name="leaveWithoutSaveTitle">Zatvori aplikaciju</string>
<string name="card">Kartica</string>
<string name="leaveWithoutSaveConfirmation">Zatvoriti aplikaciju bez spremanja\?</string>
<string name="noGiftCardsGroup">Stvori neke kartice, a zatim ih ovdje dodijeli grupi.</string>
<string name="noGiftCardsGroup">Stvori neke kartice, a zatim ih ovdje dodijeli grupi</string>
<plurals name="groupCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> kartica</item>
<item quantity="few"><xliff:g>%d</xliff:g> kartice</item>
@@ -83,26 +83,20 @@
<string name="accept">Prihvati</string>
<string name="importCatima">Uvezi iz Catima</string>
<string name="importFidme">Uvezi iz FidMe</string>
<string name="importLoyaltyCardKeychainMessage">Odaberi tvoju iz LoyaltyCardKeychain izvezenu <i>LoyaltyCardKeychain.csv</i> datoteku koju želiš uvesti.
\nStvori je putem izbornika „Uvoz/Izvoz” u aplikaciji Loyalty Card Keychain i tamo pritisni „Izvoz”.</string>
<string name="importLoyaltyCardKeychainMessage">Odaberi tvoj izvoz iz LoyaltyCardKeychain za uvoz. \nStvori je putem izbornika „Uvoz/Izvoz” u aplikaciji Loyalty Card Keychain pritiskom na „Izvoz”.</string>
<string name="updateBarcodeQuestionText">Promijenio/la si ID. Želiš li također aktualizirati crtični kod da koristi istu vrijednost\?</string>
<string name="importCards">Uvezi kartice</string>
<string name="selectColor">Odaberi boju</string>
<string name="setIcon">Postavi sličicu</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_green_theme">Zelena</string>
<string name="sort_by_expiry">Istek</string>
<string name="barcodeImageDescriptionWithType">Slika vrste crtičnog koda <xliff:g>%s</xliff:g></string>
<string name="importLoyaltyCardKeychain">Uvezi iz Loyalty Card Keychain</string>
<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>
<string name="settings_violet_theme">Ljubičasta</string>
<string name="settings_sky_blue_theme">Nebesko plava</string>
<string name="sort">Razvrstaj</string>
<string name="updateBalance">Aktualiziraj saldo</string>
<string name="sort_by">Razvrstaj po</string>
@@ -118,10 +112,10 @@
<string name="archive">Arhiviraj</string>
<string name="archived">Kartica je arhivirana</string>
<string name="unarchived">Kartica je uklonjena iz arhive</string>
<string name="failedLaunchingPhotoPicker">Nije bilo moguće pronaći podržanu aplikaciju galerije</string>
<string name="failedLaunchingPhotoPicker">Nije bilo moguće pronaći podržani birač slika</string>
<string name="cameraPermissionDeniedTitle">Nije bilo moguće pristupiti kameri</string>
<string name="noCameraPermissionDirectToSystemSetting">Za snimanje crtičnih kodova Catima treba pristup tvojoj kameri. Dodirni ovdje za mijenjanje postavki dozvola.</string>
<string name="app_libraries">Slobodne biblioteke trećih strana: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_libraries">Biblioteke trećih strana: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Odaberi crtični kod</string>
<string name="group_edit">Uredi grupu</string>
<string name="group_name_already_in_use">Ime grupe se već koristi</string>
@@ -129,14 +123,13 @@
<string name="balance">Saldo</string>
<string name="chooseImportType">Uvezi podatke iz</string>
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
<string name="importCatimaMessage">Odaberi tvoju iz Catima izvezenu <i>catima.zip</i> datoteku koju želiš uvesti.
\nStvori je putem izbornika „Uvoz/Izvoz” jedne druge Catima aplikacije pritiskom na „Izvoz”.</string>
<string name="importCatimaMessage">Odaberi tvoj izvoz iz Catima za uvoz. \nStvori je putem izbornika „Uvoz/Izvoz” jedne druge Catima aplikacije pritiskom na „Izvoz”.</string>
<string name="height">Visina</string>
<string name="switchToFrontImage">Prebaci na prednju sliku</string>
<string name="switchToBackImage">Prebaci na stražnju sliku</string>
<string name="switchToBarcode">Prebaci na crtični kod</string>
<string name="openFrontImageInGalleryApp">Otvori prednju sliku u aplikaciji galerije</string>
<string name="openBackImageInGalleryApp">Otvori stražnju sliku u aplikaciji galerije</string>
<string name="openFrontImageInGalleryApp">Otvori prednju sliku u aplikaciji prikazivača slika</string>
<string name="openBackImageInGalleryApp">Otvori stražnju sliku u aplikaciji prikazivača slika</string>
<string name="setBarcodeHeight">Postavi visinu crtičnog koda</string>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> odabrana</item>
@@ -162,12 +155,8 @@
<string name="cameraPermissionRequired">Za ovu radnju je potrebna dozvola za pristup kameri …</string>
<string name="app_license">Copylefted libre softver, GPLv3+ licenca</string>
<string name="balanceSentence">Saldo: <xliff:g>%s</xliff:g></string>
<string name="importFidmeMessage">Odaberi tvoju iz FidMe izvezenu <i>idme-export-request-xxxxxx.zip</i> datoteku koju želiš uvesti i ručno odaberi vste crtičnog koda nakon toga.
\nStvori je putem tvog FidMe profila biranjem „Zaštita podataka” a zatim pritisni „Dekomprimiraj moje podatke”.</string>
<string name="importVoucherVaultMessage">Odaberi tvoju iz Voucher Vault izvezenu <i>vouchervault.json</i> datoteku koju želiš uvesti.
\nStvori je u aplikaciji Voucher Vault i tamo pritisni „Izvoz”.</string>
<string name="settings_pink_theme">Ružičasta</string>
<string name="settings_blue_theme">Plava</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="failedToRetrieveImageFile">Neuspjelo dohvaćanje slikovne datoteke</string>
<string name="license">Licenca</string>
<string name="barcodeLongPressMessage">U aplikaciji galerije se mogu otvoriiti samo slike</string>
@@ -196,7 +185,7 @@
</plurals>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Autorska prava © 2019. <xliff:g>%d.</xliff:g> Sylvia van Os i doprinositelji</string>
<string name="debug_version_fmt">Verzija: <xliff:g id="version">%s</xliff:g></string>
<string name="app_resources">Slobodni resursi trećih strana: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_resources">Resursi trećih strana: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="group_name_is_empty">Ime grupe ne smije biti prazno</string>
<string name="group_updated">Grupa je aktualizirana</string>
<string name="all">Sve</string>
@@ -205,7 +194,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>
@@ -227,12 +216,10 @@
<string name="passwordRequired">Upiši lozinku</string>
<string name="exportPassword">Postavi lozinku za zaštitu tvog izvoza (opcionalno)</string>
<string name="settings_oled_dark">Potpuno crna pozadina za tamnu temu</string>
<string name="settings_theme_color">Boja teme</string>
<string name="settings_brown_theme">Smeđa</string>
<string name="app_contributors">Doprinositelji: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="showMoreInfo">Prikaži informacije</string>
<string name="sort_by_name">Ime</string>
<string name="sort_by_most_recently_used">Nedavno korišteno</string>
<string name="sort_by_most_recently_used">Zadnje korišteno</string>
<string name="reverse">… u obrnutom redoslijedu</string>
<string name="shortcutSelectCard">Odaberi karticu</string>
<string name="previousCard">Prethodna</string>
@@ -271,10 +258,10 @@
<string name="settings_keep_screen_on_summary">Deaktivira isključivanje ekrana tijekom prikaza kartice</string>
<string name="app_name">Catima</string>
<string name="continue_">Nastavi</string>
<string name="add_manually_warning_message">Za neke trgovine se vrijednost crtičnog koda razlikuje od broja na kartici. Zbog toga ručno upisivanje crtičnog koda možda neće uvijek funkcionirati. Preporučuje se snimanje crtičnog koda pomoću kamere. Želiš li svejedno nastaviti?</string>
<string name="add_manually_warning_message">Za neke kartice se vrijednost crtičnog koda razlikuje od broja na kartici. Zbog toga ručno upisivanje crtičnog koda možda neće uvijek funkcionirati. Preporučuje se snimanje crtičnog koda pomoću kamere. Želiš li svejedno nastaviti?</string>
<string name="add_manually_warning_title">Preporučuje se snimanje</string>
<string name="addFromPdfFile">Odaberi PDF datoteku</string>
<string name="errorReadingFile">Nije bilo moguće pročitati datoteku</string>
<string name="errorReadingFile">Nije bilo moguće čitati datoteku</string>
<string name="failedLaunchingFileManager">Nije bilo moguće pronaći podržani upravljač datoteka</string>
<string name="multipleBarcodesFoundPleaseChooseOne">Koji od pronađenih crtičnih kodova želiš koristiti?</string>
<string name="pageWithNumber">Stranica <xliff:g>%d</xliff:g></string>
@@ -298,14 +285,30 @@
<string name="settings_column_count_4">4</string>
<string name="settings_column_count_5">5</string>
<string name="settings_column_count_7">7</string>
<string name="generic_error_please_retry">Žao nam je, nešto nije u redu, pokušaj ponovo …</string>
<string name="generic_error_please_retry">Dogodila se greška</string>
<string name="addFromPkpass">Odaberi jednu Passbook datoteku (.pkpass / .pkpasses)</string>
<string name="unsupportedFile">Ova datoteka nije podržana</string>
<string name="settings_use_volume_keys_navigation_summary">Pomoću gumba za glasnoću promijeni koja se kartica prikazuje</string>
<string name="settings_use_volume_keys_navigation">Mijenjaj kartice pomoću gumba za glasnoću</string>
<string name="width">Širina</string>
<string name="card_list_widget_name">Popis kartica</string>
<string name="setBarcodeWidth">Postavi širinu barkoda</string>
<string name="setBarcodeWidth">Postavi širinu crtičnog koda</string>
<string name="cardWithNumber">Kartica <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Kartica <xliff:g>%d</xliff:g> (%s)</string>
<string name="cardWithNumberAndLocale">Kartica <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="card_list_widget_empty">Nakon što dodaš neke kartice vjernosti u Catima, one će se pojaviti ovdje. Ako već imaš kartice, provjeri da nisu sve arhivirane.</string>
<string name="pleaseDoNotRotateTheDevice">Ne okreći uređaj jer će to prekinuti radnju</string>
<string name="acra_catima_has_crashed">Žao nam je, ali aplikacija <xliff:g id="app_name">%s</xliff:g> je prekinula rad. Pomogni riješiti ovaj problem slanjem izvještaja o grešci.</string>
<string name="acra_explain_crash">Po mogućnosti dodaj više detalja o tvojim radnjama:</string>
<string name="acra_crash_email_subject">Izvještaj o prekidu rada aplikacije <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Pitaj da li poslati izvještaj o prekidu rada aplikacije</string>
<string name="pref_enable_acra_summary">Kada je uključeno, zamolit ćemo te da prijaviš prekid rada aplikacije kada se dogodi. Izvještaji o prekidu rada se nikada ne šalju automatski.</string>
<string name="copied_to_clipboard">Kopirano u međuspremnik</string>
<string name="back">Natrag</string>
<string name="copy_value">Kopiraj vrijednost</string>
<string name="nothing_to_copy">Nije pronađena vrijednost</string>
<string name="barcodeEncoding">Kodiranje barkoda</string>
<string name="change_settings">Promijeni postavke</string>
<string name="settings_disable_nfc_while_viewing_card">Pauziraj NFC</string>
<string name="nfc_blocked_while_viewing_card">NFC je pauziran</string>
<string name="settings_disable_nfc_while_viewing_card_summary">Pauzira NFC kako bi se spriječilo aktiviranje NFC plaćanja tijekom prikazivanja kartice</string>
</resources>
-9
View File
@@ -52,14 +52,11 @@
<string name="noCardExistsError">A kártya nem található</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>
<string name="setBackImage">Hátlapi kép beállítása</string>
<string name="no">Nem</string>
<string name="passwordRequired">Adja meg a jelszót</string>
<string name="settings_catima_theme">Catima</string>
<string name="exportPasswordHint">Kód beírása</string>
<string name="failedGeneratingShareURL">Nem lehetett megosztható webcímet előállítani. Kérjük, ezt jelentse.</string>
<string name="settings_theme_color">Téma színe</string>
<string name="sort">Rendezés</string>
<string name="on_google_play">a Google Playen</string>
<string name="and_data_usage">és adathasználat</string>
@@ -146,12 +143,6 @@
<string name="settings_system_locale">Rendszer</string>
<string name="selectColor">Szín kiválasztása</string>
<string name="setIcon">Miniatűr beállítása</string>
<string name="settings_pink_theme">Rózsaszín</string>
<string name="settings_magenta_theme">Bíbor</string>
<string name="settings_violet_theme">Ibolya</string>
<string name="settings_blue_theme">Kék</string>
<string name="settings_sky_blue_theme">Égszínkék</string>
<string name="settings_brown_theme">Barna</string>
<string name="app_contributors">Lehetővé tették: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="showMoreInfo">Információk megjelenítése</string>
<string name="reverse">…fordított sorrendben</string>
+44 -44
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,19 +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="settings_catima_theme">Catima</string>
<string name="settings_pink_theme">Merah Muda</string>
<string name="settings_blue_theme">Biru</string>
<string name="settings_green_theme">Hijau</string>
<string name="settings_sky_blue_theme">Biru Langit</string>
<string name="settings_brown_theme">Cokelat</string>
<string name="settings_violet_theme">Ungu</string>
<string name="settings_magenta_theme">Magenta</string>
<string name="settings_theme_color">Warna tema</string>
<string name="sort_by_most_recently_used">Yang paling baru digunakan</string>
<string name="settings_system_locale">Sistem</string>
<string name="settings_locale">Bahasa</string>
<string name="turn_flashlight_on">Hidupkan lampu flash</string>
@@ -87,9 +78,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 +89,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 +103,10 @@
<string name="exportSuccessful">Data terekspor</string>
<string name="enter_group_name">Masukan nama grup</string>
<string name="groups">Grup</string>
<string name="noGroups">Klik pada tombol tambah + untuk menambahkan grup untuk pengkategorian.</string>
<string name="noGroups">Klik tombol + untuk menambahkan grup untuk pengelompokan</string>
<string name="noGroupCards">Grup ini kosong</string>
<string name="deleteConfirmationGroup">Hapus grup?</string>
<string name="failedOpeningFileManager">Pasang aplikasi pengelola berkas terlebih dahulu.</string>
<string name="failedOpeningFileManager">Gagal membuka pengelola berkas</string>
<string name="moveUp">Pindah ke atas</string>
<string name="moveDown">Pindah ke bawah</string>
<string name="leaveWithoutSaveConfirmation">Keluar tanpa menyimpan?</string>
@@ -132,36 +123,32 @@
<string name="points">Poin</string>
<string name="app_loyalty_card_keychain">Gantungan kunci kartu kesetiaan</string>
<string name="privacy_policy">Kebijakan Privasi</string>
<string name="importCatimaMessage">Pilih ekspor <i>catima.zip</i> Anda dari Catima untuk diimpor.
\nBuat dari menu Impor/Ekspor aplikasi Catima lain dengan menekan Ekspor di sana terlebih dahulu.</string>
<string name="importFidmeMessage">Pilih ekspor <i>fidme-export-request-xxxxxx.zip</i> Anda dari FidMe untuk diimpor, dan pilih jenis barcode secara manual setelahnya.
\nBuat dari profil FidMe Anda dengan memilih Perlindungan Data lalu tekan Ekstrak data saya terlebih dahulu.</string>
<string name="importCatimaMessage">Pilih ekspor Anda dari Catima untuk diimpor.\nBuatlah dari menu Impor/Ekspor aplikasi Catima lainnya dengan menekan Ekspor.</string>
<string name="importFidmeMessage">Pilih ekspor Anda dari FidMe untuk diimpor, lalu pilih jenis barcode secara manual setelahnya.\nBuatlah dari profil FidMe Anda dengan memilih Data Protection, lalu tekan Extract my data.</string>
<string name="importLoyaltyCardKeychain">Impor dari Loyalty Card Keychain</string>
<string name="importLoyaltyCardKeychainMessage">Pilih ekspor <i>LoyaltyCardKeychain.csv</i> Anda dari Loyalty Card Keychain untuk diimpor.
\nBuat dari menu Import/Export di Loyalty Card Keychain dengan menekan Export terlebih dahulu.</string>
<string name="importLoyaltyCardKeychainMessage">Pilih ekspor Anda dari Loyalty Card Keychain untuk diimpor.\nBuatlah dari menu Impor/Ekspor di Loyalty Card Keychain dengan menekan Ekspor.</string>
<string name="importVoucherVault">Impor dari Voucher Vault</string>
<string name="importVoucherVaultMessage">Pilih ekspor <i>vouchervault.json</i> Anda dari Voucher Vault untuk diimpor.
\nBuat dengan menekan Ekspor di Voucher Vault terlebih dahulu.</string>
<string name="importVoucherVaultMessage">Pilih ekspor Anda dari Voucher Vault untuk diimpor.\nBuatlah dengan menekan tombol Ekspor di Voucher Vault.</string>
<string name="unsupportedBarcodeType">Jenis barcode ini belum dapat ditampilkan. Ini mungkin didukung di versi aplikasi yang lebih baru.</string>
<string name="wrongValueForBarcodeType">Nilai tidak berlaku untuk jenis barcode yang dipilih</string>
<string name="wrongValueForBarcodeType">Nilai tersebut tidak valid untuk jenis barcode yang dipilih</string>
<string name="frontImageDescription">Gambar depan</string>
<string name="backImageDescription">Gambar belakang</string>
<string name="updateBarcodeQuestionTitle">Perbarui barcode?</string>
<string name="updateBarcodeQuestionText">Anda mengubah ID. Apakah Anda juga ingin memperbarui barcode untuk menggunakan nilai yang sama\?</string>
<string name="passwordRequired">Silahkan masukan kata sandi</string>
<string name="passwordRequired">Masukkan kata sandi</string>
<string name="exportPassword">Tetapkan kata sandi untuk melindungi ekspor anda (opsional)</string>
<string name="failedGeneratingShareURL">Tidak dapat membuat alamat berbagi. Mohon laporkan ini.</string>
<string name="failedGeneratingShareURL">Tidak dapat menghasilkan URL yang dapat dibagikan</string>
<string name="app_contributors">Pengembangan dibantu oleh: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="reverse">…dalam urutan terbalik</string>
<string name="version_history">Riwayat Versi</string>
<string name="version_history">Riwayat versi</string>
<string name="help_translate_this_app">Bantu terjemahkan aplikasi ini</string>
<string name="source_repository">Sumber Repositori</string>
<string name="source_repository">Repositori sumber</string>
<string name="on_github">di GitHub</string>
<string name="and_data_usage">dan penggunaan data</string>
<string name="on_google_play">di Google Play</string>
<string name="cardShortcut">Pintasan Kartu</string>
<string name="barcodeImageDescriptionWithType">Gambar <xliff:g>%s</xliff:g> barcode</string>
<string name="importExportHelp">Mencadangkan data anda akan memungkinkan memindahkannya ke perangkat lain.</string>
<string name="importExportHelp">Membackup data Anda memungkinkan Anda memindahkan data tersebut ke perangkat lain</string>
<plurals name="selectedCardCount">
<item quantity="other"><xliff:g>%d</xliff:g> kartu dipilih</item>
</plurals>
@@ -174,7 +161,7 @@
<plurals name="deleteCardsTitle">
<item quantity="other">Hapus <xliff:g>%d</xliff:g> kartu</item>
</plurals>
<string name="editGroup">Menyunting Grup: <xliff:g>%s</xliff:g></string>
<string name="editGroup">Kelompok pengeditan: <xliff:g>%s</xliff:g></string>
<string name="selectColor">Pilih warna</string>
<string name="noGiftCardsGroup">Buat beberapa kartu, kemudian masukkan mereka ke grup di sini</string>
<string name="group_name_already_in_use">Nama grup telah dipakai</string>
@@ -189,7 +176,7 @@
<string name="translate_platform">di Weblate</string>
<string name="welcome">Selamat datang di Catima</string>
<string name="failedToOpenUrl">Install browser web terlebih dahulu</string>
<string name="failedLaunchingPhotoPicker">Tidak dapat menemukan aplikasi galeri yang didukung</string>
<string name="failedLaunchingPhotoPicker">Tidak dapat menemukan pemilih gambar yang didukung</string>
<string name="previousCard">Sebelumnya</string>
<string name="nextCard">Berikutnya</string>
<plurals name="balancePoints">
@@ -225,8 +212,8 @@
<string name="switchToFrontImage">Ubah ke depan gambar</string>
<string name="switchToBackImage">Ubah ke belakang gambar</string>
<string name="switchToBarcode">Ubah ke kode batang</string>
<string name="openFrontImageInGalleryApp">Buka gambar didepan di galeri app</string>
<string name="openBackImageInGalleryApp">Buka gambar dibelakang di galeri app</string>
<string name="openFrontImageInGalleryApp">Buka gambar depan di aplikasi penampil gambar</string>
<string name="openBackImageInGalleryApp">Buka gambar di aplikasi penampil gambar</string>
<string name="setBarcodeHeight">Atur tinggi kode batang</string>
<string name="donate">Donasi</string>
<string name="show_validity">Tunjukkan validitas</string>
@@ -234,7 +221,7 @@
<string name="icon_header_click_text">Tekan lama untuk mengedit thumbnail</string>
<string name="show_name_below_image_thumbnail">Tampilkan nama di bawah thumbnail gambar</string>
<string name="show_note">Tampilkan catatan</string>
<string name="permissionReadCardsLabel">Baca Kartu Catima</string>
<string name="permissionReadCardsLabel">Bacalah kartu Catima</string>
<string name="permissionReadCardsDescription">baca kartu Anda dan semua detailnya, termasuk catatan dan gambar</string>
<string name="settings_allow_content_provider_read_title">Izinkan aplikasi lain mengakses data saya</string>
<string name="settings_allow_content_provider_read_summary">Aplikasi masih harus meminta izin untuk diberikan akses</string>
@@ -260,15 +247,15 @@
<string name="app_name">Catima</string>
<string name="add_manually_warning_title">Pemindaian sangat dianjurkan</string>
<string name="continue_">Lanjut</string>
<string name="failedLaunchingFileManager">Tidak dapat menemukan pengelola file yang didukung</string>
<string name="errorReadingFile">Tidak dapat membaca file</string>
<string name="failedLaunchingFileManager">Tidak dapat menemukan pengelola berkas yang didukung</string>
<string name="errorReadingFile">Tidak dapat membaca berkas</string>
<string name="addFromPdfFile">Pilih file PDF</string>
<string name="multipleBarcodesFoundPleaseChooseOne">Barcode mana yang ingin Anda gunakan?</string>
<string name="pageWithNumber">Halaman <xliff:g>%d</xliff:g></string>
<string name="spend">Dibelanjakan</string>
<string name="receive">Terima</string>
<string name="amountParsingFailed">Jumlah tidak valid</string>
<string name="add_manually_warning_message">Untuk beberapa toko, nilai barcode berbeda dengan nomor yang tertulis di kartu. Oleh karena itu, memasukkan barcode secara manual mungkin tidak selalu berhasil. Sangat disarankan untuk memindai barcode dengan kamera anda. Apakah anda masih ingin melanjutkan?</string>
<string name="add_manually_warning_message">Untuk beberapa kartu, nilai barcode berbeda dengan angka yang tertulis di kartu. Karena itu, memasukkan barcode secara manual mungkin tidak selalu berhasil. Disarankan untuk memindai barcode menggunakan kamera Anda. Apakah Anda tetap ingin melanjutkan?</string>
<string name="noCameraFoundGuideText">Perangkat Anda sepertinya tidak memiliki kamera. Jika iya, coba mulai ulang perangkat. Jika tidak, gunakan tombol Opsi lainnya di bawah untuk menambahkan barcode dengan cara lain.</string>
<string name="importCancelled">Impor dibatalkan</string>
<string name="exportCancelled">Ekspor dibatalkan</string>
@@ -289,10 +276,23 @@
<string name="settings_column_count_5">5</string>
<string name="addFromPkpass">Pilih file Buku Tabungan (.pkpass / .pkpasses)</string>
<string name="unsupportedFile">File ini tidak didukung</string>
<string name="generic_error_please_retry">Maaf, terjadi kesalahan, silakan coba lagi...</string>
<string name="sort_by_valid_from">Berlaku dari</string>
<string name="generic_error_please_retry">Terjadi kesalahan</string>
<string name="sort_by_valid_from">Berlaku mulai</string>
<string name="width">Lebar</string>
<string name="card_list_widget_name">Daftar kartu</string>
<string name="setBarcodeWidth">Atur Lebar Barcode</string>
<string name="setBarcodeWidth">Atur lebar barcode</string>
<string name="card_list_widget_empty">Setelah Anda menambahkan beberapa kartu loyalitas di Catima, kartu tersebut akan muncul di sini. Jika Anda memiliki kartu sebelumnya, pastikan tidak semuanya diarsipkan.</string>
<string name="cardWithNumber">Kartu <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Kartu <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Jangan memutar perangkat, karena hal ini akan membatalkan tindakan</string>
<string name="acra_catima_has_crashed">Kami mohon maaf, tetapi <xliff:g id="app_name">%s</xliff:g> telah mengalami crash. Tolong bantu kami memperbaiki masalah ini dengan mengirimkan laporan kesalahan kepada kami.</string>
<string name="acra_explain_crash">Jika memungkinkan, tolong tambahkan detail lebih lanjut tentang apa yang Anda lakukan di sini:</string>
<string name="acra_crash_email_subject">Laporan crash <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Minta untuk mengirimkan laporan crash</string>
<string name="pref_enable_acra_summary">Ketika diaktifkan, Anda akan diminta untuk melaporkan crash saat terjadi. Laporan crash tidak pernah dikirim secara otomatis.</string>
<string name="copy_value">Salin nilai</string>
<string name="copied_to_clipboard">Disalin ke papan klip</string>
<string name="nothing_to_copy">Tidak ditemukan nilai</string>
<string name="barcodeEncoding">Pengkodean barcode</string>
<string name="back">Kembali</string>
</resources>
+53 -33
View File
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_add">Bæta</string>
<string name="noBarcode">Nei strikamerkið</string>
<string name="noBarcode">Ekkert strikamerki</string>
<string name="action_search">Leita að</string>
<string name="delete">Eyða</string>
<string name="noGiftCards">Smelltu á + plús takka til að bæta korti, eða að flytja inn frá ⋮ Valmynd.</string>
<string name="noGiftCards">Smelltu á + plús takka til að bæta korti, eða að flytja inn frá ⋮ Valmynd</string>
<string name="note">Athugið</string>
<string name="barcodeType">Strikamerkið tegund</string>
<string name="cancel">Hætta</string>
@@ -12,61 +12,61 @@
<string name="storeName">Nafnið</string>
<string name="star">Bæta við eftirlæti</string>
<string name="unstar">Fjarlægja frá eftirlæti</string>
<string name="save">Sparaðu</string>
<string name="save">Vista</string>
<string name="edit">Breyta</string>
<string name="confirm">Staðfesta</string>
<string name="ok">OK</string>
<string name="sendLabel">Sendu…</string>
<string name="deleteConfirmation">Eyða þetta kort til frambúðar\?</string>
<string name="deleteConfirmation">Eyða þessu korti til frambúðar?</string>
<string name="share">Deila</string>
<string name="editCardTitle">Breyta Kort</string>
<string name="addCardTitle">Bæta Kort</string>
<string name="scanCardBarcode">Skanna Kort Strikamerkið</string>
<string name="noCardsMessage">Bæta kort fyrstu</string>
<string name="exportFailedTitle">Flytja mistókst</string>
<string name="exportSuccessfulTitle">Flutt</string>
<string name="noCardExistsError">Gæti ekki fundið kort</string>
<string name="failedParsingImportUriError">Get ekki lesið inn URI</string>
<string name="exportName">Flytja</string>
<string name="importExportHelp">Stuðningur upp spil gerir þér kleift að færa þá til annar tæki.</string>
<string name="importSuccessfulTitle">Flutt</string>
<string name="editCardTitle">Breyta Korti</string>
<string name="addCardTitle">Bæta við korti</string>
<string name="scanCardBarcode">Skanna Strikamerkið</string>
<string name="noCardsMessage">Bæta fyrst við korti</string>
<string name="exportFailedTitle">Útflutningur mistókst</string>
<string name="exportSuccessfulTitle">Flutt út</string>
<string name="noCardExistsError">Gat ekki fundið þetta kort</string>
<string name="failedParsingImportUriError">Get ekki flutt inn URI</string>
<string name="exportName">Flytja út</string>
<string name="importExportHelp">Afritun gagna gerir þér kleift að færa þá annað tæki</string>
<string name="importSuccessfulTitle">Flutt inn</string>
<string name="importFailedTitle">Innflutningur mistókst</string>
<string name="exportOptionExplanation">Gögnum verður skrifað á stað af eigin vali.</string>
<string name="exportOptionExplanation">Gögnin verðs skrifuð á stað sem þú velur</string>
<string name="importOptionFilesystemTitle">Innflutningur frá möppuna</string>
<string name="importOptionFilesystemExplanation">Velja ákveðna skrá frá möppuna.</string>
<string name="importOptionFilesystemExplanation">Velja ákveðna skrá úr skráakerfinu</string>
<string name="starImage">Uppáhalds stjörnu</string>
<string name="never">Aldrei</string>
<string name="currency">Mynt</string>
<string name="moveBarcodeToTopOfScreen">Færa strikamerkið að efst á skjánum</string>
<string name="errorReadingImage">Gat ekki lesið ímynd</string>
<string name="errorReadingImage">Gat ekki lesið mynd</string>
<string name="about">Um</string>
<string name="settings">Stillingar</string>
<string name="settings_keep_screen_on">Halda á skjánum</string>
<string name="settings_disable_lockscreen_while_viewing_card">Að koma í veg fyrir að læsa skjánum</string>
<string name="settings_disable_lockscreen_while_viewing_card">Koma í veg fyrir skjálæsingu</string>
<string name="editBarcode">Breyta strikamerkið</string>
<string name="settings_light_theme">Ljós</string>
<string name="importOptionFilesystemButton">Frá möppuna</string>
<string name="chooseImportType">Flytja gögn úr\?</string>
<string name="chooseImportType">Flytja gögn úr</string>
<string name="balance">Jafnvægi</string>
<string name="points">Stig</string>
<string name="selectBarcodeTitle">Veldu Strikamerkið</string>
<string name="thumbnailDescription">Þumalinn fyrir kort</string>
<string name="selectBarcodeTitle">Velja strikamerki</string>
<string name="thumbnailDescription">Táknmynd</string>
<string name="settings_theme">Þema</string>
<string name="app_license">Copylefted frítt hugbúnaður, leyfi GPLv3+</string>
<string name="noBarcodeFound">Nei strikamerkið var komist</string>
<string name="settings_system_theme">Kerfi</string>
<string name="settings_dark_theme">Dökk</string>
<string name="settings_display_barcode_max_brightness">Bjartari strikamerkið skoða</string>
<string name="settings_display_barcode_max_brightness">Bjartari skjá</string>
<string name="enter_group_name">Inn nafn hópur</string>
<string name="intent_import_card_from_url_share_text">Ég vil deila kort með þér</string>
<string name="importSuccessful">Kortið gögn flutt</string>
<string name="importSuccessful">Gögn flutt inn</string>
<string name="groups">Tekur</string>
<string name="exportSuccessful">Kortagögn flutt út</string>
<string name="failedOpeningFileManager">Setja skráasafn fyrst.</string>
<string name="exportSuccessful">Gögn flutt út</string>
<string name="failedOpeningFileManager">Gat ekki opnað skráakerfistjóra</string>
<string name="moveUp">Fara upp</string>
<string name="leaveWithoutSaveTitle">Brottför</string>
<string name="leaveWithoutSaveConfirmation">Fara án þess að bjarga\?</string>
<string name="addFromImage">Veldu mynd frá gallerí</string>
<string name="addFromImage">Veldu mynd úr gallerí</string>
<string name="card">Kort</string>
<string name="expiryDate">Fyrningu</string>
<string name="moveDown">Fara neðar</string>
@@ -75,12 +75,32 @@
<item quantity="one"><xliff:g>%d</xliff:g> valið</item>
<item quantity="other"><xliff:g>%d</xliff:g> valin</item>
</plurals>
<string name="noGiftCardsGroup">Búðu til nokkur kort og settu þau síðan í hópinn hér.</string>
<string name="settings_brown_theme">Brún</string>
<string name="settings_green_theme">Grænn</string>
<string name="noGiftCardsGroup">Búðu til nokkur kort og settu þau síðan í hópinn hér</string>
<string name="sort">flokka</string>
<string name="sort_by">flokka Eftir</string>
<string name="nextCard">Næsta</string>
<string name="settings_blue_theme">Blár</string>
<string name="settings_sky_blue_theme">Himinblár</string>
</resources>
<plurals name="deleteCardsConfirmation">
<item quantity="one">Eyða þessu <xliff:g>%d</xliff:g> korti varanlega?</item>
<item quantity="other">Eyða þessum <xliff:g>%d</xliff:g> kortum varanlega?</item>
</plurals>
<string name="barcodeImageDescriptionWithType">Mynd <xliff:g>%s</xliff:g> strikamerki</string>
<string name="importExport">Flytja inn/út</string>
<string name="importFailed">Gat ekki flutt inn</string>
<string name="app_name">Catima</string>
<plurals name="deleteCardsTitle">
<item quantity="one">Eyða <xliff:g>%d</xliff:g> korti</item>
<item quantity="other">Eyða <xliff:g>%d</xliff:g> kortum</item>
</plurals>
<plurals name="groupCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> kort</item>
<item quantity="other"><xliff:g>%d</xliff:g> kort</item>
</plurals>
<plurals name="balancePoints">
<item quantity="one"><xliff:g>%s</xliff:g> punktur</item>
<item quantity="other"><xliff:g>%s</xliff:g> punktar</item>
</plurals>
<string name="previousCard">Fyrra</string>
<string name="failedToOpenUrl">Settu inn vef-vafra fyrst</string>
<string name="welcome">Velkomin að Catima</string>
<string name="importCards">Flytja inn kort</string>
</resources>

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