Compare commits
657 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dec85d8fa9 | ||
|
|
979d88d2e6 | ||
|
|
67b961c672 | ||
|
|
ed62ad321b | ||
|
|
251bdcd500 | ||
|
|
16f9b8f3a5 | ||
|
|
6be52f9b31 | ||
|
|
d8b901c3a0 | ||
|
|
ed52d2d666 | ||
|
|
ff6b62c3f0 | ||
|
|
da8b15a985 | ||
|
|
eccf994379 | ||
|
|
66622817cc | ||
|
|
3fcfe8afe1 | ||
|
|
2c5dae37fd | ||
|
|
295e63981d | ||
|
|
9ac0ee53b5 | ||
|
|
7e1dbf905a | ||
|
|
63de6f50ed | ||
|
|
6b24f56c79 | ||
|
|
e601d1a659 | ||
|
|
1641631bdc | ||
|
|
91c2efbdfc | ||
|
|
462f77e26f | ||
|
|
0aa1f8b7dd | ||
|
|
0f9509c0e5 | ||
|
|
95d497c7bd | ||
|
|
d760fbd3cb | ||
|
|
ad82492f63 | ||
|
|
e84a23c58e | ||
|
|
67cbb34425 | ||
|
|
0c4fed5c50 | ||
|
|
e1a72a948d | ||
|
|
50d9205aef | ||
|
|
9093c88e23 | ||
|
|
3c793fe54f | ||
|
|
a8960eb506 | ||
|
|
7913e95abf | ||
|
|
fac7b76798 | ||
|
|
aa9c4b81b4 | ||
|
|
5b3fc845f9 | ||
|
|
c8c7498dc9 | ||
|
|
712763969c | ||
|
|
d3f1e0d80b | ||
|
|
e8ab92d8c1 | ||
|
|
e0e4ba0012 | ||
|
|
cb4cede2da | ||
|
|
7bfdb3a8c3 | ||
|
|
c4210da1a5 | ||
|
|
e082f45333 | ||
|
|
d10c8668b6 | ||
|
|
a766a25f6a | ||
|
|
daa2f4137c | ||
|
|
e61d29417c | ||
|
|
cc1be04cf7 | ||
|
|
e0ed5bd11c | ||
|
|
1bda5410d8 | ||
|
|
4bbeb27714 | ||
|
|
c04f2c6785 | ||
|
|
70fa8ff11c | ||
|
|
1637895866 | ||
|
|
6e0d48e42d | ||
|
|
9e71a0d86c | ||
|
|
20365e31be | ||
|
|
fb29adbb22 | ||
|
|
c4f0ab37a9 | ||
|
|
038def0114 | ||
|
|
1f962d88c0 | ||
|
|
d897f3b137 | ||
|
|
dba3692521 | ||
|
|
7ab6ec5f75 | ||
|
|
0d6e002324 | ||
|
|
9b2748cf61 | ||
|
|
e505bce0ba | ||
|
|
e25d8910c5 | ||
|
|
3e79001e84 | ||
|
|
4b432ea94c | ||
|
|
78752e1cf2 | ||
|
|
129608befd | ||
|
|
0c2bf8b8a5 | ||
|
|
6bc08a21e8 | ||
|
|
fa5d094197 | ||
|
|
c988276ced | ||
|
|
7d5f79b7e1 | ||
|
|
67ca1ad505 | ||
|
|
210a0c564a | ||
|
|
0a3df441d3 | ||
|
|
3a97d6b191 | ||
|
|
7f5ba2c133 | ||
|
|
ccb4c3c5a2 | ||
|
|
e8b436a696 | ||
|
|
a160583b30 | ||
|
|
6c24b64837 | ||
|
|
04b824257b | ||
|
|
70251d7ea8 | ||
|
|
fa4ab0ba59 | ||
|
|
8880e35a7a | ||
|
|
c9f3054682 | ||
|
|
537141fd70 | ||
|
|
f027b1f1b8 | ||
|
|
44d7c8520d | ||
|
|
9d9f73fbba | ||
|
|
5c74517e13 | ||
|
|
ded2f003f6 | ||
|
|
fd89caee64 | ||
|
|
3fd7991663 | ||
|
|
565449d17c | ||
|
|
45f266cc93 | ||
|
|
a29edd90ac | ||
|
|
799b82e199 | ||
|
|
91e2b7acdd | ||
|
|
e59fc1ea0a | ||
|
|
2926d0d716 | ||
|
|
bcc988ff2c | ||
|
|
c03c02c6ea | ||
|
|
25d2a5ec5d | ||
|
|
344f7a9bc8 | ||
|
|
e32993ceb5 | ||
|
|
efae7bda4c | ||
|
|
1cbf90a596 | ||
|
|
9d4529b06b | ||
|
|
c471c35b82 | ||
|
|
ccd848c0b8 | ||
|
|
d8776da8b6 | ||
|
|
9de6a2e0da | ||
|
|
9f1d5ad1b3 | ||
|
|
290abd9c56 | ||
|
|
72a04b570a | ||
|
|
1fccfff338 | ||
|
|
d22f8cc5e9 | ||
|
|
7fc88cd5af | ||
|
|
ab485991f1 | ||
|
|
e579168a03 | ||
|
|
38fc515372 | ||
|
|
aad074e29e | ||
|
|
4321bf5ac8 | ||
|
|
694e9b88d7 | ||
|
|
6ba4804d9a | ||
|
|
288531dff0 | ||
|
|
678f126403 | ||
|
|
3ae496427a | ||
|
|
046348612f | ||
|
|
68bcd167a2 | ||
|
|
b7892df877 | ||
|
|
bc63f3163c | ||
|
|
2f562b432c | ||
|
|
b776f90b87 | ||
|
|
6877828853 | ||
|
|
c94c3cb47e | ||
|
|
454052638f | ||
|
|
1b060b26a5 | ||
|
|
7970bb8a40 | ||
|
|
8fa868e99b | ||
|
|
77a6cca57d | ||
|
|
5ab3a62e16 | ||
|
|
aa2ccac22b | ||
|
|
e1f4ed65bb | ||
|
|
72c1a57953 | ||
|
|
24061dae97 | ||
|
|
649daf43df | ||
|
|
fb9cf7a393 | ||
|
|
0d67680e7f | ||
|
|
b5d41b0ab2 | ||
|
|
171ec1cd7e | ||
|
|
54ee93b0cb | ||
|
|
fd6c66a86a | ||
|
|
91044fdc87 | ||
|
|
3600065ef8 | ||
|
|
4a7e8b6eba | ||
|
|
2c12c312e3 | ||
|
|
f8943af402 | ||
|
|
3990992f68 | ||
|
|
d2ac0dacda | ||
|
|
1a35150677 | ||
|
|
bf48e394e9 | ||
|
|
003aba52af | ||
|
|
aad98ba55b | ||
|
|
2b50e503da | ||
|
|
ab4c360c37 | ||
|
|
c76580d6be | ||
|
|
dc8ac6b574 | ||
|
|
0c23f25aca | ||
|
|
47b9bbd30d | ||
|
|
ce81d3afd4 | ||
|
|
cc99af13e4 | ||
|
|
ccf3d1f3d6 | ||
|
|
330ded0b5d | ||
|
|
0bcb0a7ab8 | ||
|
|
403c1fca33 | ||
|
|
ca12f8f914 | ||
|
|
d5ba632bb3 | ||
|
|
b9d158583b | ||
|
|
32b4178950 | ||
|
|
d9f97380d9 | ||
|
|
4e2fe97de5 | ||
|
|
1a3dee2c26 | ||
|
|
53f3a2109b | ||
|
|
c5c2f702d0 | ||
|
|
396801ba74 | ||
|
|
67163539d5 | ||
|
|
c1be6f2a88 | ||
|
|
c0d1b446d1 | ||
|
|
d24a418beb | ||
|
|
1f348295a0 | ||
|
|
280fe76609 | ||
|
|
8a3b623ea6 | ||
|
|
e1f66e1917 | ||
|
|
787bda1c77 | ||
|
|
c83b151c08 | ||
|
|
a642b12795 | ||
|
|
4e1fb16359 | ||
|
|
0d7d1658d6 | ||
|
|
7b7624a3e0 | ||
|
|
488125c492 | ||
|
|
3aeea02300 | ||
|
|
72227f19e8 | ||
|
|
62f7241bca | ||
|
|
80fb7e2cb3 | ||
|
|
ed048e6424 | ||
|
|
bda8abfe3d | ||
|
|
19755f4f86 | ||
|
|
9a2d195cb2 | ||
|
|
429309deef | ||
|
|
864b82d547 | ||
|
|
77a2ed29db | ||
|
|
acfb92e9fb | ||
|
|
61c2c0c84a | ||
|
|
08afc16d01 | ||
|
|
91a4461863 | ||
|
|
e37288b842 | ||
|
|
fc930f40e4 | ||
|
|
33b84ad4c3 | ||
|
|
368a2a30f6 | ||
|
|
5774064da7 | ||
|
|
8673405a50 | ||
|
|
4ec4baa53b | ||
|
|
f00be863c2 | ||
|
|
e0d85f9c8d | ||
|
|
3754243748 | ||
|
|
5ef2c4b1da | ||
|
|
8315067caf | ||
|
|
7bbf5b469c | ||
|
|
0de50e72a6 | ||
|
|
4240fd55ba | ||
|
|
29e8896059 | ||
|
|
a46200f794 | ||
|
|
061b1db245 | ||
|
|
8d3d9a21f7 | ||
|
|
9b5a731d48 | ||
|
|
5e0e8a6f67 | ||
|
|
07c74b79f3 | ||
|
|
5e836758e5 | ||
|
|
e314580ac3 | ||
|
|
2ef739fb4f | ||
|
|
bae6f746a1 | ||
|
|
22e8d7f699 | ||
|
|
48a1084c40 | ||
|
|
3d2ad1693a | ||
|
|
28b3aa2018 | ||
|
|
ed4adad087 | ||
|
|
5a898b4c4c | ||
|
|
7a78eadabb | ||
|
|
d7556b9951 | ||
|
|
c936e3c9c0 | ||
|
|
e33565abdc | ||
|
|
30419b5896 | ||
|
|
f0426b98dc | ||
|
|
bd3d67574e | ||
|
|
52a09056e8 | ||
|
|
21a8dc6867 | ||
|
|
e048fdff59 | ||
|
|
0a8c85d24c | ||
|
|
07d938701f | ||
|
|
c0c126192f | ||
|
|
7cb5eba18f | ||
|
|
134d7fd824 | ||
|
|
1615bc38bd | ||
|
|
3ecdf71331 | ||
|
|
66a2cd438c | ||
|
|
3fdff3c85a | ||
|
|
56709d40b5 | ||
|
|
a8c4c73611 | ||
|
|
31dd0e1de2 | ||
|
|
f51e97fdf7 | ||
|
|
1fcf7101f2 | ||
|
|
dbb7a4ea62 | ||
|
|
a2fa8edcf5 | ||
|
|
fd71eda561 | ||
|
|
058676bb0f | ||
|
|
1fb59979f3 | ||
|
|
9bde7ce4da | ||
|
|
db3d93e1bc | ||
|
|
4fd7872c87 | ||
|
|
fc313f0cfa | ||
|
|
03cf97cca7 | ||
|
|
a7204eb9d5 | ||
|
|
81d7250306 | ||
|
|
580025c99e | ||
|
|
d88bede310 | ||
|
|
4c9bc911ad | ||
|
|
38d6d6ccff | ||
|
|
866eaea680 | ||
|
|
5619c1c003 | ||
|
|
b8311044db | ||
|
|
3679b9a48a | ||
|
|
727f3f32fe | ||
|
|
6fa70afac5 | ||
|
|
c5ad261392 | ||
|
|
28018eb681 | ||
|
|
28f7b3db02 | ||
|
|
10e720de91 | ||
|
|
4de952ba2d | ||
|
|
c5de27abe3 | ||
|
|
cafa09a86a | ||
|
|
1635737658 | ||
|
|
6ee60687b6 | ||
|
|
0afd9358e6 | ||
|
|
cad3b492a2 | ||
|
|
59bde6916c | ||
|
|
eb6364b932 | ||
|
|
ae7ac21785 | ||
|
|
2dd02f42f5 | ||
|
|
3b8e38001c | ||
|
|
fbb0ab27ec | ||
|
|
ceea5182d1 | ||
|
|
34c07800dd | ||
|
|
8c7a3273e8 | ||
|
|
49cd9a28c7 | ||
|
|
392024f84f | ||
|
|
165f963f7d | ||
|
|
aca44c5830 | ||
|
|
e2ed5b515a | ||
|
|
ba6705b96e | ||
|
|
9f407e5eb0 | ||
|
|
c00da00be5 | ||
|
|
c2ea640a71 | ||
|
|
ba5259f0f6 | ||
|
|
b1abfdff2e | ||
|
|
39219531b4 | ||
|
|
048edf186e | ||
|
|
ee6d415d26 | ||
|
|
5143e3fe45 | ||
|
|
306ee053c1 | ||
|
|
40871690d5 | ||
|
|
0bbbabce97 | ||
|
|
f59c485472 | ||
|
|
d8543f456c | ||
|
|
fcf88a1ace | ||
|
|
a2e99c2b9d | ||
|
|
607dff03d4 | ||
|
|
73b6c99d9e | ||
|
|
036748ee56 | ||
|
|
5cae97482f | ||
|
|
f67d27167e | ||
|
|
0832c7fc97 | ||
|
|
f5e1a2d732 | ||
|
|
7b799df98a | ||
|
|
968c3e71a1 | ||
|
|
29d8da99cd | ||
|
|
79213aa3ec | ||
|
|
50fdfe92fa | ||
|
|
4eab1121b0 | ||
|
|
d3bcd3e273 | ||
|
|
9d1c9c3309 | ||
|
|
61e039c456 | ||
|
|
0e2d725591 | ||
|
|
759d42cfcf | ||
|
|
2312788a57 | ||
|
|
0820be3986 | ||
|
|
8a11953276 | ||
|
|
182b3ff3c0 | ||
|
|
79dad1b0a6 | ||
|
|
04466edb01 | ||
|
|
af3ce5a0ff | ||
|
|
3291185812 | ||
|
|
c9df3bd1d2 | ||
|
|
057c7923a8 | ||
|
|
4195d73c53 | ||
|
|
ed08f0577a | ||
|
|
a1257a1692 | ||
|
|
a51815a172 | ||
|
|
8eee8f6a0f | ||
|
|
1817c184ae | ||
|
|
773d759ef5 | ||
|
|
cbc51cfb73 | ||
|
|
46c3d190d8 | ||
|
|
9951999ca4 | ||
|
|
4843a1093d | ||
|
|
a4bde722a6 | ||
|
|
ff5e5d5833 | ||
|
|
685eaec8b5 | ||
|
|
a3556cc66d | ||
|
|
13543ea9f6 | ||
|
|
d7bf31c308 | ||
|
|
a77a968619 | ||
|
|
70c6ee17cd | ||
|
|
c233f82dc1 | ||
|
|
be0c2507f2 | ||
|
|
abfcf9d6cd | ||
|
|
4d565b3b2f | ||
|
|
27511a4ccd | ||
|
|
12440346fa | ||
|
|
870e4d0c4a | ||
|
|
6e526de087 | ||
|
|
891636b51f | ||
|
|
017d97d08c | ||
|
|
63165ca1a5 | ||
|
|
48f40a51d1 | ||
|
|
3940ba5756 | ||
|
|
b8a36e3c45 | ||
|
|
60deaae8d5 | ||
|
|
a71da8d6dc | ||
|
|
dbbbd128dc | ||
|
|
3275279501 | ||
|
|
2f6516ffb9 | ||
|
|
41c8b78275 | ||
|
|
6ed96393d5 | ||
|
|
3519e03568 | ||
|
|
1aef3f5253 | ||
|
|
c97e80432f | ||
|
|
c6265eb9e3 | ||
|
|
935cd97f99 | ||
|
|
e65d096e76 | ||
|
|
8d38918a12 | ||
|
|
57f2c7acd4 | ||
|
|
7fb6e4a8d3 | ||
|
|
56d20939be | ||
|
|
a3968f8894 | ||
|
|
74dd292f5a | ||
|
|
d7a7528114 | ||
|
|
bffeca9033 | ||
|
|
5a88afb9f3 | ||
|
|
f775f9224b | ||
|
|
17acb8370c | ||
|
|
23766fe9f0 | ||
|
|
f1b0a26591 | ||
|
|
f1b2c0d93d | ||
|
|
4034997d7f | ||
|
|
d3bbaf39f4 | ||
|
|
ae7684921f | ||
|
|
e73974536c | ||
|
|
43072e283f | ||
|
|
a3a70d459b | ||
|
|
edfc91376f | ||
|
|
5fb68055ea | ||
|
|
085a3f10e7 | ||
|
|
4070a6b4e0 | ||
|
|
bc547a1d2b | ||
|
|
4f9c75da9e | ||
|
|
fde679751a | ||
|
|
d4720db2e7 | ||
|
|
245935242f | ||
|
|
e217bd28fb | ||
|
|
ad17bc6bf3 | ||
|
|
17ab1365bf | ||
|
|
7ebbe6413f | ||
|
|
adbc6bf999 | ||
|
|
52c41f4c49 | ||
|
|
858e317d8f | ||
|
|
898e822a28 | ||
|
|
6f45f635aa | ||
|
|
7eeb87578f | ||
|
|
badba551d0 | ||
|
|
49abf8a918 | ||
|
|
5fba338223 | ||
|
|
388a0723dc | ||
|
|
e14da63d06 | ||
|
|
1673c0229a | ||
|
|
22afeddcbc | ||
|
|
7758c61079 | ||
|
|
47b30ca9fb | ||
|
|
7b4d587b13 | ||
|
|
685ee018c6 | ||
|
|
e0915578ba | ||
|
|
c733a6c3b9 | ||
|
|
ec17255a43 | ||
|
|
1fc7baa5a0 | ||
|
|
0df411ee96 | ||
|
|
23178d9694 | ||
|
|
94accc951d | ||
|
|
0207e12aed | ||
|
|
c760465b3e | ||
|
|
f480bd0c7e | ||
|
|
5599560258 | ||
|
|
7b4c119d7d | ||
|
|
6144353079 | ||
|
|
03a5334961 | ||
|
|
04e0a5716e | ||
|
|
91e3f9f785 | ||
|
|
2ebc862e27 | ||
|
|
5d122affce | ||
|
|
9e09b9052a | ||
|
|
2753b8826f | ||
|
|
4e02252b75 | ||
|
|
647ce00e72 | ||
|
|
86a5f2fb50 | ||
|
|
dca5129031 | ||
|
|
e30eb00bf7 | ||
|
|
11e5cb8ec2 | ||
|
|
bdd5c8fbbb | ||
|
|
cd79839748 | ||
|
|
d6b47914c8 | ||
|
|
ce1acb83f0 | ||
|
|
bc360aa06c | ||
|
|
ebe6139a64 | ||
|
|
d354fd1877 | ||
|
|
00b612d6c7 | ||
|
|
a307511193 | ||
|
|
f7f358c5c3 | ||
|
|
f7b50a72a4 | ||
|
|
68ce1fe9fd | ||
|
|
e822ab0b56 | ||
|
|
e33ab682a6 | ||
|
|
0d50ad6d10 | ||
|
|
4143e5c286 | ||
|
|
15425d51aa | ||
|
|
3abcb32a75 | ||
|
|
93124a88a5 | ||
|
|
29b00e3b59 | ||
|
|
04174154d6 | ||
|
|
bf60976d10 | ||
|
|
fb7e3e12f2 | ||
|
|
641d29a6f8 | ||
|
|
0f2ee296b4 | ||
|
|
ecd2ecd517 | ||
|
|
f3e7d8f5c1 | ||
|
|
f4522fb17d | ||
|
|
824a72b156 | ||
|
|
116a44fd1c | ||
|
|
b6190d3250 | ||
|
|
82c40408f7 | ||
|
|
c6c9fb763a | ||
|
|
dda5dcc652 | ||
|
|
d039ad9fa4 | ||
|
|
5c70c98eb9 | ||
|
|
2face21985 | ||
|
|
b38ec7b753 | ||
|
|
32d5aec76b | ||
|
|
bdbcaf4b1e | ||
|
|
f54905f218 | ||
|
|
2d3bd4a375 | ||
|
|
de4ab95437 | ||
|
|
09fba71710 | ||
|
|
2b84762b99 | ||
|
|
3a4850d5cf | ||
|
|
5bc8a915d6 | ||
|
|
15cbd5b641 | ||
|
|
d980079d76 | ||
|
|
e58cbd972e | ||
|
|
1fbecd439b | ||
|
|
707306ac81 | ||
|
|
86152586cd | ||
|
|
fa33e4873e | ||
|
|
24e4ef2d9b | ||
|
|
eac4142392 | ||
|
|
f38a499bd8 | ||
|
|
0b8f52b87e | ||
|
|
c8255a2648 | ||
|
|
f28326e363 | ||
|
|
a9952d021b | ||
|
|
9274339be8 | ||
|
|
18caa01089 | ||
|
|
ac495e1bf2 | ||
|
|
a8eecb8841 | ||
|
|
abdde45669 | ||
|
|
13b2faab39 | ||
|
|
433d31c30f | ||
|
|
fa7dcd253a | ||
|
|
ac057f50e2 | ||
|
|
ef33c7a33f | ||
|
|
6028f7d674 | ||
|
|
da5f999eed | ||
|
|
0a9124c834 | ||
|
|
c173012955 | ||
|
|
64db9e593d | ||
|
|
acde4ef181 | ||
|
|
807ea25ac9 | ||
|
|
ad4476fead | ||
|
|
f2c0e3904f | ||
|
|
0eeadbf77f | ||
|
|
c0e95bd4da | ||
|
|
b7e8b8d36e | ||
|
|
2bca8b738a | ||
|
|
2b617ceca5 | ||
|
|
29838f4f73 | ||
|
|
1403d4a98e | ||
|
|
c4b860bb3d | ||
|
|
2768f4c590 | ||
|
|
8660d4e218 | ||
|
|
596d899449 | ||
|
|
0db5a6f803 | ||
|
|
bbd3782bf2 | ||
|
|
b69f2d470b | ||
|
|
ce4dc01c9d | ||
|
|
5cea7eba6c | ||
|
|
0f7d64bed6 | ||
|
|
330b6ecd54 | ||
|
|
3f5bcbb941 | ||
|
|
9c74b2a264 | ||
|
|
dfc804fbe5 | ||
|
|
fa73a30bf4 | ||
|
|
449419b647 | ||
|
|
36916b622b | ||
|
|
03f97374bd | ||
|
|
8650878d95 | ||
|
|
defb8793f6 | ||
|
|
c6b7fd6032 | ||
|
|
5960f03f9b | ||
|
|
b1df0c0baf | ||
|
|
bfb131c811 | ||
|
|
4580ee8017 | ||
|
|
d76a6d79d7 | ||
|
|
b38e035fb6 | ||
|
|
eeb41376c5 | ||
|
|
89f649e5ef | ||
|
|
4bde8f49b1 | ||
|
|
87c1bbdbf4 | ||
|
|
7029818789 | ||
|
|
b64c29bf96 | ||
|
|
24317f4d99 | ||
|
|
b9d3699038 | ||
|
|
ed3c9ceb50 | ||
|
|
0a938628cb | ||
|
|
5720da1581 | ||
|
|
5393903872 | ||
|
|
8e0a4b978d | ||
|
|
df21c2577d | ||
|
|
a7d5b6fa0c | ||
|
|
b49501e66a | ||
|
|
65d0f57358 | ||
|
|
927ac61912 | ||
|
|
fd2b84f318 | ||
|
|
ed4afed538 | ||
|
|
007427023b | ||
|
|
b4ff68eddb | ||
|
|
151b81daa7 | ||
|
|
c090af1c7d | ||
|
|
54a0f89821 | ||
|
|
f01058d5b8 | ||
|
|
e01c77681b | ||
|
|
c49ecdca56 | ||
|
|
b7672e5247 | ||
|
|
71777a74fd | ||
|
|
bebab039d7 | ||
|
|
588a573878 | ||
|
|
a9bbf8fb65 | ||
|
|
175135d695 | ||
|
|
9f08d07a6a | ||
|
|
07f50b1544 | ||
|
|
584064a4a2 | ||
|
|
484f357a82 | ||
|
|
e32b40a024 | ||
|
|
010b88d477 | ||
|
|
fe0bb7b870 | ||
|
|
925f780599 | ||
|
|
9492d206d9 | ||
|
|
a32415b4d2 |
27
.github/workflows/android.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Android CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Build
|
||||
run: ./gradlew assembleRelease
|
||||
- name: Check lint
|
||||
run: ./gradlew lintRelease
|
||||
- name: Run unit tests
|
||||
run: ./gradlew testReleaseUnitTest
|
||||
- name: FindBugs
|
||||
run: ./gradlew findbugs
|
||||
31
.github/workflows/calibreapp-image-actions.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Compress Images on Push to Master
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '**.jpg'
|
||||
- '**.jpeg'
|
||||
- '**.png'
|
||||
- '**.webp'
|
||||
jobs:
|
||||
build:
|
||||
name: calibreapp/image-actions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@master
|
||||
- name: Compress Images
|
||||
id: calibre
|
||||
uses: calibreapp/image-actions@master
|
||||
with:
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
compressOnly: true
|
||||
- name: Create New Pull Request If Needed
|
||||
if: steps.calibre.outputs.markdown != ''
|
||||
uses: peter-evans/create-pull-request@master
|
||||
with:
|
||||
title: Compressed Images
|
||||
branch-suffix: timestamp
|
||||
commit-message: Compressed Images
|
||||
body: ${{ steps.calibre.outputs.markdown }}
|
||||
23
.travis.yml
@@ -1,23 +0,0 @@
|
||||
language: android
|
||||
android:
|
||||
components:
|
||||
# Uncomment the lines below if you want to
|
||||
# use the latest revision of Android SDK Tools
|
||||
- platform-tools
|
||||
- tools
|
||||
|
||||
# The BuildTools version used by your project
|
||||
- build-tools-23.0.2
|
||||
|
||||
# The SDK version used to compile your project
|
||||
- android-23
|
||||
|
||||
# Additional components
|
||||
- extra
|
||||
|
||||
script: ./gradlew build lint findbugs test
|
||||
|
||||
after_failure:
|
||||
- cat app/build/outputs/lint-results.xml
|
||||
- cat app/build/reports/findbugs/findbugs.html
|
||||
- cat app/build/reports/tests/debug/index.html
|
||||
366
CHANGELOG.md
Normal file
@@ -0,0 +1,366 @@
|
||||
# Changelog
|
||||
|
||||
## v1.2.1 (2020-11-17)
|
||||
|
||||
Changes:
|
||||
|
||||
- Fix home screen swiping triggering during vertical swipes too
|
||||
|
||||
## v1.2.0 (2020-11-17)
|
||||
|
||||
Changes:
|
||||
|
||||
- Add swiping between groups on the home screen
|
||||
- Fix crash with cards lacking header colour
|
||||
|
||||
## v1.1.0 (2020-11-11)
|
||||
|
||||
Changes:
|
||||
|
||||
- Improved edit UI
|
||||
- Removed header text colour option (now automatically generated based on brightness)
|
||||
- Updated translations
|
||||
|
||||
## v1.0.1 (2020-11-07)
|
||||
|
||||
Changes:
|
||||
|
||||
- Fix crash in search with no groups
|
||||
|
||||
## v1.0 (2020-11-06)
|
||||
|
||||
Changes:
|
||||
|
||||
- Added rounded edges to card icons on main overview
|
||||
- Added support for grouping entries
|
||||
|
||||
## v0.29 (2020-10-29)
|
||||
|
||||
Changes:
|
||||
|
||||
- Rebrand to Catima
|
||||
- Removed intro
|
||||
- Add floating action buttons
|
||||
- Fix Android 5 crash when opening About screen
|
||||
- Add favourites support
|
||||
- Fix disabled auto-rotate being ignored
|
||||
|
||||
## v0.28 (2020-03-09)
|
||||
|
||||
Changes:
|
||||
|
||||
- Fix barcode centering when exiting full screen ([#351](https://github.com/brarcher/loyalty-card-locker/pull/351))
|
||||
- Allow backup export location to be selected ([#352](https://github.com/brarcher/loyalty-card-locker/pull/352))
|
||||
- Update translations ([#357](https://github.com/brarcher/loyalty-card-locker/pull/357)) & ([#362](https://github.com/brarcher/loyalty-card-locker/pull/362))
|
||||
|
||||
## v0.27 (2020-01-26)
|
||||
|
||||
Changes:
|
||||
|
||||
- Tapping on a barcode now moves it to the top of the screen ([#348](https://github.com/brarcher/loyalty-card-locker/pull/348))
|
||||
- Add white space around barcodes to improve scanning in dark mode ([#328](https://github.com/brarcher/loyalty-card-locker/issues/328))
|
||||
- Fix swapped import buttons. ([#346](https://github.com/brarcher/loyalty-card-locker/pull/346))
|
||||
|
||||
## v0.26.1 (2020-01-09)
|
||||
|
||||
Changes:
|
||||
|
||||
- Fix issue with sharing cards without background color ([#343](https://github.com/brarcher/loyalty-card-locker/pull/343))
|
||||
|
||||
## v0.26 (2020-01-05)
|
||||
|
||||
Changes:
|
||||
|
||||
- Add ability to search for a card ([#320](https://github.com/brarcher/loyalty-card-locker/pull/320))
|
||||
- Add ability to share and receive loyalty cards ([#321](https://github.com/brarcher/loyalty-card-locker/pull/321))
|
||||
- Dark mode support ([#322](https://github.com/brarcher/loyalty-card-locker/pull/322))
|
||||
- Loyalty cards can now be barcodeless (e.g. not have a barcode) ([#324](https://github.com/brarcher/loyalty-card-locker/pull/324))
|
||||
- Notes can span multiple lines ([#326](https://github.com/brarcher/loyalty-card-locker/pull/326))
|
||||
- Improvements with the sizing of notes ([#319](https://github.com/brarcher/loyalty-card-locker/pull/319))
|
||||
- Improve notification and app icon visibility ([#330](https://github.com/brarcher/loyalty-card-locker/pull/330))
|
||||
- Update target SDK to Android 10
|
||||
- Improve the following translations:
|
||||
- German
|
||||
- Italian
|
||||
- Dutch
|
||||
- Polish
|
||||
- Russian
|
||||
|
||||
## v0.25.4 (2019-10-04)
|
||||
|
||||
Changes:
|
||||
|
||||
- Enable app backups
|
||||
- Update French and Slovenian translations
|
||||
|
||||
## v0.25.3 (2019-03-02)
|
||||
|
||||
Changes:
|
||||
|
||||
- Update Russian translations
|
||||
|
||||
## v0.25.2 (2019-01-05)
|
||||
|
||||
Changes:
|
||||
|
||||
- Update and add translations
|
||||
|
||||
## v0.25.1 (2018-10-14)
|
||||
|
||||
Changes:
|
||||
|
||||
- Fix creating new card by manually entering barcode ([issue #272](https://github.com/brarcher/loyalty-card-locker/issues/272))
|
||||
|
||||
## v0.25 (2018-10-07)
|
||||
|
||||
Changes:
|
||||
|
||||
- Sort card list case insensitive ([pull #266](https://github.com/brarcher/loyalty-card-locker/pull/266))
|
||||
- Add setting to lock orientation for all cards ([pull #269](https://github.com/brarcher/loyalty-card-locker/pull/269)
|
||||
|
||||
## v0.24 (2018-07-31)
|
||||
|
||||
Changes:
|
||||
|
||||
- Add a setting to control screen brightness when displaying a barcode ([pull #259](https://github.com/brarcher/loyalty-card-locker/pull/259))
|
||||
- Add Greek translations ([pull #252](https://github.com/brarcher/loyalty-card-locker/pull/252))
|
||||
- Add Slovenian translations ([pull #260](https://github.com/brarcher/loyalty-card-locker/pull/260))
|
||||
- Update translations ([pull #260](https://github.com/brarcher/loyalty-card-locker/pull/260), [pull #254](https://github.com/brarcher/loyalty-card-locker/pull/254))
|
||||
|
||||
## v0.23.4 (2018-05-12)
|
||||
|
||||
Changes:
|
||||
|
||||
- Fix Spanish translations ([pull #244](https://github.com/brarcher/loyalty-card-locker/pull/244))
|
||||
- Update translations ([pull #244](https://github.com/brarcher/loyalty-card-locker/pull/244))
|
||||
|
||||
## v0.23.3 (2018-05-05)
|
||||
|
||||
Changes:
|
||||
|
||||
- Added translations
|
||||
- Polish ([pull #232](https://github.com/brarcher/loyalty-card-locker/pull/232))
|
||||
- Spanish ([pull #232](https://github.com/brarcher/loyalty-card-locker/pull/232))
|
||||
- Slovak ([pull #232](https://github.com/brarcher/loyalty-card-locker/pull/232))
|
||||
- Updated translations ([pull #239](https://github.com/brarcher/loyalty-card-locker/pull/239))
|
||||
|
||||
## v0.23.2 (2018-03-11)
|
||||
|
||||
Changes:
|
||||
|
||||
- Reduce min SDK from 17 to 15. ([pull #226](https://github.com/brarcher/loyalty-card-locker/pull/226))
|
||||
- Remove usage of legacy apache library, used only in unit tests but no longer needed. ([pull #225](https://github.com/brarcher/loyalty-card-locker/pull/225))
|
||||
|
||||
## v0.23.1 (2018-03-07)
|
||||
|
||||
Changes:
|
||||
|
||||
- Prevent crash when rendering a barcode exhausts the application's memory. ([pull #219](https://github.com/brarcher/loyalty-card-locker/pull/219))
|
||||
|
||||
## v0.23 (2018-02-28)
|
||||
|
||||
Changes:
|
||||
|
||||
- Reduce space in header when viewing a card. ([pull #213](https://github.com/brarcher/loyalty-card-locker/pull/213))
|
||||
- Disable beep when scanning a barcode. ([pull #216](https://github.com/brarcher/loyalty-card-locker/pull/216))
|
||||
|
||||
## v0.22 (2018-02-19)
|
||||
|
||||
Changes:
|
||||
|
||||
- Update translations. ([pull #208](https://github.com/brarcher/loyalty-card-locker/pull/208))
|
||||
- Barcode rendering updates: ([pull #209](https://github.com/brarcher/loyalty-card-locker/pull/209))
|
||||
- Reload card view activity when screen is rotated, so barcode image is correct size.
|
||||
- Render 1D barcodes in a larger space, allowing them to better fill the screen.
|
||||
|
||||
## v0.21 (2018-02-17)
|
||||
|
||||
Changes:
|
||||
|
||||
- Add quiet space at the start/end of barcodes. ([pull #200](https://github.com/brarcher/loyalty-card-locker/pull/200))
|
||||
- Add options to configure the colors used for the store name font and background. ([pull #203](https://github.com/brarcher/loyalty-card-locker/pull/203))
|
||||
- Add options to adjust font sizes on the card listing page and single card page. ([pull #204](https://github.com/brarcher/loyalty-card-locker/pull/204))
|
||||
|
||||
## v0.20 (2018-02-10)
|
||||
|
||||
Changes:
|
||||
|
||||
- Changes to Card view to display the note, allow the card ID to take multiple lines, and show the store name. ([pull #197](https://github.com/brarcher/loyalty-card-locker/pull/197))
|
||||
|
||||
## v0.19 (2018-02-01)
|
||||
|
||||
Changes:
|
||||
|
||||
- Improved layout for card list. ([pull #188](https://github.com/brarcher/loyalty-card-locker/pull/188))
|
||||
- Improved layout when viewing a card. ([pull #190](https://github.com/brarcher/loyalty-card-locker/pull/190))
|
||||
|
||||
## v0.18.1 (2018-01-24)
|
||||
|
||||
Changes:
|
||||
|
||||
- Workaround crash during install on some Android versions (likely Android 5 and below). ([pull #184](https://github.com/brarcher/loyalty-card-locker/pull/184))
|
||||
|
||||
## v0.18 (2018-01-19)
|
||||
|
||||
Changes:
|
||||
|
||||
- Fix crash when importing certain types of corrupted CSV files. ([pull #177](https://github.com/brarcher/loyalty-card-locker/pull/177))
|
||||
- Fix importing backups directly from the file system. ([pull #180](https://github.com/brarcher/loyalty-card-locker/pull/180))
|
||||
- Fix importing backups from certain types of content providers. ([pull #179](https://github.com/brarcher/loyalty-card-locker/pull/179))
|
||||
|
||||
## v0.17 (2018-01-11)
|
||||
|
||||
Changes:
|
||||
|
||||
- Fix issue on Android SDK 24+ where using the file chooser import option would cause a crash. ([pull #170](https://github.com/brarcher/loyalty-card-locker/pull/170))
|
||||
- New icon and color scheme. ([pull #171](https://github.com/brarcher/loyalty-card-locker/pull/171))
|
||||
|
||||
## v0.16 (2017-11-29)
|
||||
|
||||
Changes:
|
||||
|
||||
- Add support for adding loyalty card shortcuts from the launcher/homescreen. ([pull #161](https://github.com/brarcher/loyalty-card-locker/pull/161))
|
||||
- Remove support for adding loyalty card shortcuts from the app itself. This removes the need for the shortcut permission. ([pull #163](https://github.com/brarcher/loyalty-card-locker/pull/163))
|
||||
|
||||
## v0.15 (2017-11-25)
|
||||
|
||||
Changes:
|
||||
|
||||
- Add support for adding shortcuts to home screen when adding or editing a card. ([pull #155](https://github.com/brarcher/loyalty-card-locker/pull/155))
|
||||
- Remove widget, as it was a poor substitute for shortcuts. ([pull #155](https://github.com/brarcher/loyalty-card-locker/pull/155))
|
||||
- Fix exporting backups on Android 7+. ([pull #153](https://github.com/brarcher/loyalty-card-locker/pull/153))
|
||||
- Report more accurate mime type when exporting backup data. ([pull #156](https://github.com/brarcher/loyalty-card-locker/pull/156))
|
||||
- Fix bug where a card could not be edited. ([pull #155](https://github.com/brarcher/loyalty-card-locker/pull/155))
|
||||
|
||||
## v0.14 (2017-10-26)
|
||||
|
||||
Changes:
|
||||
|
||||
- Add support for app shortcuts (Android 7.1+), where the most recently used cards will appear as shortcuts. ([pull #145](https://github.com/brarcher/loyalty-card-locker/pull/145))
|
||||
- Add a widget which works like a pinned app shortcut, to support devices which run below Android 7.1. ([pull #142](https://github.com/brarcher/loyalty-card-locker/pull/142))
|
||||
|
||||
## v0.13 (2017-07-25)
|
||||
|
||||
Changes:
|
||||
|
||||
- Add screen rotation lock menu option when displaying a card. If locked, the screen will transition to its "natural" orientation and further screen rotation will be blocked. ([pull #128](https://github.com/brarcher/loyalty-card-locker/pull/128))
|
||||
- If a card is selected from the main screen but cannot be loaded, the application fails gracefully and posts a message. ([pull #132](https://github.com/brarcher/loyalty-card-locker/pull/132))
|
||||
- Fix case where layout IDs for intro wizard could not be found. ([pull #128](https://github.com/brarcher/loyalty-card-locker/pull/128))
|
||||
|
||||
## v0.12 (2017-07-16)
|
||||
|
||||
Changes:
|
||||
|
||||
- A change in v0.11 reduced the memory usage of barcode drawing, but affected the barcode dimensions. This is now changed to maintain the barcode dimensions while reducing memory usage. ([pull #126](https://github.com/brarcher/loyalty-card-locker/pull/126))
|
||||
- Update German and French translations. ([pull #122](https://github.com/brarcher/loyalty-card-locker/pull/122), [pull #124](https://github.com/brarcher/loyalty-card-locker/pull/124), [pull #125](https://github.com/brarcher/loyalty-card-locker/pull/125))
|
||||
|
||||
## v0.11.1 (2017-06-29)
|
||||
|
||||
Changes:
|
||||
|
||||
- Prevent a crash when rotation the screen in the first run intro wizard.
|
||||
|
||||
## v0.11 (2017-06-26)
|
||||
|
||||
Improvements:
|
||||
|
||||
- When editing a card ID, pre-populate the existing ID to start. ([pull #94](https://github.com/brarcher/loyalty-card-locker/pull/94))
|
||||
- Limit the width of generated barcodes to reduce memory usage and out of memory errors. ([pull #103](https://github.com/brarcher/loyalty-card-locker/pull/103))
|
||||
- When editing a card, change the "Enter Card" button to say "Edit Card" if a card ID already exists. ([pull #104](https://github.com/brarcher/loyalty-card-locker/pull/104))
|
||||
- Change the color scheme to be softer and compatible with the app icon, and change the layout when viewing a card to be cleaner. ([pull #107](https://github.com/brarcher/loyalty-card-locker/pull/107))
|
||||
- Add an intro wizard which launches on the app's first launch. ([pull #108](https://github.com/brarcher/loyalty-card-locker/pull/108))
|
||||
|
||||
## v0.10 (2017-02-12)
|
||||
|
||||
Improvements:
|
||||
|
||||
- Changed the default import/export filename. ([pull #84](https://github.com/brarcher/loyalty-card-locker/pull/84))
|
||||
- Correct string on the import/export page. ([pull #87](https://github.com/brarcher/loyalty-card-locker/pull/87))
|
||||
- Improve layout of card view page. The text should be easier to read, and is selectable with a long click. ([pull #91](https://github.com/brarcher/loyalty-card-locker/pull/91))
|
||||
|
||||
## v0.9 (2017-01-17)
|
||||
|
||||
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".
|
||||
|
||||
Additional features/improvements:
|
||||
|
||||
- Importing/Exporting cards was changed to be more flexible. ([pull #76](https://github.com/brarcher/loyalty-card-locker/pull/76))
|
||||
- Translations for Lithuanian added. ([pull #62](https://github.com/brarcher/loyalty-card-locker/pull/62))
|
||||
- Translations for French added. ([pull #80](https://github.com/brarcher/loyalty-card-locker/pull/80))
|
||||
|
||||
## v0.8 (2016-11-22)
|
||||
|
||||
New features/improvements:
|
||||
|
||||
- Screen brightness increased to its maximum when displaying a card, to help barcode scanners successfully capture the barcode. ([pull #54](https://github.com/brarcher/loyalty-card-locker/pull/54))
|
||||
- Add a delete confirmation when deleting a card. ([pull #55](https://github.com/brarcher/loyalty-card-locker/pull/55))
|
||||
- Add translations for German ([pull #57](https://github.com/brarcher/loyalty-card-locker/pull/57)) and Czech ([pull #58](https://github.com/brarcher/loyalty-card-locker/pull/58)).
|
||||
- Clarification change for Italian translation. ([pull #66](https://github.com/brarcher/loyalty-card-locker/pull/66))
|
||||
|
||||
## v0.7 (2016-07-14)
|
||||
|
||||
New features/improvements:
|
||||
|
||||
- Long-click of a card brings up option to copy card ID to the clipboard. ([pull #49](https://github.com/brarcher/loyalty-card-locker/issues/49))
|
||||
|
||||
Bug fixes:
|
||||
|
||||
- Back button on Input/Export view now works, moving user to main view
|
||||
|
||||
## v0.6 (2016-05-23)
|
||||
|
||||
New features/improvements:
|
||||
|
||||
- Allow user to enter barcode manually. If a user elects to enter a barcode manually, a list of all valid and supported barcode images is displayed. The user then may select the barcode image which matches what the user wants. [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))
|
||||
|
||||
## v0.5 (2016-05-16)
|
||||
|
||||
New features/improvements:
|
||||
|
||||
- An about dialog can be opened from the main screen, which gives details about the application and project on GitHub ([issue #19](https://github.com/brarcher/loyalty-card-locker/issues/19))
|
||||
- Allow loyalty card information to be imported from/exported to a CSV file in external storage ([issue #36](https://github.com/brarcher/loyalty-card-locker/issues/36), [issue #20](https://github.com/brarcher/loyalty-card-locker/issues/20))
|
||||
|
||||
## v0.4 (2016-04-09)
|
||||
|
||||
New features/improvements:
|
||||
|
||||
- Dutch translation
|
||||
- Allow name field to be editable after adding loyalty card
|
||||
- Add an optional note field
|
||||
|
||||
Bug fixes:
|
||||
|
||||
- Resolve all issues identified by FindBugs and require all FindBugs issues be resolved prior to pull request acceptance
|
||||
|
||||
## v0.3 (2016-02-11)
|
||||
|
||||
- Now officially supports the following list of 1D and 2D barcodes:
|
||||
- AZTEC
|
||||
- CODABAR
|
||||
- CODE_39
|
||||
- CODE_128
|
||||
- DATA_MATRIX
|
||||
- EAN_8
|
||||
- EAN_13
|
||||
- ITF
|
||||
- PDF_417
|
||||
- QR_CODE
|
||||
- UPC_A
|
||||
|
||||
- Generated barcodes are larger, easier to scan from a scanning device
|
||||
|
||||
## v0.2 (2016-02-07)
|
||||
|
||||
- Italian translations
|
||||
- Support for all 1D barcode types. (Originally only product 1D barcodes were supported)
|
||||
- Add required camera permission, which was initially missing.
|
||||
|
||||
## v0.1 (2016-01-30)
|
||||
|
||||
- Ability to create/edit/delete loyalty cards
|
||||
- Capture barcode of loyalty card using a camera
|
||||
- Display captured barcode, for scanning at a store
|
||||
91
CONTRIBUTING.md
Normal file
@@ -0,0 +1,91 @@
|
||||
How to Submit Patches to the Loyalty Card Keychain Project
|
||||
===============================================================================
|
||||
https://github.com/brarcher/budget-watch
|
||||
|
||||
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
|
||||
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
|
||||
project.
|
||||
|
||||
## Test Your Code
|
||||
|
||||
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
|
||||
can be run by gradle using:
|
||||
|
||||
# ./gradlew testReleaseUnitTest
|
||||
|
||||
The second and third check for common problems using static analysis.
|
||||
These are the Android lint checker, run using:
|
||||
|
||||
# ./gradlew lintRelease
|
||||
|
||||
and FindBugs, run using:
|
||||
|
||||
# ./gradlew findbugs
|
||||
|
||||
The final check is by testing the application on a live device and verifying
|
||||
the basic functionality works as expected.
|
||||
|
||||
## Make Sure Your Code is Tested
|
||||
|
||||
The Loyalty Card Keychain code uses a fair number of unit tests to verify that
|
||||
the basic functionality is working. Submissions which add functionality
|
||||
or significantly change the existing code should include additional tests
|
||||
to verify the proper operation of the proposed changes.
|
||||
|
||||
## Explain Your Work
|
||||
|
||||
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
|
||||
implemented. If you are submitting a bug fix, it is also incredibly helpful
|
||||
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
|
||||
fixed.
|
||||
|
||||
## Sign Your Work
|
||||
|
||||
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
|
||||
open-source patch. The "Developer's Certificate of Origin" pledge is taken
|
||||
from the Linux Kernel and the rules are pretty simple:
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
|
||||
... then you just add a line to the bottom of your patch description, with
|
||||
your real name, saying:
|
||||
|
||||
Signed-off-by: Random J Developer <random@developer.example.org>
|
||||
|
||||
## Submit Patch(es) for Review
|
||||
|
||||
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
|
||||
way to do this is to submit a Pull Request to the Loyalty Card Keychain project.
|
||||
Changes need to apply cleanly onto the master branch and pass all
|
||||
unit tests and produce no errors during static analysis.
|
||||
180
Gemfile.lock
Normal file
@@ -0,0 +1,180 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.2)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.1.0)
|
||||
aws-partitions (1.388.0)
|
||||
aws-sdk-core (3.109.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.39.0)
|
||||
aws-sdk-core (~> 3, >= 3.109.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.83.1)
|
||||
aws-sdk-core (~> 3, >= 3.109.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.2.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.0.3)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander-fastlane (4.4.6)
|
||||
highline (~> 1.7.2)
|
||||
declarative (0.0.20)
|
||||
declarative-option (0.1.0)
|
||||
digest-crc (0.6.1)
|
||||
rake (~> 13.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
emoji_regex (3.2.0)
|
||||
excon (0.78.0)
|
||||
faraday (1.1.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ruby2_keywords
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday_middleware (1.0.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.0)
|
||||
fastlane (2.165.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-api-client (>= 0.37.0, < 0.39.0)
|
||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
||||
highline (>= 1.7.2, < 2.0.0)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (~> 2.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (>= 1.4.5, < 2.0.0)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
gh_inspector (1.1.3)
|
||||
google-api-client (0.38.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 0.9)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
signet (~> 0.12)
|
||||
google-cloud-core (1.5.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.4.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-errors (1.0.1)
|
||||
google-cloud-storage (1.29.1)
|
||||
addressable (~> 2.5)
|
||||
digest-crc (~> 0.4)
|
||||
google-api-client (~> 0.33)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (~> 0.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (0.14.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.14)
|
||||
highline (1.7.10)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.4.0)
|
||||
json (2.3.1)
|
||||
jwt (2.2.2)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.10.1)
|
||||
mini_mime (1.0.2)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.3.0)
|
||||
naturally (2.2.0)
|
||||
os (1.1.1)
|
||||
plist (3.5.0)
|
||||
public_suffix (4.0.6)
|
||||
rake (13.0.1)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rouge (2.0.7)
|
||||
ruby2_keywords (0.0.2)
|
||||
rubyzip (2.3.0)
|
||||
security (0.1.3)
|
||||
signet (0.14.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.8)
|
||||
CFPropertyList
|
||||
naturally
|
||||
slack-notifier (2.3.2)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.7)
|
||||
unicode-display_width (1.7.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.19.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.0)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
fastlane
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.4
|
||||
42
README.md
@@ -1,42 +0,0 @@
|
||||
# Loyalty Card Locker
|
||||
[](https://travis-ci.org/brarcher/loyalty-card-locker)
|
||||
|
||||
[](https://f-droid.org/repository/browse/?fdid=protect.card_locker "Loyalty Card Locker on F-Droid")
|
||||
|
||||
|
||||
Stores all of your store loyalty cards on your phone, removing the need to carry them around. Currently the following barcode types are supported:
|
||||
|
||||
- AZTEC
|
||||
- CODABAR
|
||||
- CODE_39
|
||||
- CODE_128
|
||||
- DATA_MATRIX
|
||||
- EAN_8
|
||||
- EAN_13
|
||||
- ITF
|
||||
- PDF_417
|
||||
- QR_CODE
|
||||
- UPC_A
|
||||
|
||||
If there is any interest in improving this project, kindly submit a pull request with
|
||||
proposed changes.
|
||||
|
||||
# Building
|
||||
|
||||
To build, use the gradle wrapper scripts provided in the top level directory of the project. The following will
|
||||
compile the application and run all unit tests:
|
||||
|
||||
GNU/Linux, OSX, UNIX:
|
||||
```
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
Windows:
|
||||
```
|
||||
./gradlew.bat build
|
||||
```
|
||||
|
||||
# Thanks
|
||||
|
||||
App icon originals by [Freepik](https://www.flaticon.com) and distributed under the [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/) license,
|
||||
and formatted using [Android Asset Studio](https://romannurik.github.io/AndroidAssetStudio/index.html).
|
||||
@@ -7,50 +7,65 @@ findbugs {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.2"
|
||||
compileSdkVersion 29
|
||||
|
||||
defaultConfig {
|
||||
applicationId "protect.card_locker"
|
||||
minSdkVersion 11
|
||||
targetSdkVersion 23
|
||||
versionCode 7
|
||||
versionName "0.7"
|
||||
applicationId "me.hackerchick.catima"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
versionCode 44
|
||||
versionName "1.2.0"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
}
|
||||
}
|
||||
lintOptions {
|
||||
disable "GoogleAppIndexingWarning"
|
||||
disable "ButtonStyle"
|
||||
disable "AlwaysShowAction"
|
||||
disable "MissingTranslation"
|
||||
disable "MissingPrefix"
|
||||
}
|
||||
|
||||
// This is for Robolectric support for SDK 23
|
||||
useLibrary 'org.apache.http.legacy'
|
||||
// Starting with Android Studio 3 Robolectric is unable to find resources.
|
||||
// The following allows it to find the resources.
|
||||
testOptions {
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:appcompat-v7:23.1.1'
|
||||
compile 'com.android.support:design:23.1.1'
|
||||
compile 'com.journeyapps:zxing-android-embedded:3.2.0@aar'
|
||||
compile 'com.google.zxing:core:3.2.1'
|
||||
compile 'org.apache.commons:commons-csv:1.2'
|
||||
compile group: 'com.google.guava', name: 'guava', version: '18.0'
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile "org.robolectric:robolectric:3.0"
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'com.journeyapps:zxing-android-embedded:3.5.0@aar'
|
||||
implementation 'com.google.zxing:core:3.3.0'
|
||||
implementation 'org.apache.commons:commons-csv:1.5'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'com.jaredrummler:colorpicker:1.0.2'
|
||||
implementation group: 'com.google.guava', name: 'guava', version: '20.0'
|
||||
implementation 'com.github.apl-devs:appintro:v4.2.0'
|
||||
implementation "com.vanniktech:vntnumberpickerpreference:1.0.0"
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation "org.robolectric:robolectric:4.0.2"
|
||||
}
|
||||
|
||||
task findbugs(type: FindBugs, dependsOn: assembleDebug) {
|
||||
task findbugs(type: FindBugs, dependsOn: 'assembleDebug') {
|
||||
|
||||
description 'Run findbugs'
|
||||
group 'verification'
|
||||
|
||||
classes = fileTree('build/intermediates/classes/debug/')
|
||||
classes = fileTree('build/intermediates/javac/debug/compileDebugJavaWithJavac/classes')
|
||||
source = fileTree('src/main/java')
|
||||
classpath = files()
|
||||
|
||||
|
||||
7
app/proguard-rules.pro
vendored
@@ -15,3 +15,10 @@
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# This keep the class and method names the same, for debugging stack traces
|
||||
-dontobfuscate
|
||||
BIN
app/release/app-release.apk
Normal file
1
app/release/output.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":41,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
|
||||
@@ -12,4 +12,4 @@ public class ApplicationTest extends ApplicationTestCase<Application>
|
||||
{
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:name=".LoyaltyCardLockerApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
@@ -32,22 +33,65 @@
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ManageGroupsActivity"
|
||||
android:label="@string/groups"
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".LoyaltyCardViewActivity"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:windowSoftInputMode="stateHidden"/>
|
||||
android:label=""
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:exported="true"/>
|
||||
<activity
|
||||
android:name=".LoyaltyCardEditActivity"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:exported="true">
|
||||
<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" />
|
||||
<!-- Accepts URIs that begin with "https://github.com/brarcher/loyalty-card-locker/” -->
|
||||
<data android:scheme="https"
|
||||
android:host="@string/intent_import_card_from_url_host"
|
||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix" />
|
||||
<data android:scheme="https"
|
||||
android:host="@string/intent_import_card_from_url_host_old"
|
||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_old" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".BarcodeSelectorActivity"
|
||||
android:label="@string/selectBarcodeTitle"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:windowSoftInputMode="stateHidden"/>
|
||||
<activity
|
||||
android:name=".preferences.SettingsActivity"
|
||||
android:label="@string/settings"/>
|
||||
<activity
|
||||
android:name=".ImportExportActivity"
|
||||
android:label="@string/importExport"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:theme="@style/AppTheme.NoActionBar"/>
|
||||
<activity
|
||||
android:name=".CardShortcutConfigure"
|
||||
android:label="@string/cardShortcut"
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.CREATE_SHORTCUT"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false"
|
||||
android:authorities="${applicationId}">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_provider_paths"/>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
BIN
app/src/main/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
@@ -5,6 +5,7 @@ import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
@@ -20,28 +21,88 @@ import java.lang.ref.WeakReference;
|
||||
*/
|
||||
class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
// When drawn in a smaller window 1D barcodes for some reason end up
|
||||
// squished, whereas 2D barcodes look fine.
|
||||
private static final int MAX_WIDTH_1D = 1500;
|
||||
private static final int MAX_WIDTH_2D = 500;
|
||||
|
||||
private final WeakReference<ImageView> imageViewReference;
|
||||
private final WeakReference<TextView> textViewReference;
|
||||
private final String cardId;
|
||||
private final BarcodeFormat format;
|
||||
private final int imageHeight;
|
||||
private final int imageWidth;
|
||||
|
||||
public BarcodeImageWriterTask(ImageView imageView, String cardIdString,
|
||||
BarcodeFormat barcodeFormat)
|
||||
BarcodeImageWriterTask(ImageView imageView, String cardIdString,
|
||||
BarcodeFormat barcodeFormat, TextView textView)
|
||||
{
|
||||
// Use a WeakReference to ensure the ImageView can be garbage collected
|
||||
imageViewReference = new WeakReference<>(imageView);
|
||||
textViewReference = new WeakReference<>(textView);
|
||||
|
||||
cardId = cardIdString;
|
||||
format = barcodeFormat;
|
||||
imageHeight = imageView.getHeight();
|
||||
imageWidth = imageView.getWidth();
|
||||
|
||||
final int MAX_WIDTH = getMaxWidth(format);
|
||||
|
||||
if(imageView.getWidth() < MAX_WIDTH)
|
||||
{
|
||||
imageHeight = imageView.getHeight();
|
||||
imageWidth = imageView.getWidth();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scale down the image to reduce the memory needed to produce it
|
||||
imageWidth = MAX_WIDTH;
|
||||
double ratio = (double)MAX_WIDTH / (double)imageView.getWidth();
|
||||
imageHeight = (int)(imageView.getHeight() * ratio);
|
||||
}
|
||||
}
|
||||
|
||||
BarcodeImageWriterTask(ImageView imageView, String cardIdString, BarcodeFormat barcodeFormat)
|
||||
{
|
||||
this(imageView, cardIdString, barcodeFormat, null);
|
||||
}
|
||||
|
||||
private int getMaxWidth(BarcodeFormat format)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap doInBackground(Void... params)
|
||||
{
|
||||
if (cardId.isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
MultiFormatWriter writer = new MultiFormatWriter();
|
||||
BitMatrix bitMatrix;
|
||||
try
|
||||
@@ -101,6 +162,12 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
||||
{
|
||||
Log.e(TAG, "Failed to generate barcode of type " + format + ": " + cardId, e);
|
||||
}
|
||||
catch(OutOfMemoryError e)
|
||||
{
|
||||
Log.w(TAG, "Insufficient memory to render barcode, "
|
||||
+ imageWidth + "x" + imageHeight + ", " + format.name()
|
||||
+ ", length=" + cardId.length(), e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -116,16 +183,25 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
||||
}
|
||||
|
||||
imageView.setImageBitmap(result);
|
||||
TextView textView = textViewReference.get();
|
||||
|
||||
if(result != null)
|
||||
{
|
||||
Log.i(TAG, "Displaying barcode");
|
||||
imageView.setVisibility(View.VISIBLE);
|
||||
|
||||
if (textView != null) {
|
||||
textView.setVisibility(View.VISIBLE);
|
||||
textView.setText(format.name());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.i(TAG, "Barcode generation failed, removing image from display");
|
||||
imageView.setVisibility(View.GONE);
|
||||
if (textView != null) {
|
||||
textView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,19 @@ import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
@@ -34,7 +36,7 @@ import java.util.Map;
|
||||
*/
|
||||
public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
// Result this activity will return
|
||||
public static final String BARCODE_CONTENTS = "contents";
|
||||
@@ -58,7 +60,7 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
BarcodeFormat.UPC_A.name()
|
||||
));
|
||||
|
||||
private Map<String, Integer> barcodeViewMap;
|
||||
private Map<String, Pair<Integer, Integer>> barcodeViewMap;
|
||||
private LinkedList<AsyncTask> barcodeGeneratorTasks = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
@@ -67,7 +69,7 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.barcode_selector_activity);
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
@@ -75,21 +77,21 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
barcodeViewMap = ImmutableMap.<String, Integer>builder()
|
||||
.put(BarcodeFormat.AZTEC.name(), R.id.aztecBarcode)
|
||||
.put(BarcodeFormat.CODE_39.name(), R.id.code39Barcode)
|
||||
.put(BarcodeFormat.CODE_128.name(), R.id.code128Barcode)
|
||||
.put(BarcodeFormat.CODABAR.name(), R.id.codabarBarcode)
|
||||
.put(BarcodeFormat.DATA_MATRIX.name(), R.id.datamatrixBarcode)
|
||||
.put(BarcodeFormat.EAN_8.name(), R.id.ean8Barcode)
|
||||
.put(BarcodeFormat.EAN_13.name(), R.id.ean13Barcode)
|
||||
.put(BarcodeFormat.ITF.name(), R.id.itfBarcode)
|
||||
.put(BarcodeFormat.PDF_417.name(), R.id.pdf417Barcode)
|
||||
.put(BarcodeFormat.QR_CODE.name(), R.id.qrcodeBarcode)
|
||||
.put(BarcodeFormat.UPC_A.name(), R.id.upcaBarcode)
|
||||
barcodeViewMap = ImmutableMap.<String, Pair<Integer, Integer>>builder()
|
||||
.put(BarcodeFormat.AZTEC.name(), new Pair<>(R.id.aztecBarcode, R.id.aztecBarcodeText))
|
||||
.put(BarcodeFormat.CODE_39.name(), new Pair<>(R.id.code39Barcode, R.id.code39BarcodeText))
|
||||
.put(BarcodeFormat.CODE_128.name(), new Pair<>(R.id.code128Barcode, R.id.code128BarcodeText))
|
||||
.put(BarcodeFormat.CODABAR.name(), new Pair<>(R.id.codabarBarcode, R.id.codabarBarcodeText))
|
||||
.put(BarcodeFormat.DATA_MATRIX.name(), new Pair<>(R.id.datamatrixBarcode, R.id.datamatrixBarcodeText))
|
||||
.put(BarcodeFormat.EAN_8.name(), new Pair<>(R.id.ean8Barcode, R.id.ean8BarcodeText))
|
||||
.put(BarcodeFormat.EAN_13.name(), new Pair<>(R.id.ean13Barcode, R.id.ean13BarcodeText))
|
||||
.put(BarcodeFormat.ITF.name(), new Pair<>(R.id.itfBarcode, R.id.itfBarcodeText))
|
||||
.put(BarcodeFormat.PDF_417.name(), new Pair<>(R.id.pdf417Barcode, R.id.pdf417BarcodeText))
|
||||
.put(BarcodeFormat.QR_CODE.name(), new Pair<>(R.id.qrcodeBarcode, R.id.qrcodeBarcodeText))
|
||||
.put(BarcodeFormat.UPC_A.name(), new Pair<>(R.id.upcaBarcode, R.id.upcaBarcodeText))
|
||||
.build();
|
||||
|
||||
EditText cardId = (EditText) findViewById(R.id.cardId);
|
||||
EditText cardId = findViewById(R.id.cardId);
|
||||
cardId.addTextChangedListener(new TextWatcher()
|
||||
{
|
||||
@Override
|
||||
@@ -113,9 +115,14 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
// Update barcodes
|
||||
for(String key : barcodeViewMap.keySet())
|
||||
{
|
||||
ImageView image = (ImageView)findViewById(barcodeViewMap.get(key));
|
||||
createBarcodeOption(image, key, s.toString());
|
||||
ImageView image = findViewById(barcodeViewMap.get(key).first);
|
||||
TextView text = findViewById(barcodeViewMap.get(key).second);
|
||||
createBarcodeOption(image, key, s.toString(), text);
|
||||
}
|
||||
|
||||
View noBarcodeButtonView = findViewById(R.id.noBarcode);
|
||||
setButtonListener(noBarcodeButtonView, s.toString());
|
||||
noBarcodeButtonView.setEnabled(s.length() > 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,9 +131,32 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
// Noting to do
|
||||
}
|
||||
});
|
||||
|
||||
final Bundle b = getIntent().getExtras();
|
||||
final String initialCardId = b != null ? b.getString("initialCardId") : null;
|
||||
|
||||
if(initialCardId != null)
|
||||
{
|
||||
cardId.setText(initialCardId);
|
||||
}
|
||||
}
|
||||
|
||||
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId)
|
||||
private void setButtonListener(final View button, final String cardId)
|
||||
{
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Log.d(TAG, "Selected no barcode");
|
||||
Intent result = new Intent();
|
||||
result.putExtra(BARCODE_FORMAT, "");
|
||||
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)
|
||||
@@ -136,7 +166,6 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
image.setImageBitmap(null);
|
||||
image.setVisibility(View.GONE);
|
||||
image.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
@@ -172,7 +201,7 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
Log.d(TAG, "Generating barcode for type " + formatType);
|
||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format);
|
||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format, text);
|
||||
barcodeGeneratorTasks.add(task);
|
||||
task.execute();
|
||||
}
|
||||
@@ -181,7 +210,7 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
else
|
||||
{
|
||||
Log.d(TAG, "Generating barcode for type " + formatType);
|
||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format);
|
||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format, text);
|
||||
barcodeGeneratorTasks.add(task);
|
||||
task.execute();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* The configuration screen for creating a shortcut.
|
||||
*/
|
||||
public class CardShortcutConfigure extends AppCompatActivity
|
||||
{
|
||||
static final String TAG = "Catima";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle)
|
||||
{
|
||||
super.onCreate(bundle);
|
||||
|
||||
// Set the result to CANCELED. This will cause nothing to happen if the
|
||||
// aback button is pressed.
|
||||
setResult(RESULT_CANCELED);
|
||||
|
||||
setContentView(R.layout.main_activity);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
toolbar.setVisibility(View.GONE);
|
||||
|
||||
final DBHelper db = new DBHelper(this);
|
||||
|
||||
// If there are no cards, bail
|
||||
if(db.getLoyaltyCardCount() == 0)
|
||||
{
|
||||
Toast.makeText(this, R.string.noCardsMessage, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
final ListView cardList = findViewById(R.id.list);
|
||||
cardList.setVisibility(View.VISIBLE);
|
||||
|
||||
Cursor cardCursor = db.getLoyaltyCardCursor();
|
||||
|
||||
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor);
|
||||
cardList.setAdapter(adapter);
|
||||
|
||||
cardList.setOnItemClickListener(new AdapterView.OnItemClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
Cursor selected = (Cursor) parent.getItemAtPosition(position);
|
||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
|
||||
|
||||
Log.d(TAG, "Creating shortcut for card " + loyaltyCard.store + "," + loyaltyCard.id);
|
||||
|
||||
Intent shortcutIntent = new Intent(CardShortcutConfigure.this, LoyaltyCardViewActivity.class);
|
||||
shortcutIntent.setAction(Intent.ACTION_MAIN);
|
||||
// Prevent instances of the view activity from piling up; if one exists let this
|
||||
// one replace it.
|
||||
shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt("id", loyaltyCard.id);
|
||||
bundle.putBoolean("view", true);
|
||||
shortcutIntent.putExtras(bundle);
|
||||
|
||||
Parcelable icon = Intent.ShortcutIconResource.fromContext(CardShortcutConfigure.this, R.mipmap.ic_launcher);
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, loyaltyCard.store);
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon);
|
||||
setResult(RESULT_OK, intent);
|
||||
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -18,24 +18,21 @@ public class CsvDatabaseExporter implements DatabaseExporter
|
||||
{
|
||||
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
|
||||
|
||||
// Print the header
|
||||
printer.printRecord(DBHelper.LoyaltyCardDbIds.ID,
|
||||
DBHelper.LoyaltyCardDbIds.STORE,
|
||||
DBHelper.LoyaltyCardDbIds.NOTE,
|
||||
DBHelper.LoyaltyCardDbIds.CARD_ID,
|
||||
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
|
||||
// Print the version
|
||||
printer.printRecord("2");
|
||||
|
||||
Cursor cursor = db.getLoyaltyCardCursor();
|
||||
printer.println();
|
||||
|
||||
while(cursor.moveToNext())
|
||||
// Print the header for groups
|
||||
printer.printRecord(DBHelper.LoyaltyCardDbGroups.ID);
|
||||
|
||||
Cursor groupCursor = db.getGroupCursor();
|
||||
|
||||
while(groupCursor.moveToNext())
|
||||
{
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
|
||||
Group group = Group.toGroup(groupCursor);
|
||||
|
||||
printer.printRecord(card.id,
|
||||
card.store,
|
||||
card.note,
|
||||
card.cardId,
|
||||
card.barcodeType);
|
||||
printer.printRecord(group._id);
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
@@ -43,7 +40,66 @@ public class CsvDatabaseExporter implements DatabaseExporter
|
||||
}
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
groupCursor.close();
|
||||
|
||||
// Print an empty line
|
||||
printer.println();
|
||||
|
||||
// Print the header for cards
|
||||
printer.printRecord(DBHelper.LoyaltyCardDbIds.ID,
|
||||
DBHelper.LoyaltyCardDbIds.STORE,
|
||||
DBHelper.LoyaltyCardDbIds.NOTE,
|
||||
DBHelper.LoyaltyCardDbIds.CARD_ID,
|
||||
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
|
||||
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
|
||||
DBHelper.LoyaltyCardDbIds.STAR_STATUS);
|
||||
|
||||
Cursor cardCursor = db.getLoyaltyCardCursor();
|
||||
|
||||
while(cardCursor.moveToNext())
|
||||
{
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
|
||||
|
||||
printer.printRecord(card.id,
|
||||
card.store,
|
||||
card.note,
|
||||
card.cardId,
|
||||
card.headerColor,
|
||||
card.barcodeType,
|
||||
card.starStatus);
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
cardCursor.close();
|
||||
|
||||
// Print an empty line
|
||||
printer.println();
|
||||
|
||||
// Print the header for card group mappings
|
||||
printer.printRecord(DBHelper.LoyaltyCardDbIdsGroups.cardID,
|
||||
DBHelper.LoyaltyCardDbIdsGroups.groupID);
|
||||
|
||||
Cursor cardCursor2 = db.getLoyaltyCardCursor();
|
||||
|
||||
while(cardCursor2.moveToNext())
|
||||
{
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor2);
|
||||
|
||||
for (Group group : db.getLoyaltyCardGroups(card.id)) {
|
||||
printer.printRecord(card.id, group._id);
|
||||
}
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
cardCursor2.close();
|
||||
|
||||
printer.close();
|
||||
}
|
||||
|
||||
@@ -6,8 +6,11 @@ import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class for importing a database from CSV (Comma Separate Values)
|
||||
@@ -19,6 +22,34 @@ import java.io.InputStreamReader;
|
||||
public class CsvDatabaseImporter implements DatabaseImporter
|
||||
{
|
||||
public void importData(DBHelper db, InputStreamReader input) throws IOException, FormatException, InterruptedException
|
||||
{
|
||||
BufferedReader bufferedReader = new BufferedReader(input);
|
||||
|
||||
bufferedReader.mark(100);
|
||||
|
||||
Integer version = 1;
|
||||
|
||||
try {
|
||||
version = Integer.parseInt(bufferedReader.readLine());
|
||||
} catch (NumberFormatException _e) {
|
||||
// Assume version 1
|
||||
}
|
||||
|
||||
bufferedReader.reset();
|
||||
|
||||
switch (version) {
|
||||
case 1:
|
||||
parseV1(db, bufferedReader);
|
||||
break;
|
||||
case 2:
|
||||
parseV2(db, bufferedReader);
|
||||
break;
|
||||
default:
|
||||
throw new FormatException(String.format("No code to parse version %s", version));
|
||||
}
|
||||
}
|
||||
|
||||
public void parseV1(DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
|
||||
{
|
||||
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.withHeader());
|
||||
|
||||
@@ -40,7 +71,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
parser.close();
|
||||
database.setTransactionSuccessful();
|
||||
}
|
||||
catch(IllegalArgumentException e)
|
||||
catch(IllegalArgumentException|IllegalStateException e)
|
||||
{
|
||||
throw new FormatException("Issue parsing CSV data", e);
|
||||
}
|
||||
@@ -51,6 +82,116 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
}
|
||||
}
|
||||
|
||||
public void parseV2(DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
|
||||
{
|
||||
SQLiteDatabase database = db.getWritableDatabase();
|
||||
database.beginTransaction();
|
||||
|
||||
Integer part = 0;
|
||||
String stringPart = "";
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
String tmp = input.readLine();
|
||||
|
||||
if (tmp == null || tmp.isEmpty()) {
|
||||
switch (part) {
|
||||
case 0:
|
||||
// This is the version info, ignore
|
||||
break;
|
||||
case 1:
|
||||
parseV2Groups(db, database, stringPart);
|
||||
break;
|
||||
case 2:
|
||||
parseV2Cards(db, database, stringPart);
|
||||
break;
|
||||
case 3:
|
||||
parseV2CardGroups(db, database, stringPart);
|
||||
break;
|
||||
default:
|
||||
throw new FormatException("Issue parsing CSV data, too many parts for v2 parsing");
|
||||
}
|
||||
|
||||
if (tmp == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
part += 1;
|
||||
stringPart = "";
|
||||
} else {
|
||||
stringPart += tmp + "\n";
|
||||
}
|
||||
}
|
||||
database.setTransactionSuccessful();
|
||||
} catch (FormatException e) {
|
||||
throw new FormatException("Issue parsing CSV data", e);
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void parseV2Groups(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
|
||||
{
|
||||
// Parse groups
|
||||
final CSVParser groupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.withHeader());
|
||||
|
||||
try {
|
||||
for (CSVRecord record : groupParser) {
|
||||
importGroup(database, db, record);
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
groupParser.close();
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw new FormatException("Issue parsing CSV data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void parseV2Cards(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
|
||||
{
|
||||
// Parse cards
|
||||
final CSVParser cardParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.withHeader());
|
||||
|
||||
try {
|
||||
for (CSVRecord record : cardParser) {
|
||||
importLoyaltyCard(database, db, record);
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
cardParser.close();
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw new FormatException("Issue parsing CSV data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void parseV2CardGroups(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
|
||||
{
|
||||
// Parse card group mappings
|
||||
final CSVParser cardGroupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.withHeader());
|
||||
|
||||
try {
|
||||
for (CSVRecord record : cardGroupParser) {
|
||||
importCardGroupMapping(database, db, record);
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
cardGroupParser.close();
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw new FormatException("Issue parsing CSV data", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a string from the items array. The index into the array
|
||||
* is determined by looking up the index in the fields map using the
|
||||
@@ -83,7 +224,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
* "key" as the key. If no such key exists, or the data is not a valid
|
||||
* int, a FormatException is thrown.
|
||||
*/
|
||||
private int extractInt(String key, CSVRecord record)
|
||||
private Integer extractInt(String key, CSVRecord record, boolean nullIsOk)
|
||||
throws FormatException
|
||||
{
|
||||
if(record.isMapped(key) == false)
|
||||
@@ -91,6 +232,12 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
throw new FormatException("Field not used but expected: " + key);
|
||||
}
|
||||
|
||||
String value = record.get(key);
|
||||
if(value.isEmpty() && nullIsOk)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Integer.parseInt(record.get(key));
|
||||
@@ -108,7 +255,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record)
|
||||
throws IOException, FormatException
|
||||
{
|
||||
int id = extractInt(DBHelper.LoyaltyCardDbIds.ID, record);
|
||||
int id = extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false);
|
||||
|
||||
String store = extractString(DBHelper.LoyaltyCardDbIds.STORE, record, "");
|
||||
if(store.isEmpty())
|
||||
@@ -125,11 +272,49 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
}
|
||||
|
||||
String barcodeType = extractString(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, record, "");
|
||||
if(barcodeType.isEmpty())
|
||||
|
||||
Integer headerColor = null;
|
||||
|
||||
if(record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR))
|
||||
{
|
||||
throw new FormatException("No barcode type listed, but is required");
|
||||
headerColor = extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record, true);
|
||||
}
|
||||
|
||||
helper.insertLoyaltyCard(database, id, store, note, cardId, barcodeType);
|
||||
int starStatus = 0;
|
||||
try {
|
||||
starStatus = extractInt(DBHelper.LoyaltyCardDbIds.STAR_STATUS, record, false);
|
||||
} catch (FormatException _e ) {
|
||||
// This field did not exist in versions 0.28 and before
|
||||
// We catch this exception so we can still import old backups
|
||||
}
|
||||
if (starStatus != 1) starStatus = 0;
|
||||
helper.insertLoyaltyCard(database, id, store, note, cardId, barcodeType, headerColor, starStatus);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a single group into the database using the given
|
||||
* session.
|
||||
*/
|
||||
private void importGroup(SQLiteDatabase database, DBHelper helper, CSVRecord record)
|
||||
throws IOException, FormatException
|
||||
{
|
||||
String id = extractString(DBHelper.LoyaltyCardDbGroups.ID, record, null);
|
||||
|
||||
helper.insertGroup(database, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a single card to group mapping into the database using the given
|
||||
* session.
|
||||
*/
|
||||
private void importCardGroupMapping(SQLiteDatabase database, DBHelper helper, CSVRecord record)
|
||||
throws IOException, FormatException
|
||||
{
|
||||
Integer cardId = extractInt(DBHelper.LoyaltyCardDbIdsGroups.cardID, record, false);
|
||||
String groupId = extractString(DBHelper.LoyaltyCardDbIdsGroups.groupID, record, null);
|
||||
|
||||
List<Group> cardGroups = helper.getLoyaltyCardGroups(cardId);
|
||||
cardGroups.add(helper.getGroup(groupId));
|
||||
helper.setLoyaltyCardGroups(database, cardId, cardGroups);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,26 @@
|
||||
package protect.card_locker;
|
||||
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.graphics.Color;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DBHelper extends SQLiteOpenHelper
|
||||
{
|
||||
public static final String DATABASE_NAME = "LoyaltyCards.db";
|
||||
public static final String DATABASE_NAME = "Catima.db";
|
||||
public static final int ORIGINAL_DATABASE_VERSION = 1;
|
||||
public static final int DATABASE_VERSION = 2;
|
||||
public static final int DATABASE_VERSION = 5;
|
||||
|
||||
static class LoyaltyCardDbGroups
|
||||
{
|
||||
public static final String TABLE = "groups";
|
||||
public static final String ID = "_id";
|
||||
}
|
||||
|
||||
static class LoyaltyCardDbIds
|
||||
{
|
||||
@@ -19,8 +28,18 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
public static final String ID = "_id";
|
||||
public static final String STORE = "store";
|
||||
public static final String NOTE = "note";
|
||||
public static final String HEADER_COLOR = "headercolor";
|
||||
public static final String HEADER_TEXT_COLOR = "headertextcolor";
|
||||
public static final String CARD_ID = "cardid";
|
||||
public static final String BARCODE_TYPE = "barcodetype";
|
||||
public static final String STAR_STATUS = "starstatus";
|
||||
}
|
||||
|
||||
static class LoyaltyCardDbIdsGroups
|
||||
{
|
||||
public static final String TABLE = "cardsGroups";
|
||||
public static final String cardID = "cardId";
|
||||
public static final String groupID = "groupId";
|
||||
}
|
||||
|
||||
public DBHelper(Context context)
|
||||
@@ -31,13 +50,26 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db)
|
||||
{
|
||||
// create table for gift cards
|
||||
// create table for card groups
|
||||
db.execSQL("create table " + LoyaltyCardDbGroups.TABLE + "(" +
|
||||
LoyaltyCardDbGroups.ID + " TEXT primary key not null)");
|
||||
|
||||
// create table for cards
|
||||
db.execSQL("create table " + LoyaltyCardDbIds.TABLE + "(" +
|
||||
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
|
||||
LoyaltyCardDbIds.STORE + " TEXT not null," +
|
||||
LoyaltyCardDbIds.NOTE + " TEXT not null," +
|
||||
LoyaltyCardDbIds.HEADER_COLOR + " INTEGER," +
|
||||
LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER," +
|
||||
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
|
||||
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT not null)");
|
||||
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT not null," +
|
||||
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0' )");
|
||||
|
||||
// create associative table for cards in groups
|
||||
db.execSQL("create table " + LoyaltyCardDbIdsGroups.TABLE + "(" +
|
||||
LoyaltyCardDbIdsGroups.cardID + " INTEGER," +
|
||||
LoyaltyCardDbIdsGroups.groupID + " TEXT," +
|
||||
"primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID +"))");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,10 +81,39 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||
+ " ADD COLUMN " + LoyaltyCardDbIds.NOTE + " TEXT not null default ''");
|
||||
}
|
||||
|
||||
// Upgrade from version 2 to version 3
|
||||
if(oldVersion < 3 && newVersion >= 3)
|
||||
{
|
||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||
+ " ADD COLUMN " + LoyaltyCardDbIds.HEADER_COLOR + " INTEGER");
|
||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||
+ " ADD COLUMN " + LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER");
|
||||
}
|
||||
|
||||
// Upgrade from version 3 to version 4
|
||||
if(oldVersion < 4 && newVersion >= 4)
|
||||
{
|
||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||
+ " ADD COLUMN " + LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'");
|
||||
}
|
||||
|
||||
// Upgrade from version 4 to version 5
|
||||
if(oldVersion < 5 && newVersion >= 5)
|
||||
{
|
||||
db.execSQL("create table " + LoyaltyCardDbGroups.TABLE + "(" +
|
||||
LoyaltyCardDbGroups.ID + " TEXT primary key not null)");
|
||||
|
||||
db.execSQL("create table " + LoyaltyCardDbIdsGroups.TABLE + "(" +
|
||||
LoyaltyCardDbIdsGroups.cardID + " INTEGER," +
|
||||
LoyaltyCardDbIdsGroups.groupID + " TEXT," +
|
||||
"primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID +"))");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean insertLoyaltyCard(final String store, final String note, final String cardId,
|
||||
final String barcodeType)
|
||||
public long insertLoyaltyCard(final String store, final String note, final String cardId,
|
||||
final String barcodeType, final Integer headerColor,
|
||||
final int starStatus)
|
||||
{
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
@@ -60,13 +121,17 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
contentValues.put(LoyaltyCardDbIds.NOTE, note);
|
||||
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
||||
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, Color.WHITE);
|
||||
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
|
||||
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
|
||||
return (newId != -1);
|
||||
return newId;
|
||||
}
|
||||
|
||||
public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id,
|
||||
final String store, final String note, final String cardId,
|
||||
final String barcodeType)
|
||||
public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id, final String store,
|
||||
final String note, final String cardId,
|
||||
final String barcodeType, final Integer headerColor,
|
||||
final int starStatus)
|
||||
{
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(LoyaltyCardDbIds.ID, id);
|
||||
@@ -74,13 +139,16 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
contentValues.put(LoyaltyCardDbIds.NOTE, note);
|
||||
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
||||
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, Color.WHITE);
|
||||
contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus);
|
||||
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
|
||||
return (newId != -1);
|
||||
}
|
||||
|
||||
|
||||
public boolean updateLoyaltyCard(final int id, final String store, final String note,
|
||||
final String cardId, final String barcodeType)
|
||||
final String cardId, final String barcodeType,
|
||||
final Integer headerColor)
|
||||
{
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
@@ -88,6 +156,19 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
contentValues.put(LoyaltyCardDbIds.NOTE, note);
|
||||
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
||||
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, Color.WHITE);
|
||||
int rowsUpdated = db.update(LoyaltyCardDbIds.TABLE, contentValues,
|
||||
LoyaltyCardDbIds.ID + "=?",
|
||||
new String[]{Integer.toString(id)});
|
||||
return (rowsUpdated == 1);
|
||||
}
|
||||
|
||||
public boolean updateLoyaltyCardStarStatus(final int id, final int starStatus)
|
||||
{
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus);
|
||||
int rowsUpdated = db.update(LoyaltyCardDbIds.TABLE, contentValues,
|
||||
LoyaltyCardDbIds.ID + "=?",
|
||||
new String[]{Integer.toString(id)});
|
||||
@@ -105,6 +186,7 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
if(data.getCount() == 1)
|
||||
{
|
||||
data.moveToFirst();
|
||||
|
||||
card = LoyaltyCard.toLoyaltyCard(data);
|
||||
}
|
||||
|
||||
@@ -113,27 +195,330 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
return card;
|
||||
}
|
||||
|
||||
public List<Group> getLoyaltyCardGroups(final int id)
|
||||
{
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor data = db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE + " g " +
|
||||
" LEFT JOIN " + LoyaltyCardDbIdsGroups.TABLE + " ig ON ig." + LoyaltyCardDbIdsGroups.groupID + " = g." + LoyaltyCardDbGroups.ID +
|
||||
" where " + LoyaltyCardDbIdsGroups.cardID + "=?" +
|
||||
" ORDER BY " + LoyaltyCardDbIdsGroups.groupID, new String[]{String.format("%d", id)});
|
||||
|
||||
List<Group> groups = new ArrayList<>();
|
||||
|
||||
if (!data.moveToFirst()) {
|
||||
return groups;
|
||||
}
|
||||
|
||||
groups.add(Group.toGroup(data));
|
||||
|
||||
while (data.moveToNext()) {
|
||||
groups.add(Group.toGroup(data));
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
public void setLoyaltyCardGroups(final int id, List<Group> groups)
|
||||
{
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
|
||||
// First delete lookup table entries associated with this card
|
||||
db.delete(LoyaltyCardDbIdsGroups.TABLE,
|
||||
LoyaltyCardDbIdsGroups.cardID + " = ? ",
|
||||
new String[]{String.format("%d", id)});
|
||||
|
||||
// Then create entries for selected values
|
||||
for (Group group : groups) {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(LoyaltyCardDbIdsGroups.cardID, id);
|
||||
contentValues.put(LoyaltyCardDbIdsGroups.groupID, group._id);
|
||||
db.insert(LoyaltyCardDbIdsGroups.TABLE, null, contentValues);
|
||||
}
|
||||
}
|
||||
|
||||
public void setLoyaltyCardGroups(final SQLiteDatabase db, final int id, List<Group> groups)
|
||||
{
|
||||
// First delete lookup table entries associated with this card
|
||||
db.delete(LoyaltyCardDbIdsGroups.TABLE,
|
||||
LoyaltyCardDbIdsGroups.cardID + " = ? ",
|
||||
new String[]{String.format("%d", id)});
|
||||
|
||||
// Then create entries for selected values
|
||||
for (Group group : groups) {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(LoyaltyCardDbIdsGroups.cardID, id);
|
||||
contentValues.put(LoyaltyCardDbIdsGroups.groupID, group._id);
|
||||
db.insert(LoyaltyCardDbIdsGroups.TABLE, null, contentValues);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean deleteLoyaltyCard (final int id)
|
||||
{
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
int rowsDeleted = db.delete(LoyaltyCardDbIds.TABLE,
|
||||
// Delete card
|
||||
int rowsDeleted = db.delete(LoyaltyCardDbIds.TABLE,
|
||||
LoyaltyCardDbIds.ID + " = ? ",
|
||||
new String[]{String.format("%d", id)});
|
||||
|
||||
// And delete lookup table entries associated with this card
|
||||
db.delete(LoyaltyCardDbIdsGroups.TABLE,
|
||||
LoyaltyCardDbIdsGroups.cardID + " = ? ",
|
||||
new String[]{String.format("%d", id)});
|
||||
|
||||
return (rowsDeleted == 1);
|
||||
}
|
||||
|
||||
public Cursor getLoyaltyCardCursor()
|
||||
{
|
||||
// An empty string will match everything
|
||||
return getLoyaltyCardCursor("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cursor to all loyalty cards with the filter text in either the store or note.
|
||||
*
|
||||
* @param filter
|
||||
* @return Cursor
|
||||
*/
|
||||
public Cursor getLoyaltyCardCursor(final String filter)
|
||||
{
|
||||
return getLoyaltyCardCursor(filter, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cursor to all loyalty cards with the filter text in either the store or note in a certain group.
|
||||
*
|
||||
* @param filter
|
||||
* @param group
|
||||
* @return Cursor
|
||||
*/
|
||||
public Cursor getLoyaltyCardCursor(final String filter, Group group)
|
||||
{
|
||||
String actualFilter = String.format("%%%s%%", filter);
|
||||
String[] selectionArgs = { actualFilter, actualFilter };
|
||||
StringBuilder groupFilter = new StringBuilder();
|
||||
String limitString = "";
|
||||
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor res = db.rawQuery("select * from " + LoyaltyCardDbIds.TABLE +
|
||||
" ORDER BY " + LoyaltyCardDbIds.STORE, null);
|
||||
|
||||
if (group != null) {
|
||||
List<Integer> allowedIds = getGroupCardIds(group._id);
|
||||
|
||||
// Empty group
|
||||
if (allowedIds.size() > 0) {
|
||||
groupFilter.append("AND (");
|
||||
|
||||
for (int i = 0; i < allowedIds.size(); i++) {
|
||||
groupFilter.append(LoyaltyCardDbIds.ID + " = " + allowedIds.get(i));
|
||||
if (i != allowedIds.size() - 1) {
|
||||
groupFilter.append(" OR ");
|
||||
}
|
||||
}
|
||||
groupFilter.append(") ");
|
||||
} else {
|
||||
limitString = "LIMIT 0";
|
||||
}
|
||||
}
|
||||
|
||||
Cursor res = db.rawQuery("select * from " + LoyaltyCardDbIds.TABLE +
|
||||
" WHERE (" + LoyaltyCardDbIds.STORE + " LIKE ? " +
|
||||
" OR " + LoyaltyCardDbIds.NOTE + " LIKE ? )" +
|
||||
groupFilter.toString() +
|
||||
" ORDER BY " + LoyaltyCardDbIds.STAR_STATUS + " DESC," + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC " +
|
||||
limitString, selectionArgs, null);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public int getLoyaltyCardCount()
|
||||
{
|
||||
// An empty string will match everything
|
||||
return getLoyaltyCardCount("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of loyalty cards with the filter text in either the store or note.
|
||||
*
|
||||
* @param filter
|
||||
* @return Integer
|
||||
*/
|
||||
public int getLoyaltyCardCount(String filter)
|
||||
{
|
||||
String actualFilter = String.format("%%%s%%", filter);
|
||||
String[] selectionArgs = { actualFilter, actualFilter };
|
||||
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbIds.TABLE, null);
|
||||
Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbIds.TABLE +
|
||||
" WHERE " + LoyaltyCardDbIds.STORE + " LIKE ? " +
|
||||
" OR " + LoyaltyCardDbIds.NOTE + " LIKE ? "
|
||||
, selectionArgs, null);
|
||||
|
||||
int numItems = 0;
|
||||
|
||||
if(data.getCount() == 1)
|
||||
{
|
||||
data.moveToFirst();
|
||||
numItems = data.getInt(0);
|
||||
}
|
||||
|
||||
data.close();
|
||||
|
||||
return numItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cursor to all groups.
|
||||
*
|
||||
* @return Cursor
|
||||
*/
|
||||
public Cursor getGroupCursor()
|
||||
{
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
|
||||
Cursor res = db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE +
|
||||
" ORDER BY " + LoyaltyCardDbGroups.ID + " COLLATE NOCASE ASC", null, null);
|
||||
return res;
|
||||
}
|
||||
|
||||
public List<Group> getGroups() {
|
||||
Cursor data = getGroupCursor();
|
||||
|
||||
List<Group> groups = new ArrayList<>();
|
||||
|
||||
if (!data.moveToFirst()) {
|
||||
return groups;
|
||||
}
|
||||
|
||||
groups.add(Group.toGroup(data));
|
||||
|
||||
while (data.moveToNext()) {
|
||||
groups.add(Group.toGroup(data));
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
public Group getGroup(final String groupName)
|
||||
{
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor data = db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE +
|
||||
" where " + LoyaltyCardDbGroups.ID + "=?", new String[]{groupName});
|
||||
|
||||
Group group = null;
|
||||
|
||||
if(data.getCount() == 1)
|
||||
{
|
||||
data.moveToFirst();
|
||||
|
||||
group = Group.toGroup(data);
|
||||
}
|
||||
|
||||
data.close();
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
public int getGroupCount()
|
||||
{
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbGroups.TABLE, null);
|
||||
|
||||
int numItems = 0;
|
||||
|
||||
if(data.getCount() == 1)
|
||||
{
|
||||
data.moveToFirst();
|
||||
numItems = data.getInt(0);
|
||||
}
|
||||
|
||||
data.close();
|
||||
|
||||
return numItems;
|
||||
}
|
||||
|
||||
public List<Integer> getGroupCardIds(final String groupName)
|
||||
{
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor data = db.rawQuery("SELECT " + LoyaltyCardDbIdsGroups.cardID +
|
||||
" FROM " + LoyaltyCardDbIdsGroups.TABLE +
|
||||
" WHERE " + LoyaltyCardDbIdsGroups.groupID + " =? ", new String[]{groupName});
|
||||
|
||||
List<Integer> cardIds = new ArrayList<>();
|
||||
|
||||
if (!data.moveToFirst()) {
|
||||
return cardIds;
|
||||
}
|
||||
|
||||
cardIds.add(data.getInt(0));
|
||||
|
||||
while (data.moveToNext()) {
|
||||
cardIds.add(data.getInt(0));
|
||||
}
|
||||
|
||||
data.close();
|
||||
|
||||
return cardIds;
|
||||
}
|
||||
|
||||
public long insertGroup(final String name)
|
||||
{
|
||||
if (name.isEmpty()) return -1;
|
||||
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(LoyaltyCardDbGroups.ID, name);
|
||||
final long newId = db.insert(LoyaltyCardDbGroups.TABLE, null, contentValues);
|
||||
return newId;
|
||||
}
|
||||
|
||||
public boolean insertGroup(final SQLiteDatabase db, final String name)
|
||||
{
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(LoyaltyCardDbGroups.ID, name);
|
||||
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;
|
||||
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(LoyaltyCardDbGroups.ID, newName);
|
||||
try {
|
||||
int rowsUpdated = db.update(LoyaltyCardDbGroups.TABLE, contentValues,
|
||||
LoyaltyCardDbGroups.ID + "=?",
|
||||
new String[]{groupName});
|
||||
return (rowsUpdated == 1);
|
||||
} catch (android.database.sqlite.SQLiteConstraintException _e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean deleteGroup(final String groupName)
|
||||
{
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
// Delete group
|
||||
int rowsDeleted = db.delete(LoyaltyCardDbGroups.TABLE,
|
||||
LoyaltyCardDbGroups.ID + " = ? ",
|
||||
new String[]{groupName});
|
||||
|
||||
// And delete lookup table entries associated with this group
|
||||
db.delete(LoyaltyCardDbIdsGroups.TABLE,
|
||||
LoyaltyCardDbIdsGroups.groupID + " = ? ",
|
||||
new String[]{groupName});
|
||||
|
||||
return (rowsDeleted == 1);
|
||||
}
|
||||
|
||||
public int getGroupCardCount(final String groupName)
|
||||
{
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
|
||||
Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbIdsGroups.TABLE +
|
||||
" where " + LoyaltyCardDbIdsGroups.groupID + "=?",
|
||||
new String[]{groupName});
|
||||
|
||||
int numItems = 0;
|
||||
|
||||
|
||||
20
app/src/main/java/protect/card_locker/Group.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
public class Group
|
||||
{
|
||||
public final String _id;
|
||||
|
||||
public Group(final String _id)
|
||||
{
|
||||
this._id = _id;
|
||||
}
|
||||
|
||||
public static Group toGroup(Cursor cursor)
|
||||
{
|
||||
String _id = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbGroups.ID));
|
||||
|
||||
return new Group(_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import protect.card_locker.preferences.Settings;
|
||||
|
||||
class GroupCursorAdapter extends CursorAdapter
|
||||
{
|
||||
Settings settings;
|
||||
DBHelper db;
|
||||
|
||||
public GroupCursorAdapter(Context context, Cursor cursor)
|
||||
{
|
||||
super(context, cursor, 0);
|
||||
settings = new Settings(context);
|
||||
|
||||
db = new DBHelper(context);
|
||||
}
|
||||
|
||||
// The newView method is used to inflate a new view and return it,
|
||||
// you don't bind any data to the view at this point.
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent)
|
||||
{
|
||||
return LayoutInflater.from(context).inflate(R.layout.group_layout, parent, false);
|
||||
}
|
||||
|
||||
// The bindView method is used to bind all data to a given view
|
||||
// such as setting the text on a TextView.
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor)
|
||||
{
|
||||
// Find fields to populate in inflated template
|
||||
TextView nameField = (TextView) view.findViewById(R.id.name);
|
||||
TextView countField = (TextView) view.findViewById(R.id.cardCount);
|
||||
|
||||
// Extract properties from cursor
|
||||
Group group = Group.toGroup(cursor);
|
||||
|
||||
// Populate fields with extracted properties
|
||||
nameField.setText(group._id);
|
||||
countField.setText(String.format(context.getString(R.string.groupCardCount), db.getGroupCardCount(group._id)));
|
||||
|
||||
nameField.setTextSize(settings.getCardTitleListFontSize());
|
||||
countField.setTextSize(settings.getCardNoteListFontSize());
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,53 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
public class ImportExportActivity extends AppCompatActivity
|
||||
{
|
||||
private static final int PERMISSIONS_EXTERNAL_STORAGE_IMPORT = 1;
|
||||
private static final int PERMISSIONS_EXTERNAL_STORAGE_EXPORT = 2;
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
ImportExportTask importExporter;
|
||||
private static final int PERMISSIONS_EXTERNAL_STORAGE = 1;
|
||||
private static final int CHOOSE_EXPORT_LOCATION = 2;
|
||||
private static final int CHOOSE_EXPORTED_FILE = 3;
|
||||
|
||||
private ImportExportTask importExporter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.import_export_activity);
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
@@ -34,82 +55,130 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
Button importButton = (Button)findViewById(R.id.importButton);
|
||||
importButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
|
||||
{
|
||||
startImport();
|
||||
}
|
||||
else
|
||||
{
|
||||
ActivityCompat.requestPermissions(ImportExportActivity.this,
|
||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||
PERMISSIONS_EXTERNAL_STORAGE_IMPORT);
|
||||
}
|
||||
}
|
||||
});
|
||||
// If the application does not have permissions to external
|
||||
// storage, ask for it now
|
||||
|
||||
Button exportButton = (Button)findViewById(R.id.exportButton);
|
||||
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
|
||||
ContextCompat.checkSelfPermission(ImportExportActivity.this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
|
||||
{
|
||||
ActivityCompat.requestPermissions(ImportExportActivity.this,
|
||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
PERMISSIONS_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
// Check that there is a file manager available
|
||||
final Intent intentCreateDocumentAction = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||
intentCreateDocumentAction.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intentCreateDocumentAction.setType("text/csv");
|
||||
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "Catima.csv");
|
||||
|
||||
Button exportButton = findViewById(R.id.exportButton);
|
||||
exportButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
|
||||
{
|
||||
startExport();
|
||||
}
|
||||
else
|
||||
{
|
||||
ActivityCompat.requestPermissions(ImportExportActivity.this,
|
||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
PERMISSIONS_EXTERNAL_STORAGE_EXPORT);
|
||||
}
|
||||
chooseFileWithIntent(intentCreateDocumentAction, CHOOSE_EXPORT_LOCATION);
|
||||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
importFilesystem.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
chooseFileWithIntent(intentGetContentAction, CHOOSE_EXPORTED_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
if(isCallable(getApplicationContext(), intentGetContentAction) == false)
|
||||
{
|
||||
findViewById(R.id.dividerImportFilesystem).setVisibility(View.GONE);
|
||||
findViewById(R.id.importOptionFilesystemTitle).setVisibility(View.GONE);
|
||||
findViewById(R.id.importOptionFilesystemExplanation).setVisibility(View.GONE);
|
||||
importFilesystem.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Check that there is an app that data can be imported from
|
||||
final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
|
||||
|
||||
Button importApplication = findViewById(R.id.importOptionApplicationButton);
|
||||
importApplication.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
chooseFileWithIntent(intentPickAction, CHOOSE_EXPORTED_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
if(isCallable(getApplicationContext(), intentPickAction) == false)
|
||||
{
|
||||
findViewById(R.id.dividerImportApplication).setVisibility(View.GONE);
|
||||
findViewById(R.id.importOptionApplicationTitle).setVisibility(View.GONE);
|
||||
findViewById(R.id.importOptionApplicationExplanation).setVisibility(View.GONE);
|
||||
importApplication.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void startImport()
|
||||
private void startImport(final InputStream target, final Uri targetUri)
|
||||
{
|
||||
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
|
||||
{
|
||||
@Override
|
||||
public void onTaskComplete(boolean success)
|
||||
{
|
||||
onImportComplete(success, targetUri);
|
||||
}
|
||||
};
|
||||
|
||||
importExporter = new ImportExportTask(ImportExportActivity.this,
|
||||
true, DataFormat.CSV);
|
||||
DataFormat.CSV, target, listener);
|
||||
importExporter.execute();
|
||||
}
|
||||
|
||||
private void startExport()
|
||||
private void startExport(final OutputStream target, final Uri targetUri)
|
||||
{
|
||||
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
|
||||
{
|
||||
@Override
|
||||
public void onTaskComplete(boolean success)
|
||||
{
|
||||
onExportComplete(success, targetUri);
|
||||
}
|
||||
};
|
||||
|
||||
importExporter = new ImportExportTask(ImportExportActivity.this,
|
||||
false, DataFormat.CSV);
|
||||
DataFormat.CSV, target, listener);
|
||||
importExporter.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)
|
||||
{
|
||||
if(requestCode == PERMISSIONS_EXTERNAL_STORAGE_IMPORT ||
|
||||
requestCode == PERMISSIONS_EXTERNAL_STORAGE_EXPORT)
|
||||
if(requestCode == PERMISSIONS_EXTERNAL_STORAGE)
|
||||
{
|
||||
// If request is cancelled, the result arrays are empty.
|
||||
if (grantResults.length > 0 &&
|
||||
grantResults[0] == PackageManager.PERMISSION_GRANTED)
|
||||
boolean success = grantResults.length > 0;
|
||||
|
||||
for(int grant : grantResults)
|
||||
{
|
||||
// permission was granted.
|
||||
if(requestCode == PERMISSIONS_EXTERNAL_STORAGE_IMPORT)
|
||||
if(grant != PackageManager.PERMISSION_GRANTED)
|
||||
{
|
||||
startImport();
|
||||
}
|
||||
else
|
||||
{
|
||||
startExport();
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if(success == false)
|
||||
{
|
||||
// External storage permission rejected, inform user that
|
||||
// import/export is prevented
|
||||
@@ -143,4 +212,188 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void onImportComplete(boolean success, Uri path)
|
||||
{
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
|
||||
if(success)
|
||||
{
|
||||
builder.setTitle(R.string.importSuccessfulTitle);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.setTitle(R.string.importFailedTitle);
|
||||
}
|
||||
|
||||
int messageId = success ? R.string.importSuccessful : R.string.importFailed;
|
||||
final String message = getResources().getString(messageId);
|
||||
|
||||
builder.setMessage(message);
|
||||
builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void onExportComplete(boolean success, final Uri path)
|
||||
{
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
|
||||
if(success)
|
||||
{
|
||||
builder.setTitle(R.string.exportSuccessfulTitle);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.setTitle(R.string.exportFailedTitle);
|
||||
}
|
||||
|
||||
int messageId = success ? R.string.exportSuccessful : R.string.exportFailed;
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
if(success)
|
||||
{
|
||||
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel);
|
||||
|
||||
builder.setPositiveButton(sendLabel, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, path);
|
||||
sendIntent.setType("text/csv");
|
||||
|
||||
// set flag to give temporary permission to external app to use the FileProvider
|
||||
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
|
||||
sendLabel));
|
||||
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if there is at least one activity that can perform the given intent
|
||||
*/
|
||||
private boolean isCallable(Context context, final Intent intent)
|
||||
{
|
||||
PackageManager manager = context.getPackageManager();
|
||||
if(manager == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
List<ResolveInfo> list = manager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
|
||||
for(ResolveInfo info : list)
|
||||
{
|
||||
if(info.activityInfo.exported)
|
||||
{
|
||||
// There is one activity which is available to be called
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void chooseFileWithIntent(Intent intent, int requestCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
startActivityForResult(intent, requestCode);
|
||||
}
|
||||
catch (ActivityNotFoundException e)
|
||||
{
|
||||
Log.e(TAG, "No activity found to handle intent", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (resultCode != RESULT_OK || (requestCode != CHOOSE_EXPORT_LOCATION && requestCode != CHOOSE_EXPORTED_FILE))
|
||||
{
|
||||
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = data.getData();
|
||||
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 export with: " + uri.toString());
|
||||
startImport(reader, uri);
|
||||
}
|
||||
}
|
||||
catch(FileNotFoundException e)
|
||||
{
|
||||
Log.e(TAG, "Failed to import/export file: " + uri.toString(), e);
|
||||
if (requestCode == CHOOSE_EXPORT_LOCATION)
|
||||
{
|
||||
onExportComplete(false, uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
onImportComplete(false, uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,67 +4,63 @@ import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
class ImportExportTask extends AsyncTask<Void, Void, Void>
|
||||
class ImportExportTask extends AsyncTask<Void, Void, Boolean>
|
||||
{
|
||||
private static final String TAG = "BudgetWatch";
|
||||
|
||||
private static final String TARGET_FILE = "LoyaltyCardLocker.csv";
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private Activity activity;
|
||||
private boolean doImport;
|
||||
private DataFormat format;
|
||||
private OutputStream outputStream;
|
||||
private InputStream inputStream;
|
||||
private TaskCompleteListener listener;
|
||||
|
||||
private ProgressDialog progress;
|
||||
|
||||
public ImportExportTask(Activity activity, boolean doImport, DataFormat format)
|
||||
/**
|
||||
* Constructor which will setup a task for exporting to the given file
|
||||
*/
|
||||
ImportExportTask(Activity activity, DataFormat format, OutputStream output,
|
||||
TaskCompleteListener listener)
|
||||
{
|
||||
super();
|
||||
this.activity = activity;
|
||||
this.doImport = doImport;
|
||||
this.doImport = false;
|
||||
this.format = format;
|
||||
this.outputStream = output;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private void toastWithArg(int stringId, String argument)
|
||||
/**
|
||||
* Constructor which will setup a task for importing from the given InputStream.
|
||||
*/
|
||||
ImportExportTask(Activity activity, DataFormat format, InputStream input,
|
||||
TaskCompleteListener listener)
|
||||
{
|
||||
final String template = activity.getResources().getString(stringId);
|
||||
final String message = String.format(template, argument);
|
||||
|
||||
activity.runOnUiThread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Toast.makeText(activity, message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
super();
|
||||
this.activity = activity;
|
||||
this.doImport = true;
|
||||
this.format = format;
|
||||
this.inputStream = input;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private void performImport(File importFile, DBHelper db)
|
||||
private boolean performImport(InputStream stream, DBHelper db)
|
||||
{
|
||||
if(importFile.exists() == false)
|
||||
{
|
||||
toastWithArg(R.string.fileMissing, importFile.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
|
||||
boolean result = false;
|
||||
|
||||
try
|
||||
{
|
||||
FileInputStream fileReader = new FileInputStream(importFile);
|
||||
InputStreamReader reader = new InputStreamReader(fileReader, Charset.forName("UTF-8"));
|
||||
InputStreamReader reader = new InputStreamReader(stream, Charset.forName("UTF-8"));
|
||||
result = MultiFormatImporter.importData(db, reader, format);
|
||||
reader.close();
|
||||
}
|
||||
@@ -73,18 +69,18 @@ class ImportExportTask extends AsyncTask<Void, Void, Void>
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
}
|
||||
|
||||
int messageId = result ? R.string.importedFrom : R.string.importFailed;
|
||||
toastWithArg(messageId, importFile.getAbsolutePath());
|
||||
Log.i(TAG, "Import result: " + result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void performExport(File exportFile, DBHelper db)
|
||||
private boolean performExport(OutputStream stream, DBHelper db)
|
||||
{
|
||||
boolean result = false;
|
||||
|
||||
try
|
||||
{
|
||||
FileOutputStream fileWriter = new FileOutputStream(exportFile);
|
||||
OutputStreamWriter writer = new OutputStreamWriter(fileWriter, Charset.forName("UTF-8"));
|
||||
OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("UTF-8"));
|
||||
result = MultiFormatExporter.exportData(db, writer, format);
|
||||
writer.close();
|
||||
}
|
||||
@@ -93,8 +89,9 @@ class ImportExportTask extends AsyncTask<Void, Void, Void>
|
||||
Log.e(TAG, "Unable to export file", e);
|
||||
}
|
||||
|
||||
int messageId = result ? R.string.exportedTo : R.string.exportFailed;
|
||||
toastWithArg(messageId, exportFile.getAbsolutePath());
|
||||
Log.i(TAG, "Export result: " + result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void onPreExecute()
|
||||
@@ -114,26 +111,27 @@ class ImportExportTask extends AsyncTask<Void, Void, Void>
|
||||
progress.show();
|
||||
}
|
||||
|
||||
protected Void doInBackground(Void... nothing)
|
||||
protected Boolean doInBackground(Void... nothing)
|
||||
{
|
||||
final File sdcardDir = Environment.getExternalStorageDirectory();
|
||||
final File importExportFile = new File(sdcardDir, TARGET_FILE);
|
||||
final DBHelper db = new DBHelper(activity);
|
||||
boolean result;
|
||||
|
||||
if(doImport)
|
||||
{
|
||||
performImport(importExportFile, db);
|
||||
result = performImport(inputStream, db);
|
||||
}
|
||||
else
|
||||
{
|
||||
performExport(importExportFile, db);
|
||||
result = performExport(outputStream, db);
|
||||
}
|
||||
|
||||
return null;
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void onPostExecute(Void result)
|
||||
protected void onPostExecute(Boolean result)
|
||||
{
|
||||
listener.onTaskComplete(result);
|
||||
|
||||
progress.dismiss();
|
||||
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
|
||||
}
|
||||
@@ -143,4 +141,9 @@ class ImportExportTask extends AsyncTask<Void, Void, Void>
|
||||
progress.dismiss();
|
||||
Log.i(TAG, (doImport ? "Import" : "Export") + " Cancelled");
|
||||
}
|
||||
interface TaskCompleteListener
|
||||
{
|
||||
void onTaskComplete(boolean success);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
95
app/src/main/java/protect/card_locker/ImportURIHelper.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import java.io.InvalidObjectException;
|
||||
|
||||
public class ImportURIHelper {
|
||||
private static final String STORE = DBHelper.LoyaltyCardDbIds.STORE;
|
||||
private static final String NOTE = DBHelper.LoyaltyCardDbIds.NOTE;
|
||||
private static final String CARD_ID = DBHelper.LoyaltyCardDbIds.CARD_ID;
|
||||
private static final String BARCODE_TYPE = DBHelper.LoyaltyCardDbIds.BARCODE_TYPE;
|
||||
|
||||
private static final String HEADER_COLOR = DBHelper.LoyaltyCardDbIds.HEADER_COLOR;
|
||||
|
||||
private final Context context;
|
||||
private final String host;
|
||||
private final String path;
|
||||
private final String oldHost;
|
||||
private final String oldPath;
|
||||
private final String shareText;
|
||||
|
||||
public ImportURIHelper(Context context) {
|
||||
this.context = context;
|
||||
host = context.getResources().getString(R.string.intent_import_card_from_url_host);
|
||||
path = context.getResources().getString(R.string.intent_import_card_from_url_path_prefix);
|
||||
oldHost = "brarcher.github.io";
|
||||
oldPath = "/loyalty-card-locker/share";
|
||||
shareText = context.getResources().getString(R.string.intent_import_card_from_url_share_text);
|
||||
}
|
||||
|
||||
private boolean isImportUri(Uri uri) {
|
||||
return (uri.getHost().equals(host) && uri.getPath().equals(path)) || (uri.getHost().equals(oldHost) && uri.getPath().equals(oldPath));
|
||||
}
|
||||
|
||||
public LoyaltyCard parse(Uri uri) throws InvalidObjectException {
|
||||
if(!isImportUri(uri)) {
|
||||
throw new InvalidObjectException("Not an import URI");
|
||||
}
|
||||
|
||||
try {
|
||||
// These values are allowed to be null
|
||||
Integer headerColor = null;
|
||||
Integer headerTextColor = null;
|
||||
|
||||
String store = uri.getQueryParameter(STORE);
|
||||
String note = uri.getQueryParameter(NOTE);
|
||||
String cardId = uri.getQueryParameter(CARD_ID);
|
||||
String barcodeType = uri.getQueryParameter(BARCODE_TYPE);
|
||||
if (store == null || note == null || cardId == null || barcodeType == null) throw new InvalidObjectException("Not a valid import URI");
|
||||
|
||||
String unparsedHeaderColor = uri.getQueryParameter(HEADER_COLOR);
|
||||
if(unparsedHeaderColor != null)
|
||||
{
|
||||
headerColor = Integer.parseInt(unparsedHeaderColor);
|
||||
}
|
||||
|
||||
return new LoyaltyCard(-1, store, note, cardId, barcodeType, headerColor, headerTextColor, 0);
|
||||
} catch (NullPointerException | NumberFormatException ex) {
|
||||
throw new InvalidObjectException("Not a valid import URI");
|
||||
}
|
||||
}
|
||||
|
||||
// Protected for usage in tests
|
||||
protected Uri toUri(LoyaltyCard loyaltyCard) {
|
||||
Uri.Builder uriBuilder = new Uri.Builder();
|
||||
uriBuilder.scheme("https");
|
||||
uriBuilder.authority(host);
|
||||
uriBuilder.path(path);
|
||||
uriBuilder.appendQueryParameter(STORE, loyaltyCard.store);
|
||||
uriBuilder.appendQueryParameter(NOTE, loyaltyCard.note);
|
||||
uriBuilder.appendQueryParameter(CARD_ID, loyaltyCard.cardId);
|
||||
uriBuilder.appendQueryParameter(BARCODE_TYPE, loyaltyCard.barcodeType);
|
||||
if(loyaltyCard.headerColor != null)
|
||||
{
|
||||
uriBuilder.appendQueryParameter(HEADER_COLOR, loyaltyCard.headerColor.toString());
|
||||
}
|
||||
//StarStatus will not be exported
|
||||
return uriBuilder.build();
|
||||
}
|
||||
|
||||
private void startShareIntent(Uri uri) {
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, shareText + "\n" + uri.toString());
|
||||
sendIntent.setType("text/plain");
|
||||
|
||||
Intent shareIntent = Intent.createChooser(sendIntent, null);
|
||||
context.startActivity(shareIntent);
|
||||
}
|
||||
|
||||
public void startShareIntent(LoyaltyCard loyaltyCard) {
|
||||
startShareIntent(toUri(loyaltyCard));
|
||||
}
|
||||
}
|
||||
140
app/src/main/java/protect/card_locker/LetterBitmap.java
Normal file
@@ -0,0 +1,140 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.TextPaint;
|
||||
|
||||
/**
|
||||
* Original from https://github.com/andOTP/andOTP/blob/master/app/src/main/java/org/shadowice/flocke/andotp/Utilities/LetterBitmap.java
|
||||
* which was originally from http://stackoverflow.com/questions/23122088/colored-boxed-with-letters-a-la-gmail
|
||||
* Used to create a {@link Bitmap} that contains a letter used in the English
|
||||
* alphabet or digit, if there is no letter or digit available, a default image
|
||||
* is shown instead.
|
||||
*/
|
||||
class LetterBitmap
|
||||
{
|
||||
|
||||
/**
|
||||
* The number of available tile colors
|
||||
*/
|
||||
private static final int NUM_OF_TILE_COLORS = 8;
|
||||
/**
|
||||
* The letter bitmap
|
||||
*/
|
||||
private final Bitmap mBitmap;
|
||||
/**
|
||||
* The background color of the letter bitmap
|
||||
*/
|
||||
private final Integer mColor;
|
||||
|
||||
/**
|
||||
* Constructor for <code>LetterTileProvider</code>
|
||||
*
|
||||
* @param context The {@link Context} to use
|
||||
* @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 tileLetterFontSize The font size used to display the letter
|
||||
* @param width The desired width of the tile
|
||||
* @param height The desired height of the tile
|
||||
* @param backgroundColor (optional) color to use for background.
|
||||
* @param textColor (optional) color to use for text.
|
||||
*/
|
||||
public LetterBitmap(Context context, String displayName, String key, int tileLetterFontSize,
|
||||
int width, int height, Integer backgroundColor, Integer textColor)
|
||||
{
|
||||
TextPaint paint = new TextPaint();
|
||||
paint.setTypeface(Typeface.create("sans-serif-light", Typeface.BOLD));
|
||||
|
||||
if(textColor != null)
|
||||
{
|
||||
paint.setColor(textColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
paint.setColor(Color.WHITE);
|
||||
}
|
||||
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
paint.setAntiAlias(true);
|
||||
|
||||
if(backgroundColor == null)
|
||||
{
|
||||
mColor = getDefaultColor(context, key);
|
||||
}
|
||||
else
|
||||
{
|
||||
mColor = backgroundColor;
|
||||
}
|
||||
|
||||
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
String firstChar = displayName.substring(0, 1);
|
||||
|
||||
final Canvas c = new Canvas();
|
||||
c.setBitmap(mBitmap);
|
||||
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();
|
||||
|
||||
paint.getTextBounds(firstCharArray, 0, 1, bounds);
|
||||
c.drawText(firstCharArray, 0, 1, width / 2.0f, height / 2.0f
|
||||
+ (bounds.bottom - bounds.top) / 2.0f, paint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link Bitmap} that contains a letter used in the English
|
||||
* alphabet or digit, if there is no letter or digit available, a
|
||||
* default image is shown instead
|
||||
*/
|
||||
public Bitmap getLetterTile()
|
||||
{
|
||||
return mBitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return background color used for letter title.
|
||||
*/
|
||||
public int getBackgroundColor()
|
||||
{
|
||||
return mColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key The key used to generate the tile color
|
||||
* @return A new or previously chosen color for <code>key</code> used as the
|
||||
* tile background color
|
||||
*/
|
||||
private static int pickColor(String key, TypedArray colors)
|
||||
{
|
||||
// String.hashCode() is not supposed to change across java versions, so
|
||||
// this should guarantee the same key always maps to the same color
|
||||
final int color = Math.abs(key.hashCode()) % NUM_OF_TILE_COLORS;
|
||||
return colors.getColor(color, Color.BLACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the color which the letter tile will use if no default
|
||||
* color is provided.
|
||||
*/
|
||||
public static int getDefaultColor(Context context, String key)
|
||||
{
|
||||
final Resources res = context.getResources();
|
||||
|
||||
TypedArray colors = res.obtainTypedArray(R.array.letter_tile_colors);
|
||||
int color = pickColor(key, colors);
|
||||
colors.recycle();
|
||||
|
||||
return color;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package protect.card_locker;
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class LoyaltyCard
|
||||
{
|
||||
public final int id;
|
||||
@@ -10,13 +12,26 @@ public class LoyaltyCard
|
||||
public final String cardId;
|
||||
public final String barcodeType;
|
||||
|
||||
public LoyaltyCard(final int id, final String store, final String note, final String cardId, final String barcodeType)
|
||||
@Nullable
|
||||
public final Integer headerColor;
|
||||
|
||||
@Nullable
|
||||
public final Integer headerTextColor;
|
||||
|
||||
public final int starStatus;
|
||||
|
||||
public LoyaltyCard(final int id, final String store, final String note, final String cardId,
|
||||
final String barcodeType, final Integer headerColor, final Integer headerTextColor,
|
||||
final int starStatus)
|
||||
{
|
||||
this.id = id;
|
||||
this.store = store;
|
||||
this.note = note;
|
||||
this.cardId = cardId;
|
||||
this.barcodeType = barcodeType;
|
||||
this.headerColor = headerColor;
|
||||
this.headerTextColor = headerTextColor;
|
||||
this.starStatus = starStatus;
|
||||
}
|
||||
|
||||
public static LoyaltyCard toLoyaltyCard(Cursor cursor)
|
||||
@@ -26,7 +41,25 @@ public class LoyaltyCard
|
||||
String note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE));
|
||||
String cardId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID));
|
||||
String barcodeType = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE));
|
||||
int starred = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS));
|
||||
|
||||
return new LoyaltyCard(id, store, note, cardId, barcodeType);
|
||||
|
||||
int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR);
|
||||
int headerTextColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR);
|
||||
|
||||
Integer headerColor = null;
|
||||
Integer headerTextColor = null;
|
||||
|
||||
if(cursor.isNull(headerColorColumn) == false)
|
||||
{
|
||||
headerColor = cursor.getInt(headerColorColumn);
|
||||
}
|
||||
|
||||
if(cursor.isNull(headerTextColorColumn) == false)
|
||||
{
|
||||
headerTextColor = cursor.getInt(headerTextColorColumn);
|
||||
}
|
||||
|
||||
return new LoyaltyCard(id, store, note, cardId, barcodeType, headerColor, headerTextColor, starred);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,27 @@ package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import protect.card_locker.preferences.Settings;
|
||||
|
||||
class LoyaltyCardCursorAdapter extends CursorAdapter
|
||||
{
|
||||
Settings settings;
|
||||
boolean darkModeEnabled;
|
||||
|
||||
public LoyaltyCardCursorAdapter(Context context, Cursor cursor)
|
||||
{
|
||||
super(context, cursor, 0);
|
||||
settings = new Settings(context);
|
||||
darkModeEnabled= MainActivity.isDarkModeEnabled(context);
|
||||
|
||||
}
|
||||
|
||||
// The newView method is used to inflate a new view and return it,
|
||||
@@ -29,25 +39,38 @@ class LoyaltyCardCursorAdapter extends CursorAdapter
|
||||
public void bindView(View view, Context context, Cursor cursor)
|
||||
{
|
||||
// Find fields to populate in inflated template
|
||||
TextView storeField = (TextView) view.findViewById(R.id.store);
|
||||
TextView cardIdField = (TextView) view.findViewById(R.id.cardId);
|
||||
ImageView thumbnail = view.findViewById(R.id.thumbnail);
|
||||
TextView storeField = view.findViewById(R.id.store);
|
||||
TextView noteField = view.findViewById(R.id.note);
|
||||
ImageView star = view.findViewById(R.id.star);
|
||||
|
||||
if(darkModeEnabled)
|
||||
{
|
||||
star.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
|
||||
}
|
||||
|
||||
// Extract properties from cursor
|
||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(cursor);
|
||||
|
||||
// Populate fields with extracted properties
|
||||
String storeAndNote = loyaltyCard.store;
|
||||
if(loyaltyCard.note.isEmpty() == false)
|
||||
storeField.setText(loyaltyCard.store);
|
||||
|
||||
storeField.setTextSize(settings.getCardTitleListFontSize());
|
||||
|
||||
if(!loyaltyCard.note.isEmpty())
|
||||
{
|
||||
String storeNameAndNoteFormat = view.getResources().getString(R.string.storeNameAndNoteFormat);
|
||||
storeAndNote = String.format(storeNameAndNoteFormat, loyaltyCard.store, loyaltyCard.note);
|
||||
noteField.setVisibility(View.VISIBLE);
|
||||
noteField.setText(loyaltyCard.note);
|
||||
noteField.setTextSize(settings.getCardNoteListFontSize());
|
||||
}
|
||||
else
|
||||
{
|
||||
noteField.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
storeField.setText(storeAndNote);
|
||||
if (loyaltyCard.starStatus!=0) star.setVisibility(View.VISIBLE);
|
||||
else star.setVisibility(View.GONE);
|
||||
|
||||
String cardIdFormat = view.getResources().getString(R.string.cardIdFormat);
|
||||
String cardIdLabel = view.getResources().getString(R.string.cardId);
|
||||
String cardIdText = String.format(cardIdFormat, cardIdLabel, loyaltyCard.cardId);
|
||||
cardIdField.setText(cardIdText);
|
||||
thumbnail.setImageBitmap(Utils.generateIcon(context, loyaltyCard.store, loyaltyCard.headerColor).getLetterTile());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,622 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.google.android.material.chip.Chip;
|
||||
import com.google.android.material.chip.ChipGroup;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
|
||||
|
||||
import java.io.InvalidObjectException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
protected static final int SELECT_BARCODE_REQUEST = 1;
|
||||
|
||||
ImageView thumbnail;
|
||||
EditText storeFieldEdit;
|
||||
EditText noteFieldEdit;
|
||||
ChipGroup groupsChips;
|
||||
View cardAndBarcodeLayout;
|
||||
TextView cardIdFieldView;
|
||||
View barcodeTypeView;
|
||||
AutoCompleteTextView barcodeTypeField;
|
||||
ImageView barcodeImage;
|
||||
View barcodeImageLayout;
|
||||
View barcodeCaptureLayout;
|
||||
|
||||
Button captureButton;
|
||||
Button enterButton;
|
||||
|
||||
int loyaltyCardId;
|
||||
boolean updateLoyaltyCard;
|
||||
Uri importLoyaltyCardUri = null;
|
||||
Integer headingColorValue = null;
|
||||
|
||||
DBHelper db;
|
||||
ImportURIHelper importUriHelper;
|
||||
|
||||
private void extractIntentFields(Intent intent)
|
||||
{
|
||||
final Bundle b = intent.getExtras();
|
||||
loyaltyCardId = b != null ? b.getInt("id") : 0;
|
||||
updateLoyaltyCard = b != null && b.getBoolean("update", false);
|
||||
importLoyaltyCardUri = intent.getData();
|
||||
|
||||
Log.d(TAG, "View activity: id=" + loyaltyCardId
|
||||
+ ", updateLoyaltyCard=" + Boolean.toString(updateLoyaltyCard));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.loyalty_card_edit_activity);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
extractIntentFields(getIntent());
|
||||
|
||||
db = new DBHelper(this);
|
||||
importUriHelper = new ImportURIHelper(this);
|
||||
|
||||
thumbnail = findViewById(R.id.thumbnail);
|
||||
storeFieldEdit = findViewById(R.id.storeNameEdit);
|
||||
noteFieldEdit = findViewById(R.id.noteEdit);
|
||||
groupsChips = findViewById(R.id.groupChips);
|
||||
cardAndBarcodeLayout = findViewById(R.id.cardAndBarcodeLayout);
|
||||
cardIdFieldView = findViewById(R.id.cardIdView);
|
||||
barcodeTypeView = findViewById(R.id.barcodeTypeView);
|
||||
barcodeTypeField = findViewById(R.id.barcodeTypeField);
|
||||
barcodeImage = findViewById(R.id.barcode);
|
||||
barcodeImageLayout = findViewById(R.id.barcodeLayout);
|
||||
barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
|
||||
|
||||
captureButton = findViewById(R.id.captureButton);
|
||||
enterButton = findViewById(R.id.enterButton);
|
||||
|
||||
storeFieldEdit.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) {
|
||||
generateIcon(s.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) { }
|
||||
});
|
||||
|
||||
cardIdFieldView.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) {
|
||||
String formatString = barcodeTypeField.getText().toString();
|
||||
|
||||
if (!formatString.isEmpty()) {
|
||||
if (formatString.equals(getString(R.string.noBarcode))) {
|
||||
hideBarcode();
|
||||
} else {
|
||||
generateBarcode(s.toString(), BarcodeFormat.valueOf(formatString));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) { }
|
||||
});
|
||||
|
||||
barcodeTypeField.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) {
|
||||
if (!s.toString().isEmpty()) {
|
||||
if (s.toString().equals(getString(R.string.noBarcode))) {
|
||||
hideBarcode();
|
||||
} else {
|
||||
generateBarcode(cardIdFieldView.getText().toString(), BarcodeFormat.valueOf(s.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) { }
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent)
|
||||
{
|
||||
super.onNewIntent(intent);
|
||||
|
||||
Log.i(TAG, "Received new intent");
|
||||
extractIntentFields(intent);
|
||||
|
||||
// Reset these fields, so they are re-populated in onResume().
|
||||
storeFieldEdit.setText("");
|
||||
noteFieldEdit.setText("");
|
||||
cardIdFieldView.setText("");
|
||||
barcodeTypeField.setText("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
|
||||
Log.i(TAG, "To view card: " + loyaltyCardId);
|
||||
|
||||
if(updateLoyaltyCard)
|
||||
{
|
||||
final LoyaltyCard loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
|
||||
if(loyaltyCard == null)
|
||||
{
|
||||
Log.w(TAG, "Could not lookup loyalty card " + loyaltyCardId);
|
||||
Toast.makeText(this, R.string.noCardExistsError, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if(storeFieldEdit.getText().length() == 0)
|
||||
{
|
||||
storeFieldEdit.setText(loyaltyCard.store);
|
||||
}
|
||||
|
||||
if(noteFieldEdit.getText().length() == 0)
|
||||
{
|
||||
noteFieldEdit.setText(loyaltyCard.note);
|
||||
}
|
||||
|
||||
if(cardIdFieldView.getText().length() == 0)
|
||||
{
|
||||
cardIdFieldView.setText(loyaltyCard.cardId);
|
||||
}
|
||||
|
||||
if(barcodeTypeField.getText().length() == 0)
|
||||
{
|
||||
barcodeTypeField.setText(loyaltyCard.barcodeType.isEmpty() ? getString(R.string.noBarcode) : loyaltyCard.barcodeType);
|
||||
}
|
||||
|
||||
if(headingColorValue == null)
|
||||
{
|
||||
headingColorValue = loyaltyCard.headerColor;
|
||||
if(headingColorValue == null)
|
||||
{
|
||||
headingColorValue = LetterBitmap.getDefaultColor(this, loyaltyCard.store);
|
||||
}
|
||||
}
|
||||
|
||||
setTitle(R.string.editCardTitle);
|
||||
}
|
||||
else if(importLoyaltyCardUri != null)
|
||||
{
|
||||
// Try to parse
|
||||
LoyaltyCard importCard;
|
||||
try {
|
||||
importCard = importUriHelper.parse(importLoyaltyCardUri);
|
||||
} catch (InvalidObjectException ex) {
|
||||
Toast.makeText(this, R.string.failedParsingImportUriError, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
storeFieldEdit.setText(importCard.store);
|
||||
noteFieldEdit.setText(importCard.note);
|
||||
cardIdFieldView.setText(importCard.cardId);
|
||||
barcodeTypeField.setText(importCard.barcodeType);
|
||||
headingColorValue = importCard.headerColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
setTitle(R.string.addCardTitle);
|
||||
hideBarcode();
|
||||
}
|
||||
|
||||
if(groupsChips.getChildCount() == 0)
|
||||
{
|
||||
List<Group> existingGroups = db.getGroups();
|
||||
|
||||
List<Group> loyaltyCardGroups = db.getLoyaltyCardGroups(loyaltyCardId);
|
||||
|
||||
if (existingGroups.isEmpty()) {
|
||||
groupsChips.setVisibility(View.GONE);
|
||||
} else {
|
||||
groupsChips.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
for (Group group : db.getGroups()) {
|
||||
Chip chip = (Chip) getLayoutInflater().inflate(R.layout.layout_chip_choice, groupsChips, false);
|
||||
chip.setText(group._id);
|
||||
chip.setTag(group);
|
||||
|
||||
chip.setChecked(false);
|
||||
for (Group loyaltyCardGroup : loyaltyCardGroups) {
|
||||
if (loyaltyCardGroup._id.equals(group._id)) {
|
||||
chip.setChecked(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
groupsChips.addView(chip);
|
||||
}
|
||||
}
|
||||
|
||||
if(headingColorValue == null)
|
||||
{
|
||||
// Select a random color to start out with.
|
||||
TypedArray colors = getResources().obtainTypedArray(R.array.letter_tile_colors);
|
||||
final int color = (int)(Math.random() * colors.length());
|
||||
headingColorValue = colors.getColor(color, Color.BLACK);
|
||||
colors.recycle();
|
||||
}
|
||||
|
||||
thumbnail.setOnClickListener(new ColorSelectListener(headingColorValue));
|
||||
|
||||
if(cardIdFieldView.getText().length() > 0 && barcodeTypeField.getText().length() > 0)
|
||||
{
|
||||
String formatString = barcodeTypeField.getText().toString();
|
||||
|
||||
if(formatString.isEmpty() || formatString.equals(getString(R.string.noBarcode)))
|
||||
{
|
||||
hideBarcode();
|
||||
}
|
||||
else
|
||||
{
|
||||
final BarcodeFormat format = BarcodeFormat.valueOf(formatString);
|
||||
final String cardIdString = cardIdFieldView.getText().toString();
|
||||
|
||||
generateBarcode(cardIdString, format);
|
||||
}
|
||||
}
|
||||
|
||||
View.OnClickListener captureCallback = new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
IntentIntegrator integrator = new IntentIntegrator(LoyaltyCardEditActivity.this);
|
||||
integrator.setDesiredBarcodeFormats(BarcodeSelectorActivity.SUPPORTED_BARCODE_TYPES);
|
||||
|
||||
String prompt = getResources().getString(R.string.scanCardBarcode);
|
||||
integrator.setPrompt(prompt);
|
||||
integrator.setBeepEnabled(false);
|
||||
integrator.initiateScan();
|
||||
}
|
||||
};
|
||||
|
||||
captureButton.setOnClickListener(captureCallback);
|
||||
|
||||
enterButton.setOnClickListener(new EditCardIdAndBarcode());
|
||||
barcodeImage.setOnClickListener(new EditCardIdAndBarcode());
|
||||
|
||||
if(cardIdFieldView.getText().length() > 0)
|
||||
{
|
||||
cardAndBarcodeLayout.setVisibility(View.VISIBLE);
|
||||
enterButton.setText(R.string.editCard);
|
||||
}
|
||||
else
|
||||
{
|
||||
cardAndBarcodeLayout.setVisibility(View.GONE);
|
||||
enterButton.setText(R.string.enterCard);
|
||||
}
|
||||
|
||||
ArrayList<String> barcodeList = new ArrayList<>(BarcodeSelectorActivity.SUPPORTED_BARCODE_TYPES);
|
||||
barcodeList.add(0, getString(R.string.noBarcode));
|
||||
ArrayAdapter<String> barcodeAdapter = new ArrayAdapter<>(this, android.R.layout.select_dialog_item, barcodeList);
|
||||
barcodeTypeField.setAdapter(barcodeAdapter);
|
||||
|
||||
FloatingActionButton saveButton = findViewById(R.id.fabSave);
|
||||
saveButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
doSave();
|
||||
}
|
||||
});
|
||||
|
||||
generateIcon(storeFieldEdit.getText().toString());
|
||||
}
|
||||
|
||||
class EditCardIdAndBarcode implements View.OnClickListener
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class);
|
||||
|
||||
String cardId = cardIdFieldView.getText().toString();
|
||||
if(cardId.length() > 0)
|
||||
{
|
||||
final Bundle b = new Bundle();
|
||||
b.putString("initialCardId", cardId);
|
||||
i.putExtras(b);
|
||||
}
|
||||
|
||||
startActivityForResult(i, SELECT_BARCODE_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
class ColorSelectListener implements View.OnClickListener
|
||||
{
|
||||
final int defaultColor;
|
||||
|
||||
ColorSelectListener(int defaultColor)
|
||||
{
|
||||
this.defaultColor = defaultColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
ColorPickerDialog dialog = ColorPickerDialog.newBuilder().setColor(defaultColor).create();
|
||||
dialog.setColorPickerDialogListener(new ColorPickerDialogListener()
|
||||
{
|
||||
@Override
|
||||
public void onColorSelected(int dialogId, int color)
|
||||
{
|
||||
headingColorValue = color;
|
||||
|
||||
generateIcon(storeFieldEdit.getText().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialogDismissed(int dialogId)
|
||||
{
|
||||
// Nothing to do, no change made
|
||||
}
|
||||
});
|
||||
dialog.show(getFragmentManager(), "color-picker-dialog");
|
||||
}
|
||||
}
|
||||
|
||||
private void doSave()
|
||||
{
|
||||
String store = storeFieldEdit.getText().toString();
|
||||
String note = noteFieldEdit.getText().toString();
|
||||
String cardId = cardIdFieldView.getText().toString();
|
||||
String barcodeType = barcodeTypeField.getText().toString();
|
||||
|
||||
// We do not want to save the no barcode string to the database
|
||||
// it is simply an empty there for no barcode
|
||||
if(barcodeType.equals(getString(R.string.noBarcode)))
|
||||
{
|
||||
barcodeType = "";
|
||||
}
|
||||
|
||||
if(store.isEmpty())
|
||||
{
|
||||
Snackbar.make(storeFieldEdit, R.string.noStoreError, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if(cardId.isEmpty())
|
||||
{
|
||||
Snackbar.make(cardIdFieldView, R.string.noCardIdError, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Group> selectedGroups = new ArrayList<>();
|
||||
|
||||
for (Integer chipId : groupsChips.getCheckedChipIds()) {
|
||||
Chip chip = groupsChips.findViewById(chipId);
|
||||
selectedGroups.add((Group) chip.getTag());
|
||||
}
|
||||
|
||||
if(updateLoyaltyCard)
|
||||
{ //update of "starStatus" not necessary, since it cannot be changed in this activity (only in ViewActivity)
|
||||
db.updateLoyaltyCard(loyaltyCardId, store, note, cardId, barcodeType, headingColorValue);
|
||||
Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
|
||||
}
|
||||
else
|
||||
{
|
||||
loyaltyCardId = (int)db.insertLoyaltyCard(store, note, cardId, barcodeType, headingColorValue, 0);
|
||||
}
|
||||
|
||||
db.setLoyaltyCardGroups(loyaltyCardId, selectedGroups);
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
if(updateLoyaltyCard)
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.card_update_menu, menu);
|
||||
}
|
||||
else
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.card_add_menu, menu);
|
||||
}
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
int id = item.getItemId();
|
||||
|
||||
switch(id)
|
||||
{
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
break;
|
||||
|
||||
case R.id.action_delete:
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.deleteTitle);
|
||||
builder.setMessage(R.string.deleteConfirmation);
|
||||
builder.setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
Log.e(TAG, "Deleting card: " + loyaltyCardId);
|
||||
|
||||
DBHelper db = new DBHelper(LoyaltyCardEditActivity.this);
|
||||
db.deleteLoyaltyCard(loyaltyCardId);
|
||||
|
||||
ShortcutHelper.removeShortcut(LoyaltyCardEditActivity.this, loyaltyCardId);
|
||||
|
||||
finish();
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent)
|
||||
{
|
||||
super.onActivityResult(requestCode, resultCode, intent);
|
||||
|
||||
String contents = null;
|
||||
String format = null;
|
||||
|
||||
IntentResult result =
|
||||
IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
||||
if (result != null)
|
||||
{
|
||||
Log.i(TAG, "Received barcode information from capture");
|
||||
contents = result.getContents();
|
||||
format = result.getFormatName();
|
||||
}
|
||||
|
||||
if(requestCode == SELECT_BARCODE_REQUEST && resultCode == Activity.RESULT_OK)
|
||||
{
|
||||
Log.i(TAG, "Received barcode information from typing it");
|
||||
|
||||
contents = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_CONTENTS);
|
||||
format = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_FORMAT);
|
||||
}
|
||||
|
||||
if(contents != null && contents.isEmpty() == false &&
|
||||
format != null)
|
||||
{
|
||||
Log.i(TAG, "Read barcode id: " + contents);
|
||||
Log.i(TAG, "Read format: " + format);
|
||||
|
||||
TextView cardIdView = findViewById(R.id.cardIdView);
|
||||
cardIdView.setText(contents);
|
||||
|
||||
// Set special NO_BARCODE value to prevent onResume from overwriting it
|
||||
barcodeTypeField.setText(format.isEmpty() ? getString(R.string.noBarcode) : format);
|
||||
onResume();
|
||||
}
|
||||
}
|
||||
|
||||
private void showBarcode() {
|
||||
barcodeImageLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void hideBarcode() {
|
||||
barcodeImageLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void generateBarcode(final String cardId, final BarcodeFormat barcodeFormat) {
|
||||
if (barcodeImage.getHeight() == 0) {
|
||||
Log.d(TAG, "ImageView size is not known known at start, waiting for load");
|
||||
// The size of the ImageView is not yet available as it has not
|
||||
// yet been drawn. Wait for it to be drawn so the size is available.
|
||||
barcodeImage.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
if (Build.VERSION.SDK_INT < 16) {
|
||||
barcodeImage.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
} else {
|
||||
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
|
||||
Log.d(TAG, "ImageView size now known");
|
||||
new BarcodeImageWriterTask(barcodeImage, cardId, barcodeFormat).execute();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.d(TAG, "ImageView size known known, creating barcode");
|
||||
new BarcodeImageWriterTask(barcodeImage, cardId, barcodeFormat).execute();
|
||||
}
|
||||
|
||||
showBarcode();
|
||||
}
|
||||
|
||||
private void generateIcon(String store) {
|
||||
if (headingColorValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
thumbnail.setBackgroundColor(headingColorValue);
|
||||
|
||||
LetterBitmap letterBitmap = Utils.generateIcon(this, store, headingColorValue);
|
||||
|
||||
if (letterBitmap != null) {
|
||||
thumbnail.setImageBitmap(letterBitmap.getLetterTile());
|
||||
} else {
|
||||
thumbnail.setImageBitmap(null);
|
||||
}
|
||||
|
||||
thumbnail.setMinimumWidth(thumbnail.getHeight());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Application;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
import protect.card_locker.preferences.Settings;
|
||||
|
||||
public class LoyaltyCardLockerApplication extends Application {
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Settings settings = new Settings(getApplicationContext());
|
||||
AppCompatDelegate.setDefaultNightMode(settings.getTheme());
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,139 @@
|
||||
package protect.card_locker;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import androidx.core.widget.TextViewCompat;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
import protect.card_locker.preferences.Settings;
|
||||
|
||||
public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "CardLocker";
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private static final int SELECT_BARCODE_REQUEST = 1;
|
||||
TextView cardIdFieldView;
|
||||
TextView noteView;
|
||||
View noteViewDivider;
|
||||
TextView storeName;
|
||||
ImageView barcodeImage;
|
||||
View collapsingToolbarLayout;
|
||||
int loyaltyCardId;
|
||||
LoyaltyCard loyaltyCard;
|
||||
boolean rotationEnabled;
|
||||
DBHelper db;
|
||||
ImportURIHelper importURIHelper;
|
||||
Settings settings;
|
||||
|
||||
String cardIdString;
|
||||
BarcodeFormat format;
|
||||
|
||||
boolean starred;
|
||||
boolean backgroundNeedsDarkIcons;
|
||||
boolean barcodeIsFullscreen = false;
|
||||
ViewGroup.LayoutParams barcodeImageState;
|
||||
|
||||
private void extractIntentFields(Intent intent)
|
||||
{
|
||||
final Bundle b = intent.getExtras();
|
||||
loyaltyCardId = b != null ? b.getInt("id") : 0;
|
||||
Log.d(TAG, "View activity: id=" + loyaltyCardId);
|
||||
}
|
||||
|
||||
private Drawable getIcon(int icon, boolean dark)
|
||||
{
|
||||
Drawable unwrappedIcon = AppCompatResources.getDrawable(this, icon);
|
||||
Drawable wrappedIcon = DrawableCompat.wrap(unwrappedIcon);
|
||||
if(dark)
|
||||
{
|
||||
DrawableCompat.setTint(wrappedIcon, Color.BLACK);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawableCompat.setTintList(wrappedIcon, null);
|
||||
}
|
||||
|
||||
return wrappedIcon;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.loyalty_card_view_activity);
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
settings = new Settings(this);
|
||||
|
||||
extractIntentFields(getIntent());
|
||||
|
||||
setContentView(R.layout.loyalty_card_view_layout);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
db = new DBHelper(this);
|
||||
importURIHelper = new ImportURIHelper(this);
|
||||
|
||||
cardIdFieldView = findViewById(R.id.cardIdView);
|
||||
noteView = findViewById(R.id.noteView);
|
||||
noteViewDivider = findViewById(R.id.noteViewDivider);
|
||||
storeName = findViewById(R.id.storeName);
|
||||
barcodeImage = findViewById(R.id.barcode);
|
||||
collapsingToolbarLayout = findViewById(R.id.collapsingToolbarLayout);
|
||||
|
||||
rotationEnabled = true;
|
||||
|
||||
// Allow making barcode fullscreen on tap
|
||||
barcodeImage.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if(barcodeIsFullscreen)
|
||||
{
|
||||
setFullscreen(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
setFullscreen(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent)
|
||||
{
|
||||
super.onNewIntent(intent);
|
||||
|
||||
Log.i(TAG, "Received new intent");
|
||||
extractIntentFields(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,214 +141,199 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
{
|
||||
super.onResume();
|
||||
|
||||
final Bundle b = getIntent().getExtras();
|
||||
final int loyaltyCardId = b != null ? b.getInt("id") : 0;
|
||||
final boolean updateLoyaltyCard = b != null && b.getBoolean("update", false);
|
||||
final boolean viewLoyaltyCard = b != null && b.getBoolean("view", false);
|
||||
|
||||
Log.i(TAG, "To view card: " + loyaltyCardId);
|
||||
|
||||
final EditText storeField = (EditText) findViewById(R.id.storeName);
|
||||
final EditText noteField = (EditText) findViewById(R.id.note);
|
||||
final EditText cardIdField = (EditText) findViewById(R.id.cardId);
|
||||
final EditText barcodeTypeField = (EditText) findViewById(R.id.barcodeType);
|
||||
final ImageView barcodeImage = (ImageView) findViewById(R.id.barcode);
|
||||
final View barcodeIdLayout = findViewById(R.id.barcodeIdLayout);
|
||||
final View barcodeTypeLayout = findViewById(R.id.barcodeTypeLayout);
|
||||
final View barcodeImageLayout = findViewById(R.id.barcodeLayout);
|
||||
final View barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
|
||||
|
||||
final Button captureButton = (Button) findViewById(R.id.captureButton);
|
||||
final Button enterButton = (Button) findViewById(R.id.enterButton);
|
||||
final Button saveButton = (Button) findViewById(R.id.saveButton);
|
||||
final Button cancelButton = (Button) findViewById(R.id.cancelButton);
|
||||
|
||||
final DBHelper db = new DBHelper(this);
|
||||
|
||||
if(updateLoyaltyCard || viewLoyaltyCard)
|
||||
if(barcodeIsFullscreen)
|
||||
{
|
||||
final LoyaltyCard loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
|
||||
// Completely reset state
|
||||
//
|
||||
// This prevents the barcode from taking up the entire screen
|
||||
// on resume and thus being stretched out of proportion.
|
||||
recreate();
|
||||
}
|
||||
|
||||
if(storeField.getText().length() == 0)
|
||||
{
|
||||
storeField.setText(loyaltyCard.store);
|
||||
}
|
||||
// The brightness value is on a scale from [0, ..., 1], where
|
||||
// '1' is the brightest. We attempt to maximize the brightness
|
||||
// to help barcode readers scan the barcode.
|
||||
Window window = getWindow();
|
||||
if(window != null && settings.useMaxBrightnessDisplayingBarcode())
|
||||
{
|
||||
WindowManager.LayoutParams attributes = window.getAttributes();
|
||||
attributes.screenBrightness = 1F;
|
||||
window.setAttributes(attributes);
|
||||
}
|
||||
|
||||
if(noteField.getText().length() == 0)
|
||||
{
|
||||
noteField.setText(loyaltyCard.note);
|
||||
}
|
||||
loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
|
||||
if(loyaltyCard == null)
|
||||
{
|
||||
Log.w(TAG, "Could not lookup loyalty card " + loyaltyCardId);
|
||||
Toast.makeText(this, R.string.noCardExistsError, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if(cardIdField.getText().length() == 0)
|
||||
{
|
||||
cardIdField.setText(loyaltyCard.cardId);
|
||||
}
|
||||
String formatString = loyaltyCard.barcodeType;
|
||||
format = !formatString.isEmpty() ? BarcodeFormat.valueOf(formatString) : null;
|
||||
cardIdString = loyaltyCard.cardId;
|
||||
|
||||
if(barcodeTypeField.getText().length() == 0)
|
||||
{
|
||||
barcodeTypeField.setText(loyaltyCard.barcodeType);
|
||||
}
|
||||
cardIdFieldView.setText(loyaltyCard.cardId);
|
||||
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(cardIdFieldView,
|
||||
getResources().getInteger(R.integer.settings_card_id_min_font_size_sp)-1, settings.getCardIdFontSize(),
|
||||
1, TypedValue.COMPLEX_UNIT_SP);
|
||||
|
||||
if(viewLoyaltyCard)
|
||||
{
|
||||
storeField.setEnabled(false);
|
||||
noteField.setEnabled(false);
|
||||
}
|
||||
|
||||
if(updateLoyaltyCard)
|
||||
{
|
||||
setTitle(R.string.editCardTitle);
|
||||
}
|
||||
else
|
||||
{
|
||||
barcodeCaptureLayout.setVisibility(View.GONE);
|
||||
captureButton.setVisibility(View.GONE);
|
||||
saveButton.setVisibility(View.GONE);
|
||||
cancelButton.setVisibility(View.GONE);
|
||||
setTitle(R.string.viewCardTitle);
|
||||
}
|
||||
if(loyaltyCard.note.length() > 0)
|
||||
{
|
||||
noteView.setText(loyaltyCard.note);
|
||||
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(noteView,
|
||||
getResources().getInteger(R.integer.settings_card_note_min_font_size_sp)-1,
|
||||
settings.getCardNoteFontSize(), 1, TypedValue.COMPLEX_UNIT_SP);
|
||||
}
|
||||
else
|
||||
{
|
||||
setTitle(R.string.addCardTitle);
|
||||
noteView.setVisibility(View.GONE);
|
||||
noteViewDivider.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if(cardIdField.getText().length() == 0)
|
||||
storeName.setText(loyaltyCard.store);
|
||||
storeName.setTextSize(settings.getCardTitleFontSize());
|
||||
|
||||
int backgroundHeaderColor;
|
||||
if(loyaltyCard.headerColor != null)
|
||||
{
|
||||
barcodeIdLayout.setVisibility(View.GONE);
|
||||
backgroundHeaderColor = loyaltyCard.headerColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
backgroundHeaderColor = LetterBitmap.getDefaultColor(this, loyaltyCard.store);
|
||||
}
|
||||
|
||||
barcodeTypeLayout.setVisibility(View.GONE);
|
||||
collapsingToolbarLayout.setBackgroundColor(backgroundHeaderColor);
|
||||
|
||||
if(cardIdField.getText().length() > 0 && barcodeTypeField.getText().length() > 0)
|
||||
int textColor;
|
||||
if(Utils.needsDarkForeground(backgroundHeaderColor))
|
||||
{
|
||||
String formatString = barcodeTypeField.getText().toString();
|
||||
final BarcodeFormat format = BarcodeFormat.valueOf(formatString);
|
||||
final String cardIdString = cardIdField.getText().toString();
|
||||
textColor = Color.BLACK;
|
||||
}
|
||||
else
|
||||
{
|
||||
textColor = Color.WHITE;
|
||||
}
|
||||
storeName.setTextColor(textColor);
|
||||
|
||||
// If the background is very bright, we should use dark icons
|
||||
backgroundNeedsDarkIcons = Utils.needsDarkForeground(backgroundHeaderColor);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.setHomeAsUpIndicator(getIcon(R.drawable.ic_arrow_back_white, backgroundNeedsDarkIcons));
|
||||
}
|
||||
|
||||
// Make notification area light if dark icons are needed
|
||||
if(Build.VERSION.SDK_INT >= 23)
|
||||
{
|
||||
window.getDecorView().setSystemUiVisibility(backgroundNeedsDarkIcons ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0);
|
||||
}
|
||||
if(Build.VERSION.SDK_INT >= 21)
|
||||
{
|
||||
window.setStatusBarColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
// Set shadow colour of store text so even same color on same color would be readable
|
||||
storeName.setShadowLayer(1, 1, 1, backgroundNeedsDarkIcons ? Color.BLACK : Color.WHITE);
|
||||
|
||||
if(format != null)
|
||||
{
|
||||
findViewById(R.id.barcode).setVisibility(View.VISIBLE);
|
||||
if(barcodeImage.getHeight() == 0)
|
||||
{
|
||||
Log.d(TAG, "ImageView size is not known known at start, waiting for load");
|
||||
// The size of the ImageView is not yet available as it has not
|
||||
// yet been drawn. Wait for it to be drawn so the size is available.
|
||||
barcodeImage.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener()
|
||||
{
|
||||
@Override
|
||||
public void onGlobalLayout()
|
||||
new ViewTreeObserver.OnGlobalLayoutListener()
|
||||
{
|
||||
if (Build.VERSION.SDK_INT < 16)
|
||||
{
|
||||
barcodeImage.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
else
|
||||
@Override
|
||||
public void onGlobalLayout()
|
||||
{
|
||||
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
|
||||
Log.d(TAG, "ImageView size now known");
|
||||
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
|
||||
}
|
||||
});
|
||||
Log.d(TAG, "ImageView size now known");
|
||||
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.d(TAG, "ImageView size known known, creating barcode");
|
||||
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
|
||||
}
|
||||
|
||||
barcodeIdLayout.setVisibility(View.VISIBLE);
|
||||
barcodeImageLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
findViewById(R.id.barcode).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
View.OnClickListener captureCallback = new View.OnClickListener()
|
||||
{
|
||||
FloatingActionButton editButton = findViewById(R.id.fabEdit);
|
||||
editButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
IntentIntegrator integrator = new IntentIntegrator(LoyaltyCardViewActivity.this);
|
||||
integrator.setDesiredBarcodeFormats(BarcodeSelectorActivity.SUPPORTED_BARCODE_TYPES);
|
||||
|
||||
String prompt = getResources().getString(R.string.scanCardBarcode);
|
||||
integrator.setPrompt(prompt);
|
||||
integrator.initiateScan();
|
||||
}
|
||||
};
|
||||
|
||||
captureButton.setOnClickListener(captureCallback);
|
||||
|
||||
enterButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class);
|
||||
startActivityForResult(i, SELECT_BARCODE_REQUEST);
|
||||
}
|
||||
});
|
||||
|
||||
saveButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(final View v)
|
||||
{
|
||||
String store = storeField.getText().toString();
|
||||
String note = noteField.getText().toString();
|
||||
String cardId = cardIdField.getText().toString();
|
||||
String barcodeType = barcodeTypeField.getText().toString();
|
||||
|
||||
if(store.isEmpty())
|
||||
{
|
||||
Snackbar.make(v, R.string.noStoreError, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if(cardId.isEmpty() || barcodeType.isEmpty())
|
||||
{
|
||||
Snackbar.make(v, R.string.noCardIdError, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if(updateLoyaltyCard)
|
||||
{
|
||||
db.updateLoyaltyCard(loyaltyCardId, store, note, cardId, barcodeType);
|
||||
Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
|
||||
}
|
||||
else
|
||||
{
|
||||
db.insertLoyaltyCard(store, note, cardId, barcodeType);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
cancelButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt("id", loyaltyCardId);
|
||||
bundle.putBoolean("update", true);
|
||||
intent.putExtras(bundle);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (barcodeIsFullscreen)
|
||||
{
|
||||
setFullscreen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
super.onBackPressed();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
final Bundle b = getIntent().getExtras();
|
||||
final boolean updateLoyaltyCard = b != null && b.getBoolean("update", false);
|
||||
final boolean viewLoyaltyCard = b != null && b.getBoolean("view", false);
|
||||
getMenuInflater().inflate(R.menu.card_view_menu, menu);
|
||||
|
||||
if(viewLoyaltyCard)
|
||||
// Always calculate lockscreen icon, it may need a black color
|
||||
boolean lockBarcodeScreenOrientation = settings.getLockBarcodeScreenOrientation();
|
||||
MenuItem item = menu.findItem(R.id.action_lock_unlock);
|
||||
setOrientatonLock(item, lockBarcodeScreenOrientation);
|
||||
if(lockBarcodeScreenOrientation)
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.card_edit_menu, menu);
|
||||
}
|
||||
else if(updateLoyaltyCard)
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.card_delete_menu, menu);
|
||||
item.setVisible(false);
|
||||
}
|
||||
|
||||
loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
|
||||
starred = loyaltyCard.starStatus != 0;
|
||||
|
||||
menu.findItem(R.id.action_share).setIcon(getIcon(R.drawable.ic_share_white, backgroundNeedsDarkIcons));
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
if (starred) {
|
||||
menu.findItem(R.id.action_star_unstar).setIcon(getIcon(R.drawable.ic_starred_white, backgroundNeedsDarkIcons));
|
||||
menu.findItem(R.id.action_star_unstar).setTitle(R.string.unstar);
|
||||
}
|
||||
else {
|
||||
menu.findItem(R.id.action_star_unstar).setIcon(getIcon(R.drawable.ic_unstarred_white, backgroundNeedsDarkIcons));
|
||||
menu.findItem(R.id.action_star_unstar).setTitle(R.string.star);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -265,70 +342,127 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
{
|
||||
int id = item.getItemId();
|
||||
|
||||
final Bundle b = getIntent().getExtras();
|
||||
final int loyaltyCardId = b != null ? b.getInt("id") : 0;
|
||||
|
||||
switch(id)
|
||||
{
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
break;
|
||||
|
||||
case R.id.action_delete:
|
||||
Log.e(TAG, "Deleting card: " + loyaltyCardId);
|
||||
|
||||
DBHelper db = new DBHelper(this);
|
||||
db.deleteLoyaltyCard(loyaltyCardId);
|
||||
finish();
|
||||
case R.id.action_share:
|
||||
importURIHelper.startShareIntent(loyaltyCard);
|
||||
return true;
|
||||
case R.id.action_edit:
|
||||
Intent intent = new Intent(getApplicationContext(), LoyaltyCardViewActivity.class);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt("id", loyaltyCardId);
|
||||
bundle.putBoolean("update", true);
|
||||
intent.putExtras(bundle);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
|
||||
case R.id.action_lock_unlock:
|
||||
if(rotationEnabled)
|
||||
{
|
||||
setOrientatonLock(item, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
setOrientatonLock(item, false);
|
||||
}
|
||||
rotationEnabled = !rotationEnabled;
|
||||
return true;
|
||||
|
||||
case R.id.action_star_unstar:
|
||||
starred = !starred;
|
||||
db.updateLoyaltyCardStarStatus(loyaltyCardId, starred ? 1 : 0);
|
||||
invalidateOptionsMenu();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent)
|
||||
|
||||
private void setOrientatonLock(MenuItem item, boolean lock)
|
||||
{
|
||||
String contents = null;
|
||||
String format = null;
|
||||
|
||||
IntentResult result =
|
||||
IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
||||
if (result != null)
|
||||
if(lock)
|
||||
{
|
||||
Log.i(TAG, "Received barcode information from capture");
|
||||
contents = result.getContents();
|
||||
format = result.getFormatName();
|
||||
|
||||
item.setIcon(getIcon(R.drawable.ic_lock_outline_white_24dp, backgroundNeedsDarkIcons));
|
||||
item.setTitle(R.string.unlockScreen);
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
|
||||
}
|
||||
|
||||
if(requestCode == SELECT_BARCODE_REQUEST && resultCode == Activity.RESULT_OK)
|
||||
else
|
||||
{
|
||||
Log.i(TAG, "Received barcode information from capture");
|
||||
|
||||
contents = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_CONTENTS);
|
||||
format = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_FORMAT);
|
||||
}
|
||||
|
||||
if(contents != null && contents.isEmpty() == false &&
|
||||
format != null && format.isEmpty() == false)
|
||||
{
|
||||
Log.i(TAG, "Read barcode id: " + contents);
|
||||
Log.i(TAG, "Read format: " + format);
|
||||
|
||||
final EditText cardIdField = (EditText) findViewById(R.id.cardId);
|
||||
cardIdField.setText(contents);
|
||||
final EditText barcodeTypeField = (EditText) findViewById(R.id.barcodeType);
|
||||
barcodeTypeField.setText(format);
|
||||
onResume();
|
||||
item.setIcon(getIcon(R.drawable.ic_lock_open_white_24dp, backgroundNeedsDarkIcons));
|
||||
item.setTitle(R.string.lockScreen);
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When enabled, hides the status bar and moves the barcode to the top of the screen.
|
||||
*
|
||||
* The purpose of this function is to make sure the barcode can be scanned from the phone
|
||||
* by machines which offer no space to insert the complete device.
|
||||
*/
|
||||
private void setFullscreen(boolean enable)
|
||||
{
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(enable && !barcodeIsFullscreen)
|
||||
{
|
||||
// Save previous barcodeImage state
|
||||
barcodeImageState = barcodeImage.getLayoutParams();
|
||||
|
||||
// Hide actionbar
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.hide();
|
||||
}
|
||||
|
||||
// Hide collapsingToolbar
|
||||
collapsingToolbarLayout.setVisibility(View.GONE);
|
||||
|
||||
// Set Android to fullscreen mode
|
||||
getWindow().getDecorView().setSystemUiVisibility(
|
||||
getWindow().getDecorView().getSystemUiVisibility()
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
);
|
||||
|
||||
// Make barcode take all space
|
||||
barcodeImage.setLayoutParams(new ConstraintLayout.LayoutParams(
|
||||
ConstraintLayout.LayoutParams.MATCH_PARENT,
|
||||
ConstraintLayout.LayoutParams.MATCH_PARENT
|
||||
));
|
||||
|
||||
// Move barcode to top
|
||||
barcodeImage.setScaleType(ImageView.ScaleType.FIT_START);
|
||||
|
||||
// Prevent centering
|
||||
barcodeImage.setAdjustViewBounds(false);
|
||||
|
||||
// Set current state
|
||||
barcodeIsFullscreen = true;
|
||||
}
|
||||
else if(!enable && barcodeIsFullscreen)
|
||||
{
|
||||
// Show actionbar
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.show();
|
||||
}
|
||||
|
||||
// Show collapsingToolbar
|
||||
collapsingToolbarLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
// Unset fullscreen mode
|
||||
getWindow().getDecorView().setSystemUiVisibility(
|
||||
getWindow().getDecorView().getSystemUiVisibility()
|
||||
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
);
|
||||
|
||||
// Turn barcode back to normal
|
||||
barcodeImage.setLayoutParams(barcodeImageState);
|
||||
|
||||
// Fix barcode centering
|
||||
barcodeImage.setAdjustViewBounds(true);
|
||||
|
||||
// Set current state
|
||||
barcodeIsFullscreen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +1,220 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import protect.card_locker.preferences.SettingsActivity;
|
||||
|
||||
public class MainActivity extends AppCompatActivity
|
||||
public class MainActivity extends AppCompatActivity implements GestureDetector.OnGestureListener
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
private static final String TAG = "Catima";
|
||||
private static final int MAIN_REQUEST_CODE = 1;
|
||||
|
||||
private Menu menu;
|
||||
private GestureDetector gestureDetector;
|
||||
protected String filter = "";
|
||||
protected int selectedTab = 0;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main_activity);
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
updateLoyaltyCardList();
|
||||
updateLoyaltyCardList(filter, null);
|
||||
|
||||
TabLayout groupsTabLayout = findViewById(R.id.groups);
|
||||
groupsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) {
|
||||
selectedTab = tab.getPosition();
|
||||
updateLoyaltyCardList(filter, tab.getTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
gestureDetector = new GestureDetector(this, this);
|
||||
|
||||
View.OnTouchListener gestureTouchListener = new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(final View v, final MotionEvent event){
|
||||
return gestureDetector.onTouchEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
final View helpText = findViewById(R.id.helpText);
|
||||
final View noMatchingCardsText = findViewById(R.id.noMatchingCardsText);
|
||||
final View list = findViewById(R.id.list);
|
||||
|
||||
helpText.setOnTouchListener(gestureTouchListener);
|
||||
noMatchingCardsText.setOnTouchListener(gestureTouchListener);
|
||||
list.setOnTouchListener(gestureTouchListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume()
|
||||
{
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
updateLoyaltyCardList();
|
||||
if (menu != null)
|
||||
{
|
||||
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
|
||||
|
||||
if (!searchView.isIconified()) {
|
||||
filter = searchView.getQuery().toString();
|
||||
}
|
||||
}
|
||||
|
||||
TabLayout groupsTabLayout = findViewById(R.id.groups);
|
||||
boolean hasReset = updateTabGroups(groupsTabLayout);
|
||||
|
||||
Object group = null;
|
||||
|
||||
if (groupsTabLayout.getTabCount() != 0) {
|
||||
TabLayout.Tab tab = groupsTabLayout.getTabAt(0);
|
||||
|
||||
if (!hasReset) {
|
||||
tab = groupsTabLayout.getTabAt(selectedTab);
|
||||
}
|
||||
|
||||
groupsTabLayout.selectTab(tab);
|
||||
group = tab.getTag();
|
||||
}
|
||||
updateLoyaltyCardList(filter, group);
|
||||
|
||||
FloatingActionButton addButton = findViewById(R.id.fabAdd);
|
||||
addButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent i = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||
startActivityForResult(i, MAIN_REQUEST_CODE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateLoyaltyCardList()
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == MAIN_REQUEST_CODE)
|
||||
{
|
||||
// We're coming back from another view so clear the search
|
||||
// We only do this now to prevent a flash of all entries right after picking one
|
||||
filter = "";
|
||||
if (menu != null)
|
||||
{
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
searchItem.collapseActionView();
|
||||
}
|
||||
|
||||
// In case the theme changed
|
||||
recreate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (menu == null)
|
||||
{
|
||||
super.onBackPressed();
|
||||
return;
|
||||
}
|
||||
|
||||
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
|
||||
|
||||
if (!searchView.isIconified()) {
|
||||
searchView.setIconified(true);
|
||||
} else {
|
||||
TabLayout groupsTabLayout = findViewById(R.id.groups);
|
||||
|
||||
if (groupsTabLayout.getVisibility() == View.VISIBLE && selectedTab != 0) {
|
||||
selectedTab = 0;
|
||||
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(0));
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLoyaltyCardList(String filterText, Object tag)
|
||||
{
|
||||
final ListView cardList = (ListView) findViewById(R.id.list);
|
||||
final TextView helpText = (TextView) findViewById(R.id.helpText);
|
||||
Group group = null;
|
||||
if (tag != null) {
|
||||
group = (Group) tag;
|
||||
}
|
||||
|
||||
final ListView cardList = findViewById(R.id.list);
|
||||
final TextView helpText = findViewById(R.id.helpText);
|
||||
final TextView noMatchingCardsText = findViewById(R.id.noMatchingCardsText);
|
||||
final DBHelper db = new DBHelper(this);
|
||||
|
||||
Cursor cardCursor = db.getLoyaltyCardCursor(filterText, group);
|
||||
|
||||
if(db.getLoyaltyCardCount() > 0)
|
||||
{
|
||||
// We want the cardList to be visible regardless of the filtered match count
|
||||
// to ensure that the noMatchingCardsText doesn't end up being shown below
|
||||
// the keyboard
|
||||
cardList.setVisibility(View.VISIBLE);
|
||||
helpText.setVisibility(View.GONE);
|
||||
if(cardCursor.getCount() > 0)
|
||||
{
|
||||
noMatchingCardsText.setVisibility(View.GONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
noMatchingCardsText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cardList.setVisibility(View.GONE);
|
||||
helpText.setVisibility(View.VISIBLE);
|
||||
noMatchingCardsText.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
Cursor cardCursor = db.getLoyaltyCardCursor();
|
||||
|
||||
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor);
|
||||
cardList.setAdapter(adapter);
|
||||
|
||||
@@ -81,15 +229,65 @@ public class MainActivity extends AppCompatActivity
|
||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
|
||||
|
||||
Intent i = new Intent(view.getContext(), LoyaltyCardViewActivity.class);
|
||||
i.setAction("");
|
||||
final Bundle b = new Bundle();
|
||||
b.putInt("id", loyaltyCard.id);
|
||||
b.putBoolean("view", true);
|
||||
i.putExtras(b);
|
||||
startActivity(i);
|
||||
|
||||
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard, i);
|
||||
|
||||
startActivityForResult(i, MAIN_REQUEST_CODE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean updateTabGroups(TabLayout groupsTabLayout)
|
||||
{
|
||||
final DBHelper db = new DBHelper(this);
|
||||
|
||||
List<Group> newGroups = db.getGroups();
|
||||
|
||||
if (newGroups.size() == 0) {
|
||||
groupsTabLayout.removeAllTabs();
|
||||
groupsTabLayout.setVisibility(View.GONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
// -1 because there is an "All" tab
|
||||
boolean isChanged = groupsTabLayout.getTabCount() - 1 != newGroups.size();
|
||||
|
||||
if (!isChanged) {
|
||||
for (int i = 0; i < newGroups.size(); i++) {
|
||||
if (!((Group) groupsTabLayout.getTabAt(i + 1).getTag())._id.equals(newGroups.get(i)._id)) {
|
||||
isChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isChanged) {
|
||||
groupsTabLayout.removeAllTabs();
|
||||
|
||||
TabLayout.Tab allTab = groupsTabLayout.newTab();
|
||||
allTab.setText(R.string.all);
|
||||
allTab.setTag(null);
|
||||
groupsTabLayout.addTab(allTab);
|
||||
|
||||
for (Group group : newGroups) {
|
||||
TabLayout.Tab tab = groupsTabLayout.newTab();
|
||||
tab.setText(group._id);
|
||||
tab.setTag(group);
|
||||
groupsTabLayout.addTab(tab);
|
||||
}
|
||||
|
||||
groupsTabLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)
|
||||
{
|
||||
@@ -105,12 +303,12 @@ public class MainActivity extends AppCompatActivity
|
||||
public boolean onContextItemSelected(MenuItem item)
|
||||
{
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
|
||||
ListView listView = (ListView) findViewById(R.id.list);
|
||||
ListView listView = findViewById(R.id.list);
|
||||
|
||||
Cursor cardCursor = (Cursor)listView.getItemAtPosition(info.position);
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
|
||||
|
||||
if(card != null && item.getItemId() == R.id.action_clipboard)
|
||||
if(item.getItemId() == R.id.action_clipboard)
|
||||
{
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(card.store, card.cardId);
|
||||
@@ -119,6 +317,12 @@ public class MainActivity extends AppCompatActivity
|
||||
Toast.makeText(this, R.string.copy_to_clipboard_toast, Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
else if(item.getItemId() == R.id.action_share)
|
||||
{
|
||||
final ImportURIHelper importURIHelper = new ImportURIHelper(this);
|
||||
importURIHelper.startShareIntent(card);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
@@ -126,7 +330,42 @@ public class MainActivity extends AppCompatActivity
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
this.menu = menu;
|
||||
|
||||
getMenuInflater().inflate(R.menu.main_menu, menu);
|
||||
|
||||
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
|
||||
if (searchManager != null) {
|
||||
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
|
||||
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
|
||||
searchView.setSubmitButtonEnabled(false);
|
||||
|
||||
searchView.setOnCloseListener(new SearchView.OnCloseListener() {
|
||||
@Override
|
||||
public boolean onClose() {
|
||||
invalidateOptionsMenu();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
filter = newText;
|
||||
|
||||
TabLayout groupsTabLayout = findViewById(R.id.groups);
|
||||
|
||||
updateLoyaltyCardList(newText, groupsTabLayout.getTabCount() > 0 ? groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition()).getTag() : null);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@@ -135,17 +374,24 @@ public class MainActivity extends AppCompatActivity
|
||||
{
|
||||
int id = item.getItemId();
|
||||
|
||||
if (id == R.id.action_add)
|
||||
if (id == R.id.action_manage_groups)
|
||||
{
|
||||
Intent i = new Intent(getApplicationContext(), LoyaltyCardViewActivity.class);
|
||||
startActivity(i);
|
||||
Intent i = new Intent(getApplicationContext(), ManageGroupsActivity.class);
|
||||
startActivityForResult(i, MAIN_REQUEST_CODE);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(id == R.id.action_import_export)
|
||||
{
|
||||
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
|
||||
startActivity(i);
|
||||
startActivityForResult(i, MAIN_REQUEST_CODE);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(id == R.id.action_settings)
|
||||
{
|
||||
Intent i = new Intent(getApplicationContext(), SettingsActivity.class);
|
||||
startActivityForResult(i, MAIN_REQUEST_CODE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -160,21 +406,35 @@ public class MainActivity extends AppCompatActivity
|
||||
|
||||
private void displayAboutDialog()
|
||||
{
|
||||
final String[][] USED_LIBRARIES = new String[][]
|
||||
{
|
||||
new String[] {"Commons CSV", "https://commons.apache.org/proper/commons-csv/"},
|
||||
new String[] {"Guava", "https://github.com/google/guava"},
|
||||
new String[] {"ZXing", "https://github.com/zxing/zxing"},
|
||||
new String[] {"ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded"},
|
||||
};
|
||||
final Map<String, String> USED_LIBRARIES = new ImmutableMap.Builder<String, String>()
|
||||
.put("Commons CSV", "https://commons.apache.org/proper/commons-csv/")
|
||||
.put("Guava", "https://github.com/google/guava")
|
||||
.put("ZXing", "https://github.com/zxing/zxing")
|
||||
.put("ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded")
|
||||
.put("AppIntro", "https://github.com/apl-devs/AppIntro")
|
||||
.put("Color Picker", "https://github.com/jaredrummler/ColorPicker")
|
||||
.put("VNTNumberPickerPreference", "https://github.com/vanniktech/VNTNumberPickerPreference")
|
||||
.build();
|
||||
|
||||
final Map<String, String> USED_ASSETS = ImmutableMap.of
|
||||
(
|
||||
"Save by Bernar Novalyi", "https://thenounproject.com/term/save/716011"
|
||||
);
|
||||
|
||||
StringBuilder libs = new StringBuilder().append("<ul>");
|
||||
for (String[] library : USED_LIBRARIES)
|
||||
for (Map.Entry<String, String> entry : USED_LIBRARIES.entrySet())
|
||||
{
|
||||
libs.append("<li><a href=\"").append(library[1]).append("\">").append(library[0]).append("</a></li>");
|
||||
libs.append("<li><a href=\"").append(entry.getValue()).append("\">").append(entry.getKey()).append("</a></li>");
|
||||
}
|
||||
libs.append("</ul>");
|
||||
|
||||
StringBuilder resources = new StringBuilder().append("<ul>");
|
||||
for (Map.Entry<String, String> entry : USED_ASSETS.entrySet())
|
||||
{
|
||||
resources.append("<li><a href=\"").append(entry.getValue()).append("\">").append(entry.getKey()).append("</a></li>");
|
||||
}
|
||||
resources.append("</ul>");
|
||||
|
||||
String appName = getString(R.string.app_name);
|
||||
int year = Calendar.getInstance().get(Calendar.YEAR);
|
||||
|
||||
@@ -190,9 +450,17 @@ public class MainActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
WebView wv = new WebView(this);
|
||||
|
||||
// Set CSS for dark mode if dark mode
|
||||
String css = "";
|
||||
if(isDarkModeEnabled(this))
|
||||
{
|
||||
css = "<style>body {color:white; background-color:black;}</style>";
|
||||
}
|
||||
|
||||
String html =
|
||||
"<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />" +
|
||||
"<img src=\"file:///android_res/mipmap/ic_launcher.png\" alt=\"" + appName + "\"/>" +
|
||||
css +
|
||||
"<h1>" +
|
||||
String.format(getString(R.string.about_title_fmt),
|
||||
"<a href=\"" + getString(R.string.app_webpage_url)) + "\">" +
|
||||
@@ -205,14 +473,18 @@ public class MainActivity extends AppCompatActivity
|
||||
"</p><p>" +
|
||||
String.format(getString(R.string.app_revision_fmt),
|
||||
"<a href=\"" + getString(R.string.app_revision_url) + "\">" +
|
||||
getString(R.string.app_revision_url) +
|
||||
"GitHub" +
|
||||
"</a>") +
|
||||
"</p><hr/><p>" +
|
||||
String.format(getString(R.string.app_copyright_fmt), year) +
|
||||
"</p><p>" +
|
||||
getString(R.string.app_copyright_old) +
|
||||
"</p><hr/><p>" +
|
||||
getString(R.string.app_license) +
|
||||
"</p><hr/><p>" +
|
||||
String.format(getString(R.string.app_libraries), appName, libs.toString());
|
||||
String.format(getString(R.string.app_libraries), appName, libs.toString()) +
|
||||
"</p><hr/><p>" +
|
||||
String.format(getString(R.string.app_resources), appName, resources.toString());
|
||||
|
||||
wv.loadDataWithBaseURL("file:///android_res/drawable/", html, "text/html", "utf-8", null);
|
||||
new AlertDialog.Builder(this)
|
||||
@@ -227,4 +499,81 @@ public class MainActivity extends AppCompatActivity
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
protected static boolean isDarkModeEnabled(Context inputContext)
|
||||
{
|
||||
Configuration config = inputContext.getResources().getConfiguration();
|
||||
int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||
return (currentNightMode == Configuration.UI_MODE_NIGHT_YES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowPress(MotionEvent e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
Log.d(TAG, "On fling");
|
||||
|
||||
// Don't swipe if we have too much vertical movement
|
||||
if (Math.abs(velocityY) > (0.75 * Math.abs(velocityX))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TabLayout groupsTabLayout = findViewById(R.id.groups);
|
||||
if (groupsTabLayout.getTabCount() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Integer currentTab = groupsTabLayout.getSelectedTabPosition();
|
||||
|
||||
// Swipe right
|
||||
if (velocityX < -150) {
|
||||
Integer nextTab = currentTab + 1;
|
||||
|
||||
if (nextTab == groupsTabLayout.getTabCount()) {
|
||||
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(0));
|
||||
} else {
|
||||
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(nextTab));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Swipe left
|
||||
if (velocityX > 150) {
|
||||
Integer nextTab = currentTab - 1;
|
||||
|
||||
if (nextTab < 0) {
|
||||
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(groupsTabLayout.getTabCount() - 1));
|
||||
} else {
|
||||
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(nextTab));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
186
app/src/main/java/protect/card_locker/ManageGroupsActivity.java
Normal file
@@ -0,0 +1,186 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
public class ManageGroupsActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private AlertDialog newGroupDialog;
|
||||
private final DBHelper db = new DBHelper(this);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.manage_groups_activity);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
newGroupDialog = createNewGroupDialog();
|
||||
|
||||
updateGroupList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
updateGroupList();
|
||||
|
||||
FloatingActionButton addButton = findViewById(R.id.fabAdd);
|
||||
addButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
newGroupDialog.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
private void updateGroupList()
|
||||
{
|
||||
final ListView groupList = findViewById(R.id.list);
|
||||
final TextView helpText = findViewById(R.id.helpText);
|
||||
final DBHelper db = new DBHelper(this);
|
||||
|
||||
if(db.getGroupCount() > 0)
|
||||
{
|
||||
groupList.setVisibility(View.VISIBLE);
|
||||
helpText.setVisibility(View.GONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
groupList.setVisibility(View.GONE);
|
||||
helpText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
Cursor groupCursor = db.getGroupCursor();
|
||||
|
||||
final GroupCursorAdapter adapter = new GroupCursorAdapter(this, groupCursor);
|
||||
groupList.setAdapter(adapter);
|
||||
|
||||
registerForContextMenu(groupList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
int id = item.getItemId();
|
||||
|
||||
if (id == android.R.id.home) {
|
||||
finish();
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public void editGroup(View view) {
|
||||
LinearLayout parentRow = (LinearLayout) view.getParent();
|
||||
|
||||
TextView groupNameTextView = (TextView) parentRow.findViewById(R.id.name);
|
||||
|
||||
final String groupName = (String) groupNameTextView.getText();
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.enter_group_name);
|
||||
final EditText input = new EditText(this);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
input.setText(groupName);
|
||||
builder.setView(input);
|
||||
|
||||
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
db.updateGroup(groupName, input.getText().toString());
|
||||
updateGroupList();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public void deleteGroup(View view) {
|
||||
LinearLayout parentRow = (LinearLayout) view.getParent();
|
||||
|
||||
TextView groupNameTextView = (TextView) parentRow.findViewById(R.id.name);
|
||||
|
||||
final String groupName = (String) groupNameTextView.getText();
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.deleteConfirmationGroup);
|
||||
builder.setMessage(groupName);
|
||||
|
||||
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
db.deleteGroup(groupName);
|
||||
updateGroupList();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private AlertDialog createNewGroupDialog() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.enter_group_name);
|
||||
final EditText input = new EditText(this);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
builder.setView(input);
|
||||
|
||||
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
db.insertGroup(input.getText().toString());
|
||||
updateGroupList();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import java.io.OutputStreamWriter;
|
||||
|
||||
public class MultiFormatExporter
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
/**
|
||||
* Attempts to export data to the output stream in the
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.io.InputStreamReader;
|
||||
|
||||
public class MultiFormatImporter
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
/**
|
||||
* Attempts to import data from the input stream of the
|
||||
|
||||
153
app/src/main/java/protect/card_locker/ShortcutHelper.java
Normal file
@@ -0,0 +1,153 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
class ShortcutHelper
|
||||
{
|
||||
// Android documentation says that no more than 5 shortcuts
|
||||
// are supported. However, that may be too many, as not all
|
||||
// launcher will show all 5. Instead, the number is limited
|
||||
// to 3 here, so that the most recent shortcut has a good
|
||||
// chance of being shown.
|
||||
private static final int MAX_SHORTCUTS = 3;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* the card is marked as the most recently used card. If adding this
|
||||
* card exceeds the max number of shortcuts, then the least recently
|
||||
* used card shortcut is discarded.
|
||||
*/
|
||||
@TargetApi(25)
|
||||
static void updateShortcuts(Context context, LoyaltyCard card, Intent intent)
|
||||
{
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1)
|
||||
{
|
||||
ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
|
||||
LinkedList<ShortcutInfo> list = new LinkedList<>(shortcutManager.getDynamicShortcuts());
|
||||
|
||||
String shortcutId = Integer.toString(card.id);
|
||||
|
||||
// Sort the shortcuts by rank, so working with the relative order will be easier.
|
||||
// This sorts so that the lowest rank is first.
|
||||
Collections.sort(list, new Comparator<ShortcutInfo>()
|
||||
{
|
||||
@Override
|
||||
public int compare(ShortcutInfo o1, ShortcutInfo o2)
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1)
|
||||
{
|
||||
return o1.getRank() - o2.getRank();
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Integer foundIndex = null;
|
||||
|
||||
for(int index = 0; index < list.size(); index++)
|
||||
{
|
||||
if(list.get(index).getId().equals(shortcutId))
|
||||
{
|
||||
// Found the item already
|
||||
foundIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(foundIndex != null)
|
||||
{
|
||||
// If the item is already found, then the list needs to be
|
||||
// reordered, so that the selected item now has the lowest
|
||||
// rank, thus letting it survive longer.
|
||||
ShortcutInfo found = list.remove(foundIndex.intValue());
|
||||
list.addFirst(found);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The item is new to the list. First, we need to trim the list
|
||||
// until it is able to accept a new item, then the item is
|
||||
// inserted.
|
||||
while(list.size() >= MAX_SHORTCUTS)
|
||||
{
|
||||
list.pollLast();
|
||||
}
|
||||
|
||||
ShortcutInfo shortcut = new ShortcutInfo.Builder(context, Integer.toString(card.id))
|
||||
.setShortLabel(card.store)
|
||||
.setLongLabel(card.store)
|
||||
.setIntent(intent)
|
||||
.build();
|
||||
|
||||
list.addFirst(shortcut);
|
||||
}
|
||||
|
||||
LinkedList<ShortcutInfo> finalList = new LinkedList<>();
|
||||
|
||||
// The ranks are now updated; the order in the list is the rank.
|
||||
for(int index = 0; index < list.size(); index++)
|
||||
{
|
||||
ShortcutInfo prevShortcut = list.get(index);
|
||||
|
||||
Intent shortcutIntent = prevShortcut.getIntent();
|
||||
|
||||
// Prevent instances of the view activity from piling up; if one exists let this
|
||||
// one replace it.
|
||||
shortcutIntent.setFlags(shortcutIntent.getFlags() | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
|
||||
ShortcutInfo updatedShortcut = new ShortcutInfo.Builder(context, prevShortcut.getId())
|
||||
.setShortLabel(prevShortcut.getShortLabel())
|
||||
.setLongLabel(prevShortcut.getLongLabel())
|
||||
.setIntent(shortcutIntent)
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.circle))
|
||||
.setRank(index)
|
||||
.build();
|
||||
|
||||
finalList.addLast(updatedShortcut);
|
||||
}
|
||||
|
||||
shortcutManager.setDynamicShortcuts(finalList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given card id from the app shortcuts, if such a
|
||||
* shortcut exists.
|
||||
*/
|
||||
@TargetApi(25)
|
||||
static void removeShortcut(Context context, int cardId)
|
||||
{
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1)
|
||||
{
|
||||
ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
|
||||
List<ShortcutInfo> list = shortcutManager.getDynamicShortcuts();
|
||||
|
||||
String shortcutId = Integer.toString(cardId);
|
||||
|
||||
for(int index = 0; index < list.size(); index++)
|
||||
{
|
||||
if(list.get(index).getId().equals(shortcutId))
|
||||
{
|
||||
list.remove(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
shortcutManager.setDynamicShortcuts(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
app/src/main/java/protect/card_locker/Utils.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
|
||||
public class Utils {
|
||||
static final double LUMINANCE_MIDPOINT = 0.5;
|
||||
|
||||
static public LetterBitmap generateIcon(Context context, String store, Integer backgroundColor) {
|
||||
if (store.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int tileLetterFontSize = context.getResources().getDimensionPixelSize(R.dimen.tileLetterFontSize);
|
||||
int pixelSize = context.getResources().getDimensionPixelSize(R.dimen.cardThumbnailSize);
|
||||
|
||||
if (backgroundColor == null) {
|
||||
backgroundColor = LetterBitmap.getDefaultColor(context, store);
|
||||
}
|
||||
|
||||
return new LetterBitmap(context, store, store,
|
||||
tileLetterFontSize, pixelSize, pixelSize, backgroundColor, needsDarkForeground(backgroundColor) ? Color.BLACK : Color.WHITE);
|
||||
}
|
||||
|
||||
static public boolean needsDarkForeground(Integer backgroundColor) {
|
||||
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package protect.card_locker.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.annotation.IntegerRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
import protect.card_locker.R;
|
||||
|
||||
public class Settings
|
||||
{
|
||||
private Context context;
|
||||
private SharedPreferences settings;
|
||||
|
||||
public Settings(Context context)
|
||||
{
|
||||
this.context = context;
|
||||
this.settings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
private String getResString(@StringRes int resId)
|
||||
{
|
||||
return context.getString(resId);
|
||||
}
|
||||
|
||||
private int getResInt(@IntegerRes int resId)
|
||||
{
|
||||
return context.getResources().getInteger(resId);
|
||||
}
|
||||
|
||||
private String getString(@StringRes int keyId, String defaultValue)
|
||||
{
|
||||
return settings.getString(getResString(keyId), defaultValue);
|
||||
}
|
||||
|
||||
private int getInt(@StringRes int keyId, @IntegerRes int defaultId)
|
||||
{
|
||||
return settings.getInt(getResString(keyId), getResInt(defaultId));
|
||||
}
|
||||
|
||||
private boolean getBoolean(@StringRes int keyId, boolean defaultValue)
|
||||
{
|
||||
return settings.getBoolean(getResString(keyId), defaultValue);
|
||||
}
|
||||
|
||||
public int getTheme()
|
||||
{
|
||||
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)))
|
||||
{
|
||||
return AppCompatDelegate.MODE_NIGHT_NO;
|
||||
}
|
||||
else if(value.equals(getResString(R.string.settings_key_dark_theme)))
|
||||
{
|
||||
return AppCompatDelegate.MODE_NIGHT_YES;
|
||||
}
|
||||
|
||||
return AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
|
||||
}
|
||||
|
||||
public int getCardTitleListFontSize()
|
||||
{
|
||||
return getInt(R.string.settings_key_card_title_list_font_size, R.integer.settings_card_title_list_font_size_sp);
|
||||
}
|
||||
|
||||
public int getCardNoteListFontSize()
|
||||
{
|
||||
return getInt(R.string.settings_key_card_note_list_font_size, R.integer.settings_card_note_list_font_size_sp);
|
||||
}
|
||||
|
||||
public int getCardTitleFontSize()
|
||||
{
|
||||
return getInt(R.string.settings_key_card_title_font_size, R.integer.settings_card_title_font_size_sp);
|
||||
}
|
||||
|
||||
public int getCardIdFontSize()
|
||||
{
|
||||
return getInt(R.string.settings_key_card_id_font_size, R.integer.settings_card_id_font_size_sp);
|
||||
}
|
||||
|
||||
public int getCardNoteFontSize()
|
||||
{
|
||||
return getInt(R.string.settings_key_card_note_font_size, R.integer.settings_card_note_font_size_sp);
|
||||
}
|
||||
|
||||
public boolean useMaxBrightnessDisplayingBarcode()
|
||||
{
|
||||
return getBoolean(R.string.settings_key_display_barcode_max_brightness, true);
|
||||
}
|
||||
|
||||
public boolean getLockBarcodeScreenOrientation()
|
||||
{
|
||||
return getBoolean(R.string.settings_key_lock_barcode_orientation, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package protect.card_locker.preferences;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import protect.card_locker.R;
|
||||
|
||||
public class SettingsActivity extends AppCompatActivity
|
||||
{
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
// Display the fragment as the main content.
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, new SettingsFragment())
|
||||
.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
int id = item.getItemId();
|
||||
|
||||
if(id == android.R.id.home)
|
||||
{
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragment
|
||||
{
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
findPreference(getResources().getString(R.string.settings_key_theme)).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
|
||||
{
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object o)
|
||||
{
|
||||
if(o.toString().equals(getResources().getString(R.string.settings_key_light_theme)))
|
||||
{
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||
}
|
||||
else if(o.toString().equals(getResources().getString(R.string.settings_key_dark_theme)))
|
||||
{
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||
}
|
||||
else
|
||||
{
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
||||
}
|
||||
|
||||
getActivity().recreate();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
app/src/main/res/drawable-hdpi/ic_arrow_back_white.png
Normal file
|
After Width: | Height: | Size: 233 B |
BIN
app/src/main/res/drawable-hdpi/ic_folder_white.png
Normal file
|
After Width: | Height: | Size: 163 B |
BIN
app/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.png
Normal file
|
After Width: | Height: | Size: 306 B |
BIN
app/src/main/res/drawable-hdpi/ic_lock_outline_white_24dp.png
Normal file
|
After Width: | Height: | Size: 303 B |
BIN
app/src/main/res/drawable-hdpi/ic_search_white.png
Normal file
|
After Width: | Height: | Size: 582 B |
BIN
app/src/main/res/drawable-hdpi/ic_share_white.png
Normal file
|
After Width: | Height: | Size: 554 B |
BIN
app/src/main/res/drawable-hdpi/ic_starred_white.png
Normal file
|
After Width: | Height: | Size: 336 B |
BIN
app/src/main/res/drawable-hdpi/ic_unstarred_white.png
Normal file
|
After Width: | Height: | Size: 439 B |
BIN
app/src/main/res/drawable-hdpi/save_24dp.png
Normal file
|
After Width: | Height: | Size: 368 B |
BIN
app/src/main/res/drawable-mdpi/ic_arrow_back_white.png
Normal file
|
After Width: | Height: | Size: 155 B |
BIN
app/src/main/res/drawable-mdpi/ic_folder_white.png
Normal file
|
After Width: | Height: | Size: 135 B |
BIN
app/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.png
Normal file
|
After Width: | Height: | Size: 200 B |
BIN
app/src/main/res/drawable-mdpi/ic_lock_outline_white_24dp.png
Normal file
|
After Width: | Height: | Size: 198 B |
BIN
app/src/main/res/drawable-mdpi/ic_search_white.png
Normal file
|
After Width: | Height: | Size: 410 B |
BIN
app/src/main/res/drawable-mdpi/ic_share_white.png
Normal file
|
After Width: | Height: | Size: 397 B |
BIN
app/src/main/res/drawable-mdpi/ic_starred_white.png
Normal file
|
After Width: | Height: | Size: 234 B |
BIN
app/src/main/res/drawable-mdpi/ic_unstarred_white.png
Normal file
|
After Width: | Height: | Size: 299 B |
BIN
app/src/main/res/drawable-mdpi/save_24dp.png
Normal file
|
After Width: | Height: | Size: 303 B |
BIN
app/src/main/res/drawable-nodpi/circle.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_arrow_back_white.png
Normal file
|
After Width: | Height: | Size: 225 B |
BIN
app/src/main/res/drawable-xhdpi/ic_folder_white.png
Normal file
|
After Width: | Height: | Size: 207 B |
BIN
app/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.png
Normal file
|
After Width: | Height: | Size: 354 B |
BIN
app/src/main/res/drawable-xhdpi/ic_lock_outline_white_24dp.png
Normal file
|
After Width: | Height: | Size: 343 B |
BIN
app/src/main/res/drawable-xhdpi/ic_search_white.png
Normal file
|
After Width: | Height: | Size: 783 B |
BIN
app/src/main/res/drawable-xhdpi/ic_share_white.png
Normal file
|
After Width: | Height: | Size: 758 B |
BIN
app/src/main/res/drawable-xhdpi/ic_starred_white.png
Normal file
|
After Width: | Height: | Size: 421 B |
BIN
app/src/main/res/drawable-xhdpi/ic_unstarred_white.png
Normal file
|
After Width: | Height: | Size: 564 B |
BIN
app/src/main/res/drawable-xhdpi/save_24dp.png
Normal file
|
After Width: | Height: | Size: 466 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_arrow_back_white.png
Normal file
|
After Width: | Height: | Size: 398 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_folder_white.png
Normal file
|
After Width: | Height: | Size: 252 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.png
Normal file
|
After Width: | Height: | Size: 512 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_lock_outline_white_24dp.png
Normal file
|
After Width: | Height: | Size: 494 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_search_white.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_share_white.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_starred_white.png
Normal file
|
After Width: | Height: | Size: 829 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_unstarred_white.png
Normal file
|
After Width: | Height: | Size: 809 B |
BIN
app/src/main/res/drawable-xxhdpi/save_24dp.png
Normal file
|
After Width: | Height: | Size: 606 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_lock_open_white_24dp.png
Normal file
|
After Width: | Height: | Size: 665 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_lock_outline_white_24dp.png
Normal file
|
After Width: | Height: | Size: 651 B |
BIN
app/src/main/res/drawable-xxxhdpi/save_24dp.png
Normal file
|
After Width: | Height: | Size: 758 B |
66
app/src/main/res/drawable/ic_android_launcher.xml
Normal file
@@ -0,0 +1,66 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="192dp"
|
||||
android:height="192dp"
|
||||
android:viewportWidth="50.8"
|
||||
android:viewportHeight="50.8">
|
||||
<path
|
||||
android:pathData="M14.3354,20.1954l20.7318,-9.2304l5.7612,12.9398l-20.7318,9.2304z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#f0f0f0"
|
||||
android:strokeColor="#c80000"/>
|
||||
<path
|
||||
android:pathData="M14.8755,10.9648l23.2041,10.3311l-6.8874,15.4694l-23.2041,-10.3311z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#f0f0f0"
|
||||
android:strokeColor="#c80000"/>
|
||||
<path
|
||||
android:pathData="M16.5599,16.1348l26.5459,7.6119l-4.5489,15.8639l-26.5459,-7.6119z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.5875"
|
||||
android:fillColor="#c80000"
|
||||
android:strokeColor="#c80000"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M12.011,15.4955h27.6157v16.5032h-27.6157z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.5875"
|
||||
android:fillColor="#ff0000"
|
||||
android:strokeColor="#ff0000"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M7.8471,23.7471a4.3659,8.5899 0,1 0,8.7317 0a4.3659,8.5899 0,1 0,-8.7317 0z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.91078"
|
||||
android:fillColor="#ff0000"
|
||||
android:strokeColor="#ff0000"/>
|
||||
<path
|
||||
android:pathData="m24.4983,25.781a1.6711,1.6711 0,0 1,-1.3809 1.6457,1.6711 1.6711,0 0,1 -1.8605,-1.0741"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#f0f0f0"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="m27.7991,26.333a1.6711,1.6711 0,0 1,-1.8605 1.0741,1.6711 1.6711,0 0,1 -1.3809,-1.6457"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#f0f0f0"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="m16.0606,22.271 l2.6458,-2.6458 2.6458,2.6458"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#f0f0f0"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="m27.7023,22.271 l2.6458,-2.6458 2.6458,2.6458"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#f0f0f0"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
||||
71
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,71 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group android:scaleX="1.5307087"
|
||||
android:scaleY="1.5307087"
|
||||
android:translateX="15.12"
|
||||
android:translateY="15.12">
|
||||
<path
|
||||
android:pathData="M14.3354,20.1954l20.7318,-9.2304l5.7612,12.9398l-20.7318,9.2304z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#f0f0f0"
|
||||
android:strokeColor="#c80000"/>
|
||||
<path
|
||||
android:pathData="M14.8755,10.9648l23.2041,10.3311l-6.8874,15.4694l-23.2041,-10.3311z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#f0f0f0"
|
||||
android:strokeColor="#c80000"/>
|
||||
<path
|
||||
android:pathData="M16.5599,16.1348l26.5459,7.6119l-4.5489,15.8639l-26.5459,-7.6119z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.5875"
|
||||
android:fillColor="#c80000"
|
||||
android:strokeColor="#c80000"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M12.011,15.4955h27.6157v16.5032h-27.6157z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.5875"
|
||||
android:fillColor="#ff0000"
|
||||
android:strokeColor="#ff0000"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M7.8471,23.7471a4.3659,8.5899 0,1 0,8.7317 0a4.3659,8.5899 0,1 0,-8.7317 0z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.91078"
|
||||
android:fillColor="#ff0000"
|
||||
android:strokeColor="#ff0000"/>
|
||||
<path
|
||||
android:pathData="m24.4983,25.781a1.6711,1.6711 0,0 1,-1.3809 1.6457,1.6711 1.6711,0 0,1 -1.8605,-1.0741"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#f0f0f0"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="m27.7991,26.333a1.6711,1.6711 0,0 1,-1.8605 1.0741,1.6711 1.6711,0 0,1 -1.3809,-1.6457"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#f0f0f0"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="m16.0606,22.271 l2.6458,-2.6458 2.6458,2.6458"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#f0f0f0"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="m27.7023,22.271 l2.6458,-2.6458 2.6458,2.6458"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.529167"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#f0f0f0"
|
||||
android:strokeLineCap="butt"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -1,23 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="fill_parent"
|
||||
@@ -64,142 +64,243 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/barcodesLayout"/>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<Button
|
||||
android:id="@+id/noBarcode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/barcodeNoBarcode"
|
||||
android:enabled="false" />
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/aztecBarcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:id="@+id/aztecBarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/code39Barcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:id="@+id/code39BarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/code128Barcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:id="@+id/code128BarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/codabarBarcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:id="@+id/codabarBarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/datamatrixBarcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:id="@+id/datamatrixBarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/ean8Barcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:id="@+id/ean8BarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/ean13Barcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:id="@+id/ean13BarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/itfBarcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:id="@+id/itfBarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/pdf417Barcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:id="@+id/pdf417BarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/qrcodeBarcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:id="@+id/qrcodeBarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/upcaBarcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:id="@+id/upcaBarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -13,13 +13,23 @@
|
||||
style="@style/AppTheme.TextView.NoData"
|
||||
android:id="@+id/helpText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:text="@string/noGiftCards"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
style="@style/AppTheme.TextView.NoData"
|
||||
android:id="@+id/noMatchingCardsText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:text="@string/noMatchingGiftCards"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ListView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/list"
|
||||
android:visibility="gone"/>
|
||||
</RelativeLayout>
|
||||
|
||||
68
app/src/main/res/layout/group_layout.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:baselineAligned="false"
|
||||
android:padding="@dimen/activity_margin">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/valueLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/storeNameTextSize"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cardCount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textSize="@dimen/noteTextSize"/>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/edit"
|
||||
android:layout_width="@dimen/cardThumbnailSize"
|
||||
android:layout_height="@dimen/cardThumbnailSize"
|
||||
android:layout_marginLeft="@dimen/activity_margin"
|
||||
android:src="@drawable/ic_mode_edit_white_24dp"
|
||||
android:contentDescription="@string/edit"
|
||||
app:tint="#000000"
|
||||
android:onClick="editGroup" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/delete"
|
||||
android:layout_width="@dimen/cardThumbnailSize"
|
||||
android:layout_height="@dimen/cardThumbnailSize"
|
||||
android:layout_marginLeft="@dimen/activity_margin"
|
||||
android:src="@drawable/ic_delete_white_24dp"
|
||||
android:contentDescription="@string/delete"
|
||||
app:tint="#000000"
|
||||
android:onClick="deleteGroup"/>
|
||||
|
||||
</LinearLayout>
|
||||
25
app/src/main/res/layout/group_main.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:context="protect.card_locker.MainActivity"
|
||||
tools:showIn="@layout/main_activity">
|
||||
|
||||
<TextView
|
||||
style="@style/AppTheme.TextView.NoData"
|
||||
android:id="@+id/helpText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/noGroups"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ListView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/list"
|
||||
android:visibility="gone"/>
|
||||
</RelativeLayout>
|
||||
@@ -1,54 +1,135 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="20dp"
|
||||
android:textSize="20sp"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/importExportHelp"/>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
<Button
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="8dp">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/importName"
|
||||
android:layout_weight="1.0"
|
||||
android:id="@+id/importButton"/>
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/importExportHelp"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/dividerExport"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="?android:attr/listDivider"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/exportOptionTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/exportName"
|
||||
android:layout_weight="1.0"
|
||||
android:id="@+id/exportButton"/>
|
||||
android:textSize="@dimen/text_size_large"
|
||||
android:text="@string/exportName"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/exportOptionExplanation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:text="@string/exportOptionExplanation"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/exportButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/exportName" />
|
||||
|
||||
<View
|
||||
android:id="@+id/dividerImportFilesystem"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="?android:attr/listDivider"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/importOptionFilesystemTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
android:text="@string/importOptionFilesystemTitle"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/importOptionFilesystemExplanation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:text="@string/importOptionFilesystemExplanation"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/importOptionFilesystemButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/importOptionFilesystemButton" />
|
||||
|
||||
<View
|
||||
android:id="@+id/dividerImportApplication"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="?android:attr/listDivider"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/importOptionApplicationTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
android:text="@string/importOptionApplicationTitle"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/importOptionApplicationExplanation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:text="@string/importOptionApplicationExplanation"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/importOptionApplicationButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/importOptionApplicationButton" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
9
app/src/main/res/layout/layout_chip_choice.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<com.google.android.material.chip.Chip xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
style="@style/Widget.MaterialComponents.Chip.Choice"
|
||||
app:checkedIconVisible="true"
|
||||
android:textAppearance="?android:attr/textAppearance" />
|
||||
222
app/src/main/res/layout/loyalty_card_edit_activity.xml
Normal file
@@ -0,0 +1,222 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabSave"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:src="@drawable/save_24dp"
|
||||
android:contentDescription="@string/save"
|
||||
android:layout_margin="16dp" />
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<ScrollView android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/inputContrastBackground"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
||||
<TableLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Store -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
android:paddingTop="@dimen/inputPadding"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="@dimen/cardThumbnailSize"
|
||||
android:layout_height="@dimen/cardThumbnailSize"
|
||||
android:layout_marginEnd="@dimen/activity_margin"
|
||||
android:layout_marginRight="@dimen/activity_margin"
|
||||
android:layout_gravity="center_vertical"
|
||||
app:cardCornerRadius="4dp"
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_width="@dimen/cardThumbnailSize"
|
||||
android:layout_height="@dimen/cardThumbnailSize"
|
||||
android:contentDescription="@string/thumbnailDescription"
|
||||
android:src="@mipmap/ic_launcher"/>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/storeNameField"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/storeName">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/storeNameEdit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Note -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
android:paddingTop="@dimen/inputPadding"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/noteField"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/note">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/noteEdit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Group -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
android:paddingTop="@dimen/inputPadding"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/groupChips"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="@dimen/inputPadding"
|
||||
android:textSize="@dimen/inputSize" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Buttons -->
|
||||
<TableLayout android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:shrinkColumns="1"
|
||||
android:background="@color/inputContrastBackground">
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/inputBackground">
|
||||
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/barcodeCaptureLayout">
|
||||
<Button android:id="@+id/captureButton"
|
||||
android:layout_margin="@dimen/inputMargin"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/capture"
|
||||
android:layout_weight="1.0"/>
|
||||
<Button android:id="@+id/enterButton"
|
||||
android:layout_margin="@dimen/inputMargin"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enterCard"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</TableLayout>
|
||||
|
||||
<!-- Card ID and Barcode type -->
|
||||
<LinearLayout
|
||||
android:id="@+id/cardAndBarcodeLayout"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
android:paddingTop="@dimen/inputPadding"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- Card ID -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/cardIdField"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:hint="@string/cardId">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/cardIdView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- Barcode type -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/barcodeTypeView"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:hint="@string/barcodeType">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/barcodeTypeField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Barcode -->
|
||||
<View
|
||||
android:layout_height="@dimen/inputBorderThickness"
|
||||
android:layout_width="match_parent" />
|
||||
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/barcodeLayout">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:padding="10.0dp"
|
||||
android:background="#ffffff"
|
||||
android:id="@+id/barcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
</TableLayout>
|
||||
</ScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,32 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="5.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="true">
|
||||
<TextView android:textSize="20.0sp"
|
||||
android:id="@+id/store"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:maxLines="1"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:baselineAligned="false"
|
||||
android:padding="@dimen/activity_margin">
|
||||
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="5.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="true">
|
||||
<TextView android:textSize="20.0sp"
|
||||
android:layout_gravity="start"
|
||||
android:id="@+id/cardId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="@dimen/cardThumbnailSize"
|
||||
android:layout_height="@dimen/cardThumbnailSize"
|
||||
android:layout_marginEnd="@dimen/activity_margin"
|
||||
android:layout_marginRight="@dimen/activity_margin"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_width="@dimen/cardThumbnailSize"
|
||||
android:layout_height="@dimen/cardThumbnailSize"
|
||||
android:contentDescription="@string/thumbnailDescription"
|
||||
android:src="@mipmap/ic_launcher"/>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/valueLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/store"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/storeNameTextSize"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/note"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textSize="@dimen/noteTextSize"/>
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:id="@+id/star"
|
||||
android:layout_width="@dimen/cardThumbnailSize"
|
||||
android:layout_height="@dimen/cardThumbnailSize"
|
||||
android:layout_marginLeft="@dimen/activity_margin"
|
||||
android:src="@drawable/ic_starred_white"
|
||||
android:tint="#000000"
|
||||
android:contentDescription="@string/starImage"/>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:id="@+id/scrollView">
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView android:textSize="16.0sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingStart="20.0dip"
|
||||
android:paddingEnd="20.0dip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@+id/storeName"
|
||||
android:text="@string/storeName" />
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<EditText android:id="@+id/storeName"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView android:textSize="16.0sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingStart="20.0dip"
|
||||
android:paddingEnd="20.0dip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@+id/note"
|
||||
android:text="@string/note" />
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<EditText android:id="@+id/note"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/barcodeIdLayout">
|
||||
<TextView android:textSize="16.0sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingStart="20.0dip"
|
||||
android:paddingEnd="20.0dip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@+id/cardId"
|
||||
android:text="@string/cardId" />
|
||||
<EditText android:id="@+id/cardId"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:enabled="false"
|
||||
android:inputType="text"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/barcodeTypeLayout">
|
||||
<TextView android:textSize="16.0sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingStart="20.0dip"
|
||||
android:paddingEnd="20.0dip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@+id/barcodeType"
|
||||
android:text="@string/barcodeType" />
|
||||
<EditText android:id="@+id/barcodeType"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:enabled="false"
|
||||
android:inputType="text"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/barcodeLayout">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/barcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/barcodeCaptureLayout">
|
||||
<Button android:id="@+id/captureButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/capture"
|
||||
android:layout_weight="1.0"/>
|
||||
<Button android:id="@+id/enterButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enterCard"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="true">
|
||||
<Button android:id="@+id/cancelButton"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/cancel"
|
||||
android:layout_weight="1.0" />
|
||||
<Button android:id="@+id/saveButton"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/save"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
155
app/src/main/res/layout/loyalty_card_view_layout.xml
Normal file
@@ -0,0 +1,155 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/coordinator_layout"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabEdit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:src="@drawable/ic_mode_edit_white_24dp"
|
||||
android:contentDescription="@string/edit"
|
||||
android:layout_margin="16dp" />
|
||||
|
||||
<FrameLayout
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/centerGuideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_percent="0.5"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/barcode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="20.0dip"
|
||||
android:layout_marginBottom="10.0dip"
|
||||
android:layout_marginStart="15.0dip"
|
||||
android:layout_marginEnd="15.0dip"
|
||||
android:padding="10.0dp"
|
||||
android:background="#ffffff"
|
||||
app:layout_constraintBottom_toTopOf="@+id/centerGuideline"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:contentDescription="@string/barcodeImageDescription"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cardIdView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginLeft="10.0dip"
|
||||
android:layout_marginRight="10.0dip"
|
||||
app:layout_constraintTop_toBottomOf="@+id/barcode"
|
||||
app:layout_constraintBottom_toTopOf="@+id/noteViewDivider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:textAlignment="center"
|
||||
app:autoSizeTextType="uniform"
|
||||
app:autoSizeMinTextSize="@dimen/singleCardCardIdTextSizeMin"
|
||||
app:autoSizeMaxTextSize="@dimen/singleCardCardIdTextSizeMax"
|
||||
android:ellipsize="end"
|
||||
android:textIsSelectable="true"/>
|
||||
|
||||
<View
|
||||
android:id="@id/noteViewDivider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@android:color/black"
|
||||
app:layout_constraintTop_toBottomOf="@id/cardIdView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/noteView"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@id/noteView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="10.0dip"
|
||||
android:layout_gravity="bottom"
|
||||
app:layout_constraintTop_toBottomOf="@id/noteViewDivider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:autoSizeTextType="uniform"
|
||||
app:autoSizeMinTextSize="@dimen/singleCardNoteTextSizeMin"
|
||||
app:autoSizeMaxTextSize="@dimen/singleCardNoteTextSizeMax"
|
||||
android:textIsSelectable="true"
|
||||
android:scrollbars="vertical"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/drop_shadow_actionbar"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="5.0dip"
|
||||
android:layout_gravity="top"/>
|
||||
</FrameLayout>
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar_layout"
|
||||
android:background="@android:color/transparent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:weightSum="1.0"
|
||||
android:fitsSystemWindows="true">
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:id="@+id/collapsingToolbarLayout"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="56.0dip"
|
||||
android:layout_weight="1.0"
|
||||
app:expandedTitleMarginStart="48dp"
|
||||
app:expandedTitleMarginEnd="64dp"
|
||||
app:contentScrim="?colorPrimary"
|
||||
app:expandedTitleGravity="top">
|
||||
<TextView
|
||||
android:id="@+id/storeName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="40sp"
|
||||
android:textAlignment="center"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="?actionBarSize"
|
||||
android:layout_marginBottom="?actionBarSize"
|
||||
app:layout_collapseMode="parallax"
|
||||
android:fitsSystemWindows="true"/>
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@id/toolbar"
|
||||
android:background="@android:color/transparent"
|
||||
android:theme="@style/CardView.ActionBarTheme"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
app:contentInsetStart="72.0dip"
|
||||
app:layout_collapseMode="pin" />
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
@@ -8,20 +8,36 @@
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="protect.card_locker.MainActivity">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabAdd"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:src="@drawable/ic_add_white_24dp"
|
||||
android:contentDescription="@string/action_add"
|
||||
android:layout_margin="16dp" />
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay"/>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/groups"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:tabMode="scrollable"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<include layout="@layout/content_main"/>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
36
app/src/main/res/layout/manage_groups_activity.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="protect.card_locker.MainActivity">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabAdd"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:src="@drawable/ic_add_white_24dp"
|
||||
android:contentDescription="@string/action_add"
|
||||
android:layout_margin="16dp" />
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay"/>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<include layout="@layout/group_main"/>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
4
app/src/main/res/menu/card_add_menu.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
</menu>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_edit"
|
||||
android:icon="@drawable/ic_mode_edit_white_24dp"
|
||||
android:title="@string/edit"
|
||||
app:showAsAction="always"/>
|
||||
</menu>
|
||||
@@ -6,4 +6,8 @@
|
||||
android:id="@+id/action_clipboard"
|
||||
android:title="@string/copy_to_clipboard"
|
||||
app:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/action_share"
|
||||
android:title="@string/share"
|
||||
app:showAsAction="always"/>
|
||||
</menu>
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_delete"
|
||||
android:icon="@drawable/ic_delete_white_24dp"
|
||||
20
app/src/main/res/menu/card_view_menu.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_lock_unlock"
|
||||
android:icon="@drawable/ic_lock_open_white_24dp"
|
||||
android:title="@string/lockScreen"
|
||||
app:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/action_share"
|
||||
android:icon="@drawable/ic_share_white"
|
||||
android:title="@string/share"
|
||||
app:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/action_star_unstar"
|
||||
android:icon="@drawable/ic_unstarred_white"
|
||||
android:title="@string/star"
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
||||
@@ -3,15 +3,25 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="protect.card_locker.MainActivity">
|
||||
<item
|
||||
android:id="@+id/action_add"
|
||||
android:icon="@drawable/ic_add_white_24dp"
|
||||
android:title="@string/action_add"
|
||||
android:id="@+id/action_search"
|
||||
android:title="@string/action_search"
|
||||
android:icon="@drawable/ic_search_white"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="always|collapseActionView"/>
|
||||
<item
|
||||
android:id="@+id/action_manage_groups"
|
||||
android:icon="@drawable/ic_folder_white"
|
||||
android:title="@string/groups"
|
||||
app:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/action_import_export"
|
||||
android:icon="@drawable/ic_import_export_white_24dp"
|
||||
android:title="@string/importExport"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:title="@string/settings"
|
||||
app:showAsAction="never"/>
|
||||
<item
|
||||
android:id="@+id/action_about"
|
||||
android:title="@string/about"
|
||||
|
||||