mirror of
https://github.com/CatimaLoyalty/Android.git
synced 2026-01-06 05:57:59 -05:00
Compare commits
1511 Commits
v2.4.0
...
fix/barcod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65d8d45018 | ||
|
|
0e7a5428f6 | ||
|
|
8030eb52f3 | ||
|
|
9fc315158f | ||
|
|
971f68b0a1 | ||
|
|
adfc17d5c2 | ||
|
|
5ee2852e4c | ||
|
|
ea1d42fa35 | ||
|
|
f8b90f2c07 | ||
|
|
6dc9821891 | ||
|
|
93a2e9cdbf | ||
|
|
6a9d54d6f0 | ||
|
|
28f0b407b5 | ||
|
|
89ed31ffe0 | ||
|
|
aa481ea094 | ||
|
|
523aaef650 | ||
|
|
a56c4d449d | ||
|
|
1468130477 | ||
|
|
0b5571a065 | ||
|
|
002c221390 | ||
|
|
2272c88d04 | ||
|
|
6a58bd632f | ||
|
|
027a7f798d | ||
|
|
dd4ad6d860 | ||
|
|
3342b8f83f | ||
|
|
5951c74fc4 | ||
|
|
2cae83c84f | ||
|
|
65ac1a2558 | ||
|
|
6e064e1533 | ||
|
|
0d1c3051c8 | ||
|
|
f2885cd96e | ||
|
|
98de0a7acb | ||
|
|
6622d0f4db | ||
|
|
ccc269ab3e | ||
|
|
4008cc2349 | ||
|
|
02bb266762 | ||
|
|
5e64400faf | ||
|
|
da2a444ae8 | ||
|
|
1a22c27326 | ||
|
|
b5a7824179 | ||
|
|
1f84aa9dc7 | ||
|
|
942336e281 | ||
|
|
27cda3a949 | ||
|
|
1d5d105f8a | ||
|
|
5c886d443f | ||
|
|
aa306ad11d | ||
|
|
d1cc0d9aac | ||
|
|
f8e08f76b8 | ||
|
|
27c18fa5ff | ||
|
|
0017e767c9 | ||
|
|
4d742dc9e4 | ||
|
|
96245543e0 | ||
|
|
59767a7c7f | ||
|
|
8766bcbc71 | ||
|
|
47e87736ca | ||
|
|
f8af01de24 | ||
|
|
4b5a1f8009 | ||
|
|
e55773c8d3 | ||
|
|
e83671eee0 | ||
|
|
8b04e36a88 | ||
|
|
76cd06904c | ||
|
|
cac7a60708 | ||
|
|
aef1078e74 | ||
|
|
20c891ee28 | ||
|
|
5bfca6e428 | ||
|
|
5971e48ab6 | ||
|
|
427b4d64ed | ||
|
|
41995b5bdd | ||
|
|
c74e372c76 | ||
|
|
e0650bc6f9 | ||
|
|
0de6a91bab | ||
|
|
54b854eb64 | ||
|
|
47dd3a35ce | ||
|
|
d16f11d9b7 | ||
|
|
ce190ba3f0 | ||
|
|
070419c888 | ||
|
|
5b6489af9f | ||
|
|
84c2c59038 | ||
|
|
8b5efac70a | ||
|
|
f82860ddd9 | ||
|
|
d3af048fd5 | ||
|
|
2b3c908298 | ||
|
|
14f35edb0f | ||
|
|
d0e80f76d6 | ||
|
|
ee155c721c | ||
|
|
8adc43c9a2 | ||
|
|
02d59dc71d | ||
|
|
3edb0f6a5d | ||
|
|
acf9029394 | ||
|
|
10588769b3 | ||
|
|
68a24fae2f | ||
|
|
625ea26b0a | ||
|
|
dc2c73baa6 | ||
|
|
b8db8bffd4 | ||
|
|
6a078e983b | ||
|
|
bdfc74759f | ||
|
|
b13e14c916 | ||
|
|
32adb85c5b | ||
|
|
a51854e5de | ||
|
|
9ce72bbaaa | ||
|
|
544020febf | ||
|
|
9cfc45e495 | ||
|
|
992ed32d7c | ||
|
|
ac4f83b9e0 | ||
|
|
5810d199fc | ||
|
|
8528e5d8f2 | ||
|
|
83d19c30c2 | ||
|
|
646dab336d | ||
|
|
d05e86cd41 | ||
|
|
6d1c5b31f4 | ||
|
|
583cb49949 | ||
|
|
7f5c7b4cd9 | ||
|
|
17350639aa | ||
|
|
edb961085a | ||
|
|
96c5952869 | ||
|
|
2bac1700d1 | ||
|
|
fe2695e6af | ||
|
|
2120eb9574 | ||
|
|
03917a4067 | ||
|
|
bc09a23c84 | ||
|
|
0ed800634c | ||
|
|
ff4ecfe780 | ||
|
|
2691442809 | ||
|
|
18515e2660 | ||
|
|
df5cbaf7ad | ||
|
|
3cacd03ccd | ||
|
|
2ec04dfa9e | ||
|
|
6943956c37 | ||
|
|
26f0f7909e | ||
|
|
2a1682133b | ||
|
|
02897f312e | ||
|
|
7d4d4cf5c0 | ||
|
|
1fcf797bfe | ||
|
|
b94d417157 | ||
|
|
330227d09b | ||
|
|
67f1ffe617 | ||
|
|
220d7a5ea2 | ||
|
|
e781e00256 | ||
|
|
e630333e8d | ||
|
|
7eb827a219 | ||
|
|
e251b4bc01 | ||
|
|
644f9e1f78 | ||
|
|
396d90c499 | ||
|
|
ff6e93ea2c | ||
|
|
fb97617de6 | ||
|
|
ff47ab0a04 | ||
|
|
ff98dae886 | ||
|
|
deae7681f8 | ||
|
|
849c1d8bec | ||
|
|
b88f0e9a82 | ||
|
|
5ea2972ca1 | ||
|
|
08ad6aaa85 | ||
|
|
8ba32a0196 | ||
|
|
68ce3ce01c | ||
|
|
fb47beb380 | ||
|
|
73a1464d5b | ||
|
|
75b1225b38 | ||
|
|
07a4c4b7a4 | ||
|
|
cbf9295225 | ||
|
|
19d7fae814 | ||
|
|
d9db571362 | ||
|
|
91e44d9418 | ||
|
|
e30b95dd6c | ||
|
|
ddf27e619d | ||
|
|
ebbcbf324f | ||
|
|
c161d4d781 | ||
|
|
da01730c73 | ||
|
|
f0d76f1bc2 | ||
|
|
3d5b1d00f0 | ||
|
|
a098839060 | ||
|
|
d3fac95701 | ||
|
|
37590dc5ee | ||
|
|
8dfbc0b5e6 | ||
|
|
0a9b292d12 | ||
|
|
e7d4228c0c | ||
|
|
a1836e5433 | ||
|
|
52e0496e36 | ||
|
|
50e6e28277 | ||
|
|
4cf770837b | ||
|
|
38fb9f7fb8 | ||
|
|
7a6232c8b6 | ||
|
|
13e62f3b38 | ||
|
|
d5590d37e3 | ||
|
|
fd21806456 | ||
|
|
34bbfffdaf | ||
|
|
56bbb4a786 | ||
|
|
2b92d200ca | ||
|
|
d4154e51d3 | ||
|
|
7d69e63dc9 | ||
|
|
0189f13ee6 | ||
|
|
11e32712f3 | ||
|
|
d124894d34 | ||
|
|
84bfcf2b3f | ||
|
|
8e96096353 | ||
|
|
a4739b2001 | ||
|
|
d3f5f33b53 | ||
|
|
11bb1aa126 | ||
|
|
688010cae4 | ||
|
|
150482eb56 | ||
|
|
c5e98c62ec | ||
|
|
7b2f8885b1 | ||
|
|
d4a7c33787 | ||
|
|
0cd245cafc | ||
|
|
7487993537 | ||
|
|
33b7829b88 | ||
|
|
7531853548 | ||
|
|
3b77cf6f8a | ||
|
|
7b0f459337 | ||
|
|
b498ffd66e | ||
|
|
6c8440a95f | ||
|
|
6bb8cc8bc9 | ||
|
|
dad531876a | ||
|
|
205a629071 | ||
|
|
b7704cc55e | ||
|
|
84ddbef585 | ||
|
|
3bed947ba9 | ||
|
|
23d2faf107 | ||
|
|
b1d06a0be1 | ||
|
|
83ad3e2200 | ||
|
|
84dcce6018 | ||
|
|
d447ea7ff8 | ||
|
|
91f8856efe | ||
|
|
77e45626bb | ||
|
|
a61304a72a | ||
|
|
7336075860 | ||
|
|
6dbd70fced | ||
|
|
81c7d8a259 | ||
|
|
a6a899e696 | ||
|
|
4ae314b3a0 | ||
|
|
16ac55fd67 | ||
|
|
c659065986 | ||
|
|
f1a6b5a7a8 | ||
|
|
036de26e2a | ||
|
|
5ca0919546 | ||
|
|
4430df50fe | ||
|
|
4ff7913283 | ||
|
|
0376245dbc | ||
|
|
3052c40359 | ||
|
|
64af34ca71 | ||
|
|
aba95bbd8a | ||
|
|
5de2a40a54 | ||
|
|
c12c5a6cc4 | ||
|
|
8674520d46 | ||
|
|
06d3a5fe54 | ||
|
|
6c485af249 | ||
|
|
f0ed989463 | ||
|
|
05de9d6941 | ||
|
|
2ddebc5970 | ||
|
|
f1265bf84e | ||
|
|
dc4c4be4c7 | ||
|
|
d5100b97f3 | ||
|
|
31dc05f6b2 | ||
|
|
ca7ee9e694 | ||
|
|
7acc2e28cd | ||
|
|
7eff64bf26 | ||
|
|
d5d3cfa4e8 | ||
|
|
2b937eda7c | ||
|
|
118b5a90b6 | ||
|
|
d9c9295220 | ||
|
|
eb4b6f276e | ||
|
|
52e68df515 | ||
|
|
c23973086c | ||
|
|
34be79f3d2 | ||
|
|
a8082748ad | ||
|
|
08a4de919f | ||
|
|
7ce54ec0d3 | ||
|
|
ce7dad0c8e | ||
|
|
e059ed361d | ||
|
|
d12d641913 | ||
|
|
5336b37133 | ||
|
|
1819476a71 | ||
|
|
e2c621ec1f | ||
|
|
854bca27ae | ||
|
|
b1dd9fa38a | ||
|
|
8776ed61e9 | ||
|
|
c35fe166ed | ||
|
|
0126b6d6da | ||
|
|
4ecb877e27 | ||
|
|
1c2e810319 | ||
|
|
4f7f72f213 | ||
|
|
88d27c0385 | ||
|
|
2f1f6ed452 | ||
|
|
1ac816000d | ||
|
|
aa069e93b1 | ||
|
|
2f0084a73b | ||
|
|
68624f8911 | ||
|
|
5d7c503465 | ||
|
|
9015b17de6 | ||
|
|
a5f7265bd6 | ||
|
|
b03d579ee7 | ||
|
|
f4511aa82e | ||
|
|
69da7e1e2c | ||
|
|
b3de8428f8 | ||
|
|
7edf97774b | ||
|
|
fddad366d6 | ||
|
|
fcb03791b1 | ||
|
|
e25b0ee251 | ||
|
|
c5b5b0e979 | ||
|
|
347e975d9b | ||
|
|
8890788c3d | ||
|
|
9f9da82e18 | ||
|
|
ab8490798b | ||
|
|
c4b9bf77d9 | ||
|
|
a1a653b939 | ||
|
|
f9cf64a2ea | ||
|
|
83e9a01b8d | ||
|
|
1d776914ee | ||
|
|
bfc103dca9 | ||
|
|
0281f15b36 | ||
|
|
e1929de4b3 | ||
|
|
81699dbed9 | ||
|
|
5c42342070 | ||
|
|
f1c21fc7fe | ||
|
|
7c418dc416 | ||
|
|
3610e9dfb2 | ||
|
|
c5793da52c | ||
|
|
deef5ced0c | ||
|
|
7861f6cd8c | ||
|
|
4ad73a754e | ||
|
|
d3487cc535 | ||
|
|
677283c872 | ||
|
|
d39b732b77 | ||
|
|
5242290a8f | ||
|
|
9d07c1a29c | ||
|
|
548b1b1e8e | ||
|
|
8318d2e7c3 | ||
|
|
99e836a8f1 | ||
|
|
3586d27249 | ||
|
|
5904ba2af7 | ||
|
|
48b89af11a | ||
|
|
0b0a5cc5fa | ||
|
|
098f987d66 | ||
|
|
0083150fa9 | ||
|
|
1c4b4f1f25 | ||
|
|
b7f596568d | ||
|
|
2df7b9e8c6 | ||
|
|
c37e5f4076 | ||
|
|
8840390b3a | ||
|
|
a8a8ac59b0 | ||
|
|
a2e37a82b7 | ||
|
|
1b52cafea6 | ||
|
|
706bf36584 | ||
|
|
05f3b77a73 | ||
|
|
00b0502a6a | ||
|
|
ed755c35b4 | ||
|
|
3101688abb | ||
|
|
98c9c9ce15 | ||
|
|
3040cbf7a7 | ||
|
|
806b232d60 | ||
|
|
982e012940 | ||
|
|
e2cff0f11e | ||
|
|
e678bae95c | ||
|
|
c3758f2a23 | ||
|
|
4852e61c99 | ||
|
|
74d55c8b73 | ||
|
|
89670b5176 | ||
|
|
6840f39e35 | ||
|
|
635fb0105b | ||
|
|
9ad50041b5 | ||
|
|
0887a16555 | ||
|
|
ea690d19dc | ||
|
|
40f7b6e417 | ||
|
|
25b3e18618 | ||
|
|
aa28a52855 | ||
|
|
fe87fe1981 | ||
|
|
6f66b95506 | ||
|
|
a80ab32f07 | ||
|
|
de6248a5d6 | ||
|
|
cfbaeb1ffc | ||
|
|
749a4ddb29 | ||
|
|
4c070c3137 | ||
|
|
5cb8cf3a14 | ||
|
|
a1410a588e | ||
|
|
b2b40809ad | ||
|
|
c092a04e9c | ||
|
|
5bd3a309cc | ||
|
|
4bf6f6cd5f | ||
|
|
1872bea0c3 | ||
|
|
665ef5f712 | ||
|
|
9fbcb23465 | ||
|
|
25c35acd6d | ||
|
|
35747b7d9f | ||
|
|
eea5cdfdd0 | ||
|
|
e0c28830ab | ||
|
|
e714d98ae6 | ||
|
|
bbfba92de0 | ||
|
|
efda8f9f10 | ||
|
|
041472b44b | ||
|
|
92f79e9d3e | ||
|
|
f932d8f6e4 | ||
|
|
5e3399cd32 | ||
|
|
b736f31dc2 | ||
|
|
ff285430c0 | ||
|
|
504c1ac516 | ||
|
|
c4746fe2b9 | ||
|
|
99383d4fc6 | ||
|
|
71cd16caac | ||
|
|
6a04077cec | ||
|
|
5664ff5631 | ||
|
|
48764e266e | ||
|
|
435cfd2839 | ||
|
|
34639f2a2e | ||
|
|
ce9c3bffe6 | ||
|
|
d0d15393f6 | ||
|
|
e9eaf51e40 | ||
|
|
de8a843414 | ||
|
|
0ad4c683b7 | ||
|
|
fdbff0f942 | ||
|
|
a02d9fd995 | ||
|
|
d7f43f8a2b | ||
|
|
3cbd69c6c7 | ||
|
|
1cff0e29e4 | ||
|
|
e718ffb77d | ||
|
|
c2d711650f | ||
|
|
f205045916 | ||
|
|
6ed6e683b5 | ||
|
|
1612bed309 | ||
|
|
ac8a2c445c | ||
|
|
f98203fc5d | ||
|
|
82298d0d50 | ||
|
|
916515f8b2 | ||
|
|
b5e6d40857 | ||
|
|
efef397870 | ||
|
|
377438b6ae | ||
|
|
27443db945 | ||
|
|
420886bc3f | ||
|
|
86a01d36db | ||
|
|
329293fa7c | ||
|
|
26bbea7377 | ||
|
|
50385c6e0e | ||
|
|
43c45c9c0c | ||
|
|
17cdc58b19 | ||
|
|
4960b77ca3 | ||
|
|
a7c6ac695e | ||
|
|
63dfe27b25 | ||
|
|
2ec44046a8 | ||
|
|
0363bc67e4 | ||
|
|
affa434679 | ||
|
|
ded151830e | ||
|
|
e1b604c1d2 | ||
|
|
45d9353b54 | ||
|
|
ceceda560a | ||
|
|
088c25dbad | ||
|
|
54fc1e9106 | ||
|
|
1ce4216584 | ||
|
|
78d0bc7bb7 | ||
|
|
fc4bb134a9 | ||
|
|
cecfcd4714 | ||
|
|
c3364d4592 | ||
|
|
df29abbebd | ||
|
|
d4f30f798b | ||
|
|
61578a6016 | ||
|
|
13695b5c37 | ||
|
|
97d1dcc7eb | ||
|
|
5efea8c1e8 | ||
|
|
c2607cc6a2 | ||
|
|
6c70e77a75 | ||
|
|
d67c896ffe | ||
|
|
c821040c25 | ||
|
|
946a56e921 | ||
|
|
b1ea2c4687 | ||
|
|
745dd109fb | ||
|
|
9f047f3dbd | ||
|
|
68afe746eb | ||
|
|
ebd3b0f7f5 | ||
|
|
163de3653e | ||
|
|
1507694bae | ||
|
|
6d134009be | ||
|
|
c1d2a30931 | ||
|
|
e5cb6fa1a7 | ||
|
|
2188280ada | ||
|
|
fdc8cb0adf | ||
|
|
b79286d1a6 | ||
|
|
d9f0ea8346 | ||
|
|
a262120bc3 | ||
|
|
446131a87d | ||
|
|
3466ba6591 | ||
|
|
5b3c6559b4 | ||
|
|
4680b53f86 | ||
|
|
8423ea9b59 | ||
|
|
1d8b2d82ed | ||
|
|
784bd299e8 | ||
|
|
c8df7a7a2e | ||
|
|
cec2058f49 | ||
|
|
c910a96005 | ||
|
|
f3c7a75963 | ||
|
|
04e0b9ba34 | ||
|
|
1b8e9e4d7c | ||
|
|
7a556e320b | ||
|
|
db706a02d3 | ||
|
|
8e452ed6bd | ||
|
|
a9e574a9b4 | ||
|
|
f24d11403c | ||
|
|
e1c658ac2a | ||
|
|
a5901700b4 | ||
|
|
bf4cd7a961 | ||
|
|
79ca5196d0 | ||
|
|
b24d641aa6 | ||
|
|
84051555b6 | ||
|
|
48ff57d0fc | ||
|
|
3a6828b34b | ||
|
|
efd36d061f | ||
|
|
a5ef8ae914 | ||
|
|
37ce61646b | ||
|
|
5e59315017 | ||
|
|
fbed1348aa | ||
|
|
438e9f09a3 | ||
|
|
aee9176518 | ||
|
|
70f7768cee | ||
|
|
bf7e35c361 | ||
|
|
71a6fb2f56 | ||
|
|
8d799fce26 | ||
|
|
ae6e98b876 | ||
|
|
44d8597fa5 | ||
|
|
de47b7b1d2 | ||
|
|
631d65708e | ||
|
|
62ffcbc948 | ||
|
|
14ef3086a4 | ||
|
|
011b0f7ecc | ||
|
|
89eaac12d5 | ||
|
|
2fbe5a821c | ||
|
|
5516acecce | ||
|
|
6163de3086 | ||
|
|
4658896304 | ||
|
|
e0d9e2b2dd | ||
|
|
7dd2291376 | ||
|
|
bc69b82029 | ||
|
|
1502ac1923 | ||
|
|
0552acae26 | ||
|
|
18e30d2726 | ||
|
|
1646db24df | ||
|
|
6a6a8fff2b | ||
|
|
a0f8f6e6b5 | ||
|
|
3dda3aa1a5 | ||
|
|
ac6bffcc67 | ||
|
|
93fac2e726 | ||
|
|
9b724ebcc7 | ||
|
|
540ef6d345 | ||
|
|
64fa790836 | ||
|
|
2c18b0dd19 | ||
|
|
f570f2746a | ||
|
|
4c9c717792 | ||
|
|
db4408108a | ||
|
|
f75df30493 | ||
|
|
0f914414af | ||
|
|
3468293f5f | ||
|
|
c9b8b2df9e | ||
|
|
3d138c9504 | ||
|
|
72b95988e5 | ||
|
|
188c147173 | ||
|
|
6224a13d17 | ||
|
|
f393b9b618 | ||
|
|
6fa6cdfadf | ||
|
|
bc7f44f60a | ||
|
|
5579784b0a | ||
|
|
a20f8b58f8 | ||
|
|
fc8d0ac1aa | ||
|
|
edf953cdad | ||
|
|
7290c4aa28 | ||
|
|
f28e614c7f | ||
|
|
616b23d3a8 | ||
|
|
5fda774626 | ||
|
|
30941a9e16 | ||
|
|
9289d3cf0b | ||
|
|
0ecc36a83c | ||
|
|
d3b8569ef7 | ||
|
|
19722af65f | ||
|
|
329be8abbb | ||
|
|
9aa6acccda | ||
|
|
ba05c6872b | ||
|
|
ca1fc214ff | ||
|
|
2ebb3e1837 | ||
|
|
019c5e232d | ||
|
|
dda527a72f | ||
|
|
df0bbe773f | ||
|
|
a320e1c776 | ||
|
|
607669a74c | ||
|
|
f50dfeac9e | ||
|
|
ef46f6a00b | ||
|
|
eafedb52ad | ||
|
|
423384fae6 | ||
|
|
09e96eba53 | ||
|
|
4b0a4a7db9 | ||
|
|
2371738a15 | ||
|
|
e22d11f8e9 | ||
|
|
917e60a7f5 | ||
|
|
4d09f66ac6 | ||
|
|
622aa96464 | ||
|
|
9887e19ec9 | ||
|
|
a308634868 | ||
|
|
44c3c8b32f | ||
|
|
69835ff0c9 | ||
|
|
8270bc6c03 | ||
|
|
9e8ea3384e | ||
|
|
c8b258cd62 | ||
|
|
f23c73a67a | ||
|
|
8ba3295ba1 | ||
|
|
6e3d61304f | ||
|
|
9b7b3c4b9f | ||
|
|
b993221af5 | ||
|
|
86c029fe92 | ||
|
|
fbe857a5cf | ||
|
|
1717560c05 | ||
|
|
02f85cd49a | ||
|
|
69a6afe611 | ||
|
|
94fa6d19c4 | ||
|
|
211ee9ce69 | ||
|
|
e818be3d50 | ||
|
|
6620369616 | ||
|
|
9d9ce1d11f | ||
|
|
76ba3c333e | ||
|
|
0429086c57 | ||
|
|
4a4b534813 | ||
|
|
8cfae80ea8 | ||
|
|
517a9cf37a | ||
|
|
242f4b7ad9 | ||
|
|
ce1a210650 | ||
|
|
9d369a8e6f | ||
|
|
240f73f743 | ||
|
|
3cf1579bb0 | ||
|
|
171fb83249 | ||
|
|
603b02911b | ||
|
|
ab5eb62bc1 | ||
|
|
40b2b5c96f | ||
|
|
6efe0f6c54 | ||
|
|
00a78c6030 | ||
|
|
0db46614a8 | ||
|
|
32d139f756 | ||
|
|
c2c8b780c8 | ||
|
|
057a58a3d6 | ||
|
|
551fbed8ad | ||
|
|
468c86b5ea | ||
|
|
50cb3146bf | ||
|
|
0853bd88a4 | ||
|
|
3bbe8e9524 | ||
|
|
ebc2e07fc4 | ||
|
|
ad368e68eb | ||
|
|
a1d4bb746f | ||
|
|
44473ae921 | ||
|
|
2c3d9f714c | ||
|
|
13121e9869 | ||
|
|
14116324fb | ||
|
|
d207197055 | ||
|
|
a36775c6fd | ||
|
|
cae95d2577 | ||
|
|
1c95a767c0 | ||
|
|
26fd3e3a58 | ||
|
|
e79be213fc | ||
|
|
2010b9a25c | ||
|
|
355c2f9ceb | ||
|
|
43daeec5c3 | ||
|
|
5ddf77dd63 | ||
|
|
64fb91d644 | ||
|
|
4921d9f4b0 | ||
|
|
8f9dd584b1 | ||
|
|
04f2cffeb3 | ||
|
|
8ca899b7d1 | ||
|
|
c0f3e814a0 | ||
|
|
1eb8d03b74 | ||
|
|
ec0dc6bd4c | ||
|
|
cd7b3d5970 | ||
|
|
55843899da | ||
|
|
d639013bea | ||
|
|
45785b66e7 | ||
|
|
6a20366cc9 | ||
|
|
4856e9e971 | ||
|
|
4141856287 | ||
|
|
12196fc9b1 | ||
|
|
c73e7924ad | ||
|
|
acf83087f4 | ||
|
|
8ef507c011 | ||
|
|
d36f82df05 | ||
|
|
5f50e86234 | ||
|
|
733c9885e8 | ||
|
|
4b7acefece | ||
|
|
a6b950e2e1 | ||
|
|
c21f8098ef | ||
|
|
3ccce45f17 | ||
|
|
f096f63352 | ||
|
|
babd6ff9bb | ||
|
|
3c5dea6fbf | ||
|
|
3bf7d827a1 | ||
|
|
eea03dd516 | ||
|
|
92ceb7ee50 | ||
|
|
aba5dbfb38 | ||
|
|
1ba4839544 | ||
|
|
cca68cfcc5 | ||
|
|
d2043c331a | ||
|
|
b57bc7d7b6 | ||
|
|
23fb6d9c6a | ||
|
|
2498ee0354 | ||
|
|
eb38d0a8aa | ||
|
|
cb0deaae27 | ||
|
|
8220c8b32e | ||
|
|
8458a2e79a | ||
|
|
d436a20b23 | ||
|
|
c24f3b6ece | ||
|
|
d5cb1a79d2 | ||
|
|
7040dbf997 | ||
|
|
d1685f2557 | ||
|
|
36237d9b3e | ||
|
|
f968aba926 | ||
|
|
2d7960ddc4 | ||
|
|
4cdd168b8c | ||
|
|
9de54872f3 | ||
|
|
f8f0edd8d3 | ||
|
|
3703f3d1ac | ||
|
|
3bbd16e3a9 | ||
|
|
a2947d1f58 | ||
|
|
4b94dd8b48 | ||
|
|
62376769cd | ||
|
|
e057a24526 | ||
|
|
25cc1a1160 | ||
|
|
d7ed4aeff8 | ||
|
|
58ebb7c7bf | ||
|
|
be2c586820 | ||
|
|
8a0b2d9460 | ||
|
|
4b1583ce4c | ||
|
|
83c611f4ae | ||
|
|
0a7082bed5 | ||
|
|
94c84c60f5 | ||
|
|
4ce7f475f4 | ||
|
|
75057d3c41 | ||
|
|
62fce7231d | ||
|
|
d201d2e98f | ||
|
|
36ff4ca9ae | ||
|
|
a7a775bc01 | ||
|
|
d217ac8752 | ||
|
|
d376569ba1 | ||
|
|
1a09b1bd5e | ||
|
|
ec1f4f8fbd | ||
|
|
3883617a34 | ||
|
|
2e7c8f39a7 | ||
|
|
7fc2bdc46e | ||
|
|
c141a8986c | ||
|
|
70ad884aa3 | ||
|
|
2bd8aed853 | ||
|
|
78f5908c33 | ||
|
|
2f43febe95 | ||
|
|
17d23de518 | ||
|
|
af831cfdbd | ||
|
|
4476646317 | ||
|
|
958dc29689 | ||
|
|
21216f3aa6 | ||
|
|
808dd73221 | ||
|
|
e3bd82f4a2 | ||
|
|
2cbb105c21 | ||
|
|
40c364a5e8 | ||
|
|
8c484078b4 | ||
|
|
f93f3b912d | ||
|
|
934ef22a8c | ||
|
|
732679f3f8 | ||
|
|
2e7cbcbac9 | ||
|
|
eb5161a382 | ||
|
|
b765d77145 | ||
|
|
ad344c7d12 | ||
|
|
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 | ||
|
|
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 |
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"
|
||||||
12
.github/workflows/android.yml
vendored
12
.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,6 +17,8 @@ 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 11
|
- name: set up JDK 11
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
@@ -24,7 +30,7 @@ jobs:
|
|||||||
- 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
|
||||||
|
|||||||
9
.github/workflows/autoclose-needs-info.yml
vendored
9
.github/workflows/autoclose-needs-info.yml
vendored
@@ -2,7 +2,7 @@ name: 'Close issues and PRs needing info for too long'
|
|||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '30 1 * * *'
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
@@ -17,7 +17,8 @@ jobs:
|
|||||||
days-before-close: 90
|
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-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.'
|
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'
|
only-labels: 'state: needs info'
|
||||||
stale-issue-label: 'needs info'
|
stale-issue-label: 'state: needs info'
|
||||||
stale-pr-label: 'needs info'
|
stale-pr-label: 'state: needs info'
|
||||||
remove-stale-when-updated: false
|
remove-stale-when-updated: false
|
||||||
|
enable-statistics: true
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
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
|
||||||
14
.github/workflows/contributors-to-file.yml
vendored
14
.github/workflows/contributors-to-file.yml
vendored
@@ -1,10 +1,12 @@
|
|||||||
|
name: Write contributors to file
|
||||||
on:
|
on:
|
||||||
push:
|
schedule:
|
||||||
branches: [ master ]
|
- cron: '3 4 * * 0'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
contributors_to_file:
|
contributors_to_file:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
name: Write contributors to file
|
name: Write contributors to file
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
@@ -12,6 +14,12 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Update contributors
|
- name: Update contributors
|
||||||
id: update_contributors
|
id: update_contributors
|
||||||
uses: TheLastProject/contributors-to-file-action@v1.0.2
|
uses: TheLastProject/contributors-to-file-action@v2
|
||||||
with:
|
with:
|
||||||
file_in_repo: app/src/main/res/raw/contributors.txt
|
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
|
||||||
|
|||||||
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)
|
||||||
500
CHANGELOG.md
500
CHANGELOG.md
@@ -1,96 +1,249 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## v2.4.0 (2021-08-29)
|
## Unreleased - 115
|
||||||
|
|
||||||
Changes:
|
- Open image in gallery on long-press
|
||||||
|
|
||||||
|
## v2.20.0 - 114
|
||||||
|
|
||||||
|
- Add Monochrome icon for Android 13
|
||||||
|
- Improve first launch screen
|
||||||
|
- Fidme import fixes
|
||||||
|
|
||||||
|
## v2.19.0 - 113
|
||||||
|
|
||||||
|
- Add previous and next buttons to the loyalty card view
|
||||||
|
- Fix foreground colour on edit button
|
||||||
|
- Replace floppy disk save icon with checkmark
|
||||||
|
|
||||||
|
## v2.18.2 - 112
|
||||||
|
|
||||||
|
- Make the possibility to set a custom header more visible
|
||||||
|
|
||||||
|
## v2.18.1 - 111
|
||||||
|
|
||||||
|
- Arabic language support
|
||||||
|
- Display archived card count in group overview
|
||||||
|
- Fix balance parsing bugs (made cards not savable in Arabic and other language with non-Western numbers)
|
||||||
|
- Fix custom theme not applying to main screen correctly
|
||||||
|
- Improve display of selected cards
|
||||||
|
- Fix crash when leaving cardview in RTL layouts for cards with expiry or balance
|
||||||
|
- Fix back arrow in card view pointing the wrong way in RTL layouts
|
||||||
|
|
||||||
|
## v2.17.1 - 109
|
||||||
|
|
||||||
|
- Fix incorrect text colour on "No barcode" button
|
||||||
|
|
||||||
|
## v2.17.0 - 108
|
||||||
|
|
||||||
|
- Add card duplication feature
|
||||||
|
- Don't allow choosing expiry before 1970 (they never worked anyway)
|
||||||
|
- Add support for archiving cards
|
||||||
|
- Move delete from edit to view
|
||||||
|
- Remove rotation lock icon in favour of a new rotation lock setting
|
||||||
|
|
||||||
|
## v2.16.3 - 107 (2022-04-15)
|
||||||
|
|
||||||
|
- Stocard import fixes
|
||||||
|
|
||||||
|
## v2.16.2 - 106 (2022-03-31)
|
||||||
|
|
||||||
|
- Fix some character sequences being shown as a single character
|
||||||
|
|
||||||
|
## v2.16.1 - 105 (2022-03-25)
|
||||||
|
|
||||||
|
- Fix gray block appearing on invalid value for barcode
|
||||||
|
- Stocard import fixes
|
||||||
|
|
||||||
|
## v2.16.0 - 104 (2022-03-09)
|
||||||
|
|
||||||
|
- Save card detail expansion state
|
||||||
|
- Minor UI fixes
|
||||||
|
|
||||||
|
## v2.15.2 - 103 (2022-02-11)
|
||||||
|
|
||||||
|
- Fix manual language selection not applying everywhere
|
||||||
|
- Fix crash in edit view on regionless locale
|
||||||
|
|
||||||
|
## v2.15.1 - 102 (2022-02-10)
|
||||||
|
|
||||||
|
- Various minor fixes
|
||||||
|
- Fix crash when using Norwegian translation
|
||||||
|
|
||||||
|
## v2.15.0 - 101 (2022-02-06)
|
||||||
|
|
||||||
|
- Fix cropper not using theme colour
|
||||||
|
- Fix minor theming issues
|
||||||
|
- Add pure black dark theme for OLED screens
|
||||||
|
|
||||||
|
## v2.14.1 - 100 (2022-01-15)
|
||||||
|
|
||||||
|
- Hide search, expand and sort icons until there is at least 1 card
|
||||||
|
- Various theming fixes
|
||||||
|
|
||||||
|
## v2.14.0 - 99 (2022-01-14)
|
||||||
|
|
||||||
|
- Material You redesign
|
||||||
|
|
||||||
|
## v2.13.1 - 98 (2022-01-09)
|
||||||
|
|
||||||
|
- Fix various TalkBack-related bugs
|
||||||
|
|
||||||
|
## v2.13.0 - 97 (2022-01-03)
|
||||||
|
|
||||||
|
- Fixed pressing the save button multiple times creating multiple entries
|
||||||
|
- Lower card header size when hiding details to fit even more cards
|
||||||
|
- Restructure edit screen
|
||||||
|
- Improve star icon contrast in main view
|
||||||
|
|
||||||
|
## v2.12.0 - 96 (2021-12-23)
|
||||||
|
|
||||||
|
- Add CODE 93 support
|
||||||
|
- Various minor bugfixes and improvements
|
||||||
|
|
||||||
|
## 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
|
- Improve card list for landscape and tablet display
|
||||||
- Add theming colour support (thanks, Subhashish Anand!)
|
- Add theming colour support (thanks, Subhashish Anand!)
|
||||||
- Don't close scan activity on camera error (so manual entry is still possible)
|
- Don't close scan activity on camera error (so manual entry is still possible)
|
||||||
- Add all contributors to the about dialog
|
- Add all contributors to the about dialog
|
||||||
|
|
||||||
## v2.3.0 (2021-08-19)
|
## v2.3.0 - 80 (2021-08-19)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix images not imported from backup
|
- Fix images not imported from backup
|
||||||
- Option to override language
|
- Option to override language
|
||||||
|
|
||||||
## v2.2.3 (2021-08-13)
|
## v2.2.3 - 79 (2021-08-13)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix widget creating different-looking shortcut than app shortcuts
|
- Fix widget creating different-looking shortcut than app shortcuts
|
||||||
- Replace default Android black screen with splash screen
|
- Replace default Android black screen with splash screen
|
||||||
|
|
||||||
## v2.2.2 (2021-08-08)
|
## v2.2.2 - 78 (2021-08-08)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix crash on rotation in loyalty card edit activity
|
- Fix crash on rotation in loyalty card edit activity
|
||||||
|
|
||||||
## v2.2.1 (2021-08-07)
|
## v2.2.1 - 77 (2021-08-07)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Improve Stocard importer
|
- Improve Stocard importer
|
||||||
- Fix importing Catima export with multiline note
|
- Fix importing Catima export with multiline note
|
||||||
- Scale card title in acceptable range
|
- Scale card title in acceptable range
|
||||||
- Animation improvements
|
- Animation improvements
|
||||||
|
|
||||||
## v2.2.0 (2021-08-02)
|
## v2.2.0 - 76 (2021-08-02)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Make links in notes clickable
|
- Make links in notes clickable
|
||||||
- Pre-select group the user is currently in when creating a new card
|
- Pre-select group the user is currently in when creating a new card
|
||||||
- Comma-separate group names in loyalty card view
|
- Comma-separate group names in loyalty card view
|
||||||
- Fix maximize button appearing on no barcode
|
- Fix maximize button appearing on no barcode
|
||||||
|
|
||||||
## v2.1.0 (2021-08-01)
|
## v2.1.0 - 75 (2021-08-01)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix selected colour in colour changing dialog
|
- Fix selected colour in colour changing dialog
|
||||||
- Support for deleting multiple cards at once
|
- Support for deleting multiple cards at once
|
||||||
- Fix possible ArithmeticException when resizing image
|
- Fix possible ArithmeticException when resizing image
|
||||||
- Fix fullscreen is closed when rotating device
|
- Fix fullscreen is closed when rotating device
|
||||||
|
|
||||||
## v2.0.4 (2021-07-27)
|
## v2.0.4 - 74 (2021-07-27)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix shortcut creation
|
- Fix shortcut creation
|
||||||
- Generate card-specific shortcut icon
|
- Generate card-specific shortcut icon
|
||||||
- Fix ability to change loyalty card colour
|
- Fix ability to change loyalty card colour
|
||||||
|
|
||||||
## v2.0.3 (2021-07-25)
|
## v2.0.3 - 73 (2021-07-25)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix loading photos when editing existing card
|
- Fix loading photos when editing existing card
|
||||||
|
|
||||||
## v2.0.2 (2021-07-25)
|
## v2.0.2 - 72 (2021-07-25)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Fix inability to configure photos in new loyalty card
|
- Fix inability to configure photos in new loyalty card
|
||||||
|
|
||||||
## v2.0.1 (2021-07-21)
|
## v2.0.1 - 71 (2021-07-21)
|
||||||
|
|
||||||
Changes:
|
|
||||||
|
|
||||||
- Several minor translation and UI fixes
|
- Several minor translation and UI fixes
|
||||||
- Fix crash in import/sharing loyalty card on Android 6
|
- Fix crash in import/sharing loyalty card on Android 6
|
||||||
|
|
||||||
## v2.0 (2021-07-14)
|
## v2.0 - 70 (2021-07-14)
|
||||||
|
|
||||||
Breaking changes:
|
|
||||||
- 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:
|
|
||||||
|
|
||||||
|
- 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
|
||||||
@@ -101,26 +254,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
|
||||||
@@ -129,175 +276,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
|
||||||
@@ -306,31 +403,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))
|
||||||
@@ -347,57 +436,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))
|
||||||
@@ -405,87 +478,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))
|
||||||
@@ -493,37 +542,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))
|
||||||
@@ -531,15 +570,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".
|
||||||
|
|
||||||
@@ -549,55 +586,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
|
||||||
@@ -614,13 +632,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,14 +1,27 @@
|
|||||||
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.
|
||||||
|
|
||||||
## Test Your Code
|
## Translation Changes
|
||||||
|
|
||||||
|
Translation changes are managed through [Weblate](https://hosted.weblate.org/projects/catima/).
|
||||||
|
Please do not supply translation updates directly through GitHub.
|
||||||
|
|
||||||
|
Weblate requires an account to translate changes, so please log in before
|
||||||
|
you start translating.
|
||||||
|
|
||||||
|
While using Weblate, please do not ignore any of its warnings. They exist
|
||||||
|
for good reason.
|
||||||
|
|
||||||
|
## Code Changes
|
||||||
|
|
||||||
|
### Test Your Code
|
||||||
|
|
||||||
There are four possible tests you can run to verify your code. The first
|
There are four possible tests you can run to verify your code. The first
|
||||||
is unit tests, which check the basic functionality of the application, and
|
is unit tests, which check the basic functionality of the application, and
|
||||||
@@ -21,21 +34,21 @@ 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.
|
||||||
|
|
||||||
## Explain Your Work
|
### Explain Your Work
|
||||||
|
|
||||||
At the top of every patch you should include a description of the problem you
|
At the top of every patch you should include a description of the problem you
|
||||||
are trying to solve, how you solved it, and why you chose the solution you
|
are trying to solve, how you solved it, and why you chose the solution you
|
||||||
@@ -44,7 +57,7 @@ if you can describe/include a reproducer for the problem in the description as
|
|||||||
well as instructions on how to test for the bug and verify that it has been
|
well as instructions on how to test for the bug and verify that it has been
|
||||||
fixed.
|
fixed.
|
||||||
|
|
||||||
## Sign Your Work
|
### Sign Your Work
|
||||||
|
|
||||||
The sign-off is a simple line at the end of the patch description, which
|
The sign-off is a simple line at the end of the patch description, which
|
||||||
certifies that you wrote it or otherwise have the right to pass it on as an
|
certifies that you wrote it or otherwise have the right to pass it on as an
|
||||||
@@ -82,10 +95,10 @@ your real name, saying:
|
|||||||
|
|
||||||
Signed-off-by: Random J Developer <random@developer.example.org>
|
Signed-off-by: Random J Developer <random@developer.example.org>
|
||||||
|
|
||||||
## 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.
|
||||||
|
|||||||
3
FUNDING.yml
Normal file
3
FUNDING.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
github: TheLastProject
|
||||||
|
custom:
|
||||||
|
- "https://paypal.me/sylviavanos"
|
||||||
184
Gemfile.lock
184
Gemfile.lock
@@ -1,58 +1,80 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.2)
|
CFPropertyList (3.0.5)
|
||||||
|
rexml
|
||||||
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.597.0)
|
||||||
aws-sdk-core (3.109.1)
|
aws-sdk-core (3.131.1)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.239.0)
|
aws-partitions (~> 1, >= 1.525.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.39.0)
|
aws-sdk-kms (1.57.0)
|
||||||
aws-sdk-core (~> 3, >= 3.109.0)
|
aws-sdk-core (~> 3, >= 3.127.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.83.1)
|
aws-sdk-s3 (1.114.0)
|
||||||
aws-sdk-core (~> 3, >= 3.109.0)
|
aws-sdk-core (~> 3, >= 3.127.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.5.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.1.0)
|
||||||
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.3)
|
||||||
excon (0.78.0)
|
excon (0.92.3)
|
||||||
faraday (1.1.0)
|
faraday (1.10.0)
|
||||||
multipart-post (>= 1.2, < 3)
|
faraday-em_http (~> 1.0)
|
||||||
ruby2_keywords
|
faraday-em_synchrony (~> 1.0)
|
||||||
|
faraday-excon (~> 1.1)
|
||||||
|
faraday-httpclient (~> 1.0)
|
||||||
|
faraday-multipart (~> 1.0)
|
||||||
|
faraday-net_http (~> 1.0)
|
||||||
|
faraday-net_http_persistent (~> 1.0)
|
||||||
|
faraday-patron (~> 1.0)
|
||||||
|
faraday-rack (~> 1.0)
|
||||||
|
faraday-retry (~> 1.0)
|
||||||
|
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-multipart (1.0.4)
|
||||||
|
multipart-post (~> 2)
|
||||||
|
faraday-net_http (1.0.1)
|
||||||
|
faraday-net_http_persistent (1.2.0)
|
||||||
|
faraday-patron (1.0.0)
|
||||||
|
faraday-rack (1.0.0)
|
||||||
|
faraday-retry (1.0.3)
|
||||||
|
faraday_middleware (1.2.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.2.0)
|
fastimage (2.2.6)
|
||||||
fastlane (2.165.0)
|
fastlane (2.206.2)
|
||||||
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 +83,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 +106,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.21.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-apis-core (0.5.0)
|
||||||
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.10.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-apis-playcustomapp_v1 (0.7.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-apis-storage_v1 (0.14.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.6.0)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 3.0)
|
||||||
google-cloud-errors (1.0.1)
|
google-cloud-errors (1.2.0)
|
||||||
google-cloud-storage (1.29.1)
|
google-cloud-storage (1.36.2)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.8)
|
||||||
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 (1.1.3)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 3.a)
|
||||||
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.16, < 2.a)
|
||||||
highline (1.7.10)
|
highline (2.0.3)
|
||||||
http-cookie (1.0.3)
|
http-cookie (1.0.5)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
jmespath (1.4.0)
|
jmespath (1.6.1)
|
||||||
json (2.3.1)
|
json (2.6.2)
|
||||||
jwt (2.2.2)
|
jwt (2.4.1)
|
||||||
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.2)
|
||||||
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)
|
||||||
os (1.1.1)
|
optparse (0.1.1)
|
||||||
plist (3.5.0)
|
os (1.1.4)
|
||||||
public_suffix (4.0.6)
|
plist (3.6.0)
|
||||||
rake (13.0.1)
|
public_suffix (4.0.7)
|
||||||
representable (3.0.4)
|
rake (13.0.6)
|
||||||
|
representable (3.2.0)
|
||||||
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.1)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.5, < 3.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.2)
|
||||||
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 +192,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.2)
|
||||||
unicode-display_width (1.7.0)
|
unicode-display_width (1.8.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,15 +11,15 @@ 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 81
|
versionCode 114
|
||||||
versionName "2.4.0"
|
versionName "2.20.0"
|
||||||
|
|
||||||
vectorDrawables.useSupportLibrary true
|
vectorDrawables.useSupportLibrary true
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
@@ -49,8 +49,8 @@ android {
|
|||||||
// Flag to enable support for the new language APIs
|
// Flag to enable support for the new language APIs
|
||||||
coreLibraryDesugaringEnabled true
|
coreLibraryDesugaringEnabled true
|
||||||
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_11
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
@@ -80,21 +80,25 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// AndroidX
|
// AndroidX
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
|
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.3.3'
|
implementation 'androidx.exifinterface:exifinterface:1.3.3'
|
||||||
implementation 'androidx.preference:preference:1.1.1'
|
implementation 'androidx.palette:palette:1.0.0'
|
||||||
implementation 'com.google.android.material:material:1.4.0'
|
implementation 'androidx.preference:preference:1.2.0'
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
implementation 'com.google.android.material:material:1.6.1'
|
||||||
|
implementation 'com.github.yalantis:ucrop:2.2.8'
|
||||||
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6'
|
||||||
|
|
||||||
|
// Splash Screen
|
||||||
|
implementation 'androidx.core:core-splashscreen:1.0.0'
|
||||||
|
|
||||||
// Third-party
|
// Third-party
|
||||||
implementation 'com.journeyapps:zxing-android-embedded:4.1.0@aar'
|
implementation 'com.journeyapps:zxing-android-embedded:4.3.0@aar'
|
||||||
implementation 'com.google.zxing:core:3.4.1'
|
implementation 'com.google.zxing:core:3.5.0'
|
||||||
implementation 'org.apache.commons:commons-csv:1.8'
|
implementation 'org.apache.commons:commons-csv:1.9.0'
|
||||||
implementation 'com.jaredrummler:colorpicker:1.1.0'
|
implementation 'com.jaredrummler:colorpicker:1.1.0'
|
||||||
implementation 'com.github.invissvenska:NumberPickerPreference:1.0.3'
|
implementation 'com.github.invissvenska:NumberPickerPreference:1.0.4'
|
||||||
implementation 'net.lingala.zip4j:zip4j:2.8.0'
|
implementation 'net.lingala.zip4j:zip4j:2.11.2'
|
||||||
|
|
||||||
// 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'
|
||||||
@@ -102,7 +106,7 @@ dependencies {
|
|||||||
// Testing
|
// Testing
|
||||||
testImplementation 'androidx.test:core:1.4.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.6.1'
|
testImplementation 'org.robolectric:robolectric:4.9'
|
||||||
}
|
}
|
||||||
|
|
||||||
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/SplashTheme">
|
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,38 +89,50 @@
|
|||||||
<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"/>
|
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=".UCropWrapper"
|
||||||
|
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,16 +1,16 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
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 android.widget.Toast;
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.core.text.HtmlCompat;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -20,21 +20,25 @@ import java.util.ArrayList;
|
|||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class AboutActivity extends CatimaAppCompatActivity
|
import androidx.appcompat.app.ActionBar;
|
||||||
{
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
import androidx.core.text.HtmlCompat;
|
||||||
|
|
||||||
|
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);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,12 +58,14 @@ public class AboutActivity extends CatimaAppCompatActivity
|
|||||||
contributors.append("<br/>");
|
contributors.append("<br/>");
|
||||||
contributors.append(tmp);
|
contributors.append(tmp);
|
||||||
}
|
}
|
||||||
} catch (IOException ignored) {}
|
} 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("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("NumberPickerPreference", "https://github.com/invissvenska/NumberPickerPreference", "GNU LGPL 3.0"));
|
USED_LIBRARIES.add(new ThirdPartyInfo("NumberPickerPreference", "https://github.com/invissvenska/NumberPickerPreference", "GNU LGPL 3.0"));
|
||||||
|
USED_LIBRARIES.add(new ThirdPartyInfo("uCrop", "https://github.com/Yalantis/uCrop", "Apache 2.0"));
|
||||||
USED_LIBRARIES.add(new ThirdPartyInfo("Zip4j", "https://github.com/srikanth-lingala/zip4j", "Apache 2.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"));
|
||||||
@@ -68,14 +74,12 @@ public class AboutActivity extends CatimaAppCompatActivity
|
|||||||
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(")");
|
||||||
}
|
}
|
||||||
|
|
||||||
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(")");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,49 +87,94 @@ public class AboutActivity extends CatimaAppCompatActivity
|
|||||||
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_contributors), contributors.toString()) +
|
error.setOnClickListener(this);
|
||||||
"<br/><br/>" +
|
rate.setOnClickListener(this);
|
||||||
String.format(getString(R.string.app_libraries), libs.toString()) +
|
|
||||||
"<br/><br/>" +
|
StringBuilder contributorInfo = new StringBuilder();
|
||||||
String.format(getString(R.string.app_resources), resources.toString()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
contributorInfo.append(HtmlCompat.fromHtml(String.format(getString(R.string.app_contributors), contributors.toString()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
||||||
aboutTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
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/CatimaLoyalty/Android/blob/master/LICENSE";
|
||||||
|
} else if (id == R.id.repo) {
|
||||||
|
url = "https://github.com/CatimaLoyalty/Android/";
|
||||||
|
} else if (id == R.id.privacy) {
|
||||||
|
url = "https://catima.app/privacy-policy/";
|
||||||
|
} else if (id == R.id.report_error) {
|
||||||
|
url = "https://github.com/CatimaLoyalty/Android/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));
|
||||||
|
try {
|
||||||
|
startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast.makeText(this, R.string.failedToOpenUrl, Toast.LENGTH_LONG).show();
|
||||||
|
Log.e(TAG, "No activity found to handle intent", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,34 @@
|
|||||||
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.util.TypedValue;
|
||||||
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;
|
||||||
|
import protect.card_locker.barcodes.Barcode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 +39,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 Barcode 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)
|
Barcode barcodeFormat, TextView textView,
|
||||||
{
|
boolean showFallback, Runnable callback, boolean roundCornerPadding
|
||||||
|
) {
|
||||||
|
mContext = context;
|
||||||
|
|
||||||
isSuccesful = true;
|
isSuccesful = true;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
|
||||||
@@ -56,107 +62,50 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
|||||||
cardId = cardIdString;
|
cardId = cardIdString;
|
||||||
format = barcodeFormat;
|
format = barcodeFormat;
|
||||||
|
|
||||||
|
int padding = 0;
|
||||||
|
// Some barcodes already have internal whitespace and shouldn't get extra padding
|
||||||
|
// TODO: Get rid of this hack by somehow detecting this extra whitespace
|
||||||
|
if (roundCornerPadding && !barcodeFormat.hasInternalPadding()) {
|
||||||
|
padding = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, context.getResources().getDisplayMetrics()));
|
||||||
|
}
|
||||||
|
|
||||||
final int MAX_WIDTH = getMaxWidth(format);
|
final int MAX_WIDTH = getMaxWidth(format);
|
||||||
|
|
||||||
if(imageView.getWidth() < MAX_WIDTH)
|
int tempImageHeight;
|
||||||
{
|
int tempImageWidth;
|
||||||
imageHeight = imageView.getHeight();
|
|
||||||
imageWidth = imageView.getWidth();
|
if (imageView.getWidth() < MAX_WIDTH) {
|
||||||
}
|
tempImageHeight = imageView.getHeight();
|
||||||
else
|
tempImageWidth = imageView.getWidth();
|
||||||
{
|
} 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;
|
tempImageWidth = MAX_WIDTH;
|
||||||
double ratio = (double)MAX_WIDTH / (double)imageView.getWidth();
|
double ratio = (double) MAX_WIDTH / (double) imageView.getWidth();
|
||||||
imageHeight = (int)(imageView.getHeight() * ratio);
|
tempImageHeight = (int) (imageView.getHeight() * ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure space for padding if wanted
|
||||||
|
imageWidth = tempImageWidth;
|
||||||
|
imageHeight = tempImageHeight - padding;
|
||||||
|
|
||||||
this.showFallback = showFallback;
|
this.showFallback = showFallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getMaxWidth(BarcodeFormat format)
|
private int getMaxWidth(Barcode format) {
|
||||||
{
|
return format.is2D() ? MAX_WIDTH_2D : MAX_WIDTH_1D;
|
||||||
switch(format)
|
|
||||||
{
|
|
||||||
// 2D barcodes
|
|
||||||
case AZTEC:
|
|
||||||
case DATA_MATRIX:
|
|
||||||
case MAXICODE:
|
|
||||||
case PDF_417:
|
|
||||||
case QR_CODE:
|
|
||||||
return MAX_WIDTH_2D;
|
|
||||||
|
|
||||||
// 1D barcodes:
|
|
||||||
case CODABAR:
|
|
||||||
case CODE_39:
|
|
||||||
case CODE_93:
|
|
||||||
case CODE_128:
|
|
||||||
case EAN_8:
|
|
||||||
case EAN_13:
|
|
||||||
case ITF:
|
|
||||||
case UPC_A:
|
|
||||||
case UPC_E:
|
|
||||||
case RSS_14:
|
|
||||||
case RSS_EXPANDED:
|
|
||||||
case UPC_EAN_EXTENSION:
|
|
||||||
default:
|
|
||||||
return MAX_WIDTH_1D;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFallbackString(BarcodeFormat format)
|
private Bitmap generate() {
|
||||||
{
|
if (cardId.isEmpty()) {
|
||||||
switch(format)
|
|
||||||
{
|
|
||||||
// 2D barcodes
|
|
||||||
case AZTEC:
|
|
||||||
return "AZTEC";
|
|
||||||
case DATA_MATRIX:
|
|
||||||
return "DATA_MATRIX";
|
|
||||||
case PDF_417:
|
|
||||||
return "PDF_417";
|
|
||||||
case QR_CODE:
|
|
||||||
return "QR_CODE";
|
|
||||||
|
|
||||||
// 1D barcodes:
|
|
||||||
case CODABAR:
|
|
||||||
return "C0C";
|
|
||||||
case CODE_39:
|
|
||||||
return "CODE_39";
|
|
||||||
case CODE_128:
|
|
||||||
return "CODE_128";
|
|
||||||
case EAN_8:
|
|
||||||
return "32123456";
|
|
||||||
case EAN_13:
|
|
||||||
return "5901234123457";
|
|
||||||
case ITF:
|
|
||||||
return "1003";
|
|
||||||
case UPC_A:
|
|
||||||
return "123456789012";
|
|
||||||
case UPC_E:
|
|
||||||
return "0123456";
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("No fallback known for this barcode type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Bitmap generate()
|
|
||||||
{
|
|
||||||
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 +120,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 +142,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 +158,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 = format.exampleValue();
|
||||||
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 +212,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 +226,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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,26 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
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 androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
|
|
||||||
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 androidx.appcompat.app.ActionBar;
|
||||||
import java.util.HashMap;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import java.util.LinkedList;
|
import protect.card_locker.barcodes.Barcode;
|
||||||
import java.util.Map;
|
import protect.card_locker.barcodes.BarcodeFactory;
|
||||||
|
import protect.card_locker.barcodes.BarcodeWithValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This activity is callable and will allow a user to enter
|
* This activity is callable and will allow a user to enter
|
||||||
@@ -33,85 +28,59 @@ import java.util.Map;
|
|||||||
* 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 CatimaAppCompatActivity
|
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);
|
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 = new HashMap<>();
|
|
||||||
barcodeViewMap.put(BarcodeFormat.AZTEC.name(), new Pair<>(R.id.aztecBarcode, R.id.aztecBarcodeText));
|
|
||||||
barcodeViewMap.put(BarcodeFormat.CODE_39.name(), new Pair<>(R.id.code39Barcode, R.id.code39BarcodeText));
|
|
||||||
barcodeViewMap.put(BarcodeFormat.CODE_128.name(), new Pair<>(R.id.code128Barcode, R.id.code128BarcodeText));
|
|
||||||
barcodeViewMap.put(BarcodeFormat.CODABAR.name(), new Pair<>(R.id.codabarBarcode, R.id.codabarBarcodeText));
|
|
||||||
barcodeViewMap.put(BarcodeFormat.DATA_MATRIX.name(), new Pair<>(R.id.datamatrixBarcode, R.id.datamatrixBarcodeText));
|
|
||||||
barcodeViewMap.put(BarcodeFormat.EAN_8.name(), new Pair<>(R.id.ean8Barcode, R.id.ean8BarcodeText));
|
|
||||||
barcodeViewMap.put(BarcodeFormat.EAN_13.name(), new Pair<>(R.id.ean13Barcode, R.id.ean13BarcodeText));
|
|
||||||
barcodeViewMap.put(BarcodeFormat.ITF.name(), new Pair<>(R.id.itfBarcode, R.id.itfBarcodeText));
|
|
||||||
barcodeViewMap.put(BarcodeFormat.PDF_417.name(), new Pair<>(R.id.pdf417Barcode, R.id.pdf417BarcodeText));
|
|
||||||
barcodeViewMap.put(BarcodeFormat.QR_CODE.name(), new Pair<>(R.id.qrcodeBarcode, R.id.qrcodeBarcodeText));
|
|
||||||
barcodeViewMap.put(BarcodeFormat.UPC_A.name(), new Pair<>(R.id.upcaBarcode, R.id.upcaBarcodeText));
|
|
||||||
barcodeViewMap.put(BarcodeFormat.UPC_E.name(), new Pair<>(R.id.upceBarcode, R.id.upceBarcodeText));
|
|
||||||
|
|
||||||
EditText cardId = findViewById(R.id.cardId);
|
EditText cardId = findViewById(R.id.cardId);
|
||||||
cardId.addTextChangedListener(new SimpleTextWatcher()
|
ListView mBarcodeList = findViewById(R.id.barcodes);
|
||||||
{
|
mAdapter = new BarcodeSelectorAdapter(this, new ArrayList<>(), this);
|
||||||
|
mBarcodeList.setAdapter(mAdapter);
|
||||||
|
|
||||||
|
cardId.addTextChangedListener(new SimpleTextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count)
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
{
|
// Delay the input processing so we avoid overload
|
||||||
Log.d(TAG, "Entered text: " + s);
|
typingDelayHandler.removeCallbacksAndMessages(null);
|
||||||
|
|
||||||
generateBarcodes(s.toString());
|
typingDelayHandler.postDelayed(() -> {
|
||||||
|
Log.d(TAG, "Entered text: " + s);
|
||||||
|
|
||||||
View noBarcodeButtonView = findViewById(R.id.noBarcode);
|
runOnUiThread(() -> {
|
||||||
setButtonListener(noBarcodeButtonView, s.toString());
|
generateBarcodes(s.toString());
|
||||||
noBarcodeButtonView.setEnabled(s.length() > 0);
|
|
||||||
|
View noBarcodeButtonView = findViewById(R.id.noBarcode);
|
||||||
|
setButtonListener(noBarcodeButtonView, s.toString());
|
||||||
|
noBarcodeButtonView.setEnabled(s.length() > 0);
|
||||||
|
});
|
||||||
|
}, INPUT_DELAY);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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("");
|
||||||
@@ -119,101 +88,29 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
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<BarcodeWithValue> barcodes = new ArrayList<>();
|
||||||
{
|
for (BarcodeFormat barcodeFormat : BarcodeFactory.getAllFormats()) {
|
||||||
ImageView image = findViewById(entry.getValue().first);
|
Barcode catimaBarcode = BarcodeFactory.fromBarcode(barcodeFormat);
|
||||||
TextView text = findViewById(entry.getValue().second);
|
barcodes.add(new BarcodeWithValue(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;
|
||||||
@@ -221,4 +118,26 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity
|
|||||||
|
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRowClicked(int inputPosition, View view) {
|
||||||
|
BarcodeWithValue 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,106 @@
|
|||||||
|
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;
|
||||||
|
import protect.card_locker.barcodes.Barcode;
|
||||||
|
import protect.card_locker.barcodes.BarcodeFactory;
|
||||||
|
import protect.card_locker.barcodes.BarcodeWithValue;
|
||||||
|
|
||||||
|
public class BarcodeSelectorAdapter extends ArrayAdapter<BarcodeWithValue> {
|
||||||
|
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<BarcodeWithValue> barcodes, BarcodeSelectorListener barcodeSelectorListener) {
|
||||||
|
super(context, 0, barcodes);
|
||||||
|
mListener = barcodeSelectorListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBarcodes(ArrayList<BarcodeWithValue> barcodes) {
|
||||||
|
clear();
|
||||||
|
addAll(barcodes);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
mTasks.flushTaskList(TaskHandler.TYPE.BARCODE, true, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
BarcodeWithValue barcodeWithValue = getItem(position);
|
||||||
|
Barcode catimaBarcode = barcodeWithValue.barcode();
|
||||||
|
String value = barcodeWithValue.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 Barcode format = BarcodeFactory.fromName(formatType);
|
||||||
|
|
||||||
|
image.setImageBitmap(null);
|
||||||
|
image.setClipToOutline(true);
|
||||||
|
|
||||||
|
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, true);
|
||||||
|
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, true);
|
||||||
|
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,69 +1,68 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
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.ShortcutInfoCompat;
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
|
||||||
{
|
|
||||||
static final String TAG = "Catima";
|
static final String TAG = "Catima";
|
||||||
final DBHelper mDb = new DBHelper(this);
|
private SQLiteDatabase mDatabase;
|
||||||
|
private LoyaltyCardCursorAdapter mAdapter;
|
||||||
|
|
||||||
@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);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
// 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) {
|
int cardCount = DBHelper.getLoyaltyCardCount(mDatabase);
|
||||||
|
if (cardCount == 0) {
|
||||||
Toast.makeText(this, R.string.noCardsMessage, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.noCardsMessage, Toast.LENGTH_LONG).show();
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If all cards are archived, bail
|
||||||
|
if (DBHelper.getArchivedCardsCount(mDatabase) == cardCount) {
|
||||||
|
Toast.makeText(this, R.string.noUnarchivedCardsMessage, Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
final RecyclerView cardList = findViewById(R.id.list);
|
final RecyclerView cardList = findViewById(R.id.list);
|
||||||
|
GridLayoutManager layoutManager = (GridLayoutManager) cardList.getLayoutManager();
|
||||||
|
if (layoutManager != null) {
|
||||||
|
layoutManager.setSpanCount(getResources().getInteger(R.integer.main_view_card_columns));
|
||||||
|
}
|
||||||
|
|
||||||
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
|
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.Unarchived);
|
||||||
cardList.setLayoutManager(mLayoutManager);
|
mAdapter = new LoyaltyCardCursorAdapter(this, cardCursor, this);
|
||||||
cardList.setItemAnimator(new DefaultItemAnimator());
|
cardList.setAdapter(mAdapter);
|
||||||
|
|
||||||
cardList.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
Cursor cardCursor = db.getLoyaltyCardCursor();
|
|
||||||
|
|
||||||
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor, this);
|
|
||||||
cardList.setAdapter(adapter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onClickAction(int position) {
|
private void onClickAction(int position) {
|
||||||
Cursor selected = mDb.getLoyaltyCardCursor();
|
Cursor selected = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.Unarchived);
|
||||||
selected.moveToPosition(position);
|
selected.moveToPosition(position);
|
||||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
|
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
|
||||||
|
|
||||||
@@ -77,8 +76,24 @@ public class CardShortcutConfigure extends AppCompatActivity implements LoyaltyC
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onIconClicked(int inputPosition) {
|
public boolean onCreateOptionsMenu(Menu inputMenu) {
|
||||||
onClickAction(inputPosition);
|
getMenuInflater().inflate(R.menu.card_details_menu, inputMenu);
|
||||||
|
Utils.updateMenuCardDetailsButtonState(inputMenu.findItem(R.id.action_unfold), mAdapter.showingDetails());
|
||||||
|
return super.onCreateOptionsMenu(inputMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem inputItem) {
|
||||||
|
int id = inputItem.getItemId();
|
||||||
|
|
||||||
|
if (id == R.id.action_unfold) {
|
||||||
|
mAdapter.showDetails(!mAdapter.showingDetails());
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(inputItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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 java.util.List;
|
||||||
|
import java.util.concurrent.Flow;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
@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, DBHelper.LoyaltyCardArchiveFilter.Unarchived);
|
||||||
|
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() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,15 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.graphics.Color;
|
||||||
import android.content.res.Resources;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.os.Bundle;
|
||||||
import android.util.TypedValue;
|
import android.view.View;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.preference.PreferenceManager;
|
|
||||||
|
|
||||||
public class CatimaAppCompatActivity extends AppCompatActivity {
|
public class CatimaAppCompatActivity extends AppCompatActivity {
|
||||||
|
|
||||||
SharedPreferences pref;
|
|
||||||
HashMap<String, Integer> supportedThemes;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(Context base) {
|
protected void attachBaseContext(Context base) {
|
||||||
// Apply chosen language
|
// Apply chosen language
|
||||||
@@ -26,32 +17,25 @@ public class CatimaAppCompatActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Resources.Theme getTheme() {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
if (supportedThemes == null) {
|
super.onCreate(savedInstanceState);
|
||||||
supportedThemes = new HashMap<>();
|
Utils.patchColors(this);
|
||||||
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() {
|
@Override
|
||||||
TypedValue typedValue = new TypedValue();
|
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
|
||||||
Resources.Theme theme = getTheme();
|
super.onPostCreate(savedInstanceState);
|
||||||
theme.resolveAttribute(R.attr.colorPrimary, typedValue, true);
|
// material 3 designer does not consider status bar colors
|
||||||
return typedValue.data;
|
// XXX changing this in onCreate causes issues with the splash screen activity, so doing this here
|
||||||
|
boolean darkMode = Utils.isDarkModeEnabled(this);
|
||||||
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
|
getWindow().setStatusBarColor(Color.TRANSPARENT);
|
||||||
|
getWindow().getDecorView().setSystemUiVisibility(darkMode ? 0 : View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||||
|
} else {
|
||||||
|
// icons are always white back then
|
||||||
|
getWindow().setStatusBarColor(darkMode ? Color.TRANSPARENT : Color.argb(127, 0, 0, 0));
|
||||||
|
}
|
||||||
|
// XXX android 9 and below has a nasty rendering bug if the theme was patched earlier
|
||||||
|
Utils.postPatchColors(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.journeyapps.barcodescanner.CaptureManager;
|
import com.journeyapps.barcodescanner.CaptureManager;
|
||||||
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
||||||
|
|
||||||
public class CatimaCaptureManager extends CaptureManager {
|
public class CatimaCaptureManager extends CaptureManager {
|
||||||
private Activity activity;
|
private final Context mContext;
|
||||||
|
|
||||||
public CatimaCaptureManager(Activity activity, DecoratedBarcodeView barcodeView) {
|
public CatimaCaptureManager(Activity activity, DecoratedBarcodeView barcodeView) {
|
||||||
super(activity, barcodeView);
|
super(activity, barcodeView);
|
||||||
|
|
||||||
this.activity = activity;
|
mContext = activity.getApplicationContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void displayFrameworkBugMessageAndExit(String message) {
|
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
|
// 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
|
// So we show a toast instead
|
||||||
Toast.makeText(activity, message, Toast.LENGTH_LONG).show();
|
Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import android.database.DatabaseUtils;
|
|||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteException;
|
import android.database.sqlite.SQLiteException;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.text.TextUtils;
|
||||||
import com.google.zxing.BarcodeFormat;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -18,21 +18,20 @@ import java.util.Currency;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class DBHelper extends SQLiteOpenHelper
|
import protect.card_locker.barcodes.Barcode;
|
||||||
{
|
|
||||||
|
public class DBHelper extends SQLiteOpenHelper {
|
||||||
public static final String DATABASE_NAME = "Catima.db";
|
public static final String DATABASE_NAME = "Catima.db";
|
||||||
public static final int ORIGINAL_DATABASE_VERSION = 1;
|
public static final int ORIGINAL_DATABASE_VERSION = 1;
|
||||||
public static final int DATABASE_VERSION = 10;
|
public static final int DATABASE_VERSION = 15;
|
||||||
|
|
||||||
public static class LoyaltyCardDbGroups
|
public static class LoyaltyCardDbGroups {
|
||||||
{
|
|
||||||
public static final String TABLE = "groups";
|
public static final String TABLE = "groups";
|
||||||
public static final String ID = "_id";
|
public static final String ID = "_id";
|
||||||
public static final String ORDER = "orderId";
|
public static final String ORDER = "orderId";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class LoyaltyCardDbIds
|
public static class LoyaltyCardDbIds {
|
||||||
{
|
|
||||||
public static final String TABLE = "cards";
|
public static final String TABLE = "cards";
|
||||||
public static final String ID = "_id";
|
public static final String ID = "_id";
|
||||||
public static final String STORE = "store";
|
public static final String STORE = "store";
|
||||||
@@ -46,35 +45,55 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
public static final String BARCODE_ID = "barcodeid";
|
public static final String BARCODE_ID = "barcodeid";
|
||||||
public static final String BARCODE_TYPE = "barcodetype";
|
public static final String BARCODE_TYPE = "barcodetype";
|
||||||
public static final String STAR_STATUS = "starstatus";
|
public static final String STAR_STATUS = "starstatus";
|
||||||
|
public static final String LAST_USED = "lastused";
|
||||||
|
public static final String ZOOM_LEVEL = "zoomlevel";
|
||||||
|
public static final String ARCHIVE_STATUS = "archive";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class LoyaltyCardDbIdsGroups
|
public static class LoyaltyCardDbIdsGroups {
|
||||||
{
|
|
||||||
public static final String TABLE = "cardsGroups";
|
public static final String TABLE = "cardsGroups";
|
||||||
public static final String cardID = "cardId";
|
public static final String cardID = "cardId";
|
||||||
public static final String groupID = "groupId";
|
public static final String groupID = "groupId";
|
||||||
}
|
}
|
||||||
|
|
||||||
private Context mContext;
|
public static class LoyaltyCardDbFTS {
|
||||||
|
public static final String TABLE = "fts";
|
||||||
|
public static final String ID = "rowid"; // This should NEVER be changed
|
||||||
|
public static final String STORE = "store";
|
||||||
|
public static final String NOTE = "note";
|
||||||
|
}
|
||||||
|
|
||||||
public DBHelper(Context context)
|
public enum LoyaltyCardOrder {
|
||||||
{
|
Alpha,
|
||||||
|
LastUsed,
|
||||||
|
Expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum LoyaltyCardOrderDirection {
|
||||||
|
Ascending,
|
||||||
|
Descending
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum LoyaltyCardArchiveFilter {
|
||||||
|
All,
|
||||||
|
Archived,
|
||||||
|
Unarchived
|
||||||
|
}
|
||||||
|
|
||||||
|
public DBHelper(Context context) {
|
||||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
|
|
||||||
mContext = context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(SQLiteDatabase db)
|
public void onCreate(SQLiteDatabase db) {
|
||||||
{
|
|
||||||
// create table for card groups
|
// create table for card groups
|
||||||
db.execSQL("create table " + LoyaltyCardDbGroups.TABLE + "(" +
|
db.execSQL("CREATE TABLE " + LoyaltyCardDbGroups.TABLE + "(" +
|
||||||
LoyaltyCardDbGroups.ID + " TEXT primary key not null," +
|
LoyaltyCardDbGroups.ID + " TEXT primary key not null," +
|
||||||
LoyaltyCardDbGroups.ORDER + " INTEGER DEFAULT '0')");
|
LoyaltyCardDbGroups.ORDER + " INTEGER DEFAULT '0')");
|
||||||
|
|
||||||
// create table for cards
|
// create table for cards
|
||||||
// Balance is TEXT and not REAL to be able to store a BigDecimal without precision loss
|
// Balance is TEXT and not REAL to be able to store a BigDecimal without precision loss
|
||||||
db.execSQL("create table " + LoyaltyCardDbIds.TABLE + "(" +
|
db.execSQL("CREATE TABLE " + LoyaltyCardDbIds.TABLE + "(" +
|
||||||
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
|
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
|
||||||
LoyaltyCardDbIds.STORE + " TEXT not null," +
|
LoyaltyCardDbIds.STORE + " TEXT not null," +
|
||||||
LoyaltyCardDbIds.NOTE + " TEXT not null," +
|
LoyaltyCardDbIds.NOTE + " TEXT not null," +
|
||||||
@@ -85,82 +104,75 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
|
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
|
||||||
LoyaltyCardDbIds.BARCODE_ID + " TEXT," +
|
LoyaltyCardDbIds.BARCODE_ID + " TEXT," +
|
||||||
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," +
|
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," +
|
||||||
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0')");
|
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'," +
|
||||||
|
LoyaltyCardDbIds.LAST_USED + " INTEGER DEFAULT '0', " +
|
||||||
|
LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '100', " +
|
||||||
|
LoyaltyCardDbIds.ARCHIVE_STATUS + " INTEGER DEFAULT '0' )");
|
||||||
|
|
||||||
// create associative table for cards in groups
|
// create associative table for cards in groups
|
||||||
db.execSQL("create table " + LoyaltyCardDbIdsGroups.TABLE + "(" +
|
db.execSQL("CREATE TABLE " + LoyaltyCardDbIdsGroups.TABLE + "(" +
|
||||||
LoyaltyCardDbIdsGroups.cardID + " INTEGER," +
|
LoyaltyCardDbIdsGroups.cardID + " INTEGER," +
|
||||||
LoyaltyCardDbIdsGroups.groupID + " TEXT," +
|
LoyaltyCardDbIdsGroups.groupID + " TEXT," +
|
||||||
"primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID +"))");
|
"primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID + "))");
|
||||||
|
|
||||||
|
// create FTS search table
|
||||||
|
db.execSQL("CREATE VIRTUAL TABLE " + LoyaltyCardDbFTS.TABLE + " USING fts4(" +
|
||||||
|
LoyaltyCardDbFTS.STORE + ", " + LoyaltyCardDbFTS.NOTE + ", " +
|
||||||
|
"tokenize=unicode61);");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
{
|
if (oldVersion < 2 && newVersion >= 2) {
|
||||||
// Upgrade from version 1 to version 2
|
|
||||||
if(oldVersion < 2 && newVersion >= 2)
|
|
||||||
{
|
|
||||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||||
+ " ADD COLUMN " + LoyaltyCardDbIds.NOTE + " TEXT not null default ''");
|
+ " ADD COLUMN " + LoyaltyCardDbIds.NOTE + " TEXT not null default ''");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade from version 2 to version 3
|
if (oldVersion < 3 && newVersion >= 3) {
|
||||||
if(oldVersion < 3 && newVersion >= 3)
|
|
||||||
{
|
|
||||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||||
+ " ADD COLUMN " + LoyaltyCardDbIds.HEADER_COLOR + " INTEGER");
|
+ " ADD COLUMN " + LoyaltyCardDbIds.HEADER_COLOR + " INTEGER");
|
||||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||||
+ " ADD COLUMN " + LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER");
|
+ " ADD COLUMN " + LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade from version 3 to version 4
|
if (oldVersion < 4 && newVersion >= 4) {
|
||||||
if(oldVersion < 4 && newVersion >= 4)
|
|
||||||
{
|
|
||||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||||
+ " ADD COLUMN " + LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'");
|
+ " ADD COLUMN " + LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade from version 4 to version 5
|
if (oldVersion < 5 && newVersion >= 5) {
|
||||||
if(oldVersion < 5 && newVersion >= 5)
|
db.execSQL("CREATE TABLE " + LoyaltyCardDbGroups.TABLE + "(" +
|
||||||
{
|
|
||||||
db.execSQL("create table " + LoyaltyCardDbGroups.TABLE + "(" +
|
|
||||||
LoyaltyCardDbGroups.ID + " TEXT primary key not null)");
|
LoyaltyCardDbGroups.ID + " TEXT primary key not null)");
|
||||||
|
|
||||||
db.execSQL("create table " + LoyaltyCardDbIdsGroups.TABLE + "(" +
|
db.execSQL("CREATE TABLE " + LoyaltyCardDbIdsGroups.TABLE + "(" +
|
||||||
LoyaltyCardDbIdsGroups.cardID + " INTEGER," +
|
LoyaltyCardDbIdsGroups.cardID + " INTEGER," +
|
||||||
LoyaltyCardDbIdsGroups.groupID + " TEXT," +
|
LoyaltyCardDbIdsGroups.groupID + " TEXT," +
|
||||||
"primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID +"))");
|
"primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID + "))");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade from version 5 to 6
|
if (oldVersion < 6 && newVersion >= 6) {
|
||||||
if(oldVersion < 6 && newVersion >= 6)
|
|
||||||
{
|
|
||||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbGroups.TABLE
|
db.execSQL("ALTER TABLE " + LoyaltyCardDbGroups.TABLE
|
||||||
+ " ADD COLUMN " + LoyaltyCardDbGroups.ORDER + " INTEGER DEFAULT '0'");
|
+ " ADD COLUMN " + LoyaltyCardDbGroups.ORDER + " INTEGER DEFAULT '0'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(oldVersion < 7 && newVersion >= 7)
|
if (oldVersion < 7 && newVersion >= 7) {
|
||||||
{
|
|
||||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||||
+ " ADD COLUMN " + LoyaltyCardDbIds.EXPIRY + " INTEGER");
|
+ " ADD COLUMN " + LoyaltyCardDbIds.EXPIRY + " INTEGER");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(oldVersion < 8 && newVersion >= 8)
|
if (oldVersion < 8 && newVersion >= 8) {
|
||||||
{
|
|
||||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||||
+ " ADD COLUMN " + LoyaltyCardDbIds.BALANCE + " TEXT not null DEFAULT '0'");
|
+ " ADD COLUMN " + LoyaltyCardDbIds.BALANCE + " TEXT not null DEFAULT '0'");
|
||||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||||
+ " ADD COLUMN " + LoyaltyCardDbIds.BALANCE_TYPE + " TEXT");
|
+ " ADD COLUMN " + LoyaltyCardDbIds.BALANCE_TYPE + " TEXT");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(oldVersion < 9 && newVersion >= 9)
|
if (oldVersion < 9 && newVersion >= 9) {
|
||||||
{
|
|
||||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||||
+ " ADD COLUMN " + LoyaltyCardDbIds.BARCODE_ID + " TEXT");
|
+ " ADD COLUMN " + LoyaltyCardDbIds.BARCODE_ID + " TEXT");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(oldVersion < 10 && newVersion >= 10)
|
if (oldVersion < 10 && newVersion >= 10) {
|
||||||
{
|
|
||||||
// SQLite doesn't support modify column
|
// SQLite doesn't support modify column
|
||||||
// So we need to create a temp column to make barcode type nullable
|
// So we need to create a temp column to make barcode type nullable
|
||||||
// Let's drop header text colour too while we're at it
|
// Let's drop header text colour too while we're at it
|
||||||
@@ -208,7 +220,7 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
|
|
||||||
db.execSQL("DROP TABLE " + LoyaltyCardDbIds.TABLE);
|
db.execSQL("DROP TABLE " + LoyaltyCardDbIds.TABLE);
|
||||||
|
|
||||||
db.execSQL("create table " + LoyaltyCardDbIds.TABLE + "(" +
|
db.execSQL("CREATE TABLE " + LoyaltyCardDbIds.TABLE + "(" +
|
||||||
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
|
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
|
||||||
LoyaltyCardDbIds.STORE + " TEXT not null," +
|
LoyaltyCardDbIds.STORE + " TEXT not null," +
|
||||||
LoyaltyCardDbIds.NOTE + " TEXT not null," +
|
LoyaltyCardDbIds.NOTE + " TEXT not null," +
|
||||||
@@ -252,15 +264,109 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 11 && newVersion >= 11) {
|
||||||
|
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||||
|
+ " ADD COLUMN " + LoyaltyCardDbIds.LAST_USED + " INTEGER DEFAULT '0'");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 12 && newVersion >= 12) {
|
||||||
|
db.execSQL("CREATE VIRTUAL TABLE " + LoyaltyCardDbFTS.TABLE + " USING fts4(" +
|
||||||
|
LoyaltyCardDbFTS.STORE + ", " + LoyaltyCardDbFTS.NOTE + ", " +
|
||||||
|
"tokenize=unicode61);");
|
||||||
|
|
||||||
|
Cursor cursor = db.rawQuery("SELECT * FROM " + LoyaltyCardDbIds.TABLE + ";", null, null);
|
||||||
|
|
||||||
|
cursor.moveToFirst();
|
||||||
|
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
int id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
|
||||||
|
String store = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE));
|
||||||
|
String note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE));
|
||||||
|
insertFTS(db, id, store, note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 13 && newVersion >= 13) {
|
||||||
|
db.execSQL("DELETE FROM " + LoyaltyCardDbFTS.TABLE + ";");
|
||||||
|
|
||||||
|
Cursor cursor = db.rawQuery("SELECT * FROM " + LoyaltyCardDbIds.TABLE + ";", null, null);
|
||||||
|
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
int id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
|
||||||
|
String store = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE));
|
||||||
|
String note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE));
|
||||||
|
insertFTS(db, id, store, note);
|
||||||
|
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
|
||||||
|
store = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE));
|
||||||
|
note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE));
|
||||||
|
insertFTS(db, id, store, note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 14 && newVersion >= 14) {
|
||||||
|
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||||
|
+ " ADD COLUMN " + LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '100' ");
|
||||||
|
}
|
||||||
|
if (oldVersion < 15 && newVersion >= 15) {
|
||||||
|
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||||
|
+ " ADD COLUMN " + LoyaltyCardDbIds.ARCHIVE_STATUS + " INTEGER DEFAULT '0' ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long insertLoyaltyCard(final String store, final String note, final Date expiry,
|
private static ContentValues generateFTSContentValues(final int id, final String store, final String note) {
|
||||||
final BigDecimal balance, final Currency balanceType,
|
// FTS on Android is severely limited and can only search for word starting with a certain string
|
||||||
final String cardId, final String barcodeId,
|
// So for each word, we grab every single substring
|
||||||
final BarcodeFormat barcodeType, final Integer headerColor,
|
// This makes it possible to find Décathlon by searching both de and cat, for example
|
||||||
final int starStatus)
|
|
||||||
{
|
ContentValues ftsContentValues = new ContentValues();
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
|
||||||
|
StringBuilder storeString = new StringBuilder();
|
||||||
|
for (String word : store.split(" ")) {
|
||||||
|
for (int i = 0; i < word.length(); i++) {
|
||||||
|
storeString.append(word);
|
||||||
|
storeString.append(" ");
|
||||||
|
word = word.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder noteString = new StringBuilder();
|
||||||
|
for (String word : note.split(" ")) {
|
||||||
|
for (int i = 0; i < word.length(); i++) {
|
||||||
|
noteString.append(word);
|
||||||
|
noteString.append(" ");
|
||||||
|
word = word.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ftsContentValues.put(LoyaltyCardDbFTS.ID, id);
|
||||||
|
ftsContentValues.put(LoyaltyCardDbFTS.STORE, storeString.toString());
|
||||||
|
ftsContentValues.put(LoyaltyCardDbFTS.NOTE, noteString.toString());
|
||||||
|
|
||||||
|
return ftsContentValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void insertFTS(final SQLiteDatabase db, final int id, final String store, final String note) {
|
||||||
|
db.insert(LoyaltyCardDbFTS.TABLE, null, generateFTSContentValues(id, store, note));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void updateFTS(final SQLiteDatabase db, final int id, final String store, final String note) {
|
||||||
|
db.update(LoyaltyCardDbFTS.TABLE, generateFTSContentValues(id, store, note),
|
||||||
|
whereAttrs(LoyaltyCardDbFTS.ID), withArgs(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long insertLoyaltyCard(
|
||||||
|
final SQLiteDatabase database, final String store, final String note, final Date expiry,
|
||||||
|
final BigDecimal balance, final Currency balanceType, final String cardId,
|
||||||
|
final String barcodeId, final Barcode barcodeType, final Integer headerColor,
|
||||||
|
final int starStatus, final Long lastUsed, final int archiveStatus) {
|
||||||
|
database.beginTransaction();
|
||||||
|
|
||||||
|
// Card
|
||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
contentValues.put(LoyaltyCardDbIds.STORE, store);
|
contentValues.put(LoyaltyCardDbIds.STORE, store);
|
||||||
contentValues.put(LoyaltyCardDbIds.NOTE, note);
|
contentValues.put(LoyaltyCardDbIds.NOTE, note);
|
||||||
@@ -269,38 +375,30 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
|
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
|
||||||
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
||||||
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
|
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
|
||||||
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.toString() : null);
|
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null);
|
||||||
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
||||||
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
|
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
|
||||||
return db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
|
contentValues.put(LoyaltyCardDbIds.LAST_USED, lastUsed != null ? lastUsed : Utils.getUnixTime());
|
||||||
|
contentValues.put(LoyaltyCardDbIds.ARCHIVE_STATUS, archiveStatus);
|
||||||
|
long id = database.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
|
||||||
|
|
||||||
|
// FTS
|
||||||
|
insertFTS(database, (int) id, store, note);
|
||||||
|
|
||||||
|
database.setTransactionSuccessful();
|
||||||
|
database.endTransaction();
|
||||||
|
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long insertLoyaltyCard(final SQLiteDatabase db, final String store,
|
public static long insertLoyaltyCard(
|
||||||
final String note, final Date expiry, final BigDecimal balance,
|
final SQLiteDatabase database, final int id, final String store, final String note,
|
||||||
final Currency balanceType, final String cardId,
|
final Date expiry, final BigDecimal balance, final Currency balanceType,
|
||||||
final String barcodeId, final BarcodeFormat barcodeType,
|
final String cardId, final String barcodeId, final Barcode barcodeType,
|
||||||
final Integer headerColor, final int starStatus)
|
final Integer headerColor, final int starStatus, final Long lastUsed, final int archiveStatus) {
|
||||||
{
|
database.beginTransaction();
|
||||||
ContentValues contentValues = new ContentValues();
|
|
||||||
contentValues.put(LoyaltyCardDbIds.STORE, store);
|
|
||||||
contentValues.put(LoyaltyCardDbIds.NOTE, note);
|
|
||||||
contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null);
|
|
||||||
contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString());
|
|
||||||
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
|
|
||||||
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
|
||||||
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
|
|
||||||
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.toString() : null);
|
|
||||||
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
|
||||||
contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus);
|
|
||||||
return db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long insertLoyaltyCard(final SQLiteDatabase db, final int id, final String store,
|
// Card
|
||||||
final String note, final Date expiry, final BigDecimal balance,
|
|
||||||
final Currency balanceType, final String cardId,
|
|
||||||
final String barcodeId, final BarcodeFormat barcodeType,
|
|
||||||
final Integer headerColor, final int starStatus)
|
|
||||||
{
|
|
||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
contentValues.put(LoyaltyCardDbIds.ID, id);
|
contentValues.put(LoyaltyCardDbIds.ID, id);
|
||||||
contentValues.put(LoyaltyCardDbIds.STORE, store);
|
contentValues.put(LoyaltyCardDbIds.STORE, store);
|
||||||
@@ -310,19 +408,30 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
|
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
|
||||||
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
||||||
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
|
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
|
||||||
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.toString() : null);
|
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null);
|
||||||
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
||||||
contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus);
|
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
|
||||||
return db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
|
contentValues.put(LoyaltyCardDbIds.LAST_USED, lastUsed != null ? lastUsed : Utils.getUnixTime());
|
||||||
|
contentValues.put(LoyaltyCardDbIds.ARCHIVE_STATUS, archiveStatus);
|
||||||
|
database.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
|
||||||
|
|
||||||
|
// FTS
|
||||||
|
insertFTS(database, id, store, note);
|
||||||
|
|
||||||
|
database.setTransactionSuccessful();
|
||||||
|
database.endTransaction();
|
||||||
|
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateLoyaltyCard(final int id, final String store, final String note,
|
public static boolean updateLoyaltyCard(
|
||||||
final Date expiry, final BigDecimal balance,
|
SQLiteDatabase database, final int id, final String store, final String note,
|
||||||
final Currency balanceType, final String cardId,
|
final Date expiry, final BigDecimal balance, final Currency balanceType,
|
||||||
final String barcodeId, final BarcodeFormat barcodeType,
|
final String cardId, final String barcodeId, final Barcode barcodeType,
|
||||||
final Integer headerColor)
|
final Integer headerColor, final int starStatus, final Long lastUsed, final int archiveStatus) {
|
||||||
{
|
database.beginTransaction();
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
|
||||||
|
// Card
|
||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
contentValues.put(LoyaltyCardDbIds.STORE, store);
|
contentValues.put(LoyaltyCardDbIds.STORE, store);
|
||||||
contentValues.put(LoyaltyCardDbIds.NOTE, note);
|
contentValues.put(LoyaltyCardDbIds.NOTE, note);
|
||||||
@@ -331,33 +440,68 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
|
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
|
||||||
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
||||||
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
|
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
|
||||||
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.toString() : null);
|
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null);
|
||||||
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
||||||
int rowsUpdated = db.update(LoyaltyCardDbIds.TABLE, contentValues,
|
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
|
||||||
|
contentValues.put(LoyaltyCardDbIds.LAST_USED, lastUsed != null ? lastUsed : Utils.getUnixTime());
|
||||||
|
contentValues.put(LoyaltyCardDbIds.ARCHIVE_STATUS, archiveStatus);
|
||||||
|
|
||||||
|
int rowsUpdated = database.update(LoyaltyCardDbIds.TABLE, contentValues,
|
||||||
whereAttrs(LoyaltyCardDbIds.ID), withArgs(id));
|
whereAttrs(LoyaltyCardDbIds.ID), withArgs(id));
|
||||||
|
|
||||||
|
// FTS
|
||||||
|
updateFTS(database, id, store, note);
|
||||||
|
|
||||||
|
database.setTransactionSuccessful();
|
||||||
|
database.endTransaction();
|
||||||
|
|
||||||
return (rowsUpdated == 1);
|
return (rowsUpdated == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateLoyaltyCardStarStatus(final int id, final int starStatus)
|
public static boolean updateLoyaltyCardArchiveStatus(SQLiteDatabase database, final int id, final int archiveStatus) {
|
||||||
{
|
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
|
||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus);
|
contentValues.put(LoyaltyCardDbIds.ARCHIVE_STATUS, archiveStatus);
|
||||||
int rowsUpdated = db.update(LoyaltyCardDbIds.TABLE, contentValues,
|
int rowsUpdated = database.update(LoyaltyCardDbIds.TABLE, contentValues,
|
||||||
whereAttrs(LoyaltyCardDbIds.ID),
|
whereAttrs(LoyaltyCardDbIds.ID),
|
||||||
withArgs(id));
|
withArgs(id));
|
||||||
return (rowsUpdated == 1);
|
return (rowsUpdated == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoyaltyCard getLoyaltyCard(final int id)
|
public static boolean updateLoyaltyCardStarStatus(SQLiteDatabase database, final int id, final int starStatus) {
|
||||||
{
|
ContentValues contentValues = new ContentValues();
|
||||||
SQLiteDatabase db = getReadableDatabase();
|
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
|
||||||
Cursor data = db.query(LoyaltyCardDbIds.TABLE, null, whereAttrs(LoyaltyCardDbIds.ID), withArgs(id), null, null, null);
|
int rowsUpdated = database.update(LoyaltyCardDbIds.TABLE, contentValues,
|
||||||
|
whereAttrs(LoyaltyCardDbIds.ID),
|
||||||
|
withArgs(id));
|
||||||
|
return (rowsUpdated == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean updateLoyaltyCardLastUsed(SQLiteDatabase database, final int id) {
|
||||||
|
ContentValues contentValues = new ContentValues();
|
||||||
|
contentValues.put(LoyaltyCardDbIds.LAST_USED, System.currentTimeMillis() / 1000);
|
||||||
|
int rowsUpdated = database.update(LoyaltyCardDbIds.TABLE, contentValues,
|
||||||
|
whereAttrs(LoyaltyCardDbIds.ID),
|
||||||
|
withArgs(id));
|
||||||
|
return (rowsUpdated == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean updateLoyaltyCardZoomLevel(SQLiteDatabase database, int loyaltyCardId, int zoomLevel) {
|
||||||
|
ContentValues contentValues = new ContentValues();
|
||||||
|
contentValues.put(LoyaltyCardDbIds.ZOOM_LEVEL, zoomLevel);
|
||||||
|
Log.d("updateLoyaltyCardZLevel", "Card Id = " + loyaltyCardId + " Zoom level= " + zoomLevel);
|
||||||
|
int rowsUpdated = database.update(LoyaltyCardDbIds.TABLE, contentValues,
|
||||||
|
whereAttrs(LoyaltyCardDbIds.ID),
|
||||||
|
withArgs(loyaltyCardId));
|
||||||
|
Log.d("updateLoyaltyCardZLevel", "Rows changed = " + rowsUpdated);
|
||||||
|
return (rowsUpdated == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LoyaltyCard getLoyaltyCard(SQLiteDatabase database, final int id) {
|
||||||
|
Cursor data = database.query(LoyaltyCardDbIds.TABLE, null, whereAttrs(LoyaltyCardDbIds.ID), withArgs(id), null, null, null);
|
||||||
|
|
||||||
LoyaltyCard card = null;
|
LoyaltyCard card = null;
|
||||||
|
|
||||||
if(data.getCount() == 1)
|
if (data.getCount() == 1) {
|
||||||
{
|
|
||||||
data.moveToFirst();
|
data.moveToFirst();
|
||||||
card = LoyaltyCard.toLoyaltyCard(data);
|
card = LoyaltyCard.toLoyaltyCard(data);
|
||||||
}
|
}
|
||||||
@@ -367,10 +511,8 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Group> getLoyaltyCardGroups(final int id)
|
public static List<Group> getLoyaltyCardGroups(SQLiteDatabase database, final int id) {
|
||||||
{
|
Cursor data = database.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE + " g " +
|
||||||
SQLiteDatabase db = getReadableDatabase();
|
|
||||||
Cursor data = db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE + " g " +
|
|
||||||
" LEFT JOIN " + LoyaltyCardDbIdsGroups.TABLE + " ig ON ig." + LoyaltyCardDbIdsGroups.groupID + " = g." + LoyaltyCardDbGroups.ID +
|
" LEFT JOIN " + LoyaltyCardDbIdsGroups.TABLE + " ig ON ig." + LoyaltyCardDbIdsGroups.groupID + " = g." + LoyaltyCardDbGroups.ID +
|
||||||
" where " + LoyaltyCardDbIdsGroups.cardID + "=?" +
|
" where " + LoyaltyCardDbIdsGroups.cardID + "=?" +
|
||||||
" ORDER BY " + LoyaltyCardDbIdsGroups.groupID, withArgs(id));
|
" ORDER BY " + LoyaltyCardDbIdsGroups.groupID, withArgs(id));
|
||||||
@@ -393,12 +535,9 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLoyaltyCardGroups(final int id, List<Group> groups)
|
public static void setLoyaltyCardGroups(SQLiteDatabase database, final int id, List<Group> groups) {
|
||||||
{
|
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
|
||||||
|
|
||||||
// First delete lookup table entries associated with this card
|
// First delete lookup table entries associated with this card
|
||||||
db.delete(LoyaltyCardDbIdsGroups.TABLE,
|
database.delete(LoyaltyCardDbIdsGroups.TABLE,
|
||||||
whereAttrs(LoyaltyCardDbIdsGroups.cardID),
|
whereAttrs(LoyaltyCardDbIdsGroups.cardID),
|
||||||
withArgs(id));
|
withArgs(id));
|
||||||
|
|
||||||
@@ -407,54 +546,67 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
contentValues.put(LoyaltyCardDbIdsGroups.cardID, id);
|
contentValues.put(LoyaltyCardDbIdsGroups.cardID, id);
|
||||||
contentValues.put(LoyaltyCardDbIdsGroups.groupID, group._id);
|
contentValues.put(LoyaltyCardDbIdsGroups.groupID, group._id);
|
||||||
db.insert(LoyaltyCardDbIdsGroups.TABLE, null, contentValues);
|
database.insert(LoyaltyCardDbIdsGroups.TABLE, null, contentValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLoyaltyCardGroups(final SQLiteDatabase db, final int id, List<Group> groups)
|
public static boolean deleteLoyaltyCard(SQLiteDatabase database, Context context, final int id) {
|
||||||
{
|
|
||||||
// First delete lookup table entries associated with this card
|
|
||||||
db.delete(LoyaltyCardDbIdsGroups.TABLE,
|
|
||||||
whereAttrs(LoyaltyCardDbIdsGroups.cardID),
|
|
||||||
withArgs(id));
|
|
||||||
|
|
||||||
// Then create entries for selected values
|
|
||||||
for (Group group : groups) {
|
|
||||||
ContentValues contentValues = new ContentValues();
|
|
||||||
contentValues.put(LoyaltyCardDbIdsGroups.cardID, id);
|
|
||||||
contentValues.put(LoyaltyCardDbIdsGroups.groupID, group._id);
|
|
||||||
db.insert(LoyaltyCardDbIdsGroups.TABLE, null, contentValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean deleteLoyaltyCard(final int id)
|
|
||||||
{
|
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
|
||||||
// Delete card
|
// Delete card
|
||||||
int rowsDeleted = db.delete(LoyaltyCardDbIds.TABLE,
|
int rowsDeleted = database.delete(LoyaltyCardDbIds.TABLE,
|
||||||
whereAttrs(LoyaltyCardDbIds.ID),
|
whereAttrs(LoyaltyCardDbIds.ID),
|
||||||
withArgs(id));
|
withArgs(id));
|
||||||
|
|
||||||
// And delete lookup table entries associated with this card
|
// And delete lookup table entries associated with this card
|
||||||
db.delete(LoyaltyCardDbIdsGroups.TABLE,
|
database.delete(LoyaltyCardDbIdsGroups.TABLE,
|
||||||
whereAttrs(LoyaltyCardDbIdsGroups.cardID),
|
whereAttrs(LoyaltyCardDbIdsGroups.cardID),
|
||||||
withArgs(id));
|
withArgs(id));
|
||||||
|
|
||||||
|
// Delete FTS table entries
|
||||||
|
database.delete(LoyaltyCardDbFTS.TABLE,
|
||||||
|
whereAttrs(LoyaltyCardDbFTS.ID),
|
||||||
|
withArgs(id));
|
||||||
|
|
||||||
// Also wipe card images associated with this card
|
// Also wipe card images associated with this card
|
||||||
try {
|
for (ImageLocationType imageLocationType : ImageLocationType.values()) {
|
||||||
Utils.saveCardImage(mContext, null, id, true);
|
try {
|
||||||
Utils.saveCardImage(mContext, null, id, false);
|
Utils.saveCardImage(context, null, id, imageLocationType);
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (rowsDeleted == 1);
|
return (rowsDeleted == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cursor getLoyaltyCardCursor()
|
public static int getArchivedCardsCount(SQLiteDatabase database) {
|
||||||
{
|
return (int) DatabaseUtils.queryNumEntries(database, LoyaltyCardDbIds.TABLE,
|
||||||
|
whereAttrs(LoyaltyCardDbIds.ARCHIVE_STATUS), withArgs(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getArchivedCardsCount(SQLiteDatabase database, final String groupName) {
|
||||||
|
Cursor data = database.rawQuery(
|
||||||
|
"select * from " + LoyaltyCardDbIds.TABLE + " c " +
|
||||||
|
" LEFT JOIN " + LoyaltyCardDbIdsGroups.TABLE + " cg " +
|
||||||
|
" ON c." + LoyaltyCardDbIds.ID + " = cg." + LoyaltyCardDbIdsGroups.cardID +
|
||||||
|
" where " + LoyaltyCardDbIds.ARCHIVE_STATUS + " = 1" +
|
||||||
|
" AND " + LoyaltyCardDbIdsGroups.groupID + "= ?",
|
||||||
|
withArgs(groupName)
|
||||||
|
);
|
||||||
|
|
||||||
|
int count = data.getCount();
|
||||||
|
|
||||||
|
data.close();
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database) {
|
||||||
// An empty string will match everything
|
// An empty string will match everything
|
||||||
return getLoyaltyCardCursor("");
|
return getLoyaltyCardCursor(database, LoyaltyCardArchiveFilter.All);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, LoyaltyCardArchiveFilter archiveFilter) {
|
||||||
|
// An empty string will match everything
|
||||||
|
return getLoyaltyCardCursor(database, "", archiveFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -463,9 +615,8 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
* @param filter
|
* @param filter
|
||||||
* @return Cursor
|
* @return Cursor
|
||||||
*/
|
*/
|
||||||
public Cursor getLoyaltyCardCursor(final String filter)
|
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, final String filter, LoyaltyCardArchiveFilter archiveFilter) {
|
||||||
{
|
return getLoyaltyCardCursor(database, filter, null, archiveFilter);
|
||||||
return getLoyaltyCardCursor(filter, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -475,24 +626,31 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
* @param group
|
* @param group
|
||||||
* @return Cursor
|
* @return Cursor
|
||||||
*/
|
*/
|
||||||
public Cursor getLoyaltyCardCursor(final String filter, Group group)
|
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, final String filter, Group group, LoyaltyCardArchiveFilter archiveFilter) {
|
||||||
{
|
return getLoyaltyCardCursor(database, filter, group, LoyaltyCardOrder.Alpha, LoyaltyCardOrderDirection.Ascending, archiveFilter);
|
||||||
String actualFilter = String.format("%%%s%%", filter);
|
}
|
||||||
String[] selectionArgs = { actualFilter, actualFilter };
|
|
||||||
|
/**
|
||||||
|
* Returns a cursor to all loyalty cards with the filter text in either the store or note in a certain group sorted as requested.
|
||||||
|
*
|
||||||
|
* @param filter
|
||||||
|
* @param group
|
||||||
|
* @param order
|
||||||
|
* @return Cursor
|
||||||
|
*/
|
||||||
|
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, String filter, Group group, LoyaltyCardOrder order, LoyaltyCardOrderDirection direction, LoyaltyCardArchiveFilter archiveFilter) {
|
||||||
StringBuilder groupFilter = new StringBuilder();
|
StringBuilder groupFilter = new StringBuilder();
|
||||||
String limitString = "";
|
String limitString = "";
|
||||||
|
|
||||||
SQLiteDatabase db = getReadableDatabase();
|
|
||||||
|
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
List<Integer> allowedIds = getGroupCardIds(group._id);
|
List<Integer> allowedIds = getGroupCardIds(database, group._id);
|
||||||
|
|
||||||
// Empty group
|
// Empty group
|
||||||
if (!allowedIds.isEmpty()) {
|
if (!allowedIds.isEmpty()) {
|
||||||
groupFilter.append("AND (");
|
groupFilter.append("AND (");
|
||||||
|
|
||||||
for (int i = 0; i < allowedIds.size(); i++) {
|
for (int i = 0; i < allowedIds.size(); i++) {
|
||||||
groupFilter.append(LoyaltyCardDbIds.ID + " = ").append(allowedIds.get(i));
|
groupFilter.append(LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.ID + " = ").append(allowedIds.get(i));
|
||||||
if (i != allowedIds.size() - 1) {
|
if (i != allowedIds.size() - 1) {
|
||||||
groupFilter.append(" OR ");
|
groupFilter.append(" OR ");
|
||||||
}
|
}
|
||||||
@@ -503,34 +661,34 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.rawQuery("select * from " + LoyaltyCardDbIds.TABLE +
|
String archiveFilterString = "";
|
||||||
" WHERE (" + LoyaltyCardDbIds.STORE + " LIKE ? " +
|
if (archiveFilter != LoyaltyCardArchiveFilter.All) {
|
||||||
" OR " + LoyaltyCardDbIds.NOTE + " LIKE ? )" +
|
archiveFilterString = " AND " + LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.ARCHIVE_STATUS + " = " + (archiveFilter.equals(LoyaltyCardArchiveFilter.Unarchived) ? 0 : 1);
|
||||||
groupFilter.toString() +
|
}
|
||||||
" ORDER BY " + LoyaltyCardDbIds.STAR_STATUS + " DESC," + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC " +
|
|
||||||
limitString, selectionArgs, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLoyaltyCardCount()
|
String orderField = getFieldForOrder(order);
|
||||||
{
|
|
||||||
SQLiteDatabase db = getReadableDatabase();
|
return database.rawQuery("SELECT " + LoyaltyCardDbIds.TABLE + ".* FROM " + LoyaltyCardDbIds.TABLE +
|
||||||
return (int) DatabaseUtils.queryNumEntries(db, LoyaltyCardDbIds.TABLE);
|
" JOIN " + LoyaltyCardDbFTS.TABLE +
|
||||||
|
" ON " + LoyaltyCardDbFTS.TABLE + "." + LoyaltyCardDbFTS.ID + " = " + LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.ID +
|
||||||
|
(filter.trim().isEmpty() ? " " : " AND " + LoyaltyCardDbFTS.TABLE + " MATCH ? ") +
|
||||||
|
groupFilter.toString() +
|
||||||
|
archiveFilterString +
|
||||||
|
" ORDER BY " + LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.ARCHIVE_STATUS + " ASC, " +
|
||||||
|
LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.STAR_STATUS + " DESC, " +
|
||||||
|
" (CASE WHEN " + LoyaltyCardDbIds.TABLE + "." + orderField + " IS NULL THEN 1 ELSE 0 END), " +
|
||||||
|
LoyaltyCardDbIds.TABLE + "." + orderField + " COLLATE NOCASE " + getDbDirection(order, direction) + ", " +
|
||||||
|
LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC " +
|
||||||
|
limitString, filter.trim().isEmpty() ? null : new String[]{TextUtils.join("* ", filter.split(" ")) + '*'}, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the amount of loyalty cards with the filter text in either the store or note.
|
* Returns the amount of loyalty cards.
|
||||||
*
|
*
|
||||||
* @param filter
|
|
||||||
* @return Integer
|
* @return Integer
|
||||||
*/
|
*/
|
||||||
public int getLoyaltyCardCount(String filter)
|
public static int getLoyaltyCardCount(SQLiteDatabase database) {
|
||||||
{
|
return (int) DatabaseUtils.queryNumEntries(database, LoyaltyCardDbIds.TABLE);
|
||||||
String actualFilter = String.format("%%%s%%", filter);
|
|
||||||
|
|
||||||
SQLiteDatabase db = getReadableDatabase();
|
|
||||||
return (int) DatabaseUtils.queryNumEntries(db, LoyaltyCardDbIds.TABLE,
|
|
||||||
LoyaltyCardDbIds.STORE + " LIKE ? " +
|
|
||||||
" OR " + LoyaltyCardDbIds.NOTE + " LIKE ? ", withArgs(actualFilter, actualFilter));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -538,42 +696,38 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
*
|
*
|
||||||
* @return Cursor
|
* @return Cursor
|
||||||
*/
|
*/
|
||||||
public Cursor getGroupCursor()
|
public static Cursor getGroupCursor(SQLiteDatabase database) {
|
||||||
{
|
return database.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE +
|
||||||
SQLiteDatabase db = getReadableDatabase();
|
|
||||||
|
|
||||||
return db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE +
|
|
||||||
" ORDER BY " + LoyaltyCardDbGroups.ORDER + " ASC," + LoyaltyCardDbGroups.ID + " COLLATE NOCASE ASC", null, null);
|
" ORDER BY " + LoyaltyCardDbGroups.ORDER + " ASC," + LoyaltyCardDbGroups.ID + " COLLATE NOCASE ASC", null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Group> getGroups() {
|
public static List<Group> getGroups(SQLiteDatabase database) {
|
||||||
try(Cursor data = getGroupCursor()) {
|
Cursor data = getGroupCursor(database);
|
||||||
List<Group> groups = new ArrayList<>();
|
|
||||||
|
|
||||||
if (!data.moveToFirst()) {
|
List<Group> groups = new ArrayList<>();
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
groups.add(Group.toGroup(data));
|
|
||||||
while (data.moveToNext()) {
|
|
||||||
groups.add(Group.toGroup(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!data.moveToFirst()) {
|
||||||
|
data.close();
|
||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
groups.add(Group.toGroup(data));
|
||||||
|
while (data.moveToNext()) {
|
||||||
|
groups.add(Group.toGroup(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
data.close();
|
||||||
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reorderGroups(final List<Group> groups)
|
public static void reorderGroups(SQLiteDatabase database, final List<Group> groups) {
|
||||||
{
|
|
||||||
Integer order = 0;
|
Integer order = 0;
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
|
||||||
|
|
||||||
for (Group group : groups)
|
for (Group group : groups) {
|
||||||
{
|
|
||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
contentValues.put(LoyaltyCardDbGroups.ORDER, order);
|
contentValues.put(LoyaltyCardDbGroups.ORDER, order);
|
||||||
|
|
||||||
db.update(LoyaltyCardDbGroups.TABLE, contentValues,
|
database.update(LoyaltyCardDbGroups.TABLE, contentValues,
|
||||||
whereAttrs(LoyaltyCardDbGroups.ID),
|
whereAttrs(LoyaltyCardDbGroups.ID),
|
||||||
withArgs(group._id));
|
withArgs(group._id));
|
||||||
|
|
||||||
@@ -581,15 +735,12 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Group getGroup(final String groupName)
|
public static Group getGroup(SQLiteDatabase database, final String groupName) {
|
||||||
{
|
Cursor data = database.query(LoyaltyCardDbGroups.TABLE, null,
|
||||||
SQLiteDatabase db = getReadableDatabase();
|
|
||||||
Cursor data = db.query(LoyaltyCardDbGroups.TABLE, null,
|
|
||||||
whereAttrs(LoyaltyCardDbGroups.ID), withArgs(groupName), null, null, null);
|
whereAttrs(LoyaltyCardDbGroups.ID), withArgs(groupName), null, null, null);
|
||||||
|
|
||||||
Group group = null;
|
Group group = null;
|
||||||
if(data.getCount() == 1)
|
if (data.getCount() == 1) {
|
||||||
{
|
|
||||||
data.moveToFirst();
|
data.moveToFirst();
|
||||||
group = Group.toGroup(data);
|
group = Group.toGroup(data);
|
||||||
}
|
}
|
||||||
@@ -598,16 +749,12 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getGroupCount()
|
public static int getGroupCount(SQLiteDatabase database) {
|
||||||
{
|
return (int) DatabaseUtils.queryNumEntries(database, LoyaltyCardDbGroups.TABLE);
|
||||||
SQLiteDatabase db = getReadableDatabase();
|
|
||||||
return (int) DatabaseUtils.queryNumEntries(db, LoyaltyCardDbGroups.TABLE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Integer> getGroupCardIds(final String groupName)
|
public static List<Integer> getGroupCardIds(SQLiteDatabase database, final String groupName) {
|
||||||
{
|
Cursor data = database.query(LoyaltyCardDbIdsGroups.TABLE, withArgs(LoyaltyCardDbIdsGroups.cardID),
|
||||||
SQLiteDatabase db = getReadableDatabase();
|
|
||||||
Cursor data = db.query(LoyaltyCardDbIdsGroups.TABLE, withArgs(LoyaltyCardDbIdsGroups.cardID),
|
|
||||||
whereAttrs(LoyaltyCardDbIdsGroups.groupID), withArgs(groupName), null, null, null);
|
whereAttrs(LoyaltyCardDbIdsGroups.groupID), withArgs(groupName), null, null, null);
|
||||||
List<Integer> cardIds = new ArrayList<>();
|
List<Integer> cardIds = new ArrayList<>();
|
||||||
|
|
||||||
@@ -626,104 +773,85 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
return cardIds;
|
return cardIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long insertGroup(final String name)
|
public static long insertGroup(SQLiteDatabase database, final String name) {
|
||||||
{
|
|
||||||
if (name.isEmpty()) return -1;
|
if (name.isEmpty()) return -1;
|
||||||
|
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
|
||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
contentValues.put(LoyaltyCardDbGroups.ID, name);
|
contentValues.put(LoyaltyCardDbGroups.ID, name);
|
||||||
contentValues.put(LoyaltyCardDbGroups.ORDER, getGroupCount());
|
contentValues.put(LoyaltyCardDbGroups.ORDER, getGroupCount(database));
|
||||||
return db.insert(LoyaltyCardDbGroups.TABLE, null, contentValues);
|
return database.insert(LoyaltyCardDbGroups.TABLE, null, contentValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean insertGroup(final SQLiteDatabase db, final String name)
|
public static boolean updateGroup(SQLiteDatabase database, final String groupName, final String newName) {
|
||||||
{
|
|
||||||
ContentValues contentValues = new ContentValues();
|
|
||||||
contentValues.put(LoyaltyCardDbGroups.ID, name);
|
|
||||||
contentValues.put(LoyaltyCardDbGroups.ORDER, getGroupCount());
|
|
||||||
final long newId = db.insert(LoyaltyCardDbGroups.TABLE, null, contentValues);
|
|
||||||
return newId != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean updateGroup(final String groupName, final String newName)
|
|
||||||
{
|
|
||||||
if (newName.isEmpty()) return false;
|
if (newName.isEmpty()) return false;
|
||||||
|
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
|
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
|
||||||
ContentValues groupContentValues = new ContentValues();
|
ContentValues groupContentValues = new ContentValues();
|
||||||
groupContentValues.put(LoyaltyCardDbGroups.ID, newName);
|
groupContentValues.put(LoyaltyCardDbGroups.ID, newName);
|
||||||
|
|
||||||
ContentValues lookupContentValues = new ContentValues();
|
ContentValues lookupContentValues = new ContentValues();
|
||||||
lookupContentValues.put(LoyaltyCardDbIdsGroups.groupID, newName);
|
lookupContentValues.put(LoyaltyCardDbIdsGroups.groupID, newName);
|
||||||
|
|
||||||
db.beginTransaction();
|
database.beginTransaction();
|
||||||
try {
|
try {
|
||||||
// Update group name
|
// Update group name
|
||||||
int groupsChanged = db.update(LoyaltyCardDbGroups.TABLE, groupContentValues,
|
int groupsChanged = database.update(LoyaltyCardDbGroups.TABLE, groupContentValues,
|
||||||
whereAttrs(LoyaltyCardDbGroups.ID),
|
whereAttrs(LoyaltyCardDbGroups.ID),
|
||||||
withArgs(groupName));
|
withArgs(groupName));
|
||||||
|
|
||||||
// Also update lookup tables
|
// Also update lookup tables
|
||||||
db.update(LoyaltyCardDbIdsGroups.TABLE, lookupContentValues,
|
database.update(LoyaltyCardDbIdsGroups.TABLE, lookupContentValues,
|
||||||
whereAttrs(LoyaltyCardDbIdsGroups.groupID),
|
whereAttrs(LoyaltyCardDbIdsGroups.groupID),
|
||||||
withArgs(groupName));
|
withArgs(groupName));
|
||||||
|
|
||||||
if (groupsChanged == 1) {
|
if (groupsChanged == 1) {
|
||||||
db.setTransactionSuccessful();
|
database.setTransactionSuccessful();
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
} catch (SQLiteException e) {
|
} catch (SQLiteException ignored) {
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction();
|
database.endTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean deleteGroup(final String groupName)
|
public static boolean deleteGroup(SQLiteDatabase database, final String groupName) {
|
||||||
{
|
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
|
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
database.beginTransaction();
|
||||||
|
|
||||||
db.beginTransaction();
|
|
||||||
try {
|
try {
|
||||||
// Delete group
|
// Delete group
|
||||||
int groupsDeleted = db.delete(LoyaltyCardDbGroups.TABLE,
|
int groupsDeleted = database.delete(LoyaltyCardDbGroups.TABLE,
|
||||||
whereAttrs(LoyaltyCardDbGroups.ID),
|
whereAttrs(LoyaltyCardDbGroups.ID),
|
||||||
withArgs(groupName));
|
withArgs(groupName));
|
||||||
|
|
||||||
// And delete lookup table entries associated with this group
|
// And delete lookup table entries associated with this group
|
||||||
db.delete(LoyaltyCardDbIdsGroups.TABLE,
|
database.delete(LoyaltyCardDbIdsGroups.TABLE,
|
||||||
whereAttrs(LoyaltyCardDbIdsGroups.groupID),
|
whereAttrs(LoyaltyCardDbIdsGroups.groupID),
|
||||||
withArgs(groupName));
|
withArgs(groupName));
|
||||||
|
|
||||||
if (groupsDeleted == 1) {
|
if (groupsDeleted == 1) {
|
||||||
db.setTransactionSuccessful();
|
database.setTransactionSuccessful();
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction();
|
database.endTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reorder after delete to ensure no bad order IDs
|
// Reorder after delete to ensure no bad order IDs
|
||||||
reorderGroups(getGroups());
|
reorderGroups(database, getGroups(database));
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getGroupCardCount(final String groupName)
|
public static int getGroupCardCount(SQLiteDatabase database, final String groupName) {
|
||||||
{
|
return (int) DatabaseUtils.queryNumEntries(database, LoyaltyCardDbIdsGroups.TABLE,
|
||||||
SQLiteDatabase db = getReadableDatabase();
|
|
||||||
|
|
||||||
return (int) DatabaseUtils.queryNumEntries(db, LoyaltyCardDbIdsGroups.TABLE,
|
|
||||||
whereAttrs(LoyaltyCardDbIdsGroups.groupID), withArgs(groupName));
|
whereAttrs(LoyaltyCardDbIdsGroups.groupID), withArgs(groupName));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String whereAttrs(String... attrs) {
|
static private String whereAttrs(String... attrs) {
|
||||||
if (attrs.length == 0) {
|
if (attrs.length == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -734,9 +862,34 @@ public class DBHelper extends SQLiteOpenHelper
|
|||||||
return whereClause.toString();
|
return whereClause.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] withArgs(Object... object) {
|
static private String[] withArgs(Object... object) {
|
||||||
return Arrays.stream(object)
|
return Arrays.stream(object)
|
||||||
.map(String::valueOf)
|
.map(String::valueOf)
|
||||||
.toArray(String[]::new);
|
.toArray(String[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getFieldForOrder(LoyaltyCardOrder order) {
|
||||||
|
if (order == LoyaltyCardOrder.Alpha) {
|
||||||
|
return LoyaltyCardDbIds.STORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order == LoyaltyCardOrder.LastUsed) {
|
||||||
|
return LoyaltyCardDbIds.LAST_USED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order == LoyaltyCardOrder.Expiry) {
|
||||||
|
return LoyaltyCardDbIds.EXPIRY;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Unknown order " + order);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getDbDirection(LoyaltyCardOrder order, LoyaltyCardOrderDirection direction) {
|
||||||
|
if (order == LoyaltyCardOrder.LastUsed) {
|
||||||
|
// We want the default sorting to put the most recently used first
|
||||||
|
return direction == LoyaltyCardOrderDirection.Descending ? "ASC" : "DESC";
|
||||||
|
}
|
||||||
|
|
||||||
|
return direction == LoyaltyCardOrderDirection.Ascending ? "ASC" : "DESC";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,8 +2,9 @@ package protect.card_locker;
|
|||||||
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
|
||||||
public class Group
|
import androidx.annotation.Nullable;
|
||||||
{
|
|
||||||
|
public class Group {
|
||||||
public final String _id;
|
public final String _id;
|
||||||
public final int order;
|
public final int order;
|
||||||
|
|
||||||
@@ -12,11 +13,28 @@ public class Group
|
|||||||
this.order = order;
|
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));
|
int order = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbGroups.ORDER));
|
||||||
|
|
||||||
return new Group(_id, order);
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
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.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;
|
||||||
@@ -10,79 +12,74 @@ import android.widget.TextView;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.AppCompatImageButton;
|
import androidx.appcompat.widget.AppCompatImageButton;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import protect.card_locker.preferences.Settings;
|
import protect.card_locker.preferences.Settings;
|
||||||
|
|
||||||
class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.GroupListItemViewHolder>
|
public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.GroupListItemViewHolder> {
|
||||||
{
|
|
||||||
Settings mSettings;
|
Settings mSettings;
|
||||||
private Cursor mCursor;
|
public final Context mContext;
|
||||||
private final Context mContext;
|
private final GroupAdapterListener mListener;
|
||||||
private final GroupCursorAdapter.GroupAdapterListener mListener;
|
SQLiteDatabase mDatabase;
|
||||||
DBHelper mDb;
|
|
||||||
|
|
||||||
public GroupCursorAdapter(Context inputContext, Cursor inputCursor, GroupCursorAdapter.GroupAdapterListener inputListener) {
|
public GroupCursorAdapter(Context inputContext, Cursor inputCursor, GroupAdapterListener inputListener) {
|
||||||
super(inputCursor);
|
super(inputCursor, DBHelper.LoyaltyCardDbGroups.ORDER);
|
||||||
setHasStableIds(true);
|
setHasStableIds(true);
|
||||||
mSettings = new Settings(inputContext);
|
mSettings = new Settings(inputContext);
|
||||||
mContext = inputContext;
|
mContext = inputContext;
|
||||||
mListener = inputListener;
|
mListener = inputListener;
|
||||||
mDb = new DBHelper(inputContext);
|
mDatabase = new DBHelper(inputContext).getReadableDatabase();
|
||||||
|
|
||||||
swapCursor(mCursor);
|
swapCursor(inputCursor);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void swapCursor(Cursor inputCursor) {
|
|
||||||
super.swapCursor(inputCursor);
|
|
||||||
mCursor = inputCursor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public GroupCursorAdapter.GroupListItemViewHolder onCreateViewHolder(ViewGroup inputParent, int inputViewType)
|
public GroupCursorAdapter.GroupListItemViewHolder onCreateViewHolder(ViewGroup inputParent, int inputViewType) {
|
||||||
{
|
|
||||||
View itemView = LayoutInflater.from(inputParent.getContext()).inflate(R.layout.group_layout, inputParent, false);
|
View itemView = LayoutInflater.from(inputParent.getContext()).inflate(R.layout.group_layout, inputParent, false);
|
||||||
return new GroupCursorAdapter.GroupListItemViewHolder(itemView);
|
return new GroupListItemViewHolder(itemView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cursor getCursor()
|
public void onBindViewHolder(GroupListItemViewHolder inputHolder, Cursor inputCursor) {
|
||||||
{
|
|
||||||
return mCursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onBindViewHolder(GroupCursorAdapter.GroupListItemViewHolder inputHolder, Cursor inputCursor) {
|
|
||||||
Group group = Group.toGroup(inputCursor);
|
Group group = Group.toGroup(inputCursor);
|
||||||
|
|
||||||
inputHolder.mName.setText(group._id);
|
inputHolder.mName.setText(group._id);
|
||||||
|
|
||||||
int groupCardCount = mDb.getGroupCardCount(group._id);
|
int groupCardCount = DBHelper.getGroupCardCount(mDatabase, group._id);
|
||||||
inputHolder.mCardCount.setText(mContext.getResources().getQuantityString(R.plurals.groupCardCount, groupCardCount, groupCardCount));
|
int archivedCardCount = DBHelper.getArchivedCardsCount(mDatabase, group._id);
|
||||||
|
|
||||||
|
Resources resources = mContext.getResources();
|
||||||
|
|
||||||
|
String cardCountText;
|
||||||
|
if (archivedCardCount > 0) {
|
||||||
|
cardCountText = resources.getQuantityString(R.plurals.groupCardCountWithArchived, groupCardCount, groupCardCount, archivedCardCount);
|
||||||
|
} else {
|
||||||
|
cardCountText = resources.getQuantityString(R.plurals.groupCardCount, groupCardCount, groupCardCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputHolder.mCardCount.setText(cardCountText);
|
||||||
inputHolder.mName.setTextSize(mSettings.getFontSizeMax(mSettings.getMediumFont()));
|
inputHolder.mName.setTextSize(mSettings.getFontSizeMax(mSettings.getMediumFont()));
|
||||||
inputHolder.mCardCount.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
|
inputHolder.mCardCount.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
|
||||||
|
|
||||||
applyClickEvents(inputHolder);
|
applyClickEvents(inputHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyClickEvents(GroupListItemViewHolder inputHolder)
|
private void applyClickEvents(GroupListItemViewHolder inputHolder) {
|
||||||
{
|
|
||||||
inputHolder.mMoveDown.setOnClickListener(view -> mListener.onMoveDownButtonClicked(inputHolder.itemView));
|
inputHolder.mMoveDown.setOnClickListener(view -> mListener.onMoveDownButtonClicked(inputHolder.itemView));
|
||||||
inputHolder.mMoveUp.setOnClickListener(view -> mListener.onMoveUpButtonClicked(inputHolder.itemView));
|
inputHolder.mMoveUp.setOnClickListener(view -> mListener.onMoveUpButtonClicked(inputHolder.itemView));
|
||||||
inputHolder.mEdit.setOnClickListener(view -> mListener.onEditButtonClicked(inputHolder.itemView));
|
inputHolder.mEdit.setOnClickListener(view -> mListener.onEditButtonClicked(inputHolder.itemView));
|
||||||
inputHolder.mDelete.setOnClickListener(view -> mListener.onDeleteButtonClicked(inputHolder.itemView));
|
inputHolder.mDelete.setOnClickListener(view -> mListener.onDeleteButtonClicked(inputHolder.itemView));
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface GroupAdapterListener
|
public interface GroupAdapterListener {
|
||||||
{
|
|
||||||
void onMoveDownButtonClicked(View view);
|
void onMoveDownButtonClicked(View view);
|
||||||
|
|
||||||
void onMoveUpButtonClicked(View view);
|
void onMoveUpButtonClicked(View view);
|
||||||
|
|
||||||
void onEditButtonClicked(View view);
|
void onEditButtonClicked(View view);
|
||||||
|
|
||||||
void onDeleteButtonClicked(View view);
|
void onDeleteButtonClicked(View view);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GroupListItemViewHolder extends RecyclerView.ViewHolder
|
public static class GroupListItemViewHolder extends RecyclerView.ViewHolder {
|
||||||
{
|
|
||||||
public TextView mName, mCardCount;
|
public TextView mName, mCardCount;
|
||||||
public AppCompatImageButton mMoveUp, mMoveDown, mEdit, mDelete;
|
public AppCompatImageButton mMoveUp, mMoveDown, mEdit, mDelete;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
public enum ImageLocationType {
|
||||||
|
front,
|
||||||
|
back,
|
||||||
|
icon
|
||||||
|
}
|
||||||
@@ -2,64 +2,65 @@ package protect.card_locker;
|
|||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
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 androidx.appcompat.app.ActionBar;
|
import java.io.IOException;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
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.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.content.ContextCompat;
|
||||||
|
import protect.card_locker.async.TaskHandler;
|
||||||
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.ImportExportResultType;
|
||||||
|
|
||||||
public class ImportExportActivity extends CatimaAppCompatActivity
|
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();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState)
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
{
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setTitle(R.string.importExport);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,15 +69,57 @@ public class ImportExportActivity extends CatimaAppCompatActivity
|
|||||||
|
|
||||||
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.toCharArray(), true);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to export file: " + result.toString(), e);
|
||||||
|
onExportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, result.toString()), 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);
|
||||||
@@ -84,45 +127,57 @@ public class ImportExportActivity extends CatimaAppCompatActivity
|
|||||||
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(new ImportExportResult(ImportExportResultType.GenericFailure, e.toString()), 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");
|
||||||
@@ -178,47 +233,67 @@ public class ImportExportActivity extends CatimaAppCompatActivity
|
|||||||
.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 startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password, final boolean closeWhenDone) {
|
||||||
{
|
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
|
||||||
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
|
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
|
||||||
{
|
|
||||||
@Override
|
@Override
|
||||||
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat)
|
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
|
||||||
{
|
|
||||||
onImportComplete(result, targetUri, dataFormat);
|
onImportComplete(result, targetUri, dataFormat);
|
||||||
|
if (closeWhenDone) {
|
||||||
|
try {
|
||||||
|
target.close();
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
ioException.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
importExporter = new ImportExportTask(ImportExportActivity.this,
|
importExporter = new ImportExportTask(ImportExportActivity.this,
|
||||||
dataFormat, target, password, listener);
|
dataFormat, target, password, listener);
|
||||||
importExporter.execute();
|
mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startExport(final OutputStream target, final Uri targetUri)
|
private void startExport(final OutputStream target, final Uri targetUri, char[] password, final boolean closeWhenDone) {
|
||||||
{
|
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
|
||||||
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
|
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
|
||||||
{
|
|
||||||
@Override
|
@Override
|
||||||
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat)
|
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
|
||||||
{
|
|
||||||
onExportComplete(result, targetUri);
|
onExportComplete(result, targetUri);
|
||||||
|
if (closeWhenDone) {
|
||||||
|
try {
|
||||||
|
target.close();
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
ioException.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
importExporter = new ImportExportTask(ImportExportActivity.this,
|
importExporter = new ImportExportTask(ImportExportActivity.this,
|
||||||
DataFormat.Catima, target, listener);
|
DataFormat.Catima, target, password, listener);
|
||||||
importExporter.execute();
|
mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -246,22 +321,17 @@ public class ImportExportActivity extends CatimaAppCompatActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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;
|
||||||
}
|
}
|
||||||
@@ -278,73 +348,58 @@ public class ImportExportActivity extends CatimaAppCompatActivity
|
|||||||
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());
|
||||||
|
|
||||||
builder.show();
|
builder.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String buildResultDialogMessage(ImportExportResult result, boolean isImport) {
|
||||||
|
int messageId;
|
||||||
|
|
||||||
|
if (result.resultType() == ImportExportResultType.Success) {
|
||||||
|
messageId = isImport ? R.string.importSuccessful : R.string.exportSuccessful;
|
||||||
|
} else {
|
||||||
|
messageId = isImport ? R.string.importFailed : R.string.exportFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder messageBuilder = new StringBuilder(getResources().getString(messageId));
|
||||||
|
if (result.developerDetails() != null) {
|
||||||
|
messageBuilder.append("\n\n");
|
||||||
|
messageBuilder.append(getResources().getString(R.string.include_if_asking_support));
|
||||||
|
messageBuilder.append("\n\n");
|
||||||
|
messageBuilder.append(result.developerDetails());
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private void onImportComplete(ImportExportResult result, Uri path, DataFormat dataFormat) {
|
private void onImportComplete(ImportExportResult result, Uri path, DataFormat dataFormat) {
|
||||||
if (result == ImportExportResult.BadPassword) {
|
ImportExportResultType resultType = result.resultType();
|
||||||
|
|
||||||
|
if (resultType == ImportExportResultType.BadPassword) {
|
||||||
retryWithPassword(dataFormat, path);
|
retryWithPassword(dataFormat, path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.importSuccessfulTitle : R.string.importFailedTitle);
|
||||||
int messageId;
|
builder.setMessage(buildResultDialogMessage(result, true));
|
||||||
|
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
|
||||||
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();
|
builder.create().show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onExportComplete(ImportExportResult result, final Uri path)
|
private void onExportComplete(ImportExportResult result, final Uri path) {
|
||||||
{
|
ImportExportResultType resultType = result.resultType();
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.exportSuccessfulTitle : R.string.exportFailedTitle);
|
||||||
int messageId;
|
builder.setMessage(buildResultDialogMessage(result, false));
|
||||||
|
|
||||||
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());
|
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
|
||||||
|
|
||||||
if(result == ImportExportResult.Success)
|
if (resultType == ImportExportResultType.Success) {
|
||||||
{
|
|
||||||
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel);
|
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel);
|
||||||
|
|
||||||
builder.setPositiveButton(sendLabel, (dialog, which) -> {
|
builder.setPositiveButton(sendLabel, (dialog, which) -> {
|
||||||
@@ -364,92 +419,4 @@ public class ImportExportActivity extends CatimaAppCompatActivity
|
|||||||
|
|
||||||
builder.create().show();
|
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) {
|
|
||||||
if (resultCode != RESULT_OK)
|
|
||||||
{
|
|
||||||
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(uri == null)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import android.app.Activity;
|
|||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
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;
|
||||||
@@ -13,13 +13,14 @@ import java.io.OutputStream;
|
|||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
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.ImportExportResultType;
|
||||||
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 Activity activity;
|
||||||
@@ -35,14 +36,14 @@ class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
|
|||||||
/**
|
/**
|
||||||
* 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 +51,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 +61,23 @@ 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 importResult = 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: " + importResult);
|
||||||
|
|
||||||
return importResult;
|
return importResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImportExportResult performExport(Context context, OutputStream stream, DBHelper db)
|
private ImportExportResult performExport(Context context, OutputStream stream, SQLiteDatabase database, char[] password) {
|
||||||
{
|
ImportExportResult result;
|
||||||
ImportExportResult result = ImportExportResult.GenericFailure;
|
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
|
OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
|
||||||
result = MultiFormatExporter.exportData(context, db, stream, format);
|
result = MultiFormatExporter.exportData(context, database, stream, format, password);
|
||||||
writer.close();
|
writer.close();
|
||||||
}
|
} catch (IOException e) {
|
||||||
catch (IOException e)
|
result = new ImportExportResult(ImportExportResultType.GenericFailure, e.toString());
|
||||||
{
|
|
||||||
Log.e(TAG, "Unable to export file", e);
|
Log.e(TAG, "Unable to export file", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,55 +86,57 @@ class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onPreExecute()
|
public void onPreExecute() {
|
||||||
{
|
|
||||||
progress = new ProgressDialog(activity);
|
progress = new ProgressDialog(activity);
|
||||||
progress.setTitle(doImport ? R.string.importing : R.string.exporting);
|
progress.setTitle(doImport ? R.string.importing : R.string.exporting);
|
||||||
|
|
||||||
progress.setOnDismissListener(new DialogInterface.OnDismissListener()
|
progress.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||||
{
|
|
||||||
@Override
|
@Override
|
||||||
public void onDismiss(DialogInterface dialog)
|
public void onDismiss(DialogInterface dialog) {
|
||||||
{
|
ImportExportTask.this.stop();
|
||||||
ImportExportTask.this.cancel(true);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
progress.show();
|
progress.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ImportExportResult doInBackground(Void... nothing)
|
protected ImportExportResult doInBackground(Void... nothing) {
|
||||||
{
|
final SQLiteDatabase database = new DBHelper(activity).getWritableDatabase();
|
||||||
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) {
|
||||||
{
|
listener.onTaskComplete((ImportExportResult) castResult, format);
|
||||||
listener.onTaskComplete(result, format);
|
|
||||||
|
|
||||||
progress.dismiss();
|
progress.dismiss();
|
||||||
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
|
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onCancelled()
|
protected void onCancelled() {
|
||||||
{
|
|
||||||
progress.dismiss();
|
progress.dismiss();
|
||||||
Log.i(TAG, (doImport ? "Import" : "Export") + " Cancelled");
|
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;
|
||||||
@@ -17,6 +15,9 @@ import java.util.Date;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import protect.card_locker.barcodes.Barcode;
|
||||||
|
import protect.card_locker.barcodes.BarcodeFactory;
|
||||||
|
|
||||||
public class ImportURIHelper {
|
public class ImportURIHelper {
|
||||||
private static final String STORE = DBHelper.LoyaltyCardDbIds.STORE;
|
private static final String STORE = DBHelper.LoyaltyCardDbIds.STORE;
|
||||||
private static final String NOTE = DBHelper.LoyaltyCardDbIds.NOTE;
|
private static final String NOTE = DBHelper.LoyaltyCardDbIds.NOTE;
|
||||||
@@ -35,7 +36,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 +58,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;
|
Barcode barcodeType = null;
|
||||||
Date expiry = null;
|
Date expiry = null;
|
||||||
BigDecimal balance = new BigDecimal("0");
|
BigDecimal balance = new BigDecimal("0");
|
||||||
Currency balanceType = null;
|
Currency balanceType = null;
|
||||||
@@ -92,37 +93,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 = BarcodeFactory.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,0);
|
||||||
} 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");
|
||||||
}
|
}
|
||||||
@@ -159,14 +156,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 +200,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import android.graphics.Paint;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.text.TextPaint;
|
import android.text.TextPaint;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.core.graphics.PaintCompat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Original from https://github.com/andOTP/andOTP/blob/master/app/src/main/java/org/shadowice/flocke/andotp/Utilities/LetterBitmap.java
|
* Original from https://github.com/andOTP/andOTP/blob/master/app/src/main/java/org/shadowice/flocke/andotp/Utilities/LetterBitmap.java
|
||||||
@@ -18,8 +21,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,59 +39,62 @@ 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));
|
|
||||||
|
|
||||||
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);
|
||||||
|
paint.setTextSize(tileLetterFontSize);
|
||||||
|
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
|
||||||
|
|
||||||
if(backgroundColor == null)
|
if (backgroundColor == null) {
|
||||||
{
|
|
||||||
mColor = getDefaultColor(context, key);
|
mColor = getDefaultColor(context, key);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
mColor = backgroundColor;
|
mColor = backgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||||
String firstChar = displayName.substring(0, 1);
|
String firstChar = displayName.substring(0, 1).toUpperCase();
|
||||||
|
int firstCharEnd = 2;
|
||||||
|
while (firstCharEnd <= displayName.length()) {
|
||||||
|
// Test for the longest render-able string
|
||||||
|
// But ignore containing only a-Z0-9 to not render things like ffi as a single character
|
||||||
|
String test = displayName.substring(0, firstCharEnd);
|
||||||
|
if (!isAlphabetical(test) && PaintCompat.hasGlyph(paint, test)) {
|
||||||
|
firstChar = test;
|
||||||
|
}
|
||||||
|
firstCharEnd++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("LetterBitmap", "using sequence " + firstChar + " to render first char which has length " + firstChar.length());
|
||||||
|
|
||||||
final Canvas c = new Canvas();
|
final Canvas c = new Canvas();
|
||||||
c.setBitmap(mBitmap);
|
c.setBitmap(mBitmap);
|
||||||
c.drawColor(mColor);
|
c.drawColor(mColor);
|
||||||
|
|
||||||
char [] firstCharArray = new char[1];
|
|
||||||
firstCharArray[0] = firstChar.toUpperCase().charAt(0);
|
|
||||||
paint.setTextSize(tileLetterFontSize);
|
|
||||||
|
|
||||||
// The bounds that enclose the letter
|
|
||||||
Rect bounds = new Rect();
|
Rect bounds = new Rect();
|
||||||
|
paint.getTextBounds(firstChar, 0, firstChar.length(), bounds);
|
||||||
|
c.drawText(firstChar,
|
||||||
|
0, firstChar.length(),
|
||||||
|
width / 2.0f, (height - (bounds.bottom + bounds.top)) / 2.0f
|
||||||
|
, paint);
|
||||||
|
|
||||||
paint.getTextBounds(firstCharArray, 0, 1, bounds);
|
|
||||||
c.drawText(firstCharArray, 0, 1, width / 2.0f, height / 2.0f
|
|
||||||
+ (bounds.bottom - bounds.top) / 2.0f, paint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,16 +102,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,20 +118,22 @@ 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;
|
||||||
return colors.getColor(color, Color.BLACK);
|
return colors.getColor(color, Color.BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isAlphabetical(String string) {
|
||||||
|
return string.matches("[a-zA-Z0-9]*");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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);
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import android.database.Cursor;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Currency;
|
import java.util.Currency;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import protect.card_locker.barcodes.Barcode;
|
||||||
|
import protect.card_locker.barcodes.BarcodeFactory;
|
||||||
|
|
||||||
public class LoyaltyCard implements Parcelable {
|
public class LoyaltyCard implements Parcelable {
|
||||||
public final int id;
|
public final int id;
|
||||||
public final String store;
|
public final String store;
|
||||||
@@ -25,18 +25,21 @@ public class LoyaltyCard implements Parcelable {
|
|||||||
public final String barcodeId;
|
public final String barcodeId;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public final BarcodeFormat barcodeType;
|
public final Barcode barcodeType;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public final Integer headerColor;
|
public final Integer headerColor;
|
||||||
|
|
||||||
public final int starStatus;
|
public final int starStatus;
|
||||||
|
public final int archiveStatus;
|
||||||
|
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,
|
||||||
@Nullable final String barcodeId, @Nullable final BarcodeFormat barcodeType, @Nullable final Integer headerColor,
|
@Nullable final String barcodeId, @Nullable final Barcode barcodeType,
|
||||||
final int starStatus)
|
@Nullable final Integer headerColor, final int starStatus,
|
||||||
{
|
final long lastUsed, final int zoomLevel, final int archiveStatus) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.note = note;
|
this.note = note;
|
||||||
@@ -48,6 +51,9 @@ public class LoyaltyCard implements Parcelable {
|
|||||||
this.barcodeType = barcodeType;
|
this.barcodeType = barcodeType;
|
||||||
this.headerColor = headerColor;
|
this.headerColor = headerColor;
|
||||||
this.starStatus = starStatus;
|
this.starStatus = starStatus;
|
||||||
|
this.lastUsed = lastUsed;
|
||||||
|
this.zoomLevel = zoomLevel;
|
||||||
|
this.archiveStatus = archiveStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected LoyaltyCard(Parcel in) {
|
protected LoyaltyCard(Parcel in) {
|
||||||
@@ -61,10 +67,13 @@ public class LoyaltyCard implements Parcelable {
|
|||||||
cardId = in.readString();
|
cardId = in.readString();
|
||||||
barcodeId = in.readString();
|
barcodeId = in.readString();
|
||||||
String tmpBarcodeType = in.readString();
|
String tmpBarcodeType = in.readString();
|
||||||
barcodeType = !tmpBarcodeType.isEmpty() ? BarcodeFormat.valueOf(tmpBarcodeType) : null;
|
barcodeType = !tmpBarcodeType.isEmpty() ? BarcodeFactory.fromName(tmpBarcodeType) : null;
|
||||||
int tmpHeaderColor = in.readInt();
|
int tmpHeaderColor = in.readInt();
|
||||||
headerColor = tmpHeaderColor != -1 ? tmpHeaderColor : null;
|
headerColor = tmpHeaderColor != -1 ? tmpHeaderColor : null;
|
||||||
starStatus = in.readInt();
|
starStatus = in.readInt();
|
||||||
|
lastUsed = in.readLong();
|
||||||
|
zoomLevel = in.readInt();
|
||||||
|
archiveStatus = in.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -77,13 +86,15 @@ public class LoyaltyCard implements Parcelable {
|
|||||||
parcel.writeValue(balanceType);
|
parcel.writeValue(balanceType);
|
||||||
parcel.writeString(cardId);
|
parcel.writeString(cardId);
|
||||||
parcel.writeString(barcodeId);
|
parcel.writeString(barcodeId);
|
||||||
parcel.writeString(barcodeType != null ? barcodeType.toString() : "");
|
parcel.writeString(barcodeType != null ? barcodeType.name() : "");
|
||||||
parcel.writeInt(headerColor != null ? headerColor : -1);
|
parcel.writeInt(headerColor != null ? headerColor : -1);
|
||||||
parcel.writeInt(starStatus);
|
parcel.writeInt(starStatus);
|
||||||
|
parcel.writeLong(lastUsed);
|
||||||
|
parcel.writeInt(zoomLevel);
|
||||||
|
parcel.writeInt(archiveStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoyaltyCard toLoyaltyCard(Cursor cursor)
|
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));
|
||||||
@@ -92,37 +103,36 @@ public class LoyaltyCard implements Parcelable {
|
|||||||
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 archived = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS));
|
||||||
|
|
||||||
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;
|
Barcode 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 = BarcodeFactory.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,archived);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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,48 +1,48 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
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.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 com.google.android.material.card.MaterialCardView;
|
import com.google.android.material.card.MaterialCardView;
|
||||||
|
|
||||||
import androidx.cardview.widget.CardView;
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
import androidx.core.graphics.BlendModeColorFilterCompat;
|
||||||
|
import androidx.core.graphics.BlendModeCompat;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
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 int mCurrentSelectedIndex = -1;
|
||||||
private Cursor mCursor;
|
|
||||||
Settings mSettings;
|
Settings mSettings;
|
||||||
boolean mDarkModeEnabled;
|
boolean mDarkModeEnabled;
|
||||||
private Context mContext;
|
public 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;
|
||||||
|
|
||||||
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);
|
setHasStableIds(true);
|
||||||
mSettings = new Settings(inputContext);
|
mSettings = new Settings(inputContext);
|
||||||
mContext = inputContext;
|
mContext = inputContext;
|
||||||
@@ -50,183 +50,164 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
|||||||
mSelectedItems = new SparseBooleanArray();
|
mSelectedItems = new SparseBooleanArray();
|
||||||
mAnimationItemsIndex = new SparseBooleanArray();
|
mAnimationItemsIndex = new SparseBooleanArray();
|
||||||
|
|
||||||
mDarkModeEnabled = MainActivity.isDarkModeEnabled(inputContext);
|
mDarkModeEnabled = Utils.isDarkModeEnabled(inputContext);
|
||||||
|
|
||||||
swapCursor(mCursor);
|
refreshState();
|
||||||
|
|
||||||
|
swapCursor(inputCursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshState() {
|
||||||
|
// Retrieve user details preference
|
||||||
|
SharedPreferences cardDetailsPref = mContext.getSharedPreferences(
|
||||||
|
mContext.getString(R.string.sharedpreference_card_details),
|
||||||
|
Context.MODE_PRIVATE);
|
||||||
|
mShowDetails = cardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showDetails(boolean show) {
|
||||||
|
mShowDetails = show;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
|
||||||
|
// Store in Shared Preference to restore next adapter launch
|
||||||
|
SharedPreferences cardDetailsPref = mContext.getSharedPreferences(
|
||||||
|
mContext.getString(R.string.sharedpreference_card_details),
|
||||||
|
Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor cardDetailsPrefEditor = cardDetailsPref.edit();
|
||||||
|
cardDetailsPrefEditor.putBoolean(mContext.getString(R.string.sharedpreference_card_details_show), show);
|
||||||
|
cardDetailsPrefEditor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean showingDetails() {
|
||||||
|
return mShowDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void swapCursor(Cursor inputCursor) {
|
public LoyaltyCardListItemViewHolder onCreateViewHolder(ViewGroup inputParent, int inputViewType) {
|
||||||
super.swapCursor(inputCursor);
|
|
||||||
mCursor = inputCursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
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) {
|
||||||
// Invisible until we want to show something more
|
// Invisible until we want to show something more
|
||||||
inputHolder.mDivider.setVisibility(View.GONE);
|
inputHolder.mDivider.setVisibility(View.GONE);
|
||||||
|
|
||||||
if (mDarkModeEnabled) {
|
|
||||||
inputHolder.mStarIcon.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
|
|
||||||
}
|
|
||||||
|
|
||||||
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.mDivider.setVisibility(View.VISIBLE);
|
inputHolder.setBalanceField(loyaltyCard.balance, loyaltyCard.balanceType);
|
||||||
inputHolder.mBalanceField.setVisibility(View.VISIBLE);
|
|
||||||
if (mDarkModeEnabled) {
|
|
||||||
inputHolder.mBalanceField.getCompoundDrawables()[0].setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
|
|
||||||
}
|
|
||||||
inputHolder.mBalanceField.setText(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.mDivider.setVisibility(View.VISIBLE);
|
inputHolder.setExpiryField(loyaltyCard.expiry);
|
||||||
inputHolder.mExpiryField.setVisibility(View.VISIBLE);
|
|
||||||
Drawable expiryIcon = inputHolder.mExpiryField.getCompoundDrawables()[0];
|
|
||||||
if (Utils.hasExpired(loyaltyCard.expiry)) {
|
|
||||||
expiryIcon.setColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP);
|
|
||||||
inputHolder.mExpiryField.setTextColor(Color.RED);
|
|
||||||
} else if (mDarkModeEnabled) {
|
|
||||||
expiryIcon.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
|
|
||||||
}
|
|
||||||
inputHolder.mExpiryField.setText(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);
|
setHeaderHeight(inputHolder, mShowDetails);
|
||||||
inputHolder.mCardIcon.setImageBitmap(Utils.generateIcon(mContext, loyaltyCard.store, loyaltyCard.headerColor).getLetterTile());
|
Bitmap cardIcon = Utils.retrieveCardImage(mContext, loyaltyCard.id, ImageLocationType.icon);
|
||||||
|
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 : R.attr.colorPrimary);
|
||||||
|
|
||||||
|
inputHolder.toggleCardStateIcon(loyaltyCard.starStatus != 0, loyaltyCard.archiveStatus != 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 setHeaderHeight(LoyaltyCardListItemViewHolder inputHolder, boolean expanded) {
|
||||||
{
|
int iconHeight;
|
||||||
inputHolder.mThumbnailContainer.setOnClickListener(inputView -> mListener.onIconClicked(inputPosition));
|
if (expanded) {
|
||||||
|
iconHeight = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||||
|
} else {
|
||||||
|
iconHeight = (int) mContext.getResources().getDimension(R.dimen.cardThumbnailSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputHolder.mIconLayout.getLayoutParams().height = expanded ? 0 : iconHeight;
|
||||||
|
inputHolder.mCardIcon.getLayoutParams().height = iconHeight;
|
||||||
|
inputHolder.mTickIcon.getLayoutParams().height = iconHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyClickEvents(LoyaltyCardListItemViewHolder inputHolder, final int inputPosition) {
|
||||||
inputHolder.mRow.setOnClickListener(inputView -> mListener.onRowClicked(inputPosition));
|
inputHolder.mRow.setOnClickListener(inputView -> mListener.onRowClicked(inputPosition));
|
||||||
inputHolder.mInformationContainer.setOnClickListener(inputView -> mListener.onRowClicked(inputPosition));
|
|
||||||
|
|
||||||
inputHolder.mRow.setOnLongClickListener(inputView -> {
|
inputHolder.mRow.setOnLongClickListener(inputView -> {
|
||||||
mListener.onRowLongClicked(inputPosition);
|
mListener.onRowLongClicked(inputPosition);
|
||||||
inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
inputHolder.mInformationContainer.setOnLongClickListener(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.mTickIcon.setVisibility(View.VISIBLE);
|
||||||
inputHolder.mThumbnailBackContainer.setAlpha(1);
|
if (mCurrentSelectedIndex == inputPosition) {
|
||||||
if (mCurrentSelectedIndex == inputPosition)
|
|
||||||
{
|
|
||||||
LoyaltyCardAnimator.flipView(mContext, inputHolder.mThumbnailBackContainer, inputHolder.mThumbnailFrontContainer, true);
|
|
||||||
resetCurrentIndex();
|
resetCurrentIndex();
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
inputHolder.mTickIcon.setVisibility(View.GONE);
|
||||||
{
|
if ((mReverseAllAnimations && mAnimationItemsIndex.get(inputPosition, false)) || mCurrentSelectedIndex == inputPosition) {
|
||||||
inputHolder.mThumbnailBackContainer.setVisibility(View.GONE);
|
|
||||||
resetIconYAxis(inputHolder.mThumbnailFrontContainer);
|
|
||||||
inputHolder.mThumbnailFrontContainer.setVisibility(View.VISIBLE);
|
|
||||||
inputHolder.mThumbnailFrontContainer.setAlpha(1);
|
|
||||||
if ((mReverseAllAnimations && mAnimationItemsIndex.get(inputPosition, false)) || mCurrentSelectedIndex == inputPosition)
|
|
||||||
{
|
|
||||||
LoyaltyCardAnimator.flipView(mContext, inputHolder.mThumbnailBackContainer, inputHolder.mThumbnailFrontContainer, false);
|
|
||||||
resetCurrentIndex();
|
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
notifyDataSetChanged();
|
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));
|
||||||
}
|
}
|
||||||
@@ -234,53 +215,169 @@ 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, mArchivedBackground;
|
||||||
public ImageView mCardIcon, mStarIcon;
|
public MaterialCardView mRow, mIconLayout;
|
||||||
public CardView mThumbnailContainer;
|
public ConstraintLayout mStar, mArchived;
|
||||||
public MaterialCardView mRow;
|
|
||||||
public View mDivider;
|
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);
|
||||||
mDivider = inputView.findViewById(R.id.info_divider);
|
mDivider = inputView.findViewById(R.id.info_divider);
|
||||||
mThumbnailFrontContainer = inputView.findViewById(R.id.thumbnail_front);
|
|
||||||
mThumbnailBackContainer = inputView.findViewById(R.id.thumbnail_back);
|
|
||||||
mInformationContainer = inputView.findViewById(R.id.information_container);
|
|
||||||
mStoreField = inputView.findViewById(R.id.store);
|
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);
|
||||||
|
mIconLayout = inputView.findViewById(R.id.icon_layout);
|
||||||
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);
|
||||||
|
mArchived = inputView.findViewById(R.id.archivedIcon);
|
||||||
|
mArchivedBackground = inputView.findViewById(R.id.archive_background);
|
||||||
|
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];
|
||||||
|
if (balanceIcon != null) {
|
||||||
|
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];
|
||||||
|
if (expiryIcon != null) {
|
||||||
|
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));
|
||||||
|
} else if (mDarkModeEnabled) {
|
||||||
|
expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mExpiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(expiry));
|
||||||
|
if (Utils.hasExpired(expiry)) {
|
||||||
|
mExpiryField.setTextColor(Color.RED);
|
||||||
|
}
|
||||||
|
mExpiryField.setTextSize(size);
|
||||||
|
}
|
||||||
|
mExpiryField.requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleCardStateIcon(boolean enableStar, boolean enableArchive, 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_white);
|
||||||
|
mStarBackground.setImageResource(R.drawable.ic_starred_black);
|
||||||
|
mArchivedBackground.setImageResource(R.drawable.ic_baseline_archive_24_black);
|
||||||
|
} else {
|
||||||
|
mStarBorder.setImageResource(R.drawable.ic_unstarred_black);
|
||||||
|
mStarBackground.setImageResource(R.drawable.ic_starred_white);
|
||||||
|
mArchivedBackground.setImageResource(R.drawable.ic_baseline_archive_24);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableStar) {
|
||||||
|
mStar.setVisibility(View.VISIBLE);
|
||||||
|
} else{
|
||||||
|
mStar.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableArchive) {
|
||||||
|
mArchived.setVisibility(View.VISIBLE);
|
||||||
|
} else{
|
||||||
|
mArchived.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
mStarBorder.invalidate();
|
||||||
|
mStarBackground.invalidate();
|
||||||
|
mArchivedBackground.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
@@ -11,5 +11,6 @@ public enum LoyaltyCardField {
|
|||||||
barcodeId,
|
barcodeId,
|
||||||
barcodeType,
|
barcodeType,
|
||||||
headerColor,
|
headerColor,
|
||||||
starStatus
|
starStatus,
|
||||||
|
archiveStatus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import android.app.Application;
|
||||||
import androidx.multidex.MultiDexApplication;
|
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import protect.card_locker.preferences.Settings;
|
import protect.card_locker.preferences.Settings;
|
||||||
|
|
||||||
public class LoyaltyCardLockerApplication extends MultiDexApplication {
|
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,43 +16,66 @@ 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.CheckBox;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.view.ActionMode;
|
import androidx.appcompat.view.ActionMode;
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.splashscreen.SplashScreen;
|
||||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
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.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import protect.card_locker.preferences.SettingsActivity;
|
import protect.card_locker.preferences.SettingsActivity;
|
||||||
|
|
||||||
public class MainActivity extends CatimaAppCompatActivity 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;
|
||||||
|
private int mLoyaltyCardCount = 0;
|
||||||
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 mHelpSection;
|
||||||
private View mNoMatchingCardsText;
|
private View mNoMatchingCardsText;
|
||||||
private View mNoGroupCardsText;
|
private View mNoGroupCardsText;
|
||||||
|
|
||||||
private ActionMode.Callback mCurrentActionModeCallback = new ActionMode.Callback()
|
private boolean mArchiveMode;
|
||||||
{
|
public static final String BUNDLE_ARCHIVE_MODE = "archiveMode";
|
||||||
|
|
||||||
|
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);
|
||||||
@@ -60,8 +83,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareActionMode(ActionMode inputMode, Menu inputMenu)
|
public boolean onPrepareActionMode(ActionMode inputMode, Menu inputMenu) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +127,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
}
|
}
|
||||||
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");
|
||||||
}
|
}
|
||||||
@@ -118,7 +140,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
inputMode.finish();
|
inputMode.finish();
|
||||||
return true;
|
return true;
|
||||||
} else if(inputItem.getItemId() == R.id.action_delete) {
|
} else if (inputItem.getItemId() == R.id.action_delete) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
|
||||||
// The following may seem weird, but it is necessary to give translators enough flexibility.
|
// 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".
|
// For example, in Russian, Android's plural quantity "one" actually refers to "any number ending on 1 but not ending in 11".
|
||||||
@@ -133,19 +155,18 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
}
|
}
|
||||||
|
|
||||||
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
|
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
|
||||||
DBHelper db = new DBHelper(MainActivity.this);
|
|
||||||
|
|
||||||
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||||
Log.e(TAG, "Deleting card: " + loyaltyCard.id);
|
Log.d(TAG, "Deleting card: " + loyaltyCard.id);
|
||||||
|
|
||||||
db.deleteLoyaltyCard(loyaltyCard.id);
|
DBHelper.deleteLoyaltyCard(mDatabase, MainActivity.this, loyaltyCard.id);
|
||||||
|
|
||||||
ShortcutHelper.removeShortcut(MainActivity.this, loyaltyCard.id);
|
ShortcutHelper.removeShortcut(MainActivity.this, loyaltyCard.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
TabLayout.Tab tab = ((TabLayout) findViewById(R.id.groups)).getTabAt(selectedTab);
|
TabLayout.Tab tab = ((TabLayout) findViewById(R.id.groups)).getTabAt(selectedTab);
|
||||||
|
mGroup = tab != null ? tab.getTag() : null;
|
||||||
|
|
||||||
updateLoyaltyCardList(mFilter, tab != null ? tab.getTag() : null);
|
updateLoyaltyCardList(true);
|
||||||
|
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
});
|
});
|
||||||
@@ -155,49 +176,95 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else if(inputItem.getItemId() == R.id.action_archive){
|
||||||
|
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||||
|
Log.d(TAG, "Archiving card: " + loyaltyCard.id);
|
||||||
|
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id,1);
|
||||||
|
updateLoyaltyCardList(false);
|
||||||
|
inputMode.finish();
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(inputItem.getItemId() == R.id.action_unarchive){
|
||||||
|
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||||
|
Log.d(TAG, "Unarchiving card: " + loyaltyCard.id);
|
||||||
|
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id,0);
|
||||||
|
updateLoyaltyCardList(false);
|
||||||
|
inputMode.finish();
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(inputItem.getItemId() == R.id.action_star){
|
||||||
|
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||||
|
Log.d(TAG, "Starring card: " + loyaltyCard.id);
|
||||||
|
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 1);
|
||||||
|
updateLoyaltyCardList(false);
|
||||||
|
inputMode.finish();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(inputItem.getItemId() == R.id.action_unstar){
|
||||||
|
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||||
|
Log.d(TAG, "Unstarring card: " + loyaltyCard.id);
|
||||||
|
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 0);
|
||||||
|
updateLoyaltyCardList(false);
|
||||||
|
inputMode.finish();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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) {
|
||||||
{
|
extractIntentFields(getIntent());
|
||||||
setTheme(R.style.AppTheme_NoActionBar);
|
SplashScreen.installSplashScreen(this);
|
||||||
super.onCreate(inputSavedInstanceState);
|
super.onCreate(inputSavedInstanceState);
|
||||||
setTitle(R.string.app_name);
|
if(!mArchiveMode) {
|
||||||
setContentView(R.layout.main_activity);
|
setTitle(R.string.app_name);
|
||||||
|
setContentView(R.layout.main_activity);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
setTitle(R.string.archiveList);
|
||||||
|
setContentView(R.layout.archive_activity);
|
||||||
|
}
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
if(mArchiveMode){
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
if (actionBar != null) {
|
||||||
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(false);
|
||||||
// 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,27 +281,23 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mHelpText = findViewById(R.id.helpText);
|
mHelpSection = findViewById(R.id.helpSection);
|
||||||
mNoMatchingCardsText = findViewById(R.id.noMatchingCardsText);
|
mNoMatchingCardsText = findViewById(R.id.noMatchingCardsText);
|
||||||
mNoGroupCardsText = findViewById(R.id.noGroupCardsText);
|
mNoGroupCardsText = findViewById(R.id.noGroupCardsText);
|
||||||
mCardList = findViewById(R.id.list);
|
mCardList = findViewById(R.id.list);
|
||||||
|
|
||||||
mHelpText.setOnTouchListener(gestureTouchListener);
|
|
||||||
mNoMatchingCardsText.setOnTouchListener(gestureTouchListener);
|
mNoMatchingCardsText.setOnTouchListener(gestureTouchListener);
|
||||||
mCardList.setOnTouchListener(gestureTouchListener);
|
mCardList.setOnTouchListener(gestureTouchListener);
|
||||||
|
mNoGroupCardsText.setOnTouchListener(gestureTouchListener);
|
||||||
|
|
||||||
mAdapter = new LoyaltyCardCursorAdapter(this, null, this);
|
mAdapter = new LoyaltyCardCursorAdapter(this, null, this);
|
||||||
mCardList.setAdapter(mAdapter);
|
mCardList.setAdapter(mAdapter);
|
||||||
registerForContextMenu(mCardList);
|
registerForContextMenu(mCardList);
|
||||||
|
|
||||||
updateLoyaltyCardList(mFilter, null);
|
mGroup = null;
|
||||||
|
updateLoyaltyCardList(true);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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.
|
||||||
@@ -266,40 +329,69 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
.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)
|
mAdapter.refreshState();
|
||||||
{
|
|
||||||
|
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);
|
||||||
@@ -309,108 +401,75 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
|
|
||||||
groupsTabLayout.selectTab(tab);
|
groupsTabLayout.selectTab(tab);
|
||||||
assert tab != null;
|
assert tab != null;
|
||||||
group = tab.getTag();
|
mGroup = tab.getTag();
|
||||||
}
|
}
|
||||||
updateLoyaltyCardList(mFilter, group);
|
updateLoyaltyCardList(true);
|
||||||
// End of active tab logic
|
// End of active tab logic
|
||||||
|
|
||||||
FloatingActionButton addButton = findViewById(R.id.fabAdd);
|
if (!mArchiveMode) {
|
||||||
addButton.setOnClickListener(v -> {
|
FloatingActionButton addButton = findViewById(R.id.fabAdd);
|
||||||
Intent intent = new Intent(getApplicationContext(), ScanActivity.class);
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
if (selectedTab != 0) {
|
|
||||||
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, groupsTabLayout.getTabAt(selectedTab).getText().toString());
|
|
||||||
}
|
|
||||||
intent.putExtras(bundle);
|
|
||||||
startActivityForResult(intent, Utils.BARCODE_SCAN);
|
|
||||||
});
|
|
||||||
addButton.bringToFront();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
addButton.setOnClickListener(v -> {
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
Intent intent = new Intent(getApplicationContext(), ScanActivity.class);
|
||||||
super.onActivityResult(requestCode, resultCode, intent);
|
Bundle bundle = new Bundle();
|
||||||
|
if (selectedTab != 0) {
|
||||||
if (requestCode == Utils.MAIN_REQUEST) {
|
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, groupsTabLayout.getTabAt(selectedTab).getText().toString());
|
||||||
// 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
|
intent.putExtras(bundle);
|
||||||
mFilter = "";
|
mBarcodeScannerLauncher.launch(intent);
|
||||||
if (mMenu != null)
|
});
|
||||||
{
|
addButton.bringToFront();
|
||||||
MenuItem searchItem = mMenu.findItem(R.id.action_search);
|
|
||||||
searchItem.collapseActionView();
|
|
||||||
}
|
|
||||||
ActivityCompat.recreate(this);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
|
|
||||||
|
|
||||||
if(!barcodeValues.isEmpty()) {
|
|
||||||
Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
|
||||||
Bundle newBundle = new Bundle();
|
|
||||||
newBundle.putString(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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed()
|
public void onBackPressed() {
|
||||||
{
|
|
||||||
if (mMenu == null)
|
if (!mSearchView.isIconified()) {
|
||||||
{
|
mSearchView.setIconified(true);
|
||||||
super.onBackPressed();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchView searchView = (SearchView) mMenu.findItem(R.id.action_search).getActionView();
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
if (!searchView.isIconified())
|
private void displayCardSetupOptions(Menu menu, boolean shouldShow) {
|
||||||
{
|
for (int id : new int[]{R.id.action_search, R.id.action_unfold, R.id.action_sort}) {
|
||||||
searchView.setIconified(true);
|
menu.findItem(id).setVisible(shouldShow);
|
||||||
} 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)
|
private void updateLoyaltyCardCount() {
|
||||||
{
|
mLoyaltyCardCount = DBHelper.getLoyaltyCardCount(mDatabase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateLoyaltyCardList(boolean updateCount) {
|
||||||
Group group = null;
|
Group group = null;
|
||||||
if (tag != null) {
|
if (mGroup != null) {
|
||||||
group = (Group) tag;
|
group = (Group) mGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
mAdapter.swapCursor(mDB.getLoyaltyCardCursor(filterText, group));
|
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase, mFilter, group, mOrder, mOrderDirection, mArchiveMode ? DBHelper.LoyaltyCardArchiveFilter.Archived : DBHelper.LoyaltyCardArchiveFilter.Unarchived));
|
||||||
|
|
||||||
if(mDB.getLoyaltyCardCount() > 0)
|
if (updateCount) {
|
||||||
{
|
updateLoyaltyCardCount();
|
||||||
|
// Update menu icons if necessary
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mLoyaltyCardCount > 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);
|
mHelpSection.setVisibility(View.GONE);
|
||||||
mHelpText.setVisibility(View.GONE);
|
|
||||||
mNoGroupCardsText.setVisibility(View.GONE);
|
mNoGroupCardsText.setVisibility(View.GONE);
|
||||||
if(mAdapter.getItemCount() > 0)
|
|
||||||
{
|
if (mAdapter.getItemCount() > 0) {
|
||||||
|
mCardList.setVisibility(View.VISIBLE);
|
||||||
mNoMatchingCardsText.setVisibility(View.GONE);
|
mNoMatchingCardsText.setVisibility(View.GONE);
|
||||||
}
|
} else {
|
||||||
else
|
mCardList.setVisibility(View.GONE);
|
||||||
{
|
if (!mFilter.isEmpty()) {
|
||||||
if(!filterText.isEmpty()) {
|
|
||||||
// Actual Empty Search Result
|
// Actual Empty Search Result
|
||||||
mNoMatchingCardsText.setVisibility(View.VISIBLE);
|
mNoMatchingCardsText.setVisibility(View.VISIBLE);
|
||||||
mNoGroupCardsText.setVisibility(View.GONE);
|
mNoGroupCardsText.setVisibility(View.GONE);
|
||||||
@@ -420,11 +479,16 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
mNoGroupCardsText.setVisibility(View.VISIBLE);
|
mNoGroupCardsText.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
if (mArchiveMode) {
|
||||||
{
|
// If an user deletes the last card in archive mode, we should close the activity
|
||||||
|
// This will move us back to the main view
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
mCardList.setVisibility(View.GONE);
|
mCardList.setVisibility(View.GONE);
|
||||||
mHelpText.setVisibility(View.VISIBLE);
|
mHelpSection.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
mNoMatchingCardsText.setVisibility(View.GONE);
|
mNoMatchingCardsText.setVisibility(View.GONE);
|
||||||
mNoGroupCardsText.setVisibility(View.GONE);
|
mNoGroupCardsText.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
@@ -434,11 +498,13 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateTabGroups(TabLayout groupsTabLayout)
|
private void extractIntentFields(Intent intent) {
|
||||||
{
|
final Bundle b = intent.getExtras();
|
||||||
final DBHelper db = new DBHelper(this);
|
mArchiveMode = b != null && b.getBoolean(BUNDLE_ARCHIVE_MODE, false);
|
||||||
|
}
|
||||||
|
|
||||||
List<Group> newGroups = db.getGroups();
|
public void updateTabGroups(TabLayout groupsTabLayout) {
|
||||||
|
List<Group> newGroups = DBHelper.getGroups(mDatabase);
|
||||||
|
|
||||||
if (newGroups.size() == 0) {
|
if (newGroups.size() == 0) {
|
||||||
groupsTabLayout.removeAllTabs();
|
groupsTabLayout.removeAllTabs();
|
||||||
@@ -461,110 +527,173 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
{
|
if(!mArchiveMode)
|
||||||
this.mMenu = inputMenu;
|
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
|
||||||
|
else{
|
||||||
|
getMenuInflater().inflate(R.menu.archive_menu, inputMenu);
|
||||||
|
}
|
||||||
|
|
||||||
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
|
Utils.updateMenuCardDetailsButtonState(inputMenu.findItem(R.id.action_unfold), mAdapter.showingDetails());
|
||||||
|
displayCardSetupOptions(inputMenu, mLoyaltyCardCount > 0);
|
||||||
|
|
||||||
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(() -> {
|
mSearchView.setOnCloseListener(() -> {
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
return false;
|
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(false);
|
||||||
mFilter,
|
|
||||||
currentTab != null ? currentTab.getTag() : null
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!mArchiveMode) {
|
||||||
|
if (DBHelper.getArchivedCardsCount(mDatabase) == 0) {
|
||||||
|
inputMenu.findItem(R.id.action_archived).setVisible(false);
|
||||||
|
} else {
|
||||||
|
inputMenu.findItem(R.id.action_archived).setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(inputMenu);
|
return super.onCreateOptionsMenu(inputMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 == android.R.id.home) {
|
||||||
{
|
onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == R.id.action_unfold) {
|
||||||
|
mAdapter.showDetails(!mAdapter.showingDetails());
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == R.id.action_sort) {
|
||||||
|
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 showReversed = (CheckBox) customLayout.findViewById(R.id.checkBox_reverse);
|
||||||
|
|
||||||
|
|
||||||
|
showReversed.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) -> {
|
||||||
|
|
||||||
|
setSort(
|
||||||
|
loyaltyCardOrders.get(currentIndex.get()),
|
||||||
|
showReversed.isChecked() ? DBHelper.LoyaltyCardOrderDirection.Descending : 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_archived) {
|
||||||
{
|
Intent i = new Intent(getApplicationContext(), MainActivity.class);
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putBoolean("archiveMode", true);
|
||||||
|
i.putExtras(bundle);
|
||||||
|
startActivity(i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -592,6 +721,12 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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");
|
||||||
@@ -607,9 +742,10 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
}
|
}
|
||||||
|
|
||||||
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()) {
|
||||||
@@ -623,6 +759,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
|
|
||||||
// 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) {
|
||||||
@@ -638,22 +775,18 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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();
|
||||||
|
|
||||||
@@ -663,10 +796,47 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
mCurrentActionMode.setTitle(getResources().getQuantityString(R.plurals.selectedCardCount, count, count));
|
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);
|
||||||
|
MenuItem archiveItem = mCurrentActionMode.getMenu().findItem(R.id.action_archive);
|
||||||
|
MenuItem unarchiveItem = mCurrentActionMode.getMenu().findItem(R.id.action_unarchive);
|
||||||
|
MenuItem starItem = mCurrentActionMode.getMenu().findItem(R.id.action_star);
|
||||||
|
MenuItem unstarItem = mCurrentActionMode.getMenu().findItem(R.id.action_unstar);
|
||||||
|
|
||||||
|
boolean hasStarred = false;
|
||||||
|
boolean hasUnstarred = false;
|
||||||
|
|
||||||
|
if(!mArchiveMode) {
|
||||||
|
unarchiveItem.setVisible(false);
|
||||||
|
archiveItem.setVisible(true);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
unarchiveItem.setVisible(true);
|
||||||
|
archiveItem.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||||
|
|
||||||
|
if (loyaltyCard.starStatus == 1) {
|
||||||
|
hasStarred = true;
|
||||||
|
} else {
|
||||||
|
hasUnstarred = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasStarred && hasUnstarred) {
|
||||||
|
hasStarred = true;
|
||||||
|
hasUnstarred = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (count == 1) {
|
if (count == 1) {
|
||||||
|
starItem.setVisible(!hasStarred);
|
||||||
|
unstarItem.setVisible(!hasUnstarred);
|
||||||
editItem.setVisible(true);
|
editItem.setVisible(true);
|
||||||
editItem.setEnabled(true);
|
editItem.setEnabled(true);
|
||||||
} else {
|
} else {
|
||||||
|
starItem.setVisible(hasUnstarred);
|
||||||
|
unstarItem.setVisible(hasStarred);
|
||||||
|
|
||||||
editItem.setVisible(false);
|
editItem.setVisible(false);
|
||||||
editItem.setEnabled(false);
|
editItem.setEnabled(false);
|
||||||
}
|
}
|
||||||
@@ -675,39 +845,44 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onIconClicked(int inputPosition)
|
|
||||||
{
|
|
||||||
if (mCurrentActionMode == null)
|
|
||||||
{
|
|
||||||
mCurrentActionMode = startSupportActionMode(mCurrentActionModeCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSelection(inputPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRowClicked(int inputPosition)
|
public void onRowClicked(int inputPosition) {
|
||||||
{
|
if (mAdapter.getSelectedItemCount() > 0) {
|
||||||
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 intent = new Intent(this, LoyaltyCardViewActivity.class);
|
||||||
i.setAction("");
|
intent.setAction("");
|
||||||
final Bundle b = new Bundle();
|
final Bundle b = new Bundle();
|
||||||
b.putInt("id", loyaltyCard.id);
|
b.putInt("id", loyaltyCard.id);
|
||||||
i.putExtras(b);
|
|
||||||
|
ArrayList<Integer> cardList = new ArrayList<>();
|
||||||
|
for (int i = 0; i < mAdapter.getItemCount(); i++) {
|
||||||
|
cardList.add(mAdapter.getCard(i).id);
|
||||||
|
}
|
||||||
|
|
||||||
|
b.putIntegerArrayList("cardList", cardList);
|
||||||
|
intent.putExtras(b);
|
||||||
|
|
||||||
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard);
|
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard);
|
||||||
|
|
||||||
startActivityForResult(i, Utils.MAIN_REQUEST);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
240
app/src/main/java/protect/card_locker/ManageGroupActivity.java
Normal file
240
app/src/main/java/protect/card_locker/ManageGroupActivity.java
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import 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 noGroupCardsText;
|
||||||
|
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();
|
||||||
|
|
||||||
|
noGroupCardsText = findViewById(R.id.noGroupCardsText);
|
||||||
|
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
|
||||||
|
noGroupCardsText.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
|
||||||
|
public boolean onCreateOptionsMenu(Menu inputMenu) {
|
||||||
|
getMenuInflater().inflate(R.menu.card_details_menu, inputMenu);
|
||||||
|
Utils.updateMenuCardDetailsButtonState(inputMenu.findItem(R.id.action_unfold), mAdapter.showingDetails());
|
||||||
|
return super.onCreateOptionsMenu(inputMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem inputItem) {
|
||||||
|
int id = inputItem.getItemId();
|
||||||
|
|
||||||
|
if (id == R.id.action_unfold) {
|
||||||
|
mAdapter.showDetails(!mAdapter.showingDetails());
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(inputItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
|
outState.putIntegerArrayList(SAVE_INSTANCE_ADAPTER_STATE, adapterStateToIntegerArray(mAdapter.exportInGroupState()));
|
||||||
|
outState.putString(SAVE_INSTANCE_CURRENT_GROUP_NAME, mGroupNameText.getText().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateLoyaltyCardList() {
|
||||||
|
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase));
|
||||||
|
|
||||||
|
if (mAdapter.getItemCount() == 0) {
|
||||||
|
mCardList.setVisibility(View.GONE);
|
||||||
|
noGroupCardsText.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
mCardList.setVisibility(View.VISIBLE);
|
||||||
|
noGroupCardsText.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void leaveWithoutSaving() {
|
||||||
|
if (hasChanged()) {
|
||||||
|
AlertDialog.Builder builder = new 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,7 +1,9 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
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;
|
||||||
@@ -9,6 +11,11 @@ import android.view.View;
|
|||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
@@ -17,32 +24,27 @@ import androidx.recyclerview.widget.DefaultItemAnimator;
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
public class ManageGroupsActivity extends CatimaAppCompatActivity implements GroupCursorAdapter.GroupAdapterListener {
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ManageGroupsActivity extends CatimaAppCompatActivity implements GroupCursorAdapter.GroupAdapterListener
|
|
||||||
{
|
|
||||||
private static final String TAG = "Catima";
|
private static final String TAG = "Catima";
|
||||||
|
|
||||||
private final DBHelper mDb = new DBHelper(this);
|
private SQLiteDatabase mDatabase;
|
||||||
private TextView mHelpText;
|
private TextView mHelpText;
|
||||||
private RecyclerView mGroupList;
|
private RecyclerView mGroupList;
|
||||||
GroupCursorAdapter mAdapter;
|
GroupCursorAdapter mAdapter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState)
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
{
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setTitle(R.string.groups);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mDatabase = new DBHelper(this).getWritableDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -72,11 +74,10 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
|
|||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateGroupList()
|
private void updateGroupList() {
|
||||||
{
|
mAdapter.swapCursor(DBHelper.getGroupCursor(mDatabase));
|
||||||
mAdapter.swapCursor(mDb.getGroupCursor());
|
|
||||||
|
|
||||||
if (mDb.getGroupCount() == 0) {
|
if (DBHelper.getGroupCount(mDatabase) == 0) {
|
||||||
mGroupList.setVisibility(View.GONE);
|
mGroupList.setVisibility(View.GONE);
|
||||||
mHelpText.setVisibility(View.VISIBLE);
|
mHelpText.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
@@ -87,8 +88,7 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
|
|||||||
mHelpText.setVisibility(View.GONE);
|
mHelpText.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -98,8 +98,7 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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) {
|
||||||
@@ -110,14 +109,23 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void createGroup() {
|
private void createGroup() {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this,R.style.AlertDialogTheme);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
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), (dialog, which) -> {
|
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
|
||||||
mDb.insertGroup(input.getText().toString());
|
String inputString = input.getText().toString().trim();
|
||||||
|
if (inputString.length() == 0) {
|
||||||
|
Toast.makeText(getApplicationContext(), R.string.group_name_is_empty, Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (DBHelper.getGroup(mDatabase, inputString) != null) {
|
||||||
|
Toast.makeText(getApplicationContext(), R.string.group_name_already_in_use, Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DBHelper.insertGroup(mDatabase, inputString);
|
||||||
updateGroupList();
|
updateGroupList();
|
||||||
});
|
});
|
||||||
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
||||||
@@ -133,10 +141,10 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void moveGroup(View view, boolean up) {
|
private void moveGroup(View view, boolean up) {
|
||||||
List<Group> groups = mDb.getGroups();
|
List<Group> groups = DBHelper.getGroups(mDatabase);
|
||||||
final String groupName = getGroupName(view);
|
final String groupName = getGroupName(view);
|
||||||
|
|
||||||
int currentIndex = mDb.getGroup(groupName).order;
|
int currentIndex = DBHelper.getGroup(mDatabase, groupName).order;
|
||||||
int newIndex;
|
int newIndex;
|
||||||
|
|
||||||
// Reinsert group in correct position
|
// Reinsert group in correct position
|
||||||
@@ -155,7 +163,7 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
|
|||||||
groups.add(newIndex, group);
|
groups.add(newIndex, group);
|
||||||
|
|
||||||
// Update database
|
// Update database
|
||||||
mDb.reorderGroups(groups);
|
DBHelper.reorderGroups(mDatabase, groups);
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
updateGroupList();
|
updateGroupList();
|
||||||
@@ -176,25 +184,9 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEditButtonClicked(View view) {
|
public void onEditButtonClicked(View view) {
|
||||||
final String groupName = getGroupName(view);
|
Intent intent = new Intent(this, ManageGroupActivity.class);
|
||||||
|
intent.putExtra("group", getGroupName(view));
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
startActivity(intent);
|
||||||
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), (dialog, which) -> {
|
|
||||||
String newGroupName = input.getText().toString();
|
|
||||||
mDb.updateGroup(groupName, newGroupName);
|
|
||||||
updateGroupList();
|
|
||||||
});
|
|
||||||
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
|
||||||
AlertDialog dialog = builder.create();
|
|
||||||
dialog.show();
|
|
||||||
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
|
||||||
input.requestFocus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -206,7 +198,7 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
|
|||||||
builder.setMessage(groupName);
|
builder.setMessage(groupName);
|
||||||
|
|
||||||
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
|
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
|
||||||
mDb.deleteGroup(groupName);
|
DBHelper.deleteGroup(mDatabase, groupName);
|
||||||
updateGroupList();
|
updateGroupList();
|
||||||
// Delete may change ordering, so invalidate
|
// Delete may change ordering, so invalidate
|
||||||
invalidateHomescreenActiveTab();
|
invalidateHomescreenActiveTab();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -10,9 +10,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 androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
|
|
||||||
import com.google.zxing.ResultPoint;
|
import com.google.zxing.ResultPoint;
|
||||||
import com.google.zxing.client.android.Intents;
|
import com.google.zxing.client.android.Intents;
|
||||||
@@ -23,9 +21,14 @@ 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.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
|
||||||
*/
|
*/
|
||||||
@@ -39,6 +42,10 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
private String addGroup;
|
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(LoyaltyCardEditActivity.BUNDLE_CARDID) : null;
|
cardId = b != null ? b.getString(LoyaltyCardEditActivity.BUNDLE_CARDID) : null;
|
||||||
@@ -54,13 +61,14 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
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_from_image).setOnClickListener(this::addFromImage);
|
||||||
findViewById(R.id.add_manually).setOnClickListener(this::addManually);
|
findViewById(R.id.add_manually).setOnClickListener(this::addManually);
|
||||||
|
|
||||||
@@ -81,7 +89,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
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) {
|
if (addGroup != null) {
|
||||||
scanResultBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
|
scanResultBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
|
||||||
}
|
}
|
||||||
@@ -127,8 +135,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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);
|
||||||
}
|
}
|
||||||
@@ -139,10 +146,8 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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;
|
||||||
@@ -163,12 +168,17 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
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();
|
||||||
@@ -191,12 +201,17 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
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);
|
try {
|
||||||
|
photoPickerLauncher.launch(photoPickerIntent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast.makeText(getApplicationContext(), R.string.failedLaunchingPhotoPicker, Toast.LENGTH_LONG).show();
|
||||||
|
Log.e(TAG, "No activity found to handle intent", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,25 @@ package protect.card_locker;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.core.content.pm.ShortcutInfoCompat;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
|
||||||
import androidx.core.graphics.drawable.IconCompat;
|
|
||||||
|
|
||||||
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
|
||||||
@@ -23,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,
|
||||||
@@ -30,11 +43,10 @@ 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.
|
||||||
*/
|
*/
|
||||||
static void updateShortcuts(Context context, LoyaltyCard card)
|
static void updateShortcuts(Context context, LoyaltyCard card) {
|
||||||
{
|
|
||||||
LinkedList<ShortcutInfoCompat> list = new LinkedList<>(ShortcutManagerCompat.getDynamicShortcuts(context));
|
LinkedList<ShortcutInfoCompat> list = new LinkedList<>(ShortcutManagerCompat.getDynamicShortcuts(context));
|
||||||
|
|
||||||
DBHelper dbHelper = new DBHelper(context);
|
SQLiteDatabase database = new DBHelper(context).getReadableDatabase();
|
||||||
|
|
||||||
String shortcutId = Integer.toString(card.id);
|
String shortcutId = Integer.toString(card.id);
|
||||||
|
|
||||||
@@ -44,31 +56,25 @@ class ShortcutHelper
|
|||||||
|
|
||||||
Integer foundIndex = null;
|
Integer foundIndex = null;
|
||||||
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
// Found the item already
|
// Found the item already
|
||||||
foundIndex = index;
|
foundIndex = index;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(foundIndex != null)
|
if (foundIndex != null) {
|
||||||
{
|
|
||||||
// If the item is already found, then the list needs to be
|
// If the item is already found, then the list needs to be
|
||||||
// reordered, so that the selected item now has the lowest
|
// reordered, so that the selected item now has the lowest
|
||||||
// rank, thus letting it survive longer.
|
// rank, thus letting it survive longer.
|
||||||
ShortcutInfoCompat found = list.remove(foundIndex.intValue());
|
ShortcutInfoCompat found = list.remove(foundIndex.intValue());
|
||||||
list.addFirst(found);
|
list.addFirst(found);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// The item is new to the list. First, we need to trim the list
|
// 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
|
// until it is able to accept a new item, then the item is
|
||||||
// inserted.
|
// inserted.
|
||||||
while(list.size() >= MAX_SHORTCUTS)
|
while (list.size() >= MAX_SHORTCUTS) {
|
||||||
{
|
|
||||||
list.pollLast();
|
list.pollLast();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,15 +86,14 @@ class ShortcutHelper
|
|||||||
LinkedList<ShortcutInfoCompat> finalList = new LinkedList<>();
|
LinkedList<ShortcutInfoCompat> finalList = new LinkedList<>();
|
||||||
|
|
||||||
// The ranks are now updated; the order in the list is the rank.
|
// The ranks are now updated; the order in the list is the rank.
|
||||||
for(int index = 0; index < list.size(); index++)
|
for (int index = 0; index < list.size(); index++) {
|
||||||
{
|
|
||||||
ShortcutInfoCompat prevShortcut = list.get(index);
|
ShortcutInfoCompat prevShortcut = list.get(index);
|
||||||
|
|
||||||
LoyaltyCard loyaltyCard = dbHelper.getLoyaltyCard(Integer.parseInt(prevShortcut.getId()));
|
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(database, Integer.parseInt(prevShortcut.getId()));
|
||||||
|
|
||||||
ShortcutInfoCompat updatedShortcut = createShortcutBuilder(context, loyaltyCard)
|
ShortcutInfoCompat updatedShortcut = createShortcutBuilder(context, loyaltyCard)
|
||||||
.setRank(index)
|
.setRank(index)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
finalList.addLast(updatedShortcut);
|
finalList.addLast(updatedShortcut);
|
||||||
}
|
}
|
||||||
@@ -100,16 +105,13 @@ class ShortcutHelper
|
|||||||
* 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.
|
||||||
*/
|
*/
|
||||||
static void removeShortcut(Context context, int cardId)
|
static void removeShortcut(Context context, int cardId) {
|
||||||
{
|
|
||||||
List<ShortcutInfoCompat> list = ShortcutManagerCompat.getDynamicShortcuts(context);
|
List<ShortcutInfoCompat> list = ShortcutManagerCompat.getDynamicShortcuts(context);
|
||||||
|
|
||||||
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);
|
list.remove(index);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -118,6 +120,16 @@ class ShortcutHelper
|
|||||||
ShortcutManagerCompat.setDynamicShortcuts(context, 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) {
|
static ShortcutInfoCompat.Builder createShortcutBuilder(Context context, LoyaltyCard loyaltyCard) {
|
||||||
Intent intent = new Intent(context, LoyaltyCardViewActivity.class);
|
Intent intent = new Intent(context, LoyaltyCardViewActivity.class);
|
||||||
intent.setAction(Intent.ACTION_MAIN);
|
intent.setAction(Intent.ACTION_MAIN);
|
||||||
@@ -129,7 +141,12 @@ class ShortcutHelper
|
|||||||
bundle.putBoolean("view", true);
|
bundle.putBoolean("view", true);
|
||||||
intent.putExtras(bundle);
|
intent.putExtras(bundle);
|
||||||
|
|
||||||
Bitmap iconBitmap = Utils.generateIcon(context, loyaltyCard, true).getLetterTile();
|
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);
|
IconCompat icon = IconCompat.createWithAdaptiveBitmap(iconBitmap);
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ import android.text.TextWatcher;
|
|||||||
|
|
||||||
public class SimpleTextWatcher implements TextWatcher {
|
public class SimpleTextWatcher implements TextWatcher {
|
||||||
@Override
|
@Override
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) { }
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s) { }
|
public void afterTextChanged(Editable s) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
app/src/main/java/protect/card_locker/UCropWrapper.java
Normal file
79
app/src/main/java/protect/card_locker/UCropWrapper.java
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import com.google.android.material.color.MaterialColors;
|
||||||
|
import com.google.android.material.textview.MaterialTextView;
|
||||||
|
import com.yalantis.ucrop.UCropActivity;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.widget.AppCompatImageView;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.graphics.ColorUtils;
|
||||||
|
|
||||||
|
public class UCropWrapper extends UCropActivity {
|
||||||
|
public static final String UCROP_TOOLBAR_TYPEFACE_STYLE = "ucop_toolbar_typeface_style";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onPostCreate(savedInstanceState);
|
||||||
|
boolean darkMode = Utils.isDarkModeEnabled(this);
|
||||||
|
// setup status bar to look like the rest of the app
|
||||||
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
|
getWindow().getDecorView().setSystemUiVisibility(darkMode ? 0 : View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||||
|
} else {
|
||||||
|
// icons are always white back then
|
||||||
|
if (!darkMode) {
|
||||||
|
getWindow().setStatusBarColor(ColorUtils.compositeColors(Color.argb(127, 0, 0, 0), getWindow().getStatusBarColor()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find and check views that we wish to color modify
|
||||||
|
// for when we update ucrop or switch to another cropper
|
||||||
|
View check = findViewById(com.yalantis.ucrop.R.id.wrapper_controls);
|
||||||
|
if (check instanceof FrameLayout) {
|
||||||
|
FrameLayout controls = (FrameLayout) check;
|
||||||
|
check = findViewById(com.yalantis.ucrop.R.id.wrapper_states);
|
||||||
|
if (check instanceof LinearLayout) {
|
||||||
|
LinearLayout states = (LinearLayout) check;
|
||||||
|
for (int i = 0; i < controls.getChildCount(); i++) {
|
||||||
|
check = controls.getChildAt(i);
|
||||||
|
if (check instanceof AppCompatImageView) {
|
||||||
|
AppCompatImageView controlsBackgroundImage = (AppCompatImageView) check;
|
||||||
|
// everything gathered and are as expected, now perform color patching
|
||||||
|
Utils.patchColors(this);
|
||||||
|
int colorSurface = MaterialColors.getColor(this, R.attr.colorSurface, ContextCompat.getColor(this, R.color.md_theme_light_surface));
|
||||||
|
int colorOnSurface = MaterialColors.getColor(this, R.attr.colorOnSurface, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
|
||||||
|
|
||||||
|
Drawable controlsBackgroundImageDrawable = controlsBackgroundImage.getBackground();
|
||||||
|
controlsBackgroundImageDrawable.mutate();
|
||||||
|
controlsBackgroundImageDrawable.setTint(darkMode ? colorOnSurface : colorSurface);
|
||||||
|
controlsBackgroundImage.setBackgroundDrawable(controlsBackgroundImageDrawable);
|
||||||
|
|
||||||
|
states.setBackgroundColor(darkMode ? colorSurface : colorOnSurface);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// change toolbar font
|
||||||
|
check = findViewById(com.yalantis.ucrop.R.id.toolbar_title);
|
||||||
|
if (check instanceof MaterialTextView) {
|
||||||
|
MaterialTextView toolbarTextview = (MaterialTextView) check;
|
||||||
|
Intent intent = getIntent();
|
||||||
|
int style = intent.getIntExtra(UCROP_TOOLBAR_TYPEFACE_STYLE, -1);
|
||||||
|
if (style != -1) {
|
||||||
|
toolbarTextview.setTypeface(Typeface.defaultFromStyle(style));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,19 +5,21 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
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.os.Build;
|
import android.os.Build;
|
||||||
import android.os.LocaleList;
|
import android.os.LocaleList;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.core.graphics.ColorUtils;
|
import com.google.android.material.color.DynamicColors;
|
||||||
import androidx.exifinterface.media.ExifInterface;
|
|
||||||
|
|
||||||
import com.google.zxing.BinaryBitmap;
|
import com.google.zxing.BinaryBitmap;
|
||||||
import com.google.zxing.LuminanceSource;
|
import com.google.zxing.LuminanceSource;
|
||||||
import com.google.zxing.MultiFormatReader;
|
import com.google.zxing.MultiFormatReader;
|
||||||
@@ -27,20 +29,26 @@ 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;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
import java.text.ParseException;
|
||||||
import java.util.Calendar;
|
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.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
|
import androidx.core.graphics.ColorUtils;
|
||||||
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
|
import androidx.palette.graphics.Palette;
|
||||||
import protect.card_locker.preferences.Settings;
|
import protect.card_locker.preferences.Settings;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
@@ -53,12 +61,15 @@ 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) {
|
static public LetterBitmap generateIcon(Context context, LoyaltyCard loyaltyCard, boolean forShortcut) {
|
||||||
return generateIcon(context, loyaltyCard.store, loyaltyCard.headerColor, forShortcut);
|
return generateIcon(context, loyaltyCard.store, loyaltyCard.headerColor, forShortcut);
|
||||||
@@ -80,7 +91,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);
|
||||||
@@ -107,7 +118,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();
|
||||||
@@ -148,6 +164,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());
|
||||||
@@ -182,7 +213,7 @@ public class Utils {
|
|||||||
|
|
||||||
if (currency == null) {
|
if (currency == null) {
|
||||||
numberFormat.setMaximumFractionDigits(0);
|
numberFormat.setMaximumFractionDigits(0);
|
||||||
return context.getString(R.string.balancePoints, numberFormat.format(value));
|
return context.getResources().getQuantityString(R.plurals.balancePoints, value.intValue(), numberFormat.format(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
|
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
|
||||||
@@ -207,32 +238,19 @@ public class Utils {
|
|||||||
return numberFormat.format(value);
|
return numberFormat.format(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Boolean currencyHasDecimals(Currency currency) {
|
static public BigDecimal parseBalance(String value, Currency currency) throws ParseException {
|
||||||
|
NumberFormat numberFormat = NumberFormat.getInstance();
|
||||||
|
|
||||||
if (currency == null) {
|
if (currency == null) {
|
||||||
return false;
|
numberFormat.setMaximumFractionDigits(0);
|
||||||
|
} else {
|
||||||
|
numberFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
|
||||||
|
numberFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
|
||||||
}
|
}
|
||||||
|
|
||||||
return currency.getDefaultFractionDigits() != 0;
|
Log.d(TAG, numberFormat.parse(value).toString());
|
||||||
}
|
|
||||||
|
|
||||||
static public BigDecimal parseCurrency(String value, Boolean hasDecimals) throws NumberFormatException {
|
return new BigDecimal(numberFormat.parse(value).toString());
|
||||||
// If there are no decimals expected, remove all separators before parsing
|
|
||||||
if (!hasDecimals) {
|
|
||||||
value = value.replaceAll("[^0-9]", "");
|
|
||||||
return new BigDecimal(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// There are many ways users can write a currency, so we fix it up a bit
|
|
||||||
// 1. Replace all non-numbers with dots
|
|
||||||
value = value.replaceAll("[^0-9]", ".");
|
|
||||||
|
|
||||||
// 2. Remove all but the last dot
|
|
||||||
while (value.split("\\.").length > 2) {
|
|
||||||
value = value.replaceFirst("\\.", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse as BigDecimal
|
|
||||||
return new BigDecimal(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static public byte[] bitmapToByteArray(Bitmap bitmap) {
|
static public byte[] bitmapToByteArray(Bitmap bitmap) {
|
||||||
@@ -245,13 +263,11 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
double maxSize = BITMAP_SIZE_BIG;
|
|
||||||
|
|
||||||
double width = bitmap.getWidth();
|
double width = bitmap.getWidth();
|
||||||
double height = bitmap.getHeight();
|
double height = bitmap.getHeight();
|
||||||
|
|
||||||
@@ -294,16 +310,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");
|
||||||
|
|
||||||
@@ -321,8 +341,16 @@ 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File retrieveCardImageAsFile(Context context, String fileName) {
|
||||||
|
return context.getFileStreamPath(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File retrieveCardImageAsFile(Context context, int loyaltyCardId, ImageLocationType type) {
|
||||||
|
return retrieveCardImageAsFile(context, getCardImageFileName(loyaltyCardId, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Bitmap retrieveCardImage(Context context, String fileName) {
|
static public Bitmap retrieveCardImage(Context context, String fileName) {
|
||||||
@@ -336,11 +364,11 @@ 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 <T,U> U mapGetOrDefault(Map<T,U> map, T key, U defaultValue) {
|
static public <T, U> U mapGetOrDefault(Map<T, U> map, T key, U defaultValue) {
|
||||||
U value = map.get(key);
|
U value = map.get(key);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
@@ -378,4 +406,126 @@ public class Utils {
|
|||||||
configuration.setLocales(localeList);
|
configuration.setLocales(localeList);
|
||||||
return context.createConfigurationContext(configuration);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/59324801/8378787
|
||||||
|
public static int getComplementaryColor(int color) {
|
||||||
|
int R = color & 255;
|
||||||
|
int G = (color >> 8) & 255;
|
||||||
|
int B = (color >> 16) & 255;
|
||||||
|
int A = (color >> 24) & 255;
|
||||||
|
R = 255 - R;
|
||||||
|
G = 255 - G;
|
||||||
|
B = 255 - B;
|
||||||
|
return R + (G << 8) + (B << 16) + (A << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace colors in the current theme
|
||||||
|
public static void patchColors(AppCompatActivity activity) {
|
||||||
|
Settings settings = new Settings(activity);
|
||||||
|
String color = settings.getColor();
|
||||||
|
|
||||||
|
Resources.Theme theme = activity.getTheme();
|
||||||
|
Resources resources = activity.getResources();
|
||||||
|
if (color.equals(resources.getString(R.string.settings_key_pink_theme))) {
|
||||||
|
theme.applyStyle(R.style.pink, true);
|
||||||
|
} else if (color.equals(resources.getString(R.string.settings_key_magenta_theme))) {
|
||||||
|
theme.applyStyle(R.style.magenta, true);
|
||||||
|
} else if (color.equals(resources.getString(R.string.settings_key_violet_theme))) {
|
||||||
|
theme.applyStyle(R.style.violet, true);
|
||||||
|
} else if (color.equals(resources.getString(R.string.settings_key_blue_theme))) {
|
||||||
|
theme.applyStyle(R.style.blue, true);
|
||||||
|
} else if (color.equals(resources.getString(R.string.settings_key_sky_blue_theme))) {
|
||||||
|
theme.applyStyle(R.style.skyblue, true);
|
||||||
|
} else if (color.equals(resources.getString(R.string.settings_key_green_theme))) {
|
||||||
|
theme.applyStyle(R.style.green, true);
|
||||||
|
} else if (color.equals(resources.getString(R.string.settings_key_brown_theme))) {
|
||||||
|
theme.applyStyle(R.style.brown, true);
|
||||||
|
} else if (color.equals(resources.getString(R.string.settings_key_catima_theme))) {
|
||||||
|
// catima theme is AppTheme itself, no dynamic colors nor applyStyle
|
||||||
|
} else {
|
||||||
|
// final catch all in case of invalid theme value from older versions
|
||||||
|
// also handles R.string.settings_key_system_theme
|
||||||
|
DynamicColors.applyIfAvailable(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDarkModeEnabled(activity) && settings.getOledDark()) {
|
||||||
|
theme.applyStyle(R.style.DarkBackground, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX android 9 and below has issues with patched theme where the background becomes a
|
||||||
|
// rendering mess
|
||||||
|
// use after views are inflated
|
||||||
|
public static void postPatchColors(AppCompatActivity activity) {
|
||||||
|
TypedValue typedValue = new TypedValue();
|
||||||
|
activity.getTheme().resolveAttribute(android.R.attr.colorBackground, typedValue, true);
|
||||||
|
activity.findViewById(android.R.id.content).setBackgroundColor(typedValue.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateMenuCardDetailsButtonState(MenuItem item, boolean currentlyExpanded) {
|
||||||
|
if (currentlyExpanded) {
|
||||||
|
item.setIcon(R.drawable.ic_baseline_unfold_less_24);
|
||||||
|
item.setTitle(R.string.action_hide_details);
|
||||||
|
} else {
|
||||||
|
item.setIcon(R.drawable.ic_baseline_unfold_more_24);
|
||||||
|
item.setTitle(R.string.action_show_details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getHeaderColorFromImage(Bitmap image, int fallback) {
|
||||||
|
if (image == null) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Palette.Builder(image).generate().getDominantColor(R.attr.colorPrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getRandomHeaderColor(Context context) {
|
||||||
|
TypedArray colors = context.getResources().obtainTypedArray(R.array.letter_tile_colors);
|
||||||
|
final int color = (int) (Math.random() * colors.length());
|
||||||
|
return colors.getColor(color, Color.BLACK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
public class AztecBarcode extends Barcode {
|
||||||
|
@Override
|
||||||
|
public String prettyName() {
|
||||||
|
return "Aztec";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BarcodeFormat format() {
|
||||||
|
return BarcodeFormat.AZTEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String exampleValue() {
|
||||||
|
return "AZTEC";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSquare() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean is2D() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasInternalPadding() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/src/main/java/protect/card_locker/barcodes/Barcode.java
Normal file
22
app/src/main/java/protect/card_locker/barcodes/Barcode.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract barcode class
|
||||||
|
*/
|
||||||
|
public abstract class Barcode {
|
||||||
|
public String name() {
|
||||||
|
return format().name();
|
||||||
|
};
|
||||||
|
abstract public String prettyName();
|
||||||
|
abstract public BarcodeFormat format();
|
||||||
|
abstract public String exampleValue();
|
||||||
|
|
||||||
|
abstract public boolean isSquare();
|
||||||
|
abstract public boolean is2D();
|
||||||
|
public boolean hasInternalPadding() {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
public boolean isSupported() { return true; };
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class BarcodeFactory {
|
||||||
|
public static final Map<String, BarcodeFormat> barcodeNames = new HashMap<>() {{
|
||||||
|
put(BarcodeFormat.AZTEC.name(), BarcodeFormat.AZTEC);
|
||||||
|
put(BarcodeFormat.CODE_39.name(), BarcodeFormat.CODE_39);
|
||||||
|
put(BarcodeFormat.CODE_93.name(), BarcodeFormat.CODE_93);
|
||||||
|
put(BarcodeFormat.CODE_128.name(), BarcodeFormat.CODE_128);
|
||||||
|
put(BarcodeFormat.CODABAR.name(), BarcodeFormat.CODABAR);
|
||||||
|
put(BarcodeFormat.DATA_MATRIX.name(), BarcodeFormat.DATA_MATRIX);
|
||||||
|
put(BarcodeFormat.EAN_8.name(), BarcodeFormat.EAN_8);
|
||||||
|
put(BarcodeFormat.EAN_13.name(), BarcodeFormat.EAN_13);
|
||||||
|
put(BarcodeFormat.ITF.name(), BarcodeFormat.ITF);
|
||||||
|
put(BarcodeFormat.PDF_417.name(), BarcodeFormat.PDF_417);
|
||||||
|
put(BarcodeFormat.QR_CODE.name(), BarcodeFormat.QR_CODE);
|
||||||
|
put(BarcodeFormat.UPC_A.name(), BarcodeFormat.UPC_A);
|
||||||
|
put(BarcodeFormat.UPC_E.name(), BarcodeFormat.UPC_E);
|
||||||
|
}};
|
||||||
|
|
||||||
|
public static Barcode fromBarcode(BarcodeFormat barcodeFormat) {
|
||||||
|
switch (barcodeFormat) {
|
||||||
|
case AZTEC: return new AztecBarcode();
|
||||||
|
case CODE_39: return new Code39Barcode();
|
||||||
|
case CODE_93: return new Code93Barcode();
|
||||||
|
case CODE_128: return new Code128Barcode();
|
||||||
|
case CODABAR: return new CodabarBarcode();
|
||||||
|
case DATA_MATRIX: return new DataMatrixBarcode();
|
||||||
|
case EAN_8: return new Ean8Barcode();
|
||||||
|
case EAN_13: return new Ean13Barcode();
|
||||||
|
case ITF: return new ItfBarcode();
|
||||||
|
case PDF_417: return new Pdf417Barcode();
|
||||||
|
case QR_CODE: return new QrCodeBarcode();
|
||||||
|
case UPC_A: return new UpcABarcode();
|
||||||
|
case UPC_E: return new UpcEBarcode();
|
||||||
|
default: throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Barcode fromName(String name) {
|
||||||
|
return fromBarcode(Objects.requireNonNull(barcodeNames.get(name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSupported(BarcodeFormat barcodeFormat) {
|
||||||
|
return barcodeNames.containsValue(barcodeFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSupported(String name) {
|
||||||
|
return barcodeNames.containsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Collection<BarcodeFormat> getAllFormats() {
|
||||||
|
return barcodeNames.values();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
public class BarcodeWithValue {
|
||||||
|
private final Barcode mBarcode;
|
||||||
|
private final String mValue;
|
||||||
|
|
||||||
|
public BarcodeWithValue(Barcode barcode, String value) {
|
||||||
|
mBarcode = barcode;
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Barcode barcode() {
|
||||||
|
return mBarcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String value() {
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
public class CodabarBarcode extends Barcode {
|
||||||
|
@Override
|
||||||
|
public String prettyName() {
|
||||||
|
return "Codabar";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BarcodeFormat format() {
|
||||||
|
return BarcodeFormat.CODABAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String exampleValue() {
|
||||||
|
return "C0C";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSquare() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean is2D() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasInternalPadding() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
public class Code128Barcode extends Barcode {
|
||||||
|
@Override
|
||||||
|
public String prettyName() {
|
||||||
|
return "Code 128";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BarcodeFormat format() {
|
||||||
|
return BarcodeFormat.CODE_128;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String exampleValue() {
|
||||||
|
return "CODE_128";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSquare() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean is2D() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
public class Code39Barcode extends Barcode {
|
||||||
|
@Override
|
||||||
|
public String prettyName() {
|
||||||
|
return "Code 39";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BarcodeFormat format() {
|
||||||
|
return BarcodeFormat.CODE_39;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String exampleValue() {
|
||||||
|
return "CODE_39";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSquare() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean is2D() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasInternalPadding() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
public class Code93Barcode extends Barcode {
|
||||||
|
@Override
|
||||||
|
public String prettyName() {
|
||||||
|
return "Code 93";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BarcodeFormat format() {
|
||||||
|
return BarcodeFormat.CODE_93;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String exampleValue() {
|
||||||
|
return "CODE_93";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSquare() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean is2D() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
public class DataMatrixBarcode extends Barcode {
|
||||||
|
@Override
|
||||||
|
public String prettyName() {
|
||||||
|
return "Data Matrix";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BarcodeFormat format() {
|
||||||
|
return BarcodeFormat.DATA_MATRIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String exampleValue() {
|
||||||
|
return "DATA_MATRIX";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSquare() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean is2D() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
public class Ean13Barcode extends Barcode {
|
||||||
|
@Override
|
||||||
|
public String prettyName() {
|
||||||
|
return "EAN 13";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BarcodeFormat format() {
|
||||||
|
return BarcodeFormat.EAN_13;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String exampleValue() {
|
||||||
|
return "5901234123457";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSquare() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean is2D() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
public class Ean8Barcode extends Barcode {
|
||||||
|
@Override
|
||||||
|
public String prettyName() {
|
||||||
|
return "EAN 8";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BarcodeFormat format() {
|
||||||
|
return BarcodeFormat.EAN_8;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String exampleValue() {
|
||||||
|
return "32123456";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSquare() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean is2D() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
public class ItfBarcode extends Barcode {
|
||||||
|
@Override
|
||||||
|
public String prettyName() {
|
||||||
|
return "ITF";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BarcodeFormat format() {
|
||||||
|
return BarcodeFormat.ITF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String exampleValue() {
|
||||||
|
return "1003";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSquare() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean is2D() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
public class Pdf417Barcode extends Barcode {
|
||||||
|
@Override
|
||||||
|
public String prettyName() {
|
||||||
|
return "PDF 417";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BarcodeFormat format() {
|
||||||
|
return BarcodeFormat.PDF_417;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String exampleValue() {
|
||||||
|
return "PDF_417";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSquare() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean is2D() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasInternalPadding() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
public class QrCodeBarcode extends Barcode {
|
||||||
|
@Override
|
||||||
|
public String prettyName() {
|
||||||
|
return "QR Code";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BarcodeFormat format() {
|
||||||
|
return BarcodeFormat.QR_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String exampleValue() {
|
||||||
|
return "QR_CODE";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSquare() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean is2D() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasInternalPadding() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
public class UpcABarcode extends Barcode {
|
||||||
|
@Override
|
||||||
|
public String prettyName() {
|
||||||
|
return "UPC A";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BarcodeFormat format() {
|
||||||
|
return BarcodeFormat.UPC_A;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String exampleValue() {
|
||||||
|
return "123456789012";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSquare() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean is2D() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package protect.card_locker.barcodes;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
public class UpcEBarcode extends Barcode {
|
||||||
|
@Override
|
||||||
|
public String prettyName() {
|
||||||
|
return "UPC E";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BarcodeFormat format() {
|
||||||
|
return BarcodeFormat.UPC_E;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String exampleValue() {
|
||||||
|
return "0123456";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSquare() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean is2D() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,10 +2,12 @@ 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.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;
|
||||||
@@ -21,6 +23,7 @@ import java.nio.charset.StandardCharsets;
|
|||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@@ -28,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) {
|
||||||
@@ -55,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) {
|
||||||
@@ -86,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
|
||||||
@@ -97,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,12 +134,13 @@ 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,
|
||||||
|
DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS);
|
||||||
|
|
||||||
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,
|
||||||
@@ -143,12 +151,13 @@ 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,
|
||||||
|
card.archiveStatus);
|
||||||
|
|
||||||
if(Thread.currentThread().isInterrupted())
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
{
|
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,18 +171,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import android.content.Context;
|
|||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.net.Uri;
|
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;
|
||||||
|
|
||||||
@@ -36,18 +34,17 @@ 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);
|
||||||
|
|
||||||
boolean isZipFile = false;
|
boolean isZipFile = false;
|
||||||
|
|
||||||
@@ -57,7 +54,7 @@ public class CatimaImporter implements Importer
|
|||||||
|
|
||||||
String fileName = Uri.parse(localFileHeader.getFileName()).getLastPathSegment();
|
String fileName = Uri.parse(localFileHeader.getFileName()).getLastPathSegment();
|
||||||
if (fileName.equals("catima.csv")) {
|
if (fileName.equals("catima.csv")) {
|
||||||
importCSV(context, db, new ByteArrayInputStream(ZipUtils.read(zipInputStream).getBytes(StandardCharsets.UTF_8)));
|
importCSV(context, database, new ByteArrayInputStream(ZipUtils.read(zipInputStream).getBytes(StandardCharsets.UTF_8)));
|
||||||
} else if (fileName.endsWith(".png")) {
|
} else if (fileName.endsWith(".png")) {
|
||||||
Utils.saveCardImage(context, ZipUtils.readImage(zipInputStream), fileName);
|
Utils.saveCardImage(context, ZipUtils.readImage(zipInputStream), fileName);
|
||||||
} else {
|
} else {
|
||||||
@@ -68,11 +65,11 @@ public class CatimaImporter implements Importer
|
|||||||
if (!isZipFile) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -89,10 +86,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));
|
||||||
@@ -101,44 +98,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 = "";
|
||||||
|
|
||||||
@@ -156,7 +134,7 @@ public class CatimaImporter implements Importer
|
|||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
try {
|
try {
|
||||||
parseV2Groups(db, database, stringPart);
|
parseV2Groups(database, stringPart);
|
||||||
sectionParsed = true;
|
sectionParsed = true;
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
// We may have a multiline field, try again
|
// We may have a multiline field, try again
|
||||||
@@ -164,7 +142,7 @@ public class CatimaImporter implements Importer
|
|||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
try {
|
try {
|
||||||
parseV2Cards(context, db, database, stringPart);
|
parseV2Cards(context, database, stringPart);
|
||||||
sectionParsed = true;
|
sectionParsed = true;
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
// We may have a multiline field, try again
|
// We may have a multiline field, try again
|
||||||
@@ -172,7 +150,7 @@ public class CatimaImporter implements Importer
|
|||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
try {
|
try {
|
||||||
parseV2CardGroups(db, database, stringPart);
|
parseV2CardGroups(database, stringPart);
|
||||||
sectionParsed = true;
|
sectionParsed = true;
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
// We may have a multiline field, try again
|
// We may have a multiline field, try again
|
||||||
@@ -196,19 +174,14 @@ public class CatimaImporter implements Importer
|
|||||||
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<>();
|
List<CSVRecord> records = new ArrayList<>();
|
||||||
|
|
||||||
@@ -227,14 +200,13 @@ public class CatimaImporter implements Importer
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (CSVRecord record : records) {
|
for (CSVRecord record : records) {
|
||||||
importGroup(database, db, record);
|
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<>();
|
List<CSVRecord> records = new ArrayList<>();
|
||||||
|
|
||||||
@@ -253,14 +225,13 @@ public class CatimaImporter implements Importer
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (CSVRecord record : records) {
|
for (CSVRecord record : records) {
|
||||||
importLoyaltyCard(context, database, db, record);
|
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<>();
|
List<CSVRecord> records = new ArrayList<>();
|
||||||
|
|
||||||
@@ -279,7 +250,7 @@ public class CatimaImporter implements Importer
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (CSVRecord record : records) {
|
for (CSVRecord record : records) {
|
||||||
importCardGroupMapping(database, db, record);
|
importCardGroupMapping(database, record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,14 +258,12 @@ public class CatimaImporter 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(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");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,12 +271,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");
|
||||||
@@ -315,72 +285,81 @@ 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);
|
int archiveStatus = 0;
|
||||||
|
try {
|
||||||
|
archiveStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS, record, false);
|
||||||
|
} catch (FormatException _e) {
|
||||||
|
// This field did not exist in versions 2.16.3 and before
|
||||||
|
// We catch this exception so we can still import old backups
|
||||||
|
}
|
||||||
|
if (archiveStatus != 1) archiveStatus = 0;
|
||||||
|
|
||||||
|
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,archiveStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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,21 +1,20 @@
|
|||||||
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 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;
|
||||||
|
|
||||||
@@ -22,17 +20,17 @@ import java.text.ParseException;
|
|||||||
|
|
||||||
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 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(context, 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(Context context, 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,25 @@ 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()) {
|
||||||
{
|
// Fidme deletes the card id if a card is expired
|
||||||
throw new FormatException("No card ID listed, but is required");
|
// Because Catima considers the card id a required field, we ignore these expired cards
|
||||||
|
// https://github.com/CatimaLoyalty/Android/issues/1005
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 or colour in the export either
|
||||||
int starStatus = 0;
|
int starStatus = 0;
|
||||||
|
int archiveStatus = 0;
|
||||||
|
int headerColor = Utils.getRandomHeaderColor(context);
|
||||||
|
|
||||||
// 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, headerColor, starStatus, null,archiveStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,23 @@
|
|||||||
package protect.card_locker.importexport;
|
package protect.card_locker.importexport;
|
||||||
|
|
||||||
public enum ImportExportResult
|
public class ImportExportResult {
|
||||||
{
|
private ImportExportResultType resultType;
|
||||||
Success,
|
private String developerDetails;
|
||||||
GenericFailure,
|
|
||||||
BadPassword
|
public ImportExportResult(ImportExportResultType resultType) {
|
||||||
;
|
this(resultType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImportExportResult(ImportExportResultType resultType, String developerDetails) {
|
||||||
|
this.resultType = resultType;
|
||||||
|
this.developerDetails = developerDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImportExportResultType resultType() {
|
||||||
|
return resultType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String developerDetails() {
|
||||||
|
return developerDetails;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package protect.card_locker.importexport;
|
||||||
|
|
||||||
|
public enum ImportExportResultType {
|
||||||
|
Success,
|
||||||
|
GenericFailure,
|
||||||
|
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,33 +1,28 @@
|
|||||||
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.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
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;
|
||||||
@@ -36,28 +31,20 @@ public class MultiFormatExporter
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(exporter != null)
|
String error;
|
||||||
{
|
if (exporter != null) {
|
||||||
try
|
try {
|
||||||
{
|
exporter.exportData(context, database, output, password);
|
||||||
exporter.exportData(context, db, output);
|
return new ImportExportResult(ImportExportResultType.Success);
|
||||||
return ImportExportResult.Success;
|
} catch (Exception e) {
|
||||||
}
|
|
||||||
catch(IOException e)
|
|
||||||
{
|
|
||||||
Log.e(TAG, "Failed to export data", e);
|
|
||||||
}
|
|
||||||
catch(InterruptedException e)
|
|
||||||
{
|
|
||||||
Log.e(TAG, "Failed to export data", e);
|
Log.e(TAG, "Failed to export data", e);
|
||||||
|
error = e.toString();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
error = "Unsupported data format exported: " + format.name();
|
||||||
|
Log.e(TAG, error);
|
||||||
|
}
|
||||||
|
|
||||||
return ImportExportResult.GenericFailure;
|
return new ImportExportResult(ImportExportResultType.GenericFailure, error);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.e(TAG, "Unsupported data format exported: " + format.name());
|
|
||||||
return ImportExportResult.GenericFailure;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,20 @@
|
|||||||
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;
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.text.ParseException;
|
|
||||||
|
|
||||||
import protect.card_locker.DBHelper;
|
public class MultiFormatImporter {
|
||||||
import protect.card_locker.FormatException;
|
|
||||||
|
|
||||||
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 +22,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,28 +40,31 @@ public class MultiFormatImporter
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (importer != null)
|
String error = null;
|
||||||
{
|
if (importer != null) {
|
||||||
try
|
database.beginTransaction();
|
||||||
{
|
try {
|
||||||
importer.importData(context, db, input, password);
|
importer.importData(context, database, input, password);
|
||||||
return ImportExportResult.Success;
|
database.setTransactionSuccessful();
|
||||||
}
|
return new ImportExportResult(ImportExportResultType.Success);
|
||||||
catch(ZipException e)
|
} catch (ZipException e) {
|
||||||
{
|
if (e.getType().equals(ZipException.Type.WRONG_PASSWORD)) {
|
||||||
return ImportExportResult.BadPassword;
|
return new ImportExportResult(ImportExportResultType.BadPassword);
|
||||||
}
|
} else {
|
||||||
catch(IOException | FormatException | InterruptedException | JSONException | ParseException | NullPointerException e)
|
Log.e(TAG, "Failed to import data", e);
|
||||||
{
|
error = e.toString();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Failed to import data", e);
|
Log.e(TAG, "Failed to import data", e);
|
||||||
|
error = e.toString();
|
||||||
|
} finally {
|
||||||
|
database.endTransaction();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
}
|
error = "Unsupported data format imported: " + format.name();
|
||||||
else
|
Log.e(TAG, error);
|
||||||
{
|
|
||||||
Log.e(TAG, "Unsupported data format imported: " + format.name());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ImportExportResult.GenericFailure;
|
return new ImportExportResult(ImportExportResultType.GenericFailure, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,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.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ import java.util.HashMap;
|
|||||||
|
|
||||||
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.R;
|
||||||
import protect.card_locker.Utils;
|
import protect.card_locker.Utils;
|
||||||
import protect.card_locker.ZipUtils;
|
import protect.card_locker.ZipUtils;
|
||||||
@@ -32,23 +34,22 @@ 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 {
|
||||||
{
|
private static final String TAG = "Catima";
|
||||||
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
|
|
||||||
|
public void importData(Context context, SQLiteDatabase database, 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, HashMap<String, String>> providers = new HashMap<>();
|
HashMap<String, HashMap<String, Object>> providers = new HashMap<>();
|
||||||
|
|
||||||
final CSVParser parser = new CSVParser(new InputStreamReader(context.getResources().openRawResource(R.raw.stocard_stores), StandardCharsets.UTF_8), CSVFormat.RFC4180.withHeader());
|
final CSVParser parser = new CSVParser(new InputStreamReader(context.getResources().openRawResource(R.raw.stocard_stores), StandardCharsets.UTF_8), CSVFormat.RFC4180.builder().setHeader().build());
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
for (CSVRecord record : parser) {
|
||||||
for (CSVRecord record : parser)
|
HashMap<String, Object> recordData = new HashMap<>();
|
||||||
{
|
|
||||||
HashMap<String, String> recordData = new HashMap<>();
|
|
||||||
recordData.put("name", record.get("name"));
|
recordData.put("name", record.get("name"));
|
||||||
recordData.put("barcodeFormat", record.get("barcodeFormat"));
|
recordData.put("barcodeFormat", record.get("barcodeFormat"));
|
||||||
|
|
||||||
@@ -56,13 +57,15 @@ public class StocardImporter implements Importer
|
|||||||
}
|
}
|
||||||
|
|
||||||
parser.close();
|
parser.close();
|
||||||
} catch(IllegalArgumentException|IllegalStateException e) {
|
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||||
throw new FormatException("Issue parsing CSV data", e);
|
throw new FormatException("Issue parsing CSV data", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
ZipInputStream zipInputStream = new ZipInputStream(input, password);
|
ZipInputStream zipInputStream = new ZipInputStream(input, password);
|
||||||
|
|
||||||
String[] providersFileName = null;
|
String[] providersFileName = null;
|
||||||
|
String[] customProvidersBaseName = null;
|
||||||
|
String customProviderId = "";
|
||||||
String[] cardBaseName = null;
|
String[] cardBaseName = null;
|
||||||
String cardName = "";
|
String cardName = "";
|
||||||
LocalFileHeader localFileHeader;
|
LocalFileHeader localFileHeader;
|
||||||
@@ -71,7 +74,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",
|
||||||
@@ -79,7 +82,15 @@ public class StocardImporter implements Importer
|
|||||||
nameParts[0],
|
nameParts[0],
|
||||||
"analytics-properties.json"
|
"analytics-properties.json"
|
||||||
};
|
};
|
||||||
cardBaseName = new String[] {
|
customProvidersBaseName = new String[]{
|
||||||
|
nameParts[0],
|
||||||
|
"sync",
|
||||||
|
"data",
|
||||||
|
"users",
|
||||||
|
nameParts[0],
|
||||||
|
"loyalty-card-custom-providers"
|
||||||
|
};
|
||||||
|
cardBaseName = new String[]{
|
||||||
nameParts[0],
|
nameParts[0],
|
||||||
"sync",
|
"sync",
|
||||||
"data",
|
"data",
|
||||||
@@ -89,6 +100,33 @@ public class StocardImporter implements Importer
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (startsWith(nameParts, customProvidersBaseName, 1)) {
|
||||||
|
// Extract providerId
|
||||||
|
customProviderId = nameParts[customProvidersBaseName.length].split("\\.", 2)[0];
|
||||||
|
|
||||||
|
// Name file
|
||||||
|
if (nameParts.length == customProvidersBaseName.length + 1) {
|
||||||
|
// Ignore the .txt file
|
||||||
|
if (fileName.endsWith(".json")) {
|
||||||
|
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
|
||||||
|
|
||||||
|
providers = appendToHashMap(
|
||||||
|
providers,
|
||||||
|
customProviderId,
|
||||||
|
"name",
|
||||||
|
jsonObject.getString("name")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (fileName.endsWith("logo.png")) {
|
||||||
|
providers = appendToHashMap(
|
||||||
|
providers,
|
||||||
|
customProviderId,
|
||||||
|
"logo",
|
||||||
|
ZipUtils.readImage(zipInputStream)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (startsWith(nameParts, cardBaseName, 1)) {
|
if (startsWith(nameParts, cardBaseName, 1)) {
|
||||||
// Extract cardName
|
// Extract cardName
|
||||||
cardName = nameParts[cardBaseName.length].split("\\.", 2)[0];
|
cardName = nameParts[cardBaseName.length].split("\\.", 2)[0];
|
||||||
@@ -99,48 +137,57 @@ public class StocardImporter implements Importer
|
|||||||
if (fileName.endsWith(".json")) {
|
if (fileName.endsWith(".json")) {
|
||||||
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
|
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
|
||||||
|
|
||||||
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
loyaltyCardHashMap = appendToHashMap(
|
||||||
loyaltyCardHashMap,
|
loyaltyCardHashMap,
|
||||||
cardName,
|
cardName,
|
||||||
"cardId",
|
"cardId",
|
||||||
jsonObject.getString("input_id")
|
jsonObject.getString("input_id")
|
||||||
);
|
);
|
||||||
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
|
||||||
|
// Provider ID can be either custom or not, extract whatever version is relevant
|
||||||
|
String customProviderPrefix = "/users/" + nameParts[0] + "/loyalty-card-custom-providers/";
|
||||||
|
String providerId = jsonObject
|
||||||
|
.getJSONObject("input_provider_reference")
|
||||||
|
.getString("identifier");
|
||||||
|
if (providerId.startsWith(customProviderPrefix)) {
|
||||||
|
providerId = providerId.substring(customProviderPrefix.length());
|
||||||
|
} else {
|
||||||
|
providerId = providerId.substring("/loyalty-card-providers/".length());
|
||||||
|
}
|
||||||
|
|
||||||
|
loyaltyCardHashMap = appendToHashMap(
|
||||||
loyaltyCardHashMap,
|
loyaltyCardHashMap,
|
||||||
cardName,
|
cardName,
|
||||||
"_providerId",
|
"_providerId",
|
||||||
jsonObject
|
providerId
|
||||||
.getJSONObject("input_provider_reference")
|
|
||||||
.getString("identifier")
|
|
||||||
.substring("/loyalty-card-providers/".length())
|
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
if (jsonObject.has("input_barcode_format")) {
|
||||||
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
loyaltyCardHashMap = appendToHashMap(
|
||||||
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 = appendToHashMap(
|
||||||
loyaltyCardHashMap,
|
loyaltyCardHashMap,
|
||||||
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 = appendToHashMap(
|
||||||
loyaltyCardHashMap,
|
loyaltyCardHashMap,
|
||||||
cardName,
|
cardName,
|
||||||
"frontImage",
|
"frontImage",
|
||||||
ZipUtils.readImage(zipInputStream)
|
ZipUtils.readImage(zipInputStream)
|
||||||
);
|
);
|
||||||
} else if (fileName.endsWith("/images/back.png")) {
|
} else if (fileName.endsWith("/images/back.png")) {
|
||||||
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
|
loyaltyCardHashMap = appendToHashMap(
|
||||||
loyaltyCardHashMap,
|
loyaltyCardHashMap,
|
||||||
cardName,
|
cardName,
|
||||||
"backImage",
|
"backImage",
|
||||||
@@ -154,40 +201,52 @@ 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 providerId = (String) loyaltyCardData.get("_providerId");
|
String providerId = (String) loyaltyCardData.get("_providerId");
|
||||||
HashMap<String, String> providerData = providers.get(providerId);
|
|
||||||
|
|
||||||
String store = providerData != null ? providerData.get("name") : providerId;
|
if (providerId == null) {
|
||||||
|
Log.d(TAG, "Missing providerId for card " + loyaltyCardData + ", ignoring...");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMap<String, Object> providerData = providers.get(providerId);
|
||||||
|
|
||||||
|
String store = providerData != null ? providerData.get("name").toString() : providerId;
|
||||||
String note = (String) Utils.mapGetOrDefault(loyaltyCardData, "note", "");
|
String note = (String) Utils.mapGetOrDefault(loyaltyCardData, "note", "");
|
||||||
String cardId = (String) loyaltyCardData.get("cardId");
|
String cardId = (String) loyaltyCardData.get("cardId");
|
||||||
String barcodeTypeString = (String) Utils.mapGetOrDefault(loyaltyCardData, "barcodeType", providerData != null ? providerData.get("barcodeFormat") : 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 && !barcodeTypeString.isEmpty()) {
|
||||||
if (barcodeTypeString.equals("RSS_DATABAR_EXPANDED")) {
|
if (barcodeTypeString.equals("RSS_DATABAR_EXPANDED")) {
|
||||||
barcodeType = BarcodeFormat.RSS_EXPANDED;
|
barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.RSS_EXPANDED);
|
||||||
|
} else if (barcodeTypeString.equals("GS1_128")) {
|
||||||
|
barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.CODE_128);
|
||||||
} 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);
|
int headerColor = Utils.getRandomHeaderColor(context);
|
||||||
|
Bitmap cardIcon = null;
|
||||||
|
if (providerData != null && providerData.containsKey("logo")) {
|
||||||
|
cardIcon = (Bitmap) providerData.get("logo");
|
||||||
|
headerColor = Utils.getHeaderColorFromImage(cardIcon, headerColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
long loyaltyCardInternalId = DBHelper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, 0, null,0);
|
||||||
|
|
||||||
|
if (cardIcon != null) {
|
||||||
|
Utils.saveCardImage(context, cardIcon, (int) loyaltyCardInternalId, ImageLocationType.icon);
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +264,7 @@ public class StocardImporter implements Importer
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HashMap<String, HashMap<String, Object>> appendToLoyaltyCardHashMap(HashMap<String, HashMap<String, Object>> loyaltyCardHashMap, String cardID, String key, Object value) {
|
private HashMap<String, HashMap<String, Object>> appendToHashMap(HashMap<String, HashMap<String, Object>> loyaltyCardHashMap, String cardID, String key, Object value) {
|
||||||
HashMap<String, Object> loyaltyCardData = loyaltyCardHashMap.get(cardID);
|
HashMap<String, Object> loyaltyCardData = loyaltyCardHashMap.get(cardID);
|
||||||
if (loyaltyCardData == null) {
|
if (loyaltyCardData == null) {
|
||||||
loyaltyCardData = new HashMap<>();
|
loyaltyCardData = new HashMap<>();
|
||||||
|
|||||||
@@ -25,17 +25,17 @@ import java.util.TimeZone;
|
|||||||
|
|
||||||
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 +45,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 +71,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 +125,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(),0);
|
||||||
}
|
}
|
||||||
|
|
||||||
database.setTransactionSuccessful();
|
|
||||||
database.endTransaction();
|
|
||||||
database.close();
|
|
||||||
|
|
||||||
bufferedReader.close();
|
bufferedReader.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,54 +3,45 @@ 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 java.util.Locale;
|
|
||||||
|
|
||||||
import protect.card_locker.R;
|
import protect.card_locker.R;
|
||||||
import protect.card_locker.Utils;
|
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 Locale getLocale()
|
public Locale getLocale() {
|
||||||
{
|
|
||||||
String value = getString(R.string.settings_key_locale, "");
|
String value = getString(R.string.settings_key_locale, "");
|
||||||
|
|
||||||
if (value.length() == 0) {
|
if (value.length() == 0) {
|
||||||
@@ -60,69 +51,63 @@ public class Settings
|
|||||||
return Utils.stringToLocale(value);
|
return Utils.stringToLocale(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTheme()
|
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 String getCardViewOrientation() {
|
||||||
{
|
return getString(R.string.settings_key_card_orientation, getResString(R.string.settings_key_follow_system_orientation));
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getOledDark() {
|
||||||
|
return getBoolean(R.string.settings_key_oled_dark, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColor() {
|
||||||
|
return getString(R.string.setting_key_theme_color, mContext.getResources().getString(R.string.settings_key_system_theme));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +1,99 @@
|
|||||||
package protect.card_locker.preferences;
|
package protect.card_locker.preferences;
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
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.appcompat.app.ActionBar;
|
import com.google.android.material.color.DynamicColors;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
import androidx.preference.ListPreference;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.preference.ListPreference;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
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.CatimaAppCompatActivity;
|
||||||
|
import protect.card_locker.MainActivity;
|
||||||
import protect.card_locker.R;
|
import protect.card_locker.R;
|
||||||
import protect.card_locker.Utils;
|
import protect.card_locker.Utils;
|
||||||
|
|
||||||
public class SettingsActivity extends CatimaAppCompatActivity
|
public class SettingsActivity extends CatimaAppCompatActivity {
|
||||||
{
|
|
||||||
|
private final static String RELOAD_MAIN_STATE = "mReloadMain";
|
||||||
|
private SettingsFragment fragment;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState)
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
{
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setTitle(R.string.settings);
|
setTitle(R.string.settings);
|
||||||
setContentView(R.layout.settings_activity);
|
setContentView(R.layout.settings_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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display the fragment as the main content.
|
// Display the fragment as the main content.
|
||||||
SettingsFragment fragment = new SettingsFragment();
|
fragment = new SettingsFragment();
|
||||||
fragment.setParentReference(this);
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.replace(R.id.settings_container, fragment)
|
.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() {
|
||||||
private static final String DIALOG_FRAGMENT_TAG = "SettingsFragment";
|
finishSettingsActivity();
|
||||||
private SettingsActivity parent;
|
}
|
||||||
|
|
||||||
public void setParentReference(SettingsActivity settingsActivity) {
|
private void finishSettingsActivity() {
|
||||||
parent = settingsActivity;
|
if (fragment.mReloadMain) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.putExtra(MainActivity.RESTART_ACTIVITY_INTENT, true);
|
||||||
|
setResult(Activity.RESULT_OK, intent);
|
||||||
|
} else {
|
||||||
|
setResult(Activity.RESULT_OK);
|
||||||
}
|
}
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
|
private static final String DIALOG_FRAGMENT_TAG = "SettingsFragment";
|
||||||
|
|
||||||
|
public boolean mReloadMain;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
@@ -104,29 +127,39 @@ public class SettingsActivity extends CatimaAppCompatActivity
|
|||||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
||||||
}
|
}
|
||||||
|
|
||||||
FragmentActivity activity = getActivity();
|
|
||||||
if (activity != null) {
|
|
||||||
ActivityCompat.recreate(activity);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
Preference colorPreference = findPreference(getResources().getString(R.string.setting_key_theme_color));
|
localePreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
refreshActivity(true);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
Preference oledDarkPreference = findPreference(getResources().getString(R.string.settings_key_oled_dark));
|
||||||
|
assert oledDarkPreference != null;
|
||||||
|
oledDarkPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
refreshActivity(true);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
ListPreference colorPreference = findPreference(getResources().getString(R.string.setting_key_theme_color));
|
||||||
assert colorPreference != null;
|
assert colorPreference != null;
|
||||||
colorPreference.setOnPreferenceChangeListener((preference, o) -> {
|
colorPreference.setOnPreferenceChangeListener((preference, o) -> {
|
||||||
FragmentActivity activity = getActivity();
|
refreshActivity(true);
|
||||||
if (activity != null) {
|
|
||||||
ActivityCompat.recreate(activity);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
localePreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
if (!DynamicColors.isDynamicColorAvailable()) {
|
||||||
// Refresh the activity
|
colorPreference.setEntryValues(R.array.color_values_no_dynamic);
|
||||||
parent.finish();
|
colorPreference.setEntries(R.array.color_value_strings_no_dynamic);
|
||||||
startActivity(parent.getIntent());
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
private void refreshActivity(boolean reloadMain) {
|
||||||
});
|
mReloadMain = reloadMain || mReloadMain;
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
activity.recreate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
10
app/src/main/res/anim/slide_in_left.xml
Normal file
10
app/src/main/res/anim/slide_in_left.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- https://stackoverflow.com/questions/5151591/android-left-to-right-slide-animation/5151774#5151774 -->
|
||||||
|
<translate
|
||||||
|
android:duration="200"
|
||||||
|
android:fromXDelta="-100%"
|
||||||
|
android:fromYDelta="0%"
|
||||||
|
android:toXDelta="0%"
|
||||||
|
android:toYDelta="0%" />
|
||||||
|
</set>
|
||||||
10
app/src/main/res/anim/slide_in_right.xml
Normal file
10
app/src/main/res/anim/slide_in_right.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- https://stackoverflow.com/questions/5151591/android-left-to-right-slide-animation/5151774#5151774 -->
|
||||||
|
<translate
|
||||||
|
android:duration="200"
|
||||||
|
android:fromXDelta="100%"
|
||||||
|
android:fromYDelta="0%"
|
||||||
|
android:toXDelta="0%"
|
||||||
|
android:toYDelta="0%" />
|
||||||
|
</set>
|
||||||
10
app/src/main/res/anim/slide_out_left.xml
Normal file
10
app/src/main/res/anim/slide_out_left.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- https://stackoverflow.com/questions/5151591/android-left-to-right-slide-animation/5151774#5151774 -->
|
||||||
|
<translate
|
||||||
|
android:duration="200"
|
||||||
|
android:fromXDelta="0%"
|
||||||
|
android:fromYDelta="0%"
|
||||||
|
android:toXDelta="-100%"
|
||||||
|
android:toYDelta="0%" />
|
||||||
|
</set>
|
||||||
10
app/src/main/res/anim/slide_out_right.xml
Normal file
10
app/src/main/res/anim/slide_out_right.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- https://stackoverflow.com/questions/5151591/android-left-to-right-slide-animation/5151774#5151774 -->
|
||||||
|
<translate
|
||||||
|
android:duration="200"
|
||||||
|
android:fromXDelta="0%"
|
||||||
|
android:fromYDelta="0%"
|
||||||
|
android:toXDelta="100%"
|
||||||
|
android:toYDelta="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>
|
|
||||||
@@ -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>
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:drawable="@color/ic_launcher_background"/>
|
|
||||||
<item android:gravity="center"
|
|
||||||
android:width="256dp"
|
|
||||||
android:height="256dp"
|
|
||||||
android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</layer-list>
|
|
||||||
5
app/src/main/res/drawable/home_arrow_back_white.xml
Normal file
5
app/src/main/res/drawable/home_arrow_back_white.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:autoMirrored="true" android:height="24dp" android:tint="?attr/colorControlNormal"
|
||||||
|
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="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
|
||||||
|
</vector>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
<path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||||
|
|||||||
10
app/src/main/res/drawable/ic_baseline_archive_24.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_archive_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="@android:color/white">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z"/>
|
||||||
|
</vector>
|
||||||
10
app/src/main/res/drawable/ic_baseline_archive_24_black.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_archive_24_black.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="@android:color/black">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/black"
|
||||||
|
android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z"/>
|
||||||
|
</vector>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M7,10l5,5 5,-5z"/>
|
<path android:fillColor="@android:color/white" android:pathData="M7,10l5,5 5,-5z"/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M7,14l5,-5 5,5z"/>
|
<path android:fillColor="@android:color/white" android:pathData="M7,14l5,-5 5,5z"/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
|
<path android:fillColor="@android:color/white" android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
|
||||||
</vector>
|
</vector>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
|
<path android:fillColor="@android:color/white" android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
|
||||||
</vector>
|
</vector>
|
||||||
5
app/src/main/res/drawable/ic_baseline_info_24.xml
Normal file
5
app/src/main/res/drawable/ic_baseline_info_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,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
|
||||||
|
</vector>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user