Compare commits

...

540 Commits
v0.29 ... v1.14

Author SHA1 Message Date
Sylvia van Os
f4d4e3d6fb Release v1.14 2021-06-07 20:21:48 +02:00
Sylvia van Os
929633e4dd Ask to update barcode value if card ID changes 2021-06-06 21:13:44 +02:00
Sylvia van Os
eec7359603 Merge pull request #230 from weblate/weblate-catima-catima
Translations update from Weblate
2021-05-25 12:07:42 +02:00
solokot
4168ec3b43 Translated using Weblate (Russian)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2021-05-25 08:33:41 +02:00
Sylvia van Os
6568ebb01c Merge pull request #224 from weblate/weblate-catima-catima
Translations update from Weblate
2021-05-09 11:56:14 +02:00
Heimen Stoffels
99e2a75d46 Translated using Weblate (Dutch)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2021-05-09 11:33:39 +02:00
Allan Nordhøy
43ae42c7c5 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nb_NO/
2021-05-09 11:33:39 +02:00
Allan Nordhøy
a7aa3e9e0e Shorter title and description (#220) 2021-05-08 00:51:49 +02:00
Weblate (bot)
5a9f0a44fd Translations update from Weblate (#222)
* Translated using Weblate (Japanese)

Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.3% (150 of 151 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hans/

Co-authored-by: Nyatsuki <Odamaki@yandex.ru>
Co-authored-by: Kevin Sicong Jiang <kev4313@yahoo.com>
2021-05-02 13:02:04 +02:00
Kevin Sicong Jiang
4a1858e47b Fix active tab lost on rotation (#221) 2021-05-01 23:41:19 +02:00
Sylvia van Os
d8d8a59707 Merge pull request #219 from weblate/weblate-catima-catima
Translations update from Weblate
2021-05-01 10:57:58 +02:00
Kevin Sicong Jiang
b027beea35 Added translation using Weblate (Chinese (Simplified)) 2021-05-01 10:35:08 +02:00
Sylvia van Os
b4d0651e99 Merge pull request #216 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-29 13:42:59 +02:00
Allan Nordhøy
b40380dff6 Translated using Weblate (Slovenian)
Currently translated at 41.7% (63 of 151 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/sl/
2021-04-29 13:32:10 +02:00
Allan Nordhøy
b1a0a98004 Translated using Weblate (Norwegian Bokmål)
Currently translated at 84.1% (127 of 151 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2021-04-29 13:32:10 +02:00
J. Lavoie
044d363f47 Translated using Weblate (Italian)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2021-04-29 13:32:09 +02:00
J. Lavoie
35d659be31 Translated using Weblate (French)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2021-04-29 13:32:09 +02:00
Adolfo Jayme Barrientos
8bc1e2d321 Translated using Weblate (Spanish)
Currently translated at 77.4% (117 of 151 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2021-04-29 13:32:09 +02:00
J. Lavoie
b8811ba053 Translated using Weblate (German)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2021-04-29 13:32:08 +02:00
Sylvia van Os
cbaf172e9d Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-04-28 12:45:39 +02:00
Sylvia van Os
78c831cb68 Add Voucher Vault PDF 417 export support
a2911e0738
2021-04-28 12:45:02 +02:00
Sylvia van Os
279e775fb6 Merge pull request #212 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-27 09:12:53 +02:00
solokot
e5b30c9528 Translated using Weblate (Russian)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-04-27 04:01:04 +02:00
Heimen Stoffels
eb9732658f Translated using Weblate (Dutch)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-04-27 04:01:03 +02:00
Sylvia van Os
5feb59612d Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-04-26 20:58:20 +02:00
Sylvia van Os
09bd9b3882 Update CHANGELOG 2021-04-26 20:54:53 +02:00
Sylvia van Os
beb619000c Merge pull request #211 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-26 20:53:33 +02:00
Heimen Stoffels
72425dd39e Translated using Weblate (Dutch)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-04-26 20:52:01 +02:00
Sylvia van Os
a93d240d9a Fix translation oops 2021-04-26 20:51:48 +02:00
Sylvia van Os
d380e284b1 Merge pull request #205 from TheLastProject/feature/multiselect
Support multi-selection
2021-04-26 20:18:06 +02:00
Sylvia van Os
cbbb434aae spotBugs 2021-04-26 20:12:05 +02:00
Sylvia van Os
6dc8490b5e Fix lint 2021-04-26 19:42:57 +02:00
Sylvia van Os
b2c57258b3 Fix unit tests 2021-04-26 19:23:22 +02:00
Sylvia van Os
fe0ae4049b Merge pull request #209 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-25 11:14:23 +02:00
Nyatsuki
0517a7514e Translated using Weblate (Japanese)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ja/
2021-04-25 09:32:11 +02:00
Sylvia van Os
39544ac853 Really fix typo 2021-04-18 22:43:38 +02:00
Sylvia van Os
f46e1d09ba Fix typo 2021-04-18 22:42:41 +02:00
Sylvia van Os
6421f09eab Implement multi-copy and multi-share 2021-04-18 13:47:51 +02:00
Arshbeer Singh
45663065f9 WIP Issue #14 (and #65)
Implement Functionality to Copy Multiple Cards
2021-04-18 00:43:03 +02:00
Arshbeer Singh
67328724fa WIP Issue #14 (and #65)
Add Ability to Select Multiple Cards
Highlight Card on Long Press/Click
Replace ListView with RecyclerView for Extra Features and Functionality
Add Card Long Press Animations
Replace CursorAdapter with a combination of RecyclerViewAdapter and Cursor Adapter
2021-04-17 23:16:54 +02:00
Arshbeer Singh
55373e82a5 Fix Issue #65 2021-04-17 15:48:48 +02:00
Sylvia van Os
acdb5d6fe7 Merge pull request #203 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-17 13:30:03 +02:00
Nyatsuki
d9461c476a Translated using Weblate (Japanese)
Currently translated at 100.0% (147 of 147 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ja/
2021-04-17 13:27:05 +02:00
solokot
deee5f6aa2 Translated using Weblate (Russian)
Currently translated at 100.0% (147 of 147 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-04-17 13:27:03 +02:00
Heimen Stoffels
efd2b1ffe1 Translated using Weblate (Dutch)
Currently translated at 100.0% (147 of 147 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-04-17 13:27:02 +02:00
J. Lavoie
a87d8bbfe4 Translated using Weblate (Italian)
Currently translated at 100.0% (147 of 147 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2021-04-17 13:27:02 +02:00
J. Lavoie
a1d5275063 Translated using Weblate (French)
Currently translated at 100.0% (147 of 147 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2021-04-17 13:27:01 +02:00
J. Lavoie
e327306955 Translated using Weblate (German)
Currently translated at 100.0% (147 of 147 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2021-04-17 13:27:01 +02:00
Sylvia van Os
9469ae37e1 Merge pull request #202 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-16 12:02:17 +02:00
Nyatsuki
9e2d65b2cd Added translation using Weblate (Japanese) 2021-04-16 11:26:53 +02:00
Allan Nordhøy
d509c06815 App strings reworked 3 (#188) 2021-04-15 21:51:41 +02:00
Sylvia van Os
70faa7636a Merge pull request #201 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-15 21:30:42 +02:00
psa-jforestier
4072bc7607 Translated using Weblate (French)
Currently translated at 100.0% (147 of 147 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2021-04-15 21:26:59 +02:00
Sylvia van Os
eced502985 Merge pull request #200 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-14 16:06:52 +02:00
solokot
47441dbb9a Translated using Weblate (Russian)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2021-04-14 15:27:05 +02:00
Allan Nordhøy
e7729d9763 README reworked (#191) 2021-04-12 22:11:21 +02:00
Sylvia van Os
df40b72f77 Fastlane fixes 2021-04-11 00:26:22 +02:00
Sylvia van Os
1a1c028565 Release 1.13 2021-04-10 13:48:13 +02:00
Sylvia van Os
5cd77c3a25 Export V2 test 2021-04-10 13:28:26 +02:00
Sylvia van Os
369631d00c Improve import/export test 2021-04-10 10:14:59 +02:00
Sylvia van Os
350031624c Add V2 import test 2021-04-09 23:01:38 +02:00
Sylvia van Os
846f4d4904 Test barcode type migration 2021-04-07 21:48:19 +02:00
Sylvia van Os
5081eb2dce Lighter grey for non-usable barcode types 2021-04-07 21:19:56 +02:00
Sylvia van Os
e964fda54a Fix tests and barcode ID import 2021-04-07 20:42:11 +02:00
Sylvia van Os
aaa4fc1ef3 Fix bad replace 2021-04-07 18:10:07 +02:00
Sylvia van Os
93331e1a27 Merge pull request #195 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-07 16:58:28 +02:00
Heimen Stoffels
3251a6266b Translated using Weblate (Dutch)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2021-04-07 13:27:23 +02:00
J. Lavoie
d3f8399cbe Translated using Weblate (German)
Currently translated at 66.6% (2 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2021-04-07 13:27:22 +02:00
J. Lavoie
7c805128a7 Translated using Weblate (French)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2021-04-07 13:27:22 +02:00
solokot
a40c4841da Translated using Weblate (Russian)
Currently translated at 100.0% (147 of 147 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-04-07 13:26:58 +02:00
Heimen Stoffels
768ac795ff Translated using Weblate (Dutch)
Currently translated at 100.0% (147 of 147 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-04-07 13:26:58 +02:00
Allan Nordhøy
e8460d52ec Translated using Weblate (Norwegian Bokmål)
Currently translated at 94.5% (139 of 147 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2021-04-07 13:26:58 +02:00
J. Lavoie
03a7efb52e Translated using Weblate (Italian)
Currently translated at 100.0% (147 of 147 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2021-04-07 13:26:58 +02:00
J. Lavoie
48b60d8b4d Translated using Weblate (French)
Currently translated at 100.0% (147 of 147 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2021-04-07 13:26:58 +02:00
J. Lavoie
562b830e5a Translated using Weblate (German)
Currently translated at 100.0% (147 of 147 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2021-04-07 13:26:57 +02:00
Sylvia van Os
ac810a0c6f Don't force-reset loyalty card type 2021-04-06 22:45:32 +02:00
Sylvia van Os
27a90615a9 Make spotBugs happy 2021-04-06 22:41:11 +02:00
Sylvia van Os
f894427247 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-04-06 22:27:36 +02:00
Sylvia van Os
8b7df8dabe Remove privay policy first start dialog
Just done with Huawei's nonsense.
2021-04-06 22:27:03 +02:00
Sylvia van Os
d66903f972 Merge pull request #193 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-06 00:39:41 +02:00
Allan Nordhøy
7834a93394 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nb_NO/
2021-04-06 00:34:01 +02:00
J. Lavoie
572378de85 Translated using Weblate (Italian)
Currently translated at 100.0% (146 of 146 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2021-04-06 00:34:00 +02:00
J. Lavoie
26e0c50a13 Translated using Weblate (French)
Currently translated at 100.0% (146 of 146 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2021-04-06 00:34:00 +02:00
J. Lavoie
cd638a96f3 Translated using Weblate (German)
Currently translated at 100.0% (146 of 146 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2021-04-06 00:34:00 +02:00
Sylvia van Os
4e043edb64 Show all barcodes and recover from invalid selection 2021-04-06 00:33:35 +02:00
Sylvia van Os
1067d09773 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-04-05 19:44:15 +02:00
Sylvia van Os
d347cdde3e Add license info for third party things in About screen 2021-04-05 19:43:49 +02:00
Sylvia van Os
b704a7492e Merge pull request #192 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-05 17:43:07 +02:00
Heimen Stoffels
4c28d5d181 Translated using Weblate (Dutch)
Currently translated at 66.6% (2 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2021-04-05 17:27:02 +02:00
solokot
47e50de063 Translated using Weblate (Russian)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2021-04-05 17:27:02 +02:00
Allan Nordhøy
3777abc2a3 Translated using Weblate (Norwegian Bokmål)
Currently translated at 33.3% (1 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nb_NO/
2021-04-05 17:27:01 +02:00
solokot
01554381b2 Translated using Weblate (Russian)
Currently translated at 100.0% (146 of 146 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-04-05 17:27:01 +02:00
Heimen Stoffels
09ca9c47ab Translated using Weblate (Dutch)
Currently translated at 100.0% (146 of 146 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-04-05 17:27:01 +02:00
Allan Nordhøy
6751befe5d Translated using Weblate (Norwegian Bokmål)
Currently translated at 94.5% (138 of 146 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2021-04-05 17:27:00 +02:00
Allan Nordhøy
91661f1059 Translated using Weblate (English)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/en/
2021-04-05 17:26:59 +02:00
Sylvia van Os
afe47f1b84 Use letter icon for shortcuts too 2021-04-05 12:59:12 +02:00
Sylvia van Os
9bcbfc6d81 Hide new FAB in shortcut configure view 2021-04-05 12:20:15 +02:00
Sylvia van Os
89a40a789d Small layout fixes 2021-04-05 00:28:02 +02:00
Allan Nordhøy
e60814d6f3 Fastlane ASO (#187) 2021-04-04 23:08:59 +02:00
Sylvia van Os
ed8028a22b Merge pull request #186 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-04 14:27:03 +02:00
Allan Nordhøy
bb106f185e Translated using Weblate (Norwegian Bokmål)
Currently translated at 93.8% (137 of 146 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2021-04-04 14:25:37 +02:00
J. Lavoie
18c4dd4dc9 Translated using Weblate (Italian)
Currently translated at 100.0% (146 of 146 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2021-04-04 14:25:37 +02:00
J. Lavoie
72672e99c2 Translated using Weblate (French)
Currently translated at 100.0% (146 of 146 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2021-04-04 14:25:37 +02:00
J. Lavoie
73067d1fe8 Translated using Weblate (German)
Currently translated at 100.0% (146 of 146 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2021-04-04 14:25:37 +02:00
Sylvia van Os
5e6a4c8184 Merge pull request #184 from comradekingu/patch-1
App strings reworked 2
2021-04-04 14:25:33 +02:00
Sylvia van Os
310228fb5e Merge branch 'master' into patch-1 2021-04-04 14:24:59 +02:00
Allan Nordhøy
5aef382b68 Spelling: fidme-export 2021-04-03 17:16:02 +00:00
Allan Nordhøy
9fa78a4ea8 Spelling: FidMe 2021-04-03 16:13:12 +00:00
Sylvia van Os
400867b03f Merge pull request #185 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-03 17:47:54 +02:00
solokot
b06c6bc94d Translated using Weblate (Russian)
Currently translated at 100.0% (146 of 146 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-04-03 17:39:05 +02:00
Heimen Stoffels
d6c48bdf6e Translated using Weblate (Dutch)
Currently translated at 100.0% (146 of 146 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-04-03 17:39:05 +02:00
Allan Nordhøy
4d11391f8a Translated using Weblate (Norwegian Bokmål)
Currently translated at 91.0% (133 of 146 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2021-04-03 17:39:05 +02:00
Sylvia van Os
8b0490fdf3 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-04-03 17:38:48 +02:00
Sylvia van Os
6ec23d976b Fix grammar mistake 2021-04-03 17:38:19 +02:00
Allan Nordhøy
11ed56ee11 App strings reworked 2 2021-04-03 15:26:53 +00:00
Sylvia van Os
4ded73f78e Merge pull request #183 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-03 15:15:08 +02:00
J. Lavoie
588f8ef677 Translated using Weblate (Korean)
Currently translated at 64.8% (94 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ko/
2021-04-03 15:12:10 +02:00
solokot
3d70095862 Translated using Weblate (Russian)
Currently translated at 100.0% (145 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-04-03 15:12:10 +02:00
J. Lavoie
410309ebd2 Translated using Weblate (Polish)
Currently translated at 68.2% (99 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2021-04-03 15:12:10 +02:00
Heimen Stoffels
b9e646a25d Translated using Weblate (Dutch)
Currently translated at 100.0% (145 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-04-03 15:12:10 +02:00
J. Lavoie
81c1ec9199 Translated using Weblate (Italian)
Currently translated at 100.0% (145 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2021-04-03 15:12:10 +02:00
J. Lavoie
4498d08afb Translated using Weblate (French)
Currently translated at 100.0% (145 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2021-04-03 15:12:09 +02:00
J. Lavoie
963789db25 Translated using Weblate (German)
Currently translated at 100.0% (145 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2021-04-03 15:12:09 +02:00
Sylvia van Os
902fbc505d Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-04-03 15:11:54 +02:00
Sylvia van Os
9889678e53 Add note on unsupported barcode type 2021-04-03 15:11:22 +02:00
Sylvia van Os
ac551ed93f Remove migration guides as the app now tells you in the import screen 2021-04-03 14:36:09 +02:00
Sylvia van Os
ec63931396 Merge pull request #180 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-03 10:22:08 +02:00
solokot
d29d6ddf4a Translated using Weblate (Russian)
Currently translated at 100.0% (145 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-04-03 08:39:48 +02:00
J. Lavoie
38aac76144 Translated using Weblate (Russian)
Currently translated at 100.0% (145 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-04-03 08:39:48 +02:00
Sylvia van Os
30de0a8266 Make spotbugs happy 2021-04-01 18:16:04 +02:00
Sylvia van Os
0016b40256 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-04-01 18:01:49 +02:00
Sylvia van Os
aa3588dbfe Fix unit tests 2021-04-01 18:01:31 +02:00
Sylvia van Os
bf7ddc023d Merge pull request #179 from weblate/weblate-catima-catima
Translations update from Weblate
2021-04-01 17:49:48 +02:00
solokot
e50cf66bca Translated using Weblate (Russian)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-04-01 17:48:30 +02:00
Heimen Stoffels
85c30185e8 Translated using Weblate (Dutch)
Currently translated at 100.0% (151 of 151 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-04-01 17:48:30 +02:00
Sylvia van Os
5bce259445 Simplify font sizing 2021-04-01 17:48:17 +02:00
Sylvia van Os
8504109399 Merge pull request #178 from TacoTheDank/master
A few miscellaneous improvements
2021-04-01 14:54:52 +02:00
Sylvia van Os
e182857e1b Merge pull request #177 from TheLastProject/feature/differentBarcodeValueThanCardId
Support setting a different barcode value than card ID
2021-04-01 14:34:30 +02:00
Sylvia van Os
981c0b9ca6 Support setting a different barcode value than card ID 2021-04-01 13:26:57 +02:00
TacoTheDank
925f62b633 Use FragmentContainerView for SettingsActivity 2021-03-31 22:56:02 -04:00
TacoTheDank
cceb1207ae Update libraries 2021-03-31 22:30:55 -04:00
TacoTheDank
2e633b19dc Update gradle plugins, rearrange libraries 2021-03-31 22:23:04 -04:00
TacoTheDank
b8b1074a46 Update gradle wrapper 2021-03-31 22:18:57 -04:00
TacoTheDank
bea65793f0 Add wrapper validation action, update upload-artifact to v2 2021-03-31 22:18:47 -04:00
Sylvia van Os
252efabdc6 Increase versioncode for forgotten translation update 2021-03-30 18:51:32 +02:00
Sylvia van Os
018efaeebb Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-03-30 18:46:14 +02:00
Sylvia van Os
56522b42e2 Release 1.12 2021-03-30 18:45:54 +02:00
Sylvia van Os
122a80f29c Merge pull request #176 from weblate/weblate-catima-catima
Translations update from Weblate
2021-03-29 20:58:07 +02:00
Samantaz Fox
9363a31a77 Translated using Weblate (French)
Currently translated at 100.0% (148 of 148 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2021-03-29 20:57:13 +02:00
Sylvia van Os
21f3c58e32 Merge pull request #175 from weblate/weblate-catima-catima
Translations update from Weblate
2021-03-29 17:43:08 +02:00
solokot
205f7bb59d Translated using Weblate (Russian)
Currently translated at 100.0% (148 of 148 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-03-29 17:26:59 +02:00
Heimen Stoffels
1cc0f11a5d Translated using Weblate (Dutch)
Currently translated at 100.0% (148 of 148 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-03-29 17:26:59 +02:00
Sylvia van Os
d2168f4d91 Merge pull request #174 from weblate/weblate-catima-catima
Translations update from Weblate
2021-03-28 15:57:57 +02:00
Sylvia van Os
ba46e2a0b8 Fix Weblate's incorrect escaping 2021-03-28 15:33:56 +02:00
solokot
6fb102d9b5 Translated using Weblate (Russian)
Currently translated at 100.0% (145 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-03-28 15:32:04 +02:00
J. Lavoie
83b3d5d31c Translated using Weblate (Spanish)
Currently translated at 86.2% (125 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2021-03-28 15:32:04 +02:00
Sylvia van Os
c93b7527ad Merge pull request #173 from TheLastProject/feature/import-local-image
Added ability to import a barcode from a local image
2021-03-28 15:31:59 +02:00
Sylvia van Os
07bfc95bf9 Update CHANGELOG 2021-03-28 15:25:23 +02:00
Sylvia van Os
d34554ac07 Split up code more 2021-03-28 15:20:54 +02:00
Sylvia van Os
63754798ef Fix error toasts not showing 2021-03-26 19:48:17 +01:00
Sylvia van Os
9ca4b7fbe1 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into feature/import-local-image 2021-03-25 21:35:28 +01:00
Sylvia van Os
4d6021ad8c Merge pull request #171 from weblate/weblate-catima-catima
Translations update from Weblate
2021-03-25 21:32:26 +01:00
Alessandro Mandelli
f76df0d252 Translated using Weblate (Italian)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/it/
2021-03-25 21:29:58 +01:00
solokot
60a172813f Translated using Weblate (Russian)
Currently translated at 100.0% (145 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-03-25 21:29:58 +01:00
Heimen Stoffels
01025e862a Translated using Weblate (Dutch)
Currently translated at 100.0% (145 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-03-25 21:29:58 +01:00
Alessandro Mandelli
7e9c7db813 Translated using Weblate (Italian)
Currently translated at 100.0% (145 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2021-03-25 21:29:57 +01:00
J. Lavoie
d8b121f503 Translated using Weblate (Italian)
Currently translated at 100.0% (145 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2021-03-25 21:29:56 +01:00
J. Lavoie
79a143ebaf Translated using Weblate (French)
Currently translated at 100.0% (145 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2021-03-25 21:29:56 +01:00
J. Lavoie
0b78f36784 Translated using Weblate (German)
Currently translated at 100.0% (145 of 145 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2021-03-25 21:29:55 +01:00
Sylvia van Os
a4c24c6436 Fix multiline note cutoff 2021-03-25 00:00:44 +01:00
Sylvia van Os
f59f9ddec8 Make spotBugs happy 2021-03-24 21:33:36 +01:00
Sylvia van Os
d21f2d12c9 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-03-24 21:17:22 +01:00
Sylvia van Os
30971b7e85 Fix unit tests 2021-03-24 21:17:02 +01:00
Sylvia van Os
7ab9e0f8b0 Merge pull request #169 from weblate/weblate-catima-catima
Translations update from Weblate
2021-03-24 20:52:34 +01:00
J. Lavoie
6e63543cd1 Translated using Weblate (Italian)
Currently translated at 66.6% (2 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/it/
2021-03-24 20:36:16 +01:00
J. Lavoie
d2de5db792 Translated using Weblate (Korean)
Currently translated at 73.7% (101 of 137 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ko/
2021-03-24 20:36:16 +01:00
Heimen Stoffels
a1bb6e3bed Translated using Weblate (Dutch)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2021-03-24 20:36:16 +01:00
J. Lavoie
e937fc60a7 Translated using Weblate (German)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2021-03-24 20:36:16 +01:00
J. Lavoie
1213ee99da Translated using Weblate (French)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2021-03-24 20:36:16 +01:00
solokot
7dadce600b Translated using Weblate (Russian)
Currently translated at 100.0% (137 of 137 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-03-24 20:36:16 +01:00
Heimen Stoffels
bb1eae6f79 Translated using Weblate (Dutch)
Currently translated at 100.0% (137 of 137 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-03-24 20:36:16 +01:00
Allan Nordhøy
cf871e9606 Translated using Weblate (Norwegian Bokmål)
Currently translated at 97.8% (134 of 137 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2021-03-24 20:36:16 +01:00
J. Lavoie
0b92a12694 Translated using Weblate (Italian)
Currently translated at 100.0% (137 of 137 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2021-03-24 20:36:16 +01:00
J. Lavoie
7636c3648c Translated using Weblate (French)
Currently translated at 100.0% (137 of 137 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2021-03-24 20:36:16 +01:00
J. Lavoie
f62352cf05 Translated using Weblate (Spanish (Argentina))
Currently translated at 14.5% (20 of 137 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es_AR/
2021-03-24 20:36:16 +01:00
J. Lavoie
8fbbfb137e Translated using Weblate (Greek)
Currently translated at 44.5% (61 of 137 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2021-03-24 20:36:16 +01:00
J. Lavoie
afb960257e Translated using Weblate (German)
Currently translated at 100.0% (137 of 137 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2021-03-24 20:36:16 +01:00
Sylvia van Os
b9e152e3c4 Add Fidme import support 2021-03-24 20:35:58 +01:00
Sylvia van Os
bd1d33867d Comply with Huawei's pedantic nonsense
https://twitter.com/SylvieLorxu/status/1374251557735256065
2021-03-23 21:01:56 +01:00
Sylvia van Os
eba1ed63a6 Release 1.11 2021-03-23 09:35:43 +01:00
Sylvia van Os
74fbdc7a5e Merge pull request #167 from weblate/weblate-catima-catima
Translations update from Weblate
2021-03-21 10:34:44 +01:00
solokot
ef61aaeac6 Translated using Weblate (Russian)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2021-03-21 06:30:31 +01:00
Sylvia van Os
b5ee7d7a2d Merge pull request #165 from weblate/weblate-catima-catima
Translations update from Weblate
2021-03-17 18:10:38 +01:00
solokot
f51ad0295a Translated using Weblate (Russian)
Currently translated at 100.0% (138 of 138 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-03-17 15:18:07 +01:00
Sylvia van Os
d0d3289efa Merge remote-tracking branch 'weblate/master' 2021-03-15 19:39:52 +01:00
Sylvia van Os
353d8a7ecd Fix typo 2021-03-15 19:37:45 +01:00
solokot
d221969b5e Translated using Weblate (Russian)
Currently translated at 100.0% (138 of 138 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-03-15 19:34:03 +01:00
Heimen Stoffels
97553e9253 Translated using Weblate (Dutch)
Currently translated at 100.0% (138 of 138 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-03-15 19:34:02 +01:00
Sylvia van Os
11b839dabe Merge pull request #164 from weblate/weblate-catima-catima
Translations update from Weblate
2021-03-15 19:33:57 +01:00
Sylvia van Os
bc5252b2ef Update incorrect Weblate escaping 2021-03-15 19:33:44 +01:00
solokot
5a6b7944b1 Translated using Weblate (Russian)
Currently translated at 100.0% (138 of 138 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-03-15 14:13:24 +01:00
solokot
a9794daca5 Translated using Weblate (Russian)
Currently translated at 100.0% (138 of 138 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-03-15 13:54:15 +01:00
solokot
aee59550e4 Translated using Weblate (Russian)
Currently translated at 100.0% (138 of 138 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2021-03-15 13:33:14 +01:00
Sylvia van Os
dcc5e5921c Fix unit tests 2021-03-14 21:34:51 +01:00
Sylvia van Os
12df426dea Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-03-14 17:58:36 +01:00
Sylvia van Os
9b4085e955 Show privacy policy dialog on first start 2021-03-14 17:58:20 +01:00
Sylvia van Os
a4f7d4e131 Merge pull request #163 from weblate/weblate-catima-catima
Translations update from Weblate
2021-03-13 17:09:01 +01:00
Sylvia van Os
70b313021c Fix incorrect escaping from Weblate 2021-03-13 17:05:20 +01:00
Schi Ri
d1bdab5c66 Translated using Weblate (German)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/de/
2021-03-12 01:02:56 +01:00
Schi Ri
237405279f Translated using Weblate (German)
Currently translated at 100.0% (134 of 134 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2021-03-12 01:02:55 +01:00
Sylvia van Os
810fa5f621 Release 1.10 2021-03-07 14:38:38 +01:00
Sylvia van Os
64b709266b Merge pull request #162 from weblate/weblate-catima-catima
Translations update from Weblate
2021-03-07 14:22:02 +01:00
Heimen Stoffels
f9a66ba3a0 Translated using Weblate (Dutch)
Currently translated at 100.0% (134 of 134 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-03-07 14:21:20 +01:00
Allan Nordhøy
bfd36bae9f Translated using Weblate (Norwegian Bokmål)
Currently translated at 98.5% (132 of 134 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2021-03-07 14:21:20 +01:00
BMN
15116ab673 Translated using Weblate (Italian)
Currently translated at 100.0% (134 of 134 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2021-03-07 14:21:20 +01:00
Sylvia van Os
d49d019f63 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-03-06 00:13:35 +01:00
Sylvia van Os
ba9d1f3891 Make Loyalty Card Keychain app name translatable
Because, well, the app itself uses different names in different
countries
2021-03-05 23:56:38 +01:00
Sylvia van Os
01596d5637 Merge pull request #160 from weblate/weblate-catima-catima
Translations update from Weblate
2021-03-05 23:50:21 +01:00
Heimen Stoffels
9bd71d5694 Translated using Weblate (Dutch)
Currently translated at 100.0% (129 of 129 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-03-05 23:47:01 +01:00
Allan Nordhøy
6d1e7ee3bb Translated using Weblate (Norwegian Bokmål)
Currently translated at 98.4% (127 of 129 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2021-03-05 23:47:01 +01:00
Sylvia van Os
0ce71039f1 Split into screen on and lockscreen bypass 2021-03-05 23:44:27 +01:00
Sylvia van Os
a03b89bc93 Merge branch 'master' into feature/keep-screen-on 2021-03-05 23:36:43 +01:00
Sylvia van Os
156b720102 Merge pull request #159 from TheLastProject/feature/voucherVaultImport
Create Voucher Vault import code and test
2021-03-05 09:50:23 +01:00
Sylvia van Os
c13b5dda18 Make Spotbugs happy 2021-03-04 23:45:03 +01:00
Sylvia van Os
ffa39000f7 Add Voucher Vault import to UI 2021-03-04 23:37:53 +01:00
Sylvia van Os
40de4a8dc4 Fix date parsing 2021-03-02 21:10:14 +01:00
Sylvia van Os
db22703ec0 Create Voucher Vault import code and test 2021-03-01 23:02:03 +01:00
Sylvia van Os
8b59ef152a Merge pull request #158 from weblate/weblate-catima-catima
Translations update from Weblate
2021-02-25 19:55:27 +01:00
Sylvia van Os
01bd91ff66 Fix Weblate incorrectly escaping XML 2021-02-25 19:54:46 +01:00
Simone Dotto
b558d2178a Translated using Weblate (Italian)
Currently translated at 100.0% (128 of 128 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2021-02-25 14:50:29 +01:00
Sylvia van Os
efc8e6ae33 Fix parsing balance for countries using space as separator 2021-02-24 19:17:16 +01:00
Sylvia van Os
aaf481a82c Merge pull request #156 from weblate/weblate-catima-catima
Translations update from Weblate
2021-02-24 12:28:32 +01:00
Sylvia van Os
d03d96b194 Fix Weblate incorrect escaping 2021-02-24 12:28:04 +01:00
inesre
1c92787254 Translated using Weblate (Spanish)
Currently translated at 94.5% (121 of 128 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2021-02-24 00:50:28 +01:00
Sylvia van Os
bc808d3045 Really fix 2021-02-23 20:55:01 +01:00
Sylvia van Os
b265fadec3 Improve currency parsing logic 2021-02-23 18:49:45 +01:00
Sylvia van Os
d2f5cd05b5 Release 1.9 2021-02-22 23:01:24 +01:00
Sylvia van Os
2dba2c63a8 Merge pull request #153 from weblate/weblate-catima-catima
Translations update from Weblate
2021-02-22 22:00:02 +01:00
Sylvia van Os
3fffcdbd67 Fix incorrect escaping from Weblate 2021-02-22 21:56:20 +01:00
Sylvia van Os
6c53b8e9ca Fix incorrect escaping from Weblate 2021-02-22 21:54:13 +01:00
Heimen Stoffels
8ac2fe575c Translated using Weblate (Dutch)
Currently translated at 100.0% (128 of 128 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-02-22 21:50:28 +01:00
Allan Nordhøy
b4a992abf8 Translated using Weblate (Norwegian Bokmål)
Currently translated at 98.4% (126 of 128 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2021-02-22 21:50:27 +01:00
Sylvia van Os
45c71f01c2 Simplify balance parsing logic 2021-02-22 20:21:24 +01:00
Sylvia van Os
9f914d4943 Reorganize barcode tab of edit view 2021-02-21 18:36:34 +01:00
Sylvia van Os
3f25f99236 Merge pull request #151 from TheLastProject/feature/balance
WIP: Initial balance support
2021-02-21 18:15:06 +01:00
Sylvia van Os
d54f574558 Update screenshots 2021-02-21 18:10:56 +01:00
Sylvia van Os
0af1935d20 Switch tests to USD 2021-02-21 18:00:06 +01:00
Sylvia van Os
9a37d917f7 Fix test URL 2021-02-20 22:59:51 +01:00
Sylvia van Os
d4f62435ae Add tests for balance 2021-02-20 22:10:23 +01:00
Sylvia van Os
0baf1ba348 Fix tests 2021-02-20 18:15:58 +01:00
Sylvia van Os
4a568d02d3 Various fixes and consistency improvements 2021-02-16 22:22:51 +01:00
Sylvia van Os
1dc30ebaad Reformat balance after focus loss 2021-02-15 22:30:45 +01:00
Sylvia van Os
4fbedbc30c Fix parsing big numbers 2021-02-15 22:02:47 +01:00
Sylvia van Os
1675ebf1dc Set hasChanged when balance was edited 2021-02-15 21:58:22 +01:00
Sylvia van Os
2d6dfdcfdf Initial balance support 2021-02-14 23:32:07 +01:00
Miha Frangež
bacb5b97ec Added ability to import a barcode from a local image 2021-02-14 19:09:33 +01:00
Miha Frange?
940be02851 Added option to keep the screen on 2021-02-14 17:29:51 +01:00
Sylvia van Os
34f8c830fd Release Catima 1.8.1 2021-02-12 18:25:03 +01:00
Sylvia van Os
ee7f5c0498 Fix crash on Android before 7 2021-02-12 12:54:50 +01:00
Sylvia van Os
9a5c3bc661 Merge pull request #145 from weblate/weblate-catima-catima
Translations update from Weblate
2021-02-09 18:11:05 +01:00
Michalis
db9ec7daea Translated using Weblate (Greek)
Currently translated at 47.5% (58 of 122 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2021-02-09 17:50:33 +01:00
Sylvia van Os
16b683fe9d Merge pull request #144 from weblate/weblate-catima-catima
Translations update from Weblate
2021-02-06 01:44:08 +01:00
Michalis
0e10c644d8 Translated using Weblate (Greek)
Currently translated at 47.5% (58 of 122 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2021-02-06 01:42:03 +01:00
Sylvia van Os
fd283a5e06 Fix wrong name in privacy policy 2021-02-03 20:35:06 +01:00
Sylvia van Os
e406320f17 Add Huawei AppGallery 2021-02-03 20:29:36 +01:00
Sylvia van Os
40d36ec8fb Release 1.8 2021-01-28 19:45:48 +01:00
Sylvia van Os
99a3849942 Fix missing lookup table update for groups update 2021-01-28 19:31:46 +01:00
Sylvia van Os
26a6a786dc Fix button colours 2021-01-28 17:59:31 +01:00
Sylvia van Os
55a5884852 Fix loyalty card view title colour in landscape mode 2021-01-27 22:21:27 +01:00
Sylvia van Os
c20ce1e555 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-01-27 21:43:59 +01:00
Sylvia van Os
a90899d41e Fix crash on no barcode 2021-01-27 21:42:57 +01:00
Sylvia van Os
413d479ea0 Merge pull request #141 from weblate/weblate-catima-catima
Translations update from Weblate
2021-01-27 21:36:19 +01:00
Heimen Stoffels
8ff452cfd2 Translated using Weblate (Dutch)
Currently translated at 100.0% (122 of 122 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-01-27 21:35:56 +01:00
Allan Nordhøy
54c279ec89 Translated using Weblate (Norwegian Bokmål)
Currently translated at 98.3% (120 of 122 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2021-01-27 21:35:56 +01:00
Sylvia van Os
000d7722da Fix tests 2021-01-27 21:35:37 +01:00
Sylvia van Os
0bb0df21e4 Make header small in landscape mode 2021-01-27 21:25:34 +01:00
Sylvia van Os
0dcfc11c53 Fix unit tests 2021-01-26 22:50:46 +01:00
Sylvia van Os
58f8a6a929 Fix bottomsheet misplacing after fullscreen change 2021-01-26 22:39:25 +01:00
Sylvia van Os
6a80c633a8 Update CHANGELOG 2021-01-25 23:55:57 +01:00
Sylvia van Os
031bfd94e0 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-01-25 23:54:55 +01:00
Sylvia van Os
45443abaf9 UI hints for barcode to top and scaling 2021-01-25 23:53:33 +01:00
Sylvia van Os
b81da59bcc Merge pull request #139 from weblate/weblate-catima-catima
Translations update from Weblate
2021-01-18 20:36:59 +01:00
Sylvia van Os
86f3530d95 Fix incorrect XML escaping 2021-01-18 20:36:30 +01:00
Sylvia van Os
863a378bf0 Fix incorrect XML escaping 2021-01-18 20:35:39 +01:00
Heimen Stoffels
a9d81b8321 Translated using Weblate (Dutch)
Currently translated at 100.0% (120 of 120 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-01-18 20:32:14 +01:00
Allan Nordhøy
46865f349b Translated using Weblate (Norwegian Bokmål)
Currently translated at 98.3% (118 of 120 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2021-01-18 20:32:13 +01:00
Sylvia van Os
1d1109d665 Fix crash in edit view 2021-01-18 19:45:45 +01:00
Sylvia van Os
c11e995684 Fix blurry barcode in edit view 2021-01-18 19:00:27 +01:00
Sylvia van Os
184af4d272 Fix wrong string on expired 2021-01-17 23:38:07 +01:00
Sylvia van Os
d7356704ea Make spotBugs happy 2021-01-17 23:31:05 +01:00
Sylvia van Os
66573fd2da Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-01-17 22:56:14 +01:00
Sylvia van Os
36ff2333b7 Fix all unit tests 2021-01-17 22:54:24 +01:00
Sylvia van Os
9325f33da6 Merge pull request #138 from weblate/weblate-catima-catima
Translations update from Weblate
2021-01-17 18:35:28 +01:00
Sylvia van Os
9e40d9b6b3 Fix incorrect escaping 2021-01-17 18:34:55 +01:00
Sylvia van Os
4679051df1 Fix incorrect escaping 2021-01-17 18:34:13 +01:00
Heimen Stoffels
27cbad1b44 Translated using Weblate (Dutch)
Currently translated at 100.0% (119 of 119 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-01-17 18:24:50 +01:00
Allan Nordhøy
ba24b49009 Translated using Weblate (Norwegian Bokmål)
Currently translated at 98.3% (117 of 119 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2021-01-17 18:24:50 +01:00
Sylvia van Os
c408beb68c Progress on fixing tests 2021-01-17 18:24:21 +01:00
Sylvia van Os
5b7590e4e1 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-01-16 21:18:19 +01:00
Sylvia van Os
574832d09b Merge pull request #137 from weblate/weblate-catima-catima
Translations update from Weblate
2021-01-16 20:20:15 +01:00
Sylvia van Os
8029be7b2b Update CHANGELOG 2021-01-16 20:19:15 +01:00
Heimen Stoffels
390a2e1b5a Translated using Weblate (Dutch)
Currently translated at 100.0% (115 of 115 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2021-01-16 20:16:51 +01:00
Allan Nordhøy
803555f94e Translated using Weblate (Norwegian Bokmål)
Currently translated at 98.2% (113 of 115 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2021-01-16 20:16:51 +01:00
Sylvia van Os
6488dc0fe3 Implement expiry 2021-01-16 20:16:20 +01:00
Sylvia van Os
ddc385c39d Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2021-01-16 15:03:15 +01:00
Sylvia van Os
6382c54d1a Separate edit UI into tabs 2021-01-16 15:02:14 +01:00
Sylvia van Os
af515d307a Merge pull request #136 from weblate/weblate-catima-catima
Translations update from Weblate
2021-01-13 09:47:44 +01:00
Yurical
e1edeb364e Translated using Weblate (Korean)
Currently translated at 87.5% (98 of 112 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ko/
2021-01-13 09:32:08 +01:00
Sylvia van Os
18b95e96eb Release 1.6.2 2021-01-04 20:42:06 +01:00
Sylvia van Os
5960857568 Fix bottom sheet or edit button drawing over card ID 2021-01-04 20:20:44 +01:00
Sylvia van Os
6426a99be4 Merge pull request #133 from TacoTheDank/master
Library updates and AndroidX Preferences
2021-01-04 20:05:50 +01:00
Sylvia van Os
1f76809300 Memory leak and spotBugs fixes 2021-01-04 19:58:08 +01:00
Sylvia van Os
5346726ae1 Merge pull request #135 from weblate/weblate-catima-catima
Translations update from Weblate
2021-01-03 23:45:39 +01:00
Sylvia van Os
3828454a26 Fix XML 2021-01-03 23:35:56 +01:00
inesre
7995c2ba1a Translated using Weblate (Spanish)
Currently translated at 100.0% (113 of 113 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2021-01-03 23:29:14 +01:00
Sylvia van Os
9ee4184122 Archive test results 2021-01-03 21:12:11 +01:00
TacoTheDank
319ba195e4 Rudimentary migration of findbugs to spotbugs 2020-12-26 20:34:44 -05:00
TacoTheDank
a21e9f142c Resolve app name in build.gradle 2020-12-26 19:05:41 -05:00
TacoTheDank
22fbbd3341 Migrate preferences to AndroidX 2020-12-26 19:05:18 -05:00
TacoTheDank
191e22db65 Update a bunch of libraries 2020-12-26 19:05:01 -05:00
TacoTheDank
ceba99d11f Clean up build gradles 2020-12-26 19:04:48 -05:00
TacoTheDank
dd1840ce5c Update gradle wrapper 2020-12-23 19:47:27 -05:00
TacoTheDank
b1608d6a9f Update .gitignore 2020-12-23 19:47:09 -05:00
Sylvia van Os
aee03170dc Fix build 2020-12-22 18:40:02 +01:00
Sylvia van Os
419e12c0eb Merge pull request #132 from weblate/weblate-catima-catima
Translations update from Weblate
2020-12-22 16:37:18 +01:00
Yurical
6c2ce61ddc Translated using Weblate (Korean)
Currently translated at 66.3% (75 of 113 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ko/
2020-12-22 16:29:11 +01:00
K. Herbert
fc342b4197 Translated using Weblate (Italian)
Currently translated at 100.0% (113 of 113 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2020-12-22 16:29:11 +01:00
K. Herbert
a6536307ad Translated using Weblate (French)
Currently translated at 100.0% (113 of 113 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-12-22 16:29:11 +01:00
K. Herbert
8a517e8161 Translated using Weblate (German)
Currently translated at 100.0% (113 of 113 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2020-12-22 16:29:10 +01:00
Sylvia van Os
34f314dad8 Merge pull request #131 from weblate/weblate-catima-catima
Translations update from Weblate
2020-12-20 09:50:40 +01:00
Yurical
2e49e36e0c Added translation using Weblate (Korean) 2020-12-20 08:55:30 +01:00
Sylvia van Os
a7afdae83c Add big translation panel
Translations are very important, we want to make it more obvious this exists
2020-12-19 23:19:44 +01:00
Sylvia van Os
c705136362 No longer use this image 2020-12-18 22:12:12 +01:00
Sylvia van Os
afa76d8f54 Fix regression in manual barcode selector 2020-12-16 18:56:01 +01:00
Sylvia van Os
cf20d03fb3 Release 1.6.0 2020-12-15 23:27:47 +01:00
Sylvia van Os
bb866819db Cleanups 2020-12-15 23:11:18 +01:00
Sylvia van Os
c9dd994120 Fix findBugs 2020-12-15 22:47:04 +01:00
Sylvia van Os
eabd988540 Fix tests 2020-12-15 22:29:01 +01:00
Sylvia van Os
af91097559 Fix back button 2020-12-15 12:06:14 +01:00
Sylvia van Os
16793d1c0b Clean up imports 2020-12-14 22:36:17 +01:00
Sylvia van Os
6f4673c9fb Theming improvements 2020-12-14 21:34:28 +01:00
Sylvia van Os
bb99321209 Camera view with manual scan button 2020-12-14 21:12:59 +01:00
Sylvia van Os
baa42c8c69 Forgot a file 2020-12-11 12:38:32 +01:00
Sylvia van Os
6c011dd9ac Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker 2020-12-10 23:49:46 +01:00
Sylvia van Os
9bf245a392 Convert everything to SVG 2020-12-10 23:47:49 +01:00
Sylvia van Os
412afcda1e Merge pull request #126 from weblate/weblate-catima-catima
Translations update from Weblate
2020-12-07 08:20:20 +01:00
Samantaz Fox
c8bc0864b2 Translated using Weblate (French)
Currently translated at 99.1% (117 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-12-07 02:29:04 +01:00
Sylvia van Os
4d59e6c48d Focus textfield on group creation/edit 2020-12-06 19:13:21 +01:00
Sylvia van Os
70cf42f144 Update translations 2020-12-05 12:24:30 +01:00
Sylvia van Os
b3fc66fa58 Merge pull request #125 from weblate/weblate-catima-catima
Translations update from Weblate
2020-12-05 12:14:06 +01:00
Allan Nordhøy
aa7315fafe Translated using Weblate (Norwegian Bokmål)
Currently translated at 98.3% (116 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2020-12-05 02:29:03 +01:00
Sylvia van Os
5325f1955e Release 1.5.1 2020-12-03 20:39:46 +01:00
Sylvia van Os
ee30180137 Release 1.5.0 2020-12-03 20:02:20 +01:00
Sylvia van Os
6bddeb2f75 Update screenshots 2020-12-03 19:42:21 +01:00
Sylvia van Os
3c69990b0d Make findBugs happy and improve bottom sheet logic 2020-12-03 19:33:20 +01:00
Sylvia van Os
43d5478ae3 Increase arrow size 2020-12-03 18:53:01 +01:00
Sylvia van Os
30a54ae22a Note size is no longer configurable 2020-12-03 18:18:32 +01:00
Sylvia van Os
9ff6b87092 More i18n fixes 2020-12-03 18:04:49 +01:00
Sylvia van Os
9763d9fb73 Merge pull request #124 from weblate/weblate-catima-catima
Translations update from Weblate
2020-12-03 18:00:59 +01:00
Heimen Stoffels
0b92686dad Translated using Weblate (Dutch)
Currently translated at 100.0% (118 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2020-12-03 17:58:34 +01:00
Sylvia van Os
cb1c834057 Fix strings 2020-12-03 17:57:53 +01:00
Sylvia van Os
98a0487d0c Merge pull request #123 from weblate/weblate-catima-catima
Translations update from Weblate
2020-12-03 17:55:08 +01:00
J. Lavoie
2df165061d Translated using Weblate (French)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2020-12-03 17:53:52 +01:00
Heimen Stoffels
fdd7ca03cf Translated using Weblate (Dutch)
Currently translated at 100.0% (118 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2020-12-03 17:53:52 +01:00
J. Lavoie
29e996a5fe Translated using Weblate (German)
Currently translated at 100.0% (118 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2020-12-03 17:53:52 +01:00
Sylvia van Os
3c1c8baaa0 Make bottom sheet button tapable and add group info 2020-12-03 17:53:23 +01:00
Sylvia van Os
6c186c2abb Merge pull request #122 from weblate/weblate-catima-catima
Translations update from Weblate
2020-12-03 17:33:23 +01:00
J. Lavoie
9deb0b94af Translated using Weblate (French)
Currently translated at 97.4% (115 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-12-03 17:32:16 +01:00
Samantaz Fox
97d54e87f2 Translated using Weblate (French)
Currently translated at 97.4% (115 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-12-03 17:32:16 +01:00
Sylvia van Os
0e329b3c31 Merge pull request #121 from weblate/weblate-catima-catima
Translations update from Weblate
2020-12-03 17:31:59 +01:00
Samantaz Fox
bd337d6da8 Translated using Weblate (French)
Currently translated at 98.3% (116 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-12-03 17:24:53 +01:00
Sylvia van Os
dc7f199e0c Fix crash 2020-12-02 21:27:58 +01:00
Sylvia van Os
b54e14def2 Create bottom sheet in loyalty card view to prevent FAB rendering over note 2020-12-02 21:17:00 +01:00
Sylvia van Os
f724e4c164 Always user white text on red buttons 2020-12-02 19:43:54 +01:00
Sylvia van Os
c389a02d69 Fix tests 2020-12-02 18:36:23 +01:00
Sylvia van Os
b7db58e7d0 Make Fastlane happy 2020-12-01 22:59:06 +01:00
Sylvia van Os
eefd26fa06 Downgrade dependencies again
It just doesn't build otherwise
2020-12-01 22:54:07 +01:00
Sylvia van Os
8d4a122cb5 Fix many lint checks 2020-12-01 21:33:36 +01:00
Sylvia van Os
13ea10d0b0 Lint fixes 2020-12-01 19:54:28 +01:00
Sylvia van Os
4366eab380 Actually show toast 2020-12-01 19:47:43 +01:00
Sylvia van Os
0c61d36cb3 Increase to minSdk 19
We were already kinda not compatible with pre-19 anymore
2020-12-01 19:46:52 +01:00
Sylvia van Os
6bc5e77bbd Release 1.4.1 2020-12-01 19:35:06 +01:00
Sylvia van Os
d8ddee0667 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into master 2020-12-01 19:01:24 +01:00
Sylvia van Os
5e8312e816 Merge pull request #119 from weblate/weblate-catima-catima
Translations update from Weblate
2020-12-01 18:59:06 +01:00
Heimen Stoffels
8e10022158 Translated using Weblate (Dutch)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2020-12-01 18:58:00 +01:00
Sylvia van Os
b209b670a5 Small UI cleanups 2020-12-01 18:49:59 +01:00
Sylvia van Os
ecdec0f773 Merge pull request #118 from weblate/weblate-catima-catima
Translations update from Weblate
2020-12-01 12:03:13 +01:00
Heimen Stoffels
5c90107a7b Translated using Weblate (Dutch)
Currently translated at 66.6% (2 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nl/
2020-12-01 12:01:32 +01:00
Samantaz Fox
6a8a9878d7 Translated using Weblate (French)
Currently translated at 66.6% (2 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2020-12-01 12:01:32 +01:00
Artem
3400d10cc6 Translated using Weblate (Russian)
Currently translated at 84.6% (105 of 124 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2020-12-01 12:01:32 +01:00
Sylvia van Os
05e171170b Translated using Weblate (Russian)
Currently translated at 84.6% (105 of 124 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2020-12-01 12:01:32 +01:00
Sylvia van Os
cb967a8d8d Translated using Weblate (Dutch)
Currently translated at 100.0% (124 of 124 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2020-12-01 12:01:32 +01:00
Sylvia van Os
5835da2931 Translated using Weblate (French)
Currently translated at 100.0% (124 of 124 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-12-01 12:01:32 +01:00
Sylvia van Os
2e9339b8aa Fix unnecessary file 2020-12-01 12:00:52 +01:00
Sylvia van Os
bbba4b2e11 Correctly use plural string 2020-11-30 21:44:38 +01:00
Sylvia van Os
d9659ecc9d Merge remote-tracking branch 'weblate/master' into master 2020-11-30 21:23:04 +01:00
Heimen Stoffels
c558278962 Translated using Weblate (Dutch)
Currently translated at 100.0% (124 of 124 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2020-11-30 21:19:44 +01:00
Sylvia van Os
c137cb9428 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into master 2020-11-30 21:19:07 +01:00
Sylvia van Os
1d2f54c69b Translation fixes 2020-11-30 19:53:32 +01:00
Sylvia van Os
d4a377fe18 Merge pull request #116 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-29 00:30:43 +01:00
Allan Nordhøy
9844113568 Translated using Weblate (Norwegian Bokmål)
Currently translated at 95.9% (119 of 124 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2020-11-29 00:29:00 +01:00
Sylvia van Os
c49f26db95 Release 1.4.0 2020-11-28 16:46:01 +01:00
Sylvia van Os
9744dfe6ff Make lint happy 2020-11-28 15:45:09 +01:00
Sylvia van Os
55dfd1fe09 Fix build 2020-11-28 15:24:07 +01:00
Sylvia van Os
0a3c81154c Fix colouring of manage group buttons 2020-11-28 14:44:09 +01:00
Sylvia van Os
70a9689bb2 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into master 2020-11-28 13:52:01 +01:00
Sylvia van Os
54f6d8d26f Fix scan/manual icon colour on light theme 2020-11-28 13:51:13 +01:00
Sylvia van Os
c0622ddaec Merge pull request #115 from TheLastProject/create-pull-request/patch-1606514188
Compressed Images
2020-11-27 22:57:16 +01:00
TheLastProject
12578eab3e Compressed Images 2020-11-27 21:56:28 +00:00
Sylvia van Os
5dd57577af Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into master 2020-11-27 22:55:20 +01:00
Sylvia van Os
d883c84b4e Fix migration guide 2020-11-27 22:54:40 +01:00
Sylvia van Os
0ebe1b387d Merge pull request #114 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-27 22:35:21 +01:00
Allan Nordhøy
942aff2af5 Translated using Weblate (Norwegian Bokmål)
Currently translated at 95.9% (117 of 122 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2020-11-27 22:34:03 +01:00
Sylvia van Os
c1606fef62 Manual group ordering 2020-11-27 22:33:59 +01:00
Sylvia van Os
cc895353a9 Update CHANGELOG 2020-11-27 14:47:27 +01:00
Sylvia van Os
73bac0ae8d Make findBugs happy 2020-11-27 13:19:35 +01:00
Sylvia van Os
158e424a47 Add dialog giving the user scan/manually enter options (#111) 2020-11-27 12:58:55 +01:00
Sylvia van Os
133fa13d3b Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into master 2020-11-27 09:42:49 +01:00
Sylvia van Os
44ff4d0cc6 Fix gitignore 2020-11-27 09:42:23 +01:00
Sylvia van Os
a7b280c08b Merge pull request #112 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-27 02:38:25 +01:00
J. Lavoie
5fe0429c98 Translated using Weblate (French)
Currently translated at 100.0% (120 of 120 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-11-27 02:28:59 +01:00
J. Lavoie
bf391022fd Translated using Weblate (German)
Currently translated at 100.0% (120 of 120 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2020-11-27 02:28:59 +01:00
Sylvia van Os
a431bf8c30 Merge pull request #109 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-23 02:37:45 +01:00
Sylvia van Os
a242944ee6 Deleted translation using Weblate (French (Belgium)) 2020-11-23 02:17:54 +01:00
Samantaz Fox
84a7e84486 Translated using Weblate (French)
Currently translated at 100.0% (120 of 120 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-11-23 02:17:54 +01:00
Samantaz Fox
d4988f1d4c Added translation using Weblate (French (Belgium)) 2020-11-23 02:17:54 +01:00
Sylvia van Os
04e52f0a8a Move About dialog into own activity 2020-11-23 02:17:18 +01:00
Sylvia van Os
370db97af1 Release 1.3.0 2020-11-22 19:43:53 +01:00
Sylvia van Os
84d7f4fbba Fix unit tests 2020-11-22 18:26:05 +01:00
Sylvia van Os
afcbca251c Merge pull request #108 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-22 17:22:59 +01:00
Samantaz Fox
aa9df04c9c Translated using Weblate (French)
Currently translated at 99.1% (119 of 120 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-11-22 17:19:32 +01:00
Allan Nordhøy
a24452a962 Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.1% (119 of 120 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2020-11-22 17:19:32 +01:00
Samantaz Fox
e74176abb0 Translated using Weblate (French)
Currently translated at 99.1% (119 of 120 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-11-22 17:19:32 +01:00
Sylvia van Os
6f4cec4cf4 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into master 2020-11-22 17:15:41 +01:00
Sylvia van Os
febd0cf463 Really fix change detection 2020-11-22 17:13:02 +01:00
Sylvia van Os
a5f91486e9 Merge pull request #107 from SamantazFox/patch-1
Propose more explicit settings names
2020-11-22 17:12:44 +01:00
Samantaz Fox
d8300640af Propose more explicit settings names 2020-11-22 16:46:15 +01:00
Sylvia van Os
afef158169 Merge pull request #106 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-21 23:14:40 +01:00
Samantaz Fox
a728d1d84d Translated using Weblate (French)
Currently translated at 79.6% (94 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-11-21 23:12:42 +01:00
Sylvia van Os
9f88b6348b Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into master 2020-11-21 23:10:56 +01:00
Sylvia van Os
6929ba1c0d Ask for confirmation before leaving edit view after changes 2020-11-21 23:08:19 +01:00
Sylvia van Os
49b296b983 Merge pull request #105 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-21 16:31:27 +01:00
Allan Nordhøy
f21a75b19a Translated using Weblate (Dutch)
Currently translated at 73.7% (87 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2020-11-21 16:28:58 +01:00
Allan Nordhøy
8946b0a9e1 Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.1% (117 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2020-11-21 16:28:57 +01:00
Allan Nordhøy
eae470f13c Translated using Weblate (Italian)
Currently translated at 73.7% (87 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2020-11-21 16:28:57 +01:00
Allan Nordhøy
5ce046b3f4 Translated using Weblate (French)
Currently translated at 74.5% (88 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-11-21 16:28:57 +01:00
Allan Nordhøy
a51debb2e1 Translated using Weblate (Spanish)
Currently translated at 62.7% (74 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2020-11-21 16:28:57 +01:00
Allan Nordhøy
7312a4bd1a Translated using Weblate (German)
Currently translated at 73.7% (87 of 118 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2020-11-21 16:28:57 +01:00
Sylvia van Os
3c1a54e456 Remove no longer relevant unit tests 2020-11-20 15:09:16 +01:00
Sylvia van Os
6d4babf944 Always show all import/export options 2020-11-20 14:56:11 +01:00
Sylvia van Os
7ecdf52f1a Fix typo 2020-11-20 00:29:49 +01:00
Sylvia van Os
c6f28272d9 Release 1.2.2 2020-11-19 21:16:36 +01:00
Sylvia van Os
ba8c20f415 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into master 2020-11-19 16:48:55 +01:00
Sylvia van Os
b41dcc7ba1 Add IzzyOnDroid button 2020-11-19 16:45:33 +01:00
Sylvia van Os
6a14b5706b Merge pull request #102 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-17 18:36:27 +01:00
opsik
5bde6796b9 Translated using Weblate (Russian)
Currently translated at 66.6% (2 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2020-11-17 18:32:39 +01:00
Sylvia van Os
3c797b5ec9 Remember active group tab 2020-11-17 18:30:47 +01:00
Sylvia van Os
dec85d8fa9 Release 1.2.1 2020-11-17 14:19:21 +01:00
Sylvia van Os
979d88d2e6 Release 1.2.0 2020-11-17 13:52:57 +01:00
Sylvia van Os
67b961c672 Fix crash on rendering card with no header colour 2020-11-17 13:51:44 +01:00
Sylvia van Os
ed62ad321b Add support for swiping between groups in main view 2020-11-17 00:00:09 +01:00
Sylvia van Os
251bdcd500 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into master 2020-11-16 20:01:35 +01:00
Sylvia van Os
16f9b8f3a5 Update deprecated requirements 2020-11-16 19:59:09 +01:00
Sylvia van Os
6be52f9b31 Merge pull request #98 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-16 17:31:37 +01:00
Airat
d8b901c3a0 Translated using Weblate (Russian)
Currently translated at 89.6% (104 of 116 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2020-11-16 17:28:49 +01:00
Franciszek Stefan
ed52d2d666 Translated using Weblate (Polish)
Currently translated at 100.0% (116 of 116 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/pl/
2020-11-16 17:28:48 +01:00
Sylvia van Os
ff6b62c3f0 Merge pull request #97 from x-jokay/feature/update-name
Update csv filename
2020-11-13 20:21:22 +01:00
D. Domig
da8b15a985 Update csv filename 2020-11-13 20:20:05 +01:00
Sylvia van Os
eccf994379 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into master 2020-11-13 19:08:33 +01:00
Sylvia van Os
66622817cc Make more findable 2020-11-13 19:08:11 +01:00
Sylvia van Os
3fcfe8afe1 Merge pull request #96 from x-jokay/feature/fix-markdown-issues
Fix some markdownlint issues
2020-11-13 16:00:56 +01:00
D. Domig
2c5dae37fd Fix some markdownlint issues 2020-11-13 15:54:53 +01:00
Sylvia van Os
295e63981d Merge pull request #95 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-12 07:06:51 +01:00
Allan Nordhøy
9ac0ee53b5 Translated using Weblate (Norwegian Bokmål)
Currently translated at 98.2% (114 of 116 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2020-11-12 00:28:38 +01:00
Sylvia van Os
7e1dbf905a Release 1.1.0 2020-11-11 18:35:01 +01:00
Sylvia van Os
63de6f50ed Merge pull request #92 from TheLastProject/feature/iconFriendlyEditUI
Icon-friendly edit UI
2020-11-10 22:50:03 +01:00
Sylvia van Os
6b24f56c79 Fix unit tests 2020-11-10 22:25:16 +01:00
Sylvia van Os
e601d1a659 Editable card ID and barcode type 2020-11-10 22:05:53 +01:00
Sylvia van Os
1641631bdc Get rid of headingStoreTextColorValue 2020-11-10 19:11:57 +01:00
Sylvia van Os
91c2efbdfc Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into feature/iconFriendlyEditUI 2020-11-10 18:28:56 +01:00
Sylvia van Os
462f77e26f Merge pull request #94 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-10 18:28:17 +01:00
Sylvia van Os
0aa1f8b7dd Translated using Weblate (Dutch)
Currently translated at 100.0% (115 of 115 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2020-11-10 18:27:07 +01:00
Allan Nordhøy
0f9509c0e5 Translated using Weblate (Norwegian Bokmål)
Currently translated at 98.2% (113 of 115 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2020-11-10 18:27:07 +01:00
Sylvia van Os
95d497c7bd Fix build 2020-11-10 18:26:54 +01:00
Sylvia van Os
d760fbd3cb Merge pull request #93 from comradekingu/patch-3
Strings reworked
2020-11-10 18:00:07 +01:00
Sylvia van Os
ad82492f63 Update app/src/main/res/values/strings.xml
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
2020-11-10 17:59:46 +01:00
Allan Nordhøy
e84a23c58e Spelling: Try changing your search 2020-11-10 12:14:17 +00:00
Allan Nordhøy
67cbb34425 Spelling: Didn't find anything 2020-11-10 12:09:16 +00:00
Allan Nordhøy
0c4fed5c50 Spelling: Grouped 2020-11-10 12:03:28 +00:00
Allan Nordhøy
e1a72a948d Requested changes made 2020-11-10 12:01:58 +00:00
Allan Nordhøy
50d9205aef Spelling: Font size for card titles 2020-11-10 09:18:25 +00:00
Allan Nordhøy
9093c88e23 Strings reworked 2020-11-10 09:17:16 +00:00
Sylvia van Os
3c793fe54f Fix logic and unit tests 2020-11-09 22:31:35 +01:00
Sylvia van Os
a8960eb506 Initial edit UI changes 2020-11-09 21:34:29 +01:00
Sylvia van Os
7913e95abf Merge pull request #91 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-09 18:12:21 +01:00
Sylvia van Os
fac7b76798 Translated using Weblate (Dutch)
Currently translated at 100.0% (115 of 115 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2020-11-09 18:10:03 +01:00
mondstern
aa9c4b81b4 Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.1% (114 of 115 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2020-11-09 18:10:03 +01:00
J. Lavoie
5b3fc845f9 Translated using Weblate (Italian)
Currently translated at 100.0% (115 of 115 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2020-11-09 18:10:03 +01:00
J. Lavoie
c8c7498dc9 Translated using Weblate (French)
Currently translated at 100.0% (115 of 115 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-11-09 18:10:03 +01:00
J. Lavoie
712763969c Translated using Weblate (German)
Currently translated at 100.0% (115 of 115 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2020-11-09 18:10:03 +01:00
Vancha March
d3f1e0d80b Translated using Weblate (Dutch)
Currently translated at 100.0% (115 of 115 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2020-11-09 18:10:03 +01:00
mondstern
e8ab92d8c1 Translated using Weblate (Dutch)
Currently translated at 100.0% (115 of 115 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nl/
2020-11-09 18:10:03 +01:00
Sylvia van Os
e0e4ba0012 Fix build 2020-11-08 20:09:26 +01:00
Sylvia van Os
cb4cede2da Rename nb_NO for consistency 2020-11-08 19:23:44 +01:00
Sylvia van Os
7bfdb3a8c3 Merge pull request #87 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-08 05:46:41 +01:00
Allan Nordhøy
c4210da1a5 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/nb_NO/
2020-11-08 01:26:54 +01:00
Allan Nordhøy
e082f45333 Translated using Weblate (Norwegian Bokmål)
Currently translated at 93.9% (108 of 115 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2020-11-08 01:26:54 +01:00
Sylvia van Os
d10c8668b6 Fix crash detected by Google Play Console pre-launch report 2020-11-07 11:34:35 +01:00
Sylvia van Os
a766a25f6a Fix unit test 2020-11-06 21:13:57 +01:00
Sylvia van Os
daa2f4137c Forgot to update CHANGELOG 2020-11-06 20:28:52 +01:00
Sylvia van Os
e61d29417c Release Catima 1.0 2020-11-06 20:25:46 +01:00
Sylvia van Os
cc1be04cf7 Update CHANGELOG 2020-11-06 20:19:12 +01:00
Sylvia van Os
e0ed5bd11c Update screenshots 2020-11-06 20:18:28 +01:00
Sylvia van Os
1bda5410d8 Fix max db version 2020-11-06 20:17:57 +01:00
Sylvia van Os
4bbeb27714 Feature/groups (#71)
* Basic group management

* Assign cards to groups

* Fix lint

* Fix findbugs 'dodgy code'

* Group name as unique key

* More group tests

* Import/export groups

* Implement group renaming and deleting

* Fix findBugs

* Fix chip marking in edit activity

* Group import/export tests

* Fix some state bugs

* Some last fixes

* Remove redundant if statement

* Fix findBugs

* Deduplicate code

* Cleanup

* Fix groups not showing up with new card

* Fix capture and enter button touching

* Fix dialog button look
2020-11-06 18:12:06 +01:00
Sylvia van Os
c04f2c6785 Use longer en-US title 2020-11-06 18:09:52 +01:00
Sylvia van Os
70fa8ff11c Merge pull request #84 from TangentFoxy/patch-1
Fix typo in Import/Export Activity
2020-11-06 09:00:50 +01:00
Rose Liverman
1637895866 Fix typo in Import/Export Activity 2020-11-05 22:22:24 -07:00
Sylvia van Os
6e0d48e42d Update CHANGELOG 2020-11-05 21:38:34 +01:00
Sylvia van Os
9e71a0d86c Fix fastlane 2020-11-05 20:29:37 +01:00
Sylvia van Os
20365e31be Merge pull request #81 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-03 19:28:17 +01:00
J. Lavoie
fb29adbb22 Translated using Weblate (French)
Currently translated at 100.0% (3 of 3 strings)

Translation: Catima/Fastlane
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2020-11-03 19:26:48 +01:00
J. Lavoie
c4f0ab37a9 Translated using Weblate (Italian)
Currently translated at 99.0% (108 of 109 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2020-11-03 19:26:48 +01:00
J. Lavoie
038def0114 Translated using Weblate (French)
Currently translated at 99.0% (108 of 109 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-11-03 19:26:48 +01:00
Sylvia van Os
1f962d88c0 Merge pull request #80 from arshbeerSingh/master
Fix Issue #56
2020-11-03 18:12:00 +01:00
Arshbeer Singh
d897f3b137 Fix Issue #56 2020-11-03 03:31:04 -08:00
Sylvia van Os
dba3692521 Merge pull request #79 from IzzySoft/fastlane
Fastlane: add German summary/description
2020-11-02 22:37:26 +01:00
Izzy
7ab6ec5f75 Fastlane: add German summary/description 2020-11-02 21:52:00 +01:00
Sylvia van Os
0d6e002324 Fix README 2020-11-02 18:58:05 +01:00
Sylvia van Os
9b2748cf61 Migrate to Fastlane 2020-11-02 18:00:43 +01:00
Sylvia van Os
e505bce0ba Merge pull request #78 from weblate/weblate-catima-catima
Translations update from Weblate
2020-11-02 11:07:23 +01:00
Allan Nordhøy
e25d8910c5 Translated using Weblate (Norwegian Bokmål)
Currently translated at 96.3% (105 of 109 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/nb_NO/
2020-11-01 14:26:48 +01:00
Artem
3e79001e84 Translated using Weblate (Russian)
Currently translated at 98.1% (104 of 106 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ru/
2020-11-01 14:26:48 +01:00
J. Lavoie
4b432ea94c Translated using Weblate (Italian)
Currently translated at 100.0% (106 of 106 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/it/
2020-11-01 14:26:48 +01:00
J. Lavoie
78752e1cf2 Translated using Weblate (French)
Currently translated at 100.0% (106 of 106 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/fr/
2020-11-01 14:26:48 +01:00
J. Lavoie
129608befd Translated using Weblate (German)
Currently translated at 100.0% (106 of 106 strings)

Translation: Catima/Catima
Translate-URL: https://hosted.weblate.org/projects/catima/catima/de/
2020-11-01 14:26:48 +01:00
Sylvia van Os
0c2bf8b8a5 Add weblate link and button 2020-10-30 23:03:24 +01:00
Sylvia van Os
6bc08a21e8 Merge pull request #76 from TheLastProject/create-pull-request/patch-1603996252
Compressed Images
2020-10-29 21:40:23 +01:00
Sylvia van Os
fa5d094197 Add logo to migration guide 2020-10-29 20:04:56 +01:00
TheLastProject
7d5f79b7e1 Compressed Images 2020-10-29 18:30:52 +00:00
260 changed files with 9860 additions and 3000 deletions

View File

@@ -13,6 +13,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
@@ -23,5 +24,11 @@ jobs:
run: ./gradlew lintRelease
- name: Run unit tests
run: ./gradlew testReleaseUnitTest
- name: FindBugs
run: ./gradlew findbugs
- name: SpotBugs
run: ./gradlew spotbugsRelease
- name: Archive test results
if: always()
uses: actions/upload-artifact@v2
with:
name: test-results
path: app/build/reports

11
.gitignore vendored
View File

@@ -1,8 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
local.properties
.idea/
.DS_Store
/build
/captures
build/
captures/
**/release
**/debug

View File

@@ -1,10 +0,0 @@
[main]
host = https://www.transifex.com
[loyalty-card-locker.stringsxml]
file_filter = app/src/main/res/values-<lang>/strings.xml
minimum_perc = 0
source_file = app/src/main/res/values/strings.xml
source_lang = en
type = ANDROID

View File

@@ -1,6 +1,195 @@
# Changelog
## v1.14 (2021-06-07)
Changes:
- Support new PDF417 export from Voucher Vault
- Support copying multiple barcodes at once
- Support sharing multiple loyalty cards at once
- Ask to update barcode value if card ID changes
## v1.13 (2021-04-10)
Changes:
- Add option to set a separate barcode value from card ID
- Simplify font sizing configuration
- Several small UI fixes
- Use letter icon for shortcuts too
- Always show all barcode types in manual entry
- Remove privacy policy first start dialog
## v1.12 (2021-03-30)
Changes:
- Support importing [Fidme](https://play.google.com/store/apps/details?id=fr.snapp.fidme) exports
- Allow importing a card from a picture stored in the user's Android gallery
- Fix multiline note cutoff
- Change "Thank you" text on privacy dialog to "Accept" because Huawei is overly pedantic
## v1.11 (2021-03-21)
Changes:
- Add privacy policy dialog on first start (required by Huawei)
## v1.10 (2021-03-07)
Changes:
- Support importing [Voucher Vault](https://github.com/tim-smart/vouchervault/) exports
- Option to keep the screen on while viewing a loyalty card
- Option to suspend the lock screen while viewing a loyalty card
## v1.9.2 (2021-02-24)
Changes:
- Fix parsing balance for countries using space as separator
## v1.9.1 (2021-02-23)
Changes:
- Improve balance parsing logic
- Fix currency decimal display on main screen
## v1.9 (2021-02-22)
Changes:
- Add balance support
- Reorganize barcode tab of edit view
## v1.8.1 (2021-02-12)
Changes:
- Fix Crash on versions before Android 7
## v1.8 (2021-01-28)
Changes:
- Add support for scaling the barcode when moving to top to fit even more small scanners
- Fix bottom sheet jumping after switching to fullscreen
- Make header in loyalty card view small in landscape mode
- Fix cards not staying in group when group gets renamed
## v1.7.1 (2021-01-18)
Changes:
- Fix crash on switching to barcode tab in edit view if there is no barcode
## v1.7.0 (2021-01-18)
Changes:
- Separate edit UI in tabs to make it feel more spacious
- Add expiry field support
## v1.6.2 (2021-01-04)
Changes:
- Fix edit button or more info bottom sheet drawing over barcode ID
## v1.6.1 (2020-12-16)
Changes:
- Fix regression causing manual barcode entry to not be saved
## v1.6.0 (2020-12-15)
Changes:
- Automatically focus text field when creating or editing a group
- Fix blurry icons (use SVG everywhere)
- Always open camera but add manual scan button to camera view
## v1.5.1 (2020-12-03)
Changes:
- Fix bottomsheet background being transparent
## v1.5.0 (2020-12-03)
Changes:
- Improve contrast by always using white text on red buttons
- Draggable bottom sheet in loyalty card view
## v1.4.1 (2020-12-01)
Changes:
- Improved translations
- Small UI fixes
## v1.4.0 (2020-11-28)
Changes:
- Move About screen into its own activity
- Ask user if they want to use their camera or manually enter ID on add/edit card
- Make group ordering manual instead of forced alphabetically
## v1.3.0 (2020-11-22)
Changes:
- Always show all import/export options and show a toast on actual issues (improves compat with XPrivacyLua)
- Ask for confirmation when leaving edit view after making changes without saving
## v1.2.2 (2020-11-19)
Changes:
- Remember active group tab between screens and sessions
## v1.2.1 (2020-11-17)
Changes:
- Fix home screen swiping triggering during vertical swipes too
## v1.2.0 (2020-11-17)
Changes:
- Add swiping between groups on the home screen
- Fix crash with cards lacking header colour
## v1.1.0 (2020-11-11)
Changes:
- Improved edit UI
- Removed header text colour option (now automatically generated based on brightness)
- Updated translations
## v1.0.1 (2020-11-07)
Changes:
- Fix crash in search with no groups
## v1.0 (2020-11-06)
Changes:
- Added rounded edges to card icons on main overview
- Added support for grouping entries
## v0.29 (2020-10-29)
Changes:
- Rebrand to Catima
- Removed intro
- Add floating action buttons
@@ -11,6 +200,7 @@ Changes:
## v0.28 (2020-03-09)
Changes:
- Fix barcode centering when exiting full screen ([#351](https://github.com/brarcher/loyalty-card-locker/pull/351))
- Allow backup export location to be selected ([#352](https://github.com/brarcher/loyalty-card-locker/pull/352))
- Update translations ([#357](https://github.com/brarcher/loyalty-card-locker/pull/357)) & ([#362](https://github.com/brarcher/loyalty-card-locker/pull/362))
@@ -18,6 +208,7 @@ Changes:
## v0.27 (2020-01-26)
Changes:
- Tapping on a barcode now moves it to the top of the screen ([#348](https://github.com/brarcher/loyalty-card-locker/pull/348))
- Add white space around barcodes to improve scanning in dark mode ([#328](https://github.com/brarcher/loyalty-card-locker/issues/328))
- Fix swapped import buttons. ([#346](https://github.com/brarcher/loyalty-card-locker/pull/346))
@@ -25,11 +216,13 @@ Changes:
## v0.26.1 (2020-01-09)
Changes:
- Fix issue with sharing cards without background color ([#343](https://github.com/brarcher/loyalty-card-locker/pull/343))
## v0.26 (2020-01-05)
Changes:
- Add ability to search for a card ([#320](https://github.com/brarcher/loyalty-card-locker/pull/320))
- Add ability to share and receive loyalty cards ([#321](https://github.com/brarcher/loyalty-card-locker/pull/321))
- Dark mode support ([#322](https://github.com/brarcher/loyalty-card-locker/pull/322))
@@ -39,178 +232,203 @@ Changes:
- Improve notification and app icon visibility ([#330](https://github.com/brarcher/loyalty-card-locker/pull/330))
- Update target SDK to Android 10
- Improve the following translations:
* German
* Italian
* Dutch
* Polish
* Russian
- German
- Italian
- Dutch
- Polish
- Russian
## v0.25.4 (2019-10-04)
Changes
Changes:
- Enable app backups
- Update French and Slovenian translations
## v0.25.3 (2019-03-02)
Changes
Changes:
- Update Russian translations
## v0.25.2 (2019-01-05)
Changes
Changes:
- Update and add translations
## v0.25.1 (2018-10-14)
Changes:
- Fix creating new card by manually entering barcode (https://github.com/brarcher/loyalty-card-locker/issues/272)
- Fix creating new card by manually entering barcode ([issue #272](https://github.com/brarcher/loyalty-card-locker/issues/272))
## v0.25 (2018-10-07)
Changes:
- Sort card list case insensitive (https://github.com/brarcher/loyalty-card-locker/pull/266)
- Add setting to lock orientation for all cards (https://github.com/brarcher/loyalty-card-locker/pull/269)
- Sort card list case insensitive ([pull #266](https://github.com/brarcher/loyalty-card-locker/pull/266))
- Add setting to lock orientation for all cards ([pull #269](https://github.com/brarcher/loyalty-card-locker/pull/269)
## v0.24 (2018-07-31)
Changes:
- Add a setting to control screen brightness when displaying a barcode (https://github.com/brarcher/loyalty-card-locker/pull/259)
- Add Greek translations (https://github.com/brarcher/loyalty-card-locker/pull/252)
- Add Slovenian translations (https://github.com/brarcher/loyalty-card-locker/pull/260)
- Update translations (https://github.com/brarcher/loyalty-card-locker/pull/260, https://github.com/brarcher/loyalty-card-locker/pull/254)
- Add a setting to control screen brightness when displaying a barcode ([pull #259](https://github.com/brarcher/loyalty-card-locker/pull/259))
- Add Greek translations ([pull #252](https://github.com/brarcher/loyalty-card-locker/pull/252))
- Add Slovenian translations ([pull #260](https://github.com/brarcher/loyalty-card-locker/pull/260))
- Update translations ([pull #260](https://github.com/brarcher/loyalty-card-locker/pull/260), [pull #254](https://github.com/brarcher/loyalty-card-locker/pull/254))
## v0.23.4 (2018-05-12)
Changes:
- Fix Spanish translations (https://github.com/brarcher/loyalty-card-locker/pull/244)
- Update translations (https://github.com/brarcher/loyalty-card-locker/pull/244)
Changes:
- Fix Spanish translations ([pull #244](https://github.com/brarcher/loyalty-card-locker/pull/244))
- Update translations ([pull #244](https://github.com/brarcher/loyalty-card-locker/pull/244))
## v0.23.3 (2018-05-05)
Changes:
- Added translations
* Polish (https://github.com/brarcher/loyalty-card-locker/pull/232)
* Spanish (https://github.com/brarcher/loyalty-card-locker/pull/232)
* Slovak (https://github.com/brarcher/loyalty-card-locker/pull/232)
- Updated translations (https://github.com/brarcher/loyalty-card-locker/pull/239)
- Polish ([pull #232](https://github.com/brarcher/loyalty-card-locker/pull/232))
- Spanish ([pull #232](https://github.com/brarcher/loyalty-card-locker/pull/232))
- Slovak ([pull #232](https://github.com/brarcher/loyalty-card-locker/pull/232))
- Updated translations ([pull #239](https://github.com/brarcher/loyalty-card-locker/pull/239))
## v0.23.2 (2018-03-11)
Changes:
- Reduce min SDK from 17 to 15. (https://github.com/brarcher/loyalty-card-locker/pull/226)
- Remove usage of legacy apache library, used only in unit tests but no longer needed. (https://github.com/brarcher/loyalty-card-locker/pull/225)
- Reduce min SDK from 17 to 15. ([pull #226](https://github.com/brarcher/loyalty-card-locker/pull/226))
- Remove usage of legacy apache library, used only in unit tests but no longer needed. ([pull #225](https://github.com/brarcher/loyalty-card-locker/pull/225))
## v0.23.1 (2018-03-07)
Changes:
- Prevent crash when rendering a barcode exhausts the application's memory. (https://github.com/brarcher/loyalty-card-locker/pull/219)
- Prevent crash when rendering a barcode exhausts the application's memory. ([pull #219](https://github.com/brarcher/loyalty-card-locker/pull/219))
## v0.23 (2018-02-28)
Changes:
- Reduce space in header when viewing a card. (https://github.com/brarcher/loyalty-card-locker/pull/213)
- Disable beep when scanning a barcode. (https://github.com/brarcher/loyalty-card-locker/pull/216)
- Reduce space in header when viewing a card. ([pull #213](https://github.com/brarcher/loyalty-card-locker/pull/213))
- Disable beep when scanning a barcode. ([pull #216](https://github.com/brarcher/loyalty-card-locker/pull/216))
## v0.22 (2018-02-19)
Changes:
- Update translations. (https://github.com/brarcher/loyalty-card-locker/pull/208)
- Barcode rendering updates: (https://github.com/brarcher/loyalty-card-locker/pull/209)
* Reload card view activity when screen is rotated, so barcode image is correct size.
* Render 1D barcodes in a larger space, allowing them to better fill the screen.
- Update translations. ([pull #208](https://github.com/brarcher/loyalty-card-locker/pull/208))
- Barcode rendering updates: ([pull #209](https://github.com/brarcher/loyalty-card-locker/pull/209))
- Reload card view activity when screen is rotated, so barcode image is correct size.
- Render 1D barcodes in a larger space, allowing them to better fill the screen.
## v0.21 (2018-02-17)
Changes
- Add quiet space at the start/end of barcodes. (https://github.com/brarcher/loyalty-card-locker/pull/200)
- Add options to configure the colors used for the store name font and background. (https://github.com/brarcher/loyalty-card-locker/pull/203)
- Add options to adjust font sizes on the card listing page and single card page. (https://github.com/brarcher/loyalty-card-locker/pull/204)
Changes:
- Add quiet space at the start/end of barcodes. ([pull #200](https://github.com/brarcher/loyalty-card-locker/pull/200))
- Add options to configure the colors used for the store name font and background. ([pull #203](https://github.com/brarcher/loyalty-card-locker/pull/203))
- Add options to adjust font sizes on the card listing page and single card page. ([pull #204](https://github.com/brarcher/loyalty-card-locker/pull/204))
## v0.20 (2018-02-10)
Changes:
- Changes to Card view to display the note, allow the card ID to take multiple lines, and show the store name. (https://github.com/brarcher/loyalty-card-locker/pull/197)
- Changes to Card view to display the note, allow the card ID to take multiple lines, and show the store name. ([pull #197](https://github.com/brarcher/loyalty-card-locker/pull/197))
## v0.19 (2018-02-01)
Changes:
- Improved layout for card list. (https://github.com/brarcher/loyalty-card-locker/pull/188)
- Improved layout when viewing a card. (https://github.com/brarcher/loyalty-card-locker/pull/190)
- Improved layout for card list. ([pull #188](https://github.com/brarcher/loyalty-card-locker/pull/188))
- Improved layout when viewing a card. ([pull #190](https://github.com/brarcher/loyalty-card-locker/pull/190))
## v0.18.1 (2018-01-24)
Changes:
- Workaround crash during install on some Android versions (likely Android 5 and below). (https://github.com/brarcher/loyalty-card-locker/pull/184)
- Workaround crash during install on some Android versions (likely Android 5 and below). ([pull #184](https://github.com/brarcher/loyalty-card-locker/pull/184))
## v0.18 (2018-01-19)
Changes:
- Fix crash when importing certain types of corrupted CSV files. (https://github.com/brarcher/loyalty-card-locker/pull/177)
- Fix importing backups directly from the file system. (https://github.com/brarcher/loyalty-card-locker/pull/180)
- Fix importing backups from certain types of content providers. (https://github.com/brarcher/loyalty-card-locker/pull/179)
- Fix crash when importing certain types of corrupted CSV files. ([pull #177](https://github.com/brarcher/loyalty-card-locker/pull/177))
- Fix importing backups directly from the file system. ([pull #180](https://github.com/brarcher/loyalty-card-locker/pull/180))
- Fix importing backups from certain types of content providers. ([pull #179](https://github.com/brarcher/loyalty-card-locker/pull/179))
## v0.17 (2018-01-11)
Changes:
- Fix issue on Android SDK 24+ where using the file chooser import option would cause a crash. (https://github.com/brarcher/loyalty-card-locker/pull/170)
- New icon and color scheme. (https://github.com/brarcher/loyalty-card-locker/pull/171)
- Fix issue on Android SDK 24+ where using the file chooser import option would cause a crash. ([pull #170](https://github.com/brarcher/loyalty-card-locker/pull/170))
- New icon and color scheme. ([pull #171](https://github.com/brarcher/loyalty-card-locker/pull/171))
## v0.16 (2017-11-29)
Changes:
- Add support for adding loyalty card shortcuts from the launcher/homescreen. (https://github.com/brarcher/loyalty-card-locker/pull/161)
- Remove support for adding loyalty card shortcuts from the app itself. This removes the need for the shortcut permission. (https://github.com/brarcher/loyalty-card-locker/pull/163)
- Add support for adding loyalty card shortcuts from the launcher/homescreen. ([pull #161](https://github.com/brarcher/loyalty-card-locker/pull/161))
- Remove support for adding loyalty card shortcuts from the app itself. This removes the need for the shortcut permission. ([pull #163](https://github.com/brarcher/loyalty-card-locker/pull/163))
## v0.15 (2017-11-25)
Changes:
- Add support for adding shortcuts to home screen when adding or editing a card. (https://github.com/brarcher/loyalty-card-locker/pull/155)
- Remove widget, as it was a poor substitute for shortcuts. (https://github.com/brarcher/loyalty-card-locker/pull/155)
- Fix exporting backups on Android 7+. (https://github.com/brarcher/loyalty-card-locker/pull/153)
- Report more accurate mime type when exporting backup data. (https://github.com/brarcher/loyalty-card-locker/pull/156)
- Fix bug where a card could not be edited. (https://github.com/brarcher/loyalty-card-locker/pull/155)
- Add support for adding shortcuts to home screen when adding or editing a card. ([pull #155](https://github.com/brarcher/loyalty-card-locker/pull/155))
- Remove widget, as it was a poor substitute for shortcuts. ([pull #155](https://github.com/brarcher/loyalty-card-locker/pull/155))
- Fix exporting backups on Android 7+. ([pull #153](https://github.com/brarcher/loyalty-card-locker/pull/153))
- Report more accurate mime type when exporting backup data. ([pull #156](https://github.com/brarcher/loyalty-card-locker/pull/156))
- Fix bug where a card could not be edited. ([pull #155](https://github.com/brarcher/loyalty-card-locker/pull/155))
## v0.14 (2017-10-26)
Changes:
- Add support for app shortcuts (Android 7.1+), where the most recently used cards will appear as shortcuts. (https://github.com/brarcher/loyalty-card-locker/pull/145)
- Add a widget which works like a pinned app shortcut, to support devices which run below Android 7.1. (https://github.com/brarcher/loyalty-card-locker/pull/142)
- Add support for app shortcuts (Android 7.1+), where the most recently used cards will appear as shortcuts. ([pull #145](https://github.com/brarcher/loyalty-card-locker/pull/145))
- Add a widget which works like a pinned app shortcut, to support devices which run below Android 7.1. ([pull #142](https://github.com/brarcher/loyalty-card-locker/pull/142))
## v0.13 (2017-07-25)
Changes:
- Add screen rotation lock menu option when displaying a card. If locked, the screen will transition to its "natural" orientation and further screen rotation will be blocked. (https://github.com/brarcher/loyalty-card-locker/pull/128)
- If a card is selected from the main screen but cannot be loaded, the application fails gracefully and posts a message. (https://github.com/brarcher/loyalty-card-locker/pull/132)
- Fix case where layout IDs for intro wizard could not be found. (https://github.com/brarcher/loyalty-card-locker/pull/128)
- Add screen rotation lock menu option when displaying a card. If locked, the screen will transition to its "natural" orientation and further screen rotation will be blocked. ([pull #128](https://github.com/brarcher/loyalty-card-locker/pull/128))
- If a card is selected from the main screen but cannot be loaded, the application fails gracefully and posts a message. ([pull #132](https://github.com/brarcher/loyalty-card-locker/pull/132))
- Fix case where layout IDs for intro wizard could not be found. ([pull #128](https://github.com/brarcher/loyalty-card-locker/pull/128))
## v0.12 (2017-07-16)
Changes:
- A change in v0.11 reduced the memory usage of barcode drawing, but affected the barcode dimensions. This is now changed to maintain the barcode dimensions while reducing memory usage. (https://github.com/brarcher/loyalty-card-locker/pull/126)
- Update German and French translations. (https://github.com/brarcher/loyalty-card-locker/pull/122, https://github.com/brarcher/loyalty-card-locker/pull/124, https://github.com/brarcher/loyalty-card-locker/pull/125)
- A change in v0.11 reduced the memory usage of barcode drawing, but affected the barcode dimensions. This is now changed to maintain the barcode dimensions while reducing memory usage. ([pull #126](https://github.com/brarcher/loyalty-card-locker/pull/126))
- Update German and French translations. ([pull #122](https://github.com/brarcher/loyalty-card-locker/pull/122), [pull #124](https://github.com/brarcher/loyalty-card-locker/pull/124), [pull #125](https://github.com/brarcher/loyalty-card-locker/pull/125))
## v0.11.1 (2017-06-29)
Changes:
- Prevent a crash when rotation the screen in the first run intro wizard.
## v0.11 (2017-06-26)
Improvements:
- When editing a card ID, pre-populate the existing ID to start. (https://github.com/brarcher/loyalty-card-locker/pull/94)
- Limit the width of generated barcodes to reduce memory usage and out of memory errors. (https://github.com/brarcher/loyalty-card-locker/pull/103)
- When editing a card, change the "Enter Card" button to say "Edit Card" if a card ID already exists. (https://github.com/brarcher/loyalty-card-locker/pull/104)
- Change the color scheme to be softer and compatible with the app icon, and change the layout when viewing a card to be cleaner. (https://github.com/brarcher/loyalty-card-locker/pull/107)
- Add an intro wizard which launches on the app's first launch. (https://github.com/brarcher/loyalty-card-locker/pull/108)
- When editing a card ID, pre-populate the existing ID to start. ([pull #94](https://github.com/brarcher/loyalty-card-locker/pull/94))
- Limit the width of generated barcodes to reduce memory usage and out of memory errors. ([pull #103](https://github.com/brarcher/loyalty-card-locker/pull/103))
- When editing a card, change the "Enter Card" button to say "Edit Card" if a card ID already exists. ([pull #104](https://github.com/brarcher/loyalty-card-locker/pull/104))
- Change the color scheme to be softer and compatible with the app icon, and change the layout when viewing a card to be cleaner. ([pull #107](https://github.com/brarcher/loyalty-card-locker/pull/107))
- Add an intro wizard which launches on the app's first launch. ([pull #108](https://github.com/brarcher/loyalty-card-locker/pull/108))
## v0.10 (2017-02-12)
Improvements:
- Changed the default import/export filename. (https://github.com/brarcher/loyalty-card-locker/pull/84)
- Correct string on the import/export page. (https://github.com/brarcher/loyalty-card-locker/pull/87)
- Improve layout of card view page. The text should be easier to read, and is selectable with a long click. (https://github.com/brarcher/loyalty-card-locker/pull/91)
- Changed the default import/export filename. ([pull #84](https://github.com/brarcher/loyalty-card-locker/pull/84))
- Correct string on the import/export page. ([pull #87](https://github.com/brarcher/loyalty-card-locker/pull/87))
- Improve layout of card view page. The text should be easier to read, and is selectable with a long click. ([pull #91](https://github.com/brarcher/loyalty-card-locker/pull/91))
## v0.9 (2017-01-17)
@@ -218,43 +436,50 @@ The "Locker" part of the name was not intuitive. To help remedy this a new appli
Additional features/improvements:
- Importing/Exporting cards was changed to be more flexible. (https://github.com/brarcher/loyalty-card-locker/pull/76)
- Translations for Lithuanian added. (https://github.com/brarcher/loyalty-card-locker/pull/62)
- Translations for French added. (https://github.com/brarcher/loyalty-card-locker/pull/80)
- Importing/Exporting cards was changed to be more flexible. ([pull #76](https://github.com/brarcher/loyalty-card-locker/pull/76))
- Translations for Lithuanian added. ([pull #62](https://github.com/brarcher/loyalty-card-locker/pull/62))
- Translations for French added. ([pull #80](https://github.com/brarcher/loyalty-card-locker/pull/80))
## v0.8 (2016-11-22)
New features/improvements:
- Screen brightness increased to its maximum when displaying a card, to help barcode scanners successfully capture the barcode. (https://github.com/brarcher/loyalty-card-locker/pull/54)
- Add a delete confirmation when deleting a card. (https://github.com/brarcher/loyalty-card-locker/pull/55)
- Add translations for German (https://github.com/brarcher/loyalty-card-locker/pull/57) and Czech (https://github.com/brarcher/loyalty-card-locker/pull/58).
- Clarification change for Italian translation. (https://github.com/brarcher/loyalty-card-locker/pull/66)
- Screen brightness increased to its maximum when displaying a card, to help barcode scanners successfully capture the barcode. ([pull #54](https://github.com/brarcher/loyalty-card-locker/pull/54))
- Add a delete confirmation when deleting a card. ([pull #55](https://github.com/brarcher/loyalty-card-locker/pull/55))
- Add translations for German ([pull #57](https://github.com/brarcher/loyalty-card-locker/pull/57)) and Czech ([pull #58](https://github.com/brarcher/loyalty-card-locker/pull/58)).
- Clarification change for Italian translation. ([pull #66](https://github.com/brarcher/loyalty-card-locker/pull/66))
## v0.7 (2016-07-14)
New features/improvements:
- Long-click of a card brings up option to copy card ID to the clipboard. (https://github.com/brarcher/loyalty-card-locker/issues/49)
- Long-click of a card brings up option to copy card ID to the clipboard. ([pull #49](https://github.com/brarcher/loyalty-card-locker/issues/49))
Bug fixes:
- Back button on Input/Export view now works, moving user to main view
## v0.6 (2016-05-23)
New features/improvements:
- Allow user to enter barcode manually. If a user elects to enter a barcode manually, a list of all valid and supported barcode images is displayed. The user then may select the barcode image which matches what the user wants. https://github.com/brarcher/loyalty-card-locker/issues/33, https://github.com/brarcher/loyalty-card-locker/pull/44
- Allow user to enter barcode manually. If a user elects to enter a barcode manually, a list of all valid and supported barcode images is displayed. The user then may select the barcode image which matches what the user wants. [issue #33](https://github.com/brarcher/loyalty-card-locker/issues/33), [pull #44](https://github.com/brarcher/loyalty-card-locker/pull/44)
Bug fixes:
- Resolve issue where some displayed barcodes were blurry. (https://github.com/brarcher/loyalty-card-locker/issues/37)
- Resolve issue where some displayed barcodes were blurry. ([issue #37](https://github.com/brarcher/loyalty-card-locker/issues/37))
## v0.5 (2016-05-16)
New features/improvements:
- An about dialog can be opened from the main screen, which gives details about the application and project on GitHub (https://github.com/brarcher/loyalty-card-locker/issues/19)
- Allow loyalty card information to be imported from/exported to a CSV file in external storage (https://github.com/brarcher/loyalty-card-locker/issues/36 https://github.com/brarcher/loyalty-card-locker/issues/20)
- An about dialog can be opened from the main screen, which gives details about the application and project on GitHub ([issue #19](https://github.com/brarcher/loyalty-card-locker/issues/19))
- Allow loyalty card information to be imported from/exported to a CSV file in external storage ([issue #36](https://github.com/brarcher/loyalty-card-locker/issues/36), [issue #20](https://github.com/brarcher/loyalty-card-locker/issues/20))
## v0.4 (2016-04-09)
New features/improvements:
- Dutch translation
- Allow name field to be editable after adding loyalty card
- Add an optional note field
@@ -266,17 +491,18 @@ Bug fixes:
## v0.3 (2016-02-11)
- Now officially supports the following list of 1D and 2D barcodes:
* AZTEC
* CODABAR
* CODE_39
* CODE_128
* DATA_MATRIX
* EAN_8
* EAN_13
* ITF
* PDF_417
* QR_CODE
* UPC_A
- AZTEC
- CODABAR
- CODE_39
- CODE_128
- DATA_MATRIX
- EAN_8
- EAN_13
- ITF
- PDF_417
- QR_CODE
- UPC_A
- Generated barcodes are larger, easier to scan from a scanning device
## v0.2 (2016-02-07)
@@ -285,7 +511,6 @@ Bug fixes:
- Support for all 1D barcode types. (Originally only product 1D barcodes were supported)
- Add required camera permission, which was initially missing.
## v0.1 (2016-01-30)
- Ability to create/edit/delete loyalty cards

3
Gemfile Normal file
View File

@@ -0,0 +1,3 @@
source "https://rubygems.org"
gem "fastlane"

180
Gemfile.lock Normal file
View File

@@ -0,0 +1,180 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.2)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
aws-eventstream (1.1.0)
aws-partitions (1.388.0)
aws-sdk-core (3.109.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.39.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.83.1)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.2)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
declarative (0.0.20)
declarative-option (0.1.0)
digest-crc (0.6.1)
rake (~> 13.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.2.0)
excon (0.78.0)
faraday (1.1.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday_middleware (1.0.0)
faraday (~> 1.0)
fastimage (2.2.0)
fastlane (2.165.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.37.0, < 0.39.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
google-api-client (0.38.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.4.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.0.1)
google-cloud-storage (1.29.1)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-api-client (~> 0.33)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
googleauth (0.14.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.14)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.4.0)
json (2.3.1)
jwt (2.2.2)
memoist (0.16.2)
mini_magick (4.10.1)
mini_mime (1.0.2)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.0)
os (1.1.1)
plist (3.5.0)
public_suffix (4.0.6)
rake (13.0.1)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
ruby2_keywords (0.0.2)
rubyzip (2.3.0)
security (0.1.3)
signet (0.14.0)
addressable (~> 2.3)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
CFPropertyList
naturally
slack-notifier (2.3.2)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.19.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.0)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
fastlane
BUNDLED WITH
2.1.4

1
app/.gitignore vendored
View File

@@ -1 +0,0 @@
/build

View File

@@ -1,79 +1,101 @@
apply plugin: 'com.android.application'
apply plugin: 'findbugs'
import com.github.spotbugs.snom.SpotBugsTask
findbugs {
sourceSets = []
apply plugin: 'com.android.application'
apply plugin: 'com.github.spotbugs'
spotbugs {
ignoreFailures = false
effort = 'max'
excludeFilter = file("./config/spotbugs/exclude.xml")
reportsDir = file("$buildDir/reports/spotbugs/")
}
android {
compileSdkVersion 29
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "me.hackerchick.catima"
minSdkVersion 16
minSdkVersion 19
targetSdkVersion 29
versionCode 40
versionName "0.29"
versionCode 68
versionName "1.14"
vectorDrawables.useSupportLibrary true
}
buildTypes {
release {
minifyEnabled false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
resValue "string", "app_name", "Catima"
}
debug {
applicationIdSuffix ".debug"
resValue "string", "app_name", "Catima Debug"
}
}
compileOptions {
encoding "UTF-8"
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
disable "GoogleAppIndexingWarning"
disable "ButtonStyle"
disable "AlwaysShowAction"
disable "MissingTranslation"
disable "MissingPrefix"
disable "GoogleAppIndexingWarning", "ButtonStyle", "AlwaysShowAction",
"MissingTranslation", "MissingPrefix"
}
// Starting with Android Studio 3 Robolectric is unable to find resources.
// The following allows it to find the resources.
testOptions {
unitTests {
includeAndroidResources = true
all {
testLogging {
events 'started', 'passed', 'skipped', 'failed'
}
}
includeAndroidResources true
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'androidx.appcompat:appcompat:1.2.0'
compile 'com.google.android.material:material:1.2.0-alpha03'
compile 'androidx.legacy:legacy-support-v4:1.0.0'
compile 'com.journeyapps:zxing-android-embedded:3.5.0@aar'
compile 'com.google.zxing:core:3.3.0'
compile 'org.apache.commons:commons-csv:1.5'
compile 'androidx.constraintlayout:constraintlayout:1.1.3'
compile 'com.jaredrummler:colorpicker:1.0.2'
compile group: 'com.google.guava', name: 'guava', version: '20.0'
compile 'com.github.apl-devs:appintro:v4.2.0'
compile "com.vanniktech:vntnumberpickerpreference:1.0.0"
testCompile 'junit:junit:4.12'
testCompile "org.robolectric:robolectric:4.0.2"
// AndroidX
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.preference:preference:1.1.1'
implementation 'com.google.android.material:material:1.3.0'
// Third-party
implementation 'com.journeyapps:zxing-android-embedded:4.1.0@aar'
// Do not upgrade past 3.3.3! Causes a crash on versions before Android Nougat
//noinspection GradleDependency
implementation 'com.google.zxing:core:3.3.3'
implementation 'org.apache.commons:commons-csv:1.8'
implementation 'com.jaredrummler:colorpicker:1.1.0'
implementation 'com.google.guava:guava:30.1.1-jre'
implementation 'com.github.invissvenska:NumberPickerPreference:1.0.2'
// SpotBugs
implementation 'io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0'
// Testing
testImplementation 'androidx.test:core:1.3.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.4'
}
task findbugs(type: FindBugs, dependsOn: 'assembleDebug') {
tasks.withType(SpotBugsTask) {
description 'Run findbugs'
description 'Run spotbugs'
group 'verification'
classes = fileTree('build/intermediates/javac/debug/compileDebugJavaWithJavac/classes')
source = fileTree('src/main/java')
classpath = files()
effort = 'max'
excludeFilter = file("./config/findbugs/exclude.xml")
//classes = fileTree('build/intermediates/javac/debug/compileDebugJavaWithJavac/classes')
//source = fileTree('src/main/java')
//classpath = files()
reports {
xml.enabled = false

View File

@@ -1,15 +0,0 @@
package protect.card_locker;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application>
{
public ApplicationTest()
{
super(Application.class);
}
}

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="protect.card_locker"
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission
android:name="android.permission.CAMERA"/>
@@ -16,6 +17,8 @@
android:name="android.hardware.camera.autofocus"
android:required="false" />
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
<application
android:name=".LoyaltyCardLockerApplication"
android:allowBackup="true"
@@ -33,10 +36,19 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".AboutActivity"
android:label="@string/about"
android:theme="@style/AppTheme.NoActionBar">
</activity>
<activity
android:name=".ManageGroupsActivity"
android:label="@string/groups"
android:theme="@style/AppTheme.NoActionBar">
</activity>
<activity
android:name=".LoyaltyCardViewActivity"
android:theme="@style/AppTheme.NoActionBar"
android:label=""
android:windowSoftInputMode="stateHidden"
android:exported="true"/>
<activity
@@ -57,6 +69,10 @@
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_old" />
</intent-filter>
</activity>
<activity
android:name=".ScanActivity"
android:label="@string/scanCardBarcode"
android:theme="@style/AppTheme.NoActionBar"/>
<activity
android:name=".BarcodeSelectorActivity"
android:label="@string/selectBarcodeTitle"

View File

@@ -0,0 +1,107 @@
package protect.card_locker;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.MenuItem;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.text.HtmlCompat;
public class AboutActivity extends AppCompatActivity
{
private static final String TAG = "Catima";
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.about_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
actionBar.setDisplayHomeAsUpEnabled(true);
}
final List<ThirdPartyInfo> USED_LIBRARIES = new ArrayList<>();
USED_LIBRARIES.add(new ThirdPartyInfo("Commons CSV", "https://commons.apache.org/proper/commons-csv/", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("Guava", "https://github.com/google/guava", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("ZXing", "https://github.com/zxing/zxing", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("Color Picker", "https://github.com/jaredrummler/ColorPicker", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("NumberPickerPreference", "https://github.com/invissvenska/NumberPickerPreference", "GNU LGPL 3.0"));
final List<ThirdPartyInfo> USED_ASSETS = new ArrayList<>();
USED_ASSETS.add(new ThirdPartyInfo("Android icons", "https://fonts.google.com/icons?selected=Material+Icons", "Apache 2.0"));
StringBuilder libs = new StringBuilder().append("<br/>");
for (ThirdPartyInfo entry : USED_LIBRARIES)
{
libs.append("<br/><a href=\"").append(entry.url()).append("\">").append(entry.name()).append("</a> (").append(entry.license()).append(")<br/>");
}
StringBuilder resources = new StringBuilder().append("<br/>");
for (ThirdPartyInfo entry : USED_ASSETS)
{
resources.append("<br/><a href=\"").append(entry.url()).append("\">").append(entry.name()).append("</a> (").append(entry.license()).append(")<br/>");
}
String appName = getString(R.string.app_name);
int year = Calendar.getInstance().get(Calendar.YEAR);
String version = "?";
try
{
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
version = pi.versionName;
}
catch (PackageManager.NameNotFoundException e)
{
Log.w(TAG, "Package name not found", e);
}
setTitle(String.format(getString(R.string.about_title_fmt), appName));
TextView aboutTextView = findViewById(R.id.aboutText);
aboutTextView.setText(HtmlCompat.fromHtml(String.format(getString(R.string.debug_version_fmt), version) +
"<br/><br/>" +
String.format(getString(R.string.app_revision_fmt),
"<a href=\"" + getString(R.string.app_revision_url) + "\">" +
"GitHub" +
"</a>") +
"<br/><br/>" +
String.format(getString(R.string.app_copyright_fmt), year) +
"<br/><br/>" +
getString(R.string.app_copyright_old) +
"<br/><br/>" +
getString(R.string.app_license) +
"<br/><br/>" +
String.format(getString(R.string.app_libraries), libs.toString()) +
"<br/><br/>" +
String.format(getString(R.string.app_resources), resources.toString()), HtmlCompat.FROM_HTML_MODE_COMPACT));
aboutTextView.setMovementMethod(LinkMovementMethod.getInstance());
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
int id = item.getItemId();
if (id == android.R.id.home) {
finish();
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -1,6 +1,8 @@
package protect.card_locker;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.os.AsyncTask;
import android.util.Log;
import android.view.View;
@@ -23,6 +25,9 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
{
private static final String TAG = "Catima";
private static final int IS_VALID = 999;
private boolean isSuccesful;
// When drawn in a smaller window 1D barcodes for some reason end up
// squished, whereas 2D barcodes look fine.
private static final int MAX_WIDTH_1D = 1500;
@@ -30,14 +35,20 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
private final WeakReference<ImageView> imageViewReference;
private final WeakReference<TextView> textViewReference;
private final String cardId;
private String cardId;
private final BarcodeFormat format;
private final int imageHeight;
private final int imageWidth;
private final boolean showFallback;
private final Runnable callback;
BarcodeImageWriterTask(ImageView imageView, String cardIdString,
BarcodeFormat barcodeFormat, TextView textView)
BarcodeFormat barcodeFormat, TextView textView,
boolean showFallback, Runnable callback)
{
isSuccesful = true;
this.callback = callback;
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<>(imageView);
textViewReference = new WeakReference<>(textView);
@@ -59,11 +70,8 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
double ratio = (double)MAX_WIDTH / (double)imageView.getWidth();
imageHeight = (int)(imageView.getHeight() * ratio);
}
}
BarcodeImageWriterTask(ImageView imageView, String cardIdString, BarcodeFormat barcodeFormat)
{
this(imageView, cardIdString, barcodeFormat, null);
this.showFallback = showFallback;
}
private int getMaxWidth(BarcodeFormat format)
@@ -96,7 +104,41 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
}
}
public Bitmap doInBackground(Void... params)
private String getFallbackString(BarcodeFormat format)
{
switch(format)
{
// 2D barcodes
case AZTEC:
return "AZTEC";
case DATA_MATRIX:
return "DATA_MATRIX";
case PDF_417:
return "PDF_417";
case QR_CODE:
return "QR_CODE";
// 1D barcodes:
case CODABAR:
return "C0C";
case CODE_39:
return "CODE_39";
case CODE_128:
return "CODE_128";
case EAN_8:
return "32123456";
case EAN_13:
return "5901234123457";
case ITF:
return "1003";
case UPC_A:
return "123456789012";
default:
throw new IllegalArgumentException("No fallback known for this barcode type");
}
}
private Bitmap generate()
{
if (cardId.isEmpty())
{
@@ -165,13 +207,30 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
catch(OutOfMemoryError e)
{
Log.w(TAG, "Insufficient memory to render barcode, "
+ imageWidth + "x" + imageHeight + ", " + format.name()
+ ", length=" + cardId.length(), e);
+ imageWidth + "x" + imageHeight + ", " + format.name()
+ ", length=" + cardId.length(), e);
}
return null;
}
public Bitmap doInBackground(Void... params)
{
Bitmap bitmap = generate();
if (bitmap == null) {
isSuccesful = false;
if (showFallback) {
Log.i(TAG, "Barcode generation failed, generating fallback...");
cardId = getFallbackString(format);
bitmap = generate();
}
}
return bitmap;
}
protected void onPostExecute(Bitmap result)
{
Log.i(TAG, "Finished generating barcode image of type " + format + ": " + cardId);
@@ -182,6 +241,8 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
return;
}
imageView.setTag(isSuccesful);
imageView.setImageBitmap(result);
TextView textView = textViewReference.get();
@@ -190,6 +251,12 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
Log.i(TAG, "Displaying barcode");
imageView.setVisibility(View.VISIBLE);
if (isSuccesful) {
imageView.setColorFilter(null);
} else {
imageView.setColorFilter(Color.LTGRAY, PorterDuff.Mode.LIGHTEN);
}
if (textView != null) {
textView.setVisibility(View.VISIBLE);
textView.setText(format.name());
@@ -203,5 +270,9 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
textView.setVisibility(View.GONE);
}
}
if (callback != null) {
callback.run();
}
}
}

View File

@@ -3,11 +3,7 @@ package protect.card_locker;
import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
@@ -18,6 +14,7 @@ import android.view.ViewTreeObserver;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.common.collect.ImmutableMap;
import com.google.zxing.BarcodeFormat;
@@ -28,6 +25,10 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.Map;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
/**
* This activity is callable and will allow a user to enter
* barcode data and generate all barcodes possible for
@@ -105,20 +106,7 @@ public class BarcodeSelectorActivity extends AppCompatActivity
{
Log.d(TAG, "Entered text: " + s);
// Stop any async tasks which may not have been started yet
for(AsyncTask task : barcodeGeneratorTasks)
{
task.cancel(false);
}
barcodeGeneratorTasks.clear();
// Update barcodes
for(String key : barcodeViewMap.keySet())
{
ImageView image = findViewById(barcodeViewMap.get(key).first);
TextView text = findViewById(barcodeViewMap.get(key).second);
createBarcodeOption(image, key, s.toString(), text);
}
generateBarcodes(s.toString());
View noBarcodeButtonView = findViewById(R.id.noBarcode);
setButtonListener(noBarcodeButtonView, s.toString());
@@ -138,6 +126,25 @@ public class BarcodeSelectorActivity extends AppCompatActivity
if(initialCardId != null)
{
cardId.setText(initialCardId);
} else {
generateBarcodes("");
}
}
private void generateBarcodes(String value) {
// Stop any async tasks which may not have been started yet
for(AsyncTask task : barcodeGeneratorTasks)
{
task.cancel(false);
}
barcodeGeneratorTasks.clear();
// Update barcodes
for(Map.Entry<String, Pair<Integer, Integer>> entry : barcodeViewMap.entrySet())
{
ImageView image = findViewById(entry.getValue().first);
TextView text = findViewById(entry.getValue().second);
createBarcodeOption(image, entry.getKey(), value, text);
}
}
@@ -172,6 +179,12 @@ public class BarcodeSelectorActivity extends AppCompatActivity
public void onClick(View v)
{
Log.d(TAG, "Selected barcode type " + formatType);
if (!((boolean) image.getTag())) {
Toast.makeText(BarcodeSelectorActivity.this, getString(R.string.wrongValueForBarcodeType), Toast.LENGTH_LONG).show();
return;
}
Intent result = new Intent();
result.putExtra(BARCODE_FORMAT, formatType);
result.putExtra(BARCODE_CONTENTS, cardId);
@@ -191,17 +204,10 @@ public class BarcodeSelectorActivity extends AppCompatActivity
public void onGlobalLayout()
{
Log.d(TAG, "Global layout finished, type: + " + formatType + ", width: " + image.getWidth());
if (Build.VERSION.SDK_INT < 16)
{
image.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
else
{
image.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
image.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Log.d(TAG, "Generating barcode for type " + formatType);
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format, text);
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format, text, true, null);
barcodeGeneratorTasks.add(task);
task.execute();
}
@@ -210,7 +216,7 @@ public class BarcodeSelectorActivity extends AppCompatActivity
else
{
Log.d(TAG, "Generating barcode for type " + formatType);
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format, text);
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format, text, true, null);
barcodeGeneratorTasks.add(task);
task.execute();
}

View File

@@ -0,0 +1,23 @@
package protect.card_locker;
public class BarcodeValues {
private final String mFormat;
private final String mContent;
public BarcodeValues(String format, String content) {
mFormat = format;
mContent = content;
}
public String format() {
return mFormat;
}
public String content() {
return mContent;
}
public boolean isEmpty() {
return mFormat == null && mContent == null;
}
}

View File

@@ -0,0 +1,87 @@
package protect.card_locker;
import android.database.Cursor;
import androidx.recyclerview.widget.RecyclerView;
public abstract class BaseCursorAdapter<V extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<V>
{
private Cursor mCursor;
private boolean mDataValid;
private int mRowIDColumn;
public BaseCursorAdapter(Cursor inputCursor)
{
setHasStableIds(true);
swapCursor(inputCursor);
}
public abstract void onBindViewHolder(V inputHolder, Cursor inputCursor);
@Override
public void onBindViewHolder(V inputHolder, int inputPosition)
{
if (!mDataValid)
{
throw new IllegalStateException("Cannot bind view holder when cursor is in invalid state.");
}
if (!mCursor.moveToPosition(inputPosition))
{
throw new IllegalStateException("Could not move cursor to position " + inputPosition + " when trying to bind view holder");
}
onBindViewHolder(inputHolder, mCursor);
}
@Override
public int getItemCount()
{
if (mDataValid)
{
return mCursor.getCount();
}
else
{
return 0;
}
}
@Override
public long getItemId(int inputPosition)
{
if (!mDataValid)
{
throw new IllegalStateException("Cannot lookup item id when cursor is in invalid state.");
}
if (!mCursor.moveToPosition(inputPosition))
{
throw new IllegalStateException("Could not move cursor to position " + inputPosition + " when trying to get an item id");
}
return mCursor.getLong(mRowIDColumn);
}
public void swapCursor(Cursor inputCursor)
{
if (inputCursor == mCursor)
{
return;
}
if (inputCursor != null)
{
mCursor = inputCursor;
mDataValid = true;
notifyDataSetChanged();
}
else
{
notifyItemRangeRemoved(0, getItemCount());
mCursor = null;
mRowIDColumn = -1;
mDataValid = false;
}
}
}

View File

@@ -4,24 +4,26 @@ import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.RecyclerView;
/**
* The configuration screen for creating a shortcut.
*/
public class CardShortcutConfigure extends AppCompatActivity
public class CardShortcutConfigure extends AppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener
{
static final String TAG = "Catima";
final DBHelper mDb = new DBHelper(this);
@Override
public void onCreate(Bundle bundle)
{
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
// Set the result to CANCELED. This will cause nothing to happen if the
@@ -32,52 +34,66 @@ public class CardShortcutConfigure extends AppCompatActivity
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setVisibility(View.GONE);
// Hide new button because it won't work here anyway
FloatingActionButton newFab = findViewById(R.id.fabAdd);
newFab.setVisibility(View.GONE);
final DBHelper db = new DBHelper(this);
// If there are no cards, bail
if(db.getLoyaltyCardCount() == 0)
{
if (db.getLoyaltyCardCount() == 0) {
Toast.makeText(this, R.string.noCardsMessage, Toast.LENGTH_LONG).show();
finish();
}
final ListView cardList = findViewById(R.id.list);
final RecyclerView cardList = findViewById(R.id.list);
cardList.setVisibility(View.VISIBLE);
Cursor cardCursor = db.getLoyaltyCardCursor();
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor);
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor, this);
cardList.setAdapter(adapter);
}
cardList.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
Cursor selected = (Cursor) parent.getItemAtPosition(position);
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
private void onClickAction(int position) {
Cursor selected = mDb.getLoyaltyCardCursor();
selected.moveToPosition(position);
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
Log.d(TAG, "Creating shortcut for card " + loyaltyCard.store + "," + loyaltyCard.id);
Log.d(TAG, "Creating shortcut for card " + loyaltyCard.store + "," + loyaltyCard.id);
Intent shortcutIntent = new Intent(CardShortcutConfigure.this, LoyaltyCardViewActivity.class);
shortcutIntent.setAction(Intent.ACTION_MAIN);
// Prevent instances of the view activity from piling up; if one exists let this
// one replace it.
shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
Bundle bundle = new Bundle();
bundle.putInt("id", loyaltyCard.id);
bundle.putBoolean("view", true);
shortcutIntent.putExtras(bundle);
Intent shortcutIntent = new Intent(CardShortcutConfigure.this, LoyaltyCardViewActivity.class);
shortcutIntent.setAction(Intent.ACTION_MAIN);
// Prevent instances of the view activity from piling up; if one exists let this
// one replace it.
shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
Bundle bundle = new Bundle();
bundle.putInt("id", loyaltyCard.id);
bundle.putBoolean("view", true);
shortcutIntent.putExtras(bundle);
Parcelable icon = Intent.ShortcutIconResource.fromContext(CardShortcutConfigure.this, R.mipmap.ic_launcher);
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, loyaltyCard.store);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon);
setResult(RESULT_OK, intent);
Parcelable icon = Intent.ShortcutIconResource.fromContext(CardShortcutConfigure.this, R.mipmap.ic_launcher);
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, loyaltyCard.store);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon);
setResult(RESULT_OK, intent);
finish();
}
});
finish();
}
@Override
public void onIconClicked(int inputPosition) {
onClickAction(inputPosition);
}
@Override
public void onRowClicked(int inputPosition) {
onClickAction(inputPosition);
}
@Override
public void onRowLongClicked(int inputPosition) {
// do nothing
}
}

View File

@@ -1,56 +0,0 @@
package protect.card_locker;
import android.database.Cursor;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import java.io.IOException;
import java.io.OutputStreamWriter;
/**
* Class for exporting the database into CSV (Comma Separate Values)
* format.
*/
public class CsvDatabaseExporter implements DatabaseExporter
{
public void exportData(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException
{
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
// Print the header
printer.printRecord(DBHelper.LoyaltyCardDbIds.ID,
DBHelper.LoyaltyCardDbIds.STORE,
DBHelper.LoyaltyCardDbIds.NOTE,
DBHelper.LoyaltyCardDbIds.CARD_ID,
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR,
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
DBHelper.LoyaltyCardDbIds.STAR_STATUS);
Cursor cursor = db.getLoyaltyCardCursor();
while(cursor.moveToNext())
{
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
printer.printRecord(card.id,
card.store,
card.note,
card.cardId,
card.headerColor,
card.headerTextColor,
card.barcodeType,
card.starStatus);
if(Thread.currentThread().isInterrupted())
{
throw new InterruptedException();
}
}
cursor.close();
printer.close();
}
}

View File

@@ -1,155 +0,0 @@
package protect.card_locker;
import android.database.sqlite.SQLiteDatabase;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* Class for importing a database from CSV (Comma Separate Values)
* formatted data.
*
* The database's loyalty cards are expected to appear in the CSV data.
* A header is expected for the each table showing the names of the columns.
*/
public class CsvDatabaseImporter implements DatabaseImporter
{
public void importData(DBHelper db, InputStreamReader input) throws IOException, FormatException, InterruptedException
{
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.withHeader());
SQLiteDatabase database = db.getWritableDatabase();
database.beginTransaction();
try
{
for (CSVRecord record : parser)
{
importLoyaltyCard(database, db, record);
if(Thread.currentThread().isInterrupted())
{
throw new InterruptedException();
}
}
parser.close();
database.setTransactionSuccessful();
}
catch(IllegalArgumentException|IllegalStateException e)
{
throw new FormatException("Issue parsing CSV data", e);
}
finally
{
database.endTransaction();
database.close();
}
}
/**
* Extract a string from the items array. The index into the array
* is determined by looking up the index in the fields map using the
* "key" as the key. If no such key exists, defaultValue is returned
* if it is not null. Otherwise, a FormatException is thrown.
*/
private String extractString(String key, CSVRecord record, String defaultValue)
throws FormatException
{
String toReturn = defaultValue;
if(record.isMapped(key))
{
toReturn = record.get(key);
}
else
{
if(defaultValue == null)
{
throw new FormatException("Field not used but expected: " + key);
}
}
return toReturn;
}
/**
* Extract an integer from the items array. The index into the array
* is determined by looking up the index in the fields map using the
* "key" as the key. If no such key exists, or the data is not a valid
* int, a FormatException is thrown.
*/
private Integer extractInt(String key, CSVRecord record, boolean nullIsOk)
throws FormatException
{
if(record.isMapped(key) == false)
{
throw new FormatException("Field not used but expected: " + key);
}
String value = record.get(key);
if(value.isEmpty() && nullIsOk)
{
return null;
}
try
{
return Integer.parseInt(record.get(key));
}
catch(NumberFormatException e)
{
throw new FormatException("Failed to parse field: " + key, e);
}
}
/**
* Import a single loyalty card into the database using the given
* session.
*/
private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record)
throws IOException, FormatException
{
int id = extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false);
String store = extractString(DBHelper.LoyaltyCardDbIds.STORE, record, "");
if(store.isEmpty())
{
throw new FormatException("No store listed, but is required");
}
String note = extractString(DBHelper.LoyaltyCardDbIds.NOTE, record, "");
String cardId = extractString(DBHelper.LoyaltyCardDbIds.CARD_ID, record, "");
if(cardId.isEmpty())
{
throw new FormatException("No card ID listed, but is required");
}
String barcodeType = extractString(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, record, "");
Integer headerColor = null;
Integer headerTextColor = null;
if(record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR) &&
record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR))
{
headerColor = extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record, true);
headerTextColor = extractInt(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR, record, true);
}
int starStatus = 0;
try {
starStatus = extractInt(DBHelper.LoyaltyCardDbIds.STAR_STATUS, record, false);
} catch (FormatException _ ) {
// This field did not exist in versions 0.28 and before
// We catch this exception so we can still import old backups
}
if (starStatus != 1) starStatus = 0;
helper.insertLoyaltyCard(database, id, store, note, cardId, barcodeType, headerColor, headerTextColor, starStatus);
}
}

View File

@@ -1,31 +1,57 @@
package protect.card_locker;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import com.google.zxing.BarcodeFormat;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Currency;
import java.util.Date;
import java.util.List;
public class DBHelper extends SQLiteOpenHelper
{
public static final String DATABASE_NAME = "Catima.db";
public static final int ORIGINAL_DATABASE_VERSION = 1;
public static final int DATABASE_VERSION = 4;
public static final int DATABASE_VERSION = 10;
static class LoyaltyCardDbIds
public static class LoyaltyCardDbGroups
{
public static final String TABLE = "groups";
public static final String ID = "_id";
public static final String ORDER = "orderId";
}
public static class LoyaltyCardDbIds
{
public static final String TABLE = "cards";
public static final String ID = "_id";
public static final String STORE = "store";
public static final String EXPIRY = "expiry";
public static final String BALANCE = "balance";
public static final String BALANCE_TYPE = "balancetype";
public static final String NOTE = "note";
public static final String HEADER_COLOR = "headercolor";
public static final String HEADER_TEXT_COLOR = "headertextcolor";
public static final String CARD_ID = "cardid";
public static final String BARCODE_ID = "barcodeid";
public static final String BARCODE_TYPE = "barcodetype";
public static final String STAR_STATUS = "starstatus";
}
public static class LoyaltyCardDbIdsGroups
{
public static final String TABLE = "cardsGroups";
public static final String cardID = "cardId";
public static final String groupID = "groupId";
}
public DBHelper(Context context)
{
super(context, DATABASE_NAME, null, DATABASE_VERSION);
@@ -34,16 +60,31 @@ public class DBHelper extends SQLiteOpenHelper
@Override
public void onCreate(SQLiteDatabase db)
{
// create table for gift cards
// create table for card groups
db.execSQL("create table " + LoyaltyCardDbGroups.TABLE + "(" +
LoyaltyCardDbGroups.ID + " TEXT primary key not null," +
LoyaltyCardDbGroups.ORDER + " INTEGER DEFAULT '0')");
// create table for cards
// Balance is TEXT and not REAL to be able to store a BigDecimal without precision loss
db.execSQL("create table " + LoyaltyCardDbIds.TABLE + "(" +
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
LoyaltyCardDbIds.STORE + " TEXT not null," +
LoyaltyCardDbIds.NOTE + " TEXT not null," +
LoyaltyCardDbIds.EXPIRY + " INTEGER," +
LoyaltyCardDbIds.BALANCE + " TEXT not null DEFAULT '0'," +
LoyaltyCardDbIds.BALANCE_TYPE + " TEXT," +
LoyaltyCardDbIds.HEADER_COLOR + " INTEGER," +
LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER," +
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT not null," +
LoyaltyCardDbIds.BARCODE_ID + " TEXT," +
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," +
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0' )");
// create associative table for cards in groups
db.execSQL("create table " + LoyaltyCardDbIdsGroups.TABLE + "(" +
LoyaltyCardDbIdsGroups.cardID + " INTEGER," +
LoyaltyCardDbIdsGroups.groupID + " TEXT," +
"primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID +"))");
}
@Override
@@ -64,63 +105,230 @@ public class DBHelper extends SQLiteOpenHelper
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER");
}
// Upgrade from version 3 to version 4
if(oldVersion < 4 && newVersion >= 4)
{
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'");
}
// Upgrade from version 4 to version 5
if(oldVersion < 5 && newVersion >= 5)
{
db.execSQL("create table " + LoyaltyCardDbGroups.TABLE + "(" +
LoyaltyCardDbGroups.ID + " TEXT primary key not null)");
db.execSQL("create table " + LoyaltyCardDbIdsGroups.TABLE + "(" +
LoyaltyCardDbIdsGroups.cardID + " INTEGER," +
LoyaltyCardDbIdsGroups.groupID + " TEXT," +
"primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID +"))");
}
// Upgrade from version 5 to 6
if(oldVersion < 6 && newVersion >= 6)
{
db.execSQL("ALTER TABLE " + LoyaltyCardDbGroups.TABLE
+ " ADD COLUMN " + LoyaltyCardDbGroups.ORDER + " INTEGER DEFAULT '0'");
}
if(oldVersion < 7 && newVersion >= 7)
{
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.EXPIRY + " INTEGER");
}
if(oldVersion < 8 && newVersion >= 8)
{
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.BALANCE + " TEXT not null DEFAULT '0'");
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.BALANCE_TYPE + " TEXT");
}
if(oldVersion < 9 && newVersion >= 9)
{
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.BARCODE_ID + " TEXT");
}
if(oldVersion < 10 && newVersion >= 10)
{
// SQLite doesn't support modify column
// So we need to create a temp column to make barcode type nullable
// Let's drop header text colour too while we're at it
// https://www.sqlite.org/faq.html#q11
db.beginTransaction();
db.execSQL("CREATE TEMPORARY TABLE tmp (" +
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
LoyaltyCardDbIds.STORE + " TEXT not null," +
LoyaltyCardDbIds.NOTE + " TEXT not null," +
LoyaltyCardDbIds.EXPIRY + " INTEGER," +
LoyaltyCardDbIds.BALANCE + " TEXT not null DEFAULT '0'," +
LoyaltyCardDbIds.BALANCE_TYPE + " TEXT," +
LoyaltyCardDbIds.HEADER_COLOR + " INTEGER," +
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
LoyaltyCardDbIds.BARCODE_ID + " TEXT," +
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," +
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0' )");
db.execSQL("INSERT INTO tmp (" +
LoyaltyCardDbIds.ID + " ," +
LoyaltyCardDbIds.STORE + " ," +
LoyaltyCardDbIds.NOTE + " ," +
LoyaltyCardDbIds.EXPIRY + " ," +
LoyaltyCardDbIds.BALANCE + " ," +
LoyaltyCardDbIds.BALANCE_TYPE + " ," +
LoyaltyCardDbIds.HEADER_COLOR + " ," +
LoyaltyCardDbIds.CARD_ID + " ," +
LoyaltyCardDbIds.BARCODE_ID + " ," +
LoyaltyCardDbIds.BARCODE_TYPE + " ," +
LoyaltyCardDbIds.STAR_STATUS + ")" +
" SELECT " +
LoyaltyCardDbIds.ID + " ," +
LoyaltyCardDbIds.STORE + " ," +
LoyaltyCardDbIds.NOTE + " ," +
LoyaltyCardDbIds.EXPIRY + " ," +
LoyaltyCardDbIds.BALANCE + " ," +
LoyaltyCardDbIds.BALANCE_TYPE + " ," +
LoyaltyCardDbIds.HEADER_COLOR + " ," +
LoyaltyCardDbIds.CARD_ID + " ," +
LoyaltyCardDbIds.BARCODE_ID + " ," +
" NULLIF(" + LoyaltyCardDbIds.BARCODE_TYPE + ",'') ," +
LoyaltyCardDbIds.STAR_STATUS +
" FROM " + LoyaltyCardDbIds.TABLE);
db.execSQL("DROP TABLE " + LoyaltyCardDbIds.TABLE);
db.execSQL("create table " + LoyaltyCardDbIds.TABLE + "(" +
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
LoyaltyCardDbIds.STORE + " TEXT not null," +
LoyaltyCardDbIds.NOTE + " TEXT not null," +
LoyaltyCardDbIds.EXPIRY + " INTEGER," +
LoyaltyCardDbIds.BALANCE + " TEXT not null DEFAULT '0'," +
LoyaltyCardDbIds.BALANCE_TYPE + " TEXT," +
LoyaltyCardDbIds.HEADER_COLOR + " INTEGER," +
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
LoyaltyCardDbIds.BARCODE_ID + " TEXT," +
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," +
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0' )");
db.execSQL("INSERT INTO " + LoyaltyCardDbIds.TABLE + "(" +
LoyaltyCardDbIds.ID + " ," +
LoyaltyCardDbIds.STORE + " ," +
LoyaltyCardDbIds.NOTE + " ," +
LoyaltyCardDbIds.EXPIRY + " ," +
LoyaltyCardDbIds.BALANCE + " ," +
LoyaltyCardDbIds.BALANCE_TYPE + " ," +
LoyaltyCardDbIds.HEADER_COLOR + " ," +
LoyaltyCardDbIds.CARD_ID + " ," +
LoyaltyCardDbIds.BARCODE_ID + " ," +
LoyaltyCardDbIds.BARCODE_TYPE + " ," +
LoyaltyCardDbIds.STAR_STATUS + ")" +
" SELECT " +
LoyaltyCardDbIds.ID + " ," +
LoyaltyCardDbIds.STORE + " ," +
LoyaltyCardDbIds.NOTE + " ," +
LoyaltyCardDbIds.EXPIRY + " ," +
LoyaltyCardDbIds.BALANCE + " ," +
LoyaltyCardDbIds.BALANCE_TYPE + " ," +
LoyaltyCardDbIds.HEADER_COLOR + " ," +
LoyaltyCardDbIds.CARD_ID + " ," +
LoyaltyCardDbIds.BARCODE_ID + " ," +
LoyaltyCardDbIds.BARCODE_TYPE + " ," +
LoyaltyCardDbIds.STAR_STATUS +
" FROM tmp");
db.execSQL("DROP TABLE tmp");
db.setTransactionSuccessful();
db.endTransaction();
}
}
public long insertLoyaltyCard(final String store, final String note, final String cardId,
final String barcodeType, final Integer headerColor,
final Integer headerTextColor, final int starStatus)
public long insertLoyaltyCard(final String store, final String note, final Date expiry,
final BigDecimal balance, final Currency balanceType,
final String cardId, final String barcodeId,
final BarcodeFormat barcodeType, final Integer headerColor,
final int starStatus)
{
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.STORE, store);
contentValues.put(LoyaltyCardDbIds.NOTE, note);
contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null);
contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString());
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.toString() : null);
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, headerTextColor);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
return newId;
}
public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id,
final String store, final String note, final String cardId,
final String barcodeType, final Integer headerColor,
final Integer headerTextColor, final int starStatus)
public boolean insertLoyaltyCard(final SQLiteDatabase db, final String store,
final String note, final Date expiry, final BigDecimal balance,
final Currency balanceType, final String cardId,
final String barcodeId, final BarcodeFormat barcodeType,
final Integer headerColor, final int starStatus)
{
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.ID, id);
contentValues.put(LoyaltyCardDbIds.STORE, store);
contentValues.put(LoyaltyCardDbIds.NOTE, note);
contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null);
contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString());
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.toString() : null);
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, headerTextColor);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus);
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
return (newId != -1);
}
public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id, final String store,
final String note, final Date expiry, final BigDecimal balance,
final Currency balanceType, final String cardId,
final String barcodeId, final BarcodeFormat barcodeType,
final Integer headerColor, final int starStatus)
{
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.ID, id);
contentValues.put(LoyaltyCardDbIds.STORE, store);
contentValues.put(LoyaltyCardDbIds.NOTE, note);
contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null);
contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString());
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.toString() : null);
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus);
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
return (newId != -1);
}
public boolean updateLoyaltyCard(final int id, final String store, final String note,
final String cardId, final String barcodeType,
final Integer headerColor, final Integer headerTextColor)
final Date expiry, final BigDecimal balance,
final Currency balanceType, final String cardId,
final String barcodeId, final BarcodeFormat barcodeType,
final Integer headerColor)
{
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.STORE, store);
contentValues.put(LoyaltyCardDbIds.NOTE, note);
contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null);
contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString());
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.toString() : null);
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, headerTextColor);
int rowsUpdated = db.update(LoyaltyCardDbIds.TABLE, contentValues,
LoyaltyCardDbIds.ID + "=?",
new String[]{Integer.toString(id)});
@@ -149,6 +357,7 @@ public class DBHelper extends SQLiteOpenHelper
if(data.getCount() == 1)
{
data.moveToFirst();
card = LoyaltyCard.toLoyaltyCard(data);
}
@@ -157,12 +366,79 @@ public class DBHelper extends SQLiteOpenHelper
return card;
}
public List<Group> getLoyaltyCardGroups(final int id)
{
SQLiteDatabase db = getReadableDatabase();
Cursor data = db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE + " g " +
" LEFT JOIN " + LoyaltyCardDbIdsGroups.TABLE + " ig ON ig." + LoyaltyCardDbIdsGroups.groupID + " = g." + LoyaltyCardDbGroups.ID +
" where " + LoyaltyCardDbIdsGroups.cardID + "=?" +
" ORDER BY " + LoyaltyCardDbIdsGroups.groupID, new String[]{String.format("%d", id)});
List<Group> groups = new ArrayList<>();
if (!data.moveToFirst()) {
data.close();
return groups;
}
groups.add(Group.toGroup(data));
while (data.moveToNext()) {
groups.add(Group.toGroup(data));
}
data.close();
return groups;
}
public void setLoyaltyCardGroups(final int id, List<Group> groups)
{
SQLiteDatabase db = getWritableDatabase();
// First delete lookup table entries associated with this card
db.delete(LoyaltyCardDbIdsGroups.TABLE,
LoyaltyCardDbIdsGroups.cardID + " = ? ",
new String[]{String.format("%d", id)});
// Then create entries for selected values
for (Group group : groups) {
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIdsGroups.cardID, id);
contentValues.put(LoyaltyCardDbIdsGroups.groupID, group._id);
db.insert(LoyaltyCardDbIdsGroups.TABLE, null, contentValues);
}
}
public void setLoyaltyCardGroups(final SQLiteDatabase db, final int id, List<Group> groups)
{
// First delete lookup table entries associated with this card
db.delete(LoyaltyCardDbIdsGroups.TABLE,
LoyaltyCardDbIdsGroups.cardID + " = ? ",
new String[]{String.format("%d", id)});
// Then create entries for selected values
for (Group group : groups) {
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIdsGroups.cardID, id);
contentValues.put(LoyaltyCardDbIdsGroups.groupID, group._id);
db.insert(LoyaltyCardDbIdsGroups.TABLE, null, contentValues);
}
}
public boolean deleteLoyaltyCard (final int id)
{
SQLiteDatabase db = getWritableDatabase();
int rowsDeleted = db.delete(LoyaltyCardDbIds.TABLE,
// Delete card
int rowsDeleted = db.delete(LoyaltyCardDbIds.TABLE,
LoyaltyCardDbIds.ID + " = ? ",
new String[]{String.format("%d", id)});
// And delete lookup table entries associated with this card
db.delete(LoyaltyCardDbIdsGroups.TABLE,
LoyaltyCardDbIdsGroups.cardID + " = ? ",
new String[]{String.format("%d", id)});
return (rowsDeleted == 1);
}
@@ -179,16 +455,52 @@ public class DBHelper extends SQLiteOpenHelper
* @return Cursor
*/
public Cursor getLoyaltyCardCursor(final String filter)
{
return getLoyaltyCardCursor(filter, null);
}
/**
* Returns a cursor to all loyalty cards with the filter text in either the store or note in a certain group.
*
* @param filter
* @param group
* @return Cursor
*/
public Cursor getLoyaltyCardCursor(final String filter, Group group)
{
String actualFilter = String.format("%%%s%%", filter);
String[] selectionArgs = { actualFilter, actualFilter };
StringBuilder groupFilter = new StringBuilder();
String limitString = "";
SQLiteDatabase db = getReadableDatabase();
if (group != null) {
List<Integer> allowedIds = getGroupCardIds(group._id);
// Empty group
if (allowedIds.size() > 0) {
groupFilter.append("AND (");
for (int i = 0; i < allowedIds.size(); i++) {
groupFilter.append(LoyaltyCardDbIds.ID + " = " + allowedIds.get(i));
if (i != allowedIds.size() - 1) {
groupFilter.append(" OR ");
}
}
groupFilter.append(") ");
} else {
limitString = "LIMIT 0";
}
}
Cursor res = db.rawQuery("select * from " + LoyaltyCardDbIds.TABLE +
" WHERE " + LoyaltyCardDbIds.STORE + " LIKE ? " +
" OR " + LoyaltyCardDbIds.NOTE + " LIKE ? " +
" ORDER BY " + LoyaltyCardDbIds.STAR_STATUS + " DESC," + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC", selectionArgs, null);
" WHERE (" + LoyaltyCardDbIds.STORE + " LIKE ? " +
" OR " + LoyaltyCardDbIds.NOTE + " LIKE ? )" +
groupFilter.toString() +
" ORDER BY " + LoyaltyCardDbIds.STAR_STATUS + " DESC," + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC " +
limitString, selectionArgs, null);
return res;
}
@@ -227,5 +539,230 @@ public class DBHelper extends SQLiteOpenHelper
return numItems;
}
/**
* Returns a cursor to all groups.
*
* @return Cursor
*/
public Cursor getGroupCursor()
{
SQLiteDatabase db = getReadableDatabase();
Cursor res = db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE +
" ORDER BY " + LoyaltyCardDbGroups.ORDER + " ASC," + LoyaltyCardDbGroups.ID + " COLLATE NOCASE ASC", null, null);
return res;
}
public List<Group> getGroups() {
Cursor data = getGroupCursor();
List<Group> groups = new ArrayList<>();
if (!data.moveToFirst()) {
data.close();
return groups;
}
groups.add(Group.toGroup(data));
while (data.moveToNext()) {
groups.add(Group.toGroup(data));
}
data.close();
return groups;
}
public void reorderGroups(final List<Group> groups)
{
Integer order = 0;
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues;
for (Group group : groups)
{
contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbGroups.ORDER, order);
db.update(LoyaltyCardDbGroups.TABLE, contentValues,
LoyaltyCardDbGroups.ID + "=?",
new String[]{group._id});
order++;
}
}
public Group getGroup(final String groupName)
{
SQLiteDatabase db = getReadableDatabase();
Cursor data = db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE +
" where " + LoyaltyCardDbGroups.ID + "=?", new String[]{groupName});
Group group = null;
if(data.getCount() == 1)
{
data.moveToFirst();
group = Group.toGroup(data);
}
data.close();
return group;
}
public int getGroupCount()
{
SQLiteDatabase db = getReadableDatabase();
Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbGroups.TABLE, null);
int numItems = 0;
if(data.getCount() == 1)
{
data.moveToFirst();
numItems = data.getInt(0);
}
data.close();
return numItems;
}
public List<Integer> getGroupCardIds(final String groupName)
{
SQLiteDatabase db = getReadableDatabase();
Cursor data = db.rawQuery("SELECT " + LoyaltyCardDbIdsGroups.cardID +
" FROM " + LoyaltyCardDbIdsGroups.TABLE +
" WHERE " + LoyaltyCardDbIdsGroups.groupID + " =? ", new String[]{groupName});
List<Integer> cardIds = new ArrayList<>();
if (!data.moveToFirst()) {
return cardIds;
}
cardIds.add(data.getInt(0));
while (data.moveToNext()) {
cardIds.add(data.getInt(0));
}
data.close();
return cardIds;
}
public long insertGroup(final String name)
{
if (name.isEmpty()) return -1;
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbGroups.ID, name);
contentValues.put(LoyaltyCardDbGroups.ORDER, getGroupCount());
final long newId = db.insert(LoyaltyCardDbGroups.TABLE, null, contentValues);
return newId;
}
public boolean insertGroup(final SQLiteDatabase db, final String name)
{
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbGroups.ID, name);
contentValues.put(LoyaltyCardDbGroups.ORDER, getGroupCount());
final long newId = db.insert(LoyaltyCardDbGroups.TABLE, null, contentValues);
return (newId != -1);
}
public boolean updateGroup(final String groupName, final String newName)
{
if (newName.isEmpty()) return false;
boolean success = false;
SQLiteDatabase db = getWritableDatabase();
ContentValues groupContentValues = new ContentValues();
groupContentValues.put(LoyaltyCardDbGroups.ID, newName);
ContentValues lookupContentValues = new ContentValues();
lookupContentValues.put(LoyaltyCardDbIdsGroups.groupID, newName);
db.beginTransaction();
try {
// Update group name
int groupsChanged = db.update(LoyaltyCardDbGroups.TABLE, groupContentValues,
LoyaltyCardDbGroups.ID + "=?",
new String[]{groupName});
// Also update lookup tables
db.update(LoyaltyCardDbIdsGroups.TABLE, lookupContentValues,
LoyaltyCardDbIdsGroups.groupID + "=?",
new String[]{groupName});
if (groupsChanged == 1) {
db.setTransactionSuccessful();
success = true;
}
} catch (SQLiteException e) {
} finally {
db.endTransaction();
}
return success;
}
public boolean deleteGroup(final String groupName)
{
boolean success = false;
SQLiteDatabase db = getWritableDatabase();
db.beginTransaction();
try {
// Delete group
int groupsDeleted = db.delete(LoyaltyCardDbGroups.TABLE,
LoyaltyCardDbGroups.ID + " = ? ",
new String[]{groupName});
// And delete lookup table entries associated with this group
db.delete(LoyaltyCardDbIdsGroups.TABLE,
LoyaltyCardDbIdsGroups.groupID + " = ? ",
new String[]{groupName});
if (groupsDeleted == 1) {
db.setTransactionSuccessful();
success = true;
}
} finally {
db.endTransaction();
}
return success;
}
public int getGroupCardCount(final String groupName)
{
SQLiteDatabase db = getReadableDatabase();
Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbIdsGroups.TABLE +
" where " + LoyaltyCardDbIdsGroups.groupID + "=?",
new String[]{groupName});
int numItems = 0;
if(data.getCount() == 1)
{
data.moveToFirst();
numItems = data.getInt(0);
}
data.close();
return numItems;
}
}

View File

@@ -2,7 +2,8 @@ package protect.card_locker;
public enum DataFormat
{
CSV,
Catima,
Fidme,
VoucherVault
;
}

View File

@@ -1,19 +0,0 @@
package protect.card_locker;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* Interface for a class which can import the contents of a stream
* into the database.
*/
public interface DatabaseImporter
{
/**
* Import data from the input stream in a given format into
* the database.
* @throws IOException
* @throws FormatException
*/
void importData(DBHelper db, InputStreamReader input) throws IOException, FormatException, InterruptedException;
}

View File

@@ -0,0 +1,20 @@
package protect.card_locker;
import android.database.Cursor;
public class Group
{
public final String _id;
public Group(final String _id)
{
this._id = _id;
}
public static Group toGroup(Cursor cursor)
{
String _id = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbGroups.ID));
return new Group(_id);
}
}

View File

@@ -0,0 +1,56 @@
package protect.card_locker;
import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.TextView;
import protect.card_locker.preferences.Settings;
class GroupCursorAdapter extends CursorAdapter
{
Settings settings;
DBHelper db;
public GroupCursorAdapter(Context context, Cursor cursor)
{
super(context, cursor, 0);
settings = new Settings(context);
db = new DBHelper(context);
}
// The newView method is used to inflate a new view and return it,
// you don't bind any data to the view at this point.
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent)
{
return LayoutInflater.from(context).inflate(R.layout.group_layout, parent, false);
}
// The bindView method is used to bind all data to a given view
// such as setting the text on a TextView.
@Override
public void bindView(View view, Context context, Cursor cursor)
{
// Find fields to populate in inflated template
TextView nameField = view.findViewById(R.id.name);
TextView countField = view.findViewById(R.id.cardCount);
// Extract properties from cursor
Group group = Group.toGroup(cursor);
Integer groupCardCount = db.getGroupCardCount(group._id);
// Populate fields with extracted properties
nameField.setText(group._id);
countField.setText(context.getResources().getQuantityString(R.plurals.groupCardCount, groupCardCount, groupCardCount));
nameField.setTextSize(settings.getFontSizeMax(settings.getMediumFont()));
countField.setTextSize(settings.getFontSizeMax(settings.getSmallFont()));
}
}

View File

@@ -2,22 +2,12 @@ package protect.card_locker;
import android.Manifest;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
@@ -30,7 +20,13 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class ImportExportActivity extends AppCompatActivity
{
@@ -38,10 +34,14 @@ public class ImportExportActivity extends AppCompatActivity
private static final int PERMISSIONS_EXTERNAL_STORAGE = 1;
private static final int CHOOSE_EXPORT_LOCATION = 2;
private static final int CHOOSE_EXPORTED_FILE = 3;
private static final int IMPORT = 3;
private ImportExportTask importExporter;
private String importAlertTitle;
private String importAlertMessage;
private DataFormat importDataFormat;
@Override
protected void onCreate(Bundle savedInstanceState)
{
@@ -96,18 +96,10 @@ public class ImportExportActivity extends AppCompatActivity
@Override
public void onClick(View v)
{
chooseFileWithIntent(intentGetContentAction, CHOOSE_EXPORTED_FILE);
chooseImportType(intentGetContentAction);
}
});
if(isCallable(getApplicationContext(), intentGetContentAction) == false)
{
findViewById(R.id.dividerImportFilesystem).setVisibility(View.GONE);
findViewById(R.id.importOptionFilesystemTitle).setVisibility(View.GONE);
findViewById(R.id.importOptionFilesystemExplanation).setVisibility(View.GONE);
importFilesystem.setVisibility(View.GONE);
}
// Check that there is an app that data can be imported from
final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
@@ -117,20 +109,60 @@ public class ImportExportActivity extends AppCompatActivity
@Override
public void onClick(View v)
{
chooseFileWithIntent(intentPickAction, CHOOSE_EXPORTED_FILE);
chooseImportType(intentPickAction);
}
});
if(isCallable(getApplicationContext(), intentPickAction) == false)
{
findViewById(R.id.dividerImportApplication).setVisibility(View.GONE);
findViewById(R.id.importOptionApplicationTitle).setVisibility(View.GONE);
findViewById(R.id.importOptionApplicationExplanation).setVisibility(View.GONE);
importApplication.setVisibility(View.GONE);
}
}
private void startImport(final InputStream target, final Uri targetUri)
private void chooseImportType(Intent baseIntent) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.chooseImportType)
.setItems(R.array.import_types_array, (dialog, which) -> {
switch (which) {
// Catima
case 0:
importAlertTitle = getString(R.string.importCatima);
importAlertMessage = getString(R.string.importCatimaMessage);
importDataFormat = DataFormat.Catima;
break;
// Fidme
case 1:
importAlertTitle = getString(R.string.importFidme);
importAlertMessage = getString(R.string.importFidmeMessage);
importDataFormat = DataFormat.Fidme;
break;
// Loyalty Card Keychain
case 2:
importAlertTitle = getString(R.string.importLoyaltyCardKeychain);
importAlertMessage = getString(R.string.importLoyaltyCardKeychainMessage);
importDataFormat = DataFormat.Catima;
break;
// Voucher Vault
case 3:
importAlertTitle = getString(R.string.importVoucherVault);
importAlertMessage = getString(R.string.importVoucherVaultMessage);
importDataFormat = DataFormat.VoucherVault;
break;
default:
throw new IllegalArgumentException("Unknown DataFormat");
}
new AlertDialog.Builder(this)
.setTitle(importAlertTitle)
.setMessage(importAlertMessage)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
chooseFileWithIntent(baseIntent, IMPORT);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
});
builder.show();
}
private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat)
{
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
@@ -142,7 +174,7 @@ public class ImportExportActivity extends AppCompatActivity
};
importExporter = new ImportExportTask(ImportExportActivity.this,
DataFormat.CSV, target, listener);
dataFormat, target, listener);
importExporter.execute();
}
@@ -158,7 +190,7 @@ public class ImportExportActivity extends AppCompatActivity
};
importExporter = new ImportExportTask(ImportExportActivity.this,
DataFormat.CSV, target, listener);
DataFormat.Catima, target, listener);
importExporter.execute();
}
@@ -295,31 +327,6 @@ public class ImportExportActivity extends AppCompatActivity
builder.create().show();
}
/**
* Determines if there is at least one activity that can perform the given intent
*/
private boolean isCallable(Context context, final Intent intent)
{
PackageManager manager = context.getPackageManager();
if(manager == null)
{
return false;
}
List<ResolveInfo> list = manager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for(ResolveInfo info : list)
{
if(info.activityInfo.exported)
{
// There is one activity which is available to be called
return true;
}
}
return false;
}
private void chooseFileWithIntent(Intent intent, int requestCode)
{
try
@@ -328,6 +335,7 @@ public class ImportExportActivity extends AppCompatActivity
}
catch (ActivityNotFoundException e)
{
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
}
@@ -337,7 +345,7 @@ public class ImportExportActivity extends AppCompatActivity
{
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK || (requestCode != CHOOSE_EXPORT_LOCATION && requestCode != CHOOSE_EXPORTED_FILE))
if (resultCode != RESULT_OK)
{
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
return;
@@ -379,8 +387,9 @@ public class ImportExportActivity extends AppCompatActivity
reader = new FileInputStream(new File(uri.toString()));
}
Log.e(TAG, "Starting file export with: " + uri.toString());
startImport(reader, uri);
Log.e(TAG, "Starting file import with: " + uri.toString());
startImport(reader, uri, importDataFormat);
}
}
catch(FileNotFoundException e)

View File

@@ -8,11 +8,13 @@ import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import protect.card_locker.importexport.MultiFormatExporter;
import protect.card_locker.importexport.MultiFormatImporter;
class ImportExportTask extends AsyncTask<Void, Void, Boolean>
{
private static final String TAG = "Catima";
@@ -58,16 +60,8 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
{
boolean result = false;
try
{
InputStreamReader reader = new InputStreamReader(stream, Charset.forName("UTF-8"));
result = MultiFormatImporter.importData(db, reader, format);
reader.close();
}
catch(IOException e)
{
Log.e(TAG, "Unable to import file", e);
}
result = MultiFormatImporter.importData(db, stream, format);
Log.i(TAG, "Import result: " + result);

View File

@@ -3,18 +3,26 @@ package protect.card_locker;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import com.google.zxing.BarcodeFormat;
import java.io.InvalidObjectException;
import java.math.BigDecimal;
import java.util.Currency;
import java.util.Date;
import java.util.List;
public class ImportURIHelper {
private static final String STORE = DBHelper.LoyaltyCardDbIds.STORE;
private static final String NOTE = DBHelper.LoyaltyCardDbIds.NOTE;
private static final String EXPIRY = DBHelper.LoyaltyCardDbIds.EXPIRY;
private static final String BALANCE = DBHelper.LoyaltyCardDbIds.BALANCE;
private static final String BALANCE_TYPE = DBHelper.LoyaltyCardDbIds.BALANCE_TYPE;
private static final String CARD_ID = DBHelper.LoyaltyCardDbIds.CARD_ID;
private static final String BARCODE_ID = DBHelper.LoyaltyCardDbIds.BARCODE_ID;
private static final String BARCODE_TYPE = DBHelper.LoyaltyCardDbIds.BARCODE_TYPE;
private static final String HEADER_COLOR = DBHelper.LoyaltyCardDbIds.HEADER_COLOR;
private static final String HEADER_TEXT_COLOR = DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR;
private final Context context;
private final String host;
@@ -22,6 +30,7 @@ public class ImportURIHelper {
private final String oldHost;
private final String oldPath;
private final String shareText;
private final String shareMultipleText;
public ImportURIHelper(Context context) {
this.context = context;
@@ -30,6 +39,7 @@ public class ImportURIHelper {
oldHost = "brarcher.github.io";
oldPath = "/loyalty-card-locker/share";
shareText = context.getResources().getString(R.string.intent_import_card_from_url_share_text);
shareMultipleText = context.getResources().getString(R.string.intent_import_card_from_url_share_multiple_text);
}
private boolean isImportUri(Uri uri) {
@@ -43,27 +53,47 @@ public class ImportURIHelper {
try {
// These values are allowed to be null
BarcodeFormat barcodeType = null;
Date expiry = null;
BigDecimal balance = new BigDecimal("0");
Currency balanceType = null;
Integer headerColor = null;
Integer headerTextColor = null;
String store = uri.getQueryParameter(STORE);
String note = uri.getQueryParameter(NOTE);
String cardId = uri.getQueryParameter(CARD_ID);
String barcodeType = uri.getQueryParameter(BARCODE_TYPE);
if (store == null || note == null || cardId == null || barcodeType == null) throw new InvalidObjectException("Not a valid import URI");
String barcodeId = uri.getQueryParameter(BARCODE_ID);
if (store == null || note == null || cardId == null) throw new InvalidObjectException("Not a valid import URI");
String unparsedBarcodeType = uri.getQueryParameter(BARCODE_TYPE);
if(unparsedBarcodeType != null && !unparsedBarcodeType.equals(""))
{
barcodeType = BarcodeFormat.valueOf(unparsedBarcodeType);
}
String unparsedBalance = uri.getQueryParameter(BALANCE);
if(unparsedBalance != null && !unparsedBalance.equals(""))
{
balance = new BigDecimal(unparsedBalance);
}
String unparsedBalanceType = uri.getQueryParameter(BALANCE_TYPE);
if (unparsedBalanceType != null && !unparsedBalanceType.equals(""))
{
balanceType = Currency.getInstance(unparsedBalanceType);
}
String unparsedExpiry = uri.getQueryParameter(EXPIRY);
if(unparsedExpiry != null && !unparsedExpiry.equals(""))
{
expiry = new Date(Long.parseLong(unparsedExpiry));
}
String unparsedHeaderColor = uri.getQueryParameter(HEADER_COLOR);
if(unparsedHeaderColor != null)
{
headerColor = Integer.parseInt(unparsedHeaderColor);
}
String unparsedHeaderTextColor = uri.getQueryParameter(HEADER_TEXT_COLOR);
if(unparsedHeaderTextColor != null)
{
headerTextColor = Integer.parseInt(unparsedHeaderTextColor);
}
return new LoyaltyCard(-1, store, note, cardId, barcodeType, headerColor, headerTextColor, 0);
return new LoyaltyCard(-1, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0);
} catch (NullPointerException | NumberFormatException ex) {
throw new InvalidObjectException("Not a valid import URI");
}
@@ -77,31 +107,55 @@ public class ImportURIHelper {
uriBuilder.path(path);
uriBuilder.appendQueryParameter(STORE, loyaltyCard.store);
uriBuilder.appendQueryParameter(NOTE, loyaltyCard.note);
uriBuilder.appendQueryParameter(CARD_ID, loyaltyCard.cardId);
uriBuilder.appendQueryParameter(BARCODE_TYPE, loyaltyCard.barcodeType);
if(loyaltyCard.headerColor != null)
{
uriBuilder.appendQueryParameter(HEADER_COLOR, loyaltyCard.headerColor.toString());
uriBuilder.appendQueryParameter(BALANCE, loyaltyCard.balance.toString());
if (loyaltyCard.balanceType != null) {
uriBuilder.appendQueryParameter(BALANCE_TYPE, loyaltyCard.balanceType.getCurrencyCode());
}
if(loyaltyCard.headerTextColor != null)
{
uriBuilder.appendQueryParameter(HEADER_TEXT_COLOR, loyaltyCard.headerTextColor.toString());
if (loyaltyCard.expiry != null) {
uriBuilder.appendQueryParameter(EXPIRY, String.valueOf(loyaltyCard.expiry.getTime()));
}
uriBuilder.appendQueryParameter(CARD_ID, loyaltyCard.cardId);
if(loyaltyCard.barcodeId != null) {
uriBuilder.appendQueryParameter(BARCODE_ID, loyaltyCard.barcodeId);
}
if(loyaltyCard.barcodeType != null) {
uriBuilder.appendQueryParameter(BARCODE_TYPE, loyaltyCard.barcodeType.toString());
}
if(loyaltyCard.headerColor != null) {
uriBuilder.appendQueryParameter(HEADER_COLOR, loyaltyCard.headerColor.toString());
}
//StarStatus will not be exported
return uriBuilder.build();
}
private void startShareIntent(Uri uri) {
public void startShareIntent(List<LoyaltyCard> loyaltyCards) {
int loyaltyCardCount = loyaltyCards.size();
StringBuilder text = new StringBuilder();
if (loyaltyCardCount == 1) {
text.append(shareText);
} else {
text.append(shareMultipleText);
}
text.append("\n\n");
for (int i = 0; i < loyaltyCardCount; i++) {
LoyaltyCard loyaltyCard = loyaltyCards.get(i);
text.append(loyaltyCard.store + ": " + toUri(loyaltyCard));
if (i < (loyaltyCardCount - 1)) {
text.append("\n\n");
}
}
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, shareText + "\n" + uri.toString());
sendIntent.putExtra(Intent.EXTRA_TEXT, text.toString());
sendIntent.setType("text/plain");
Intent shareIntent = Intent.createChooser(sendIntent, null);
context.startActivity(shareIntent);
}
public void startShareIntent(LoyaltyCard loyaltyCard) {
startShareIntent(toUri(loyaltyCard));
}
}

View File

@@ -1,6 +1,13 @@
package protect.card_locker;
import android.database.Cursor;
import com.google.zxing.BarcodeFormat;
import java.math.BigDecimal;
import java.util.Currency;
import java.util.Date;
import androidx.annotation.Nullable;
public class LoyaltyCard
@@ -8,27 +15,36 @@ public class LoyaltyCard
public final int id;
public final String store;
public final String note;
public final Date expiry;
public final BigDecimal balance;
public final Currency balanceType;
public final String cardId;
public final String barcodeType;
@Nullable
public final String barcodeId;
public final BarcodeFormat barcodeType;
@Nullable
public final Integer headerColor;
@Nullable
public final Integer headerTextColor;
public final int starStatus;
public LoyaltyCard(final int id, final String store, final String note, final String cardId,
final String barcodeType, final Integer headerColor, final Integer headerTextColor,final int starStatus)
public LoyaltyCard(final int id, final String store, final String note, final Date expiry,
final BigDecimal balance, final Currency balanceType, final String cardId,
final String barcodeId, final BarcodeFormat barcodeType, final Integer headerColor,
final int starStatus)
{
this.id = id;
this.store = store;
this.note = note;
this.expiry = expiry;
this.balance = balance;
this.balanceType = balanceType;
this.cardId = cardId;
this.barcodeId = barcodeId;
this.barcodeType = barcodeType;
this.headerColor = headerColor;
this.headerTextColor = headerTextColor;
this.starStatus = starStatus;
}
@@ -37,27 +53,41 @@ public class LoyaltyCard
int id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
String store = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE));
String note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE));
long expiryLong = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.EXPIRY));
BigDecimal balance = new BigDecimal(cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE)));
String cardId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID));
String barcodeType = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE));
String barcodeId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_ID));
int starred = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS));
int barcodeTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
int balanceTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE);
int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR);
int headerTextColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR);
BarcodeFormat barcodeType = null;
Currency balanceType = null;
Date expiry = null;
Integer headerColor = null;
Integer headerTextColor = null;
if (cursor.isNull(barcodeTypeColumn) == false)
{
barcodeType = BarcodeFormat.valueOf(cursor.getString(barcodeTypeColumn));
}
if (cursor.isNull(balanceTypeColumn) == false)
{
balanceType = Currency.getInstance(cursor.getString(balanceTypeColumn));
}
if(expiryLong > 0)
{
expiry = new Date(expiryLong);
}
if(cursor.isNull(headerColorColumn) == false)
{
headerColor = cursor.getInt(headerColorColumn);
}
if(cursor.isNull(headerTextColorColumn) == false)
{
headerTextColor = cursor.getInt(headerTextColorColumn);
}
return new LoyaltyCard(id, store, note, cardId, barcodeType, headerColor, headerTextColor, starred);
return new LoyaltyCard(id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starred);
}
}

View File

@@ -0,0 +1,37 @@
package protect.card_locker;
import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
import android.content.Context;
import android.view.View;
public class LoyaltyCardAnimator {
private static AnimatorSet selectedViewIn, defaultViewOut, selectedViewOut, defaultViewIn;
public static void flipView(Context inputContext, final View inputSelectedView, final View inputDefaultView, boolean inputItemSelected) {
selectedViewIn = (AnimatorSet) AnimatorInflater.loadAnimator(inputContext, R.animator.flip_left_in);
defaultViewOut = (AnimatorSet) AnimatorInflater.loadAnimator(inputContext, R.animator.flip_right_out);
selectedViewOut = (AnimatorSet) AnimatorInflater.loadAnimator(inputContext, R.animator.flip_left_out);
defaultViewIn = (AnimatorSet) AnimatorInflater.loadAnimator(inputContext, R.animator.flip_right_in);
final AnimatorSet showFrontAnim = new AnimatorSet();
final AnimatorSet showBackAnim = new AnimatorSet();
selectedViewIn.setTarget(inputSelectedView);
defaultViewOut.setTarget(inputDefaultView);
showFrontAnim.playTogether(selectedViewIn, defaultViewOut);
selectedViewOut.setTarget(inputSelectedView);
defaultViewIn.setTarget(inputDefaultView);
showBackAnim.playTogether(defaultViewIn, selectedViewOut);
if (inputItemSelected) {
showFrontAnim.start();
} else {
showBackAnim.start();
}
}
}

View File

@@ -4,80 +4,294 @@ import android.content.Context;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.util.SparseBooleanArray;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.ArrayList;
import androidx.cardview.widget.CardView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import protect.card_locker.preferences.Settings;
class LoyaltyCardCursorAdapter extends CursorAdapter
public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCursorAdapter.LoyaltyCardListItemViewHolder>
{
Settings settings;
boolean darkModeEnabled;
private static int mCurrentSelectedIndex = -1;
private Cursor mCursor;
Settings mSettings;
boolean mDarkModeEnabled;
private Context mContext;
private CardAdapterListener mListener;
private SparseBooleanArray mSelectedItems;
private SparseBooleanArray mAnimationItemsIndex;
private boolean mReverseAllAnimations = false;
public LoyaltyCardCursorAdapter(Context context, Cursor cursor)
public LoyaltyCardCursorAdapter(Context inputContext, Cursor inputCursor, CardAdapterListener inputListener)
{
super(context, cursor, 0);
settings = new Settings(context);
darkModeEnabled= MainActivity.isDarkModeEnabled(context);
super(inputCursor);
mSettings = new Settings(inputContext);
mCursor = inputCursor;
mContext = inputContext;
mListener = inputListener;
mSelectedItems = new SparseBooleanArray();
mAnimationItemsIndex = new SparseBooleanArray();
mDarkModeEnabled = MainActivity.isDarkModeEnabled(inputContext);
}
// The newView method is used to inflate a new view and return it,
// you don't bind any data to the view at this point.
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent)
public LoyaltyCardListItemViewHolder onCreateViewHolder(ViewGroup inputParent, int inputViewType)
{
return LayoutInflater.from(context).inflate(R.layout.loyalty_card_layout, parent, false);
View itemView = LayoutInflater.from(inputParent.getContext()).inflate(R.layout.loyalty_card_layout, inputParent, false);
return new LoyaltyCardListItemViewHolder(itemView);
}
// The bindView method is used to bind all data to a given view
// such as setting the text on a TextView.
@Override
public void bindView(View view, Context context, Cursor cursor)
public Cursor getCursor()
{
// Find fields to populate in inflated template
ImageView thumbnail = view.findViewById(R.id.thumbnail);
TextView storeField = view.findViewById(R.id.store);
TextView noteField = view.findViewById(R.id.note);
ImageView star = view.findViewById(R.id.star);
return mCursor;
}
if(darkModeEnabled)
{
star.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
public void onBindViewHolder(LoyaltyCardListItemViewHolder inputHolder, Cursor inputCursor) {
if (mDarkModeEnabled) {
inputHolder.mStarIcon.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
}
// Extract properties from cursor
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(cursor);
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(inputCursor);
// Populate fields with extracted properties
storeField.setText(loyaltyCard.store);
inputHolder.mStoreField.setText(loyaltyCard.store);
inputHolder.mStoreField.setTextSize(mSettings.getFontSizeMax(mSettings.getMediumFont()));
if (!loyaltyCard.note.isEmpty()) {
inputHolder.mNoteField.setVisibility(View.VISIBLE);
inputHolder.mNoteField.setText(loyaltyCard.note);
inputHolder.mNoteField.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
} else {
inputHolder.mNoteField.setVisibility(View.GONE);
}
storeField.setTextSize(settings.getCardTitleListFontSize());
if (!loyaltyCard.balance.equals(new BigDecimal("0"))) {
inputHolder.mBalanceField.setVisibility(View.VISIBLE);
inputHolder.mBalanceField.setText(mContext.getString(R.string.balanceSentence, Utils.formatBalance(mContext, loyaltyCard.balance, loyaltyCard.balanceType)));
inputHolder.mBalanceField.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
} else {
inputHolder.mBalanceField.setVisibility(View.GONE);
}
if(!loyaltyCard.note.isEmpty())
if (loyaltyCard.expiry != null)
{
noteField.setVisibility(View.VISIBLE);
noteField.setText(loyaltyCard.note);
noteField.setTextSize(settings.getCardNoteListFontSize());
inputHolder.mExpiryField.setVisibility(View.VISIBLE);
int expiryString = R.string.expiryStateSentence;
if(Utils.hasExpired(loyaltyCard.expiry)) {
expiryString = R.string.expiryStateSentenceExpired;
inputHolder.mExpiryField.setTextColor(mContext.getResources().getColor(R.color.alert));
}
inputHolder.mExpiryField.setText(mContext.getString(expiryString, DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.expiry)));
inputHolder.mExpiryField.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
} else {
inputHolder.mExpiryField.setVisibility(View.GONE);
}
inputHolder.mStarIcon.setVisibility((loyaltyCard.starStatus != 0) ? View.VISIBLE : View.GONE);
inputHolder.mCardIcon.setImageBitmap(Utils.generateIcon(mContext, loyaltyCard.store, loyaltyCard.headerColor).getLetterTile());
inputHolder.itemView.setActivated(mSelectedItems.get(inputCursor.getPosition(), false));
applyIconAnimation(inputHolder, inputCursor.getPosition());
applyClickEvents(inputHolder, inputCursor.getPosition());
}
private void applyClickEvents(LoyaltyCardListItemViewHolder inputHolder, final int inputPosition)
{
inputHolder.mThumbnailContainer.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View inputView)
{
mListener.onIconClicked(inputPosition);
}
});
inputHolder.mRow.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View inputView)
{
mListener.onRowClicked(inputPosition);
}
});
inputHolder.mInformationContainer.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View inputView)
{
mListener.onRowClicked(inputPosition);
}
});
inputHolder.mRow.setOnLongClickListener(new View.OnLongClickListener()
{
@Override
public boolean onLongClick(View inputView)
{
mListener.onRowLongClicked(inputPosition);
inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
return true;
}
});
inputHolder.mInformationContainer.setOnLongClickListener(new View.OnLongClickListener()
{
@Override
public boolean onLongClick(View inputView)
{
mListener.onRowLongClicked(inputPosition);
inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
return true;
}
});
}
private void applyIconAnimation(LoyaltyCardListItemViewHolder inputHolder, int inputPosition)
{
if (mSelectedItems.get(inputPosition, false))
{
inputHolder.mThumbnailFrontContainer.setVisibility(View.GONE);
resetIconYAxis(inputHolder.mThumbnailBackContainer);
inputHolder.mThumbnailBackContainer.setVisibility(View.VISIBLE);
inputHolder.mThumbnailBackContainer.setAlpha(1);
if (mCurrentSelectedIndex == inputPosition)
{
LoyaltyCardAnimator.flipView(mContext, inputHolder.mThumbnailBackContainer, inputHolder.mThumbnailFrontContainer, true);
resetCurrentIndex();
}
}
else
{
noteField.setVisibility(View.GONE);
inputHolder.mThumbnailBackContainer.setVisibility(View.GONE);
resetIconYAxis(inputHolder.mThumbnailFrontContainer);
inputHolder.mThumbnailFrontContainer.setVisibility(View.VISIBLE);
inputHolder.mThumbnailFrontContainer.setAlpha(1);
if ((mReverseAllAnimations && mAnimationItemsIndex.get(inputPosition, false)) || mCurrentSelectedIndex == inputPosition)
{
LoyaltyCardAnimator.flipView(mContext, inputHolder.mThumbnailBackContainer, inputHolder.mThumbnailFrontContainer, false);
resetCurrentIndex();
}
}
}
private void resetIconYAxis(View inputView)
{
if (inputView.getRotationY() != 0)
{
inputView.setRotationY(0);
}
}
public void resetAnimationIndex()
{
mReverseAllAnimations = false;
mAnimationItemsIndex.clear();
}
@SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
public void toggleSelection(int inputPosition)
{
mCurrentSelectedIndex = inputPosition;
if (mSelectedItems.get(inputPosition, false))
{
mSelectedItems.delete(inputPosition);
mAnimationItemsIndex.delete(inputPosition);
}
else
{
mSelectedItems.put(inputPosition, true);
mAnimationItemsIndex.put(inputPosition, true);
}
notifyItemChanged(inputPosition);
}
public void clearSelections()
{
mReverseAllAnimations = true;
mSelectedItems.clear();
notifyDataSetChanged();
}
public int getSelectedItemCount()
{
return mSelectedItems.size();
}
public ArrayList<LoyaltyCard> getSelectedItems()
{
ArrayList<LoyaltyCard> result = new ArrayList<>();
int i;
for(i = 0; i < mSelectedItems.size(); i++)
{
mCursor.moveToPosition(mSelectedItems.keyAt(i));
result.add(LoyaltyCard.toLoyaltyCard(mCursor));
}
if (loyaltyCard.starStatus!=0) star.setVisibility(View.VISIBLE);
else star.setVisibility(View.GONE);
int tileLetterFontSize = context.getResources().getDimensionPixelSize(R.dimen.tileLetterFontSize);
int pixelSize = context.getResources().getDimensionPixelSize(R.dimen.cardThumbnailSize);
Integer letterBackgroundColor = loyaltyCard.headerColor;
Integer letterTextColor = loyaltyCard.headerTextColor;
LetterBitmap letterBitmap = new LetterBitmap(context, loyaltyCard.store, loyaltyCard.store,
tileLetterFontSize, pixelSize, pixelSize, letterBackgroundColor, letterTextColor);
thumbnail.setImageBitmap(letterBitmap.getLetterTile());
return result;
}
}
private void resetCurrentIndex()
{
mCurrentSelectedIndex = -1;
}
public interface CardAdapterListener
{
void onIconClicked(int inputPosition);
void onRowClicked(int inputPosition);
void onRowLongClicked(int inputPosition);
}
public class LoyaltyCardListItemViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener
{
public TextView mStoreField, mNoteField, mBalanceField, mExpiryField;
public LinearLayout mInformationContainer;
public ImageView mCardIcon, mStarIcon;
public CardView mThumbnailContainer;
public ConstraintLayout mRow;
public RelativeLayout mThumbnailFrontContainer, mThumbnailBackContainer;
public LoyaltyCardListItemViewHolder(View inputView)
{
super(inputView);
mThumbnailContainer = inputView.findViewById(R.id.thumbnail_container);
mRow = inputView.findViewById(R.id.row);
mThumbnailFrontContainer = inputView.findViewById(R.id.thumbnail_front);
mThumbnailBackContainer = inputView.findViewById(R.id.thumbnail_back);
mInformationContainer = inputView.findViewById(R.id.information_container);
mStoreField = inputView.findViewById(R.id.store);
mNoteField = inputView.findViewById(R.id.note);
mBalanceField = inputView.findViewById(R.id.balance);
mExpiryField = inputView.findViewById(R.id.expiry);
mCardIcon = inputView.findViewById(R.id.thumbnail);
mStarIcon = inputView.findViewById(R.id.star);
inputView.setOnLongClickListener(this);
}
@Override
public boolean onLongClick(View inputView)
{
mListener.onRowLongClicked(getAdapterPosition());
inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
return true;
}
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
package protect.card_locker;
import android.app.Application;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.app.AppCompatDelegate;
import protect.card_locker.preferences.Settings;
public class LoyaltyCardLockerApplication extends Application {

View File

@@ -1,51 +1,65 @@
package protect.card_locker;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.graphics.ColorUtils;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.widget.TextViewCompat;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.Toolbar;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.zxing.BarcodeFormat;
import protect.card_locker.preferences.Settings;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.Toolbar;
import androidx.constraintlayout.widget.Guideline;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.widget.TextViewCompat;
import protect.card_locker.preferences.Settings;
public class LoyaltyCardViewActivity extends AppCompatActivity
{
private static final String TAG = "Catima";
private static final double LUMINANCE_MIDPOINT = 0.5;
TextView cardIdFieldView;
BottomSheetBehavior behavior;
View bottomSheet;
ImageView bottomSheetButton;
TextView noteView;
View noteViewDivider;
TextView groupsView;
TextView balanceView;
TextView expiryView;
TextView storeName;
ImageButton maximizeButton;
ImageView barcodeImage;
ImageButton minimizeButton;
View collapsingToolbarLayout;
AppBarLayout appBarLayout;
int loyaltyCardId;
LoyaltyCard loyaltyCard;
boolean rotationEnabled;
@@ -54,12 +68,17 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
Settings settings;
String cardIdString;
String barcodeIdString;
BarcodeFormat format;
FloatingActionButton editButton;
Guideline centerGuideline;
SeekBar barcodeScaler;
boolean starred;
boolean backgroundNeedsDarkIcons;
boolean barcodeIsFullscreen = false;
ViewGroup.LayoutParams barcodeImageState;
private void extractIntentFields(Intent intent)
{
@@ -71,6 +90,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
private Drawable getIcon(int icon, boolean dark)
{
Drawable unwrappedIcon = AppCompatResources.getDrawable(this, icon);
assert unwrappedIcon != null;
Drawable wrappedIcon = DrawableCompat.wrap(unwrappedIcon);
if(dark)
{
@@ -95,39 +115,102 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
setContentView(R.layout.loyalty_card_view_layout);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
actionBar.setDisplayHomeAsUpEnabled(true);
}
db = new DBHelper(this);
importURIHelper = new ImportURIHelper(this);
cardIdFieldView = findViewById(R.id.cardIdView);
bottomSheet = findViewById(R.id.bottom_sheet);
bottomSheetButton = findViewById(R.id.bottomSheetButton);
noteView = findViewById(R.id.noteView);
noteViewDivider = findViewById(R.id.noteViewDivider);
groupsView = findViewById(R.id.groupsView);
balanceView = findViewById(R.id.balanceView);
expiryView = findViewById(R.id.expiryView);
storeName = findViewById(R.id.storeName);
maximizeButton = findViewById(R.id.maximizeButton);
barcodeImage = findViewById(R.id.barcode);
minimizeButton = findViewById(R.id.minimizeButton);
collapsingToolbarLayout = findViewById(R.id.collapsingToolbarLayout);
appBarLayout = findViewById(R.id.app_bar_layout);
centerGuideline = findViewById(R.id.centerGuideline);
centerGuideline.setGuidelinePercent(0.5f);
barcodeScaler = findViewById(R.id.barcodeScaler);
barcodeScaler.setProgress(100);
barcodeScaler.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
Log.d(TAG, "Progress is " + progress);
Log.d(TAG, "Max is " + barcodeScaler.getMax());
float scale = (float) progress / (float) barcodeScaler.getMax();
Log.d(TAG, "Scaling to " + scale);
redrawBarcodeAfterResize();
centerGuideline.setGuidelinePercent(0.5f * scale);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
rotationEnabled = true;
// Allow making barcode fullscreen on tap
barcodeImage.setOnClickListener(new View.OnClickListener() {
maximizeButton.setOnClickListener(v -> setFullscreen(true));
barcodeImage.setOnClickListener(view -> {
if (barcodeIsFullscreen)
{
setFullscreen(false);
}
else
{
setFullscreen(true);
}
});
minimizeButton.setOnClickListener(v -> setFullscreen(false));
editButton = findViewById(R.id.fabEdit);
editButton.setOnClickListener(v -> {
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle bundle = new Bundle();
bundle.putInt("id", loyaltyCardId);
bundle.putBoolean("update", true);
intent.putExtras(bundle);
startActivity(intent);
finish();
});
behavior = BottomSheetBehavior.from(bottomSheet);
behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onClick(View view) {
if(barcodeIsFullscreen)
{
setFullscreen(false);
}
else
{
setFullscreen(true);
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_DRAGGING) {
editButton.hide();
} else if (newState == BottomSheetBehavior.STATE_EXPANDED) {
bottomSheetButton.setImageResource(R.drawable.ic_baseline_arrow_drop_down_24);
editButton.hide();
} else if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
bottomSheetButton.setImageResource(R.drawable.ic_baseline_arrow_drop_up_24);
editButton.show();
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) { }
});
bottomSheetButton.setOnClickListener(v -> {
if (behavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else {
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
});
}
@@ -147,23 +230,28 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
Log.i(TAG, "To view card: " + loyaltyCardId);
if(barcodeIsFullscreen)
{
// Completely reset state
//
// This prevents the barcode from taking up the entire screen
// on resume and thus being stretched out of proportion.
recreate();
}
// The brightness value is on a scale from [0, ..., 1], where
// '1' is the brightest. We attempt to maximize the brightness
// to help barcode readers scan the barcode.
Window window = getWindow();
if(window != null && settings.useMaxBrightnessDisplayingBarcode())
if(window != null)
{
WindowManager.LayoutParams attributes = window.getAttributes();
attributes.screenBrightness = 1F;
if (settings.useMaxBrightnessDisplayingBarcode())
{
attributes.screenBrightness = 1F;
}
if (settings.getKeepScreenOn()) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
if (settings.getDisableLockscreenWhileViewingCard()) {
window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD|
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
}
window.setAttributes(attributes);
}
@@ -176,41 +264,79 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
return;
}
String formatString = loyaltyCard.barcodeType;
format = !formatString.isEmpty() ? BarcodeFormat.valueOf(formatString) : null;
setupOrientation();
format = loyaltyCard.barcodeType;
cardIdString = loyaltyCard.cardId;
barcodeIdString = loyaltyCard.barcodeId;
cardIdFieldView.setText(loyaltyCard.cardId);
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(cardIdFieldView,
getResources().getInteger(R.integer.settings_card_id_min_font_size_sp)-1, settings.getCardIdFontSize(),
settings.getFontSizeMin(settings.getLargeFont()), settings.getFontSizeMax(settings.getLargeFont()),
1, TypedValue.COMPLEX_UNIT_SP);
if(loyaltyCard.note.length() > 0)
{
noteView.setVisibility(View.VISIBLE);
noteView.setText(loyaltyCard.note);
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(noteView,
getResources().getInteger(R.integer.settings_card_note_min_font_size_sp)-1,
settings.getCardNoteFontSize(), 1, TypedValue.COMPLEX_UNIT_SP);
noteView.setTextSize(settings.getFontSizeMax(settings.getMediumFont()));
}
else
{
noteView.setVisibility(View.GONE);
noteViewDivider.setVisibility(View.GONE);
}
storeName.setText(loyaltyCard.store);
storeName.setTextSize(settings.getCardTitleFontSize());
List<Group> loyaltyCardGroups = db.getLoyaltyCardGroups(loyaltyCardId);
int textColor;
if(loyaltyCard.headerTextColor != null)
{
textColor = loyaltyCard.headerTextColor;
if(loyaltyCardGroups.size() > 0) {
StringBuilder groupsString = new StringBuilder();
for (Group group : loyaltyCardGroups) {
groupsString.append(group._id);
groupsString.append(" ");
}
groupsView.setVisibility(View.VISIBLE);
groupsView.setText(getString(R.string.groupsList, groupsString.toString()));
groupsView.setTextSize(settings.getFontSizeMax(settings.getMediumFont()));
}
else
{
textColor = Color.WHITE;
groupsView.setVisibility(View.GONE);
}
storeName.setTextColor(textColor);
if(!loyaltyCard.balance.equals(new BigDecimal(0))) {
balanceView.setVisibility(View.VISIBLE);
balanceView.setText(getString(R.string.balanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)));
balanceView.setTextSize(settings.getFontSizeMax(settings.getMediumFont()));
}
else
{
balanceView.setVisibility(View.GONE);
}
if(loyaltyCard.expiry != null) {
expiryView.setVisibility(View.VISIBLE);
int expiryString = R.string.expiryStateSentence;
if(Utils.hasExpired(loyaltyCard.expiry)) {
expiryString = R.string.expiryStateSentenceExpired;
expiryView.setTextColor(getResources().getColor(R.color.alert));
}
expiryView.setText(getString(expiryString, DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.expiry)));
expiryView.setTextSize(settings.getFontSizeMax(settings.getMediumFont()));
}
else
{
expiryView.setVisibility(View.GONE);
}
expiryView.setTag(loyaltyCard.expiry);
if (!barcodeIsFullscreen) {
makeBottomSheetVisibleIfUseful();
}
storeName.setText(loyaltyCard.store);
storeName.setTextSize(settings.getFontSizeMax(settings.getLargeFont()));
int backgroundHeaderColor;
if(loyaltyCard.headerColor != null)
@@ -223,9 +349,22 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
}
collapsingToolbarLayout.setBackgroundColor(backgroundHeaderColor);
appBarLayout.setBackgroundColor(backgroundHeaderColor);
int textColor;
if(Utils.needsDarkForeground(backgroundHeaderColor))
{
textColor = Color.BLACK;
}
else
{
textColor = Color.WHITE;
}
storeName.setTextColor(textColor);
((Toolbar) findViewById(R.id.toolbar_landscape)).setTitleTextColor(textColor);
// If the background is very bright, we should use dark icons
backgroundNeedsDarkIcons = (ColorUtils.calculateLuminance(backgroundHeaderColor) > LUMINANCE_MIDPOINT);
backgroundNeedsDarkIcons = Utils.needsDarkForeground(backgroundHeaderColor);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
@@ -245,51 +384,48 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
// Set shadow colour of store text so even same color on same color would be readable
storeName.setShadowLayer(1, 1, 1, backgroundNeedsDarkIcons ? Color.BLACK : Color.WHITE);
if(format != null)
Boolean isBarcodeSupported = true;
if (format != null && !BarcodeSelectorActivity.SUPPORTED_BARCODE_TYPES.contains(format.name())) {
isBarcodeSupported = false;
Toast.makeText(this, getString(R.string.unsupportedBarcodeType), Toast.LENGTH_LONG).show();
}
if(format != null && isBarcodeSupported)
{
findViewById(R.id.barcode).setVisibility(View.VISIBLE);
if (!barcodeIsFullscreen) {
maximizeButton.setVisibility(View.VISIBLE);
}
barcodeImage.setVisibility(View.VISIBLE);
if(barcodeImage.getHeight() == 0)
{
Log.d(TAG, "ImageView size is not known known at start, waiting for load");
// The size of the ImageView is not yet available as it has not
// yet been drawn. Wait for it to be drawn so the size is available.
barcodeImage.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener()
{
@Override
public void onGlobalLayout()
{
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Log.d(TAG, "ImageView size now known");
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
}
});
redrawBarcodeAfterResize();
}
else
{
Log.d(TAG, "ImageView size known known, creating barcode");
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
new BarcodeImageWriterTask(
barcodeImage,
barcodeIdString != null ? barcodeIdString : cardIdString,
format,
null,
false,
null)
.execute();
}
// Force redraw fullscreen state
setFullscreen(barcodeIsFullscreen);
}
else
{
findViewById(R.id.barcode).setVisibility(View.GONE);
maximizeButton.setVisibility(View.GONE);
barcodeImage.setVisibility(View.GONE);
}
FloatingActionButton editButton = findViewById(R.id.fabEdit);
editButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle bundle = new Bundle();
bundle.putInt("id", loyaltyCardId);
bundle.putBoolean("update", true);
intent.putExtras(bundle);
startActivity(intent);
finish();
}
});
}
@Override
@@ -301,7 +437,6 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
}
super.onBackPressed();
return;
}
@Override
@@ -353,7 +488,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
break;
case R.id.action_share:
importURIHelper.startShareIntent(loyaltyCard);
importURIHelper.startShareIntent(Arrays.asList(loyaltyCard));
return true;
case R.id.action_lock_unlock:
@@ -378,7 +513,40 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
return super.onOptionsItemSelected(item);
}
private void setupOrientation()
{
Toolbar portraitToolbar = findViewById(R.id.toolbar);
Toolbar landscapeToolbar = findViewById(R.id.toolbar_landscape);
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
Log.d(TAG, "Detected landscape mode");
setTitle(loyaltyCard.store);
collapsingToolbarLayout.setVisibility(View.GONE);
portraitToolbar.setVisibility(View.GONE);
landscapeToolbar.setVisibility(View.VISIBLE);
setSupportActionBar(landscapeToolbar);
} else {
Log.d(TAG, "Detected portrait mode");
setTitle("");
collapsingToolbarLayout.setVisibility(View.VISIBLE);
portraitToolbar.setVisibility(View.VISIBLE);
landscapeToolbar.setVisibility(View.GONE);
setSupportActionBar(portraitToolbar);
}
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
private void setOrientatonLock(MenuItem item, boolean lock)
{
if(lock)
@@ -396,6 +564,40 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
}
}
private void makeBottomSheetVisibleIfUseful()
{
if (noteView.getVisibility() == View.VISIBLE || groupsView.getVisibility() == View.VISIBLE || balanceView.getVisibility() == View.VISIBLE || expiryView.getVisibility() == View.VISIBLE) {
bottomSheet.setVisibility(View.VISIBLE);
}
else
{
bottomSheet.setVisibility(View.GONE);
}
}
private void redrawBarcodeAfterResize()
{
if (format != null) {
barcodeImage.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Log.d(TAG, "ImageView size now known");
new BarcodeImageWriterTask(
barcodeImage,
barcodeIdString != null ? barcodeIdString : cardIdString,
format,
null,
false,
null)
.execute();
}
});
};
}
/**
* When enabled, hides the status bar and moves the barcode to the top of the screen.
*
@@ -405,10 +607,16 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
private void setFullscreen(boolean enable)
{
ActionBar actionBar = getSupportActionBar();
if(enable && !barcodeIsFullscreen)
if(enable)
{
// Save previous barcodeImage state
barcodeImageState = barcodeImage.getLayoutParams();
Log.d(TAG, "Move into of fullscreen");
// Prepare redraw after size change
redrawBarcodeAfterResize();
// Hide maximize and show minimize button and scaler
maximizeButton.setVisibility(View.GONE);
minimizeButton.setVisibility(View.VISIBLE);
barcodeScaler.setVisibility(View.VISIBLE);
// Hide actionbar
if(actionBar != null)
@@ -416,55 +624,73 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
actionBar.hide();
}
// Hide collapsingToolbar
// Hide toolbars
//
// Appbar needs to be invisible and have padding removed
// Or the barcode will be centered instead of on top of the screen
// Don't ask me why...
appBarLayout.setVisibility(View.INVISIBLE);
appBarLayout.setPadding(0, 0, 0, 0);
collapsingToolbarLayout.setVisibility(View.GONE);
findViewById(R.id.toolbar_landscape).setVisibility(View.GONE);
// Hide other UI elements
cardIdFieldView.setVisibility(View.GONE);
bottomSheet.setVisibility(View.GONE);
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
editButton.hide();
// Set Android to fullscreen mode
getWindow().getDecorView().setSystemUiVisibility(
getWindow().getDecorView().getSystemUiVisibility()
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_FULLSCREEN
getWindow().getDecorView().getSystemUiVisibility()
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_FULLSCREEN
);
// Make barcode take all space
barcodeImage.setLayoutParams(new ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.MATCH_PARENT,
ConstraintLayout.LayoutParams.MATCH_PARENT
));
// Move barcode to top
barcodeImage.setScaleType(ImageView.ScaleType.FIT_START);
// Prevent centering
barcodeImage.setAdjustViewBounds(false);
// Set current state
barcodeIsFullscreen = true;
}
else if(!enable && barcodeIsFullscreen)
else if(!enable)
{
Log.d(TAG, "Move out of fullscreen");
// Reset center guideline
barcodeScaler.setProgress(100);
// Prepare redraw after size change
redrawBarcodeAfterResize();
// Show maximize and hide minimize button and scaler
maximizeButton.setVisibility(View.VISIBLE);
minimizeButton.setVisibility(View.GONE);
barcodeScaler.setVisibility(View.GONE);
// Show actionbar
if(actionBar != null)
{
actionBar.show();
}
// Show collapsingToolbar
collapsingToolbarLayout.setVisibility(View.VISIBLE);
// Show appropriate toolbar
// And restore 24dp paddingTop for appBarLayout
appBarLayout.setVisibility(View.VISIBLE);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
appBarLayout.setPadding(0, (int) Math.ceil(metrics.density * 24), 0, 0);
setupOrientation();
// Show other UI elements
cardIdFieldView.setVisibility(View.VISIBLE);
makeBottomSheetVisibleIfUseful();
editButton.show();
// Unset fullscreen mode
getWindow().getDecorView().setSystemUiVisibility(
getWindow().getDecorView().getSystemUiVisibility()
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
getWindow().getDecorView().getSystemUiVisibility()
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
);
// Turn barcode back to normal
barcodeImage.setLayoutParams(barcodeImageState);
// Fix barcode centering
barcodeImage.setAdjustViewBounds(true);
// Set current state
barcodeIsFullscreen = false;
}

View File

@@ -4,130 +4,367 @@ import android.app.SearchManager;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebView;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import java.util.List;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.common.collect.ImmutableMap;
import java.util.Calendar;
import java.util.Map;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import protect.card_locker.preferences.SettingsActivity;
public class MainActivity extends AppCompatActivity
public class MainActivity extends AppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener, GestureDetector.OnGestureListener
{
private static final String TAG = "Catima";
private static final int MAIN_REQUEST_CODE = 1;
private Menu menu;
protected String filter = "";
private final DBHelper mDB = new DBHelper(this);
private LoyaltyCardCursorAdapter mAdapter;
private ActionMode mCurrentActionMode;
private Menu mMenu;
private GestureDetector mGestureDetector;
protected String mFilter = "";
protected int selectedTab = 0;
private RecyclerView mCardList;
private ActionMode.Callback mCurrentActionModeCallback = new ActionMode.Callback()
{
@Override
public boolean onCreateActionMode(ActionMode inputMode, Menu inputMenu)
{
inputMode.getMenuInflater().inflate(R.menu.card_longclick_menu, inputMenu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode inputMode, Menu inputMenu)
{
return false;
}
@Override
public boolean onActionItemClicked(ActionMode inputMode, MenuItem inputItem)
{
if (inputItem.getItemId() == R.id.action_copy_to_clipboard)
{
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
String clipboardData;
int cardCount = mAdapter.getSelectedItemCount();
if (cardCount == 1) {
clipboardData = mAdapter.getSelectedItems().get(0).cardId;
} else {
StringBuilder cardIds = new StringBuilder();
for (int i = 0; i < cardCount; i++) {
LoyaltyCard loyaltyCard = mAdapter.getSelectedItems().get(i);
cardIds.append(loyaltyCard.store + ": " + loyaltyCard.cardId);
if (i < (cardCount - 1)) {
cardIds.append("\n");
}
}
clipboardData = cardIds.toString();
}
ClipData clip = ClipData.newPlainText(getString(R.string.card_ids_copied), clipboardData);
clipboard.setPrimaryClip(clip);
Toast.makeText(MainActivity.this, cardCount > 1 ? R.string.copy_to_clipboard_multiple_toast : R.string.copy_to_clipboard_toast, Toast.LENGTH_LONG).show();
inputMode.finish();
return true;
}
else if (inputItem.getItemId() == R.id.action_share)
{
final ImportURIHelper importURIHelper = new ImportURIHelper(MainActivity.this);
importURIHelper.startShareIntent(mAdapter.getSelectedItems());
inputMode.finish();
return true;
}
else if(inputItem.getItemId() == R.id.action_edit)
{
if (mAdapter.getSelectedItemCount() != 1) {
throw new IllegalArgumentException("Cannot edit more than 1 card at a time");
}
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle bundle = new Bundle();
bundle.putInt("id", mAdapter.getSelectedItems().get(0).id);
bundle.putBoolean("update", true);
intent.putExtras(bundle);
startActivity(intent);
inputMode.finish();
return true;
}
return false;
}
@Override
public void onDestroyActionMode(ActionMode inputMode)
{
mAdapter.clearSelections();
mCurrentActionMode = null;
mCardList.post(new Runnable()
{
@Override
public void run()
{
mAdapter.resetAnimationIndex();
}
});
}
};
@Override
protected void onCreate(Bundle savedInstanceState)
protected void onCreate(Bundle inputSavedInstanceState)
{
super.onCreate(savedInstanceState);
super.onCreate(inputSavedInstanceState);
setContentView(R.layout.main_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
updateLoyaltyCardList("");
updateLoyaltyCardList(mFilter, null);
TabLayout groupsTabLayout = findViewById(R.id.groups);
groupsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
selectedTab = tab.getPosition();
updateLoyaltyCardList(mFilter, tab.getTag());
// Store active tab in Shared Preference to restore next app launch
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
Context.MODE_PRIVATE);
SharedPreferences.Editor activeTabPrefEditor = activeTabPref.edit();
activeTabPrefEditor.putInt(getString(R.string.sharedpreference_active_tab), selectedTab);
activeTabPrefEditor.apply();
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
mGestureDetector = new GestureDetector(this, this);
View.OnTouchListener gestureTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(final View v, final MotionEvent event){
return mGestureDetector.onTouchEvent(event);
}
};
final View helpText = findViewById(R.id.helpText);
final View noMatchingCardsText = findViewById(R.id.noMatchingCardsText);
final View list = findViewById(R.id.list);
helpText.setOnTouchListener(gestureTouchListener);
noMatchingCardsText.setOnTouchListener(gestureTouchListener);
list.setOnTouchListener(gestureTouchListener);
/*
* This was added for Huawei, but Huawei is just too much of a fucking pain.
* Just leaving this commented out if needed for the future idk
* https://twitter.com/SylvieLorxu/status/1379437902741012483
*
// Show privacy policy on first run
SharedPreferences privacyPolicyShownPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_privacy_policy_shown),
Context.MODE_PRIVATE);
if (privacyPolicyShownPref.getInt(getString(R.string.sharedpreference_privacy_policy_shown), 0) == 0) {
SharedPreferences.Editor privacyPolicyShownPrefEditor = privacyPolicyShownPref.edit();
privacyPolicyShownPrefEditor.putInt(getString(R.string.sharedpreference_privacy_policy_shown), 1);
privacyPolicyShownPrefEditor.apply();
new AlertDialog.Builder(this)
.setTitle(R.string.privacy_policy)
.setMessage(R.string.privacy_policy_popup_text)
.setPositiveButton(R.string.accept, null)
.setNegativeButton(R.string.privacy_policy, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
openPrivacyPolicy();
}
})
.setIcon(android.R.drawable.ic_dialog_info)
.show();
}
*/
}
@Override
protected void onResume() {
protected void onResume()
{
super.onResume();
if (menu != null)
if(mCurrentActionMode != null)
{
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
mAdapter.clearSelections();
mCurrentActionMode.finish();
}
if (!searchView.isIconified()) {
filter = searchView.getQuery().toString();
if (mMenu != null)
{
SearchView searchView = (SearchView) mMenu.findItem(R.id.action_search).getActionView();
if (!searchView.isIconified())
{
mFilter = searchView.getQuery().toString();
}
}
updateLoyaltyCardList(filter);
// Start of active tab logic
TabLayout groupsTabLayout = findViewById(R.id.groups);
updateTabGroups(groupsTabLayout);
// Restore active tab from Shared Preference
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
Context.MODE_PRIVATE);
selectedTab = activeTabPref.getInt(getString(R.string.sharedpreference_active_tab), 0);
Object group = null;
if (groupsTabLayout.getTabCount() != 0) {
TabLayout.Tab tab = groupsTabLayout.getTabAt(selectedTab);
if (tab == null) {
tab = groupsTabLayout.getTabAt(0);
}
groupsTabLayout.selectTab(tab);
assert tab != null;
group = tab.getTag();
}
updateLoyaltyCardList(mFilter, group);
// End of active tab logic
FloatingActionButton addButton = findViewById(R.id.fabAdd);
addButton.setOnClickListener(new View.OnClickListener() {
addButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
Intent i = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
startActivityForResult(i, MAIN_REQUEST_CODE);
Intent i = new Intent(getApplicationContext(), ScanActivity.class);
startActivityForResult(i, Utils.BARCODE_SCAN);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (requestCode == MAIN_REQUEST_CODE)
{
if (requestCode == Utils.MAIN_REQUEST) {
// We're coming back from another view so clear the search
// We only do this now to prevent a flash of all entries right after picking one
filter = "";
if (menu != null)
mFilter = "";
if (mMenu != null)
{
MenuItem searchItem = menu.findItem(R.id.action_search);
MenuItem searchItem = mMenu.findItem(R.id.action_search);
searchItem.collapseActionView();
}
// In case the theme changed
recreate();
return;
}
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
if(!barcodeValues.isEmpty()) {
Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle newBundle = new Bundle();
newBundle.putString("barcodeType", barcodeValues.format());
newBundle.putString("cardId", barcodeValues.content());
newIntent.putExtras(newBundle);
startActivity(newIntent);
}
}
@Override
public void onBackPressed() {
if (menu == null)
public void onBackPressed()
{
if (mMenu == null)
{
super.onBackPressed();
return;
}
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
SearchView searchView = (SearchView) mMenu.findItem(R.id.action_search).getActionView();
if (!searchView.isIconified()) {
if (!searchView.isIconified())
{
searchView.setIconified(true);
} else {
super.onBackPressed();
TabLayout groupsTabLayout = findViewById(R.id.groups);
if (groupsTabLayout.getVisibility() == View.VISIBLE && selectedTab != 0) {
selectedTab = 0;
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(0));
} else {
super.onBackPressed();
}
}
}
private void updateLoyaltyCardList(String filterText)
private void updateLoyaltyCardList(String filterText, Object tag)
{
final ListView cardList = findViewById(R.id.list);
Group group = null;
if (tag != null) {
group = (Group) tag;
}
mCardList = findViewById(R.id.list);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
mCardList.setLayoutManager(mLayoutManager);
mCardList.setItemAnimator(new DefaultItemAnimator());
final TextView helpText = findViewById(R.id.helpText);
final TextView noMatchingCardsText = findViewById(R.id.noMatchingCardsText);
final DBHelper db = new DBHelper(this);
if(db.getLoyaltyCardCount() > 0)
Cursor cardCursor = mDB.getLoyaltyCardCursor(filterText, group);
mAdapter = new LoyaltyCardCursorAdapter(this, cardCursor, this);
mCardList.setAdapter(mAdapter);
registerForContextMenu(mCardList);
if(mDB.getLoyaltyCardCount() > 0)
{
// We want the cardList to be visible regardless of the filtered match count
// to ensure that the noMatchingCardsText doesn't end up being shown below
// the keyboard
cardList.setVisibility(View.VISIBLE);
mCardList.setVisibility(View.VISIBLE);
helpText.setVisibility(View.GONE);
if(db.getLoyaltyCardCount(filterText) > 0)
if(mAdapter.getItemCount() > 0)
{
noMatchingCardsText.setVisibility(View.GONE);
}
@@ -138,239 +375,145 @@ public class MainActivity extends AppCompatActivity
}
else
{
cardList.setVisibility(View.GONE);
mCardList.setVisibility(View.GONE);
helpText.setVisibility(View.VISIBLE);
noMatchingCardsText.setVisibility(View.GONE);
}
Cursor cardCursor = db.getLoyaltyCardCursor(filterText);
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor);
cardList.setAdapter(adapter);
registerForContextMenu(cardList);
cardList.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
Cursor selected = (Cursor) parent.getItemAtPosition(position);
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
Intent i = new Intent(view.getContext(), LoyaltyCardViewActivity.class);
i.setAction("");
final Bundle b = new Bundle();
b.putInt("id", loyaltyCard.id);
i.putExtras(b);
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard, i);
startActivityForResult(i, MAIN_REQUEST_CODE);
}
});
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, v, menuInfo);
if (v.getId()==R.id.list)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.card_longclick_menu, menu);
if (mCurrentActionMode != null) {
mCurrentActionMode.finish();
}
}
@Override
public boolean onContextItemSelected(MenuItem item)
public void updateTabGroups(TabLayout groupsTabLayout)
{
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
ListView listView = findViewById(R.id.list);
final DBHelper db = new DBHelper(this);
Cursor cardCursor = (Cursor)listView.getItemAtPosition(info.position);
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
List<Group> newGroups = db.getGroups();
if(item.getItemId() == R.id.action_clipboard)
{
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(card.store, card.cardId);
clipboard.setPrimaryClip(clip);
Toast.makeText(this, R.string.copy_to_clipboard_toast, Toast.LENGTH_LONG).show();
return true;
}
else if(item.getItemId() == R.id.action_share)
{
final ImportURIHelper importURIHelper = new ImportURIHelper(this);
importURIHelper.startShareIntent(card);
return true;
if (newGroups.size() == 0) {
groupsTabLayout.removeAllTabs();
groupsTabLayout.setVisibility(View.GONE);
return;
}
return super.onContextItemSelected(item);
groupsTabLayout.removeAllTabs();
TabLayout.Tab allTab = groupsTabLayout.newTab();
allTab.setText(R.string.all);
allTab.setTag(null);
groupsTabLayout.addTab(allTab, false);
for (Group group : newGroups) {
TabLayout.Tab tab = groupsTabLayout.newTab();
tab.setText(group._id);
tab.setTag(group);
groupsTabLayout.addTab(tab, false);
}
groupsTabLayout.setVisibility(View.VISIBLE);
}
private void openPrivacyPolicy() {
Intent browserIntent = new Intent(
Intent.ACTION_VIEW,
Uri.parse("https://thelastproject.github.io/Catima/privacy-policy")
);
startActivity(browserIntent);
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
public boolean onCreateOptionsMenu(Menu inputMenu)
{
this.menu = menu;
this.mMenu = inputMenu;
getMenuInflater().inflate(R.menu.main_menu, menu);
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
if (searchManager != null) {
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
if (searchManager != null)
{
SearchView searchView = (SearchView) inputMenu.findItem(R.id.action_search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setSubmitButtonEnabled(false);
searchView.setOnCloseListener(new SearchView.OnCloseListener() {
searchView.setOnCloseListener(new SearchView.OnCloseListener()
{
@Override
public boolean onClose() {
public boolean onClose()
{
invalidateOptionsMenu();
return false;
}
});
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener()
{
@Override
public boolean onQueryTextSubmit(String query) {
public boolean onQueryTextSubmit(String query)
{
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
filter = newText;
updateLoyaltyCardList(newText);
public boolean onQueryTextChange(String newText)
{
mFilter = newText;
TabLayout groupsTabLayout = findViewById(R.id.groups);
TabLayout.Tab currentTab = groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition());
updateLoyaltyCardList(
mFilter,
currentTab != null ? currentTab.getTag() : null
);
return true;
}
});
}
return super.onCreateOptionsMenu(menu);
return super.onCreateOptionsMenu(inputMenu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
public boolean onOptionsItemSelected(MenuItem inputItem)
{
int id = item.getItemId();
int id = inputItem.getItemId();
if(id == R.id.action_import_export)
if (id == R.id.action_manage_groups)
{
Intent i = new Intent(getApplicationContext(), ManageGroupsActivity.class);
startActivityForResult(i, Utils.MAIN_REQUEST);
return true;
}
if (id == R.id.action_import_export)
{
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
startActivityForResult(i, MAIN_REQUEST_CODE);
startActivityForResult(i, Utils.MAIN_REQUEST);
return true;
}
if(id == R.id.action_settings)
if (id == R.id.action_settings)
{
Intent i = new Intent(getApplicationContext(), SettingsActivity.class);
startActivityForResult(i, MAIN_REQUEST_CODE);
startActivityForResult(i, Utils.MAIN_REQUEST);
return true;
}
if(id == R.id.action_about)
if(id == R.id.action_privacy_policy)
{
displayAboutDialog();
openPrivacyPolicy();
return true;
}
return super.onOptionsItemSelected(item);
}
private void displayAboutDialog()
{
final Map<String, String> USED_LIBRARIES = new ImmutableMap.Builder<String, String>()
.put("Commons CSV", "https://commons.apache.org/proper/commons-csv/")
.put("Guava", "https://github.com/google/guava")
.put("ZXing", "https://github.com/zxing/zxing")
.put("ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded")
.put("AppIntro", "https://github.com/apl-devs/AppIntro")
.put("Color Picker", "https://github.com/jaredrummler/ColorPicker")
.put("VNTNumberPickerPreference", "https://github.com/vanniktech/VNTNumberPickerPreference")
.build();
final Map<String, String> USED_ASSETS = ImmutableMap.of
(
"Save by Bernar Novalyi", "https://thenounproject.com/term/save/716011"
);
StringBuilder libs = new StringBuilder().append("<ul>");
for (Map.Entry<String, String> entry : USED_LIBRARIES.entrySet())
if (id == R.id.action_about)
{
libs.append("<li><a href=\"").append(entry.getValue()).append("\">").append(entry.getKey()).append("</a></li>");
}
libs.append("</ul>");
StringBuilder resources = new StringBuilder().append("<ul>");
for (Map.Entry<String, String> entry : USED_ASSETS.entrySet())
{
resources.append("<li><a href=\"").append(entry.getValue()).append("\">").append(entry.getKey()).append("</a></li>");
}
resources.append("</ul>");
String appName = getString(R.string.app_name);
int year = Calendar.getInstance().get(Calendar.YEAR);
String version = "?";
try
{
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
version = pi.versionName;
}
catch (PackageManager.NameNotFoundException e)
{
Log.w(TAG, "Package name not found", e);
Intent i = new Intent(getApplicationContext(), AboutActivity.class);
startActivityForResult(i, Utils.MAIN_REQUEST);
return true;
}
WebView wv = new WebView(this);
// Set CSS for dark mode if dark mode
String css = "";
if(isDarkModeEnabled(this))
{
css = "<style>body {color:white; background-color:black;}</style>";
}
String html =
"<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />" +
css +
"<h1>" +
String.format(getString(R.string.about_title_fmt),
"<a href=\"" + getString(R.string.app_webpage_url)) + "\">" +
appName +
"</a>" +
"</h1><p>" +
appName +
" " +
String.format(getString(R.string.debug_version_fmt), version) +
"</p><p>" +
String.format(getString(R.string.app_revision_fmt),
"<a href=\"" + getString(R.string.app_revision_url) + "\">" +
"GitHub" +
"</a>") +
"</p><hr/><p>" +
String.format(getString(R.string.app_copyright_fmt), year) +
"</p><p>" +
getString(R.string.app_copyright_old) +
"</p><hr/><p>" +
getString(R.string.app_license) +
"</p><hr/><p>" +
String.format(getString(R.string.app_libraries), appName, libs.toString()) +
"</p><hr/><p>" +
String.format(getString(R.string.app_resources), appName, resources.toString());
wv.loadDataWithBaseURL("file:///android_res/drawable/", html, "text/html", "utf-8", null);
new AlertDialog.Builder(this)
.setView(wv)
.setCancelable(true)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
})
.show();
return super.onOptionsItemSelected(inputItem);
}
protected static boolean isDarkModeEnabled(Context inputContext)
@@ -380,4 +523,148 @@ public class MainActivity extends AppCompatActivity
return (currentNightMode == Configuration.UI_MODE_NIGHT_YES);
}
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.d(TAG, "On fling");
// Don't swipe if we have too much vertical movement
if (Math.abs(velocityY) > (0.75 * Math.abs(velocityX))) {
return false;
}
TabLayout groupsTabLayout = findViewById(R.id.groups);
if (groupsTabLayout.getTabCount() < 2) {
return false;
}
Integer currentTab = groupsTabLayout.getSelectedTabPosition();
// Swipe right
if (velocityX < -150) {
Integer nextTab = currentTab + 1;
if (nextTab == groupsTabLayout.getTabCount()) {
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(0));
} else {
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(nextTab));
}
return true;
}
// Swipe left
if (velocityX > 150) {
Integer nextTab = currentTab - 1;
if (nextTab < 0) {
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(groupsTabLayout.getTabCount() - 1));
} else {
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(nextTab));
}
return true;
}
return false;
}
@Override
public void onRowLongClicked(int inputPosition)
{
enableActionMode(inputPosition);
}
private void enableActionMode(int inputPosition)
{
if (mCurrentActionMode == null)
{
mCurrentActionMode = startSupportActionMode(mCurrentActionModeCallback);
}
toggleSelection(inputPosition);
}
private void toggleSelection(int inputPosition)
{
mAdapter.toggleSelection(inputPosition);
int count = mAdapter.getSelectedItemCount();
if (count == 0) {
mCurrentActionMode.finish();
} else {
mCurrentActionMode.setTitle("Selected: " + count + " Cards");
MenuItem editItem = mCurrentActionMode.getMenu().findItem(R.id.action_edit);
if (count == 1) {
editItem.setVisible(true);
editItem.setEnabled(true);
} else {
editItem.setVisible(false);
editItem.setEnabled(false);
}
mCurrentActionMode.invalidate();
}
}
@Override
public void onIconClicked(int inputPosition)
{
if (mCurrentActionMode == null)
{
mCurrentActionMode = startSupportActionMode(mCurrentActionModeCallback);
}
toggleSelection(inputPosition);
}
@Override
public void onRowClicked(int inputPosition)
{
if (mAdapter.getSelectedItemCount() > 0)
{
enableActionMode(inputPosition);
}
else
{
Cursor selected = mAdapter.getCursor();
selected.moveToPosition(inputPosition);
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
Intent i = new Intent(this, LoyaltyCardViewActivity.class);
i.setAction("");
final Bundle b = new Bundle();
b.putInt("id", loyaltyCard.id);
i.putExtras(b);
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard, i);
startActivityForResult(i, Utils.MAIN_REQUEST);
}
}
}

View File

@@ -0,0 +1,255 @@
package protect.card_locker;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Bundle;
import android.text.InputType;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.List;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
public class ManageGroupsActivity extends AppCompatActivity
{
private static final String TAG = "Catima";
private final DBHelper db = new DBHelper(this);
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.manage_groups_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
actionBar.setDisplayHomeAsUpEnabled(true);
}
updateGroupList();
}
@Override
protected void onResume() {
super.onResume();
updateGroupList();
FloatingActionButton addButton = findViewById(R.id.fabAdd);
addButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
createGroup();
}
});
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
private void updateGroupList()
{
final ListView groupList = findViewById(R.id.list);
final TextView helpText = findViewById(R.id.helpText);
final DBHelper db = new DBHelper(this);
if(db.getGroupCount() > 0)
{
groupList.setVisibility(View.VISIBLE);
helpText.setVisibility(View.GONE);
}
else
{
groupList.setVisibility(View.GONE);
helpText.setVisibility(View.VISIBLE);
}
Cursor groupCursor = db.getGroupCursor();
final GroupCursorAdapter adapter = new GroupCursorAdapter(this, groupCursor);
groupList.setAdapter(adapter);
registerForContextMenu(groupList);
}
private void invalidateHomescreenActiveTab()
{
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
Context.MODE_PRIVATE);
SharedPreferences.Editor activeTabPrefEditor = activeTabPref.edit();
activeTabPrefEditor.putInt(getString(R.string.sharedpreference_active_tab), 0);
activeTabPrefEditor.apply();
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
int id = item.getItemId();
if (id == android.R.id.home) {
finish();
}
return super.onOptionsItemSelected(item);
}
public void moveGroupUp(View view) {
moveGroup(view, true);
}
public void moveGroupDown(View view) {
moveGroup(view, false);
}
public void editGroup(View view) {
final String groupName = getGroupname(view);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.enter_group_name);
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setText(groupName);
builder.setView(input);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
db.updateGroup(groupName, input.getText().toString());
updateGroupList();
}
});
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
AlertDialog dialog = builder.create();
dialog.show();
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
input.requestFocus();
}
public void deleteGroup(View view) {
final String groupName = getGroupname(view);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.deleteConfirmationGroup);
builder.setMessage(groupName);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
db.deleteGroup(groupName);
updateGroupList();
// Delete may change ordering, so invalidate
invalidateHomescreenActiveTab();
}
});
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
private void createGroup() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.enter_group_name);
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
builder.setView(input);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
db.insertGroup(input.getText().toString());
updateGroupList();
}
});
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
AlertDialog dialog = builder.create();
dialog.show();
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
input.requestFocus();
}
private String getGroupname(View view) {
LinearLayout parentRow = (LinearLayout) view.getParent().getParent();
TextView groupNameTextView = parentRow.findViewById(R.id.name);
return (String) groupNameTextView.getText();
}
private void moveGroup(View view, boolean up) {
final String groupName = getGroupname(view);
List<Group> groups = db.getGroups();
int currentIndex = -1;
Integer newIndex;
// Get current index in group list
for (int i = 0; i < groups.size(); i++) {
if (groups.get(i)._id.equals(groupName)) {
currentIndex = i;
break;
}
}
if (currentIndex == -1) {
throw new IndexOutOfBoundsException();
}
// Reinsert group in correct position
if (up) {
newIndex = currentIndex - 1;
} else {
newIndex = currentIndex + 1;
}
// Don't try to move out of bounds
if (newIndex < 0 || newIndex >= groups.size()) {
return;
}
Group group = groups.remove(currentIndex);
groups.add(newIndex, group);
// Update database
db.reorderGroups(groups);
// Update UI
updateGroupList();
// Ordering may have changed, so invalidate
invalidateHomescreenActiveTab();
}
}

View File

@@ -0,0 +1,163 @@
package protect.card_locker;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import com.google.zxing.ResultPoint;
import com.google.zxing.client.android.Intents;
import com.journeyapps.barcodescanner.BarcodeCallback;
import com.journeyapps.barcodescanner.BarcodeResult;
import com.journeyapps.barcodescanner.CaptureManager;
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
import java.util.List;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
/**
* Custom Scannner Activity extending from Activity to display a custom layout form scanner view.
*
* Based on https://github.com/journeyapps/zxing-android-embedded/blob/0fdfbce9fb3285e985bad9971c5f7c0a7a334e7b/sample/src/main/java/example/zxing/CustomScannerActivity.java
* originally licensed under Apache 2.0
*/
public class ScanActivity extends AppCompatActivity {
private static final String TAG = "Catima";
private CaptureManager capture;
private DecoratedBarcodeView barcodeScannerView;
private String cardId;
private void extractIntentFields(Intent intent) {
final Bundle b = intent.getExtras();
cardId = b != null ? b.getString("cardId") : null;
Log.d(TAG, "Scan activity: id=" + cardId);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.scan_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
actionBar.setDisplayHomeAsUpEnabled(true);
}
extractIntentFields(getIntent());
barcodeScannerView = findViewById(R.id.zxing_barcode_scanner);
// Even though we do the actual decoding with the barcodeScannerView
// CaptureManager needs to be running to show the camera and scanning bar
capture = new CaptureManager(this, barcodeScannerView);
Intent captureIntent = new Intent();
Bundle captureIntentBundle = new Bundle();
captureIntentBundle.putBoolean(Intents.Scan.BEEP_ENABLED, false);
captureIntent.putExtras(captureIntentBundle);
capture.initializeFromIntent(captureIntent, savedInstanceState);
barcodeScannerView.decodeSingle(new BarcodeCallback() {
@Override
public void barcodeResult(BarcodeResult result) {
Intent scanResult = new Intent();
Bundle scanResultBundle = new Bundle();
scanResultBundle.putString(BarcodeSelectorActivity.BARCODE_CONTENTS, result.getText());
scanResultBundle.putString(BarcodeSelectorActivity.BARCODE_FORMAT, result.getBarcodeFormat().toString());
scanResult.putExtras(scanResultBundle);
ScanActivity.this.setResult(RESULT_OK, scanResult);
finish();
}
@Override
public void possibleResultPoints(List<ResultPoint> resultPoints) {
}
});
}
@Override
protected void onResume() {
super.onResume();
capture.onResume();
}
@Override
protected void onPause() {
super.onPause();
capture.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
capture.onDestroy();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
capture.onSaveInstanceState(outState);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
if (item.getItemId() == android.R.id.home)
{
setResult(Activity.RESULT_CANCELED);
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent)
{
super.onActivityResult(requestCode, resultCode, intent);
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
if (!barcodeValues.isEmpty()) {
Intent manualResult = new Intent();
Bundle manualResultBundle = new Bundle();
manualResultBundle.putString(BarcodeSelectorActivity.BARCODE_CONTENTS, barcodeValues.content());
manualResultBundle.putString(BarcodeSelectorActivity.BARCODE_FORMAT, barcodeValues.format());
manualResult.putExtras(manualResultBundle);
ScanActivity.this.setResult(RESULT_OK, manualResult);
finish();
}
}
public void addManually(View view) {
Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class);
if (cardId != null) {
final Bundle b = new Bundle();
b.putString("initialCardId", cardId);
i.putExtras(b);
}
startActivityForResult(i, Utils.SELECT_BARCODE_REQUEST);
}
public void addFromImage(View view) {
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType("image/*");
startActivityForResult(photoPickerIntent, Utils.BARCODE_IMPORT_FROM_IMAGE_FILE);
}
}

View File

@@ -0,0 +1,25 @@
package protect.card_locker;
public class ThirdPartyInfo {
private final String mName;
private final String mUrl;
private final String mLicense;
public ThirdPartyInfo(String name, String url, String license) {
mName = name;
mUrl = url;
mLicense = license;
}
public String name() {
return mName;
}
public String url() {
return mUrl;
}
public String license() {
return mLicense;
}
}

View File

@@ -0,0 +1,211 @@
package protect.card_locker;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.Toast;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.Calendar;
import java.util.Currency;
import java.util.Date;
import java.util.GregorianCalendar;
import androidx.core.graphics.ColorUtils;
public class Utils {
private static final String TAG = "Catima";
// Activity request codes
public static final int MAIN_REQUEST = 1;
public static final int SELECT_BARCODE_REQUEST = 2;
public static final int BARCODE_SCAN = 3;
public static final int BARCODE_IMPORT_FROM_IMAGE_FILE = 4;
static final double LUMINANCE_MIDPOINT = 0.5;
static public LetterBitmap generateIcon(Context context, String store, Integer backgroundColor) {
return generateIcon(context, store, backgroundColor, false);
}
static public LetterBitmap generateIcon(Context context, String store, Integer backgroundColor, boolean forShortcut) {
if (store.length() == 0) {
return null;
}
int tileLetterFontSize;
if (forShortcut) {
tileLetterFontSize = context.getResources().getDimensionPixelSize(R.dimen.tileLetterFontSizeForShortcut);
} else {
tileLetterFontSize = context.getResources().getDimensionPixelSize(R.dimen.tileLetterFontSize);
}
int pixelSize = context.getResources().getDimensionPixelSize(R.dimen.cardThumbnailSize);
if (backgroundColor == null) {
backgroundColor = LetterBitmap.getDefaultColor(context, store);
}
return new LetterBitmap(context, store, store,
tileLetterFontSize, pixelSize, pixelSize, backgroundColor, needsDarkForeground(backgroundColor) ? Color.BLACK : Color.WHITE);
}
static public boolean needsDarkForeground(Integer backgroundColor) {
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
}
static public BarcodeValues parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) {
String contents;
String format;
if (resultCode != Activity.RESULT_OK) {
return new BarcodeValues(null, null);
}
if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) {
Log.i(TAG, "Received image file with possible barcode");
Bitmap bitmap;
try {
bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), intent.getData());
} 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 BarcodeValues(null, null);
}
BarcodeValues barcodeFromBitmap = getBarcodeFromBitmap(bitmap);
if (barcodeFromBitmap.isEmpty()) {
Log.i(TAG, "No barcode found in image file");
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
}
Log.i(TAG, "Read barcode id: " + barcodeFromBitmap.content());
Log.i(TAG, "Read format: " + barcodeFromBitmap.format());
return barcodeFromBitmap;
}
if (requestCode == Utils.BARCODE_SCAN || requestCode == Utils.SELECT_BARCODE_REQUEST) {
if (requestCode == Utils.BARCODE_SCAN) {
Log.i(TAG, "Received barcode information from camera");
} else if (requestCode == Utils.SELECT_BARCODE_REQUEST) {
Log.i(TAG, "Received barcode information from typing it");
}
contents = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_CONTENTS);
format = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_FORMAT);
Log.i(TAG, "Read barcode id: " + contents);
Log.i(TAG, "Read format: " + format);
return new BarcodeValues(format, contents);
}
throw new UnsupportedOperationException("Unknown request code for parseSetBarcodeActivityResult");
}
static public BarcodeValues getBarcodeFromBitmap(Bitmap bitmap) {
// In order to decode it, the Bitmap must first be converted into a pixel array...
int[] intArray = new int[bitmap.getWidth() * bitmap.getHeight()];
bitmap.getPixels(intArray, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
// ...and then turned into a binary bitmap from its luminance
LuminanceSource source = new RGBLuminanceSource(bitmap.getWidth(), bitmap.getHeight(), intArray);
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
Result barcodeResult = new MultiFormatReader().decode(binaryBitmap);
return new BarcodeValues(barcodeResult.getBarcodeFormat().name(), barcodeResult.getText());
} catch (NotFoundException e) {
return new BarcodeValues(null, null);
}
}
static public Boolean hasExpired(Date expiryDate) {
// today
Calendar date = new GregorianCalendar();
// reset hour, minutes, seconds and millis
date.set(Calendar.HOUR_OF_DAY, 0);
date.set(Calendar.MINUTE, 0);
date.set(Calendar.SECOND, 0);
date.set(Calendar.MILLISECOND, 0);
return expiryDate.before(date.getTime());
}
static public String formatBalance(Context context, BigDecimal value, Currency currency) {
NumberFormat numberFormat = NumberFormat.getInstance();
if (currency == null) {
numberFormat.setMaximumFractionDigits(0);
return context.getString(R.string.balancePoints, numberFormat.format(value));
}
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
currencyFormat.setCurrency(currency);
currencyFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
currencyFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
return currencyFormat.format(value);
}
static public String formatBalanceWithoutCurrencySymbol(BigDecimal value, Currency currency) {
NumberFormat numberFormat = NumberFormat.getInstance();
if (currency == null) {
numberFormat.setMaximumFractionDigits(0);
return numberFormat.format(value);
}
numberFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
numberFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
return numberFormat.format(value);
}
static public Boolean currencyHasDecimals(Currency currency) {
if (currency == null) {
return false;
}
return currency.getDefaultFractionDigits() != 0;
}
static public BigDecimal parseCurrency(String value, Boolean hasDecimals) throws NumberFormatException {
// If there are no decimals expected, remove all separators before parsing
if (!hasDecimals) {
value = value.replaceAll("[^0-9]", "");
return new BigDecimal(value);
}
// There are many ways users can write a currency, so we fix it up a bit
// 1. Replace all non-numbers with dots
value = value.replaceAll("[^0-9]", ".");
// 2. Remove all but the last dot
while (value.split("\\.").length > 2) {
value = value.replaceFirst("\\.", "");
}
// Parse as BigDecimal
return new BigDecimal(value);
}
}

View File

@@ -0,0 +1,93 @@
package protect.card_locker.importexport;
import org.apache.commons.csv.CSVRecord;
import protect.card_locker.FormatException;
public class CSVHelpers {
/**
* Extract a string from the items array. The index into the array
* is determined by looking up the index in the fields map using the
* "key" as the key. If no such key exists, defaultValue is returned
* if it is not null. Otherwise, a FormatException is thrown.
*/
static String extractString(String key, CSVRecord record, String defaultValue)
throws FormatException
{
String toReturn = defaultValue;
if(record.isMapped(key))
{
toReturn = record.get(key);
}
else
{
if(defaultValue == null)
{
throw new FormatException("Field not used but expected: " + key);
}
}
return toReturn;
}
/**
* Extract an integer from the items array. The index into the array
* is determined by looking up the index in the fields map using the
* "key" as the key. If no such key exists, or the data is not a valid
* int, a FormatException is thrown.
*/
static Integer extractInt(String key, CSVRecord record, boolean nullIsOk)
throws FormatException
{
if(record.isMapped(key) == false)
{
throw new FormatException("Field not used but expected: " + key);
}
String value = record.get(key);
if(value.isEmpty() && nullIsOk)
{
return null;
}
try
{
return Integer.parseInt(record.get(key));
}
catch(NumberFormatException e)
{
throw new FormatException("Failed to parse field: " + key, e);
}
}
/**
* Extract a long from the items array. The index into the array
* is determined by looking up the index in the fields map using the
* "key" as the key. If no such key exists, or the data is not a valid
* int, a FormatException is thrown.
*/
static Long extractLong(String key, CSVRecord record, boolean nullIsOk)
throws FormatException
{
if(record.isMapped(key) == false)
{
throw new FormatException("Field not used but expected: " + key);
}
String value = record.get(key);
if(value.isEmpty() && nullIsOk)
{
return null;
}
try
{
return Long.parseLong(record.get(key));
}
catch(NumberFormatException e)
{
throw new FormatException("Failed to parse field: " + key, e);
}
}
}

View File

@@ -0,0 +1,116 @@
package protect.card_locker.importexport;
import android.database.Cursor;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import protect.card_locker.DBHelper;
import protect.card_locker.Group;
import protect.card_locker.LoyaltyCard;
/**
* Class for exporting the database into CSV (Comma Separate Values)
* format.
*/
public class CsvDatabaseExporter implements DatabaseExporter
{
public void exportData(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException
{
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
// Print the version
printer.printRecord("2");
printer.println();
// Print the header for groups
printer.printRecord(DBHelper.LoyaltyCardDbGroups.ID);
Cursor groupCursor = db.getGroupCursor();
while(groupCursor.moveToNext())
{
Group group = Group.toGroup(groupCursor);
printer.printRecord(group._id);
if(Thread.currentThread().isInterrupted())
{
throw new InterruptedException();
}
}
groupCursor.close();
// Print an empty line
printer.println();
// Print the header for cards
printer.printRecord(DBHelper.LoyaltyCardDbIds.ID,
DBHelper.LoyaltyCardDbIds.STORE,
DBHelper.LoyaltyCardDbIds.NOTE,
DBHelper.LoyaltyCardDbIds.EXPIRY,
DBHelper.LoyaltyCardDbIds.BALANCE,
DBHelper.LoyaltyCardDbIds.BALANCE_TYPE,
DBHelper.LoyaltyCardDbIds.CARD_ID,
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
DBHelper.LoyaltyCardDbIds.STAR_STATUS);
Cursor cardCursor = db.getLoyaltyCardCursor();
while(cardCursor.moveToNext())
{
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
printer.printRecord(card.id,
card.store,
card.note,
card.expiry != null ? card.expiry.getTime() : "",
card.balance,
card.balanceType,
card.cardId,
card.headerColor,
card.barcodeType,
card.starStatus);
if(Thread.currentThread().isInterrupted())
{
throw new InterruptedException();
}
}
cardCursor.close();
// Print an empty line
printer.println();
// Print the header for card group mappings
printer.printRecord(DBHelper.LoyaltyCardDbIdsGroups.cardID,
DBHelper.LoyaltyCardDbIdsGroups.groupID);
Cursor cardCursor2 = db.getLoyaltyCardCursor();
while(cardCursor2.moveToNext())
{
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor2);
for (Group group : db.getLoyaltyCardGroups(card.id)) {
printer.printRecord(card.id, group._id);
}
if(Thread.currentThread().isInterrupted())
{
throw new InterruptedException();
}
}
cardCursor2.close();
printer.close();
}
}

View File

@@ -0,0 +1,306 @@
package protect.card_locker.importexport;
import android.database.sqlite.SQLiteDatabase;
import com.google.zxing.BarcodeFormat;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Currency;
import java.util.Date;
import java.util.List;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
import protect.card_locker.Group;
/**
* Class for importing a database from CSV (Comma Separate Values)
* formatted data.
*
* The database's loyalty cards are expected to appear in the CSV data.
* A header is expected for the each table showing the names of the columns.
*/
public class CsvDatabaseImporter implements DatabaseImporter
{
public void importData(DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException
{
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
bufferedReader.mark(100);
Integer version = 1;
try {
version = Integer.parseInt(bufferedReader.readLine());
} catch (NumberFormatException _e) {
// Assume version 1
}
bufferedReader.reset();
switch (version) {
case 1:
parseV1(db, bufferedReader);
break;
case 2:
parseV2(db, bufferedReader);
break;
default:
throw new FormatException(String.format("No code to parse version %s", version));
}
bufferedReader.close();
}
public void parseV1(DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
{
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.withHeader());
SQLiteDatabase database = db.getWritableDatabase();
database.beginTransaction();
try
{
for (CSVRecord record : parser)
{
importLoyaltyCard(database, db, record);
if(Thread.currentThread().isInterrupted())
{
throw new InterruptedException();
}
}
parser.close();
database.setTransactionSuccessful();
}
catch(IllegalArgumentException|IllegalStateException e)
{
throw new FormatException("Issue parsing CSV data", e);
}
finally
{
database.endTransaction();
database.close();
}
}
public void parseV2(DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
{
SQLiteDatabase database = db.getWritableDatabase();
database.beginTransaction();
Integer part = 0;
String stringPart = "";
try {
while (true) {
String tmp = input.readLine();
if (tmp == null || tmp.isEmpty()) {
switch (part) {
case 0:
// This is the version info, ignore
break;
case 1:
parseV2Groups(db, database, stringPart);
break;
case 2:
parseV2Cards(db, database, stringPart);
break;
case 3:
parseV2CardGroups(db, database, stringPart);
break;
default:
throw new FormatException("Issue parsing CSV data, too many parts for v2 parsing");
}
if (tmp == null) {
break;
}
part += 1;
stringPart = "";
} else {
stringPart += tmp + "\n";
}
}
database.setTransactionSuccessful();
} catch (FormatException e) {
throw new FormatException("Issue parsing CSV data", e);
} finally {
database.endTransaction();
database.close();
}
}
public void parseV2Groups(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
{
// Parse groups
final CSVParser groupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.withHeader());
try {
for (CSVRecord record : groupParser) {
importGroup(database, db, record);
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
}
} catch (IllegalArgumentException | IllegalStateException e) {
throw new FormatException("Issue parsing CSV data", e);
} finally {
groupParser.close();
}
}
public void parseV2Cards(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
{
// Parse cards
final CSVParser cardParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.withHeader());
try {
for (CSVRecord record : cardParser) {
importLoyaltyCard(database, db, record);
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
}
} catch (IllegalArgumentException | IllegalStateException e) {
throw new FormatException("Issue parsing CSV data", e);
} finally {
cardParser.close();
}
}
public void parseV2CardGroups(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
{
// Parse card group mappings
final CSVParser cardGroupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.withHeader());
try {
for (CSVRecord record : cardGroupParser) {
importCardGroupMapping(database, db, record);
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
}
} catch (IllegalArgumentException | IllegalStateException e) {
throw new FormatException("Issue parsing CSV data", e);
} finally {
cardGroupParser.close();
}
}
/**
* Import a single loyalty card into the database using the given
* session.
*/
private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record)
throws IOException, FormatException
{
int id = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false);
String store = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.STORE, record, "");
if(store.isEmpty())
{
throw new FormatException("No store listed, but is required");
}
String note = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.NOTE, record, "");
Date expiry = null;
try {
expiry = new Date(CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.EXPIRY, record, true));
} catch (NullPointerException | FormatException e) { }
BigDecimal balance;
try {
balance = new BigDecimal(CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE, record, null));
} catch (FormatException _e ) {
// These fields did not exist in versions 1.8.1 and before
// We catch this exception so we can still import old backups
balance = new BigDecimal("0");
}
Currency balanceType = null;
String unparsedBalanceType = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE, record, "");
if(!unparsedBalanceType.isEmpty()) {
balanceType = Currency.getInstance(unparsedBalanceType);
}
String cardId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.CARD_ID, record, "");
if(cardId.isEmpty())
{
throw new FormatException("No card ID listed, but is required");
}
String barcodeId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BARCODE_ID, record, "");
if(barcodeId.isEmpty())
{
barcodeId = null;
}
BarcodeFormat barcodeType = null;
String unparsedBarcodeType = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, record, "");
if(!unparsedBarcodeType.isEmpty())
{
barcodeType = BarcodeFormat.valueOf(unparsedBarcodeType);
}
Integer headerColor = null;
if(record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR))
{
headerColor = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record, true);
}
int starStatus = 0;
try {
starStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.STAR_STATUS, record, false);
} catch (FormatException _e ) {
// This field did not exist in versions 0.28 and before
// We catch this exception so we can still import old backups
}
if (starStatus != 1) starStatus = 0;
helper.insertLoyaltyCard(database, id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus);
}
/**
* Import a single group into the database using the given
* session.
*/
private void importGroup(SQLiteDatabase database, DBHelper helper, CSVRecord record)
throws IOException, FormatException
{
String id = CSVHelpers.extractString(DBHelper.LoyaltyCardDbGroups.ID, record, null);
helper.insertGroup(database, id);
}
/**
* Import a single card to group mapping into the database using the given
* session.
*/
private void importCardGroupMapping(SQLiteDatabase database, DBHelper helper, CSVRecord record)
throws IOException, FormatException
{
Integer cardId = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIdsGroups.cardID, record, false);
String groupId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIdsGroups.groupID, record, null);
List<Group> cardGroups = helper.getLoyaltyCardGroups(cardId);
cardGroups.add(helper.getGroup(groupId));
helper.setLoyaltyCardGroups(database, cardId, cardGroups);
}
}

View File

@@ -1,8 +1,10 @@
package protect.card_locker;
package protect.card_locker.importexport;
import java.io.IOException;
import java.io.OutputStreamWriter;
import protect.card_locker.DBHelper;
/**
* Interface for a class which can export the contents of the database
* in a given format.

View File

@@ -0,0 +1,25 @@
package protect.card_locker.importexport;
import org.json.JSONException;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
/**
* Interface for a class which can import the contents of a stream
* into the database.
*/
public interface DatabaseImporter
{
/**
* Import data from the input stream in a given format into
* the database.
* @throws IOException
* @throws FormatException
*/
void importData(DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException, JSONException, ParseException;
}

View File

@@ -0,0 +1,135 @@
package protect.card_locker.importexport;
import android.database.sqlite.SQLiteDatabase;
import com.google.zxing.BarcodeFormat;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.json.JSONException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
/**
* Class for importing a database from CSV (Comma Separate Values)
* formatted data.
*
* The database's loyalty cards are expected to appear in the CSV data.
* A header is expected for the each table showing the names of the columns.
*/
public class FidmeImporter implements DatabaseImporter
{
public void importData(DBHelper db, InputStream input) throws IOException, FormatException, JSONException, ParseException {
// We actually retrieve a .zip file
ZipInputStream zipInputStream = new ZipInputStream(input);
StringBuilder loyaltyCards = new StringBuilder();
byte[] buffer = new byte[1024];
int read = 0;
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (zipEntry.getName().equals("loyalty_programs.csv")) {
while ((read = zipInputStream.read(buffer, 0, 1024)) >= 0) {
loyaltyCards.append(new String(buffer, 0, read, StandardCharsets.UTF_8));
}
}
}
if (loyaltyCards.length() == 0) {
throw new FormatException("Couldn't find loyalty_programs.csv in zip file or it is empty");
}
SQLiteDatabase database = db.getWritableDatabase();
database.beginTransaction();
final CSVParser fidmeParser = new CSVParser(new StringReader(loyaltyCards.toString()), CSVFormat.RFC4180.withDelimiter(';').withHeader());
try {
for (CSVRecord record : fidmeParser) {
importLoyaltyCard(database, db, record);
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
}
} catch (IllegalArgumentException | IllegalStateException | InterruptedException e) {
throw new FormatException("Issue parsing CSV data", e);
} finally {
fidmeParser.close();
}
database.setTransactionSuccessful();
database.endTransaction();
database.close();
zipInputStream.close();
}
/**
* Import a single loyalty card into the database using the given
* session.
*/
private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record)
throws IOException, FormatException
{
// A loyalty card export from Fidme contains the following fields:
// Retailer (store name)
// Program (program name)
// Added at (YYYY-MM-DD HH:MM:SS UTC)
// Reference (card ID)
// Firstname (card holder first name)
// Lastname (card holder last name)
// The store is called Retailer
String store = CSVHelpers.extractString("Retailer", record, "");
if (store.isEmpty())
{
throw new FormatException("No store listed, but is required");
}
// There seems to be no note field in the CSV? So let's combine other fields instead...
String program = CSVHelpers.extractString("Program", record, "");
String addedAt = CSVHelpers.extractString("Added At", record, "");
String firstName = CSVHelpers.extractString("Firstname", record, "");
String lastName = CSVHelpers.extractString("Lastname", record, "");
String combinedName = String.format("%s %s", firstName, lastName);
StringBuilder noteBuilder = new StringBuilder();
if (!program.isEmpty()) noteBuilder.append(program).append('\n');
if (!addedAt.isEmpty()) noteBuilder.append(addedAt).append('\n');
if (!combinedName.isEmpty()) noteBuilder.append(combinedName).append('\n');
String note = noteBuilder.toString();
// The ID is called reference
String cardId = CSVHelpers.extractString("Reference", record, "");
if(cardId.isEmpty())
{
throw new FormatException("No card ID listed, but is required");
}
// Sadly, Fidme exports don't contain the card type
// I guess they have an online DB of all the different companies and what type they use
// TODO: Hook this into our own loyalty card DB if we ever get one
BarcodeFormat barcodeType = null;
// No favourite data in the export either
int starStatus = 0;
helper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, null, starStatus);
}
}

View File

@@ -1,10 +1,13 @@
package protect.card_locker;
package protect.card_locker.importexport;
import android.util.Log;
import java.io.IOException;
import java.io.OutputStreamWriter;
import protect.card_locker.DBHelper;
import protect.card_locker.DataFormat;
public class MultiFormatExporter
{
private static final String TAG = "Catima";
@@ -25,9 +28,12 @@ public class MultiFormatExporter
switch(format)
{
case CSV:
case Catima:
exporter = new CsvDatabaseExporter();
break;
default:
Log.e(TAG, "Failed to export data, unknown format " + format.name());
break;
}
if(exporter != null)

View File

@@ -1,9 +1,16 @@
package protect.card_locker;
package protect.card_locker.importexport;
import android.util.Log;
import org.json.JSONException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.text.ParseException;
import protect.card_locker.DBHelper;
import protect.card_locker.DataFormat;
import protect.card_locker.FormatException;
public class MultiFormatImporter
{
@@ -20,43 +27,40 @@ public class MultiFormatImporter
* false otherwise. If false, no data was written to
* the database.
*/
public static boolean importData(DBHelper db, InputStreamReader input, DataFormat format)
public static boolean importData(DBHelper db, InputStream input, DataFormat format)
{
DatabaseImporter importer = null;
switch(format)
{
case CSV:
case Catima:
importer = new CsvDatabaseImporter();
break;
case Fidme:
importer = new FidmeImporter();
break;
case VoucherVault:
importer = new VoucherVaultImporter();
break;
}
if(importer != null)
if (importer != null)
{
try
{
importer.importData(db, input);
return true;
}
catch(IOException e)
catch(IOException | FormatException | InterruptedException | JSONException | ParseException e)
{
Log.e(TAG, "Failed to input data", e);
}
catch(FormatException e)
{
Log.e(TAG, "Failed to input data", e);
}
catch(InterruptedException e)
{
Log.e(TAG, "Failed to input data", e);
Log.e(TAG, "Failed to import data", e);
}
return false;
}
else
{
Log.e(TAG, "Unsupported data format imported: " + format.name());
return false;
}
return false;
}
}

View File

@@ -0,0 +1,137 @@
package protect.card_locker.importexport;
import android.annotation.SuppressLint;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Color;
import com.google.zxing.BarcodeFormat;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Currency;
import java.util.Date;
import java.util.TimeZone;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
/**
* Class for importing a database from CSV (Comma Separate Values)
* formatted data.
*
* The database's loyalty cards are expected to appear in the CSV data.
* A header is expected for the each table showing the names of the columns.
*/
public class VoucherVaultImporter implements DatabaseImporter
{
public void importData(DBHelper db, InputStream input) throws IOException, FormatException, JSONException, ParseException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
JSONArray jsonArray = new JSONArray(sb.toString());
SQLiteDatabase database = db.getWritableDatabase();
database.beginTransaction();
// See https://github.com/tim-smart/vouchervault/issues/4#issuecomment-788226503 for more info
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonCard = jsonArray.getJSONObject(i);
String store = jsonCard.getString("description");
Date expiry = null;
if (!jsonCard.isNull("expires")) {
@SuppressLint("SimpleDateFormat") SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
expiry = dateFormat.parse(jsonCard.getString("expires"));
}
BigDecimal balance;
if (!jsonCard.isNull("balance")) {
balance = new BigDecimal(String.valueOf(jsonCard.getDouble("balance")));
} else {
balance = new BigDecimal("0");
}
Currency balanceType = Currency.getInstance("USD");
String cardId = jsonCard.getString("code");
BarcodeFormat barcodeType = null;
String codeTypeFromJSON = jsonCard.getString("codeType");
switch (codeTypeFromJSON) {
case "CODE128":
barcodeType = BarcodeFormat.CODE_128;
break;
case "CODE39":
barcodeType = BarcodeFormat.CODE_39;
break;
case "EAN13":
barcodeType = BarcodeFormat.EAN_13;
break;
case "PDF417":
barcodeType = BarcodeFormat.PDF_417;
break;
case "QR":
barcodeType = BarcodeFormat.QR_CODE;
break;
case "TEXT":
break;
default:
throw new FormatException("Unknown barcode type found: " + codeTypeFromJSON);
}
int headerColor;
String colorFromJSON = jsonCard.getString("color");
switch (colorFromJSON) {
case "GREY":
headerColor = Color.GRAY;
break;
case "BLUE":
headerColor = Color.BLUE;
break;
case "GREEN":
headerColor = Color.GREEN;
break;
case "ORANGE":
headerColor = Color.rgb(255, 165, 0);
break;
case "PURPLE":
headerColor = Color.rgb(128, 0, 128);
break;
case "RED":
headerColor = Color.RED;
break;
case "YELLOW":
headerColor = Color.YELLOW;
break;
default:
throw new FormatException("Unknown colour type foun: " + colorFromJSON);
}
db.insertLoyaltyCard(store, "", expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0);
}
database.setTransactionSuccessful();
database.endTransaction();
database.close();
bufferedReader.close();
}
}

View File

@@ -2,11 +2,11 @@ package protect.card_locker.preferences;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import androidx.annotation.IntegerRes;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.PreferenceManager;
import protect.card_locker.R;
public class Settings
@@ -61,29 +61,34 @@ public class Settings
return AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
}
public int getCardTitleListFontSize()
public double getFontSizeScale()
{
return getInt(R.string.settings_key_card_title_list_font_size, R.integer.settings_card_title_list_font_size_sp);
return getInt(R.string.settings_key_max_font_size_scale, R.integer.settings_max_font_size_scale_pct) / 100.0;
}
public int getCardNoteListFontSize()
public int getSmallFont()
{
return getInt(R.string.settings_key_card_note_list_font_size, R.integer.settings_card_note_list_font_size_sp);
return 14;
}
public int getCardTitleFontSize()
public int getMediumFont()
{
return getInt(R.string.settings_key_card_title_font_size, R.integer.settings_card_title_font_size_sp);
return 28;
}
public int getCardIdFontSize()
public int getLargeFont()
{
return getInt(R.string.settings_key_card_id_font_size, R.integer.settings_card_id_font_size_sp);
return 40;
}
public int getCardNoteFontSize()
public int getFontSizeMin(int fontSize)
{
return getInt(R.string.settings_key_card_note_font_size, R.integer.settings_card_note_font_size_sp);
return (int) (Math.round(fontSize / 2.0) - 1);
}
public int getFontSizeMax(int fontSize)
{
return (int) Math.round(fontSize * getFontSizeScale());
}
public boolean useMaxBrightnessDisplayingBarcode()
@@ -95,4 +100,14 @@ public class Settings
{
return getBoolean(R.string.settings_key_lock_barcode_orientation, false);
}
public boolean getKeepScreenOn()
{
return getBoolean(R.string.settings_key_keep_screen_on, true);
}
public boolean getDisableLockscreenWhileViewingCard()
{
return getBoolean(R.string.settings_key_disable_lockscreen_while_viewing_card, true);
}
}

View File

@@ -1,13 +1,17 @@
package protect.card_locker.preferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.view.MenuItem;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import android.view.MenuItem;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import nl.invissvenska.numberpickerpreference.NumberDialogPreference;
import nl.invissvenska.numberpickerpreference.NumberPickerPreferenceDialogFragment;
import protect.card_locker.R;
public class SettingsActivity extends AppCompatActivity
@@ -16,16 +20,17 @@ public class SettingsActivity extends AppCompatActivity
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_activity);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
}
// Display the fragment as the main content.
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new SettingsFragment())
getSupportFragmentManager().beginTransaction()
.replace(R.id.settings_container, new SettingsFragment())
.commit();
}
@@ -43,17 +48,18 @@ public class SettingsActivity extends AppCompatActivity
return super.onOptionsItemSelected(item);
}
public static class SettingsFragment extends PreferenceFragment
public static class SettingsFragment extends PreferenceFragmentCompat
{
private static final String DIALOG_FRAGMENT_TAG = "SettingsFragment";
@Override
public void onCreate(final Bundle savedInstanceState)
public void onCreatePreferences(Bundle savedInstanceState, String rootKey)
{
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
findPreference(getResources().getString(R.string.settings_key_theme)).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
Preference themePreference = findPreference(getResources().getString(R.string.settings_key_theme));
assert themePreference != null;
themePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
{
@Override
public boolean onPreferenceChange(Preference preference, Object o)
@@ -71,11 +77,37 @@ public class SettingsActivity extends AppCompatActivity
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
}
getActivity().recreate();
FragmentActivity activity = getActivity();
if (activity != null) {
activity.recreate();
}
return true;
}
});
}
@Override
public void onDisplayPreferenceDialog(Preference preference)
{
if (preference instanceof NumberDialogPreference)
{
NumberDialogPreference dialogPreference = (NumberDialogPreference) preference;
DialogFragment dialogFragment = NumberPickerPreferenceDialogFragment
.newInstance(
dialogPreference.getKey(),
dialogPreference.getMinValue(),
dialogPreference.getMaxValue(),
dialogPreference.getStepValue(),
dialogPreference.getUnitText()
);
dialogFragment.setTargetFragment(this, 0);
dialogFragment.show(getParentFragmentManager(), DIALOG_FRAGMENT_TAG);
}
else
{
super.onDisplayPreferenceDialog(preference);
}
}
}
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="0"
android:propertyName="alpha"
android:valueFrom="1.0"
android:valueTo="0.0" />
<objectAnimator
android:duration="@integer/full_rotation_duration"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="rotationY"
android:valueFrom="-180"
android:valueTo="0" />
<objectAnimator
android:duration="1"
android:propertyName="alpha"
android:startOffset="@integer/half_rotation_duration"
android:valueFrom="0.0"
android:valueTo="1.0" />
</set>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="@integer/full_rotation_duration"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="rotationY"
android:valueFrom="0"
android:valueTo="180" />
<objectAnimator
android:duration="1"
android:propertyName="alpha"
android:startOffset="@integer/half_rotation_duration"
android:valueFrom="1.0"
android:valueTo="0.0" />
</set>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="0"
android:propertyName="alpha"
android:valueFrom="1.0"
android:valueTo="0.0" />
<objectAnimator
android:duration="@integer/full_rotation_duration"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="rotationY"
android:valueFrom="180"
android:valueTo="0" />
<objectAnimator
android:duration="1"
android:propertyName="alpha"
android:startOffset="@integer/half_rotation_duration"
android:valueFrom="0.0"
android:valueTo="1.0" />
</set>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="@integer/full_rotation_duration"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="rotationY"
android:valueFrom="0"
android:valueTo="-180" />
<objectAnimator
android:duration="1"
android:propertyName="alpha"
android:startOffset="@integer/half_rotation_duration"
android:valueFrom="1.0"
android:valueTo="0.0" />
</set>

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 582 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 554 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 783 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 758 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 466 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 512 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 829 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 809 B

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