mirror of
https://github.com/CatimaLoyalty/Android.git
synced 2026-01-04 13:08:04 -05:00
Compare commits
1009 Commits
v2.0
...
fix/deprec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a434397551 | ||
|
|
d84ce6ff82 | ||
|
|
d4e388cf5d | ||
|
|
34fcedfa28 | ||
|
|
c75eed56f0 | ||
|
|
8ce0ce82ef | ||
|
|
304754f991 | ||
|
|
ab6e90d9f6 | ||
|
|
9df22f18f0 | ||
|
|
2eeec777d7 | ||
|
|
1d41039de2 | ||
|
|
112b4a034c | ||
|
|
8c06427976 | ||
|
|
486a2b887e | ||
|
|
3f3f289f62 | ||
|
|
bf2b28065d | ||
|
|
d00d25ad00 | ||
|
|
f1371daecb | ||
|
|
2fecc524f0 | ||
|
|
f5beffcad5 | ||
|
|
b78bdb3ced | ||
|
|
f16b3ca119 | ||
|
|
f2e820f7bb | ||
|
|
cf38357f3e | ||
|
|
9862a9912f | ||
|
|
990968a67b | ||
|
|
3331cf9ccf | ||
|
|
8a45222783 | ||
|
|
d92efc020b | ||
|
|
deb1edb7a6 | ||
|
|
3265a9eb4d | ||
|
|
c0ed86818f | ||
|
|
67d8a242b5 | ||
|
|
d12a4bf75d | ||
|
|
e86db3887b | ||
|
|
ce8488e515 | ||
|
|
5ef9aad5a7 | ||
|
|
d9fd3fd001 | ||
|
|
f9f3efe1e2 | ||
|
|
c5b9399fc4 | ||
|
|
19ad303508 | ||
|
|
d5b9bb9ca1 | ||
|
|
c50661f1bd | ||
|
|
afd8d2e675 | ||
|
|
be8765aab1 | ||
|
|
5656751a14 | ||
|
|
12b81f7fe5 | ||
|
|
3ff64d9abc | ||
|
|
0a39a8bb5b | ||
|
|
64f68d5c19 | ||
|
|
2975c46f96 | ||
|
|
f6762df1cf | ||
|
|
1aa0585909 | ||
|
|
1cb20eeab3 | ||
|
|
c8acee2fbd | ||
|
|
ff35773eed | ||
|
|
48403399ab | ||
|
|
96dafeab70 | ||
|
|
d10e358e7d | ||
|
|
71eb8c53bc | ||
|
|
186c2d1d56 | ||
|
|
2efa35504f | ||
|
|
ca1996da53 | ||
|
|
b8d0724446 | ||
|
|
c9a08e4f5b | ||
|
|
4920b67d0c | ||
|
|
e8eb3fce5c | ||
|
|
aaccae647a | ||
|
|
df4e851bfe | ||
|
|
05c85dac14 | ||
|
|
86d953bb74 | ||
|
|
1ecc39bea5 | ||
|
|
4c9182eb0b | ||
|
|
62fafaff95 | ||
|
|
aa0fdcc6b6 | ||
|
|
6c33dba401 | ||
|
|
c07d9805e4 | ||
|
|
941a123db0 | ||
|
|
2e58421a80 | ||
|
|
ae28e02cce | ||
|
|
e5d8dc8d59 | ||
|
|
b85de0e089 | ||
|
|
4264155837 | ||
|
|
d2a500824a | ||
|
|
6a91d59050 | ||
|
|
451c49d05f | ||
|
|
b4ec4b74ca | ||
|
|
6c254315af | ||
|
|
dfaf61722f | ||
|
|
2dbebea884 | ||
|
|
d7da942481 | ||
|
|
6b4a01c954 | ||
|
|
aee7728f56 | ||
|
|
6fbbe38542 | ||
|
|
651744b770 | ||
|
|
a189ac22ce | ||
|
|
f4d71e4525 | ||
|
|
45ec062499 | ||
|
|
82d8e84d78 | ||
|
|
fa25bff46f | ||
|
|
5648061a31 | ||
|
|
9cd2f66b40 | ||
|
|
90b7b43faf | ||
|
|
cfe5865d35 | ||
|
|
243898c441 | ||
|
|
0afc6f3976 | ||
|
|
fa95ea6a62 | ||
|
|
abb559afd6 | ||
|
|
61e7d1b154 | ||
|
|
da594a8287 | ||
|
|
ae1ccab059 | ||
|
|
15f5c4a67b | ||
|
|
19f7af1e42 | ||
|
|
b5b994642a | ||
|
|
45da445866 | ||
|
|
55b1ad1c55 | ||
|
|
fcd6f075f6 | ||
|
|
3d2f119fdb | ||
|
|
05f4add5cd | ||
|
|
eb71d5f717 | ||
|
|
a64603d64e | ||
|
|
e686bb36ce | ||
|
|
351a805046 | ||
|
|
466ac404bc | ||
|
|
b5c0be8ed5 | ||
|
|
f963b2fd4c | ||
|
|
c26ae45cdc | ||
|
|
addb0896c0 | ||
|
|
c75bef4f01 | ||
|
|
f633fb39aa | ||
|
|
49200fff48 | ||
|
|
f4bd6a3f59 | ||
|
|
18021e7653 | ||
|
|
de364a4cc4 | ||
|
|
3ba42aad6d | ||
|
|
63fad8bae1 | ||
|
|
ad418bc9bd | ||
|
|
b0263d8eb5 | ||
|
|
97d3969f93 | ||
|
|
1c85172206 | ||
|
|
711fa7ef81 | ||
|
|
2220a8e3c6 | ||
|
|
cd1bd31e23 | ||
|
|
f812194b1a | ||
|
|
79c26cfed7 | ||
|
|
85ea10303a | ||
|
|
4196ad8d61 | ||
|
|
90be6a418b | ||
|
|
2e2563cfc0 | ||
|
|
983c207019 | ||
|
|
6a5983b7bf | ||
|
|
b34e69d5d5 | ||
|
|
f1cd9ac935 | ||
|
|
5eb56c934b | ||
|
|
f9b24d4b1b | ||
|
|
537b2cba82 | ||
|
|
d788a274a8 | ||
|
|
57c383c064 | ||
|
|
49a8828007 | ||
|
|
d56a31160a | ||
|
|
bb78f9c12a | ||
|
|
00c005afce | ||
|
|
c008767bd6 | ||
|
|
5e15555ad4 | ||
|
|
916d12c504 | ||
|
|
854958e364 | ||
|
|
d295dd40fd | ||
|
|
069a3c99ef | ||
|
|
f1e7e5494d | ||
|
|
189fe7d101 | ||
|
|
9a9ff77d1c | ||
|
|
b8c1eaf88e | ||
|
|
e525a32511 | ||
|
|
de9f108534 | ||
|
|
c5f0d03a1c | ||
|
|
e592452d02 | ||
|
|
d3d2b37001 | ||
|
|
3dbe4f8327 | ||
|
|
3d81c1be08 | ||
|
|
333874e5e1 | ||
|
|
69a53a8408 | ||
|
|
8dc479793b | ||
|
|
a2d48a236b | ||
|
|
67f390f594 | ||
|
|
1a5109e036 | ||
|
|
ff42ead6b7 | ||
|
|
2f8d8e79c1 | ||
|
|
efa6ac1f6c | ||
|
|
4fc56068e1 | ||
|
|
7a0253ddcd | ||
|
|
ac3647695b | ||
|
|
fc902db170 | ||
|
|
b7230ba2a7 | ||
|
|
f5b71beb4b | ||
|
|
380a12e926 | ||
|
|
f92f84ee5c | ||
|
|
e1eb049d05 | ||
|
|
e03ca71728 | ||
|
|
9f3633b2ef | ||
|
|
0b53bdf6eb | ||
|
|
2536767437 | ||
|
|
2bc7d13d50 | ||
|
|
84fef5a615 | ||
|
|
0993f3180b | ||
|
|
f143e01685 | ||
|
|
cb5a98edad | ||
|
|
74157b2fe5 | ||
|
|
5b7d5599f9 | ||
|
|
cdfcfebd77 | ||
|
|
301fd880f5 | ||
|
|
4a4ef1b148 | ||
|
|
0bc7f4a164 | ||
|
|
5e593d0ded | ||
|
|
e958fff7b4 | ||
|
|
d81b236aa6 | ||
|
|
8be8737459 | ||
|
|
e80238bfcf | ||
|
|
211032c152 | ||
|
|
0bf50fb491 | ||
|
|
6f22e2d185 | ||
|
|
31c341663f | ||
|
|
40a68a55e2 | ||
|
|
1fd0acf6e4 | ||
|
|
127d288ace | ||
|
|
11587631a9 | ||
|
|
466b3d91d3 | ||
|
|
8f4f7524c4 | ||
|
|
f35aa2a064 | ||
|
|
c20206fe28 | ||
|
|
45de3f6616 | ||
|
|
6f42b30790 | ||
|
|
35eab50775 | ||
|
|
19d302e0a4 | ||
|
|
a17ccfa41a | ||
|
|
a22e05284c | ||
|
|
64491a1f7a | ||
|
|
472d8ae18d | ||
|
|
388db6feab | ||
|
|
5833d49fbf | ||
|
|
ffb2fadcfc | ||
|
|
5f534b9646 | ||
|
|
e011324b9f | ||
|
|
21df76a2b6 | ||
|
|
1af9789cf6 | ||
|
|
b891e05fb9 | ||
|
|
55e09b602f | ||
|
|
af4075b9e2 | ||
|
|
da0a221c85 | ||
|
|
191445b822 | ||
|
|
d890b062a9 | ||
|
|
59173443e7 | ||
|
|
5da76c9d9c | ||
|
|
f048566929 | ||
|
|
fe0f4314db | ||
|
|
8d39bd671c | ||
|
|
9ad33a047f | ||
|
|
2a78335607 | ||
|
|
d99c72982d | ||
|
|
79ced018d5 | ||
|
|
f80c79ffb1 | ||
|
|
11970004f0 | ||
|
|
a348ad62a2 | ||
|
|
22d612427f | ||
|
|
a907b28fe8 | ||
|
|
663612dce9 | ||
|
|
a55da89eb2 | ||
|
|
63ceb2589f | ||
|
|
dd981d72d7 | ||
|
|
8940a8ea77 | ||
|
|
57cfac3172 | ||
|
|
f475844bd0 | ||
|
|
4536453fdf | ||
|
|
3ee533b815 | ||
|
|
a744c19cce | ||
|
|
9cc66c5d67 | ||
|
|
3e5b018b55 | ||
|
|
f6fee780ee | ||
|
|
6971f23d0a | ||
|
|
6d33576936 | ||
|
|
b6c9b9059e | ||
|
|
000bd7b734 | ||
|
|
3dde4de775 | ||
|
|
9b867370c2 | ||
|
|
c092a04390 | ||
|
|
50d80c1a9e | ||
|
|
67e2147d33 | ||
|
|
cfe2aaaad8 | ||
|
|
88a91de63b | ||
|
|
2cdeb1af9c | ||
|
|
c815c3908f | ||
|
|
9e831924c6 | ||
|
|
cfc901855b | ||
|
|
a00a69e0c0 | ||
|
|
426acc701e | ||
|
|
d6eccd11a5 | ||
|
|
cb8275771f | ||
|
|
9252c01aa7 | ||
|
|
b4b544e342 | ||
|
|
f667fcbebe | ||
|
|
cc402c39be | ||
|
|
d5d921a1c8 | ||
|
|
d8794811a1 | ||
|
|
e56795307e | ||
|
|
6ad99d47aa | ||
|
|
2998a4d553 | ||
|
|
63db8fa0f3 | ||
|
|
ee14d9e0c5 | ||
|
|
1455265045 | ||
|
|
4f5cd87912 | ||
|
|
d5516c59a6 | ||
|
|
5654abe6e9 | ||
|
|
b17a50f9c2 | ||
|
|
87c9bb590b | ||
|
|
dc9360eb8a | ||
|
|
c8d2aa26f5 | ||
|
|
a5074eb191 | ||
|
|
9df0a8ff57 | ||
|
|
a79cf72ad7 | ||
|
|
db0134a44c | ||
|
|
5f6424778a | ||
|
|
950264105b | ||
|
|
7b2d9eb47f | ||
|
|
088ee66b5a | ||
|
|
2487edf4b2 | ||
|
|
e16d2e22ac | ||
|
|
7b335c69ba | ||
|
|
c1bd7043b1 | ||
|
|
71f3b5bc5f | ||
|
|
e2f33bf4b4 | ||
|
|
b1685ac90e | ||
|
|
394cc6b30f | ||
|
|
75e320e108 | ||
|
|
e8b38b3367 | ||
|
|
ffb24c973a | ||
|
|
d43693b1d8 | ||
|
|
b7f17181f9 | ||
|
|
10dc84820c | ||
|
|
5f0d92470e | ||
|
|
6bb4376e4d | ||
|
|
92da792378 | ||
|
|
81499cd362 | ||
|
|
fc91f1ae63 | ||
|
|
cec7bc880e | ||
|
|
4027df2119 | ||
|
|
93132961d3 | ||
|
|
638528d4fc | ||
|
|
67ef623761 | ||
|
|
cb3afe1878 | ||
|
|
e3f4aec1c5 | ||
|
|
d2f196da21 | ||
|
|
e491a960d7 | ||
|
|
f5c83793bf | ||
|
|
1624d56edb | ||
|
|
e334a5e454 | ||
|
|
cbbd071f4f | ||
|
|
524a6b1ffb | ||
|
|
7954adf7e3 | ||
|
|
0b31a08b9f | ||
|
|
a0884aa81c | ||
|
|
46f64d1a61 | ||
|
|
81cb6ad4fd | ||
|
|
f2f330e3e4 | ||
|
|
5b14670785 | ||
|
|
c5f68aa50f | ||
|
|
5203f89e9d | ||
|
|
8f11d73649 | ||
|
|
3d376e2a90 | ||
|
|
0e814c3359 | ||
|
|
4755cc3f7f | ||
|
|
7afbe41f95 | ||
|
|
6d4cbee499 | ||
|
|
f16a597b91 | ||
|
|
3ba0649891 | ||
|
|
297ecc371e | ||
|
|
79c8570507 | ||
|
|
8f81d2d141 | ||
|
|
325535af12 | ||
|
|
f7af4169a3 | ||
|
|
e5ec45f877 | ||
|
|
902a4e411b | ||
|
|
03ec2b4492 | ||
|
|
6a2f8a630a | ||
|
|
78e998fad9 | ||
|
|
e6ccef8ad0 | ||
|
|
acbd614b50 | ||
|
|
445e661fac | ||
|
|
d774231c05 | ||
|
|
b5d2a5dbab | ||
|
|
6ab61eb78a | ||
|
|
bbd8bb7d29 | ||
|
|
3e082b598f | ||
|
|
70510ed9d1 | ||
|
|
5da1c67dd6 | ||
|
|
dfa237958e | ||
|
|
127d53e85a | ||
|
|
7cce782143 | ||
|
|
662eb853d7 | ||
|
|
d8e58f8b38 | ||
|
|
4748e24dda | ||
|
|
5af024f482 | ||
|
|
8adc013330 | ||
|
|
4edfe962ef | ||
|
|
d80a71c852 | ||
|
|
299c9f2b4d | ||
|
|
03e3e45f07 | ||
|
|
c35c316788 | ||
|
|
d68f0c60d4 | ||
|
|
5faa28a7e7 | ||
|
|
2fffb89179 | ||
|
|
bd27ccd535 | ||
|
|
3149234f5d | ||
|
|
dcf55c93a8 | ||
|
|
3f3394c1e9 | ||
|
|
2b22dd4a1f | ||
|
|
7f1b1484ac | ||
|
|
a70710ca6a | ||
|
|
cd3baf5bd9 | ||
|
|
1138d6387e | ||
|
|
b664fd391a | ||
|
|
11cc65bae3 | ||
|
|
eaafa56503 | ||
|
|
09c6d58639 | ||
|
|
1767a009a7 | ||
|
|
a2e25512ec | ||
|
|
824b931764 | ||
|
|
0cb75fd421 | ||
|
|
36f0d814f6 | ||
|
|
2aa0c46d30 | ||
|
|
ff63dbfdaa | ||
|
|
5cacdbd192 | ||
|
|
c99bf206cc | ||
|
|
f546bb2681 | ||
|
|
bef750e047 | ||
|
|
6998599377 | ||
|
|
11164f9a73 | ||
|
|
b867795a08 | ||
|
|
9998205051 | ||
|
|
7692f19ea6 | ||
|
|
d8361cc288 | ||
|
|
93d0724c60 | ||
|
|
2c03f193f0 | ||
|
|
7028a1f4ca | ||
|
|
32d62dd9ba | ||
|
|
401fc98b4d | ||
|
|
993589427b | ||
|
|
2390568bdf | ||
|
|
50ccf6da46 | ||
|
|
44066e6599 | ||
|
|
fc9c616869 | ||
|
|
f09f35e44b | ||
|
|
f81fb0c673 | ||
|
|
b0b4e80da4 | ||
|
|
923982e449 | ||
|
|
d05c97946d | ||
|
|
504a1fd01b | ||
|
|
5efacec417 | ||
|
|
595d10fc44 | ||
|
|
2128f0a601 | ||
|
|
0b0a45da05 | ||
|
|
94fb4b4411 | ||
|
|
fc7932d3bf | ||
|
|
97a5311593 | ||
|
|
c7db676b2b | ||
|
|
f8c8b3b2e6 | ||
|
|
df4cb5b189 | ||
|
|
d2b2a53109 | ||
|
|
5107f88998 | ||
|
|
78f2e1698f | ||
|
|
6887cd098b | ||
|
|
0881d745f5 | ||
|
|
94b3a07f90 | ||
|
|
dc280b4023 | ||
|
|
edce6d6cd3 | ||
|
|
23dc47a5b9 | ||
|
|
683b4f46d9 | ||
|
|
ac021bce2a | ||
|
|
b0c34cbca0 | ||
|
|
4a0c8270ef | ||
|
|
793f1e2f87 | ||
|
|
0101b6fa25 | ||
|
|
47dff850fb | ||
|
|
fe7503c3ac | ||
|
|
5df7d8a530 | ||
|
|
ee129a3373 | ||
|
|
b979b1fc86 | ||
|
|
361ebb8b80 | ||
|
|
fd49466e0d | ||
|
|
bf8f00f877 | ||
|
|
9f0b7604cb | ||
|
|
6d151e7377 | ||
|
|
6ecc94526e | ||
|
|
1462911ffa | ||
|
|
952f7a933b | ||
|
|
b76c4ef352 | ||
|
|
48048cdb82 | ||
|
|
a555f1c41b | ||
|
|
2ab7ebb2c0 | ||
|
|
e2b0113687 | ||
|
|
dc7e9b541a | ||
|
|
b44ef7dfc0 | ||
|
|
4d1f4a64fa | ||
|
|
fd7e6e4993 | ||
|
|
b749c79a81 | ||
|
|
5d019a8e5b | ||
|
|
4e203aebfe | ||
|
|
fb2bede135 | ||
|
|
5a88909cd2 | ||
|
|
ab1e6eed0c | ||
|
|
ba5cf81bb4 | ||
|
|
017034a788 | ||
|
|
66ff6f8199 | ||
|
|
ddccbad020 | ||
|
|
578fb068ee | ||
|
|
d624eb3842 | ||
|
|
f46fedda4b | ||
|
|
6e5ac2ca3d | ||
|
|
20de874ea1 | ||
|
|
c8a207083b | ||
|
|
ab7505c67a | ||
|
|
b26050b6bf | ||
|
|
84e9c8efd4 | ||
|
|
2811a14627 | ||
|
|
3b6d4d44b0 | ||
|
|
71bc304c51 | ||
|
|
7b0652ff11 | ||
|
|
61a3054655 | ||
|
|
54c1cc3661 | ||
|
|
ee0f9e04de | ||
|
|
ebe5289d6e | ||
|
|
15ba15c602 | ||
|
|
fd0448efc4 | ||
|
|
43fa2623d4 | ||
|
|
c1b9babf33 | ||
|
|
4c8edf9d9a | ||
|
|
ff4271f51d | ||
|
|
2424ab01cf | ||
|
|
b38dfeeed7 | ||
|
|
7912cd190f | ||
|
|
11962ad930 | ||
|
|
a8e2c65072 | ||
|
|
1b8ca5f467 | ||
|
|
46fff6da5b | ||
|
|
47e75f64ed | ||
|
|
fb95c8c9d4 | ||
|
|
66cc97214c | ||
|
|
f52423ed70 | ||
|
|
29bea052eb | ||
|
|
b683f1fce4 | ||
|
|
2ec55473a5 | ||
|
|
2fe89ffcbc | ||
|
|
7e88a10884 | ||
|
|
f260051160 | ||
|
|
b8dacc2459 | ||
|
|
b311dd99dc | ||
|
|
1d7a9843d8 | ||
|
|
03e59ad00f | ||
|
|
c083af1c76 | ||
|
|
6abb0a2a75 | ||
|
|
e672e7f1a6 | ||
|
|
852a638ea1 | ||
|
|
ec1b642614 | ||
|
|
145dac72af | ||
|
|
7d2679b2a3 | ||
|
|
ffbcd2183b | ||
|
|
b9d646868c | ||
|
|
2ce530a644 | ||
|
|
1c8e9ba1cf | ||
|
|
9a2560dbc2 | ||
|
|
f900c4299d | ||
|
|
f78494f882 | ||
|
|
b55f10b59d | ||
|
|
277ee90421 | ||
|
|
fcdde83038 | ||
|
|
12fb29ecce | ||
|
|
ae7dd63d6b | ||
|
|
48f680f7bd | ||
|
|
e3d4e393b3 | ||
|
|
81f13c255a | ||
|
|
97ce322d0c | ||
|
|
a3e876d9a2 | ||
|
|
234356f8f2 | ||
|
|
d3222f7bdc | ||
|
|
a5658e72c7 | ||
|
|
d1e1fcfabe | ||
|
|
9d6bd0770c | ||
|
|
6f76ee389b | ||
|
|
87aa74f231 | ||
|
|
a0dc80c1a5 | ||
|
|
d8dbe25a64 | ||
|
|
e3bec085df | ||
|
|
4d4b92b33f | ||
|
|
cc58c769cc | ||
|
|
37f08f6fc4 | ||
|
|
73e02e7c06 | ||
|
|
7a1b6fccc2 | ||
|
|
2473e3f91a | ||
|
|
0a6bb2805c | ||
|
|
1eecc6f065 | ||
|
|
b8b4fe4958 | ||
|
|
6dd8b6798b | ||
|
|
69d0a3f4aa | ||
|
|
ce81934890 | ||
|
|
ba06f47e3e | ||
|
|
cfc37d4af6 | ||
|
|
43cd6edda2 | ||
|
|
0bd262d82f | ||
|
|
f09bafa104 | ||
|
|
20e34ee365 | ||
|
|
7a6bd8f661 | ||
|
|
54c3765e36 | ||
|
|
4d7f563b0d | ||
|
|
4e0ecaa7be | ||
|
|
4f41d238eb | ||
|
|
7c3d021427 | ||
|
|
bfde036484 | ||
|
|
84ef4ad030 | ||
|
|
3b85fccd60 | ||
|
|
830d0f6e6a | ||
|
|
1ceede27a3 | ||
|
|
fa33cdaca4 | ||
|
|
f39fbb55a1 | ||
|
|
f3ffa0ab88 | ||
|
|
48e1fcc38e | ||
|
|
5b889c4c0c | ||
|
|
616ca77c39 | ||
|
|
59bf064783 | ||
|
|
220393c445 | ||
|
|
b976c03fb0 | ||
|
|
dad0493666 | ||
|
|
776613c507 | ||
|
|
7570d9d319 | ||
|
|
2e648d1062 | ||
|
|
5ad02ae9cc | ||
|
|
99fc568419 | ||
|
|
5f835716e0 | ||
|
|
1f6fe787eb | ||
|
|
1b08d30611 | ||
|
|
05ad33a756 | ||
|
|
b103366a10 | ||
|
|
343d37d4b8 | ||
|
|
60dc050633 | ||
|
|
96c3583393 | ||
|
|
43656dbcca | ||
|
|
7c38bbe6ab | ||
|
|
c6c6448501 | ||
|
|
7ff9da2d7f | ||
|
|
39893cc66d | ||
|
|
5110618d7d | ||
|
|
a856831ee1 | ||
|
|
c5fc6a4212 | ||
|
|
7708646c68 | ||
|
|
c84dbac020 | ||
|
|
56c2297e02 | ||
|
|
6956805e62 | ||
|
|
7736e30043 | ||
|
|
ace441f072 | ||
|
|
a37e99ce39 | ||
|
|
6b1bdbaa78 | ||
|
|
b4077a2fc3 | ||
|
|
2f660c6fec | ||
|
|
cc0511a2aa | ||
|
|
2f367efa70 | ||
|
|
bff1b54b7e | ||
|
|
010e9556b8 | ||
|
|
7c54c01dfe | ||
|
|
5709c8dbec | ||
|
|
0f434d7fe7 | ||
|
|
a2d7e70431 | ||
|
|
0c0c462372 | ||
|
|
f630539d9a | ||
|
|
7e27ddcc94 | ||
|
|
f9b61c24e9 | ||
|
|
d26a138e3e | ||
|
|
e695a4e993 | ||
|
|
8cae6aed4a | ||
|
|
56b72dd544 | ||
|
|
b935b30e00 | ||
|
|
b9a6c6248d | ||
|
|
09d8703545 | ||
|
|
6e25211f12 | ||
|
|
6f1dc74f66 | ||
|
|
61b1317fc5 | ||
|
|
129ee2d5aa | ||
|
|
2358ea1f89 | ||
|
|
87a172afd5 | ||
|
|
5a64b6996e | ||
|
|
9ba07982bf | ||
|
|
98c41ec003 | ||
|
|
bc9875e7fc | ||
|
|
b8bc7eded3 | ||
|
|
6625deadf7 | ||
|
|
c781e4ff4e | ||
|
|
b5a2508027 | ||
|
|
4916917ac9 | ||
|
|
4f9b1dae04 | ||
|
|
9b841399f7 | ||
|
|
45fdc81aa7 | ||
|
|
fdb28a3e52 | ||
|
|
a151a9334e | ||
|
|
618c7a294d | ||
|
|
f926d61213 | ||
|
|
d572add506 | ||
|
|
012da7397f | ||
|
|
92e4908ea8 | ||
|
|
a051d628ab | ||
|
|
0116346035 | ||
|
|
3049b7b0ae | ||
|
|
0c484d26ee | ||
|
|
55bea5262e | ||
|
|
f64d0dcc16 | ||
|
|
ad23a671a2 | ||
|
|
718e6b433d | ||
|
|
65e2965df2 | ||
|
|
2bf815942c | ||
|
|
b5547c5cdf | ||
|
|
371fd00606 | ||
|
|
4f2a448e7e | ||
|
|
2755641e6b | ||
|
|
3e80066a68 | ||
|
|
2e78f77be6 | ||
|
|
d042d9a7e8 | ||
|
|
c4064a2ed1 | ||
|
|
0ac0fad091 | ||
|
|
7fdad67ea4 | ||
|
|
be21bc1654 | ||
|
|
e65fb4390f | ||
|
|
9f7bd4a0bd | ||
|
|
b43817cbde | ||
|
|
da0e2ebb99 | ||
|
|
aa87f33676 | ||
|
|
4e7d4383e8 | ||
|
|
0702aa1c79 | ||
|
|
cd97f89d90 | ||
|
|
eab6c4d2bd | ||
|
|
20775cbc79 | ||
|
|
90ecb1997f | ||
|
|
64b1483fa5 | ||
|
|
8365a719d8 | ||
|
|
068e793682 | ||
|
|
2e1c72e401 | ||
|
|
3e213c6996 | ||
|
|
ec65abadc3 | ||
|
|
d24b56b5c1 | ||
|
|
b56f4e1653 | ||
|
|
efba8773c5 | ||
|
|
15339a8c44 | ||
|
|
050021de5c | ||
|
|
16cff71cfb | ||
|
|
76226a319f | ||
|
|
eed138a2fb | ||
|
|
fd202fc940 | ||
|
|
5364b57cee | ||
|
|
3136b80f26 | ||
|
|
f3471b082f | ||
|
|
9d24ef7791 | ||
|
|
6223ccd4bc | ||
|
|
a81cc27414 | ||
|
|
78481516b0 | ||
|
|
423b74273e | ||
|
|
83e0a988d9 | ||
|
|
1adea88ff1 | ||
|
|
dda912c8c5 | ||
|
|
f3b31bb306 | ||
|
|
3b31c4e05a | ||
|
|
1155108086 | ||
|
|
73bb7ded09 | ||
|
|
d8655eb631 | ||
|
|
55cddfef91 | ||
|
|
c35dc736f4 | ||
|
|
23b762a500 | ||
|
|
b9b638de45 | ||
|
|
5f57507dda | ||
|
|
852eb6612c | ||
|
|
d96aac7bbf | ||
|
|
9018479959 | ||
|
|
ce8a99e840 | ||
|
|
9236f66bc6 | ||
|
|
37b3964351 | ||
|
|
a60d902fda | ||
|
|
a97ce6f9aa | ||
|
|
a15bce04e8 | ||
|
|
34fa68f852 | ||
|
|
e5fef55fc7 | ||
|
|
c5cca1b14d | ||
|
|
f34ec1cc4b | ||
|
|
95aafa5c99 | ||
|
|
00160b0c45 | ||
|
|
a7e6d56bc9 | ||
|
|
9dc8d5e2f4 | ||
|
|
cf7cf679e0 | ||
|
|
4e7e0d9480 | ||
|
|
fbb3013b20 | ||
|
|
ca37e2aa84 | ||
|
|
e6bf60d457 | ||
|
|
35835fda34 | ||
|
|
a3133de6cf | ||
|
|
40fb10a2ec | ||
|
|
856c735ba3 | ||
|
|
16fb0df55d | ||
|
|
5658acbb07 | ||
|
|
8b5faac0e0 | ||
|
|
10be9b58ce | ||
|
|
832840be08 | ||
|
|
5806156495 | ||
|
|
0be9b908ab | ||
|
|
718d2b7567 | ||
|
|
fed9754908 | ||
|
|
c0e09ca429 | ||
|
|
4a3737b846 | ||
|
|
b4add4a0ea | ||
|
|
5aabbd629e | ||
|
|
a5ef50317d | ||
|
|
48a8cc927a | ||
|
|
cbb97d7d57 | ||
|
|
5eee344035 | ||
|
|
4c7e0c7174 | ||
|
|
37bb9c86f4 | ||
|
|
83ea6ffbf7 | ||
|
|
f4945c2cd2 | ||
|
|
24d9c7825a | ||
|
|
2aa7bb4639 | ||
|
|
110b3bcef3 | ||
|
|
f625efc76d | ||
|
|
22608a9802 | ||
|
|
abf6c1328d | ||
|
|
225058ab2b | ||
|
|
275dc6caa2 | ||
|
|
ceb7b12154 | ||
|
|
849da26198 | ||
|
|
5808c991bb | ||
|
|
e8554cb221 | ||
|
|
46092c060d | ||
|
|
736eebd45c | ||
|
|
f72db53345 | ||
|
|
b8c017259e | ||
|
|
4b4a237b9a | ||
|
|
17034e992f | ||
|
|
bf143038b0 | ||
|
|
73805ad3bb | ||
|
|
7006e35ebf | ||
|
|
fc598018b2 | ||
|
|
d01ec85c32 | ||
|
|
04ee918152 | ||
|
|
943b70647b | ||
|
|
edba5d5dca | ||
|
|
86be5d1994 | ||
|
|
5be2308606 | ||
|
|
a2e81d08d7 | ||
|
|
9f5f0e79f4 | ||
|
|
3f8c4c94ce | ||
|
|
46895c0037 | ||
|
|
65c98a4840 | ||
|
|
67c58c10b2 | ||
|
|
8d82a4616f | ||
|
|
01a272f8e6 | ||
|
|
0235a397cf | ||
|
|
e37ee6f812 | ||
|
|
2b5ea47d9f | ||
|
|
0fcdaf3f60 | ||
|
|
b2cb38b61d | ||
|
|
fd1fe168cc | ||
|
|
9deb80ff9b | ||
|
|
75fdd7390f | ||
|
|
7cd6c17b55 | ||
|
|
c273d8d17f | ||
|
|
c1b02f58ff | ||
|
|
0bac2dfb62 | ||
|
|
4f0288e396 | ||
|
|
55f0399325 | ||
|
|
e756c6e50b | ||
|
|
b8c022dc70 | ||
|
|
d823ddd8aa | ||
|
|
352b42bba0 | ||
|
|
89894131d9 | ||
|
|
5ddcb0a668 | ||
|
|
5d5b1f0a63 | ||
|
|
03e638786b | ||
|
|
7d5b2f8dc8 | ||
|
|
13257d17fc | ||
|
|
5e8a589d9a | ||
|
|
44711043b9 | ||
|
|
80ee8aa860 | ||
|
|
cd6685b974 | ||
|
|
b5f464000b | ||
|
|
27159323d5 | ||
|
|
9551ce8a8b | ||
|
|
525471f749 | ||
|
|
57bedd0300 | ||
|
|
c3345a1a15 | ||
|
|
15f6bd86a1 | ||
|
|
e2ad1c9da2 | ||
|
|
7082669612 | ||
|
|
d595d24769 | ||
|
|
4287d66f6f | ||
|
|
311361d105 | ||
|
|
4a88e39deb | ||
|
|
4511590263 | ||
|
|
d0c30ffa1c | ||
|
|
813c2bff85 | ||
|
|
0c3cf43841 | ||
|
|
6c651f7e3e | ||
|
|
ef7fc92920 | ||
|
|
ccaf70c749 | ||
|
|
2299bf9d86 | ||
|
|
b65f8f32ca | ||
|
|
47695d3a72 | ||
|
|
439c660b2e | ||
|
|
4293d7d4b2 | ||
|
|
e368e66b8a | ||
|
|
992a7f9e84 | ||
|
|
ecf0faf00a | ||
|
|
64178f3fd1 | ||
|
|
dab6588800 | ||
|
|
f71bb32592 | ||
|
|
0c44212d92 | ||
|
|
ce149c91e9 | ||
|
|
05dfd48f57 | ||
|
|
ef955b866d | ||
|
|
a843b5a1b9 | ||
|
|
8305d58ccc | ||
|
|
6af79d5f41 | ||
|
|
3b326d6f9c | ||
|
|
1456e5073e | ||
|
|
6a13dbf66a | ||
|
|
c5b1718e8e | ||
|
|
e2bcc24867 | ||
|
|
c86cc22a93 | ||
|
|
765577ae9e | ||
|
|
d1d15644b5 | ||
|
|
5d41b213db | ||
|
|
aa515b1192 | ||
|
|
068660fe3f | ||
|
|
99ffaf97d1 | ||
|
|
b53999c1cf | ||
|
|
57f87d7dc0 | ||
|
|
760f6a873d | ||
|
|
675bd43ff8 | ||
|
|
07532ce001 | ||
|
|
908a7055c7 | ||
|
|
2be371caed | ||
|
|
f4f3f2e307 | ||
|
|
edbf76c7dc | ||
|
|
f8615f45f0 | ||
|
|
c3bc3e9911 | ||
|
|
8adcca1df2 | ||
|
|
bdde8669ac | ||
|
|
59db2df525 | ||
|
|
3b83ff5b0e | ||
|
|
19fb290a51 | ||
|
|
e1e823d9e0 | ||
|
|
7ed477b670 | ||
|
|
1960fb0b6a | ||
|
|
e913482790 | ||
|
|
2e5bd76d31 | ||
|
|
876ae979da | ||
|
|
d918c15ad6 | ||
|
|
d57cb307c3 | ||
|
|
cbcf1bcd99 | ||
|
|
96bc10583f | ||
|
|
5ca4d29a36 | ||
|
|
2efb666fae | ||
|
|
89dce1068f | ||
|
|
9854125af9 | ||
|
|
6e307ab1f0 | ||
|
|
c4f0d1bef6 | ||
|
|
5201788818 | ||
|
|
8472bc9755 | ||
|
|
ce0f531831 | ||
|
|
18e9c3ccb5 | ||
|
|
19afe8e69c | ||
|
|
8e5bace7cc | ||
|
|
1ca85e9d7b | ||
|
|
eb24af8266 | ||
|
|
12ba01eb87 | ||
|
|
a0e30bdccc | ||
|
|
4663e22128 | ||
|
|
7bd1d16d24 | ||
|
|
fd838cfd43 | ||
|
|
1eca79b4cb | ||
|
|
1d694f5f2c | ||
|
|
96a046b165 | ||
|
|
5ff9c9c469 | ||
|
|
836cdd87bc | ||
|
|
0af6dd4d44 | ||
|
|
034d62a643 | ||
|
|
417224602e | ||
|
|
c711aeae7f | ||
|
|
1f12543e3e | ||
|
|
dc31175b5d | ||
|
|
719b8112eb | ||
|
|
f19a3c507b | ||
|
|
2416ec396a | ||
|
|
d1fe92e967 | ||
|
|
ff5fd49b89 | ||
|
|
1015e0d94e | ||
|
|
d872828e7d | ||
|
|
8f6ad6d1bd | ||
|
|
05fd629ad4 | ||
|
|
efdb0dd6bb | ||
|
|
2e0482beef | ||
|
|
0f25743da4 | ||
|
|
e970bf185a | ||
|
|
60f3547b01 | ||
|
|
ae09db428b | ||
|
|
ebca7ca150 | ||
|
|
b6ef6806a0 | ||
|
|
20a9cb30c4 | ||
|
|
a43112f469 | ||
|
|
2a95b2f530 |
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gradle" # See documentation for possible values
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
19
.github/workflows/android.yml
vendored
19
.github/workflows/android.yml
vendored
@@ -2,9 +2,13 @@ name: Android CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches:
|
||||||
|
- master
|
||||||
|
- staging
|
||||||
|
- trying
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -13,17 +17,20 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
- name: Fail on bad translations
|
||||||
|
run: if grep -ri "<xliff" app/src/main/res/values*/strings.xml; then echo "Invalidly escaped translations found"; exit 1; fi
|
||||||
- uses: gradle/wrapper-validation-action@v1
|
- uses: gradle/wrapper-validation-action@v1
|
||||||
- name: set up JDK 1.8
|
- name: set up JDK 11
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
distribution: 'adopt'
|
||||||
|
java-version: '11'
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew assembleRelease
|
run: ./gradlew assembleRelease
|
||||||
- name: Check lint
|
- name: Check lint
|
||||||
run: ./gradlew lintRelease
|
run: ./gradlew lintRelease
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: ./gradlew testReleaseUnitTest
|
run: ./gradlew testReleaseUnitTest || ./gradlew testReleaseUnitTest
|
||||||
- name: SpotBugs
|
- name: SpotBugs
|
||||||
run: ./gradlew spotbugsRelease
|
run: ./gradlew spotbugsRelease
|
||||||
- name: Archive test results
|
- name: Archive test results
|
||||||
|
|||||||
23
.github/workflows/autoclose-needs-info.yml
vendored
Normal file
23
.github/workflows/autoclose-needs-info.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: 'Close issues and PRs needing info for too long'
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v4
|
||||||
|
with:
|
||||||
|
days-before-stale: -1
|
||||||
|
days-before-close: 90
|
||||||
|
close-issue-message: 'This issue is missing necessary information and cannot be worked on in its current state. It has therefore been closed to keep the issue tracker clean. If you have more information, feel free to reopen it.'
|
||||||
|
close-pr-message: 'This PR is missing necessary information and cannot be merged in its current state. It has therefore been closed to keep the issue tracker clean. If you have more information, feel free to reopen it.'
|
||||||
|
only-labels: 'needs info'
|
||||||
|
stale-issue-label: 'needs info'
|
||||||
|
stale-pr-label: 'needs info'
|
||||||
|
remove-stale-when-updated: false
|
||||||
@@ -10,20 +10,23 @@ on:
|
|||||||
- '**.webp'
|
- '**.webp'
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
# Only run on Pull Requests within the same repository, and not from forks.
|
||||||
|
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||||
name: calibreapp/image-actions
|
name: calibreapp/image-actions
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@v2
|
||||||
- name: Compress Images
|
- name: Compress Images
|
||||||
id: calibre
|
id: calibre
|
||||||
uses: calibreapp/image-actions@master
|
uses: calibreapp/image-actions@1.1.0
|
||||||
with:
|
with:
|
||||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
ignorePaths: 'app/src/test'
|
||||||
compressOnly: true
|
compressOnly: true
|
||||||
- name: Create New Pull Request If Needed
|
- name: Create New Pull Request If Needed
|
||||||
if: steps.calibre.outputs.markdown != ''
|
if: steps.calibre.outputs.markdown != ''
|
||||||
uses: peter-evans/create-pull-request@master
|
uses: peter-evans/create-pull-request@v3
|
||||||
with:
|
with:
|
||||||
title: Compressed Images
|
title: Compressed Images
|
||||||
branch-suffix: timestamp
|
branch-suffix: timestamp
|
||||||
|
|||||||
26
.github/workflows/changelog-to-fastlane.yml
vendored
Normal file
26
.github/workflows/changelog-to-fastlane.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Convert CHANGELOG to Fastlane
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
convert_changelog_to_fastlane:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Convert CHANGELOG to Fastlane
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
id: checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Run converter script
|
||||||
|
run: python .scripts/changelog_to_fastlane.py
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@v3
|
||||||
|
with:
|
||||||
|
title: "Update Fastlane changelogs"
|
||||||
|
commit-message: "Update Fastlane changelogs"
|
||||||
|
branch-suffix: timestamp
|
||||||
73
.github/workflows/codeql-analysis.yml
vendored
Normal file
73
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
schedule:
|
||||||
|
- cron: '33 1 * * 4'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'java' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||||
|
# Learn more:
|
||||||
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
||||||
25
.github/workflows/contributors-to-file.yml
vendored
Normal file
25
.github/workflows/contributors-to-file.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
name: Write contributors to file
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '3 4 * * 0'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
contributors_to_file:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
name: Write contributors to file
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
id: checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Update contributors
|
||||||
|
id: update_contributors
|
||||||
|
uses: TheLastProject/contributors-to-file-action@v2
|
||||||
|
with:
|
||||||
|
file_in_repo: app/src/main/res/raw/contributors.txt
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@v3
|
||||||
|
with:
|
||||||
|
title: "Update contributors"
|
||||||
|
commit-message: "Update contributors"
|
||||||
|
branch-suffix: timestamp
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ build/
|
|||||||
captures/
|
captures/
|
||||||
**/release
|
**/release
|
||||||
**/debug
|
**/debug
|
||||||
|
app/*.log
|
||||||
|
|||||||
33
.scripts/changelog_to_fastlane.py
Normal file
33
.scripts/changelog_to_fastlane.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
changelogs = {}
|
||||||
|
|
||||||
|
with open('CHANGELOG.md') as changelog:
|
||||||
|
version_code = None
|
||||||
|
text = []
|
||||||
|
|
||||||
|
for line in changelog:
|
||||||
|
if line.startswith("## "):
|
||||||
|
if version_code != None:
|
||||||
|
changelogs[version_code] = text
|
||||||
|
|
||||||
|
text = []
|
||||||
|
match = re.match("## \S* - (\d*).*", line)
|
||||||
|
if not match:
|
||||||
|
raise ValueError(f"Invalid version line: {line}")
|
||||||
|
version_code = match.group(1)
|
||||||
|
elif line:
|
||||||
|
# Turn Markdown [links](to_url) into links (to_url)
|
||||||
|
text.append(re.sub(r'\[(.*?)\]\((.*?)\)', r'\1 (\2)', line))
|
||||||
|
|
||||||
|
for version, description in changelogs.items():
|
||||||
|
description = "".join(description).strip()
|
||||||
|
|
||||||
|
if not description:
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(os.path.join("fastlane", "metadata", "android", "en-US", "changelogs", f"{version}.txt"), "w") as fastlane_file:
|
||||||
|
fastlane_file.write(description)
|
||||||
418
CHANGELOG.md
418
CHANGELOG.md
@@ -1,13 +1,152 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## v2.0 (2021-07-14)
|
## Unreleased - 96
|
||||||
|
|
||||||
Breaking changes:
|
- Add CODE 93 support
|
||||||
- The backup format changed, see https://github.com/TheLastProject/Catima/wiki/Export-format
|
|
||||||
- The URL sharing format changed, see https://github.com/TheLastProject/Catima/wiki/Card-sharing-URL-format
|
|
||||||
|
|
||||||
Changes:
|
## v2.11.2 - 95 (2021-12-04)
|
||||||
|
|
||||||
|
- Fix crash on sharing card
|
||||||
|
|
||||||
|
## v2.11.1 - 94 (2021-11-30)
|
||||||
|
|
||||||
|
- Fix blurriness of main screen letter icons
|
||||||
|
- Fix icons sometimes disappearing after selection
|
||||||
|
- Fix status bar icons possibly being invisible on Android 5
|
||||||
|
|
||||||
|
## v2.11.0 - 93 (2021-11-28)
|
||||||
|
|
||||||
|
- Add Catima to [Quick Access Device Controls](https://developer.android.com/guide/topics/ui/device-control)
|
||||||
|
- Fix some groups not showing up correctly in group management screen
|
||||||
|
|
||||||
|
## v2.10.0 - 92 (2021-11-20)
|
||||||
|
|
||||||
|
- New main screen layout
|
||||||
|
- Fix bottomsheet sizing issues when switching in and out of fullscreen
|
||||||
|
|
||||||
|
## v2.9.0 - 91 (2021-11-14)
|
||||||
|
|
||||||
|
- Improved group management support
|
||||||
|
- Support cropping images
|
||||||
|
- Fix image data loss when saving after rotating in edit view
|
||||||
|
- Ability to set a custom image as card icon
|
||||||
|
|
||||||
|
## v2.8.1 - 90 (2021-10-27)
|
||||||
|
|
||||||
|
- Fix dots in card view having the wrong colour when changing theme manually
|
||||||
|
- Fix crash in card view on rotation/theme change
|
||||||
|
- Fix flashing of cards list
|
||||||
|
- Fix text overlaying star icon
|
||||||
|
|
||||||
|
## v2.8.0 - 89 (2021-10-25)
|
||||||
|
|
||||||
|
- Fix swiping between groups not working on an empty group
|
||||||
|
- Allow password-protecting exports
|
||||||
|
- Improve usage of space for QR codes
|
||||||
|
- Save the last used zoom level per card
|
||||||
|
- Fix a crash when swiping right after a tap
|
||||||
|
|
||||||
|
## v2.7.3 - 88 (2021-10-10)
|
||||||
|
|
||||||
|
- Fix incorrect migration making first card become invisible
|
||||||
|
|
||||||
|
## v2.7.2 - 87 (2021-10-09)
|
||||||
|
|
||||||
|
- Fix regression breaking import/export
|
||||||
|
|
||||||
|
## v2.7.1 - 86 (2021-10-07)
|
||||||
|
|
||||||
|
- Improve search with spaces
|
||||||
|
|
||||||
|
## v2.7.0 - 85 (2021-10-05)
|
||||||
|
|
||||||
|
Android 4.4 is no longer supported starting with this release. If you want to use Catima on Android 4.4, please use version 2.6.1.
|
||||||
|
|
||||||
|
- Improved Android 12 support
|
||||||
|
- Improved about screen
|
||||||
|
- Search now ignores accents
|
||||||
|
|
||||||
|
## v2.6.1 - 84 (2021-09-25)
|
||||||
|
|
||||||
|
- Minor bugfixes and improvements
|
||||||
|
|
||||||
|
## v2.6.0 - 83 (2021-09-19)
|
||||||
|
|
||||||
|
- Support for changing the sorting order
|
||||||
|
- Prevent Out Of Memory on scanning large pictures for barcode
|
||||||
|
|
||||||
|
## v2.5.0 - 82 (2021-09-10)
|
||||||
|
|
||||||
|
- Improved support for screen readers
|
||||||
|
- Don't crash when trying to open a video from gallery
|
||||||
|
- Swipe support on loyalty card view screen
|
||||||
|
- Don't reset group on back button press
|
||||||
|
|
||||||
|
## v2.4.0 - 81 (2021-08-29)
|
||||||
|
|
||||||
|
- Improve card list for landscape and tablet display
|
||||||
|
- Add theming colour support (thanks, Subhashish Anand!)
|
||||||
|
- Don't close scan activity on camera error (so manual entry is still possible)
|
||||||
|
- Add all contributors to the about dialog
|
||||||
|
|
||||||
|
## v2.3.0 - 80 (2021-08-19)
|
||||||
|
|
||||||
|
- Fix images not imported from backup
|
||||||
|
- Option to override language
|
||||||
|
|
||||||
|
## v2.2.3 - 79 (2021-08-13)
|
||||||
|
|
||||||
|
- Fix widget creating different-looking shortcut than app shortcuts
|
||||||
|
- Replace default Android black screen with splash screen
|
||||||
|
|
||||||
|
## v2.2.2 - 78 (2021-08-08)
|
||||||
|
|
||||||
|
- Fix crash on rotation in loyalty card edit activity
|
||||||
|
|
||||||
|
## v2.2.1 - 77 (2021-08-07)
|
||||||
|
|
||||||
|
- Improve Stocard importer
|
||||||
|
- Fix importing Catima export with multiline note
|
||||||
|
- Scale card title in acceptable range
|
||||||
|
- Animation improvements
|
||||||
|
|
||||||
|
## v2.2.0 - 76 (2021-08-02)
|
||||||
|
|
||||||
|
- Make links in notes clickable
|
||||||
|
- Pre-select group the user is currently in when creating a new card
|
||||||
|
- Comma-separate group names in loyalty card view
|
||||||
|
- Fix maximize button appearing on no barcode
|
||||||
|
|
||||||
|
## v2.1.0 - 75 (2021-08-01)
|
||||||
|
|
||||||
|
- Fix selected colour in colour changing dialog
|
||||||
|
- Support for deleting multiple cards at once
|
||||||
|
- Fix possible ArithmeticException when resizing image
|
||||||
|
- Fix fullscreen is closed when rotating device
|
||||||
|
|
||||||
|
## v2.0.4 - 74 (2021-07-27)
|
||||||
|
|
||||||
|
- Fix shortcut creation
|
||||||
|
- Generate card-specific shortcut icon
|
||||||
|
- Fix ability to change loyalty card colour
|
||||||
|
|
||||||
|
## v2.0.3 - 73 (2021-07-25)
|
||||||
|
|
||||||
|
- Fix loading photos when editing existing card
|
||||||
|
|
||||||
|
## v2.0.2 - 72 (2021-07-25)
|
||||||
|
|
||||||
|
- Fix inability to configure photos in new loyalty card
|
||||||
|
|
||||||
|
## v2.0.1 - 71 (2021-07-21)
|
||||||
|
|
||||||
|
- Several minor translation and UI fixes
|
||||||
|
- Fix crash in import/sharing loyalty card on Android 6
|
||||||
|
|
||||||
|
## v2.0 - 70 (2021-07-14)
|
||||||
|
|
||||||
|
- BREAKING CHANGE: The backup format changed, see https://github.com/TheLastProject/Catima/wiki/Export-format
|
||||||
|
- BREAKING CHANGE: The URL sharing format changed, see https://github.com/TheLastProject/Catima/wiki/Card-sharing-URL-format
|
||||||
- Make it possible to enable or disable the flashlight while scanning
|
- Make it possible to enable or disable the flashlight while scanning
|
||||||
- Add UPC-E support
|
- Add UPC-E support
|
||||||
- Support adding a front and back photo to each card
|
- Support adding a front and back photo to each card
|
||||||
@@ -18,26 +157,20 @@ Changes:
|
|||||||
- Fix Floating Action Buttons being behind other UI elements on Android 4
|
- Fix Floating Action Buttons being behind other UI elements on Android 4
|
||||||
- Fix loyalty card viewer appbar top margin
|
- Fix loyalty card viewer appbar top margin
|
||||||
|
|
||||||
## v1.14.1 (2021-06-14)
|
## v1.14.1 - 69 (2021-06-14)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Add missing barcode ID to export
|
- Add missing barcode ID to export
|
||||||
- Don't show update barcode dialog if value is the same as card ID
|
- Don't show update barcode dialog if value is the same as card ID
|
||||||
- Add Finnish translation
|
- Add Finnish translation
|
||||||
|
|
||||||
## v1.14 (2021-06-07)
|
## v1.14 - 68 (2021-06-07)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Support new PDF417 export from Voucher Vault
|
- Support new PDF417 export from Voucher Vault
|
||||||
- Support copying multiple barcodes at once
|
- Support copying multiple barcodes at once
|
||||||
- Support sharing multiple loyalty cards at once
|
- Support sharing multiple loyalty cards at once
|
||||||
- Ask to update barcode value if card ID changes
|
- Ask to update barcode value if card ID changes
|
||||||
|
|
||||||
## v1.13 (2021-04-10)
|
## v1.13 - 67 (2021-04-10)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Add option to set a separate barcode value from card ID
|
- Add option to set a separate barcode value from card ID
|
||||||
- Simplify font sizing configuration
|
- Simplify font sizing configuration
|
||||||
@@ -46,175 +179,125 @@ Changes:
|
|||||||
- Always show all barcode types in manual entry
|
- Always show all barcode types in manual entry
|
||||||
- Remove privacy policy first start dialog
|
- Remove privacy policy first start dialog
|
||||||
|
|
||||||
## v1.12 (2021-03-30)
|
## v1.12 - 66 (2021-03-30)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Support importing [Fidme](https://play.google.com/store/apps/details?id=fr.snapp.fidme) exports
|
- 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
|
- Allow importing a card from a picture stored in the user's Android gallery
|
||||||
- Fix multiline note cutoff
|
- Fix multiline note cutoff
|
||||||
- Change "Thank you" text on privacy dialog to "Accept" because Huawei is overly pedantic
|
- Change "Thank you" text on privacy dialog to "Accept" because Huawei is overly pedantic
|
||||||
|
|
||||||
## v1.11 (2021-03-21)
|
## v1.11 - 64 (2021-03-21)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Add privacy policy dialog on first start (required by Huawei)
|
- Add privacy policy dialog on first start (required by Huawei)
|
||||||
|
|
||||||
## v1.10 (2021-03-07)
|
## v1.10 - 63 (2021-03-07)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Support importing [Voucher Vault](https://github.com/tim-smart/vouchervault/) exports
|
- Support importing [Voucher Vault](https://github.com/tim-smart/vouchervault/) exports
|
||||||
- Option to keep the screen on while viewing a loyalty card
|
- Option to keep the screen on while viewing a loyalty card
|
||||||
- Option to suspend the lock screen while viewing a loyalty card
|
- Option to suspend the lock screen while viewing a loyalty card
|
||||||
|
|
||||||
## v1.9.2 (2021-02-24)
|
## v1.9.2 - 62 (2021-02-24)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix parsing balance for countries using space as separator
|
- Fix parsing balance for countries using space as separator
|
||||||
|
|
||||||
## v1.9.1 (2021-02-23)
|
## v1.9.1 - 61 (2021-02-23)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Improve balance parsing logic
|
- Improve balance parsing logic
|
||||||
- Fix currency decimal display on main screen
|
- Fix currency decimal display on main screen
|
||||||
|
|
||||||
## v1.9 (2021-02-22)
|
## v1.9 - 59 (2021-02-22)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Add balance support
|
- Add balance support
|
||||||
- Reorganize barcode tab of edit view
|
- Reorganize barcode tab of edit view
|
||||||
|
|
||||||
## v1.8.1 (2021-02-12)
|
## v1.8.1 - 58 (2021-02-12)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix Crash on versions before Android 7
|
- Fix Crash on versions before Android 7
|
||||||
|
|
||||||
## v1.8 (2021-01-28)
|
## v1.8 - 57 (2021-01-28)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Add support for scaling the barcode when moving to top to fit even more small scanners
|
- Add support for scaling the barcode when moving to top to fit even more small scanners
|
||||||
- Fix bottom sheet jumping after switching to fullscreen
|
- Fix bottom sheet jumping after switching to fullscreen
|
||||||
- Make header in loyalty card view small in landscape mode
|
- Make header in loyalty card view small in landscape mode
|
||||||
- Fix cards not staying in group when group gets renamed
|
- Fix cards not staying in group when group gets renamed
|
||||||
|
|
||||||
## v1.7.1 (2021-01-18)
|
## v1.7.1 - 56 (2021-01-18)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix crash on switching to barcode tab in edit view if there is no barcode
|
- Fix crash on switching to barcode tab in edit view if there is no barcode
|
||||||
|
|
||||||
## v1.7.0 (2021-01-18)
|
## v1.7.0 - 55 (2021-01-18)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Separate edit UI in tabs to make it feel more spacious
|
- Separate edit UI in tabs to make it feel more spacious
|
||||||
- Add expiry field support
|
- Add expiry field support
|
||||||
|
|
||||||
## v1.6.2 (2021-01-04)
|
## v1.6.2 - 54 (2021-01-04)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix edit button or more info bottom sheet drawing over barcode ID
|
- Fix edit button or more info bottom sheet drawing over barcode ID
|
||||||
|
|
||||||
## v1.6.1 (2020-12-16)
|
## v1.6.1 - 64 (2020-12-16)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix regression causing manual barcode entry to not be saved
|
- Fix regression causing manual barcode entry to not be saved
|
||||||
|
|
||||||
## v1.6.0 (2020-12-15)
|
## v1.6.0 - 52 (2020-12-15)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Automatically focus text field when creating or editing a group
|
- Automatically focus text field when creating or editing a group
|
||||||
- Fix blurry icons (use SVG everywhere)
|
- Fix blurry icons (use SVG everywhere)
|
||||||
- Always open camera but add manual scan button to camera view
|
- Always open camera but add manual scan button to camera view
|
||||||
|
|
||||||
## v1.5.1 (2020-12-03)
|
## v1.5.1 - 51 (2020-12-03)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix bottomsheet background being transparent
|
- Fix bottomsheet background being transparent
|
||||||
|
|
||||||
## v1.5.0 (2020-12-03)
|
## v1.5.0 - 50 (2020-12-03)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Improve contrast by always using white text on red buttons
|
- Improve contrast by always using white text on red buttons
|
||||||
- Draggable bottom sheet in loyalty card view
|
- Draggable bottom sheet in loyalty card view
|
||||||
|
|
||||||
## v1.4.1 (2020-12-01)
|
## v1.4.1 - 49 (2020-12-01)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Improved translations
|
- Improved translations
|
||||||
- Small UI fixes
|
- Small UI fixes
|
||||||
|
|
||||||
## v1.4.0 (2020-11-28)
|
## v1.4.0 - 48 (2020-11-28)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Move About screen into its own activity
|
- Move About screen into its own activity
|
||||||
- Ask user if they want to use their camera or manually enter ID on add/edit card
|
- 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
|
- Make group ordering manual instead of forced alphabetically
|
||||||
|
|
||||||
## v1.3.0 (2020-11-22)
|
## v1.3.0 - 47 (2020-11-22)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Always show all import/export options and show a toast on actual issues (improves compat with XPrivacyLua)
|
- 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
|
- Ask for confirmation when leaving edit view after making changes without saving
|
||||||
|
|
||||||
## v1.2.2 (2020-11-19)
|
## v1.2.2 - 46 (2020-11-19)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Remember active group tab between screens and sessions
|
- Remember active group tab between screens and sessions
|
||||||
|
|
||||||
## v1.2.1 (2020-11-17)
|
## v1.2.1 - 45 (2020-11-17)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix home screen swiping triggering during vertical swipes too
|
- Fix home screen swiping triggering during vertical swipes too
|
||||||
|
|
||||||
## v1.2.0 (2020-11-17)
|
## v1.2.0 - 44 (2020-11-17)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Add swiping between groups on the home screen
|
- Add swiping between groups on the home screen
|
||||||
- Fix crash with cards lacking header colour
|
- Fix crash with cards lacking header colour
|
||||||
|
|
||||||
## v1.1.0 (2020-11-11)
|
## v1.1.0 - 43 (2020-11-11)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Improved edit UI
|
- Improved edit UI
|
||||||
- Removed header text colour option (now automatically generated based on brightness)
|
- Removed header text colour option (now automatically generated based on brightness)
|
||||||
- Updated translations
|
- Updated translations
|
||||||
|
|
||||||
## v1.0.1 (2020-11-07)
|
## v1.0.1 - 42 (2020-11-07)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix crash in search with no groups
|
- Fix crash in search with no groups
|
||||||
|
|
||||||
## v1.0 (2020-11-06)
|
## v1.0 - 41 (2020-11-06)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Added rounded edges to card icons on main overview
|
- Added rounded edges to card icons on main overview
|
||||||
- Added support for grouping entries
|
- Added support for grouping entries
|
||||||
|
|
||||||
## v0.29 (2020-10-29)
|
## v0.29 - 40 (2020-10-29)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Rebrand to Catima
|
- Rebrand to Catima
|
||||||
- Removed intro
|
- Removed intro
|
||||||
@@ -223,31 +306,23 @@ Changes:
|
|||||||
- Add favourites support
|
- Add favourites support
|
||||||
- Fix disabled auto-rotate being ignored
|
- Fix disabled auto-rotate being ignored
|
||||||
|
|
||||||
## v0.28 (2020-03-09)
|
## v0.28 - 39 (2020-03-09)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix barcode centering when exiting full screen ([#351](https://github.com/brarcher/loyalty-card-locker/pull/351))
|
- 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))
|
- 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))
|
- Update translations ([#357](https://github.com/brarcher/loyalty-card-locker/pull/357)) & ([#362](https://github.com/brarcher/loyalty-card-locker/pull/362))
|
||||||
|
|
||||||
## v0.27 (2020-01-26)
|
## v0.27 - 38 (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))
|
- 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))
|
- 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))
|
- Fix swapped import buttons. ([#346](https://github.com/brarcher/loyalty-card-locker/pull/346))
|
||||||
|
|
||||||
## v0.26.1 (2020-01-09)
|
## v0.26.1 - 37 (2020-01-09)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix issue with sharing cards without background color ([#343](https://github.com/brarcher/loyalty-card-locker/pull/343))
|
- Fix issue with sharing cards without background color ([#343](https://github.com/brarcher/loyalty-card-locker/pull/343))
|
||||||
|
|
||||||
## v0.26 (2020-01-05)
|
## v0.26 - 36 (2020-01-05)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Add ability to search for a card ([#320](https://github.com/brarcher/loyalty-card-locker/pull/320))
|
- 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))
|
- Add ability to share and receive loyalty cards ([#321](https://github.com/brarcher/loyalty-card-locker/pull/321))
|
||||||
@@ -264,57 +339,41 @@ Changes:
|
|||||||
- Polish
|
- Polish
|
||||||
- Russian
|
- Russian
|
||||||
|
|
||||||
## v0.25.4 (2019-10-04)
|
## v0.25.4 - 35 (2019-10-04)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Enable app backups
|
- Enable app backups
|
||||||
- Update French and Slovenian translations
|
- Update French and Slovenian translations
|
||||||
|
|
||||||
## v0.25.3 (2019-03-02)
|
## v0.25.3 - 34 (2019-03-02)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Update Russian translations
|
- Update Russian translations
|
||||||
|
|
||||||
## v0.25.2 (2019-01-05)
|
## v0.25.2 - 33 (2019-01-05)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Update and add translations
|
- Update and add translations
|
||||||
|
|
||||||
## v0.25.1 (2018-10-14)
|
## v0.25.1 - 32 (2018-10-14)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix creating new card by manually entering barcode ([issue #272](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)
|
## v0.25 - 31 (2018-10-07)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Sort card list case insensitive ([pull #266](https://github.com/brarcher/loyalty-card-locker/pull/266))
|
- 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)
|
- Add setting to lock orientation for all cards ([pull #269](https://github.com/brarcher/loyalty-card-locker/pull/269)
|
||||||
|
|
||||||
## v0.24 (2018-07-31)
|
## v0.24 - 30 (2018-07-31)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Add a setting to control screen brightness when displaying a barcode ([pull #259](https://github.com/brarcher/loyalty-card-locker/pull/259))
|
- 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 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))
|
- 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))
|
- 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)
|
## v0.23.4 - 29 (2018-05-12)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix Spanish translations ([pull #244](https://github.com/brarcher/loyalty-card-locker/pull/244))
|
- 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))
|
- Update translations ([pull #244](https://github.com/brarcher/loyalty-card-locker/pull/244))
|
||||||
|
|
||||||
## v0.23.3 (2018-05-05)
|
## v0.23.3 - 28 (2018-05-05)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Added translations
|
- Added translations
|
||||||
- Polish ([pull #232](https://github.com/brarcher/loyalty-card-locker/pull/232))
|
- Polish ([pull #232](https://github.com/brarcher/loyalty-card-locker/pull/232))
|
||||||
@@ -322,87 +381,63 @@ Changes:
|
|||||||
- Slovak ([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))
|
- Updated translations ([pull #239](https://github.com/brarcher/loyalty-card-locker/pull/239))
|
||||||
|
|
||||||
## v0.23.2 (2018-03-11)
|
## v0.23.2 - 27 (2018-03-11)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Reduce min SDK from 17 to 15. ([pull #226](https://github.com/brarcher/loyalty-card-locker/pull/226))
|
- 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))
|
- 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)
|
## v0.23.1 - 26 (2018-03-07)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Prevent crash when rendering a barcode exhausts the application's memory. ([pull #219](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)
|
## v0.23 - 25 (2018-02-28)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Reduce space in header when viewing a card. ([pull #213](https://github.com/brarcher/loyalty-card-locker/pull/213))
|
- 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))
|
- Disable beep when scanning a barcode. ([pull #216](https://github.com/brarcher/loyalty-card-locker/pull/216))
|
||||||
|
|
||||||
## v0.22 (2018-02-19)
|
## v0.22 - 24 (2018-02-19)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Update translations. ([pull #208](https://github.com/brarcher/loyalty-card-locker/pull/208))
|
- 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))
|
- 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.
|
- 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.
|
- Render 1D barcodes in a larger space, allowing them to better fill the screen.
|
||||||
|
|
||||||
## v0.21 (2018-02-17)
|
## v0.21 - 23 (2018-02-17)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Add quiet space at the start/end of barcodes. ([pull #200](https://github.com/brarcher/loyalty-card-locker/pull/200))
|
- 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 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))
|
- 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)
|
## v0.20 - 22 (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. ([pull #197](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)
|
## v0.19 - 21 (2018-02-01)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Improved layout for card list. ([pull #188](https://github.com/brarcher/loyalty-card-locker/pull/188))
|
- 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))
|
- Improved layout when viewing a card. ([pull #190](https://github.com/brarcher/loyalty-card-locker/pull/190))
|
||||||
|
|
||||||
## v0.18.1 (2018-01-24)
|
## v0.18.1 - 20 (2018-01-24)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Workaround crash during install on some Android versions (likely Android 5 and below). ([pull #184](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)
|
## v0.18 - 19 (2018-01-19)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix crash when importing certain types of corrupted CSV files. ([pull #177](https://github.com/brarcher/loyalty-card-locker/pull/177))
|
- 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 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))
|
- 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)
|
## v0.17 - 18 (2018-01-11)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- 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))
|
- 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))
|
- New icon and color scheme. ([pull #171](https://github.com/brarcher/loyalty-card-locker/pull/171))
|
||||||
|
|
||||||
## v0.16 (2017-11-29)
|
## v0.16 - 17 (2017-11-29)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Add support for adding loyalty card shortcuts from the launcher/homescreen. ([pull #161](https://github.com/brarcher/loyalty-card-locker/pull/161))
|
- 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))
|
- 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)
|
## v0.15 - 16 (2017-11-25)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- 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))
|
- 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))
|
- Remove widget, as it was a poor substitute for shortcuts. ([pull #155](https://github.com/brarcher/loyalty-card-locker/pull/155))
|
||||||
@@ -410,37 +445,27 @@ Changes:
|
|||||||
- Report more accurate mime type when exporting backup data. ([pull #156](https://github.com/brarcher/loyalty-card-locker/pull/156))
|
- 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))
|
- 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)
|
## v0.14 - 15 (2017-10-26)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- 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 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))
|
- 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)
|
## v0.13 - 14 (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. ([pull #128](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))
|
- 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))
|
- 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)
|
## v0.12 - 13 (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. ([pull #126](https://github.com/brarcher/loyalty-card-locker/pull/126))
|
- 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))
|
- 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)
|
## v0.11.1 - 12 (2017-06-29)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Prevent a crash when rotation the screen in the first run intro wizard.
|
- Prevent a crash when rotation the screen in the first run intro wizard.
|
||||||
|
|
||||||
## v0.11 (2017-06-26)
|
## v0.11 - 11 (2017-06-26)
|
||||||
|
|
||||||
Improvements:
|
|
||||||
|
|
||||||
- When editing a card ID, pre-populate the existing ID to start. ([pull #94](https://github.com/brarcher/loyalty-card-locker/pull/94))
|
- 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))
|
- 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))
|
||||||
@@ -448,15 +473,13 @@ Improvements:
|
|||||||
- 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))
|
- 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))
|
- 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)
|
## v0.10 - 10 (2017-02-12)
|
||||||
|
|
||||||
Improvements:
|
|
||||||
|
|
||||||
- Changed the default import/export filename. ([pull #84](https://github.com/brarcher/loyalty-card-locker/pull/84))
|
- 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))
|
- 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))
|
- 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)
|
## v0.9 - 9 (2017-01-17)
|
||||||
|
|
||||||
The "Locker" part of the name was not intuitive. To help remedy this a new application icon was created by betsythefc which better represents the purpose of the application: to store loyalty cards which use barcodes. Along with this new icon the name of the application has been changed to "Loyalty Card Keychain".
|
The "Locker" part of the name was not intuitive. To help remedy this a new application icon was created by betsythefc which better represents the purpose of the application: to store loyalty cards which use barcodes. Along with this new icon the name of the application has been changed to "Loyalty Card Keychain".
|
||||||
|
|
||||||
@@ -466,55 +489,36 @@ Additional features/improvements:
|
|||||||
- Translations for Lithuanian added. ([pull #62](https://github.com/brarcher/loyalty-card-locker/pull/62))
|
- 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))
|
- Translations for French added. ([pull #80](https://github.com/brarcher/loyalty-card-locker/pull/80))
|
||||||
|
|
||||||
## v0.8 (2016-11-22)
|
## v0.8 - 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. ([pull #54](https://github.com/brarcher/loyalty-card-locker/pull/54))
|
- 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 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)).
|
- 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))
|
- Clarification change for Italian translation. ([pull #66](https://github.com/brarcher/loyalty-card-locker/pull/66))
|
||||||
|
|
||||||
## v0.7 (2016-07-14)
|
## v0.7 - 7 (2016-07-14)
|
||||||
|
|
||||||
New features/improvements:
|
|
||||||
|
|
||||||
- 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))
|
- 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
|
- Back button on Input/Export view now works, moving user to main view
|
||||||
|
|
||||||
## v0.6 (2016-05-23)
|
## v0.6 - 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. [issue #33](https://github.com/brarcher/loyalty-card-locker/issues/33), [pull #44](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. ([issue #37](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)
|
## v0.5 - 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 ([issue #19](https://github.com/brarcher/loyalty-card-locker/issues/19))
|
- 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))
|
- 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)
|
## v0.4 - 4 (2016-04-09)
|
||||||
|
|
||||||
New features/improvements:
|
|
||||||
|
|
||||||
- Dutch translation
|
- Dutch translation
|
||||||
- Allow name field to be editable after adding loyalty card
|
- Allow name field to be editable after adding loyalty card
|
||||||
- Add an optional note field
|
- Add an optional note field
|
||||||
|
|
||||||
Bug fixes:
|
|
||||||
|
|
||||||
- Resolve all issues identified by FindBugs and require all FindBugs issues be resolved prior to pull request acceptance
|
- Resolve all issues identified by FindBugs and require all FindBugs issues be resolved prior to pull request acceptance
|
||||||
|
|
||||||
## v0.3 (2016-02-11)
|
## v0.3 - 3 (2016-02-11)
|
||||||
|
|
||||||
- Now officially supports the following list of 1D and 2D barcodes:
|
- Now officially supports the following list of 1D and 2D barcodes:
|
||||||
- AZTEC
|
- AZTEC
|
||||||
@@ -531,13 +535,13 @@ Bug fixes:
|
|||||||
|
|
||||||
- Generated barcodes are larger, easier to scan from a scanning device
|
- Generated barcodes are larger, easier to scan from a scanning device
|
||||||
|
|
||||||
## v0.2 (2016-02-07)
|
## v0.2 - 2 (2016-02-07)
|
||||||
|
|
||||||
- Italian translations
|
- Italian translations
|
||||||
- Support for all 1D barcode types. (Originally only product 1D barcodes were supported)
|
- Support for all 1D barcode types. (Originally only product 1D barcodes were supported)
|
||||||
- Add required camera permission, which was initially missing.
|
- Add required camera permission, which was initially missing.
|
||||||
|
|
||||||
## v0.1 (2016-01-30)
|
## v0.1 - 1 (2016-01-30)
|
||||||
|
|
||||||
- Ability to create/edit/delete loyalty cards
|
- Ability to create/edit/delete loyalty cards
|
||||||
- Capture barcode of loyalty card using a camera
|
- Capture barcode of loyalty card using a camera
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
How to Submit Patches to the Loyalty Card Keychain Project
|
How to Submit Patches to the Catima Project
|
||||||
===============================================================================
|
===============================================================================
|
||||||
https://github.com/brarcher/budget-watch
|
https://github.com/TheLastProject/Catima
|
||||||
|
|
||||||
This document is intended to act as a guide to help you contribute to the
|
This document is intended to act as a guide to help you contribute to the
|
||||||
Loyalty Card Keychain project. It is not perfect, and there will always be exceptions
|
Catima project. It is not perfect, and there will always be exceptions
|
||||||
to the rules described here, but by following the instructions below you
|
to the rules described here, but by following the instructions below you
|
||||||
should have a much easier time getting your work merged with the upstream
|
should have a much easier time getting your work merged with the upstream
|
||||||
project.
|
project.
|
||||||
@@ -21,16 +21,16 @@ These are the Android lint checker, run using:
|
|||||||
|
|
||||||
# ./gradlew lintRelease
|
# ./gradlew lintRelease
|
||||||
|
|
||||||
and FindBugs, run using:
|
and SpotBugs, run using:
|
||||||
|
|
||||||
# ./gradlew findbugs
|
# ./gradlew spotbugsRelease
|
||||||
|
|
||||||
The final check is by testing the application on a live device and verifying
|
The final check is by testing the application on a live device and verifying
|
||||||
the basic functionality works as expected.
|
the basic functionality works as expected.
|
||||||
|
|
||||||
## Make Sure Your Code is Tested
|
## Make Sure Your Code is Tested
|
||||||
|
|
||||||
The Loyalty Card Keychain code uses a fair number of unit tests to verify that
|
The Catima code uses a fair number of unit tests to verify that
|
||||||
the basic functionality is working. Submissions which add functionality
|
the basic functionality is working. Submissions which add functionality
|
||||||
or significantly change the existing code should include additional tests
|
or significantly change the existing code should include additional tests
|
||||||
to verify the proper operation of the proposed changes.
|
to verify the proper operation of the proposed changes.
|
||||||
@@ -85,7 +85,7 @@ your real name, saying:
|
|||||||
## Submit Patch(es) for Review
|
## Submit Patch(es) for Review
|
||||||
|
|
||||||
Finally, you will need to submit your patches so that they can be reviewed
|
Finally, you will need to submit your patches so that they can be reviewed
|
||||||
and potentially merged into the main Loyalty Card Keychain repository. The preferred
|
and potentially merged into the main Catima repository. The preferred
|
||||||
way to do this is to submit a Pull Request to the Loyalty Card Keychain project.
|
way to do this is to submit a Pull Request to the Catima project.
|
||||||
Changes need to apply cleanly onto the master branch and pass all
|
Changes need to apply cleanly onto the master branch and pass all
|
||||||
unit tests and produce no errors during static analysis.
|
unit tests and produce no errors during static analysis.
|
||||||
|
|||||||
155
Gemfile.lock
155
Gemfile.lock
@@ -1,58 +1,75 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.2)
|
CFPropertyList (3.0.3)
|
||||||
addressable (2.8.0)
|
addressable (2.8.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
|
artifactory (3.0.15)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.1.0)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.388.0)
|
aws-partitions (1.501.0)
|
||||||
aws-sdk-core (3.109.1)
|
aws-sdk-core (3.121.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.239.0)
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-kms (1.39.0)
|
aws-sdk-kms (1.48.0)
|
||||||
aws-sdk-core (~> 3, >= 3.109.0)
|
aws-sdk-core (~> 3, >= 3.120.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.83.1)
|
aws-sdk-s3 (1.102.0)
|
||||||
aws-sdk-core (~> 3, >= 3.109.0)
|
aws-sdk-core (~> 3, >= 3.120.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.4)
|
||||||
aws-sigv4 (1.2.2)
|
aws-sigv4 (1.4.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.4)
|
babosa (1.0.4)
|
||||||
claide (1.0.3)
|
claide (1.0.3)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
commander-fastlane (4.4.6)
|
commander (4.6.0)
|
||||||
highline (~> 1.7.2)
|
highline (~> 2.0.0)
|
||||||
declarative (0.0.20)
|
declarative (0.0.20)
|
||||||
declarative-option (0.1.0)
|
digest-crc (0.6.4)
|
||||||
digest-crc (0.6.1)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
rake (~> 13.0)
|
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
dotenv (2.7.6)
|
dotenv (2.7.6)
|
||||||
emoji_regex (3.2.0)
|
emoji_regex (3.2.2)
|
||||||
excon (0.78.0)
|
excon (0.85.0)
|
||||||
faraday (1.1.0)
|
faraday (1.7.2)
|
||||||
|
faraday-em_http (~> 1.0)
|
||||||
|
faraday-em_synchrony (~> 1.0)
|
||||||
|
faraday-excon (~> 1.1)
|
||||||
|
faraday-httpclient (~> 1.0.1)
|
||||||
|
faraday-net_http (~> 1.0)
|
||||||
|
faraday-net_http_persistent (~> 1.1)
|
||||||
|
faraday-patron (~> 1.0)
|
||||||
|
faraday-rack (~> 1.0)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
ruby2_keywords
|
ruby2_keywords (>= 0.0.4)
|
||||||
faraday-cookie_jar (0.0.7)
|
faraday-cookie_jar (0.0.7)
|
||||||
faraday (>= 0.8.0)
|
faraday (>= 0.8.0)
|
||||||
http-cookie (~> 1.0.0)
|
http-cookie (~> 1.0.0)
|
||||||
faraday_middleware (1.0.0)
|
faraday-em_http (1.0.0)
|
||||||
|
faraday-em_synchrony (1.0.0)
|
||||||
|
faraday-excon (1.1.0)
|
||||||
|
faraday-httpclient (1.0.1)
|
||||||
|
faraday-net_http (1.0.1)
|
||||||
|
faraday-net_http_persistent (1.2.0)
|
||||||
|
faraday-patron (1.0.0)
|
||||||
|
faraday-rack (1.0.0)
|
||||||
|
faraday_middleware (1.1.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.2.0)
|
fastimage (2.2.5)
|
||||||
fastlane (2.165.0)
|
fastlane (2.193.1)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.3, < 3.0.0)
|
addressable (>= 2.8, < 3.0.0)
|
||||||
|
artifactory (~> 3.0)
|
||||||
aws-sdk-s3 (~> 1.0)
|
aws-sdk-s3 (~> 1.0)
|
||||||
babosa (>= 1.0.3, < 2.0.0)
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
bundler (>= 1.12.0, < 3.0.0)
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
colored
|
colored
|
||||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
commander (~> 4.6)
|
||||||
dotenv (>= 2.1.1, < 3.0.0)
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
emoji_regex (>= 0.1, < 4.0)
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
excon (>= 0.71.0, < 1.0.0)
|
excon (>= 0.71.0, < 1.0.0)
|
||||||
@@ -61,18 +78,20 @@ GEM
|
|||||||
faraday_middleware (~> 1.0)
|
faraday_middleware (~> 1.0)
|
||||||
fastimage (>= 2.1.0, < 3.0.0)
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
google-api-client (>= 0.37.0, < 0.39.0)
|
google-apis-androidpublisher_v3 (~> 0.3)
|
||||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
google-apis-playcustomapp_v1 (~> 0.1)
|
||||||
highline (>= 1.7.2, < 2.0.0)
|
google-cloud-storage (~> 1.31)
|
||||||
|
highline (~> 2.0)
|
||||||
json (< 3.0.0)
|
json (< 3.0.0)
|
||||||
jwt (>= 2.1.0, < 3)
|
jwt (>= 2.1.0, < 3)
|
||||||
mini_magick (>= 4.9.4, < 5.0.0)
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
multipart-post (~> 2.0.0)
|
multipart-post (~> 2.0.0)
|
||||||
|
naturally (~> 2.2)
|
||||||
|
optparse (~> 0.1.1)
|
||||||
plist (>= 3.1.0, < 4.0.0)
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
rubyzip (>= 2.0.0, < 3.0.0)
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
security (= 0.1.3)
|
security (= 0.1.3)
|
||||||
simctl (~> 1.6.3)
|
simctl (~> 1.6.3)
|
||||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
|
||||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||||
terminal-table (>= 1.4.5, < 2.0.0)
|
terminal-table (>= 1.4.5, < 2.0.0)
|
||||||
tty-screen (>= 0.6.3, < 1.0.0)
|
tty-screen (>= 0.6.3, < 1.0.0)
|
||||||
@@ -82,73 +101,85 @@ GEM
|
|||||||
xcpretty (~> 0.3.0)
|
xcpretty (~> 0.3.0)
|
||||||
xcpretty-travis-formatter (>= 0.0.3)
|
xcpretty-travis-formatter (>= 0.0.3)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
google-api-client (0.38.0)
|
google-apis-androidpublisher_v3 (0.11.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-apis-core (0.4.1)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (~> 0.9)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
httpclient (>= 2.8.1, < 3.0)
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.0)
|
retriable (>= 2.0, < 4.a)
|
||||||
signet (~> 0.12)
|
rexml
|
||||||
google-cloud-core (1.5.0)
|
webrick
|
||||||
|
google-apis-iamcredentials_v1 (0.7.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-apis-playcustomapp_v1 (0.5.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-apis-storage_v1 (0.6.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-cloud-core (1.6.0)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.4.0)
|
google-cloud-env (1.5.0)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
google-cloud-errors (1.0.1)
|
google-cloud-errors (1.1.0)
|
||||||
google-cloud-storage (1.29.1)
|
google-cloud-storage (1.34.1)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-api-client (~> 0.33)
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
google-cloud-core (~> 1.2)
|
google-apis-storage_v1 (~> 0.1)
|
||||||
googleauth (~> 0.9)
|
google-cloud-core (~> 1.6)
|
||||||
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (0.14.0)
|
googleauth (0.17.1)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (~> 0.14)
|
signet (~> 0.15)
|
||||||
highline (1.7.10)
|
highline (2.0.3)
|
||||||
http-cookie (1.0.3)
|
http-cookie (1.0.4)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
jmespath (1.4.0)
|
jmespath (1.4.0)
|
||||||
json (2.3.1)
|
json (2.5.1)
|
||||||
jwt (2.2.2)
|
jwt (2.2.3)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
mini_magick (4.10.1)
|
mini_magick (4.11.0)
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.1.1)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
nanaimo (0.3.0)
|
nanaimo (0.3.0)
|
||||||
naturally (2.2.0)
|
naturally (2.2.1)
|
||||||
|
optparse (0.1.1)
|
||||||
os (1.1.1)
|
os (1.1.1)
|
||||||
plist (3.5.0)
|
plist (3.6.0)
|
||||||
public_suffix (4.0.6)
|
public_suffix (4.0.6)
|
||||||
rake (13.0.1)
|
rake (13.0.6)
|
||||||
representable (3.0.4)
|
representable (3.1.1)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
declarative-option (< 0.2.0)
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
|
rexml (3.2.5)
|
||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
ruby2_keywords (0.0.2)
|
ruby2_keywords (0.0.5)
|
||||||
rubyzip (2.3.0)
|
rubyzip (2.3.2)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.14.0)
|
signet (0.16.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
simctl (1.6.8)
|
simctl (1.6.8)
|
||||||
CFPropertyList
|
CFPropertyList
|
||||||
naturally
|
naturally
|
||||||
slack-notifier (2.3.2)
|
|
||||||
terminal-notifier (2.0.0)
|
terminal-notifier (2.0.0)
|
||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
|
trailblazer-option (0.1.1)
|
||||||
tty-cursor (0.7.1)
|
tty-cursor (0.7.1)
|
||||||
tty-screen (0.8.1)
|
tty-screen (0.8.1)
|
||||||
tty-spinner (0.9.3)
|
tty-spinner (0.9.3)
|
||||||
@@ -156,18 +187,20 @@ GEM
|
|||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.7)
|
unf_ext (0.0.8)
|
||||||
unicode-display_width (1.7.0)
|
unicode-display_width (1.7.0)
|
||||||
|
webrick (1.7.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.19.0)
|
xcodeproj (1.21.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
colored2 (~> 3.1)
|
colored2 (~> 3.1)
|
||||||
nanaimo (~> 0.3.0)
|
nanaimo (~> 0.3.0)
|
||||||
|
rexml (~> 3.2.4)
|
||||||
xcpretty (0.3.0)
|
xcpretty (0.3.0)
|
||||||
rouge (~> 2.0.7)
|
rouge (~> 2.0.7)
|
||||||
xcpretty-travis-formatter (1.0.0)
|
xcpretty-travis-formatter (1.0.1)
|
||||||
xcpretty (~> 0.2, >= 0.0.7)
|
xcpretty (~> 0.2, >= 0.0.7)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
|
|||||||
@@ -11,17 +11,18 @@ spotbugs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 31
|
||||||
buildToolsVersion "30.0.3"
|
buildToolsVersion "31.0.0"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "me.hackerchick.catima"
|
applicationId "me.hackerchick.catima"
|
||||||
minSdkVersion 19
|
minSdkVersion 21
|
||||||
targetSdkVersion 30
|
targetSdkVersion 31
|
||||||
versionCode 70
|
versionCode 95
|
||||||
versionName "2.0"
|
versionName "2.11.2"
|
||||||
|
|
||||||
vectorDrawables.useSupportLibrary true
|
vectorDrawables.useSupportLibrary true
|
||||||
|
multiDexEnabled true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -36,10 +37,20 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bundle {
|
||||||
|
language {
|
||||||
|
enableSplit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
encoding "UTF-8"
|
encoding "UTF-8"
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
// Flag to enable support for the new language APIs
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
|
|
||||||
|
sourceCompatibility JavaVersion.VERSION_11
|
||||||
|
targetCompatibility JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
@@ -69,30 +80,33 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// AndroidX
|
// AndroidX
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.4.0'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
implementation 'androidx.exifinterface:exifinterface:1.3.3'
|
||||||
|
implementation 'androidx.palette:palette:1.0.0'
|
||||||
implementation 'androidx.preference:preference:1.1.1'
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
implementation 'com.google.android.material:material:1.3.0'
|
implementation 'com.google.android.material:material:1.4.0'
|
||||||
|
implementation 'com.github.yalantis:ucrop:2.2.7'
|
||||||
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
|
// Splash Screen
|
||||||
|
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
|
||||||
|
|
||||||
// Third-party
|
// Third-party
|
||||||
implementation 'com.journeyapps:zxing-android-embedded:4.1.0@aar'
|
implementation 'com.journeyapps:zxing-android-embedded:4.3.0@aar'
|
||||||
// Do not upgrade past 3.3.3! Causes a crash on versions before Android Nougat
|
implementation 'com.google.zxing:core:3.4.1'
|
||||||
//noinspection GradleDependency
|
implementation 'org.apache.commons:commons-csv:1.9.0'
|
||||||
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.jaredrummler:colorpicker:1.1.0'
|
||||||
implementation 'com.google.guava:guava:30.1.1-jre'
|
implementation 'com.github.invissvenska:NumberPickerPreference:1.0.4'
|
||||||
implementation 'com.github.invissvenska:NumberPickerPreference:1.0.2'
|
implementation 'net.lingala.zip4j:zip4j:2.9.1'
|
||||||
implementation 'net.lingala.zip4j:zip4j:2.8.0'
|
|
||||||
|
|
||||||
// SpotBugs
|
// SpotBugs
|
||||||
implementation 'io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0'
|
implementation 'io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0'
|
||||||
|
|
||||||
// Testing
|
// Testing
|
||||||
testImplementation 'androidx.test:core:1.3.0'
|
testImplementation 'androidx.test:core:1.4.0'
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
testImplementation 'org.robolectric:robolectric:4.4'
|
testImplementation 'org.robolectric:robolectric:4.7.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(SpotBugsTask) {
|
tasks.withType(SpotBugsTask) {
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest package="protect.card_locker"
|
<manifest 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"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
package="protect.card_locker">
|
||||||
|
|
||||||
<uses-permission
|
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
|
||||||
android:name="android.permission.CAMERA"/>
|
|
||||||
<uses-permission
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
|
||||||
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.camera"
|
android:name="android.hardware.camera"
|
||||||
@@ -17,8 +16,6 @@
|
|||||||
android:name="android.hardware.camera.autofocus"
|
android:name="android.hardware.camera.autofocus"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".LoyaltyCardLockerApplication"
|
android:name=".LoyaltyCardLockerApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@@ -27,47 +24,64 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
<activity
|
<activity
|
||||||
android:name="protect.card_locker.MainActivity"
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/AppTheme.NoActionBar">
|
android:theme="@style/Theme.App.Starting">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".AboutActivity"
|
android:name=".AboutActivity"
|
||||||
android:label="@string/about"
|
android:label="@string/about"
|
||||||
android:theme="@style/AppTheme.NoActionBar">
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
</activity>
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ManageGroupsActivity"
|
android:name=".ManageGroupsActivity"
|
||||||
android:label="@string/groups"
|
android:label="@string/groups"
|
||||||
android:theme="@style/AppTheme.NoActionBar">
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
</activity>
|
<activity
|
||||||
|
android:name=".ManageGroupActivity"
|
||||||
|
android:label="@string/group_edit"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar"/>
|
||||||
<activity
|
<activity
|
||||||
android:name=".LoyaltyCardViewActivity"
|
android:name=".LoyaltyCardViewActivity"
|
||||||
|
android:exported="true"
|
||||||
android:theme="@style/AppTheme.NoActionBar"
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden" />
|
||||||
android:exported="true"/>
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".LoyaltyCardEditActivity"
|
android:name=".LoyaltyCardEditActivity"
|
||||||
|
android:exported="true"
|
||||||
android:theme="@style/AppTheme.NoActionBar"
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden">
|
||||||
android:exported="true">
|
<intent-filter
|
||||||
<intent-filter android:label="@string/app_name">
|
android:autoVerify="true"
|
||||||
|
android:label="@string/app_name">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<!-- Listen to known card sharing URIs -->
|
<!-- Main card sharing URIs -->
|
||||||
<data android:scheme="https"
|
<data android:scheme="http" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
<data
|
||||||
android:host="@string/intent_import_card_from_url_host_catima_app"
|
android:host="@string/intent_import_card_from_url_host_catima_app"
|
||||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_catima_app" />
|
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_catima_app" />
|
||||||
<data android:scheme="https"
|
</intent-filter>
|
||||||
|
<intent-filter android:label="@string/app_name">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<!-- Old card sharing URIs -->
|
||||||
|
<data android:scheme="http" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
<data
|
||||||
android:host="@string/intent_import_card_from_url_host_thelastproject"
|
android:host="@string/intent_import_card_from_url_host_thelastproject"
|
||||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_thelastproject" />
|
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_thelastproject" />
|
||||||
<data android:scheme="https"
|
<data
|
||||||
android:host="@string/intent_import_card_from_url_host_brarcher"
|
android:host="@string/intent_import_card_from_url_host_brarcher"
|
||||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_brarcher" />
|
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_brarcher" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@@ -75,37 +89,52 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".ScanActivity"
|
android:name=".ScanActivity"
|
||||||
android:label="@string/scanCardBarcode"
|
android:label="@string/scanCardBarcode"
|
||||||
android:theme="@style/AppTheme.NoActionBar"/>
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".BarcodeSelectorActivity"
|
android:name=".BarcodeSelectorActivity"
|
||||||
android:label="@string/selectBarcodeTitle"
|
android:label="@string/selectBarcodeTitle"
|
||||||
android:theme="@style/AppTheme.NoActionBar"
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
android:windowSoftInputMode="stateHidden"/>
|
android:windowSoftInputMode="stateHidden" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".preferences.SettingsActivity"
|
android:name=".preferences.SettingsActivity"
|
||||||
android:label="@string/settings"/>
|
android:label="@string/settings"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ImportExportActivity"
|
android:name=".ImportExportActivity"
|
||||||
android:label="@string/importExport"
|
android:label="@string/importExport"
|
||||||
android:theme="@style/AppTheme.NoActionBar"/>
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".CardShortcutConfigure"
|
android:name=".CardShortcutConfigure"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/cardShortcut"
|
android:label="@string/cardShortcut"
|
||||||
android:theme="@style/AppTheme.NoActionBar">
|
android:theme="@style/AppTheme.NoActionBar">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.CREATE_SHORTCUT"/>
|
<action android:name="android.intent.action.CREATE_SHORTCUT" />
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.yalantis.ucrop.UCropActivity"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:grantUriPermissions="true"
|
android:authorities="${applicationId}"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:authorities="${applicationId}">
|
android:grantUriPermissions="true">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/file_provider_paths"/>
|
android:resource="@xml/file_provider_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
<service android:name=".CardsOnPowerScreenService" android:label="@string/app_name"
|
||||||
|
android:permission="android.permission.BIND_CONTROLS" android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.service.controls.ControlsProviderService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,107 +1,172 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.method.LinkMovementMethod;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
import androidx.core.text.HtmlCompat;
|
import androidx.core.text.HtmlCompat;
|
||||||
|
|
||||||
public class AboutActivity extends AppCompatActivity
|
public class AboutActivity extends CatimaAppCompatActivity implements View.OnClickListener {
|
||||||
{
|
|
||||||
private static final String TAG = "Catima";
|
private static final String TAG = "Catima";
|
||||||
|
ConstraintLayout version_history, translate, license, repo, privacy, error, credits, rate;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState)
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
{
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
setTitle(R.string.about);
|
||||||
setContentView(R.layout.about_activity);
|
setContentView(R.layout.about_activity);
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
if(actionBar != null)
|
if (actionBar != null) {
|
||||||
{
|
|
||||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StringBuilder contributors = new StringBuilder().append("<br/>");
|
||||||
|
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(getResources().openRawResource(R.raw.contributors), StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
String tmp = reader.readLine();
|
||||||
|
|
||||||
|
if (tmp == null || tmp.isEmpty()) {
|
||||||
|
reader.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
contributors.append("<br/>");
|
||||||
|
contributors.append(tmp);
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
final List<ThirdPartyInfo> USED_LIBRARIES = new ArrayList<>();
|
final List<ThirdPartyInfo> USED_LIBRARIES = new ArrayList<>();
|
||||||
|
USED_LIBRARIES.add(new ThirdPartyInfo("Color Picker", "https://github.com/jaredrummler/ColorPicker", "Apache 2.0"));
|
||||||
USED_LIBRARIES.add(new ThirdPartyInfo("Commons CSV", "https://commons.apache.org/proper/commons-csv/", "Apache 2.0"));
|
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("NumberPickerPreference", "https://github.com/invissvenska/NumberPickerPreference", "GNU LGPL 3.0"));
|
||||||
|
USED_LIBRARIES.add(new ThirdPartyInfo("Zip4j", "https://github.com/srikanth-lingala/zip4j", "Apache 2.0"));
|
||||||
USED_LIBRARIES.add(new ThirdPartyInfo("ZXing", "https://github.com/zxing/zxing", "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("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<>();
|
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"));
|
USED_ASSETS.add(new ThirdPartyInfo("Android icons", "https://fonts.google.com/icons?selected=Material+Icons", "Apache 2.0"));
|
||||||
|
|
||||||
StringBuilder libs = new StringBuilder().append("<br/>");
|
StringBuilder libs = new StringBuilder().append("<br/>");
|
||||||
for (ThirdPartyInfo entry : USED_LIBRARIES)
|
for (ThirdPartyInfo entry : USED_LIBRARIES) {
|
||||||
{
|
libs.append("<br/><a href=\"").append(entry.url()).append("\">").append(entry.name()).append("</a> (").append(entry.license()).append(")");
|
||||||
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/>");
|
StringBuilder resources = new StringBuilder().append("<br/>");
|
||||||
for (ThirdPartyInfo entry : USED_ASSETS)
|
for (ThirdPartyInfo entry : USED_ASSETS) {
|
||||||
{
|
resources.append("<br/><a href=\"").append(entry.url()).append("\">").append(entry.name()).append("</a> (").append(entry.license()).append(")");
|
||||||
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);
|
String appName = getString(R.string.app_name);
|
||||||
int year = Calendar.getInstance().get(Calendar.YEAR);
|
int year = Calendar.getInstance().get(Calendar.YEAR);
|
||||||
|
|
||||||
String version = "?";
|
String version = "?";
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
|
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||||
version = pi.versionName;
|
version = pi.versionName;
|
||||||
}
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
catch (PackageManager.NameNotFoundException e)
|
|
||||||
{
|
|
||||||
Log.w(TAG, "Package name not found", e);
|
Log.w(TAG, "Package name not found", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextView copyright = findViewById(R.id.credits_sub);
|
||||||
|
copyright.setText(String.format(getString(R.string.app_copyright_fmt), year));
|
||||||
|
TextView vHistory = findViewById(R.id.version_history_sub);
|
||||||
|
vHistory.setText(String.format(getString(R.string.debug_version_fmt), version));
|
||||||
|
|
||||||
setTitle(String.format(getString(R.string.about_title_fmt), appName));
|
setTitle(String.format(getString(R.string.about_title_fmt), appName));
|
||||||
|
|
||||||
TextView aboutTextView = findViewById(R.id.aboutText);
|
version_history = findViewById(R.id.version_history);
|
||||||
aboutTextView.setText(HtmlCompat.fromHtml(String.format(getString(R.string.debug_version_fmt), version) +
|
translate = findViewById(R.id.translate);
|
||||||
"<br/><br/>" +
|
license = findViewById(R.id.license);
|
||||||
String.format(getString(R.string.app_revision_fmt),
|
repo = findViewById(R.id.repo);
|
||||||
"<a href=\"" + getString(R.string.app_revision_url) + "\">" +
|
privacy = findViewById(R.id.privacy);
|
||||||
"GitHub" +
|
error = findViewById(R.id.report_error);
|
||||||
"</a>") +
|
credits = findViewById(R.id.credits);
|
||||||
"<br/><br/>" +
|
rate = findViewById(R.id.rate);
|
||||||
String.format(getString(R.string.app_copyright_fmt), year) +
|
|
||||||
"<br/><br/>" +
|
version_history.setOnClickListener(this);
|
||||||
getString(R.string.app_copyright_old) +
|
translate.setOnClickListener(this);
|
||||||
"<br/><br/>" +
|
license.setOnClickListener(this);
|
||||||
getString(R.string.app_license) +
|
repo.setOnClickListener(this);
|
||||||
"<br/><br/>" +
|
privacy.setOnClickListener(this);
|
||||||
String.format(getString(R.string.app_libraries), libs.toString()) +
|
error.setOnClickListener(this);
|
||||||
"<br/><br/>" +
|
rate.setOnClickListener(this);
|
||||||
String.format(getString(R.string.app_resources), resources.toString()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
|
||||||
aboutTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
StringBuilder contributorInfo = new StringBuilder();
|
||||||
|
contributorInfo.append(HtmlCompat.fromHtml(String.format(getString(R.string.app_contributors), contributors.toString()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
||||||
|
contributorInfo.append("\n\n");
|
||||||
|
contributorInfo.append(getString(R.string.app_copyright_old));
|
||||||
|
contributorInfo.append("\n\n");
|
||||||
|
contributorInfo.append(HtmlCompat.fromHtml(String.format(getString(R.string.app_libraries), libs.toString()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
||||||
|
contributorInfo.append("\n\n");
|
||||||
|
contributorInfo.append(HtmlCompat.fromHtml(String.format(getString(R.string.app_resources), resources.toString()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
||||||
|
|
||||||
|
credits.setOnClickListener(view -> new AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.credits)
|
||||||
|
.setMessage(contributorInfo.toString())
|
||||||
|
.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
||||||
|
})
|
||||||
|
.show());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item)
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
{
|
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
|
|
||||||
if (id == android.R.id.home) {
|
if (id == android.R.id.home) {
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
int id = view.getId();
|
||||||
|
|
||||||
|
String url;
|
||||||
|
if (id == R.id.version_history) {
|
||||||
|
url = "https://catima.app/changelog/";
|
||||||
|
} else if (id == R.id.translate) {
|
||||||
|
url = "https://hosted.weblate.org/engage/catima/";
|
||||||
|
} else if (id == R.id.license) {
|
||||||
|
url = "https://github.com/TheLastProject/Catima/blob/master/LICENSE";
|
||||||
|
} else if (id == R.id.repo) {
|
||||||
|
url = "https://github.com/TheLastProject/Catima/";
|
||||||
|
} else if (id == R.id.privacy) {
|
||||||
|
url = "https://catima.app/privacy-policy/";
|
||||||
|
} else if (id == R.id.report_error) {
|
||||||
|
url = "https://github.com/TheLastProject/Catima/issues";
|
||||||
|
} else if (id == R.id.rate) {
|
||||||
|
url = "https://play.google.com/store/apps/details?id=me.hackerchick.catima";
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse(url));
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,31 +1,32 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
|
||||||
import com.google.zxing.MultiFormatWriter;
|
import com.google.zxing.MultiFormatWriter;
|
||||||
import com.google.zxing.WriterException;
|
import com.google.zxing.WriterException;
|
||||||
import com.google.zxing.common.BitMatrix;
|
import com.google.zxing.common.BitMatrix;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
import protect.card_locker.async.CompatCallable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This task will generate a barcode and load it into an ImageView.
|
* This task will generate a barcode and load it into an ImageView.
|
||||||
* Only a weak reference of the ImageView is kept, so this class will not
|
* Only a weak reference of the ImageView is kept, so this class will not
|
||||||
* prevent the ImageView from being garbage collected.
|
* prevent the ImageView from being garbage collected.
|
||||||
*/
|
*/
|
||||||
class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
|
||||||
{
|
|
||||||
private static final String TAG = "Catima";
|
private static final String TAG = "Catima";
|
||||||
|
|
||||||
private static final int IS_VALID = 999;
|
private static final int IS_VALID = 999;
|
||||||
|
private final Context mContext;
|
||||||
private boolean isSuccesful;
|
private boolean isSuccesful;
|
||||||
|
|
||||||
// When drawn in a smaller window 1D barcodes for some reason end up
|
// When drawn in a smaller window 1D barcodes for some reason end up
|
||||||
@@ -36,16 +37,19 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
|||||||
private final WeakReference<ImageView> imageViewReference;
|
private final WeakReference<ImageView> imageViewReference;
|
||||||
private final WeakReference<TextView> textViewReference;
|
private final WeakReference<TextView> textViewReference;
|
||||||
private String cardId;
|
private String cardId;
|
||||||
private final BarcodeFormat format;
|
private final CatimaBarcode format;
|
||||||
private final int imageHeight;
|
private final int imageHeight;
|
||||||
private final int imageWidth;
|
private final int imageWidth;
|
||||||
private final boolean showFallback;
|
private final boolean showFallback;
|
||||||
private final Runnable callback;
|
private final Runnable callback;
|
||||||
|
|
||||||
BarcodeImageWriterTask(ImageView imageView, String cardIdString,
|
BarcodeImageWriterTask(
|
||||||
BarcodeFormat barcodeFormat, TextView textView,
|
Context context, ImageView imageView, String cardIdString,
|
||||||
boolean showFallback, Runnable callback)
|
CatimaBarcode barcodeFormat, TextView textView,
|
||||||
{
|
boolean showFallback, Runnable callback
|
||||||
|
) {
|
||||||
|
mContext = context;
|
||||||
|
|
||||||
isSuccesful = true;
|
isSuccesful = true;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
|
||||||
@@ -58,26 +62,21 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
|||||||
|
|
||||||
final int MAX_WIDTH = getMaxWidth(format);
|
final int MAX_WIDTH = getMaxWidth(format);
|
||||||
|
|
||||||
if(imageView.getWidth() < MAX_WIDTH)
|
if (imageView.getWidth() < MAX_WIDTH) {
|
||||||
{
|
|
||||||
imageHeight = imageView.getHeight();
|
imageHeight = imageView.getHeight();
|
||||||
imageWidth = imageView.getWidth();
|
imageWidth = imageView.getWidth();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// Scale down the image to reduce the memory needed to produce it
|
// Scale down the image to reduce the memory needed to produce it
|
||||||
imageWidth = MAX_WIDTH;
|
imageWidth = MAX_WIDTH;
|
||||||
double ratio = (double)MAX_WIDTH / (double)imageView.getWidth();
|
double ratio = (double) MAX_WIDTH / (double) imageView.getWidth();
|
||||||
imageHeight = (int)(imageView.getHeight() * ratio);
|
imageHeight = (int) (imageView.getHeight() * ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showFallback = showFallback;
|
this.showFallback = showFallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getMaxWidth(BarcodeFormat format)
|
private int getMaxWidth(CatimaBarcode format) {
|
||||||
{
|
switch (format.format()) {
|
||||||
switch(format)
|
|
||||||
{
|
|
||||||
// 2D barcodes
|
// 2D barcodes
|
||||||
case AZTEC:
|
case AZTEC:
|
||||||
case DATA_MATRIX:
|
case DATA_MATRIX:
|
||||||
@@ -104,10 +103,8 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFallbackString(BarcodeFormat format)
|
private String getFallbackString(CatimaBarcode format) {
|
||||||
{
|
switch (format.format()) {
|
||||||
switch(format)
|
|
||||||
{
|
|
||||||
// 2D barcodes
|
// 2D barcodes
|
||||||
case AZTEC:
|
case AZTEC:
|
||||||
return "AZTEC";
|
return "AZTEC";
|
||||||
@@ -123,6 +120,8 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
|||||||
return "C0C";
|
return "C0C";
|
||||||
case CODE_39:
|
case CODE_39:
|
||||||
return "CODE_39";
|
return "CODE_39";
|
||||||
|
case CODE_93:
|
||||||
|
return "CODE_93";
|
||||||
case CODE_128:
|
case CODE_128:
|
||||||
return "CODE_128";
|
return "CODE_128";
|
||||||
case EAN_8:
|
case EAN_8:
|
||||||
@@ -140,23 +139,17 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap generate()
|
private Bitmap generate() {
|
||||||
{
|
if (cardId.isEmpty()) {
|
||||||
if (cardId.isEmpty())
|
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiFormatWriter writer = new MultiFormatWriter();
|
MultiFormatWriter writer = new MultiFormatWriter();
|
||||||
BitMatrix bitMatrix;
|
BitMatrix bitMatrix;
|
||||||
try
|
try {
|
||||||
{
|
try {
|
||||||
try
|
bitMatrix = writer.encode(cardId, format.format(), imageWidth, imageHeight, null);
|
||||||
{
|
} catch (Exception e) {
|
||||||
bitMatrix = writer.encode(cardId, format, imageWidth, imageHeight, null);
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
// Cast a wider net here and catch any exception, as there are some
|
// Cast a wider net here and catch any exception, as there are some
|
||||||
// cases where an encoder may fail if the data is invalid for the
|
// cases where an encoder may fail if the data is invalid for the
|
||||||
// barcode type. If this happens, we want to fail gracefully.
|
// barcode type. If this happens, we want to fail gracefully.
|
||||||
@@ -171,11 +164,9 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
|||||||
|
|
||||||
int[] pixels = new int[bitMatrixWidth * bitMatrixHeight];
|
int[] pixels = new int[bitMatrixWidth * bitMatrixHeight];
|
||||||
|
|
||||||
for (int y = 0; y < bitMatrixHeight; y++)
|
for (int y = 0; y < bitMatrixHeight; y++) {
|
||||||
{
|
|
||||||
int offset = y * bitMatrixWidth;
|
int offset = y * bitMatrixWidth;
|
||||||
for (int x = 0; x < bitMatrixWidth; x++)
|
for (int x = 0; x < bitMatrixWidth; x++) {
|
||||||
{
|
|
||||||
int color = bitMatrix.get(x, y) ? BLACK : WHITE;
|
int color = bitMatrix.get(x, y) ? BLACK : WHITE;
|
||||||
pixels[offset + x] = color;
|
pixels[offset + x] = color;
|
||||||
}
|
}
|
||||||
@@ -195,19 +186,14 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
|||||||
int widthScale = imageWidth / bitMatrixHeight;
|
int widthScale = imageWidth / bitMatrixHeight;
|
||||||
int scalingFactor = Math.min(heightScale, widthScale);
|
int scalingFactor = Math.min(heightScale, widthScale);
|
||||||
|
|
||||||
if(scalingFactor > 1)
|
if (scalingFactor > 1) {
|
||||||
{
|
|
||||||
bitmap = Bitmap.createScaledBitmap(bitmap, bitMatrixWidth * scalingFactor, bitMatrixHeight * scalingFactor, false);
|
bitmap = Bitmap.createScaledBitmap(bitmap, bitMatrixWidth * scalingFactor, bitMatrixHeight * scalingFactor, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
} catch (WriterException e) {
|
||||||
catch (WriterException e)
|
|
||||||
{
|
|
||||||
Log.e(TAG, "Failed to generate barcode of type " + format + ": " + cardId, e);
|
Log.e(TAG, "Failed to generate barcode of type " + format + ": " + cardId, e);
|
||||||
}
|
} catch (OutOfMemoryError e) {
|
||||||
catch(OutOfMemoryError e)
|
|
||||||
{
|
|
||||||
Log.w(TAG, "Insufficient memory to render barcode, "
|
Log.w(TAG, "Insufficient memory to render barcode, "
|
||||||
+ imageWidth + "x" + imageHeight + ", " + format.name()
|
+ imageWidth + "x" + imageHeight + ", " + format.name()
|
||||||
+ ", length=" + cardId.length(), e);
|
+ ", length=" + cardId.length(), e);
|
||||||
@@ -216,40 +202,49 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap doInBackground(Void... params)
|
public Bitmap doInBackground(Void... params) {
|
||||||
{
|
// Only do the hard tasks if we've not already been cancelled
|
||||||
Bitmap bitmap = generate();
|
if (!Thread.currentThread().isInterrupted()) {
|
||||||
|
Bitmap bitmap = generate();
|
||||||
|
|
||||||
if (bitmap == null) {
|
if (bitmap == null) {
|
||||||
isSuccesful = false;
|
isSuccesful = false;
|
||||||
|
|
||||||
if (showFallback) {
|
if (showFallback && !Thread.currentThread().isInterrupted()) {
|
||||||
Log.i(TAG, "Barcode generation failed, generating fallback...");
|
Log.i(TAG, "Barcode generation failed, generating fallback...");
|
||||||
cardId = getFallbackString(format);
|
cardId = getFallbackString(format);
|
||||||
bitmap = generate();
|
bitmap = generate();
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return bitmap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bitmap;
|
// We've been interrupted - create a empty fallback
|
||||||
|
Bitmap.Config config = Bitmap.Config.ARGB_8888;
|
||||||
|
return Bitmap.createBitmap(imageWidth, imageHeight, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onPostExecute(Bitmap result)
|
public void onPostExecute(Object castResult) {
|
||||||
{
|
Bitmap result = (Bitmap) castResult;
|
||||||
|
|
||||||
Log.i(TAG, "Finished generating barcode image of type " + format + ": " + cardId);
|
Log.i(TAG, "Finished generating barcode image of type " + format + ": " + cardId);
|
||||||
ImageView imageView = imageViewReference.get();
|
ImageView imageView = imageViewReference.get();
|
||||||
if(imageView == null)
|
if (imageView == null) {
|
||||||
{
|
|
||||||
// The ImageView no longer exists, nothing to do
|
// The ImageView no longer exists, nothing to do
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String formatPrettyName = format.prettyName();
|
||||||
|
|
||||||
imageView.setTag(isSuccesful);
|
imageView.setTag(isSuccesful);
|
||||||
|
|
||||||
imageView.setImageBitmap(result);
|
imageView.setImageBitmap(result);
|
||||||
|
imageView.setContentDescription(mContext.getString(R.string.barcodeImageDescriptionWithType, formatPrettyName));
|
||||||
TextView textView = textViewReference.get();
|
TextView textView = textViewReference.get();
|
||||||
|
|
||||||
if(result != null)
|
if (result != null) {
|
||||||
{
|
|
||||||
Log.i(TAG, "Displaying barcode");
|
Log.i(TAG, "Displaying barcode");
|
||||||
imageView.setVisibility(View.VISIBLE);
|
imageView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
@@ -261,11 +256,9 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
|||||||
|
|
||||||
if (textView != null) {
|
if (textView != null) {
|
||||||
textView.setVisibility(View.VISIBLE);
|
textView.setVisibility(View.VISIBLE);
|
||||||
textView.setText(format.name());
|
textView.setText(formatPrettyName);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.i(TAG, "Barcode generation failed, removing image from display");
|
Log.i(TAG, "Barcode generation failed, removing image from display");
|
||||||
imageView.setVisibility(View.GONE);
|
imageView.setVisibility(View.GONE);
|
||||||
if (textView != null) {
|
if (textView != null) {
|
||||||
@@ -277,4 +270,19 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
|||||||
callback.run();
|
callback.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPreExecute() {
|
||||||
|
// No Action
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provided to comply with Callable while keeping the original Syntax of AsyncTask
|
||||||
|
*
|
||||||
|
* @return generated Bitmap
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Bitmap call() {
|
||||||
|
return doInBackground();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,31 +2,21 @@ package protect.card_locker;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.os.Handler;
|
||||||
import android.text.TextWatcher;
|
import android.os.Looper;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageView;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,98 +25,59 @@ import androidx.appcompat.widget.Toolbar;
|
|||||||
* the data. The user may then select any barcode, where its
|
* the data. The user may then select any barcode, where its
|
||||||
* data and type will be returned to the caller.
|
* data and type will be returned to the caller.
|
||||||
*/
|
*/
|
||||||
public class BarcodeSelectorActivity extends AppCompatActivity
|
public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements BarcodeSelectorAdapter.BarcodeSelectorListener {
|
||||||
{
|
|
||||||
private static final String TAG = "Catima";
|
private static final String TAG = "Catima";
|
||||||
|
|
||||||
// Result this activity will return
|
// Result this activity will return
|
||||||
public static final String BARCODE_CONTENTS = "contents";
|
public static final String BARCODE_CONTENTS = "contents";
|
||||||
public static final String BARCODE_FORMAT = "format";
|
public static final String BARCODE_FORMAT = "format";
|
||||||
|
|
||||||
// These are all the barcode types that the zxing library
|
private final Handler typingDelayHandler = new Handler(Looper.getMainLooper());
|
||||||
// is able to generate a barcode for, and thus should be
|
public static final Integer INPUT_DELAY = 250;
|
||||||
// the only barcodes which we should attempt to scan.
|
|
||||||
public static final Collection<String> SUPPORTED_BARCODE_TYPES = Collections.unmodifiableList(
|
|
||||||
Arrays.asList(
|
|
||||||
BarcodeFormat.AZTEC.name(),
|
|
||||||
BarcodeFormat.CODE_39.name(),
|
|
||||||
BarcodeFormat.CODE_128.name(),
|
|
||||||
BarcodeFormat.CODABAR.name(),
|
|
||||||
BarcodeFormat.DATA_MATRIX.name(),
|
|
||||||
BarcodeFormat.EAN_8.name(),
|
|
||||||
BarcodeFormat.EAN_13.name(),
|
|
||||||
BarcodeFormat.ITF.name(),
|
|
||||||
BarcodeFormat.PDF_417.name(),
|
|
||||||
BarcodeFormat.QR_CODE.name(),
|
|
||||||
BarcodeFormat.UPC_A.name(),
|
|
||||||
BarcodeFormat.UPC_E.name()
|
|
||||||
));
|
|
||||||
|
|
||||||
private Map<String, Pair<Integer, Integer>> barcodeViewMap;
|
private BarcodeSelectorAdapter mAdapter;
|
||||||
private LinkedList<AsyncTask> barcodeGeneratorTasks = new LinkedList<>();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState)
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
{
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
setTitle(R.string.selectBarcodeTitle);
|
||||||
setContentView(R.layout.barcode_selector_activity);
|
setContentView(R.layout.barcode_selector_activity);
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
if(actionBar != null)
|
if (actionBar != null) {
|
||||||
{
|
|
||||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
barcodeViewMap = ImmutableMap.<String, Pair<Integer, Integer>>builder()
|
|
||||||
.put(BarcodeFormat.AZTEC.name(), new Pair<>(R.id.aztecBarcode, R.id.aztecBarcodeText))
|
|
||||||
.put(BarcodeFormat.CODE_39.name(), new Pair<>(R.id.code39Barcode, R.id.code39BarcodeText))
|
|
||||||
.put(BarcodeFormat.CODE_128.name(), new Pair<>(R.id.code128Barcode, R.id.code128BarcodeText))
|
|
||||||
.put(BarcodeFormat.CODABAR.name(), new Pair<>(R.id.codabarBarcode, R.id.codabarBarcodeText))
|
|
||||||
.put(BarcodeFormat.DATA_MATRIX.name(), new Pair<>(R.id.datamatrixBarcode, R.id.datamatrixBarcodeText))
|
|
||||||
.put(BarcodeFormat.EAN_8.name(), new Pair<>(R.id.ean8Barcode, R.id.ean8BarcodeText))
|
|
||||||
.put(BarcodeFormat.EAN_13.name(), new Pair<>(R.id.ean13Barcode, R.id.ean13BarcodeText))
|
|
||||||
.put(BarcodeFormat.ITF.name(), new Pair<>(R.id.itfBarcode, R.id.itfBarcodeText))
|
|
||||||
.put(BarcodeFormat.PDF_417.name(), new Pair<>(R.id.pdf417Barcode, R.id.pdf417BarcodeText))
|
|
||||||
.put(BarcodeFormat.QR_CODE.name(), new Pair<>(R.id.qrcodeBarcode, R.id.qrcodeBarcodeText))
|
|
||||||
.put(BarcodeFormat.UPC_A.name(), new Pair<>(R.id.upcaBarcode, R.id.upcaBarcodeText))
|
|
||||||
.put(BarcodeFormat.UPC_E.name(), new Pair<>(R.id.upceBarcode, R.id.upceBarcodeText))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
EditText cardId = findViewById(R.id.cardId);
|
EditText cardId = findViewById(R.id.cardId);
|
||||||
cardId.addTextChangedListener(new TextWatcher()
|
ListView mBarcodeList = findViewById(R.id.barcodes);
|
||||||
{
|
mAdapter = new BarcodeSelectorAdapter(this, new ArrayList<>(), this);
|
||||||
|
mBarcodeList.setAdapter(mAdapter);
|
||||||
|
|
||||||
|
cardId.addTextChangedListener(new SimpleTextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after)
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
{
|
// Delay the input processing so we avoid overload
|
||||||
// Noting to do
|
typingDelayHandler.removeCallbacksAndMessages(null);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
typingDelayHandler.postDelayed(() -> {
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count)
|
Log.d(TAG, "Entered text: " + s);
|
||||||
{
|
|
||||||
Log.d(TAG, "Entered text: " + s);
|
|
||||||
|
|
||||||
generateBarcodes(s.toString());
|
runOnUiThread(() -> {
|
||||||
|
generateBarcodes(s.toString());
|
||||||
|
|
||||||
View noBarcodeButtonView = findViewById(R.id.noBarcode);
|
View noBarcodeButtonView = findViewById(R.id.noBarcode);
|
||||||
setButtonListener(noBarcodeButtonView, s.toString());
|
setButtonListener(noBarcodeButtonView, s.toString());
|
||||||
noBarcodeButtonView.setEnabled(s.length() > 0);
|
noBarcodeButtonView.setEnabled(s.length() > 0);
|
||||||
}
|
});
|
||||||
|
}, INPUT_DELAY);
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s)
|
|
||||||
{
|
|
||||||
// Noting to do
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final Bundle b = getIntent().getExtras();
|
final Bundle b = getIntent().getExtras();
|
||||||
final String initialCardId = b != null ? b.getString("initialCardId") : null;
|
final String initialCardId = b != null ? b.getString("initialCardId") : null;
|
||||||
|
|
||||||
if(initialCardId != null)
|
if (initialCardId != null) {
|
||||||
{
|
|
||||||
cardId.setText(initialCardId);
|
cardId.setText(initialCardId);
|
||||||
} else {
|
} else {
|
||||||
generateBarcodes("");
|
generateBarcodes("");
|
||||||
@@ -134,101 +85,29 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void generateBarcodes(String value) {
|
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
|
// Update barcodes
|
||||||
for(Map.Entry<String, Pair<Integer, Integer>> entry : barcodeViewMap.entrySet())
|
ArrayList<CatimaBarcodeWithValue> barcodes = new ArrayList<>();
|
||||||
{
|
for (BarcodeFormat barcodeFormat : CatimaBarcode.barcodeFormats) {
|
||||||
ImageView image = findViewById(entry.getValue().first);
|
CatimaBarcode catimaBarcode = CatimaBarcode.fromBarcode(barcodeFormat);
|
||||||
TextView text = findViewById(entry.getValue().second);
|
barcodes.add(new CatimaBarcodeWithValue(catimaBarcode, value));
|
||||||
createBarcodeOption(image, entry.getKey(), value, text);
|
|
||||||
}
|
}
|
||||||
|
mAdapter.setBarcodes(barcodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setButtonListener(final View button, final String cardId)
|
private void setButtonListener(final View button, final String cardId) {
|
||||||
{
|
button.setOnClickListener(view -> {
|
||||||
button.setOnClickListener(new View.OnClickListener() {
|
Log.d(TAG, "Selected no barcode");
|
||||||
@Override
|
Intent result = new Intent();
|
||||||
public void onClick(View view) {
|
result.putExtra(BARCODE_FORMAT, "");
|
||||||
Log.d(TAG, "Selected no barcode");
|
result.putExtra(BARCODE_CONTENTS, cardId);
|
||||||
Intent result = new Intent();
|
BarcodeSelectorActivity.this.setResult(RESULT_OK, result);
|
||||||
result.putExtra(BARCODE_FORMAT, "");
|
finish();
|
||||||
result.putExtra(BARCODE_CONTENTS, cardId);
|
|
||||||
BarcodeSelectorActivity.this.setResult(RESULT_OK, result);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId, final TextView text)
|
|
||||||
{
|
|
||||||
final BarcodeFormat format = BarcodeFormat.valueOf(formatType);
|
|
||||||
if(format == null)
|
|
||||||
{
|
|
||||||
Log.w(TAG, "Unsupported barcode format: " + formatType);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
image.setImageBitmap(null);
|
|
||||||
image.setOnClickListener(new View.OnClickListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
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);
|
|
||||||
BarcodeSelectorActivity.this.setResult(RESULT_OK, result);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(image.getHeight() == 0)
|
|
||||||
{
|
|
||||||
// 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.
|
|
||||||
image.getViewTreeObserver().addOnGlobalLayoutListener(
|
|
||||||
new ViewTreeObserver.OnGlobalLayoutListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onGlobalLayout()
|
|
||||||
{
|
|
||||||
Log.d(TAG, "Global layout finished, type: + " + formatType + ", width: " + image.getWidth());
|
|
||||||
image.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
|
||||||
|
|
||||||
Log.d(TAG, "Generating barcode for type " + formatType);
|
|
||||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format, text, true, null);
|
|
||||||
barcodeGeneratorTasks.add(task);
|
|
||||||
task.execute();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.d(TAG, "Generating barcode for type " + formatType);
|
|
||||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format, text, true, null);
|
|
||||||
barcodeGeneratorTasks.add(task);
|
|
||||||
task.execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item)
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
{
|
if (item.getItemId() == android.R.id.home) {
|
||||||
if (item.getItemId() == android.R.id.home)
|
|
||||||
{
|
|
||||||
setResult(Activity.RESULT_CANCELED);
|
setResult(Activity.RESULT_CANCELED);
|
||||||
finish();
|
finish();
|
||||||
return true;
|
return true;
|
||||||
@@ -236,4 +115,26 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
|||||||
|
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRowClicked(int inputPosition, View view) {
|
||||||
|
CatimaBarcodeWithValue barcodeWithValue = mAdapter.getItem(inputPosition);
|
||||||
|
CatimaBarcode catimaBarcode = barcodeWithValue.catimaBarcode();
|
||||||
|
|
||||||
|
if (!mAdapter.isValid(view)) {
|
||||||
|
Toast.makeText(this, getString(R.string.wrongValueForBarcodeType), Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String barcodeFormat = catimaBarcode.format().name();
|
||||||
|
String value = barcodeWithValue.value();
|
||||||
|
|
||||||
|
Log.d(TAG, "Selected barcode type " + barcodeFormat);
|
||||||
|
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra(BARCODE_FORMAT, barcodeFormat);
|
||||||
|
result.putExtra(BARCODE_CONTENTS, value);
|
||||||
|
BarcodeSelectorActivity.this.setResult(RESULT_OK, result);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import protect.card_locker.async.TaskHandler;
|
||||||
|
|
||||||
|
public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue> {
|
||||||
|
private static final String TAG = "Catima";
|
||||||
|
|
||||||
|
private final TaskHandler mTasks = new TaskHandler();
|
||||||
|
private final BarcodeSelectorListener mListener;
|
||||||
|
|
||||||
|
private static class ViewHolder {
|
||||||
|
ImageView image;
|
||||||
|
TextView text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface BarcodeSelectorListener {
|
||||||
|
void onRowClicked(int inputPosition, View view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BarcodeSelectorAdapter(Context context, ArrayList<CatimaBarcodeWithValue> barcodes, BarcodeSelectorListener barcodeSelectorListener) {
|
||||||
|
super(context, 0, barcodes);
|
||||||
|
mListener = barcodeSelectorListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBarcodes(ArrayList<CatimaBarcodeWithValue> barcodes) {
|
||||||
|
clear();
|
||||||
|
addAll(barcodes);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
mTasks.flushTaskList(TaskHandler.TYPE.BARCODE, true, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
CatimaBarcodeWithValue catimaBarcodeWithValue = getItem(position);
|
||||||
|
CatimaBarcode catimaBarcode = catimaBarcodeWithValue.catimaBarcode();
|
||||||
|
String value = catimaBarcodeWithValue.value();
|
||||||
|
|
||||||
|
ViewHolder viewHolder;
|
||||||
|
if (convertView == null) {
|
||||||
|
viewHolder = new ViewHolder();
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||||
|
convertView = inflater.inflate(R.layout.barcode_layout, parent, false);
|
||||||
|
viewHolder.image = convertView.findViewById(R.id.barcodeImage);
|
||||||
|
viewHolder.text = convertView.findViewById(R.id.barcodeName);
|
||||||
|
convertView.setTag(viewHolder);
|
||||||
|
} else {
|
||||||
|
viewHolder = (ViewHolder) convertView.getTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
createBarcodeOption(viewHolder.image, catimaBarcode.format().name(), value, viewHolder.text);
|
||||||
|
|
||||||
|
View finalConvertView = convertView;
|
||||||
|
convertView.setOnClickListener(view -> mListener.onRowClicked(position, finalConvertView));
|
||||||
|
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid(View view) {
|
||||||
|
ViewHolder viewHolder = (ViewHolder) view.getTag();
|
||||||
|
return viewHolder.image.getTag() != null && (boolean) viewHolder.image.getTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId, final TextView text) {
|
||||||
|
final CatimaBarcode format = CatimaBarcode.fromName(formatType);
|
||||||
|
|
||||||
|
image.setImageBitmap(null);
|
||||||
|
|
||||||
|
if (image.getHeight() == 0) {
|
||||||
|
// 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.
|
||||||
|
image.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||||
|
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||||
|
@Override
|
||||||
|
public void onGlobalLayout() {
|
||||||
|
Log.d(TAG, "Global layout finished, type: + " + formatType + ", width: " + image.getWidth());
|
||||||
|
image.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||||
|
|
||||||
|
Log.d(TAG, "Generating barcode for type " + formatType);
|
||||||
|
|
||||||
|
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, text, true, null);
|
||||||
|
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Generating barcode for type " + formatType);
|
||||||
|
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, text, true, null);
|
||||||
|
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,30 +4,30 @@ import android.database.Cursor;
|
|||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
public abstract class BaseCursorAdapter<V extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<V>
|
public abstract class BaseCursorAdapter<V extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<V> {
|
||||||
{
|
public Cursor mCursor;
|
||||||
private Cursor mCursor;
|
|
||||||
private boolean mDataValid;
|
private boolean mDataValid;
|
||||||
private int mRowIDColumn;
|
private int mRowIDColumn;
|
||||||
|
|
||||||
public BaseCursorAdapter(Cursor inputCursor)
|
private String mRowIDColumnName;
|
||||||
{
|
|
||||||
|
public BaseCursorAdapter(Cursor inputCursor, String rowIDColumnName) {
|
||||||
setHasStableIds(true);
|
setHasStableIds(true);
|
||||||
|
|
||||||
|
mRowIDColumnName = rowIDColumnName;
|
||||||
|
|
||||||
swapCursor(inputCursor);
|
swapCursor(inputCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void onBindViewHolder(V inputHolder, Cursor inputCursor);
|
public abstract void onBindViewHolder(V inputHolder, Cursor inputCursor);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(V inputHolder, int inputPosition)
|
public void onBindViewHolder(V inputHolder, int inputPosition) {
|
||||||
{
|
if (!mDataValid) {
|
||||||
if (!mDataValid)
|
|
||||||
{
|
|
||||||
throw new IllegalStateException("Cannot bind view holder when cursor is in invalid state.");
|
throw new IllegalStateException("Cannot bind view holder when cursor is in invalid state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mCursor.moveToPosition(inputPosition))
|
if (!mCursor.moveToPosition(inputPosition)) {
|
||||||
{
|
|
||||||
throw new IllegalStateException("Could not move cursor to position " + inputPosition + " when trying to bind view holder");
|
throw new IllegalStateException("Could not move cursor to position " + inputPosition + " when trying to bind view holder");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,49 +35,38 @@ public abstract class BaseCursorAdapter<V extends RecyclerView.ViewHolder> exten
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount()
|
public int getItemCount() {
|
||||||
{
|
if (mDataValid) {
|
||||||
if (mDataValid)
|
|
||||||
{
|
|
||||||
return mCursor.getCount();
|
return mCursor.getCount();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(int inputPosition)
|
public long getItemId(int inputPosition) {
|
||||||
{
|
if (!mDataValid) {
|
||||||
if (!mDataValid)
|
|
||||||
{
|
|
||||||
throw new IllegalStateException("Cannot lookup item id when cursor is in invalid state.");
|
throw new IllegalStateException("Cannot lookup item id when cursor is in invalid state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mCursor.moveToPosition(inputPosition))
|
if (!mCursor.moveToPosition(inputPosition)) {
|
||||||
{
|
|
||||||
throw new IllegalStateException("Could not move cursor to position " + inputPosition + " when trying to get an item id");
|
throw new IllegalStateException("Could not move cursor to position " + inputPosition + " when trying to get an item id");
|
||||||
}
|
}
|
||||||
|
|
||||||
return mCursor.getLong(mRowIDColumn);
|
return mCursor.getLong(mRowIDColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void swapCursor(Cursor inputCursor)
|
public void swapCursor(Cursor inputCursor) {
|
||||||
{
|
if (inputCursor == mCursor) {
|
||||||
if (inputCursor == mCursor)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputCursor != null)
|
if (inputCursor != null) {
|
||||||
{
|
|
||||||
mCursor = inputCursor;
|
mCursor = inputCursor;
|
||||||
|
mRowIDColumn = mCursor.getColumnIndex(mRowIDColumnName);
|
||||||
mDataValid = true;
|
mDataValid = true;
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
notifyItemRangeRemoved(0, getItemCount());
|
notifyItemRangeRemoved(0, getItemCount());
|
||||||
mCursor = null;
|
mCursor = null;
|
||||||
mRowIDColumn = -1;
|
mRowIDColumn = -1;
|
||||||
|
|||||||
@@ -1,91 +1,70 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.core.content.pm.ShortcutInfoCompat;
|
||||||
|
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The configuration screen for creating a shortcut.
|
* The configuration screen for creating a shortcut.
|
||||||
*/
|
*/
|
||||||
public class CardShortcutConfigure extends AppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener
|
public class CardShortcutConfigure extends AppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
|
||||||
{
|
|
||||||
static final String TAG = "Catima";
|
static final String TAG = "Catima";
|
||||||
final DBHelper mDb = new DBHelper(this);
|
private SQLiteDatabase mDatabase;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
|
mDatabase = new DBHelper(this).getReadableDatabase();
|
||||||
|
|
||||||
// Set the result to CANCELED. This will cause nothing to happen if the
|
// Set the result to CANCELED. This will cause nothing to happen if the
|
||||||
// aback button is pressed.
|
// aback button is pressed.
|
||||||
setResult(RESULT_CANCELED);
|
setResult(RESULT_CANCELED);
|
||||||
|
|
||||||
setContentView(R.layout.main_activity);
|
setContentView(R.layout.simple_toolbar_list_activity);
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
toolbar.setVisibility(View.GONE);
|
toolbar.setTitle(R.string.shortcutSelectCard);
|
||||||
|
|
||||||
// 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 there are no cards, bail
|
||||||
if (db.getLoyaltyCardCount() == 0) {
|
if (DBHelper.getLoyaltyCardCount(mDatabase) == 0) {
|
||||||
Toast.makeText(this, R.string.noCardsMessage, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.noCardsMessage, Toast.LENGTH_LONG).show();
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
final RecyclerView cardList = findViewById(R.id.list);
|
final RecyclerView cardList = findViewById(R.id.list);
|
||||||
cardList.setVisibility(View.VISIBLE);
|
GridLayoutManager layoutManager = (GridLayoutManager) cardList.getLayoutManager();
|
||||||
|
if (layoutManager != null) {
|
||||||
Cursor cardCursor = db.getLoyaltyCardCursor();
|
layoutManager.setSpanCount(getResources().getInteger(R.integer.main_view_card_columns));
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(mDatabase);
|
||||||
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor, this);
|
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor, this);
|
||||||
cardList.setAdapter(adapter);
|
cardList.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onClickAction(int position) {
|
private void onClickAction(int position) {
|
||||||
Cursor selected = mDb.getLoyaltyCardCursor();
|
Cursor selected = DBHelper.getLoyaltyCardCursor(mDatabase);
|
||||||
selected.moveToPosition(position);
|
selected.moveToPosition(position);
|
||||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
|
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);
|
ShortcutInfoCompat shortcut = ShortcutHelper.createShortcutBuilder(CardShortcutConfigure.this, loyaltyCard).build();
|
||||||
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);
|
setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(CardShortcutConfigure.this, shortcut));
|
||||||
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
|
@Override
|
||||||
public void onRowClicked(int inputPosition) {
|
public void onRowClicked(int inputPosition) {
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.Icon;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.service.controls.Control;
|
||||||
|
import android.service.controls.ControlsProviderService;
|
||||||
|
import android.service.controls.DeviceTypes;
|
||||||
|
import android.service.controls.actions.ControlAction;
|
||||||
|
import android.service.controls.templates.StatelessTemplate;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Flow;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
|
public class CardsOnPowerScreenService extends ControlsProviderService {
|
||||||
|
|
||||||
|
public static final String PREFIX = "catima-";
|
||||||
|
static final String TAG = "Catima";
|
||||||
|
private SQLiteDatabase mDatabase;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
|
mDatabase = new DBHelper(this).getReadableDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Flow.Publisher<Control> createPublisherForAllAvailable() {
|
||||||
|
Cursor loyaltyCardCursor = DBHelper.getLoyaltyCardCursor(mDatabase);
|
||||||
|
return subscriber -> {
|
||||||
|
while (loyaltyCardCursor.moveToNext()) {
|
||||||
|
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(loyaltyCardCursor);
|
||||||
|
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.putExtra("id", card.id);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), card.id, openIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
subscriber.onNext(
|
||||||
|
new Control.StatelessBuilder(PREFIX + card.id, pendingIntent)
|
||||||
|
.setControlId(PREFIX + card.id)
|
||||||
|
.setTitle(card.store)
|
||||||
|
.setDeviceType(DeviceTypes.TYPE_GENERIC_OPEN_CLOSE)
|
||||||
|
.setSubtitle(card.note)
|
||||||
|
.setCustomIcon(Icon.createWithBitmap(getIcon(this, card)))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
subscriber.onComplete();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Flow.Publisher<Control> createPublisherFor(@NonNull List<String> controlIds) {
|
||||||
|
return subscriber -> {
|
||||||
|
subscriber.onSubscribe(new NoOpSubscription());
|
||||||
|
for (String controlId : controlIds) {
|
||||||
|
Control control;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Integer cardId = this.controlIdToCardId(controlId);
|
||||||
|
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, cardId);
|
||||||
|
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.putExtra("id", card.id);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), card.id, openIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
control = new Control.StatefulBuilder(controlId, pendingIntent)
|
||||||
|
.setTitle(card.store)
|
||||||
|
.setDeviceType(DeviceTypes.TYPE_GENERIC_OPEN_CLOSE)
|
||||||
|
.setSubtitle(card.note)
|
||||||
|
.setStatus(Control.STATUS_OK)
|
||||||
|
.setControlTemplate(new StatelessTemplate(controlId))
|
||||||
|
.setCustomIcon(Icon.createWithBitmap(getIcon(this, card)))
|
||||||
|
.build();
|
||||||
|
} catch (NullPointerException ignored) {
|
||||||
|
Intent mainScreenIntent = new Intent(this, MainActivity.class)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), -1, mainScreenIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
control = new Control.StatefulBuilder(controlId, pendingIntent)
|
||||||
|
.setStatus(Control.STATUS_NOT_FOUND)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Dispatching widget " + controlId);
|
||||||
|
subscriber.onNext(control);
|
||||||
|
}
|
||||||
|
subscriber.onComplete();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap getIcon(Context context, LoyaltyCard loyaltyCard) {
|
||||||
|
Bitmap cardIcon = Utils.retrieveCardImage(context, loyaltyCard.id, ImageLocationType.icon);
|
||||||
|
|
||||||
|
if (cardIcon != null) {
|
||||||
|
return cardIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utils.generateIcon(this, loyaltyCard.store, loyaltyCard.headerColor).getLetterTile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer controlIdToCardId(String controlId) {
|
||||||
|
if (controlId == null)
|
||||||
|
return null;
|
||||||
|
if (!controlId.startsWith(PREFIX)) {
|
||||||
|
Log.w(TAG, "Unsupported control ID format: " + controlId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
controlId = controlId.substring(PREFIX.length());
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(controlId);
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
Log.e(TAG, "Unsupported control ID format. Expected numeric after prefix, found: " + controlId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performControlAction(@NonNull String controlId, @NonNull ControlAction action, @NonNull Consumer<Integer> consumer) {
|
||||||
|
consumer.accept(ControlAction.RESPONSE_OK);
|
||||||
|
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.putExtra("id", controlIdToCardId(controlId));
|
||||||
|
startActivity(openIntent);
|
||||||
|
|
||||||
|
closePowerScreenOnAndroid11();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint({"MissingPermission", "deprecation"})
|
||||||
|
private void closePowerScreenOnAndroid11() {
|
||||||
|
// Android 12 will auto-close the power screen, but earlier versions won't
|
||||||
|
// Lint complains about this but on Android 11 the permission is not needed
|
||||||
|
// On Android 12, we don't need it, and Google will probably get angry if we ask for it
|
||||||
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
|
||||||
|
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A no-op subscription.
|
||||||
|
* <p>
|
||||||
|
* Flow.Subscriptions are made to last during time and receive periodic updates.
|
||||||
|
* Our app does not require sending periodic updates of loyalty cards, so we are just ignoring anything in the subscription
|
||||||
|
* Also, our db is quick enough to respond that the Publisher is immediately sending and completing data.
|
||||||
|
* This facility is overkill, but if we don't call onSubscribe the service won't work
|
||||||
|
*/
|
||||||
|
private static class NoOpSubscription implements Flow.Subscription {
|
||||||
|
@Override
|
||||||
|
public void request(long l) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class CatimaAppCompatActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
SharedPreferences pref;
|
||||||
|
HashMap<String, Integer> supportedThemes;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attachBaseContext(Context base) {
|
||||||
|
// Apply chosen language
|
||||||
|
super.attachBaseContext(Utils.updateBaseContextLocale(base));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Resources.Theme getTheme() {
|
||||||
|
if (supportedThemes == null) {
|
||||||
|
supportedThemes = new HashMap<>();
|
||||||
|
supportedThemes.put(getString(R.string.settings_key_blue_theme), R.style.AppTheme_blue);
|
||||||
|
supportedThemes.put(getString(R.string.settings_key_brown_theme), R.style.AppTheme_brown);
|
||||||
|
supportedThemes.put(getString(R.string.settings_key_green_theme), R.style.AppTheme_green);
|
||||||
|
supportedThemes.put(getString(R.string.settings_key_grey_theme), R.style.AppTheme_grey);
|
||||||
|
supportedThemes.put(getString(R.string.settings_key_magenta_theme), R.style.AppTheme_magenta);
|
||||||
|
supportedThemes.put(getString(R.string.settings_key_pink_theme), R.style.AppTheme_pink);
|
||||||
|
supportedThemes.put(getString(R.string.settings_key_sky_blue_theme), R.style.AppTheme_sky_blue);
|
||||||
|
supportedThemes.put(getString(R.string.settings_key_violet_theme), R.style.AppTheme_violet);
|
||||||
|
}
|
||||||
|
|
||||||
|
Resources.Theme theme = super.getTheme();
|
||||||
|
pref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||||
|
String themeName = pref.getString(getString(R.string.setting_key_theme_color), getString(R.string.settings_key_catima_theme));
|
||||||
|
|
||||||
|
theme.applyStyle(Utils.mapGetOrDefault(supportedThemes, themeName, R.style.AppTheme_NoActionBar), true);
|
||||||
|
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getThemeColor() {
|
||||||
|
TypedValue typedValue = new TypedValue();
|
||||||
|
Resources.Theme theme = getTheme();
|
||||||
|
theme.resolveAttribute(R.attr.colorPrimary, typedValue, true);
|
||||||
|
return typedValue.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
92
app/src/main/java/protect/card_locker/CatimaBarcode.java
Normal file
92
app/src/main/java/protect/card_locker/CatimaBarcode.java
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CatimaBarcode {
|
||||||
|
public static final List<BarcodeFormat> barcodeFormats = Collections.unmodifiableList(Arrays.asList(
|
||||||
|
BarcodeFormat.AZTEC,
|
||||||
|
BarcodeFormat.CODE_39,
|
||||||
|
BarcodeFormat.CODE_93,
|
||||||
|
BarcodeFormat.CODE_128,
|
||||||
|
BarcodeFormat.CODABAR,
|
||||||
|
BarcodeFormat.DATA_MATRIX,
|
||||||
|
BarcodeFormat.EAN_8,
|
||||||
|
BarcodeFormat.EAN_13,
|
||||||
|
BarcodeFormat.ITF,
|
||||||
|
BarcodeFormat.PDF_417,
|
||||||
|
BarcodeFormat.QR_CODE,
|
||||||
|
BarcodeFormat.UPC_A,
|
||||||
|
BarcodeFormat.UPC_E
|
||||||
|
));
|
||||||
|
|
||||||
|
public static final List<String> barcodePrettyNames = Collections.unmodifiableList(Arrays.asList(
|
||||||
|
"Aztec",
|
||||||
|
"Code 39",
|
||||||
|
"Code 93",
|
||||||
|
"Code 128",
|
||||||
|
"Codabar",
|
||||||
|
"Data Matrix",
|
||||||
|
"EAN 8",
|
||||||
|
"EAN 13",
|
||||||
|
"ITF",
|
||||||
|
"PDF 417",
|
||||||
|
"QR Code",
|
||||||
|
"UPC A",
|
||||||
|
"UPC E"
|
||||||
|
));
|
||||||
|
|
||||||
|
private final BarcodeFormat mBarcodeFormat;
|
||||||
|
|
||||||
|
private CatimaBarcode(BarcodeFormat barcodeFormat) {
|
||||||
|
mBarcodeFormat = barcodeFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CatimaBarcode fromBarcode(BarcodeFormat barcodeFormat) {
|
||||||
|
return new CatimaBarcode(barcodeFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CatimaBarcode fromName(String name) {
|
||||||
|
return new CatimaBarcode(BarcodeFormat.valueOf(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CatimaBarcode fromPrettyName(String prettyName) {
|
||||||
|
try {
|
||||||
|
return new CatimaBarcode(barcodeFormats.get(barcodePrettyNames.indexOf(prettyName)));
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
throw new IllegalArgumentException("No barcode type with pretty name " + prettyName + " known!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSupported() {
|
||||||
|
return barcodeFormats.contains(mBarcodeFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSquare() {
|
||||||
|
return mBarcodeFormat == BarcodeFormat.AZTEC
|
||||||
|
|| mBarcodeFormat == BarcodeFormat.DATA_MATRIX
|
||||||
|
|| mBarcodeFormat == BarcodeFormat.MAXICODE
|
||||||
|
|| mBarcodeFormat == BarcodeFormat.QR_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BarcodeFormat format() {
|
||||||
|
return mBarcodeFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String name() {
|
||||||
|
return mBarcodeFormat.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String prettyName() {
|
||||||
|
int index = barcodeFormats.indexOf(mBarcodeFormat);
|
||||||
|
|
||||||
|
if (index == -1 || index >= barcodePrettyNames.size()) {
|
||||||
|
return mBarcodeFormat.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
return barcodePrettyNames.get(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
public class CatimaBarcodeWithValue {
|
||||||
|
private final CatimaBarcode mCatimaBarcode;
|
||||||
|
private final String mValue;
|
||||||
|
|
||||||
|
public CatimaBarcodeWithValue(CatimaBarcode catimaBarcode, String value) {
|
||||||
|
mCatimaBarcode = catimaBarcode;
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CatimaBarcode catimaBarcode() {
|
||||||
|
return mCatimaBarcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String value() {
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.journeyapps.barcodescanner.CaptureManager;
|
||||||
|
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
||||||
|
|
||||||
|
public class CatimaCaptureManager extends CaptureManager {
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
public CatimaCaptureManager(Activity activity, DecoratedBarcodeView barcodeView) {
|
||||||
|
super(activity, barcodeView);
|
||||||
|
|
||||||
|
mContext = activity.getApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void displayFrameworkBugMessageAndExit(String message) {
|
||||||
|
// We don't want to exit, as we also have a enter from card image and add manually button here
|
||||||
|
// So we show a toast instead
|
||||||
|
Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -5,15 +5,12 @@ package protect.card_locker;
|
|||||||
* encountered with the format of data being
|
* encountered with the format of data being
|
||||||
* imported or exported.
|
* imported or exported.
|
||||||
*/
|
*/
|
||||||
public class FormatException extends Exception
|
public class FormatException extends Exception {
|
||||||
{
|
public FormatException(String message) {
|
||||||
public FormatException(String message)
|
|
||||||
{
|
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FormatException(String message, Exception rootCause)
|
public FormatException(String message, Exception rootCause) {
|
||||||
{
|
|
||||||
super(message, rootCause);
|
super(message, rootCause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,39 @@ package protect.card_locker;
|
|||||||
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
|
||||||
public class Group
|
import androidx.annotation.Nullable;
|
||||||
{
|
|
||||||
public final String _id;
|
|
||||||
|
|
||||||
public Group(final String _id)
|
public class Group {
|
||||||
{
|
public final String _id;
|
||||||
|
public final int order;
|
||||||
|
|
||||||
|
public Group(final String _id, final int order) {
|
||||||
this._id = _id;
|
this._id = _id;
|
||||||
|
this.order = order;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Group toGroup(Cursor cursor)
|
public static Group toGroup(Cursor cursor) {
|
||||||
{
|
|
||||||
String _id = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbGroups.ID));
|
String _id = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbGroups.ID));
|
||||||
|
int order = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbGroups.ORDER));
|
||||||
|
|
||||||
return new Group(_id);
|
return new Group(_id, order);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof Group)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Group anotherGroup = (Group) obj;
|
||||||
|
return _id.equals(anotherGroup._id) && order == anotherGroup.order;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
String combined = _id + "_" + order;
|
||||||
|
return combined.hashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,55 +2,84 @@ package protect.card_locker;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.CursorAdapter;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.widget.AppCompatImageButton;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import protect.card_locker.preferences.Settings;
|
import protect.card_locker.preferences.Settings;
|
||||||
|
|
||||||
class GroupCursorAdapter extends CursorAdapter
|
public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.GroupListItemViewHolder> {
|
||||||
{
|
Settings mSettings;
|
||||||
Settings settings;
|
private final Context mContext;
|
||||||
DBHelper db;
|
private final GroupAdapterListener mListener;
|
||||||
|
SQLiteDatabase mDatabase;
|
||||||
|
|
||||||
public GroupCursorAdapter(Context context, Cursor cursor)
|
public GroupCursorAdapter(Context inputContext, Cursor inputCursor, GroupAdapterListener inputListener) {
|
||||||
{
|
super(inputCursor, DBHelper.LoyaltyCardDbGroups.ORDER);
|
||||||
super(context, cursor, 0);
|
setHasStableIds(true);
|
||||||
settings = new Settings(context);
|
mSettings = new Settings(inputContext);
|
||||||
|
mContext = inputContext.getApplicationContext();
|
||||||
|
mListener = inputListener;
|
||||||
|
mDatabase = new DBHelper(inputContext).getReadableDatabase();
|
||||||
|
|
||||||
db = new DBHelper(context);
|
swapCursor(inputCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The newView method is used to inflate a new view and return it,
|
@NonNull
|
||||||
// you don't bind any data to the view at this point.
|
|
||||||
@Override
|
@Override
|
||||||
public View newView(Context context, Cursor cursor, ViewGroup parent)
|
public GroupCursorAdapter.GroupListItemViewHolder onCreateViewHolder(ViewGroup inputParent, int inputViewType) {
|
||||||
{
|
View itemView = LayoutInflater.from(inputParent.getContext()).inflate(R.layout.group_layout, inputParent, false);
|
||||||
return LayoutInflater.from(context).inflate(R.layout.group_layout, parent, false);
|
return new GroupListItemViewHolder(itemView);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The bindView method is used to bind all data to a given view
|
public void onBindViewHolder(GroupListItemViewHolder inputHolder, Cursor inputCursor) {
|
||||||
// such as setting the text on a TextView.
|
Group group = Group.toGroup(inputCursor);
|
||||||
@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
|
inputHolder.mName.setText(group._id);
|
||||||
Group group = Group.toGroup(cursor);
|
|
||||||
|
|
||||||
Integer groupCardCount = db.getGroupCardCount(group._id);
|
int groupCardCount = DBHelper.getGroupCardCount(mDatabase, group._id);
|
||||||
|
inputHolder.mCardCount.setText(mContext.getResources().getQuantityString(R.plurals.groupCardCount, groupCardCount, groupCardCount));
|
||||||
|
|
||||||
// Populate fields with extracted properties
|
inputHolder.mName.setTextSize(mSettings.getFontSizeMax(mSettings.getMediumFont()));
|
||||||
nameField.setText(group._id);
|
inputHolder.mCardCount.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
|
||||||
|
|
||||||
countField.setText(context.getResources().getQuantityString(R.plurals.groupCardCount, groupCardCount, groupCardCount));
|
applyClickEvents(inputHolder);
|
||||||
|
}
|
||||||
|
|
||||||
nameField.setTextSize(settings.getFontSizeMax(settings.getMediumFont()));
|
private void applyClickEvents(GroupListItemViewHolder inputHolder) {
|
||||||
countField.setTextSize(settings.getFontSizeMax(settings.getSmallFont()));
|
inputHolder.mMoveDown.setOnClickListener(view -> mListener.onMoveDownButtonClicked(inputHolder.itemView));
|
||||||
|
inputHolder.mMoveUp.setOnClickListener(view -> mListener.onMoveUpButtonClicked(inputHolder.itemView));
|
||||||
|
inputHolder.mEdit.setOnClickListener(view -> mListener.onEditButtonClicked(inputHolder.itemView));
|
||||||
|
inputHolder.mDelete.setOnClickListener(view -> mListener.onDeleteButtonClicked(inputHolder.itemView));
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface GroupAdapterListener {
|
||||||
|
void onMoveDownButtonClicked(View view);
|
||||||
|
|
||||||
|
void onMoveUpButtonClicked(View view);
|
||||||
|
|
||||||
|
void onEditButtonClicked(View view);
|
||||||
|
|
||||||
|
void onDeleteButtonClicked(View view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GroupListItemViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
public TextView mName, mCardCount;
|
||||||
|
public AppCompatImageButton mMoveUp, mMoveDown, mEdit, mDelete;
|
||||||
|
|
||||||
|
public GroupListItemViewHolder(View inputView) {
|
||||||
|
super(inputView);
|
||||||
|
mName = inputView.findViewById(R.id.name);
|
||||||
|
mCardCount = inputView.findViewById(R.id.cardCount);
|
||||||
|
mMoveUp = inputView.findViewById(R.id.moveUp);
|
||||||
|
mMoveDown = inputView.findViewById(R.id.moveDown);
|
||||||
|
mEdit = inputView.findViewById(R.id.edit);
|
||||||
|
mDelete = inputView.findViewById(R.id.delete);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
public enum ImageLocationType {
|
||||||
|
front,
|
||||||
|
back,
|
||||||
|
icon
|
||||||
|
}
|
||||||
@@ -1,63 +1,78 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.File;
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
import protect.card_locker.async.TaskHandler;
|
||||||
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;
|
|
||||||
import protect.card_locker.importexport.DataFormat;
|
import protect.card_locker.importexport.DataFormat;
|
||||||
import protect.card_locker.importexport.ImportExportResult;
|
import protect.card_locker.importexport.ImportExportResult;
|
||||||
|
|
||||||
public class ImportExportActivity extends AppCompatActivity
|
public class ImportExportActivity extends CatimaAppCompatActivity {
|
||||||
{
|
|
||||||
private static final String TAG = "Catima";
|
private static final String TAG = "Catima";
|
||||||
|
|
||||||
private static final int PERMISSIONS_EXTERNAL_STORAGE = 1;
|
private static final int PERMISSIONS_EXTERNAL_STORAGE = 1;
|
||||||
private static final int CHOOSE_EXPORT_LOCATION = 2;
|
|
||||||
private static final int IMPORT = 3;
|
|
||||||
|
|
||||||
private ImportExportTask importExporter;
|
private ImportExportTask importExporter;
|
||||||
|
|
||||||
private String importAlertTitle;
|
private String importAlertTitle;
|
||||||
private String importAlertMessage;
|
private String importAlertMessage;
|
||||||
private DataFormat importDataFormat;
|
private DataFormat importDataFormat;
|
||||||
|
private String exportPassword;
|
||||||
|
|
||||||
|
private ActivityResultLauncher<Intent> fileCreateLauncher;
|
||||||
|
private ActivityResultLauncher<String> fileOpenLauncher;
|
||||||
|
private ActivityResultLauncher<Intent> filePickerLauncher;
|
||||||
|
|
||||||
|
final private TaskHandler mTasks = new TaskHandler();
|
||||||
|
|
||||||
|
private NotificationManager mNotifyManager;
|
||||||
|
private NotificationCompat.Builder mBuilder;
|
||||||
|
|
||||||
|
private static final int NOTIFICATION_IMPORT = 1;
|
||||||
|
private static final int NOTIFICATION_EXPORT = 2;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState)
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
{
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
setTitle(R.string.importExport);
|
||||||
setContentView(R.layout.import_export_activity);
|
setContentView(R.layout.import_export_activity);
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
if(actionBar != null)
|
if (actionBar != null) {
|
||||||
{
|
|
||||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,61 +81,115 @@ public class ImportExportActivity extends AppCompatActivity
|
|||||||
|
|
||||||
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
|
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
|
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
|
||||||
ContextCompat.checkSelfPermission(ImportExportActivity.this,
|
ContextCompat.checkSelfPermission(ImportExportActivity.this,
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
|
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||||
{
|
|
||||||
ActivityCompat.requestPermissions(ImportExportActivity.this,
|
ActivityCompat.requestPermissions(ImportExportActivity.this,
|
||||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
|
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||||
PERMISSIONS_EXTERNAL_STORAGE);
|
PERMISSIONS_EXTERNAL_STORAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// would use ActivityResultContracts.CreateDocument() but mime type cannot be set
|
||||||
|
fileCreateLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||||
|
Intent intent = result.getData();
|
||||||
|
if (intent == null) {
|
||||||
|
Log.e(TAG, "Activity returned NULL data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Uri uri = intent.getData();
|
||||||
|
if (uri == null) {
|
||||||
|
Log.e(TAG, "Activity returned NULL uri");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
OutputStream writer = getContentResolver().openOutputStream(uri);
|
||||||
|
Log.e(TAG, "Starting file export with: " + result.toString());
|
||||||
|
startExport(writer, uri, exportPassword != null ? exportPassword.toCharArray() : null, true);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to export file: " + result.toString(), e);
|
||||||
|
onExportComplete(ImportExportResult.GenericFailure, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
fileOpenLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), result -> {
|
||||||
|
if (result == null) {
|
||||||
|
Log.e(TAG, "Activity returned NULL data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openFileForImport(result, null);
|
||||||
|
});
|
||||||
|
filePickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||||
|
Intent intent = result.getData();
|
||||||
|
if (intent == null) {
|
||||||
|
Log.e(TAG, "Activity returned NULL data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Uri uri = intent.getData();
|
||||||
|
if (uri == null) {
|
||||||
|
Log.e(TAG, "Activity returned NULL uri");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openFileForImport(intent.getData(), null);
|
||||||
|
});
|
||||||
|
|
||||||
// Check that there is a file manager available
|
// Check that there is a file manager available
|
||||||
final Intent intentCreateDocumentAction = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
final Intent intentCreateDocumentAction = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||||
intentCreateDocumentAction.addCategory(Intent.CATEGORY_OPENABLE);
|
intentCreateDocumentAction.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
intentCreateDocumentAction.setType("application/zip");
|
intentCreateDocumentAction.setType("application/zip");
|
||||||
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "Catima.zip");
|
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "catima.zip");
|
||||||
|
|
||||||
Button exportButton = findViewById(R.id.exportButton);
|
Button exportButton = findViewById(R.id.exportButton);
|
||||||
exportButton.setOnClickListener(new View.OnClickListener()
|
exportButton.setOnClickListener(v -> {
|
||||||
{
|
AlertDialog.Builder builder = new AlertDialog.Builder(ImportExportActivity.this);
|
||||||
@Override
|
builder.setTitle(R.string.exportPassword);
|
||||||
public void onClick(View v)
|
|
||||||
{
|
FrameLayout container = new FrameLayout(ImportExportActivity.this);
|
||||||
chooseFileWithIntent(intentCreateDocumentAction, CHOOSE_EXPORT_LOCATION);
|
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
}
|
params.leftMargin = 50;
|
||||||
|
params.rightMargin = 50;
|
||||||
|
|
||||||
|
final EditText input = new EditText(ImportExportActivity.this);
|
||||||
|
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
|
input.setLayoutParams(params);
|
||||||
|
input.setHint(R.string.exportPasswordHint);
|
||||||
|
|
||||||
|
container.addView(input);
|
||||||
|
builder.setView(container);
|
||||||
|
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
||||||
|
exportPassword = input.getText().toString();
|
||||||
|
try {
|
||||||
|
fileCreateLauncher.launch(intentCreateDocumentAction);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
|
||||||
|
Log.e(TAG, "No activity found to handle intent", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
|
||||||
|
builder.show();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check that there is a file manager available
|
// Check that there is a file manager available
|
||||||
final Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT);
|
|
||||||
intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE);
|
|
||||||
intentGetContentAction.setType("*/*");
|
|
||||||
|
|
||||||
Button importFilesystem = findViewById(R.id.importOptionFilesystemButton);
|
Button importFilesystem = findViewById(R.id.importOptionFilesystemButton);
|
||||||
importFilesystem.setOnClickListener(new View.OnClickListener()
|
importFilesystem.setOnClickListener(v -> chooseImportType(false));
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onClick(View v)
|
|
||||||
{
|
|
||||||
chooseImportType(intentGetContentAction);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check that there is an app that data can be imported from
|
// Check that there is an app that data can be imported from
|
||||||
final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
|
|
||||||
|
|
||||||
Button importApplication = findViewById(R.id.importOptionApplicationButton);
|
Button importApplication = findViewById(R.id.importOptionApplicationButton);
|
||||||
importApplication.setOnClickListener(new View.OnClickListener()
|
importApplication.setOnClickListener(v -> chooseImportType(true));
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onClick(View v)
|
|
||||||
{
|
|
||||||
chooseImportType(intentPickAction);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void chooseImportType(Intent baseIntent) {
|
private void openFileForImport(Uri uri, char[] password) {
|
||||||
|
try {
|
||||||
|
InputStream reader = getContentResolver().openInputStream(uri);
|
||||||
|
Log.e(TAG, "Starting file import with: " + uri.toString());
|
||||||
|
startImport(reader, uri, importDataFormat, password, true);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to import file: " + uri.toString(), e);
|
||||||
|
onImportComplete(ImportExportResult.GenericFailure, uri, importDataFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chooseImportType(boolean choosePicker) {
|
||||||
List<CharSequence> betaImportOptions = new ArrayList<>();
|
List<CharSequence> betaImportOptions = new ArrayList<>();
|
||||||
betaImportOptions.add("Fidme");
|
betaImportOptions.add("Fidme");
|
||||||
betaImportOptions.add("Stocard");
|
betaImportOptions.add("Stocard");
|
||||||
@@ -176,67 +245,119 @@ public class ImportExportActivity extends AppCompatActivity
|
|||||||
.setTitle(importAlertTitle)
|
.setTitle(importAlertTitle)
|
||||||
.setMessage(importAlertMessage)
|
.setMessage(importAlertMessage)
|
||||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
chooseFileWithIntent(baseIntent, IMPORT);
|
try {
|
||||||
|
if (choosePicker) {
|
||||||
|
final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
|
||||||
|
filePickerLauncher.launch(intentPickAction);
|
||||||
|
} else {
|
||||||
|
fileOpenLauncher.launch("*/*");
|
||||||
}
|
}
|
||||||
})
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
|
||||||
|
Log.e(TAG, "No activity found to handle intent", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.show();
|
.show();
|
||||||
});
|
});
|
||||||
builder.show();
|
builder.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password)
|
private void startProgressNotification(boolean importing) {
|
||||||
{
|
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
|
mBuilder = new NotificationCompat.Builder(this, NotificationType.getImportExportChannel(this));
|
||||||
{
|
mBuilder.setContentTitle(getString(importing ? R.string.importing : R.string.exporting))
|
||||||
@Override
|
.setContentText(null)
|
||||||
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat)
|
.setSmallIcon(R.drawable.ic_import_export_white_24dp)
|
||||||
{
|
.setColor(getThemeColor())
|
||||||
onImportComplete(result, targetUri, dataFormat);
|
.setProgress(0, 0, true);
|
||||||
}
|
mNotifyManager.notify(importing ? NOTIFICATION_IMPORT : NOTIFICATION_EXPORT, mBuilder.build());
|
||||||
};
|
|
||||||
|
|
||||||
importExporter = new ImportExportTask(ImportExportActivity.this,
|
|
||||||
dataFormat, target, password, listener);
|
|
||||||
importExporter.execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startExport(final OutputStream target, final Uri targetUri)
|
private void endProgressNotification(boolean importing, ImportExportResult result, PendingIntent sendIntent) {
|
||||||
{
|
String notificationTitle;
|
||||||
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
|
String notificationMessage;
|
||||||
{
|
|
||||||
@Override
|
if (result.equals(ImportExportResult.Success)) {
|
||||||
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat)
|
notificationTitle = getString(importing ? R.string.importSuccessfulTitle : R.string.exportSuccessfulTitle);
|
||||||
{
|
notificationMessage = getString(importing ? R.string.importSuccessful : R.string.exportSuccessful);
|
||||||
onExportComplete(result, targetUri);
|
} else {
|
||||||
|
int reason = R.string.unknown_failure;
|
||||||
|
if (result.equals(ImportExportResult.BadPassword)) {
|
||||||
|
reason = R.string.incorrect_password;
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationTitle = getString(importing ? R.string.importFailedTitle : R.string.exportFailedTitle);
|
||||||
|
notificationMessage = String.format(getString(importing ? R.string.importFailed : R.string.exportFailed), getString(reason));
|
||||||
|
}
|
||||||
|
|
||||||
|
mBuilder.setContentTitle(notificationTitle)
|
||||||
|
.setContentText(notificationMessage)
|
||||||
|
.setProgress(0,0, false);
|
||||||
|
|
||||||
|
if (sendIntent != null) {
|
||||||
|
mBuilder.addAction(R.drawable.ic_share, getString(R.string.sendLabel), sendIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
mNotifyManager.notify(importing ? NOTIFICATION_IMPORT : NOTIFICATION_EXPORT, mBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password, final boolean closeWhenDone) {
|
||||||
|
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
|
||||||
|
ImportExportTask.TaskCompleteListener listener = (result, dataFormat1) -> {
|
||||||
|
onImportComplete(result, targetUri, dataFormat1);
|
||||||
|
if (closeWhenDone) {
|
||||||
|
try {
|
||||||
|
target.close();
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
ioException.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
startProgressNotification(true);
|
||||||
importExporter = new ImportExportTask(ImportExportActivity.this,
|
importExporter = new ImportExportTask(ImportExportActivity.this,
|
||||||
DataFormat.Catima, target, listener);
|
dataFormat, target, password, listener);
|
||||||
importExporter.execute();
|
mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startExport(final OutputStream target, final Uri targetUri, char[] password, final boolean closeWhenDone) {
|
||||||
|
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
|
||||||
|
ImportExportTask.TaskCompleteListener listener = (result, dataFormat) -> {
|
||||||
|
onExportComplete(result, targetUri);
|
||||||
|
if (closeWhenDone) {
|
||||||
|
try {
|
||||||
|
target.close();
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
ioException.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
startProgressNotification(false);
|
||||||
|
importExporter = new ImportExportTask(ImportExportActivity.this,
|
||||||
|
DataFormat.Catima, target, password, listener);
|
||||||
|
mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)
|
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
|
||||||
{
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
if(requestCode == PERMISSIONS_EXTERNAL_STORAGE)
|
|
||||||
{
|
if (requestCode == PERMISSIONS_EXTERNAL_STORAGE) {
|
||||||
// If request is cancelled, the result arrays are empty.
|
// If request is cancelled, the result arrays are empty.
|
||||||
boolean success = grantResults.length > 0;
|
boolean success = grantResults.length > 0;
|
||||||
|
|
||||||
for(int grant : grantResults)
|
for (int grant : grantResults) {
|
||||||
{
|
if (grant != PackageManager.PERMISSION_GRANTED) {
|
||||||
if(grant != PackageManager.PERMISSION_GRANTED)
|
|
||||||
{
|
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!success)
|
if (!success) {
|
||||||
{
|
|
||||||
// External storage permission rejected, inform user that
|
// External storage permission rejected, inform user that
|
||||||
// import/export is prevented
|
// import/export is prevented
|
||||||
Toast.makeText(getApplicationContext(), R.string.noExternalStoragePermissionError,
|
Toast.makeText(getApplicationContext(), R.string.noExternalStoragePermissionError,
|
||||||
@@ -247,22 +368,17 @@ public class ImportExportActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy()
|
protected void onDestroy() {
|
||||||
{
|
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
|
||||||
if(importExporter != null && importExporter.getStatus() != AsyncTask.Status.RUNNING)
|
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
|
||||||
{
|
|
||||||
importExporter.cancel(true);
|
|
||||||
}
|
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item)
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
{
|
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
|
|
||||||
if(id == android.R.id.home)
|
if (id == android.R.id.home) {
|
||||||
{
|
|
||||||
finish();
|
finish();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -279,7 +395,7 @@ public class ImportExportActivity extends AppCompatActivity
|
|||||||
builder.setView(input);
|
builder.setView(input);
|
||||||
|
|
||||||
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
||||||
activityResultParser(IMPORT, RESULT_OK, uri, input.getText().toString().toCharArray());
|
openFileForImport(uri, input.getText().toString().toCharArray());
|
||||||
});
|
});
|
||||||
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
|
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
|
||||||
|
|
||||||
@@ -287,170 +403,27 @@ public class ImportExportActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onImportComplete(ImportExportResult result, Uri path, DataFormat dataFormat) {
|
private void onImportComplete(ImportExportResult result, Uri path, DataFormat dataFormat) {
|
||||||
|
endProgressNotification(true, result, null);
|
||||||
|
|
||||||
if (result == ImportExportResult.BadPassword) {
|
if (result == ImportExportResult.BadPassword) {
|
||||||
retryWithPassword(dataFormat, path);
|
retryWithPassword(dataFormat, path);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
||||||
|
|
||||||
int messageId;
|
|
||||||
|
|
||||||
if (result == ImportExportResult.Success)
|
|
||||||
{
|
|
||||||
builder.setTitle(R.string.importSuccessfulTitle);
|
|
||||||
messageId = R.string.importSuccessful;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.setTitle(R.string.importFailedTitle);
|
|
||||||
messageId = R.string.importFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String message = getResources().getString(messageId);
|
|
||||||
|
|
||||||
builder.setMessage(message);
|
|
||||||
builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which)
|
|
||||||
{
|
|
||||||
dialog.dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.create().show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onExportComplete(ImportExportResult result, final Uri path)
|
|
||||||
{
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
||||||
|
|
||||||
int messageId;
|
|
||||||
|
|
||||||
if(result == ImportExportResult.Success)
|
|
||||||
{
|
|
||||||
builder.setTitle(R.string.exportSuccessfulTitle);
|
|
||||||
messageId = R.string.exportSuccessful;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.setTitle(R.string.exportFailedTitle);
|
|
||||||
messageId = R.string.exportFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String message = getResources().getString(messageId);
|
|
||||||
|
|
||||||
builder.setMessage(message);
|
|
||||||
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
|
|
||||||
|
|
||||||
if(result == ImportExportResult.Success)
|
|
||||||
{
|
|
||||||
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel);
|
|
||||||
|
|
||||||
builder.setPositiveButton(sendLabel, (dialog, which) -> {
|
|
||||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
|
||||||
sendIntent.putExtra(Intent.EXTRA_STREAM, path);
|
|
||||||
sendIntent.setType("text/csv");
|
|
||||||
|
|
||||||
// set flag to give temporary permission to external app to use the FileProvider
|
|
||||||
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
|
|
||||||
ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
|
|
||||||
sendLabel));
|
|
||||||
|
|
||||||
dialog.dismiss();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.create().show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void chooseFileWithIntent(Intent intent, int requestCode)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
startActivityForResult(intent, requestCode);
|
|
||||||
}
|
|
||||||
catch (ActivityNotFoundException e)
|
|
||||||
{
|
|
||||||
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
|
|
||||||
Log.e(TAG, "No activity found to handle intent", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void activityResultParser(int requestCode, int resultCode, Uri uri, char[] password) {
|
private void onExportComplete(ImportExportResult result, final Uri path) {
|
||||||
if (resultCode != RESULT_OK)
|
PendingIntent pendingIntent = null;
|
||||||
{
|
|
||||||
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
|
if (result == ImportExportResult.Success) {
|
||||||
return;
|
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||||
|
sendIntent.putExtra(Intent.EXTRA_STREAM, path);
|
||||||
|
sendIntent.setType("text/csv");
|
||||||
|
|
||||||
|
// set flag to give temporary permission to external app to use the FileProvider
|
||||||
|
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
|
||||||
|
pendingIntent = PendingIntent.getActivity(this, NOTIFICATION_EXPORT, sendIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(uri == null)
|
endProgressNotification(false, result, pendingIntent);
|
||||||
{
|
|
||||||
Log.e(TAG, "Activity returned a NULL URI");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (requestCode == CHOOSE_EXPORT_LOCATION)
|
|
||||||
{
|
|
||||||
OutputStream writer;
|
|
||||||
if (uri.getScheme() != null)
|
|
||||||
{
|
|
||||||
writer = getContentResolver().openOutputStream(uri);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
writer = new FileOutputStream(new File(uri.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.e(TAG, "Starting file export with: " + uri.toString());
|
|
||||||
startExport(writer, uri);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
InputStream reader;
|
|
||||||
if(uri.getScheme() != null)
|
|
||||||
{
|
|
||||||
reader = getContentResolver().openInputStream(uri);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
reader = new FileInputStream(new File(uri.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.e(TAG, "Starting file import with: " + uri.toString());
|
|
||||||
|
|
||||||
startImport(reader, uri, importDataFormat, password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(FileNotFoundException e)
|
|
||||||
{
|
|
||||||
Log.e(TAG, "Failed to import/export file: " + uri.toString(), e);
|
|
||||||
if (requestCode == CHOOSE_EXPORT_LOCATION)
|
|
||||||
{
|
|
||||||
onExportComplete(ImportExportResult.GenericFailure, uri);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
onImportComplete(ImportExportResult.GenericFailure, uri, importDataFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
|
||||||
{
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
|
|
||||||
if(data == null)
|
|
||||||
{
|
|
||||||
Log.e(TAG, "Activity returned NULL data");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
activityResultParser(requestCode, resultCode, data.getData(), null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,48 +1,47 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
import android.app.NotificationManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.os.AsyncTask;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import protect.card_locker.async.CompatCallable;
|
||||||
import protect.card_locker.importexport.DataFormat;
|
import protect.card_locker.importexport.DataFormat;
|
||||||
import protect.card_locker.importexport.ImportExportResult;
|
import protect.card_locker.importexport.ImportExportResult;
|
||||||
import protect.card_locker.importexport.MultiFormatExporter;
|
import protect.card_locker.importexport.MultiFormatExporter;
|
||||||
import protect.card_locker.importexport.MultiFormatImporter;
|
import protect.card_locker.importexport.MultiFormatImporter;
|
||||||
|
|
||||||
class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
|
public class ImportExportTask implements CompatCallable<ImportExportResult> {
|
||||||
{
|
|
||||||
private static final String TAG = "Catima";
|
private static final String TAG = "Catima";
|
||||||
|
|
||||||
private Activity activity;
|
private final Activity activity;
|
||||||
private boolean doImport;
|
private final boolean doImport;
|
||||||
private DataFormat format;
|
private final DataFormat format;
|
||||||
private OutputStream outputStream;
|
private OutputStream outputStream;
|
||||||
private InputStream inputStream;
|
private InputStream inputStream;
|
||||||
private char[] password;
|
private final char[] password;
|
||||||
private TaskCompleteListener listener;
|
private final TaskCompleteListener listener;
|
||||||
|
|
||||||
private ProgressDialog progress;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor which will setup a task for exporting to the given file
|
* Constructor which will setup a task for exporting to the given file
|
||||||
*/
|
*/
|
||||||
ImportExportTask(Activity activity, DataFormat format, OutputStream output,
|
ImportExportTask(Activity activity, DataFormat format, OutputStream output, char[] password,
|
||||||
TaskCompleteListener listener)
|
TaskCompleteListener listener) {
|
||||||
{
|
|
||||||
super();
|
super();
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
this.doImport = false;
|
this.doImport = false;
|
||||||
this.format = format;
|
this.format = format;
|
||||||
this.outputStream = output;
|
this.outputStream = output;
|
||||||
|
this.password = password;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +49,7 @@ class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
|
|||||||
* Constructor which will setup a task for importing from the given InputStream.
|
* Constructor which will setup a task for importing from the given InputStream.
|
||||||
*/
|
*/
|
||||||
ImportExportTask(Activity activity, DataFormat format, InputStream input, char[] password,
|
ImportExportTask(Activity activity, DataFormat format, InputStream input, char[] password,
|
||||||
TaskCompleteListener listener)
|
TaskCompleteListener listener) {
|
||||||
{
|
|
||||||
super();
|
super();
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
this.doImport = true;
|
this.doImport = true;
|
||||||
@@ -61,27 +59,22 @@ class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
|
|||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImportExportResult performImport(Context context, InputStream stream, DBHelper db, char[] password)
|
private ImportExportResult performImport(Context context, InputStream stream, SQLiteDatabase database, char[] password) {
|
||||||
{
|
ImportExportResult result = MultiFormatImporter.importData(context, database, stream, format, password);
|
||||||
ImportExportResult importResult = MultiFormatImporter.importData(context, db, stream, format, password);
|
|
||||||
|
|
||||||
Log.i(TAG, "Import result: " + importResult.name());
|
Log.i(TAG, "Import result: " + result.name());
|
||||||
|
|
||||||
return importResult;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImportExportResult performExport(Context context, OutputStream stream, DBHelper db)
|
private ImportExportResult performExport(Context context, OutputStream stream, SQLiteDatabase database, char[] password) {
|
||||||
{
|
|
||||||
ImportExportResult result = ImportExportResult.GenericFailure;
|
ImportExportResult result = ImportExportResult.GenericFailure;
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
|
||||||
OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("UTF-8"));
|
result = MultiFormatExporter.exportData(context, database, stream, format, password);
|
||||||
result = MultiFormatExporter.exportData(context, db, stream, format);
|
|
||||||
writer.close();
|
writer.close();
|
||||||
}
|
} catch (IOException e) {
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
Log.e(TAG, "Unable to export file", e);
|
Log.e(TAG, "Unable to export file", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,55 +83,43 @@ class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onPreExecute()
|
protected ImportExportResult doInBackground(Void... nothing) {
|
||||||
{
|
final SQLiteDatabase database = new DBHelper(activity).getWritableDatabase();
|
||||||
progress = new ProgressDialog(activity);
|
|
||||||
progress.setTitle(doImport ? R.string.importing : R.string.exporting);
|
|
||||||
|
|
||||||
progress.setOnDismissListener(new DialogInterface.OnDismissListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onDismiss(DialogInterface dialog)
|
|
||||||
{
|
|
||||||
ImportExportTask.this.cancel(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
progress.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ImportExportResult doInBackground(Void... nothing)
|
|
||||||
{
|
|
||||||
final DBHelper db = new DBHelper(activity);
|
|
||||||
ImportExportResult result;
|
ImportExportResult result;
|
||||||
|
|
||||||
if(doImport)
|
if (doImport) {
|
||||||
{
|
result = performImport(activity.getApplicationContext(), inputStream, database, password);
|
||||||
result = performImport(activity.getApplicationContext(), inputStream, db, password);
|
} else {
|
||||||
}
|
result = performExport(activity.getApplicationContext(), outputStream, database, password);
|
||||||
else
|
|
||||||
{
|
|
||||||
result = performExport(activity.getApplicationContext(), outputStream, db);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
database.close();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onPostExecute(ImportExportResult result)
|
public void onPostExecute(Object castResult) {
|
||||||
{
|
ImportExportResult result = (ImportExportResult) castResult;
|
||||||
|
|
||||||
listener.onTaskComplete(result, format);
|
listener.onTaskComplete(result, format);
|
||||||
|
|
||||||
progress.dismiss();
|
|
||||||
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
|
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onCancelled()
|
@Override
|
||||||
{
|
public void onPreExecute() {
|
||||||
progress.dismiss();
|
|
||||||
Log.i(TAG, (doImport ? "Import" : "Export") + " Cancelled");
|
|
||||||
}
|
}
|
||||||
interface TaskCompleteListener
|
|
||||||
{
|
protected void stop() {
|
||||||
|
// Whelp
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImportExportResult call() {
|
||||||
|
return doInBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TaskCompleteListener {
|
||||||
void onTaskComplete(ImportExportResult result, DataFormat format);
|
void onTaskComplete(ImportExportResult result, DataFormat format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
|
||||||
|
|
||||||
import java.io.InvalidObjectException;
|
import java.io.InvalidObjectException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -35,7 +33,7 @@ public class ImportURIHelper {
|
|||||||
private final String shareMultipleText;
|
private final String shareMultipleText;
|
||||||
|
|
||||||
public ImportURIHelper(Context context) {
|
public ImportURIHelper(Context context) {
|
||||||
this.context = context;
|
this.context = context.getApplicationContext();
|
||||||
hosts[0] = context.getResources().getString(R.string.intent_import_card_from_url_host_catima_app);
|
hosts[0] = context.getResources().getString(R.string.intent_import_card_from_url_host_catima_app);
|
||||||
paths[0] = context.getResources().getString(R.string.intent_import_card_from_url_path_prefix_catima_app);
|
paths[0] = context.getResources().getString(R.string.intent_import_card_from_url_path_prefix_catima_app);
|
||||||
hosts[1] = context.getResources().getString(R.string.intent_import_card_from_url_host_thelastproject);
|
hosts[1] = context.getResources().getString(R.string.intent_import_card_from_url_host_thelastproject);
|
||||||
@@ -57,13 +55,13 @@ public class ImportURIHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public LoyaltyCard parse(Uri uri) throws InvalidObjectException {
|
public LoyaltyCard parse(Uri uri) throws InvalidObjectException {
|
||||||
if(!isImportUri(uri)) {
|
if (!isImportUri(uri)) {
|
||||||
throw new InvalidObjectException("Not an import URI");
|
throw new InvalidObjectException("Not an import URI");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// These values are allowed to be null
|
// These values are allowed to be null
|
||||||
BarcodeFormat barcodeType = null;
|
CatimaBarcode barcodeType = null;
|
||||||
Date expiry = null;
|
Date expiry = null;
|
||||||
BigDecimal balance = new BigDecimal("0");
|
BigDecimal balance = new BigDecimal("0");
|
||||||
Currency balanceType = null;
|
Currency balanceType = null;
|
||||||
@@ -83,7 +81,7 @@ public class ImportURIHelper {
|
|||||||
if (fragment != null) {
|
if (fragment != null) {
|
||||||
for (String fragmentPart : fragment.split("&")) {
|
for (String fragmentPart : fragment.split("&")) {
|
||||||
String[] fragmentData = fragmentPart.split("=", 2);
|
String[] fragmentData = fragmentPart.split("=", 2);
|
||||||
kv.put(fragmentData[0], URLDecoder.decode(fragmentData[1], StandardCharsets.UTF_8.toString()));
|
kv.put(fragmentData[0], URLDecoder.decode(fragmentData[1], StandardCharsets.UTF_8.name()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,37 +90,33 @@ public class ImportURIHelper {
|
|||||||
String note = kv.get(NOTE);
|
String note = kv.get(NOTE);
|
||||||
String cardId = kv.get(CARD_ID);
|
String cardId = kv.get(CARD_ID);
|
||||||
String barcodeId = kv.get(BARCODE_ID);
|
String barcodeId = kv.get(BARCODE_ID);
|
||||||
if (store == null || note == null || cardId == null) throw new InvalidObjectException("Not a valid import URI: " + uri.toString());
|
if (store == null || note == null || cardId == null)
|
||||||
|
throw new InvalidObjectException("Not a valid import URI: " + uri.toString());
|
||||||
|
|
||||||
String unparsedBarcodeType = kv.get(BARCODE_TYPE);
|
String unparsedBarcodeType = kv.get(BARCODE_TYPE);
|
||||||
if(unparsedBarcodeType != null && !unparsedBarcodeType.equals(""))
|
if (unparsedBarcodeType != null && !unparsedBarcodeType.equals("")) {
|
||||||
{
|
barcodeType = CatimaBarcode.fromName(unparsedBarcodeType);
|
||||||
barcodeType = BarcodeFormat.valueOf(unparsedBarcodeType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String unparsedBalance = kv.get(BALANCE);
|
String unparsedBalance = kv.get(BALANCE);
|
||||||
if(unparsedBalance != null && !unparsedBalance.equals(""))
|
if (unparsedBalance != null && !unparsedBalance.equals("")) {
|
||||||
{
|
|
||||||
balance = new BigDecimal(unparsedBalance);
|
balance = new BigDecimal(unparsedBalance);
|
||||||
}
|
}
|
||||||
String unparsedBalanceType = kv.get(BALANCE_TYPE);
|
String unparsedBalanceType = kv.get(BALANCE_TYPE);
|
||||||
if (unparsedBalanceType != null && !unparsedBalanceType.equals(""))
|
if (unparsedBalanceType != null && !unparsedBalanceType.equals("")) {
|
||||||
{
|
|
||||||
balanceType = Currency.getInstance(unparsedBalanceType);
|
balanceType = Currency.getInstance(unparsedBalanceType);
|
||||||
}
|
}
|
||||||
String unparsedExpiry = kv.get(EXPIRY);
|
String unparsedExpiry = kv.get(EXPIRY);
|
||||||
if(unparsedExpiry != null && !unparsedExpiry.equals(""))
|
if (unparsedExpiry != null && !unparsedExpiry.equals("")) {
|
||||||
{
|
|
||||||
expiry = new Date(Long.parseLong(unparsedExpiry));
|
expiry = new Date(Long.parseLong(unparsedExpiry));
|
||||||
}
|
}
|
||||||
|
|
||||||
String unparsedHeaderColor = kv.get(HEADER_COLOR);
|
String unparsedHeaderColor = kv.get(HEADER_COLOR);
|
||||||
if(unparsedHeaderColor != null)
|
if (unparsedHeaderColor != null) {
|
||||||
{
|
|
||||||
headerColor = Integer.parseInt(unparsedHeaderColor);
|
headerColor = Integer.parseInt(unparsedHeaderColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new LoyaltyCard(-1, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0);
|
return new LoyaltyCard(-1, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0, Utils.getUnixTime(), 100);
|
||||||
} catch (NullPointerException | NumberFormatException | UnsupportedEncodingException ex) {
|
} catch (NullPointerException | NumberFormatException | UnsupportedEncodingException ex) {
|
||||||
throw new InvalidObjectException("Not a valid import URI");
|
throw new InvalidObjectException("Not a valid import URI");
|
||||||
}
|
}
|
||||||
@@ -134,7 +128,7 @@ public class ImportURIHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Double-encode the value to make sure it can't accidentally contain symbols that'll break the parser
|
// Double-encode the value to make sure it can't accidentally contain symbols that'll break the parser
|
||||||
fragment.append(key).append("=").append(URLEncoder.encode(value, StandardCharsets.UTF_8.toString()));
|
fragment.append(key).append("=").append(URLEncoder.encode(value, StandardCharsets.UTF_8.name()));
|
||||||
|
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
@@ -159,14 +153,14 @@ public class ImportURIHelper {
|
|||||||
fragment = appendFragment(fragment, EXPIRY, String.valueOf(loyaltyCard.expiry.getTime()));
|
fragment = appendFragment(fragment, EXPIRY, String.valueOf(loyaltyCard.expiry.getTime()));
|
||||||
}
|
}
|
||||||
fragment = appendFragment(fragment, CARD_ID, loyaltyCard.cardId);
|
fragment = appendFragment(fragment, CARD_ID, loyaltyCard.cardId);
|
||||||
if(loyaltyCard.barcodeId != null) {
|
if (loyaltyCard.barcodeId != null) {
|
||||||
fragment = appendFragment(fragment, BARCODE_ID, loyaltyCard.barcodeId);
|
fragment = appendFragment(fragment, BARCODE_ID, loyaltyCard.barcodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(loyaltyCard.barcodeType != null) {
|
if (loyaltyCard.barcodeType != null) {
|
||||||
fragment = appendFragment(fragment, BARCODE_TYPE, loyaltyCard.barcodeType.toString());
|
fragment = appendFragment(fragment, BARCODE_TYPE, loyaltyCard.barcodeType.name());
|
||||||
}
|
}
|
||||||
if(loyaltyCard.headerColor != null) {
|
if (loyaltyCard.headerColor != null) {
|
||||||
fragment = appendFragment(fragment, HEADER_COLOR, loyaltyCard.headerColor.toString());
|
fragment = appendFragment(fragment, HEADER_COLOR, loyaltyCard.headerColor.toString());
|
||||||
}
|
}
|
||||||
// Star status will not be exported
|
// Star status will not be exported
|
||||||
@@ -203,6 +197,7 @@ public class ImportURIHelper {
|
|||||||
sendIntent.setType("text/plain");
|
sendIntent.setType("text/plain");
|
||||||
|
|
||||||
Intent shareIntent = Intent.createChooser(sendIntent, null);
|
Intent shareIntent = Intent.createChooser(sendIntent, null);
|
||||||
|
shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
context.startActivity(shareIntent);
|
context.startActivity(shareIntent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ import android.text.TextPaint;
|
|||||||
* alphabet or digit, if there is no letter or digit available, a default image
|
* alphabet or digit, if there is no letter or digit available, a default image
|
||||||
* is shown instead.
|
* is shown instead.
|
||||||
*/
|
*/
|
||||||
class LetterBitmap
|
class LetterBitmap {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of available tile colors
|
* The number of available tile colors
|
||||||
@@ -37,39 +36,32 @@ class LetterBitmap
|
|||||||
/**
|
/**
|
||||||
* Constructor for <code>LetterTileProvider</code>
|
* Constructor for <code>LetterTileProvider</code>
|
||||||
*
|
*
|
||||||
* @param context The {@link Context} to use
|
* @param context The {@link Context} to use
|
||||||
* @param displayName The name used to create the letter for the tile
|
* @param displayName The name used to create the letter for the tile
|
||||||
* @param key The key used to generate the background color for the tile
|
* @param key The key used to generate the background color for the tile
|
||||||
* @param tileLetterFontSize The font size used to display the letter
|
* @param tileLetterFontSize The font size used to display the letter
|
||||||
* @param width The desired width of the tile
|
* @param width The desired width of the tile
|
||||||
* @param height The desired height of the tile
|
* @param height The desired height of the tile
|
||||||
* @param backgroundColor (optional) color to use for background.
|
* @param backgroundColor (optional) color to use for background.
|
||||||
* @param textColor (optional) color to use for text.
|
* @param textColor (optional) color to use for text.
|
||||||
*/
|
*/
|
||||||
public LetterBitmap(Context context, String displayName, String key, int tileLetterFontSize,
|
public LetterBitmap(Context context, String displayName, String key, int tileLetterFontSize,
|
||||||
int width, int height, Integer backgroundColor, Integer textColor)
|
int width, int height, Integer backgroundColor, Integer textColor) {
|
||||||
{
|
|
||||||
TextPaint paint = new TextPaint();
|
TextPaint paint = new TextPaint();
|
||||||
paint.setTypeface(Typeface.create("sans-serif-light", Typeface.BOLD));
|
paint.setTypeface(Typeface.create("sans-serif-light", Typeface.BOLD));
|
||||||
|
|
||||||
if(textColor != null)
|
if (textColor != null) {
|
||||||
{
|
|
||||||
paint.setColor(textColor);
|
paint.setColor(textColor);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
paint.setColor(Color.WHITE);
|
paint.setColor(Color.WHITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
paint.setTextAlign(Paint.Align.CENTER);
|
paint.setTextAlign(Paint.Align.CENTER);
|
||||||
paint.setAntiAlias(true);
|
paint.setAntiAlias(true);
|
||||||
|
|
||||||
if(backgroundColor == null)
|
if (backgroundColor == null) {
|
||||||
{
|
|
||||||
mColor = getDefaultColor(context, key);
|
mColor = getDefaultColor(context, key);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
mColor = backgroundColor;
|
mColor = backgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +72,7 @@ class LetterBitmap
|
|||||||
c.setBitmap(mBitmap);
|
c.setBitmap(mBitmap);
|
||||||
c.drawColor(mColor);
|
c.drawColor(mColor);
|
||||||
|
|
||||||
char [] firstCharArray = new char[1];
|
char[] firstCharArray = new char[1];
|
||||||
firstCharArray[0] = firstChar.toUpperCase().charAt(0);
|
firstCharArray[0] = firstChar.toUpperCase().charAt(0);
|
||||||
paint.setTextSize(tileLetterFontSize);
|
paint.setTextSize(tileLetterFontSize);
|
||||||
|
|
||||||
@@ -97,16 +89,14 @@ class LetterBitmap
|
|||||||
* alphabet or digit, if there is no letter or digit available, a
|
* alphabet or digit, if there is no letter or digit available, a
|
||||||
* default image is shown instead
|
* default image is shown instead
|
||||||
*/
|
*/
|
||||||
public Bitmap getLetterTile()
|
public Bitmap getLetterTile() {
|
||||||
{
|
|
||||||
return mBitmap;
|
return mBitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return background color used for letter title.
|
* @return background color used for letter title.
|
||||||
*/
|
*/
|
||||||
public int getBackgroundColor()
|
public int getBackgroundColor() {
|
||||||
{
|
|
||||||
return mColor;
|
return mColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,8 +105,7 @@ class LetterBitmap
|
|||||||
* @return A new or previously chosen color for <code>key</code> used as the
|
* @return A new or previously chosen color for <code>key</code> used as the
|
||||||
* tile background color
|
* tile background color
|
||||||
*/
|
*/
|
||||||
private static int pickColor(String key, TypedArray colors)
|
private static int pickColor(String key, TypedArray colors) {
|
||||||
{
|
|
||||||
// String.hashCode() is not supposed to change across java versions, so
|
// String.hashCode() is not supposed to change across java versions, so
|
||||||
// this should guarantee the same key always maps to the same color
|
// this should guarantee the same key always maps to the same color
|
||||||
final int color = Math.abs(key.hashCode()) % NUM_OF_TILE_COLORS;
|
final int color = Math.abs(key.hashCode()) % NUM_OF_TILE_COLORS;
|
||||||
@@ -127,8 +116,7 @@ class LetterBitmap
|
|||||||
* Determine the color which the letter tile will use if no default
|
* Determine the color which the letter tile will use if no default
|
||||||
* color is provided.
|
* color is provided.
|
||||||
*/
|
*/
|
||||||
public static int getDefaultColor(Context context, String key)
|
public static int getDefaultColor(Context context, String key) {
|
||||||
{
|
|
||||||
final Resources res = context.getResources();
|
final Resources res = context.getResources();
|
||||||
|
|
||||||
TypedArray colors = res.obtainTypedArray(R.array.letter_tile_colors);
|
TypedArray colors = res.obtainTypedArray(R.array.letter_tile_colors);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.os.Parcel;
|
||||||
import com.google.zxing.BarcodeFormat;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Currency;
|
import java.util.Currency;
|
||||||
@@ -10,8 +10,7 @@ import java.util.Date;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
public class LoyaltyCard
|
public class LoyaltyCard implements Parcelable {
|
||||||
{
|
|
||||||
public final int id;
|
public final int id;
|
||||||
public final String store;
|
public final String store;
|
||||||
public final String note;
|
public final String note;
|
||||||
@@ -23,18 +22,20 @@ public class LoyaltyCard
|
|||||||
@Nullable
|
@Nullable
|
||||||
public final String barcodeId;
|
public final String barcodeId;
|
||||||
|
|
||||||
public final BarcodeFormat barcodeType;
|
@Nullable
|
||||||
|
public final CatimaBarcode barcodeType;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public final Integer headerColor;
|
public final Integer headerColor;
|
||||||
|
|
||||||
public final int starStatus;
|
public final int starStatus;
|
||||||
|
public final long lastUsed;
|
||||||
|
public int zoomLevel;
|
||||||
|
|
||||||
public LoyaltyCard(final int id, final String store, final String note, final Date expiry,
|
public LoyaltyCard(final int id, final String store, final String note, final Date expiry,
|
||||||
final BigDecimal balance, final Currency balanceType, final String cardId,
|
final BigDecimal balance, final Currency balanceType, final String cardId,
|
||||||
final String barcodeId, final BarcodeFormat barcodeType, final Integer headerColor,
|
@Nullable final String barcodeId, @Nullable final CatimaBarcode barcodeType,
|
||||||
final int starStatus)
|
@Nullable final Integer headerColor, final int starStatus, final long lastUsed, final int zoomLevel) {
|
||||||
{
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.note = note;
|
this.note = note;
|
||||||
@@ -46,10 +47,47 @@ public class LoyaltyCard
|
|||||||
this.barcodeType = barcodeType;
|
this.barcodeType = barcodeType;
|
||||||
this.headerColor = headerColor;
|
this.headerColor = headerColor;
|
||||||
this.starStatus = starStatus;
|
this.starStatus = starStatus;
|
||||||
|
this.lastUsed = lastUsed;
|
||||||
|
this.zoomLevel = zoomLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoyaltyCard toLoyaltyCard(Cursor cursor)
|
protected LoyaltyCard(Parcel in) {
|
||||||
{
|
id = in.readInt();
|
||||||
|
store = in.readString();
|
||||||
|
note = in.readString();
|
||||||
|
long tmpExpiry = in.readLong();
|
||||||
|
expiry = tmpExpiry != -1 ? new Date(tmpExpiry) : null;
|
||||||
|
balance = (BigDecimal) in.readValue(BigDecimal.class.getClassLoader());
|
||||||
|
balanceType = (Currency) in.readValue(Currency.class.getClassLoader());
|
||||||
|
cardId = in.readString();
|
||||||
|
barcodeId = in.readString();
|
||||||
|
String tmpBarcodeType = in.readString();
|
||||||
|
barcodeType = !tmpBarcodeType.isEmpty() ? CatimaBarcode.fromName(tmpBarcodeType) : null;
|
||||||
|
int tmpHeaderColor = in.readInt();
|
||||||
|
headerColor = tmpHeaderColor != -1 ? tmpHeaderColor : null;
|
||||||
|
starStatus = in.readInt();
|
||||||
|
lastUsed = in.readLong();
|
||||||
|
zoomLevel = in.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel parcel, int i) {
|
||||||
|
parcel.writeInt(id);
|
||||||
|
parcel.writeString(store);
|
||||||
|
parcel.writeString(note);
|
||||||
|
parcel.writeLong(expiry != null ? expiry.getTime() : -1);
|
||||||
|
parcel.writeValue(balance);
|
||||||
|
parcel.writeValue(balanceType);
|
||||||
|
parcel.writeString(cardId);
|
||||||
|
parcel.writeString(barcodeId);
|
||||||
|
parcel.writeString(barcodeType != null ? barcodeType.name() : "");
|
||||||
|
parcel.writeInt(headerColor != null ? headerColor : -1);
|
||||||
|
parcel.writeInt(starStatus);
|
||||||
|
parcel.writeLong(lastUsed);
|
||||||
|
parcel.writeInt(zoomLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LoyaltyCard toLoyaltyCard(Cursor cursor) {
|
||||||
int id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
|
int id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
|
||||||
String store = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE));
|
String store = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE));
|
||||||
String note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE));
|
String note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE));
|
||||||
@@ -58,36 +96,51 @@ public class LoyaltyCard
|
|||||||
String cardId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID));
|
String cardId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID));
|
||||||
String barcodeId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_ID));
|
String barcodeId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_ID));
|
||||||
int starred = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS));
|
int starred = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS));
|
||||||
|
long lastUsed = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.LAST_USED));
|
||||||
|
int zoomLevel = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ZOOM_LEVEL));
|
||||||
|
|
||||||
int barcodeTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
|
int barcodeTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
|
||||||
int balanceTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE);
|
int balanceTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE);
|
||||||
int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR);
|
int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR);
|
||||||
|
|
||||||
BarcodeFormat barcodeType = null;
|
CatimaBarcode barcodeType = null;
|
||||||
Currency balanceType = null;
|
Currency balanceType = null;
|
||||||
Date expiry = null;
|
Date expiry = null;
|
||||||
Integer headerColor = null;
|
Integer headerColor = null;
|
||||||
|
|
||||||
if (cursor.isNull(barcodeTypeColumn) == false)
|
if (cursor.isNull(barcodeTypeColumn) == false) {
|
||||||
{
|
barcodeType = CatimaBarcode.fromName(cursor.getString(barcodeTypeColumn));
|
||||||
barcodeType = BarcodeFormat.valueOf(cursor.getString(barcodeTypeColumn));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursor.isNull(balanceTypeColumn) == false)
|
if (cursor.isNull(balanceTypeColumn) == false) {
|
||||||
{
|
|
||||||
balanceType = Currency.getInstance(cursor.getString(balanceTypeColumn));
|
balanceType = Currency.getInstance(cursor.getString(balanceTypeColumn));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(expiryLong > 0)
|
if (expiryLong > 0) {
|
||||||
{
|
|
||||||
expiry = new Date(expiryLong);
|
expiry = new Date(expiryLong);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cursor.isNull(headerColorColumn) == false)
|
if (cursor.isNull(headerColorColumn) == false) {
|
||||||
{
|
|
||||||
headerColor = cursor.getInt(headerColorColumn);
|
headerColor = cursor.getInt(headerColorColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new LoyaltyCard(id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starred);
|
return new LoyaltyCard(id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starred, lastUsed, zoomLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<LoyaltyCard> CREATOR = new Creator<LoyaltyCard>() {
|
||||||
|
@Override
|
||||||
|
public LoyaltyCard createFromParcel(Parcel in) {
|
||||||
|
return new LoyaltyCard(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LoyaltyCard[] newArray(int size) {
|
||||||
|
return new LoyaltyCard[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,245 +1,184 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.util.SparseBooleanArray;
|
import android.util.SparseBooleanArray;
|
||||||
|
import android.util.TypedValue;
|
||||||
import android.view.HapticFeedbackConstants;
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.RelativeLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.graphics.BlendModeColorFilterCompat;
|
||||||
|
import androidx.core.graphics.BlendModeCompat;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.google.android.material.card.MaterialCardView;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Currency;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
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;
|
import protect.card_locker.preferences.Settings;
|
||||||
|
|
||||||
public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCursorAdapter.LoyaltyCardListItemViewHolder>
|
public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCursorAdapter.LoyaltyCardListItemViewHolder> {
|
||||||
{
|
private int mCurrentSelectedIndex = -1;
|
||||||
private static int mCurrentSelectedIndex = -1;
|
|
||||||
private Cursor mCursor;
|
|
||||||
Settings mSettings;
|
Settings mSettings;
|
||||||
boolean mDarkModeEnabled;
|
boolean mDarkModeEnabled;
|
||||||
private Context mContext;
|
private final Context mContext;
|
||||||
private CardAdapterListener mListener;
|
private final CardAdapterListener mListener;
|
||||||
private SparseBooleanArray mSelectedItems;
|
protected SparseBooleanArray mSelectedItems;
|
||||||
private SparseBooleanArray mAnimationItemsIndex;
|
protected SparseBooleanArray mAnimationItemsIndex;
|
||||||
private boolean mReverseAllAnimations = false;
|
private boolean mReverseAllAnimations = false;
|
||||||
|
private boolean mShowDetails = true;
|
||||||
|
|
||||||
public LoyaltyCardCursorAdapter(Context inputContext, Cursor inputCursor, CardAdapterListener inputListener)
|
public LoyaltyCardCursorAdapter(Context inputContext, Cursor inputCursor, CardAdapterListener inputListener) {
|
||||||
{
|
super(inputCursor, DBHelper.LoyaltyCardDbIds.ID);
|
||||||
super(inputCursor);
|
setHasStableIds(true);
|
||||||
mSettings = new Settings(inputContext);
|
mSettings = new Settings(inputContext);
|
||||||
mCursor = inputCursor;
|
mContext = inputContext.getApplicationContext();
|
||||||
mContext = inputContext;
|
|
||||||
mListener = inputListener;
|
mListener = inputListener;
|
||||||
mSelectedItems = new SparseBooleanArray();
|
mSelectedItems = new SparseBooleanArray();
|
||||||
mAnimationItemsIndex = new SparseBooleanArray();
|
mAnimationItemsIndex = new SparseBooleanArray();
|
||||||
|
|
||||||
mDarkModeEnabled = MainActivity.isDarkModeEnabled(inputContext);
|
mDarkModeEnabled = Utils.isDarkModeEnabled(inputContext);
|
||||||
|
|
||||||
|
swapCursor(inputCursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showDetails(boolean show) {
|
||||||
|
mShowDetails = show;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean showingDetails() {
|
||||||
|
return mShowDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoyaltyCardListItemViewHolder onCreateViewHolder(ViewGroup inputParent, int inputViewType)
|
public LoyaltyCardListItemViewHolder onCreateViewHolder(ViewGroup inputParent, int inputViewType) {
|
||||||
{
|
|
||||||
View itemView = LayoutInflater.from(inputParent.getContext()).inflate(R.layout.loyalty_card_layout, inputParent, false);
|
View itemView = LayoutInflater.from(inputParent.getContext()).inflate(R.layout.loyalty_card_layout, inputParent, false);
|
||||||
return new LoyaltyCardListItemViewHolder(itemView);
|
return new LoyaltyCardListItemViewHolder(itemView, mListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cursor getCursor()
|
public LoyaltyCard getCard(int position) {
|
||||||
{
|
mCursor.moveToPosition(position);
|
||||||
return mCursor;
|
return LoyaltyCard.toLoyaltyCard(mCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onBindViewHolder(LoyaltyCardListItemViewHolder inputHolder, Cursor inputCursor) {
|
public void onBindViewHolder(LoyaltyCardListItemViewHolder inputHolder, Cursor inputCursor) {
|
||||||
if (mDarkModeEnabled) {
|
// Invisible until we want to show something more
|
||||||
inputHolder.mStarIcon.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
|
inputHolder.mDivider.setVisibility(View.GONE);
|
||||||
}
|
|
||||||
|
|
||||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(inputCursor);
|
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(inputCursor);
|
||||||
|
|
||||||
inputHolder.mStoreField.setText(loyaltyCard.store);
|
inputHolder.setStoreField(loyaltyCard.store);
|
||||||
inputHolder.mStoreField.setTextSize(mSettings.getFontSizeMax(mSettings.getMediumFont()));
|
if (mShowDetails && !loyaltyCard.note.isEmpty()) {
|
||||||
if (!loyaltyCard.note.isEmpty()) {
|
inputHolder.setNoteField(loyaltyCard.note);
|
||||||
inputHolder.mNoteField.setVisibility(View.VISIBLE);
|
|
||||||
inputHolder.mNoteField.setText(loyaltyCard.note);
|
|
||||||
inputHolder.mNoteField.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
|
|
||||||
} else {
|
} else {
|
||||||
inputHolder.mNoteField.setVisibility(View.GONE);
|
inputHolder.setNoteField(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!loyaltyCard.balance.equals(new BigDecimal("0"))) {
|
if (mShowDetails && !loyaltyCard.balance.equals(new BigDecimal("0"))) {
|
||||||
inputHolder.mBalanceField.setVisibility(View.VISIBLE);
|
inputHolder.setBalanceField(loyaltyCard.balance, loyaltyCard.balanceType);
|
||||||
inputHolder.mBalanceField.setText(mContext.getString(R.string.balanceSentence, Utils.formatBalance(mContext, loyaltyCard.balance, loyaltyCard.balanceType)));
|
|
||||||
inputHolder.mBalanceField.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
|
|
||||||
} else {
|
} else {
|
||||||
inputHolder.mBalanceField.setVisibility(View.GONE);
|
inputHolder.setBalanceField(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loyaltyCard.expiry != null)
|
if (mShowDetails && loyaltyCard.expiry != null) {
|
||||||
{
|
inputHolder.setExpiryField(loyaltyCard.expiry);
|
||||||
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 {
|
} else {
|
||||||
inputHolder.mExpiryField.setVisibility(View.GONE);
|
inputHolder.setExpiryField(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
inputHolder.mStarIcon.setVisibility((loyaltyCard.starStatus != 0) ? View.VISIBLE : View.GONE);
|
Bitmap cardIcon = Utils.retrieveCardImage(mContext, loyaltyCard.id, ImageLocationType.icon);
|
||||||
inputHolder.mCardIcon.setImageBitmap(Utils.generateIcon(mContext, loyaltyCard.store, loyaltyCard.headerColor).getLetterTile());
|
if (cardIcon != null) {
|
||||||
|
inputHolder.mCardIcon.setImageBitmap(cardIcon);
|
||||||
|
inputHolder.mCardIcon.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||||
|
} else {
|
||||||
|
inputHolder.mCardIcon.setImageBitmap(Utils.generateIcon(mContext, loyaltyCard.store, loyaltyCard.headerColor).getLetterTile());
|
||||||
|
inputHolder.mCardIcon.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||||
|
}
|
||||||
|
inputHolder.setIconBackgroundColor(loyaltyCard.headerColor != null ? loyaltyCard.headerColor : ContextCompat.getColor(mContext, R.color.colorPrimary));
|
||||||
|
|
||||||
|
inputHolder.toggleStar(loyaltyCard.starStatus != 0, itemSelected(inputCursor.getPosition()));
|
||||||
|
|
||||||
inputHolder.itemView.setActivated(mSelectedItems.get(inputCursor.getPosition(), false));
|
inputHolder.itemView.setActivated(mSelectedItems.get(inputCursor.getPosition(), false));
|
||||||
applyIconAnimation(inputHolder, inputCursor.getPosition());
|
applyIconAnimation(inputHolder, inputCursor.getPosition());
|
||||||
applyClickEvents(inputHolder, inputCursor.getPosition());
|
applyClickEvents(inputHolder, inputCursor.getPosition());
|
||||||
|
|
||||||
|
// Force redraw to fix size not shrinking after data change
|
||||||
|
inputHolder.mRow.requestLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyClickEvents(LoyaltyCardListItemViewHolder inputHolder, final int inputPosition)
|
private void applyClickEvents(LoyaltyCardListItemViewHolder inputHolder, final int inputPosition) {
|
||||||
{
|
inputHolder.mRow.setOnClickListener(inputView -> mListener.onRowClicked(inputPosition));
|
||||||
inputHolder.mThumbnailContainer.setOnClickListener(new View.OnClickListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onClick(View inputView)
|
|
||||||
{
|
|
||||||
mListener.onIconClicked(inputPosition);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
inputHolder.mRow.setOnClickListener(new View.OnClickListener()
|
inputHolder.mRow.setOnLongClickListener(inputView -> {
|
||||||
{
|
mListener.onRowLongClicked(inputPosition);
|
||||||
@Override
|
inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||||
public void onClick(View inputView)
|
return true;
|
||||||
{
|
|
||||||
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)
|
private boolean itemSelected(int inputPosition) {
|
||||||
{
|
return mSelectedItems.get(inputPosition, false);
|
||||||
if (mSelectedItems.get(inputPosition, false))
|
}
|
||||||
{
|
|
||||||
inputHolder.mThumbnailFrontContainer.setVisibility(View.GONE);
|
private void applyIconAnimation(LoyaltyCardListItemViewHolder inputHolder, int inputPosition) {
|
||||||
resetIconYAxis(inputHolder.mThumbnailBackContainer);
|
if (itemSelected(inputPosition)) {
|
||||||
inputHolder.mThumbnailBackContainer.setVisibility(View.VISIBLE);
|
inputHolder.mCardIcon.setVisibility(View.GONE);
|
||||||
inputHolder.mThumbnailBackContainer.setAlpha(1);
|
inputHolder.mTickIcon.setVisibility(View.VISIBLE);
|
||||||
if (mCurrentSelectedIndex == inputPosition)
|
if (mCurrentSelectedIndex == inputPosition) {
|
||||||
{
|
|
||||||
LoyaltyCardAnimator.flipView(mContext, inputHolder.mThumbnailBackContainer, inputHolder.mThumbnailFrontContainer, true);
|
|
||||||
resetCurrentIndex();
|
resetCurrentIndex();
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
inputHolder.mTickIcon.setVisibility(View.GONE);
|
||||||
{
|
inputHolder.mCardIcon.setVisibility(View.VISIBLE);
|
||||||
inputHolder.mThumbnailBackContainer.setVisibility(View.GONE);
|
if ((mReverseAllAnimations && mAnimationItemsIndex.get(inputPosition, false)) || mCurrentSelectedIndex == inputPosition) {
|
||||||
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();
|
resetCurrentIndex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetIconYAxis(View inputView)
|
public void toggleSelection(int inputPosition) {
|
||||||
{
|
|
||||||
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;
|
mCurrentSelectedIndex = inputPosition;
|
||||||
if (mSelectedItems.get(inputPosition, false))
|
if (mSelectedItems.get(inputPosition, false)) {
|
||||||
{
|
|
||||||
mSelectedItems.delete(inputPosition);
|
mSelectedItems.delete(inputPosition);
|
||||||
mAnimationItemsIndex.delete(inputPosition);
|
mAnimationItemsIndex.delete(inputPosition);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
mSelectedItems.put(inputPosition, true);
|
mSelectedItems.put(inputPosition, true);
|
||||||
mAnimationItemsIndex.put(inputPosition, true);
|
mAnimationItemsIndex.put(inputPosition, true);
|
||||||
}
|
}
|
||||||
notifyItemChanged(inputPosition);
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearSelections()
|
public void clearSelections() {
|
||||||
{
|
|
||||||
mReverseAllAnimations = true;
|
mReverseAllAnimations = true;
|
||||||
mSelectedItems.clear();
|
mSelectedItems.clear();
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSelectedItemCount()
|
public int getSelectedItemCount() {
|
||||||
{
|
|
||||||
return mSelectedItems.size();
|
return mSelectedItems.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayList<LoyaltyCard> getSelectedItems()
|
public ArrayList<LoyaltyCard> getSelectedItems() {
|
||||||
{
|
|
||||||
|
|
||||||
ArrayList<LoyaltyCard> result = new ArrayList<>();
|
ArrayList<LoyaltyCard> result = new ArrayList<>();
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
for(i = 0; i < mSelectedItems.size(); i++)
|
for (i = 0; i < mSelectedItems.size(); i++) {
|
||||||
{
|
|
||||||
mCursor.moveToPosition(mSelectedItems.keyAt(i));
|
mCursor.moveToPosition(mSelectedItems.keyAt(i));
|
||||||
result.add(LoyaltyCard.toLoyaltyCard(mCursor));
|
result.add(LoyaltyCard.toLoyaltyCard(mCursor));
|
||||||
}
|
}
|
||||||
@@ -247,51 +186,146 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetCurrentIndex()
|
private void resetCurrentIndex() {
|
||||||
{
|
|
||||||
mCurrentSelectedIndex = -1;
|
mCurrentSelectedIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface CardAdapterListener
|
public interface CardAdapterListener {
|
||||||
{
|
|
||||||
void onIconClicked(int inputPosition);
|
|
||||||
void onRowClicked(int inputPosition);
|
void onRowClicked(int inputPosition);
|
||||||
|
|
||||||
void onRowLongClicked(int inputPosition);
|
void onRowLongClicked(int inputPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LoyaltyCardListItemViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener
|
public class LoyaltyCardListItemViewHolder extends RecyclerView.ViewHolder {
|
||||||
{
|
|
||||||
|
|
||||||
public TextView mStoreField, mNoteField, mBalanceField, mExpiryField;
|
public TextView mStoreField, mNoteField, mBalanceField, mExpiryField;
|
||||||
public LinearLayout mInformationContainer;
|
public ImageView mCardIcon, mStarBackground, mStarBorder, mTickIcon;
|
||||||
public ImageView mCardIcon, mStarIcon;
|
public MaterialCardView mRow;
|
||||||
public CardView mThumbnailContainer;
|
public ConstraintLayout mStar;
|
||||||
public ConstraintLayout mRow;
|
public View mDivider;
|
||||||
public RelativeLayout mThumbnailFrontContainer, mThumbnailBackContainer;
|
|
||||||
|
|
||||||
public LoyaltyCardListItemViewHolder(View inputView)
|
private int mIconBackgroundColor;
|
||||||
{
|
|
||||||
|
protected LoyaltyCardListItemViewHolder(View inputView, CardAdapterListener inputListener) {
|
||||||
super(inputView);
|
super(inputView);
|
||||||
mThumbnailContainer = inputView.findViewById(R.id.thumbnail_container);
|
|
||||||
mRow = inputView.findViewById(R.id.row);
|
mRow = inputView.findViewById(R.id.row);
|
||||||
mThumbnailFrontContainer = inputView.findViewById(R.id.thumbnail_front);
|
mDivider = inputView.findViewById(R.id.info_divider);
|
||||||
mThumbnailBackContainer = inputView.findViewById(R.id.thumbnail_back);
|
|
||||||
mInformationContainer = inputView.findViewById(R.id.information_container);
|
|
||||||
mStoreField = inputView.findViewById(R.id.store);
|
mStoreField = inputView.findViewById(R.id.store);
|
||||||
mNoteField = inputView.findViewById(R.id.note);
|
mNoteField = inputView.findViewById(R.id.note);
|
||||||
mBalanceField = inputView.findViewById(R.id.balance);
|
mBalanceField = inputView.findViewById(R.id.balance);
|
||||||
mExpiryField = inputView.findViewById(R.id.expiry);
|
mExpiryField = inputView.findViewById(R.id.expiry);
|
||||||
|
|
||||||
mCardIcon = inputView.findViewById(R.id.thumbnail);
|
mCardIcon = inputView.findViewById(R.id.thumbnail);
|
||||||
mStarIcon = inputView.findViewById(R.id.star);
|
mStar = inputView.findViewById(R.id.star);
|
||||||
inputView.setOnLongClickListener(this);
|
mStarBackground = inputView.findViewById(R.id.star_background);
|
||||||
|
mStarBorder = inputView.findViewById(R.id.star_border);
|
||||||
|
mTickIcon = inputView.findViewById(R.id.selected_thumbnail);
|
||||||
|
inputView.setOnLongClickListener(view -> {
|
||||||
|
inputListener.onRowClicked(getAdapterPosition());
|
||||||
|
inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void setStoreField(String text) {
|
||||||
public boolean onLongClick(View inputView)
|
mStoreField.setText(text);
|
||||||
{
|
mStoreField.setTextSize(mSettings.getFontSizeMax(mSettings.getMediumFont()));
|
||||||
mListener.onRowLongClicked(getAdapterPosition());
|
mStoreField.requestLayout();
|
||||||
inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
}
|
||||||
return true;
|
|
||||||
|
public void setNoteField(String text) {
|
||||||
|
if (text == null) {
|
||||||
|
mNoteField.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
mNoteField.setVisibility(View.VISIBLE);
|
||||||
|
mNoteField.setText(text);
|
||||||
|
mNoteField.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
|
||||||
|
}
|
||||||
|
mNoteField.requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBalanceField(BigDecimal balance, Currency balanceType) {
|
||||||
|
if (balance == null) {
|
||||||
|
mBalanceField.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
int size = mSettings.getFontSizeMax(mSettings.getSmallFont());
|
||||||
|
int drawableSize = dpToPx((size * 24) / 14, mContext);
|
||||||
|
mDivider.setVisibility(View.VISIBLE);
|
||||||
|
mBalanceField.setVisibility(View.VISIBLE);
|
||||||
|
Drawable balanceIcon = mBalanceField.getCompoundDrawables()[0];
|
||||||
|
balanceIcon.setBounds(0, 0, drawableSize, drawableSize);
|
||||||
|
mBalanceField.setCompoundDrawablesRelative(balanceIcon, null, null, null);
|
||||||
|
if (mDarkModeEnabled) {
|
||||||
|
balanceIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
|
||||||
|
}
|
||||||
|
mBalanceField.setText(Utils.formatBalance(mContext, balance, balanceType));
|
||||||
|
mBalanceField.setTextSize(size);
|
||||||
|
}
|
||||||
|
mBalanceField.requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpiryField(Date expiry) {
|
||||||
|
if (expiry == null) {
|
||||||
|
mExpiryField.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
int size = mSettings.getFontSizeMax(mSettings.getSmallFont());
|
||||||
|
int drawableSize = dpToPx((size * 24) / 14, mContext);
|
||||||
|
mDivider.setVisibility(View.VISIBLE);
|
||||||
|
mExpiryField.setVisibility(View.VISIBLE);
|
||||||
|
Drawable expiryIcon = mExpiryField.getCompoundDrawables()[0];
|
||||||
|
expiryIcon.setBounds(0, 0, drawableSize, drawableSize);
|
||||||
|
mExpiryField.setCompoundDrawablesRelative(expiryIcon, null, null, null);
|
||||||
|
if (Utils.hasExpired(expiry)) {
|
||||||
|
expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.RED, BlendModeCompat.SRC_ATOP));
|
||||||
|
mExpiryField.setTextColor(Color.RED);
|
||||||
|
} else if (mDarkModeEnabled) {
|
||||||
|
expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
|
||||||
|
}
|
||||||
|
mExpiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(expiry));
|
||||||
|
mExpiryField.setTextSize(size);
|
||||||
|
}
|
||||||
|
mExpiryField.requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleStar(boolean enable, boolean colorByTheme) {
|
||||||
|
/* the below code does not work in android 5! hence the change of drawable instead
|
||||||
|
boolean needDarkForeground = Utils.needsDarkForeground(mIconBackgroundColor);
|
||||||
|
Drawable borderDrawable = mStarBorder.getDrawable().mutate();
|
||||||
|
Drawable backgroundDrawable = mStarBackground.getDrawable().mutate();
|
||||||
|
DrawableCompat.setTint(borderDrawable, needsDarkForeground ? Color.BLACK : Color.WHITE);
|
||||||
|
DrawableCompat.setTint(backgroundDrawable, needsDarkForeground ? Color.BLACK : Color.WHITE);
|
||||||
|
mStarBorder.setImageDrawable(borderDrawable);
|
||||||
|
mStarBackground.setImageDrawable(backgroundDrawable);
|
||||||
|
*/
|
||||||
|
boolean dark = Utils.needsDarkForeground(mIconBackgroundColor);
|
||||||
|
if (colorByTheme) {
|
||||||
|
dark = !mDarkModeEnabled;
|
||||||
|
}
|
||||||
|
if (dark) {
|
||||||
|
mStarBorder.setImageResource(R.drawable.ic_unstarred_black);
|
||||||
|
mStarBackground.setImageResource(R.drawable.ic_starred_black);
|
||||||
|
} else {
|
||||||
|
mStarBorder.setImageResource(R.drawable.ic_unstarred_white);
|
||||||
|
mStarBackground.setImageResource(R.drawable.ic_starred_white);
|
||||||
|
}
|
||||||
|
if (enable) {
|
||||||
|
mStar.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
mStar.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
mStarBorder.invalidate();
|
||||||
|
mStarBackground.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIconBackgroundColor(int color) {
|
||||||
|
mIconBackgroundColor = color;
|
||||||
|
mCardIcon.setBackgroundColor(color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public int dpToPx(int dp, Context mContext) {
|
||||||
|
Resources r = mContext.getResources();
|
||||||
|
int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
|
||||||
|
return px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
15
app/src/main/java/protect/card_locker/LoyaltyCardField.java
Normal file
15
app/src/main/java/protect/card_locker/LoyaltyCardField.java
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
public enum LoyaltyCardField {
|
||||||
|
id,
|
||||||
|
store,
|
||||||
|
note,
|
||||||
|
expiry,
|
||||||
|
balance,
|
||||||
|
balanceType,
|
||||||
|
cardId,
|
||||||
|
barcodeId,
|
||||||
|
barcodeType,
|
||||||
|
headerColor,
|
||||||
|
starStatus
|
||||||
|
}
|
||||||
@@ -6,10 +6,12 @@ import androidx.appcompat.app.AppCompatDelegate;
|
|||||||
import protect.card_locker.preferences.Settings;
|
import protect.card_locker.preferences.Settings;
|
||||||
|
|
||||||
public class LoyaltyCardLockerApplication extends Application {
|
public class LoyaltyCardLockerApplication extends Application {
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
Settings settings = new Settings(getApplicationContext());
|
Settings settings = new Settings(this);
|
||||||
AppCompatDelegate.setDefaultNightMode(settings.getTheme());
|
AppCompatDelegate.setDefaultNightMode(settings.getTheme());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,14 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.SearchManager;
|
import android.app.SearchManager;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Configuration;
|
import android.database.CursorIndexOutOfBoundsException;
|
||||||
import android.database.Cursor;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.GestureDetector;
|
import android.view.GestureDetector;
|
||||||
@@ -16,57 +16,65 @@ import android.view.Menu;
|
|||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.CheckBox;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.view.ActionMode;
|
||||||
|
import androidx.appcompat.widget.SearchView;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.core.splashscreen.SplashScreen;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.view.ActionMode;
|
|
||||||
import androidx.appcompat.widget.SearchView;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import protect.card_locker.preferences.SettingsActivity;
|
import protect.card_locker.preferences.SettingsActivity;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener, GestureDetector.OnGestureListener
|
public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener, GestureDetector.OnGestureListener {
|
||||||
{
|
|
||||||
private static final String TAG = "Catima";
|
private static final String TAG = "Catima";
|
||||||
|
public static final String RESTART_ACTIVITY_INTENT = "restart_activity_intent";
|
||||||
|
|
||||||
private final DBHelper mDB = new DBHelper(this);
|
private SQLiteDatabase mDatabase;
|
||||||
private LoyaltyCardCursorAdapter mAdapter;
|
private LoyaltyCardCursorAdapter mAdapter;
|
||||||
private ActionMode mCurrentActionMode;
|
private ActionMode mCurrentActionMode;
|
||||||
private Menu mMenu;
|
private SearchView mSearchView;
|
||||||
private GestureDetector mGestureDetector;
|
private GestureDetector mGestureDetector;
|
||||||
protected String mFilter = "";
|
protected String mFilter = "";
|
||||||
|
protected Object mGroup = null;
|
||||||
|
protected DBHelper.LoyaltyCardOrder mOrder = DBHelper.LoyaltyCardOrder.Alpha;
|
||||||
|
protected DBHelper.LoyaltyCardOrderDirection mOrderDirection = DBHelper.LoyaltyCardOrderDirection.Ascending;
|
||||||
protected int selectedTab = 0;
|
protected int selectedTab = 0;
|
||||||
private RecyclerView mCardList;
|
private RecyclerView mCardList;
|
||||||
|
private View mHelpText;
|
||||||
|
private View mNoMatchingCardsText;
|
||||||
|
private View mNoGroupCardsText;
|
||||||
|
|
||||||
private ActionMode.Callback mCurrentActionModeCallback = new ActionMode.Callback()
|
private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
|
||||||
{
|
private ActivityResultLauncher<Intent> mSettingsLauncher;
|
||||||
|
|
||||||
|
private ActionMode.Callback mCurrentActionModeCallback = new ActionMode.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateActionMode(ActionMode inputMode, Menu inputMenu)
|
public boolean onCreateActionMode(ActionMode inputMode, Menu inputMenu) {
|
||||||
{
|
|
||||||
inputMode.getMenuInflater().inflate(R.menu.card_longclick_menu, inputMenu);
|
inputMode.getMenuInflater().inflate(R.menu.card_longclick_menu, inputMenu);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareActionMode(ActionMode inputMode, Menu inputMenu)
|
public boolean onPrepareActionMode(ActionMode inputMode, Menu inputMenu) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onActionItemClicked(ActionMode inputMode, MenuItem inputItem)
|
public boolean onActionItemClicked(ActionMode inputMode, MenuItem inputItem) {
|
||||||
{
|
if (inputItem.getItemId() == R.id.action_copy_to_clipboard) {
|
||||||
if (inputItem.getItemId() == R.id.action_copy_to_clipboard)
|
|
||||||
{
|
|
||||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||||
|
|
||||||
String clipboardData;
|
String clipboardData;
|
||||||
@@ -94,9 +102,7 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
Toast.makeText(MainActivity.this, cardCount > 1 ? R.string.copy_to_clipboard_multiple_toast : R.string.copy_to_clipboard_toast, Toast.LENGTH_LONG).show();
|
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();
|
inputMode.finish();
|
||||||
return true;
|
return true;
|
||||||
}
|
} else if (inputItem.getItemId() == R.id.action_share) {
|
||||||
else if (inputItem.getItemId() == R.id.action_share)
|
|
||||||
{
|
|
||||||
final ImportURIHelper importURIHelper = new ImportURIHelper(MainActivity.this);
|
final ImportURIHelper importURIHelper = new ImportURIHelper(MainActivity.this);
|
||||||
try {
|
try {
|
||||||
importURIHelper.startShareIntent(mAdapter.getSelectedItems());
|
importURIHelper.startShareIntent(mAdapter.getSelectedItems());
|
||||||
@@ -106,20 +112,55 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
}
|
}
|
||||||
inputMode.finish();
|
inputMode.finish();
|
||||||
return true;
|
return true;
|
||||||
}
|
} else if (inputItem.getItemId() == R.id.action_edit) {
|
||||||
else if(inputItem.getItemId() == R.id.action_edit)
|
|
||||||
{
|
|
||||||
if (mAdapter.getSelectedItemCount() != 1) {
|
if (mAdapter.getSelectedItemCount() != 1) {
|
||||||
throw new IllegalArgumentException("Cannot edit more than 1 card at a time");
|
throw new IllegalArgumentException("Cannot edit more than 1 card at a time");
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putInt("id", mAdapter.getSelectedItems().get(0).id);
|
bundle.putInt(LoyaltyCardEditActivity.BUNDLE_ID, mAdapter.getSelectedItems().get(0).id);
|
||||||
bundle.putBoolean("update", true);
|
bundle.putBoolean(LoyaltyCardEditActivity.BUNDLE_UPDATE, true);
|
||||||
intent.putExtras(bundle);
|
intent.putExtras(bundle);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
inputMode.finish();
|
inputMode.finish();
|
||||||
|
return true;
|
||||||
|
} else if (inputItem.getItemId() == R.id.action_delete) {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
|
||||||
|
// The following may seem weird, but it is necessary to give translators enough flexibility.
|
||||||
|
// For example, in Russian, Android's plural quantity "one" actually refers to "any number ending on 1 but not ending in 11".
|
||||||
|
// So while in English the extra non-plural form seems unnecessary duplication, it is necessary to give translators enough flexibility.
|
||||||
|
// In here, we use the plain string when meaning exactly 1, and otherwise use the plural forms
|
||||||
|
if (mAdapter.getSelectedItemCount() == 1) {
|
||||||
|
builder.setTitle(R.string.deleteTitle);
|
||||||
|
builder.setMessage(R.string.deleteConfirmation);
|
||||||
|
} else {
|
||||||
|
builder.setTitle(getResources().getQuantityString(R.plurals.deleteCardsTitle, mAdapter.getSelectedItemCount(), mAdapter.getSelectedItemCount()));
|
||||||
|
builder.setMessage(getResources().getQuantityString(R.plurals.deleteCardsConfirmation, mAdapter.getSelectedItemCount(), mAdapter.getSelectedItemCount()));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
|
||||||
|
DBHelper db = new DBHelper(MainActivity.this);
|
||||||
|
|
||||||
|
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||||
|
Log.e(TAG, "Deleting card: " + loyaltyCard.id);
|
||||||
|
|
||||||
|
DBHelper.deleteLoyaltyCard(mDatabase, MainActivity.this, loyaltyCard.id);
|
||||||
|
|
||||||
|
ShortcutHelper.removeShortcut(MainActivity.this, loyaltyCard.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
TabLayout.Tab tab = ((TabLayout) findViewById(R.id.groups)).getTabAt(selectedTab);
|
||||||
|
mGroup = tab != null ? tab.getTag() : null;
|
||||||
|
|
||||||
|
updateLoyaltyCardList();
|
||||||
|
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
|
||||||
|
AlertDialog dialog = builder.create();
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,44 +168,37 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyActionMode(ActionMode inputMode)
|
public void onDestroyActionMode(ActionMode inputMode) {
|
||||||
{
|
|
||||||
mAdapter.clearSelections();
|
mAdapter.clearSelections();
|
||||||
mCurrentActionMode = null;
|
mCurrentActionMode = null;
|
||||||
mCardList.post(new Runnable()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
mAdapter.resetAnimationIndex();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle inputSavedInstanceState)
|
protected void onCreate(Bundle inputSavedInstanceState) {
|
||||||
{
|
|
||||||
super.onCreate(inputSavedInstanceState);
|
super.onCreate(inputSavedInstanceState);
|
||||||
|
SplashScreen.installSplashScreen(this);
|
||||||
|
setTitle(R.string.app_name);
|
||||||
setContentView(R.layout.main_activity);
|
setContentView(R.layout.main_activity);
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
updateLoyaltyCardList(mFilter, null);
|
mDatabase = new DBHelper(this).getWritableDatabase();
|
||||||
|
|
||||||
TabLayout groupsTabLayout = findViewById(R.id.groups);
|
TabLayout groupsTabLayout = findViewById(R.id.groups);
|
||||||
groupsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
groupsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onTabSelected(TabLayout.Tab tab) {
|
public void onTabSelected(TabLayout.Tab tab) {
|
||||||
selectedTab = tab.getPosition();
|
selectedTab = tab.getPosition();
|
||||||
updateLoyaltyCardList(mFilter, tab.getTag());
|
Log.d("onTabSelected", "Tab Position " + tab.getPosition());
|
||||||
|
mGroup = tab.getTag();
|
||||||
|
updateLoyaltyCardList();
|
||||||
// Store active tab in Shared Preference to restore next app launch
|
// Store active tab in Shared Preference to restore next app launch
|
||||||
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
|
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
|
||||||
getString(R.string.sharedpreference_active_tab),
|
getString(R.string.sharedpreference_active_tab),
|
||||||
Context.MODE_PRIVATE);
|
Context.MODE_PRIVATE);
|
||||||
SharedPreferences.Editor activeTabPrefEditor = activeTabPref.edit();
|
SharedPreferences.Editor activeTabPrefEditor = activeTabPref.edit();
|
||||||
activeTabPrefEditor.putInt(getString(R.string.sharedpreference_active_tab), selectedTab);
|
activeTabPrefEditor.putInt(getString(R.string.sharedpreference_active_tab), tab.getPosition());
|
||||||
activeTabPrefEditor.apply();
|
activeTabPrefEditor.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,20 +215,24 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
|
|
||||||
mGestureDetector = new GestureDetector(this, this);
|
mGestureDetector = new GestureDetector(this, this);
|
||||||
|
|
||||||
View.OnTouchListener gestureTouchListener = new View.OnTouchListener() {
|
View.OnTouchListener gestureTouchListener = (v, event) -> mGestureDetector.onTouchEvent(event);
|
||||||
@Override
|
|
||||||
public boolean onTouch(final View v, final MotionEvent event){
|
|
||||||
return mGestureDetector.onTouchEvent(event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
final View helpText = findViewById(R.id.helpText);
|
mHelpText = findViewById(R.id.helpText);
|
||||||
final View noMatchingCardsText = findViewById(R.id.noMatchingCardsText);
|
mNoMatchingCardsText = findViewById(R.id.noMatchingCardsText);
|
||||||
final View list = findViewById(R.id.list);
|
mNoGroupCardsText = findViewById(R.id.noGroupCardsText);
|
||||||
|
mCardList = findViewById(R.id.list);
|
||||||
|
|
||||||
helpText.setOnTouchListener(gestureTouchListener);
|
mHelpText.setOnTouchListener(gestureTouchListener);
|
||||||
noMatchingCardsText.setOnTouchListener(gestureTouchListener);
|
mNoMatchingCardsText.setOnTouchListener(gestureTouchListener);
|
||||||
list.setOnTouchListener(gestureTouchListener);
|
mCardList.setOnTouchListener(gestureTouchListener);
|
||||||
|
mNoGroupCardsText.setOnTouchListener(gestureTouchListener);
|
||||||
|
|
||||||
|
mAdapter = new LoyaltyCardCursorAdapter(this, null, this);
|
||||||
|
mCardList.setAdapter(mAdapter);
|
||||||
|
registerForContextMenu(mCardList);
|
||||||
|
|
||||||
|
mGroup = null;
|
||||||
|
updateLoyaltyCardList();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This was added for Huawei, but Huawei is just too much of a fucking pain.
|
* This was added for Huawei, but Huawei is just too much of a fucking pain.
|
||||||
@@ -226,40 +264,67 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
mBarcodeScannerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||||
|
Intent intent = result.getData();
|
||||||
|
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, this);
|
||||||
|
|
||||||
|
if (!barcodeValues.isEmpty()) {
|
||||||
|
Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||||
|
Bundle newBundle = new Bundle();
|
||||||
|
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
|
||||||
|
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
|
||||||
|
Bundle inputBundle = intent.getExtras();
|
||||||
|
if (inputBundle != null && inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) != null) {
|
||||||
|
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP));
|
||||||
|
}
|
||||||
|
newIntent.putExtras(newBundle);
|
||||||
|
startActivity(newIntent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mSettingsLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||||
|
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||||
|
Intent intent = result.getData();
|
||||||
|
if (intent != null && intent.getBooleanExtra(RESTART_ACTIVITY_INTENT, false)) {
|
||||||
|
recreate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume()
|
protected void onResume() {
|
||||||
{
|
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
if(mCurrentActionMode != null)
|
if (mCurrentActionMode != null) {
|
||||||
{
|
|
||||||
mAdapter.clearSelections();
|
mAdapter.clearSelections();
|
||||||
mCurrentActionMode.finish();
|
mCurrentActionMode.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mMenu != null)
|
if (mSearchView != null && !mSearchView.isIconified()) {
|
||||||
{
|
mFilter = mSearchView.getQuery().toString();
|
||||||
SearchView searchView = (SearchView) mMenu.findItem(R.id.action_search).getActionView();
|
|
||||||
|
|
||||||
if (!searchView.isIconified())
|
|
||||||
{
|
|
||||||
mFilter = searchView.getQuery().toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start of active tab logic
|
// Start of active tab logic
|
||||||
TabLayout groupsTabLayout = findViewById(R.id.groups);
|
TabLayout groupsTabLayout = findViewById(R.id.groups);
|
||||||
updateTabGroups(groupsTabLayout);
|
updateTabGroups(groupsTabLayout);
|
||||||
|
|
||||||
// Restore active tab from Shared Preference
|
// Restore settings from Shared Preference
|
||||||
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
|
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
|
||||||
getString(R.string.sharedpreference_active_tab),
|
getString(R.string.sharedpreference_active_tab),
|
||||||
Context.MODE_PRIVATE);
|
Context.MODE_PRIVATE);
|
||||||
selectedTab = activeTabPref.getInt(getString(R.string.sharedpreference_active_tab), 0);
|
selectedTab = activeTabPref.getInt(getString(R.string.sharedpreference_active_tab), 0);
|
||||||
|
SharedPreferences sortPref = getApplicationContext().getSharedPreferences(
|
||||||
|
getString(R.string.sharedpreference_sort),
|
||||||
|
Context.MODE_PRIVATE);
|
||||||
|
try {
|
||||||
|
mOrder = DBHelper.LoyaltyCardOrder.valueOf(sortPref.getString(getString(R.string.sharedpreference_sort_order), null));
|
||||||
|
mOrderDirection = DBHelper.LoyaltyCardOrderDirection.valueOf(sortPref.getString(getString(R.string.sharedpreference_sort_direction), null));
|
||||||
|
} catch (IllegalArgumentException | NullPointerException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
Object group = null;
|
mGroup = null;
|
||||||
|
|
||||||
if (groupsTabLayout.getTabCount() != 0) {
|
if (groupsTabLayout.getTabCount() != 0) {
|
||||||
TabLayout.Tab tab = groupsTabLayout.getTabAt(selectedTab);
|
TabLayout.Tab tab = groupsTabLayout.getTabAt(selectedTab);
|
||||||
@@ -269,118 +334,68 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
|
|
||||||
groupsTabLayout.selectTab(tab);
|
groupsTabLayout.selectTab(tab);
|
||||||
assert tab != null;
|
assert tab != null;
|
||||||
group = tab.getTag();
|
mGroup = tab.getTag();
|
||||||
}
|
}
|
||||||
updateLoyaltyCardList(mFilter, group);
|
updateLoyaltyCardList();
|
||||||
// End of active tab logic
|
// End of active tab logic
|
||||||
|
|
||||||
FloatingActionButton addButton = findViewById(R.id.fabAdd);
|
FloatingActionButton addButton = findViewById(R.id.fabAdd);
|
||||||
addButton.setOnClickListener(v -> {
|
addButton.setOnClickListener(v -> {
|
||||||
Intent i = new Intent(getApplicationContext(), ScanActivity.class);
|
Intent intent = new Intent(getApplicationContext(), ScanActivity.class);
|
||||||
startActivityForResult(i, Utils.BARCODE_SCAN);
|
Bundle bundle = new Bundle();
|
||||||
|
if (selectedTab != 0) {
|
||||||
|
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, groupsTabLayout.getTabAt(selectedTab).getText().toString());
|
||||||
|
}
|
||||||
|
intent.putExtras(bundle);
|
||||||
|
mBarcodeScannerLauncher.launch(intent);
|
||||||
});
|
});
|
||||||
addButton.bringToFront();
|
addButton.bringToFront();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
public void onBackPressed() {
|
||||||
super.onActivityResult(requestCode, resultCode, intent);
|
if (!mSearchView.isIconified()) {
|
||||||
|
mSearchView.setIconified(true);
|
||||||
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
|
|
||||||
mFilter = "";
|
|
||||||
if (mMenu != null)
|
|
||||||
{
|
|
||||||
MenuItem searchItem = mMenu.findItem(R.id.action_search);
|
|
||||||
searchItem.collapseActionView();
|
|
||||||
}
|
|
||||||
recreate();
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
|
super.onBackPressed();
|
||||||
|
|
||||||
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
|
private void updateLoyaltyCardList() {
|
||||||
public void onBackPressed()
|
|
||||||
{
|
|
||||||
if (mMenu == null)
|
|
||||||
{
|
|
||||||
super.onBackPressed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchView searchView = (SearchView) mMenu.findItem(R.id.action_search).getActionView();
|
|
||||||
|
|
||||||
if (!searchView.isIconified())
|
|
||||||
{
|
|
||||||
searchView.setIconified(true);
|
|
||||||
} else {
|
|
||||||
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, Object tag)
|
|
||||||
{
|
|
||||||
Group group = null;
|
Group group = null;
|
||||||
if (tag != null) {
|
if (mGroup != null) {
|
||||||
group = (Group) tag;
|
group = (Group) mGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
mCardList = findViewById(R.id.list);
|
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase, mFilter, group, mOrder, mOrderDirection));
|
||||||
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
|
|
||||||
mCardList.setLayoutManager(mLayoutManager);
|
|
||||||
mCardList.setItemAnimator(new DefaultItemAnimator());
|
|
||||||
|
|
||||||
final TextView helpText = findViewById(R.id.helpText);
|
if (DBHelper.getLoyaltyCardCount(mDatabase) > 0) {
|
||||||
final TextView noMatchingCardsText = findViewById(R.id.noMatchingCardsText);
|
|
||||||
|
|
||||||
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
|
// 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
|
// to ensure that the noMatchingCardsText doesn't end up being shown below
|
||||||
// the keyboard
|
// the keyboard
|
||||||
mCardList.setVisibility(View.VISIBLE);
|
mHelpText.setVisibility(View.GONE);
|
||||||
helpText.setVisibility(View.GONE);
|
mNoGroupCardsText.setVisibility(View.GONE);
|
||||||
if(mAdapter.getItemCount() > 0)
|
if (mAdapter.getItemCount() > 0) {
|
||||||
{
|
mCardList.setVisibility(View.VISIBLE);
|
||||||
noMatchingCardsText.setVisibility(View.GONE);
|
mNoMatchingCardsText.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
mCardList.setVisibility(View.GONE);
|
||||||
|
if (!mFilter.isEmpty()) {
|
||||||
|
// Actual Empty Search Result
|
||||||
|
mNoMatchingCardsText.setVisibility(View.VISIBLE);
|
||||||
|
mNoGroupCardsText.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
// Group Tab with no Group Cards
|
||||||
|
mNoMatchingCardsText.setVisibility(View.GONE);
|
||||||
|
mNoGroupCardsText.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
} else {
|
||||||
{
|
|
||||||
noMatchingCardsText.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mCardList.setVisibility(View.GONE);
|
mCardList.setVisibility(View.GONE);
|
||||||
helpText.setVisibility(View.VISIBLE);
|
mHelpText.setVisibility(View.VISIBLE);
|
||||||
noMatchingCardsText.setVisibility(View.GONE);
|
mNoMatchingCardsText.setVisibility(View.GONE);
|
||||||
|
mNoGroupCardsText.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mCurrentActionMode != null) {
|
if (mCurrentActionMode != null) {
|
||||||
@@ -388,11 +403,8 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateTabGroups(TabLayout groupsTabLayout)
|
public void updateTabGroups(TabLayout groupsTabLayout) {
|
||||||
{
|
List<Group> newGroups = DBHelper.getGroups(mDatabase);
|
||||||
final DBHelper db = new DBHelper(this);
|
|
||||||
|
|
||||||
List<Group> newGroups = db.getGroups();
|
|
||||||
|
|
||||||
if (newGroups.size() == 0) {
|
if (newGroups.size() == 0) {
|
||||||
groupsTabLayout.removeAllTabs();
|
groupsTabLayout.removeAllTabs();
|
||||||
@@ -417,58 +429,36 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
groupsTabLayout.setVisibility(View.VISIBLE);
|
groupsTabLayout.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openPrivacyPolicy() {
|
|
||||||
Intent browserIntent = new Intent(
|
|
||||||
Intent.ACTION_VIEW,
|
|
||||||
Uri.parse("https://catima.app/privacy-policy")
|
|
||||||
);
|
|
||||||
startActivity(browserIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu inputMenu)
|
public boolean onCreateOptionsMenu(Menu inputMenu) {
|
||||||
{
|
|
||||||
this.mMenu = inputMenu;
|
|
||||||
|
|
||||||
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
|
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
|
||||||
|
|
||||||
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
|
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
|
||||||
if (searchManager != null)
|
if (searchManager != null) {
|
||||||
{
|
mSearchView = (SearchView) inputMenu.findItem(R.id.action_search).getActionView();
|
||||||
SearchView searchView = (SearchView) inputMenu.findItem(R.id.action_search).getActionView();
|
mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
|
||||||
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
|
mSearchView.setSubmitButtonEnabled(false);
|
||||||
searchView.setSubmitButtonEnabled(false);
|
|
||||||
|
|
||||||
searchView.setOnCloseListener(new SearchView.OnCloseListener()
|
mSearchView.setOnCloseListener(() -> {
|
||||||
{
|
invalidateOptionsMenu();
|
||||||
@Override
|
return false;
|
||||||
public boolean onClose()
|
|
||||||
{
|
|
||||||
invalidateOptionsMenu();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener()
|
mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||||
{
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onQueryTextSubmit(String query)
|
public boolean onQueryTextSubmit(String query) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onQueryTextChange(String newText)
|
public boolean onQueryTextChange(String newText) {
|
||||||
{
|
|
||||||
mFilter = newText;
|
mFilter = newText;
|
||||||
|
|
||||||
TabLayout groupsTabLayout = findViewById(R.id.groups);
|
TabLayout groupsTabLayout = findViewById(R.id.groups);
|
||||||
TabLayout.Tab currentTab = groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition());
|
TabLayout.Tab currentTab = groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition());
|
||||||
|
mGroup = currentTab != null ? currentTab.getTag() : null;
|
||||||
|
|
||||||
updateLoyaltyCardList(
|
updateLoyaltyCardList();
|
||||||
mFilter,
|
|
||||||
currentTab != null ? currentTab.getTag() : null
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -478,52 +468,107 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem inputItem)
|
public boolean onOptionsItemSelected(MenuItem inputItem) {
|
||||||
{
|
|
||||||
int id = inputItem.getItemId();
|
int id = inputItem.getItemId();
|
||||||
|
|
||||||
if (id == R.id.action_manage_groups)
|
if (id == R.id.action_unfold) {
|
||||||
{
|
boolean shouldShow = !mAdapter.showingDetails();
|
||||||
|
|
||||||
|
if (shouldShow) {
|
||||||
|
inputItem.setIcon(R.drawable.ic_baseline_unfold_less_24);
|
||||||
|
inputItem.setTitle(R.string.action_hide_details);
|
||||||
|
} else {
|
||||||
|
inputItem.setIcon(R.drawable.ic_baseline_unfold_more_24);
|
||||||
|
inputItem.setTitle(R.string.action_show_details);
|
||||||
|
}
|
||||||
|
|
||||||
|
mAdapter.showDetails(shouldShow);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == R.id.action_sort) {
|
||||||
|
TabLayout.Tab tab = ((TabLayout) findViewById(R.id.groups)).getTabAt(selectedTab);
|
||||||
|
AtomicInteger currentIndex = new AtomicInteger();
|
||||||
|
List<DBHelper.LoyaltyCardOrder> loyaltyCardOrders = Arrays.asList(DBHelper.LoyaltyCardOrder.values());
|
||||||
|
for (int i = 0; i < loyaltyCardOrders.size(); i++) {
|
||||||
|
if (mOrder == loyaltyCardOrders.get(i)) {
|
||||||
|
currentIndex.set(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
|
||||||
|
builder.setTitle(R.string.sort_by);
|
||||||
|
|
||||||
|
final View customLayout = getLayoutInflater().inflate(R.layout.sorting_option, null);
|
||||||
|
builder.setView(customLayout);
|
||||||
|
|
||||||
|
CheckBox ch = (CheckBox) customLayout.findViewById(R.id.checkBox_reverse);
|
||||||
|
ch.setChecked(mOrderDirection == DBHelper.LoyaltyCardOrderDirection.Descending);
|
||||||
|
|
||||||
|
builder.setSingleChoiceItems(R.array.sort_types_array, currentIndex.get(), (dialog, which) -> currentIndex.set(which));
|
||||||
|
|
||||||
|
builder.setPositiveButton(R.string.sort, (dialog, which) -> {
|
||||||
|
if (ch.isChecked()) {
|
||||||
|
setSort(loyaltyCardOrders.get(currentIndex.get()), DBHelper.LoyaltyCardOrderDirection.Descending);
|
||||||
|
} else {
|
||||||
|
setSort(loyaltyCardOrders.get(currentIndex.get()), DBHelper.LoyaltyCardOrderDirection.Ascending);
|
||||||
|
}
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
|
||||||
|
|
||||||
|
AlertDialog dialog = builder.create();
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == R.id.action_manage_groups) {
|
||||||
Intent i = new Intent(getApplicationContext(), ManageGroupsActivity.class);
|
Intent i = new Intent(getApplicationContext(), ManageGroupsActivity.class);
|
||||||
startActivityForResult(i, Utils.MAIN_REQUEST);
|
startActivity(i);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id == R.id.action_import_export)
|
if (id == R.id.action_import_export) {
|
||||||
{
|
|
||||||
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
|
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
|
||||||
startActivityForResult(i, Utils.MAIN_REQUEST);
|
startActivity(i);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id == R.id.action_settings)
|
if (id == R.id.action_settings) {
|
||||||
{
|
|
||||||
Intent i = new Intent(getApplicationContext(), SettingsActivity.class);
|
Intent i = new Intent(getApplicationContext(), SettingsActivity.class);
|
||||||
startActivityForResult(i, Utils.MAIN_REQUEST);
|
mSettingsLauncher.launch(i);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(id == R.id.action_privacy_policy)
|
if (id == R.id.action_about) {
|
||||||
{
|
|
||||||
openPrivacyPolicy();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id == R.id.action_about)
|
|
||||||
{
|
|
||||||
Intent i = new Intent(getApplicationContext(), AboutActivity.class);
|
Intent i = new Intent(getApplicationContext(), AboutActivity.class);
|
||||||
startActivityForResult(i, Utils.MAIN_REQUEST);
|
startActivity(i);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onOptionsItemSelected(inputItem);
|
return super.onOptionsItemSelected(inputItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static boolean isDarkModeEnabled(Context inputContext)
|
private void setSort(DBHelper.LoyaltyCardOrder order, DBHelper.LoyaltyCardOrderDirection direction) {
|
||||||
{
|
// Update values
|
||||||
Configuration config = inputContext.getResources().getConfiguration();
|
mOrder = order;
|
||||||
int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
mOrderDirection = direction;
|
||||||
return (currentNightMode == Configuration.UI_MODE_NIGHT_YES);
|
|
||||||
|
// Store in Shared Preference to restore next app launch
|
||||||
|
SharedPreferences sortPref = getApplicationContext().getSharedPreferences(
|
||||||
|
getString(R.string.sharedpreference_sort),
|
||||||
|
Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor sortPrefEditor = sortPref.edit();
|
||||||
|
sortPrefEditor.putString(getString(R.string.sharedpreference_sort_order), order.name());
|
||||||
|
sortPrefEditor.putString(getString(R.string.sharedpreference_sort_direction), direction.name());
|
||||||
|
sortPrefEditor.apply();
|
||||||
|
|
||||||
|
// Update card list
|
||||||
|
updateLoyaltyCardList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -551,6 +596,12 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||||
|
mGestureDetector.onTouchEvent(ev);
|
||||||
|
return super.dispatchTouchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||||
Log.d(TAG, "On fling");
|
Log.d(TAG, "On fling");
|
||||||
@@ -566,9 +617,10 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
}
|
}
|
||||||
|
|
||||||
Integer currentTab = groupsTabLayout.getSelectedTabPosition();
|
Integer currentTab = groupsTabLayout.getSelectedTabPosition();
|
||||||
|
Log.d("onFling", "Current Tab " + currentTab);
|
||||||
// Swipe right
|
// Swipe right
|
||||||
if (velocityX < -150) {
|
if (velocityX < -150) {
|
||||||
|
Log.d("onFling", "Right Swipe detected " + velocityX);
|
||||||
Integer nextTab = currentTab + 1;
|
Integer nextTab = currentTab + 1;
|
||||||
|
|
||||||
if (nextTab == groupsTabLayout.getTabCount()) {
|
if (nextTab == groupsTabLayout.getTabCount()) {
|
||||||
@@ -582,6 +634,7 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
|
|
||||||
// Swipe left
|
// Swipe left
|
||||||
if (velocityX > 150) {
|
if (velocityX > 150) {
|
||||||
|
Log.d("onFling", "Left Swipe detected " + velocityX);
|
||||||
Integer nextTab = currentTab - 1;
|
Integer nextTab = currentTab - 1;
|
||||||
|
|
||||||
if (nextTab < 0) {
|
if (nextTab < 0) {
|
||||||
@@ -597,29 +650,25 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRowLongClicked(int inputPosition)
|
public void onRowLongClicked(int inputPosition) {
|
||||||
{
|
|
||||||
enableActionMode(inputPosition);
|
enableActionMode(inputPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableActionMode(int inputPosition)
|
private void enableActionMode(int inputPosition) {
|
||||||
{
|
if (mCurrentActionMode == null) {
|
||||||
if (mCurrentActionMode == null)
|
|
||||||
{
|
|
||||||
mCurrentActionMode = startSupportActionMode(mCurrentActionModeCallback);
|
mCurrentActionMode = startSupportActionMode(mCurrentActionModeCallback);
|
||||||
}
|
}
|
||||||
toggleSelection(inputPosition);
|
toggleSelection(inputPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleSelection(int inputPosition)
|
private void toggleSelection(int inputPosition) {
|
||||||
{
|
|
||||||
mAdapter.toggleSelection(inputPosition);
|
mAdapter.toggleSelection(inputPosition);
|
||||||
int count = mAdapter.getSelectedItemCount();
|
int count = mAdapter.getSelectedItemCount();
|
||||||
|
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
mCurrentActionMode.finish();
|
mCurrentActionMode.finish();
|
||||||
} else {
|
} else {
|
||||||
mCurrentActionMode.setTitle("Selected: " + count + " Cards");
|
mCurrentActionMode.setTitle(getResources().getQuantityString(R.plurals.selectedCardCount, count, count));
|
||||||
|
|
||||||
MenuItem editItem = mCurrentActionMode.getMenu().findItem(R.id.action_edit);
|
MenuItem editItem = mCurrentActionMode.getMenu().findItem(R.id.action_edit);
|
||||||
if (count == 1) {
|
if (count == 1) {
|
||||||
@@ -635,29 +684,25 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onIconClicked(int inputPosition)
|
public void onRowClicked(int inputPosition) {
|
||||||
{
|
if (mAdapter.getSelectedItemCount() > 0) {
|
||||||
if (mCurrentActionMode == null)
|
|
||||||
{
|
|
||||||
mCurrentActionMode = startSupportActionMode(mCurrentActionModeCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSelection(inputPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRowClicked(int inputPosition)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (mAdapter.getSelectedItemCount() > 0)
|
|
||||||
{
|
|
||||||
enableActionMode(inputPosition);
|
enableActionMode(inputPosition);
|
||||||
}
|
} else {
|
||||||
else
|
// FIXME
|
||||||
{
|
//
|
||||||
Cursor selected = mAdapter.getCursor();
|
// There is a really nasty edge case that can happen when someone taps a card but right
|
||||||
selected.moveToPosition(inputPosition);
|
// after it swipes (very small window, hard to reproduce). The cursor gets replaced and
|
||||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
|
// may not have a card at the ID number that is returned from onRowClicked.
|
||||||
|
//
|
||||||
|
// The proper fix, obviously, would involve makes sure an onFling can't happen while a
|
||||||
|
// click is being processed. Sadly, I have not yet found a way to make that possible.
|
||||||
|
LoyaltyCard loyaltyCard;
|
||||||
|
try {
|
||||||
|
loyaltyCard = mAdapter.getCard(inputPosition);
|
||||||
|
} catch (CursorIndexOutOfBoundsException e) {
|
||||||
|
Log.w(TAG, "Prevented crash from tap + swipe on ID " + inputPosition + ": " + e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Intent i = new Intent(this, LoyaltyCardViewActivity.class);
|
Intent i = new Intent(this, LoyaltyCardViewActivity.class);
|
||||||
i.setAction("");
|
i.setAction("");
|
||||||
@@ -665,9 +710,9 @@ public class MainActivity extends AppCompatActivity implements LoyaltyCardCursor
|
|||||||
b.putInt("id", loyaltyCard.id);
|
b.putInt("id", loyaltyCard.id);
|
||||||
i.putExtras(b);
|
i.putExtras(b);
|
||||||
|
|
||||||
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard, i);
|
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard);
|
||||||
|
|
||||||
startActivityForResult(i, Utils.MAIN_REQUEST);
|
startActivity(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
219
app/src/main/java/protect/card_locker/ManageGroupActivity.java
Normal file
219
app/src/main/java/protect/card_locker/ManageGroupActivity.java
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
public class ManageGroupActivity extends CatimaAppCompatActivity implements ManageGroupCursorAdapter.CardAdapterListener {
|
||||||
|
|
||||||
|
private SQLiteDatabase mDatabase;
|
||||||
|
private ManageGroupCursorAdapter mAdapter;
|
||||||
|
|
||||||
|
private final String SAVE_INSTANCE_ADAPTER_STATE = "adapterState";
|
||||||
|
private final String SAVE_INSTANCE_CURRENT_GROUP_NAME = "currentGroupName";
|
||||||
|
|
||||||
|
protected Group mGroup = null;
|
||||||
|
private RecyclerView mCardList;
|
||||||
|
private TextView mHelpText;
|
||||||
|
private EditText mGroupNameText;
|
||||||
|
|
||||||
|
private boolean mGroupNameNotInUse;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle inputSavedInstanceState) {
|
||||||
|
super.onCreate(inputSavedInstanceState);
|
||||||
|
setContentView(R.layout.activity_manage_group);
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
mDatabase = new DBHelper(this).getWritableDatabase();
|
||||||
|
|
||||||
|
mHelpText = findViewById(R.id.helpText);
|
||||||
|
mCardList = findViewById(R.id.list);
|
||||||
|
FloatingActionButton saveButton = findViewById(R.id.fabSave);
|
||||||
|
|
||||||
|
mGroupNameText = findViewById(R.id.editTextGroupName);
|
||||||
|
|
||||||
|
mGroupNameText.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {
|
||||||
|
mGroupNameNotInUse = true;
|
||||||
|
mGroupNameText.setError(null);
|
||||||
|
String currentGroupName = mGroupNameText.getText().toString().trim();
|
||||||
|
if (currentGroupName.length() == 0) {
|
||||||
|
mGroupNameText.setError(getResources().getText(R.string.group_name_is_empty));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mGroup._id.equals(currentGroupName)) {
|
||||||
|
if (DBHelper.getGroup(mDatabase, currentGroupName) != null) {
|
||||||
|
mGroupNameNotInUse = false;
|
||||||
|
mGroupNameText.setError(getResources().getText(R.string.group_name_already_in_use));
|
||||||
|
} else {
|
||||||
|
mGroupNameNotInUse = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
String groupId = intent.getStringExtra("group");
|
||||||
|
if (groupId == null) {
|
||||||
|
throw (new IllegalArgumentException("this activity expects a group loaded into it's intent"));
|
||||||
|
}
|
||||||
|
Log.d("groupId", "groupId: " + groupId);
|
||||||
|
mGroup = DBHelper.getGroup(mDatabase, groupId);
|
||||||
|
if (mGroup == null) {
|
||||||
|
throw (new IllegalArgumentException("cannot load group " + groupId + " from database"));
|
||||||
|
}
|
||||||
|
mGroupNameText.setText(mGroup._id);
|
||||||
|
setTitle(getString(R.string.editGroup, mGroup._id));
|
||||||
|
mAdapter = new ManageGroupCursorAdapter(this, null, this, mGroup);
|
||||||
|
mCardList.setAdapter(mAdapter);
|
||||||
|
registerForContextMenu(mCardList);
|
||||||
|
|
||||||
|
if (inputSavedInstanceState != null) {
|
||||||
|
mAdapter.importInGroupState(integerArrayToAdapterState(inputSavedInstanceState.getIntegerArrayList(SAVE_INSTANCE_ADAPTER_STATE)));
|
||||||
|
mGroupNameText.setText(inputSavedInstanceState.getString(SAVE_INSTANCE_CURRENT_GROUP_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
if (actionBar == null) {
|
||||||
|
throw (new RuntimeException("mActionBar is not expected to be null here"));
|
||||||
|
}
|
||||||
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
|
actionBar.setDisplayShowHomeEnabled(true);
|
||||||
|
|
||||||
|
saveButton.setOnClickListener(v -> {
|
||||||
|
String currentGroupName = mGroupNameText.getText().toString().trim();
|
||||||
|
if (!currentGroupName.equals(mGroup._id)) {
|
||||||
|
if (currentGroupName.length() == 0) {
|
||||||
|
Toast.makeText(getApplicationContext(), R.string.group_name_is_empty, Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mGroupNameNotInUse) {
|
||||||
|
Toast.makeText(getApplicationContext(), R.string.group_name_already_in_use, Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mAdapter.commitToDatabase();
|
||||||
|
if (!currentGroupName.equals(mGroup._id)) {
|
||||||
|
DBHelper.updateGroup(mDatabase, mGroup._id, currentGroupName);
|
||||||
|
}
|
||||||
|
Toast.makeText(getApplicationContext(), R.string.group_updated, Toast.LENGTH_SHORT).show();
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
// this setText is here because content_main.xml is reused from main activity
|
||||||
|
mHelpText.setText(getResources().getText(R.string.noGiftCardsGroup));
|
||||||
|
updateLoyaltyCardList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<Integer> adapterStateToIntegerArray(HashMap<Integer, Boolean> adapterState) {
|
||||||
|
ArrayList<Integer> ret = new ArrayList<>(adapterState.size() * 2);
|
||||||
|
for (Map.Entry<Integer, Boolean> entry : adapterState.entrySet()) {
|
||||||
|
ret.add(entry.getKey());
|
||||||
|
ret.add(entry.getValue() ? 1 : 0);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashMap<Integer, Boolean> integerArrayToAdapterState(ArrayList<Integer> in) {
|
||||||
|
HashMap<Integer, Boolean> ret = new HashMap<>();
|
||||||
|
if (in.size() % 2 != 0) {
|
||||||
|
throw (new RuntimeException("failed restoring adapterState from integer array list"));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < in.size(); i += 2) {
|
||||||
|
ret.put(in.get(i), in.get(i + 1) == 1);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
|
outState.putIntegerArrayList(SAVE_INSTANCE_ADAPTER_STATE, adapterStateToIntegerArray(mAdapter.exportInGroupState()));
|
||||||
|
outState.putString(SAVE_INSTANCE_CURRENT_GROUP_NAME, mGroupNameText.getText().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateLoyaltyCardList() {
|
||||||
|
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase));
|
||||||
|
|
||||||
|
if (mAdapter.getItemCount() == 0) {
|
||||||
|
mCardList.setVisibility(View.GONE);
|
||||||
|
mHelpText.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
mCardList.setVisibility(View.VISIBLE);
|
||||||
|
mHelpText.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void leaveWithoutSaving() {
|
||||||
|
if (hasChanged()) {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(ManageGroupActivity.this);
|
||||||
|
builder.setTitle(R.string.leaveWithoutSaveTitle);
|
||||||
|
builder.setMessage(R.string.leaveWithoutSaveConfirmation);
|
||||||
|
builder.setPositiveButton(R.string.confirm, (dialog, which) -> finish());
|
||||||
|
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
|
||||||
|
AlertDialog dialog = builder.create();
|
||||||
|
dialog.show();
|
||||||
|
} else {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
leaveWithoutSaving();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSupportNavigateUp() {
|
||||||
|
onBackPressed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasChanged() {
|
||||||
|
return mAdapter.hasChanged() || !mGroup._id.equals(mGroupNameText.getText().toString().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRowLongClicked(int inputPosition) {
|
||||||
|
mAdapter.toggleSelection(inputPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRowClicked(int inputPosition) {
|
||||||
|
mAdapter.toggleSelection(inputPosition);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ManageGroupCursorAdapter extends LoyaltyCardCursorAdapter {
|
||||||
|
private HashMap<Integer, Integer> mIndexCardMap;
|
||||||
|
private HashMap<Integer, Boolean> mInGroupOverlay;
|
||||||
|
private HashMap<Integer, Boolean> mIsLoyaltyCardInGroupCache;
|
||||||
|
private HashMap<Integer, List<Group>> mGetGroupCache;
|
||||||
|
final private Group mGroup;
|
||||||
|
final private SQLiteDatabase mDatabase;
|
||||||
|
|
||||||
|
public ManageGroupCursorAdapter(Context inputContext, Cursor inputCursor, CardAdapterListener inputListener, Group group) {
|
||||||
|
super(inputContext, inputCursor, inputListener);
|
||||||
|
mGroup = new Group(group._id, group.order);
|
||||||
|
mInGroupOverlay = new HashMap<>();
|
||||||
|
mDatabase = new DBHelper(inputContext).getWritableDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void swapCursor(Cursor inputCursor) {
|
||||||
|
super.swapCursor(inputCursor);
|
||||||
|
mIndexCardMap = new HashMap<>();
|
||||||
|
mIsLoyaltyCardInGroupCache = new HashMap<>();
|
||||||
|
mGetGroupCache = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(LoyaltyCardListItemViewHolder inputHolder, Cursor inputCursor) {
|
||||||
|
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(inputCursor);
|
||||||
|
Boolean overlayValue = mInGroupOverlay.get(loyaltyCard.id);
|
||||||
|
if ((overlayValue != null ? overlayValue : isLoyaltyCardInGroup(loyaltyCard.id))) {
|
||||||
|
mAnimationItemsIndex.put(inputCursor.getPosition(), true);
|
||||||
|
mSelectedItems.put(inputCursor.getPosition(), true);
|
||||||
|
}
|
||||||
|
mIndexCardMap.put(inputCursor.getPosition(), loyaltyCard.id);
|
||||||
|
super.onBindViewHolder(inputHolder, inputCursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Group> getGroups(int cardId) {
|
||||||
|
List<Group> cache = mGetGroupCache.get(cardId);
|
||||||
|
if (cache != null) {
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
List<Group> groups = DBHelper.getLoyaltyCardGroups(mDatabase, cardId);
|
||||||
|
mGetGroupCache.put(cardId, groups);
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLoyaltyCardInGroup(int cardId) {
|
||||||
|
Boolean cache = mIsLoyaltyCardInGroupCache.get(cardId);
|
||||||
|
if (cache != null) {
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
List<Group> groups = getGroups(cardId);
|
||||||
|
if (groups.contains(mGroup)) {
|
||||||
|
mIsLoyaltyCardInGroupCache.put(cardId, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
mIsLoyaltyCardInGroupCache.put(cardId, false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toggleSelection(int inputPosition) {
|
||||||
|
super.toggleSelection(inputPosition);
|
||||||
|
Integer cardId = mIndexCardMap.get(inputPosition);
|
||||||
|
if (cardId == null) {
|
||||||
|
throw (new RuntimeException("cardId should not be null here"));
|
||||||
|
}
|
||||||
|
Boolean overlayValue = mInGroupOverlay.get(cardId);
|
||||||
|
if (overlayValue == null) {
|
||||||
|
mInGroupOverlay.put(cardId, !isLoyaltyCardInGroup(cardId));
|
||||||
|
} else {
|
||||||
|
mInGroupOverlay.remove(cardId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasChanged() {
|
||||||
|
return mInGroupOverlay.size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void commitToDatabase() {
|
||||||
|
for (Map.Entry<Integer, Boolean> entry : mInGroupOverlay.entrySet()) {
|
||||||
|
int cardId = entry.getKey();
|
||||||
|
List<Group> groups = getGroups(cardId);
|
||||||
|
if (entry.getValue()) {
|
||||||
|
groups.add(mGroup);
|
||||||
|
} else {
|
||||||
|
groups.remove(mGroup);
|
||||||
|
}
|
||||||
|
DBHelper.setLoyaltyCardGroups(mDatabase, cardId, groups);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void importInGroupState(HashMap<Integer, Boolean> cardIdInGroupMap) {
|
||||||
|
mInGroupOverlay = new HashMap<>(cardIdInGroupMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<Integer, Boolean> exportInGroupState() {
|
||||||
|
return new HashMap<>(mInGroupOverlay);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.database.Cursor;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
@@ -20,40 +19,54 @@ import java.util.List;
|
|||||||
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
public class ManageGroupsActivity extends AppCompatActivity
|
public class ManageGroupsActivity extends CatimaAppCompatActivity implements GroupCursorAdapter.GroupAdapterListener {
|
||||||
{
|
|
||||||
private static final String TAG = "Catima";
|
private static final String TAG = "Catima";
|
||||||
|
|
||||||
private final DBHelper db = new DBHelper(this);
|
private SQLiteDatabase mDatabase;
|
||||||
|
private TextView mHelpText;
|
||||||
|
private RecyclerView mGroupList;
|
||||||
|
GroupCursorAdapter mAdapter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState)
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
{
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
setTitle(R.string.groups);
|
||||||
setContentView(R.layout.manage_groups_activity);
|
setContentView(R.layout.manage_groups_activity);
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
if(actionBar != null)
|
if (actionBar != null) {
|
||||||
{
|
|
||||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGroupList();
|
mDatabase = new DBHelper(this).getWritableDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
updateGroupList();
|
|
||||||
|
|
||||||
FloatingActionButton addButton = findViewById(R.id.fabAdd);
|
FloatingActionButton addButton = findViewById(R.id.fabAdd);
|
||||||
addButton.setOnClickListener(v -> createGroup());
|
addButton.setOnClickListener(v -> createGroup());
|
||||||
addButton.bringToFront();
|
addButton.bringToFront();
|
||||||
|
|
||||||
|
mGroupList = findViewById(R.id.list);
|
||||||
|
mHelpText = findViewById(R.id.helpText);
|
||||||
|
|
||||||
|
// Init group list
|
||||||
|
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
|
||||||
|
mGroupList.setLayoutManager(mLayoutManager);
|
||||||
|
mGroupList.setItemAnimator(new DefaultItemAnimator());
|
||||||
|
|
||||||
|
mAdapter = new GroupCursorAdapter(this, null, this);
|
||||||
|
mGroupList.setAdapter(mAdapter);
|
||||||
|
|
||||||
|
updateGroupList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -61,33 +74,21 @@ public class ManageGroupsActivity extends AppCompatActivity
|
|||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateGroupList()
|
private void updateGroupList() {
|
||||||
{
|
mAdapter.swapCursor(DBHelper.getGroupCursor(mDatabase));
|
||||||
final ListView groupList = findViewById(R.id.list);
|
|
||||||
final TextView helpText = findViewById(R.id.helpText);
|
|
||||||
final DBHelper db = new DBHelper(this);
|
|
||||||
|
|
||||||
if(db.getGroupCount() > 0)
|
if (DBHelper.getGroupCount(mDatabase) == 0) {
|
||||||
{
|
mGroupList.setVisibility(View.GONE);
|
||||||
groupList.setVisibility(View.VISIBLE);
|
mHelpText.setVisibility(View.VISIBLE);
|
||||||
helpText.setVisibility(View.GONE);
|
|
||||||
}
|
return;
|
||||||
else
|
|
||||||
{
|
|
||||||
groupList.setVisibility(View.GONE);
|
|
||||||
helpText.setVisibility(View.VISIBLE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Cursor groupCursor = db.getGroupCursor();
|
mGroupList.setVisibility(View.VISIBLE);
|
||||||
|
mHelpText.setVisibility(View.GONE);
|
||||||
final GroupCursorAdapter adapter = new GroupCursorAdapter(this, groupCursor);
|
|
||||||
groupList.setAdapter(adapter);
|
|
||||||
|
|
||||||
registerForContextMenu(groupList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void invalidateHomescreenActiveTab()
|
private void invalidateHomescreenActiveTab() {
|
||||||
{
|
|
||||||
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
|
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
|
||||||
getString(R.string.sharedpreference_active_tab),
|
getString(R.string.sharedpreference_active_tab),
|
||||||
Context.MODE_PRIVATE);
|
Context.MODE_PRIVATE);
|
||||||
@@ -97,8 +98,7 @@ public class ManageGroupsActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item)
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
{
|
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
|
|
||||||
if (id == android.R.id.home) {
|
if (id == android.R.id.home) {
|
||||||
@@ -108,121 +108,44 @@ public class ManageGroupsActivity extends AppCompatActivity
|
|||||||
return super.onOptionsItemSelected(item);
|
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() {
|
private void createGroup() {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.AlertDialogTheme);
|
||||||
builder.setTitle(R.string.enter_group_name);
|
builder.setTitle(R.string.enter_group_name);
|
||||||
final EditText input = new EditText(this);
|
final EditText input = new EditText(this);
|
||||||
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||||
builder.setView(input);
|
builder.setView(input);
|
||||||
|
|
||||||
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
|
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
|
||||||
@Override
|
String inputString = input.getText().toString().trim();
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
if (inputString.length() == 0) {
|
||||||
db.insertGroup(input.getText().toString());
|
Toast.makeText(getApplicationContext(), R.string.group_name_is_empty, Toast.LENGTH_SHORT).show();
|
||||||
updateGroupList();
|
return;
|
||||||
}
|
}
|
||||||
});
|
if (DBHelper.getGroup(mDatabase, inputString) != null) {
|
||||||
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
|
Toast.makeText(getApplicationContext(), R.string.group_name_already_in_use, Toast.LENGTH_SHORT).show();
|
||||||
@Override
|
return;
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
dialog.cancel();
|
|
||||||
}
|
}
|
||||||
|
DBHelper.insertGroup(mDatabase, inputString);
|
||||||
|
updateGroupList();
|
||||||
});
|
});
|
||||||
|
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
||||||
AlertDialog dialog = builder.create();
|
AlertDialog dialog = builder.create();
|
||||||
dialog.show();
|
dialog.show();
|
||||||
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||||
input.requestFocus();
|
input.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getGroupname(View view) {
|
private String getGroupName(View view) {
|
||||||
LinearLayout parentRow = (LinearLayout) view.getParent().getParent();
|
TextView groupNameTextView = view.findViewById(R.id.name);
|
||||||
TextView groupNameTextView = parentRow.findViewById(R.id.name);
|
|
||||||
return (String) groupNameTextView.getText();
|
return (String) groupNameTextView.getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void moveGroup(View view, boolean up) {
|
private void moveGroup(View view, boolean up) {
|
||||||
final String groupName = getGroupname(view);
|
List<Group> groups = DBHelper.getGroups(mDatabase);
|
||||||
|
final String groupName = getGroupName(view);
|
||||||
|
|
||||||
List<Group> groups = db.getGroups();
|
int currentIndex = DBHelper.getGroup(mDatabase, groupName).order;
|
||||||
|
int newIndex;
|
||||||
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
|
// Reinsert group in correct position
|
||||||
if (up) {
|
if (up) {
|
||||||
@@ -240,7 +163,7 @@ public class ManageGroupsActivity extends AppCompatActivity
|
|||||||
groups.add(newIndex, group);
|
groups.add(newIndex, group);
|
||||||
|
|
||||||
// Update database
|
// Update database
|
||||||
db.reorderGroups(groups);
|
DBHelper.reorderGroups(mDatabase, groups);
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
updateGroupList();
|
updateGroupList();
|
||||||
@@ -248,4 +171,40 @@ public class ManageGroupsActivity extends AppCompatActivity
|
|||||||
// Ordering may have changed, so invalidate
|
// Ordering may have changed, so invalidate
|
||||||
invalidateHomescreenActiveTab();
|
invalidateHomescreenActiveTab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMoveDownButtonClicked(View view) {
|
||||||
|
moveGroup(view, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMoveUpButtonClicked(View view) {
|
||||||
|
moveGroup(view, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEditButtonClicked(View view) {
|
||||||
|
Intent intent = new Intent(this, ManageGroupActivity.class);
|
||||||
|
intent.putExtra("group", getGroupName(view));
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeleteButtonClicked(View view) {
|
||||||
|
final String groupName = getGroupName(view);
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setTitle(R.string.deleteConfirmationGroup);
|
||||||
|
builder.setMessage(groupName);
|
||||||
|
|
||||||
|
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
|
||||||
|
DBHelper.deleteGroup(mDatabase, groupName);
|
||||||
|
updateGroupList();
|
||||||
|
// Delete may change ordering, so invalidate
|
||||||
|
invalidateHomescreenActiveTab();
|
||||||
|
});
|
||||||
|
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
||||||
|
AlertDialog dialog = builder.create();
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
29
app/src/main/java/protect/card_locker/NotificationType.java
Normal file
29
app/src/main/java/protect/card_locker/NotificationType.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
public class NotificationType {
|
||||||
|
private final static String NOTIFICATION_IMPORT_EXPORT = "NOTIFICATION_IMPORT_EXPORT";
|
||||||
|
|
||||||
|
public static String getImportExportChannel(Context context) {
|
||||||
|
createNotificationChannel(context, NOTIFICATION_IMPORT_EXPORT, context.getString(R.string.importExport), context.getString(R.string.importExportDescription), NotificationManager.IMPORTANCE_HIGH);
|
||||||
|
|
||||||
|
return NOTIFICATION_IMPORT_EXPORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createNotificationChannel(Context context, String channelId, CharSequence name, String description, int importance) {
|
||||||
|
// Create the NotificationType, but only on API 26+ because
|
||||||
|
// the NotificationType class is new and not in the support library
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationChannel channel = new NotificationChannel(channelId, name, importance);
|
||||||
|
channel.setDescription(description);
|
||||||
|
// Register the channel with the system; you can't change the importance
|
||||||
|
// or other notification behaviors after this
|
||||||
|
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
|
||||||
|
notificationManager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import android.view.KeyEvent;
|
|||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.google.zxing.ResultPoint;
|
import com.google.zxing.ResultPoint;
|
||||||
import com.google.zxing.client.android.Intents;
|
import com.google.zxing.client.android.Intents;
|
||||||
@@ -19,50 +20,62 @@ import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom Scannner Activity extending from Activity to display a custom layout form scanner view.
|
* Custom Scannner Activity extending from Activity to display a custom layout form scanner view.
|
||||||
*
|
* <p>
|
||||||
* Based on https://github.com/journeyapps/zxing-android-embedded/blob/0fdfbce9fb3285e985bad9971c5f7c0a7a334e7b/sample/src/main/java/example/zxing/CustomScannerActivity.java
|
* 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
|
* originally licensed under Apache 2.0
|
||||||
*/
|
*/
|
||||||
public class ScanActivity extends AppCompatActivity {
|
public class ScanActivity extends CatimaAppCompatActivity {
|
||||||
private static final String TAG = "Catima";
|
private static final String TAG = "Catima";
|
||||||
|
|
||||||
private CaptureManager capture;
|
private CaptureManager capture;
|
||||||
private DecoratedBarcodeView barcodeScannerView;
|
private DecoratedBarcodeView barcodeScannerView;
|
||||||
|
|
||||||
private String cardId;
|
private String cardId;
|
||||||
|
private String addGroup;
|
||||||
private boolean torch = false;
|
private boolean torch = false;
|
||||||
|
|
||||||
|
private ActivityResultLauncher<Intent> manualAddLauncher;
|
||||||
|
// can't use the pre-made contract because that launches the file manager for image type instead of gallery
|
||||||
|
private ActivityResultLauncher<Intent> photoPickerLauncher;
|
||||||
|
|
||||||
private void extractIntentFields(Intent intent) {
|
private void extractIntentFields(Intent intent) {
|
||||||
final Bundle b = intent.getExtras();
|
final Bundle b = intent.getExtras();
|
||||||
cardId = b != null ? b.getString("cardId") : null;
|
cardId = b != null ? b.getString(LoyaltyCardEditActivity.BUNDLE_CARDID) : null;
|
||||||
|
addGroup = b != null ? b.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) : null;
|
||||||
Log.d(TAG, "Scan activity: id=" + cardId);
|
Log.d(TAG, "Scan activity: id=" + cardId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
setTitle(R.string.scanCardBarcode);
|
||||||
setContentView(R.layout.scan_activity);
|
setContentView(R.layout.scan_activity);
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
if(actionBar != null)
|
if (actionBar != null) {
|
||||||
{
|
|
||||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
extractIntentFields(getIntent());
|
extractIntentFields(getIntent());
|
||||||
|
|
||||||
|
manualAddLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.SELECT_BARCODE_REQUEST, result.getResultCode(), result.getData()));
|
||||||
|
photoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_IMAGE_FILE, result.getResultCode(), result.getData()));
|
||||||
|
findViewById(R.id.add_from_image).setOnClickListener(this::addFromImage);
|
||||||
|
findViewById(R.id.add_manually).setOnClickListener(this::addManually);
|
||||||
|
|
||||||
barcodeScannerView = findViewById(R.id.zxing_barcode_scanner);
|
barcodeScannerView = findViewById(R.id.zxing_barcode_scanner);
|
||||||
|
|
||||||
// Even though we do the actual decoding with the barcodeScannerView
|
// Even though we do the actual decoding with the barcodeScannerView
|
||||||
// CaptureManager needs to be running to show the camera and scanning bar
|
// CaptureManager needs to be running to show the camera and scanning bar
|
||||||
capture = new CaptureManager(this, barcodeScannerView);
|
capture = new CatimaCaptureManager(this, barcodeScannerView);
|
||||||
Intent captureIntent = new Intent();
|
Intent captureIntent = new Intent();
|
||||||
Bundle captureIntentBundle = new Bundle();
|
Bundle captureIntentBundle = new Bundle();
|
||||||
captureIntentBundle.putBoolean(Intents.Scan.BEEP_ENABLED, false);
|
captureIntentBundle.putBoolean(Intents.Scan.BEEP_ENABLED, false);
|
||||||
@@ -75,7 +88,10 @@ public class ScanActivity extends AppCompatActivity {
|
|||||||
Intent scanResult = new Intent();
|
Intent scanResult = new Intent();
|
||||||
Bundle scanResultBundle = new Bundle();
|
Bundle scanResultBundle = new Bundle();
|
||||||
scanResultBundle.putString(BarcodeSelectorActivity.BARCODE_CONTENTS, result.getText());
|
scanResultBundle.putString(BarcodeSelectorActivity.BARCODE_CONTENTS, result.getText());
|
||||||
scanResultBundle.putString(BarcodeSelectorActivity.BARCODE_FORMAT, result.getBarcodeFormat().toString());
|
scanResultBundle.putString(BarcodeSelectorActivity.BARCODE_FORMAT, result.getBarcodeFormat().name());
|
||||||
|
if (addGroup != null) {
|
||||||
|
scanResultBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
|
||||||
|
}
|
||||||
scanResult.putExtras(scanResultBundle);
|
scanResult.putExtras(scanResultBundle);
|
||||||
ScanActivity.this.setResult(RESULT_OK, scanResult);
|
ScanActivity.this.setResult(RESULT_OK, scanResult);
|
||||||
finish();
|
finish();
|
||||||
@@ -118,8 +134,7 @@ public class ScanActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu)
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
{
|
|
||||||
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
|
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
|
||||||
getMenuInflater().inflate(R.menu.scan_menu, menu);
|
getMenuInflater().inflate(R.menu.scan_menu, menu);
|
||||||
}
|
}
|
||||||
@@ -130,15 +145,12 @@ public class ScanActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item)
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
{
|
if (item.getItemId() == android.R.id.home) {
|
||||||
if (item.getItemId() == android.R.id.home)
|
|
||||||
{
|
|
||||||
setResult(Activity.RESULT_CANCELED);
|
setResult(Activity.RESULT_CANCELED);
|
||||||
finish();
|
finish();
|
||||||
return true;
|
return true;
|
||||||
} else if (item.getItemId() == R.id.action_toggle_flashlight)
|
} else if (item.getItemId() == R.id.action_toggle_flashlight) {
|
||||||
{
|
|
||||||
if (torch) {
|
if (torch) {
|
||||||
torch = false;
|
torch = false;
|
||||||
barcodeScannerView.setTorchOff();
|
barcodeScannerView.setTorchOff();
|
||||||
@@ -155,18 +167,26 @@ public class ScanActivity extends AppCompatActivity {
|
|||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void handleActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent intent)
|
|
||||||
{
|
|
||||||
super.onActivityResult(requestCode, resultCode, intent);
|
super.onActivityResult(requestCode, resultCode, intent);
|
||||||
|
|
||||||
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
|
BarcodeValues barcodeValues;
|
||||||
|
|
||||||
|
try {
|
||||||
|
barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!barcodeValues.isEmpty()) {
|
if (!barcodeValues.isEmpty()) {
|
||||||
Intent manualResult = new Intent();
|
Intent manualResult = new Intent();
|
||||||
Bundle manualResultBundle = new Bundle();
|
Bundle manualResultBundle = new Bundle();
|
||||||
manualResultBundle.putString(BarcodeSelectorActivity.BARCODE_CONTENTS, barcodeValues.content());
|
manualResultBundle.putString(BarcodeSelectorActivity.BARCODE_CONTENTS, barcodeValues.content());
|
||||||
manualResultBundle.putString(BarcodeSelectorActivity.BARCODE_FORMAT, barcodeValues.format());
|
manualResultBundle.putString(BarcodeSelectorActivity.BARCODE_FORMAT, barcodeValues.format());
|
||||||
|
if (addGroup != null) {
|
||||||
|
manualResultBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
|
||||||
|
}
|
||||||
manualResult.putExtras(manualResultBundle);
|
manualResult.putExtras(manualResultBundle);
|
||||||
ScanActivity.this.setResult(RESULT_OK, manualResult);
|
ScanActivity.this.setResult(RESULT_OK, manualResult);
|
||||||
finish();
|
finish();
|
||||||
@@ -180,12 +200,12 @@ public class ScanActivity extends AppCompatActivity {
|
|||||||
b.putString("initialCardId", cardId);
|
b.putString("initialCardId", cardId);
|
||||||
i.putExtras(b);
|
i.putExtras(b);
|
||||||
}
|
}
|
||||||
startActivityForResult(i, Utils.SELECT_BARCODE_REQUEST);
|
manualAddLauncher.launch(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addFromImage(View view) {
|
public void addFromImage(View view) {
|
||||||
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
|
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
|
||||||
photoPickerIntent.setType("image/*");
|
photoPickerIntent.setType("image/*");
|
||||||
startActivityForResult(photoPickerIntent, Utils.BARCODE_IMPORT_FROM_IMAGE_FILE);
|
photoPickerLauncher.launch(photoPickerIntent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,26 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ShortcutInfo;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.content.pm.ShortcutManager;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.drawable.Icon;
|
import android.graphics.Canvas;
|
||||||
import android.os.Build;
|
import android.graphics.Color;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
class ShortcutHelper
|
import androidx.core.content.pm.ShortcutInfoCompat;
|
||||||
{
|
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||||
|
import androidx.core.graphics.ColorUtils;
|
||||||
|
import androidx.core.graphics.drawable.IconCompat;
|
||||||
|
|
||||||
|
class ShortcutHelper {
|
||||||
// Android documentation says that no more than 5 shortcuts
|
// Android documentation says that no more than 5 shortcuts
|
||||||
// are supported. However, that may be too many, as not all
|
// are supported. However, that may be too many, as not all
|
||||||
// launcher will show all 5. Instead, the number is limited
|
// launcher will show all 5. Instead, the number is limited
|
||||||
@@ -22,6 +28,14 @@ class ShortcutHelper
|
|||||||
// chance of being shown.
|
// chance of being shown.
|
||||||
private static final int MAX_SHORTCUTS = 3;
|
private static final int MAX_SHORTCUTS = 3;
|
||||||
|
|
||||||
|
// https://developer.android.com/reference/android/graphics/drawable/AdaptiveIconDrawable.html
|
||||||
|
private static final int ADAPTIVE_BITMAP_SCALE = 1;
|
||||||
|
private static final int ADAPTIVE_BITMAP_SIZE = 108 * ADAPTIVE_BITMAP_SCALE;
|
||||||
|
private static final int ADAPTIVE_BITMAP_VISIBLE_SIZE = 72 * ADAPTIVE_BITMAP_SCALE;
|
||||||
|
private static final int ADAPTIVE_BITMAP_IMAGE_SIZE = ADAPTIVE_BITMAP_VISIBLE_SIZE + 5 * ADAPTIVE_BITMAP_SCALE;
|
||||||
|
private static final int PADDING_COLOR = Color.argb(255, 255, 255, 255);
|
||||||
|
private static final int PADDING_COLOR_OVERLAY = Color.argb(127, 0, 0, 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a card to the app shortcuts, and maintain a list of the most
|
* Add a card to the app shortcuts, and maintain a list of the most
|
||||||
* recently used cards. If there is already a shortcut for the card,
|
* recently used cards. If there is already a shortcut for the card,
|
||||||
@@ -29,125 +43,117 @@ class ShortcutHelper
|
|||||||
* card exceeds the max number of shortcuts, then the least recently
|
* card exceeds the max number of shortcuts, then the least recently
|
||||||
* used card shortcut is discarded.
|
* used card shortcut is discarded.
|
||||||
*/
|
*/
|
||||||
@TargetApi(25)
|
static void updateShortcuts(Context context, LoyaltyCard card) {
|
||||||
static void updateShortcuts(Context context, LoyaltyCard card, Intent intent)
|
LinkedList<ShortcutInfoCompat> list = new LinkedList<>(ShortcutManagerCompat.getDynamicShortcuts(context));
|
||||||
{
|
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1)
|
|
||||||
{
|
|
||||||
ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
|
|
||||||
LinkedList<ShortcutInfo> list = new LinkedList<>(shortcutManager.getDynamicShortcuts());
|
|
||||||
|
|
||||||
String shortcutId = Integer.toString(card.id);
|
SQLiteDatabase database = new DBHelper(context).getReadableDatabase();
|
||||||
|
|
||||||
// Sort the shortcuts by rank, so working with the relative order will be easier.
|
String shortcutId = Integer.toString(card.id);
|
||||||
// This sorts so that the lowest rank is first.
|
|
||||||
Collections.sort(list, new Comparator<ShortcutInfo>()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public int compare(ShortcutInfo o1, ShortcutInfo o2)
|
|
||||||
{
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1)
|
|
||||||
{
|
|
||||||
return o1.getRank() - o2.getRank();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Integer foundIndex = null;
|
// Sort the shortcuts by rank, so working with the relative order will be easier.
|
||||||
|
// This sorts so that the lowest rank is first.
|
||||||
|
Collections.sort(list, Comparator.comparingInt(ShortcutInfoCompat::getRank));
|
||||||
|
|
||||||
for(int index = 0; index < list.size(); index++)
|
Integer foundIndex = null;
|
||||||
{
|
|
||||||
if(list.get(index).getId().equals(shortcutId))
|
for (int index = 0; index < list.size(); index++) {
|
||||||
{
|
if (list.get(index).getId().equals(shortcutId)) {
|
||||||
// Found the item already
|
// Found the item already
|
||||||
foundIndex = index;
|
foundIndex = index;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundIndex != null) {
|
||||||
|
// If the item is already found, then the list needs to be
|
||||||
|
// reordered, so that the selected item now has the lowest
|
||||||
|
// rank, thus letting it survive longer.
|
||||||
|
ShortcutInfoCompat found = list.remove(foundIndex.intValue());
|
||||||
|
list.addFirst(found);
|
||||||
|
} else {
|
||||||
|
// The item is new to the list. First, we need to trim the list
|
||||||
|
// until it is able to accept a new item, then the item is
|
||||||
|
// inserted.
|
||||||
|
while (list.size() >= MAX_SHORTCUTS) {
|
||||||
|
list.pollLast();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(foundIndex != null)
|
ShortcutInfoCompat shortcut = createShortcutBuilder(context, card).build();
|
||||||
{
|
|
||||||
// If the item is already found, then the list needs to be
|
|
||||||
// reordered, so that the selected item now has the lowest
|
|
||||||
// rank, thus letting it survive longer.
|
|
||||||
ShortcutInfo found = list.remove(foundIndex.intValue());
|
|
||||||
list.addFirst(found);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// The item is new to the list. First, we need to trim the list
|
|
||||||
// until it is able to accept a new item, then the item is
|
|
||||||
// inserted.
|
|
||||||
while(list.size() >= MAX_SHORTCUTS)
|
|
||||||
{
|
|
||||||
list.pollLast();
|
|
||||||
}
|
|
||||||
|
|
||||||
ShortcutInfo shortcut = new ShortcutInfo.Builder(context, Integer.toString(card.id))
|
list.addFirst(shortcut);
|
||||||
.setShortLabel(card.store)
|
}
|
||||||
.setLongLabel(card.store)
|
|
||||||
.setIntent(intent)
|
LinkedList<ShortcutInfoCompat> finalList = new LinkedList<>();
|
||||||
|
|
||||||
|
// The ranks are now updated; the order in the list is the rank.
|
||||||
|
for (int index = 0; index < list.size(); index++) {
|
||||||
|
ShortcutInfoCompat prevShortcut = list.get(index);
|
||||||
|
|
||||||
|
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(database, Integer.parseInt(prevShortcut.getId()));
|
||||||
|
|
||||||
|
ShortcutInfoCompat updatedShortcut = createShortcutBuilder(context, loyaltyCard)
|
||||||
|
.setRank(index)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
list.addFirst(shortcut);
|
finalList.addLast(updatedShortcut);
|
||||||
}
|
|
||||||
|
|
||||||
LinkedList<ShortcutInfo> finalList = new LinkedList<>();
|
|
||||||
|
|
||||||
// The ranks are now updated; the order in the list is the rank.
|
|
||||||
for(int index = 0; index < list.size(); index++)
|
|
||||||
{
|
|
||||||
ShortcutInfo prevShortcut = list.get(index);
|
|
||||||
|
|
||||||
Intent shortcutIntent = prevShortcut.getIntent();
|
|
||||||
|
|
||||||
// Prevent instances of the view activity from piling up; if one exists let this
|
|
||||||
// one replace it.
|
|
||||||
shortcutIntent.setFlags(shortcutIntent.getFlags() | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
|
||||||
|
|
||||||
ShortcutInfo updatedShortcut = new ShortcutInfo.Builder(context, prevShortcut.getId())
|
|
||||||
.setShortLabel(prevShortcut.getShortLabel())
|
|
||||||
.setLongLabel(prevShortcut.getLongLabel())
|
|
||||||
.setIntent(shortcutIntent)
|
|
||||||
.setIcon(Icon.createWithResource(context, R.drawable.circle))
|
|
||||||
.setRank(index)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
finalList.addLast(updatedShortcut);
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcutManager.setDynamicShortcuts(finalList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShortcutManagerCompat.setDynamicShortcuts(context, finalList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the given card id from the app shortcuts, if such a
|
* Remove the given card id from the app shortcuts, if such a
|
||||||
* shortcut exists.
|
* shortcut exists.
|
||||||
*/
|
*/
|
||||||
@TargetApi(25)
|
static void removeShortcut(Context context, int cardId) {
|
||||||
static void removeShortcut(Context context, int cardId)
|
List<ShortcutInfoCompat> list = ShortcutManagerCompat.getDynamicShortcuts(context);
|
||||||
{
|
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1)
|
|
||||||
{
|
|
||||||
ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
|
|
||||||
List<ShortcutInfo> list = shortcutManager.getDynamicShortcuts();
|
|
||||||
|
|
||||||
String shortcutId = Integer.toString(cardId);
|
String shortcutId = Integer.toString(cardId);
|
||||||
|
|
||||||
for(int index = 0; index < list.size(); index++)
|
for (int index = 0; index < list.size(); index++) {
|
||||||
{
|
if (list.get(index).getId().equals(shortcutId)) {
|
||||||
if(list.get(index).getId().equals(shortcutId))
|
list.remove(index);
|
||||||
{
|
break;
|
||||||
list.remove(index);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shortcutManager.setDynamicShortcuts(list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShortcutManagerCompat.setDynamicShortcuts(context, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
static @NotNull
|
||||||
|
Bitmap createAdaptiveBitmap(@NotNull Bitmap in, int paddingColor) {
|
||||||
|
Bitmap ret = Bitmap.createBitmap(ADAPTIVE_BITMAP_SIZE, ADAPTIVE_BITMAP_SIZE, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas output = new Canvas(ret);
|
||||||
|
output.drawColor(ColorUtils.compositeColors(PADDING_COLOR_OVERLAY, paddingColor));
|
||||||
|
Bitmap resized = Utils.resizeBitmap(in, ADAPTIVE_BITMAP_IMAGE_SIZE);
|
||||||
|
output.drawBitmap(resized, (ADAPTIVE_BITMAP_SIZE - resized.getWidth()) / 2f, (ADAPTIVE_BITMAP_SIZE - resized.getHeight()) / 2f, null);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ShortcutInfoCompat.Builder createShortcutBuilder(Context context, LoyaltyCard loyaltyCard) {
|
||||||
|
Intent intent = new Intent(context, LoyaltyCardViewActivity.class);
|
||||||
|
intent.setAction(Intent.ACTION_MAIN);
|
||||||
|
// Prevent instances of the view activity from piling up; if one exists let this
|
||||||
|
// one replace it.
|
||||||
|
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
final Bundle bundle = new Bundle();
|
||||||
|
bundle.putInt("id", loyaltyCard.id);
|
||||||
|
bundle.putBoolean("view", true);
|
||||||
|
intent.putExtras(bundle);
|
||||||
|
|
||||||
|
Bitmap iconBitmap = Utils.retrieveCardImage(context, loyaltyCard.id, ImageLocationType.icon);
|
||||||
|
if (iconBitmap == null) {
|
||||||
|
iconBitmap = Utils.generateIcon(context, loyaltyCard, true).getLetterTile();
|
||||||
|
} else {
|
||||||
|
iconBitmap = createAdaptiveBitmap(iconBitmap, loyaltyCard.headerColor == null ? PADDING_COLOR : loyaltyCard.headerColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
IconCompat icon = IconCompat.createWithAdaptiveBitmap(iconBitmap);
|
||||||
|
|
||||||
|
return new ShortcutInfoCompat.Builder(context, Integer.toString(loyaltyCard.id))
|
||||||
|
.setShortLabel(loyaltyCard.store)
|
||||||
|
.setLongLabel(loyaltyCard.store)
|
||||||
|
.setIntent(intent)
|
||||||
|
.setIcon(icon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
app/src/main/java/protect/card_locker/SimpleTextWatcher.java
Normal file
18
app/src/main/java/protect/card_locker/SimpleTextWatcher.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
|
||||||
|
public class SimpleTextWatcher implements TextWatcher {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,13 +3,16 @@ package protect.card_locker;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.ImageDecoder;
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
import android.media.ExifInterface;
|
import android.os.Build;
|
||||||
|
import android.os.LocaleList;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.util.Base64;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@@ -22,6 +25,7 @@ import com.google.zxing.Result;
|
|||||||
import com.google.zxing.common.HybridBinarizer;
|
import com.google.zxing.common.HybridBinarizer;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@@ -32,10 +36,13 @@ import java.util.Calendar;
|
|||||||
import java.util.Currency;
|
import java.util.Currency;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.HashMap;
|
import java.util.Locale;
|
||||||
import java.util.List;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.core.graphics.ColorUtils;
|
import androidx.core.graphics.ColorUtils;
|
||||||
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
|
import protect.card_locker.preferences.Settings;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
private static final String TAG = "Catima";
|
private static final String TAG = "Catima";
|
||||||
@@ -47,12 +54,19 @@ public class Utils {
|
|||||||
public static final int BARCODE_IMPORT_FROM_IMAGE_FILE = 4;
|
public static final int BARCODE_IMPORT_FROM_IMAGE_FILE = 4;
|
||||||
public static final int CARD_IMAGE_FROM_CAMERA_FRONT = 5;
|
public static final int CARD_IMAGE_FROM_CAMERA_FRONT = 5;
|
||||||
public static final int CARD_IMAGE_FROM_CAMERA_BACK = 6;
|
public static final int CARD_IMAGE_FROM_CAMERA_BACK = 6;
|
||||||
public static final int CARD_IMAGE_FROM_FILE_FRONT = 7;
|
public static final int CARD_IMAGE_FROM_CAMERA_ICON = 7;
|
||||||
public static final int CARD_IMAGE_FROM_FILE_BACK = 8;
|
public static final int CARD_IMAGE_FROM_FILE_FRONT = 8;
|
||||||
|
public static final int CARD_IMAGE_FROM_FILE_BACK = 9;
|
||||||
|
public static final int CARD_IMAGE_FROM_FILE_ICON = 10;
|
||||||
|
|
||||||
static final double LUMINANCE_MIDPOINT = 0.5;
|
static final double LUMINANCE_MIDPOINT = 0.5;
|
||||||
|
|
||||||
static final int BITMAP_SIZE_BIG = 512;
|
static final int BITMAP_SIZE_SMALL = 512;
|
||||||
|
static final int BITMAP_SIZE_BIG = 2048;
|
||||||
|
|
||||||
|
static public LetterBitmap generateIcon(Context context, LoyaltyCard loyaltyCard, boolean forShortcut) {
|
||||||
|
return generateIcon(context, loyaltyCard.store, loyaltyCard.headerColor, forShortcut);
|
||||||
|
}
|
||||||
|
|
||||||
static public LetterBitmap generateIcon(Context context, String store, Integer backgroundColor) {
|
static public LetterBitmap generateIcon(Context context, String store, Integer backgroundColor) {
|
||||||
return generateIcon(context, store, backgroundColor, false);
|
return generateIcon(context, store, backgroundColor, false);
|
||||||
@@ -70,7 +84,7 @@ public class Utils {
|
|||||||
tileLetterFontSize = context.getResources().getDimensionPixelSize(R.dimen.tileLetterFontSize);
|
tileLetterFontSize = context.getResources().getDimensionPixelSize(R.dimen.tileLetterFontSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
int pixelSize = context.getResources().getDimensionPixelSize(R.dimen.cardThumbnailSize);
|
int pixelSize = context.getResources().getDimensionPixelSize(R.dimen.tileLetterImageSize);
|
||||||
|
|
||||||
if (backgroundColor == null) {
|
if (backgroundColor == null) {
|
||||||
backgroundColor = LetterBitmap.getDefaultColor(context, store);
|
backgroundColor = LetterBitmap.getDefaultColor(context, store);
|
||||||
@@ -97,7 +111,12 @@ public class Utils {
|
|||||||
|
|
||||||
Bitmap bitmap;
|
Bitmap bitmap;
|
||||||
try {
|
try {
|
||||||
bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), intent.getData());
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
ImageDecoder.Source image_source = ImageDecoder.createSource(context.getContentResolver(), intent.getData());
|
||||||
|
bitmap = ImageDecoder.decodeBitmap(image_source, (decoder, info, source) -> decoder.setMutableRequired(true));
|
||||||
|
} else {
|
||||||
|
bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), intent.getData());
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Error getting data from image file");
|
Log.e(TAG, "Error getting data from image file");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -138,6 +157,21 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static public BarcodeValues getBarcodeFromBitmap(Bitmap bitmap) {
|
static public BarcodeValues getBarcodeFromBitmap(Bitmap bitmap) {
|
||||||
|
// This function is vulnerable to OOM, so we try again with a smaller bitmap is we get OOM
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
try {
|
||||||
|
return Utils.getBarcodeFromBitmapReal(bitmap);
|
||||||
|
} catch (OutOfMemoryError e) {
|
||||||
|
Log.w(TAG, "Ran OOM in getBarcodeFromBitmap! Trying again with smaller picture! Retry " + i + " of 10.");
|
||||||
|
bitmap = Bitmap.createScaledBitmap(bitmap, (int) Math.round(0.75 * bitmap.getWidth()), (int) Math.round(0.75 * bitmap.getHeight()), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give up
|
||||||
|
return new BarcodeValues(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static private BarcodeValues getBarcodeFromBitmapReal(Bitmap bitmap) {
|
||||||
// In order to decode it, the Bitmap must first be converted into a pixel array...
|
// In order to decode it, the Bitmap must first be converted into a pixel array...
|
||||||
int[] intArray = new int[bitmap.getWidth() * bitmap.getHeight()];
|
int[] intArray = new int[bitmap.getWidth() * bitmap.getHeight()];
|
||||||
bitmap.getPixels(intArray, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
|
bitmap.getPixels(intArray, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
|
||||||
@@ -235,22 +269,20 @@ public class Utils {
|
|||||||
return bos.toByteArray();
|
return bos.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Bitmap resizeBitmap(Bitmap bitmap) {
|
static public Bitmap resizeBitmap(Bitmap bitmap, double maxSize) {
|
||||||
if (bitmap == null) {
|
if (bitmap == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer maxSize = BITMAP_SIZE_BIG;
|
double width = bitmap.getWidth();
|
||||||
|
double height = bitmap.getHeight();
|
||||||
Integer width = bitmap.getWidth();
|
|
||||||
Integer height = bitmap.getHeight();
|
|
||||||
|
|
||||||
if (height > width) {
|
if (height > width) {
|
||||||
Integer scale = height / maxSize;
|
double scale = height / maxSize;
|
||||||
height = maxSize;
|
height = maxSize;
|
||||||
width = width / scale;
|
width = width / scale;
|
||||||
} else if (width > height) {
|
} else if (width > height) {
|
||||||
Integer scale = width / maxSize;
|
double scale = width / maxSize;
|
||||||
width = maxSize;
|
width = maxSize;
|
||||||
height = height / scale;
|
height = height / scale;
|
||||||
} else {
|
} else {
|
||||||
@@ -258,7 +290,7 @@ public class Utils {
|
|||||||
width = maxSize;
|
width = maxSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Bitmap.createScaledBitmap(bitmap, width, height, true);
|
return Bitmap.createScaledBitmap(bitmap, (int) Math.round(width), (int) Math.round(height), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Bitmap rotateBitmap(Bitmap bitmap, ExifInterface exifInterface) {
|
static public Bitmap rotateBitmap(Bitmap bitmap, ExifInterface exifInterface) {
|
||||||
@@ -284,16 +316,20 @@ public class Utils {
|
|||||||
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public String getCardImageFileName(int loyaltyCardId, boolean front) {
|
static public String getCardImageFileName(int loyaltyCardId, ImageLocationType type) {
|
||||||
StringBuilder cardImageFileNameBuilder = new StringBuilder();
|
StringBuilder cardImageFileNameBuilder = new StringBuilder();
|
||||||
|
|
||||||
cardImageFileNameBuilder.append("card_");
|
cardImageFileNameBuilder.append("card_");
|
||||||
cardImageFileNameBuilder.append(loyaltyCardId);
|
cardImageFileNameBuilder.append(loyaltyCardId);
|
||||||
cardImageFileNameBuilder.append("_");
|
cardImageFileNameBuilder.append("_");
|
||||||
if (front) {
|
if (type == ImageLocationType.front) {
|
||||||
cardImageFileNameBuilder.append("front");
|
cardImageFileNameBuilder.append("front");
|
||||||
} else {
|
} else if (type == ImageLocationType.back) {
|
||||||
cardImageFileNameBuilder.append("back");
|
cardImageFileNameBuilder.append("back");
|
||||||
|
} else if (type == ImageLocationType.icon) {
|
||||||
|
cardImageFileNameBuilder.append("icon");
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unknown image type");
|
||||||
}
|
}
|
||||||
cardImageFileNameBuilder.append(".png");
|
cardImageFileNameBuilder.append(".png");
|
||||||
|
|
||||||
@@ -311,8 +347,8 @@ public class Utils {
|
|||||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public void saveCardImage(Context context, Bitmap bitmap, int loyaltyCardId, boolean front) throws FileNotFoundException {
|
static public void saveCardImage(Context context, Bitmap bitmap, int loyaltyCardId, ImageLocationType type) throws FileNotFoundException {
|
||||||
saveCardImage(context, bitmap, getCardImageFileName(loyaltyCardId, front));
|
saveCardImage(context, bitmap, getCardImageFileName(loyaltyCardId, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Bitmap retrieveCardImage(Context context, String fileName) {
|
static public Bitmap retrieveCardImage(Context context, String fileName) {
|
||||||
@@ -326,19 +362,90 @@ public class Utils {
|
|||||||
return BitmapFactory.decodeStream(in);
|
return BitmapFactory.decodeStream(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Bitmap retrieveCardImage(Context context, int loyaltyCardId, boolean front) {
|
static public Bitmap retrieveCardImage(Context context, int loyaltyCardId, ImageLocationType type) {
|
||||||
return retrieveCardImage(context, getCardImageFileName(loyaltyCardId, front));
|
return retrieveCardImage(context, getCardImageFileName(loyaltyCardId, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Object hashmapGetOrDefault(HashMap hashMap, Object key, Object defaultValue, Class keyType) {
|
static public <T, U> U mapGetOrDefault(Map<T, U> map, T key, U defaultValue) {
|
||||||
Object value = hashMap.get(keyType.cast(key));
|
U value = map.get(key);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Object hashmapGetOrDefault(HashMap hashMap, String key, Object defaultValue) {
|
static public Locale stringToLocale(String localeString) {
|
||||||
return hashmapGetOrDefault(hashMap, key, defaultValue, String.class);
|
String[] localeParts = localeString.split("-");
|
||||||
|
if (localeParts.length == 1) {
|
||||||
|
return new Locale(localeParts[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localeParts[1].startsWith("r")) {
|
||||||
|
localeParts[1] = localeParts[1].substring(1);
|
||||||
|
}
|
||||||
|
return new Locale(localeParts[0], localeParts[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public Context updateBaseContextLocale(Context context) {
|
||||||
|
Settings settings = new Settings(context);
|
||||||
|
|
||||||
|
Locale chosenLocale = settings.getLocale();
|
||||||
|
|
||||||
|
Resources res = context.getResources();
|
||||||
|
Configuration configuration = res.getConfiguration();
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||||
|
configuration.locale = chosenLocale != null ? chosenLocale : Locale.getDefault();
|
||||||
|
res.updateConfiguration(configuration, res.getDisplayMetrics());
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocaleList localeList = chosenLocale != null ? new LocaleList(chosenLocale) : LocaleList.getDefault();
|
||||||
|
LocaleList.setDefault(localeList);
|
||||||
|
configuration.setLocales(localeList);
|
||||||
|
return context.createConfigurationContext(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public long getUnixTime() {
|
||||||
|
return System.currentTimeMillis() / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public boolean isDarkModeEnabled(Context inputContext) {
|
||||||
|
int nightModeSetting = new Settings(inputContext).getTheme();
|
||||||
|
if (nightModeSetting == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) {
|
||||||
|
Configuration config = inputContext.getResources().getConfiguration();
|
||||||
|
int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||||
|
return (currentNightMode == Configuration.UI_MODE_NIGHT_YES);
|
||||||
|
} else {
|
||||||
|
return nightModeSetting == AppCompatDelegate.MODE_NIGHT_YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File createTempFile(Context context, String name) {
|
||||||
|
return new File(context.getCacheDir() + "/" + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String saveTempImage(Context context, Bitmap in, String name, Bitmap.CompressFormat format) {
|
||||||
|
File image = createTempFile(context, name);
|
||||||
|
try (FileOutputStream out = new FileOutputStream(image)) {
|
||||||
|
in.compress(format, 100, out);
|
||||||
|
return image.getAbsolutePath();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d("store temp image", "failed writing temp file for temporary image, name: " + name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap loadImage(String path) {
|
||||||
|
try {
|
||||||
|
return BitmapFactory.decodeStream(new FileInputStream(path));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d("load image", "failed loading image from " + path);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap loadTempImage(Context context, String name) {
|
||||||
|
return loadImage(context.getCacheDir() + "/" + name);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,12 @@ import java.io.BufferedReader;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
public class ZipUtils {
|
public class ZipUtils {
|
||||||
static public String read(ZipInputStream zipInputStream) throws IOException {
|
static public String read(ZipInputStream zipInputStream) throws IOException {
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
Reader reader = new BufferedReader(new InputStreamReader(zipInputStream, Charset.forName(StandardCharsets.UTF_8.name())));
|
Reader reader = new BufferedReader(new InputStreamReader(zipInputStream, StandardCharsets.UTF_8));
|
||||||
int c;
|
int c;
|
||||||
while ((c = reader.read()) != -1) {
|
while ((c = reader.read()) != -1) {
|
||||||
stringBuilder.append((char) c);
|
stringBuilder.append((char) c);
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package protect.card_locker.async;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
public interface CompatCallable<T> extends Callable<T> {
|
||||||
|
void onPostExecute(Object result);
|
||||||
|
|
||||||
|
void onPreExecute();
|
||||||
|
}
|
||||||
175
app/src/main/java/protect/card_locker/async/TaskHandler.java
Normal file
175
app/src/main/java/protect/card_locker/async/TaskHandler.java
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package protect.card_locker.async;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AsyncTask has been deprecated so this provides very rudimentary compatibility without
|
||||||
|
* needing to redo too many Parts.
|
||||||
|
* <p>
|
||||||
|
* However this is a much, much more cooperative Behaviour than before so
|
||||||
|
* the callers need to ensure we do NOT rely on forced cancellation and feed less into the
|
||||||
|
* ThreadPools so we don't OOM/Overload the Users device
|
||||||
|
* <p>
|
||||||
|
* This assumes single-threaded callers.
|
||||||
|
*/
|
||||||
|
public class TaskHandler {
|
||||||
|
|
||||||
|
public enum TYPE {
|
||||||
|
BARCODE,
|
||||||
|
IMPORT,
|
||||||
|
EXPORT
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMap<TYPE, ThreadPoolExecutor> executors = generateExecutors();
|
||||||
|
|
||||||
|
final private HashMap<TYPE, LinkedList<Future<?>>> taskList = new HashMap<>();
|
||||||
|
|
||||||
|
private final Handler uiHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
private HashMap<TYPE, ThreadPoolExecutor> generateExecutors() {
|
||||||
|
HashMap<TYPE, ThreadPoolExecutor> initExecutors = new HashMap<>();
|
||||||
|
for (TYPE type : TYPE.values()) {
|
||||||
|
replaceExecutor(initExecutors, type, false, false);
|
||||||
|
}
|
||||||
|
return initExecutors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces (or initializes) an Executor with a clean (new) one
|
||||||
|
*
|
||||||
|
* @param executors Map Reference
|
||||||
|
* @param type Which Queue
|
||||||
|
* @param flushOld attempt shutdown
|
||||||
|
* @param waitOnOld wait for Termination
|
||||||
|
*/
|
||||||
|
private void replaceExecutor(HashMap<TYPE, ThreadPoolExecutor> executors, TYPE type, Boolean flushOld, Boolean waitOnOld) {
|
||||||
|
ThreadPoolExecutor oldExecutor = executors.get(type);
|
||||||
|
if (oldExecutor != null) {
|
||||||
|
if (flushOld) {
|
||||||
|
oldExecutor.shutdownNow();
|
||||||
|
}
|
||||||
|
if (waitOnOld) {
|
||||||
|
try {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
oldExecutor.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
executors.put(type, (ThreadPoolExecutor) Executors.newCachedThreadPool());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue a Pseudo-AsyncTask for execution
|
||||||
|
*
|
||||||
|
* @param type Queue
|
||||||
|
* @param callable PseudoAsyncTask
|
||||||
|
*/
|
||||||
|
public void executeTask(TYPE type, CompatCallable<?> callable) {
|
||||||
|
Runnable runner = () -> {
|
||||||
|
try {
|
||||||
|
// Run on the UI Thread
|
||||||
|
uiHandler.post(callable::onPreExecute);
|
||||||
|
|
||||||
|
// Background
|
||||||
|
final Object result = callable.call();
|
||||||
|
|
||||||
|
// Post results on UI Thread so we can show them
|
||||||
|
uiHandler.post(() -> {
|
||||||
|
callable.onPostExecute(result);
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LinkedList<Future<?>> list = taskList.get(type);
|
||||||
|
|
||||||
|
if (list == null) {
|
||||||
|
list = new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadPoolExecutor executor = executors.get(type);
|
||||||
|
|
||||||
|
if (executor != null) {
|
||||||
|
Future<?> task = executor.submit(runner);
|
||||||
|
// Test Queue Cancellation:
|
||||||
|
// task.cancel(true);
|
||||||
|
list.push(task);
|
||||||
|
taskList.put(type, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will attempt to cancel a currently running list of Tasks
|
||||||
|
* Useful to ignore scheduled tasks - but not able to hard-stop tasks that are running
|
||||||
|
*
|
||||||
|
* @param type Which Queue to target
|
||||||
|
* @param forceCancel attempt to close the Queue and force-replace it after
|
||||||
|
* @param waitForFinish wait and return after the old executor finished. Times out after 5s
|
||||||
|
* @param waitForCurrentlyRunning wait before cancelling tasks. Useful for tests.
|
||||||
|
*/
|
||||||
|
public void flushTaskList(TYPE type, Boolean forceCancel, Boolean waitForFinish, Boolean waitForCurrentlyRunning) {
|
||||||
|
// Only used for Testing
|
||||||
|
if (waitForCurrentlyRunning) {
|
||||||
|
ThreadPoolExecutor oldExecutor = executors.get(type);
|
||||||
|
|
||||||
|
if (oldExecutor != null) {
|
||||||
|
try {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
oldExecutor.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to cancel known Tasks and clean the List
|
||||||
|
LinkedList<Future<?>> tasks = taskList.get(type);
|
||||||
|
if (tasks != null) {
|
||||||
|
for (Future<?> task : tasks) {
|
||||||
|
if (!task.isDone() || !task.isCancelled()) {
|
||||||
|
// Interrupt any Task we can
|
||||||
|
task.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tasks = new LinkedList<>();
|
||||||
|
taskList.put(type, tasks);
|
||||||
|
|
||||||
|
if (forceCancel || waitForFinish) {
|
||||||
|
ThreadPoolExecutor oldExecutor = executors.get(type);
|
||||||
|
|
||||||
|
if (oldExecutor != null) {
|
||||||
|
if (forceCancel) {
|
||||||
|
if (waitForFinish) {
|
||||||
|
try {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
oldExecutor.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oldExecutor.shutdownNow();
|
||||||
|
replaceExecutor(executors, type, true, false);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
oldExecutor.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,18 +12,13 @@ public class CSVHelpers {
|
|||||||
* if it is not null. Otherwise, a FormatException is thrown.
|
* if it is not null. Otherwise, a FormatException is thrown.
|
||||||
*/
|
*/
|
||||||
static String extractString(String key, CSVRecord record, String defaultValue)
|
static String extractString(String key, CSVRecord record, String defaultValue)
|
||||||
throws FormatException
|
throws FormatException {
|
||||||
{
|
|
||||||
String toReturn = defaultValue;
|
String toReturn = defaultValue;
|
||||||
|
|
||||||
if(record.isMapped(key))
|
if (record.isMapped(key)) {
|
||||||
{
|
|
||||||
toReturn = record.get(key);
|
toReturn = record.get(key);
|
||||||
}
|
} else {
|
||||||
else
|
if (defaultValue == null) {
|
||||||
{
|
|
||||||
if(defaultValue == null)
|
|
||||||
{
|
|
||||||
throw new FormatException("Field not used but expected: " + key);
|
throw new FormatException("Field not used but expected: " + key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,25 +33,19 @@ public class CSVHelpers {
|
|||||||
* int, a FormatException is thrown.
|
* int, a FormatException is thrown.
|
||||||
*/
|
*/
|
||||||
static Integer extractInt(String key, CSVRecord record, boolean nullIsOk)
|
static Integer extractInt(String key, CSVRecord record, boolean nullIsOk)
|
||||||
throws FormatException
|
throws FormatException {
|
||||||
{
|
if (record.isMapped(key) == false) {
|
||||||
if(record.isMapped(key) == false)
|
|
||||||
{
|
|
||||||
throw new FormatException("Field not used but expected: " + key);
|
throw new FormatException("Field not used but expected: " + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
String value = record.get(key);
|
String value = record.get(key);
|
||||||
if(value.isEmpty() && nullIsOk)
|
if (value.isEmpty() && nullIsOk) {
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
return Integer.parseInt(record.get(key));
|
return Integer.parseInt(record.get(key));
|
||||||
}
|
} catch (NumberFormatException e) {
|
||||||
catch(NumberFormatException e)
|
|
||||||
{
|
|
||||||
throw new FormatException("Failed to parse field: " + key, e);
|
throw new FormatException("Failed to parse field: " + key, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,25 +57,19 @@ public class CSVHelpers {
|
|||||||
* int, a FormatException is thrown.
|
* int, a FormatException is thrown.
|
||||||
*/
|
*/
|
||||||
static Long extractLong(String key, CSVRecord record, boolean nullIsOk)
|
static Long extractLong(String key, CSVRecord record, boolean nullIsOk)
|
||||||
throws FormatException
|
throws FormatException {
|
||||||
{
|
if (record.isMapped(key) == false) {
|
||||||
if(record.isMapped(key) == false)
|
|
||||||
{
|
|
||||||
throw new FormatException("Field not used but expected: " + key);
|
throw new FormatException("Field not used but expected: " + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
String value = record.get(key);
|
String value = record.get(key);
|
||||||
if(value.isEmpty() && nullIsOk)
|
if (value.isEmpty() && nullIsOk) {
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
return Long.parseLong(record.get(key));
|
return Long.parseLong(record.get(key));
|
||||||
}
|
} catch (NumberFormatException e) {
|
||||||
catch(NumberFormatException e)
|
|
||||||
{
|
|
||||||
throw new FormatException("Failed to parse field: " + key, e);
|
throw new FormatException("Failed to parse field: " + key, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,29 +2,28 @@ package protect.card_locker.importexport;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
import net.lingala.zip4j.ZipFile;
|
|
||||||
import net.lingala.zip4j.io.outputstream.ZipOutputStream;
|
import net.lingala.zip4j.io.outputstream.ZipOutputStream;
|
||||||
import net.lingala.zip4j.model.ZipParameters;
|
import net.lingala.zip4j.model.ZipParameters;
|
||||||
|
import net.lingala.zip4j.model.enums.EncryptionMethod;
|
||||||
import net.lingala.zip4j.util.InternalZipConstants;
|
import net.lingala.zip4j.util.InternalZipConstants;
|
||||||
|
|
||||||
import org.apache.commons.csv.CSVFormat;
|
import org.apache.commons.csv.CSVFormat;
|
||||||
import org.apache.commons.csv.CSVPrinter;
|
import org.apache.commons.csv.CSVPrinter;
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
|
|
||||||
import protect.card_locker.DBHelper;
|
import protect.card_locker.DBHelper;
|
||||||
import protect.card_locker.Group;
|
import protect.card_locker.Group;
|
||||||
|
import protect.card_locker.ImageLocationType;
|
||||||
import protect.card_locker.LoyaltyCard;
|
import protect.card_locker.LoyaltyCard;
|
||||||
import protect.card_locker.Utils;
|
import protect.card_locker.Utils;
|
||||||
|
|
||||||
@@ -32,25 +31,28 @@ import protect.card_locker.Utils;
|
|||||||
* Class for exporting the database into CSV (Comma Separate Values)
|
* Class for exporting the database into CSV (Comma Separate Values)
|
||||||
* format.
|
* format.
|
||||||
*/
|
*/
|
||||||
public class CatimaExporter implements Exporter
|
public class CatimaExporter implements Exporter {
|
||||||
{
|
public void exportData(Context context, SQLiteDatabase database, OutputStream output, char[] password) throws IOException, InterruptedException {
|
||||||
public void exportData(Context context, DBHelper db, OutputStream output) throws IOException, InterruptedException
|
|
||||||
{
|
|
||||||
// Necessary vars
|
// Necessary vars
|
||||||
int readLen;
|
int readLen;
|
||||||
byte[] readBuffer = new byte[InternalZipConstants.BUFF_SIZE];
|
byte[] readBuffer = new byte[InternalZipConstants.BUFF_SIZE];
|
||||||
|
|
||||||
// Create zip output stream
|
// Create zip output stream
|
||||||
ZipOutputStream zipOutputStream = new ZipOutputStream(output);
|
ZipOutputStream zipOutputStream;
|
||||||
|
|
||||||
|
if (password != null && password.length > 0) {
|
||||||
|
zipOutputStream = new ZipOutputStream(output, password);
|
||||||
|
} else {
|
||||||
|
zipOutputStream = new ZipOutputStream(output);
|
||||||
|
}
|
||||||
|
|
||||||
// Generate CSV
|
// Generate CSV
|
||||||
ByteArrayOutputStream catimaOutputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream catimaOutputStream = new ByteArrayOutputStream();
|
||||||
OutputStreamWriter catimaOutputStreamWriter = new OutputStreamWriter(catimaOutputStream, StandardCharsets.UTF_8);
|
OutputStreamWriter catimaOutputStreamWriter = new OutputStreamWriter(catimaOutputStream, StandardCharsets.UTF_8);
|
||||||
writeCSV(db, catimaOutputStreamWriter);
|
writeCSV(database, catimaOutputStreamWriter);
|
||||||
|
|
||||||
// Add CSV to zip file
|
// Add CSV to zip file
|
||||||
ZipParameters csvZipParameters = new ZipParameters();
|
ZipParameters csvZipParameters = createZipParameters("catima.csv", password);
|
||||||
csvZipParameters.setFileNameInZip("catima.csv");
|
|
||||||
zipOutputStream.putNextEntry(csvZipParameters);
|
zipOutputStream.putNextEntry(csvZipParameters);
|
||||||
InputStream csvInputStream = new ByteArrayInputStream(catimaOutputStream.toByteArray());
|
InputStream csvInputStream = new ByteArrayInputStream(catimaOutputStream.toByteArray());
|
||||||
while ((readLen = csvInputStream.read(readBuffer)) != -1) {
|
while ((readLen = csvInputStream.read(readBuffer)) != -1) {
|
||||||
@@ -59,24 +61,17 @@ public class CatimaExporter implements Exporter
|
|||||||
zipOutputStream.closeEntry();
|
zipOutputStream.closeEntry();
|
||||||
|
|
||||||
// Loop over all cards again
|
// Loop over all cards again
|
||||||
Cursor cardCursor = db.getLoyaltyCardCursor();
|
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(database);
|
||||||
while(cardCursor.moveToNext())
|
while (cardCursor.moveToNext()) {
|
||||||
{
|
|
||||||
// For each card
|
// For each card
|
||||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
|
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
|
||||||
|
|
||||||
// Prepare looping over both front and back image
|
|
||||||
boolean[] frontValues = new boolean[2];
|
|
||||||
frontValues[0] = true;
|
|
||||||
frontValues[1] = false;
|
|
||||||
|
|
||||||
// For each image
|
// For each image
|
||||||
for (boolean front : frontValues) {
|
for (ImageLocationType imageLocationType : ImageLocationType.values()) {
|
||||||
// If it exists, add to the .zip file
|
// If it exists, add to the .zip file
|
||||||
Bitmap image = Utils.retrieveCardImage(context, card.id, front);
|
Bitmap image = Utils.retrieveCardImage(context, card.id, imageLocationType);
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
ZipParameters imageZipParameters = new ZipParameters();
|
ZipParameters imageZipParameters = createZipParameters(Utils.getCardImageFileName(card.id, imageLocationType), password);
|
||||||
imageZipParameters.setFileNameInZip(Utils.getCardImageFileName(card.id, front));
|
|
||||||
zipOutputStream.putNextEntry(imageZipParameters);
|
zipOutputStream.putNextEntry(imageZipParameters);
|
||||||
InputStream imageInputStream = new ByteArrayInputStream(Utils.bitmapToByteArray(image));
|
InputStream imageInputStream = new ByteArrayInputStream(Utils.bitmapToByteArray(image));
|
||||||
while ((readLen = imageInputStream.read(readBuffer)) != -1) {
|
while ((readLen = imageInputStream.read(readBuffer)) != -1) {
|
||||||
@@ -90,7 +85,17 @@ public class CatimaExporter implements Exporter
|
|||||||
zipOutputStream.close();
|
zipOutputStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeCSV(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException {
|
private ZipParameters createZipParameters(String fileName, char[] password) {
|
||||||
|
ZipParameters zipParameters = new ZipParameters();
|
||||||
|
zipParameters.setFileNameInZip(fileName);
|
||||||
|
if (password != null && password.length > 0) {
|
||||||
|
zipParameters.setEncryptFiles(true);
|
||||||
|
zipParameters.setEncryptionMethod(EncryptionMethod.AES);
|
||||||
|
}
|
||||||
|
return zipParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeCSV(SQLiteDatabase database, OutputStreamWriter output) throws IOException, InterruptedException {
|
||||||
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
|
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
|
||||||
|
|
||||||
// Print the version
|
// Print the version
|
||||||
@@ -101,16 +106,14 @@ public class CatimaExporter implements Exporter
|
|||||||
// Print the header for groups
|
// Print the header for groups
|
||||||
printer.printRecord(DBHelper.LoyaltyCardDbGroups.ID);
|
printer.printRecord(DBHelper.LoyaltyCardDbGroups.ID);
|
||||||
|
|
||||||
Cursor groupCursor = db.getGroupCursor();
|
Cursor groupCursor = DBHelper.getGroupCursor(database);
|
||||||
|
|
||||||
while(groupCursor.moveToNext())
|
while (groupCursor.moveToNext()) {
|
||||||
{
|
|
||||||
Group group = Group.toGroup(groupCursor);
|
Group group = Group.toGroup(groupCursor);
|
||||||
|
|
||||||
printer.printRecord(group._id);
|
printer.printRecord(group._id);
|
||||||
|
|
||||||
if(Thread.currentThread().isInterrupted())
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
{
|
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,12 +134,12 @@ public class CatimaExporter implements Exporter
|
|||||||
DBHelper.LoyaltyCardDbIds.BARCODE_ID,
|
DBHelper.LoyaltyCardDbIds.BARCODE_ID,
|
||||||
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
|
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
|
||||||
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
|
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
|
||||||
DBHelper.LoyaltyCardDbIds.STAR_STATUS);
|
DBHelper.LoyaltyCardDbIds.STAR_STATUS,
|
||||||
|
DBHelper.LoyaltyCardDbIds.LAST_USED);
|
||||||
|
|
||||||
Cursor cardCursor = db.getLoyaltyCardCursor();
|
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(database);
|
||||||
|
|
||||||
while(cardCursor.moveToNext())
|
while (cardCursor.moveToNext()) {
|
||||||
{
|
|
||||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
|
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
|
||||||
|
|
||||||
printer.printRecord(card.id,
|
printer.printRecord(card.id,
|
||||||
@@ -147,12 +150,12 @@ public class CatimaExporter implements Exporter
|
|||||||
card.balanceType,
|
card.balanceType,
|
||||||
card.cardId,
|
card.cardId,
|
||||||
card.barcodeId,
|
card.barcodeId,
|
||||||
card.barcodeType,
|
card.barcodeType != null ? card.barcodeType.name() : "",
|
||||||
card.headerColor,
|
card.headerColor,
|
||||||
card.starStatus);
|
card.starStatus,
|
||||||
|
card.lastUsed);
|
||||||
|
|
||||||
if(Thread.currentThread().isInterrupted())
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
{
|
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,18 +169,16 @@ public class CatimaExporter implements Exporter
|
|||||||
printer.printRecord(DBHelper.LoyaltyCardDbIdsGroups.cardID,
|
printer.printRecord(DBHelper.LoyaltyCardDbIdsGroups.cardID,
|
||||||
DBHelper.LoyaltyCardDbIdsGroups.groupID);
|
DBHelper.LoyaltyCardDbIdsGroups.groupID);
|
||||||
|
|
||||||
Cursor cardCursor2 = db.getLoyaltyCardCursor();
|
Cursor cardCursor2 = DBHelper.getLoyaltyCardCursor(database);
|
||||||
|
|
||||||
while(cardCursor2.moveToNext())
|
while (cardCursor2.moveToNext()) {
|
||||||
{
|
|
||||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor2);
|
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor2);
|
||||||
|
|
||||||
for (Group group : db.getLoyaltyCardGroups(card.id)) {
|
for (Group group : DBHelper.getLoyaltyCardGroups(database, card.id)) {
|
||||||
printer.printRecord(card.id, group._id);
|
printer.printRecord(card.id, group._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Thread.currentThread().isInterrupted())
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
{
|
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ package protect.card_locker.importexport;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.net.Uri;
|
||||||
import com.google.zxing.BarcodeFormat;
|
|
||||||
|
|
||||||
import net.lingala.zip4j.io.inputstream.ZipInputStream;
|
import net.lingala.zip4j.io.inputstream.ZipInputStream;
|
||||||
import net.lingala.zip4j.model.LocalFileHeader;
|
import net.lingala.zip4j.model.LocalFileHeader;
|
||||||
@@ -21,10 +20,12 @@ import java.io.InputStreamReader;
|
|||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Currency;
|
import java.util.Currency;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import protect.card_locker.CatimaBarcode;
|
||||||
import protect.card_locker.DBHelper;
|
import protect.card_locker.DBHelper;
|
||||||
import protect.card_locker.FormatException;
|
import protect.card_locker.FormatException;
|
||||||
import protect.card_locker.Group;
|
import protect.card_locker.Group;
|
||||||
@@ -34,40 +35,42 @@ import protect.card_locker.ZipUtils;
|
|||||||
/**
|
/**
|
||||||
* Class for importing a database from CSV (Comma Separate Values)
|
* Class for importing a database from CSV (Comma Separate Values)
|
||||||
* formatted data.
|
* formatted data.
|
||||||
*
|
* <p>
|
||||||
* The database's loyalty cards are expected to appear in the CSV 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.
|
* A header is expected for the each table showing the names of the columns.
|
||||||
*/
|
*/
|
||||||
public class CatimaImporter implements Importer
|
public class CatimaImporter implements Importer {
|
||||||
{
|
public void importData(Context context, SQLiteDatabase database, InputStream input, char[] password) throws IOException, FormatException, InterruptedException {
|
||||||
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, InterruptedException {
|
|
||||||
InputStream bufferedInputStream = new BufferedInputStream(input);
|
InputStream bufferedInputStream = new BufferedInputStream(input);
|
||||||
bufferedInputStream.mark(100);
|
bufferedInputStream.mark(100);
|
||||||
|
|
||||||
// First, check if this is a zip file
|
// First, check if this is a zip file
|
||||||
ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream);
|
ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream, password);
|
||||||
LocalFileHeader localFileHeader = zipInputStream.getNextEntry();
|
|
||||||
|
|
||||||
if (localFileHeader == null) {
|
boolean isZipFile = false;
|
||||||
|
|
||||||
|
LocalFileHeader localFileHeader;
|
||||||
|
while ((localFileHeader = zipInputStream.getNextEntry()) != null) {
|
||||||
|
isZipFile = true;
|
||||||
|
|
||||||
|
String fileName = Uri.parse(localFileHeader.getFileName()).getLastPathSegment();
|
||||||
|
if (fileName.equals("catima.csv")) {
|
||||||
|
importCSV(context, database, new ByteArrayInputStream(ZipUtils.read(zipInputStream).getBytes(StandardCharsets.UTF_8)));
|
||||||
|
} else if (fileName.endsWith(".png")) {
|
||||||
|
Utils.saveCardImage(context, ZipUtils.readImage(zipInputStream), fileName);
|
||||||
|
} else {
|
||||||
|
throw new FormatException("Unexpected file in import: " + fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isZipFile) {
|
||||||
// This is not a zip file, try importing as bare CSV
|
// This is not a zip file, try importing as bare CSV
|
||||||
bufferedInputStream.reset();
|
bufferedInputStream.reset();
|
||||||
importCSV(context, db, bufferedInputStream);
|
importCSV(context, database, bufferedInputStream);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
importZipFile(context, db, zipInputStream, localFileHeader);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void importZipFile(Context context, DBHelper db, ZipInputStream input, LocalFileHeader localFileHeader) throws IOException, FormatException, InterruptedException {
|
|
||||||
String fileName = localFileHeader.getFileName();
|
|
||||||
if (fileName.equals("catima.csv")) {
|
|
||||||
importCSV(context, db, new ByteArrayInputStream(ZipUtils.read(input).getBytes(StandardCharsets.UTF_8)));
|
|
||||||
} else {
|
|
||||||
Utils.saveCardImage(context, ZipUtils.readImage(input), fileName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void importCSV(Context context, DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException {
|
public void importCSV(Context context, SQLiteDatabase database, InputStream input) throws IOException, FormatException, InterruptedException {
|
||||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
|
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
bufferedReader.mark(100);
|
bufferedReader.mark(100);
|
||||||
@@ -84,10 +87,10 @@ public class CatimaImporter implements Importer
|
|||||||
|
|
||||||
switch (version) {
|
switch (version) {
|
||||||
case 1:
|
case 1:
|
||||||
parseV1(context, db, bufferedReader);
|
parseV1(context, database, bufferedReader);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
parseV2(context, db, bufferedReader);
|
parseV2(context, database, bufferedReader);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new FormatException(String.format("No code to parse version %s", version));
|
throw new FormatException(String.format("No code to parse version %s", version));
|
||||||
@@ -96,44 +99,25 @@ public class CatimaImporter implements Importer
|
|||||||
bufferedReader.close();
|
bufferedReader.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseV1(Context context, DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
|
public void parseV1(Context context, SQLiteDatabase database, BufferedReader input) throws IOException, FormatException, InterruptedException {
|
||||||
{
|
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.builder().setHeader().build());
|
||||||
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.withHeader());
|
|
||||||
|
|
||||||
SQLiteDatabase database = db.getWritableDatabase();
|
try {
|
||||||
database.beginTransaction();
|
for (CSVRecord record : parser) {
|
||||||
|
importLoyaltyCard(context, database, record);
|
||||||
|
|
||||||
try
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
{
|
|
||||||
for (CSVRecord record : parser)
|
|
||||||
{
|
|
||||||
importLoyaltyCard(context, database, db, record);
|
|
||||||
|
|
||||||
if(Thread.currentThread().isInterrupted())
|
|
||||||
{
|
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parser.close();
|
parser.close();
|
||||||
database.setTransactionSuccessful();
|
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||||
}
|
|
||||||
catch(IllegalArgumentException|IllegalStateException e)
|
|
||||||
{
|
|
||||||
throw new FormatException("Issue parsing CSV data", e);
|
throw new FormatException("Issue parsing CSV data", e);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
database.endTransaction();
|
|
||||||
database.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseV2(Context context, DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
|
public void parseV2(Context context, SQLiteDatabase database, BufferedReader input) throws IOException, FormatException, InterruptedException {
|
||||||
{
|
|
||||||
SQLiteDatabase database = db.getWritableDatabase();
|
|
||||||
database.beginTransaction();
|
|
||||||
|
|
||||||
Integer part = 0;
|
Integer part = 0;
|
||||||
String stringPart = "";
|
String stringPart = "";
|
||||||
|
|
||||||
@@ -142,18 +126,36 @@ public class CatimaImporter implements Importer
|
|||||||
String tmp = input.readLine();
|
String tmp = input.readLine();
|
||||||
|
|
||||||
if (tmp == null || tmp.isEmpty()) {
|
if (tmp == null || tmp.isEmpty()) {
|
||||||
|
boolean sectionParsed = false;
|
||||||
|
|
||||||
switch (part) {
|
switch (part) {
|
||||||
case 0:
|
case 0:
|
||||||
// This is the version info, ignore
|
// This is the version info, ignore
|
||||||
|
sectionParsed = true;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
parseV2Groups(db, database, stringPart);
|
try {
|
||||||
|
parseV2Groups(database, stringPart);
|
||||||
|
sectionParsed = true;
|
||||||
|
} catch (FormatException e) {
|
||||||
|
// We may have a multiline field, try again
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
parseV2Cards(context, db, database, stringPart);
|
try {
|
||||||
|
parseV2Cards(context, database, stringPart);
|
||||||
|
sectionParsed = true;
|
||||||
|
} catch (FormatException e) {
|
||||||
|
// We may have a multiline field, try again
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
parseV2CardGroups(db, database, stringPart);
|
try {
|
||||||
|
parseV2CardGroups(database, stringPart);
|
||||||
|
sectionParsed = true;
|
||||||
|
} catch (FormatException e) {
|
||||||
|
// We may have a multiline field, try again
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new FormatException("Issue parsing CSV data, too many parts for v2 parsing");
|
throw new FormatException("Issue parsing CSV data, too many parts for v2 parsing");
|
||||||
@@ -163,29 +165,30 @@ public class CatimaImporter implements Importer
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
part += 1;
|
if (sectionParsed) {
|
||||||
stringPart = "";
|
part += 1;
|
||||||
|
stringPart = "";
|
||||||
|
} else {
|
||||||
|
stringPart += tmp + "\n";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
stringPart += tmp + "\n";
|
stringPart += tmp + "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
database.setTransactionSuccessful();
|
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new FormatException("Issue parsing CSV data", 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
|
public void parseV2Groups(SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException {
|
||||||
{
|
|
||||||
// Parse groups
|
// Parse groups
|
||||||
final CSVParser groupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.withHeader());
|
final CSVParser groupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.builder().setHeader().build());
|
||||||
|
|
||||||
|
List<CSVRecord> records = new ArrayList<>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (CSVRecord record : groupParser) {
|
for (CSVRecord record : groupParser) {
|
||||||
importGroup(database, db, record);
|
records.add(record);
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
@@ -196,16 +199,21 @@ public class CatimaImporter implements Importer
|
|||||||
} finally {
|
} finally {
|
||||||
groupParser.close();
|
groupParser.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (CSVRecord record : records) {
|
||||||
|
importGroup(database, record);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseV2Cards(Context context, DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
|
public void parseV2Cards(Context context, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException {
|
||||||
{
|
|
||||||
// Parse cards
|
// Parse cards
|
||||||
final CSVParser cardParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.withHeader());
|
final CSVParser cardParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.builder().setHeader().build());
|
||||||
|
|
||||||
|
List<CSVRecord> records = new ArrayList<>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (CSVRecord record : cardParser) {
|
for (CSVRecord record : cardParser) {
|
||||||
importLoyaltyCard(context, database, db, record);
|
records.add(record);
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
@@ -216,16 +224,21 @@ public class CatimaImporter implements Importer
|
|||||||
} finally {
|
} finally {
|
||||||
cardParser.close();
|
cardParser.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (CSVRecord record : records) {
|
||||||
|
importLoyaltyCard(context, database, record);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseV2CardGroups(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
|
public void parseV2CardGroups(SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException {
|
||||||
{
|
|
||||||
// Parse card group mappings
|
// Parse card group mappings
|
||||||
final CSVParser cardGroupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.withHeader());
|
final CSVParser cardGroupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.builder().setHeader().build());
|
||||||
|
|
||||||
|
List<CSVRecord> records = new ArrayList<>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (CSVRecord record : cardGroupParser) {
|
for (CSVRecord record : cardGroupParser) {
|
||||||
importCardGroupMapping(database, db, record);
|
records.add(record);
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
@@ -236,20 +249,22 @@ public class CatimaImporter implements Importer
|
|||||||
} finally {
|
} finally {
|
||||||
cardGroupParser.close();
|
cardGroupParser.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (CSVRecord record : records) {
|
||||||
|
importCardGroupMapping(database, record);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import a single loyalty card into the database using the given
|
* Import a single loyalty card into the database using the given
|
||||||
* session.
|
* session.
|
||||||
*/
|
*/
|
||||||
private void importLoyaltyCard(Context context, SQLiteDatabase database, DBHelper helper, CSVRecord record)
|
private void importLoyaltyCard(Context context, SQLiteDatabase database, CSVRecord record)
|
||||||
throws IOException, FormatException
|
throws IOException, FormatException {
|
||||||
{
|
|
||||||
int id = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false);
|
int id = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false);
|
||||||
|
|
||||||
String store = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.STORE, record, "");
|
String store = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.STORE, record, "");
|
||||||
if(store.isEmpty())
|
if (store.isEmpty()) {
|
||||||
{
|
|
||||||
throw new FormatException("No store listed, but is required");
|
throw new FormatException("No store listed, but is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,12 +272,13 @@ public class CatimaImporter implements Importer
|
|||||||
Date expiry = null;
|
Date expiry = null;
|
||||||
try {
|
try {
|
||||||
expiry = new Date(CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.EXPIRY, record, true));
|
expiry = new Date(CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.EXPIRY, record, true));
|
||||||
} catch (NullPointerException | FormatException e) { }
|
} catch (NullPointerException | FormatException e) {
|
||||||
|
}
|
||||||
|
|
||||||
BigDecimal balance;
|
BigDecimal balance;
|
||||||
try {
|
try {
|
||||||
balance = new BigDecimal(CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE, record, null));
|
balance = new BigDecimal(CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE, record, null));
|
||||||
} catch (FormatException _e ) {
|
} catch (FormatException _e) {
|
||||||
// These fields did not exist in versions 1.8.1 and before
|
// These fields did not exist in versions 1.8.1 and before
|
||||||
// We catch this exception so we can still import old backups
|
// We catch this exception so we can still import old backups
|
||||||
balance = new BigDecimal("0");
|
balance = new BigDecimal("0");
|
||||||
@@ -270,72 +286,72 @@ public class CatimaImporter implements Importer
|
|||||||
|
|
||||||
Currency balanceType = null;
|
Currency balanceType = null;
|
||||||
String unparsedBalanceType = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE, record, "");
|
String unparsedBalanceType = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE, record, "");
|
||||||
if(!unparsedBalanceType.isEmpty()) {
|
if (!unparsedBalanceType.isEmpty()) {
|
||||||
balanceType = Currency.getInstance(unparsedBalanceType);
|
balanceType = Currency.getInstance(unparsedBalanceType);
|
||||||
}
|
}
|
||||||
|
|
||||||
String cardId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.CARD_ID, record, "");
|
String cardId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.CARD_ID, record, "");
|
||||||
if(cardId.isEmpty())
|
if (cardId.isEmpty()) {
|
||||||
{
|
|
||||||
throw new FormatException("No card ID listed, but is required");
|
throw new FormatException("No card ID listed, but is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
String barcodeId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BARCODE_ID, record, "");
|
String barcodeId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BARCODE_ID, record, "");
|
||||||
if(barcodeId.isEmpty())
|
if (barcodeId.isEmpty()) {
|
||||||
{
|
|
||||||
barcodeId = null;
|
barcodeId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
BarcodeFormat barcodeType = null;
|
CatimaBarcode barcodeType = null;
|
||||||
String unparsedBarcodeType = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, record, "");
|
String unparsedBarcodeType = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, record, "");
|
||||||
if(!unparsedBarcodeType.isEmpty())
|
if (!unparsedBarcodeType.isEmpty()) {
|
||||||
{
|
barcodeType = CatimaBarcode.fromName(unparsedBarcodeType);
|
||||||
barcodeType = BarcodeFormat.valueOf(unparsedBarcodeType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer headerColor = null;
|
Integer headerColor = null;
|
||||||
|
|
||||||
if(record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR))
|
if (record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR)) {
|
||||||
{
|
|
||||||
headerColor = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record, true);
|
headerColor = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
int starStatus = 0;
|
int starStatus = 0;
|
||||||
try {
|
try {
|
||||||
starStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.STAR_STATUS, record, false);
|
starStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.STAR_STATUS, record, false);
|
||||||
} catch (FormatException _e ) {
|
} catch (FormatException _e) {
|
||||||
// This field did not exist in versions 0.278 and before
|
// This field did not exist in versions 0.28 and before
|
||||||
// We catch this exception so we can still import old backups
|
// We catch this exception so we can still import old backups
|
||||||
}
|
}
|
||||||
if (starStatus != 1) starStatus = 0;
|
if (starStatus != 1) starStatus = 0;
|
||||||
|
|
||||||
helper.insertLoyaltyCard(database, id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus);
|
Long lastUsed = 0L;
|
||||||
|
try {
|
||||||
|
lastUsed = CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.LAST_USED, record, false);
|
||||||
|
} catch (FormatException _e) {
|
||||||
|
// This field did not exist in versions 2.5.0 and before
|
||||||
|
// We catch this exception so we can still import old backups
|
||||||
|
}
|
||||||
|
|
||||||
|
DBHelper.insertLoyaltyCard(database, id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus, lastUsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import a single group into the database using the given
|
* Import a single group into the database using the given
|
||||||
* session.
|
* session.
|
||||||
*/
|
*/
|
||||||
private void importGroup(SQLiteDatabase database, DBHelper helper, CSVRecord record)
|
private void importGroup(SQLiteDatabase database, CSVRecord record) throws FormatException {
|
||||||
throws IOException, FormatException
|
|
||||||
{
|
|
||||||
String id = CSVHelpers.extractString(DBHelper.LoyaltyCardDbGroups.ID, record, null);
|
String id = CSVHelpers.extractString(DBHelper.LoyaltyCardDbGroups.ID, record, null);
|
||||||
|
|
||||||
helper.insertGroup(database, id);
|
DBHelper.insertGroup(database, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import a single card to group mapping into the database using the given
|
* Import a single card to group mapping into the database using the given
|
||||||
* session.
|
* session.
|
||||||
*/
|
*/
|
||||||
private void importCardGroupMapping(SQLiteDatabase database, DBHelper helper, CSVRecord record)
|
private void importCardGroupMapping(SQLiteDatabase database, CSVRecord record) throws FormatException {
|
||||||
throws IOException, FormatException
|
|
||||||
{
|
|
||||||
Integer cardId = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIdsGroups.cardID, record, false);
|
Integer cardId = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIdsGroups.cardID, record, false);
|
||||||
String groupId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIdsGroups.groupID, record, null);
|
String groupId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIdsGroups.groupID, record, null);
|
||||||
|
|
||||||
List<Group> cardGroups = helper.getLoyaltyCardGroups(cardId);
|
List<Group> cardGroups = DBHelper.getLoyaltyCardGroups(database, cardId);
|
||||||
cardGroups.add(helper.getGroup(groupId));
|
cardGroups.add(DBHelper.getGroup(database, groupId));
|
||||||
helper.setLoyaltyCardGroups(database, cardId, cardGroups);
|
DBHelper.setLoyaltyCardGroups(database, cardId, cardGroups);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
package protect.card_locker.importexport;
|
package protect.card_locker.importexport;
|
||||||
|
|
||||||
public enum DataFormat
|
public enum DataFormat {
|
||||||
{
|
|
||||||
Catima,
|
Catima,
|
||||||
Fidme,
|
Fidme,
|
||||||
Stocard,
|
Stocard,
|
||||||
VoucherVault
|
VoucherVault;
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package protect.card_locker.importexport;
|
package protect.card_locker.importexport;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
|
|
||||||
import protect.card_locker.DBHelper;
|
import protect.card_locker.DBHelper;
|
||||||
|
|
||||||
@@ -12,11 +12,11 @@ import protect.card_locker.DBHelper;
|
|||||||
* Interface for a class which can export the contents of the database
|
* Interface for a class which can export the contents of the database
|
||||||
* in a given format.
|
* in a given format.
|
||||||
*/
|
*/
|
||||||
public interface Exporter
|
public interface Exporter {
|
||||||
{
|
|
||||||
/**
|
/**
|
||||||
* Export the database to the output stream in a given format.
|
* Export the database to the output stream in a given format.
|
||||||
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
void exportData(Context context, DBHelper db, OutputStream output) throws IOException, InterruptedException;
|
void exportData(Context context, SQLiteDatabase database, OutputStream output, char[] password) throws IOException, InterruptedException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package protect.card_locker.importexport;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
|
||||||
|
|
||||||
import net.lingala.zip4j.io.inputstream.ZipInputStream;
|
import net.lingala.zip4j.io.inputstream.ZipInputStream;
|
||||||
import net.lingala.zip4j.model.LocalFileHeader;
|
import net.lingala.zip4j.model.LocalFileHeader;
|
||||||
|
|
||||||
@@ -20,19 +18,19 @@ import java.math.BigDecimal;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
|
|
||||||
|
import protect.card_locker.CatimaBarcode;
|
||||||
import protect.card_locker.DBHelper;
|
import protect.card_locker.DBHelper;
|
||||||
import protect.card_locker.FormatException;
|
import protect.card_locker.FormatException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for importing a database from CSV (Comma Separate Values)
|
* Class for importing a database from CSV (Comma Separate Values)
|
||||||
* formatted data.
|
* formatted data.
|
||||||
*
|
* <p>
|
||||||
* The database's loyalty cards are expected to appear in the CSV 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.
|
* A header is expected for the each table showing the names of the columns.
|
||||||
*/
|
*/
|
||||||
public class FidmeImporter implements Importer
|
public class FidmeImporter implements Importer {
|
||||||
{
|
public void importData(Context context, SQLiteDatabase database, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
||||||
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
|
||||||
// We actually retrieve a .zip file
|
// We actually retrieve a .zip file
|
||||||
ZipInputStream zipInputStream = new ZipInputStream(input, password);
|
ZipInputStream zipInputStream = new ZipInputStream(input, password);
|
||||||
|
|
||||||
@@ -54,14 +52,11 @@ public class FidmeImporter implements Importer
|
|||||||
throw new FormatException("Couldn't find loyalty_programs.csv in zip file or it is empty");
|
throw new FormatException("Couldn't find loyalty_programs.csv in zip file or it is empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
SQLiteDatabase database = db.getWritableDatabase();
|
final CSVParser fidmeParser = new CSVParser(new StringReader(loyaltyCards.toString()), CSVFormat.RFC4180.builder().setDelimiter(';').setHeader().build());
|
||||||
database.beginTransaction();
|
|
||||||
|
|
||||||
final CSVParser fidmeParser = new CSVParser(new StringReader(loyaltyCards.toString()), CSVFormat.RFC4180.withDelimiter(';').withHeader());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (CSVRecord record : fidmeParser) {
|
for (CSVRecord record : fidmeParser) {
|
||||||
importLoyaltyCard(database, db, record);
|
importLoyaltyCard(database, record);
|
||||||
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
@@ -73,10 +68,6 @@ public class FidmeImporter implements Importer
|
|||||||
fidmeParser.close();
|
fidmeParser.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
database.setTransactionSuccessful();
|
|
||||||
database.endTransaction();
|
|
||||||
database.close();
|
|
||||||
|
|
||||||
zipInputStream.close();
|
zipInputStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,9 +75,8 @@ public class FidmeImporter implements Importer
|
|||||||
* Import a single loyalty card into the database using the given
|
* Import a single loyalty card into the database using the given
|
||||||
* session.
|
* session.
|
||||||
*/
|
*/
|
||||||
private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record)
|
private void importLoyaltyCard(SQLiteDatabase database, CSVRecord record)
|
||||||
throws IOException, FormatException
|
throws FormatException {
|
||||||
{
|
|
||||||
// A loyalty card export from Fidme contains the following fields:
|
// A loyalty card export from Fidme contains the following fields:
|
||||||
// Retailer (store name)
|
// Retailer (store name)
|
||||||
// Program (program name)
|
// Program (program name)
|
||||||
@@ -98,8 +88,7 @@ public class FidmeImporter implements Importer
|
|||||||
// The store is called Retailer
|
// The store is called Retailer
|
||||||
String store = CSVHelpers.extractString("Retailer", record, "");
|
String store = CSVHelpers.extractString("Retailer", record, "");
|
||||||
|
|
||||||
if (store.isEmpty())
|
if (store.isEmpty()) {
|
||||||
{
|
|
||||||
throw new FormatException("No store listed, but is required");
|
throw new FormatException("No store listed, but is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,21 +108,20 @@ public class FidmeImporter implements Importer
|
|||||||
|
|
||||||
// The ID is called reference
|
// The ID is called reference
|
||||||
String cardId = CSVHelpers.extractString("Reference", record, "");
|
String cardId = CSVHelpers.extractString("Reference", record, "");
|
||||||
if(cardId.isEmpty())
|
if (cardId.isEmpty()) {
|
||||||
{
|
|
||||||
throw new FormatException("No card ID listed, but is required");
|
throw new FormatException("No card ID listed, but is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sadly, Fidme exports don't contain the card type
|
// 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
|
// 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
|
// TODO: Hook this into our own loyalty card DB if we ever get one
|
||||||
BarcodeFormat barcodeType = null;
|
CatimaBarcode barcodeType = null;
|
||||||
|
|
||||||
// No favourite data in the export either
|
// No favourite data in the export either
|
||||||
int starStatus = 0;
|
int starStatus = 0;
|
||||||
|
|
||||||
// TODO: Front and back image
|
// TODO: Front and back image
|
||||||
|
|
||||||
helper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, null, starStatus);
|
DBHelper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, null, starStatus, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
package protect.card_locker.importexport;
|
package protect.card_locker.importexport;
|
||||||
|
|
||||||
public enum ImportExportResult
|
public enum ImportExportResult {
|
||||||
{
|
|
||||||
Success,
|
Success,
|
||||||
GenericFailure,
|
GenericFailure,
|
||||||
BadPassword
|
BadPassword;
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package protect.card_locker.importexport;
|
package protect.card_locker.importexport;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
|
||||||
@@ -8,20 +9,19 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
|
|
||||||
import protect.card_locker.DBHelper;
|
|
||||||
import protect.card_locker.FormatException;
|
import protect.card_locker.FormatException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for a class which can import the contents of a stream
|
* Interface for a class which can import the contents of a stream
|
||||||
* into the database.
|
* into the database.
|
||||||
*/
|
*/
|
||||||
public interface Importer
|
public interface Importer {
|
||||||
{
|
|
||||||
/**
|
/**
|
||||||
* Import data from the input stream in a given format into
|
* Import data from the input stream in a given format into
|
||||||
* the database.
|
* the database.
|
||||||
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws FormatException
|
* @throws FormatException
|
||||||
*/
|
*/
|
||||||
void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, InterruptedException, JSONException, ParseException;
|
void importData(Context context, SQLiteDatabase database, InputStream input, char[] password) throws IOException, FormatException, InterruptedException, JSONException, ParseException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,31 @@
|
|||||||
package protect.card_locker.importexport;
|
package protect.card_locker.importexport;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
|
|
||||||
import protect.card_locker.DBHelper;
|
import protect.card_locker.DBHelper;
|
||||||
|
|
||||||
public class MultiFormatExporter
|
public class MultiFormatExporter {
|
||||||
{
|
|
||||||
private static final String TAG = "Catima";
|
private static final String TAG = "Catima";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to export data to the output stream in the
|
* Attempts to export data to the output stream in the
|
||||||
* given format, if possible.
|
* given format, if possible.
|
||||||
*
|
* <p>
|
||||||
* The output stream is closed on success.
|
* The output stream is closed on success.
|
||||||
*
|
*
|
||||||
* @return ImportExportResult.Success if the database was successfully exported,
|
* @return ImportExportResult.Success if the database was successfully exported,
|
||||||
* another ImportExportResult otherwise. If not Success, partial data may have been
|
* another ImportExportResult otherwise. If not Success, partial data may have been
|
||||||
* written to the output stream, and it should be discarded.
|
* written to the output stream, and it should be discarded.
|
||||||
*/
|
*/
|
||||||
public static ImportExportResult exportData(Context context, DBHelper db, OutputStream output, DataFormat format)
|
public static ImportExportResult exportData(Context context, SQLiteDatabase database, OutputStream output, DataFormat format, char[] password) {
|
||||||
{
|
|
||||||
Exporter exporter = null;
|
Exporter exporter = null;
|
||||||
|
|
||||||
switch(format)
|
switch (format) {
|
||||||
{
|
|
||||||
case Catima:
|
case Catima:
|
||||||
exporter = new CatimaExporter();
|
exporter = new CatimaExporter();
|
||||||
break;
|
break;
|
||||||
@@ -37,26 +34,18 @@ public class MultiFormatExporter
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(exporter != null)
|
if (exporter != null) {
|
||||||
{
|
try {
|
||||||
try
|
exporter.exportData(context, database, output, password);
|
||||||
{
|
|
||||||
exporter.exportData(context, db, output);
|
|
||||||
return ImportExportResult.Success;
|
return ImportExportResult.Success;
|
||||||
}
|
} catch (IOException e) {
|
||||||
catch(IOException e)
|
|
||||||
{
|
|
||||||
Log.e(TAG, "Failed to export data", e);
|
Log.e(TAG, "Failed to export data", e);
|
||||||
}
|
} catch (InterruptedException e) {
|
||||||
catch(InterruptedException e)
|
|
||||||
{
|
|
||||||
Log.e(TAG, "Failed to export data", e);
|
Log.e(TAG, "Failed to export data", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ImportExportResult.GenericFailure;
|
return ImportExportResult.GenericFailure;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.e(TAG, "Unsupported data format exported: " + format.name());
|
Log.e(TAG, "Unsupported data format exported: " + format.name());
|
||||||
return ImportExportResult.GenericFailure;
|
return ImportExportResult.GenericFailure;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package protect.card_locker.importexport;
|
package protect.card_locker.importexport;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import net.lingala.zip4j.exception.ZipException;
|
import net.lingala.zip4j.exception.ZipException;
|
||||||
@@ -14,14 +15,13 @@ import java.text.ParseException;
|
|||||||
import protect.card_locker.DBHelper;
|
import protect.card_locker.DBHelper;
|
||||||
import protect.card_locker.FormatException;
|
import protect.card_locker.FormatException;
|
||||||
|
|
||||||
public class MultiFormatImporter
|
public class MultiFormatImporter {
|
||||||
{
|
|
||||||
private static final String TAG = "Catima";
|
private static final String TAG = "Catima";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to import data from the input stream of the
|
* Attempts to import data from the input stream of the
|
||||||
* given format into the database.
|
* given format into the database.
|
||||||
*
|
* <p>
|
||||||
* The input stream is not closed, and doing so is the
|
* The input stream is not closed, and doing so is the
|
||||||
* responsibility of the caller.
|
* responsibility of the caller.
|
||||||
*
|
*
|
||||||
@@ -29,12 +29,10 @@ public class MultiFormatImporter
|
|||||||
* or another result otherwise. If no Success, no data was written to
|
* or another result otherwise. If no Success, no data was written to
|
||||||
* the database.
|
* the database.
|
||||||
*/
|
*/
|
||||||
public static ImportExportResult importData(Context context, DBHelper db, InputStream input, DataFormat format, char[] password)
|
public static ImportExportResult importData(Context context, SQLiteDatabase database, InputStream input, DataFormat format, char[] password) {
|
||||||
{
|
|
||||||
Importer importer = null;
|
Importer importer = null;
|
||||||
|
|
||||||
switch(format)
|
switch (format) {
|
||||||
{
|
|
||||||
case Catima:
|
case Catima:
|
||||||
importer = new CatimaImporter();
|
importer = new CatimaImporter();
|
||||||
break;
|
break;
|
||||||
@@ -49,25 +47,20 @@ public class MultiFormatImporter
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (importer != null)
|
if (importer != null) {
|
||||||
{
|
database.beginTransaction();
|
||||||
try
|
try {
|
||||||
{
|
importer.importData(context, database, input, password);
|
||||||
importer.importData(context, db, input, password);
|
database.setTransactionSuccessful();
|
||||||
return ImportExportResult.Success;
|
return ImportExportResult.Success;
|
||||||
}
|
} catch (ZipException e) {
|
||||||
catch(ZipException e)
|
|
||||||
{
|
|
||||||
return ImportExportResult.BadPassword;
|
return ImportExportResult.BadPassword;
|
||||||
}
|
} catch (IOException | FormatException | InterruptedException | JSONException | ParseException | NullPointerException e) {
|
||||||
catch(IOException | FormatException | InterruptedException | JSONException | ParseException | NullPointerException e)
|
|
||||||
{
|
|
||||||
Log.e(TAG, "Failed to import data", e);
|
Log.e(TAG, "Failed to import data", e);
|
||||||
|
} finally {
|
||||||
|
database.endTransaction();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.e(TAG, "Unsupported data format imported: " + format.name());
|
Log.e(TAG, "Unsupported data format imported: " + format.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,47 +3,61 @@ package protect.card_locker.importexport;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
import net.lingala.zip4j.io.inputstream.ZipInputStream;
|
import net.lingala.zip4j.io.inputstream.ZipInputStream;
|
||||||
import net.lingala.zip4j.model.LocalFileHeader;
|
import net.lingala.zip4j.model.LocalFileHeader;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.apache.commons.csv.CSVFormat;
|
||||||
|
import org.apache.commons.csv.CSVParser;
|
||||||
|
import org.apache.commons.csv.CSVRecord;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.Reader;
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
import protect.card_locker.CatimaBarcode;
|
||||||
import protect.card_locker.DBHelper;
|
import protect.card_locker.DBHelper;
|
||||||
import protect.card_locker.FormatException;
|
import protect.card_locker.FormatException;
|
||||||
|
import protect.card_locker.ImageLocationType;
|
||||||
|
import protect.card_locker.R;
|
||||||
import protect.card_locker.Utils;
|
import protect.card_locker.Utils;
|
||||||
import protect.card_locker.ZipUtils;
|
import protect.card_locker.ZipUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for importing a database from CSV (Comma Separate Values)
|
* Class for importing a database from CSV (Comma Separate Values)
|
||||||
* formatted data.
|
* formatted data.
|
||||||
*
|
* <p>
|
||||||
* The database's loyalty cards are expected to appear in the CSV 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.
|
* A header is expected for the each table showing the names of the columns.
|
||||||
*/
|
*/
|
||||||
public class StocardImporter implements Importer
|
public class StocardImporter implements Importer {
|
||||||
{
|
public void importData(Context context, SQLiteDatabase database, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
||||||
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
|
||||||
HashMap<String, HashMap<String, Object>> loyaltyCardHashMap = new HashMap<>();
|
HashMap<String, HashMap<String, Object>> loyaltyCardHashMap = new HashMap<>();
|
||||||
HashMap<String, String> providers = new HashMap<>();
|
HashMap<String, HashMap<String, String>> providers = new HashMap<>();
|
||||||
|
|
||||||
|
final CSVParser parser = new CSVParser(new InputStreamReader(context.getResources().openRawResource(R.raw.stocard_stores), StandardCharsets.UTF_8), CSVFormat.RFC4180.builder().setHeader().build());
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (CSVRecord record : parser) {
|
||||||
|
HashMap<String, String> recordData = new HashMap<>();
|
||||||
|
recordData.put("name", record.get("name"));
|
||||||
|
recordData.put("barcodeFormat", record.get("barcodeFormat"));
|
||||||
|
|
||||||
|
providers.put(record.get("_id"), recordData);
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.close();
|
||||||
|
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||||
|
throw new FormatException("Issue parsing CSV data", e);
|
||||||
|
}
|
||||||
|
|
||||||
ZipInputStream zipInputStream = new ZipInputStream(input, password);
|
ZipInputStream zipInputStream = new ZipInputStream(input, password);
|
||||||
|
|
||||||
@@ -56,7 +70,7 @@ public class StocardImporter implements Importer
|
|||||||
String[] nameParts = fileName.split("/");
|
String[] nameParts = fileName.split("/");
|
||||||
|
|
||||||
if (providersFileName == null) {
|
if (providersFileName == null) {
|
||||||
providersFileName = new String[] {
|
providersFileName = new String[]{
|
||||||
nameParts[0],
|
nameParts[0],
|
||||||
"sync",
|
"sync",
|
||||||
"data",
|
"data",
|
||||||
@@ -64,7 +78,7 @@ public class StocardImporter implements Importer
|
|||||||
nameParts[0],
|
nameParts[0],
|
||||||
"analytics-properties.json"
|
"analytics-properties.json"
|
||||||
};
|
};
|
||||||
cardBaseName = new String[] {
|
cardBaseName = new String[]{
|
||||||
nameParts[0],
|
nameParts[0],
|
||||||
"sync",
|
"sync",
|
||||||
"data",
|
"data",
|
||||||
@@ -74,9 +88,7 @@ public class StocardImporter implements Importer
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startsWith(nameParts, providersFileName, 0) && !localFileHeader.isDirectory()) {
|
if (startsWith(nameParts, cardBaseName, 1)) {
|
||||||
providers = parseProviders(zipInputStream);
|
|
||||||
} else if (startsWith(nameParts, cardBaseName, 1)) {
|
|
||||||
// Extract cardName
|
// Extract cardName
|
||||||
cardName = nameParts[cardBaseName.length].split("\\.", 2)[0];
|
cardName = nameParts[cardBaseName.length].split("\\.", 2)[0];
|
||||||
|
|
||||||
@@ -97,19 +109,19 @@ public class StocardImporter implements Importer
|
|||||||
cardName,
|
cardName,
|
||||||
"_providerId",
|
"_providerId",
|
||||||
jsonObject
|
jsonObject
|
||||||
.getJSONObject("input_provider_reference")
|
.getJSONObject("input_provider_reference")
|
||||||
.getString("identifier")
|
.getString("identifier")
|
||||||
.substring("/loyalty-card-providers/".length())
|
.substring("/loyalty-card-providers/".length())
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
if (jsonObject.has("input_barcode_format")) {
|
||||||
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
||||||
loyaltyCardHashMap,
|
loyaltyCardHashMap,
|
||||||
cardName,
|
cardName,
|
||||||
"barcodeType",
|
"barcodeType",
|
||||||
jsonObject.getString("input_barcode_format")
|
jsonObject.getString("input_barcode_format")
|
||||||
);
|
);
|
||||||
} catch (JSONException ignored) {}
|
}
|
||||||
}
|
}
|
||||||
} else if (fileName.endsWith("notes/default.json")) {
|
} else if (fileName.endsWith("notes/default.json")) {
|
||||||
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
||||||
@@ -117,7 +129,7 @@ public class StocardImporter implements Importer
|
|||||||
cardName,
|
cardName,
|
||||||
"note",
|
"note",
|
||||||
ZipUtils.readJSON(zipInputStream)
|
ZipUtils.readJSON(zipInputStream)
|
||||||
.getString("content")
|
.getString("content")
|
||||||
);
|
);
|
||||||
} else if (fileName.endsWith("/images/front.png")) {
|
} else if (fileName.endsWith("/images/front.png")) {
|
||||||
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
||||||
@@ -141,37 +153,33 @@ public class StocardImporter implements Importer
|
|||||||
throw new FormatException("Couldn't find any loyalty cards in this Stocard export.");
|
throw new FormatException("Couldn't find any loyalty cards in this Stocard export.");
|
||||||
}
|
}
|
||||||
|
|
||||||
SQLiteDatabase database = db.getWritableDatabase();
|
|
||||||
database.beginTransaction();
|
|
||||||
|
|
||||||
for (HashMap<String, Object> loyaltyCardData : loyaltyCardHashMap.values()) {
|
for (HashMap<String, Object> loyaltyCardData : loyaltyCardHashMap.values()) {
|
||||||
String store = providers.get(loyaltyCardData.get("_providerId").toString());
|
String providerId = (String) loyaltyCardData.get("_providerId");
|
||||||
String note = (String) Utils.hashmapGetOrDefault(loyaltyCardData, "note", "");
|
HashMap<String, String> providerData = providers.get(providerId);
|
||||||
|
|
||||||
|
String store = providerData != null ? providerData.get("name") : providerId;
|
||||||
|
String note = (String) Utils.mapGetOrDefault(loyaltyCardData, "note", "");
|
||||||
String cardId = (String) loyaltyCardData.get("cardId");
|
String cardId = (String) loyaltyCardData.get("cardId");
|
||||||
String barcodeTypeString = (String) Utils.hashmapGetOrDefault(loyaltyCardData, "barcodeType", null);
|
String barcodeTypeString = (String) Utils.mapGetOrDefault(loyaltyCardData, "barcodeType", providerData != null ? providerData.get("barcodeFormat") : null);
|
||||||
BarcodeFormat barcodeType = null;
|
CatimaBarcode barcodeType = null;
|
||||||
if (barcodeTypeString != null) {
|
if (barcodeTypeString != null) {
|
||||||
if (barcodeTypeString.equals("RSS_DATABAR_EXPANDED")) {
|
if (barcodeTypeString.equals("RSS_DATABAR_EXPANDED")) {
|
||||||
barcodeType = BarcodeFormat.RSS_EXPANDED;
|
barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.RSS_EXPANDED);
|
||||||
} else {
|
} else {
|
||||||
barcodeType = BarcodeFormat.valueOf(barcodeTypeString);
|
barcodeType = CatimaBarcode.fromName(barcodeTypeString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
long loyaltyCardInternalId = db.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, null, 0);
|
long loyaltyCardInternalId = DBHelper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, null, 0, null);
|
||||||
|
|
||||||
if (loyaltyCardData.containsKey("frontImage")) {
|
if (loyaltyCardData.containsKey("frontImage")) {
|
||||||
Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("frontImage"), (int) loyaltyCardInternalId, true);
|
Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("frontImage"), (int) loyaltyCardInternalId, ImageLocationType.front);
|
||||||
}
|
}
|
||||||
if (loyaltyCardData.containsKey("backImage")) {
|
if (loyaltyCardData.containsKey("backImage")) {
|
||||||
Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("backImage"), (int) loyaltyCardInternalId, false);
|
Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("backImage"), (int) loyaltyCardInternalId, ImageLocationType.back);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
database.setTransactionSuccessful();
|
|
||||||
database.endTransaction();
|
|
||||||
database.close();
|
|
||||||
|
|
||||||
zipInputStream.close();
|
zipInputStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,33 +208,4 @@ public class StocardImporter implements Importer
|
|||||||
|
|
||||||
return loyaltyCardHashMap;
|
return loyaltyCardHashMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HashMap<String, String> parseProviders(ZipInputStream zipInputStream) throws IOException, JSONException {
|
|
||||||
// FIXME: This is probably completely wrong, but it works for the one and only test file I have
|
|
||||||
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
|
|
||||||
|
|
||||||
JSONArray providerIdList = jsonObject.getJSONArray("provider_id_list");
|
|
||||||
JSONArray providerList = jsonObject.getJSONArray("provider_list");
|
|
||||||
|
|
||||||
// Resort, put IDs with - in them after IDs without any -
|
|
||||||
List<String> providerIds = new ArrayList<>();
|
|
||||||
List<String> customProviderIds = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < providerIdList.length(); i++) {
|
|
||||||
String providerId = providerIdList.get(i).toString();
|
|
||||||
if (providerId.contains("-")) {
|
|
||||||
customProviderIds.add(providerId);
|
|
||||||
} else {
|
|
||||||
providerIds.add(providerId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
providerIds.addAll(customProviderIds);
|
|
||||||
|
|
||||||
HashMap<String, String> providers = new HashMap<>();
|
|
||||||
for (int i = 0; i < jsonObject.getInt("number_of_cards"); i++) {
|
|
||||||
providers.put(providerIds.get(i), providerList.get(i).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return providers;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -23,19 +23,20 @@ import java.util.Currency;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import protect.card_locker.CatimaBarcode;
|
||||||
import protect.card_locker.DBHelper;
|
import protect.card_locker.DBHelper;
|
||||||
import protect.card_locker.FormatException;
|
import protect.card_locker.FormatException;
|
||||||
|
import protect.card_locker.Utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for importing a database from CSV (Comma Separate Values)
|
* Class for importing a database from CSV (Comma Separate Values)
|
||||||
* formatted data.
|
* formatted data.
|
||||||
*
|
* <p>
|
||||||
* The database's loyalty cards are expected to appear in the CSV 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.
|
* A header is expected for the each table showing the names of the columns.
|
||||||
*/
|
*/
|
||||||
public class VoucherVaultImporter implements Importer
|
public class VoucherVaultImporter implements Importer {
|
||||||
{
|
public void importData(Context context, SQLiteDatabase database, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
||||||
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
|
||||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
|
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@@ -45,9 +46,6 @@ public class VoucherVaultImporter implements Importer
|
|||||||
}
|
}
|
||||||
JSONArray jsonArray = new JSONArray(sb.toString());
|
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
|
// See https://github.com/tim-smart/vouchervault/issues/4#issuecomment-788226503 for more info
|
||||||
for (int i = 0; i < jsonArray.length(); i++) {
|
for (int i = 0; i < jsonArray.length(); i++) {
|
||||||
JSONObject jsonCard = jsonArray.getJSONObject(i);
|
JSONObject jsonCard = jsonArray.getJSONObject(i);
|
||||||
@@ -74,24 +72,24 @@ public class VoucherVaultImporter implements Importer
|
|||||||
|
|
||||||
String cardId = jsonCard.getString("code");
|
String cardId = jsonCard.getString("code");
|
||||||
|
|
||||||
BarcodeFormat barcodeType = null;
|
CatimaBarcode barcodeType = null;
|
||||||
|
|
||||||
String codeTypeFromJSON = jsonCard.getString("codeType");
|
String codeTypeFromJSON = jsonCard.getString("codeType");
|
||||||
switch (codeTypeFromJSON) {
|
switch (codeTypeFromJSON) {
|
||||||
case "CODE128":
|
case "CODE128":
|
||||||
barcodeType = BarcodeFormat.CODE_128;
|
barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.CODE_128);
|
||||||
break;
|
break;
|
||||||
case "CODE39":
|
case "CODE39":
|
||||||
barcodeType = BarcodeFormat.CODE_39;
|
barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.CODE_39);
|
||||||
break;
|
break;
|
||||||
case "EAN13":
|
case "EAN13":
|
||||||
barcodeType = BarcodeFormat.EAN_13;
|
barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.EAN_13);
|
||||||
break;
|
break;
|
||||||
case "PDF417":
|
case "PDF417":
|
||||||
barcodeType = BarcodeFormat.PDF_417;
|
barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.PDF_417);
|
||||||
break;
|
break;
|
||||||
case "QR":
|
case "QR":
|
||||||
barcodeType = BarcodeFormat.QR_CODE;
|
barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.QR_CODE);
|
||||||
break;
|
break;
|
||||||
case "TEXT":
|
case "TEXT":
|
||||||
break;
|
break;
|
||||||
@@ -128,13 +126,9 @@ public class VoucherVaultImporter implements Importer
|
|||||||
throw new FormatException("Unknown colour type found: " + colorFromJSON);
|
throw new FormatException("Unknown colour type found: " + colorFromJSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
db.insertLoyaltyCard(store, "", expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0);
|
DBHelper.insertLoyaltyCard(database, store, "", expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0, Utils.getUnixTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
database.setTransactionSuccessful();
|
|
||||||
database.endTransaction();
|
|
||||||
database.close();
|
|
||||||
|
|
||||||
bufferedReader.close();
|
bufferedReader.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,111 +3,103 @@ package protect.card_locker.preferences;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import androidx.annotation.IntegerRes;
|
import androidx.annotation.IntegerRes;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import protect.card_locker.R;
|
import protect.card_locker.R;
|
||||||
|
import protect.card_locker.Utils;
|
||||||
|
|
||||||
public class Settings
|
public class Settings {
|
||||||
{
|
private final Context mContext;
|
||||||
private Context context;
|
private SharedPreferences mSettings;
|
||||||
private SharedPreferences settings;
|
|
||||||
|
|
||||||
public Settings(Context context)
|
public Settings(Context context) {
|
||||||
{
|
mContext = context.getApplicationContext();
|
||||||
this.context = context;
|
mSettings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
this.settings = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getResString(@StringRes int resId)
|
private String getResString(@StringRes int resId) {
|
||||||
{
|
return mContext.getString(resId);
|
||||||
return context.getString(resId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getResInt(@IntegerRes int resId)
|
private int getResInt(@IntegerRes int resId) {
|
||||||
{
|
return mContext.getResources().getInteger(resId);
|
||||||
return context.getResources().getInteger(resId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getString(@StringRes int keyId, String defaultValue)
|
private String getString(@StringRes int keyId, String defaultValue) {
|
||||||
{
|
return mSettings.getString(getResString(keyId), defaultValue);
|
||||||
return settings.getString(getResString(keyId), defaultValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getInt(@StringRes int keyId, @IntegerRes int defaultId)
|
private int getInt(@StringRes int keyId, @IntegerRes int defaultId) {
|
||||||
{
|
return mSettings.getInt(getResString(keyId), getResInt(defaultId));
|
||||||
return settings.getInt(getResString(keyId), getResInt(defaultId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean getBoolean(@StringRes int keyId, boolean defaultValue)
|
private boolean getBoolean(@StringRes int keyId, boolean defaultValue) {
|
||||||
{
|
return mSettings.getBoolean(getResString(keyId), defaultValue);
|
||||||
return settings.getBoolean(getResString(keyId), defaultValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTheme()
|
public Locale getLocale() {
|
||||||
{
|
String value = getString(R.string.settings_key_locale, "");
|
||||||
|
|
||||||
|
if (value.length() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utils.stringToLocale(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTheme() {
|
||||||
String value = getString(R.string.settings_key_theme, getResString(R.string.settings_key_system_theme));
|
String value = getString(R.string.settings_key_theme, getResString(R.string.settings_key_system_theme));
|
||||||
|
|
||||||
if(value.equals(getResString(R.string.settings_key_light_theme)))
|
if (value.equals(getResString(R.string.settings_key_light_theme))) {
|
||||||
{
|
|
||||||
return AppCompatDelegate.MODE_NIGHT_NO;
|
return AppCompatDelegate.MODE_NIGHT_NO;
|
||||||
}
|
} else if (value.equals(getResString(R.string.settings_key_dark_theme))) {
|
||||||
else if(value.equals(getResString(R.string.settings_key_dark_theme)))
|
|
||||||
{
|
|
||||||
return AppCompatDelegate.MODE_NIGHT_YES;
|
return AppCompatDelegate.MODE_NIGHT_YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
return AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
|
return AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getFontSizeScale()
|
public double getFontSizeScale() {
|
||||||
{
|
|
||||||
return getInt(R.string.settings_key_max_font_size_scale, R.integer.settings_max_font_size_scale_pct) / 100.0;
|
return getInt(R.string.settings_key_max_font_size_scale, R.integer.settings_max_font_size_scale_pct) / 100.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSmallFont()
|
public int getSmallFont() {
|
||||||
{
|
|
||||||
return 14;
|
return 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMediumFont()
|
public int getMediumFont() {
|
||||||
{
|
|
||||||
return 28;
|
return 28;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLargeFont()
|
public int getLargeFont() {
|
||||||
{
|
|
||||||
return 40;
|
return 40;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFontSizeMin(int fontSize)
|
public int getFontSizeMin(int fontSize) {
|
||||||
{
|
|
||||||
return (int) (Math.round(fontSize / 2.0) - 1);
|
return (int) (Math.round(fontSize / 2.0) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFontSizeMax(int fontSize)
|
public int getFontSizeMax(int fontSize) {
|
||||||
{
|
|
||||||
return (int) Math.round(fontSize * getFontSizeScale());
|
return (int) Math.round(fontSize * getFontSizeScale());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean useMaxBrightnessDisplayingBarcode()
|
public boolean useMaxBrightnessDisplayingBarcode() {
|
||||||
{
|
|
||||||
return getBoolean(R.string.settings_key_display_barcode_max_brightness, true);
|
return getBoolean(R.string.settings_key_display_barcode_max_brightness, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getLockBarcodeScreenOrientation()
|
public boolean getLockBarcodeScreenOrientation() {
|
||||||
{
|
|
||||||
return getBoolean(R.string.settings_key_lock_barcode_orientation, false);
|
return getBoolean(R.string.settings_key_lock_barcode_orientation, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getKeepScreenOn()
|
public boolean getKeepScreenOn() {
|
||||||
{
|
|
||||||
return getBoolean(R.string.settings_key_keep_screen_on, true);
|
return getBoolean(R.string.settings_key_keep_screen_on, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getDisableLockscreenWhileViewingCard()
|
public boolean getDisableLockscreenWhileViewingCard() {
|
||||||
{
|
|
||||||
return getBoolean(R.string.settings_key_disable_lockscreen_while_viewing_card, true);
|
return getBoolean(R.string.settings_key_disable_lockscreen_while_viewing_card, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,96 +1,157 @@
|
|||||||
package protect.card_locker.preferences;
|
package protect.card_locker.preferences;
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.preference.ListPreference;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import nl.invissvenska.numberpickerpreference.NumberDialogPreference;
|
import nl.invissvenska.numberpickerpreference.NumberDialogPreference;
|
||||||
import nl.invissvenska.numberpickerpreference.NumberPickerPreferenceDialogFragment;
|
import nl.invissvenska.numberpickerpreference.NumberPickerPreferenceDialogFragment;
|
||||||
|
import protect.card_locker.CatimaAppCompatActivity;
|
||||||
|
import protect.card_locker.MainActivity;
|
||||||
import protect.card_locker.R;
|
import protect.card_locker.R;
|
||||||
|
import protect.card_locker.Utils;
|
||||||
|
|
||||||
|
public class SettingsActivity extends CatimaAppCompatActivity {
|
||||||
|
|
||||||
|
private final static String RELOAD_MAIN_STATE = "mReloadMain";
|
||||||
|
private SettingsFragment fragment;
|
||||||
|
|
||||||
public class SettingsActivity extends AppCompatActivity
|
|
||||||
{
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState)
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
{
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
setTitle(R.string.settings);
|
||||||
setContentView(R.layout.settings_activity);
|
setContentView(R.layout.settings_activity);
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
if(actionBar != null)
|
if (actionBar != null) {
|
||||||
{
|
|
||||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display the fragment as the main content.
|
// Display the fragment as the main content.
|
||||||
|
fragment = new SettingsFragment();
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.replace(R.id.settings_container, new SettingsFragment())
|
.replace(R.id.settings_container, fragment)
|
||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
|
// restore reload main state
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
fragment.mReloadMain = savedInstanceState.getBoolean(RELOAD_MAIN_STATE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item)
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
{
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putBoolean(RELOAD_MAIN_STATE, fragment.mReloadMain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
|
|
||||||
if(id == android.R.id.home)
|
if (id == android.R.id.home) {
|
||||||
{
|
finishSettingsActivity();
|
||||||
finish();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SettingsFragment extends PreferenceFragmentCompat
|
@Override
|
||||||
{
|
public void onBackPressed() {
|
||||||
|
finishSettingsActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishSettingsActivity() {
|
||||||
|
if (fragment.mReloadMain) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.putExtra(MainActivity.RESTART_ACTIVITY_INTENT, true);
|
||||||
|
setResult(Activity.RESULT_OK, intent);
|
||||||
|
} else {
|
||||||
|
setResult(Activity.RESULT_OK);
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
private static final String DIALOG_FRAGMENT_TAG = "SettingsFragment";
|
private static final String DIALOG_FRAGMENT_TAG = "SettingsFragment";
|
||||||
|
|
||||||
|
public boolean mReloadMain;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey)
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
{
|
|
||||||
// Load the preferences from an XML resource
|
// Load the preferences from an XML resource
|
||||||
addPreferencesFromResource(R.xml.preferences);
|
addPreferencesFromResource(R.xml.preferences);
|
||||||
|
|
||||||
|
// Show pretty names
|
||||||
|
ListPreference localePreference = findPreference(getResources().getString(R.string.settings_key_locale));
|
||||||
|
assert localePreference != null;
|
||||||
|
CharSequence[] entryValues = localePreference.getEntryValues();
|
||||||
|
List<CharSequence> entries = new ArrayList<>();
|
||||||
|
for (CharSequence entry : entryValues) {
|
||||||
|
if (entry.length() == 0) {
|
||||||
|
entries.add(getResources().getString(R.string.settings_system_locale));
|
||||||
|
} else {
|
||||||
|
Locale entryLocale = Utils.stringToLocale(entry.toString());
|
||||||
|
entries.add(entryLocale.getDisplayName(entryLocale));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localePreference.setEntries(entries.toArray(new CharSequence[entryValues.length]));
|
||||||
|
|
||||||
Preference themePreference = findPreference(getResources().getString(R.string.settings_key_theme));
|
Preference themePreference = findPreference(getResources().getString(R.string.settings_key_theme));
|
||||||
assert themePreference != null;
|
assert themePreference != null;
|
||||||
themePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
|
themePreference.setOnPreferenceChangeListener((preference, o) -> {
|
||||||
{
|
if (o.toString().equals(getResources().getString(R.string.settings_key_light_theme))) {
|
||||||
@Override
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||||
public boolean onPreferenceChange(Preference preference, Object o)
|
} else if (o.toString().equals(getResources().getString(R.string.settings_key_dark_theme))) {
|
||||||
{
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||||
if(o.toString().equals(getResources().getString(R.string.settings_key_light_theme)))
|
} else {
|
||||||
{
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
||||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
|
||||||
}
|
|
||||||
else if(o.toString().equals(getResources().getString(R.string.settings_key_dark_theme)))
|
|
||||||
{
|
|
||||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
|
||||||
}
|
|
||||||
|
|
||||||
FragmentActivity activity = getActivity();
|
|
||||||
if (activity != null) {
|
|
||||||
activity.recreate();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
Preference colorPreference = findPreference(getResources().getString(R.string.setting_key_theme_color));
|
||||||
|
assert colorPreference != null;
|
||||||
|
colorPreference.setOnPreferenceChangeListener((preference, o) -> {
|
||||||
|
refreshActivity(true);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
localePreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
refreshActivity(true);
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void refreshActivity(boolean reloadMain) {
|
||||||
|
mReloadMain = reloadMain || mReloadMain;
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
activity.recreate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisplayPreferenceDialog(Preference preference)
|
public void onDisplayPreferenceDialog(Preference preference) {
|
||||||
{
|
if (preference instanceof NumberDialogPreference) {
|
||||||
if (preference instanceof NumberDialogPreference)
|
|
||||||
{
|
|
||||||
NumberDialogPreference dialogPreference = (NumberDialogPreference) preference;
|
NumberDialogPreference dialogPreference = (NumberDialogPreference) preference;
|
||||||
DialogFragment dialogFragment = NumberPickerPreferenceDialogFragment
|
DialogFragment dialogFragment = NumberPickerPreferenceDialogFragment
|
||||||
.newInstance(
|
.newInstance(
|
||||||
@@ -102,9 +163,7 @@ public class SettingsActivity extends AppCompatActivity
|
|||||||
);
|
);
|
||||||
dialogFragment.setTargetFragment(this, 0);
|
dialogFragment.setTargetFragment(this, 0);
|
||||||
dialogFragment.show(getParentFragmentManager(), DIALOG_FRAGMENT_TAG);
|
dialogFragment.show(getParentFragmentManager(), DIALOG_FRAGMENT_TAG);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
super.onDisplayPreferenceDialog(preference);
|
super.onDisplayPreferenceDialog(preference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?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>
|
|
||||||
9
app/src/main/res/drawable/active_dot.xml
Normal file
9
app/src/main/res/drawable/active_dot.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval" android:useLevel="true"
|
||||||
|
android:dither="true">
|
||||||
|
|
||||||
|
<size android:height="12dip" android:width="12dip"/>
|
||||||
|
|
||||||
|
<solid android:color="@android:color/white"/>
|
||||||
|
</shape>
|
||||||
13
app/src/main/res/drawable/ic_baseline_access_time_24.xml
Normal file
13
app/src/main/res/drawable/ic_baseline_access_time_24.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
|
||||||
|
</vector>
|
||||||
10
app/src/main/res/drawable/ic_baseline_payments_24.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_payments_24.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M19,14L19,6c0,-1.1 -0.9,-2 -2,-2L3,4c-1.1,0 -2,0.9 -2,2v8c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM10,13c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM23,7v11c0,1.1 -0.9,2 -2,2L4,20v-2h17L21,7h2z"/>
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/ic_baseline_sort_24.xml
Normal file
5
app/src/main/res/drawable/ic_baseline_sort_24.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:autoMirrored="true" android:height="24dp"
|
||||||
|
android:tint="#FFFFFF" android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/ic_baseline_unfold_less_24.xml
Normal file
5
app/src/main/res/drawable/ic_baseline_unfold_less_24.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M7.41,18.59L8.83,20 12,16.83 15.17,20l1.41,-1.41L12,14l-4.59,4.59zM16.59,5.41L15.17,4 12,7.17 8.83,4 7.41,5.41 12,10l4.59,-4.59z"/>
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/ic_baseline_unfold_more_24.xml
Normal file
5
app/src/main/res/drawable/ic_baseline_unfold_more_24.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,5.83L15.17,9l1.41,-1.41L12,3 7.41,7.59 8.83,9 12,5.83zM12,18.17L8.83,15l-1.41,1.41L12,21l4.59,-4.59L15.17,15 12,18.17z"/>
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/ic_starred_black.xml
Normal file
5
app/src/main/res/drawable/ic_starred_black.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/ic_unstarred_black.xml
Normal file
5
app/src/main/res/drawable/ic_unstarred_black.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
|
||||||
|
</vector>
|
||||||
9
app/src/main/res/drawable/inactive_dot.xml
Normal file
9
app/src/main/res/drawable/inactive_dot.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval" android:useLevel="true"
|
||||||
|
android:dither="true">
|
||||||
|
|
||||||
|
<size android:height="8dip" android:width="8dip"/>
|
||||||
|
|
||||||
|
<solid android:color="@android:color/white"/>
|
||||||
|
</shape>
|
||||||
@@ -22,21 +22,344 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="?attr/actionBarSize"
|
android:layout_marginTop="?attr/actionBarSize"
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="10dp">
|
android:padding="10dp">
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/aboutText"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:linksClickable="true"
|
android:orientation="vertical">
|
||||||
android:singleLine="false"
|
|
||||||
android:focusable="true" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/version_history"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/version_history_main"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:text="@string/version_history"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/version_history_sub"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/version_history_main" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:text="@string/arrow"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/credits"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/credits_main"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:text="@string/credits"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/credits_sub"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/credits_main" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:text="@string/arrow"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/translate"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/translate_main"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:text="@string/help_translate_this_app"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/translate_sub"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:text="@string/translate_platform"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/translate_main"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:text="@string/arrow"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/license"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/license_main"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:text="@string/license"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/license_sub"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:text="@string/app_license"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/license_main"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:text="@string/arrow"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/repo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/repo_main"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:text="@string/source_repository"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/repo_sub"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:text="@string/on_github"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/repo_main" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:text="@string/arrow"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/privacy"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/privacy_main"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:text="@string/privacy_policy"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/privacy_sub"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:text="@string/and_data_usage"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/privacy_main" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:text="@string/arrow"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/rate"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/rate_main"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:text="@string/rate_this_app"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/rate_sub"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:text="@string/on_google_play"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/rate_main" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:text="@string/arrow"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:padding="8dp"
|
||||||
|
android:id="@+id/report_error"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/report_error_main"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:text="@string/report_error"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/report_error_sub"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/report_error_main"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:text="@string/on_github"
|
||||||
|
android:padding="2dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:text="@string/arrow"
|
||||||
|
android:textColor="@color/colorSecondaryText"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
65
app/src/main/res/layout/activity_manage_group.xml
Normal file
65
app/src/main/res/layout/activity_manage_group.xml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
tools:context="protect.card_locker.ManageGroupActivity">
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/fabSave"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end|bottom"
|
||||||
|
app:srcCompat="@drawable/save_24dp"
|
||||||
|
android:contentDescription="@string/save"
|
||||||
|
android:layout_margin="16dp" />
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:theme="@style/AppTheme.AppBarOverlay">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/groups"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:tabMode="scrollable" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/storeNameField"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/storeName">
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/editTextGroupName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:singleLine="true" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/include"
|
||||||
|
layout="@layout/content_main" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
22
app/src/main/res/layout/barcode_layout.xml
Normal file
22
app/src/main/res/layout/barcode_layout.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout android:orientation="vertical"
|
||||||
|
android:padding="10.0dp"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/barcode_disp_height"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:id="@+id/barcodeImage"
|
||||||
|
android:layout_weight="1.0"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/barcodeName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10.0dip"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="@dimen/text_size_medium"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_weight="1.0" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,330 +1,88 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_height="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:fitsSystemWindows="true">
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:theme="@style/AppTheme.AppBarOverlay">
|
android:theme="@style/AppTheme.AppBarOverlay">
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
android:background="?attr/colorPrimary"
|
android:background="?attr/colorPrimary"
|
||||||
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<ScrollView
|
<LinearLayout
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
android:id="@+id/scrollView">
|
android:orientation="vertical">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/explanationText"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingStart="20.0dip"
|
||||||
|
android:paddingEnd="20.0dip"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/barcodeIdLayout"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:text="@string/enterBarcodeInstructions" />
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/barcodeIdLayout"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="10.0dip"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/explanationText"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/noBarcode"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
<TextView android:textSize="16.0sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingStart="20.0dip"
|
||||||
|
android:paddingEnd="20.0dip"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:labelFor="@+id/cardId"
|
||||||
|
android:text="@string/cardId" />
|
||||||
|
|
||||||
<LinearLayout android:orientation="vertical"
|
<EditText
|
||||||
android:layout_width="fill_parent"
|
android:id="@+id/cardId"
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<LinearLayout android:orientation="horizontal"
|
|
||||||
android:padding="10.0dip"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<TextView android:textSize="20sp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:paddingStart="20.0dip"
|
|
||||||
android:paddingEnd="20.0dip"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/enterBarcodeInstructions" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:orientation="horizontal"
|
|
||||||
android:padding="10.0dip"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/barcodeIdLayout">
|
|
||||||
<TextView android:textSize="16.0sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:paddingStart="20.0dip"
|
|
||||||
android:paddingEnd="20.0dip"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:labelFor="@+id/cardId"
|
|
||||||
android:text="@string/cardId" />
|
|
||||||
<EditText android:id="@+id/cardId"
|
|
||||||
android:hint="AB1234"
|
|
||||||
android:importantForAutofill="no"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="text"/>
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/barcodesLayout"/>
|
android:hint="AB1234"
|
||||||
<Button
|
android:importantForAutofill="no"
|
||||||
android:id="@+id/noBarcode"
|
android:inputType="text"
|
||||||
android:layout_width="match_parent"
|
android:minHeight="48dp" />
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/barcodeNoBarcode"
|
|
||||||
android:textColor="#FFFFFF"
|
|
||||||
android:enabled="false" />
|
|
||||||
<LinearLayout android:orientation="vertical"
|
|
||||||
android:padding="10.0dp"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/barcode_disp_height"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:id="@+id/aztecBarcode"
|
|
||||||
android:contentDescription="@string/barcodeImageDescription"
|
|
||||||
android:layout_weight="1.0"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/aztecBarcodeText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10.0dip"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_weight="1.0" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:orientation="vertical"
|
|
||||||
android:padding="10.0dp"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/barcode_disp_height"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:id="@+id/code39Barcode"
|
|
||||||
android:contentDescription="@string/barcodeImageDescription"
|
|
||||||
android:layout_weight="1.0"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/code39BarcodeText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10.0dip"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_weight="1.0" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:orientation="vertical"
|
|
||||||
android:padding="10.0dp"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/barcode_disp_height"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:id="@+id/code128Barcode"
|
|
||||||
android:contentDescription="@string/barcodeImageDescription"
|
|
||||||
android:layout_weight="1.0"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/code128BarcodeText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10.0dip"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_weight="1.0" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:orientation="vertical"
|
|
||||||
android:padding="10.0dp"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/barcode_disp_height"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:id="@+id/codabarBarcode"
|
|
||||||
android:contentDescription="@string/barcodeImageDescription"
|
|
||||||
android:layout_weight="1.0"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/codabarBarcodeText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10.0dip"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_weight="1.0" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:orientation="vertical"
|
|
||||||
android:padding="10.0dp"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/barcode_disp_height"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:id="@+id/datamatrixBarcode"
|
|
||||||
android:contentDescription="@string/barcodeImageDescription"
|
|
||||||
android:layout_weight="1.0"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/datamatrixBarcodeText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10.0dip"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_weight="1.0" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:orientation="vertical"
|
|
||||||
android:padding="10.0dp"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/barcode_disp_height"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:id="@+id/ean8Barcode"
|
|
||||||
android:contentDescription="@string/barcodeImageDescription"
|
|
||||||
android:layout_weight="1.0"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/ean8BarcodeText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10.0dip"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_weight="1.0" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:orientation="vertical"
|
|
||||||
android:padding="10.0dp"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/barcode_disp_height"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:id="@+id/ean13Barcode"
|
|
||||||
android:contentDescription="@string/barcodeImageDescription"
|
|
||||||
android:layout_weight="1.0"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/ean13BarcodeText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10.0dip"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_weight="1.0" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:orientation="vertical"
|
|
||||||
android:padding="10.0dp"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/barcode_disp_height"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:id="@+id/itfBarcode"
|
|
||||||
android:contentDescription="@string/barcodeImageDescription"
|
|
||||||
android:layout_weight="1.0"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/itfBarcodeText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10.0dip"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_weight="1.0" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:orientation="vertical"
|
|
||||||
android:padding="10.0dp"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/barcode_disp_height"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:id="@+id/pdf417Barcode"
|
|
||||||
android:contentDescription="@string/barcodeImageDescription"
|
|
||||||
android:layout_weight="1.0"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/pdf417BarcodeText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10.0dip"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_weight="1.0" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:orientation="vertical"
|
|
||||||
android:padding="10.0dp"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/barcode_disp_height"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:id="@+id/qrcodeBarcode"
|
|
||||||
android:contentDescription="@string/barcodeImageDescription"
|
|
||||||
android:layout_weight="1.0"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/qrcodeBarcodeText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10.0dip"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_weight="1.0" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:orientation="vertical"
|
|
||||||
android:padding="10.0dp"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/barcode_disp_height"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:id="@+id/upcaBarcode"
|
|
||||||
android:contentDescription="@string/barcodeImageDescription"
|
|
||||||
android:layout_weight="1.0"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/upcaBarcodeText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10.0dip"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_weight="1.0" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:orientation="vertical"
|
|
||||||
android:padding="10.0dp"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/barcode_disp_height"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:id="@+id/upceBarcode"
|
|
||||||
android:contentDescription="@string/barcodeImageDescription"
|
|
||||||
android:layout_weight="1.0"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/upceBarcodeText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10.0dip"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_weight="1.0" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
<Button
|
||||||
|
android:id="@+id/noBarcode"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/barcodeIdLayout"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/barcodes"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:text="@string/barcodeNoBarcode"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:enabled="false" />
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/barcodes"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/noBarcode"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:scrollbars="vertical" />
|
||||||
|
</LinearLayout>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
@@ -26,12 +26,24 @@
|
|||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="@string/noMatchingGiftCards"
|
android:text="@string/noMatchingGiftCards"
|
||||||
android:visibility="gone"/>
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/AppTheme.TextView.NoData"
|
||||||
|
android:id="@+id/noGroupCardsText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/noGroupCards"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/list"
|
android:id="@+id/list"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
|
app:spanCount="@integer/main_view_card_columns"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:scrollbars="vertical"
|
android:scrollbars="vertical"
|
||||||
android:visibility="gone" />
|
android:background="@color/mainLoyaltyCardBackground"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|||||||
@@ -30,14 +30,12 @@
|
|||||||
android:id="@+id/add_from_image"
|
android:id="@+id/add_from_image"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:onClick="addFromImage"
|
|
||||||
android:text="@string/addFromImage" />
|
android:text="@string/addFromImage" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/add_manually"
|
android:id="@+id/add_manually"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:onClick="addManually"
|
|
||||||
android:text="@string/addManually" />
|
android:text="@string/addManually" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</merge>
|
</merge>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:baselineAligned="false"
|
android:baselineAligned="false"
|
||||||
android:padding="@dimen/activity_margin">
|
android:padding="@dimen/activity_margin">
|
||||||
@@ -44,8 +44,7 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24"
|
app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24"
|
||||||
android:contentDescription="@string/moveUp"
|
android:contentDescription="@string/moveUp"
|
||||||
app:tint="@color/iconColor"
|
app:tint="@color/iconColor"/>
|
||||||
android:onClick="moveGroupUp"/>
|
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/moveDown"
|
android:id="@+id/moveDown"
|
||||||
@@ -54,8 +53,7 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
app:srcCompat="@drawable/ic_baseline_arrow_drop_down_24"
|
app:srcCompat="@drawable/ic_baseline_arrow_drop_down_24"
|
||||||
android:contentDescription="@string/moveDown"
|
android:contentDescription="@string/moveDown"
|
||||||
app:tint="@color/iconColor"
|
app:tint="@color/iconColor"/>
|
||||||
android:onClick="moveGroupDown"/>
|
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/edit"
|
android:id="@+id/edit"
|
||||||
@@ -64,8 +62,7 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
app:srcCompat="@drawable/ic_mode_edit_white_24dp"
|
app:srcCompat="@drawable/ic_mode_edit_white_24dp"
|
||||||
android:contentDescription="@string/edit"
|
android:contentDescription="@string/edit"
|
||||||
app:tint="@color/iconColor"
|
app:tint="@color/iconColor"/>
|
||||||
android:onClick="editGroup" />
|
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/delete"
|
android:id="@+id/delete"
|
||||||
@@ -74,8 +71,7 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
app:srcCompat="@drawable/ic_delete_white_24dp"
|
app:srcCompat="@drawable/ic_delete_white_24dp"
|
||||||
android:contentDescription="@string/delete"
|
android:contentDescription="@string/delete"
|
||||||
app:tint="@color/iconColor"
|
app:tint="@color/iconColor"/>
|
||||||
android:onClick="deleteGroup"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,10 @@
|
|||||||
android:text="@string/noGroups"
|
android:text="@string/noGroups"
|
||||||
android:visibility="gone"/>
|
android:visibility="gone"/>
|
||||||
|
|
||||||
<ListView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/list"
|
android:id="@+id/list"
|
||||||
android:visibility="gone"/>
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
android:visibility="gone" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fitsSystemWindows="true">
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/fabSave"
|
android:id="@+id/fabSave"
|
||||||
@@ -67,14 +68,16 @@
|
|||||||
android:paddingTop="@dimen/inputPadding"
|
android:paddingTop="@dimen/inputPadding"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:layout_width="@dimen/cardThumbnailSize"
|
android:layout_width="@dimen/cardThumbnailSize"
|
||||||
android:layout_height="@dimen/cardThumbnailSize"
|
android:layout_height="@dimen/cardThumbnailSize"
|
||||||
android:layout_marginEnd="@dimen/activity_margin"
|
android:layout_marginEnd="@dimen/activity_margin"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
app:cardCornerRadius="4dp"
|
app:cardCornerRadius="4dp"
|
||||||
android:paddingHorizontal="@dimen/inputPadding"
|
android:paddingHorizontal="@dimen/inputPadding"
|
||||||
app:cardElevation="0dp">
|
app:cardElevation="0dp"
|
||||||
|
app:cardBackgroundColor="@android:color/transparent"
|
||||||
|
android:outlineProvider="none">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/thumbnail"
|
android:id="@+id/thumbnail"
|
||||||
@@ -83,7 +86,7 @@
|
|||||||
android:contentDescription="@string/thumbnailDescription"
|
android:contentDescription="@string/thumbnailDescription"
|
||||||
android:src="@mipmap/ic_launcher"/>
|
android:src="@mipmap/ic_launcher"/>
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/storeNameField"
|
android:id="@+id/storeNameField"
|
||||||
@@ -96,7 +99,7 @@
|
|||||||
android:id="@+id/storeNameEdit"
|
android:id="@+id/storeNameEdit"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
/>
|
android:singleLine="true" />
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -120,7 +123,7 @@
|
|||||||
android:id="@+id/cardIdView"
|
android:id="@+id/cardIdView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
/>
|
android:singleLine="true" />
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -312,7 +315,6 @@
|
|||||||
android:padding="10.0dp"
|
android:padding="10.0dp"
|
||||||
android:background="#ffffff"
|
android:background="#ffffff"
|
||||||
android:id="@+id/barcode"
|
android:id="@+id/barcode"
|
||||||
android:contentDescription="@string/barcodeImageDescription"
|
|
||||||
android:layout_weight="1.0"/>
|
android:layout_weight="1.0"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -347,7 +349,7 @@
|
|||||||
android:paddingTop="@dimen/inputPadding">
|
android:paddingTop="@dimen/inputPadding">
|
||||||
|
|
||||||
<!-- Front image -->
|
<!-- Front image -->
|
||||||
<androidx.cardview.widget.CardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
@@ -365,12 +367,11 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
android:minHeight="50dp"
|
android:minHeight="50dp"
|
||||||
android:background="@color/colorPrimary"
|
|
||||||
android:contentDescription="@string/frontImageDescription"
|
android:contentDescription="@string/frontImageDescription"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:srcCompat="@drawable/ic_camera_white" />
|
app:srcCompat="@drawable/ic_camera_white" />
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Back image -->
|
<!-- Back image -->
|
||||||
@@ -383,7 +384,7 @@
|
|||||||
android:paddingTop="@dimen/inputPadding">
|
android:paddingTop="@dimen/inputPadding">
|
||||||
|
|
||||||
<!-- Back image -->
|
<!-- Back image -->
|
||||||
<androidx.cardview.widget.CardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
@@ -401,12 +402,11 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
android:minHeight="50dp"
|
android:minHeight="50dp"
|
||||||
android:background="@color/colorPrimary"
|
|
||||||
android:contentDescription="@string/backImageDescription"
|
android:contentDescription="@string/backImageDescription"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:srcCompat="@drawable/ic_camera_white" />
|
app:srcCompat="@drawable/ic_camera_white" />
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</TableLayout>
|
</TableLayout>
|
||||||
</TableLayout>
|
</TableLayout>
|
||||||
|
|||||||
@@ -1,118 +1,171 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/row"
|
android:id="@+id/row"
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="0.5dp"
|
android:layout_margin="8dp">
|
||||||
android:background="@drawable/list_row"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:padding="@dimen/activity_margin">
|
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/information_container"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/activity_margin"
|
android:layout_marginBottom="4dp">
|
||||||
android:layout_marginLeft="@dimen/activity_margin"
|
|
||||||
android:layout_toEndOf="@+id/thumbnail_container"
|
|
||||||
android:layout_toRightOf="@+id/thumbnail_container"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:orientation="vertical"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/thumbnail_container"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/star"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/thumbnail_container"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/thumbnail_container">
|
|
||||||
|
|
||||||
<TextView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/store"
|
android:id="@+id/icon_layout"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="0dp"
|
||||||
android:textSize="@dimen/storeNameTextSize"
|
android:outlineProvider="none"
|
||||||
android:textStyle="bold" />
|
app:cardCornerRadius="8dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/store"
|
||||||
<TextView
|
app:layout_constraintDimensionRatio="85.6f:53.98f"
|
||||||
android:id="@+id/note"
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:lines="1"
|
|
||||||
android:textSize="@dimen/noteTextSize" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/balance"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:lines="1"
|
|
||||||
android:textSize="@dimen/noteTextSize" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/expiry"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:lines="1"
|
|
||||||
android:textSize="@dimen/noteTextSize" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:id="@+id/thumbnail_container"
|
|
||||||
android:layout_width="@dimen/cardThumbnailSize"
|
|
||||||
android:layout_height="@dimen/cardThumbnailSize"
|
|
||||||
app:cardCornerRadius="4dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/thumbnail_front"
|
|
||||||
android:layout_width="@dimen/cardThumbnailSize"
|
|
||||||
android:layout_height="@dimen/cardThumbnailSize">
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/thumbnail"
|
android:id="@+id/thumbnail"
|
||||||
android:layout_width="@dimen/cardThumbnailSize"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/cardThumbnailSize"
|
android:layout_height="match_parent"
|
||||||
android:contentDescription="@string/thumbnailDescription"
|
android:contentDescription="@string/thumbnailDescription"
|
||||||
android:src="@mipmap/ic_launcher" />
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@mipmap/ic_launcher"
|
||||||
</RelativeLayout>
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
<RelativeLayout
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
android:id="@+id/thumbnail_back"
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
android:layout_width="@dimen/cardThumbnailSize"
|
|
||||||
android:layout_height="@dimen/cardThumbnailSize">
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/cardThumbnailSize"
|
android:id="@+id/selected_thumbnail"
|
||||||
android:layout_height="@dimen/cardThumbnailSize"
|
android:layout_width="match_parent"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_height="match_parent"
|
||||||
android:contentDescription="@string/thumbnailDescription"
|
android:contentDescription="@string/thumbnailDescription"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:srcCompat="@drawable/ic_done" />
|
app:srcCompat="@drawable/ic_done" />
|
||||||
|
|
||||||
</RelativeLayout>
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
</androidx.cardview.widget.CardView>
|
android:id="@+id/star"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/star"
|
android:id="@+id/star_background"
|
||||||
android:layout_width="@dimen/cardThumbnailSize"
|
android:layout_width="@dimen/cardThumbnailSize"
|
||||||
android:layout_height="@dimen/cardThumbnailSize"
|
android:layout_height="@dimen/cardThumbnailSize"
|
||||||
android:layout_marginLeft="@dimen/activity_margin"
|
android:layout_gravity="end"
|
||||||
app:srcCompat="@drawable/ic_starred_white"
|
android:alpha="0.5"
|
||||||
android:layout_alignParentEnd="true"
|
android:contentDescription="@string/starImage"
|
||||||
android:layout_alignParentRight="true"
|
android:elevation="4dp"
|
||||||
android:layout_centerVertical="true"
|
android:visibility="visible"
|
||||||
android:layout_marginStart="@dimen/activity_margin"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:contentDescription="@string/starImage"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:srcCompat="@drawable/ic_starred_white"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
tools:ignore="ImageContrastCheck" />
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:tint="@android:color/black" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
<ImageView
|
||||||
|
android:id="@+id/star_border"
|
||||||
|
android:layout_width="@dimen/cardThumbnailSize"
|
||||||
|
android:layout_height="@dimen/cardThumbnailSize"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:alpha="0.5"
|
||||||
|
android:contentDescription="@string/starImage"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_unstarred_white"
|
||||||
|
tools:ignore="ImageContrastCheck" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/store"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:textAppearance="?attr/textAppearanceHeadline1"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/icon_layout"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/note"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:text="Example store"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/note"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:textAppearance="?attr/textAppearanceBody2"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/store"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/info_divider"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:text="Example note"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/info_divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:background="?android:attr/dividerVertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/note"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/balance"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/balance"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:textAppearance="?attr/textAppearanceBody2"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
app:drawableLeftCompat="@drawable/ic_baseline_payments_24"
|
||||||
|
android:drawablePadding="4dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/info_divider"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/expiry"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:visibility="visible"
|
||||||
|
tools:text="525 points"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/expiry"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:textAppearance="?attr/textAppearanceBody2"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
app:drawableLeftCompat="@drawable/ic_baseline_access_time_24"
|
||||||
|
android:drawablePadding="4dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/balance"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:visibility="visible"
|
||||||
|
tools:text="Tomorrow"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/maximizeButton"
|
android:id="@+id/maximizeButton"
|
||||||
|
android:visibility="gone"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:layout_marginStart="15.0dip"
|
android:layout_marginStart="15.0dip"
|
||||||
@@ -53,26 +54,25 @@
|
|||||||
android:padding="0dp"
|
android:padding="0dp"
|
||||||
app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24"
|
app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24"
|
||||||
android:contentDescription="@string/moveBarcodeToTopOfScreen"
|
android:contentDescription="@string/moveBarcodeToTopOfScreen"
|
||||||
android:tint="@android:color/white"
|
app:tint="#ffffff"
|
||||||
android:background="@color/colorPrimary"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/barcode"
|
app:layout_constraintBottom_toTopOf="@+id/mainImage"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/barcode"
|
android:id="@+id/mainImage"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
android:layout_marginStart="15.0dip"
|
android:layout_marginStart="15.0dip"
|
||||||
android:layout_marginEnd="15.0dip"
|
android:layout_marginEnd="15.0dip"
|
||||||
android:padding="10dp"
|
|
||||||
android:background="#ffffff"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/centerGuideline"
|
app:layout_constraintBottom_toTopOf="@+id/centerGuideline"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/maximizeButton"
|
app:layout_constraintTop_toBottomOf="@+id/maximizeButton"/>
|
||||||
android:contentDescription="@string/barcodeImageDescription"/>
|
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/minimizeButton"
|
android:id="@+id/minimizeButton"
|
||||||
@@ -81,15 +81,31 @@
|
|||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:layout_marginStart="15.0dip"
|
android:layout_marginStart="15.0dip"
|
||||||
android:layout_marginEnd="15.0dip"
|
android:layout_marginEnd="15.0dip"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
android:padding="0dp"
|
android:padding="0dp"
|
||||||
app:srcCompat="@drawable/ic_baseline_arrow_drop_down_24"
|
app:srcCompat="@drawable/ic_baseline_arrow_drop_down_24"
|
||||||
android:contentDescription="@string/moveBarcodeToCenterOfScreen"
|
android:contentDescription="@string/moveBarcodeToCenterOfScreen"
|
||||||
android:tint="@android:color/white"
|
app:tint="#ffffff"
|
||||||
android:background="@color/colorPrimary"
|
app:layout_constraintTop_toBottomOf="@+id/mainImage"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/barcode"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/dotIndicator"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:layout_marginStart="15.0dip"
|
||||||
|
android:layout_marginEnd="15.0dip"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/minimizeButton"/>
|
||||||
|
|
||||||
<SeekBar
|
<SeekBar
|
||||||
android:id="@+id/barcodeScaler"
|
android:id="@+id/barcodeScaler"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
@@ -106,12 +122,16 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/cardIdView"
|
android:id="@+id/cardIdView"
|
||||||
|
android:enabled="true"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:longClickable="true"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginLeft="10.0dip"
|
android:layout_marginLeft="10.0dip"
|
||||||
android:layout_marginRight="10.0dip"
|
android:layout_marginRight="10.0dip"
|
||||||
android:paddingBottom="80dp"
|
android:paddingBottom="80dp"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/minimizeButton"
|
app:layout_constraintTop_toBottomOf="@+id/dotIndicator"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -135,8 +155,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/inputBackground"
|
android:background="@color/inputBackground"
|
||||||
android:fitsSystemWindows="false"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="0px"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:behavior_hideable="false"
|
app:behavior_hideable="false"
|
||||||
app:behavior_peekHeight="80dp"
|
app:behavior_peekHeight="80dp"
|
||||||
@@ -148,10 +168,10 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="80dp"
|
android:layout_height="80dp"
|
||||||
android:layout_gravity="top|start"
|
android:layout_gravity="top|start"
|
||||||
android:background="@color/colorPrimary"
|
android:contentDescription="@string/toggleMoreInfo"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:tint="@android:color/white"
|
app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24"
|
||||||
app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24" />
|
app:tint="#ffffff" />
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:id="@+id/bottomSheetContentWrapper"
|
android:id="@+id/bottomSheetContentWrapper"
|
||||||
@@ -163,148 +183,129 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:id="@+id/frontImageView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginStart="@dimen/activity_margin"
|
|
||||||
android:layout_marginTop="@dimen/activity_margin"
|
|
||||||
android:layout_marginEnd="@dimen/activity_margin"
|
|
||||||
android:layout_marginBottom="@dimen/activity_margin"
|
|
||||||
android:paddingHorizontal="@dimen/inputPadding"
|
|
||||||
android:layout_weight="1"
|
|
||||||
app:cardCornerRadius="4dp"
|
|
||||||
app:cardElevation="0dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/frontImage"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
android:contentDescription="@string/frontImageDescription"
|
|
||||||
android:scaleType="fitCenter" />
|
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:id="@+id/backImageView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginStart="@dimen/activity_margin"
|
|
||||||
android:layout_marginTop="@dimen/activity_margin"
|
|
||||||
android:layout_marginEnd="@dimen/activity_margin"
|
|
||||||
android:layout_marginBottom="@dimen/activity_margin"
|
|
||||||
android:paddingHorizontal="@dimen/inputPadding"
|
|
||||||
android:layout_weight="1"
|
|
||||||
app:cardCornerRadius="4dp"
|
|
||||||
app:cardElevation="0dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/backImage"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
android:contentDescription="@string/backImageDescription"
|
|
||||||
android:scaleType="fitCenter" />
|
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/noteView"
|
android:id="@+id/noteView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:autoLink="all"
|
||||||
|
android:enabled="true"
|
||||||
|
android:focusable="true"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:longClickable="true"
|
||||||
android:padding="20dp"
|
android:padding="20dp"
|
||||||
|
android:textIsSelectable="true"
|
||||||
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/groupsView"
|
android:id="@+id/groupsView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:enabled="true"
|
||||||
|
android:focusable="true"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:longClickable="true"
|
||||||
android:padding="20dp"
|
android:padding="20dp"
|
||||||
|
android:textIsSelectable="true"
|
||||||
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/balanceView"
|
android:id="@+id/balanceView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:enabled="true"
|
||||||
|
android:focusable="true"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:longClickable="true"
|
||||||
android:padding="20dp"
|
android:padding="20dp"
|
||||||
|
android:textIsSelectable="true"
|
||||||
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/expiryView"
|
android:id="@+id/expiryView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:enabled="true"
|
||||||
|
android:focusable="true"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:longClickable="true"
|
||||||
android:padding="20dp"
|
android:padding="20dp"
|
||||||
|
android:textIsSelectable="true"
|
||||||
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
android:textSize="@dimen/singleCardNoteTextSizeMin" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/app_bar_layout"
|
android:id="@+id/app_bar_layout"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:clipChildren="false"
|
android:clipChildren="false"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:layout_width="fill_parent"
|
android:fitsSystemWindows="true"
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:weightSum="1.0"
|
android:weightSum="1.0"
|
||||||
android:fitsSystemWindows="true">
|
>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar_landscape"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="?actionBarSize"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:fitsSystemWindows="false"
|
||||||
|
android:paddingTop="0dp"
|
||||||
|
android:theme="@style/CardView.ActionBarTheme"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:contentInsetStart="72.0dip"
|
||||||
|
app:layout_collapseMode="pin" />
|
||||||
|
|
||||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
android:id="@+id/collapsingToolbarLayout"
|
android:id="@+id/collapsingToolbarLayout"
|
||||||
android:clipChildren="false"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:minHeight="56.0dip"
|
android:minHeight="56.0dip"
|
||||||
android:layout_weight="1.0"
|
|
||||||
app:expandedTitleMarginStart="48dp"
|
|
||||||
app:expandedTitleMarginEnd="64dp"
|
|
||||||
app:contentScrim="?colorPrimary"
|
app:contentScrim="?colorPrimary"
|
||||||
app:expandedTitleGravity="top">
|
app:expandedTitleGravity="top"
|
||||||
<TextView
|
app:expandedTitleMarginEnd="64dp"
|
||||||
|
app:expandedTitleMarginStart="48dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/storeName"
|
android:id="@+id/storeName"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:maxLines="1"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:textColor="@android:color/white"
|
|
||||||
android:textSize="40sp"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:layout_marginTop="?actionBarSize"
|
android:layout_marginTop="?actionBarSize"
|
||||||
android:layout_marginBottom="?actionBarSize"
|
android:layout_marginBottom="?actionBarSize"
|
||||||
app:layout_collapseMode="parallax"
|
android:ellipsize="end"
|
||||||
android:fitsSystemWindows="true"/>
|
android:maxLines="1"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="40sp"
|
||||||
|
app:layout_collapseMode="parallax" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@id/toolbar"
|
android:id="@id/toolbar"
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:theme="@style/CardView.ActionBarTheme"
|
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="?actionBarSize"
|
android:layout_height="?actionBarSize"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:theme="@style/CardView.ActionBarTheme"
|
||||||
app:contentInsetStart="72.0dip"
|
app:contentInsetStart="72.0dip"
|
||||||
app:layout_collapseMode="pin" />
|
app:layout_collapseMode="pin" />
|
||||||
|
|
||||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar_landscape"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:theme="@style/CardView.ActionBarTheme"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="?actionBarSize"
|
|
||||||
app:contentInsetStart="72.0dip"
|
|
||||||
app:layout_collapseMode="pin"
|
|
||||||
android:paddingTop="6dp" />
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:srcCompat="@drawable/ic_launcher_foreground"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
@@ -2,8 +2,23 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:theme="@style/AppTheme.AppBarOverlay">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
app:popupTheme="@style/AppTheme.PopupOverlay"/>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/settings_container"
|
android:id="@+id/settings_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
35
app/src/main/res/layout/simple_toolbar_list_activity.xml
Normal file
35
app/src/main/res/layout/simple_toolbar_list_activity.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:theme="@style/AppTheme.AppBarOverlay">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
app:popupTheme="@style/AppTheme.PopupOverlay"/>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/list"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
|
app:spanCount="1"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_width="match_parent" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
21
app/src/main/res/layout/sorting_option.xml
Normal file
21
app/src/main/res/layout/sorting_option.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/checkBox_reverse"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="20dp"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:text="@string/reverse"
|
||||||
|
android:textSize="19sp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -7,20 +7,27 @@
|
|||||||
android:title="@string/copy_to_clipboard"
|
android:title="@string/copy_to_clipboard"
|
||||||
android:icon="@drawable/ic_copy"
|
android:icon="@drawable/ic_copy"
|
||||||
android:titleCondensed="@string/copy_to_clipboard"
|
android:titleCondensed="@string/copy_to_clipboard"
|
||||||
app:showAsAction="always"/>
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_share"
|
android:id="@+id/action_share"
|
||||||
android:title="@string/share"
|
android:title="@string/share"
|
||||||
android:icon="@drawable/ic_share"
|
android:icon="@drawable/ic_share"
|
||||||
android:titleCondensed="@string/share"
|
android:titleCondensed="@string/share"
|
||||||
app:showAsAction="always"/>
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_edit"
|
android:id="@+id/action_edit"
|
||||||
android:icon="@drawable/ic_edit"
|
android:icon="@drawable/ic_edit"
|
||||||
android:title="@string/editCardTitle"
|
android:title="@string/editCardTitle"
|
||||||
android:titleCondensed="@string/editCardTitle"
|
android:titleCondensed="@string/editCardTitle"
|
||||||
app:showAsAction="always"/>
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_delete"
|
||||||
|
android:icon="@drawable/ic_delete_white_24dp"
|
||||||
|
android:title="@string/delete"
|
||||||
|
android:titleCondensed="@string/delete"
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
@@ -8,24 +8,30 @@
|
|||||||
android:icon="@drawable/ic_search_white"
|
android:icon="@drawable/ic_search_white"
|
||||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||||
app:showAsAction="always|collapseActionView"/>
|
app:showAsAction="always|collapseActionView"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_unfold"
|
||||||
|
android:title="@string/action_hide_details"
|
||||||
|
android:icon="@drawable/ic_baseline_unfold_less_24"
|
||||||
|
app:showAsAction="always"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_sort"
|
||||||
|
android:title="@string/sort"
|
||||||
|
android:icon="@drawable/ic_baseline_sort_24"
|
||||||
|
app:showAsAction="always"/>
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_manage_groups"
|
android:id="@+id/action_manage_groups"
|
||||||
android:icon="@drawable/ic_folder_white"
|
android:icon="@drawable/ic_folder_white"
|
||||||
android:title="@string/groups"
|
android:title="@string/groups"
|
||||||
app:showAsAction="always"/>
|
app:showAsAction="ifRoom"/>
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_import_export"
|
android:id="@+id/action_import_export"
|
||||||
android:icon="@drawable/ic_import_export_white_24dp"
|
android:icon="@drawable/ic_import_export_white_24dp"
|
||||||
android:title="@string/importExport"
|
android:title="@string/importExport"
|
||||||
app:showAsAction="ifRoom"/>
|
app:showAsAction="never"/>
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_settings"
|
android:id="@+id/action_settings"
|
||||||
android:title="@string/settings"
|
android:title="@string/settings"
|
||||||
app:showAsAction="never"/>
|
app:showAsAction="never"/>
|
||||||
<item
|
|
||||||
android:id="@+id/action_privacy_policy"
|
|
||||||
android:title="@string/privacy_policy"
|
|
||||||
app:showAsAction="never"/>
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_about"
|
android:id="@+id/action_about"
|
||||||
android:title="@string/about"
|
android:title="@string/about"
|
||||||
|
|||||||
86
app/src/main/res/raw/contributors.txt
Normal file
86
app/src/main/res/raw/contributors.txt
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
Sylvia van Os
|
||||||
|
Branden Archer
|
||||||
|
J. Lavoie
|
||||||
|
Allan Nordhøy
|
||||||
|
Heimen Stoffels
|
||||||
|
solokot
|
||||||
|
mondstern
|
||||||
|
Katharine Chui
|
||||||
|
Oğuz Ersen
|
||||||
|
IllusiveMan196
|
||||||
|
Petr Novák
|
||||||
|
Taco
|
||||||
|
Gediminas Murauskas
|
||||||
|
Joel A
|
||||||
|
StoyanDimitrov
|
||||||
|
Nyatsuki
|
||||||
|
Altonss
|
||||||
|
Samantaz Fox
|
||||||
|
arno-github
|
||||||
|
Ankit Tiwari
|
||||||
|
Sergio Paredes
|
||||||
|
laralem
|
||||||
|
arshbeerSingh
|
||||||
|
huuhaa
|
||||||
|
Miha Frangež
|
||||||
|
Michael Moroni
|
||||||
|
Olivia (Zoe)
|
||||||
|
betsythefc
|
||||||
|
waffshappen
|
||||||
|
sr093906
|
||||||
|
K. Herbert
|
||||||
|
Quentin PAGÈS
|
||||||
|
String E. Fighter
|
||||||
|
Yurical
|
||||||
|
/usr/local/ΕΨΗΕΛΩΝ
|
||||||
|
Adolfo Jayme-Barrientos
|
||||||
|
Alessandro Mandelli
|
||||||
|
KovalevArtem
|
||||||
|
Clonewayx
|
||||||
|
D. Domig
|
||||||
|
Diego
|
||||||
|
Jane Kong
|
||||||
|
Lukas Grassauer
|
||||||
|
Marnick L'Eau
|
||||||
|
Michalis
|
||||||
|
Rosdyana Kusuma
|
||||||
|
schirinowski
|
||||||
|
Thomas Bertels
|
||||||
|
inesre
|
||||||
|
lgasp
|
||||||
|
phlostically
|
||||||
|
Aditya Das
|
||||||
|
Kevin Sicong Jiang
|
||||||
|
Airat
|
||||||
|
Andreas Blaser
|
||||||
|
BMN
|
||||||
|
Biren
|
||||||
|
Kasina Dheeraj
|
||||||
|
Flav
|
||||||
|
Franciszek Stefan
|
||||||
|
Izzy
|
||||||
|
Karol Kosek
|
||||||
|
bittin
|
||||||
|
Maciej Błędkowski
|
||||||
|
Marco
|
||||||
|
Mattia
|
||||||
|
Michael Gangolf
|
||||||
|
pbeckmann
|
||||||
|
Peer Beckmann
|
||||||
|
Quang Nguyen
|
||||||
|
Reza
|
||||||
|
Rohan Babbar
|
||||||
|
Ronak Upadhyay
|
||||||
|
Rose Liverman
|
||||||
|
Simone Dotto
|
||||||
|
Still Hsu
|
||||||
|
Subhashish Anand
|
||||||
|
Tymofii Lytvynenko
|
||||||
|
Tjipke van der Heide
|
||||||
|
avikkundu
|
||||||
|
opsik
|
||||||
|
psa-jforestier
|
||||||
|
Robin
|
||||||
|
sergio
|
||||||
|
Marcus
|
||||||
|
techwebpd
|
||||||
7
app/src/main/res/raw/readme.md
Normal file
7
app/src/main/res/raw/readme.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# stocard_stores.csv
|
||||||
|
|
||||||
|
stocard_stores.csv was created by extracting /data/data/de.stocard/de.stocard.stocard/databases/stores on a rooted devices and running the following command over it:
|
||||||
|
|
||||||
|
sqlite3 -header -csv stores "select _id,name,barcodeFormat from stores" > stocard_stores.csv
|
||||||
|
|
||||||
|
Only used for data portability reasons (ensuring importing works). Do NOT copy this anywhere else or use it for any purpose other than ensuring we can import a GDPR-provided export. We want to make sure this stays under fair use.
|
||||||
4249
app/src/main/res/raw/stocard_stores.csv
Normal file
4249
app/src/main/res/raw/stocard_stores.csv
Normal file
File diff suppressed because it is too large
Load Diff
223
app/src/main/res/values-bg/strings.xml
Normal file
223
app/src/main/res/values-bg/strings.xml
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="action_add">Добавяне</string>
|
||||||
|
<string name="action_search">Търсене</string>
|
||||||
|
<string name="leaveWithoutSaveTitle">Изход</string>
|
||||||
|
<string name="confirm">Потвърждаване</string>
|
||||||
|
<string name="delete">Премахване</string>
|
||||||
|
<string name="edit">Редактиране</string>
|
||||||
|
<string name="save">Запазване</string>
|
||||||
|
<string name="cancel">Отказ</string>
|
||||||
|
<string name="unstar">Премахва от любими</string>
|
||||||
|
<string name="star">Добавя към любими</string>
|
||||||
|
<string name="noBarcode">Без щрихкод</string>
|
||||||
|
<string name="barcodeNoBarcode">Картата няма щрихкод</string>
|
||||||
|
<string name="barcodeType">Вид на щрихкод</string>
|
||||||
|
<string name="cardId">Идентификатор на карта</string>
|
||||||
|
<string name="note">Бележка</string>
|
||||||
|
<string name="storeName">Наименование</string>
|
||||||
|
<string name="noMatchingGiftCards">Няма резултати. Променете критериите за търсене.</string>
|
||||||
|
<string name="noGiftCards">Докоснете бутона +, за да добавите карта или внесете от менюто ⋮.</string>
|
||||||
|
<string name="all">Всички</string>
|
||||||
|
<plurals name="groupCardCount">
|
||||||
|
<item quantity="one"><xliff:g>%d</xliff:g> карта</item>
|
||||||
|
<item quantity="other"><xliff:g>%d</xliff:g> карти</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="failedOpeningFileManager">Инсталирайте приложение за управление на файлове.</string>
|
||||||
|
<string name="app_license">Свободен софтуер с авторски права, лицензиран под GPLv3+</string>
|
||||||
|
<string name="frontImageDescription">Снимка на предната страна на карта</string>
|
||||||
|
<string name="backImageDescription">Снимка на задната страна на карта</string>
|
||||||
|
<string name="parsingBalanceFailed"><xliff:g>%s</xliff:g> не изглежда истинска наличност.</string>
|
||||||
|
<string name="no">Не</string>
|
||||||
|
<string name="yes">Да</string>
|
||||||
|
<string name="setBackImage">Снимка на задната страна</string>
|
||||||
|
<string name="setFrontImage">Снимка на предната страна</string>
|
||||||
|
<string name="photos">Снимки</string>
|
||||||
|
<string name="importOptionApplicationExplanation">Изберете файл чрез друго приложение.</string>
|
||||||
|
<string name="noExternalStoragePermissionError">Дайте разрешение за достъп до хранилището, за да работи внасянето и изнасянето</string>
|
||||||
|
<string name="noCardExistsError">Картата не е намерена</string>
|
||||||
|
<string name="updateBarcodeQuestionText">Идентификаторът е променен. Желаете ли с неговата стойност да бъде променен и щрихкода\?</string>
|
||||||
|
<string name="updateBarcodeQuestionTitle">Обновяване на щрихкода\?</string>
|
||||||
|
<string name="noCardIdError">Не е въведен идентификатор</string>
|
||||||
|
<string name="noCardsMessage">Добавете карта</string>
|
||||||
|
<string name="cardShortcut">Пряк път до карта</string>
|
||||||
|
<string name="addCardTitle">Добавяне на карта</string>
|
||||||
|
<string name="removeImage">Премахване на изображение</string>
|
||||||
|
<string name="takePhoto">Снимане</string>
|
||||||
|
<string name="copy_to_clipboard_multiple_toast">Идентификаторите са копирани в междинната памет</string>
|
||||||
|
<string name="intent_import_card_from_url_share_multiple_text">Искам да споделя тези карти с вас</string>
|
||||||
|
<string name="wrongValueForBarcodeType">Стойността е невалидна за избрания вид щрихкод</string>
|
||||||
|
<string name="setBarcodeId">Задаване на стойност</string>
|
||||||
|
<string name="sameAsCardId">Същата като идентификатора</string>
|
||||||
|
<string name="barcodeId">Стойност на щрихкода</string>
|
||||||
|
<string name="importLoyaltyCardKeychain">Внасяне от Loyalty Card Keychain</string>
|
||||||
|
<string name="importFidmeMessage">Изберете файла <i>fidme-export-request-xxxxxx.zip</i>, предварително изнесен от FidMe и ръчно изберете вида на щрихкодовете.
|
||||||
|
\nСъздайте такъв файл от Data Protection в менюто на профила във FidMe и изберете „Extract my data“.</string>
|
||||||
|
<string name="importFidme">Внасяне от FidMe</string>
|
||||||
|
<string name="exportOptionExplanation">Данните ще бъдат запазени на място по ваш избор.</string>
|
||||||
|
<string name="accept">Приемане</string>
|
||||||
|
<string name="privacy_policy_popup_text">Политика за личните данни (необходима от някои магазини за приложения):
|
||||||
|
\n
|
||||||
|
\nНЕ СЕ СЪБИРАТ ИЗОБЩО НИКАКВИ ДАННИ, което може да бъде потвърдено, защото приложението е със свободен код.</string>
|
||||||
|
<string name="privacy_policy">Политика за личните данни</string>
|
||||||
|
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
|
||||||
|
<string name="turn_flashlight_off">Изключва светкавицата</string>
|
||||||
|
<string name="turn_flashlight_on">Включва светкавицата</string>
|
||||||
|
<string name="passwordRequired">Въведете паролата</string>
|
||||||
|
<string name="points">Точки</string>
|
||||||
|
<string name="currency">Валута</string>
|
||||||
|
<string name="balance">Наличност</string>
|
||||||
|
<string name="errorReadingImage">Изображението е нечетимо</string>
|
||||||
|
<string name="noBarcodeFound">Не е намерен щрихкод</string>
|
||||||
|
<string name="moveBarcodeToCenterOfScreen">Премества щрихкода в центъра на екрана</string>
|
||||||
|
<string name="moveBarcodeToTopOfScreen">Премества щрихкода най-горе на екрана</string>
|
||||||
|
<string name="never">Не изтича</string>
|
||||||
|
<string name="chooseExpiryDate">Дата на изтичане</string>
|
||||||
|
<string name="expiryDate">Валидност</string>
|
||||||
|
<string name="editBarcode">Редактиране на щрихкод</string>
|
||||||
|
<string name="barcode">Щрихкод</string>
|
||||||
|
<string name="card">Карта</string>
|
||||||
|
<string name="groupsList">Списъци: <xliff:g>%s</xliff:g></string>
|
||||||
|
<string name="expiryStateSentence">Валидност: <xliff:g>%s</xliff:g></string>
|
||||||
|
<string name="expiryStateSentenceExpired">Изтекла: <xliff:g>%s</xliff:g></string>
|
||||||
|
<string name="balancePoints"><xliff:g>%s</xliff:g> точки</string>
|
||||||
|
<string name="balanceSentence">Наличност: <xliff:g>%s</xliff:g></string>
|
||||||
|
<string name="noGroups">Докоснете бутона +, за да добавите списък.</string>
|
||||||
|
<string name="noStoreError">Не е въведено наименование</string>
|
||||||
|
<string name="groups">Списъци</string>
|
||||||
|
<string name="enter_group_name">Въведете име на списъка</string>
|
||||||
|
<string name="intent_import_card_from_url_share_text">Искам да споделя тази карта с вас</string>
|
||||||
|
<string name="settings_display_barcode_max_brightness">Увеличаване на яркостта при видим щрихкод</string>
|
||||||
|
<string name="settings_lock_barcode_orientation">Без на завъртане на щрихкода</string>
|
||||||
|
<string name="settings_keep_screen_on">Поддържане на екрана включен</string>
|
||||||
|
<string name="settings_disable_lockscreen_while_viewing_card">Предотвратяване на заключване на екрана</string>
|
||||||
|
<string name="settings_max_font_size_scale">Максимален размер на шрифта</string>
|
||||||
|
<string name="settings_dark_theme">Тъмна</string>
|
||||||
|
<string name="settings_light_theme">Светла</string>
|
||||||
|
<string name="settings_system_theme">Системна</string>
|
||||||
|
<string name="settings_theme">Тема</string>
|
||||||
|
<string name="settings_category_title_ui">Потребителски интерфейс</string>
|
||||||
|
<string name="settings">Настройки</string>
|
||||||
|
<string name="starImage">Звезда за любимо</string>
|
||||||
|
<string name="thumbnailDescription">Миниатюра на картата</string>
|
||||||
|
<string name="copy_to_clipboard_toast">Идентификаторът на картата е копиран в междинната памет</string>
|
||||||
|
<string name="enterBarcodeInstructions">Въведете идентификатор на картата или като изберете вида на щрихкода или докоснете бутона „Картата няма щрихкод“.</string>
|
||||||
|
<string name="selectBarcodeTitle">Избиране на щрихкод</string>
|
||||||
|
<string name="importOptionApplicationButton">Избиране чрез приложение</string>
|
||||||
|
<string name="importing">Внасяне…</string>
|
||||||
|
<string name="exporting">Изнасяне…</string>
|
||||||
|
<string name="exportFailed">Картите не могат да бъдат изнесени: <xliff:g>%s</xliff:g></string>
|
||||||
|
<string name="exportFailedTitle">Грешка при изнасяне</string>
|
||||||
|
<string name="importFailed">Картите не могат да бъдат внесени: <xliff:g>%s</xliff:g></string>
|
||||||
|
<string name="importFailedTitle">Грешка при внасяне</string>
|
||||||
|
<string name="exportSuccessfulTitle">Резултат от изнасяне</string>
|
||||||
|
<string name="importSuccessfulTitle">Резултат от внасяне</string>
|
||||||
|
<string name="importExportHelp">Резервните копия на картите ви дават възможност да ги преместите на друго устройство.</string>
|
||||||
|
<string name="exportName">Изнасяне</string>
|
||||||
|
<string name="importExport">Внасяне/изнасяне</string>
|
||||||
|
<string name="sendLabel">Изпращане…</string>
|
||||||
|
<string name="scanCardBarcode">Снемане на щрихкод от карта</string>
|
||||||
|
<string name="editCardTitle">Редактиране на карта</string>
|
||||||
|
<string name="share">Споделя</string>
|
||||||
|
<string name="copy_to_clipboard">Копира идентификатора в междинната памет</string>
|
||||||
|
<string name="ok">Добре</string>
|
||||||
|
<string name="importSuccessful">Картите са внесени успешно</string>
|
||||||
|
<string name="chooseImportType">От къде ще внесете\?</string>
|
||||||
|
<string name="importCatimaMessage">Изберете файла <i>catima.zip</i>, предварително изнесен от Catima.
|
||||||
|
\nСъздайте такъв файл от меню Внасяне/изнасяне от друго устройство с Catima като изберете Изнасяне.</string>
|
||||||
|
<string name="importOptionApplicationTitle">От друго приложение</string>
|
||||||
|
<string name="importOptionFilesystemButton">Избиране от файлова система</string>
|
||||||
|
<string name="importOptionFilesystemExplanation">Изберете определен файл от файловата система.</string>
|
||||||
|
<string name="app_resources">Свободни ресурси: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||||
|
<string name="app_libraries">Свободни библиотеки: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||||
|
<string name="app_revision_fmt">Компилация: <xliff:g id="app_revision_url">%s</xliff:g></string>
|
||||||
|
<string name="debug_version_fmt">Издание: <xliff:g id="version">%s</xliff:g></string>
|
||||||
|
<string name="about_title_fmt">Относно <xliff:g id="app_name">%s</xliff:g></string>
|
||||||
|
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Всички права запазени © 2019–<xliff:g>%d</xliff:g> Sylvia van Os</string>
|
||||||
|
<string name="app_copyright_old">На основата на Loyalty Card Keychain
|
||||||
|
\nвсички права запазени © 2016–2020 Branden Archer</string>
|
||||||
|
<string name="about">Относно</string>
|
||||||
|
<string name="importOptionFilesystemTitle">Внасяне от файловата система</string>
|
||||||
|
<string name="importCatima">Внасяне от Catima</string>
|
||||||
|
<string name="exportSuccessful">Картите са изнесени успешно</string>
|
||||||
|
<string name="unlockScreen">Разрешава автоматичното завъртане</string>
|
||||||
|
<string name="lockScreen">Спира автоматичното завъртане</string>
|
||||||
|
<plurals name="selectedCardCount">
|
||||||
|
<item quantity="one"><xliff:g>%d</xliff:g> избрана карта</item>
|
||||||
|
<item quantity="other"><xliff:g>%d</xliff:g> избрани карти</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="deleteConfirmationGroup">Изтриване на група\?</string>
|
||||||
|
<string name="moveDown">Преместване надолу</string>
|
||||||
|
<string name="moveUp">Преместване нагоре</string>
|
||||||
|
<string name="addFromImage">Избор от галерията</string>
|
||||||
|
<string name="addManually">Ръчно въвеждане</string>
|
||||||
|
<string name="leaveWithoutSaveConfirmation">Оставяте промените незапазени\?</string>
|
||||||
|
<string name="unsupportedBarcodeType">Щрихкод от този вид не може да бъде показан. Може да бъде поддържан в следващо издание.</string>
|
||||||
|
<string name="importStocard">Внасяне от Stocard</string>
|
||||||
|
<string name="importVoucherVault">Внасяне от Voucher Vault</string>
|
||||||
|
<string name="importVoucherVaultMessage">Изберете файла <i>vouchervault.json</i>, предварително изнесен от Voucher Vault.
|
||||||
|
\nСъздайте такъв файл от меню „Export“ във Voucher Vault.</string>
|
||||||
|
<string name="importStocardMessage">Изберете файла <i>***-sync.zip</i>, предварително изнесен от Stocard.
|
||||||
|
\nПолучете го като изпратите писмо на support@stocardapp.com с искане за изнасяне вашите данни.</string>
|
||||||
|
<string name="importLoyaltyCardKeychainMessage">Изберете файла <i>LoyaltyCardKeychain.csv</i>, предварително изнесен от Loyalty Card Keychain.
|
||||||
|
\nСъздайте такъв файл от меню Внасяне/изнасяне от друго устройство с Loyalty Card Keychain като изберете Изнасяне.</string>
|
||||||
|
<string name="failedParsingImportUriError">Препратката не може да бъде анализирана за внасяне</string>
|
||||||
|
<string name="card_ids_copied">[не превеждай този низ, https://github.com/TheLastProject/Catima/issues/278]</string>
|
||||||
|
<string name="failedGeneratingShareURL">Грешка при създаване на адрес за споделяне. Изпратете доклад за дефект.</string>
|
||||||
|
<string name="deleteTitle">Премахване на карта</string>
|
||||||
|
<plurals name="deleteCardsTitle">
|
||||||
|
<item quantity="one">Изтриване на <xliff:g>%d</xliff:g> карта</item>
|
||||||
|
<item quantity="other">Изтриване на <xliff:g>%d</xliff:g> карти</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="deleteConfirmation">Потвърдете премахване на картата.</string>
|
||||||
|
<plurals name="deleteCardsConfirmation">
|
||||||
|
<item quantity="one">Желаете ли <xliff:g>%d</xliff:g> карта да бъде премахната\?</item>
|
||||||
|
<item quantity="other">Желаете ли тези <xliff:g>%d</xliff:g> карти да бъдат премахнати\?</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="app_contributors">Осъществено от: <xliff:g id="app_contributors">%s</xliff:g></string>
|
||||||
|
<string name="settings_brown_theme">Кафяво</string>
|
||||||
|
<string name="settings_grey_theme">Сиво</string>
|
||||||
|
<string name="settings_green_theme">Зелено</string>
|
||||||
|
<string name="settings_sky_blue_theme">Небесносиньо</string>
|
||||||
|
<string name="settings_blue_theme">Синьо</string>
|
||||||
|
<string name="settings_violet_theme">Виолетово</string>
|
||||||
|
<string name="settings_magenta_theme">Цикламено</string>
|
||||||
|
<string name="settings_pink_theme">Розово</string>
|
||||||
|
<string name="settings_catima_theme">Catima</string>
|
||||||
|
<string name="settings_theme_color">Цвят на темата</string>
|
||||||
|
<string name="settings_system_locale">Система</string>
|
||||||
|
<string name="settings_locale">Език</string>
|
||||||
|
<string name="noGroupCards">Групата не съдържа карти</string>
|
||||||
|
<string name="toggleMoreInfo">Превключване на повече информация</string>
|
||||||
|
<string name="barcodeImageDescriptionWithType">Изображение на щрихкод на карта от вида <xliff:g>%s</xliff:g></string>
|
||||||
|
<string name="swipeToSwitchImages">Плъзване или задържане за смяна на изображения</string>
|
||||||
|
<string name="sort_by">Сортиране по</string>
|
||||||
|
<string name="reverse">Наобратно</string>
|
||||||
|
<string name="sort_by_balance">Наличност</string>
|
||||||
|
<string name="sort_by_expiry">Валидност</string>
|
||||||
|
<string name="sort_by_most_recently_used">Последно използване</string>
|
||||||
|
<string name="sort_by_name">Наименование</string>
|
||||||
|
<string name="sort">Сортиране</string>
|
||||||
|
<string name="credits">Заслуги</string>
|
||||||
|
<string name="license">Лиценз</string>
|
||||||
|
<string name="source_repository">Хранилище на изходния код</string>
|
||||||
|
<string name="on_github">в GitHub</string>
|
||||||
|
<string name="rate_this_app">Оценете приложението</string>
|
||||||
|
<string name="report_error">Докладване на грешка</string>
|
||||||
|
<string name="version_history">История на изданията</string>
|
||||||
|
<string name="on_google_play">в Google Play</string>
|
||||||
|
<string name="and_data_usage">и използване на данни</string>
|
||||||
|
<string name="help_translate_this_app">Помогнете за превода на приложението</string>
|
||||||
|
<string name="exportPasswordHint">Въведете парола</string>
|
||||||
|
<string name="exportPassword">Задаване на парола за защита на изнесеното (по избор)</string>
|
||||||
|
<string name="setIcon">Задаване на пиктограма</string>
|
||||||
|
<string name="editGroup">Променяне на списъка: <xliff:g>%s</xliff:g></string>
|
||||||
|
<string name="group_name_already_in_use">Има списък с това име</string>
|
||||||
|
<string name="group_updated">Промените са запазени</string>
|
||||||
|
<string name="selectColor">Избиране на цвят</string>
|
||||||
|
<string name="group_name_is_empty">Името на списъка не може да е празно</string>
|
||||||
|
<string name="group_edit">Редактиране на списък</string>
|
||||||
|
<string name="action_show_details">Повече детайли</string>
|
||||||
|
<string name="action_hide_details">По-малко детайли</string>
|
||||||
|
<string name="noGiftCardsGroup">Няма карти. След като добавите ще можете да ги зачислите към списък от тук.</string>
|
||||||
|
</resources>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user