Compare commits

..
237 Commits
Author SHA1 Message Date
Sylvia van Os e68dbe255f Merge pull request #3319 from CatimaLoyalty/fix/pkpasses_import
Fix importing pkpasses file from network location
2026-09-08 19:28:00 +02:00
Sylvia van Os 3fffc83c08 Fix importing pkpasses file from network location
In the scan activity, the mime type info is not always available. This
made pkpasses files fail to parse, because they only went down the
pkpass path
2026-09-07 21:23:34 +02:00
Sylvia van Os 03e1f5c672 Merge pull request #3318 from CatimaLoyalty/create-pull-request/patch-1788682987
Update contributors
2026-09-06 11:00:15 +02:00
TheLastProject 40295f7f37 Update contributors 2026-09-06 08:23:06 +00:00
Sylvia van Os bbf00e1d90 Merge pull request #3317 from CatimaLoyalty/create-pull-request/patch-1788604415
Update Fastlane changelogs
2026-09-05 12:37:56 +02:00
TheLastProject 61637599e3 Update Fastlane changelogs 2026-09-05 10:33:35 +00:00
Sylvia van Os b748f2e002 Update CHANGELOG 2026-09-05 12:32:28 +02:00
Matt Van HornandMatt Van Horn f3424b716d fix: Import cloud-backed PKPASS files off the main thread (#3282)
* fix: Import cloud-backed PKPASS files off the main thread

Fixes #2464

* Address review: move all import parsing off the main thread, restore failure toasts

- Route image/PDF/pkpass/espass imports through one background importFile
- Restore errorReadingFile / errorReadingImage / noBarcodeFound toasts via a
  main-looper-marshalling showToast helper
- Replace fixed-timeout awaits in the activity tests with a waitFor predicate

* Start the import coroutine on the main dispatcher, hop to IO for the read

launch(Dispatchers.IO) starts the coroutine on a background thread immediately,
so a paused Robolectric main looper never drives it and the activity tests time
out. Launch on the lifecycle scope's default (main) dispatcher and wrap only the
blocking read in withContext(Dispatchers.IO).

* Fix the two failing import tests

Two separate bugs, both in the tests I added:

- new Intent(ACTION_VIEW, uri).setType(...) clears the data URI (setType and
  setData clear each other), so the activity received a null Uri and the read
  never started. Use setDataAndType.
- The intermediate-state assertions (no started activity / RESULT_CANCELED)
  are not observable: waiting on the main looper drains it, and Robolectric
  drains it again during .visible(), so the import has already run to
  completion by the time the test regains control. Assert the off-main-thread
  read directly instead, which is the property these tests exist to prove.

* fix: make showToast reusable with a caller-supplied duration

Take the toast length as a parameter instead of hardcoding LENGTH_LONG, so
the helper can serve short toasts too, and document why it exists: it shows
the toast directly when already on the main looper and otherwise posts it
there, which is what the off-main-thread import path needs.

Reorders the modifiers to the file's `static private` convention.

All existing call sites pass LENGTH_LONG, so behavior is unchanged.

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-09-05 12:31:04 +02:00
Sylvia van Os 3a413127a5 Merge pull request #3315 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-09-05 00:53:23 +02:00
NooB9496 352086887d Translated using Weblate (Polish)
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2026-09-03 10:54:23 +02:00
Sylvia van Os 2e3770706c Merge pull request #3314 from CatimaLoyalty/dependabot/gradle/com.android.application-9.4.0
Bump com.android.application from 9.3.2 to 9.4.0
2026-09-02 08:11:23 +02:00
dependabot[bot] 92abf69236 Bump com.android.application from 9.3.2 to 9.4.0
Bumps com.android.application from 9.3.2 to 9.4.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-02 05:57:27 +00:00
Sylvia van Os c9e6606524 Merge pull request #3313 from CatimaLoyalty/dependabot/gradle/com.android.library-9.4.0
Bump com.android.library from 9.3.2 to 9.4.0
2026-09-02 07:55:20 +02:00
dependabot[bot] 9e58c9da44 Bump com.android.library from 9.3.2 to 9.4.0
Bumps com.android.library from 9.3.2 to 9.4.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-02 02:54:29 +00:00
Sylvia van Os b99204c55b Merge pull request #3312 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-09-01 23:12:13 +02:00
Liner Seven 15035fe38e Translated using Weblate (Japanese)
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2026-09-01 20:52:39 +02:00
Sylvain Pichon 5d58fae618 Translated using Weblate (French)
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2026-09-01 20:52:39 +02:00
Sylvia van Os 363fce3064 Merge pull request #3311 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-31 20:33:32 +02:00
Gabriel 868f12f072 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt_BR/
2026-08-31 19:01:57 +02:00
Hosted Weblate user 54392 3332cf732e Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2026-08-31 19:01:56 +02:00
Fjuro abec7ea821 Translated using Weblate (Czech)
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2026-08-31 19:01:55 +02:00
Stephan Paternotte 555f3dff46 Translated using Weblate (Dutch)
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2026-08-31 19:01:55 +02:00
solokot 59e14a516f Translated using Weblate (Russian)
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2026-08-31 19:01:54 +02:00
B o d o 6fc798dfc6 Translated using Weblate (German)
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-08-31 19:01:53 +02:00
Sylvia van Os b9462df24a Merge pull request #3310 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-30 20:40:58 +02:00
Arif Budiman 9344df33d7 Translated using Weblate (Indonesian)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/id/
2026-08-30 17:52:49 +02:00
泥鰍 fbec74ae54 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 90.8% (326 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hant/
2026-08-30 17:52:48 +02:00
Joel A d493733abd Translated using Weblate (Swedish)
Currently translated at 95.2% (342 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sv/
2026-08-30 17:52:47 +02:00
solokot dbae33492d Translated using Weblate (Russian)
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2026-08-30 17:52:47 +02:00
Sylvia van Os 2b8b48afdc Merge pull request #3309 from CatimaLoyalty/create-pull-request/patch-1788083003
Update contributors
2026-08-30 11:46:00 +02:00
TheLastProject 3bb8b94c6d Update contributors 2026-08-30 09:43:23 +00:00
Sylvia van Os 3b0f3b3a5b Merge pull request #3011 from hidrohase/patch-1
Add Obtainum Link and Pic
2026-08-30 11:13:16 +02:00
Sylvia van Os 2885357b80 Move Obtainium button above Google Play button
I still would much rather people use IzzyOnDroid or F-Droid than
Obtainium, as Obtainium has 0 security measures against me getting
hacked and publishing a malicious release. However, many Obtainium users
exist and right now adding Catima is just broken due to the Wear OS
releases.

Also, Google just constantly treats me horrendously so.... yeah.
Anything is better than Google Play.
2026-08-30 11:12:49 +02:00
hidrohase d08bbb5e3c Add Obtainum Link and Pic
The button filters out Wear OS releases
2026-08-30 11:08:00 +02:00
Sylvia van Os 1da01b846a Merge pull request #3308 from CatimaLoyalty/TheLastProject-patch-1
Split GitHub badge into core and Wear OS
2026-08-29 11:28:27 +02:00
Sylvia van Os 1f2870d3d8 Split GitHub badge into core and Wear OS 2026-08-29 11:06:00 +02:00
Sylvia van Os a0a2f061bd Release Catima Wear OS 1.0.1 2026-08-28 17:09:28 +02:00
Sylvia van Os c70b3ec172 Merge pull request #3306 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-28 17:08:26 +02:00
Sylvia van Os a3ad6a8568 Merge pull request #3305 from CatimaLoyalty/create-pull-request/patch-1787928982
Update Fastlane changelogs
2026-08-28 17:07:33 +02:00
Hosted Weblate 1a9d162370 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/
2026-08-28 17:06:43 +02:00
Sylvia van Os 950d19ce8c Translated using Weblate (Persian)
Currently translated at 81.8% (294 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fa/
2026-08-28 17:06:43 +02:00
Sylvain Pichon b531081806 Translated using Weblate (French)
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2026-08-28 17:06:43 +02:00
TheLastProject b46313a112 Update Fastlane changelogs 2026-08-28 14:56:22 +00:00
Sylvia van Os b54fc85fdb Update CHANGELOG 2026-08-28 16:56:09 +02:00
Sylvia van Os b71a2a0bbf Merge pull request #3303 from CatimaLoyalty/feature/update_wearos_screenshots
Update WearOS screenshots based on current state
2026-08-28 16:55:29 +02:00
Sylvia van Os 8ba44eeba5 Update WearOS screenshots based on current state 2026-08-28 16:25:41 +02:00
Sylvia van Os 8ef9f39fb7 Merge pull request #3302 from CatimaLoyalty/fix/hide_barcode_wearos
WearOS: Hide barcode as a hack for Google approval
2026-08-28 16:21:28 +02:00
Sylvia van Os 86543c8fb9 WearOS: Hide barcode as a hack for Google approval 2026-08-28 15:56:24 +02:00
Sylvia van Os 3c8bae3acc Merge pull request #3300 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-27 23:32:07 +02:00
Sylvia van Os 0a19db7c7e Merge pull request #3301 from CatimaLoyalty/dependabot/gradle/gradle-wrapper-9.7.1
Bump gradle-wrapper from 9.7.0 to 9.7.1
2026-08-27 11:24:32 +02:00
dependabot[bot] 7b55f593d0 Bump gradle-wrapper from 9.7.0 to 9.7.1
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.7.0 to 9.7.1.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.7.0...v9.7.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-27 02:55:14 +00:00
Fjuro ced6b08e6c Translated using Weblate (Czech)
Currently translated at 100.0% (1 of 1 strings)

Translation: Catima/Wear OS (Debug)
Translate-URL: https://hosted.weblate.org/projects/catima/wear-os-debug/cs/
2026-08-26 19:52:20 +00:00
Fjuro 23589e88df Translated using Weblate (Czech)
Currently translated at 100.0% (13 of 13 strings)

Translation: Catima/Wear OS
Translate-URL: https://hosted.weblate.org/projects/catima/wear-os/cs/
2026-08-26 19:52:20 +00:00
delvani 4bc38a1162 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_BR/
2026-08-26 19:52:19 +00:00
امیرضا 69ce67cda4 Translated using Weblate (Persian)
Currently translated at 82.4% (296 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fa/
2026-08-26 19:52:19 +00:00
Fjuro ce76901d6e Translated using Weblate (Czech)
Currently translated at 100.0% (1 of 1 strings)

Translation: Catima/Android (Debug)
Translate-URL: https://hosted.weblate.org/projects/catima/android-debug/cs/
2026-08-26 19:52:18 +00:00
Hosted Weblate user 54392 fa4b89d3a9 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2026-08-26 19:52:18 +00:00
Watchman89 3bac76db8f Translated using Weblate (Dutch)
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2026-08-26 19:52:17 +00:00
J. Lavoie f93e7d857a Translated using Weblate (Italian)
Currently translated at 90.8% (326 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2026-08-26 19:52:17 +00:00
J. Lavoie 1b894e5043 Translated using Weblate (French)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2026-08-26 19:52:17 +00:00
Sylvia van Os 08508952fc Merge pull request #3299 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-26 10:50:48 +02:00
Fjuro e7159b7980 Added translation using Weblate (Czech) 2026-08-25 21:38:58 +02:00
Fjuro 223b5a04db Added translation using Weblate (Czech) 2026-08-25 21:38:27 +02:00
Fjuro b7aa25a5e3 Translated using Weblate (Czech)
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2026-08-25 21:38:27 +02:00
B o d o 1d9fbd6633 Translated using Weblate (German)
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-08-25 21:38:27 +02:00
Sylvia van Os 4be04fb057 Merge pull request #3298 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-25 19:12:41 +02:00
Hugo Perez Montero efdb68cb90 Translated using Weblate (Catalan)
Currently translated at 71.5% (257 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ca/
2026-08-25 16:51:43 +00:00
Sylvia van Os 1f61b00ed5 Merge pull request #3297 from CatimaLoyalty/dependabot/gradle/com.android.library-9.3.2
Bump com.android.library from 9.3.1 to 9.3.2
2026-08-25 12:26:44 +02:00
dependabot[bot] 7afa0399d4 Bump com.android.library from 9.3.1 to 9.3.2
Bumps com.android.library from 9.3.1 to 9.3.2.

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-25 09:54:55 +00:00
Sylvia van Os efe441b0bd Merge pull request #3296 from CatimaLoyalty/dependabot/gradle/com.android.application-9.3.2
Bump com.android.application from 9.3.1 to 9.3.2
2026-08-25 11:53:01 +02:00
dependabot[bot] 59efd0892f Bump com.android.application from 9.3.1 to 9.3.2
Bumps com.android.application from 9.3.1 to 9.3.2.

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-25 02:53:10 +00:00
Sylvia van Os 1cee23baad Merge pull request #3295 from CatimaLoyalty/create-pull-request/patch-1787608078
Update Fastlane changelogs
2026-08-24 23:50:34 +02:00
TheLastProject d13519a75f Update Fastlane changelogs 2026-08-24 21:47:57 +00:00
Sylvia van Os 5a12769f22 Update CHANGELOG 2026-08-24 23:47:45 +02:00
Sylvia van Os 14ed18c4d0 Merge pull request #3294 from CatimaLoyalty/fix/card_list_widget_update
Update list widgets on card open
2026-08-24 23:45:56 +02:00
Sylvia van Os 59e2083511 Update list widgets on card open
This ensures the order is correct for users who sort by most recently
used
2026-08-24 23:22:46 +02:00
Sylvia van Os a9daca77a2 Merge pull request #3293 from CatimaLoyalty/create-pull-request/patch-1787582401
Update Fastlane changelogs
2026-08-24 16:47:33 +02:00
TheLastProject 8b67797e71 Update Fastlane changelogs 2026-08-24 14:40:01 +00:00
Sylvia van Os 4195c2b75e Update CHANGELOG 2026-08-24 16:39:47 +02:00
Sylvia van Os 5e3bb55ba5 Merge pull request #3292 from CatimaLoyalty/feature/wearos_scrollbar
Add scroll bar and fix some padding bugs
2026-08-24 16:38:03 +02:00
Sylvia van Os 0a0c378f56 Merge pull request #3291 from CatimaLoyalty/feature/wear_os_splashscreen
Implement Wear OS splash screen
2026-08-24 16:20:37 +02:00
Sylvia van Os 2e4fb34dbe Add scroll bar and fix some padding bugs 2026-08-24 16:20:19 +02:00
Sylvia van Os 2df681a5b8 Implement Wear OS splash screen 2026-08-24 15:45:25 +02:00
Sylvia van Os 7c11508b12 Merge pull request #3289 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-24 14:46:38 +02:00
Audrick Stephan Moraes 54bbe1e99a Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.4% (167 of 168 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt_BR/
2026-08-24 08:52:42 +02:00
Audrick Stephan Moraes 24bd17eb9c Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.4% (350 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_BR/
2026-08-24 08:52:41 +02:00
Arif Budiman 33653fab99 Translated using Weblate (Indonesian)
Currently translated at 99.7% (358 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/id/
2026-08-24 08:52:41 +02:00
109247019824 9efb246133 Translated using Weblate (Bulgarian)
Currently translated at 91.6% (329 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bg/
2026-08-24 08:52:40 +02:00
Roberto Angel e807e1f4ff Translated using Weblate (Spanish)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2026-08-24 08:52:39 +02:00
Sylvia van Os d1d8eaacae Merge pull request #3288 from CatimaLoyalty/create-pull-request/patch-1787459658
Update contributors
2026-08-23 14:31:49 +02:00
TheLastProject 4030cd0284 Update contributors 2026-08-23 04:34:18 +00:00
Sylvia van Os 80ec71d9d9 Merge pull request #3286 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-21 17:31:07 +02:00
josé m. 2cd3b4a131 Translated using Weblate (Galician)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/gl/
2026-08-21 11:53:24 +00:00
NooB9496 e90ab64194 Translated using Weblate (Polish)
Currently translated at 100.0% (168 of 168 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2026-08-21 11:53:22 +00:00
NooB9496 0656767d07 Translated using Weblate (Polish)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2026-08-21 11:53:21 +00:00
Sylvia van Os ab4685b725 Merge pull request #3280 from CatimaLoyalty/dependabot/gradle/androidx.appcompat-appcompat-1.8.0
Bump androidx.appcompat:appcompat from 1.7.1 to 1.8.0
2026-08-20 12:07:34 +02:00
dependabot[bot] 42beafa255 Bump androidx.appcompat:appcompat from 1.7.1 to 1.8.0
Bumps androidx.appcompat:appcompat from 1.7.1 to 1.8.0.

---
updated-dependencies:
- dependency-name: androidx.appcompat:appcompat
  dependency-version: 1.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-20 02:54:28 +00:00
Sylvia van Os 3df40a8808 Merge pull request #3278 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-18 13:19:01 +02:00
Priit Jõerüüt eefe52320a Translated using Weblate (Estonian)
Currently translated at 100.0% (13 of 13 strings)

Translation: Catima/Wear OS
Translate-URL: https://hosted.weblate.org/projects/catima/wear-os/et/
2026-08-18 11:51:58 +02:00
Priit Jõerüüt 96cf6c04be Translated using Weblate (Estonian)
Currently translated at 12.5% (21 of 168 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/et/
2026-08-18 11:51:58 +02:00
Sylvia van Os f397692f3b Merge pull request #3277 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-17 21:54:29 +02:00
AhmedAwad ec17b3a8d9 Translated using Weblate (Arabic)
Currently translated at 41.0% (69 of 168 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ar/
2026-08-17 00:50:38 +02:00
AhmedAwad 03ba942423 Translated using Weblate (Arabic)
Currently translated at 99.7% (358 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ar/
2026-08-17 00:50:38 +02:00
Eisa Ali 67d812b291 Translated using Weblate (Arabic)
Currently translated at 99.7% (358 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ar/
2026-08-17 00:50:38 +02:00
B o d o e9ca16ffd3 Translated using Weblate (German)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2026-08-17 00:50:38 +02:00
Sylvia van Os 66f96b64e2 Merge pull request #3275 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-16 09:42:20 +02:00
Sylvia van Os f165ae525a Merge pull request #3276 from CatimaLoyalty/create-pull-request/patch-1786854833
Update contributors
2026-08-16 09:28:03 +02:00
TheLastProject dfc27cf4fe Update contributors 2026-08-16 04:33:53 +00:00
Eisa Ali c6bf7101c3 Translated using Weblate (Arabic)
Currently translated at 100.0% (13 of 13 strings)

Translation: Catima/Wear OS
Translate-URL: https://hosted.weblate.org/projects/catima/wear-os/ar/
2026-08-15 22:52:25 +02:00
Sylvia van Os a1a97aea9c Merge pull request #3274 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-15 21:08:25 +02:00
Eisa Ali a338eb6e1b Added translation using Weblate (Arabic) 2026-08-15 15:10:05 +02:00
Sylvia van Os 74b248f565 Translated using Weblate (Serbian)
Currently translated at 97.2% (349 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sr/
2026-08-15 15:10:05 +02:00
Sylvain Pichon dae297a41f Translated using Weblate (French)
Currently translated at 100.0% (168 of 168 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2026-08-15 15:10:04 +02:00
Stephan Paternotte 944fa35f83 Translated using Weblate (Dutch)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2026-08-15 15:10:03 +02:00
Sylvia van Os d4196e4029 Translated using Weblate (Dutch)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2026-08-15 15:10:03 +02:00
Sylvain Pichon 62866c93db Translated using Weblate (French)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2026-08-15 15:10:02 +02:00
Vasilis K. db03cdcb3e Translated using Weblate (Greek)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2026-08-15 15:10:02 +02:00
Sylvia van Os 0c28c3e5f7 Merge pull request #3271 from CatimaLoyalty/dependabot/gradle/gradle-wrapper-9.7.0
Bump gradle-wrapper from 9.6.1 to 9.7.0
2026-08-14 19:40:08 +02:00
Sylvia van Os bbe39f3d6a Merge pull request #3272 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-14 19:38:04 +02:00
Petar Mladenović (retr0_master) 4e976caaaf Translated using Weblate (Serbian)
Currently translated at 97.2% (349 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sr/
2026-08-14 09:14:14 +02:00
Edgars Andersons ff804f9352 Translated using Weblate (Latvian)
Currently translated at 16.0% (27 of 168 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lv/
2026-08-14 09:14:13 +02:00
Stephan Paternotte a3805c48d3 Translated using Weblate (Dutch)
Currently translated at 100.0% (168 of 168 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2026-08-14 09:14:11 +02:00
Stephan Paternotte 8eb2dc61d7 Translated using Weblate (Dutch)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2026-08-14 09:14:09 +02:00
Priit Jõerüüt 5b7bb3acc4 Translated using Weblate (Estonian)
Currently translated at 15.3% (2 of 13 strings)

Translation: Catima/Wear OS
Translate-URL: https://hosted.weblate.org/projects/catima/wear-os/et/
2026-08-14 03:59:35 +00:00
Petar Mladenović (retr0_master) c001e317ec Translated using Weblate (Serbian)
Currently translated at 91.0% (327 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sr/
2026-08-14 03:59:34 +00:00
Edgars Andersons 4eb02446aa Translated using Weblate (Latvian)
Currently translated at 15.4% (26 of 168 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lv/
2026-08-14 03:59:34 +00:00
Edgars Andersons 84e092fce4 Translated using Weblate (Latvian)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/lv/
2026-08-14 03:59:33 +00:00
Sylvia van Os b84730877b Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (168 of 168 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2026-08-14 03:59:33 +00:00
dependabot[bot] bad9e0d9d8 Bump gradle-wrapper from 9.6.1 to 9.7.0
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.6.1 to 9.7.0.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.6.1...v9.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-14 02:55:47 +00:00
Sylvia van Os 10ea218b34 Merge pull request #3267 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-13 20:40:40 +02:00
Sylvia van Os 59f52608db Merge pull request #3269 from CatimaLoyalty/wearos/readme_update
Mention smartwatch support in README
2026-08-13 20:27:49 +02:00
Sylvia van Os 239fc9754d Mention smartwatch support in README 2026-08-13 20:24:22 +02:00
Priit Jõerüüt f520534890 Added translation using Weblate (Estonian) 2026-08-13 19:38:48 +02:00
Priit Jõerüüt d91a7dd4a1 Translated using Weblate (Estonian)
Currently translated at 100.0% (1 of 1 strings)

Translation: Catima/Wear OS (Debug)
Translate-URL: https://hosted.weblate.org/projects/catima/wear-os-debug/et/
2026-08-13 19:38:48 +02:00
Priit Jõerüüt b3573891bf Translated using Weblate (Estonian)
Currently translated at 100.0% (1 of 1 strings)

Translation: Catima/Android (Debug)
Translate-URL: https://hosted.weblate.org/projects/catima/android-debug/et/
2026-08-13 19:38:47 +02:00
Priit Jõerüüt fb75cf91c3 Added translation using Weblate (Estonian) 2026-08-13 19:38:01 +02:00
Oğuz Ersen 15022515bf Translated using Weblate (Turkish)
Currently translated at 100.0% (13 of 13 strings)

Translation: Catima/Wear OS
Translate-URL: https://hosted.weblate.org/projects/catima/wear-os/tr/
2026-08-13 19:38:01 +02:00
Priit Jõerüüt 19c277b5b7 Translated using Weblate (Estonian)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/et/
2026-08-13 19:38:01 +02:00
Oğuz Ersen 0af89b9516 Added translation using Weblate (Turkish) 2026-08-13 19:38:00 +02:00
Oğuz Ersen 0450bc5e9b Translated using Weblate (Turkish)
Currently translated at 100.0% (1 of 1 strings)

Translation: Catima/Wear OS (Debug)
Translate-URL: https://hosted.weblate.org/projects/catima/wear-os-debug/tr/
2026-08-13 19:37:59 +02:00
Oğuz Ersen 2ca2840c3c Added translation using Weblate (Turkish) 2026-08-13 19:37:59 +02:00
B o d o 192d516acd Translated using Weblate (German)
Currently translated at 100.0% (13 of 13 strings)

Translation: Catima/Wear OS
Translate-URL: https://hosted.weblate.org/projects/catima/wear-os/de/
2026-08-13 19:37:58 +02:00
B o d o d636097845 Translated using Weblate (German)
Currently translated at 100.0% (1 of 1 strings)

Translation: Catima/Wear OS (Debug)
Translate-URL: https://hosted.weblate.org/projects/catima/wear-os-debug/de/
2026-08-13 19:37:58 +02:00
Oğuz Ersen e5b1c209de Translated using Weblate (Turkish)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/tr/
2026-08-13 19:37:57 +02:00
Hosted Weblate user 54392 61ddf10df5 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (168 of 168 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/zh_Hans/
2026-08-13 19:37:57 +02:00
Hosted Weblate user 54392 96533db864 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hans/
2026-08-13 19:37:56 +02:00
Fjuro 2f84e53257 Translated using Weblate (Czech)
Currently translated at 100.0% (168 of 168 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2026-08-13 19:37:56 +02:00
Liner Seven bb760c5db4 Translated using Weblate (Japanese)
Currently translated at 100.0% (168 of 168 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2026-08-13 19:37:55 +02:00
Liner Seven 078b708c8a Translated using Weblate (Japanese)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2026-08-13 19:37:54 +02:00
solokot edb14da576 Translated using Weblate (Russian)
Currently translated at 100.0% (168 of 168 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2026-08-13 19:37:54 +02:00
B o d o a321edde1c Translated using Weblate (German)
Currently translated at 100.0% (168 of 168 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-08-13 19:37:53 +02:00
solokot 449a89a6f3 Translated using Weblate (Russian)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2026-08-13 19:37:53 +02:00
Kachelkaiser 246605e3ca Translated using Weblate (German)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2026-08-13 19:37:52 +02:00
Fjuro 2ddc36bada Translated using Weblate (Czech)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/cs/
2026-08-13 19:37:52 +02:00
ChaoticNeutralCzech 1e512bac09 Translated using Weblate (Czech)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/cs/
2026-08-13 19:37:51 +02:00
Slávek Banko 60ec5d2520 Translated using Weblate (Czech)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/cs/
2026-08-13 19:37:51 +02:00
Sylvia van Os a790762071 Merge pull request #3268 from CatimaLoyalty/wear/lint
Set up basic lint for Wear OS
2026-08-13 17:48:46 +02:00
Sylvia van Os 07709873c9 Merge pull request #3266 from CatimaLoyalty/dependabot/github_actions/gradle/actions-6.3.0
Bump gradle/actions from 6.2.0 to 6.3.0
2026-08-13 17:24:43 +02:00
Sylvia van Os fa06b40a97 Set up basic lint for Wear OS 2026-08-13 17:24:00 +02:00
Sylvia van Os 537a66ad5d Fix missing dates on CHANGELOGs 2026-08-13 17:20:47 +02:00
dependabot[bot] 0b618e57a9 Bump gradle/actions from 6.2.0 to 6.3.0
Bumps [gradle/actions](https://github.com/gradle/actions) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v6.2.0...v6.3.0)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-13 15:09:40 +00:00
Sylvia van Os 28c6afa9ba Merge pull request #3265 from CatimaLoyalty/dependabot/github_actions/actions/checkout-7.0.1
Bump actions/checkout from 7.0.0 to 7.0.1
2026-08-13 17:07:22 +02:00
Sylvia van Os 0ccc29bfc6 Merge pull request #3264 from CatimaLoyalty/dependabot/gradle/com.android.library-9.3.1
Bump com.android.library from 9.2.1 to 9.3.1
2026-08-13 17:06:50 +02:00
dependabot[bot] 85ea4c0000 Bump actions/checkout from 7.0.0 to 7.0.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v7...v7.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-13 02:52:40 +00:00
dependabot[bot] 9c21e1f3c1 Bump com.android.library from 9.2.1 to 9.3.1
Bumps com.android.library from 9.2.1 to 9.3.1.

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-13 02:52:40 +00:00
Sylvia van Os 1ba2a44dde Merge pull request #3263 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-12 21:06:14 +02:00
B o d o 3a2d125bb2 Added translation using Weblate (German) 2026-08-12 20:46:13 +02:00
B o d o de8d2f78c2 Added translation using Weblate (German) 2026-08-12 20:46:13 +02:00
B o d o e55d6f3189 Translated using Weblate (German)
Currently translated at 100.0% (166 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-08-12 20:46:13 +02:00
Vasilis K. ad5449488f Translated using Weblate (Greek)
Currently translated at 91.0% (327 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2026-08-12 20:46:13 +02:00
B o d o 5d9caa27fe Translated using Weblate (German)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2026-08-12 20:46:13 +02:00
Sylvia van Os 22193b1872 Release Catima v2.45.0 2026-08-12 18:56:26 +02:00
Sylvia van Os 306e7b5201 Release Catima Wear OS v1.0.0 2026-08-12 18:46:49 +02:00
Sylvia van Os 0c8104af96 Merge pull request #3260 from CatimaLoyalty/create-pull-request/patch-1786549695
Update Fastlane changelogs
2026-08-12 18:40:58 +02:00
TheLastProject 3b06f7a105 Update Fastlane changelogs 2026-08-12 15:48:15 +00:00
Sylvia van Os 0869623c1d Merge pull request #3218 from CatimaLoyalty/wip/wearos
WearOS support
2026-08-12 17:48:01 +02:00
Sylvia van Os bfa79a6f68 Notify on foreground service start failure 2026-08-11 22:06:01 +02:00
Sylvia van Os cda842485f Update privacy policy 2026-08-11 22:06:01 +02:00
Sylvia van Os db35306a26 Add WearOS screenshots and description change 2026-08-11 22:06:01 +02:00
Sylvia van Os 2363861a62 Fail pipeline on bad libraries 2026-08-11 22:06:01 +02:00
Sylvia van Os 2960585250 Prepare new versioning and CHANGELOG structure 2026-08-11 22:06:01 +02:00
Sylvia van Os ff830ee50d Improve notification icons
This improves the sizing of the notification icons and uses a generic
sync icon for the Bluetooth server running (so users don't get used to
ignoring the Catima icon).

Generated throudh Android Studio New -> Image Asset -> Notification
Icons -> Path: .design/master_monochrome.svg -> Trim: Yes -> Padding: 0%
2026-08-11 22:06:01 +02:00
Sylvia van Os 3c917da56a Modify code to support minor API versions 2026-08-11 22:06:01 +02:00
Sylvia van Os 6ea1d9ec73 Fix some documentation mistakes for the Content Provider 2026-08-11 22:06:01 +02:00
Sylvia van Os 5ebd7cbb63 Add documentation for Bluetooth server 2026-08-11 22:06:01 +02:00
Sylvia van Os 0d91444ee2 Consistently use Wear OS naming 2026-08-11 22:06:01 +02:00
Sylvia van Os 9459c78e7c Migrate WearOS app to Material3
Also always display sync status on screen
2026-08-11 22:06:01 +02:00
Fabio ManganielloandSylvia van Os b326a7148d feat(bluetooth): Add trusted device pairing (#3223)
Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
2026-08-11 22:06:01 +02:00
Sylvia van Os 5ae850dbe8 Restructure smartwatch support settings 2026-08-11 22:06:01 +02:00
Fabio ManganielloandSylvia van Os 2ef9c9c68e Add WearOS companion app (#3185)
Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
2026-08-11 22:06:01 +02:00
Sylvia van Os 613bd000d2 Merge pull request #3256 from CatimaLoyalty/create-pull-request/patch-1786251663
Update contributors
2026-08-09 10:37:08 +02:00
TheLastProject f7ea5bcb7b Update contributors 2026-08-09 05:01:02 +00:00
Sylvia van Os b8b5b8ae9b Merge pull request #3252 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-06 12:02:55 +02:00
Sylvia van Os e6085a64be Merge pull request #3253 from CatimaLoyalty/dependabot/gradle/androidx.constraintlayout-constraintlayout-2.2.2
Bump androidx.constraintlayout:constraintlayout from 2.2.1 to 2.2.2
2026-08-06 08:08:45 +02:00
dependabot[bot] e77acf9b1e Bump androidx.constraintlayout:constraintlayout from 2.2.1 to 2.2.2
Bumps androidx.constraintlayout:constraintlayout from 2.2.1 to 2.2.2.

---
updated-dependencies:
- dependency-name: androidx.constraintlayout:constraintlayout
  dependency-version: 2.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-06 02:53:46 +00:00
Joel A dc4299d315 Translated using Weblate (Swedish)
Currently translated at 100.0% (324 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sv/
2026-08-06 04:04:57 +02:00
Damjan Gerl 1c8d2ba19e Translated using Weblate (Slovenian)
Currently translated at 100.0% (166 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sl/
2026-08-06 04:04:56 +02:00
NooB9496 d46455c021 Translated using Weblate (Polish)
Currently translated at 100.0% (166 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pl/
2026-08-06 04:04:53 +02:00
Watchman89 abd4c24138 Translated using Weblate (Dutch)
Currently translated at 100.0% (166 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2026-08-06 04:04:53 +02:00
NooB9496 1e922ae074 Translated using Weblate (Polish)
Currently translated at 100.0% (324 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2026-08-06 04:04:47 +02:00
Watchman89 335059a681 Translated using Weblate (Dutch)
Currently translated at 100.0% (324 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2026-08-06 04:04:45 +02:00
Sylvia van Os a530991119 Merge pull request #3248 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-05 18:12:01 +02:00
Edgars Andersons 45130b5f85 Translated using Weblate (Latvian)
Currently translated at 100.0% (324 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/lv/
2026-08-05 00:03:39 +02:00
Damjan Gerl 70fd411640 Translated using Weblate (Slovenian)
Currently translated at 94.5% (157 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sl/
2026-08-05 00:03:37 +02:00
Sylvia van Os 4d47cd4a4a Merge pull request #3246 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-08-03 19:46:26 +02:00
Sylvia van Os bb694ca9cb Translated using Weblate (Arabic)
Currently translated at 98.4% (319 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ar/
2026-08-03 17:21:05 +02:00
Damjan Gerl 7ede8424d0 Translated using Weblate (Slovenian)
Currently translated at 78.9% (131 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sl/
2026-08-03 17:21:04 +02:00
Sylvia van Os 1167cf51b5 Translated using Weblate (Lithuanian)
Currently translated at 8.4% (14 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lt/
2026-08-03 17:21:01 +02:00
Eduardo Augustinho 13d2c81c5c Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.3% (165 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/pt_BR/
2026-08-02 15:05:14 +00:00
Eduardo Augustinho 102a34a3f3 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (324 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pt_BR/
2026-08-02 15:05:12 +00:00
E Ta 0c490f2bdb Translated using Weblate (Arabic)
Currently translated at 98.4% (319 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ar/
2026-08-02 15:05:11 +00:00
Zeet Baral 8f69f9cef9 Translated using Weblate (Bengali (India))
Currently translated at 62.0% (201 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bn_IN/
2026-08-02 15:05:09 +00:00
109247019824 00e65a39de Translated using Weblate (Bulgarian)
Currently translated at 100.0% (324 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/bg/
2026-08-02 15:05:08 +00:00
Damjan Gerl 8698c6099c Translated using Weblate (Slovenian)
Currently translated at 64.4% (107 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sl/
2026-08-02 15:05:07 +00:00
dukezas da3fbdb401 Translated using Weblate (Lithuanian)
Currently translated at 8.4% (14 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/lt/
2026-08-02 15:05:06 +00:00
Nik b28052c46c Translated using Weblate (Italian)
Currently translated at 100.0% (166 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/it/
2026-08-02 15:05:04 +00:00
dukezas b438a7f03d Translated using Weblate (Lithuanian)
Currently translated at 83.9% (272 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/lt/
2026-08-02 15:05:03 +00:00
Nik c144035d95 Translated using Weblate (Italian)
Currently translated at 100.0% (324 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2026-08-02 15:05:02 +00:00
Sylvia van Os 7658c3980a Merge pull request #3245 from CatimaLoyalty/create-pull-request/patch-1785652221
Update contributors
2026-08-02 10:03:34 +02:00
TheLastProject c11297a733 Update contributors 2026-08-02 06:30:20 +00:00
Sylvia van Os c89e60adf6 Merge pull request #3239 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-07-31 17:07:41 +02:00
josé m. fa33f61183 Translated using Weblate (Galician)
Currently translated at 100.0% (324 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/gl/
2026-07-31 03:02:41 +00:00
bruh f3e57d816c Translated using Weblate (Vietnamese)
Currently translated at 100.0% (324 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/vi/
2026-07-31 03:02:39 +00:00
Fjuro bf2e1eface Translated using Weblate (Czech)
Currently translated at 100.0% (166 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/cs/
2026-07-31 03:02:37 +00:00
Fjuro a69fff9b53 Translated using Weblate (Czech)
Currently translated at 100.0% (324 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/cs/
2026-07-31 03:02:34 +00:00
Sylvia van Os 9fc0038bd5 Merge pull request #3238 from CatimaLoyalty/dependabot/github_actions/actions/setup-python-7.0.0
Bump actions/setup-python from 6.3.0 to 7.0.0
2026-07-30 08:21:46 +02:00
dependabot[bot] ffd8dd5633 Bump actions/setup-python from 6.3.0 to 7.0.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6.3.0 to 7.0.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v6.3.0...v7.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-30 06:20:46 +00:00
Sylvia van Os c3947b0f6a Merge pull request #3237 from CatimaLoyalty/dependabot/github_actions/actions/checkout-7.0.1
Bump actions/checkout from 7.0.0 to 7.0.1
2026-07-30 08:19:02 +02:00
dependabot[bot] 84b17b17fe Bump actions/checkout from 7.0.0 to 7.0.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v7.0.0...v7.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-30 02:52:41 +00:00
Sylvia van Os 3956a1123e Merge pull request #3236 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-07-29 20:55:24 +02:00
Sylvia van Os f04185f6fd Merge pull request #2580 from CatimaLoyalty/fix/weblate_missingquantity_fix
Re-enable missingQuantity lint checks
2026-07-29 20:48:07 +02:00
Sylvia van Os 10d188ee40 Re-enable missingQuantity lint checks where possible
https://github.com/WeblateOrg/weblate/issues/7520
2026-07-29 20:32:12 +02:00
Priit Jõerüüt 63e2d3740f Translated using Weblate (Estonian)
Currently translated at 100.0% (324 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/et/
2026-07-29 20:04:49 +02:00
Damjan Gerl 672173437f Translated using Weblate (Slovenian)
Currently translated at 64.4% (107 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sl/
2026-07-29 20:04:46 +02:00
B o d o 2c851158f5 Translated using Weblate (German)
Currently translated at 100.0% (166 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2026-07-29 20:04:38 +02:00
Sylvain Pichon 367733676a Translated using Weblate (French)
Currently translated at 100.0% (166 of 166 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2026-07-29 20:04:38 +02:00
Damjan Gerl 538cd2078b Translated using Weblate (Slovenian)
Currently translated at 100.0% (324 of 324 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sl/
2026-07-29 20:04:37 +02:00
307 changed files with 4878 additions and 410 deletions

No files matched your search

+26 -15
View File
@@ -4,11 +4,7 @@ on:
push:
branches:
- main
- staging
- trying
pull_request:
branches:
- main
permissions:
actions: none
checks: none
@@ -30,25 +26,40 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
flavor: [Foss, Gplay]
flavor: [fdroidLegacy, foss, gplay]
steps:
- uses: actions/checkout@v7.0.0
- uses: gradle/actions/wrapper-validation@v6.2.0
- uses: actions/checkout@v7.0.1
- name: Ensure sentence-cased flavor variables
run: |
flavor="$(echo ${{ matrix.flavor }})"
echo "sentenceCasedFlavor=${flavor^}" >> "$GITHUB_ENV"
- uses: gradle/actions/wrapper-validation@v6.3.0
- name: set up OpenJDK 25
run: |
sudo apt-get update
sudo apt-get install -y openjdk-25-jdk-headless
sudo update-alternatives --auto java
- name: Build
run: ./gradlew assemble${{ matrix.flavor }}Release
run: ./gradlew assemble${sentenceCasedFlavor}Release
- name: Check for non-free libraries
run: |
wget -q https://codeberg.org/Katastima/apkscanner/releases/download/v0.0.7/apk-scanner_v0.0.7.jar
java -jar apk-scanner_v0.0.7.jar scan-apk "$(echo 'app/build/outputs/apk/${{ matrix.flavor }}/release/app-${{ matrix.flavor }}-release-unsigned.apk' | tr '[:upper:]' '[:lower:]')"
# Get Katastima APK scanner
wget -q https://codeberg.org/Katastima/apkscanner/releases/download/v0.0.9/apk-scanner_v0.0.9.jar
# Print pretty result
java -jar apk-scanner_v0.0.9.jar scan-apk "$(echo 'app/build/outputs/apk/${{ matrix.flavor }}/release/app-${{ matrix.flavor }}-release-unsigned.apk')"
# Check for offending libraries
# Exit code 4 = no result, which is what we want
set +e
java -jar apk-scanner_v0.0.9.jar scan-apk --json=yes "$(echo 'app/build/outputs/apk/${{ matrix.flavor }}/release/app-${{ matrix.flavor }}-release-unsigned.apk')" | jq --exit-status '.libraryCheckResult.offendingLibraries[] | select((.libraryId == "/org/acra" and .license == "Apache-2.0")|not)'
if [ "$?" -ne 4 ]; then
echo "Bad libraries found!"
exit 1
fi
set -e
- name: Check lint
run: ./gradlew lint${{ matrix.flavor }}Release
run: ./gradlew lint${sentenceCasedFlavor}Release
- name: Run unit tests
run: timeout 5m ./gradlew test${{ matrix.flavor }}DebugUnitTest || { ./gradlew --stop && timeout 5m ./gradlew test${{ matrix.flavor }}DebugUnitTest; }
run: timeout 5m ./gradlew test${sentenceCasedFlavor}DebugUnitTest || { ./gradlew --stop && timeout 5m ./gradlew test${sentenceCasedFlavor}DebugUnitTest; }
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
@@ -59,16 +70,16 @@ jobs:
with:
api-level: 23
arch: x86_64
script: ./gradlew connected${{ matrix.flavor }}DebugAndroidTest
script: ./gradlew connected${sentenceCasedFlavor}DebugAndroidTest
- name: Run instrumented tests (API 35)
uses: ReactiveCircus/android-emulator-runner@v2.38.0
with:
api-level: 35
arch: x86_64
script: ./gradlew connected${{ matrix.flavor }}DebugAndroidTest
script: ./gradlew connected${sentenceCasedFlavor}DebugAndroidTest
- name: Archive test results
if: always()
uses: actions/upload-artifact@v7.0.1
with:
name: test-results-flavor${{ matrix.flavor }}
name: test-results-flavor-${{ matrix.flavor }}
path: app/build/reports
+2 -2
View File
@@ -19,9 +19,9 @@ jobs:
steps:
- name: Checkout repo
id: checkout
uses: actions/checkout@v7.0.0
uses: actions/checkout@v7.0.1
- name: Setup Python
uses: actions/setup-python@v6.3.0
uses: actions/setup-python@v7.0.0
with:
python-version: '3.x'
- name: Run converter script
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
steps:
- name: Checkout repo
id: checkout
uses: actions/checkout@v7.0.0
uses: actions/checkout@v7.0.1
- name: Update contributors
id: update_contributors
uses: TheLastProject/contributors-to-file-action@v3.2.0
@@ -17,7 +17,7 @@ jobs:
generate-feature-graphic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/checkout@v7.0.1
- name: Install requirements
run: |
sudo apt-get update
+1 -5
View File
@@ -4,11 +4,7 @@ on:
push:
branches:
- main
- staging
- trying
pull_request:
branches:
- main
permissions:
actions: none
checks: none
@@ -27,7 +23,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/checkout@v7.0.1
- name: Fail on bad translations
run: if grep -ri "&lt;xliff" app/src/main/res/values*/strings.xml; then echo "Invalidly escaped translations found"; exit 1; fi
- name: Check app_name consistency
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
update-locales:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/checkout@v7.0.1
- name: Add new locales
run: .scripts/new-locales.py
- name: Update locales
+53
View File
@@ -0,0 +1,53 @@
name: Android Wear CI
on:
workflow_dispatch:
push:
branches:
- main
pull_request:
permissions:
actions: none
checks: none
contents: read
deployments: none
discussions: none
id-token: none
issues: none
packages: none
pages: none
pull-requests: none
repository-projects: none
security-events: none
statuses: none
env:
JAVA_HOME: /usr/lib/jvm/java-25-openjdk-amd64
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.1
- uses: gradle/actions/wrapper-validation@v6.3.0
- name: set up OpenJDK 25
run: |
sudo apt-get update
sudo apt-get install -y openjdk-25-jdk-headless
sudo update-alternatives --auto java
- name: Build
run: ./gradlew :wear:assembleRelease
- name: Check for non-free libraries
run: |
# Get Katastima APK scanner
wget -q https://codeberg.org/Katastima/apkscanner/releases/download/v0.0.9/apk-scanner_v0.0.9.jar
# Print pretty result
java -jar apk-scanner_v0.0.9.jar scan-apk "wear/build/outputs/apk/release/wear-release-unsigned.apk"
# Check for offending libraries
# Exit code 4 = no result, which is what we want
set +e
java -jar apk-scanner_v0.0.9.jar scan-apk --json=yes "wear/build/outputs/apk/release/wear-release-unsigned.apk" | jq --exit-status '.libraryCheckResult.offendingLibraries[]'
if [ "$?" -ne 4 ]; then
echo "Bad libraries found!"
exit 1
fi
set -e
- name: Check lint
run: ./gradlew :wear:lintRelease
+3
View File
@@ -19,6 +19,8 @@
/app/*.log
/app/build
/app/release
/wear/build
/wear/release
/.idea/*
!/.idea/icon.svg
# Bundle
@@ -28,3 +30,4 @@
# Catima-specific
SHA256SUMS
/shared/build
+1 -1
View File
@@ -15,7 +15,7 @@ with open('CHANGELOG.md') as changelog:
changelogs[version_code] = text
text = []
match = re.match("## \S* - (\d*).*", line)
match = re.match("## .+ - (\d*).*", line)
if not match:
raise ValueError(f"Invalid version line: {line}")
version_code = match.group(1)
+21
View File
@@ -1,5 +1,26 @@
# Changelog
## Unreleased (Android) - 1003
- Fix list widget not updating when opening a card from it
- Fix opening pkpass/pdf files from network locations sometimes failing
## v1.0.1 (Wear OS) - 201 (2026-08-28)
- Add scroll bar to card list
- Add splash screen
- Fix some padding issues
- Remove barcode value from card view
## v2.45.0 (Android) - 1002 (2026-08-12)
- Add a Bluetooth server for Wear OS companion app support
- Remove embedded CHANGELOG
## v1.0.0 (Wear OS) - 200 (2026-08-12)
- Initial release of Wear OS companion app
## v2.44.0 - 168 (2026-07-28)
- Add option to open image in internal image viewer in gallery
+2 -1
View File
@@ -1,11 +1,12 @@
**Last updated**
April 29 2026
August 7 2026
# Privacy Policy
Catima does not collect or transmit any personal information.
To ensure correct app functionality, we require access to the following:
- Bluetooth: When using Catima together with the Wear OS companion app, Bluetooth access is needed to be able to communicate to Wear OS. This may show up as "find, connect to, and determine the relative position of nearby devices" because Android considers Bluetooth as part of location, but location information is never collected or used. The app can still be used when Bluetooth permission is denied, but you will not be able to use the Wear OS companion app.
- Camera: We need access to your camera to be able to scan barcodes. The app can still be used when camera access is denied, but you will have to manually type the barcode information.
- NFC: We need NFC access to temporarily disable NFC while showing a barcode to prevent NFC payment apps from triggering (such as when using a check-in gates which support both barcodes and NFC payments). This functionality can be disabled in settings.
- Storage (Android 5 and 6 only): We need access to your device storage to create or import backups. The app can still be used when storage access is denied, but you will not be able to create or import backups.
+11 -4
View File
@@ -19,8 +19,8 @@ android {
applicationId = "me.hackerchick.catima"
minSdk = 23
targetSdk = 36
versionCode = 168
versionName = "2.44.0"
versionCode = 1002
versionName = "2.45.0"
vectorDrawables.useSupportLibrary = true
multiDexEnabled = true
@@ -59,8 +59,14 @@ android {
dimension = "type"
isDefault = true
}
create("fdroidLegacy") {
dimension = "type"
versionNameSuffix = " (F-Droid)"
versionCode = defaultConfig.versionCode!! - 1
}
create("gplay") {
dimension = "type"
versionNameSuffix = " (Google)"
// Google doesn't allow donation links
buildConfigField("boolean", "showDonate", "false")
@@ -113,6 +119,8 @@ android {
}
dependencies {
implementation(project(":shared"))
// AndroidX
implementation(libs.androidx.appcompat.appcompat)
implementation(libs.androidx.constraintlayout.constraintlayout)
@@ -165,7 +173,6 @@ dependencies {
tasks.register("copyRawResFiles", Copy::class) {
from(
layout.projectDirectory.file("../CHANGELOG.md"),
layout.projectDirectory.file("../PRIVACY.md")
)
into(layout.projectDirectory.dir("src/main/res/raw"))
@@ -173,7 +180,7 @@ tasks.register("copyRawResFiles", Copy::class) {
}.also {
tasks.preBuild.dependsOn(it)
tasks.getByName<Delete>("clean") {
val filesNamesToDelete = listOf("CHANGELOG", "PRIVACY")
val filesNamesToDelete = listOf("PRIVACY")
filesNamesToDelete.forEach { fileName ->
delete(layout.projectDirectory.file("src/main/res/raw/${fileName.lowercase()}.md"))
}
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Catima Ladění</string>
<string name="app_name">Catima (ladicí verze)</string>
</resources>
+2 -2
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Catima Debug</string>
</resources>
<string name="app_name">Catima silumine ja veaotsing</string>
</resources>
+27
View File
@@ -13,6 +13,12 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="23" />
<uses-feature
@@ -226,5 +232,26 @@
<action android:name="android.service.controls.ControlsProviderService" />
</intent-filter>
</service>
<service
android:name=".wearos.BluetoothServerService"
android:exported="false"
android:foregroundServiceType="connectedDevice" />
<activity
android:name=".wearos.BluetoothPairingActivity"
android:exported="false"
android:excludeFromRecents="true"
android:launchMode="singleTop"
android:theme="@style/AppTheme.Transparent" />
<receiver
android:name=".wearos.BluetoothPairingReceiver"
android:exported="false">
<intent-filter>
<action android:name="protect.card_locker.wearos.action.ALLOW_DEVICE" />
<action android:name="protect.card_locker.wearos.action.BLOCK_DEVICE" />
</intent-filter>
</receiver>
</application>
</manifest>
@@ -69,16 +69,7 @@ fun AboutScreenContent(
stringResource(R.string.version_history),
content.versionHistory,
modifier = Modifier.testTag("card_version_history"),
onClickUrl = "https://catima.app/changelog/",
onClickDialogText = AnnotatedString.fromHtml(
htmlString = content.historyHtml,
linkStyles = TextLinkStyles(
style = SpanStyle(
textDecoration = TextDecoration.Underline,
color = MaterialTheme.colorScheme.primary
)
)
)
onClickUrl = "https://catima.app/changelog/"
)
CatimaAboutSection(
stringResource(R.string.credits),
@@ -62,18 +62,6 @@ public class AboutContent {
return contributors.replace("\n", "<br />");
}
public String getHistoryHtml() {
String versionHistory;
try {
versionHistory = Utils.readTextFile(context, R.raw.changelog)
.replace("# Changelog\n\n", "");
} catch (IOException ignored) {
return "";
}
return Utils.linkify(Utils.basicMDToHTML(versionHistory))
.replace("\n", "<br />");
}
public String getLicenseHtml() {
try {
return Utils.readTextFile(context, R.raw.license);
@@ -14,6 +14,7 @@ import android.widget.RemoteViews
import androidx.core.widget.RemoteViewsCompat
import protect.card_locker.DBHelper.LoyaltyCardArchiveFilter
import protect.card_locker.cardview.LoyaltyCardViewActivity
import protect.card_locker.shared.ForegroundColorHelper
class ListWidget : AppWidgetProvider() {
fun updateAll(context: Context) {
@@ -100,7 +101,7 @@ class ListWidget : AppWidgetProvider() {
// Note: Android 5 will not use bitmaps
val remoteViews = RemoteViews(context.packageName, R.layout.list_widget_item).apply {
val headerColor = Utils.getHeaderColor(context, loyaltyCard)
val foreground = if (Utils.needsDarkForeground(headerColor)) Color.BLACK else Color.WHITE
val foreground = if (ForegroundColorHelper.needsDarkForeground(headerColor)) Color.BLACK else Color.WHITE
setInt(R.id.item_container_foreground, "setBackgroundColor", headerColor)
val icon = loyaltyCard.getImageThumbnail(context)
// FIXME: The icon flow causes a crash up to Android 12L, so force anything below 33 down this path
@@ -91,6 +91,7 @@ import java.util.concurrent.Callable;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.databinding.LayoutChipChoiceBinding;
import protect.card_locker.databinding.LoyaltyCardEditActivityBinding;
import protect.card_locker.shared.ForegroundColorHelper;
import protect.card_locker.viewmodels.LoyaltyCardEditActivityViewModel;
public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements BarcodeImageWriterResultCallback, ColorPickerDialogListener {
@@ -896,8 +897,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
Integer headerColor = viewModel.getLoyaltyCard().headerColor;
if (headerColor != null) {
thumbnail.setOnClickListener(new ChooseCardImage());
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
thumbnailEditIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
}
onResuming = false;
@@ -919,18 +920,18 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
setLoyaltyCardHeaderColor(headerColor);
thumbnail.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnail.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
thumbnailEditIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
} else {
generateIcon(storeFieldEdit.getText().toString().trim());
Integer headerColor = viewModel.getLoyaltyCard().headerColor;
if (headerColor != null) {
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
thumbnailEditIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
}
}
}
@@ -1,9 +1,9 @@
package protect.card_locker;
import android.app.Application;
import android.content.Intent;
import androidx.appcompat.app.AppCompatDelegate;
import org.acra.ACRA;
import org.acra.config.CoreConfigurationBuilder;
import org.acra.config.DialogConfigurationBuilder;
@@ -11,6 +11,7 @@ import org.acra.config.MailSenderConfigurationBuilder;
import org.acra.data.StringFormat;
import protect.card_locker.preferences.Settings;
import protect.card_locker.wearos.WearSyncServiceManager;
public class LoyaltyCardLockerApplication extends Application {
@@ -41,5 +42,10 @@ public class LoyaltyCardLockerApplication extends Application {
// Set theme
Settings settings = new Settings(this);
AppCompatDelegate.setDefaultNightMode(settings.getTheme());
// Start Bluetooth server for Wear OS companion if enabled.
// The service checks BLUETOOTH_CONNECT itself and stops if the permission is missing.
// The permission is requested from the launcher Activity when the UI resumes.
WearSyncServiceManager.INSTANCE.synchronize(this, null);
}
}
@@ -23,7 +23,9 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.core.content.edit
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayout
@@ -37,9 +39,12 @@ import protect.card_locker.databinding.MainActivityBinding
import protect.card_locker.databinding.SortingOptionBinding
import protect.card_locker.preferences.Settings
import protect.card_locker.preferences.SettingsActivity
import protect.card_locker.wearos.WearSyncPermissionRequester
import java.io.UnsupportedEncodingException
import java.util.concurrent.atomic.AtomicInteger
import androidx.core.content.edit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
private lateinit var binding: MainActivityBinding
@@ -61,6 +66,7 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
private lateinit var mUpdateLoyaltyCardListRunnable: Runnable
private lateinit var mBarcodeScannerLauncher: ActivityResultLauncher<Intent>
private lateinit var mSettingsLauncher: ActivityResultLauncher<Intent>
private lateinit var mWearSyncPermissionRequester: WearSyncPermissionRequester
private val mCurrentActionModeCallback: ActionMode.Callback = object : ActionMode.Callback {
override fun onCreateActionMode(inputMode: ActionMode, inputMenu: Menu?): Boolean {
@@ -318,6 +324,8 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
}
}
mWearSyncPermissionRequester = WearSyncPermissionRequester(this, this)
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (mSearchView != null && !mSearchView!!.isIconified) {
@@ -394,6 +402,8 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
val settings = Settings(this)
layoutManager.setSpanCount(settings.getPreferredColumnCount())
}
mWearSyncPermissionRequester.synchronize()
}
private fun displayCardSetupOptions(menu: Menu, shouldShow: Boolean) {
@@ -467,11 +477,7 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
ShortcutHelper.updateShortcuts(mAdapter.mContext)
}
private fun processParseResultList(
parseResultList: MutableList<ParseResult?>,
group: String?,
closeAppOnNoBarcode: Boolean
) {
private fun processParseResultList(parseResultList: MutableList<ParseResult?>) {
require(!parseResultList.isEmpty()) { "parseResultList may not be empty" }
Utils.makeUserChooseParseResultFromList(
@@ -482,17 +488,12 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
val intent =
Intent(applicationContext, LoyaltyCardEditActivity::class.java)
val bundle = parseResult.toLoyaltyCardBundle(this@MainActivity)
if (group != null) {
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group)
}
intent.putExtras(bundle)
startActivity(intent)
}
override fun onUserDismissedSelector() {
if (closeAppOnNoBarcode) {
finish()
}
finish()
}
})
}
@@ -505,13 +506,11 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
return
}
val parseResultList: MutableList<ParseResult?>?
// Check for shared text
if (receivedAction == Intent.ACTION_SEND && receivedType == "text/plain") {
val loyaltyCard = LoyaltyCard()
loyaltyCard.setCardId(intent.getStringExtra(Intent.EXTRA_TEXT)!!)
parseResultList = mutableListOf(ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard))
processParseResultList(mutableListOf(ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard)))
} else {
// Parse whatever file was sent, regardless of opening or sharing
val data: Uri? = when (receivedAction) {
@@ -527,36 +526,54 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
}
}
if (receivedType.startsWith("image/")) {
parseResultList = Utils.retrieveBarcodesFromImage(this, data)
} else if (receivedType == "application/pdf") {
parseResultList = Utils.retrieveBarcodesFromPdf(this, data)
} else if (mutableListOf<String?>(
"application/vnd.apple.pkpass",
"application/vnd-com.apple.pkpass"
).contains(receivedType)
) {
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data)
} else if (receivedType == "application/vnd.espass-espass") {
// FIXME: espass is not pkpass
// However, several users stated in https://github.com/CatimaLoyalty/Android/issues/2197 that the formats are extremely similar to the point they could rename an .espass file to .pkpass and have it imported
// So it makes sense to "unofficially" treat it as a PKPASS for now, even though not completely correct
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data)
} else if (receivedType == "application/vnd.apple.pkpasses") {
parseResultList = Utils.retrieveBarcodesFromPkPasses(this, data)
} else {
Log.e(TAG, "Wrong mime-type")
return
importFile(data, receivedType)
}
}
private fun importFile(data: Uri?, receivedType: String) {
lifecycleScope.launch {
val parseResultList: MutableList<ParseResult?> = withContext(Dispatchers.IO) {
try {
when {
receivedType.startsWith("image/") ->
Utils.retrieveBarcodesFromImage(this@MainActivity, data)
receivedType == "application/pdf" ->
Utils.retrieveBarcodesFromPdf(this@MainActivity, data)
receivedType == "application/vnd.apple.pkpass" ||
receivedType == "application/vnd-com.apple.pkpass" ->
Utils.retrieveBarcodesFromPkPass(this@MainActivity, data)
// FIXME: espass is not pkpass
// However, several users stated in https://github.com/CatimaLoyalty/Android/issues/2197 that the formats are extremely similar to the point they could rename an .espass file to .pkpass and have it imported
// So it makes sense to "unofficially" treat it as a PKPASS for now, even though not completely correct
receivedType == "application/vnd.espass-espass" ->
Utils.retrieveBarcodesFromPkPass(this@MainActivity, data)
receivedType == "application/vnd.apple.pkpasses" ->
Utils.retrieveBarcodesFromPkPasses(this@MainActivity, data)
else -> {
Log.e(TAG, "Wrong mime-type")
return@withContext null
}
}
} catch (e: Exception) {
Log.e(TAG, "Parsing failed: $e");
e.printStackTrace();
Utils.showToast(this@MainActivity, R.string.errorReadingFile, Toast.LENGTH_LONG);
return@withContext null;
}
} ?: return@launch
if (parseResultList.isEmpty()) {
Utils.showToast(this@MainActivity, R.string.noBarcodeFound, Toast.LENGTH_LONG);
finish()
return@launch
}
}
// Give up if we should parse but there is nothing to parse
if (parseResultList == null || parseResultList.isEmpty()) {
finish()
return
processParseResultList(parseResultList)
}
processParseResultList(parseResultList, null, true)
}
private fun extractIntentFields(intent: Intent) {
@@ -0,0 +1,11 @@
package protect.card_locker
object NotificationInfo {
object WearBluetooth {
const val NOTIFICATION_ID = 1001
const val CHANNEL_ID = "catima_wear_bt"
const val PAIRING_CHANNEL_ID = "catima_wear_bt_pairing"
const val CRITICAL_ERROR_NOTIFICATION_ID = 1002
const val CRITICAL_ERROR_CHANNEL_ID = "catima_wear_bt_critical_error"
}
}
@@ -22,6 +22,8 @@ class PkpassesParser(context: Context, uri: Uri?) {
throw IOException(context.getString(R.string.errorReadingFile))
}
var hasPkpassFiles = false
try {
mContext.contentResolver.openInputStream(uri).use { inputStream ->
ZipInputStream(inputStream).use { zipInputStream ->
@@ -42,6 +44,9 @@ class PkpassesParser(context: Context, uri: Uri?) {
// Ignore non-pkpass files
if (!localFileHeader.fileName.endsWith(".pkpass")) continue
// Mark as found
hasPkpassFiles = true
// Extract .pkpass (.zip) inside .pkpasses to cache directory
val tempFileName = "pkpassparser_" + System.currentTimeMillis() + "_" + localFileHeader.fileName
val tempFile = Utils.copyToTempFile(mContext, zipInputStream, tempFileName)
@@ -61,6 +66,11 @@ class PkpassesParser(context: Context, uri: Uri?) {
} catch (e: Exception) {
throw e
}
if (!hasPkpassFiles) {
Log.d(TAG, "No pkpass files found")
throw IOException(mContext.getString(R.string.errorReadingFile))
}
}
fun getPkpassParsers(): ArrayList<PkpassParser> {
@@ -30,6 +30,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.zxing.DecodeHintType
import com.google.zxing.ResultPoint
@@ -37,6 +38,9 @@ import com.journeyapps.barcodescanner.BarcodeCallback
import com.journeyapps.barcodescanner.BarcodeResult
import com.journeyapps.barcodescanner.CaptureManager
import com.journeyapps.barcodescanner.DecoratedBarcodeView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import protect.card_locker.databinding.CustomBarcodeScannerBinding
import protect.card_locker.databinding.ScanActivityBinding
@@ -322,15 +326,29 @@ class ScanActivity : CatimaAppCompatActivity() {
private fun handleActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
super.onActivityResult(resultCode, resultCode, intent)
val parseResultList: List<ParseResult> =
Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this)
lifecycleScope.launch(Dispatchers.IO) {
val parseResultList =
Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this@ScanActivity)
if (parseResultList.isEmpty()) {
setScannerActive(true)
return
withContext(Dispatchers.Main) {
if (parseResultList == null) {
Utils.showToast(this@ScanActivity, R.string.errorReadingFile, Toast.LENGTH_LONG)
setScannerActive(true)
return@withContext
}
if (parseResultList.isEmpty()) {
Utils.showToast(this@ScanActivity, R.string.noBarcodeFound, Toast.LENGTH_LONG)
setScannerActive(true)
return@withContext
}
processParseResultList(parseResultList)
}
}
}
private fun processParseResultList(parseResultList: List<ParseResult>) {
Utils.makeUserChooseParseResultFromList(
this,
parseResultList,
@@ -18,6 +18,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.LinkedList;
import protect.card_locker.cardview.LoyaltyCardViewActivity;
import protect.card_locker.shared.ForegroundColorHelper;
public class ShortcutHelper {
/**
@@ -102,7 +103,7 @@ public class ShortcutHelper {
if (iconBitmap == null) {
iconBitmap = Utils.generateIcon(context, loyaltyCard, true).getLetterTile();
} else {
iconBitmap = createAdaptiveBitmap(iconBitmap, Utils.needsDarkForeground(Utils.getHeaderColor(context, loyaltyCard)) ? Color.BLACK : Color.WHITE);
iconBitmap = createAdaptiveBitmap(iconBitmap, ForegroundColorHelper.Companion.needsDarkForeground(Utils.getHeaderColor(context, loyaltyCard)) ? Color.BLACK : Color.WHITE);
}
IconCompat icon = IconCompat.createWithAdaptiveBitmap(iconBitmap);
@@ -19,6 +19,8 @@ import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraManager;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.text.Layout;
@@ -39,7 +41,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.RawRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.graphics.ColorUtils;
import androidx.core.graphics.Insets;
import androidx.core.os.LocaleListCompat;
import androidx.core.view.ViewCompat;
@@ -95,6 +96,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import protect.card_locker.preferences.Settings;
import protect.card_locker.shared.ForegroundColorHelper;
public class Utils {
private static final String TAG = "Catima";
@@ -115,11 +117,19 @@ public class Utils {
public static final String CARD_IMAGE_FILENAME_REGEX = "^(card_)(\\d+)(_(?:front|back|icon)\\.png)$";
static final double LUMINANCE_MIDPOINT = 0.5;
static final int BITMAP_SIZE_SMALL = 512;
static final int BITMAP_SIZE_BIG = 1600;
// Displays the toast immediately on the main UI loop, or asks Android to display it there otherwise.
static public void showToast(Context context, int message, int duration) {
if (Looper.myLooper() == Looper.getMainLooper()) {
Toast.makeText(context, message, duration).show();
} else {
new Handler(Looper.getMainLooper()).post(
() -> Toast.makeText(context, message, duration).show());
}
}
static public LetterBitmap generateIcon(Context context, LoyaltyCard loyaltyCard, boolean forShortcut) {
return generateIcon(context, loyaltyCard.store, loyaltyCard.headerColor, forShortcut);
}
@@ -147,57 +157,38 @@ public class Utils {
}
return new LetterBitmap(context, store, store,
tileLetterFontSize, pixelSize, pixelSize, backgroundColor, needsDarkForeground(backgroundColor) ? Color.BLACK : Color.WHITE);
tileLetterFontSize, pixelSize, pixelSize, backgroundColor, ForegroundColorHelper.Companion.needsDarkForeground(backgroundColor) ? Color.BLACK : Color.WHITE);
}
static public boolean needsDarkForeground(Integer backgroundColor) {
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
}
static public List<ParseResult> retrieveBarcodesFromImage(Context context, Uri uri) {
static public List<ParseResult> retrieveBarcodesFromImage(Context context, Uri uri) throws FileNotFoundException {
Log.i(TAG, "Received image file with possible barcode");
if (uri == null) {
Log.e(TAG, "Uri did not contain any data");
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
return new ArrayList<>();
throw new FileNotFoundException("Uri did not contain any data");
}
Bitmap bitmap;
try {
bitmap = retrieveImageFromUri(context, uri);
} catch (IOException e) {
Log.e(TAG, "Error getting data from image file");
e.printStackTrace();
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
return new ArrayList<>();
throw new IllegalArgumentException("Error reading image file", e);
}
List<ParseResult> barcodesFromBitmap = getBarcodesFromBitmap(bitmap);
if (barcodesFromBitmap.isEmpty()) {
Log.i(TAG, "No barcode found in image file");
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
}
return barcodesFromBitmap;
// Returns barcodes or an empty list if nothing found
return getBarcodesFromBitmap(bitmap);
}
static public List<ParseResult> retrieveBarcodesFromPkPass(Context context, Uri uri) {
static public List<ParseResult> retrieveBarcodesFromPkPass(Context context, Uri uri) throws FileNotFoundException {
Log.i(TAG, "Received Pkpass file with possible barcode");
if (uri == null) {
Log.e(TAG, "Pkpass did not contain any data");
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
return new ArrayList<>();
throw new FileNotFoundException("Uri did not contain any data");
}
PkpassParser pkpassParser;
try {
pkpassParser = new PkpassParser(context, uri);
} catch (Exception e) {
Log.e(TAG, "Error reading pkpass file", e);
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
return new ArrayList<>();
throw new IllegalArgumentException("Error reading pkpass file", e);
}
List<String> locales = pkpassParser.listLocales();
@@ -205,9 +196,7 @@ public class Utils {
try {
return Collections.singletonList(new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(null)));
} catch (Exception e) {
Log.e(TAG, "Error calling toLoyaltyCard on pkpass file", e);
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
return new ArrayList<>();
throw new IllegalArgumentException("Error calling toLoyaltyCard on pkpass file", e);
}
}
@@ -217,9 +206,7 @@ public class Utils {
try {
parseResult = new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(locale));
} catch (Exception e) {
Log.e(TAG, "Error calling toLoyaltyCard on pkpass file", e);
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
return new ArrayList<>();
throw new IllegalArgumentException("Error calling toLoyaltyCard on pkpass file", e);
}
parseResult.setNote(locale);
parseResultList.add(parseResult);
@@ -228,21 +215,17 @@ public class Utils {
return parseResultList;
}
static public List<ParseResult> retrieveBarcodesFromPkPasses(Context context, Uri uri) {
static public List<ParseResult> retrieveBarcodesFromPkPasses(Context context, Uri uri) throws FileNotFoundException {
Log.i(TAG, "Received Pkpasses file with possible barcode");
if (uri == null) {
Log.e(TAG, "Pkpasses did not contain any data");
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
return new ArrayList<>();
throw new FileNotFoundException("Uri did not contain any data");
}
PkpassesParser pkpassesParser;
try {
pkpassesParser = new PkpassesParser(context, uri);
} catch (Exception e) {
Log.e(TAG, "Error reading pkpasses file", e);
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
return new ArrayList<>();
throw new IllegalArgumentException("Error reading pkpasses file", e);
}
List<ParseResult> parseResultList = new ArrayList<>();
@@ -254,9 +237,7 @@ public class Utils {
try {
parseResult = new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(null));
} catch (Exception e) {
Log.e(TAG, "Error calling toLoyaltyCard on pkpass file", e);
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
return new ArrayList<>();
throw new IllegalArgumentException("Error calling toLoyaltyCard on pkpass file", e);
}
parseResult.setNote(String.format(context.getString(R.string.cardWithNumber), i+1));
parseResultList.add(parseResult);
@@ -265,9 +246,7 @@ public class Utils {
try {
parseResult = new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(locale));
} catch (Exception e) {
Log.e(TAG, "Error calling toLoyaltyCard on pkpass file", e);
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
return new ArrayList<>();
throw new IllegalArgumentException("Error calling toLoyaltyCard on pkpass file", e);
}
parseResult.setNote(String.format(context.getString(R.string.cardWithNumberAndLocale), i+1, locale));
parseResultList.add(parseResult);
@@ -280,12 +259,10 @@ public class Utils {
return parseResultList;
}
static public List<ParseResult> retrieveBarcodesFromPdf(Context context, Uri uri) {
static public List<ParseResult> retrieveBarcodesFromPdf(Context context, Uri uri) throws FileNotFoundException {
Log.i(TAG, "Received PDF file with possible barcode");
if (uri == null) {
Log.e(TAG, "Uri did not contain any data");
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
return new ArrayList<>();
throw new FileNotFoundException("Uri did not contain any data");
}
ParcelFileDescriptor parcelFileDescriptor = null;
@@ -320,8 +297,7 @@ public class Utils {
}
}
} catch (IOException e) {
Log.e(TAG, "Error reading PDF file", e);
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
throw new IllegalArgumentException("Error reading PDF file", e);
} finally {
// Resource handling
if (renderer != null) {
@@ -336,10 +312,7 @@ public class Utils {
}
}
if (barcodesFromPdfPages.isEmpty()) {
Log.i(TAG, "No barcode found in pdf file");
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
}
// Returns barcodes or an empty list if nothing found
return barcodesFromPdfPages;
}
@@ -354,6 +327,7 @@ public class Utils {
* @param context
* @return List<ParseResult>
*/
@Nullable
static public List<ParseResult> parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) {
String contents;
String format;
@@ -362,28 +336,35 @@ public class Utils {
return new ArrayList<>();
}
if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) {
return retrieveBarcodesFromImage(context, intent.getData());
}
if (requestCode == Utils.BARCODE_IMPORT_FROM_PDF_FILE) {
return retrieveBarcodesFromPdf(context, intent.getData());
}
if (requestCode == Utils.BARCODE_IMPORT_FROM_PKPASS_FILE) {
Uri intentData = intent.getData();
if (intentData == null) {
Log.e(TAG, "Uri did not contain any data");
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
return new ArrayList<>();
try {
if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) {
return retrieveBarcodesFromImage(context, intent.getData());
}
if (Objects.equals(context.getContentResolver().getType(intentData), "application/vnd.apple.pkpasses")) {
return retrieveBarcodesFromPkPasses(context, intentData);
if (requestCode == Utils.BARCODE_IMPORT_FROM_PDF_FILE) {
return retrieveBarcodesFromPdf(context, intent.getData());
}
return retrieveBarcodesFromPkPass(context, intentData);
if (requestCode == Utils.BARCODE_IMPORT_FROM_PKPASS_FILE) {
Uri intentData = intent != null ? intent.getData() : null;
if (intentData == null) {
throw new FileNotFoundException("Uri did not contain any data");
}
// Mime type info is not always available. So we're going to first guess it's a pkpass file and if that fails, try pkpasses
try {
return retrieveBarcodesFromPkPass(context, intentData);
} catch (Exception e) {
Log.e(TAG, "Error reading pkpass file, attempting as pkpasses file", e);
return retrieveBarcodesFromPkPasses(context, intentData);
}
}
} catch (Exception e) {
// Return null instead of an empty list to differentiate between error or no result
Log.e(TAG, "Parsing failed: " + e);
e.printStackTrace();
return null;
}
if (requestCode == Utils.BARCODE_SCAN || requestCode == Utils.SELECT_BARCODE_REQUEST) {
@@ -1070,7 +1051,7 @@ public class Utils {
if (icon != null) {
// Use header colour to decide if this image will need a white or black background
backgroundOrIcon.setBackgroundColor(needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
backgroundOrIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
// Ensure correct cropping style
backgroundOrIcon.setScaleType(Utils.getRecommendedScaleTypeForThumbnailImage(icon));
@@ -1100,7 +1081,7 @@ public class Utils {
// Actually set the text and colour
textWhenNoImage.setVisibility(View.VISIBLE);
textWhenNoImage.setText(loyaltyCard.store);
textWhenNoImage.setTextColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
textWhenNoImage.setTextColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
}
return headerColor;
@@ -54,6 +54,7 @@ import protect.card_locker.cardimageview.LoyaltyCardImageViewActivity;
import protect.card_locker.databinding.LoyaltyCardViewLayoutBinding;
import protect.card_locker.preferences.Settings;
import protect.card_locker.preferences.SettingsActivity;
import protect.card_locker.shared.ForegroundColorHelper;
public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements BarcodeImageWriterResultCallback {
private LoyaltyCardViewLayoutBinding binding;
@@ -95,6 +96,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
private long initTime = System.currentTimeMillis();
private final ListWidget listWidget = new ListWidget();
private enum AdjacentCardDirection {
PREVIOUS,
NEXT
@@ -453,6 +456,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
invalidateOptionsMenu();
ShortcutHelper.updateShortcuts(this);
listWidget.updateAll(LoyaltyCardViewActivity.this);
}
@Override
@@ -598,20 +602,20 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
// Set bottomAppBar and system navigation bar color
binding.bottomAppBar.setBackgroundColor(darkenedColor);
Utils.setNavigationBarColor(null, window, darkenedColor, Utils.needsDarkForeground(darkenedColor));
Utils.setNavigationBarColor(null, window, darkenedColor, ForegroundColorHelper.Companion.needsDarkForeground(darkenedColor));
int complementaryColor = Utils.getComplementaryColor(darkenedColor);
binding.fabEdit.setBackgroundTintList(ColorStateList.valueOf(complementaryColor));
Drawable editButtonIcon = binding.fabEdit.getDrawable();
editButtonIcon.mutate();
editButtonIcon.setTint(Utils.needsDarkForeground(complementaryColor) ? Color.BLACK : Color.WHITE);
editButtonIcon.setTint(ForegroundColorHelper.Companion.needsDarkForeground(complementaryColor) ? Color.BLACK : Color.WHITE);
binding.fabEdit.setImageDrawable(editButtonIcon);
Bitmap icon = loyaltyCard.getImageThumbnail(this);
Utils.setIconOrTextWithBackground(this, loyaltyCard, icon, binding.iconImage, binding.iconText, 1);
// If the background is very bright, we should use dark icons
backgroundNeedsDarkIcons = Utils.needsDarkForeground(backgroundHeaderColor);
backgroundNeedsDarkIcons = ForegroundColorHelper.Companion.needsDarkForeground(backgroundHeaderColor);
fixBottomAppBarImageButtonColor(binding.bottomAppBarInfoButton);
fixBottomAppBarImageButtonColor(binding.bottomAppBarPreviousButton);
@@ -774,7 +778,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
} else if (id == R.id.action_star_unstar) {
DBHelper.updateLoyaltyCardStarStatus(database, loyaltyCardId, loyaltyCard.starStatus == 0 ? 1 : 0);
new ListWidget().updateAll(LoyaltyCardViewActivity.this);
listWidget.updateAll(LoyaltyCardViewActivity.this);
// Re-init loyaltyCard with new data from DB
onResume();
@@ -785,7 +789,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
DBHelper.updateLoyaltyCardArchiveStatus(database, loyaltyCardId, 1);
Toast.makeText(LoyaltyCardViewActivity.this, R.string.archived, Toast.LENGTH_LONG).show();
new ListWidget().updateAll(LoyaltyCardViewActivity.this);
listWidget.updateAll(LoyaltyCardViewActivity.this);
// Re-init loyaltyCard with new data from DB
onResume();
@@ -810,7 +814,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
DBHelper.deleteLoyaltyCard(database, LoyaltyCardViewActivity.this, loyaltyCardId);
new ListWidget().updateAll(LoyaltyCardViewActivity.this);
listWidget.updateAll(LoyaltyCardViewActivity.this);
finish();
dialog.dismiss();
@@ -112,4 +112,8 @@ public class Settings {
public boolean useVolumeKeysForNavigation() {
return getBoolean(R.string.settings_key_use_volume_keys_navigation, false);
}
public boolean getWearSyncEnabled() {
return getBoolean(R.string.settings_key_wear_sync, false);
}
}
@@ -1,22 +1,42 @@
package protect.card_locker.preferences
import android.app.Activity
import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.MenuItem
import android.widget.LinearLayout
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import androidx.core.os.LocaleListCompat
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import protect.card_locker.BuildConfig
import protect.card_locker.CatimaAppCompatActivity
import protect.card_locker.MainActivity
import protect.card_locker.R
import protect.card_locker.Utils
import protect.card_locker.databinding.SettingsActivityBinding
import protect.card_locker.shared.BluetoothPermissionHelper
import protect.card_locker.shared.WearBluetoothSecurity
import protect.card_locker.wearos.BluetoothPairingNotificationManager
import protect.card_locker.wearos.WearSyncPermissionRequester
class SettingsActivity : CatimaAppCompatActivity() {
@@ -81,11 +101,62 @@ class SettingsActivity : CatimaAppCompatActivity() {
class SettingsFragment : PreferenceFragmentCompat() {
var mReloadMain: Boolean = false
private lateinit var wearSyncPermissionRequester: WearSyncPermissionRequester
private var currentDevicesDialog: AlertDialog? = null
private var currentDevicesDialogIsBlocked: Boolean = false
private val devicesChangedReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != BluetoothPairingNotificationManager.ACTION_DEVICES_CHANGED) {
return
}
findPreference<Preference>(getString(R.string.settings_key_wear_sync_devices))?.let { updateWearSyncAllowedDevicesSummary(it) }
findPreference<Preference>(getString(R.string.settings_key_wear_sync_blocked_devices))?.let { updateWearSyncBlockedDevicesSummary(it) }
if (currentDevicesDialog?.isShowing != true) {
return
}
val isBlocked = currentDevicesDialogIsBlocked
currentDevicesDialog?.dismiss()
if (isBlocked) {
showWearSyncBlockedDevicesDialog()
} else {
showWearSyncAllowedDevicesDialog()
}
}
}
override fun onResume() {
super.onResume()
wearSyncPermissionRequester.synchronize()
findPreference<Preference>(getString(R.string.settings_key_wear_sync_devices))?.let { updateWearSyncAllowedDevicesSummary(it) }
findPreference<Preference>(getString(R.string.settings_key_wear_sync_blocked_devices))?.let { updateWearSyncBlockedDevicesSummary(it) }
ContextCompat.registerReceiver(
requireContext(),
devicesChangedReceiver,
IntentFilter(BluetoothPairingNotificationManager.ACTION_DEVICES_CHANGED),
ContextCompat.RECEIVER_NOT_EXPORTED
)
}
override fun onPause() {
super.onPause()
requireContext().unregisterReceiver(devicesChangedReceiver)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
wearSyncPermissionRequester = WearSyncPermissionRequester(this, requireContext())
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences)
// Show pretty names and summaries
setupThemePreference()
setupOledDarkPreference()
setupLocalePreference()
setupCrashReporterPreference()
setupWearSyncPreference()
setupWearSyncDevicePreferences()
}
private fun setupThemePreference() {
val themePreference = findPreference<ListPreference>(getString(R.string.settings_key_theme))
themePreference!!.setOnPreferenceChangeListener { _, o ->
when (o.toString()) {
@@ -101,13 +172,17 @@ class SettingsActivity : CatimaAppCompatActivity() {
}
true
}
}
private fun setupOledDarkPreference() {
val oledDarkPreference = findPreference<Preference>(getString(R.string.settings_key_oled_dark))
oledDarkPreference!!.setOnPreferenceChangeListener { _, _ ->
refreshActivity(true)
refreshActivity()
true
}
}
private fun setupLocalePreference() {
val localePreference =
findPreference<ListPreference>(getString(R.string.settings_key_locale))!!
localePreference.let {
@@ -148,7 +223,7 @@ class SettingsActivity : CatimaAppCompatActivity() {
localePreference.setOnPreferenceChangeListener { _, newValue ->
// See corresponding comment in Utils.updateBaseContextLocale for Android 6- notes
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
refreshActivity(true)
refreshActivity()
return@setOnPreferenceChangeListener true
}
val newLocale = newValue as String
@@ -156,14 +231,205 @@ class SettingsActivity : CatimaAppCompatActivity() {
AppCompatDelegate.setApplicationLocales(if (newLocale.isEmpty()) LocaleListCompat.getEmptyLocaleList() else LocaleListCompat.create(Utils.stringToLocale(newLocale)))
true
}
}
private fun setupCrashReporterPreference() {
// Hide crash reporter settings on builds it's not enabled on
val crashReporterPreference = findPreference<Preference>("acra.enable")
crashReporterPreference!!.isVisible = BuildConfig.useAcraCrashReporter
}
private fun refreshActivity(reloadMain: Boolean) {
mReloadMain = reloadMain || mReloadMain
private fun setupWearSyncPreference() {
val wearSyncPreference = findPreference<SwitchPreferenceCompat>(getString(R.string.settings_key_wear_sync))
wearSyncPreference!!.setOnPreferenceChangeListener { preference, newValue ->
val enabled = newValue as Boolean
if (enabled) {
wearSyncPermissionRequester.onWearSyncChanged(true) { granted ->
if (granted) {
(preference as? SwitchPreferenceCompat)?.isChecked = true
} else {
showWearSyncPermissionDeniedSnackbar()
}
}
false
} else {
wearSyncPermissionRequester.onWearSyncChanged(false)
true
}
}
}
private fun setupWearSyncDevicePreferences() {
val allowedPreference = findPreference<Preference>(getString(R.string.settings_key_wear_sync_devices))
allowedPreference!!.setOnPreferenceClickListener {
showWearSyncAllowedDevicesDialog()
true
}
updateWearSyncAllowedDevicesSummary(allowedPreference)
val blockedPreference = findPreference<Preference>(getString(R.string.settings_key_wear_sync_blocked_devices))
blockedPreference!!.setOnPreferenceClickListener {
showWearSyncBlockedDevicesDialog()
true
}
updateWearSyncBlockedDevicesSummary(blockedPreference)
}
private fun updateWearSyncAllowedDevicesSummary(preference: Preference) {
val count = WearBluetoothSecurity.listTrustedDevices(requireContext()).size
preference.summary = if (count == 0) {
getString(R.string.settings_wear_sync_no_devices)
} else {
resources.getQuantityString(R.plurals.settings_wear_sync_devices_summary_count, count, count)
}
}
private fun updateWearSyncBlockedDevicesSummary(preference: Preference) {
val count = WearBluetoothSecurity.listBlockedDevices(requireContext()).size
preference.summary = if (count == 0) {
getString(R.string.settings_wear_sync_no_blocked_devices)
} else {
resources.getQuantityString(R.plurals.settings_wear_sync_blocked_devices_summary_count, count, count)
}
}
@SuppressLint("MissingPermission")
private fun showWearSyncDeviceListDialog(
titleRes: Int,
noDevicesRes: Int,
messageRes: Int,
addresses: Set<String>,
isBlocked: Boolean,
onRemove: (String, String) -> Unit
) {
val context = requireContext()
if (!BluetoothPermissionHelper.isBluetoothConnectGranted(context)) {
showWearSyncPermissionDeniedSnackbar()
return
}
val knownAddresses = addresses.toList()
currentDevicesDialog?.dismiss()
currentDevicesDialogIsBlocked = isBlocked
if (knownAddresses.isEmpty()) {
currentDevicesDialog = MaterialAlertDialogBuilder(context)
.setTitle(titleRes)
.setMessage(noDevicesRes)
.setOnDismissListener {
currentDevicesDialog = null
currentDevicesDialogIsBlocked = false
}
.setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() }
.show()
return
}
val adapter = context.getSystemService(BLUETOOTH_SERVICE) as? BluetoothManager
val bondedDevices = try {
adapter?.adapter?.bondedDevices ?: emptySet()
} catch (_: SecurityException) {
emptySet<BluetoothDevice>()
}
val deviceMap = bondedDevices.associateBy { it.address }
val content = View.inflate(context, R.layout.wear_sync_device_list, null)
content.findViewById<TextView>(R.id.wear_sync_device_list_message).text = getString(messageRes)
val container = content.findViewById<LinearLayout>(R.id.wear_sync_device_list_container)
knownAddresses.forEach { address ->
val name = deviceName(deviceMap[address], address)
val itemView = LayoutInflater.from(context).inflate(R.layout.wear_sync_device_list_item, container, false) as TextView
itemView.text = getString(R.string.settings_wear_sync_device_name, name, address)
itemView.setOnClickListener { onRemove(address, name) }
container.addView(itemView)
}
currentDevicesDialog = MaterialAlertDialogBuilder(context)
.setTitle(titleRes)
.setView(content)
.setOnDismissListener {
currentDevicesDialog = null
currentDevicesDialogIsBlocked = false
}
.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.dismiss() }
.show()
}
private fun showWearSyncAllowedDevicesDialog() {
showWearSyncDeviceListDialog(
R.string.settings_wear_sync_devices,
R.string.settings_wear_sync_no_devices,
R.string.settings_wear_sync_tap_to_remove,
WearBluetoothSecurity.listTrustedDevices(requireContext()),
isBlocked = false
) { address, name -> confirmRemoveAllowedDevice(address, name) }
}
private fun showWearSyncBlockedDevicesDialog() {
showWearSyncDeviceListDialog(
R.string.settings_wear_sync_blocked_devices,
R.string.settings_wear_sync_no_blocked_devices,
R.string.settings_wear_sync_tap_to_remove,
WearBluetoothSecurity.listBlockedDevices(requireContext()),
isBlocked = true
) { address, name -> confirmRemoveBlockedDevice(address, name) }
}
private fun confirmRemoveAllowedDevice(address: String, name: String) {
confirmRemoveDevice(
address,
name,
R.string.settings_wear_sync_unpair_message,
onRemove = { WearBluetoothSecurity.untrustDevice(requireContext(), address) }
)
}
private fun confirmRemoveBlockedDevice(address: String, name: String) {
confirmRemoveDevice(
address,
name,
R.string.settings_wear_sync_unblock_message,
onRemove = { WearBluetoothSecurity.unblockDevice(requireContext(), address) }
)
}
private fun confirmRemoveDevice(address: String, name: String, messageRes: Int, onRemove: () -> Unit) {
val context = requireContext()
MaterialAlertDialogBuilder(context)
.setTitle(R.string.settings_wear_sync_unpair_title)
.setMessage(getString(messageRes, name))
.setPositiveButton(R.string.settings_wear_sync_unpair) { dialog, _ ->
onRemove()
BluetoothPairingNotificationManager.cancel(context, address)
BluetoothPairingNotificationManager.notifyDevicesChanged(context)
findPreference<Preference>(getString(R.string.settings_key_wear_sync_devices))?.let { updateWearSyncAllowedDevicesSummary(it) }
findPreference<Preference>(getString(R.string.settings_key_wear_sync_blocked_devices))?.let { updateWearSyncBlockedDevicesSummary(it) }
dialog.dismiss()
}
.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.dismiss() }
.show()
}
private fun deviceName(device: BluetoothDevice?, fallback: String): String {
return try { device?.name } catch (_: SecurityException) { null } ?: fallback
}
private fun showWearSyncPermissionDeniedSnackbar() {
Snackbar.make(
requireView(),
R.string.wear_sync_permission_required,
Snackbar.LENGTH_LONG
).setAction(R.string.open_settings) {
startActivity(
Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", requireContext().packageName, null)
)
)
}.show()
}
private fun refreshActivity() {
mReloadMain = true
activity?.recreate()
}
}
@@ -0,0 +1,68 @@
package protect.card_locker.wearos
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import protect.card_locker.R
import protect.card_locker.shared.WearBluetoothSecurity
class BluetoothPairingActivity : AppCompatActivity() {
private var currentDialog: AlertDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
showPairingDialog(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
showPairingDialog(intent)
}
override fun onDestroy() {
currentDialog?.dismiss()
currentDialog = null
super.onDestroy()
}
private fun showPairingDialog(intent: Intent) {
val address = intent.getStringExtra(BluetoothPairingConstants.DEVICE_ADDRESS) ?: return finish()
val deviceName = intent.getStringExtra(BluetoothPairingConstants.DEVICE_NAME) ?: address
val token = intent.getStringExtra(BluetoothPairingConstants.DEVICE_TOKEN)
val isMismatch = intent.getBooleanExtra(BluetoothPairingConstants.IS_MISMATCH, false)
val titleRes = if (isMismatch) {
R.string.wear_bt_pairing_mismatch_title
} else {
R.string.wear_bt_pairing_title
}
val messageRes = if (isMismatch) {
R.string.wear_bt_pairing_mismatch_message
} else {
R.string.wear_bt_pairing_message
}
currentDialog?.dismiss()
currentDialog = MaterialAlertDialogBuilder(this)
.setTitle(getString(titleRes, deviceName))
.setMessage(getString(messageRes, deviceName))
.setPositiveButton(R.string.wear_bt_pairing_allow) { _, _ ->
WearBluetoothSecurity.trustDevice(this, address, token)
BluetoothPairingNotificationManager.updateResultNotification(this, address, deviceName, true)
BluetoothPairingNotificationManager.notifyDevicesChanged(this)
finish()
}
.setNegativeButton(R.string.wear_bt_pairing_block) { _, _ ->
WearBluetoothSecurity.blockDevice(this, address)
BluetoothPairingNotificationManager.updateResultNotification(this, address, deviceName, false)
BluetoothPairingNotificationManager.notifyDevicesChanged(this)
finish()
}
.setOnCancelListener { finish() }
.setCancelable(true)
.show()
}
}
@@ -0,0 +1,8 @@
package protect.card_locker.wearos
object BluetoothPairingConstants {
const val DEVICE_ADDRESS = "device_address"
const val DEVICE_NAME = "device_name"
const val DEVICE_TOKEN = "device_token"
const val IS_MISMATCH = "is_mismatch"
}
@@ -0,0 +1,122 @@
package protect.card_locker.wearos
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.widget.Toast
import androidx.core.app.NotificationCompat
import protect.card_locker.NotificationInfo
import protect.card_locker.R
object BluetoothPairingNotificationManager {
const val ACTION_DEVICES_CHANGED = "protect.card_locker.wearos.ACTION_DEVICES_CHANGED"
fun notifyDevicesChanged(context: Context) {
context.sendBroadcast(Intent(ACTION_DEVICES_CHANGED).apply { setPackage(context.packageName) })
}
fun showAuthorizationNotification(
context: Context,
deviceName: String,
address: String,
token: String,
isMismatch: Boolean = false
) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
NotificationInfo.WearBluetooth.PAIRING_CHANNEL_ID,
context.getString(R.string.wear_bt_pairing_channel_name),
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(channel)
}
val titleRes = if (isMismatch) {
R.string.wear_bt_pairing_mismatch_title
} else {
R.string.wear_bt_pairing_title
}
val messageRes = if (isMismatch) {
R.string.wear_bt_pairing_mismatch_message
} else {
R.string.wear_bt_pairing_message
}
val allowIntent = Intent(context, BluetoothPairingReceiver::class.java).apply {
action = BluetoothPairingReceiver.ACTION_ALLOW
putExtra(BluetoothPairingConstants.DEVICE_ADDRESS, address)
putExtra(BluetoothPairingConstants.DEVICE_NAME, deviceName)
putExtra(BluetoothPairingConstants.DEVICE_TOKEN, token)
}
val blockIntent = Intent(context, BluetoothPairingReceiver::class.java).apply {
action = BluetoothPairingReceiver.ACTION_BLOCK
putExtra(BluetoothPairingConstants.DEVICE_ADDRESS, address)
putExtra(BluetoothPairingConstants.DEVICE_NAME, deviceName)
putExtra(BluetoothPairingConstants.IS_MISMATCH, isMismatch)
}
val allowPendingIntent = PendingIntent.getBroadcast(
context,
address.hashCode(),
allowIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val blockPendingIntent = PendingIntent.getBroadcast(
context,
address.hashCode() + 1,
blockIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val contentIntent = PendingIntent.getActivity(
context,
address.hashCode() + 2,
Intent(context, BluetoothPairingActivity::class.java).apply {
putExtra(BluetoothPairingConstants.DEVICE_ADDRESS, address)
putExtra(BluetoothPairingConstants.DEVICE_NAME, deviceName)
putExtra(BluetoothPairingConstants.DEVICE_TOKEN, token)
putExtra(BluetoothPairingConstants.IS_MISMATCH, isMismatch)
},
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(context, NotificationInfo.WearBluetooth.PAIRING_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification_catima)
.setContentTitle(context.getString(titleRes, deviceName))
.setContentText(context.getString(messageRes, deviceName))
.setStyle(NotificationCompat.BigTextStyle().bigText(context.getString(messageRes, deviceName)))
.addAction(0, context.getString(R.string.wear_bt_pairing_allow), allowPendingIntent)
.addAction(0, context.getString(R.string.wear_bt_pairing_block), blockPendingIntent)
.setContentIntent(contentIntent)
.setAutoCancel(false)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_STATUS)
.build()
notificationManager.notify(notificationId(address), notification)
}
fun cancel(context: Context, address: String) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(notificationId(address))
}
fun updateResultNotification(context: Context, address: String, deviceName: String, allowed: Boolean) {
cancel(context, address)
val message = if (allowed) {
context.getString(R.string.wear_bt_pairing_allowed, deviceName)
} else {
context.getString(R.string.wear_bt_pairing_blocked, deviceName)
}
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
fun notificationId(address: String): Int = address.hashCode()
}
@@ -0,0 +1,33 @@
package protect.card_locker.wearos
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import protect.card_locker.shared.WearBluetoothSecurity
class BluetoothPairingReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val address = intent.getStringExtra(BluetoothPairingConstants.DEVICE_ADDRESS) ?: return
val deviceName = intent.getStringExtra(BluetoothPairingConstants.DEVICE_NAME) ?: address
val token = intent.getStringExtra(BluetoothPairingConstants.DEVICE_TOKEN)
when (intent.action) {
ACTION_ALLOW -> {
WearBluetoothSecurity.trustDevice(context, address, token)
BluetoothPairingNotificationManager.updateResultNotification(context, address, deviceName, true)
BluetoothPairingNotificationManager.notifyDevicesChanged(context)
}
ACTION_BLOCK -> {
WearBluetoothSecurity.blockDevice(context, address)
BluetoothPairingNotificationManager.updateResultNotification(context, address, deviceName, false)
BluetoothPairingNotificationManager.notifyDevicesChanged(context)
}
}
}
companion object {
const val ACTION_ALLOW = "protect.card_locker.wearos.action.ALLOW_DEVICE"
const val ACTION_BLOCK = "protect.card_locker.wearos.action.BLOCK_DEVICE"
}
}
@@ -0,0 +1,310 @@
package protect.card_locker.wearos
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothServerSocket
import android.bluetooth.BluetoothSocket
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import org.json.JSONArray
import org.json.JSONObject
import protect.card_locker.DBHelper
import protect.card_locker.NotificationInfo
import protect.card_locker.R
import protect.card_locker.Utils
import protect.card_locker.shared.BluetoothPermissionHelper
import protect.card_locker.shared.WearBluetoothProtocol
import protect.card_locker.shared.WearBluetoothSecurity
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.io.PrintWriter
class BluetoothServerService : Service() {
companion object {
private const val TAG = "CatimaBtServer"
private const val NOTIFICATION_ID = NotificationInfo.WearBluetooth.NOTIFICATION_ID
private const val CHANNEL_ID = NotificationInfo.WearBluetooth.CHANNEL_ID
}
private var serverThread: AcceptThread? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (!BluetoothPermissionHelper.isBluetoothConnectGranted(this)) {
Log.w(TAG, "BLUETOOTH_CONNECT permission not granted, stopping")
stopSelf()
return START_NOT_STICKY
}
val adapter = (getSystemService(BLUETOOTH_SERVICE) as? BluetoothManager)?.adapter
if (adapter == null || !adapter.isEnabled) {
Log.w(TAG, "Bluetooth not available or disabled")
stopSelf()
return START_NOT_STICKY
}
// Avoid tearing down a working accept socket every time Settings is resumed.
if (serverThread?.isAlive == true) {
Log.d(TAG, "Bluetooth server already listening")
return START_STICKY
}
startForegroundWithNotification()
serverThread?.cancel()
serverThread = AcceptThread(adapter).also { it.start() }
Log.d(TAG, "Bluetooth server started")
return START_STICKY
}
private fun startForegroundWithNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
getString(R.string.wear_bt_channel_name),
NotificationManager.IMPORTANCE_MIN
).apply { setShowBadge(false) }
getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
}
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(getString(R.string.wear_bt_notification_title))
.setSmallIcon(R.drawable.ic_notification_sync)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setSilent(true)
.build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(NOTIFICATION_ID, notification,
android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE)
} else {
startForeground(NOTIFICATION_ID, notification)
}
}
override fun onDestroy() {
serverThread?.cancel()
serverThread = null
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? = null
private inner class AcceptThread(adapter: BluetoothAdapter) : Thread() {
private var serverSocket: BluetoothServerSocket? = null
@Volatile private var running = true
init {
try {
if (BluetoothPermissionHelper.isBluetoothConnectGranted(this@BluetoothServerService)) {
serverSocket = adapter.listenUsingRfcommWithServiceRecord(
WearBluetoothProtocol.BT_SERVICE_NAME,
WearBluetoothProtocol.BT_SERVICE_UUID
)
} else {
Log.w(TAG, "BLUETOOTH_CONNECT permission missing, cannot open server socket")
running = false
}
} catch (e: SecurityException) {
Log.e(TAG, "BLUETOOTH_CONNECT permission missing, cannot create server socket", e)
running = false
} catch (e: Exception) {
Log.e(TAG, "Failed to create server socket", e)
running = false
}
}
override fun run() {
Log.d(TAG, "Listening for Bluetooth connections")
while (running) {
val socket: BluetoothSocket = try {
serverSocket?.accept() ?: break
} catch (e: Exception) {
if (running) Log.e(TAG, "Accept failed", e)
break
}
// Handle each client on its own thread so a slow/stuck client
// cannot block the accept loop.
Thread { handleConnection(socket) }.start()
}
Log.d(TAG, "Accept loop ended")
if (running) {
Log.w(TAG, "Accept loop exited unexpectedly, restarting service")
stopSelf()
}
}
private fun handleConnection(socket: BluetoothSocket) {
val address = try { socket.remoteDevice.address } catch (_: SecurityException) { null } ?: return
val deviceName = try { socket.remoteDevice.name } catch (_: SecurityException) { address }
Log.d(TAG, "Connected to $deviceName ($address)")
try {
val reader = BufferedReader(InputStreamReader(socket.inputStream, "UTF-8"))
val writer = PrintWriter(OutputStreamWriter(socket.outputStream, "UTF-8"), false)
val firstLine = reader.readLine()?.trim() ?: return
if (firstLine == WearBluetoothProtocol.BT_CMD_VERSIONS) {
val versions = JSONArray().put("${WearBluetoothProtocol.PROTOCOL_VERSION}.${WearBluetoothProtocol.PROTOCOL_MINOR_VERSION}").toString()
writer.println(versions)
writer.flush()
Log.d(TAG, "Sent supported versions to $deviceName: $versions")
return
}
var commandLine: String? = firstLine
while (commandLine != null) {
val command = commandLine
Log.d(TAG, "Received command: $command from $deviceName")
if (!command.startsWith(WearBluetoothProtocol.BT_CMD_VERSION_PREFIX)) {
notAuthorized(writer)
Log.w(TAG, "Unsupported command from $deviceName: $command")
break
}
val tokenLine = reader.readLine()?.trim()
if (tokenLine == null || !tokenLine.startsWith(WearBluetoothProtocol.BT_CMD_TOKEN_PREFIX)) {
notAuthorized(writer)
Log.w(TAG, "Malformed token line from $deviceName: $tokenLine")
break
}
val token = tokenLine.removePrefix(WearBluetoothProtocol.BT_CMD_TOKEN_PREFIX)
val isTrusted = WearBluetoothSecurity.isDeviceTrusted(this@BluetoothServerService, address)
val storedToken = WearBluetoothSecurity.getDeviceToken(this@BluetoothServerService, address)
val tokenMatches = storedToken != null && storedToken == token
when {
WearBluetoothSecurity.isDeviceBlocked(this@BluetoothServerService, address) -> {
notAuthorized(writer)
Log.w(TAG, "Rejected blocked device $deviceName")
break
}
isTrusted && tokenMatches -> {
if (!serveCommand(writer, deviceName, command)) {
break
}
}
isTrusted && !tokenMatches -> {
BluetoothPairingNotificationManager.showAuthorizationNotification(
this@BluetoothServerService,
deviceName,
address,
token,
isMismatch = true
)
notAuthorized(writer)
Log.w(TAG, "Token mismatch for $deviceName")
break
}
else -> {
BluetoothPairingNotificationManager.showAuthorizationNotification(
this@BluetoothServerService,
deviceName,
address,
token,
isMismatch = false
)
notAuthorized(writer)
Log.d(TAG, "Requested authorization for $deviceName")
break
}
}
commandLine = reader.readLine()?.trim()
}
} catch (_: IOException) {
// Client closed the socket; this is a normal end-of-stream after
// the watch has received the pages it needs.
Log.d(TAG, "Connection closed by $deviceName")
} catch (e: Exception) {
Log.e(TAG, "Error handling connection from $deviceName", e)
} finally {
try { socket.close() } catch (_: Exception) { }
}
}
private fun notAuthorized(writer: PrintWriter) {
writer.println(WearBluetoothProtocol.BT_RESPONSE_NOT_AUTHORIZED)
writer.flush()
}
private fun serveCommand(
writer: PrintWriter,
deviceName: String,
command: String
): Boolean {
when {
command.startsWith(WearBluetoothProtocol.BT_CMD_CARDS_PAGE_PREFIX) -> {
val pageIndex = command.removePrefix(WearBluetoothProtocol.BT_CMD_CARDS_PAGE_PREFIX).toIntOrNull()
if (pageIndex == null || pageIndex < 0) {
Log.w(TAG, "Invalid page index from $deviceName: $command")
notAuthorized(writer)
return false
}
val json = buildCardsPageJson(pageIndex)
writer.println(json)
writer.flush()
Log.d(TAG, "Sent page $pageIndex to $deviceName")
return true
}
else -> {
Log.w(TAG, "Unsupported command from $deviceName: $command")
notAuthorized(writer)
return false
}
}
}
private fun buildCardsPageJson(pageIndex: Int): String {
val dbHelper = DBHelper(this@BluetoothServerService)
val db = dbHelper.readableDatabase
val order = Utils.getLoyaltyCardOrder(this@BluetoothServerService)
val orderDirection = Utils.getLoyaltyCardOrderDirection(this@BluetoothServerService)
val cursor = DBHelper.getLoyaltyCardCursor(
db, "", null, order, orderDirection,
DBHelper.LoyaltyCardArchiveFilter.Unarchived
)
val totalCards = cursor.count
val totalPages = if (totalCards == 0) 1 else (totalCards + WearBluetoothProtocol.PAGE_SIZE - 1) / WearBluetoothProtocol.PAGE_SIZE
val offset = pageIndex * WearBluetoothProtocol.PAGE_SIZE
val array = JSONArray()
cursor.use {
if (it.move(offset + 1)) {
val idIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID)
val storeIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE)
val cardIdIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID)
val barcodeIdIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_ID)
val barcodeTypeIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE)
val headerColorIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR)
var count = 0
do {
array.put(JSONObject().apply {
put("id", it.getInt(idIdx))
put("store", it.getString(storeIdx) ?: "")
put("cardId", it.getString(cardIdIdx) ?: "")
put("barcodeId", if (it.isNull(barcodeIdIdx)) JSONObject.NULL else it.getString(barcodeIdIdx))
put("barcodeType", if (it.isNull(barcodeTypeIdx)) JSONObject.NULL else it.getString(barcodeTypeIdx))
put("headerColor", if (it.isNull(headerColorIdx)) JSONObject.NULL else it.getInt(headerColorIdx))
})
count++
} while (count < WearBluetoothProtocol.PAGE_SIZE && it.moveToNext())
}
}
return JSONObject().apply {
put("page", pageIndex)
put("totalPages", totalPages)
put("cards", array)
}.toString()
}
fun cancel() {
running = false
try { serverSocket?.close() } catch (_: Exception) { }
}
}
}
@@ -0,0 +1,167 @@
package protect.card_locker.wearos
import android.Manifest
import android.app.ForegroundServiceStartNotAllowedException
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.activity.result.ActivityResultCaller
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import protect.card_locker.NotificationInfo
import protect.card_locker.R
import protect.card_locker.preferences.Settings
import protect.card_locker.shared.BluetoothPermissionHelper
class WearSyncPermissionRequester(
caller: ActivityResultCaller,
context: Context
) {
private val context = context.applicationContext
private var pendingResultCallback: ((Boolean) -> Unit)? = null
private val permissionLauncher = caller.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { results ->
val allGranted = results.all { it.value }
pendingResultCallback?.invoke(allGranted)
WearSyncServiceManager.onPermissionResult(this.context, allGranted)
pendingResultCallback = null
}
fun synchronize() {
WearSyncServiceManager.synchronize(context) {
permissionLauncher.launch(getRequiredPermissions())
}
}
fun onWearSyncChanged(enabled: Boolean, onPermissionResult: ((Boolean) -> Unit)? = null) {
val applied = WearSyncServiceManager.onWearSyncChanged(context, enabled) {
pendingResultCallback = onPermissionResult
permissionLauncher.launch(getRequiredPermissions())
}
if (applied) {
onPermissionResult?.invoke(true)
}
}
private fun getRequiredPermissions(): Array<String> = buildList {
if (BluetoothPermissionHelper.isBluetoothConnectRequired()) {
add(Manifest.permission.BLUETOOTH_CONNECT)
}
if (BluetoothPermissionHelper.isPostNotificationsRequired()) {
add(Manifest.permission.POST_NOTIFICATIONS)
}
}.toTypedArray()
}
object WearSyncServiceManager {
private const val TAG = "CatimaWearSyncServiceManager"
fun synchronize(context: Context, requestPermission: (() -> Unit)? = null) {
if (!Settings(context).wearSyncEnabled) {
stop(context)
} else if (arePermissionsGranted(context)) {
start(context)
} else if (requestPermission != null) {
requestPermission()
} else {
stop(context)
}
}
fun onWearSyncChanged(
context: Context,
enabled: Boolean,
requestPermission: () -> Unit
): Boolean {
return if (enabled) {
if (arePermissionsGranted(context)) {
start(context)
true
} else {
requestPermission()
false
}
} else {
stop(context)
true
}
}
private fun arePermissionsGranted(context: Context): Boolean {
return BluetoothPermissionHelper.isBluetoothConnectGranted(context) &&
BluetoothPermissionHelper.isPostNotificationsGranted(context)
}
fun onPermissionResult(context: Context, granted: Boolean) {
if (granted && Settings(context).wearSyncEnabled) {
start(context)
} else {
stop(context)
if (!granted) {
PreferenceManager.getDefaultSharedPreferences(context).edit {
putBoolean(context.getString(R.string.settings_key_wear_sync), false)
}
}
}
}
private fun start(context: Context) {
// Exception can only throw on API level 31 and up
if (Build.VERSION.SDK_INT >= 31) {
try {
ContextCompat.startForegroundService(
context,
Intent(context, BluetoothServerService::class.java)
)
} catch (e: ForegroundServiceStartNotAllowedException) {
// We naively assume we have enough permissions to show a notification as the service should never try to start
// before the user gave notification access anyway
Log.d(TAG, "Foreground service failed to launch: $e")
val channel = NotificationChannel(
NotificationInfo.WearBluetooth.CRITICAL_ERROR_CHANNEL_ID,
context.getString(R.string.wear_bt_critical_error_channel_name),
NotificationManager.IMPORTANCE_HIGH
).apply { setShowBadge(false) }
context.getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
val notification = NotificationCompat.Builder(context, NotificationInfo.WearBluetooth.CRITICAL_ERROR_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification_error)
.setContentTitle(context.getString(R.string.wear_bt_failed_foreground_notification_title))
.setContentText(e.message)
.build()
with(NotificationManagerCompat.from(context)) {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
return@with
}
notify(
NotificationInfo.WearBluetooth.CRITICAL_ERROR_NOTIFICATION_ID,
notification
)
}
}
} else {
ContextCompat.startForegroundService(
context,
Intent(context, BluetoothServerService::class.java)
)
}
}
private fun stop(context: Context) {
context.stopService(Intent(context, BluetoothServerService::class.java))
}
}
File diff suppressed because one or more lines are too long.
@@ -0,0 +1,30 @@
<!--
~ Copyright (C) 2026 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="#FFFFFF">
<group android:scaleX="1.104"
android:scaleY="1.104"
android:translateX="-49.92"
android:translateY="-49.92">
<path
android:fillColor="@android:color/white"
android:pathData="M508.5,668.5Q520,657 520,640Q520,623 508.5,611.5Q497,600 480,600Q463,600 451.5,611.5Q440,623 440,640Q440,657 451.5,668.5Q463,680 480,680Q497,680 508.5,668.5ZM440,520L520,520L520,280L440,280L440,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
</group>
</vector>
@@ -0,0 +1,30 @@
<!--
~ Copyright (C) 2026 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="#FFFFFF">
<group android:scaleX="0.92"
android:scaleY="0.92"
android:translateX="38.4"
android:translateY="38.4">
<path
android:fillColor="@android:color/white"
android:pathData="M160,800L160,720L270,720L254,706Q202,660 181,601Q160,542 160,482Q160,371 226.5,284.5Q293,198 400,170L400,254Q328,280 284,342.5Q240,405 240,482Q240,527 257,569.5Q274,612 310,648L320,658L320,560L400,560L400,800L160,800ZM560,790L560,706Q632,680 676,617.5Q720,555 720,478Q720,433 703,390.5Q686,348 650,312L640,302L640,400L560,400L560,160L800,160L800,240L690,240L706,254Q755,303 777.5,360.5Q800,418 800,478Q800,589 733.5,675.5Q667,762 560,790Z"/>
</group>
</vector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 B

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false">
<LinearLayout
android:id="@+id/wear_sync_device_list_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/wear_sync_device_list_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="?attr/dialogPreferredPadding"
android:paddingEnd="?attr/dialogPreferredPadding"
style="?attr/materialAlertDialogBodyTextStyle" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:gravity="center_vertical"
android:paddingStart="?attr/listPreferredItemPaddingLeft"
android:paddingEnd="?attr/listPreferredItemPaddingRight"
android:background="?attr/selectableItemBackground"
android:ellipsize="marquee"
android:focusable="true"
android:clickable="true"
style="?attr/materialAlertDialogBodyTextStyle" />
+22 -18
View File
@@ -1,12 +1,12 @@
Sylvia van Os
Branden Archer
J. Lavoie
solokot
J. Lavoie
Allan Nordhøy
Heimen Stoffels
Oğuz Ersen
FC (Fay) Stegerman
B o d o
Heimen Stoffels
FC (Fay) Stegerman
大王叫我来巡山
StoyanDimitrov
SlavekB
@@ -14,15 +14,15 @@ Katharine Chui / Katie
Silvério Santos
mondstern
IllusiveMan196
Максим Горпиніч
Fjuro
Edgars Andersons
Liner Seven
Fjuro
Altonss
Joel A
Максим Горпиніч
GitSpoon
Michael Moroni
Priit Jõerüüt
Joel A
Altonss
Michael Moroni
Eric
GM
Petr Novák
@@ -33,24 +33,25 @@ Milo Ivir
nadiafekihahmed
pfaffenrodt
Aayush Gupta
damjang
Scrambled777
josé m
Kachelkaiser
ikanakova
Nyatsuki
Giovanni Donisi
Kachelkaiser
Jiri Grönroos
Giovanni Donisi
109247019824
HudobniVolk
Warder
Traductor
Balázs Meskó
Samantaz Fox
damjang
mdvhimself
Traductor
AMIR-G98
Sergio Paredes
Samantaz Fox
mdvhimself
NooB9496
Cliff Heraldo
Sergio Paredes
Ankit Tiwari
Arno-github
Feike
@@ -59,7 +60,9 @@ Milan Šalka
Robin
தமிழ்நேரம்
huuhaa
Watchman89
Skrripy
Hosted Weblate user 54392
Govindgopalyadav
Gideon
waffshappen
@@ -67,6 +70,7 @@ Marnick L'Eau
JungHee Lee
pacavarn
ngocanhtve
josé m.
aradxxx
StellarSand
Quentin PAGÈS
@@ -75,26 +79,26 @@ Aliaksandr Trush
e-michalak
தமிழ் நேரம்
hajertabbane
inavleb
Denis Shilin
NooB9496
Renko
Ricky Tigg
Robin Liu
Ziad OUALHADJ
inavleb
しいたけ
Alexander Ivanov
Miha Frangež
stavpup
mrestivill
josé m.
ezn24
ehrt74
dukezas
Virginie
Tim Trek
Thu-Trang Pham
Alì Mortacci
Andre Costa
Arif Budiman
arshbeerSingh
Augustin LAVILLE
Freddo espresso
+50 -7
View File
@@ -2,7 +2,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_search">بحث</string>
<string name="action_add">إضافة</string>
<string name="noGiftCards">انقر على زر + لإضافة بطاقة، أو استوردها من قائمة </string>
<string name="noGiftCards">انقر على زر ”+“ لإضافة بطاقة، أو استوردها من قائمة ”⋮“</string>
<string name="noMatchingGiftCards">لا توجد نتائج. حاول تغيير بحثك.</string>
<string name="storeName">الاسم</string>
<string name="note">ملاحظة</string>
@@ -16,7 +16,7 @@
<string name="edit">تعديل</string>
<string name="delete">حذف</string>
<string name="confirm">تأكيد</string>
<string name="deleteConfirmation">حذف هذه البطاقة نهائيًّا؟</string>
<string name="deleteConfirmation">هل ترغب في حذف هذه البطاقة نهائيًّا؟</string>
<string name="ok">حسنًا</string>
<string name="share">مشاركة</string>
<string name="sendLabel">إرسال…</string>
@@ -161,9 +161,9 @@
<item quantity="zero">حذف هذه <xliff:g>%d</xliff:g> البطاقة نهائيًا؟</item>
<item quantity="one">حذف هذه <xliff:g>%d</xliff:g> البطاقة نهائيًا؟</item>
<item quantity="two">حذف هذه <xliff:g>%d</xliff:g> البطاقتين نهائيًا؟</item>
<item quantity="few">حذف هذه <xliff:g>%d</xliff:g> البطاقات نهائيًا؟</item>
<item quantity="many">حذف هذه <xliff:g>%d</xliff:g> البطاقات نهائيًا؟</item>
<item quantity="other">حذف هذه <xliff:g>%d</xliff:g> البطاقات نهائيًا؟</item>
<item quantity="few">هل ترغب في حذف هذه البطاقات الـ <xliff:g>%d</xliff:g> نهائيًّا؟</item>
<item quantity="many">هل ترغب في حذف هذه البطاقات الـ <xliff:g>%d</xliff:g> نهائيًّا؟</item>
<item quantity="other">هل ترغب في حذف هذه البطاقات الـ <xliff:g>%d</xliff:g> نهائيًا؟</item>
</plurals>
<string name="importOptionFilesystemTitle">الاستيراد من نظام الملفات</string>
<string name="importOptionFilesystemExplanation">اختر ملفًا محددًا من نظام الملفات</string>
@@ -255,9 +255,9 @@
<string name="show_note">إظهار الملاحظة</string>
<string name="switchToBackImage">التبديل إلى الصورة الخلفية</string>
<string name="switchToFrontImage">التبديل إلى الصورة الأمامية</string>
<string name="settings_allow_content_provider_read_summary">سيظل يتعين على التطبيقات طلب الإذن لمنحها حق الوصول</string>
<string name="settings_allow_content_provider_read_summary">يسمح لتطبيق Gadgetbridge بمزامنة بطاقاتك مع الساعات المدعومة</string>
<string name="setBarcodeHeight">ضبط ارتفاع الباركود</string>
<string name="settings_allow_content_provider_read_title">السماح للتطبيقات الأخرى بالوصول إلى بياناتي</string>
<string name="settings_allow_content_provider_read_title">المزامنة مع Gadgetbridge</string>
<string name="donate">تبرّع</string>
<string name="show_archived_cards">عرض البطاقات المؤرشفة</string>
<string name="settings_category_title_privacy">خصوصية</string>
@@ -327,4 +327,47 @@
<string name="nfc_blocked_while_viewing_card">NFC موقوف</string>
<string name="change_settings">تغير الاعدادات</string>
<string name="nfc_block_system_error">فشل في ايقاف NFC</string>
<string name="sort_by_most_recently_added">تمت إضافته مؤخرًا</string>
<string name="zoomFrontImage">تكبير الصورة الأمامية</string>
<string name="zoomBackImage">تكبير الصورة للخلف</string>
<string name="show_card_id">عرض رقم ID للبطاقة</string>
<string name="settings_wear_sync">المزامنة مع Wear OS</string>
<string name="settings_wear_sync_summary">يسمح التطبيق لـ Wear OS بقراءة بطاقاتك عبر البلوتوث</string>
<string name="wear_sync_permission_required">كاتيما تحتاج الأذونات التي رفضتها للمزامنة مع ساعة Wear OS الذكية الخاصة بك</string>
<string name="settings_wear_sync_devices">أجهزة Wear OS المسموح بها</string>
<string name="settings_wear_sync_devices_summary">إدارة الأجهزة المسموح لها بالمزامنة عبر البلوتوث</string>
<string name="settings_wear_sync_no_devices">لا توجد أجهزة مسموحة</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="zero"><xliff:g>%d</xliff:g> جهاز مسموح</item>
<item quantity="one"><xliff:g>%d</xliff:g> جهاز مسموح</item>
<item quantity="two"><xliff:g>%d</xliff:g> جهازان مسموحان</item>
<item quantity="few"><xliff:g>%d</xliff:g> أجهزة مسموحة</item>
<item quantity="many"><xliff:g>%d</xliff:g> أجهزة مسموحة</item>
<item quantity="other"><xliff:g>%d</xliff:g> جهاز مسموح</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">أجهزة Wear OS المحجوبة</string>
<string name="settings_wear_sync_blocked_devices_summary">إدارة أجهزة Wear OS المحجوبة</string>
<string name="settings_wear_sync_no_blocked_devices">لا توجد أجهزة محجوبة</string>
<string name="open_in_gallery">افتح في معرض الصور</string>
<string name="settings_wear_sync_unpair_title">هل تريد إزالة الجهاز؟</string>
<string name="settings_wear_sync_unpair_message">هل تريد إزالة <xliff:g>%1$s</xliff:g> من كاتيما؟ لن تكون قادرًا بعد الآن على مزامنة بطاقاتك.</string>
<string name="settings_wear_sync_unblock_message">هل تريد إزالة <xliff:g>%1$s</xliff:g> من القائمة المحظورة؟ سيتمكن من طلب الوصول مرة أخرى.</string>
<string name="settings_wear_sync_unpair">إزالة</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_tap_to_remove">اضغط على جهاز لإزالته.</string>
<string name="wear_bt_pairing_channel_name">إقران جهاز Wear OS</string>
<string name="wear_bt_pairing_title">جهاز Wear OS يريد المزامنة</string>
<string name="wear_bt_pairing_message">هل تريد السماح لـ <xliff:g>%1$s</xliff:g> بالوصول إلى بطاقاتك؟</string>
<string name="wear_bt_pairing_allow">سماح</string>
<string name="wear_bt_pairing_block">حظر</string>
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> مسموح له بالمزامنة.</string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> تم حظره.</string>
<string name="wear_bt_pairing_mismatch_title">طلب تفويض جديد من <xliff:g>%1$s</xliff:g></string>
<string name="wear_bt_pairing_mismatch_message">تمت الموافقة على هذا الجهاز سابقًا، لكنه يعرض الآن رمز هوية مختلف. يحدث هذا عندما يتم إعادة تثبيت تطبيق Wear OS أو مسح بياناته. كما يمكن أن يشير إلى محاولة انتحال عنوان MAC عبر البلوتوث.\n\nاقبل فقط إذا قمت مؤخرًا بإعادة تثبيت تطبيق كاتيما Wear OS على هذه الساعة.</string>
<string name="open_settings">فتح الإعدادات</string>
<string name="wear_bt_channel_name">رفيق Wear OS</string>
<string name="wear_bt_notification_title">مزامنة كاتيما لـ Wear OS نشطة</string>
<string name="settings_category_title_smartwatch">الساعة الذكية مدعومة</string>
<string name="wear_bt_failed_foreground_notification_title">فشل في بدء خدمة مزامنة Wear OS</string>
<string name="wear_bt_critical_error_channel_name">خطأ كبير في مزامنة Wear OS</string>
</resources>
+5 -2
View File
@@ -226,10 +226,10 @@
<string name="show_name_below_image_thumbnail">Наименование</string>
<string name="permissionReadCardsLabel">Четене на карти на Catima</string>
<string name="permissionReadCardsDescription">четене на картите на Catima с всички подробности, включително бележки и изображения</string>
<string name="settings_allow_content_provider_read_title">Разрешаване на достъп на други приложения до данните</string>
<string name="settings_allow_content_provider_read_title">Синхронизиране чрез Gadgetbridge</string>
<string name="settings_display_barcode_max_brightness_summary">Необходимо за работата на някои скенери</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">Без заключване на екрана при преглед на карта</string>
<string name="settings_allow_content_provider_read_summary">Приложенията ще искат разрешение, за да получат достъп</string>
<string name="settings_allow_content_provider_read_summary">Разрешава на приложението Gadgetbridge да синхронизира картите с поддържаните часовници</string>
<string name="settings_oled_dark_summary">Намалява разхода на батерия от OLED дисплеи</string>
<string name="settings_category_title_cards">Преглед на карта</string>
<string name="settings_category_title_general">Общи</string>
@@ -307,4 +307,7 @@
<string name="zoomFrontImage">Увеличаване на предното изображение</string>
<string name="zoomBackImage">Увеличаване на задното изображение</string>
<string name="show_card_id">Показване номера на картата</string>
<string name="open_in_gallery">Отваряне в галерията</string>
<string name="settings_wear_sync">Синхронизиране с Wear OS</string>
<string name="settings_wear_sync_summary">Разрешава на приложението за Wear OS да чете картите чрез Bluetooth</string>
</resources>
+5 -5
View File
@@ -3,13 +3,13 @@
<string name="save">সংরক্ষণ</string>
<string name="cancel">বাতিল</string>
<string name="unstar">তারা মুক্ত</string>
<string name="star">তারা</string>
<string name="barcodeType">বারকোড ধরন</string>
<string name="note">বিঃদ্রঃ</string>
<string name="storeName">দোকানের নাম</string>
<string name="star">পছন্দের তালিকায় যোগ করুন</string>
<string name="barcodeType">বারকোডের ধরন</string>
<string name="note">উল্লেখ্য</string>
<string name="storeName">নাম</string>
<string name="noMatchingGiftCards">কোনো ফলাফল পাওয়া যায়নি। অনুসন্ধানের বাক্যটি বদলে দেখুন।</string>
<string name="noGiftCards">প্লাস বোতামটি টিপে একটি কার্ড যোগ করুন বা ⋮ মেনু থেকে কিছু নিয়ে আসুন।</string>
<string name="action_add">কর্ম যোগ</string>
<string name="action_add">যোগ করুন</string>
<string name="all">সব</string>
<string name="noGroupCards">এই গ্রুপটি খালি</string>
<string name="noGroups">+ যোগ বোতামটি টিপে গ্রুপ যোগ করুন যাতে তাদের শ্রেণীকরণ করা যায়।</string>
+3 -3
View File
@@ -12,7 +12,7 @@
<string name="action_search">Cerca</string>
<string name="deleteTitle">Elimina la targeta</string>
<string name="welcome">Benvingut a Catima</string>
<string name="noGiftCards">Fes clic al botó + per afegir una targeta, o importa des del menú ⋮</string>
<string name="noGiftCards">Fes clic al botó mes + per afegir una targeta, o importa des del menú ⋮</string>
<string name="photos">Fotos</string>
<string name="app_name">Catima</string>
<string name="moveDown">Baixar abaix</string>
@@ -91,7 +91,7 @@
<string name="importCancelled">Importació anulada</string>
<string name="exportCancelled">Exportació cancelada</string>
<string name="noGiftCardsGroup">Crea algunes targetes i després asigna-les en al grup aquí</string>
<string name="noMatchingGiftCards">No hi ha resultats; prova de modificar la cerca.</string>
<string name="noMatchingGiftCards">No hi ha resultats. Prova de modificar la cerca.</string>
<string name="storeName">Nom</string>
<string name="note">Nota</string>
<string name="cardId">Id. de la Targeta</string>
@@ -153,7 +153,7 @@
<string name="enter_card_id">Introdueixi el nombre ID o text de la seva targeta</string>
<string name="card_id_must_not_be_empty">ID de la Targeta no pot estar buit</string>
<string name="add_a_card_in_a_different_way">Afegeix una targeta de forma diferent</string>
<string name="unstar">Treure entre les preferides</string>
<string name="unstar">Treure de les preferides</string>
<string name="star">Afegir a les preferides</string>
<string name="cancel">Cancel•la</string>
<string name="deleteConfirmation">Vols eliminar de forma permanent aquesta targeta?</string>
+45 -2
View File
@@ -230,9 +230,9 @@
<string name="show_validity">Zobrazit platnost</string>
<string name="show_balance">Zobrazit zůstatek</string>
<string name="permissionReadCardsDescription">číst vaše karty Catima a všechny jejich podrobnosti, včetně poznámek a obrázků</string>
<string name="settings_allow_content_provider_read_summary">Aplikace budou i tak muset požádat o povolení k poskytnutí přístupu</string>
<string name="settings_allow_content_provider_read_summary">Umožňuje aplikaci Gadgetbridge synchronizovat vaše karty do podporovaných chytrých hodinek</string>
<string name="permissionReadCardsLabel">Číst karty Catima</string>
<string name="settings_allow_content_provider_read_title">Umožnit ostatním aplikacím přístup k mým datům</string>
<string name="settings_allow_content_provider_read_title">Synchronizace s Gadgetbridge</string>
<string name="settings_keep_screen_on_summary">Při prohlížení karty vypnout časovač zhasnutí obrazovky</string>
<string name="settings_oled_dark_summary">Snižuje používání baterie na displejích OLED</string>
<string name="settings_category_title_privacy">Soukromí</string>
@@ -313,4 +313,47 @@
<string name="zoomFrontImage">Přiblížit přední obrázek</string>
<string name="zoomBackImage">Přiblížit zadní obrázek</string>
<string name="show_card_id">Zobrazit ID karty</string>
<string name="open_in_gallery">Otevřít v galerii</string>
<string name="settings_wear_sync">Synchronizovat s Wear OS</string>
<string name="settings_wear_sync_summary">Umožňuje doprovodné aplikaci Wear OS číst vaše karty skrze Bluetooth</string>
<string name="wear_sync_permission_required">Catima potřebuje oprávnění, které jste zamítli, k synchronizaci s vašimi chytrými hodinkami Wear OS</string>
<string name="settings_wear_sync_devices">Povolená zařízení Wear OS</string>
<string name="settings_wear_sync_devices_summary">Správa zařízení, u kterých je povolena synchronizace skrze Bluetooth</string>
<string name="settings_wear_sync_no_devices">Žádná povolená zařízení</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> povolené zařízení</item>
<item quantity="few"><xliff:g>%d</xliff:g> povolená zařízení</item>
<item quantity="many"><xliff:g>%d</xliff:g> povolených zařízení</item>
<item quantity="other"><xliff:g>%d</xliff:g> povolených zařízení</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">Zablokovaná zařízení Wear OS</string>
<string name="settings_wear_sync_blocked_devices_summary">Spravovat zablokovaná zařízení Wear OS</string>
<string name="settings_wear_sync_no_blocked_devices">Žádná zablokovaná zařízení</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> zablokované zařízení</item>
<item quantity="few"><xliff:g>%d</xliff:g> zablokovaná zařízení</item>
<item quantity="many"><xliff:g>%d</xliff:g> zablokovaných zařízení</item>
<item quantity="other"><xliff:g>%d</xliff:g> zablokovaných zařízení</item>
</plurals>
<string name="settings_wear_sync_unpair_title">Odstranit zařízení?</string>
<string name="settings_wear_sync_unpair_message">Odstranit zařízení <xliff:g>%1$s</xliff:g> z aplikace Catima? Již nebude moci synchronizovat vaše karty.</string>
<string name="settings_wear_sync_unblock_message">Odstranit zařízení <xliff:g>%1$s</xliff:g> ze seznamu zablokovaných? Bude moci znovu žádat o přístup.</string>
<string name="settings_wear_sync_unpair">Odstranit</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_tap_to_remove">Klepněte na zařízení pro jeho odstranění.</string>
<string name="wear_bt_pairing_channel_name">Párování zařízení s Wear OS</string>
<string name="wear_bt_pairing_title">Zařízení s Wear OS požaduje synchronizaci</string>
<string name="wear_bt_pairing_message">Povolit zařízení <xliff:g>%1$s</xliff:g> přístup k vašim kartám?</string>
<string name="wear_bt_pairing_allow">Povolit</string>
<string name="wear_bt_pairing_block">Zablokovat</string>
<string name="wear_bt_pairing_allowed">Zařízení <xliff:g>%1$s</xliff:g> má nyní povolenou synchronizaci.</string>
<string name="wear_bt_pairing_blocked">Zařízení <xliff:g>%1$s</xliff:g> bylo zablokováno.</string>
<string name="wear_bt_pairing_mismatch_title">Nový požadavek na autorizaci ze zařízení <xliff:g>%1$s</xliff:g></string>
<string name="wear_bt_pairing_mismatch_message">Toto zařízení bylo v minulosti autorizováno, nyní se ale prezentuje jiným tokenem identity. K této situaci může dojít při reinstalaci aplikace Wear OS nebo vymazání jejích dat. Může to ale také značit pokus o maskování adresy Bluetooth MAC.\n\nPřijměte pouze, pokud jste na těchto hodinkách nedávno přeinstalovali aplikaci Catima pro Wear OS.</string>
<string name="open_settings">Otevřít nastavení</string>
<string name="wear_bt_channel_name">Doprovoná aplikace na Wear OS</string>
<string name="wear_bt_notification_title">Synchronizace s Catima Wear OS je aktivní</string>
<string name="settings_category_title_smartwatch">Podpora chytrých hodinek</string>
<string name="wear_bt_failed_foreground_notification_title">Nepodařilo se spustit synchronizační službu Wear OS</string>
<string name="wear_bt_critical_error_channel_name">Kritická chyba synchronizace s Wear OS</string>
</resources>
+40 -2
View File
@@ -223,10 +223,10 @@
<string name="show_balance">Betrag anzeigen</string>
<string name="show_validity">Gültigkeitsdauer anzeigen</string>
<string name="show_name_below_image_thumbnail">Namen unter dem Vorschaubild anzeigen</string>
<string name="settings_allow_content_provider_read_title">Anderen Apps den Zugriff auf meine Daten gestatten</string>
<string name="settings_allow_content_provider_read_title">Synchronisation mit Gadgetbrigde</string>
<string name="permissionReadCardsLabel">Catima-Karten lesen</string>
<string name="permissionReadCardsDescription">Lesen deiner Catima Karten mit allen Details, einschließlich Notizen und Bildern</string>
<string name="settings_allow_content_provider_read_summary">Apps müssen weiterhin eine Genehmigung beantragen, um Zugriff zu erhalten</string>
<string name="settings_allow_content_provider_read_summary">Ermöglicht der Gadgetbridge-App, deine Karten mit unterstützten Uhren zu synchronisieren</string>
<string name="settings_display_barcode_max_brightness_summary">Erforderlich für das Funktionieren einiger Scanner</string>
<string name="settings_keep_screen_on_summary">Deaktiviert die Bildschirmzeitüberschreitung beim Anzeigen einer Karte</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">Deaktiviert die Bildschirmsperre während der Anzeige einer Karte</string>
@@ -308,4 +308,42 @@
<string name="zoomFrontImage">Vorderseite vergrößern</string>
<string name="sort_by_most_recently_added">Zuletzt hinzugefügt</string>
<string name="open_in_gallery">In Bildergalerie öffnen</string>
<string name="wear_bt_critical_error_channel_name">Kritischer Wear OS Synchronisierungsfehler</string>
<string name="wear_bt_failed_foreground_notification_title">Synchronisierungsdienst zu Wear OS konnte nicht gestartet werden</string>
<string name="settings_category_title_smartwatch">Smartwatch Unterstützung</string>
<string name="wear_bt_notification_title">Catima Wear OS Synchronisierung ist aktiv</string>
<string name="wear_bt_channel_name">Wear OS Begleit-App</string>
<string name="open_settings">Einstellungen öffnen</string>
<string name="wear_bt_pairing_mismatch_message">Dieses Gerät wurde zuvor autorisiert, besitzt nun aber einen anderen Identitäts-Token. Dies kann vorkommen, wenn die Daten der Wear OS App gelöscht wurden oder die App erneut installiert wurde. Es kann ebenso auf den Versuch einer Bluetooth MAC Manipulation hindeuten.\n\nAkzeptiere die Autorisierung nur, wenn die Catima Wear OS App kürzlich neu auf dieser Uhr installiert wurde.</string>
<string name="wear_bt_pairing_mismatch_title">Neue Anfrage zur Autorisation von <xliff:g>%1$s</xliff:g></string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> wurde blockiert.</string>
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> darf jetzt synchronisieren.</string>
<string name="wear_bt_pairing_block">Blockiert</string>
<string name="wear_bt_pairing_allow">Zugelassen</string>
<string name="wear_bt_pairing_message">Erlaube <xliff:g>%1$s</xliff:g> den Zugang zu deinen Karten?</string>
<string name="wear_bt_pairing_title">Wear OS Gerät möchte synchronisieren</string>
<string name="wear_bt_pairing_channel_name">Wear OS Gerät koppeln</string>
<string name="settings_wear_sync_tap_to_remove">Tippe auf ein Gerät, um es zu entfernen.</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_unpair">Entfernen</string>
<string name="settings_wear_sync_unblock_message">Entferne <xliff:g>%1$s</xliff:g> von der Blockierungsliste? Eine erneute Anfrage zum Zugriff ist dann möglich.</string>
<string name="settings_wear_sync_unpair_message">Entferne <xliff:g>%1$s</xliff:g> von Catima? Es wird dann nicht mehr möglich sein Karten zu synchronisieren.</string>
<string name="settings_wear_sync_unpair_title">Entferne Gerät?</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> Gerät blockiert</item>
<item quantity="other"><xliff:g>%d</xliff:g> Geräte blockiert</item>
</plurals>
<string name="settings_wear_sync_no_blocked_devices">Keine blockierten Geräte</string>
<string name="settings_wear_sync_blocked_devices_summary">Blockierte Wear OS Geräte verwalten</string>
<string name="settings_wear_sync_blocked_devices">Blockierte Wear OS Geräte</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> Gerät zugelassen</item>
<item quantity="other"><xliff:g>%d</xliff:g> Geräte zugelassen</item>
</plurals>
<string name="settings_wear_sync_no_devices">Keine zugelassenen Geräte</string>
<string name="settings_wear_sync_devices_summary">Verwalte die Geräte, die per Bluetooth synchronisieren dürfen</string>
<string name="settings_wear_sync_devices">Zugelassene Wear OS Geräte</string>
<string name="wear_sync_permission_required">Catima benötigt die von dir verweigerte Erlaubnis, um mit deiner Wear OS Smartwatch zu synchronisieren</string>
<string name="settings_wear_sync_summary">Erlaube der Wear OS Begleit-App die Karten per Bluetooth auszulesen</string>
<string name="settings_wear_sync">Synchronisiere mit Wear OS</string>
</resources>
+40 -2
View File
@@ -210,7 +210,7 @@
<string name="settings_disable_lockscreen_while_viewing_card_summary">Απενεργοποιεί το κλείδωμα οθόνης κατά την προβολή μιας κάρτας</string>
<string name="settings_display_barcode_max_brightness_summary">Απαραίτητο για να δουλέψει σε κάποιους σαρωτές</string>
<string name="cameraPermissionRequired">Δικαίωμα πρόσβασης στην κάμερα απαραίτητο γι\' αυτή την ενέργεια…</string>
<string name="settings_allow_content_provider_read_title">Να επιτρέπεται σε άλλες εφαρμογές να έχουν πρόσβαση στα δεδομένα μου</string>
<string name="settings_allow_content_provider_read_title">Συγχρονισμός με Gadgetbridge</string>
<string name="app_copyright_short">Πνευματικά δικαιώματα © Sylvia van Os και συνεργάτες</string>
<string name="height">Ύψος</string>
<string name="switchToFrontImage">Μετάβαση στην εμπρόσθια εικόνα</string>
@@ -255,7 +255,7 @@
<string name="show_validity">Εμφάνιση εγκυρότητας</string>
<string name="add_a_card_in_a_different_way">Προσθέστε μια κάρτα με διαφορετικό τρόπο</string>
<string name="card_id_must_not_be_empty">Ο κωδικός αριθμός της κάρτας δεν πρέπει να είναι κενός</string>
<string name="settings_allow_content_provider_read_summary">Οι εφαρμογές θα πρέπει ωστόσο να ζητήσουν άδεια για να τους δοθεί πρόσβαση</string>
<string name="settings_allow_content_provider_read_summary">Επιτρέπει στην εφαρμογή Gadgetbridge να συγχρονίζει τις κάρτες σας με τα υποστηριζόμενα ρολόγια</string>
<string name="field_must_not_be_empty">Το πεδίο δεν πρέπει να είναι κενό</string>
<string name="manually_enter_barcode_instructions">Εισάγετε τον κωδικό αριθμό ή το κείμενο στην κάρτα σας και πατήστε τον γραμμωτό κώδικα που μοιάζει με αυτόν της κάρτας σας.</string>
<string name="add_manually_warning_title">Συνιστάται η σάρωση</string>
@@ -308,4 +308,42 @@
<string name="zoomBackImage">Μεγέθυνση πίσω εικόνας</string>
<string name="show_card_id">Εμφάνιση ταυτότητας κάρτας</string>
<string name="open_in_gallery">Άνοιγμα στη συλλογή εικόνων</string>
<string name="settings_wear_sync">Συγχρονισμός με Wear OS</string>
<string name="settings_wear_sync_summary">Επιτρέπει στην συνοδευτική εφαρμογή Wear OS να διαβάζει τις κάρτες σας μέσω Bluetooth</string>
<string name="wear_sync_permission_required">Η Catima χρειάζεται τα δικαιώματα που απορρίψατε για να συγχρονιστεί με το Wear OS smartwatch σας</string>
<string name="settings_wear_sync_devices">Επιτρεπόμενες συσκευές Wear OS</string>
<string name="settings_wear_sync_devices_summary">Διαχείριση συσκευών που επιτρέπεται να συγχρονίζονται μέσω Bluetooth</string>
<string name="settings_wear_sync_no_devices">Δεν επιτρέπονται συσκευές</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> συσκευή επιτρέπεται</item>
<item quantity="other"><xliff:g>%d</xliff:g> συσκευές επιτρέπονται</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">Αποκλεισμένες συσκευές Wear OS</string>
<string name="settings_wear_sync_blocked_devices_summary">Διαχείριση αποκλεισμένων συσκευών Wear OS</string>
<string name="settings_wear_sync_no_blocked_devices">Δεν υπάρχουν αποκλεισμένες συσκευές</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> συσκευή αποκλεισμένη</item>
<item quantity="other"><xliff:g>%d</xliff:g> συσκευές αποκλεισμένες</item>
</plurals>
<string name="settings_wear_sync_unpair_title">Αφαίρεση συσκευής;</string>
<string name="settings_wear_sync_unpair_message">Να καταργηθεί η <xliff:g>%1$s</xliff:g> από το Catima; Δεν θα είναι πλέον δυνατός ο συγχρονισμός των καρτών σας.</string>
<string name="settings_wear_sync_unblock_message">Να καταργηθεί η <xliff:g>%1$s</xliff:g> από τη λίστα αποκλεισμένων; Θα μπορεί να ζητήσει ξανά πρόσβαση.</string>
<string name="settings_wear_sync_unpair">Αφαίρεση</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_tap_to_remove">Αγγίξτε μια συσκευή για να την καταργήσετε.</string>
<string name="wear_bt_pairing_channel_name">Σύζευξη συσκευών Wear OS</string>
<string name="wear_bt_pairing_title">Η συσκευή Wear OS θέλει συγχρονισμό</string>
<string name="wear_bt_pairing_message">Να επιτραπεί στη <xliff:g>%1$s</xliff:g> η πρόσβαση στις κάρτες σας;</string>
<string name="wear_bt_pairing_allow">Να επιτραπεί</string>
<string name="wear_bt_pairing_block">Φραγή</string>
<string name="wear_bt_pairing_allowed">Η <xliff:g>%1$s</xliff:g> έχει πλέον δικαίωμα συγχρονισμού.</string>
<string name="wear_bt_pairing_blocked">Η <xliff:g>%1$s</xliff:g> έχει αποκλειστεί.</string>
<string name="wear_bt_pairing_mismatch_title">Νέο αίτημα εξουσιοδότησης από <xliff:g>%1$s</xliff:g></string>
<string name="wear_bt_pairing_mismatch_message">Αυτή η συσκευή είχε προηγουμένως εξουσιοδοτηθεί, αλλά τώρα παρουσιάζει διαφορετικό διακριτικό ταυτότητας. Αυτό συμβαίνει όταν η εφαρμογή Wear OS επανεγκαθίσταται ή τα δεδομένα της διαγράφονται. Μπορεί επίσης να υποδεικνύει μια προσπάθεια πλαστογράφησης MAC Bluetooth.\n\nΑποδεχθείτε μόνο εάν επανεγκαταστήσατε πρόσφατα την εφαρμογή Catima Wear OS σε αυτό το ρολόι.</string>
<string name="open_settings">Άνοιγμα ρυθμίσεων</string>
<string name="wear_bt_channel_name">Συνοδευτική εφαρμογή Wear OS</string>
<string name="wear_bt_notification_title">Ο συγχρονισμός Wear OS του Catima είναι ενεργός</string>
<string name="settings_category_title_smartwatch">Συγχρονισμός smartwatch</string>
<string name="wear_bt_failed_foreground_notification_title">Αποτυχία εκκίνησης της υπηρεσίας συγχρονισμού Wear OS</string>
<string name="wear_bt_critical_error_channel_name">Σοβαρό σφάλμα συγχρονισμού Wear OS</string>
</resources>
+43 -2
View File
@@ -232,10 +232,10 @@
<string name="show_balance">Mostrar el saldo</string>
<string name="permissionReadCardsLabel">Leer Tarjetas Catima</string>
<string name="permissionReadCardsDescription">lea sus tarjetas Catima y todos sus detalles, incluyendo notas e imágenes</string>
<string name="settings_allow_content_provider_read_title">Permite a otras aplicaciones acceder a mis datos</string>
<string name="settings_allow_content_provider_read_title">Sincronizar con Gadgetbridge</string>
<string name="settings_display_barcode_max_brightness_summary">Necesario para que funcione en algunos escáneres</string>
<string name="settings_keep_screen_on_summary">Deshabilita el tiempo de espera de la pantalla mientras se ve una tarjeta</string>
<string name="settings_allow_content_provider_read_summary">Las aplicaciones todavía tendrán que solicitar permiso para conseguir acceso</string>
<string name="settings_allow_content_provider_read_summary">Permite a la app Gadgetbridge sincronizar tus tarjetas con los relojes soportados</string>
<string name="settings_oled_dark_summary">Reduce el uso de batería en pantallas OLED</string>
<string name="settings_category_title_cards">Vista de tarjeta</string>
<string name="settings_category_title_general">General</string>
@@ -313,4 +313,45 @@
<string name="zoomFrontImage">Imagen frontal de zoom</string>
<string name="zoomBackImage">Imagen trasera de zoom</string>
<string name="show_card_id">Mostrar ID de tarjeta</string>
<string name="settings_wear_sync">Sincronizar con Wear OS</string>
<string name="settings_wear_sync_summary">Permite a la app de acompañamiento de Wear OS leer tus tarjetas a través de Bluetooth</string>
<string name="wear_sync_permission_required">Catima necesita los permisos que has denegado para sincronizarse con tu reloj inteligente Wear OS</string>
<string name="settings_wear_sync_devices">Dispositivos Wear OS permitidos</string>
<string name="settings_wear_sync_devices_summary">Gestiona los dispositivos con permiso para sincronizarse por Bluetooth</string>
<string name="settings_wear_sync_no_devices">No hay dispositivos permitidos</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> dispositivo permitido</item>
<item quantity="many"><xliff:g>%d</xliff:g> dispositivos permitidos</item>
<item quantity="other"><xliff:g>%d</xliff:g> dispositivos permitidos</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">Dispositivos Wear OS bloqueados</string>
<string name="settings_wear_sync_blocked_devices_summary">Gestiona los dispositivos Wear OS bloqueados</string>
<string name="settings_wear_sync_no_blocked_devices">No hay dispositivos bloqueados</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> dispositivo bloqueado</item>
<item quantity="many"><xliff:g>%d</xliff:g> dispositivos bloqueados</item>
<item quantity="other"><xliff:g>%d</xliff:g> dispositivos bloqueados</item>
</plurals>
<string name="settings_wear_sync_unpair_title">¿Eliminar dispositivo?</string>
<string name="settings_wear_sync_unpair_message">¿Eliminar <xliff:g>%1$s</xliff:g> de Catima? No podrá seguir sincronizándose con tus tarjetas.</string>
<string name="settings_wear_sync_unblock_message">¿Eliminar <xliff:g>%1$s</xliff:g> de la lista de bloqueos? Podrá volver a solicitar acceso de nuevo.</string>
<string name="settings_wear_sync_unpair">Eliminar</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_tap_to_remove">Toca un dispositivo para eliminarlo.</string>
<string name="wear_bt_pairing_channel_name">Emparejamiento de dispositivos Wear OS</string>
<string name="wear_bt_pairing_title">Un dispositivo Wear OS solicita sincronización</string>
<string name="wear_bt_pairing_message">¿Conceder a <xliff:g>%1$s</xliff:g> acceso a tus tarjetas?</string>
<string name="wear_bt_pairing_allow">Permitir</string>
<string name="wear_bt_pairing_block">Bloquear</string>
<string name="wear_bt_pairing_allowed">Ahora <xliff:g>%1$s</xliff:g> tiene permitido sincronizarse.</string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> ha sido bloqueado.</string>
<string name="wear_bt_pairing_mismatch_title">Nueva petición de autorización desde <xliff:g>%1$s</xliff:g></string>
<string name="wear_bt_pairing_mismatch_message">Este dispositivo había sido autorizado previamente pero ahora emite un identificador diferente. Esto ocurre cuando la app de Wear OS se reinstala o se borran sus datos. También puede indicar un intento de suplantación MAC por Bluetooth.\n\nAcepta solo si has reinstalado recientemente la app de Catima para Wear OS en este reloj.</string>
<string name="open_settings">Abrir ajustes</string>
<string name="open_in_gallery">Abrir en galería de imágenes</string>
<string name="wear_bt_channel_name">Aplicación de acompañamiento de Wear OS</string>
<string name="wear_bt_notification_title">Sincronización activa con Catima Wear OS</string>
<string name="settings_category_title_smartwatch">Soporte para reloj inteligente</string>
<string name="wear_bt_failed_foreground_notification_title">Fallo al iniciar el servicio de sincronización de Wear OS</string>
<string name="wear_bt_critical_error_channel_name">Error crítico de sincronización con Wear OS</string>
</resources>
+41 -2
View File
@@ -86,7 +86,7 @@
<string name="settings_system_theme">Süsteemi kujundus</string>
<string name="settings_display_barcode_max_brightness_summary">See on vajalik mõnede skännerite toimimiseks</string>
<string name="expiryStateSentenceExpired">Aegus: <xliff:g>%s</xliff:g></string>
<string name="settings_allow_content_provider_read_summary">Selle valiku sisselülitamisel peavad muud rakendused lisaks küsima õigust vaadata kaartide andmeid</string>
<string name="settings_allow_content_provider_read_summary">Selle valiku sisselülitamisel lubad Gadgetbridge\'i abil sünkroonida kaarte toetatud nutikelladesse</string>
<string name="noGroups">Kui soovid sarnaseid kaarte omavahel liigitada, siis + pluss nupuga lisa kaardigruppe</string>
<string name="group_name_is_empty">Kaardigrupi nimi ei saa jääda tühjaks</string>
<string name="groupsList">Grupid: <xliff:g>%s</xliff:g></string>
@@ -139,7 +139,7 @@
<string name="settings_keep_screen_on_summary">Lülitab kaardi vaatamise ajaks välja ekraani energiahalduse</string>
<string name="settings_disable_lockscreen_while_viewing_card">Keela ekraanilukustus</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">Lülitab kaardi vaatamise ajaks välja ekraani lukustamise</string>
<string name="settings_allow_content_provider_read_title">Luba teistel rakendustel minu andmeid lugeda</string>
<string name="settings_allow_content_provider_read_title">Sünkrooni Gadgetbridge\'iga</string>
<string name="importSuccessful">Andmed on imporditud</string>
<string name="exportSuccessful">Andmed on eksporditud</string>
<string name="enter_group_name">Sisesta kaardigrupi nimi</string>
@@ -307,4 +307,43 @@
<string name="zoomFrontImage">Suumi esikaane pilti</string>
<string name="zoomBackImage">Suumi tagakaane pilti</string>
<string name="show_card_id">Näita kaardi tunnust</string>
<string name="open_in_gallery">Ava pildigaleriis</string>
<string name="settings_wear_sync">Sünkrooni Wear OS-iga</string>
<string name="settings_wear_sync_devices">Lubatud Wear OS-i seadmed</string>
<string name="settings_wear_sync_devices_summary">Halda seadmeid, mis võivad Bluetoothi abil kaarte sünkroonida</string>
<string name="settings_wear_sync_summary">Võimaldab Wear OS-i kaasrakendusel lugeda sinu kaarte Bluetoothi abil</string>
<string name="wear_sync_permission_required">Catima sinu Wear OS-i põhise nutikellaga sünkroonimiseks sinu luba</string>
<string name="settings_wear_sync_no_devices">Ühtegi lubatud seadet pole</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> lubatud seade</item>
<item quantity="other"><xliff:g>%d</xliff:g> lubatud seadet</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">Blokeeritud Wear OS-i seadmed</string>
<string name="settings_wear_sync_blocked_devices_summary">Halda blokeeritud Wear OS-i seadmeid</string>
<string name="settings_wear_sync_no_blocked_devices">Ühtegi blokeeritud seadet pole</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> blokeeritud seade</item>
<item quantity="other"><xliff:g>%d</xliff:g> blokeeritud seadet</item>
</plurals>
<string name="settings_wear_sync_unpair_title">Kas eemaldad seadme?</string>
<string name="settings_wear_sync_unpair_message">Kas eemaldad <xliff:g>%1$s</xliff:g> seadme Catimast? Peale seda pole enam sinu kaarte võimalik sinna sünkroonida.</string>
<string name="settings_wear_sync_unblock_message">Kas eemaldad <xliff:g>%1$s</xliff:g> seadme blokeeritud seadmete loendist? Peale seda saab ta uuesti küsida ligipääsuõigusi.</string>
<string name="settings_wear_sync_unpair">Eemalda</string>
<string name="settings_wear_sync_tap_to_remove">Seadme eemaldamiseks klõpsa temal.</string>
<string name="wear_bt_pairing_channel_name">Paardumine Wear OS-i seadmega</string>
<string name="wear_bt_pairing_title">Wear OS-i seade soovib sünkroonida</string>
<string name="wear_bt_pairing_message">Kas lubad <xliff:g>%1$s</xliff:g> seadmele ligipääsu sinu kaartidele?</string>
<string name="wear_bt_pairing_allow">Luba</string>
<string name="wear_bt_pairing_block">Blokeeri</string>
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> võib nüüd sünkroonida.</string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> on nüüd blokeeritud.</string>
<string name="wear_bt_pairing_mismatch_title">Uus autentimispäring <xliff:g>%1$s</xliff:g> seadmest</string>
<string name="open_settings">Ava seadistused</string>
<string name="wear_bt_channel_name">Wear OS-i kaaslasrakendus</string>
<string name="wear_bt_notification_title">Catima Wear OS-i sünkroonimine on aktiivne</string>
<string name="settings_category_title_smartwatch">Nutikellade tugi</string>
<string name="wear_bt_failed_foreground_notification_title">Wear OS-i sünkroonimisteenuse käivitamine ei õnnestunud</string>
<string name="wear_bt_critical_error_channel_name">Kriitiline viga Wear OS-iga sünkroonimisel</string>
<string name="wear_bt_pairing_mismatch_message">See seade oli varem autoriseeritud, kuid esitab nüüd päringutele teise tunnusloa. See juhtub siis, kui Wear OS-i rakendus on uuesti paigaldatud või selle andmed on kustutatud. See võib viidata ka Bluetoothi MAC-aadressi võltsimise katsele.\n\nNõustu sellega ainult juhul, kui oled hiljuti sellele kellale Catima Wear OS-i rakenduse uuesti paigaldanud.</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
</resources>
+5
View File
@@ -287,4 +287,9 @@
<string name="cardWithNumber">کارت <xliff:g>%d</xliff:g></string>
<string name="cardWithNumberAndLocale">کارت <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
<string name="pleaseDoNotRotateTheDevice">لطفا دستگاه‌تان را نچرخوانید، زیرا کار در حال انجام را لغو میکند</string>
<string name="settings_disable_nfc_while_viewing_card">توقف NFC</string>
<string name="settings_disable_nfc_while_viewing_card_summary">NFC را متوقف می‌کند تا هنگام مشاهده کارت، پرداخت‌های NFC فعال نشوند.</string>
<string name="settings_wear_sync_blocked_devices">دستگاه‌های Wear OS مسدودشده</string>
<string name="settings_wear_sync_blocked_devices_summary">مدیریت دستگاه‌های Wear OS مسدودشده</string>
<string name="settings_wear_sync_no_blocked_devices">هیچ دستگاه مسدودشده‌ای وجود ندارد</string>
</resources>
+43 -3
View File
@@ -168,7 +168,7 @@
<string name="version_history">Historique des versions</string>
<string name="exportPassword">Définissez un mot de passe pour protéger vos exportations (facultatif)</string>
<string name="exportPasswordHint">Entrez le mot de passe</string>
<string name="editGroup">Modification du groupe : <xliff:g> %s </xliff:g></string>
<string name="editGroup">Modification du groupe : <xliff:g>%s</xliff:g></string>
<string name="noGiftCardsGroup">Créez des cartes, puis affectez-les au groupe ici</string>
<string name="group_edit">Modifier le groupe</string>
<string name="group_name_already_in_use">Nom de groupe déjà utilisé</string>
@@ -231,8 +231,8 @@
<string name="show_balance">Afficher le solde</string>
<string name="permissionReadCardsLabel">Lire les cartes Catima</string>
<string name="permissionReadCardsDescription">lisez vos cartes Catima et tous leurs détails, y compris les notes et les images</string>
<string name="settings_allow_content_provider_read_title">Autoriser d\'autres applications à accéder à mes données</string>
<string name="settings_allow_content_provider_read_summary">Les applications devront toujours demander une autorisation pour obtenir l\'accès</string>
<string name="settings_allow_content_provider_read_title">Synchroniser avec Gadgetbridge</string>
<string name="settings_allow_content_provider_read_summary">Permet à l\'appli Gadgetbridge de synchroniser vos cartes vers des montres prises en charge</string>
<string name="settings_display_barcode_max_brightness_summary">Nécessaire au fonctionnement de certains scanneurs</string>
<string name="settings_keep_screen_on_summary">Désactive la temporisation de l\'écran lors de la visualisation d\'une carte</string>
<string name="settings_oled_dark_summary">Réduit l\'utilisation de la batterie sur les écrans OLED</string>
@@ -314,4 +314,44 @@
<string name="zoomBackImage">Grossir l\'image arrière</string>
<string name="show_card_id">Afficher l\'ID de carte</string>
<string name="open_in_gallery">Ouvrir dans la galerie d\'images</string>
<string name="settings_wear_sync">Synchroniser avec Wear OS</string>
<string name="settings_wear_sync_summary">Permet à l\'appli compagnon Wear OS de lire vos cartes par Bluetooth</string>
<string name="wear_sync_permission_required">Catima a besoin de l\'autorisation que vous avez refusée pour se synchroniser avec votre montre connectée Wear OS</string>
<string name="settings_wear_sync_devices">Appareils Wear OS autorisés</string>
<string name="settings_wear_sync_devices_summary">Gérer les appareils autorisés à se synchroniser par Bluetooth</string>
<string name="settings_wear_sync_no_devices">Aucun appareil autorisé</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> appareil autorisé</item>
<item quantity="many"><xliff:g>%d</xliff:g> appareils autorisés</item>
<item quantity="other"><xliff:g>%d</xliff:g> appareils autorisés</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">Appareils Wear OS bloqués</string>
<string name="settings_wear_sync_blocked_devices_summary">Gérer les appareils Wear OS bloqués</string>
<string name="settings_wear_sync_no_blocked_devices">Aucun appareil bloqué</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> appareil bloqué</item>
<item quantity="many"><xliff:g>%d</xliff:g> appareils bloqués</item>
<item quantity="other"><xliff:g>%d</xliff:g> appareils bloqués</item>
</plurals>
<string name="settings_wear_sync_unpair_title">Supprimer l\'appareil?</string>
<string name="settings_wear_sync_unpair_message">Retirer <xliff:g>%1$s</xliff:g> de Catima? Il ne pourra plus synchroniser de cartes.</string>
<string name="settings_wear_sync_unblock_message">Retirer <xliff:g>%1$s</xliff:g> de la liste des appareils bloqués? Il pourra demander l\'accès à nouveau.</string>
<string name="settings_wear_sync_unpair">Retirer</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_tap_to_remove">Appuyer sur un appareil pour le supprimer.</string>
<string name="wear_bt_pairing_channel_name">Appairage d\'appareil Wear OS</string>
<string name="wear_bt_pairing_title">L\'appareil Wear OS souhaite se synchroniser</string>
<string name="wear_bt_pairing_message">Autoriser <xliff:g>%1$s</xliff:g> à accéder à vos cartes?</string>
<string name="wear_bt_pairing_allow">Autoriser</string>
<string name="wear_bt_pairing_block">Bloquer</string>
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> est maintenant autorisé à se synchroniser.</string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> a été bloqué.</string>
<string name="wear_bt_pairing_mismatch_title">Nouvelle demande d\'autorisation de <xliff:g>%1$s</xliff:g></string>
<string name="open_settings">Ouvrir les paramètres</string>
<string name="wear_bt_channel_name">Compagnon Wear OS</string>
<string name="wear_bt_notification_title">Synchronisation Catima Wear OS active</string>
<string name="settings_category_title_smartwatch">Prise en charge des montres connectées</string>
<string name="wear_bt_failed_foreground_notification_title">Erreur au démarrage du service de synchronisation Wear OS</string>
<string name="wear_bt_critical_error_channel_name">Erreur critique de synchronisation Wear OS</string>
<string name="wear_bt_pairing_mismatch_message">Cet appareil était autorisé précédemment mais affiche maintenant un jeton d\'identité différent. Cela arrive lorsque l\'appli Wear OS est réinstallée ou ses données sont supprimées. Cela peut aussi indiquer une tentative d\'usurpation d\'adresse MAC du Bluetooth.\n\nAcceptez uniquement si vous avez récemment réinstallée l\'appli Catima Wear OS sur cette montre.</string>
</resources>
+41 -2
View File
@@ -64,8 +64,8 @@
<string name="settings_keep_screen_on_summary">Evita que se apague a pantalla cando se ve unha tarxeta</string>
<string name="settings_disable_lockscreen_while_viewing_card">Evitar bloqueo da pantalla</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">Desactiva o bloqueo da pantalla cando se ve unha tarxeta</string>
<string name="settings_allow_content_provider_read_title">Permitir a outras apps acceder aos meus datos</string>
<string name="settings_allow_content_provider_read_summary">As app terán que solicitar igualmente o permiso para acceder aos datos</string>
<string name="settings_allow_content_provider_read_title">Sincronizar con Gadgetbridge</string>
<string name="settings_allow_content_provider_read_summary">Permite á app Gadgetbridge sincronizar as túas tarxetas cos reloxos compatibles</string>
<string name="settings_use_volume_keys_navigation">Cambiar de tarxeta cos botóns de volume</string>
<string name="settings_use_volume_keys_navigation_summary">Usar os botóns de volume para cambiar a tarxeta que se mostra</string>
<string name="intent_import_card_from_url_share_text">Quero compartir unha tarxeta contigo</string>
@@ -306,4 +306,43 @@
<string name="zoomFrontImage">Zoom na imaxe frontal</string>
<string name="zoomBackImage">Zoom na imaxe posterior</string>
<string name="show_card_id">Mostrar ID da tarxeta</string>
<string name="open_in_gallery">Abrir na galería de imaxes</string>
<string name="settings_wear_sync">Sincronizar con Wear OS</string>
<string name="settings_wear_sync_summary">Permite que a app asocidada de Wear OS lea as túas tarxetas utilizando Bluetooth</string>
<string name="wear_sync_permission_required">Catima precisa os permisos que non concedeches para sincronizar co teu reloxo Wear OS</string>
<string name="settings_wear_sync_devices">Dispositivos Wear OS permitidos</string>
<string name="settings_wear_sync_devices_summary">Xestiona os dispositivos con permiso para sincronizar usando Bluetooth</string>
<string name="settings_wear_sync_no_devices">Sen dispositivos permitidos</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> dispositivo permitido</item>
<item quantity="other"><xliff:g>%d</xliff:g> dispositivos permitidos</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">Dispositivos Wear OS bloqueados</string>
<string name="settings_wear_sync_blocked_devices_summary">Xestiona os dispositivos Wear OS bloquedos</string>
<string name="settings_wear_sync_no_blocked_devices">Sen dispositivos bloqueados</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> dispositivo bloqueado</item>
<item quantity="other"><xliff:g>%d</xliff:g> dispositivos bloqueados</item>
</plurals>
<string name="settings_wear_sync_unpair_title">Retirar dispositivo?</string>
<string name="settings_wear_sync_unpair_message">Retirar <xliff:g>%1$s</xliff:g> de Catima? Non poderá seguir sincronizándose coas túas tarxetas.</string>
<string name="settings_wear_sync_unblock_message">Retirar <xliff:g>%1$s</xliff:g> da lista de bloqueados? Poderá volver a solicitar o acceso.</string>
<string name="settings_wear_sync_unpair">Retirar</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_tap_to_remove">Toca nun dispositivo para retiralo.</string>
<string name="wear_bt_pairing_channel_name">Emparellando dispositivo Wear OS</string>
<string name="wear_bt_pairing_title">O dispositivo Wear OS quere sincronizar</string>
<string name="wear_bt_pairing_message">Permitirlle a <xliff:g>%1$s</xliff:g> que acceda ás tarxetas?</string>
<string name="wear_bt_pairing_allow">Permitir</string>
<string name="wear_bt_pairing_block">Bloquear</string>
<string name="wear_bt_pairing_allowed">Agora <xliff:g>%1$s</xliff:g> ten permiso para sincronizar.</string>
<string name="wear_bt_pairing_blocked">Bloqueaches a <xliff:g>%1$s</xliff:g>.</string>
<string name="wear_bt_pairing_mismatch_title">Nova solicitude de autorización desde <xliff:g>%1$s</xliff:g></string>
<string name="wear_bt_pairing_mismatch_message">O dispositivo tiña permiso anteriormente pero agora presenta un token de identidade diferente. Isto acontece cando se reinstala a app Wear OS ou se limparon os datos. Tamén pode indicar un intento de suplantación utilizando a MAC de Bluetooth.\n\nSó acepta se reinstalaches recentemente a app Catima Wear OS neste reloxo.</string>
<string name="open_settings">Abrir os axustes</string>
<string name="wear_bt_channel_name">Aplicación Wear OS</string>
<string name="wear_bt_notification_title">Sincronización activa Wear OS para Catima</string>
<string name="settings_category_title_smartwatch">Compatibilidade con reloxos</string>
<string name="wear_bt_failed_foreground_notification_title">Fallou o inicio do servizo de sincronización con Wear OS</string>
<string name="wear_bt_critical_error_channel_name">Erro crítico ao sincronizar con Wear OS</string>
</resources>
+39 -2
View File
@@ -220,8 +220,8 @@
<string name="show_note">Tampilkan catatan</string>
<string name="permissionReadCardsLabel">Bacalah kartu Catima</string>
<string name="permissionReadCardsDescription">baca kartu Anda dan semua detailnya, termasuk catatan dan gambar</string>
<string name="settings_allow_content_provider_read_title">Izinkan aplikasi lain mengakses data saya</string>
<string name="settings_allow_content_provider_read_summary">Aplikasi masih harus meminta izin untuk diberikan akses</string>
<string name="settings_allow_content_provider_read_title">Sinkronkan dengan Gadgetbridge</string>
<string name="settings_allow_content_provider_read_summary">Memungkinkan aplikasi Gadgetbridge menyinkronkan kartu Anda ke jam tangan yang didukung</string>
<string name="settings_keep_screen_on_summary">Menonaktifkan batas waktu layar saat melihat kartu</string>
<string name="settings_oled_dark_summary">Mengurangi penggunaan baterai pada layar OLED</string>
<string name="settings_category_title_general">Umum</string>
@@ -301,4 +301,41 @@
<string name="sort_by_most_recently_added">Terakhir ditambahkan</string>
<string name="zoomFrontImage">Perbesar gambar depan</string>
<string name="zoomBackImage">Perbesar gambar belakang</string>
<string name="settings_wear_sync">Sinkronkan dengan Wear OS</string>
<string name="settings_wear_sync_summary">Memungkinkan aplikasi pendamping Wear OS membaca kartu Anda melalui Bluetooth</string>
<string name="wear_sync_permission_required">Catima memerlukan izin yang Anda tolak untuk menyinkronkan dengan smartwatch Wear OS Anda</string>
<string name="settings_wear_sync_devices">Perangkat Wear OS yang diizinkan</string>
<string name="settings_wear_sync_devices_summary">Kelola perangkat yang diizinkan untuk sinkronisasi melalui Bluetooth</string>
<string name="settings_wear_sync_no_devices">Tidak ada perangkat yang diizinkan</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="other"><xliff:g>%d</xliff:g> perangkat diizinkan</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">Perangkat Wear OS yang diblokir</string>
<string name="settings_wear_sync_blocked_devices_summary">Kelola perangkat Wear OS yang diblokir</string>
<string name="settings_wear_sync_no_blocked_devices">Tidak ada perangkat yang diblokir</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="other"><xliff:g>%d</xliff:g> perangkat diblokir</item>
</plurals>
<string name="settings_wear_sync_unpair_title">Hapus perangkat?</string>
<string name="settings_wear_sync_unpair_message">Hapus <xliff:g>%1$s</xliff:g> dari Catima? Perangkat ini tidak akan dapat menyinkronkan kartu Anda lagi.</string>
<string name="settings_wear_sync_unblock_message">Hapus <xliff:g>%1$s</xliff:g> dari daftar yang diblokir? Perangkat ini akan dapat meminta akses lagi.</string>
<string name="settings_wear_sync_unpair">Hapus</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_tap_to_remove">Ketuk perangkat untuk menghapusnya.</string>
<string name="wear_bt_pairing_channel_name">Pemasangan perangkat Wear OS</string>
<string name="wear_bt_pairing_title">Perangkat Wear OS ingin menyinkronkan</string>
<string name="wear_bt_pairing_message">Izinkan <xliff:g>%1$s</xliff:g> mengakses kartu Anda?</string>
<string name="wear_bt_pairing_allow">Izinkan</string>
<string name="wear_bt_pairing_block">Blokir</string>
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> kini diizinkan untuk menyinkronkan.</string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> telah diblokir.</string>
<string name="wear_bt_pairing_mismatch_title">Permintaan otorisasi baru dari <xliff:g>%1$s</xliff:g></string>
<string name="wear_bt_pairing_mismatch_message">Perangkat ini sebelumnya diotorisasi tetapi kini menampilkan token identitas yang berbeda. Hal ini terjadi ketika aplikasi Wear OS diinstal ulang atau datanya dihapus. Hal ini juga dapat mengindikasikan upaya spoofing MAC Bluetooth.\n\nTerima hanya jika Anda baru-baru ini menginstal ulang aplikasi Catima Wear OS di jam tangan ini.</string>
<string name="open_settings">Buka pengaturan</string>
<string name="open_in_gallery">Buka di galeri gambar</string>
<string name="wear_bt_channel_name">Pendamping Wear OS</string>
<string name="wear_bt_notification_title">Sinkronisasi Catima Wear OS aktif</string>
<string name="settings_category_title_smartwatch">Dukungan smartwatch</string>
<string name="wear_bt_failed_foreground_notification_title">Gagal memulai layanan sinkronisasi Wear OS</string>
<string name="wear_bt_critical_error_channel_name">Kesalahan kritis sinkronisasi Wear OS</string>
</resources>
+2
View File
@@ -313,4 +313,6 @@
<string name="show_card_id">Mostra ID carta</string>
<string name="zoomFrontImage">Zoom immagine fronte</string>
<string name="zoomBackImage">Zoom immagine retro</string>
<string name="open_in_gallery">Apri nella galleria immagini</string>
<string name="open_settings">Apri le impostazioni</string>
</resources>
+38 -2
View File
@@ -217,14 +217,14 @@
<string name="settings_column_count_4">4</string>
<string name="settings_category_title_privacy">プライバシー</string>
<string name="exportCancelled">輸出がキャンセルされた</string>
<string name="settings_allow_content_provider_read_title">他のアプリが自分のデータにアクセスすることを許可する</string>
<string name="settings_allow_content_provider_read_title">Gadgetbridgeと同期</string>
<string name="permissionReadCardsDescription">Catimaカードと、ノートや画像を含むすべての詳細を読み取る</string>
<string name="settings_use_volume_keys_navigation_summary">ボリュームボタンを使ってどのカードを表示するかを変更する</string>
<string name="unsupportedFile">このファイルはサポートされていません</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">著作権 © 2019<xliff:g>%d</xliff:g> Sylvia van Os と貢献者一同</string>
<string name="app_copyright_short">著作権 © Sylvia van Os と貢献者一同</string>
<string name="app_copyright_old">Loyalty Card Keychain が基になりました\n著作権 © 20162020 Branden Archer</string>
<string name="settings_allow_content_provider_read_summary">アプリは継続したアクセス許可を要求します</string>
<string name="settings_allow_content_provider_read_summary">Gadgetbridge アプリによる対応スマートウォッチとカードの同期を許可する</string>
<string name="settings_use_volume_keys_navigation">音量ボタンでカードを切り替え</string>
<plurals name="balancePoints">
<item quantity="other"><xliff:g>%s</xliff:g> ポイント</item>
@@ -301,4 +301,40 @@
<string name="zoomBackImage">背面を拡大</string>
<string name="show_card_id">カードIDを表示</string>
<string name="open_in_gallery">ギャラリーを開く</string>
<string name="settings_wear_sync">Wear OS と同期</string>
<string name="settings_wear_sync_summary">Wear OS コンパニオンアプリによる Bluetooth 経由のカード読取を許可する</string>
<string name="wear_sync_permission_required">貴方が拒否した権限は Catima が Wear OS スマートウォッチと同期するのに必要です</string>
<string name="settings_wear_sync_devices">対応している Wear OS デバイス</string>
<string name="settings_wear_sync_devices_summary">Bluetooth 経由での同期が許可されたデバイスの管理</string>
<string name="settings_wear_sync_no_devices">デバイスが許可されていません</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="other"><xliff:g>%d</xliff:g> 台のデバイスが許可されています</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">ブロックされた Wear OS デバイス</string>
<string name="settings_wear_sync_blocked_devices_summary">ブロックされた Wear OS デバイスの管理</string>
<string name="settings_wear_sync_no_blocked_devices">ブロックされているデバイスはありません</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="other"><xliff:g>%d</xliff:g> 台のデバイスがブロックされています</item>
</plurals>
<string name="settings_wear_sync_unpair_title">デバイスを削除しますか?</string>
<string name="settings_wear_sync_unpair_message">Catima から <xliff:g>%1$s</xliff:g> を削除しますか? 削除するとカードの同期は出来なくなります。</string>
<string name="settings_wear_sync_unblock_message">ブロックの一覧から <xliff:g>%1$s</xliff:g> を削除しますか? 削除すればアクセスを再要求できるようになります。</string>
<string name="settings_wear_sync_unpair">削除</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> <xliff:g>%2$s</xliff:g></string>
<string name="settings_wear_sync_tap_to_remove">タップしてデバイスを削除します。</string>
<string name="wear_bt_pairing_channel_name">Wear OS デバイスを接続しました</string>
<string name="wear_bt_pairing_title">Wear OS デバイスとの同期を要求</string>
<string name="wear_bt_pairing_message"><xliff:g>%1$s</xliff:g> にカードへのアクセスを許可しますか?</string>
<string name="wear_bt_pairing_allow">許可</string>
<string name="wear_bt_pairing_block">ブロック</string>
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> への同期が許可されました。</string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> はブロックされました。</string>
<string name="wear_bt_pairing_mismatch_title"><xliff:g>%1$s</xliff:g>からの新たな認証要求</string>
<string name="wear_bt_pairing_mismatch_message">当該デバイスは以前に認証済みでしたが異なる識別トークンを現段階で提示しています。これは Wear OS アプリを再インストールした場合もしくはそのデータが消去された場合に発生します。Bluetooth MACアドレスのなりすまし攻撃の試行の可能性もあります。\n\n当該スマートウォッチで最近 Catima Wear OS アプリを再インストールした場合のみ承認して下さい。</string>
<string name="open_settings">設定を開く</string>
<string name="wear_bt_channel_name">Wear OS コンパニオン</string>
<string name="wear_bt_notification_title">Catima は Wear OS と同期中です</string>
<string name="settings_category_title_smartwatch">対応するスマートウォッチです</string>
<string name="wear_bt_failed_foreground_notification_title">Wear OSとの同期サービスの開始が失敗しました</string>
<string name="wear_bt_critical_error_channel_name">Wear OS 同期の深刻なエラー</string>
</resources>
+11 -11
View File
@@ -11,10 +11,10 @@
<string name="delete">Ištrinti</string>
<string name="confirm">Patvirtinti</string>
<string name="ok">Gerai</string>
<string name="editCardTitle">Redaguoti lojalumo kortelę</string>
<string name="addCardTitle">Pridėti lojalumo kortelę</string>
<string name="editCardTitle">Redaguoti kortelę</string>
<string name="addCardTitle">Pridėti kortelę</string>
<string name="scanCardBarcode">Nuskaityti brūkšninį kodą</string>
<string name="importExport">Importuoti/Exportuoti</string>
<string name="importExport">Importas/Exportas</string>
<string name="exportName">Exportuoti</string>
<string name="importFailed">Nepavyko importuoti</string>
<string name="exportFailed">Nepavyko eksportuoti</string>
@@ -43,7 +43,7 @@
<string name="app_loyalty_card_keychain">Lojalumo kortelės</string>
<string name="moveBarcodeToTopOfScreen">Perkelti brūkšninį kodą į ekrano viršų</string>
<string name="settings_display_barcode_max_brightness">Šviesinti ekraną</string>
<string name="failedParsingImportUriError">Nepavyko iššifruoti importo URI</string>
<string name="failedParsingImportUriError">Nepavyko nuskaityti importo URI</string>
<string name="turn_flashlight_off">Išjunkti žibintuvėlį</string>
<string name="turn_flashlight_on">Įjunkti žibintuvėlį</string>
<string name="failedGeneratingShareURL">Nepavyko sugeneruoti bendrinamo URL</string>
@@ -65,11 +65,9 @@
<string name="setBarcodeId">Nustatyti brūkšninio kodo reikšmę</string>
<string name="sameAsCardId">Tokia pat kaip ID</string>
<string name="barcodeId">Brūkšninio kodo reikšmė</string>
<string name="importFidmeMessage">Pasirinkite <i>fidme-export-request-xxxxxx.zip</i> eksportą iš FidMe, kurį norite importuoti, ir po to brūkšninių kodų tipus pasirinkite rankiniu būdu.
\nSukurkite jį iš savo FidMe profilio, pasirinkę Duomenų apsauga ir pirmiausia paspaudę Išgauti mano duomenis.</string>
<string name="importFidmeMessage">Importavimui pasirinkite eksportą iš FidMe ir po to brūkšninių kodų tipus pasirinkite rankiniu būdu. \nSukurkite jį iš savo FidMe profilio pasirinkę \"Duomenų apsauga\" ir po to paspausdami \"Išgauti mano duomenis\".</string>
<string name="importFidme">Importuoti iš FidMe</string>
<string name="importCatimaMessage">Norėdami importuoti, pasirinkite savo <i> catima.zip </i> eksportavimą iš Catima.
\nSukurkite ją iš kitos Catima programos importavimo / eksportavimo meniu, pirmiausia ten paspausdami Eksportuoti.</string>
<string name="importCatimaMessage">Norėdami importuoti, pasirinkite savo <i> catima.zip </i> eksportavimą iš Catima. \nSukurkite ją iš kitos Catima programos importavimo/eksportavimo meniu paspausdami \"Eksportuoti\".</string>
<string name="importCatima">Importuoti iš Catima</string>
<string name="accept">Priimti</string>
<string name="privacy_policy">Privatumo politika</string>
@@ -117,8 +115,8 @@
<string name="settings">Nustatymai</string>
<string name="starImage">Mėgstamiausia žvaigždė</string>
<string name="thumbnailDescription">Miniatiūra</string>
<string name="app_resources">Laisvosios trečiųjų šalių ištekliai: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Laisvosios trečiųjų šalių bibliotekos: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources">Trečiųjų šalių ištekliai: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Trečiųjų šalių bibliotekos: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_copyright_old">Paremta Loyalty Card Keychain
\nautorinės teisės © 20162020 Branden Archer</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Autorinės teisės © 2019<xliff:g>%d</xliff:g> Sylvia van Os</string>
@@ -248,7 +246,7 @@
<string name="add_manually_warning_title">Rekomenduojama skenuoti</string>
<string name="continue_">Tęsti</string>
<string name="add_manually_warning_message">Kai kuriuose parduotuvėse brūkšninio kodo vertė gali skirtis nuo kortelėje nurodyto numerio. Dėlto įvedus brūkšninį kodą rankiniu būdu gali nepavykti. Vietoj to, labai rekomenduojama nuskaityti brūkšninį kodą naudojant kamerą. Ar vis tiek norite tęsti?</string>
<string name="permissionReadCardsLabel">Skenuoti Catima Korteles</string>
<string name="permissionReadCardsLabel">Skaityti Catima korteles</string>
<string name="settings_category_title_cards">Kortelių peržiūra</string>
<string name="settings_category_title_general">Bendri</string>
<string name="settings_category_title_privacy">Privatumąi</string>
@@ -281,4 +279,6 @@
<string name="settings_column_count_portrait">Stulpeliai vertikaliame režime</string>
<string name="settings_column_count_landscape">Stulpeliai horizontaliame režime</string>
<string name="settings_automatic_column_count">Automatiškai</string>
<string name="settings_disable_nfc_while_viewing_card">NFC pauzė</string>
<string name="settings_disable_nfc_while_viewing_card_summary">Pristabdo NFC, kad peržiūrint kortelę nebūtų suaktyvinti NFC mokėjimai</string>
</resources>
+47 -2
View File
@@ -220,8 +220,8 @@
<string name="add_a_card_in_a_different_way">Pievienot karti citā veidā</string>
<string name="add_manually_warning_message">Dažām kartēm svītrkoda vērtība atšķiras no numura, kas ir rakstīts uz tās. Šī iemesla dēļ pašrocīgi ievadīts svītrkods ne vienmēr var darboties. Ir ieteicams nolasīt svītrkodu ar kameru. Vai joprojām turpināt?</string>
<string name="noCameraFoundGuideText">Šķiet, ka ierīcei nav kameras. Ja tā tomēr ir, jāmēģina pārsāknēt ierīci. Pretējā gadījumā jāizmanto zemāk esošā poga \"Vairāk iespēju\", lai pievienot svītrkodu citā veidā.</string>
<string name="settings_allow_content_provider_read_title">Ļaut citām lietotnēm piekļūt maniem datiem</string>
<string name="settings_allow_content_provider_read_summary">Lietotnēm joprojām būs jāpieprasa atļauja, lai nodrošinātu piekļuvi</string>
<string name="settings_allow_content_provider_read_title">Vienādot ar Gadgetbridge</string>
<string name="settings_allow_content_provider_read_summary">Atļauj Gadgetbridge lietotnei vienādot kartes ar atbalstītajiem pulksteņiem</string>
<string name="show_note">Rādīt piezīmi</string>
<string name="show_balance">Rādīt atlikumu</string>
<string name="settings_category_title_privacy">Privātums</string>
@@ -309,4 +309,49 @@
<string name="settings_disable_nfc_while_viewing_card_summary">Aptur NFC, lai novērstu NFC maksājumu izsaukšanu kartes apskatīšanas laikā</string>
<string name="nfc_blocked_while_viewing_card">NFC ir apturēts</string>
<string name="change_settings">Mainīt iestatījumus</string>
<string name="open_in_gallery">Atvērt attēlu galerijā</string>
<string name="sort_by_most_recently_added">Visnesenāk pievienotās</string>
<string name="zoomFrontImage">Pietuvināt priekšpuses attēlu</string>
<string name="zoomBackImage">Pietuvināt aizmugures attēlu</string>
<string name="show_card_id">Rādīt kartes Id</string>
<string name="settings_wear_sync">Vienādot ar Wear OS</string>
<string name="settings_wear_sync_summary">Ļauj Wear OS palīglietotnei lasīt kartes ar Bluetooth starpniecību</string>
<string name="wear_sync_permission_required">Catima ir nepieciešamas iepriekš noraidītās atļaujas, lai vienādotu ar Wear OS viedpulksteni</string>
<string name="settings_wear_sync_devices">Atļautās Wear OS ierīces</string>
<string name="wear_bt_critical_error_channel_name">Būtiska Wear OS vienādošanas kļūda</string>
<string name="wear_bt_failed_foreground_notification_title">Neizdevās palaist Wear OS vienādošanas pakalpojumu</string>
<string name="settings_category_title_smartwatch">Viedpulksteņu atbalsts</string>
<string name="wear_bt_notification_title">Catima Wear OS vienādošana darbojas</string>
<string name="wear_bt_channel_name">Wear OS palīgs</string>
<string name="open_settings">Atvērt iestatījumus</string>
<string name="wear_bt_pairing_mismatch_message">Šī ierīce iepriekš bija pilnvarota, bet tagad norāda citu identitātes pilnvaru. Tas notiek, kad Wear OS lietotne ir uzstādīta no jauna vai ir notīrīti tās dati. Tas var norādīt arī uz Bluetooth MAC atdarināšanas mēģinājumu.\n\nPieņemt tikai tad, ja šajā pulkstenī Catima Wear OS lietotne tika uzstādīta no jauna.</string>
<string name="wear_bt_pairing_mismatch_title">Jauns pilnvarošanas pieprasījums no <xliff:g>%1$s</xliff:g></string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> tika liegts.</string>
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> tagad ir atļauta vienādošana.</string>
<string name="wear_bt_pairing_block">Liegt</string>
<string name="wear_bt_pairing_allow">Atļaut</string>
<string name="wear_bt_pairing_message">Atļaut <xliff:g>%1$s</xliff:g> piekļūt kartēm?</string>
<string name="wear_bt_pairing_title">Wear OS ierīce vēlas vienādot</string>
<string name="wear_bt_pairing_channel_name">Wear OS ierīces pārošana</string>
<string name="settings_wear_sync_tap_to_remove">Piesist ierīces nosaukumam, lai to noņemtu.</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_unpair">Noņemt</string>
<string name="settings_wear_sync_unblock_message">Noņemt <xliff:g>%1$s</xliff:g> no liegumu saraksta? Tā atkal varēs pieprasīt piekļuvi.</string>
<string name="settings_wear_sync_unpair_message">Noņemt <xliff:g>%1$s</xliff:g> no Catima? Tā vairs nevarēs vienādot kartes.</string>
<string name="settings_wear_sync_unpair_title">Noņemt ierīci?</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="zero">Liegtas <xliff:g>%d</xliff:g> ierīces</item>
<item quantity="one">Liegta <xliff:g>%d</xliff:g> ierīce</item>
<item quantity="other">Liegtas <xliff:g>%d</xliff:g> ierīces</item>
</plurals>
<string name="settings_wear_sync_no_blocked_devices">Nav liegtu ierīču</string>
<string name="settings_wear_sync_blocked_devices_summary">Liegto Wear OS ierīču pārvaldīšana</string>
<string name="settings_wear_sync_blocked_devices">Liegtās Wear OS ierīces</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="zero">Atļautas <xliff:g>%d</xliff:g> ierīces</item>
<item quantity="one">Atļauta <xliff:g>%d</xliff:g> ierīce</item>
<item quantity="other">Atļautas <xliff:g>%d</xliff:g> ierīces</item>
</plurals>
<string name="settings_wear_sync_no_devices">Nav atļautu ierīču</string>
<string name="settings_wear_sync_devices_summary">Ierīču, kurām ir atļauts vienādot ar Bluetooth starpniecību, pārvaldīšana</string>
</resources>
+50 -11
View File
@@ -32,10 +32,10 @@
<string name="exportSuccessfulTitle">Exporteren voltooid</string>
<string name="exportFailedTitle">Exporteren mislukt</string>
<string name="exportFailed">Het exporteren is mislukt</string>
<string name="importing">Bezig met importeren…</string>
<string name="importing">Importeren…</string>
<string name="exporting">Bezig met exporteren…</string>
<string name="exportOptionExplanation">De gegevens worden weggeschreven op een locatie naar keuze</string>
<string name="importOptionFilesystemTitle">Importeren uit bestandssysteem</string>
<string name="importOptionFilesystemTitle">Importeren</string>
<string name="importOptionFilesystemExplanation">Kies een specifiek bestand van het bestandssysteem</string>
<string name="importOptionFilesystemButton">Van bestandssysteem</string>
<string name="about">Over</string>
@@ -52,7 +52,7 @@
<string name="settings_display_barcode_max_brightness">Scherm oplichten bij tonen van barcode</string>
<string name="intent_import_card_from_url_share_text">Ik wil een klantenkaart met je delen</string>
<string name="all">Alles</string>
<string name="importSuccessful">De gegevens zijn geïmporteerd</string>
<string name="importSuccessful">Gegevens geïmporteerd</string>
<string name="deleteConfirmationGroup">Groep verwijderen\?</string>
<string name="noGroups">Druk op de plusknop (+) om een groep toe te voegen</string>
<string name="exportSuccessful">De gegevens zijn geëxporteerd</string>
@@ -95,8 +95,8 @@
<string name="accept">Accepteren</string>
<string name="importVoucherVaultMessage">Kies het te importeren exportbestand. \nGa naar het exportmenu van Voucher Vault om een exportbestand samen te stellen.</string>
<string name="importVoucherVault">Importeren uit Voucher Vault</string>
<string name="importLoyaltyCardKeychainMessage">Kies het te importeren exportbestand. \nGa naar het import-/exportmenu van Klantenkaartkluis om een exportbestand samen te stellen.</string>
<string name="importLoyaltyCardKeychain">Importeren uit Klantenkaartkluis</string>
<string name="importLoyaltyCardKeychainMessage">Kies het te importeren exportbestand. \nGa naar het import-/exportmenu van Loyalty Card Keychain om een exportbestand samen te stellen.</string>
<string name="importLoyaltyCardKeychain">Importeren uit Loyalty Card Keychain</string>
<string name="importFidmeMessage">Kies het te importeren exportbestand en kies nadien de juiste barcodes. \nGa naar je FidMe-profiel en druk op Gegevensbescherming om een exportbestand samen te stellen.</string>
<string name="importCatimaMessage">Kies het te importeren exportbestand. \nGa naar het import-/exportmenu van Catima op een ander apparaat om een exportbestand samen te stellen.</string>
<string name="importFidme">Importeren uit FidMe</string>
@@ -225,9 +225,9 @@
<string name="show_note">Aantekening tonen</string>
<string name="show_validity">Geldigheid tonen</string>
<string name="permissionReadCardsLabel">Catimakaarten uitlezen</string>
<string name="settings_allow_content_provider_read_summary">Let op: apps moeten nog steeds om toestemming vragen</string>
<string name="settings_allow_content_provider_read_summary">Hiermee kan de Gadgetbridge-app jouw kaarten synchroniseren met ondersteunde horloges</string>
<string name="permissionReadCardsDescription">Lees alle Catima-kaarten uit, inclusief alle details, aantekeningen en afbeeldingen</string>
<string name="settings_allow_content_provider_read_title">Andere apps toegang geven tot gegevens</string>
<string name="settings_allow_content_provider_read_title">Synchroniseren met Gadgetbridge</string>
<string name="settings_keep_screen_on_summary">Laat het scherm niet op zwart gaan tijdens het bekijken van een kaart</string>
<string name="settings_category_title_privacy">Privacy</string>
<string name="settings_display_barcode_max_brightness_summary">Benodigd voor de werking van sommige scanners</string>
@@ -249,7 +249,7 @@
<string name="field_must_not_be_empty">Dit veld is vereist</string>
<string name="app_name">Catima</string>
<string name="add_manually_warning_title">Scannen wordt aangeraden</string>
<string name="add_manually_warning_message">Bij sommige kaarten wijkt de barcodewaarde af van het nummer op de kaart. Hierdoor werkt het handmatig invoeren van een barcode niet altijd. Het wordt aangeraden om in plaats daarvan de barcode met je camera te scannen. Wilt u nog steeds doorgaan?</string>
<string name="add_manually_warning_message">Bij sommige kaarten wijkt de barcodewaarde af van het nummer op de kaart. Hierdoor werkt het handmatig invoeren van een barcode niet altijd. Het wordt aangeraden om in plaats daarvan de barcode met je camera te scannen. Wil je nog steeds doorgaan?</string>
<string name="continue_">Ga door</string>
<string name="spend">Geef uit</string>
<string name="receive">Ontvang</string>
@@ -259,7 +259,7 @@
<string name="failedLaunchingFileManager">Geen ondersteunde bestandsbeheerder aangetroffen</string>
<string name="multipleBarcodesFoundPleaseChooseOne">Welke van de gevonden barcodes wil je gebruiken?</string>
<string name="pageWithNumber">Pagina <xliff:g>%d</xliff:g></string>
<string name="noCameraFoundGuideText">Uw apparaat lijkt niet over een camera te beschikken. Als het dat wel doet, probeer uw apparaat dan opnieuw op te starten. Anders gebruikt u de knop \"Meer opties\" beneden om handmatig een streepjescode in te voeren.</string>
<string name="noCameraFoundGuideText">Jouw apparaat lijkt niet over een camera te beschikken. Als het dat wel doet, probeer het apparaat dan opnieuw op te starten. Gebruik anders de knop \"Meer opties\" beneden om handmatig een streepjescode in te voeren.</string>
<string name="importCancelled">Importeren geannuleerd</string>
<string name="exportCancelled">Exporteren geannuleerd</string>
<string name="useFrontImage">Gebruik voorzijde van kaart</string>
@@ -299,12 +299,51 @@
<string name="back">Terug</string>
<string name="barcodeEncoding">Barcodecodering</string>
<string name="change_settings">Verander instellingen</string>
<string name="settings_disable_nfc_while_viewing_card">Pauzeer NFC</string>
<string name="settings_disable_nfc_while_viewing_card">NFC pauzeren</string>
<string name="settings_disable_nfc_while_viewing_card_summary">Pauzeert NFC om te voorkomen dat NFC-betalingen activeren tijdens het bekijken van een kaart</string>
<string name="nfc_blocked_while_viewing_card">NFC is gepauzeerd</string>
<string name="nfc_block_system_error">Pauzeren van NFC mislukt</string>
<string name="nfc_block_system_error">NFC pauzeren is mislukt</string>
<string name="sort_by_most_recently_added">Op onlangs toegevoegd</string>
<string name="show_card_id">Kaart ID tonen</string>
<string name="zoomFrontImage">Voorzijde inzoomen</string>
<string name="zoomBackImage">Achterzijde inzoomen</string>
<string name="open_in_gallery">Open in afbeeldingsgalerij</string>
<string name="settings_wear_sync">Synchroniseren met Wear OS</string>
<string name="wear_sync_permission_required">Catima heeft toestemming nodig om te synchroniseren met je Wear OS smartwatch</string>
<string name="settings_wear_sync_devices">Toegestane Wear OS-apparaten</string>
<string name="settings_wear_sync_devices_summary">Apparaten beheren die via Bluetooth kunnen worden gesynchroniseerd</string>
<string name="settings_wear_sync_no_devices">Geen toegestane apparaten</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> apparaat toegestaan</item>
<item quantity="other"><xliff:g>%d</xliff:g> apparaten toegestaan</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">Geblokkeerde Wear OS-apparaten</string>
<string name="settings_wear_sync_blocked_devices_summary">Geblokkeerde Wear OS-apparaten beheren</string>
<string name="settings_wear_sync_no_blocked_devices">Geen geblokkeerde apparaten</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> apparaat geblokkeerd</item>
<item quantity="other"><xliff:g>%d</xliff:g> apparaten geblokkeerd</item>
</plurals>
<string name="settings_wear_sync_unpair_title">Apparaat verwijderen?</string>
<string name="settings_wear_sync_unpair_message"><xliff:g>%1$s</xliff:g> van Catima verwijderen? Het zal je kaarten niet meer kunnen synchroniseren.</string>
<string name="wear_bt_pairing_message"><xliff:g>%1$s</xliff:g> toestaan om toegang te krijgen tot jouw kaarten?</string>
<string name="settings_wear_sync_summary">Stelt de Wear OS companion app in staat om jouw kaarten via Bluetooth te lezen</string>
<string name="wear_bt_channel_name">Wear OS companion</string>
<string name="settings_wear_sync_unblock_message"><xliff:g>%1$s</xliff:g> van de blokkeringslijst verwijderen? Het zal opnieuw toegang kunnen aanvragen.</string>
<string name="settings_wear_sync_unpair">Verwijderen</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_tap_to_remove">Tik op een apparaat om het te verwijderen.</string>
<string name="wear_bt_pairing_channel_name">Wear OS-apparaat koppelen</string>
<string name="wear_bt_pairing_title">Wear OS-apparaat wil synchroniseren</string>
<string name="wear_bt_pairing_allow">Toestaan</string>
<string name="wear_bt_pairing_block">Blokkeren</string>
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> mag niet synchroniseren.</string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> is geblokkeerd.</string>
<string name="wear_bt_pairing_mismatch_title">Nieuw autorisatieverzoek van <xliff:g>%1$s</xliff:g></string>
<string name="wear_bt_pairing_mismatch_message">Dit apparaat was eerder geautoriseerd, maar presenteert nu een ander identiteitstoken. Dit gebeurt wanneer de Wear OS-app opnieuw wordt geïnstalleerd of de gegevens ervan worden gewist. Het kan ook duiden op een Bluetooth MAC-spoofingpoging.\n\nAccepteer alleen als je onlangs de Catima Wear OS-app opnieuw op dit horloge hebt geïnstalleerd.</string>
<string name="open_settings">Instellingen openen</string>
<string name="wear_bt_notification_title">Catima Wear OS synchronisatie is geactiveerd</string>
<string name="settings_category_title_smartwatch">Smartwatch-ondersteuning</string>
<string name="wear_bt_failed_foreground_notification_title">Starten van Wear OS-synchronisatieservice is mislukt</string>
<string name="wear_bt_critical_error_channel_name">Kritische fout met Wear OS-synchronisatie</string>
</resources>
+45 -2
View File
@@ -238,11 +238,11 @@
<string name="show_note">Pokaż notatkę</string>
<string name="permissionReadCardsLabel">Odczytaj karty Catima</string>
<string name="permissionReadCardsDescription">odczytywanie swoich kart Catima i ich szczegółów, włącznie z notatkami i obrazkami</string>
<string name="settings_allow_content_provider_read_title">Pozwól innym aplikacjom na dostęp do moich danych</string>
<string name="settings_allow_content_provider_read_title">Synchronizacja z Gadgetbridge</string>
<string name="settings_display_barcode_max_brightness_summary">Potrzebne aby niektóre skanery działały</string>
<string name="settings_keep_screen_on_summary">Wyłącza wygaszanie ekranu kiedy wyświetlana jest karta</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">Wyłącza blokadę ekranu kiedy wyświetlana jest karta</string>
<string name="settings_allow_content_provider_read_summary">Aplikacje będą wymagały pozwolenia aby otrzymać dostęp do danych</string>
<string name="settings_allow_content_provider_read_summary">Umożliwia aplikacji Gadgetbridge synchronizację kart z obsługiwanymi zegarkami</string>
<string name="settings_oled_dark_summary">Zmniejsza zużycie baterii na wyświetlaczach OLED</string>
<string name="settings_category_title_cards">Widok karty</string>
<string name="settings_category_title_general">Ogólne</string>
@@ -319,4 +319,47 @@
<string name="zoomFrontImage">Powiększ zdjęcie z przodu</string>
<string name="zoomBackImage">Powiększ zdjęcie z tyłu</string>
<string name="show_card_id">Pokaż ID karty</string>
<string name="open_in_gallery">Otwórz zdjęcie w galerii</string>
<string name="settings_wear_sync">Synchronizacja z systemem Wear OS</string>
<string name="settings_wear_sync_summary">Umożliwia aplikacji towarzyszącej Wear OS odczytywanie kart za pośrednictwem Bluetooth</string>
<string name="wear_sync_permission_required">Aplikacja Catima potrzebuje uprawnień, których nie przyznałeś, aby zsynchronizować się z Twoim smartwatchem z systemem Wear OS</string>
<string name="settings_wear_sync_devices">Obsługiwane urządzenia z systemem Wear OS</string>
<string name="settings_wear_sync_devices_summary">Zarządzaj urządzeniami, które mogą synchronizować się przez Bluetooth</string>
<string name="settings_wear_sync_no_devices">Żadne urządzenia nie są dozwolone</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> urządzenie dozwolone</item>
<item quantity="few"><xliff:g>%d</xliff:g> urządzenia dozwolone</item>
<item quantity="many"><xliff:g>%d</xliff:g> urządzeń dozwolonych</item>
<item quantity="other"><xliff:g>%d</xliff:g> urządzeń dozwolonych</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">Zablokowane urządzenia z systemem Wear OS</string>
<string name="settings_wear_sync_blocked_devices_summary">Zarządzanie zablokowanymi urządzeniami z systemem Wear OS</string>
<string name="settings_wear_sync_no_blocked_devices">Brak zablokowanych urządzeń</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> urządzenie zablokowane</item>
<item quantity="few"><xliff:g>%d</xliff:g> urządzenia zablokowane</item>
<item quantity="many"><xliff:g>%d</xliff:g> urządzeń zablokowanych</item>
<item quantity="other"><xliff:g>%d</xliff:g> urządzeń zablokowanych</item>
</plurals>
<string name="settings_wear_sync_unpair_title">Usunąć urządzenie?</string>
<string name="settings_wear_sync_unpair_message">Czy chcesz usunąć <xliff:g>%1$s</xliff:g> z aplikacji Catima? W takim przypadku aplikacja nie będzie już mogła synchronizować Twoich kart.</string>
<string name="settings_wear_sync_unblock_message">Czy chcesz usunąć <xliff:g>%1$s</xliff:g> z listy zablokowanych? Wówczas będzie mogło ponownie poprosić o dostęp.</string>
<string name="settings_wear_sync_unpair">Usuń</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_tap_to_remove">Dotknij urządzenia, aby je usunąć.</string>
<string name="wear_bt_pairing_channel_name">Parowanie urządzeń z systemem Wear OS</string>
<string name="wear_bt_pairing_title">Urządzenie z systemem Wear OS próbuje się zsynchronizować</string>
<string name="wear_bt_pairing_message">Czy zezwolić <xliff:g>%1$s</xliff:g> na dostęp do Twoich kart?</string>
<string name="wear_bt_pairing_allow">Zezwól</string>
<string name="wear_bt_pairing_block">Zablokuj</string>
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> może teraz zostać zsynchronizowane.</string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> zostało zablokowane.</string>
<string name="wear_bt_pairing_mismatch_title">Nowy wniosek o autoryzację od <xliff:g>%1$s</xliff:g></string>
<string name="wear_bt_pairing_mismatch_message">To urządzenie zostało wcześniej autoryzowane, ale obecnie przedstawia inny token tożsamości. Sytuacja ta ma miejsce po ponownej instalacji aplikacji Wear OS lub wyczyszczeniu jej danych. Może to również wskazywać na próbę sfałszowania adresu MAC przez Bluetooth.\n\nZatwierdź tylko wtedy, gdy niedawno ponownie zainstalowałeś aplikację Catima Wear OS na tym zegarku.</string>
<string name="open_settings">Otwórz ustawienia</string>
<string name="wear_bt_channel_name">Aplikacja towarzysząca dla Wear OS</string>
<string name="wear_bt_notification_title">Synchronizacja Catima Wear OS jest aktywna</string>
<string name="settings_category_title_smartwatch">Obsługa smartwatchów</string>
<string name="wear_bt_failed_foreground_notification_title">Nie udało się uruchomić usługi synchronizacji Wear OS</string>
<string name="wear_bt_critical_error_channel_name">Krytyczny błąd synchronizacji systemu Wear OS</string>
</resources>
+43 -2
View File
@@ -218,8 +218,8 @@
<string name="intent_import_card_from_url_share_text">Eu quero compartilhar um cartão com você</string>
<string name="settings_keep_screen_on">Manter tela ligada</string>
<string name="settings_disable_lockscreen_while_viewing_card">Previnir bloqueio de tela</string>
<string name="settings_allow_content_provider_read_title">Permitir que outros aplicativos acessem meus dados</string>
<string name="settings_allow_content_provider_read_summary">Aplicativos ainda precisarão pedir permissão para ter acesso concedido</string>
<string name="settings_allow_content_provider_read_title">Sincronização com o Gadgetbridge</string>
<string name="settings_allow_content_provider_read_summary">Permite que o aplicativo Gadgetbridge sincronize seus cartões com relógios suportados</string>
<string name="group_edit">Editar grupo</string>
<string name="exportSuccessful">Dados exportados</string>
<string name="noGroups">Clique no botão \"+\" para adicionar grupos a serem categorizados</string>
@@ -313,4 +313,45 @@
<string name="zoomFrontImage">Ampliar imagem frontal</string>
<string name="zoomBackImage">Reduzir o zoom da imagem</string>
<string name="show_card_id">Mostrar ID do cartão</string>
<string name="open_in_gallery">Abrir na galeria de imagens</string>
<string name="settings_wear_sync">Sincronizar com o Wear OS</string>
<string name="settings_wear_sync_summary">Permite que o aplicativo complementar do Wear OS leia seus cartões via Bluetooth</string>
<string name="wear_sync_permission_required">A Catima precisa das permissões que você negou para sincronizar com seu smartwatch Wear OS</string>
<string name="settings_wear_sync_devices">Dispositivos permitidos pelo Wear OS</string>
<string name="settings_wear_sync_devices_summary">Gerencie dispositivos autorizados a sincronizar via Bluetooth</string>
<string name="settings_wear_sync_no_devices">Dispositivos não permitidos</string>
<string name="settings_wear_sync_blocked_devices">Dispositivos Wear OS bloqueados</string>
<string name="settings_wear_sync_blocked_devices_summary">Gerencie dispositivos bloqueados do Wear OS</string>
<string name="settings_wear_sync_no_blocked_devices">Nenhum dispositivo bloqueado</string>
<string name="settings_wear_sync_unpair_title">Remover o dispositivo?</string>
<string name="settings_wear_sync_unpair">Remover</string>
<string name="settings_wear_sync_tap_to_remove">Toque em um dispositivo para removê-lo.</string>
<string name="wear_bt_pairing_channel_name">Pareamento de dispositivos Wear OS</string>
<string name="wear_bt_pairing_title">Dispositivo Wear OS quer sincronizar</string>
<string name="wear_bt_pairing_allow">Permita</string>
<string name="wear_bt_pairing_block">Bloquear</string>
<string name="wear_bt_pairing_mismatch_message">Este dispositivo já foi autorizado anteriormente, mas agora apresenta um token de identidade diferente. Isso acontece quando o app Wear OS é reinstalado ou seus dados são apagados. Também pode indicar uma tentativa de falsificação de MAC por Bluetooth.\n\nAceite apenas se você reinstalou recentemente o aplicativo Catima Wear OS neste relógio.</string>
<string name="open_settings">Abrir configurações</string>
<string name="wear_bt_channel_name">Companheiro Wear OS</string>
<string name="wear_bt_notification_title">Sincronização do Catima Wear OS ativa</string>
<string name="settings_category_title_smartwatch">Suporte a smartwatch</string>
<string name="wear_bt_failed_foreground_notification_title">Falhou ao iniciar o serviço de sincronização do Wear OS</string>
<string name="wear_bt_critical_error_channel_name">Erro crítico de sincronização do Wear OS</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> dispositivo permitido</item>
<item quantity="many"><xliff:g>%d</xliff:g> dispositivos permitidos</item>
<item quantity="other"><xliff:g>%d</xliff:g> dispositivos permitidos</item>
</plurals>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> dispositivo bloqueado</item>
<item quantity="many"><xliff:g>%d</xliff:g> dispositivos bloqueados</item>
<item quantity="other"><xliff:g>%d</xliff:g> dispositivos bloqueados</item>
</plurals>
<string name="settings_wear_sync_unpair_message">Remover <xliff:g>%1$s</xliff:g> do Catima? Ele não poderá mais sincronizar seus cartões.</string>
<string name="settings_wear_sync_unblock_message">Remover <xliff:g>%1$s</xliff:g> da lista de bloqueados? O item poderá solicitar acesso novamente.</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="wear_bt_pairing_message">Permitir que <xliff:g>%1$s</xliff:g> acesse seus cartões?</string>
<string name="wear_bt_pairing_allowed">Agora, a sincronização de <xliff:g>%1$s</xliff:g> é permitida.</string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> foi bloqueado.</string>
<string name="wear_bt_pairing_mismatch_title">Nova solicitação de autorização de <xliff:g>%1$s</xliff:g></string>
</resources>
+44 -2
View File
@@ -238,8 +238,8 @@
<string name="show_balance">Показывать баланс</string>
<string name="permissionReadCardsDescription">чтение ваших карт Catima и всех их данных, включая заметки и изображения</string>
<string name="permissionReadCardsLabel">Чтение карт Catima</string>
<string name="settings_allow_content_provider_read_summary">Приложениям по-прежнему придётся запрашивать разрешение для получения доступа</string>
<string name="settings_allow_content_provider_read_title">Разрешать другим приложениям доступ к моим данным</string>
<string name="settings_allow_content_provider_read_summary">Разрешить приложению Gadgetbridge синхронизировать ваши карты с поддерживаемыми часами</string>
<string name="settings_allow_content_provider_read_title">Синхронизация с Gadgetbridge</string>
<string name="settings_category_title_privacy">Конфиденциальность</string>
<string name="settings_display_barcode_max_brightness_summary">Необходимо для работы некоторых сканеров</string>
<string name="settings_keep_screen_on_summary">Отключение функции автоотключения экрана при отображении карты</string>
@@ -320,4 +320,46 @@
<string name="zoomFrontImage">Масштабирование лицевого изображения</string>
<string name="zoomBackImage">Масштабирование заднего изображения</string>
<string name="open_in_gallery">Открыть в галерее</string>
<string name="settings_wear_sync">Синхронизация с Wear OS</string>
<string name="settings_wear_sync_summary">Разрешить приложению-компаньону Wear OS читать ваши карты по Bluetooth</string>
<string name="wear_sync_permission_required">Для синхронизации умными часами Wear OS Catima требуются разрешения, в которых ранее было отказано</string>
<string name="settings_wear_sync_devices">Доверенные устройства Wear OS</string>
<string name="settings_wear_sync_devices_summary">Управление устройствами, которым разрешена синхронизация по Bluetooth</string>
<string name="settings_wear_sync_no_devices">Нет доверенных устройств</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> доверенное устройство</item>
<item quantity="few"><xliff:g>%d</xliff:g> доверенных устройства</item>
<item quantity="many"><xliff:g>%d</xliff:g> доверенных устройств</item>
<item quantity="other"><xliff:g>%d</xliff:g> доверенных устройств</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">Заблокированные устройства Wear OS</string>
<string name="settings_wear_sync_blocked_devices_summary">Управление заблокированными устройствами Wear OS</string>
<string name="settings_wear_sync_no_blocked_devices">Нет заблокированных устройств</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> заблокированное устройство</item>
<item quantity="few"><xliff:g>%d</xliff:g> заблокированных устройства</item>
<item quantity="many"><xliff:g>%d</xliff:g> заблокированных устройств</item>
<item quantity="other"><xliff:g>%d</xliff:g> заблокированных устройств</item>
</plurals>
<string name="settings_wear_sync_unpair_title">Удалить устройство?</string>
<string name="settings_wear_sync_unpair_message">Удалить <xliff:g>%1$s</xliff:g> из Catima? Он больше не сможет синхронизировать ваши карты.</string>
<string name="settings_wear_sync_unblock_message">Удалить устройство <xliff:g>%1$s</xliff:g> из списка заблокированных? Оно снова сможет запросить доступ.</string>
<string name="settings_wear_sync_unpair">Удалить</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_tap_to_remove">Нажмите на устройство, чтобы удалить его.</string>
<string name="wear_bt_pairing_channel_name">Сопряжение устройства Wear OS</string>
<string name="wear_bt_pairing_title">Устройство Wear OS запрашивает синхронизацию</string>
<string name="wear_bt_pairing_message">Разрешить устройству <xliff:g>%1$s</xliff:g> доступ к вашим картам?</string>
<string name="wear_bt_pairing_allow">Разрешить</string>
<string name="wear_bt_pairing_block">Заблокировать</string>
<string name="wear_bt_pairing_allowed">Устройству <xliff:g>%1$s</xliff:g> разрешена синхронизация.</string>
<string name="wear_bt_pairing_blocked">Устройство <xliff:g>%1$s</xliff:g> заблокировано.</string>
<string name="wear_bt_pairing_mismatch_title">Новый запрос на авторизацию от <xliff:g>%1$s</xliff:g></string>
<string name="wear_bt_pairing_mismatch_message">Данное устройство ранее было авторизовано, но теперь предоставляет другой токен идентификации. Это происходит, когда приложение Wear OS переустанавливается или его данные очищаются. Также это может указывать на попытку подделать MAC-адрес Bluetooth.\n\nРазрешайте только в том случае, если вы недавно переустановили приложение Catima Wear OS на этих часах.</string>
<string name="open_settings">Открыть настройки</string>
<string name="wear_bt_channel_name">Компаньон Wear OS</string>
<string name="wear_bt_notification_title">Синхронизация Catima с Wear OS активна</string>
<string name="settings_category_title_smartwatch">Поддержка умных часов</string>
<string name="wear_bt_failed_foreground_notification_title">Невозможно запустить службу синхронизации Wear OS</string>
<string name="wear_bt_critical_error_channel_name">Критическая ошибка синхронизации Wear OS</string>
</resources>
+1
View File
@@ -318,4 +318,5 @@
<string name="zoomFrontImage">Povečaj sprednjo sliko</string>
<string name="zoomBackImage">Povečaj zadnjo sliko</string>
<string name="show_card_id">Prikaži ID kartice</string>
<string name="open_in_gallery">Odpri v galeriji slik</string>
</resources>
+33 -2
View File
@@ -62,8 +62,8 @@
<string name="selectBarcodeTitle">Odaberi bar-kod</string>
<string name="thumbnailDescription">Naslovna fotografija</string>
<string name="settings_disable_lockscreen_while_viewing_card">Spreči zaključavanje ekrana</string>
<string name="settings_allow_content_provider_read_title">Dozvoli drugim aplikacijama da pristupe mojim podacima</string>
<string name="settings_allow_content_provider_read_summary">Aplikacije će i dalje morati da zatraže dozvolu da bi im se odobrio pristup</string>
<string name="settings_allow_content_provider_read_title">Sinhronizuj sa Gadgetbridge-om</string>
<string name="settings_allow_content_provider_read_summary">Omogućava aplikaciji Gadgetbridge da sinhronizuje tvoje kartice na podržane satove</string>
<string name="intent_import_card_from_url_share_text">Želim da podelim karticu sa tobom</string>
<string name="importSuccessful">Podaci uvezeni</string>
<string name="exportSuccessful">Podaci izvezeni</string>
@@ -309,4 +309,35 @@
<string name="nfc_blocked_while_viewing_card">НФЦ је заустављен</string>
<string name="change_settings">Измени поставке</string>
<string name="nfc_block_system_error">Нисам успео застанути НФЦ</string>
<string name="sort_by_most_recently_added">Nedavno dodate</string>
<string name="zoomBackImage">Uvećaj zadnju sliku</string>
<string name="show_card_id">Prikaži ID kartice</string>
<string name="settings_wear_sync">Sinhronizuj sa Wear OS-om</string>
<string name="settings_wear_sync_summary">Omogućava Wear OS pratnoj aplikaciji da pročita tvoje kartice preko Bluetootha</string>
<string name="wear_sync_permission_required">Catima trebaju dozvole koje si odbio da bi mogla sinhronizovati sa tvojim Wear OS pametnim satom</string>
<string name="settings_wear_sync_devices">Dozvoljeni Wear OS uređaji</string>
<string name="settings_wear_sync_devices_summary">Upravljaj dozvoljenim uređajima za sinhronizovanje preko Bluetootha</string>
<string name="settings_wear_sync_no_devices">Nema dozvoljenih uređaja</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> uređaj dozvoljen</item>
<item quantity="few"><xliff:g>%d</xliff:g> uređaja dozvoljena</item>
<item quantity="other"><xliff:g>%d</xliff:g> uređaja dozvoljeno</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">Blokirani Wear OS uređaji</string>
<string name="settings_wear_sync_blocked_devices_summary">Upravljaj blokiranim Wear OS uređajima</string>
<string name="settings_wear_sync_no_blocked_devices">Nema blokiranih uređaja</string>
<string name="settings_wear_sync_unpair_title">Obriši uređaj?</string>
<string name="settings_wear_sync_unpair">Obriši</string>
<string name="settings_wear_sync_tap_to_remove">Dodirni uređaj da ga ukloniš.</string>
<string name="wear_bt_pairing_channel_name">Wear OS uređaj se uparuje</string>
<string name="wear_bt_pairing_title">Wear OS uređaj želi da se sinhronizuje</string>
<string name="wear_bt_pairing_allow">Dozvoli</string>
<string name="wear_bt_pairing_block">Blokiraj</string>
<string name="wear_bt_pairing_mismatch_message">Ovaj uređaj je bio prethodno autorizovan, ali sada prikazuje drugi token identiteta. Ovo se dešava kada se Wear OS aplikacija ponovno instalira ili se njeni podaci obrišu. To može takođe ukazivati na pokušaj MAC spoofinga preko Bluetootha.\n\nPrihvati samo ako si nedavno ponovno instalirao/la Catima Wear OS aplikaciju na ovaj sat.</string>
<string name="open_settings">Otvori podešavanja</string>
<string name="open_in_gallery">Otvori u galeriji slika</string>
<string name="wear_bt_notification_title">Catima Wear OS sinhronizacija aktivna</string>
<string name="settings_category_title_smartwatch">Podrška za pametni sat</string>
<string name="wear_bt_failed_foreground_notification_title">Greška pri pokretanju Wear OS servisa sinhronizacije</string>
<string name="wear_bt_critical_error_channel_name">Wear OS sinhronizacija - kritična greška</string>
</resources>
+27 -1
View File
@@ -232,7 +232,7 @@
<string name="settings_allow_content_provider_read_summary">Appar måste fortfarande begära åtkomst för att få tillgång</string>
<string name="setBarcodeHeight">Ställ in streckkodens höjd</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Kopieringsskydd © 2019<xliff:g>%d</xliff:g> Sylvia van Os och medverkande</string>
<string name="settings_allow_content_provider_read_title">Tillåt andra appar att komma åt min data</string>
<string name="settings_allow_content_provider_read_title">Synka med Gadgetbridge</string>
<string name="permissionReadCardsLabel">Läsa Catima-kort</string>
<string name="donate">Donera</string>
<string name="show_archived_cards">Visa arkiverade kort</string>
@@ -304,4 +304,30 @@
<string name="nfc_block_system_error">Lyckades inte pausa NFC</string>
<string name="settings_disable_nfc_while_viewing_card_summary">Pausar NFC för att förhindra att NFC-betalningar genomförs av misstag medan du tittar på ett kort</string>
<string name="sort_by_most_recently_added">Senast tillagda</string>
<string name="zoomFrontImage">Zooma framsidesbilden</string>
<string name="zoomBackImage">Zooma baksidesbilden</string>
<string name="show_card_id">Visa kort-ID</string>
<string name="open_in_gallery">Öppna i bildgalleriet</string>
<string name="settings_wear_sync">Synka med Wear OS</string>
<string name="wear_sync_permission_required">Catima behöver få de behörigheter du nekade appen för att kunna synka med din smarta klocka med Wear OS</string>
<string name="settings_wear_sync_devices">Tillåtna Wear OS-enheter</string>
<string name="settings_wear_sync_devices_summary">Hantera enheter som tillåts synka över Bluetooth</string>
<string name="settings_wear_sync_no_devices">Inga tillåtna enheter</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> tillåten enhet</item>
<item quantity="other"><xliff:g>%d</xliff:g> tillåtna enheter</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">Blockerade Wear OS-enheter</string>
<string name="settings_wear_sync_blocked_devices_summary">Hantera blockerade Wear OS-enheter</string>
<string name="settings_wear_sync_no_blocked_devices">Inga blockerade enheter</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> blockerad enhet</item>
<item quantity="other"><xliff:g>%d</xliff:g> blockerade enheter</item>
</plurals>
<string name="wear_bt_pairing_allow">Tillåt</string>
<string name="wear_bt_pairing_block">Blockera</string>
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> tillåts nu synka.</string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> har blockerats.</string>
<string name="wear_bt_critical_error_channel_name">Kritiskt fel vid Wear OS-synkning</string>
<string name="open_settings">Öppna inställningar</string>
</resources>
+40 -2
View File
@@ -227,10 +227,10 @@
<string name="permissionReadCardsLabel">Catima kartlarını oku</string>
<string name="permissionReadCardsDescription">Catima kartlarınızı ve tüm detaylarını, notlar ve resimler de dahil olmak üzere okuyun</string>
<string name="settings_keep_screen_on_summary">Ekran zaman aşımını bir kart görüntülerken devre dışı bırakır</string>
<string name="settings_allow_content_provider_read_title">Diğer uygulamaların verilerime erişmesine izin ver</string>
<string name="settings_allow_content_provider_read_title">Gadgetbridge ile eşzamanla</string>
<string name="settings_display_barcode_max_brightness_summary">Bazı tarayıcıların çalışması için gereklidir</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">Ekran kilidini bir kart görüntülerken devre dışı bırakır</string>
<string name="settings_allow_content_provider_read_summary">Uygulamaların erişim izni almak için yine de izin istemesi gerekecek</string>
<string name="settings_allow_content_provider_read_summary">Gadgetbridge uygulamasının kartlarınızı desteklenen saatlerle eşzamanlamasına izin verir</string>
<string name="settings_category_title_general">Genel</string>
<string name="settings_category_title_privacy">Gizlilik</string>
<string name="settings_oled_dark_summary">OLED ekranlarda güç kullanımını düşürür</string>
@@ -308,4 +308,42 @@
<string name="zoomBackImage">Arka görüntüyü yakınlaştır</string>
<string name="show_card_id">Kart numarasını göster</string>
<string name="open_in_gallery">Resim galerisinde aç</string>
<string name="settings_wear_sync">Wear OS ile eşzamanla</string>
<string name="settings_wear_sync_summary">Wear OS yardımcı uygulamasının Bluetooth üzerinden kartlarınızı okumasına izin verir</string>
<string name="wear_sync_permission_required">Catima, Wear OS akıllı saatinizle eşzamanlama için reddettiğiniz izinlere ihtiyaç duyuyor</string>
<string name="settings_wear_sync_devices">İzin verilen Wear OS aygıtları</string>
<string name="settings_wear_sync_devices_summary">Bluetooth üzerinden eşzamanlanmasına izin verilen aygıtları yönetin</string>
<string name="settings_wear_sync_no_devices">İzin verilen aygıt yok</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> aygıta izin verildi</item>
<item quantity="other"><xliff:g>%d</xliff:g> aygıta izin verildi</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">Engellenen Wear OS aygıtları</string>
<string name="settings_wear_sync_blocked_devices_summary">Engellenen Wear OS aygıtlarını yönetin</string>
<string name="settings_wear_sync_no_blocked_devices">Engellenen aygıt yok</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> aygıt engellendi</item>
<item quantity="other"><xliff:g>%d</xliff:g> aygıt engellendi</item>
</plurals>
<string name="settings_wear_sync_unpair_title">Aygıt kaldırılsın mı?</string>
<string name="settings_wear_sync_unpair_message"><xliff:g>%1$s</xliff:g> Catima\'dan kaldırılsın mı? Artık kartlarınızı eşzamanlayamayacak.</string>
<string name="settings_wear_sync_unblock_message"><xliff:g>%1$s</xliff:g> engellenen listesinden kaldırılsın mı? Tekrar erişim isteğinde bulunabilecek.</string>
<string name="settings_wear_sync_unpair">Kaldır</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_tap_to_remove">Aygıtı kaldırmak için üzerine dokunun.</string>
<string name="wear_bt_pairing_channel_name">Wear OS aygıt eşleştirmesi</string>
<string name="wear_bt_pairing_title">Wear OS aygıtı eşzamanlamak istiyor</string>
<string name="wear_bt_pairing_message"><xliff:g>%1$s</xliff:g> için kartlarınıza erişim izni verilsin mi?</string>
<string name="wear_bt_pairing_allow">İzin ver</string>
<string name="wear_bt_pairing_block">Engelle</string>
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> artık eşzamanlayabilir.</string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> engellendi.</string>
<string name="wear_bt_pairing_mismatch_title"><xliff:g>%1$s</xliff:g> için yeni yetkilendirme isteği</string>
<string name="wear_bt_pairing_mismatch_message">Bu aygıt daha önce yetkilendirildi, ancak şu anda farklı bir kimlik belirteci sunuyor. Bu durum, Wear OS uygulamasının yeniden kurulması veya verilerinin silinmesi durumunda ortaya çıkar. Ayrıca, bir Bluetooth MAC sahteciliği girişimi olduğunu da gösterebilir.\n\nYalnızca bu saatte Catima Wear OS uygulamasını yakın zamanda yeniden kurduysanız kabul edin.</string>
<string name="open_settings">Ayarları</string>
<string name="wear_bt_channel_name">Wear OS yardımcısı</string>
<string name="wear_bt_notification_title">Catima Wear OS eşzamanlaması etkin</string>
<string name="settings_category_title_smartwatch">Akıllı saat desteği</string>
<string name="wear_bt_failed_foreground_notification_title">Wear OS eşzamanlama hizmeti başlatılamadı</string>
<string name="wear_bt_critical_error_channel_name">Wear OS eşzamanlaması önemli hata</string>
</resources>
+5
View File
@@ -297,4 +297,9 @@
<string name="nfc_blocked_while_viewing_card">Đã tạm dừng NFC</string>
<string name="change_settings">Thay đổi cài đặt</string>
<string name="nfc_block_system_error">Không thể tạm dừng NFC</string>
<string name="sort_by_most_recently_added">Đã thêm gần đây nhất</string>
<string name="zoomFrontImage">Phóng ảnh mặt trước</string>
<string name="zoomBackImage">Phóng ảnh mặt sau</string>
<string name="show_card_id">Hiện ID của thẻ</string>
<string name="open_in_gallery">Mở trong thư viện ảnh</string>
</resources>
+38 -2
View File
@@ -218,8 +218,8 @@
<string name="show_name_below_image_thumbnail">在图像缩略图下方显示名称</string>
<string name="show_validity">显示有效性</string>
<string name="permissionReadCardsLabel">读取 Catima 卡</string>
<string name="settings_allow_content_provider_read_summary">应用程序仍然需要请求权限</string>
<string name="settings_allow_content_provider_read_title">允许其他应用程序访问我的数据</string>
<string name="settings_allow_content_provider_read_summary">允许 Gadgetbridge 应用同步卡片到支持的手表</string>
<string name="settings_allow_content_provider_read_title">与 Gadgetbridge 同步</string>
<string name="permissionReadCardsDescription">读取你的 Catima 卡片及其所有细节,包括笔记和图像</string>
<string name="settings_keep_screen_on_summary">在查看卡片时禁止屏幕超时</string>
<string name="settings_oled_dark_summary">减少 OLED 显示屏上的电池使用量</string>
@@ -302,4 +302,40 @@
<string name="zoomBackImage">缩放反面图</string>
<string name="show_card_id">显示卡片 ID</string>
<string name="open_in_gallery">在图库中打开</string>
<string name="settings_wear_sync">与 WearOS 同步</string>
<string name="settings_wear_sync_summary">允许 WearOS 伴侣应用通过蓝牙读取你的卡片</string>
<string name="wear_sync_permission_required">Catima 需要你拒绝的权限来和你的 Wear OS 智能手表同步</string>
<string name="settings_wear_sync_devices">允许的 Wear OS 设备</string>
<string name="settings_wear_sync_devices_summary">管理允许经蓝牙同步的设备</string>
<string name="settings_wear_sync_no_devices">没有允许的设备</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="other">允许了 <xliff:g>%d</xliff:g> 台设备</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">阻止的 Wear OS 设备</string>
<string name="settings_wear_sync_blocked_devices_summary">管理阻止的 Wear OS 设备</string>
<string name="settings_wear_sync_no_blocked_devices">无阻止的设备</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="other">阻止了 <xliff:g>%d</xliff:g> 台设备</item>
</plurals>
<string name="settings_wear_sync_unpair_title">删除设备?</string>
<string name="settings_wear_sync_unpair_message">从 Catima 删除 <xliff:g>%1$s</xliff:g> ?它将无法再和你的卡片同步。</string>
<string name="settings_wear_sync_unblock_message">从拦截列表删除 <xliff:g>%1$s</xliff:g> ?它将能再次请求访问。</string>
<string name="settings_wear_sync_unpair">删除</string>
<string name="wear_bt_pairing_mismatch_message">该设备先前已授权但现在提供了不同的身份令牌。重新安装 Wear OS 应用或应用数据被清除时可能发生这种情况。这种情况还可能表明存在蓝牙 MAC 伪造企图。\n\n除非你最近在这块手表上重新安装过 Catima Wear OS 应用,否则请不要接受。</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_tap_to_remove">轻触设备删除。</string>
<string name="wear_bt_pairing_channel_name">Wear OS 设备配对</string>
<string name="wear_bt_pairing_title">Wear OS 设备想同步</string>
<string name="wear_bt_pairing_message">允许 <xliff:g>%1$s</xliff:g> 访问你的卡片?</string>
<string name="wear_bt_pairing_allow">允许</string>
<string name="wear_bt_pairing_block">拦截</string>
<string name="wear_bt_pairing_allowed">已允许 <xliff:g>%1$s</xliff:g> 同步。</string>
<string name="wear_bt_pairing_blocked">已拦截 <xliff:g>%1$s</xliff:g></string>
<string name="wear_bt_pairing_mismatch_title">来自 <xliff:g>%1$s</xliff:g> 的新授权请求</string>
<string name="open_settings">打开设置</string>
<string name="wear_bt_channel_name">Wear OS 伴侣</string>
<string name="wear_bt_notification_title">Catima Wear OS 同步活跃</string>
<string name="settings_category_title_smartwatch">智能手表支持</string>
<string name="wear_bt_failed_foreground_notification_title">未能启动 Wear OS 同步服务</string>
<string name="wear_bt_critical_error_channel_name">Wear OS 同步严重错误</string>
</resources>
+6 -2
View File
@@ -222,8 +222,8 @@
<string name="app_name">卡提碼</string>
<string name="permissionReadCardsDescription">讀取卡提碼卡片及卡片資訊,包含註記與圖片</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">觀看卡片時避免螢幕鎖定</string>
<string name="settings_allow_content_provider_read_summary">其他程式仍然需要取得權限</string>
<string name="settings_allow_content_provider_read_title">允許其他程式使用我的資料</string>
<string name="settings_allow_content_provider_read_summary">允許 Gadgetbridge 應用程式同步卡片到支援的手表</string>
<string name="settings_allow_content_provider_read_title">與 Gadgetbridge 同步</string>
<string name="settings_keep_screen_on_summary">觀看卡片時避免螢幕關閉</string>
<string name="balanceParsingFailed">無效餘額</string>
<string name="view_online">網站上顯示</string>
@@ -296,4 +296,8 @@
<string name="nfc_blocked_while_viewing_card">NFC 已暫停</string>
<string name="change_settings">變更設定</string>
<string name="nfc_block_system_error">暫停 NFC 失敗</string>
<string name="sort_by_most_recently_added">最近添加</string>
<string name="show_card_id">顯示卡片 ID</string>
<string name="settings_wear_sync">與 WearOS 同步</string>
<string name="settings_wear_sync_devices">允許的 Wear OS 裝置</string>
</resources>
+43 -2
View File
@@ -104,8 +104,8 @@
<string name="settings_disable_nfc_while_viewing_card">Pause NFC</string>
<string name="settings_disable_nfc_while_viewing_card_summary">Pauses NFC to prevent NFC payments from triggering while viewing a card</string>
<string name="settings_key_disable_nfc_while_viewing_card" translatable="false">pref_disable_nfc_while_viewing_card</string>
<string name="settings_allow_content_provider_read_title">Allow other apps to access my data</string>
<string name="settings_allow_content_provider_read_summary">Apps will still have to request permission to be granted access</string>
<string name="settings_allow_content_provider_read_title">Sync with Gadgetbridge</string>
<string name="settings_allow_content_provider_read_summary">Allows the Gadgetbridge app to sync your cards to supported watches</string>
<string name="settings_key_disable_lockscreen_while_viewing_card" translatable="false">pref_disable_lockscreen_while_viewing_card</string>
<string name="settings_key_allow_content_provider_read" translatable="false">pref_allow_content_provider_read</string>
<string name="settings_key_oled_dark" translatable="false">pref_oled_dark</string>
@@ -304,6 +304,42 @@
<string name="setting_key_column_count_landscape" translatable="false">pref_column_count_landscape</string>
<string name="settings_category_title_general">General</string>
<string name="settings_category_title_privacy">Privacy</string>
<string name="settings_key_wear_sync" translatable="false">pref_wear_sync</string>
<string name="settings_wear_sync">Sync with Wear OS</string>
<string name="settings_wear_sync_summary">Allows the Wear OS companion app to read your cards over Bluetooth</string>
<string name="wear_sync_permission_required">Catima needs the permissions you denied to sync with your Wear OS smartwatch</string>
<string name="settings_key_wear_sync_devices" translatable="false">pref_wear_sync_devices</string>
<string name="settings_wear_sync_devices">Allowed Wear OS devices</string>
<string name="settings_wear_sync_devices_summary">Manage devices allowed to sync over Bluetooth</string>
<string name="settings_wear_sync_no_devices">No allowed devices</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> device allowed</item>
<item quantity="other"><xliff:g>%d</xliff:g> devices allowed</item>
</plurals>
<string name="settings_key_wear_sync_blocked_devices" translatable="false">pref_wear_sync_blocked_devices</string>
<string name="settings_wear_sync_blocked_devices">Blocked Wear OS devices</string>
<string name="settings_wear_sync_blocked_devices_summary">Manage blocked Wear OS devices</string>
<string name="settings_wear_sync_no_blocked_devices">No blocked devices</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> device blocked</item>
<item quantity="other"><xliff:g>%d</xliff:g> devices blocked</item>
</plurals>
<string name="settings_wear_sync_unpair_title">Remove device?</string>
<string name="settings_wear_sync_unpair_message">Remove <xliff:g>%1$s</xliff:g> from Catima? It will no longer be able to sync your cards.</string>
<string name="settings_wear_sync_unblock_message">Remove <xliff:g>%1$s</xliff:g> from the blocked list? It will be able to request access again.</string>
<string name="settings_wear_sync_unpair">Remove</string>
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
<string name="settings_wear_sync_tap_to_remove">Tap a device to remove it.</string>
<string name="wear_bt_pairing_channel_name">Wear OS device pairing</string>
<string name="wear_bt_pairing_title">Wear OS device wants to sync</string>
<string name="wear_bt_pairing_message">Allow <xliff:g>%1$s</xliff:g> to access your cards?</string>
<string name="wear_bt_pairing_allow">Allow</string>
<string name="wear_bt_pairing_block">Block</string>
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> is now allowed to sync.</string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> has been blocked.</string>
<string name="wear_bt_pairing_mismatch_title">New authorization request from "<xliff:g>%1$s</xliff:g>"</string>
<string name="wear_bt_pairing_mismatch_message">This device was previously authorized but is now presenting a different identity token. This happens when the Wear OS app is reinstalled or its data is cleared. It can also indicate a Bluetooth MAC spoofing attempt.\n\nOnly accept if you recently reinstalled the Catima Wear OS app on this watch.</string>
<string name="open_settings">Open settings</string>
<string name="action_display_options">Display options</string>
<string name="show_archived_cards">Show archived cards</string>
<string name="view_online">View online</string>
@@ -354,4 +390,9 @@
<string name="change_settings">Change settings</string>
<string name="nfc_block_system_error">Failed to pause NFC</string>
<string name="open_in_gallery">Open in image gallery</string>
<string name="wear_bt_channel_name">Wear OS companion</string>
<string name="wear_bt_notification_title">Catima Wear OS sync active</string>
<string name="settings_category_title_smartwatch">Smartwatch support</string>
<string name="wear_bt_failed_foreground_notification_title">Failed to start Wear OS sync service</string>
<string name="wear_bt_critical_error_channel_name">Wear OS sync critical error</string>
</resources>
+7
View File
@@ -57,6 +57,13 @@
<item name="postSplashScreenTheme">@style/AppTheme</item>
</style>
<style name="AppTheme.Transparent" parent="AppTheme.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:backgroundDimEnabled">true</item>
</style>
<!-- place holder for dark background night theme overrides -->
<style name="DarkBackground" />
</resources>
+24 -1
View File
@@ -121,7 +121,7 @@
</PreferenceCategory>
<PreferenceCategory
android:title="@string/settings_category_title_privacy"
android:title="@string/settings_category_title_smartwatch"
app:iconSpaceReserved="false">
<SwitchPreferenceCompat
@@ -132,6 +132,29 @@
android:title="@string/settings_allow_content_provider_read_title"
app:iconSpaceReserved="false"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preference_material_switch"
android:defaultValue="false"
android:key="@string/settings_key_wear_sync"
android:summary="@string/settings_wear_sync_summary"
android:title="@string/settings_wear_sync"
app:iconSpaceReserved="false"
app:singleLineTitle="false" />
<Preference
android:key="@string/settings_key_wear_sync_devices"
android:title="@string/settings_wear_sync_devices"
android:summary="@string/settings_wear_sync_devices_summary"
app:iconSpaceReserved="false"
app:singleLineTitle="false" />
<Preference
android:key="@string/settings_key_wear_sync_blocked_devices"
android:title="@string/settings_wear_sync_blocked_devices"
android:summary="@string/settings_wear_sync_blocked_devices_summary"
app:iconSpaceReserved="false"
app:singleLineTitle="false" />
</PreferenceCategory>
</PreferenceScreen>
@@ -2,16 +2,21 @@ package protect.card_locker;
import static android.os.Looper.getMainLooper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.robolectric.Shadows.shadowOf;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Color;
import android.net.Uri;
import android.os.Looper;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -30,17 +35,197 @@ import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.LooperMode;
import org.robolectric.shadows.ShadowActivity;
import org.robolectric.shadows.ShadowContentResolver;
import org.robolectric.shadows.ShadowToast;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BooleanSupplier;
@RunWith(RobolectricTestRunner.class)
public class MainActivityTest {
private SharedPreferences prefs;
@Test
@LooperMode(LooperMode.Mode.PAUSED)
public void pkpassSharedIntentReadsOffMainThreadAndProcessesOnMainThread() throws Exception {
Uri uri = Uri.parse("content://test/cloud-backed.pkpass");
CountDownLatch readStarted = new CountDownLatch(1);
CountDownLatch streamClosed = new CountDownLatch(1);
AtomicReference<Looper> readLooper = new AtomicReference<>();
InputStream pkpass = getClass().getResourceAsStream(
"pkpass/DCBLN24/DCBLN24-QLUKT-1-passbook.pkpass");
assertNotNull(pkpass);
new ShadowContentResolver().registerInputStream(
uri, new RecordingInputStream(pkpass, readStarted, streamClosed, readLooper));
Intent intent = new Intent(Intent.ACTION_VIEW)
.setDataAndType(uri, "application/vnd.apple.pkpass");
ActivityController<MainActivity> controller =
Robolectric.buildActivity(MainActivity.class, intent).create().start().resume().visible();
MainActivity activity = controller.get();
// Do NOT idle the main looper here: the read runs on a real background
// thread, so the latch fires without it, and idling would run the
// main-thread continuation and defeat the ordering assertion below.
assertTrue(readStarted.await(5, TimeUnit.SECONDS));
// The read must not happen on the main looper. This is the property the
// fix is about; it is asserted directly rather than inferred from
// ordering. Robolectric drains the main looper during .visible(), so by
// the time the test regains control the whole import has already run to
// completion and there is no observable in-between state to assert on.
assertTrue(readLooper.get() != getMainLooper());
assertTrue(streamClosed.await(5, TimeUnit.SECONDS));
waitFor(() -> shadowOf(activity).peekNextStartedActivity() != null);
Intent editIntent = shadowOf(activity).getNextStartedActivity();
assertEquals(LoyaltyCardEditActivity.class.getName(), editIntent.getComponent().getClassName());
assertNotNull(editIntent.getExtras());
controller.destroy();
}
@Test
@LooperMode(LooperMode.Mode.PAUSED)
public void destroyingActivityCancelsPendingPkpassResult() throws Exception {
Uri uri = Uri.parse("content://test/pending-cloud-backed.pkpass");
CountDownLatch readStarted = new CountDownLatch(1);
CountDownLatch releaseRead = new CountDownLatch(1);
CountDownLatch streamClosed = new CountDownLatch(1);
InputStream pkpass = getClass().getResourceAsStream(
"pkpass/DCBLN24/DCBLN24-QLUKT-1-passbook.pkpass");
assertNotNull(pkpass);
new ShadowContentResolver().registerInputStream(
uri, new BlockingInputStream(pkpass, readStarted, releaseRead, streamClosed));
Intent intent = new Intent(Intent.ACTION_VIEW)
.setDataAndType(uri, "application/vnd.apple.pkpass");
ActivityController<MainActivity> controller =
Robolectric.buildActivity(MainActivity.class, intent).create().start().resume();
MainActivity activity = controller.get();
waitFor(() -> readStarted.getCount() == 0);
controller.destroy();
releaseRead.countDown();
assertTrue(streamClosed.await(5, TimeUnit.SECONDS));
shadowOf(getMainLooper()).idle();
assertNull(shadowOf(activity).peekNextStartedActivity());
assertFalse(ShadowToast.showedToast(activity.getString(R.string.errorReadingFile)));
}
private static void waitFor(BooleanSupplier condition) throws Exception {
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
while (!condition.getAsBoolean() && System.nanoTime() < deadline) {
shadowOf(getMainLooper()).idle();
Thread.sleep(10);
}
shadowOf(getMainLooper()).idle();
assertTrue("Timed out waiting for asynchronous activity work", condition.getAsBoolean());
}
private static class RecordingInputStream extends FilterInputStream {
private final CountDownLatch readStarted;
private final CountDownLatch streamClosed;
private final AtomicReference<Looper> readLooper;
RecordingInputStream(
InputStream inputStream,
CountDownLatch readStarted,
CountDownLatch streamClosed,
AtomicReference<Looper> readLooper) {
super(inputStream);
this.readStarted = readStarted;
this.streamClosed = streamClosed;
this.readLooper = readLooper;
}
private void recordRead() {
readLooper.compareAndSet(null, Looper.myLooper());
readStarted.countDown();
}
@Override
public int read() throws IOException {
recordRead();
return super.read();
}
@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
recordRead();
return super.read(buffer, offset, length);
}
@Override
public void close() throws IOException {
try {
super.close();
} finally {
streamClosed.countDown();
}
}
}
private static class BlockingInputStream extends FilterInputStream {
private final CountDownLatch readStarted;
private final CountDownLatch releaseRead;
private final CountDownLatch streamClosed;
BlockingInputStream(
InputStream inputStream,
CountDownLatch readStarted,
CountDownLatch releaseRead,
CountDownLatch streamClosed) {
super(inputStream);
this.readStarted = readStarted;
this.releaseRead = releaseRead;
this.streamClosed = streamClosed;
}
private void awaitRelease() throws IOException {
readStarted.countDown();
try {
if (!releaseRead.await(5, TimeUnit.SECONDS)) {
throw new IOException("Timed out waiting to release provider stream");
}
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
throw new IOException(exception);
}
}
@Override
public int read() throws IOException {
awaitRelease();
return super.read();
}
@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
awaitRelease();
return super.read(buffer, offset, length);
}
@Override
public void close() throws IOException {
try {
super.close();
} finally {
streamClosed.countDown();
}
}
}
@Test
public void initiallyNoLoyaltyCards() {
Activity activity = Robolectric.setupActivity(MainActivity.class);
@@ -0,0 +1,225 @@
package protect.card_locker
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Looper
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.android.controller.ActivityController
import org.robolectric.annotation.LooperMode
import org.robolectric.shadows.ShadowContentResolver
import org.robolectric.shadows.ShadowToast
import java.io.FilterInputStream
import java.io.IOException
import java.io.InputStream
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
@RunWith(RobolectricTestRunner::class)
@LooperMode(LooperMode.Mode.PAUSED)
class ScanActivityTest {
@Before
fun setUp() {
ShadowToast.reset()
}
@Test
fun pkpassPickerReadsOffMainThreadAndReturnsResultOnMainThread() {
val uri = Uri.parse("content://test/picker-cloud-backed.pkpass")
val readStarted = CountDownLatch(1)
val streamClosed = CountDownLatch(1)
val readLooper = AtomicReference<Looper>()
val pkpass = javaClass.getResourceAsStream(
"pkpass/DCBLN24/DCBLN24-QLUKT-1-passbook.pkpass"
)
assertNotNull(pkpass)
ShadowContentResolver().registerInputStream(
uri,
RecordingInputStream(pkpass, readStarted, streamClosed, readLooper)
)
val controller = createActivity()
val activity = controller.get()
handlePkpassResult(activity, Intent().setData(uri))
assertTrue(readStarted.await(5, TimeUnit.SECONDS))
// The read must not happen on the main looper. Asserted directly rather
// than inferred from an intermediate result code: waiting on the main
// looper drains it, so the flow has already reached its final state by
// the time the test regains control.
assertTrue(readLooper.get() !== Looper.getMainLooper())
assertTrue(streamClosed.await(5, TimeUnit.SECONDS))
waitFor { shadowOf(activity).resultCode == Activity.RESULT_OK }
assertNotNull(shadowOf(activity).resultIntent)
assertTrue(activity.isFinishing)
controller.destroy()
}
@Test
fun providerFailureShowsOneErrorAndReenablesScanner() {
val uri = Uri.parse("content://test/unavailable-cloud-backed.pkpass")
ShadowContentResolver().registerInputStream(uri, FailingInputStream())
val controller = createActivity()
val activity = controller.get()
setScannerActive(activity, false)
val initialToastCount = ShadowToast.shownToastCount()
handlePkpassResult(activity, Intent().setData(uri))
waitFor { ShadowToast.shownToastCount() > initialToastCount }
assertEquals(initialToastCount + 1, ShadowToast.shownToastCount())
assertEquals(activity.getString(R.string.errorReadingFile), ShadowToast.getTextOfLatestToast())
assertTrue(isScannerActive(activity))
assertFalse(activity.isFinishing)
controller.destroy()
}
@Test
fun destroyingActivityCancelsPendingPkpassResult() {
val uri = Uri.parse("content://test/pending-picker-cloud-backed.pkpass")
val readStarted = CountDownLatch(1)
val releaseRead = CountDownLatch(1)
val streamClosed = CountDownLatch(1)
val pkpass = javaClass.getResourceAsStream(
"pkpass/DCBLN24/DCBLN24-QLUKT-1-passbook.pkpass"
)
assertNotNull(pkpass)
ShadowContentResolver().registerInputStream(
uri,
BlockingInputStream(pkpass, readStarted, releaseRead, streamClosed)
)
val controller = createActivity()
val activity = controller.get()
handlePkpassResult(activity, Intent().setData(uri))
waitFor { readStarted.count == 0L }
controller.destroy()
releaseRead.countDown()
assertTrue(streamClosed.await(5, TimeUnit.SECONDS))
shadowOf(Looper.getMainLooper()).idle()
assertEquals(Activity.RESULT_CANCELED, shadowOf(activity).resultCode)
assertFalse(ShadowToast.showedToast(activity.getString(R.string.errorReadingFile)))
}
private fun createActivity(): ActivityController<ScanActivity> =
Robolectric.buildActivity(ScanActivity::class.java).create().start().resume().visible()
private fun handlePkpassResult(activity: ScanActivity, intent: Intent) {
ScanActivity::class.java.getDeclaredMethod(
"handleActivityResult",
Int::class.javaPrimitiveType,
Int::class.javaPrimitiveType,
Intent::class.java
).apply { isAccessible = true }.invoke(
activity,
Utils.BARCODE_IMPORT_FROM_PKPASS_FILE,
Activity.RESULT_OK,
intent
)
}
private fun setScannerActive(activity: ScanActivity, active: Boolean) {
ScanActivity::class.java.getDeclaredMethod(
"setScannerActive",
Boolean::class.javaPrimitiveType
).apply { isAccessible = true }.invoke(activity, active)
}
private fun isScannerActive(activity: ScanActivity): Boolean =
ScanActivity::class.java.getDeclaredField("mScannerActive")
.apply { isAccessible = true }
.getBoolean(activity)
private fun waitFor(condition: () -> Boolean) {
val deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5)
while (!condition() && System.nanoTime() < deadline) {
shadowOf(Looper.getMainLooper()).idle()
Thread.sleep(10)
}
shadowOf(Looper.getMainLooper()).idle()
assertTrue("Timed out waiting for asynchronous activity work", condition())
}
private class RecordingInputStream(
inputStream: InputStream,
private val readStarted: CountDownLatch,
private val streamClosed: CountDownLatch,
private val readLooper: AtomicReference<Looper>
) : FilterInputStream(inputStream) {
private fun recordRead() {
readLooper.compareAndSet(null, Looper.myLooper())
readStarted.countDown()
}
override fun read(): Int {
recordRead()
return super.read()
}
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
recordRead()
return super.read(buffer, offset, length)
}
override fun close() {
try {
super.close()
} finally {
streamClosed.countDown()
}
}
}
private class BlockingInputStream(
inputStream: InputStream,
private val readStarted: CountDownLatch,
private val releaseRead: CountDownLatch,
private val streamClosed: CountDownLatch
) : FilterInputStream(inputStream) {
private fun awaitRelease() {
readStarted.countDown()
try {
if (!releaseRead.await(5, TimeUnit.SECONDS)) {
throw IOException("Timed out waiting to release provider stream")
}
} catch (exception: InterruptedException) {
Thread.currentThread().interrupt()
throw IOException(exception)
}
}
override fun read(): Int {
awaitRelease()
return super.read()
}
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
awaitRelease()
return super.read(buffer, offset, length)
}
override fun close() {
try {
super.close()
} finally {
streamClosed.countDown()
}
}
}
private class FailingInputStream : InputStream() {
override fun read(): Int = throw IOException("Remote provider unavailable")
}
}
@@ -11,6 +11,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import protect.card_locker.shared.ForegroundColorHelper;
@RunWith(RobolectricTestRunner.class)
public class UtilsTest {
@@ -26,7 +28,7 @@ public class UtilsTest {
for (int i = 0; i < colors.length(); i++) {
// Grab white as fallback so that if the retrieval somehow fails the test is guaranteed to fail because a white background will have black foreground
int color = colors.getColor(i, Color.WHITE);
assertFalse(Utils.needsDarkForeground(color));
assertFalse(ForegroundColorHelper.Companion.needsDarkForeground(color));
}
});
}
@@ -0,0 +1,37 @@
package protect.card_locker.wearos
import android.content.Context
import androidx.preference.PreferenceManager
import androidx.test.core.app.ApplicationProvider
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import protect.card_locker.R
@RunWith(RobolectricTestRunner::class)
class WearSyncServiceManagerTest {
@Test
fun onPermissionResult_denied_disablesWearSync() {
val context = ApplicationProvider.getApplicationContext<Context>()
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.edit().putBoolean(context.getString(R.string.settings_key_wear_sync), true).commit()
WearSyncServiceManager.onPermissionResult(context, false)
assertFalse(prefs.getBoolean(context.getString(R.string.settings_key_wear_sync), true))
}
@Test
fun onPermissionResult_granted_keepsWearSyncEnabled() {
val context = ApplicationProvider.getApplicationContext<Context>()
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.edit().putBoolean(context.getString(R.string.settings_key_wear_sync), true).commit()
WearSyncServiceManager.onPermissionResult(context, true)
assertTrue(prefs.getBoolean(context.getString(R.string.settings_key_wear_sync), false))
}
}
+2
View File
@@ -2,7 +2,9 @@
plugins {
alias(libs.plugins.com.android.application) apply false
alias(libs.plugins.com.android.library) apply false
alias(libs.plugins.org.jetbrains.kotlin.android) apply false
alias(libs.plugins.org.jetbrains.kotlin.plugin.compose) apply false
}
allprojects {
+36 -8
View File
@@ -5,6 +5,16 @@ IFS=$'\n\t'
### build.sh
### Builds Catima the same way rbtlog/IzzyOnDroid does for reproducible builds
if [ -z "${BUILD_TYPE:-}" ]; then
echo "BUILD_TYPE is not set, setting to app."
export BUILD_TYPE="app"
fi
if [ "${BUILD_TYPE}" != "app" ] && [ "${BUILD_TYPE}" != "wear" ]; then
echo "Invalid BUILD_TYPE ${BUILD_TYPE}, must be either app or wear."
exit 1
fi
if [ -z "${ANDROID_HOME:-}" ]; then
echo "ANDROID_HOME is not set, setting to $HOME/Android/Sdk";
export ANDROID_HOME=$HOME/Android/Sdk
@@ -22,13 +32,17 @@ if [ -z "${JAVA_HOME:-}" ]; then
fi
echo "Starting build"
./gradlew clean assembleRelease
./gradlew clean ":${BUILD_TYPE}:assembleRelease"
echo "Build finished (unsigned)"
flavourDirs=$(find app/build/outputs/apk/ -mindepth 1 -maxdepth 1 -type d)
flavourDirs=$(find "${BUILD_TYPE}/build/outputs/apk/" -mindepth 1 -maxdepth 1 -type d)
for flavourDir in $flavourDirs; do
flavourName="$(basename "$flavourDir")"
echo "Your $flavourName flavour is at $flavourDir/release/app-$flavourName-release-unsigned.apk"
if [ "$flavourName" == "release" ]; then
echo "Your $flavourName flavour is at $flavourDir/${BUILD_TYPE}-release-unsigned.apk"
else
echo "Your $flavourName flavour is at $flavourDir/release/${BUILD_TYPE}-$flavourName-release-unsigned.apk"
fi
done
if [ -z "${KEYSTORE:-}" ]; then
@@ -44,19 +58,33 @@ else
for flavourDir in $flavourDirs; do
flavourName="$(basename "$flavourDir")"
echo "Signing $flavourName flavour..."
cp "$flavourDir/release/app-$flavourName-release-unsigned.apk" "$flavourDir/release/app-$flavourName-release.apk"
"$ANDROID_HOME/build-tools/$apksigner_version/apksigner" sign -v --ks "$KEYSTORE" --ks-key-alias "$KEYSTORE_ALIAS" "$flavourDir/release/app-$flavourName-release.apk"
if [ "$flavourName" == "release" ]; then
cp "$flavourDir/${BUILD_TYPE}-release-unsigned.apk" "$flavourDir/${BUILD_TYPE}-release.apk"
"$ANDROID_HOME/build-tools/$apksigner_version/apksigner" sign -v --ks "$KEYSTORE" --ks-key-alias "$KEYSTORE_ALIAS" "$flavourDir/${BUILD_TYPE}-release.apk"
else
cp "$flavourDir/release/${BUILD_TYPE}-$flavourName-release-unsigned.apk" "$flavourDir/release/${BUILD_TYPE}-$flavourName-release.apk"
"$ANDROID_HOME/build-tools/$apksigner_version/apksigner" sign -v --ks "$KEYSTORE" --ks-key-alias "$KEYSTORE_ALIAS" "$flavourDir/release/${BUILD_TYPE}-$flavourName-release.apk"
fi
echo "Build finished (signed)"
echo "Your $flavourName flavour is at $flavourDir/release/app-$flavourName-release.apk"
if [ "$flavourName" == "release" ]; then
echo "Your $flavourName flavour is at $flavourDir/${BUILD_TYPE}-release.apk"
else
echo "Your $flavourName flavour is at $flavourDir/release/${BUILD_TYPE}-$flavourName-release.apk"
fi
done
shasumPath="$(pwd)/SHA256SUMS"
echo "" > "$shasumPath"
for flavourDir in $flavourDirs; do
pushd "$flavourDir/release/"
sha256sum -- *.apk >> "$shasumPath"
flavourName="$(basename "$flavourDir")"
if [ "$flavourName" == "release" ]; then
pushd "$flavourDir"
else
pushd "$flavourDir/release/"
fi
sha256sum -- "${BUILD_TYPE}"-*.apk >> "$shasumPath"
popd
done
+97
View File
@@ -0,0 +1,97 @@
# Bluetooth server (for WearOS support and other Bluetooth devices)
Catima implements a [Rfcomm Bluetooth server](https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#listenUsingRfcommWithServiceRecord(java.lang.String,%20java.util.UUID)) to allow Bluetooth devices to interact with the cards database. Right now, this only provides read-only functionality.
The UUID of the Rfcomm Bluetooth server is `e5b4f020-3a7e-4b6d-9f2c-1a8c5d3e7f90`. You should bind to this using [createRfcommSocketToServiceRecord](https://developer.android.com/reference/android/bluetooth/BluetoothDevice#createRfcommSocketToServiceRecord(java.util.UUID)) on Android or, if not Android, whatever is the alternative for your platform. Insecure Rfcomm sockets are not supported.
Since running the Bluetooth server requires extra permissions (Bluetooth and Notifications) and runs a constant background process it is disabled by default.
For security, Catima keeps a list of known MAC addresses and tokens and will only allow devices explicitly allowed by the user to interact with the Bluetooth server. This prevents every paired Bluetooth device from being able to access cards without user authentication.
The Bluetooth server can be controlled with the setting "Sync with Wear OS" under "Smartwatch support". It is disabled by default.
The Bluetooth server is currently only used by the Catima WearOS companion app. If your app/device uses it, please let us know, so we can update the documentation and contact your project in case of planned breaking changes.
## API versions
For each API version, we have a major and a minor version. The rules for versioning are as follows:
1. A new minor version may add fields or endpoints, but must never modify or remove existing fields or endpoints.
2. A new minor version must not change any authentication systems compared to the previous minor version.
3. A device cannot request Catima to use a specific minor version, it will always return data as per the most recently supported minor version.
The main driver of compatibility is the major version.
Due to this system, a device only supporting API version 1.0 can talk to Catima supporting API version 1.1 with no issues, as long as it ignores fields it doesn't know. On the other side, a device wanting to use API version 1.1 with Catima on API version 1.0 has to accept certain fields may not be available. Which minor version introduces which additional fields and endpoints is documented in this file.
### Unversioned
#### /VERSIONS
Return type: JSON.
Returns all supported major versions and the most recent supported minor version.
Example request:
```
/VERSIONS
```
Example return value:
```
["1.0"]
```
In this example case, the phone supports only API version 1. Of API version 1, the most recent minor version supported is 0.
### V1
All V1 endpoints are authenticated and must be communicated with by sending 2 lines over an RfConn socket. First the full command, then a device-specific token (recommended: a base64-encoded version of a 32 characters long SecureRandom-generated string).
The device-specific token must be generated once on the companion device and never changed.
For example, to call the first page of `/V1/CARDS_REQUEST_PAGE/` you should send:
```
/V1/CARDS_REQUEST_PAGE/0
TOKEN: your_base64_encoded_token_here
```
#### /V1/CARDS_REQUEST_PAGE/{id:int}
Return type: JSON.
Request a single page of cards with page and total page details. The page ID is 0-indexed, making 0 the first page. Currently, a page is 10 cards but this may change in the future.
Each card contains the following fields:
| Column | Type | Description | Introduced in minor version |
|-------------------|-------------------|----------------------------| --------------------------- |
| `id` | `int` | Unique card ID | 0 |
| `store` | `String` | Card name | 0 |
| `cardId` | `String` | Card ID. | 0 |
| `barcodeId` | `Nullable String` | Barcode value. If empty, it's the same as the card ID. | 0 |
| `barcodeType` | `Nullable String` | The barcode type name, matching [com.google.zxing.BarcodeFormat](https://zxing.github.io/zxing/apidocs/com/google/zxing/BarcodeFormat.html). When null, there is no barcode for this card. | 0 |
| `headerColor` | `int` | Header color, matching [Android color int](https://developer.android.com/reference/android/graphics/Color). | 0 |
Example request:
```
/V1/CARDS_REQUEST_PAGE/0
TOKEN: your_base64_encoded_token_here
```
Example return value:
```
{
"page": 0,
"totalPages": 1,
"cards": [
{
"id": 1,
"store": "Bookshop",
"cardId": "123456",
"barcodeId": null,
"barcodeType": "QR_CODE",
"headerColor": -1
}
]
}
```
+22 -18
View File
@@ -1,9 +1,13 @@
# Content Provider
# Content Provider (for Gadgetbridge and other local apps)
Catima implements a [content provider](https://developer.android.com/guide/topics/providers/content-providers) to allow for external apps to interact with the cards database. Right now, this only provides read-only functionality.
Since runtime permissions are only available since Android 6.0 (API level 23), the content provider is disabled for older android versions in order to prevent unwanted access to the data.
The content provider can be controlled with the setting "Sync with Gadgetbridge" under "Smartwatch support". It is enabled by default.
The content provider is currently only used by Gadgetbridge. If your app uses it, please let us know, so we can update the documentation and contact your project in case of planned breaking changes.
## Package Names
There are 3 release channels, with 2 possible package names:
@@ -41,23 +45,23 @@ A major version change implies breaking changes (eg. columns being renamed or re
### /cards
| Column | Type | Description |
|-------------------|----------|----------------------------|
| `_id` | `int` | Unique card ID |
| `store` | `String` | Card name |
| `validfrom` | `long` | Timestamp from which the card is valid (unix epoch millis). |
| `expiry` | `long` | Expiration timestamp (unix epoch millis). |
| `balance` | `String` | Current balance, as a string-formatted big decimal. |
| `balancetype` | `String` | Balance currency code, ISO 4217. |
| `note` | `String` | A note. |
| `headercolor` | `int` | Header color, in RGBA. |
| `cardid` | `String` | Card ID. |
| `barcodeid` | `String` | Barcode value. If empty, it's the same as the card ID. |
| `barcodetype` | `String` | The barcode type name, matching [com.google.zxing.BarcodeFormat](https://zxing.github.io/zxing/apidocs/com/google/zxing/BarcodeFormat.html). |
| `barcodeencoding` | `String` | The barcode encoding used to render the barcode, matching a [StandardCharset](https://developer.android.com/reference/java/nio/charset/StandardCharsets)'s `.name()` output. Only `ISO-8859-1` and `UTF-8` are user selectable. |
| `starstatus` | `int` | 1 if starred, 0 if not |
| `lastused` | `long` | Timestamp of last card usage (unix epoch millis). |
| `archive` | `int` | 1 if archived, 0 if not |
| Column | Type | Description |
|-------------------|-------------------|----------------------------|
| `_id` | `int` | Unique card ID |
| `store` | `String` | Card name |
| `validfrom` | `long` | Timestamp from which the card is valid (unix epoch millis). |
| `expiry` | `long` | Expiration timestamp (unix epoch millis). |
| `balance` | `String` | Current balance, as a string-formatted big decimal. |
| `balancetype` | `Nullable String` | Balance currency code, ISO 4217. When null, it's a generic "Points" indicator. |
| `note` | `String` | A note. |
| `headercolor` | `int` | Header color, matching [Android color int](https://developer.android.com/reference/android/graphics/Color). |
| `cardid` | `String` | Card ID. |
| `barcodeid` | `Nullable String` | Barcode value. If null, it's the same as the card ID. |
| `barcodetype` | `Nullable String` | The barcode type name, matching [com.google.zxing.BarcodeFormat](https://zxing.github.io/zxing/apidocs/com/google/zxing/BarcodeFormat.html). When null, there is no barcode for this card. |
| `barcodeencoding` | `String` | The barcode encoding used to render the barcode, matching a [StandardCharset](https://developer.android.com/reference/java/nio/charset/StandardCharsets)'s `.name()` output. Only `ISO-8859-1` and `UTF-8` are user selectable. |
| `starstatus` | `int` | 1 if starred, 0 if not |
| `lastused` | `long` | Timestamp of last card usage (unix epoch millis). |
| `archive` | `int` | 1 if archived, 0 if not |
### /groups
+15 -2
View File
@@ -1,8 +1,9 @@
# Catima
Copylefted libre software (GPLv3+) card management app.
Copylefted libre software (GPLv3+) card management app for Android 6.0 and up.
[![GitHub Version](https://img.shields.io/github/v/release/CatimaLoyalty/Android.svg?logo=github&label=GitHub)](https://github.com/CatimaLoyalty/Android/releases)
[![GitHub Version](https://img.shields.io/github/v/release/CatimaLoyalty/Android.svg?logo=github&label=GitHub&filter=!*-wearos)](https://github.com/CatimaLoyalty/Android/releases)
[![GitHub Wear OS Version](https://img.shields.io/github/v/release/CatimaLoyalty/Android.svg?logo=github&label=GitHub%20(Wear%20OS)&filter=*-wearos)](https://github.com/CatimaLoyalty/Android/releases)
[![IzzyOnDroid Version](https://img.shields.io/endpoint?url=https://apt.izzysoft.de/fdroid/api/v1/shield/me.hackerchick.catima)](https://apt.izzysoft.de/fdroid/index/apk/me.hackerchick.catima)
[![F-Droid Version](https://img.shields.io/f-droid/v/me.hackerchick.catima.svg?logo=f-droid&label=F-Droid)](https://f-droid.org/packages/me.hackerchick.catima/)
[![Google Play Store Version](https://img.shields.io/endpoint?color=blue&logo=google-play&url=https%3A%2F%2Fplay.cuzi.workers.dev%2Fplay%3Fi%3Dme.hackerchick.catima%26l%3DGoogle%2520Play%26m%3D%24version)](https://play.google.com/store/apps/details?id=me.hackerchick.catima)
@@ -18,6 +19,8 @@ Copylefted libre software (GPLv3+) card management app.
<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" alt="Get it on IzzyOnDroid" height="90"/></a>
<a href="https://f-droid.org/repository/browse/?fdid=me.hackerchick.catima" target="_blank">
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="90"/></a>
<a href="https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/%7B%22id%22%3A%22me.hackerchick.catima%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2FCatimaLoyalty%2FAndroid%22%2C%22author%22%3A%22CatimaLoyalty%22%2C%22name%22%3A%22Catima%22%2C%22preferredApkIndex%22%3A0%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Afalse%2C%5C%22fallbackToOlderReleases%5C%22%3Atrue%2C%5C%22filterReleaseTitlesByRegEx%5C%22%3A%5C%22%5E((%3F!.*Wear.*).)*%24%5C%22%2C%5C%22filterReleaseNotesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22verifyLatestTag%5C%22%3Afalse%2C%5C%22sortMethodChoice%5C%22%3A%5C%22smartname%5C%22%2C%5C%22useLatestAssetDateAsReleaseDate%5C%22%3Afalse%2C%5C%22releaseTitleAsVersion%5C%22%3Atrue%2C%5C%22trackOnly%5C%22%3Afalse%2C%5C%22versionExtractionRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22matchGroupToUse%5C%22%3A%5C%22%5C%22%2C%5C%22versionDetection%5C%22%3Atrue%2C%5C%22releaseDateAsVersion%5C%22%3Afalse%2C%5C%22useVersionCodeAsOSVersion%5C%22%3Afalse%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22invertAPKFilter%5C%22%3Afalse%2C%5C%22autoApkFilterByArch%5C%22%3Atrue%2C%5C%22minimumUpdateAgeDays%5C%22%3A%5C%22%5C%22%2C%5C%22appName%5C%22%3A%5C%22%5C%22%2C%5C%22appAuthor%5C%22%3A%5C%22Sylvia%20van%20Os%5C%22%2C%5C%22shizukuPretendToBeGooglePlay%5C%22%3Afalse%2C%5C%22allowInsecure%5C%22%3Afalse%2C%5C%22exemptFromBackgroundUpdates%5C%22%3Afalse%2C%5C%22skipUpdateNotifications%5C%22%3Afalse%2C%5C%22about%5C%22%3A%5C%22%5C%22%2C%5C%22refreshBeforeDownload%5C%22%3Afalse%2C%5C%22includeZips%5C%22%3Afalse%2C%5C%22zippedApkFilterRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22includeTarballs%5C%22%3Afalse%2C%5C%22tarballedApkFilterRegEx%5C%22%3A%5C%22%5C%22%7D%22%2C%22overrideSource%22%3Anull%7D" target="_blank">
<img src="https://github.com/user-attachments/assets/713d71c5-3dec-4ec4-a3f2-8d28d025a9c6" alt="Get it on Obtainium" height="90"/></a>
<a href="https://play.google.com/store/apps/details?id=me.hackerchick.catima" target="_blank">
<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="90"/></a>
@@ -41,8 +44,12 @@ Supported barcodes:
- UPC_A
- UPC_E
Works fully offline, supports pkpass ("Add to Apple Wallet") files and select smartwatches through [Gadgetbridge](https://gadgetbridge.org/basics/integrations/cards/) and Wear OS companion app.
# Screenshots
## Core app
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-01.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-01.png)
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-02.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-02.png)
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-03.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-03.png)
@@ -52,6 +59,12 @@ Supported barcodes:
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-07.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-07.png)
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-08.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-08.png)
## Wear OS companion app
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/wearScreenshots/screenshot-01.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/wearScreenshots/screenshot-01.png)
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/wearScreenshots/screenshot-02.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/wearScreenshots/screenshot-02.png)
[<img src="https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/wearScreenshots/screenshot-03.png" width=250>](https://github.com/CatimaLoyalty/Android/raw/main/fastlane/metadata/android/en-US/images/wearScreenshots/screenshot-03.png)
# Moving data from other apps
Within the app you can import cards and codes from files, Catima, FidMe, Loyalty Card Keychain and Voucher Vault.
+22 -5
View File
@@ -1,14 +1,31 @@
**This documentation is for maintainers. If you're a user, please ignore it.**
# General versioning note
Android releases should be at least versionCode 1000 and end on an even number (the uneven numbers are used for F-Droid legacy builds). Valid: 1002, 1004, etc.
WearOS releases should be between 200 and 999 and only have to be bumped by 1. Valid: 200, 201, etc.
Version codes below 200 are from the pre-WearOS days.
# When releasing, do the following:
1. Press "Commit" and "Push" on Weblate to ensure all translations are up to date
2. Merge Weblate pull request
3. Make sure to pull the `main` branch locally
4. Update `CHANGELOG.md` with the new version name and the release date
5. Update `app/build.gradle.kts` with the new `versionCode` and `versionName`
6. Create a commit for the new release: `git add CHANGELOG.md app/build.gradle.kts && git commit -m "Release Catima <VERSION>"`
7. Build the new .apks: `KEYSTORE=/path/to/keystore KEYSTORE_ALIAS=catima ./build.sh`
8. Upload `app/build/outputs/apk/gplay/release/app-gplay-release.apk` to Google Play Open Testing
5. Update the relevant build.gradle.kts with the new `versionCode` and `versionName`
- For Android releases, this is `app/build.gradle.kts`, make sure to use an even number above 1000
- For Wear OS releases, this is `wear/build.gradle.kts`, make sure to use a number between 200 and 1000
6. Create a commit for the new release:
- For Android releases, `git add CHANGELOG.md app/build.gradle.kts && git commit -m "Release Catima <VERSION>"`
- For Wear OS releases, `git add CHANGELOG.md wear/build.gradle.kts && git commit -m "Release Catima Wear OS <VERSION>"`
7. Build the new .apks:
- For Android releases, `BUILD_TYPE=app KEYSTORE=/path/to/keystore KEYSTORE_ALIAS=catima ./build.sh`
- For Wear OS releases, `BUILD_TYPE=wear KEYSTORE=/path/to/keystore KEYSTORE_ALIAS=catima ./build.sh`
8. Upload the Google release to Google Play Open Testing
- For Android releases, this is `app/build/outputs/apk/gplay/release/app-gplay-release.apk`
- For Wear OS releases, this is `wear/build/outputs/apk/release/wear-release.apk` (Wear OS currently does not use flavours)
9. Push the version update commit: `git push`
10. Create a new release on GitHub and attach the `app/build/outputs/apk/foss/release/app-foss-release.apk` and `SHA256SUMS` files
10. Create a new release on GitHub and attach the non-Google APKs and `SHA256SUMS` files
- For Android releases, this is `app/build/outputs/apk/foss/release/app-foss-release.apk` and `SHA256SUMS`. Releases are tagged vX.X.X.
- For Wear OS releases, this is `wear/build/outputs/apk/release/wear-release.apk` and `SHA256SUMs` (Wear OS currently does not use flavours). Releases are tagged vX.X.X-wearos.
11. When pushing the release to Google Play Production, update the metadata there: `bundle exec fastlane supply --version_code <VERSION_CODE>`
@@ -1,22 +1,25 @@
توقف عن بحثك لبطاقات المكافآت البلاستيكية عندما تخرج من المتجر أو تطبيق التسوق.
<b> امسح الباركود ضوئيًا إلى جهازك باستخدام الكاميرا ، وتجاهل تلك البطاقات الفعلية. </b>
توقف عن البحث عن بطاقات المكافآت البلاستيكية عند الدفع في المتجر أو على المتجر الإلكتروني.
<b>امسح الباركود إلى جهازك باستخدام الكاميرا، وانسَ البطاقات.</b>
انس محفظتك ، أو اجعلها خفيفة للغايةلتحفظ أغراضك الثمينة
انسَ محفظتك، أو احتفظ بها خفيفة جدًا للأغراض الثمينة.
باستخدام أداة الحمل اليومية الأساسية، يمكنك استبدال البلاستيك عديم الفائدة بالنقود.
مع هذه الأداة الأساسية للحمل اليومي (EDC) يمكنك استبدال البلاستيك عديم الفائدة بالنقد.
- يتميز بأذونات قليلة جدًا. لا اتصال بالإنترنت ولا إعلانات!
- أضف بطاقات أو أكواد بأسماء وألوان على ذوقك
- عندما لا يوجد رمز باركود، يمكنك إدخال الرمز يدويا ، أو لا يمكن استخدامه.
- استورد البطاقات والأكواد من الملفات، Stocard, Loyalty Card Keychain, FidMe, Catima و Voucher Vault.
- احم بياناتك من الضياع باستخدام نسخة احتياطية لجميع بطاقاتك وإذا أردت يمكنك نقلها إلى جهاز جديد.
- شارك القسائم والعروض الحصرية والرموز الترويجية أو البطاقات والرموز في أي تطبيق
- المظهر الداكن وخيارات إمكانية الوصول للمستخدمين ضعاف البصر.
- مصنوع للجميع من مطورين لبرامج مفتوحة المصدر!
- مترجم لأكثر من 20 لغة.
- مجاني بالكامل، بدعم من مساهمات المجتمع.
- استخدمها وادرسها وقم بتغييرها وشاركها كما يحلو لك ؛ <i> مع الجميع </i>.
- التطبيق ليس فقط مفتوح المصدر. بل إدارة بطاقات <i>متروكة الحقوق</i> البرمجيات الحرة (+GPLv3).
- تجنب التجسس مع أذونات قليلة جدًا. لا يوجد اتصال بالإنترنت ولا إعلانات.
- أضف البطاقات أو الأكواد مع الأسماء والألوان القابلة للتخصيص.
- إدخال الكود يدويًا إذا لم يكن هناك باركود لتخزينه، أو لا يمكن استخدامه.
- استيراد البطاقات والأكواد من الملفات، Catima، FidMe، سلسلة مفاتيح بطاقة الولاء وVoucher Vault.
- قم بعمل نسخة احتياطية لجميع بطاقاتك وانقلها إلى جهاز جديد إذا رغبت.
- شارك القسائم، العروض الحصرية، أكواد الترويج، أو البطاقات والأكواد باستخدام أي تطبيق.
- المزامنة مع الساعات الذكية المدعومة من Gadgetbridge أو Wear OS.
- الوضع الداكن وخيارات الوصول لمستخدمي ضعف البصر.
- مصمم للجميع من قبل مجتمع البرمجيات الحرة.
- ترجمات محلية مصنوعة يدوياً لأكثر من 40 لغة.
- مجانية، مدعومة بمساهمات المجتمع.
- استخدمها، ادرسها، عدلها وشاركها كما تشاء؛ <i>مع كل شيء</i>.
بسط حياتك وتجربة تسوقك ، ولا تخف من فقدان الإيصال الورقي أو بطاقة هدايا الدفع في المتجر أو تذكرة الطائرة مرة أخرى!
احفظ كل مكافآتك وهداياك معك أينما ذهبت.
- ليست فقط برامج مجانية / مفتوحة المصدر. إدارة بطاقات برامج <i>مفتوحة الحقوق</i> (GPLv3+).
بسّط حياتك وتسوقك، ولا تفقد أبداً إيصال ورقي، بطاقة هدايا للدفع في المتجر أو تذكرة طائرة.
خذ كل مكافآتك وبونصاتك معك، وتقدم.
@@ -0,0 +1,2 @@
- Přidán Bluetooth server pro podporu doprovodných aplikací Wear OS
- Odstraněn vložený CHANGELOG (seznam změn)
@@ -0,0 +1 @@
- Opravena neaktualizace seznamu widgetu při otevření karty z něj
@@ -0,0 +1 @@
- Přidána možnost otevření obrázku v interním prohlížeči obrázků v galerii
@@ -0,0 +1 @@
- Prvotní vydání doprovodné aplikace Wear OS
@@ -0,0 +1,4 @@
- Přidán posuvník na seznam karet
- Přidána úvodní obrazovka
- Opraveny problémy s odsazením
- Odstraněna hodnota čárového kódu ze zobrazení karty
Loaded 100 of 307 files, more files were not shown because too many files have changed in this diff. Show more