Compare commits

...
Author SHA1 Message Date
Sylvia van Os e212fb27cf Merge pull request #3321 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2026-09-09 17:20:51 +02:00
Damjan Gerl a8973296ee Translated using Weblate (Slovenian)
Currently translated at 100.0% (170 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/sl/
2026-09-09 13:52:15 +02:00
justcontributor 0d3fe0b253 Translated using Weblate (Korean)
Currently translated at 99.4% (169 of 170 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ko/
2026-09-09 13:52:15 +02:00
justcontributor 275880944d Translated using Weblate (Korean)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ko/
2026-09-09 13:52:15 +02:00
Damjan Gerl 36f38f9b3e Translated using Weblate (Slovenian)
Currently translated at 100.0% (359 of 359 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sl/
2026-09-09 13:52:15 +02:00
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
69 changed files with 918 additions and 211 deletions

No files matched your search

+12
View File
@@ -1,5 +1,17 @@
# 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
+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>
@@ -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
@@ -40,7 +42,9 @@ 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
@@ -502,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) {
@@ -524,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)
}
private fun extractIntentFields(intent: Intent) {
@@ -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,
@@ -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;
@@ -118,6 +120,16 @@ public class Utils {
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);
}
@@ -148,50 +160,35 @@ public class Utils {
tileLetterFontSize, pixelSize, pixelSize, backgroundColor, ForegroundColorHelper.Companion.needsDarkForeground(backgroundColor) ? Color.BLACK : Color.WHITE);
}
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();
@@ -199,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);
}
}
@@ -211,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);
@@ -222,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<>();
@@ -248,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);
@@ -259,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);
@@ -274,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;
@@ -314,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) {
@@ -330,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;
}
@@ -348,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;
@@ -356,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) {
@@ -96,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
@@ -454,6 +456,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
invalidateOptionsMenu();
ShortcutHelper.updateShortcuts(this);
listWidget.updateAll(LoyaltyCardViewActivity.this);
}
@Override
@@ -775,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();
@@ -786,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();
@@ -811,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();
+27 -26
View File
@@ -4,8 +4,8 @@ solokot
J. Lavoie
Allan Nordhøy
Oğuz Ersen
Heimen Stoffels
B o d o
Heimen Stoffels
FC (Fay) Stegerman
大王叫我来巡山
StoyanDimitrov
@@ -14,28 +14,28 @@ Katharine Chui / Katie
Silvério Santos
mondstern
IllusiveMan196
Edgars Andersons
Максим Горпиніч
Liner Seven
Fjuro
Priit Jõerüüt
Edgars Andersons
Liner Seven
Максим Горпиніч
GitSpoon
Priit Jõerüüt
Joel A
Altonss
Michael Moroni
Eric
GM
laralem
Petr Novák
laralem
Vasilis
Taco
Milo Ivir
nadiafekihahmed
pfaffenrodt
josé m
Scrambled777
damjang
Aayush Gupta
damjang
Scrambled777
josé m
Kachelkaiser
ikanakova
Nyatsuki
@@ -46,21 +46,23 @@ HudobniVolk
Warder
Balázs Meskó
Traductor
AMIR-G98
Samantaz Fox
mdvhimself
AMIR-G98
NooB9496
Cliff Heraldo
Sergio Paredes
Ankit Tiwari
Arno-github
Feike
NooB9496
Jose Delvani
Milan Šalka
Robin
தமிழ்நேரம்
huuhaa
Watchman89
Skrripy
Hosted Weblate user 54392
Govindgopalyadav
Gideon
waffshappen
@@ -70,7 +72,6 @@ pacavarn
ngocanhtve
josé m.
aradxxx
Watchman89
StellarSand
Quentin PAGÈS
Projjal Moitra
@@ -78,26 +79,26 @@ Aliaksandr Trush
e-michalak
தமிழ் நேரம்
hajertabbane
inavleb
Ziad OUALHADJ
Robin Liu
Ricky Tigg
Renko
Hosted Weblate user 54392
Denis Shilin
Renko
Ricky Tigg
Robin Liu
Ziad OUALHADJ
inavleb
しいたけ
Alexander Ivanov
Thu-Trang Pham
Tim Trek
Virginie
dukezas
ehrt74
ezn24
mrestivill
stavpup
Miha Frangež
stavpup
mrestivill
ezn24
ehrt74
dukezas
Virginie
Tim Trek
Thu-Trang Pham
Alì Mortacci
Andre Costa
Arif Budiman
arshbeerSingh
Augustin LAVILLE
Freddo espresso
+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>
+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>
+4 -4
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>
@@ -334,9 +334,9 @@
<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">Supprimer <xliff:g>%1$s</xliff:g> de Catima? Il ne pourra plus synchroniser de cartes.</string>
<string name="settings_wear_sync_unblock_message">Supprimer <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">Supprimer</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>
+1 -1
View File
@@ -220,7 +220,7 @@
<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_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>
+1
View File
@@ -314,4 +314,5 @@
<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>
+43 -2
View File
@@ -222,8 +222,8 @@
<string name="permissionReadCardsDescription">당신의 Catima 카드와 그에 대한 모든 세부 정보, 메모 및 이미지를 읽으세요</string>
<string name="settings_oled_dark_summary">OLED 화면에서의 배터리 사용량 줄임</string>
<string name="app_copyright_short">저작권 © Sylvia van Os 및 기여자</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="permissionReadCardsLabel">Catima 카드 읽기</string>
<string name="settings_keep_screen_on_summary">카드가 보일 때 화면 꺼짐 방지</string>
<string name="view_online">온라인에서 보기</string>
@@ -297,4 +297,45 @@
<string name="cardWithNumberAndLocale"><xliff:g>%d</xliff:g> 번 카드 (<xliff:g>%s</xliff:g>)</string>
<string name="acra_explain_crash">가능하다면, 오류가 발생했을 때 어떤 작업을 하고 계셨는지 자세히 적어주세요:</string>
<string name="barcodeEncoding">바코드 인코딩</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="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최근 이 시계에 Catima Wear OS 앱을 재설치한 경우에만 수락하십시오.</string>
<string name="open_settings">설정 열기</string>
<string name="open_in_gallery">사진 갤러리에서 열기</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>
<string name="wear_bt_channel_name">Wear OS 컴패니언</string>
<string name="wear_bt_notification_title">Catima Wear OS 동기화 활성화됨</string>
<string name="sort_by_most_recently_added">가장 최근에 추가함</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">Catima에서 Wear OS 스마트워치와 동기화하려면 거절하셨던 권한이 필요합니다</string>
<string name="zoomFrontImage">전면 사진 확대</string>
<string name="zoomBackImage">후면 사진 확대</string>
</resources>
@@ -337,4 +337,21 @@
<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
@@ -231,8 +231,8 @@
<string name="app_name">Catima</string>
<string name="settings_keep_screen_on_summary">Med ogledom kartice ohrani vključen zaslon</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">Med ogledom kartice onemogoči zaklepanje zaslona</string>
<string name="settings_allow_content_provider_read_title">Dovoli drugim aplikacijam dostop do mojih podatkov</string>
<string name="settings_allow_content_provider_read_summary">Aplikacije bodo morale še vedno zahtevati dovoljenje za dostop</string>
<string name="settings_allow_content_provider_read_title">Sinhronizacija z Gadgetbridgeom</string>
<string name="settings_allow_content_provider_read_summary">Omogoča aplikaciji Gadgetbridge, da sinhronizira tvoje kartice s podprtimi urami</string>
<string name="settings_use_volume_keys_navigation">Preklopi kartice z uporabo gumbov za glasnost</string>
<string name="settings_use_volume_keys_navigation_summary">Uporabi gumbe za glasnost za preklop prikazane kartice</string>
<string name="donate">Daruj</string>
@@ -319,4 +319,46 @@
<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>
<string name="settings_wear_sync">Sinhronizacija z Wear OS</string>
<string name="settings_wear_sync_summary">Omogoča spremljevalni aplikaciji Wear OS branje tvojih kartic prek Bluetootha</string>
<string name="wear_sync_permission_required">Catima potrebuje dovoljenja, ki si jih zavrnil/a, za sinhronizacijo z vašo pametno uro Wear OS</string>
<string name="settings_wear_sync_devices">Dovoljene naprave Wear OS</string>
<string name="settings_wear_sync_devices_summary">Upravljanje naprav, ki imajo dovoljenje za sinhronizacijo prek Bluetootha</string>
<string name="settings_wear_sync_no_devices">Ni dovoljenih naprav</string>
<plurals name="settings_wear_sync_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> dovoljena naprava</item>
<item quantity="two"><xliff:g>%d</xliff:g> dovoljeni napravi</item>
<item quantity="few"><xliff:g>%d</xliff:g> dovoljene naprave</item>
<item quantity="other"><xliff:g>%d</xliff:g> dovoljenih naprav</item>
</plurals>
<string name="settings_wear_sync_blocked_devices">Blokirane naprave Wear OS</string>
<string name="settings_wear_sync_blocked_devices_summary">Upravljanje blokiranih naprav Wear OS</string>
<string name="settings_wear_sync_no_blocked_devices">Ni blokiranih naprav</string>
<plurals name="settings_wear_sync_blocked_devices_summary_count">
<item quantity="one"><xliff:g>%d</xliff:g> blokirana naprava</item>
<item quantity="two"><xliff:g>%d</xliff:g> blokirani napravi</item>
<item quantity="few"><xliff:g>%d</xliff:g> blokirane naprave</item>
<item quantity="other"><xliff:g>%d</xliff:g> blokiranih naprav</item>
</plurals>
<string name="settings_wear_sync_unpair_title">Želiš odstraniti napravo?</string>
<string name="settings_wear_sync_unpair_message">Želiš odstraniti <xliff:g>%1$s</xliff:g> iz Catime? Naprava ne bo več mogla sinhronizirati tvojih kartic.</string>
<string name="settings_wear_sync_unblock_message">Želiš odstraniti <xliff:g>%1$s</xliff:g> s seznama blokiranih naprav? Naprava bo lahko ponovno zaprosila za dostop.</string>
<string name="settings_wear_sync_unpair">Odstrani</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">Tapni napravo, da jo odstraniš.</string>
<string name="wear_bt_pairing_channel_name">Povezovanje naprav Wear OS</string>
<string name="wear_bt_pairing_title">Naprava Wear OS se želi sinhronizirati</string>
<string name="wear_bt_pairing_message">Želiš dovoliti napravi <xliff:g>%1$s</xliff:g> dostop do tvojih kartic?</string>
<string name="wear_bt_pairing_allow">Dovoli</string>
<string name="wear_bt_pairing_block">Blokiraj</string>
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> ima zdaj dovoljenje za sinhronizacijo.</string>
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> je bila blokirana.</string>
<string name="wear_bt_pairing_mismatch_title">Nova zahteva za dostop od <xliff:g>%1$s</xliff:g></string>
<string name="wear_bt_pairing_mismatch_message">Ta naprava je bila že avtorizirana, zdaj pa prikazuje drugačen identifikacijski žeton. To se zgodi, ko se aplikacija Wear OS ponovno namesti ali se njeni podatki izbrišejo. Lahko pa gre tudi za poskus ponarejanja MAC-naslova prek Bluetootha.\n\nSprejmi le, če si pred kratkim na tej uri ponovno namestil/a aplikacijo Catima Wear OS.</string>
<string name="open_settings">Odpri nastavitve</string>
<string name="wear_bt_channel_name">Spremljevalna aplikacija Wear OS</string>
<string name="wear_bt_notification_title">Sinhronizacija Catima Wear OS je aktivna</string>
<string name="settings_category_title_smartwatch">Podpora za pametne ure</string>
<string name="wear_bt_failed_foreground_notification_title">Zagon storitve sinhronizacije Wear OS ni uspel</string>
<string name="wear_bt_critical_error_channel_name">Kritična napaka pri sinhronizaciji Wear OS</string>
</resources>
+23 -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>
@@ -308,4 +308,26 @@
<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>
+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>
@@ -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")
}
}
+4 -1
View File
@@ -2,7 +2,8 @@
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 for Android 6.0 and up.
<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>
@@ -0,0 +1 @@
- Opravena neaktualizace seznamu widgetu při otevření karty z něj
@@ -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
@@ -0,0 +1 @@
- Fehler behoben, bei welchem das Listen-Widget nicht aktualisiert, wenn eine Karte von dort geöffnet wird
@@ -0,0 +1,4 @@
- Bildlaufleiste zur Kartenliste hinzugefügt
- Startbildschirm hinzugefügt
- Einige Probleme mit den Abständen zum Bildschirmrand behoben
- Barcode vorübergehend aus der Kartenansicht entfernt
@@ -0,0 +1,2 @@
- Fix list widget not updating when opening a card from it
- Fix opening pkpass/pdf files from network locations sometimes failing
@@ -0,0 +1,4 @@
- Add scroll bar to card list
- Add splash screen
- Fix some padding issues
- Remove barcode value from card view
Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

@@ -0,0 +1 @@
- Correction du widget de liste qui ne se met pas à jour lorsqu'on ouvre une carte depuis celui-ci
@@ -0,0 +1,4 @@
- Ajout d'une barre de défilement dans la liste des cartes
- Ajout d'un écran de démarrage
- Correction de quelques soucis sur les marges
- Suppression de la valeur du code-barres dans la vue des cartes
@@ -0,0 +1 @@
- カードをリストウィジェットから開いた場合にリストが更新されない問題を修正しました
@@ -0,0 +1,4 @@
- スクロールばーをカード一覧に追加
- スプラッシュ画面を追加
- パディングに関する幾つかの問題を修正
- カード表示からバーコードの値を削除
@@ -0,0 +1,2 @@
- Wear OS 컴패니언 앱을 지원하기 위해 블루투스 서버 추가
- 내장 변경 내역 제거
@@ -0,0 +1 @@
- 목록 위젯에서 카드를 열 때 업데이트되지 않던 오류 수정
@@ -0,0 +1,3 @@
- 카드 목록에서 카드 ID 표시 지원 추가
- 가장 최근에 추가된 순으로 정렬 지원 추가
- 카드 전면/후면/아이콘 이미지를 확대할 숭 씨도록 내장 이미지 뷰어 추가
@@ -0,0 +1 @@
- 갤러리의 내장 이미지 뷰어에서 이미지를 여는 옵션 추가
@@ -0,0 +1 @@
- Wear OS 컴패니언 앱 첫 출시
@@ -0,0 +1,4 @@
- 카드 목록에 스크롤바 추가
- 스플래시 화면 추가
- 일부 여백 문제 수정
- 카드 보기에서 바코드 값 제거
@@ -0,0 +1 @@
- Opgelost dat de lijstwidget niet update als er een kaart door geopend wordt
@@ -0,0 +1,4 @@
- Scrollbalk toegevoegd aan kaartenlijst
- Welkomstscherm toegevoegd
- Enkele problemen met afstanden tot beeldschermrand opgelost
- Barcodewaarde verwijderd van kaartweergave
@@ -0,0 +1 @@
- Naprawiono błąd, w wyniku którego widżet listy nie aktualizował się po otwarciu karty z tego widżetu
@@ -0,0 +1,4 @@
- Dodano pasek przewijania do listy kart
- Dodano ekran powitalny
- Naprawiono pewne problemy związane z odstępami
- Usunięto wartość kodu kreskowego z widoku karty
@@ -0,0 +1 @@
- Corrigir a não atualização do widget de lista ao abrir um cartão a partir dele
@@ -1,3 +1,3 @@
- Adicionada opção de menu para bloquear a rotação da tela ao exibir um cartão. Se bloqueada, a tela muda para sua orientação "natural" e novas rotações serão impedidas. (pull #128 (https://github.com/brarcher/loyalty-card-locker/pull/128))
- Se um cartão for selecionado na tela principal, mas não puder ser carregado, o aplicativo trata a falha adequadamente e exibe uma mensagem
- Corrigido um caso em que os IDs de layout do assistente de introdução não eram encontrados.
- Adicionado bloqueio de rotação ao exibir cartão. Se bloqueado, a tela muda para orientação "natural" e impede novas rotações.
- Se um cartão na tela principal falhar ao carregar, o app trata o erro e exibe mensagem. (pull #132 (https://github.com/brarcher/loyalty-card-locker/pull/132))
- Corrigido erro de IDs de layout do assistente não encontrados. (pull #128 (https://github.com/brarcher/loyalty-card-locker/pull/128))
@@ -1,5 +1,5 @@
- Adicionado suporte para criar atalhos na tela inicial ao adicionar ou editar um cartão.
- Widget removido, pois era um substituto insatisfatório para os atalhos.
- Corrigida a exportação de backups no Android 7+.
- Informado um tipo mime mais preciso ao exportar dados de backup.
- Corrigido um erro que impedia a edição de cartões.
- Suporte a atalhos na tela inicial ao criar/editar cartão (pull #155)
- Removido widget por ser um péssimo substituto para atalhos (pull #155)
- Corrigida a exportação de backups no Android 7+ (pull #153)
- Tipo mime mais preciso ao exportar dados de backup (pull #156)
- Corrigido erro onde o cartão não podia ser editado (pull #155)
@@ -0,0 +1,4 @@
- Adicionar barra de rolagem à lista de cartões
- Adicionar tela de abertura (splash screen)
- Corrigir alguns problemas de espaçamento (padding)
- Remover o valor do código de barras da visualização do cartão
@@ -1,2 +1,2 @@
- Permitir que o usuário insira o código de barras manualmente. Se o usuário optar por inserir um código de barras manualmente, uma lista de todas as imagens de código de barras válidas e suportadas é exibida. O usuário pode então selecionar a imagem do código de barras correspondente ao que deseja.
- Corrigir problema em que alguns códigos de barras exibidos ficavam borrados.
- Permite inserir código de barras manualmente. Se optado, é exibida lista de imagens válidas e suportadas para seleção. (issue #33, pull #44)
- Corrigido problema onde alguns códigos de barras exibidos ficavam borrados (issue #37)
@@ -1,2 +1,2 @@
- Um clique longo em um cartão exibe a opção de copiar o ID do cartão para a área de transferência. (pull #49 (https://github.com/brarcher/loyalty-card-locker/issues/49))
- O botão Voltar na visualização de Importação/Exportação agora funciona, levando o usuário de volta à visualização principal.
- O botão Voltar na visualização de Importação/Exportação agora funciona, levando o usuário para a tela principal
@@ -1,4 +1,4 @@
- O brilho da tela aumenta para o máximo ao exibir um cartão, para ajudar os leitores de código de barras a capturá-lo com sucesso.
- Adição de confirmação ao excluir um cartão.
- Adição de traduções para alemão e tcheco.
- Alteração para maior clareza na tradução para italiano.
- Brilho da tela aumenta ao máximo ao exibir cartão para ajudar os leitores na leitura. (pull #54)
- Adicionada confirmação ao excluir um cartão. (pull #55)
- Adicionadas traduções para alemão (pull #57) e tcheco (pull #58)
- Esclarecimento na tradução para o italiano (pull #66)
@@ -1,22 +1,23 @@
Pare de procurar cartões de fidelidade de plástico na hora de pagar em lojas físicas ou virtuais.
<b>Escaneie códigos de barras usando a câmera do seu dispositivo e esqueça os cartões físicos.</b>
<b>Escaneie códigos de barras com a câmera do seu dispositivo e esqueça os cartões físicos.</b>
Esqueça a carteira ou mantenha-a leve, guardando apenas itens essenciais.
Deixe a carteira em casa ou mantenha-a leve, guardando apenas itens essenciais de valor.
Com esta ferramenta indispensável para o dia a dia (EDC), você pode substituir o plástico inútil por uma solução digital prática.
Com esta ferramenta indispensável para o dia a dia (EDC), você substitui o plástico inútil pela praticidade digital.
- Privacidade garantida com o mínimo de permissões: sem acesso à internet e sem anúncios.
- Adicione cartões ou códigos com nomes e cores personalizáveis.
- Insira códigos manualmente caso não haja código de barras ou se ele não puder ser utilizado.
- Importe cartões e códigos de arquivos e de aplicativos como Catima, FidMe, Loyalty Card Keychain e Voucher Vault.
- Faça backup de todos os seus cartões e transfira-os para um novo dispositivo, se desejar.
- Compartilhe cupons, ofertas exclusivas, códigos promocionais ou os próprios cartões e códigos usando qualquer aplicativo.
- Insira códigos manualmente caso não haja código de barras ou se a leitura não for possível.
- Importe cartões e códigos de arquivos ou de aplicativos como Catima, FidMe, Loyalty Card Keychain e Voucher Vault.
- Faça backup de todos os seus cartões e transfira-os para um novo dispositivo quando quiser.
- Compartilhe cupons, ofertas exclusivas, códigos promocionais, cartões ou códigos usando qualquer aplicativo.
- Sincronize com smartwatches compatíveis (Gadgetbridge ou Wear OS).
- Inclui tema escuro e opções de acessibilidade para usuários com deficiência visual.
- Criado para todos pela comunidade de software livre.
- Traduções manuais e localizadas para mais de 40 idiomas.
- Gratuito e mantido por contribuições da comunidade.
- Use, estude, modifique e compartilhe como quiser; <i>com todos</i>.
- Mais do que apenas Software Livre / Código Aberto: um gerenciador de cartões de software livre sob licença <i>copyleft</i> (GPLv3+).
- Mais do que apenas Software Livre/Código Aberto: um gerenciador de cartões de software livre com licença <i>copyleft</i> (GPLv3+).
Simplifique sua vida e suas compras; nunca mais perca um recibo de papel, um cartão-presente ou uma passagem aérea.
Leve todos os seus cartões de fidelidade e bônus com você e economize sempre.
@@ -0,0 +1 @@
- Исправлена ошибка, при которой виджет списка не обновлялся при открытии карты из него
@@ -0,0 +1,4 @@
- Добавлена полоса прокрутки в список карт
- Добавлен заставочный экран
- Исправлены некоторые проблемы с отступами
- Убрано значение штрих-кода из отображения карты
@@ -0,0 +1,2 @@
- Dodan strežnik Bluetooth za podporo spremljevalni aplikaciji Wear OS
- Odstranitev vgrajenega seznama sprememb (CHANGELOG)
@@ -0,0 +1 @@
- Popravljena napaka, zaradi katere se gradnik seznama ni posodobil ob odpiranju kartice iz njega
@@ -0,0 +1 @@
- Prva izdaja spremljevalne aplikacije za Wear OS
@@ -0,0 +1,4 @@
- Dodan drsnik na seznam kartic
- Dodan začetni zaslon
- Popravljene nekatere težave z razmiki
- Odstranjena vrednost črtne kode iz pogleda kartice
@@ -2,7 +2,8 @@
- Omogočen je vklop svetilke med skeniranjem
- Dodana podpora za UPC-E
- Podpora dodajanju slike prednje/zadnje strani kartice
- Podpora uvozu datotek ZIP, zaščitenih z geslom in iz Stocard (Beta)
- Podpora uvozu datotek ZIP, zaščitenih z geslom
- Podpora uvozu datotek iz Stocard (Beta)
- Popravek praznih prostorov v opombah pri uvozu iz Fidme
- Podpora novemu formatu izvoza Voucher Vault
- Popravek plavajočih gumbov v Android 4
@@ -11,11 +11,12 @@ S tem osnovnim orodjem na svoji elektronski napravi, lahko neuporabno plastiko n
- Uvozi kartice in kode iz datotek, Catima, FidMe, Loyalty Card Keychain in Voucher Vault.
- Naredi varnostno kopijo vseh svojih kartic in jih po želji prenesi v novo napravo.
- Deli kupone, ekskluzivne ponudbe, promocijske kode ali kartice in kode s katero koli aplikacijo.
- Sinhronizacija s podprtimi pametnimi urami Gadgetbridge ali Wear OS.
- Temna tema in možnosti dostopnosti za uporabnike z okvarami vida.
- Izdelana za vsakogar s strani skupnosti programske opreme libre.
- Izdelana za vsakogar s strani skupnosti proste programske opreme.
- Lokalizirani ročno izdelani prevodi za več kot 40 jezikov.
- Brezplačno, podprto s prispevki skupnosti.
- Uporabljaj, preučuj, spreminjaj in delite po želji; <i>z vsemi</i>.
- Uporabljaj, preučuj, spreminjaj in deli po želji; <i>z vsemi</i>.
- Programska oprema je odprtokodna in brezplačna. Upravljanje kartic s pomočjo <i>Copylefted</i> proste programske opreme (GPLv3+ licenca).
Poenostavi si življenje in nakupovanje ter nikoli več ne izgubi papirnatega računa, darilne kartice za plačilo v trgovini ali letalske karte.
@@ -0,0 +1 @@
- 修复从列表小部件打开卡片时,列表小部件不显示的问题
@@ -0,0 +1,4 @@
- 卡片列表添加滚动条
- 添加启动画面
- 修复一些填充问题
- 从卡片视图删除条形码值
+2 -2
View File
@@ -62,8 +62,8 @@ junit-junit = { group = "junit", name = "junit", version = "4.13.2" }
org-robolectric-robolectric = { group = "org.robolectric", name = "robolectric", version = "4.16.1" }
[plugins]
com-android-application = { id = "com.android.application", version = "9.3.1" }
com-android-library = { id = "com.android.library", version = "9.3.1" }
com-android-application = { id = "com.android.application", version = "9.4.0" }
com-android-library = { id = "com.android.library", version = "9.4.0" }
org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
org-jetbrains-kotlin-plugin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=84fbba45c7f4c64abc77460e1c00f541e9f960e3c7ed2538f1ede19eacd873ae
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip
distributionSha256Sum=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
+2 -2
View File
@@ -16,8 +16,8 @@ android {
applicationId = "me.hackerchick.catima"
minSdk = 26
targetSdk = 36
versionCode = 200
versionName = "1.0.0 Wear OS"
versionCode = 201
versionName = "1.0.1 Wear OS"
}
buildTypes {
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Catima (ladicí verze)</string>
</resources>
@@ -17,10 +17,12 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
import androidx.wear.compose.foundation.lazy.items
import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
import androidx.wear.compose.material3.CircularProgressIndicator
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.SurfaceTransformation
import androidx.wear.compose.material3.lazy.rememberTransformationSpec
import androidx.wear.compose.material3.lazy.transformedHeight
@@ -67,32 +69,43 @@ fun CardListScreen(
)
}
else -> {
val transformationSpec = rememberTransformationSpec()
TransformingLazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 32.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
items(cards, key = { it.id }) { card ->
Button(
modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec),
transformation = SurfaceTransformation(transformationSpec),
label = {
Text(
text = card.store,
maxLines = 1,
)
},
onClick = { onCardClick(card) },
colors = if (card.headerColor != null) {
ButtonDefaults.buttonColors(
containerColor = Color(card.headerColor),
contentColor = if (ForegroundColorHelper.needsDarkForeground(card.headerColor)) Color.Black else Color.White
)
} else {
ButtonDefaults.buttonColors()
},
)
val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(
scrollState = columnState,
) { contentPadding ->
val transformationSpec = rememberTransformationSpec()
TransformingLazyColumn(
state = columnState,
contentPadding = contentPadding,
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
items(cards, key = { it.id }) { card ->
Button(
modifier = Modifier
.fillMaxWidth()
.transformedHeight(this, transformationSpec)
.minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
transformation = SurfaceTransformation(transformationSpec),
label = {
Text(
text = card.store,
maxLines = 1,
)
},
onClick = { onCardClick(card) },
colors = if (card.headerColor != null) {
ButtonDefaults.buttonColors(
containerColor = Color(card.headerColor),
contentColor = if (ForegroundColorHelper.needsDarkForeground(
card.headerColor
)
) Color.Black else Color.White
)
} else {
ButtonDefaults.buttonColors()
},
)
}
}
}
val footerLabel = syncStatus.labelRes
@@ -21,6 +21,7 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color as ComposeColor
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.stringResource
@@ -138,6 +139,9 @@ private fun CardDetail(card: WearCard) {
textAlign = TextAlign.Center,
maxLines = 2,
color = ComposeColor.Black,
// Hide the barcode value until we can be completely certain the element won't partially fall out of the UI
// This is a workaround for a requirement for Google Play: https://github.com/CatimaLoyalty/Android/issues/3284
modifier = Modifier.alpha(0f)
)
}
}
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Catima</string>
<string name="no_cards">Nenalezeny žádné karty.\nOtevřete aplikaci Catima na telefonu pro přidání karet.</string>
<string name="phone_not_connected">Nepodařilo se spojit s aplikací Catima na vašem telefonu.\nUjistěte se, že na vašem telefonu běží aplikace Catima, vaše hodinky jsou připojeny a v nastavení Catima je povolena podpora Wear OS.</string>
<string name="loading_cards">Načítání karet…</string>
<string name="no_barcode">Pro tuto kartu není k dispozici žádný čárový kód.</string>
<string name="card_id_label">ID karty</string>
<string name="syncing">Synchronizace s telefonem…</string>
<string name="sync_failed">Telefon není dostupný zobrazuji karty z mezipaměti</string>
<string name="version_incompatible">Aplikace Catima na vašem telefonu a hodinkách jsou nekompatibilní.\nAktualizujte prosím obě.</string>
<string name="bluetooth_permission_denied">Oprávnění Bluetooth zamítnuto.\nUdělte jej v Nastavení pro synchronizaci karet.</string>
<string name="bluetooth_disabled">Bluetooth je vypnut.\nPovolte jej pro synchronizaci karet z vašeho telefonu.</string>
<string name="sync_error">Nepodařilo se přečíst karty z vašeho telefonu.\nZkuste znovu provést synchronizaci.</string>
<string name="unauthorized">Neautorizováno.\nPovolte těmto hodinkám číst vaše karty v nastavení aplikace Catima.</string>
</resources>