Compare commits

...

158 Commits

Author SHA1 Message Date
TheLastProject
c000cd4e7f Update feature graphic 2025-10-30 17:31:13 +00:00
Sylvia van Os
4a43487856 Tweak Tamil feature graphic sizing more 2025-10-30 18:28:35 +01:00
Sylvia van Os
05a06eea27 Merge pull request #2800 from CatimaLoyalty/gradlew-update-9.2.0
Update Gradle Wrapper from 9.1.0 to 9.2.0
2025-10-30 17:13:22 +01:00
gradle-update-robot
663d7f3354 Update Gradle Wrapper from 9.1.0 to 9.2.0
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2025-10-30 01:00:21 +00:00
Sylvia van Os
dbe5b88b52 Merge pull request #2798 from CatimaLoyalty/create-pull-request/patch-1761761200
Update feature graphic
2025-10-29 19:29:46 +01:00
Sylvia van Os
c839fffadb Merge pull request #2797 from CatimaLoyalty/fix/tamil_feature_graphic
Lower Tamil font size
2025-10-29 19:28:23 +01:00
TheLastProject
8edfe53b45 Update feature graphic 2025-10-29 18:06:39 +00:00
Sylvia van Os
0153fc54f1 Lower Tamil font size
This should make the feature graphic fit properly
2025-10-29 19:02:05 +01:00
Sylvia van Os
f6b0af153f Merge pull request #2779 from adammazechen/refactor/scanactivity_java_to_kt
Refactor ScanActivity.java to kotlin
2025-10-29 18:42:42 +01:00
Sylvia van Os
cfefce1baf Minor cleanups 2025-10-29 18:27:15 +01:00
Zechen Ma
ff1683d5b4 refactor: convert ScanActivity.java to Kotlin.
bug: fix indentation and several bugs, convert more code blocks into apply blocks.
2025-10-29 08:35:13 +11:00
Sylvia van Os
e181a866f7 Merge pull request #2794 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2025-10-28 20:31:36 +01:00
Hosted Weblate
1571d5766c Update translation files
Updated by "Remove blank strings" hook in Weblate.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Convert SettingsActivity to Kotlin

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

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

* Apply Android Studio suggested fixup

---------

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

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

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

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

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

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

* Fix build

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

* Use non-native release

* Apply autogenerated ProGuard missing rules

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/hr/
2025-10-01 20:02:34 +02:00
311 changed files with 3148 additions and 2094 deletions

View File

@@ -35,7 +35,7 @@ jobs:
- 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
- uses: gradle/actions/wrapper-validation@v5
- name: set up OpenJDK 21
run: |
sudo apt-get update
@@ -66,7 +66,7 @@ jobs:
script: ./gradlew connected${{ matrix.flavor }}DebugAndroidTest
- name: Archive test results
if: always()
uses: actions/upload-artifact@v4.6.2
uses: actions/upload-artifact@v5.0.0
with:
name: test-results-flavor${{ matrix.flavor }}
path: app/build/reports

View File

@@ -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="120"/' featureGraphic.svg ;;
zh-CN) sed -i "s/Lexend/Noto Sans CJK SC/" featureGraphic.svg ;;
zh-TW) sed -i -e "s/Lobster/Noto Sans CJK TC/" -e "s/Lexend/Noto Sans CJK TC/" featureGraphic.svg ;;
*) ;;

View File

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

View File

@@ -1,8 +1,8 @@
import com.android.build.gradle.internal.tasks.factory.dependsOn
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
alias(libs.plugins.com.android.application)
alias(libs.plugins.org.jetbrains.kotlin.android)
}
kotlin {
@@ -113,43 +113,38 @@ android {
dependencies {
// AndroidX
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
implementation("androidx.core:core-ktx:1.17.0")
implementation("androidx.core:core-remoteviews:1.1.0")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.exifinterface:exifinterface:1.4.1")
implementation("androidx.palette:palette:1.0.0")
implementation("androidx.preference:preference:1.2.1")
implementation("com.google.android.material:material:1.13.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
implementation(libs.androidx.appcompat.appcompat)
implementation(libs.androidx.constraintlayout.constraintlayout)
implementation(libs.androidx.core.core.ktx)
implementation(libs.androidx.core.core.remoteviews)
implementation(libs.androidx.core.core.splashscreen)
implementation(libs.androidx.exifinterface.exifinterface)
implementation(libs.androidx.palette.palette)
implementation(libs.androidx.preference.preference)
implementation(libs.com.google.android.material.material)
coreLibraryDesugaring(libs.com.android.tools.desugar.jdk.libs)
// Third-party
implementation("com.journeyapps:zxing-android-embedded:4.3.0@aar")
implementation("com.github.yalantis:ucrop:2.2.10")
implementation("com.google.zxing:core:3.5.3")
implementation("org.apache.commons:commons-csv:1.9.0")
implementation("com.jaredrummler:colorpicker:1.1.0")
implementation("net.lingala.zip4j:zip4j:2.11.5")
implementation(libs.com.journeyapps.zxing.android.embedded)
implementation(libs.com.github.yalantis.ucrop)
implementation(libs.com.google.zxing.core)
implementation(libs.org.apache.commons.commons.csv)
implementation(libs.com.jaredrummler.colorpicker)
implementation(libs.net.lingala.zip4j.zip4j)
// Crash reporting
val acraVersion = "5.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.uiautomator.uiautomator)
androidTestImplementation(libs.androidx.test.espresso.espresso.core)
}
tasks.register("copyRawResFiles", Copy::class) {

View File

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

View File

@@ -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();
}
}

View File

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

View File

@@ -719,7 +719,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
int colorOnSurface = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSurface, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
int colorBackground = MaterialColors.getColor(this, android.R.attr.colorBackground, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
mCropperOptions.setToolbarColor(colorSurface);
mCropperOptions.setStatusBarColor(colorSurface);
mCropperOptions.setToolbarWidgetColor(colorOnSurface);
mCropperOptions.setRootViewBackgroundColor(colorBackground);
// set tool tip to be the darker of primary color

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,15 +3,15 @@
<string name="scanCardBarcode">Scan stregkode</string>
<string name="addCardTitle">Tilføj kort</string>
<string name="editCardTitle">Rediger kort</string>
<string name="sendLabel">Afsend…</string>
<string name="share">Aktie</string>
<string name="sendLabel">Send…</string>
<string name="share">Del</string>
<string name="ok">OK</string>
<string name="deleteConfirmation">Slete dette kort permanent\?</string>
<string name="deleteConfirmation">Slet dette kort permanent?</string>
<plurals name="deleteCardsTitle">
<item quantity="one">Streichen <xliff:g>%d</xliff:g> kort</item>
<item quantity="other">Streichen <xliff:g>%d</xliff:g> korts</item>
<item quantity="one">Slet <xliff:g>%d</xliff:g> kort</item>
<item quantity="other">Slet <xliff:g>%d</xliff:g> korts</item>
</plurals>
<string name="deleteTitle">Karte streichen</string>
<string name="deleteTitle">Slet kort</string>
<string name="confirm">Bekræft</string>
<string name="delete">Slet</string>
<string name="edit">Rediger</string>
@@ -34,7 +34,7 @@
<string name="action_search">Søg</string>
<string name="importExport">Import/eksport</string>
<string name="exportName">Eksport</string>
<string name="importExportHelp">Sikkerhedskopiering af dit data, giver dig mulighed for at flytte dem til en anden enhed.</string>
<string name="importExportHelp">Sikkerhedskopiering af dine data, giver dig mulighed for at flytte dem til en anden enhed.</string>
<string name="importSuccessfulTitle">Importeret</string>
<string name="importFailedTitle">Import mislykkedes</string>
<string name="importFailed">Kunne ikke udføre importering</string>
@@ -54,12 +54,12 @@
\ncopyright © 2016-2020 Branden Archer.</string>
<string name="about">Om</string>
<string name="noCardsMessage">Tilføj først et kort</string>
<string name="cardShortcut">Kort genvej</string>
<string name="cardShortcut">Genvej til kort</string>
<string name="importOptionFilesystemButton">Fra filsystemet</string>
<string name="importOptionFilesystemExplanation">Vælg en bestemt fil fra filsystemet.</string>
<string name="importOptionFilesystemTitle">Import fra filsystem</string>
<string name="exportOptionExplanation">Dataene skrives til en placering efter eget valg.</string>
<string name="failedParsingImportUriError">Kunne ikke analysere import-URI\'en</string>
<string name="failedParsingImportUriError">Kunne ikke importere URI\'en</string>
<string name="noCardExistsError">Kunne ikke finde det kort</string>
<string name="deleteConfirmationGroup">Slet gruppe\?</string>
<string name="all">Alle</string>
@@ -79,16 +79,16 @@
<string name="moveDown">Bevæger sig nedad</string>
<string name="leaveWithoutSaveTitle">Afslut</string>
<string name="addManually">Indtast stregkoden manuelt</string>
<string name="noGiftCardsGroup">Opret kort og tildel dem gupper her.</string>
<string name="noGiftCardsGroup">Opret kort og tildel dem grupper her.</string>
<plurals name="deleteCardsConfirmation">
<item quantity="one">Slet dette <xliff:g>%d</xliff:g> kort permanent\?</item>
<item quantity="other">Slet disse <xliff:g>%d</xliff:g> kort permanent\?</item>
</plurals>
<string name="app_name">Catima</string>
<string name="cameraPermissionRequired">Behov for kamera adgang krævet for denne funktion…</string>
<string name="storageReadPermissionRequired">Behov for lager adgang krævet for denne funktion…</string>
<string name="cameraPermissionRequired">Behov for kamera adgang er krævet for denne funktion…</string>
<string name="storageReadPermissionRequired">Behov for lager adgang er krævet for denne funktion…</string>
<string name="permissionReadCardsLabel">Læs Catima Kort</string>
<string name="permissionReadCardsDescription">læs dine Catima kort og alle deres detaljer, også noter og billeder</string>
<string name="permissionReadCardsDescription">læs dit Catima kort og alle kortets detaljer, også noter og billeder</string>
<string name="cameraPermissionDeniedTitle">Kunne ikke få adgang til kamera</string>
<string name="noCameraPermissionDirectToSystemSetting">For at scanne stregkoder, har Catima behov for at få adgang til dit kamera. Klik her for at ændre dine tilladelser i indstillinger.</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Copyright © 2019<xliff:g>%d</xliff:g> Sylvia van Os og hjælpere</string>
@@ -144,4 +144,4 @@
<item quantity="one"><xliff:g>%s</xliff:g> point</item>
<item quantity="other"><xliff:g>%s</xliff:g> point</item>
</plurals>
</resources>
</resources>

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>

View File

@@ -305,4 +305,10 @@
<string name="card_list_widget_empty">Después de añadir algunas tarjetas de fidelidad en Catima, aparecerán aquí. Si tienes tarjetas, asegúrate de que no estén archivadas.</string>
<string name="cardWithNumber">Tarjeta <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Tarjeta <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Por favor, no rote el dispositivo, ya que esto cancelará la acción</string>
<string name="acra_catima_has_crashed">Lo sentimos, pero <xliff:g id="app_name">%s</xliff:g> ha fallado. Por favor, ayúdenos a resolver esta incidencia enviándonos un reporte del error.</string>
<string name="acra_explain_crash">Si es posible, por favor añada más detalles sobre lo que estaba haciendo aquí:</string>
<string name="acra_crash_email_subject">Reporte del fallo <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Solicitar envío de reportes de fallos</string>
<string name="pref_enable_acra_summary">Cuando está activado, se le pedirá que informe sobre un fallo cuando ocurra. Los informes de fallo nunca se envían automáticamente.</string>
</resources>

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_search">Cerca</string>
<string name="action_add">Aggiungi</string>
<string name="noGiftCards">Premi il pulsante + per aggiungere una carta oppure importala dal menù</string>
<string name="noGiftCards">Premi il pulsante + per aggiungere una carta oppure importala dal menù</string>
<string name="noMatchingGiftCards">Nessun risultato. Prova a cambiare la tua ricerca.</string>
<string name="storeName">Nome</string>
<string name="note">Note</string>
@@ -183,7 +183,7 @@
<string name="report_error">Segnala un errore</string>
<string name="editGroup">Modifica del gruppo: <xliff:g>%s</xliff:g></string>
<string name="group_name_is_empty">Il nome del gruppo non deve essere vuoto</string>
<string name="noGiftCardsGroup">Crea alcune carte e poi assegnale al gruppo qui.</string>
<string name="noGiftCardsGroup">Crea alcune carte e poi assegnale al gruppo qui</string>
<string name="group_edit">Modifica il gruppo</string>
<string name="group_name_already_in_use">Il nome del gruppo è già in uso</string>
<string name="group_updated">Gruppo aggiornato</string>

View File

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

View File

@@ -2,10 +2,10 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_search">Zoeken</string>
<string name="action_add">Toevoegen</string>
<string name="noGiftCards">Druk op de plusknop (+) om een kaart toe te voegen of importeer kaarten via het ⋮-menu</string>
<string name="noMatchingGiftCards">Geen zoekresultaten - probeer een andere zoekopdracht.</string>
<string name="noGiftCards">Druk op de + plusknop om een kaart toe te voegen of importeer kaarten via het ⋮-menu</string>
<string name="noMatchingGiftCards">Geen zoekresultaten probeer een andere zoekopdracht.</string>
<string name="storeName">Naam</string>
<string name="note">Aantekening</string>
<string name="note">Notitie</string>
<string name="cardId">Kaartnummer</string>
<string name="barcodeType">Soort barcode</string>
<string name="cancel">Annuleren</string>

View File

@@ -34,7 +34,7 @@
<string name="importing">Importowanie…</string>
<string name="exporting">Eksportowanie…</string>
<string name="importOptionFilesystemTitle">Importuj z systemu plików</string>
<string name="importOptionFilesystemExplanation">Wybierz określony plik z systemu plików.</string>
<string name="importOptionFilesystemExplanation">Wybierz określony plik z systemu plików</string>
<string name="importOptionFilesystemButton">Z systemu plików</string>
<string name="about">O aplikacji</string>
<string name="app_license">Wolne oprogramowanie typu copyleft, na licencji GPLv3+</string>
@@ -174,8 +174,8 @@
<string name="sort_by">Sortuj według</string>
<string name="credits">Podziękowania</string>
<string name="help_translate_this_app">Pomóż przetłumaczyć tę aplikację</string>
<string name="source_repository">Repozytorium Źródłowe</string>
<string name="report_error">Zgłoś Błąd</string>
<string name="source_repository">Repozytorium źródłowe</string>
<string name="report_error">Zgłoś błąd</string>
<string name="setIcon">Ustaw miniaturę</string>
<string name="on_github">na GitHub\'ie</string>
<string name="selectColor">Wybierz kolor</string>
@@ -243,10 +243,10 @@
<string name="switchToFrontImage">Przełącz na obraz z przodu</string>
<string name="switchToBackImage">Przełącz na obraz z tyłu</string>
<string name="switchToBarcode">Przełącz na kod kreskowy</string>
<string name="openFrontImageInGalleryApp">Otwórz obraz z przodu w aplikacji galeria</string>
<string name="openFrontImageInGalleryApp">Otwórz obraz z przodu w galerii</string>
<string name="setBarcodeHeight">Ustaw wysokość kodu kreskowego</string>
<string name="donate">Darowizna</string>
<string name="openBackImageInGalleryApp">Otwórz obraz z powrotem w aplikacji galerii</string>
<string name="openBackImageInGalleryApp">Otwórz obraz z powrotem w galerii</string>
<string name="icon_header_click_text">Przytrzymaj, aby edytować miniaturę</string>
<string name="show_name_below_image_thumbnail">Pokaż nazwę pod miniaturką zdjęcia</string>
<string name="show_balance">Pokaż balans</string>
@@ -315,4 +315,9 @@
<string name="card_list_widget_name">Lista kart</string>
<string name="cardWithNumber">Karta <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Karta <xliff:g>%d</xliff:g> (%s)</string>
<string name="pleaseDoNotRotateTheDevice">Proszę nie obracać urządzenia, gdyż anuluje to obecne zadanie</string>
<string name="acra_explain_crash">Jeśli możliwe, dodaj więcej szczegółów na temat co robiłeś/aś tutaj:</string>
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> raport błędu</string>
<string name="pref_enable_acra">Zapytaj o wysłanie raportu błędu</string>
<string name="pref_enable_acra_summary">Kiedy zaznaczone, będziesz proszony/a o zgłoszenie raportu błędu, gdyby zaistniał. Raporty błędu nigdy nie są wysyłane automatycznie.</string>
</resources>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_add">Adicionar</string>
<string name="importOptionFilesystemExplanation">Escolha um ficheiro específico a partir do sistema de ficheiros.</string>
<string name="importOptionFilesystemExplanation">Escolha um ficheiro específico do sistema de ficheiros</string>
<string name="action_search">Pesquisa</string>
<string name="star">Adicionar aos favoritos</string>
<string name="noMatchingGiftCards">Sem resultados. Tente alterar a sua pesquisa.</string>
@@ -11,7 +11,7 @@
<string name="cancel">Cancelar</string>
<string name="save">Guardar</string>
<string name="edit">Editar</string>
<string name="noGiftCards">Clique no botão + para adicionar um cartão ou importe-o no menu ⋮.</string>
<string name="noGiftCards">Clique no botão + para adicionar um cartão ou importe-o no menu ⋮</string>
<string name="noBarcode">Sem código de barras</string>
<string name="unstar">Retirar dos favoritos</string>
<string name="importOptionFilesystemButton">Do sistema de ficheiros</string>
@@ -36,10 +36,10 @@
<string name="noCardsMessage">Adicione um cartão primeiro</string>
<string name="noCardExistsError">Não foi possível encontrar esse cartão</string>
<string name="failedParsingImportUriError">Não foi possível analisar o URI de importação</string>
<string name="importExport">Importar / Exportar</string>
<string name="importExport">Importar/exportar</string>
<string name="exportName">Exportar</string>
<string name="importSuccessful">Dados importados</string>
<string name="noGroups">Clique no botão + para adicionar grupos para categorização.</string>
<string name="noGroups">Clique no botão + para adicionar grupos para a categorização</string>
<string name="noGroupCards">Este grupo está vazio</string>
<string name="intent_import_card_from_url_share_text">Quero partilhar um cartão</string>
<string name="settings_display_barcode_max_brightness">Iluminar o ecrã</string>
@@ -58,11 +58,11 @@
<string name="selectBarcodeTitle">Selecionar código de barras</string>
<string name="thumbnailDescription">Miniatura</string>
<string name="starImage">Favorito</string>
<string name="failedOpeningFileManager">Instalar primeiro um gestor de ficheiros.</string>
<string name="failedOpeningFileManager">Falha ao abrir o gestor de ficheiros</string>
<string name="moveUp">Subir</string>
<string name="moveDown">Descer</string>
<string name="leaveWithoutSaveTitle">Sair</string>
<string name="importExportHelp">A cópia de segurança dos seus dados permite-lhe movê-los para outro dispositivo.</string>
<string name="importExportHelp">A cópia de segurança dos seus dados permite-lhe movê-los para outro dispositivo</string>
<string name="importSuccessfulTitle">Importado</string>
<string name="importFailedTitle">A importação falhou</string>
<string name="importFailed">Não foi possível importar</string>
@@ -76,18 +76,17 @@
<string name="chooseImportType">Importar dados de</string>
<string name="card">Cartão</string>
<string name="expiryStateSentence">Expiram: <xliff:g>%s</xliff:g></string>
<string name="app_resources">Recursos livres de terceiros: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Bibliotecas livres de terceiros: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Recursos de terceiros: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Bibliotecas de terceiros: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="takePhoto">Tirar uma fotografia</string>
<string name="yes">Sim</string>
<string name="exportPassword">Defina uma palavra-passe para proteger a exportação (opcional)</string>
<string name="exportPasswordHint">Digite a palavra-passe</string>
<string name="setBarcodeId">Definir o valor do código de barras</string>
<string name="sameAsCardId">Igual ao identificador</string>
<string name="importFidmeMessage">Selecione a exportação <i>fidme-export-request-xxxxxx.zip</i> do FidMe para importar e depois selecione os tipos de código de barras manualmente.
\nPrimeiro crie a exportação no seu perfil do FidMe escolhendo a opção \"Proteção de dados\" e em seguida pressionando \"Extrair os meus dados\".</string>
<string name="importFidmeMessage">Selecione o seu ficheiro exportado do FidMe a importae e depois selecione manualmente os tipos de código de barras. \nCrie-o no seu perfil do FidMe a escolher a opção \"Proteção de dados\" e depois pressionar \"Extrair os meus dados\".</string>
<string name="barcodeId">Valor do código de barras</string>
<string name="wrongValueForBarcodeType">O valor não é válido para o tipo de código de barras selecionado</string>
<string name="wrongValueForBarcodeType">O valor é inválido para o tipo de código de barras selecionado</string>
<string name="intent_import_card_from_url_share_multiple_text">Quero partilhar alguns cartões</string>
<string name="removeImage">Remover imagem</string>
<string name="backImageDescription">Imagem de trás</string>
@@ -130,19 +129,16 @@
<string name="privacy_policy">Política de privacidade</string>
<string name="accept">Aceitar</string>
<string name="importCatima">Importar do Catima</string>
<string name="importCatimaMessage">Selecione a exportação <i>catima.zip</i> do Catima a importar.
\nPrimeiro crie a exportação no menu \"Importar / exportar\" de outra aplicação Catima pressionando \"Exportar\" nesse menu.</string>
<string name="importCatimaMessage">Selecione a exportação <i>catima.zip</i> do Catima a importar. \nPrimeiro crie a exportação no menu \"Importar / exportar\" de outra aplicação Catima pressionando \"Exportar\" nesse menu.</string>
<string name="importFidme">Importar do FidMe</string>
<string name="importLoyaltyCardKeychain">Importar do Loyalty Card Keychain</string>
<string name="importLoyaltyCardKeychainMessage">Selecione a exportação <i>LoyaltyCardKeychain.csv</i> do Loyalty Card Keychain para importar.
\nPrimeiro crie a exportação no menu \"Importar / exportar\" no Loyalty Card Keychain pressionando \"Exportar\".</string>
<string name="importLoyaltyCardKeychainMessage">Selecione a exportação do Loyalty Card Keychain a importar. \nCrie a exportação no menu \"Importar/exportar\" no Loyalty Card Keychain a pressionar \"Exportar\".</string>
<string name="importVoucherVault">Importar do Voucher Vault</string>
<string name="importVoucherVaultMessage">Selecione a exportação <i>vouchervault.json</i> do Voucher Vault para importar.
\nCrie-a primeiro pressionando a opção \"Exportar\" no Voucher Vault.</string>
<string name="importVoucherVaultMessage">Selecione a exportação do Voucher Vault a importar. \nCrie-a a pressionar a opção Exportar no Voucher Vault.</string>
<string name="unsupportedBarcodeType">Este tipo de código de barras ainda não pode ser mostrado. Pode vir a ser suportado numa versão posterior da aplicação.</string>
<string name="setFrontImage">Definir imagem frontal</string>
<string name="setBackImage">Definir imagem de trás</string>
<string name="failedGeneratingShareURL">Não foi possível gerar um URL partilhável. Por favor reporte isto aos programadores.</string>
<string name="failedGeneratingShareURL">Não foi possível gerar um URL partilhável</string>
<string name="turn_flashlight_on">Ligar lanterna</string>
<string name="turn_flashlight_off">Desligar lanterna</string>
<string name="settings_locale">Idioma</string>
@@ -156,7 +152,7 @@
<string name="app_contributors">Tornado possível por: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="sort">Ordenar</string>
<string name="sort_by_name">Nome</string>
<string name="sort_by_most_recently_used">Mais usados recentemente</string>
<string name="sort_by_most_recently_used">Mais recentemente utilizado</string>
<string name="sort_by_expiry">Validade</string>
<string name="reverse">…na ordem inversa</string>
<string name="sort_by">Ordenar por</string>
@@ -169,7 +165,7 @@
<string name="and_data_usage">e utilização de dados</string>
<string name="rate_this_app">Avalie esta aplicação</string>
<string name="on_google_play">no Google Play</string>
<string name="exportOptionExplanation">Os dados serão guardados num local à sua escolha.</string>
<string name="exportOptionExplanation">Os dados serão guardados num local à sua escolha</string>
<plurals name="deleteCardsTitle">
<item quantity="one">Eliminar <xliff:g>%d</xliff:g> cartão</item>
<item quantity="many">Eliminar <xliff:g>%d</xliff:g> cartões</item>
@@ -187,7 +183,7 @@
<string name="group_name_is_empty">O nome do grupo não pode ser vazio</string>
<string name="group_updated">Grupo atualizado</string>
<string name="editGroup">A editar grupo: <xliff:g>%s</xliff:g></string>
<string name="noGiftCardsGroup">Crie alguns cartões e atribua-os depois ao grupo aqui.</string>
<string name="noGiftCardsGroup">Crie alguns cartões e atribua-os depois ao grupo aqui</string>
<string name="selectColor">Selecionar cor</string>
<string name="setIcon">Definir miniatura</string>
<string name="shortcutSelectCard">Selecione um cartão</string>
@@ -212,7 +208,7 @@
<item quantity="many"><xliff:g>%1$d</xliff:g> cartões (<xliff:g id="archivedCount">%2$d</xliff:g> arquivados)</item>
<item quantity="other"><xliff:g>%1$d</xliff:g> cartões (<xliff:g id="archivedCount">%2$d</xliff:g> arquivados)</item>
</plurals>
<string name="failedLaunchingPhotoPicker">Não foi encontrada nenhuma aplicação de galeria de imagens</string>
<string name="failedLaunchingPhotoPicker">Não foi possível encontrar um seletor de imagens compatível</string>
<string name="nextCard">Próximo</string>
<string name="previousCard">Anterior</string>
<string name="failedToOpenUrl">Instale primeiro um navegador de Internet</string>
@@ -237,13 +233,13 @@
<string name="height">Altura</string>
<string name="switchToBackImage">Mudar para a imagem de trás</string>
<string name="switchToBarcode">Mudar para o código de barras</string>
<string name="openFrontImageInGalleryApp">Abrir a imagem frontal na aplicação da galeria</string>
<string name="openBackImageInGalleryApp">Abrir a imagem traseira na aplicação da galeria</string>
<string name="openFrontImageInGalleryApp">Abrir imagem frontal na app visualizadora de imagens</string>
<string name="openBackImageInGalleryApp">Abrir imagem traseira na app visualizadora de imagens</string>
<string name="setBarcodeHeight">Definir altura do código de barras</string>
<string name="donate">Doar</string>
<string name="show_validity">Mostrar validade</string>
<string name="show_balance">Mostrar saldo</string>
<string name="permissionReadCardsLabel">Ler Cartões Catima</string>
<string name="permissionReadCardsLabel">Ler cartões Catima</string>
<string name="permissionReadCardsDescription">leia seus cartões do Catima e todos os seus detalhes, incluindo notas e imagens</string>
<string name="show_note">Mostrar nota</string>
<string name="show_name_below_image_thumbnail">Mostrar nome abaixo da miniatura do ícone</string>
@@ -272,7 +268,7 @@
<string name="app_name">Catima</string>
<string name="continue_">Continuar</string>
<string name="add_manually_warning_title">Recomenda-se a digitalização</string>
<string name="add_manually_warning_message">Em algumas lojas, o valor do código de barras é diferente do número escrito no cartão. Por este motivo, a introdução manual de um código de barras pode nem sempre funcionar. Recomenda-se vivamente que, em vez disso, digitalize o código de barras com a sua câmara. Ainda quer continuar?</string>
<string name="add_manually_warning_message">Em alguns cartões, o valor do código de barras é diferente do número escrito no cartão. Por este motivo, a introdução manual de um código de barras pode nem sempre funcionar. Recomenda-se que, em vez disso, digitalizar o código de barras com a sua câmara. Ainda quer continuar?</string>
<string name="spend">Gastar</string>
<string name="receive">Receber</string>
<string name="amountParsingFailed">Montante inválido</string>
@@ -280,7 +276,7 @@
<string name="errorReadingFile">Não foi possível ler o ficheiro</string>
<string name="multipleBarcodesFoundPleaseChooseOne">Qual dos códigos de barras encontrados pretende utilizar?</string>
<string name="pageWithNumber">Página <xliff:g>%d</xliff:g></string>
<string name="failedLaunchingFileManager">Não foi possível encontrar um gestor de ficheiros suportado</string>
<string name="failedLaunchingFileManager">Não foi possível encontrar um gestor de ficheiros apoiado</string>
<string name="noCameraFoundGuideText">O seu dispositivo não parece ter uma câmara. Se tiver, tente reiniciar o dispositivo. Caso contrário, utilize o botão \"Mais opções\" abaixo para adicionar um código de barras de outra maneira.</string>
<string name="importCancelled">Importação cancelada</string>
<string name="exportCancelled">Exportação cancelada</string>
@@ -301,12 +297,18 @@
<string name="settings_column_count_7">7</string>
<string name="addFromPkpass">Selecionar um ficheiro Passbook (.pkpass / .pkpasses)</string>
<string name="unsupportedFile">Este ficheiro não é suportado</string>
<string name="generic_error_please_retry">Lamento, ocorreu um erro, tente novamente...</string>
<string name="generic_error_please_retry">Ocorreu um erro</string>
<string name="sort_by_valid_from">Válido a partir de</string>
<string name="width">Largura</string>
<string name="setBarcodeWidth">Definir a largura do código de barras</string>
<string name="card_list_widget_name">Lista de cartões</string>
<string name="card_list_widget_empty">Após adicionar cartões de fidelidade em Catima, eles aparecerão aqui. Se tem cartões, certifique-se de que não estão todos arquivados.</string>
<string name="cardWithNumber">Cartão <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Cartão <xliff:g>%d</xliff:g> (%s)</string>
<string name="cardWithNumberAndLocale">Cartão <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Não gire o dispositivo, pois cancelará a ação</string>
<string name="acra_catima_has_crashed">Lamentamos, mas o <xliff:g id="app_name">%s</xliff:g> travou. Ajude-nos a corrigir este problema a enviar um relatório de erro.</string>
<string name="acra_explain_crash">Se possível, acrescente detalhes sobre o que fazia aqui:</string>
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> relatório de travamento</string>
<string name="pref_enable_acra">Solicitar o envio de relatórios de falhas</string>
<string name="pref_enable_acra_summary">Quando ativado, relatar uma falha será solicitado quando isto ocorrer. Os relatórios de falhas nunca são enviados automaticamente.</string>
</resources>

View File

@@ -303,5 +303,11 @@
<string name="card_list_widget_name">Lista de cartões</string>
<string name="card_list_widget_empty">Depois que você adicionar alguns cartões de fidelidade no Catima, eles aparecerão aqui. Se você tiver cartões, verifique se eles não estão todos arquivados.</string>
<string name="cardWithNumber">Cartão <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Cartão <xliff:g>%d</xliff:g> (%s)</string>
<string name="cardWithNumberAndLocale">Cartão <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Por favor, não gire o dispositivo, pois cancelará a ação</string>
<string name="acra_catima_has_crashed">Lamentamos, mas o <xliff:g id="app_name">%s</xliff:g> travou. Ajude-nos a corrigir este problema a enviar um relatório de erro.</string>
<string name="acra_explain_crash">Se possível, acrescente detalhes sobre o que fazia aqui:</string>
<string name="acra_crash_email_subject">Relatório de falha em <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Solicitar o envio de relatórios de falhas</string>
<string name="pref_enable_acra_summary">Quando ativado, relatar uma falha será solicitado quando isto ocorrer. Os relatórios de falhas nunca são enviados automaticamente.</string>
</resources>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_add">Dodaj</string>
<string name="noGiftCards">Pritisni gumb + za dodajanje nove kartice ali gumb ⋮ v meniju za uvoz.</string>
<string name="noGiftCards">Pritisni gumb + za dodajanje nove kartice ali gumb ⋮ v meniju za uvoz</string>
<string name="storeName">Ime</string>
<string name="note">Opomba</string>
<string name="cardId">Št. kartice</string>
@@ -18,9 +18,9 @@
<string name="cardShortcut">Bližnjica do kartice</string>
<string name="noCardsMessage">Najprej dodaj kartico</string>
<string name="noCardExistsError">Te kartice ni bilo mogoče najti</string>
<string name="importExport">Uvozi/Izvozi</string>
<string name="importExport">Uvozi/izvozi</string>
<string name="exportName">Izvozi</string>
<string name="importExportHelp">Varnostna kopija podatkovne baze omogoča prenos na drugo napravo.</string>
<string name="importExportHelp">Varnostna kopija podatkovne baze omogoča prenos na drugo napravo</string>
<string name="importSuccessfulTitle">Uvoz je bil uspešen</string>
<string name="importFailedTitle">Uvoz ni uspel</string>
<string name="importFailed">Napaka pri uvozu</string>
@@ -30,7 +30,7 @@
<string name="importing">Uvažanje …</string>
<string name="exporting">Izvažanje …</string>
<string name="importOptionFilesystemTitle">Uvozi iz datotečnega sistema</string>
<string name="importOptionFilesystemExplanation">Izberi specifično datoteko iz datotečnega sistema.</string>
<string name="importOptionFilesystemExplanation">Izberi specifično datoteko iz datotečnega sistema</string>
<string name="importOptionFilesystemButton">Iz datotečnega sistema</string>
<string name="about">Več o aplikaciji</string>
<string name="app_license">Prosta programska oprema s copyleftom, licenca GPL3+</string>
@@ -49,11 +49,11 @@
<string name="leaveWithoutSaveTitle">Izhod</string>
<string name="moveDown">Premikanje navzdol</string>
<string name="moveUp">Premik navzgor</string>
<string name="failedOpeningFileManager">Najprej namesti upravitelja datotek.</string>
<string name="failedOpeningFileManager">Ni mogoče odpreti upravitelja datotek</string>
<string name="deleteConfirmationGroup">Brisanje skupine\?</string>
<string name="all">Vse</string>
<string name="noGroupCards">Ta skupina je prazna</string>
<string name="noGroups">Pritisni gumb +, če želiš dodati skupine za kategorizacijo.</string>
<string name="noGroups">Pritisni gumb +, če želiš dodati skupine za kategorizacijo</string>
<string name="groups">Skupine</string>
<string name="enter_group_name">Vnesi ime skupine</string>
<string name="exportSuccessful">Podatkovna baza izvožena</string>
@@ -67,7 +67,7 @@
<string name="settings_theme">Tema</string>
<string name="starImage">Zvezdica za priljubljene</string>
<string name="app_copyright_old">Na podlagi aplikacije Loyalty Card Keychain \navtorske pravice © 2016-2020 Branden Archer</string>
<string name="exportOptionExplanation">Podatki bodo zapisani na izbrano mesto.</string>
<string name="exportOptionExplanation">Podatki bodo zapisani na izbrano mesto</string>
<string name="failedParsingImportUriError">Ni bilo mogoče razčleniti URI uvoza</string>
<string name="share">Deli</string>
<string name="unstar">Odstrani iz priljubljenih</string>
@@ -171,7 +171,7 @@
<string name="unarchive">Odpakiraj arhiv</string>
<string name="archived">Kartica arhivirana</string>
<string name="unarchived">Kartica ni arhivirana</string>
<string name="failedLaunchingPhotoPicker">Ni mogoče najti podprte aplikacije za gledanje slik</string>
<string name="failedLaunchingPhotoPicker">Ni mogoče najti podprte aplikacije za slike</string>
<string name="previousCard">Prejšnja</string>
<string name="nextCard">Naslednja</string>
<string name="updateBalanceTitle">Koliko si porabil ali prejel?</string>
@@ -180,18 +180,18 @@
<string name="group_name_is_empty">Ime skupine ne sme biti prazno</string>
<string name="group_updated">Skupina posodobljena</string>
<string name="groupsList">Skupine: <xliff:g>%s</xliff:g></string>
<string name="app_libraries">Proste knjižnice tretjih oseb: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Prosti viri tretjih oseb: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Knjižnice tretjih oseb: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Viri tretjih oseb: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="expiryStateSentence">Poteče: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentenceExpired">Poteklo: <xliff:g>%s</xliff:g></string>
<string name="expiryDate">Datum poteka veljavnosti</string>
<string name="chooseExpiryDate">Izberi datum poteka veljavnosti</string>
<string name="moveBarcodeToTopOfScreen">Premakni črtno kodo na vrh zaslona</string>
<string name="importCatimaMessage">Izberi svoj obstoječ Catima <i>catima.zip</i> izvoz podatkov za uvoz v aplikacijo. \nNajprej izvozi podatke v meniju \"Uvozi/Izvozi\" v drugi aplikaciji Catima s pritiskom na Izvozi.</string>
<string name="importVoucherVaultMessage">Izberi svoj <i>vouchervault.json</i> Voucher Vault izvoz podatkov za uvoz. \nIzvoz podatkov dobiš s pritiskom na gumb »Export« v Voucher Vault first.</string>
<string name="importCatimaMessage">Izberi svoj obstoječ izvoz podatkov za uvoz v aplikacijo. \nNajprej izvozi podatke v meniju Uvozi/izvozi v drugi aplikaciji Catima s pritiskom na Izvozi.</string>
<string name="importVoucherVaultMessage">Izberi svoj Voucher Vault izvoz podatkov za uvoz. \nIzvoz podatkov dobiš s pritiskom na gumb »Export« v Voucher Vault.</string>
<string name="failedToOpenUrl">Prvo namesti spletni brskalnik</string>
<string name="welcome">Pozdravljen v Catimi</string>
<string name="noGiftCardsGroup">Ustvari kartice in jih dodeli tej skupini.</string>
<string name="noGiftCardsGroup">Ustvari kartice in jih dodeli tej skupini</string>
<plurals name="deleteCardsTitle">
<item quantity="one">Izbriši <xliff:g>%d</xliff:g> kartico</item>
<item quantity="two">Izbriši <xliff:g>%d</xliff:g> kartici</item>
@@ -213,9 +213,9 @@
<item quantity="other"><xliff:g>%d</xliff:g> kartic</item>
</plurals>
<string name="editGroup">Urejanje skupine: <xliff:g>%s</xliff:g></string>
<string name="importFidmeMessage">Izberi svoj <i>fidme-export-request-xxxxxx.zip</i> FidMe izvoz podatkov za uvoz in naknadno ročno izberi tipe črtnih kod. \nFidMe izvoz podatkov naredi v svojem FidMe profilu z izbiro »Data Protection« in nato s pritiskom na gumb »Extract my data first«.</string>
<string name="importLoyaltyCardKeychainMessage">Izberi svoj <i>LoyaltyCardKeychain.csv</i> Kartice zvestobe izvoz podatkov za uvoz. \nKartice zvestobe izvoz podatkov naredi s pritiskom na gumb »Import/Export« v meniju s pritiskom najprej na gumb »Export«.</string>
<string name="failedGeneratingShareURL">URL-ja za skupno rabo ni bilo mogoče ustvariti. Prosim prijavi napako.</string>
<string name="importFidmeMessage">Izberi svoj izvoz iz FindMe za uvoz in naknadno ročno izberi tipe črtnih kod. \nFidMe izvoz podatkov naredi v svojem FidMe profilu z izbiro »Data Protection« in nato s pritiskom na gumb »Extract my data«.</string>
<string name="importLoyaltyCardKeychainMessage">Izberi svoj izvoz iz Kartice zvestobe za uvoz. \nKartice zvestobe izvoz podatkov naredi s pritiskom na gumb »Uvoz/izvoz« v meniju s pritiskom na gumb »Export«.</string>
<string name="failedGeneratingShareURL">URL-ja za skupno rabo ni bilo mogoče ustvariti</string>
<string name="settings_oled_dark">Čisto črno ozadje za temno temo</string>
<string name="selectColor">Izberi barvo</string>
<string name="settings_catima_theme">Catima</string>
@@ -276,7 +276,7 @@
<string name="field_must_not_be_empty">Polje ne sme biti prazno</string>
<string name="manually_enter_barcode_instructions">Vnesi identifikacijsko številko ali besedilo na kartici in pritisni črtno kodo, ki je podobna tisti na kartici.</string>
<string name="add_manually_warning_title">Priporočljivo je skeniranje</string>
<string name="add_manually_warning_message">V nekaterih trgovinah se vrednost črtne kode razlikuje od številke, napisane na kartici. Zaradi tega ročno vnašanje črtne kode morda ne bo vedno delovalo. Priporočamo, da črtno kodo raje skeniraš s kamero. Želiš nadaljevati?</string>
<string name="add_manually_warning_message">V nekatere kartice se vrednost črtne kode razlikuje od številke, napisane na kartici. Zaradi tega ročno vnašanje črtne kode morda ne bo vedno delovalo. Priporočamo, da črtno kodo raje skeniraš s kamero. Želiš nadaljevati?</string>
<string name="continue_">Nadaljuj</string>
<string name="spend">Porabi</string>
<string name="receive">Prejmi</string>
@@ -296,16 +296,24 @@
<string name="switchToFrontImage">Preklopi na prednjo sliko</string>
<string name="switchToBackImage">Preklopi na zadnjo sliko</string>
<string name="switchToBarcode">Preklopi na črtno kodo</string>
<string name="openFrontImageInGalleryApp">Odpri sprednjo sliko v galeriji</string>
<string name="openBackImageInGalleryApp">Odpri zadnjo sliko v galeriji</string>
<string name="openFrontImageInGalleryApp">Odpri sprednjo sliko v aplikaciji za slike</string>
<string name="openBackImageInGalleryApp">Odpri zadnjo sliko v aplikaciji za slike</string>
<string name="setBarcodeHeight">Nastavi višino črtne kode</string>
<string name="useFrontImage">Uporabi prednjo sliko</string>
<string name="useBackImage">Uporabi zadnjo sliko</string>
<string name="addFromPkpass">Izberi Passbook datoteko (.pkpass)</string>
<string name="addFromPkpass">Izberi Passbook datoteko (.pkpass / .pkpasses)</string>
<string name="unsupportedFile">Ta datoteka ni podprta</string>
<string name="generic_error_please_retry">Žal se je pojavila napaka, poskusi znova …</string>
<string name="generic_error_please_retry">Prišlo je do napake</string>
<string name="width">Širina</string>
<string name="card_list_widget_name">Seznam kartic</string>
<string name="setBarcodeWidth">Nastavi širino črtne kode</string>
<string name="card_list_widget_empty">Ko v Catimi dodaš nekaj kartic zvestobe, se bodo te prikazale tukaj. Če imaš kartice, se prepričaj, da niso vse arhivirane.</string>
<string name="cardWithNumber">Kartica <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">Kartica <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">Naprave ne obračaj, saj bo to prekinilo postopek</string>
<string name="acra_catima_has_crashed">Žal nam je, vendar se je aplikacija <xliff:g id="app_name">%s</xliff:g> sesula. Pomagaj nam odpraviti to težavo tako, da nam pošlješ poročilo o napaki.</string>
<string name="acra_explain_crash">Če je mogoče, dodaj več podrobnosti o tem, kaj si tukaj počel/a:</string>
<string name="acra_crash_email_subject">Poročilo o sesutju aplikacije <xliff:g id="app_name">%s</xliff:g></string>
<string name="pref_enable_acra">Prosi za pošiljanje poročila o sesutju</string>
<string name="pref_enable_acra_summary">Ko je ta možnost omogočena, boš ob pojavu sesutja pozvan, da prijaviš napako. Poročilo o sesutju se nikoli ne pošilja samodejno.</string>
</resources>

View File

@@ -296,4 +296,12 @@
<string name="width">அகலம்</string>
<string name="card_list_widget_name">அட்டை பட்டியல்</string>
<string name="card_list_widget_empty">கேட்டிமாவில் நீங்கள் விசுவாச அட்டைகளை சேர்த்த பிறகு, அவை இங்கு தோன்றும். அட்டைகள் காப்பகப்படுத்த படவில்லை என உறுதி செய்க.</string>
<string name="cardWithNumber">அட்டை<xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">அட்டை<xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">தயவுசெய்து சாதனத்தை சுழற்றாதீர்கள், இது செயலை ரத்து செய்யும்.</string>
<string name="acra_catima_has_crashed">மன்னிக்கவும், <xliff:g id="app_name">%s</xliff:g> செயலி செயலிழந்துள்ளது. தயவுசெய்து பிழை அறிக்கையை அனுப்பி, இந்த பிரச்சினையை சரிசெய்ய எங்களுக்கு உதவுங்கள்.</string>
<string name="acra_explain_crash">சாத்தியமானால், நீங்கள் இங்கே என்ன செய்தீர்கள் என்பதைப் பற்றிய மேலும் விவரங்களைச் சேர்க்கவும்:</string>
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> செயலிழப்பு அறிக்கை</string>
<string name="pref_enable_acra">செயலிழப்பு அறிக்கைகளை அனுப்ப வேண்டுமா</string>
<string name="pref_enable_acra_summary">இது இயக்கப்பட்டால், செயலி செயலிழக்கும் போது அதைப் பற்றிய அறிக்கையை அனுப்பும்படி உங்களிடம் கேட்கப்படும். செயலிழப்பு அறிக்கைகள் ஒருபோதும் தானாக அனுப்பப்படமாட்டாது.</string>
</resources>

View File

@@ -198,7 +198,7 @@
<string name="archived">Kart arşivlendi</string>
<string name="unarchived">Kart arşivden çıkarıldı</string>
<string name="archive">Arşivle</string>
<string name="failedLaunchingPhotoPicker">"Desteklenen bir resim seçici bulunamadı"</string>
<string name="failedLaunchingPhotoPicker">Desteklenen bir resim seçici bulunamadı</string>
<plurals name="groupCardCountWithArchived">
<item quantity="one"><xliff:g>%1$d</xliff:g> kart (<xliff:g id="archivedCount">%2$d</xliff:g> tane arşivlendi)</item>
<item quantity="other"><xliff:g>%1$d</xliff:g> kart (<xliff:g id="archivedCount">%2$d</xliff:g> tane arşivlendi)</item>
@@ -304,4 +304,5 @@
<string name="acra_explain_crash">Mümkünse lütfen ne yaptığınızla ilgili daha fazla detay ekleyin:</string>
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> çökme raporu</string>
<string name="pref_enable_acra_summary">Etkinleştirildiğinde, bir çökmeyi şikayet etmeniz istenecektir. Çökme raporları hiç bir zaman otomatik olarak gönderilmez.</string>
<string name="pref_enable_acra">Çökme bildirimlerini göndermeyi iste</string>
</resources>

View File

@@ -2,7 +2,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_search">搜尋</string>
<string name="action_add">新增</string>
<string name="noGiftCards">點選 + 按鈕以新增卡片,或從 ⋮ 選單中匯入</string>
<string name="noGiftCards">點選 + 按鈕以新增卡片,或從 ⋮ 選單中匯入</string>
<string name="noMatchingGiftCards">找不到相關結果。試試其他關鍵字。</string>
<string name="storeName">名稱</string>
<string name="note">註記</string>
@@ -40,7 +40,7 @@
<string name="deleteConfirmationGroup">刪除此群組?</string>
<string name="deleteTitle">刪除卡片</string>
<string name="editBarcode">編輯條碼</string>
<string name="editCardTitle">編輯</string>
<string name="editCardTitle">編輯</string>
<string name="enter_group_name">輸入群組名稱</string>
<string name="errorReadingImage">無法讀取此圖片</string>
<string name="expiryDate">逾期日期</string>
@@ -50,12 +50,12 @@
<string name="exportFailedTitle">匯出失敗</string>
<string name="exporting">匯出中…</string>
<string name="exportName">匯出</string>
<string name="exportOptionExplanation">資料將寫至您所選的位置</string>
<string name="exportOptionExplanation">資料將寫至您所選的位置</string>
<string name="exportPassword">透過密碼保護您的匯出檔(選用)</string>
<string name="exportPasswordHint">輸入密碼</string>
<string name="exportSuccessful">已匯出資料</string>
<string name="exportSuccessfulTitle">已匯出</string>
<string name="failedOpeningFileManager">請先安裝檔案管理員</string>
<string name="failedOpeningFileManager">無法開啟檔案管理員</string>
<string name="failedParsingImportUriError">無法讀取匯入 URI</string>
<string name="frontImageDescription">正面圖片</string>
<string name="groups">群組</string>
@@ -143,36 +143,33 @@
<string name="ok">OK</string>
<string name="sendLabel">送出…</string>
<string name="scanCardBarcode">掃描條碼</string>
<string name="importExportHelp">備份您的資料以將其轉移至其他裝置中</string>
<string name="importExportHelp">備份您的資料以將其轉移至其他裝置中</string>
<string name="importOptionFilesystemTitle">自檔案系統中匯入</string>
<string name="importOptionFilesystemExplanation">自檔案系統中選取檔案</string>
<string name="importOptionFilesystemExplanation">自檔案系統中選取檔案</string>
<string name="importOptionFilesystemButton">自檔案系統</string>
<string name="app_copyright_fmt">著作權所有 © 2019<xliff:g>%d</xliff:g> Sylvia van Os 與其他貢獻者</string>
<string name="importVoucherVault">自 Voucher Vault 中匯入</string>
<string name="importVoucherVaultMessage">選取您 Voucher Vault 匯出的 <i>vouchervault.json</i> 檔案以進行匯入。
\n請您先透過 Voucher Vault 進行匯出。</string>
<string name="importVoucherVaultMessage">選取您 Voucher Vault 匯出的檔案以進行匯入。\n您可以在 Voucher Vault 中按下「匯出」來建立此檔案。</string>
<string name="importLoyaltyCardKeychain">自 Loyalty Card Keychain 中匯入</string>
<string name="importLoyaltyCardKeychainMessage">選取您 Loyalty Card Keychain <i>LoyaltyCardKeychain.csv</i> 檔案以進行匯入。
\n請您先透過 Loyalty Card Keychain 的匯入/匯出選單進行匯出。</string>
<string name="importLoyaltyCardKeychainMessage">選取您 Loyalty Card Keychain 匯出的檔案以進行匯入。\n您可以在 Loyalty Card Keychain 的「匯入/匯出」選單中按下「匯出」來建立此檔案。</string>
<string name="importFidme">自 FidMe 匯入</string>
<string name="importFidmeMessage">選取您 FidMe 匯出的<i>fidme-export-request-xxxxxx.zip</i> 檔案以進行匯入,並手動選擇條碼種類。
\n請您先透過您的 FidMe 個人檔案選取『Data Protection』並選擇『Extract my data』。</string>
<string name="importFidmeMessage">選取您 FidMe 匯出的檔案以進行匯入,並在之後手動選擇條碼類型。\n您可以在 FidMe 的個人檔案中選擇「Data Protection」然後按下「Extract my data」來建立此檔案。</string>
<string name="importCatima">自卡提碼匯入</string>
<string name="importCatimaMessage">選取您自卡提碼匯出的 <i>catima.zip</i> 檔案以進行匯入。 \n您可透過其他裝置的卡提碼程式中的匯入/匯出選單進行匯出</string>
<string name="importCatimaMessage">選取您從 Catima 匯出檔案以進行匯入。\n您可以在另一台裝置的 Catima 應用程式中,透過「匯入/匯出選單按下「匯出」來建立此檔案</string>
<string name="points"></string>
<string name="app_libraries">第三方自由函式庫:<xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">第三方自由資源:<xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">第三方式庫:<xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">第三方資源:<xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">選擇條碼</string>
<string name="noGroups">請點選 + 加號按鈕新增群組</string>
<string name="noGroups">請點選 + 加號按鈕新增群組</string>
<string name="moveBarcodeToTopOfScreen">將條碼移至螢幕上方</string>
<string name="app_loyalty_card_keychain">萬用卡片錢包</string>
<string name="unsupportedBarcodeType">尚支援此條碼種類,但未來版本的應用程式可能會支援此條碼種類。</string>
<string name="wrongValueForBarcodeType">條碼內容不適用於此條碼種類</string>
<string name="backImageDescription">背面圖片</string>
<string name="updateBarcodeQuestionText">您已更新了 ID是否要更新條碼內容以匹配此 ID</string>
<string name="failedGeneratingShareURL">無法建立可分享的 URL,請回報此錯誤。</string>
<string name="failedGeneratingShareURL">無法建立可分享的 URL</string>
<string name="starImage">收藏標示</string>
<string name="noGiftCardsGroup">建立一些卡片,然後將它們分配到這個群組中</string>
<string name="noGiftCardsGroup">建立一些卡片,然後將它們分配到這個群組中</string>
<string name="showMoreInfo">顯示資訊</string>
<string name="shortcutSelectCard">選擇卡片</string>
<string name="starred">已收藏</string>
@@ -201,7 +198,7 @@
<item quantity="other"><xliff:g>%1$d</xliff:g> 張卡片 (<xliff:g id="archivedCount">%2$d</xliff:g> 張已封存)</item>
</plurals>
<string name="failedToOpenUrl">先安裝網頁瀏覽器</string>
<string name="failedLaunchingPhotoPicker">無法找到支援的圖庫應用程式</string>
<string name="failedLaunchingPhotoPicker">無法找到支援的圖片選取器</string>
<string name="previousCard">上一張</string>
<string name="nextCard">下一張</string>
<string name="welcome">歡迎使用卡提碼</string>
@@ -220,7 +217,7 @@
<string name="currentBalanceSentence">餘額:<xliff:g>%s</xliff:g></string>
<string name="newBalanceSentence">新的餘額:<xliff:g>%s</xliff:g></string>
<string name="switchToBarcode">選擇條碼</string>
<string name="openFrontImageInGalleryApp">以圖庫軟件開啟正面圖片</string>
<string name="openFrontImageInGalleryApp">在圖片檢視應用程式中開啟正面圖片</string>
<string name="show_note">顯示備註</string>
<string name="show_balance">顯示餘額</string>
<string name="show_validity">顯示有效性</string>
@@ -229,7 +226,7 @@
<string name="height"></string>
<string name="donate">捐款</string>
<string name="icon_header_click_text">長按以編輯縮圖</string>
<string name="openBackImageInGalleryApp">以圖庫軟體開啟背面圖片</string>
<string name="openBackImageInGalleryApp">在圖片檢視應用程式中開啟背面圖片</string>
<string name="show_name_below_image_thumbnail">在縮圖下方顯示名稱</string>
<string name="setBarcodeHeight">設定條碼高度</string>
<string name="app_copyright_short">著作權所有© Sylvia van Os與其他貢獻者</string>
@@ -265,7 +262,7 @@
<string name="continue_">繼續</string>
<string name="multipleBarcodesFoundPleaseChooseOne">你想要使用哪個找到的條碼?</string>
<string name="pageWithNumber"><xliff:g>%d</xliff:g></string>
<string name="add_manually_warning_message">對於某些商店,條碼值與卡片上寫的數字並不相同。因此手動輸入條碼可能並不總是有效。強烈建議使用相機掃描條碼。你還想繼續嗎?</string>
<string name="add_manually_warning_message">有些卡片上的條碼值與卡面上印的號碼可能不同,因此手動輸入條碼可能無法正常運作。建議您改用相機掃描條碼。您仍要繼續嗎?</string>
<string name="spend">花費</string>
<string name="noCameraFoundGuideText">您的裝置似乎沒有相機鏡頭。如果實際上有相機鏡頭,請嘗試重新啟動此裝置,否則請點選下方的「更多」按鈕,以其它方式新增條碼。</string>
<string name="exportCancelled">已取消匯出</string>
@@ -285,12 +282,20 @@
<string name="settings_category_title_cards_overview">卡片概覽</string>
<string name="settings_column_count_portrait">縱向模式下的列數</string>
<string name="settings_column_count_landscape">横向模式下的列數</string>
<string name="addFromPkpass">選擇 Passbook 檔案 (.pkpass)</string>
<string name="addFromPkpass">選擇 Passbook 檔案 (.pkpass / .pkpasses)</string>
<string name="unsupportedFile">不支援此檔案</string>
<string name="generic_error_please_retry">抱歉,似乎出了點錯誤,請您再試一次...</string>
<string name="generic_error_please_retry">發生錯誤</string>
<string name="sort_by_valid_from">有效期限開始日</string>
<string name="card_list_widget_empty">加入卡提碼的卡片會在這顯示。若您已加入卡片,請確認卡片是否被歸檔。</string>
<string name="width"></string>
<string name="card_list_widget_name">卡片清單</string>
<string name="setBarcodeWidth">設定條碼寬度</string>
<string name="cardWithNumber">卡片 <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">卡片 <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">請勿旋轉裝置,否則此操作將被取消</string>
<string name="acra_catima_has_crashed">很抱歉,<xliff:g id="app_name">%s</xliff:g> 已發生當機。請協助我們修正此問題,並傳送錯誤報告給我們。</string>
<string name="acra_explain_crash">如果可以的話,請在此提供您當時的操作詳細資訊:</string>
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> 當機報告</string>
<string name="pref_enable_acra">詢問是否傳送當機報告</string>
<string name="pref_enable_acra_summary">啟用後,當發生當機時系統會詢問您是否要回報。當機報告絕不會自動傳送。</string>
</resources>

View File

@@ -99,6 +99,7 @@
-->
<string-array name="locale_values">
<item />
<!-- <item>af</item> -->
<item>ar</item>
<!-- <item>ast</item> -->
<item>be</item>
@@ -121,6 +122,7 @@
<item>fi</item>
<!-- <item>fil</item> -->
<item>fr</item>
<!-- <item>fy</item> -->
<item>gl</item>
<item>he-rIL</item>
<item>hi</item>

View File

@@ -1,69 +0,0 @@
package protect.card_locker;
import static org.junit.Assert.assertEquals;
import static org.robolectric.Shadows.shadowOf;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.view.View;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class ImportExportActivityTest {
private void registerIntentHandler(String handler) {
// Add something that will 'handle' the given intent type
PackageManager packageManager = RuntimeEnvironment.application.getPackageManager();
ResolveInfo info = new ResolveInfo();
info.isDefault = true;
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.packageName = "does.not.matter";
info.activityInfo = new ActivityInfo();
info.activityInfo.applicationInfo = applicationInfo;
info.activityInfo.name = "DoesNotMatter";
info.activityInfo.exported = true;
Intent intent = new Intent(handler);
if (handler.equals(Intent.ACTION_GET_CONTENT)) {
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
}
shadowOf(packageManager).addResolveInfoForIntent(intent, info);
}
private void checkVisibility(Activity activity, int state, int divider, int title, int message, int button) {
View dividerView = activity.findViewById(divider);
View titleView = activity.findViewById(title);
View messageView = activity.findViewById(message);
View buttonView = activity.findViewById(button);
assertEquals(state, dividerView.getVisibility());
assertEquals(state, titleView.getVisibility());
assertEquals(state, messageView.getVisibility());
assertEquals(state, buttonView.getVisibility());
}
@Test
public void testAllOptionsAvailable() {
registerIntentHandler(Intent.ACTION_PICK);
registerIntentHandler(Intent.ACTION_GET_CONTENT);
Activity activity = Robolectric.setupActivity(ImportExportActivity.class);
checkVisibility(activity, View.VISIBLE, R.id.dividerImportFilesystem,
R.id.importOptionFilesystemTitle, R.id.importOptionFilesystemExplanation,
R.id.importOptionFilesystemButton);
}
}

View File

@@ -0,0 +1,77 @@
package protect.card_locker
import android.app.Activity
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.ResolveInfo
import android.view.View
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.Shadows.shadowOf
@RunWith(RobolectricTestRunner::class)
class ImportExportActivityTest {
private fun registerIntentHandler(handler: String) {
// Add something that will 'handle' the given intent type
val packageManager = RuntimeEnvironment.application.packageManager
val info = ResolveInfo().apply {
isDefault = true
activityInfo = ActivityInfo().apply {
applicationInfo = ApplicationInfo().apply {
packageName = "does.not.matter"
}
name = "DoesNotMatter"
exported = true
}
}
val intent = Intent(handler)
if (handler == Intent.ACTION_GET_CONTENT) {
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "*/*"
}
shadowOf(packageManager).addResolveInfoForIntent(intent, info)
}
private fun checkVisibility(
activity: Activity,
state: Int,
divider: Int,
title: Int,
message: Int,
button: Int
) {
val dividerView = activity.findViewById<View>(divider)
val titleView = activity.findViewById<View>(title)
val messageView = activity.findViewById<View>(message)
val buttonView = activity.findViewById<View>(button)
assertEquals(state, dividerView.visibility)
assertEquals(state, titleView.visibility)
assertEquals(state, messageView.visibility)
assertEquals(state, buttonView.visibility)
}
@Test
fun testAllOptionsAvailable() {
registerIntentHandler(Intent.ACTION_PICK)
registerIntentHandler(Intent.ACTION_GET_CONTENT)
val activity = Robolectric.setupActivity(ImportExportActivity::class.java)
checkVisibility(
activity, View.VISIBLE, R.id.dividerImportFilesystem,
R.id.importOptionFilesystemTitle, R.id.importOptionFilesystemExplanation,
R.id.importOptionFilesystemButton
)
}
}

View File

@@ -1,8 +1,8 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.13.0" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
alias(libs.plugins.com.android.application) apply false
alias(libs.plugins.org.jetbrains.kotlin.android) apply false
}
allprojects {

View File

@@ -0,0 +1,2 @@
- Diverses correccions menors
- S'ha corregit un error en utilitzar la traducció al noruec

View File

@@ -0,0 +1,2 @@
- Corregeix la selecció d'idioma manual que no s'aplica a tot arreu
- Corregeix el bloqueig a la vista d'edició en la configuració regional sense regió

View File

@@ -0,0 +1,2 @@
- Desa l'estat de l'expansió dels detalls de la targeta
- Correccions menors de la interfície d'usuari

View File

@@ -0,0 +1,2 @@
- Correcció d'un bloc gris que apareix en un valor no vàlid per al codi de barres
- Correccions d'importació de Stocard

View File

@@ -0,0 +1 @@
- Corregeix algunes seqüències de caràcters que es mostraven com un sol caràcter

View File

@@ -0,0 +1 @@
- Correccions d'importació de Stocard

View File

@@ -0,0 +1,5 @@
- Afegeix la funció de duplicació de targetes
- No permet triar la caducitat abans de 1970 (de totes maneres, mai van funcionar)
- Afegeix compatibilitat amb l'arxivament de targetes
- Mou l'eliminació de l'edició a la vista
- Elimina la icona de bloqueig de rotació en favor d'una nova configuració de bloqueig de rotació

View File

@@ -0,0 +1 @@
- Corregeix el color de text incorrecte al botó "Sense codi de barres"

View File

@@ -0,0 +1,5 @@
- Quan editeu un ID de targeta, ompliu prèviament l'ID existent per començar. (pull #94 (https://github.com/brarcher/loyalty-card-locker/pull/94))
- Limiteu l'amplada dels codis de barres generats per reduir l'ús de memòria i els errors de memòria insuficient. (pull #103 (https://github.com/brarcher/loyalty-card-locker/pull/103))
- Quan editeu una targeta, canvieu el botó "Introduïu la targeta" per "Edita la targeta" si ja existeix un ID de targeta. (pull #104 (https://github.com/brarcher/loyalty-card-locker/pull/104))
- Canvieu l'esquema de colors perquè sigui més suau i compatible amb la icona de l'aplicació i canvieu el disseny quan visualitzeu una targeta perquè sigui més net. (extracció #107 (https://github.com/brarcher/loyalty-card-locker/pull/107))
- Afegeix un assistent d'introducció que s'inicia en el primer inici de l'aplicació. (extracció #108 (https://github.com/brarcher/loyalty-card-locker/pull/108))

View File

@@ -0,0 +1,7 @@
- Compatibilitat amb l'àrab
- Mostra el recompte de cartes arxivades a la vista general del grup
- Corregeix errors d'anàlisi de saldo (les cartes no es podien desar en àrab ni en altres idiomes amb números no occidentals)
- Corregeix el tema personalitzat que no s'aplicava correctament a la pantalla principal
- Millora la visualització de les cartes seleccionades
- Corregeix el bloqueig en sortir de la vista de cartes en els dissenys RTL per a cartes amb caducitat o saldo
- Corregeix la fletxa enrere a la vista de cartes que apuntava en la direcció incorrecta en els dissenys RTL

View File

@@ -0,0 +1 @@
- Fer més visible la possibilitat de definir una capçalera personalitzada

View File

@@ -0,0 +1,3 @@
- Afegir botons anterior i següent a la vista de la targeta de fidelització
- Corregir el color de primer pla del botó d'edició
- Substituir la icona de desar al disquet per una marca de verificació

View File

@@ -0,0 +1,3 @@
- Afegeix una icona monocroma per a Android 13
- Millora la primera pantalla d'inici
- Correccions d'importació de Fidme

View File

@@ -0,0 +1,4 @@
- Obre la imatge a la galeria amb una pulsació llarga
- Aplica l'estil Material als diàlegs
- Permet crear targetes compartint una imatge a Catima
- Afegeix un botó de despesa ràpida a la pantalla de la targeta

View File

@@ -0,0 +1,2 @@
- S'ha solucionat el quadre de diàleg de despesa ràpida que no permetia el separador
- Suport per carregar imatges des del gestor de fitxers

View File

@@ -0,0 +1,2 @@
- Elimina els permisos innecessaris
- Ataca Android 13

View File

@@ -0,0 +1,2 @@
- Suport per configurar l'inici de validesa de la targeta
- Correcció de la importació de Stocard (el format d'exportació de Stocard ha canviat)

View File

@@ -0,0 +1 @@
- Utilitza els colors de Material You en més dispositius (actualització de la biblioteca de Google)

View File

@@ -0,0 +1 @@
- Evita que es bloquegi la pantalla en girar l'assistent d'introducció per primera vegada.

View File

@@ -0,0 +1,3 @@
- Redisseny complet de les pantalles principal i de la targeta de fidelització
- Material que dissenyeu per a la pantalla de configuració
- Correcció d'un error en utilitzar "Fes una foto" amb l'aplicació de càmera desactivada

View File

@@ -0,0 +1 @@
- Actualitzar les biblioteques utilitzades

View File

@@ -0,0 +1,3 @@
- Premeu llargament la icona de la targeta a l'activitat de visualització per canviar-la
- Milloreu l'estil dels botons a la pantalla Grups
- Corregiu els valors de codi de barres llargs que feien que el codi de barres es reduís a zero

View File

@@ -0,0 +1,2 @@
- Millores menors de la interfície d'usuari
- Correcció del nou disseny que no es podia utilitzar en dispositius amb pantalles quadrades

View File

@@ -0,0 +1 @@
- Suport per seleccionar exactament quins detalls es veuran a la vista general de la targeta

View File

@@ -0,0 +1 @@
- Gestioneu amb més elegància els colors de capçalera que falten

View File

@@ -0,0 +1 @@
- Diverses correccions RTL

View File

@@ -0,0 +1,4 @@
- Millores en el renderitzat de codis de barres
- Interoperabilitat bàsica amb aplicacions externes (Android 6.0+)
- Pantalla de configuració reorganitzada
- Correcció de la importació des d'alguns navegadors que afegeixen una / final a l'URL de compartició

View File

@@ -0,0 +1,3 @@
- Importador de Catima millorat (corregeix targetes que falten en importar)
- Corregeix un error en girar la pantalla mentre es configura la data de vàlid des de/caducitat
- Ajustos menors a la interfície d'usuari

View File

@@ -0,0 +1,2 @@
- Un canvi a la v0.11 va reduir l'ús de memòria del dibuix del codi de barres, però va afectar les dimensions del codi de barres. Ara això s'ha canviat per mantenir les dimensions del codi de barres i reduir l'ús de memòria. (pull #126 (https://github.com/brarcher/loyalty-card-locker/pull/126))
- Actualització de les traduccions a l'alemany i al francès.

View File

@@ -0,0 +1,4 @@
- Correccions menors de la interfície d'usuari
- S'ha corregit la data de vàlid des de i la data de caducitat que es restableix en girar la pantalla d'edició de targetes
- S'ha corregit el bloqueig en girar la pantalla mentre es mostra el selector de color
- S'ha corregit la importació de Stocard

View File

@@ -0,0 +1,3 @@
- Moure el "Mode d'arxiu" al menú "Opcions de visualització" (anteriorment "Mostra detalls")
- Compatibilitat amb idiomes per aplicació d'Android 13
- Incorporar la política de privadesa, el registre de canvis i la llicència a l'aplicació

View File

@@ -0,0 +1,6 @@
- Refinar el flux de treball "Afegir targeta"
- Millores en el flux de validació
- Corregir el cas límit que causava un estat de la interfície d'usuari no vàlid en mostrar l'arxiu
- Utilitzar un tema o un color de targeta per a la barra de navegació (Android 8.1+)
- Selector de dates de validesa i caducitat actualitzat
- Afegir l'opció per girar sempre (ignorant la configuració del sistema)

View File

@@ -0,0 +1,4 @@
- Target Android 14
- Obre la icona de la targeta a la galeria en tocar-la
- Millora el disseny de la pestanya Fotos a la vista d'edició
- Actualitza la pantalla de despeses perquè també admeti rebre

View File

@@ -0,0 +1,3 @@
- Compatibilitat amb l'escaneig de codis de barres de fitxers PDF
- Compatibilitat amb fitxers d'imatge amb diversos codis de barres
- Correccions menors de la interfície d'usuari

View File

@@ -0,0 +1 @@
- Diverses correccions i millores en la gestió de l'equilibri

View File

@@ -0,0 +1,4 @@
- Compatibilitat amb la creació d'una targeta en compartir text sense format
- Mostrar el tipus d'imatge en lloc del codi de barres sota les imatges
- Corregir un possible error en intentar importar una còpia de seguretat des de l'aplicació Nextcloud
- Millora de la compatibilitat amb dispositius sense càmera

View File

@@ -0,0 +1,4 @@
- Permet que els noms de botigues llargs de la vista prèvia es divideixin en diverses línies
- Opció d'utilitzar la imatge frontal i posterior al menú de miniatures
- Correccions menors d'importació/exportació
- Correccions menors de la interfície d'usuari

View File

@@ -0,0 +1 @@
- Corregeix el gest de retrocés a la pantalla principal que tancava el teclat i la cerca a Android 13+

View File

@@ -0,0 +1,3 @@
- Opció per navegar per les targetes amb els botons de volum
- Correcció de la importació de Stocard
- Correcció del missatge "Importació cancel·lada" que apareix després d'una importació correcta

View File

@@ -0,0 +1,3 @@
- S'ha afegit l'opció de menú de bloqueig de rotació de la pantalla en mostrar una targeta. Si està bloquejada, la pantalla passarà a la seva orientació "natural" i es bloquejarà la rotació posterior de la pantalla
- Si es selecciona una targeta des de la pantalla principal però no es pot carregar, l'aplicació falla correctament i publica un missatge.
- S'ha corregit el cas en què no es podien trobar els ID de disseny per a l'assistent d'introducció.

View File

@@ -0,0 +1 @@
- Corregeix l'ajustament de text al quadre de diàleg d'afegir

View File

@@ -0,0 +1,4 @@
- Canvia la columna predeterminada a les pantalles amples a 4
- Permet la substitució del recompte de columnes per a vertical i horitzontal a la configuració
- Mantén el filtre de cerca de la pantalla principal en girar la pantalla o obrir una targeta
- Limita la longitud màxima de la visualització de la nota a la pantalla principal

View File

@@ -0,0 +1,3 @@
- Afegir compatibilitat amb Passbook (.pkpass)
- Corregir la importació de fitxers PDF transparents
- Millora la visualització de miniatures transparents

View File

@@ -0,0 +1 @@
- Corregeix el bloqueig en obrir fitxers pkpass no vàlids

View File

@@ -0,0 +1 @@
- Millora la visualització de les icones d'arxiu/destacat

View File

@@ -0,0 +1,3 @@
- Target Android 15
- Correcció del teclat que cobreix el botó de desar a la pantalla d'edició
- Correcció d'alguns fitxers pkpass que no es detecten com a pkpass (compatibilitat amb el tipus MIME application/vnd-com.apple.pkpass)

View File

@@ -0,0 +1,2 @@
- Possibilitat d'ordenar les targetes per inici de validesa
- Torna temporalment a la configuració d'Android 14 per solucionar alguns problemes d'IU

View File

@@ -0,0 +1,3 @@
- Target Android 15
- Corregeix un error en llegir fitxers pkpass no compatibles
- Millora la compatibilitat amb pkpass

View File

@@ -0,0 +1,4 @@
- Afegir la possibilitat de triar l'amplada del codi de barres en la vista de pantalla completa
- Eliminar la funció d'importació confusa de l'aplicació
- Diverses correccions d'escaneig
- Corregir el bloqueig en carregar un fitxer pkpass sense codi de barres

View File

@@ -0,0 +1 @@
- Actualitzacions de dependències i traduccions

View File

@@ -0,0 +1,2 @@
- Afegeix compatibilitat amb dreceres d'aplicacions (Android 7.1+), on les targetes utilitzades més recentment apareixeran com a dreceres. (extracció #145 (https://github.com/brarcher/loyalty-card-locker/pull/145))
- Afegeix un widget que funciona com una drecera d'aplicació fixada, per admetre dispositius que executen versions anteriors d'Android 7.1. (extracció #142 (https://github.com/brarcher/loyalty-card-locker/pull/142))

View File

@@ -0,0 +1,2 @@
- Afegeix un widget que mostri totes les targetes no arxivades
- Evita que el teclat se superposi al botó de desar a les pantalles d'edició i agrupació

View File

@@ -0,0 +1,2 @@
- Nou redisseny del logotip de Catima
- Actualitzacions de traducció

View File

@@ -0,0 +1,3 @@
- Afegir compatibilitat amb fitxers .pkpasses
- Eliminar l'importador de Stocard (ja no existeix)
- Desactivar temporalment les imatges dels widgets a Android 12L (solució alternativa per a un problema de bloqueig)

View File

@@ -0,0 +1,4 @@
- Destinat a Android 16
- Corregeix un possible error després de treure la imatge de la targeta
- Elimina la funció "Orientació de la pantalla" (Google ha eliminat la possibilitat que les aplicacions controlessin la rotació de la pantalla quan es dirigien a Android 16)
- Afegeix un informe d'errors a la compilació de FOSS (no s'utilitza a la versió de Google Play, només en altres botigues d'aplicacions)

View File

@@ -0,0 +1 @@
- Corregeix un possible error que es podia produir en targetes a les quals falta informació de color a la base de dades

View File

@@ -0,0 +1,5 @@
- S'ha afegit compatibilitat per afegir dreceres a la pantalla d'inici en afegir o editar una targeta. (extracció #155 (https://github.com/brarcher/loyalty-card-locker/pull/155))
- S'ha eliminat el widget, ja que era un mal substitut de les dreceres.
- S'ha corregit l'exportació de còpies de seguretat a Android 7+.
- S'ha informat d'un tipus MIME més precís en exportar dades de còpia de seguretat.
- S'ha corregit l'error que feia que no es pogués editar una targeta.

View File

@@ -0,0 +1,22 @@
Deixa de buscar targetes de recompensa de plàstic durant el pagament a la botiga o a la botiga en línia.
<b>Escaneja codis de barres al teu dispositiu amb la càmera, oblida't de les targetes.</b>
Oblida't de la cartera o mantén-la ultralleugera per a objectes de valor.
Amb aquesta eina essencial per portar cada dia (EDC) pots substituir el plàstic inútil per diners en efectiu.
- Evita l'espionatge amb molt pocs permisos. Sense accés a Internet i sense anuncis.
- Afegeix targetes o codis amb noms i colors personalitzables.
- Introducció manual del codi si no hi ha cap codi de barres per emmagatzemar o no es pot utilitzar.
- Importa targetes i codis des de fitxers, Catima, FidMe, clauer de targetes de fidelització i caixa forta de vals.
- Fes una còpia de seguretat de totes les teves targetes i transfereix-les a un dispositiu nou si vols.
- Comparteix cupons, ofertes exclusives, codis promocionals o targetes i codis amb qualsevol aplicació.
- Tema fosc i opcions d'accessibilitat per a usuaris amb discapacitat visual.
- Fet per a tothom per la comunitat de programari lliure.
- Traduccions localitzades a mà per a més de 40 idiomes. - Gratuït, amb el suport de les contribucions de la comunitat.
- Utilitza-ho, estudia-ho, canvia-ho i comparteix-ho com vulguis; <i>amb tothom</i>.
- No només programari lliure / codi obert. Gestió de targetes de programari lliure amb <i>copyleft</i> (GPLv3+).
Simplifica la teva vida i les teves compres, i no tornis a perdre mai més un rebut en paper, una targeta regal de pagament a la botiga o un bitllet d'avió.
Emporta't totes les teves recompenses i bonificacions i estalvia sobre la marxa.

View File

@@ -0,0 +1 @@
- Opraven možný pád, který mohl nastat u karet, kterým chybí barva v databázi

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