mirror of
https://github.com/CatimaLoyalty/Android.git
synced 2026-01-03 12:37:54 -05:00
Compare commits
1400 Commits
create-pul
...
create-pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98ba87ea56 | ||
|
|
d5c03f9d36 | ||
|
|
6ed2ff849a | ||
|
|
2945450186 | ||
|
|
8944c6d5dc | ||
|
|
97db8ee025 | ||
|
|
777a3cce28 | ||
|
|
91386df0f7 | ||
|
|
9f95812d32 | ||
|
|
361b011205 | ||
|
|
ac45a5cdfe | ||
|
|
e892f5d57c | ||
|
|
a3d24ebe3b | ||
|
|
29ce8e63b3 | ||
|
|
223d6d1d35 | ||
|
|
e712765f0b | ||
|
|
ad68c43857 | ||
|
|
626c25b781 | ||
|
|
0e9366e0ce | ||
|
|
cccffe9543 | ||
|
|
821e81303c | ||
|
|
d3ba0ff93f | ||
|
|
2bf9f58054 | ||
|
|
07ba380c21 | ||
|
|
6e523dff7e | ||
|
|
702cc17bd5 | ||
|
|
6b9c452de7 | ||
|
|
5d9a670225 | ||
|
|
4cb939ab6b | ||
|
|
f14b2dffcd | ||
|
|
55b0ee7a63 | ||
|
|
4adfb0d292 | ||
|
|
948ee26852 | ||
|
|
a652d3fd9a | ||
|
|
18e530f3a0 | ||
|
|
a55dcd760c | ||
|
|
2bbcad03ef | ||
|
|
cc9c50032c | ||
|
|
06cf3e9288 | ||
|
|
a25e91dede | ||
|
|
57086649f4 | ||
|
|
7d2fc03248 | ||
|
|
b2ad30d1ae | ||
|
|
8970a0a031 | ||
|
|
c9e713cf7f | ||
|
|
64e315f0b0 | ||
|
|
f60cf0e674 | ||
|
|
5d2a1b0263 | ||
|
|
77860441ad | ||
|
|
f46ec1ec51 | ||
|
|
e644c347ec | ||
|
|
0b28df9999 | ||
|
|
dddb3e3a11 | ||
|
|
0518821341 | ||
|
|
2eefcf1270 | ||
|
|
93c3705207 | ||
|
|
9fcf9e4dd9 | ||
|
|
392ce9bc79 | ||
|
|
da3eaf36f1 | ||
|
|
0458c501c8 | ||
|
|
632ef8b3a8 | ||
|
|
40e5014cc6 | ||
|
|
ba09eb4ca3 | ||
|
|
2c77dcadb5 | ||
|
|
99eaaebf1c | ||
|
|
f2e77cdf09 | ||
|
|
8f00b17315 | ||
|
|
3a17756532 | ||
|
|
cff3fff971 | ||
|
|
2763635f17 | ||
|
|
1dd1a1273f | ||
|
|
3f5b953c16 | ||
|
|
1539d88d78 | ||
|
|
91ee846054 | ||
|
|
c5cecee7b1 | ||
|
|
1e0a52d780 | ||
|
|
0792bb1407 | ||
|
|
6e9a05389a | ||
|
|
68833a7ff1 | ||
|
|
cc7553850a | ||
|
|
e66277d665 | ||
|
|
caea63ea32 | ||
|
|
1761465532 | ||
|
|
8218cc63c7 | ||
|
|
c2ff763311 | ||
|
|
813287a657 | ||
|
|
66c3046c4d | ||
|
|
acbdf79d79 | ||
|
|
ade3db0902 | ||
|
|
d66066c195 | ||
|
|
0a222ca16a | ||
|
|
48fa369dc3 | ||
|
|
9e861b1b10 | ||
|
|
6b7926ecde | ||
|
|
ccd6e12547 | ||
|
|
c869104e82 | ||
|
|
f430d3071b | ||
|
|
26460753a6 | ||
|
|
2653c7aea3 | ||
|
|
1a892b2de3 | ||
|
|
a9e9ee511d | ||
|
|
7719a2d3fd | ||
|
|
84a6202db1 | ||
|
|
4d8a4fbd6a | ||
|
|
d556e7e0a2 | ||
|
|
5f039c5c3f | ||
|
|
5bf69db296 | ||
|
|
f20cac5d85 | ||
|
|
69976289ca | ||
|
|
8b30f92dfd | ||
|
|
477c70a8e5 | ||
|
|
2e9d1a41f6 | ||
|
|
ff617036c7 | ||
|
|
2b4e8b1f8b | ||
|
|
7bcbdd8358 | ||
|
|
45c212c5cc | ||
|
|
00d7b5e231 | ||
|
|
0314d49b77 | ||
|
|
6335b731df | ||
|
|
c248d1e84c | ||
|
|
94de9829b9 | ||
|
|
0448efd04c | ||
|
|
21f4b9b5cf | ||
|
|
01a30db52e | ||
|
|
01ed9bd30f | ||
|
|
583edb53d9 | ||
|
|
f51cc70d8e | ||
|
|
539d05a603 | ||
|
|
906162ebb4 | ||
|
|
b06203dc8a | ||
|
|
6aec4b93ff | ||
|
|
3ea6f64021 | ||
|
|
51fb01b190 | ||
|
|
01b23109ae | ||
|
|
4e291b58d3 | ||
|
|
1689cf1026 | ||
|
|
a55ce2c952 | ||
|
|
d2ff17a90e | ||
|
|
70c446e88d | ||
|
|
0d6dc40da5 | ||
|
|
a4f3a81d58 | ||
|
|
c64f75ffe4 | ||
|
|
0fdb6ee567 | ||
|
|
9aa40fc88c | ||
|
|
4f4c0171c0 | ||
|
|
e4644b3eb7 | ||
|
|
999cf241f8 | ||
|
|
42b964a324 | ||
|
|
1262bfd502 | ||
|
|
81fe561678 | ||
|
|
9cb989155d | ||
|
|
59587f4a40 | ||
|
|
4dc0d4cd24 | ||
|
|
89598be72e | ||
|
|
1d2359f295 | ||
|
|
026984920b | ||
|
|
03ed289ef9 | ||
|
|
0ccbeb42dd | ||
|
|
3f9b772a11 | ||
|
|
e97c9de471 | ||
|
|
872db6b966 | ||
|
|
724d036df9 | ||
|
|
fc98e6651a | ||
|
|
a70e4aa7a7 | ||
|
|
d542be322c | ||
|
|
d5a977c5b0 | ||
|
|
bac9199974 | ||
|
|
bb92ec2291 | ||
|
|
b155f45b01 | ||
|
|
b2f79f836b | ||
|
|
9ba7e4a92b | ||
|
|
28088beb72 | ||
|
|
25351f4138 | ||
|
|
1ac4db8641 | ||
|
|
5cee4ef497 | ||
|
|
b323c2e5cd | ||
|
|
66be4a8c18 | ||
|
|
dd2bd294ba | ||
|
|
e4db1e931a | ||
|
|
f8b3e9c65b | ||
|
|
880a596621 | ||
|
|
9903b4d955 | ||
|
|
40bc87c772 | ||
|
|
586642c6f6 | ||
|
|
f3847a4a2b | ||
|
|
63352c310e | ||
|
|
e7aac163c2 | ||
|
|
5a3d61d8f0 | ||
|
|
33ba81d3b0 | ||
|
|
6a0956181d | ||
|
|
aca7d3bd9c | ||
|
|
e3e8378f8e | ||
|
|
61d235b497 | ||
|
|
e069d7a956 | ||
|
|
3d8806e38c | ||
|
|
650da3bd6a | ||
|
|
26b7cece35 | ||
|
|
1d3da4d1d1 | ||
|
|
bc3aeed090 | ||
|
|
6606eeb9a7 | ||
|
|
2401a3f8bf | ||
|
|
b3685c0c7e | ||
|
|
868315948e | ||
|
|
19a68719fe | ||
|
|
92570ec75c | ||
|
|
573817f81d | ||
|
|
21da1fed85 | ||
|
|
c23527eb82 | ||
|
|
926b9e0822 | ||
|
|
d32a0b698d | ||
|
|
47bf18f219 | ||
|
|
91096c5839 | ||
|
|
477c16360e | ||
|
|
9a1d7d2e44 | ||
|
|
be54c50786 | ||
|
|
2624924f53 | ||
|
|
724552eaa3 | ||
|
|
bef0d70d09 | ||
|
|
d7201f62ab | ||
|
|
1f229980b7 | ||
|
|
8f08b1989b | ||
|
|
e3c83c0cb0 | ||
|
|
431e380f5e | ||
|
|
5c7b102e08 | ||
|
|
6dc1a1b196 | ||
|
|
6c01d2ffa9 | ||
|
|
3156fd6daf | ||
|
|
6fa4ab3e3b | ||
|
|
9fa67b59c7 | ||
|
|
0935a17fec | ||
|
|
5fdc575485 | ||
|
|
d6bbd16945 | ||
|
|
1110f7ee99 | ||
|
|
d64860a34e | ||
|
|
9fb0da6954 | ||
|
|
12b0f8806b | ||
|
|
5c5a1df39d | ||
|
|
5b0ac65fbe | ||
|
|
e9b542ee6e | ||
|
|
ae6bf5e8f6 | ||
|
|
4dc564ffcf | ||
|
|
49a9ef2f80 | ||
|
|
1907f482eb | ||
|
|
08aad7ce20 | ||
|
|
f555a160d5 | ||
|
|
2d9b7f358e | ||
|
|
db396b5f21 | ||
|
|
018d36c3fd | ||
|
|
e6621244b8 | ||
|
|
29aef64d97 | ||
|
|
cbe243feb3 | ||
|
|
ff02c59f85 | ||
|
|
3895bc1f73 | ||
|
|
cd0cce532a | ||
|
|
3fdf6693d2 | ||
|
|
d2be344700 | ||
|
|
9bd2898678 | ||
|
|
07f2348a6d | ||
|
|
ddd1c1a516 | ||
|
|
8de76e3c52 | ||
|
|
6ea2645e9d | ||
|
|
ed166b1b33 | ||
|
|
2dd53a5300 | ||
|
|
97f9120acf | ||
|
|
dd29be75f2 | ||
|
|
fa87df2ed8 | ||
|
|
1fb94de809 | ||
|
|
0e18e84c8f | ||
|
|
0ba2261033 | ||
|
|
703ebf9418 | ||
|
|
ff08dbe5d5 | ||
|
|
3003a10550 | ||
|
|
f95be6cc71 | ||
|
|
23bc40e630 | ||
|
|
45c47674fb | ||
|
|
cf1d1a8122 | ||
|
|
45d2629863 | ||
|
|
f6b0736bbf | ||
|
|
83f9a2bcc9 | ||
|
|
1b817222f0 | ||
|
|
50db796a47 | ||
|
|
f2f7fe3151 | ||
|
|
90293b90fb | ||
|
|
f1751eaebf | ||
|
|
19a3aa0b86 | ||
|
|
a3b901e357 | ||
|
|
fbe12cfadc | ||
|
|
3ffbf11902 | ||
|
|
5c3b32a6ee | ||
|
|
36267b8255 | ||
|
|
668c9b0d76 | ||
|
|
eebbe6dec8 | ||
|
|
a84ae51a4f | ||
|
|
bdbb977233 | ||
|
|
ac27cc6a1e | ||
|
|
91551bf4e8 | ||
|
|
e8c11debfd | ||
|
|
e29a1c659f | ||
|
|
d66bf0e3fd | ||
|
|
172b71dd00 | ||
|
|
10ce432c97 | ||
|
|
3299a8dca3 | ||
|
|
5dca8bc5d2 | ||
|
|
1b8f8704f5 | ||
|
|
64e801311b | ||
|
|
da8202b4e1 | ||
|
|
72d70f1265 | ||
|
|
660597e89a | ||
|
|
4a48ac8797 | ||
|
|
4c61c19e4f | ||
|
|
bb3b13364c | ||
|
|
b34bb65f79 | ||
|
|
a7cfb4b68f | ||
|
|
8d58a6192d | ||
|
|
1da34ceda4 | ||
|
|
dc31b7e839 | ||
|
|
5c6ee7b787 | ||
|
|
5620a62cad | ||
|
|
429abb4914 | ||
|
|
f90d33daf7 | ||
|
|
baa67cf9f4 | ||
|
|
4054269db3 | ||
|
|
559b8d00dd | ||
|
|
9b9c19586f | ||
|
|
a3407734a7 | ||
|
|
99c579ee51 | ||
|
|
81bc7db43e | ||
|
|
117e08d957 | ||
|
|
89356dac50 | ||
|
|
c7bf1f5f30 | ||
|
|
1a7a2d31f4 | ||
|
|
8fd4335a98 | ||
|
|
6b4b0e846b | ||
|
|
451fa87990 | ||
|
|
2c50c2b48c | ||
|
|
8b9c26ad5a | ||
|
|
ef7db55d8c | ||
|
|
8f2d39d0ec | ||
|
|
3597e3098f | ||
|
|
0702fc579c | ||
|
|
017cf19d27 | ||
|
|
83a3a5b9e2 | ||
|
|
54828c3c93 | ||
|
|
31e517d7ee | ||
|
|
00957ac576 | ||
|
|
852c38d88d | ||
|
|
67314b09a9 | ||
|
|
944e2ca0c0 | ||
|
|
68564864d5 | ||
|
|
19bccc0c82 | ||
|
|
04905edab4 | ||
|
|
179a9adbe5 | ||
|
|
3925a79158 | ||
|
|
71de6f8c99 | ||
|
|
9d81ca5c9b | ||
|
|
75c393af92 | ||
|
|
3e16515b6c | ||
|
|
3e9857f14f | ||
|
|
20ee7c9324 | ||
|
|
fa6ee155ac | ||
|
|
c186e53eab | ||
|
|
75abd6f80c | ||
|
|
0a98e6154c | ||
|
|
c2b31fad43 | ||
|
|
2043dffc10 | ||
|
|
244aabcc1a | ||
|
|
a1faed8717 | ||
|
|
fcf9176523 | ||
|
|
30db9e3cd6 | ||
|
|
fb3df0ff4d | ||
|
|
9b4b48e6e3 | ||
|
|
9a9cdaff09 | ||
|
|
3a82efcf6f | ||
|
|
7c2d08b06f | ||
|
|
aebe84c2cd | ||
|
|
3768f0ebcf | ||
|
|
8f7d527c21 | ||
|
|
54ec5ee0d0 | ||
|
|
dccb244c8f | ||
|
|
a37084af1a | ||
|
|
af0a7ae89b | ||
|
|
315cbab4be | ||
|
|
bc3830d685 | ||
|
|
4893d28bb0 | ||
|
|
2a7670e19d | ||
|
|
86e6a1bb2d | ||
|
|
d1d3f95dfe | ||
|
|
ebfc90e998 | ||
|
|
704db9eb46 | ||
|
|
3557870d34 | ||
|
|
59354c7251 | ||
|
|
cae25599f8 | ||
|
|
e89eb46f79 | ||
|
|
039b433d0a | ||
|
|
0319d16f2b | ||
|
|
f11c45d169 | ||
|
|
572c0fea4a | ||
|
|
5e9d364e5e | ||
|
|
57d62fdb29 | ||
|
|
0c7332396b | ||
|
|
7bdca36f53 | ||
|
|
f8a8a84f1e | ||
|
|
8009baca26 | ||
|
|
e0786594bc | ||
|
|
7fe67960bf | ||
|
|
83fca93649 | ||
|
|
96a9850d9c | ||
|
|
1cb9ddecac | ||
|
|
f7697ce8bf | ||
|
|
9358348795 | ||
|
|
711ca1e761 | ||
|
|
8eeff0058b | ||
|
|
ea456c6d80 | ||
|
|
3bdc06b5b4 | ||
|
|
2c9fbfcae8 | ||
|
|
e4a28f9fc9 | ||
|
|
630bf14387 | ||
|
|
4dd85f845e | ||
|
|
8382d1975b | ||
|
|
4d4cac6a9e | ||
|
|
5bd276a32d | ||
|
|
e31ad60d32 | ||
|
|
f511b29fcb | ||
|
|
158abdb96c | ||
|
|
959f6770dd | ||
|
|
c429fef56d | ||
|
|
4ce8d293f4 | ||
|
|
ef9b28671b | ||
|
|
e3afaa1d49 | ||
|
|
094e6951d0 | ||
|
|
73f33e238c | ||
|
|
d1b8051771 | ||
|
|
17fd6db65f | ||
|
|
e6345bc2fe | ||
|
|
208b1a2eba | ||
|
|
b796425551 | ||
|
|
425a9f9a57 | ||
|
|
3701c1f2cb | ||
|
|
7a5233ed1a | ||
|
|
5df349c118 | ||
|
|
f97da3afcb | ||
|
|
d997759ccf | ||
|
|
5e710ba424 | ||
|
|
88c1dd1bc6 | ||
|
|
b63fbfa4c6 | ||
|
|
565b441444 | ||
|
|
6900cf3475 | ||
|
|
2c0c63aad4 | ||
|
|
b48e6f2437 | ||
|
|
37b4096713 | ||
|
|
7a0894e4d2 | ||
|
|
e6f8c29078 | ||
|
|
b1672408c3 | ||
|
|
b79f2ae51d | ||
|
|
2a758d2e57 | ||
|
|
07def17334 | ||
|
|
1c3fb47c89 | ||
|
|
d1e4deb7eb | ||
|
|
ad97571ab0 | ||
|
|
4bbf0983c5 | ||
|
|
13e86e5e53 | ||
|
|
454070c346 | ||
|
|
10458d9975 | ||
|
|
823c38c50c | ||
|
|
a593e68ffb | ||
|
|
5ef8cf5381 | ||
|
|
db94d5bc5d | ||
|
|
b6fe1bc2a7 | ||
|
|
78754ca313 | ||
|
|
8e8667b721 | ||
|
|
40731104cb | ||
|
|
879872202a | ||
|
|
0fcf16d9d2 | ||
|
|
23d27ab838 | ||
|
|
0b4df647d1 | ||
|
|
a74c4f822e | ||
|
|
389372e8f3 | ||
|
|
c228d274b5 | ||
|
|
d8e0e03aa5 | ||
|
|
171dd7af59 | ||
|
|
10bf27a246 | ||
|
|
199c243eb0 | ||
|
|
11b0143b1c | ||
|
|
f2e20eaea3 | ||
|
|
c8725a4588 | ||
|
|
f881965485 | ||
|
|
3e16d0daf6 | ||
|
|
7068f57443 | ||
|
|
8b611511f5 | ||
|
|
1c9f0ee724 | ||
|
|
2d816b39f6 | ||
|
|
c0b13bf040 | ||
|
|
e40d98f386 | ||
|
|
8af295a72b | ||
|
|
ac4dde1f8b | ||
|
|
2dc87fd37c | ||
|
|
aebceec1f6 | ||
|
|
95b59a9cc3 | ||
|
|
4ff72b66a8 | ||
|
|
d1162d0272 | ||
|
|
794ac2bab2 | ||
|
|
7b76fe86b2 | ||
|
|
4387c7ed1e | ||
|
|
f819fc7db9 | ||
|
|
fb5a882fdb | ||
|
|
be8c0a6787 | ||
|
|
5dc84fbd77 | ||
|
|
0c37827fb0 | ||
|
|
cee5d39b27 | ||
|
|
9c5603eae6 | ||
|
|
dd42c99ca7 | ||
|
|
403c60ec6c | ||
|
|
7c7e6d3b45 | ||
|
|
316e51efb2 | ||
|
|
9e838e266b | ||
|
|
a56b984c53 | ||
|
|
4040b270a8 | ||
|
|
eab8614bcc | ||
|
|
ef8befe0cd | ||
|
|
860b79fc9b | ||
|
|
93341fc4ec | ||
|
|
51f123bc41 | ||
|
|
f0512fbeca | ||
|
|
71f6fcd2e6 | ||
|
|
d1a244efa0 | ||
|
|
075478db17 | ||
|
|
6f4582eec9 | ||
|
|
91f5f9a8b4 | ||
|
|
f04adc0901 | ||
|
|
f184ee7e58 | ||
|
|
9abeec578e | ||
|
|
8deab5f94f | ||
|
|
50fca74267 | ||
|
|
9e7bf97cc9 | ||
|
|
96be72b4c6 | ||
|
|
5c31ac6d00 | ||
|
|
1675d591dd | ||
|
|
361495dfed | ||
|
|
2b81e861f6 | ||
|
|
657a761c3a | ||
|
|
6d401c4234 | ||
|
|
8a33d2e5ed | ||
|
|
66e994be60 | ||
|
|
4e50ca9340 | ||
|
|
af064d1cda | ||
|
|
585c444f2d | ||
|
|
85683a0b3f | ||
|
|
4c7aa2b26b | ||
|
|
af236e1099 | ||
|
|
79f6014e26 | ||
|
|
0fa3de40ba | ||
|
|
d77f938107 | ||
|
|
e44aaa6618 | ||
|
|
96d88bb08c | ||
|
|
b502f89da6 | ||
|
|
d7b8cd7e03 | ||
|
|
61681bdc2b | ||
|
|
dc65030a63 | ||
|
|
328c110a97 | ||
|
|
0b7cd32209 | ||
|
|
b1c0ab74d7 | ||
|
|
faee01cde2 | ||
|
|
6f456437f7 | ||
|
|
1b0fd94a8c | ||
|
|
268d04841f | ||
|
|
9408ac5d9f | ||
|
|
45d2b6206e | ||
|
|
a943fdcc5a | ||
|
|
06e02f0b0f | ||
|
|
5b29dc66a9 | ||
|
|
5187916fd3 | ||
|
|
d6e444c6e9 | ||
|
|
59f4ef1091 | ||
|
|
bab1b3e9df | ||
|
|
4e8cbdc93b | ||
|
|
d3e5c5b335 | ||
|
|
2b4b38ddc6 | ||
|
|
d3e6d775e8 | ||
|
|
ecf3b57679 | ||
|
|
115981ecc6 | ||
|
|
009f297f59 | ||
|
|
f539fb1acf | ||
|
|
297e1e79c2 | ||
|
|
d8a95d9d48 | ||
|
|
1d8e87981b | ||
|
|
5c97f9c89a | ||
|
|
a3b02d775c | ||
|
|
a83b01b0f4 | ||
|
|
4be1d76869 | ||
|
|
15a6924894 | ||
|
|
fdeef6504c | ||
|
|
00ce3466fd | ||
|
|
ee3013499d | ||
|
|
0f966d036b | ||
|
|
2c09e625fb | ||
|
|
8908796b44 | ||
|
|
a01fd67565 | ||
|
|
07fe173487 | ||
|
|
e9e63e66a5 | ||
|
|
6cbfb6dd48 | ||
|
|
e1f4c0a1ca | ||
|
|
5a6b912037 | ||
|
|
996324829d | ||
|
|
e6e556f9af | ||
|
|
0318b99aa7 | ||
|
|
bb79ba3ca5 | ||
|
|
57679fdc40 | ||
|
|
01c80d1f8b | ||
|
|
76d69219f8 | ||
|
|
1b0761a36d | ||
|
|
b519507600 | ||
|
|
cdb7077b91 | ||
|
|
55639e3cf2 | ||
|
|
088ee5dc34 | ||
|
|
20d8374edc | ||
|
|
265b90251d | ||
|
|
49d833eee7 | ||
|
|
98f4e0dd7f | ||
|
|
4ec1a947bc | ||
|
|
dfe825ebec | ||
|
|
4f24e0a2dc | ||
|
|
ed57ab52c8 | ||
|
|
0d3c3c5e35 | ||
|
|
333acb7911 | ||
|
|
056b363b7b | ||
|
|
693e89c635 | ||
|
|
d95a7f7982 | ||
|
|
2f72fee1aa | ||
|
|
59ffa6c357 | ||
|
|
1eea1b4402 | ||
|
|
c936d0df49 | ||
|
|
41dd6b2b69 | ||
|
|
524ef19991 | ||
|
|
3312f7db8c | ||
|
|
49ea6e39cf | ||
|
|
4c15ddc35c | ||
|
|
1a7c8f088d | ||
|
|
1d6af0c540 | ||
|
|
261130aa6f | ||
|
|
98526fcd0b | ||
|
|
64d1e70d33 | ||
|
|
71473f3804 | ||
|
|
f360ee172f | ||
|
|
4940f32fbf | ||
|
|
b025af8d07 | ||
|
|
6418ab7cbe | ||
|
|
30edf0efcf | ||
|
|
37b02b13fa | ||
|
|
05453e92ea | ||
|
|
4c3b830788 | ||
|
|
0c706f2e5b | ||
|
|
5ebe02fb89 | ||
|
|
3b8bf99e79 | ||
|
|
916c3d4db8 | ||
|
|
b1e89424dc | ||
|
|
9d2af821d1 | ||
|
|
e9eee27932 | ||
|
|
4d23dce932 | ||
|
|
7277097d59 | ||
|
|
d6bc1f407b | ||
|
|
f002c83cbd | ||
|
|
afcd63805b | ||
|
|
f5cca4f3fb | ||
|
|
ad2048a72c | ||
|
|
e1ceb8b6af | ||
|
|
a3ce7ba3ad | ||
|
|
41f3845f63 | ||
|
|
86434d3919 | ||
|
|
75efd9a733 | ||
|
|
f5b691d68b | ||
|
|
e9bf7bb547 | ||
|
|
c6b726eb63 | ||
|
|
42638e3df6 | ||
|
|
33a3c7645e | ||
|
|
86a0a8d19a | ||
|
|
a1da4a7ef3 | ||
|
|
ecfb7bb188 | ||
|
|
bf2047e1d6 | ||
|
|
09dc0e4c15 | ||
|
|
eed5bcda30 | ||
|
|
d35c9887f6 | ||
|
|
7eb98c9d4b | ||
|
|
bc0d89573e | ||
|
|
67c8fc7dbd | ||
|
|
d472948201 | ||
|
|
3d44f6000d | ||
|
|
9b2d001c3c | ||
|
|
74ae9ee040 | ||
|
|
d1e455f038 | ||
|
|
7d49fa98e7 | ||
|
|
db59441d11 | ||
|
|
5da8afeb7d | ||
|
|
7575d7f271 | ||
|
|
7830502987 | ||
|
|
a3decdf9ad | ||
|
|
051ece8358 | ||
|
|
92da7fc877 | ||
|
|
727d9ce781 | ||
|
|
94cee771bd | ||
|
|
73db115f02 | ||
|
|
c8aab06d77 | ||
|
|
be80003860 | ||
|
|
639c024e7e | ||
|
|
8dc1e9f6b5 | ||
|
|
a2a4d9ac16 | ||
|
|
60de179f74 | ||
|
|
518f384b99 | ||
|
|
a83598676b | ||
|
|
59ddfe1d05 | ||
|
|
d6def93b4e | ||
|
|
65d95f4616 | ||
|
|
b858f5dc01 | ||
|
|
648dbe7031 | ||
|
|
7b57c73d2a | ||
|
|
9c2ec91100 | ||
|
|
c5769d5a81 | ||
|
|
d3d5f62801 | ||
|
|
bf8146d509 | ||
|
|
fad5e7e238 | ||
|
|
af79b22f71 | ||
|
|
a31afef298 | ||
|
|
6096876894 | ||
|
|
0fe3d79103 | ||
|
|
8b0ae9d61a | ||
|
|
4489b46200 | ||
|
|
1240e8e353 | ||
|
|
74fa400237 | ||
|
|
cc7fe900d2 | ||
|
|
aab416e62b | ||
|
|
d924c292bc | ||
|
|
a251fbfcc6 | ||
|
|
5bea05ee15 | ||
|
|
21c7eee893 | ||
|
|
c9a5e12352 | ||
|
|
956140c6db | ||
|
|
fde0b3e412 | ||
|
|
2e8ba7a185 | ||
|
|
cc27988681 | ||
|
|
590af0c276 | ||
|
|
32fe6b2f77 | ||
|
|
9f5607016d | ||
|
|
2e6ecdf02d | ||
|
|
ef6cb69fb0 | ||
|
|
71504af6b2 | ||
|
|
a3ca084444 | ||
|
|
35082be6cd | ||
|
|
934ddb1bfe | ||
|
|
5d0a1d8fb2 | ||
|
|
1c8c492400 | ||
|
|
186db1d419 | ||
|
|
b596628f81 | ||
|
|
74aad20d6b | ||
|
|
e7a50901ea | ||
|
|
b7fa0dc70c | ||
|
|
c19dc78f3e | ||
|
|
c7639f0a02 | ||
|
|
ad984b2ec4 | ||
|
|
baa0378fa2 | ||
|
|
9f422dc5ac | ||
|
|
3ab28d8270 | ||
|
|
f63950ff3c | ||
|
|
b4dd713dab | ||
|
|
80e4701d41 | ||
|
|
7c7a05f1f8 | ||
|
|
6e57d4711a | ||
|
|
626c4c956e | ||
|
|
522ae07942 | ||
|
|
e5b7f723d2 | ||
|
|
1c8830f59c | ||
|
|
ad25cf9c9e | ||
|
|
c4c63e5ddc | ||
|
|
443c858700 | ||
|
|
c19b80f991 | ||
|
|
f3ee88b43c | ||
|
|
1ac6ee6c59 | ||
|
|
3f4da811ca | ||
|
|
598a9dd2da | ||
|
|
7a7a81d5c4 | ||
|
|
2730d157d8 | ||
|
|
55519e487d | ||
|
|
55fb14b6df | ||
|
|
6b8fdd9c5c | ||
|
|
94b15220ea | ||
|
|
bbb3be736e | ||
|
|
0355dc6cf3 | ||
|
|
287918372a | ||
|
|
6597ec1b6e | ||
|
|
3184db27d1 | ||
|
|
99434e69a7 | ||
|
|
cb0997a0b1 | ||
|
|
6fa2c71bd3 | ||
|
|
2f66317190 | ||
|
|
c718d27a4c | ||
|
|
cadb92c4ef | ||
|
|
16d66f6c90 | ||
|
|
5c157888af | ||
|
|
a556899881 | ||
|
|
3ed6ad8b11 | ||
|
|
a06887e663 | ||
|
|
ef9757df75 | ||
|
|
03811f1f39 | ||
|
|
c1801ae450 | ||
|
|
24ee045dcf | ||
|
|
06c0ddf6ae | ||
|
|
e22a7c3063 | ||
|
|
b91dcc8e27 | ||
|
|
496ba82835 | ||
|
|
410518e677 | ||
|
|
7cafebab7d | ||
|
|
429e22cc5e | ||
|
|
1edc17fb7e | ||
|
|
dd3cf771bd | ||
|
|
eebb5e5cd4 | ||
|
|
46fc2aab55 | ||
|
|
b765560fff | ||
|
|
6a3dd841b0 | ||
|
|
1b899a40a5 | ||
|
|
a05769c42d | ||
|
|
2c2ff2d1d2 | ||
|
|
045363f138 | ||
|
|
6860e1deb0 | ||
|
|
469ed374ad | ||
|
|
d952f194be | ||
|
|
ccbf4afd44 | ||
|
|
2b1a950204 | ||
|
|
e930ee8efd | ||
|
|
d469e50985 | ||
|
|
39ba630479 | ||
|
|
074f84818d | ||
|
|
dd645a761d | ||
|
|
a12125e26d | ||
|
|
67e82c5d8f | ||
|
|
52b30cf988 | ||
|
|
ccfdc5f3b5 | ||
|
|
339d002529 | ||
|
|
5afe0bdc59 | ||
|
|
59d3a74206 | ||
|
|
58d3d29fbe | ||
|
|
c340544886 | ||
|
|
c137e6178a | ||
|
|
98f1fd42f6 | ||
|
|
f344a3b125 | ||
|
|
24bdffdce8 | ||
|
|
853f4aabfc | ||
|
|
439cd870e5 | ||
|
|
ab47a769ea | ||
|
|
b73b6584c2 | ||
|
|
06f4fb29b4 | ||
|
|
3d19acd434 | ||
|
|
40dd882857 | ||
|
|
029f49078e | ||
|
|
d5f19877f2 | ||
|
|
9679b0746b | ||
|
|
55209cbca5 | ||
|
|
63d7ea9b06 | ||
|
|
1062d9a302 | ||
|
|
30b2243d1e | ||
|
|
9f755c2939 | ||
|
|
375f6d9117 | ||
|
|
1e3b6f197d | ||
|
|
9cf21f9d75 | ||
|
|
033168cc8d | ||
|
|
e698240a91 | ||
|
|
83df2a3262 | ||
|
|
9dce540451 | ||
|
|
b8f683a335 | ||
|
|
9781d9bbd9 | ||
|
|
43f81c5368 | ||
|
|
c702d1523c | ||
|
|
4def250361 | ||
|
|
c34455db3c | ||
|
|
e252d80de9 | ||
|
|
60cf1dbf23 | ||
|
|
5839a6b2c5 | ||
|
|
c0da6a3176 | ||
|
|
85d5316d1f | ||
|
|
cb9493eb6e | ||
|
|
cfb473df8c | ||
|
|
2ff395f5ef | ||
|
|
73fa270fce | ||
|
|
552d49ea7b | ||
|
|
f1f6dea5e7 | ||
|
|
a677b004b3 | ||
|
|
fd34f59ff9 | ||
|
|
0c09707744 | ||
|
|
142da5b0b6 | ||
|
|
80b72c1984 | ||
|
|
e9f87684af | ||
|
|
5fb2f546b0 | ||
|
|
c234444026 | ||
|
|
eda9dfaa10 | ||
|
|
4f13827ae0 | ||
|
|
47b076b41d | ||
|
|
1e40241219 | ||
|
|
796f4c918d | ||
|
|
ae6ce7f443 | ||
|
|
95189ce5ce | ||
|
|
8de78406d5 | ||
|
|
ea253313d5 | ||
|
|
c3413c4340 | ||
|
|
d22e6ca984 | ||
|
|
9b8ab86219 | ||
|
|
8a9c603725 | ||
|
|
8f4da1b601 | ||
|
|
ce917be59d | ||
|
|
1b1d8788df | ||
|
|
c126bff23c | ||
|
|
64355e0ddc | ||
|
|
510b7a0e56 | ||
|
|
12da47d34a | ||
|
|
3dca6c1db2 | ||
|
|
29aec92a7a | ||
|
|
c373c1f9dd | ||
|
|
cba7c77fa2 | ||
|
|
80e3db0b2d | ||
|
|
4452336aa0 | ||
|
|
d0710a16b6 | ||
|
|
38f603cc0d | ||
|
|
5a6652c864 | ||
|
|
59ed3e3fba | ||
|
|
870720ad4b | ||
|
|
7d7d5280c5 | ||
|
|
a93ee4bbff | ||
|
|
d9b719103a | ||
|
|
c65f488536 | ||
|
|
3c11817edb | ||
|
|
8a5dac9c24 | ||
|
|
968729fbe6 | ||
|
|
8a8d5c65f2 | ||
|
|
80e05a0392 | ||
|
|
2374e998ee | ||
|
|
48b6b83fba | ||
|
|
6eac516d01 | ||
|
|
f1d42ccc77 | ||
|
|
68ebf3243f | ||
|
|
f8a93ed5f2 | ||
|
|
ce14cd0447 | ||
|
|
ee5ac2785d | ||
|
|
1c68bfea8d | ||
|
|
a097264983 | ||
|
|
19d5c1144e | ||
|
|
c123dd5d42 | ||
|
|
3fe0fec801 | ||
|
|
245ff6ade9 | ||
|
|
353cf1daad | ||
|
|
2e5dbd5cc1 | ||
|
|
7d6ce95998 | ||
|
|
dca9816fbe | ||
|
|
a5eee0a3b1 | ||
|
|
5d0b890de9 | ||
|
|
7c6492965f | ||
|
|
5f5280e2a6 | ||
|
|
220d6cb993 | ||
|
|
5f33807560 | ||
|
|
6d64a900f0 | ||
|
|
3550c2a9f7 | ||
|
|
0bdda70761 | ||
|
|
01debf1a09 | ||
|
|
76df487661 | ||
|
|
73a129b430 | ||
|
|
71afba8ab9 | ||
|
|
c0b01fca35 | ||
|
|
7158de8b81 | ||
|
|
c1afefb712 | ||
|
|
d924029a5a | ||
|
|
f5d104fa68 | ||
|
|
1c0c1c6efc | ||
|
|
4d4e449150 | ||
|
|
900f92cbd6 | ||
|
|
48aab74737 | ||
|
|
427aa039d6 | ||
|
|
91909e2ce3 | ||
|
|
967f20edc7 | ||
|
|
61d2e9545e | ||
|
|
b1ea40abeb | ||
|
|
b898be199c | ||
|
|
e82b61ed7e | ||
|
|
31aef55343 | ||
|
|
3da7797ecc | ||
|
|
b677a34942 | ||
|
|
8275a0da69 | ||
|
|
cc86506868 | ||
|
|
83ee75dc55 | ||
|
|
a8d92c1e69 | ||
|
|
ccf98f9b32 | ||
|
|
6c17498481 | ||
|
|
ce9794f72f | ||
|
|
a48ddeef7f | ||
|
|
864298cee8 | ||
|
|
63b48685a6 | ||
|
|
37d4c2c552 | ||
|
|
0515f7876b | ||
|
|
9e801f0f1c | ||
|
|
c7a8d6ddae | ||
|
|
0f828b5663 | ||
|
|
3f28a69d7d | ||
|
|
8980d92263 | ||
|
|
dd7512cc62 | ||
|
|
3005f7d845 | ||
|
|
cbbd90a884 | ||
|
|
883803d348 | ||
|
|
56b73ef7e2 | ||
|
|
b1c98d33e5 | ||
|
|
4a4feea1be | ||
|
|
ea015b531a | ||
|
|
5b8c40dd8a | ||
|
|
4859499179 | ||
|
|
0e6cdbbb6b | ||
|
|
cc8c8ead2e | ||
|
|
2b6a4ef466 | ||
|
|
a3b9d78463 | ||
|
|
7873d21644 | ||
|
|
2ad1b08de2 | ||
|
|
4fae252d0a | ||
|
|
f89179da21 | ||
|
|
b4e63f89ce | ||
|
|
ff872edc4b | ||
|
|
013258d421 | ||
|
|
15575e4189 | ||
|
|
3c051cde95 | ||
|
|
5d2a173e22 | ||
|
|
a2032ab730 | ||
|
|
9e4d8b846c | ||
|
|
7e8dd2dc2e | ||
|
|
b73d0f1836 | ||
|
|
b478e54ec0 | ||
|
|
cf8c6b8c24 | ||
|
|
d64294284a | ||
|
|
098468e6bd | ||
|
|
cebc87a197 | ||
|
|
0d3ceb0e61 | ||
|
|
e017b07337 | ||
|
|
c4ec81f22b | ||
|
|
c597df056d | ||
|
|
5517b418ac | ||
|
|
81e7642bb8 | ||
|
|
17718d5349 | ||
|
|
f5ef97fa30 | ||
|
|
30c6657148 | ||
|
|
2b173dc7f6 | ||
|
|
cd48a28b5a | ||
|
|
b7ad5f1f42 | ||
|
|
9bf6795328 | ||
|
|
4364189b3e | ||
|
|
40676a365f | ||
|
|
97314a0436 | ||
|
|
d1b93018e3 | ||
|
|
a6b7b227f7 | ||
|
|
1c6a5454b8 | ||
|
|
06745becd7 | ||
|
|
c172a12956 | ||
|
|
c6f18fb7b2 | ||
|
|
dc3fabbbb9 | ||
|
|
905402ebab | ||
|
|
fbed0d7857 | ||
|
|
cc74395297 | ||
|
|
560c55d5db | ||
|
|
a45c972a38 | ||
|
|
51c1af5ad9 | ||
|
|
d537164799 | ||
|
|
012910268b | ||
|
|
74d9068925 | ||
|
|
5d68f558a1 | ||
|
|
2164c1a51a | ||
|
|
5f180fc289 | ||
|
|
5be44d9bd2 | ||
|
|
54c9857072 | ||
|
|
c48ac797f6 | ||
|
|
9180d22629 | ||
|
|
aa0274534c | ||
|
|
c97b37f279 | ||
|
|
14c12533a7 | ||
|
|
39f50bad30 | ||
|
|
014061d0a4 | ||
|
|
76c498a7f9 | ||
|
|
fa9229c687 | ||
|
|
182e7ef9a6 | ||
|
|
08e6600880 | ||
|
|
edeb95aee0 | ||
|
|
4b1d1f4541 | ||
|
|
801d3fa8cd | ||
|
|
d15a46fc6f | ||
|
|
7f46a267b6 | ||
|
|
195cb8d5ee | ||
|
|
7454a965bc | ||
|
|
9ef988c259 | ||
|
|
7a2ff0995f | ||
|
|
9b65d3926b | ||
|
|
06b3536079 | ||
|
|
315396fd42 | ||
|
|
b90c43f667 | ||
|
|
6d97a29e9c | ||
|
|
7f0e2acab9 | ||
|
|
be35886c92 | ||
|
|
73ad9c5365 | ||
|
|
818751ffad | ||
|
|
d5856e7974 | ||
|
|
2caf3d42f4 | ||
|
|
e766743baa | ||
|
|
facb23f0a5 | ||
|
|
a2dff0e6bf | ||
|
|
60c53ec1d1 | ||
|
|
e3c903f773 | ||
|
|
955764aa18 | ||
|
|
ed22aa844f | ||
|
|
6b8fc50021 | ||
|
|
1aed5c36b1 | ||
|
|
4690f53be7 | ||
|
|
29a22926aa | ||
|
|
a4da8144c3 | ||
|
|
84bfa304c4 | ||
|
|
72749015dd | ||
|
|
ae5d656a64 | ||
|
|
d8f2cde9de | ||
|
|
935a4ae46e | ||
|
|
ffecec9cba | ||
|
|
734845eff0 | ||
|
|
bd6b18eea2 | ||
|
|
6473bfe84e | ||
|
|
66fff2d3d9 | ||
|
|
ef1b470f0a | ||
|
|
b087d49530 | ||
|
|
cd46cd3f8d | ||
|
|
c44133942a | ||
|
|
24e40f41a5 | ||
|
|
43b90587a5 | ||
|
|
f5b2516492 | ||
|
|
5bec40eb59 | ||
|
|
6b1cccfdf8 | ||
|
|
ddf28d7542 | ||
|
|
9cf69a3128 | ||
|
|
8dc62def08 | ||
|
|
ac8b898ccc | ||
|
|
97e675e476 | ||
|
|
4b488ce1f7 | ||
|
|
7f5fe8831a | ||
|
|
f7ff63301b | ||
|
|
9bb1602370 | ||
|
|
40da1d2e16 | ||
|
|
8dd61d026c | ||
|
|
4d0464f729 | ||
|
|
e0f1410c8b | ||
|
|
2bf7698961 | ||
|
|
0654a2baef | ||
|
|
0e785b2ccf | ||
|
|
46088a48f9 | ||
|
|
82acaef96f | ||
|
|
8556f4d586 | ||
|
|
e8308b1069 | ||
|
|
6516d18cc2 | ||
|
|
6dd0105908 | ||
|
|
3e27b931b5 | ||
|
|
73046e56e5 | ||
|
|
84c350af90 | ||
|
|
30bd8d810f | ||
|
|
d67b17c58a | ||
|
|
5033fbba50 | ||
|
|
5e752ecfb4 | ||
|
|
e976bf3ef6 | ||
|
|
24ae8d3c4c | ||
|
|
26fb9482aa | ||
|
|
1a3019b94f | ||
|
|
67136b708f | ||
|
|
07990cea6f | ||
|
|
a92895f96e | ||
|
|
861df94e69 | ||
|
|
5efd18dac7 | ||
|
|
77fcc8cd7e | ||
|
|
b5e53eb076 | ||
|
|
39fe0a17b8 | ||
|
|
524278a94c | ||
|
|
8d3f36d62c | ||
|
|
459b318fdf | ||
|
|
68e7930835 | ||
|
|
947b3f79be | ||
|
|
8070e5564b | ||
|
|
443a67581e | ||
|
|
60aaff9f38 | ||
|
|
0464e3dbfe | ||
|
|
6f892e5174 | ||
|
|
187cbb7d67 | ||
|
|
b114b6da81 | ||
|
|
1228d55fcb | ||
|
|
b5c17c9776 | ||
|
|
27ee52b5c0 | ||
|
|
ccbbd2555a | ||
|
|
d78b7b2fee | ||
|
|
047639a36e | ||
|
|
86b5007ba5 | ||
|
|
4879aab701 | ||
|
|
da49b651b8 | ||
|
|
c2e9c0eebc | ||
|
|
9ed6bc921d | ||
|
|
d78ecf17e2 | ||
|
|
8aeddf1141 | ||
|
|
31924276d3 | ||
|
|
3321b11e2a | ||
|
|
920379c5af | ||
|
|
598a4345f0 | ||
|
|
ff8013ed44 | ||
|
|
20b69b4286 | ||
|
|
60165bad5c | ||
|
|
149f7ff106 | ||
|
|
ac699a1c76 | ||
|
|
a43c480e0c | ||
|
|
6eda7791c6 | ||
|
|
7dc0bf027a | ||
|
|
c218518805 | ||
|
|
0505c51ee8 | ||
|
|
f29da33bcf | ||
|
|
5f8e3069b3 | ||
|
|
8d7abddace | ||
|
|
39fe61f57e | ||
|
|
176f7a0cc8 | ||
|
|
e9ec9889d4 | ||
|
|
0a161bdf55 | ||
|
|
e817f190b6 | ||
|
|
f88a96cd4f | ||
|
|
7cf47935e0 | ||
|
|
f2844003e8 | ||
|
|
9702fd6ce5 | ||
|
|
577c3f2990 | ||
|
|
bf9216168b | ||
|
|
95dc2f1235 | ||
|
|
84503253d2 | ||
|
|
5fd48c6707 | ||
|
|
335668c03a | ||
|
|
cba4b6856c | ||
|
|
c345c6cdd1 | ||
|
|
a1e214e657 | ||
|
|
ff1654b3f7 | ||
|
|
12e24abdd6 | ||
|
|
33a9d7fbcf | ||
|
|
bdff59e628 | ||
|
|
c1ef5c346a | ||
|
|
a573db1be9 | ||
|
|
84e3e37a44 | ||
|
|
f5724dfca0 | ||
|
|
f848710afa | ||
|
|
1e0dc4f541 | ||
|
|
470bbf4261 | ||
|
|
8519e12aa7 | ||
|
|
026bfaf7a9 | ||
|
|
4d4e76960f | ||
|
|
3637c37cca | ||
|
|
a5ed6caa5a | ||
|
|
1429abd94d | ||
|
|
0e873b9ea5 | ||
|
|
cbc8886241 | ||
|
|
6c7bfb4c50 | ||
|
|
6ed5e52890 | ||
|
|
bc1bd88d75 | ||
|
|
09e1201086 | ||
|
|
df829a4956 | ||
|
|
46dc163e06 | ||
|
|
e70b2bdbbd | ||
|
|
c1f8e930bb | ||
|
|
c8dd1d8509 | ||
|
|
e50adc39c5 | ||
|
|
09948b6a21 | ||
|
|
f50e80d24d | ||
|
|
777512aad7 | ||
|
|
423fd272d4 | ||
|
|
969256f130 | ||
|
|
702d4af8d9 | ||
|
|
02696c1328 | ||
|
|
dcffdf3329 | ||
|
|
4110b5ab48 | ||
|
|
dea96069b0 | ||
|
|
04c5cb08f1 | ||
|
|
b37f8943c7 | ||
|
|
54d4f8b70b | ||
|
|
658cc43f86 | ||
|
|
958abe5ed7 | ||
|
|
fa99c2ef7c | ||
|
|
2b38f474fd | ||
|
|
5643814e65 | ||
|
|
59b588b797 | ||
|
|
757321930d | ||
|
|
03a5a382e9 | ||
|
|
66a91db8f0 | ||
|
|
013dd020c1 | ||
|
|
948e469c05 | ||
|
|
d4aff16da8 | ||
|
|
efa17689b6 | ||
|
|
57ab82b2f0 | ||
|
|
4052cec9ba | ||
|
|
3fc1057999 | ||
|
|
8458beaab0 | ||
|
|
c04c505368 | ||
|
|
2ee913c833 | ||
|
|
09074cbc9e | ||
|
|
29b79a010b | ||
|
|
992f2e2e44 | ||
|
|
7cca7154c5 | ||
|
|
daa7f7a7e2 | ||
|
|
21c1580513 | ||
|
|
9c700eb8d0 | ||
|
|
be21ba515a | ||
|
|
671b0b1228 | ||
|
|
dd455bfabd | ||
|
|
b3294112aa | ||
|
|
baae7ab358 | ||
|
|
d14d5ef83e | ||
|
|
9b92882fc5 | ||
|
|
ebfe0d0b6c | ||
|
|
83a35e02ce | ||
|
|
13ae1f5014 | ||
|
|
4327c45cd3 | ||
|
|
3f4076bc7d | ||
|
|
1ae09c3dff | ||
|
|
daa8127f47 | ||
|
|
aadb5334c2 | ||
|
|
b5b897f7b9 | ||
|
|
273035b209 | ||
|
|
6867408d14 | ||
|
|
bf17d12b08 | ||
|
|
523a602442 | ||
|
|
103beef323 | ||
|
|
e700afac60 | ||
|
|
4e60bc2029 | ||
|
|
a233db799d | ||
|
|
c4958ff40d | ||
|
|
02329e1176 | ||
|
|
480bf8489e | ||
|
|
6762c342c9 | ||
|
|
d760e17095 | ||
|
|
3b6d1e1764 | ||
|
|
9250b2b3dc | ||
|
|
0c1cdc98e8 | ||
|
|
a90f000d46 | ||
|
|
90fe400069 | ||
|
|
de7bc3d7ea | ||
|
|
46b1040108 | ||
|
|
d42908e163 | ||
|
|
6468491e04 | ||
|
|
1e58cfb5f3 | ||
|
|
f01c388f0c | ||
|
|
0e5c84e8f2 | ||
|
|
ca733a5404 | ||
|
|
59a28ebd62 | ||
|
|
d48247f107 | ||
|
|
d084d5f67b | ||
|
|
d11483dcf2 | ||
|
|
2386ee30f7 | ||
|
|
9899d80719 | ||
|
|
e809392191 | ||
|
|
94d9010731 | ||
|
|
b160b1dca8 | ||
|
|
6e2fa6fd22 | ||
|
|
2b0a126d3c | ||
|
|
d8297db010 | ||
|
|
fd7b72d6e5 | ||
|
|
45471910e6 | ||
|
|
bb36804a46 | ||
|
|
0e2991cb7f | ||
|
|
efbcd00b55 | ||
|
|
27e252f68a | ||
|
|
d2cdced416 | ||
|
|
b150ace903 | ||
|
|
7697855e0d | ||
|
|
08f4cb32a5 | ||
|
|
eef026ff4f | ||
|
|
3fac41e8b1 | ||
|
|
5c01f638be | ||
|
|
9e2d41b821 | ||
|
|
472f837f44 | ||
|
|
f2d1b9b5c9 | ||
|
|
621bfa3f99 | ||
|
|
f64703d683 | ||
|
|
75d1fba8f8 | ||
|
|
308a2bbaf3 | ||
|
|
4ba752a10e | ||
|
|
df826d76e5 | ||
|
|
f70138cd28 | ||
|
|
ea81304dd5 | ||
|
|
683ef8cc49 | ||
|
|
f9db51c8d7 | ||
|
|
e5de170887 | ||
|
|
c45761f069 | ||
|
|
65937be5b2 | ||
|
|
b249dd141d | ||
|
|
f67548d18b | ||
|
|
a8b8c10f0f | ||
|
|
3cd0aab25e | ||
|
|
4dcafe0abe | ||
|
|
0de1fcbe9b | ||
|
|
2b8214e144 | ||
|
|
0300aaa850 | ||
|
|
2605372e5d | ||
|
|
72b09506dc | ||
|
|
14b42e8980 | ||
|
|
7c25bb9b79 | ||
|
|
f3b2e1e1ac | ||
|
|
ef4ad0e197 | ||
|
|
34d8331c50 | ||
|
|
8430dff21c | ||
|
|
ba87ee739e | ||
|
|
41503f912f | ||
|
|
fd396b8fc7 |
66
.github/workflows/android.yml
vendored
66
.github/workflows/android.yml
vendored
@@ -28,27 +28,47 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
flavor: [Foss, Gplay]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.1
|
- uses: actions/checkout@v4.2.2
|
||||||
- name: Fail on bad translations
|
- name: Fail on bad translations
|
||||||
run: if grep -ri "<xliff" app/src/main/res/values*/strings.xml; then echo "Invalidly escaped translations found"; exit 1; fi
|
run: if grep -ri "<xliff" app/src/main/res/values*/strings.xml; then echo "Invalidly escaped translations found"; exit 1; fi
|
||||||
- uses: gradle/wrapper-validation-action@v2
|
- uses: gradle/actions/wrapper-validation@v4
|
||||||
- name: set up OpenJDK 17
|
- name: set up OpenJDK 17
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y openjdk-17-jdk-headless
|
sudo apt-get install -y openjdk-17-jdk-headless
|
||||||
sudo update-alternatives --auto java
|
sudo update-alternatives --auto java
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew assembleRelease
|
run: ./gradlew assemble${{ matrix.flavor }}Release
|
||||||
- name: Check lint
|
- name: Check lint
|
||||||
run: ./gradlew lintRelease
|
run: ./gradlew lint${{ matrix.flavor }}Release
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: timeout 5m ./gradlew testReleaseUnitTest || { ./gradlew --stop && timeout 5m ./gradlew testReleaseUnitTest; }
|
run: timeout 5m ./gradlew test${{ matrix.flavor }}ReleaseUnitTest || { ./gradlew --stop && timeout 5m ./gradlew test${{ matrix.flavor }}ReleaseUnitTest; }
|
||||||
- name: SpotBugs
|
- name: Enable KVM
|
||||||
run: ./gradlew spotbugsRelease
|
run: |
|
||||||
- name: Archive test results
|
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||||
if: always()
|
sudo udevadm control --reload-rules
|
||||||
uses: actions/upload-artifact@v4.3.1
|
sudo udevadm trigger --name-match=kvm
|
||||||
with:
|
- name: Run instrumented tests (API 21)
|
||||||
name: test-results
|
uses: ReactiveCircus/android-emulator-runner@v2
|
||||||
path: app/build/reports
|
with:
|
||||||
|
api-level: 21
|
||||||
|
arch: x86_64
|
||||||
|
script: ./gradlew connected${{ matrix.flavor }}DebugAndroidTest
|
||||||
|
- name: Run instrumented tests (API 35)
|
||||||
|
uses: ReactiveCircus/android-emulator-runner@v2
|
||||||
|
with:
|
||||||
|
api-level: 35
|
||||||
|
arch: x86_64
|
||||||
|
script: ./gradlew connected${{ matrix.flavor }}DebugAndroidTest
|
||||||
|
- name: SpotBugs
|
||||||
|
run: ./gradlew spotbugs${{ matrix.flavor }}Release
|
||||||
|
- name: Archive test results
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4.6.2
|
||||||
|
with:
|
||||||
|
name: test-results-flavor${{ matrix.flavor }}
|
||||||
|
path: app/build/reports
|
||||||
|
|||||||
6
.github/workflows/changelog-to-fastlane.yml
vendored
6
.github/workflows/changelog-to-fastlane.yml
vendored
@@ -27,15 +27,15 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
id: checkout
|
id: checkout
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.5.0
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
- name: Run converter script
|
- name: Run converter script
|
||||||
run: python .scripts/changelog_to_fastlane.py
|
run: python .scripts/changelog_to_fastlane.py
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6.0.0
|
uses: peter-evans/create-pull-request@v7.0.8
|
||||||
with:
|
with:
|
||||||
title: "Update Fastlane changelogs"
|
title: "Update Fastlane changelogs"
|
||||||
commit-message: "Update Fastlane changelogs"
|
commit-message: "Update Fastlane changelogs"
|
||||||
|
|||||||
4
.github/workflows/contributors-to-file.yml
vendored
4
.github/workflows/contributors-to-file.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
id: checkout
|
id: checkout
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Update contributors
|
- name: Update contributors
|
||||||
id: update_contributors
|
id: update_contributors
|
||||||
uses: TheLastProject/contributors-to-file-action@v3.2.0
|
uses: TheLastProject/contributors-to-file-action@v3.2.0
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
file_in_repo: app/src/main/res/raw/contributors.txt
|
file_in_repo: app/src/main/res/raw/contributors.txt
|
||||||
min_commit_count: 5
|
min_commit_count: 5
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6.0.0
|
uses: peter-evans/create-pull-request@v7.0.8
|
||||||
with:
|
with:
|
||||||
title: "Update contributors"
|
title: "Update contributors"
|
||||||
commit-message: "Update contributors"
|
commit-message: "Update contributors"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ on:
|
|||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- 'fastlane/**/title.txt'
|
- 'fastlane/**/title.txt'
|
||||||
|
- '.scripts/generate_feature_graphic/**'
|
||||||
permissions:
|
permissions:
|
||||||
actions: none
|
actions: none
|
||||||
checks: none
|
checks: none
|
||||||
@@ -24,11 +25,11 @@ jobs:
|
|||||||
generate-feature-graphic:
|
generate-feature-graphic:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.1
|
- uses: actions/checkout@v4.2.2
|
||||||
- name: Install requirements
|
- name: Install requirements
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install optipng mat2
|
sudo apt-get install imagemagick mat2 optipng
|
||||||
# Install 200 weight versions of relevant Noto (to use for languages not supported by Lexend Deca)
|
# Install 200 weight versions of relevant Noto (to use for languages not supported by Lexend Deca)
|
||||||
sudo apt-get install fonts-noto-extra fonts-noto-cjk-extra
|
sudo apt-get install fonts-noto-extra fonts-noto-cjk-extra
|
||||||
# Custom fonts
|
# Custom fonts
|
||||||
@@ -38,7 +39,7 @@ jobs:
|
|||||||
- name: Generate featureGraphic.png for each language
|
- name: Generate featureGraphic.png for each language
|
||||||
run: .scripts/generate_feature_graphic/generate_feature_graphic.sh
|
run: .scripts/generate_feature_graphic/generate_feature_graphic.sh
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6.0.0
|
uses: peter-evans/create-pull-request@v7.0.8
|
||||||
with:
|
with:
|
||||||
title: "Update feature graphic"
|
title: "Update feature graphic"
|
||||||
commit-message: "Update feature graphic"
|
commit-message: "Update feature graphic"
|
||||||
|
|||||||
8
.github/workflows/gradle-update.yml
vendored
8
.github/workflows/gradle-update.yml
vendored
@@ -21,12 +21,12 @@ jobs:
|
|||||||
gradle-update:
|
gradle-update:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.1
|
- uses: actions/checkout@v4.2.2
|
||||||
- uses: obfusk/gradle-update-action@v2.0.0
|
- uses: obfusk/gradle-update-action@v3.0.0
|
||||||
id: gradle-update
|
id: gradle-update
|
||||||
- uses: gradle/wrapper-validation-action@v2
|
- uses: gradle/actions/wrapper-validation@v4
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6.0.0
|
uses: peter-evans/create-pull-request@v7.0.8
|
||||||
with:
|
with:
|
||||||
title: "Update Gradle to ${{ steps.gradle-update.outputs.version }}"
|
title: "Update Gradle to ${{ steps.gradle-update.outputs.version }}"
|
||||||
commit-message: "Update Gradle to ${{ steps.gradle-update.outputs.version }}"
|
commit-message: "Update Gradle to ${{ steps.gradle-update.outputs.version }}"
|
||||||
|
|||||||
4
.github/workflows/update-locales.yml
vendored
4
.github/workflows/update-locales.yml
vendored
@@ -25,13 +25,13 @@ jobs:
|
|||||||
update-locales:
|
update-locales:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.1
|
- uses: actions/checkout@v4.2.2
|
||||||
- name: Add new locales
|
- name: Add new locales
|
||||||
run: .scripts/new-locales.py
|
run: .scripts/new-locales.py
|
||||||
- name: Update locales
|
- name: Update locales
|
||||||
run: .scripts/locales.py
|
run: .scripts/locales.py
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6.0.0
|
uses: peter-evans/create-pull-request@v7.0.8
|
||||||
with:
|
with:
|
||||||
title: "Update locales"
|
title: "Update locales"
|
||||||
commit-message: "Update locales"
|
commit-message: "Update locales"
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -25,3 +25,6 @@
|
|||||||
/.bundle/
|
/.bundle/
|
||||||
/vendor/bundle
|
/vendor/bundle
|
||||||
/lib/bundler/man/
|
/lib/bundler/man/
|
||||||
|
|
||||||
|
# Catima-specific
|
||||||
|
SHA256SUMS
|
||||||
|
|||||||
23
.scripts/generate_feature_graphic/generate_feature_graphic.sh
Normal file → Executable file
23
.scripts/generate_feature_graphic/generate_feature_graphic.sh
Normal file → Executable file
@@ -4,20 +4,32 @@ set -euo pipefail
|
|||||||
script_location="$(dirname "$(readlink -f "$0")")"
|
script_location="$(dirname "$(readlink -f "$0")")"
|
||||||
|
|
||||||
for lang in "$script_location/../../fastlane/metadata/android/"*; do
|
for lang in "$script_location/../../fastlane/metadata/android/"*; do
|
||||||
|
# Skip languages without title.txt
|
||||||
|
if [ ! -f "$lang/title.txt" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
pushd "$lang"
|
pushd "$lang"
|
||||||
# Place temporary copy for editing if needed
|
# Place temporary copy for editing if needed
|
||||||
cp "$script_location/featureGraphic.svg" featureGraphic.svg
|
cp "$script_location/featureGraphic.svg" featureGraphic.svg
|
||||||
if grep -q — title.txt; then
|
if grep -q — title.txt; then
|
||||||
# Try splitting title.txt on — (em dash)
|
# Try splitting title.txt on — (em dash)
|
||||||
IFS='—' read -r appname subtext < title.txt
|
IFS='—' read -r appname subtext < title.txt
|
||||||
else
|
elif grep -q – title.txt; then
|
||||||
|
# No result, try splitting title.txt on – (en dash)
|
||||||
|
IFS='–' read -r appname subtext < title.txt
|
||||||
|
elif grep -q - title.txt; then
|
||||||
# No result, try splitting on - (dash)
|
# No result, try splitting on - (dash)
|
||||||
IFS='-' read -r appname subtext < title.txt
|
IFS='-' read -r appname subtext < title.txt
|
||||||
|
else
|
||||||
|
# No result, use the full title as app name and default subtext
|
||||||
|
appname=$(< title.txt)
|
||||||
|
subtext="Loyalty Card Wallet"
|
||||||
fi
|
fi
|
||||||
export appname=${appname%% }
|
export appname=${appname%% }
|
||||||
export subtext=${subtext## }
|
export subtext=${subtext## }
|
||||||
# If there is subtext, change the .svg accordingly
|
# If the appname isn't Catima or there is subtext, change the .svg accordingly
|
||||||
if [ -n "$subtext" ]; then
|
if [ "$appname" != "Catima" ] || [ -n "$subtext" ]; then
|
||||||
perl -pi -e 's/Catima/$ENV{appname}/' featureGraphic.svg
|
perl -pi -e 's/Catima/$ENV{appname}/' featureGraphic.svg
|
||||||
perl -pi -e 's/Loyalty Card Wallet/$ENV{subtext}/' featureGraphic.svg
|
perl -pi -e 's/Loyalty Card Wallet/$ENV{subtext}/' featureGraphic.svg
|
||||||
# Set correct font or font size for language if needed
|
# Set correct font or font size for language if needed
|
||||||
@@ -25,11 +37,12 @@ for lang in "$script_location/../../fastlane/metadata/android/"*; do
|
|||||||
# We specifically need the Serif version because of the 200 weight
|
# We specifically need the Serif version because of the 200 weight
|
||||||
case "$(basename "$lang")" in
|
case "$(basename "$lang")" in
|
||||||
bg|el-GR|ru-RU|uk) sed -i "s/Lexend Deca/Noto Serif/" featureGraphic.svg ;;
|
bg|el-GR|ru-RU|uk) sed -i "s/Lexend Deca/Noto Serif/" featureGraphic.svg ;;
|
||||||
|
hi-IN) sed -i -e "s/Yesteryear/Noto Sans Devanagari/" -e "s/Lexend Deca/Noto Serif Devanagari/" featureGraphic.svg ;;
|
||||||
ja-JP) sed -i "s/Lexend Deca/Noto Serif CJK JP/" featureGraphic.svg ;;
|
ja-JP) sed -i "s/Lexend Deca/Noto Serif CJK JP/" featureGraphic.svg ;;
|
||||||
|
kn-IN) sed -i -e 's/font-size="150"/font-size="100"/' -e "s/Yesteryear/Noto Serif Kannada/" featureGraphic.svg ;;
|
||||||
ko) sed -i "s/Lexend Deca/Noto Serif CJK KR/" featureGraphic.svg ;;
|
ko) sed -i "s/Lexend Deca/Noto Serif CJK KR/" featureGraphic.svg ;;
|
||||||
kn-IN) sed -i -e 's/font-size="150"/font-size="100"/' -e 's/y="285.511"/y="235.511"/' featureGraphic.svg ;;
|
|
||||||
zh-CN) sed -i "s/Lexend Deca/Noto Serif CJK SC/" featureGraphic.svg ;;
|
zh-CN) sed -i "s/Lexend Deca/Noto Serif CJK SC/" featureGraphic.svg ;;
|
||||||
zh-TW) sed -i "s/Lexend Deca/Noto Serif CJK TC/" featureGraphic.svg ;;
|
zh-TW) sed -i -e "s/Yesteryear/Noto Sans CJK TC/" -e "s/Lexend Deca/Noto Serif CJK TC/" featureGraphic.svg ;;
|
||||||
*) ;;
|
*) ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ sed = [
|
|||||||
]
|
]
|
||||||
subprocess.run(sed, check=True)
|
subprocess.run(sed, check=True)
|
||||||
|
|
||||||
with open("app/src/main/res/xml/locales_config.xml", "w") as fh:
|
with open("app/src/main/res/xml/locales_config.xml", "w", encoding="utf-8") as fh:
|
||||||
fh.write('<?xml version="1.0" encoding="utf-8"?>\n')
|
fh.write('<?xml version="1.0" encoding="utf-8"?>\n')
|
||||||
fh.write('<locale-config xmlns:android="http://schemas.android.com/apk/res/android">\n')
|
fh.write('<locale-config xmlns:android="http://schemas.android.com/apk/res/android">\n')
|
||||||
fh.write(' <locale android:name="en-US" />\n')
|
fh.write(' <locale android:name="en-US" />\n')
|
||||||
|
|||||||
@@ -19,15 +19,27 @@ REPLACE_CODES = {
|
|||||||
STATS_URL = "https://hosted.weblate.org/api/components/catima/catima/statistics/"
|
STATS_URL = "https://hosted.weblate.org/api/components/catima/catima/statistics/"
|
||||||
|
|
||||||
|
|
||||||
|
class Error(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_weblate_langs() -> List[Tuple[str, int]]:
|
def get_weblate_langs() -> List[Tuple[str, int]]:
|
||||||
r = requests.get(STATS_URL, timeout=5)
|
url = STATS_URL
|
||||||
r.raise_for_status()
|
|
||||||
results = []
|
results = []
|
||||||
for lang in r.json()["results"]:
|
for _ in range(16): # avoid endless loops just in case
|
||||||
if lang["code"] != "en":
|
r = requests.get(url, timeout=5)
|
||||||
code = REPLACE_CODES.get(lang["code"], lang["code"]).replace("_", "-r")
|
r.raise_for_status()
|
||||||
results.append((code, round(lang["translated_percent"])))
|
data = r.json()
|
||||||
return sorted(results)
|
for lang in data["results"]:
|
||||||
|
if lang["code"] != "en":
|
||||||
|
code = REPLACE_CODES.get(lang["code"], lang["code"]).replace("_", "-r")
|
||||||
|
results.append((code, round(lang["translated_percent"])))
|
||||||
|
url = data["next"]
|
||||||
|
if not url:
|
||||||
|
return sorted(results)
|
||||||
|
if not url.split("?")[0] == STATS_URL:
|
||||||
|
raise Error(f"Unexpected next URL: {url}")
|
||||||
|
raise Error("Too many pages")
|
||||||
|
|
||||||
|
|
||||||
def get_dir_langs() -> List[str]:
|
def get_dir_langs() -> List[str]:
|
||||||
@@ -42,7 +54,7 @@ def get_dir_langs() -> List[str]:
|
|||||||
def get_xml_langs() -> List[Tuple[str, bool]]:
|
def get_xml_langs() -> List[Tuple[str, bool]]:
|
||||||
results = []
|
results = []
|
||||||
in_section = False
|
in_section = False
|
||||||
with open("app/src/main/res/values/settings.xml") as fh:
|
with open("app/src/main/res/values/settings.xml", encoding="utf-8") as fh:
|
||||||
for line in fh:
|
for line in fh:
|
||||||
if not in_section and 'name="locale_values"' in line:
|
if not in_section and 'name="locale_values"' in line:
|
||||||
in_section = True
|
in_section = True
|
||||||
@@ -59,7 +71,7 @@ def get_xml_langs() -> List[Tuple[str, bool]]:
|
|||||||
def update_xml_langs(langs: List[Tuple[str, bool]]) -> None:
|
def update_xml_langs(langs: List[Tuple[str, bool]]) -> None:
|
||||||
lines: List[str] = []
|
lines: List[str] = []
|
||||||
in_section = False
|
in_section = False
|
||||||
with open("app/src/main/res/values/settings.xml") as fh:
|
with open("app/src/main/res/values/settings.xml", encoding="utf-8") as fh:
|
||||||
for line in fh:
|
for line in fh:
|
||||||
if not in_section and 'name="locale_values"' in line:
|
if not in_section and 'name="locale_values"' in line:
|
||||||
in_section = True
|
in_section = True
|
||||||
@@ -70,7 +82,7 @@ def update_xml_langs(langs: List[Tuple[str, bool]]) -> None:
|
|||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
with open("app/src/main/res/values/settings.xml", "w") as fh:
|
with open("app/src/main/res/values/settings.xml", "w", encoding="utf-8") as fh:
|
||||||
for line in lines:
|
for line in lines:
|
||||||
fh.write(line)
|
fh.write(line)
|
||||||
|
|
||||||
|
|||||||
79
CHANGELOG.md
79
CHANGELOG.md
@@ -1,10 +1,87 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Unreleased - 133
|
## v2.34.5 - 147 (2025-03-22)
|
||||||
|
|
||||||
|
- Target Android 15
|
||||||
|
- Fix crash reading unsupported pkpass files
|
||||||
|
- Improve pkpass support
|
||||||
|
|
||||||
|
## v2.34.4 - 146 (2025-01-17)
|
||||||
|
|
||||||
|
- Ability to sort cards by start of validity
|
||||||
|
- Temporarily revert to targeting Android 14 to fix some UI issues
|
||||||
|
|
||||||
|
## v2.34.3 - 145 (2025-01-15)
|
||||||
|
|
||||||
|
- Target Android 15
|
||||||
|
- Fix keyboard covering save button in edit screen
|
||||||
|
- Fix some pkpass files not being detected as pkpass (application/vnd-com.apple.pkpass mime type support)
|
||||||
|
|
||||||
|
## v2.34.2 - 144 (2024-12-26)
|
||||||
|
|
||||||
|
- Improve archive/starred icon display
|
||||||
|
|
||||||
|
## v2.34.1 - 143 (2024-12-12)
|
||||||
|
|
||||||
|
- Fix crash when opening invalid pkpass files
|
||||||
|
|
||||||
|
## v2.34.0 - 142 (2024-12-10)
|
||||||
|
|
||||||
|
- Add Passbook (.pkpass) support
|
||||||
|
- Fix import of transparent PDF files
|
||||||
|
- Improve display of transparent thumbnails
|
||||||
|
|
||||||
|
## v2.33.0 - 141 (2024-11-19)
|
||||||
|
|
||||||
|
- Change default column on wide screens to 4
|
||||||
|
- Allow overriding column counts for portrait and landscape in settings
|
||||||
|
- Keep main screen search filter when rotating screen or opening a card
|
||||||
|
- Limit max length of note display on main screen
|
||||||
|
|
||||||
|
## v2.32.1 - 140 (2024-10-29)
|
||||||
|
|
||||||
|
- Fix text wrapping on add dialog
|
||||||
|
|
||||||
|
## v2.32.0 - 139 (2024-10-28)
|
||||||
|
|
||||||
|
- Option to navigate cards using the volume buttons
|
||||||
|
- Fix Stocard import
|
||||||
|
- Fix "Import cancelled" message appearing after successful import
|
||||||
|
|
||||||
|
## v2.31.1 - 138 (2024-08-24)
|
||||||
|
|
||||||
|
- Fix back gesture on main screen dismissing keyboard and search on Android 13+
|
||||||
|
|
||||||
|
## v2.31.0 - 137 (2024-07-26)
|
||||||
|
|
||||||
|
- Allow long store names in preview to split over multiple lines
|
||||||
|
- Option to use front of back image in thumbnail menu
|
||||||
|
- Minor import/export fixes
|
||||||
|
- Minor UI fixes
|
||||||
|
|
||||||
|
## v2.30.0 - 136 (2024-06-18)
|
||||||
|
|
||||||
|
- Support for creating a card when sharing plain text
|
||||||
|
- Display image type instead of barcode below images
|
||||||
|
- Fix possible crash when trying to import a backup from the Nextcloud app
|
||||||
|
- Improved support for devices without camera
|
||||||
|
|
||||||
|
## v2.29.1 - 135 (2024-05-19)
|
||||||
|
|
||||||
|
- Various fixes and improvements to balance handling
|
||||||
|
|
||||||
|
## v2.29.0 - 134 (2024-04-19)
|
||||||
|
|
||||||
|
- Support for scanning PDF files for barcodes
|
||||||
|
- Support for image files with multiple barcodes
|
||||||
|
- Minor UI fixes
|
||||||
|
|
||||||
|
## v2.28.0 - 133 (2024-03-08)
|
||||||
|
|
||||||
- Target Android 14
|
- Target Android 14
|
||||||
- Open card icon in gallery on touch
|
- Open card icon in gallery on touch
|
||||||
- Improve design of Photos tab in edit view
|
- Improve design of Photos tab in edit view
|
||||||
|
- Update spending screen to also support receiving
|
||||||
|
|
||||||
## v2.27.0 - 132 (2024-01-30)
|
## v2.27.0 - 132 (2024-01-30)
|
||||||
|
|
||||||
|
|||||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
catima.g9ex3@hackerchick.me.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||||
|
enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
|
https://www.contributor-covenant.org/translations.
|
||||||
122
Gemfile.lock
122
Gemfile.lock
@@ -1,42 +1,46 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.6)
|
CFPropertyList (3.0.7)
|
||||||
|
base64
|
||||||
|
nkf
|
||||||
rexml
|
rexml
|
||||||
addressable (2.8.6)
|
addressable (2.8.7)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
artifactory (3.0.15)
|
artifactory (3.0.17)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.2)
|
||||||
aws-partitions (1.884.0)
|
aws-partitions (1.1072.0)
|
||||||
aws-sdk-core (3.191.0)
|
aws-sdk-core (3.220.2)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
aws-sigv4 (~> 1.8)
|
aws-sigv4 (~> 1.9)
|
||||||
|
base64
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.77.0)
|
aws-sdk-kms (1.99.0)
|
||||||
aws-sdk-core (~> 3, >= 3.191.0)
|
aws-sdk-core (~> 3, >= 3.216.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sdk-s3 (1.143.0)
|
aws-sdk-s3 (1.182.0)
|
||||||
aws-sdk-core (~> 3, >= 3.191.0)
|
aws-sdk-core (~> 3, >= 3.216.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.8)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sigv4 (1.8.0)
|
aws-sigv4 (1.11.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.4)
|
babosa (1.0.4)
|
||||||
|
base64 (0.2.0)
|
||||||
claide (1.1.0)
|
claide (1.1.0)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
commander (4.6.0)
|
commander (4.6.0)
|
||||||
highline (~> 2.0.0)
|
highline (~> 2.0.0)
|
||||||
declarative (0.0.20)
|
declarative (0.0.20)
|
||||||
digest-crc (0.6.5)
|
digest-crc (0.7.0)
|
||||||
rake (>= 12.0.0, < 14.0.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
domain_name (0.6.20240107)
|
domain_name (0.6.20240107)
|
||||||
dotenv (2.8.1)
|
dotenv (2.8.1)
|
||||||
emoji_regex (3.2.3)
|
emoji_regex (3.2.3)
|
||||||
excon (0.109.0)
|
excon (0.112.0)
|
||||||
faraday (1.10.3)
|
faraday (1.10.4)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-em_http (~> 1.0)
|
||||||
faraday-em_synchrony (~> 1.0)
|
faraday-em_synchrony (~> 1.0)
|
||||||
faraday-excon (~> 1.1)
|
faraday-excon (~> 1.1)
|
||||||
@@ -55,24 +59,24 @@ GEM
|
|||||||
faraday-em_synchrony (1.0.0)
|
faraday-em_synchrony (1.0.0)
|
||||||
faraday-excon (1.1.0)
|
faraday-excon (1.1.0)
|
||||||
faraday-httpclient (1.0.1)
|
faraday-httpclient (1.0.1)
|
||||||
faraday-multipart (1.0.4)
|
faraday-multipart (1.1.0)
|
||||||
multipart-post (~> 2)
|
multipart-post (~> 2.0)
|
||||||
faraday-net_http (1.0.1)
|
faraday-net_http (1.0.2)
|
||||||
faraday-net_http_persistent (1.2.0)
|
faraday-net_http_persistent (1.2.0)
|
||||||
faraday-patron (1.0.0)
|
faraday-patron (1.0.0)
|
||||||
faraday-rack (1.0.0)
|
faraday-rack (1.0.0)
|
||||||
faraday-retry (1.0.3)
|
faraday-retry (1.0.3)
|
||||||
faraday_middleware (1.2.0)
|
faraday_middleware (1.2.1)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.3.0)
|
fastimage (2.4.0)
|
||||||
fastlane (2.219.0)
|
fastlane (2.227.0)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.8, < 3.0.0)
|
addressable (>= 2.8, < 3.0.0)
|
||||||
artifactory (~> 3.0)
|
artifactory (~> 3.0)
|
||||||
aws-sdk-s3 (~> 1.0)
|
aws-sdk-s3 (~> 1.0)
|
||||||
babosa (>= 1.0.3, < 2.0.0)
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
bundler (>= 1.12.0, < 3.0.0)
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
colored
|
colored (~> 1.2)
|
||||||
commander (~> 4.6)
|
commander (~> 4.6)
|
||||||
dotenv (>= 2.1.1, < 3.0.0)
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
emoji_regex (>= 0.1, < 4.0)
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
@@ -81,6 +85,7 @@ GEM
|
|||||||
faraday-cookie_jar (~> 0.0.6)
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
faraday_middleware (~> 1.0)
|
faraday_middleware (~> 1.0)
|
||||||
fastimage (>= 2.1.0, < 3.0.0)
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
|
fastlane-sirp (>= 1.0.0)
|
||||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
google-apis-androidpublisher_v3 (~> 0.3)
|
google-apis-androidpublisher_v3 (~> 0.3)
|
||||||
google-apis-playcustomapp_v1 (~> 0.1)
|
google-apis-playcustomapp_v1 (~> 0.1)
|
||||||
@@ -93,10 +98,10 @@ GEM
|
|||||||
mini_magick (>= 4.9.4, < 5.0.0)
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
multipart-post (>= 2.0.0, < 3.0.0)
|
multipart-post (>= 2.0.0, < 3.0.0)
|
||||||
naturally (~> 2.2)
|
naturally (~> 2.2)
|
||||||
optparse (>= 0.1.1)
|
optparse (>= 0.1.1, < 1.0.0)
|
||||||
plist (>= 3.1.0, < 4.0.0)
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
rubyzip (>= 2.0.0, < 3.0.0)
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
security (= 0.1.3)
|
security (= 0.1.5)
|
||||||
simctl (~> 1.6.3)
|
simctl (~> 1.6.3)
|
||||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||||
terminal-table (~> 3)
|
terminal-table (~> 3)
|
||||||
@@ -104,8 +109,10 @@ GEM
|
|||||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||||
word_wrap (~> 1.0.0)
|
word_wrap (~> 1.0.0)
|
||||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||||
xcpretty (~> 0.3.0)
|
xcpretty (~> 0.4.0)
|
||||||
xcpretty-travis-formatter (>= 0.0.3)
|
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||||
|
fastlane-sirp (1.0.0)
|
||||||
|
sysrandom (~> 1.0)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
google-apis-androidpublisher_v3 (0.54.0)
|
google-apis-androidpublisher_v3 (0.54.0)
|
||||||
google-apis-core (>= 0.11.0, < 2.a)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
@@ -123,12 +130,12 @@ GEM
|
|||||||
google-apis-core (>= 0.11.0, < 2.a)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-apis-storage_v1 (0.31.0)
|
google-apis-storage_v1 (0.31.0)
|
||||||
google-apis-core (>= 0.11.0, < 2.a)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-cloud-core (1.6.1)
|
google-cloud-core (1.8.0)
|
||||||
google-cloud-env (>= 1.0, < 3.a)
|
google-cloud-env (>= 1.0, < 3.a)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.6.0)
|
google-cloud-env (1.6.0)
|
||||||
faraday (>= 0.17.3, < 3.0)
|
faraday (>= 0.17.3, < 3.0)
|
||||||
google-cloud-errors (1.3.1)
|
google-cloud-errors (1.5.0)
|
||||||
google-cloud-storage (1.47.0)
|
google-cloud-storage (1.47.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
@@ -144,34 +151,38 @@ GEM
|
|||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (>= 0.16, < 2.a)
|
signet (>= 0.16, < 2.a)
|
||||||
highline (2.0.3)
|
highline (2.0.3)
|
||||||
http-cookie (1.0.5)
|
http-cookie (1.0.8)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.9.0)
|
||||||
|
mutex_m
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.7.1)
|
json (2.10.2)
|
||||||
jwt (2.7.1)
|
jwt (2.10.1)
|
||||||
mini_magick (4.12.0)
|
base64
|
||||||
|
mini_magick (4.13.2)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.3.0)
|
multipart-post (2.4.1)
|
||||||
nanaimo (0.3.0)
|
mutex_m (0.3.0)
|
||||||
|
nanaimo (0.4.0)
|
||||||
naturally (2.2.1)
|
naturally (2.2.1)
|
||||||
optparse (0.4.0)
|
nkf (0.2.0)
|
||||||
|
optparse (0.6.0)
|
||||||
os (1.1.4)
|
os (1.1.4)
|
||||||
plist (3.7.1)
|
plist (3.7.2)
|
||||||
public_suffix (5.0.4)
|
public_suffix (6.0.1)
|
||||||
rake (13.1.0)
|
rake (13.2.1)
|
||||||
representable (3.2.0)
|
representable (3.2.0)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rexml (3.2.6)
|
rexml (3.4.1)
|
||||||
rouge (2.0.7)
|
rouge (3.28.0)
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.4.1)
|
||||||
security (0.1.3)
|
security (0.1.5)
|
||||||
signet (0.18.0)
|
signet (0.19.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.5, < 3.a)
|
faraday (>= 0.17.5, < 3.a)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
@@ -179,6 +190,7 @@ GEM
|
|||||||
simctl (1.6.10)
|
simctl (1.6.10)
|
||||||
CFPropertyList
|
CFPropertyList
|
||||||
naturally
|
naturally
|
||||||
|
sysrandom (1.0.5)
|
||||||
terminal-notifier (2.0.0)
|
terminal-notifier (2.0.0)
|
||||||
terminal-table (3.0.2)
|
terminal-table (3.0.2)
|
||||||
unicode-display_width (>= 1.1.1, < 3)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
@@ -188,17 +200,17 @@ GEM
|
|||||||
tty-spinner (0.9.3)
|
tty-spinner (0.9.3)
|
||||||
tty-cursor (~> 0.7)
|
tty-cursor (~> 0.7)
|
||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unicode-display_width (2.5.0)
|
unicode-display_width (2.6.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.24.0)
|
xcodeproj (1.27.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
colored2 (~> 3.1)
|
colored2 (~> 3.1)
|
||||||
nanaimo (~> 0.3.0)
|
nanaimo (~> 0.4.0)
|
||||||
rexml (~> 3.2.4)
|
rexml (>= 3.3.6, < 4.0)
|
||||||
xcpretty (0.3.0)
|
xcpretty (0.4.0)
|
||||||
rouge (~> 2.0.7)
|
rouge (~> 3.28.0)
|
||||||
xcpretty-travis-formatter (1.0.1)
|
xcpretty-travis-formatter (1.0.1)
|
||||||
xcpretty (~> 0.2, >= 0.0.7)
|
xcpretty (~> 0.2, >= 0.0.7)
|
||||||
|
|
||||||
@@ -209,4 +221,4 @@ DEPENDENCIES
|
|||||||
fastlane
|
fastlane
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.3.26
|
2.5.22
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.github.spotbugs.snom.SpotBugsTask
|
|||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("com.github.spotbugs")
|
id("com.github.spotbugs")
|
||||||
|
id("org.jetbrains.kotlin.android")
|
||||||
}
|
}
|
||||||
|
|
||||||
spotbugs {
|
spotbugs {
|
||||||
@@ -15,19 +16,24 @@ spotbugs {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "protect.card_locker"
|
namespace = "protect.card_locker"
|
||||||
compileSdk = 34
|
compileSdk = 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "me.hackerchick.catima"
|
applicationId = "me.hackerchick.catima"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 35
|
||||||
versionCode = 132
|
versionCode = 147
|
||||||
versionName = "2.27.0"
|
versionName = "2.34.5"
|
||||||
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
resourceConfigurations += listOf("ar", "bg", "bn", "bn-rIN", "bs", "cs", "da", "de", "el-rGR", "en", "eo", "es", "es-rAR", "fi", "fr", "he-rIL", "hi", "hr", "hu", "in-rID", "is", "it", "ja", "ko", "lt", "lv", "nb-rNO", "nl", "oc", "pl", "pt-rPT", "ro-rRO", "ru", "sk", "sl", "sv", "tr", "uk", "vi", "zh-rCN", "zh-rTW")
|
resourceConfigurations += listOf("ar", "bg", "bn", "bn-rIN", "bs", "cs", "da", "de", "el-rGR", "en", "eo", "es", "es-rAR", "et", "fi", "fr", "gl", "he-rIL", "hi", "hr", "hu", "in-rID", "is", "it", "ja", "ko", "lt", "lv", "nb-rNO", "nl", "oc", "pl", "pt", "pt-rBR", "pt-rPT", "ro-rRO", "ru", "sk", "sl", "sr", "sv", "ta", "tr", "uk", "vi", "zh-rCN", "zh-rTW")
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
buildConfigField("boolean", "showDonate", "true")
|
||||||
|
buildConfigField("boolean", "showRateOnGooglePlay", "false")
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -48,6 +54,21 @@ android {
|
|||||||
viewBinding = true
|
viewBinding = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flavorDimensions.add("type")
|
||||||
|
productFlavors {
|
||||||
|
create("foss") {
|
||||||
|
dimension = "type"
|
||||||
|
isDefault = true
|
||||||
|
}
|
||||||
|
create("gplay") {
|
||||||
|
dimension = "type"
|
||||||
|
|
||||||
|
// Google doesn't allow donation links
|
||||||
|
buildConfigField("boolean", "showDonate", "false")
|
||||||
|
buildConfigField("boolean", "showRateOnGooglePlay", "true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bundle {
|
bundle {
|
||||||
language {
|
language {
|
||||||
enableSplit = false
|
enableSplit = false
|
||||||
@@ -60,8 +81,8 @@ android {
|
|||||||
// Flag to enable support for the new language APIs
|
// Flag to enable support for the new language APIs
|
||||||
isCoreLibraryDesugaringEnabled = true
|
isCoreLibraryDesugaringEnabled = true
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -82,25 +103,26 @@ android {
|
|||||||
lint {
|
lint {
|
||||||
lintConfig = file("lint.xml")
|
lintConfig = file("lint.xml")
|
||||||
}
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
// AndroidX
|
// AndroidX
|
||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
|
||||||
implementation("androidx.exifinterface:exifinterface:1.3.7")
|
implementation("androidx.core:core-ktx:1.15.0")
|
||||||
|
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||||
|
implementation("androidx.exifinterface:exifinterface:1.4.0")
|
||||||
implementation("androidx.palette:palette:1.0.0")
|
implementation("androidx.palette:palette:1.0.0")
|
||||||
implementation("androidx.preference:preference:1.2.1")
|
implementation("androidx.preference:preference:1.2.1")
|
||||||
implementation("com.google.android.material:material:1.11.0")
|
implementation("com.google.android.material:material:1.12.0")
|
||||||
implementation("com.github.yalantis:ucrop:2.2.8")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
|
|
||||||
|
|
||||||
// Splash Screen
|
|
||||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
|
||||||
|
|
||||||
// Third-party
|
// Third-party
|
||||||
implementation("com.journeyapps:zxing-android-embedded:4.3.0@aar")
|
implementation("com.journeyapps:zxing-android-embedded:4.3.0@aar")
|
||||||
|
implementation("com.github.yalantis:ucrop:2.2.10")
|
||||||
implementation("com.google.zxing:core:3.5.3")
|
implementation("com.google.zxing:core:3.5.3")
|
||||||
implementation("org.apache.commons:commons-csv:1.9.0")
|
implementation("org.apache.commons:commons-csv:1.9.0")
|
||||||
implementation("com.jaredrummler:colorpicker:1.1.0")
|
implementation("com.jaredrummler:colorpicker:1.1.0")
|
||||||
@@ -110,9 +132,18 @@ dependencies {
|
|||||||
implementation("io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0")
|
implementation("io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0")
|
||||||
|
|
||||||
// Testing
|
// Testing
|
||||||
testImplementation("androidx.test:core:1.5.0")
|
val androidXTestVersion = "1.6.1"
|
||||||
testImplementation("junit:junit:4.13.2")
|
val junitVersion = "4.13.2"
|
||||||
testImplementation("org.robolectric:robolectric:4.11.1")
|
testImplementation("androidx.test:core:$androidXTestVersion")
|
||||||
|
testImplementation("junit:junit:$junitVersion")
|
||||||
|
testImplementation("org.robolectric:robolectric:4.14.1")
|
||||||
|
|
||||||
|
androidTestImplementation("androidx.test:core:$androidXTestVersion")
|
||||||
|
androidTestImplementation("junit:junit:$junitVersion")
|
||||||
|
androidTestImplementation("androidx.test.ext:junit:1.2.1")
|
||||||
|
androidTestImplementation("androidx.test:runner:$androidXTestVersion")
|
||||||
|
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0")
|
||||||
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<SpotBugsTask>().configureEach {
|
tasks.withType<SpotBugsTask>().configureEach {
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import static androidx.test.espresso.Espresso.onView;
|
||||||
|
import static androidx.test.espresso.action.ViewActions.click;
|
||||||
|
import static androidx.test.espresso.action.ViewActions.typeText;
|
||||||
|
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.withChild;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.test.core.app.ActivityScenario;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import androidx.test.uiautomator.UiDevice;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class MainActivitySearchViewTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenSearchViewIsExpandedAndBackIsPressedThenMenuItemShouldNotBeCollapsed() {
|
||||||
|
String query = "random arbitrary text";
|
||||||
|
try (ActivityScenario<MainActivity> mainActivityScenario = ActivityScenario.launch(MainActivity.class)) {
|
||||||
|
mainActivityScenario.onActivity(this::makeSearchMenuItemVisible);
|
||||||
|
onView(withId(R.id.action_search)).perform(click());
|
||||||
|
onView(withId(androidx.appcompat.R.id.search_src_text)).perform(typeText(query));
|
||||||
|
|
||||||
|
pressBack();
|
||||||
|
|
||||||
|
onView(withId(androidx.appcompat.R.id.search_src_text)).check(matches(withText(query)));
|
||||||
|
mainActivityScenario.onActivity(activity -> assertEquals(query, activity.mFilter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenSearchViewIsExpandedThenItShouldOnlyBeCollapsedWhenBackIsPressedTwice() {
|
||||||
|
try (ActivityScenario<MainActivity> mainActivityScenario = ActivityScenario.launch(MainActivity.class)) {
|
||||||
|
mainActivityScenario.onActivity(this::makeSearchMenuItemVisible);
|
||||||
|
onView(withId(R.id.action_search)).perform(click());
|
||||||
|
|
||||||
|
pressBack();
|
||||||
|
|
||||||
|
onView(withId(androidx.appcompat.R.id.search_src_text)).check(matches(isDisplayed()));
|
||||||
|
|
||||||
|
pressBack();
|
||||||
|
|
||||||
|
onView(withId(android.R.id.content)).check(matches(is(not(withChild(withId(androidx.appcompat.R.id.search_src_text))))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeSearchMenuItemVisible(MainActivity activity) {
|
||||||
|
Toolbar toolbar = activity.findViewById(R.id.toolbar);
|
||||||
|
toolbar.getMenu().findItem(R.id.action_search).setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pressBack() {
|
||||||
|
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).pressBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources></resources>
|
<resources>
|
||||||
|
<string name="app_name">تصحيح Catima</string>
|
||||||
|
</resources>
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources></resources>
|
<resources>
|
||||||
|
<string name="app_name">Debugar Catima</string>
|
||||||
|
</resources>
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources></resources>
|
<resources>
|
||||||
|
<string name="app_name">Αποσφαλμάτωση Catima</string>
|
||||||
|
</resources>
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources></resources>
|
<resources>
|
||||||
|
<string name="app_name">Catima Debug</string>
|
||||||
|
</resources>
|
||||||
4
app/src/debug/res/values-et/strings.xml
Normal file
4
app/src/debug/res/values-et/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Catima Debug</string>
|
||||||
|
</resources>
|
||||||
4
app/src/debug/res/values-fa/strings.xml
Normal file
4
app/src/debug/res/values-fa/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">عیبیابی کاتیما</string>
|
||||||
|
</resources>
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources></resources>
|
<resources>
|
||||||
|
<string name="app_name">Catima-vianmääritys</string>
|
||||||
|
</resources>
|
||||||
4
app/src/debug/res/values-gl/strings.xml
Normal file
4
app/src/debug/res/values-gl/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Depuración de Catima</string>
|
||||||
|
</resources>
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources></resources>
|
<resources>
|
||||||
|
<string name="app_name">Catimaデバーグ</string>
|
||||||
|
</resources>
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources></resources>
|
<resources>
|
||||||
|
<string name="app_name">Catima atkļūdošana</string>
|
||||||
|
</resources>
|
||||||
4
app/src/debug/res/values-pt-rBR/strings.xml
Normal file
4
app/src/debug/res/values-pt-rBR/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Depuração do Catima</string>
|
||||||
|
</resources>
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources></resources>
|
<resources>
|
||||||
|
<string name="app_name">Catima Debug</string>
|
||||||
|
</resources>
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources></resources>
|
<resources>
|
||||||
|
<string name="app_name">Catima 除錯版</string>
|
||||||
|
</resources>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.camera"
|
android:name="android.hardware.camera"
|
||||||
android:required="true" />
|
android:required="false" />
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.camera.autofocus"
|
android:name="android.hardware.camera.autofocus"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
@@ -40,10 +40,28 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
|
<data android:scheme="content"/>
|
||||||
|
<data android:host="*"/>
|
||||||
<data android:mimeType="image/*" />
|
<data android:mimeType="image/*" />
|
||||||
|
<data android:mimeType="application/pdf" />
|
||||||
|
<data android:mimeType="application/vnd.apple.pkpass" />
|
||||||
|
<data android:mimeType="application/vnd-com.apple.pkpass" />
|
||||||
|
<data android:mimeType="application/vnd.espass-espass" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
<data android:mimeType="image/*" />
|
||||||
|
<data android:mimeType="application/pdf" />
|
||||||
|
<data android:mimeType="application/vnd.apple.pkpass" />
|
||||||
|
<data android:mimeType="application/vnd-com.apple.pkpass" />
|
||||||
|
<data android:mimeType="application/vnd.espass-espass" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
@@ -61,13 +79,12 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".LoyaltyCardViewActivity"
|
android:name=".LoyaltyCardViewActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/AppTheme.NoActionBar"
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
android:windowSoftInputMode="stateHidden" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".LoyaltyCardEditActivity"
|
android:name=".LoyaltyCardEditActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/AppTheme.NoActionBar"
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
android:windowSoftInputMode="stateHidden">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<intent-filter
|
<intent-filter
|
||||||
android:autoVerify="true"
|
android:autoVerify="true"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
@@ -105,8 +122,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".BarcodeSelectorActivity"
|
android:name=".BarcodeSelectorActivity"
|
||||||
android:label="@string/selectBarcodeTitle"
|
android:label="@string/selectBarcodeTitle"
|
||||||
android:theme="@style/AppTheme.NoActionBar"
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
android:windowSoftInputMode="stateHidden" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".preferences.SettingsActivity"
|
android:name=".preferences.SettingsActivity"
|
||||||
android:label="@string/settings"
|
android:label="@string/settings"
|
||||||
@@ -181,7 +197,8 @@
|
|||||||
android:resource="@xml/file_provider_paths" />
|
android:resource="@xml/file_provider_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
<service android:name=".CardsOnPowerScreenService" android:label="@string/app_name"
|
<service android:name=".CardsOnPowerScreenService" android:label="@string/app_name"
|
||||||
android:permission="android.permission.BIND_CONTROLS" android:exported="true">
|
android:permission="android.permission.BIND_CONTROLS" android:exported="true"
|
||||||
|
tools:targetApi="r">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.controls.ControlsProviderService" />
|
<action android:name="android.service.controls.ControlsProviderService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
package protect.card_locker;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.Spanned;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ScrollView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|
||||||
|
|
||||||
import protect.card_locker.databinding.AboutActivityBinding;
|
|
||||||
|
|
||||||
public class AboutActivity extends CatimaAppCompatActivity {
|
|
||||||
|
|
||||||
private static final String TAG = "Catima";
|
|
||||||
|
|
||||||
private AboutActivityBinding binding;
|
|
||||||
private AboutContent content;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
binding = AboutActivityBinding.inflate(getLayoutInflater());
|
|
||||||
content = new AboutContent(this);
|
|
||||||
setTitle(content.getPageTitle());
|
|
||||||
setContentView(binding.getRoot());
|
|
||||||
setSupportActionBar(binding.toolbar);
|
|
||||||
enableToolbarBackButton();
|
|
||||||
|
|
||||||
TextView copyright = binding.creditsSub;
|
|
||||||
copyright.setText(content.getCopyrightShort());
|
|
||||||
TextView versionHistory = binding.versionHistorySub;
|
|
||||||
versionHistory.setText(content.getVersionHistory());
|
|
||||||
|
|
||||||
binding.versionHistory.setTag("https://catima.app/changelog/");
|
|
||||||
binding.translate.setTag("https://hosted.weblate.org/engage/catima/");
|
|
||||||
binding.license.setTag("https://github.com/CatimaLoyalty/Android/blob/main/LICENSE");
|
|
||||||
binding.repo.setTag("https://github.com/CatimaLoyalty/Android/");
|
|
||||||
binding.privacy.setTag("https://catima.app/privacy-policy/");
|
|
||||||
binding.reportError.setTag("https://github.com/CatimaLoyalty/Android/issues");
|
|
||||||
binding.rate.setTag("https://play.google.com/store/apps/details?id=me.hackerchick.catima");
|
|
||||||
binding.donate.setTag("https://catima.app/donate");
|
|
||||||
|
|
||||||
boolean installedFromGooglePlay = Utils.installedFromGooglePlay(this);
|
|
||||||
// Hide Google Play rate button if not on Google Play
|
|
||||||
binding.rate.setVisibility(installedFromGooglePlay ? View.VISIBLE : View.GONE);
|
|
||||||
// Hide donate button on Google Play (Google Play doesn't allow donation links)
|
|
||||||
binding.donate.setVisibility(installedFromGooglePlay ? View.GONE : View.VISIBLE);
|
|
||||||
|
|
||||||
bindClickListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
int id = item.getItemId();
|
|
||||||
if (id == android.R.id.home) {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
content.destroy();
|
|
||||||
clearClickListeners();
|
|
||||||
binding = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void bindClickListeners() {
|
|
||||||
binding.versionHistory.setOnClickListener(this::showHistory);
|
|
||||||
binding.translate.setOnClickListener(this::openExternalBrowser);
|
|
||||||
binding.license.setOnClickListener(this::showLicense);
|
|
||||||
binding.repo.setOnClickListener(this::openExternalBrowser);
|
|
||||||
binding.privacy.setOnClickListener(this::showPrivacy);
|
|
||||||
binding.reportError.setOnClickListener(this::openExternalBrowser);
|
|
||||||
binding.rate.setOnClickListener(this::openExternalBrowser);
|
|
||||||
binding.donate.setOnClickListener(this::openExternalBrowser);
|
|
||||||
|
|
||||||
binding.credits.setOnClickListener(view -> showCredits());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearClickListeners() {
|
|
||||||
binding.versionHistory.setOnClickListener(null);
|
|
||||||
binding.translate.setOnClickListener(null);
|
|
||||||
binding.license.setOnClickListener(null);
|
|
||||||
binding.repo.setOnClickListener(null);
|
|
||||||
binding.privacy.setOnClickListener(null);
|
|
||||||
binding.reportError.setOnClickListener(null);
|
|
||||||
binding.rate.setOnClickListener(null);
|
|
||||||
binding.donate.setOnClickListener(null);
|
|
||||||
|
|
||||||
binding.credits.setOnClickListener(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showCredits() {
|
|
||||||
showHTML(R.string.credits, content.getContributorInfo(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showHistory(View view) {
|
|
||||||
showHTML(R.string.version_history, content.getHistoryInfo(), view);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showLicense(View view) {
|
|
||||||
showHTML(R.string.license, content.getLicenseInfo(), view);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showPrivacy(View view) {
|
|
||||||
showHTML(R.string.privacy_policy, content.getPrivacyInfo(), view);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showHTML(@StringRes int title, final Spanned text, @Nullable View view) {
|
|
||||||
int dialogContentPadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
|
|
||||||
TextView textView = new TextView(this);
|
|
||||||
textView.setText(text);
|
|
||||||
Utils.makeTextViewLinksClickable(textView, text);
|
|
||||||
ScrollView scrollView = new ScrollView(this);
|
|
||||||
scrollView.addView(textView);
|
|
||||||
scrollView.setPadding(dialogContentPadding, dialogContentPadding / 2, dialogContentPadding, 0);
|
|
||||||
|
|
||||||
// Create dialog
|
|
||||||
MaterialAlertDialogBuilder materialAlertDialogBuilder = new MaterialAlertDialogBuilder(this);
|
|
||||||
materialAlertDialogBuilder
|
|
||||||
.setTitle(title)
|
|
||||||
.setView(scrollView)
|
|
||||||
.setPositiveButton(R.string.ok, null);
|
|
||||||
|
|
||||||
// Add View online button if an URL is linked to this view
|
|
||||||
if (view != null && view.getTag() != null) {
|
|
||||||
materialAlertDialogBuilder.setNeutralButton(R.string.view_online, (dialog, which) -> openExternalBrowser(view));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show dialog
|
|
||||||
materialAlertDialogBuilder.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void openExternalBrowser(View view) {
|
|
||||||
Object tag = view.getTag();
|
|
||||||
if (tag instanceof String && ((String) tag).startsWith("https://")) {
|
|
||||||
(new OpenWebLinkHandler()).openBrowser(this, (String) tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
149
app/src/main/java/protect/card_locker/AboutActivity.kt
Normal file
149
app/src/main/java/protect/card_locker/AboutActivity.kt
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package protect.card_locker
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ScrollView
|
||||||
|
import android.widget.TextView
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
||||||
|
import protect.card_locker.databinding.AboutActivityBinding
|
||||||
|
|
||||||
|
class AboutActivity : CatimaAppCompatActivity() {
|
||||||
|
private companion object {
|
||||||
|
private const val TAG = "Catima"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: AboutActivityBinding
|
||||||
|
private lateinit var content: AboutContent
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = AboutActivityBinding.inflate(layoutInflater)
|
||||||
|
content = AboutContent(this)
|
||||||
|
title = content.pageTitle
|
||||||
|
setContentView(binding.root)
|
||||||
|
setSupportActionBar(binding.toolbar)
|
||||||
|
enableToolbarBackButton()
|
||||||
|
|
||||||
|
binding.apply {
|
||||||
|
creditsSub.text = content.copyrightShort
|
||||||
|
versionHistorySub.text = content.versionHistory
|
||||||
|
|
||||||
|
versionHistory.tag = "https://catima.app/changelog/"
|
||||||
|
translate.tag = "https://hosted.weblate.org/engage/catima/"
|
||||||
|
license.tag = "https://github.com/CatimaLoyalty/Android/blob/main/LICENSE"
|
||||||
|
repo.tag = "https://github.com/CatimaLoyalty/Android/"
|
||||||
|
privacy.tag = "https://catima.app/privacy-policy/"
|
||||||
|
reportError.tag = "https://github.com/CatimaLoyalty/Android/issues"
|
||||||
|
rate.tag = "https://play.google.com/store/apps/details?id=me.hackerchick.catima"
|
||||||
|
donate.tag = "https://catima.app/donate"
|
||||||
|
|
||||||
|
// Hide Google Play rate button if not on Google Play
|
||||||
|
rate.isVisible = BuildConfig.showRateOnGooglePlay
|
||||||
|
// Hide donate button on Google Play (Google Play doesn't allow donation links)
|
||||||
|
donate.isVisible = BuildConfig.showDonate
|
||||||
|
}
|
||||||
|
|
||||||
|
bindClickListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
return when (item.itemId) {
|
||||||
|
android.R.id.home -> {
|
||||||
|
finish()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
content.destroy()
|
||||||
|
clearClickListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindClickListeners() {
|
||||||
|
binding.apply {
|
||||||
|
versionHistory.setOnClickListener { showHistory(it) }
|
||||||
|
translate.setOnClickListener { openExternalBrowser(it) }
|
||||||
|
license.setOnClickListener { showLicense(it) }
|
||||||
|
repo.setOnClickListener { openExternalBrowser(it) }
|
||||||
|
privacy.setOnClickListener { showPrivacy(it) }
|
||||||
|
reportError.setOnClickListener { openExternalBrowser(it) }
|
||||||
|
rate.setOnClickListener { openExternalBrowser(it) }
|
||||||
|
donate.setOnClickListener { openExternalBrowser(it) }
|
||||||
|
credits.setOnClickListener { showCredits() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearClickListeners() {
|
||||||
|
binding.apply {
|
||||||
|
versionHistory.setOnClickListener(null)
|
||||||
|
translate.setOnClickListener(null)
|
||||||
|
license.setOnClickListener(null)
|
||||||
|
repo.setOnClickListener(null)
|
||||||
|
privacy.setOnClickListener(null)
|
||||||
|
reportError.setOnClickListener(null)
|
||||||
|
rate.setOnClickListener(null)
|
||||||
|
donate.setOnClickListener(null)
|
||||||
|
credits.setOnClickListener(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showCredits() {
|
||||||
|
showHTML(R.string.credits, content.contributorInfo, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showHistory(view: View) {
|
||||||
|
showHTML(R.string.version_history, content.historyInfo, view)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showLicense(view: View) {
|
||||||
|
showHTML(R.string.license, content.licenseInfo, view)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showPrivacy(view: View) {
|
||||||
|
showHTML(R.string.privacy_policy, content.privacyInfo, view)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showHTML(@StringRes title: Int, text: Spanned, view: View?) {
|
||||||
|
val dialogContentPadding = resources.getDimensionPixelSize(R.dimen.alert_dialog_content_padding)
|
||||||
|
val textView = TextView(this).apply {
|
||||||
|
setText(text)
|
||||||
|
Utils.makeTextViewLinksClickable(this, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
val scrollView = ScrollView(this).apply {
|
||||||
|
addView(textView)
|
||||||
|
setPadding(dialogContentPadding, dialogContentPadding / 2, dialogContentPadding, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder(this).apply {
|
||||||
|
setTitle(title)
|
||||||
|
setView(scrollView)
|
||||||
|
setPositiveButton(R.string.ok, null)
|
||||||
|
|
||||||
|
// Add View online button if an URL is linked to this view
|
||||||
|
view?.tag?.let {
|
||||||
|
setNeutralButton(R.string.view_online) { _, _ -> openExternalBrowser(view) }
|
||||||
|
}
|
||||||
|
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openExternalBrowser(view: View) {
|
||||||
|
val tag = view.tag
|
||||||
|
if (tag is String && tag.startsWith("https://")) {
|
||||||
|
OpenWebLinkHandler().openBrowser(this, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,6 +45,7 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements
|
|||||||
binding = BarcodeSelectorActivityBinding.inflate(getLayoutInflater());
|
binding = BarcodeSelectorActivityBinding.inflate(getLayoutInflater());
|
||||||
setTitle(R.string.selectBarcodeTitle);
|
setTitle(R.string.selectBarcodeTitle);
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
Utils.applyWindowInsets(binding.getRoot());
|
||||||
Toolbar toolbar = binding.toolbar;
|
Toolbar toolbar = binding.toolbar;
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
enableToolbarBackButton();
|
enableToolbarBackButton();
|
||||||
@@ -71,7 +72,7 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements
|
|||||||
});
|
});
|
||||||
|
|
||||||
final Bundle b = getIntent().getExtras();
|
final Bundle b = getIntent().getExtras();
|
||||||
final String initialCardId = b != null ? b.getString("initialCardId") : null;
|
final String initialCardId = b != null ? b.getString(LoyaltyCard.BUNDLE_LOYALTY_CARD_CARD_ID) : null;
|
||||||
|
|
||||||
if (initialCardId != null) {
|
if (initialCardId != null) {
|
||||||
cardId.setText(initialCardId);
|
cardId.setText(initialCardId);
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
package protect.card_locker;
|
|
||||||
|
|
||||||
public class BarcodeValues {
|
|
||||||
private final String mFormat;
|
|
||||||
private final String mContent;
|
|
||||||
|
|
||||||
public BarcodeValues(String format, String content) {
|
|
||||||
mFormat = format;
|
|
||||||
mContent = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String format() {
|
|
||||||
return mFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String content() {
|
|
||||||
return mContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return mFormat == null && mContent == null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,15 +12,15 @@ import androidx.appcompat.widget.Toolbar;
|
|||||||
import androidx.core.content.pm.ShortcutInfoCompat;
|
import androidx.core.content.pm.ShortcutInfoCompat;
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import protect.card_locker.databinding.SimpleToolbarListActivityBinding;
|
import protect.card_locker.databinding.CardShortcutConfigureActivityBinding;
|
||||||
|
import protect.card_locker.preferences.Settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The configuration screen for creating a shortcut.
|
* The configuration screen for creating a shortcut.
|
||||||
*/
|
*/
|
||||||
public class CardShortcutConfigure extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
|
public class CardShortcutConfigure extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
|
||||||
private SimpleToolbarListActivityBinding binding;
|
private CardShortcutConfigureActivityBinding binding;
|
||||||
static final String TAG = "Catima";
|
static final String TAG = "Catima";
|
||||||
private SQLiteDatabase mDatabase;
|
private SQLiteDatabase mDatabase;
|
||||||
private LoyaltyCardCursorAdapter mAdapter;
|
private LoyaltyCardCursorAdapter mAdapter;
|
||||||
@@ -28,7 +28,7 @@ public class CardShortcutConfigure extends CatimaAppCompatActivity implements Lo
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
binding = SimpleToolbarListActivityBinding.inflate(getLayoutInflater());
|
binding = CardShortcutConfigureActivityBinding.inflate(getLayoutInflater());
|
||||||
mDatabase = new DBHelper(this).getReadableDatabase();
|
mDatabase = new DBHelper(this).getReadableDatabase();
|
||||||
|
|
||||||
// Set the result to CANCELED. This will cause nothing to happen if the
|
// Set the result to CANCELED. This will cause nothing to happen if the
|
||||||
@@ -36,6 +36,7 @@ public class CardShortcutConfigure extends CatimaAppCompatActivity implements Lo
|
|||||||
setResult(RESULT_CANCELED);
|
setResult(RESULT_CANCELED);
|
||||||
|
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
Utils.applyWindowInsets(binding.getRoot());
|
||||||
Toolbar toolbar = binding.toolbar;
|
Toolbar toolbar = binding.toolbar;
|
||||||
toolbar.setTitle(R.string.shortcutSelectCard);
|
toolbar.setTitle(R.string.shortcutSelectCard);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
@@ -47,21 +48,26 @@ public class CardShortcutConfigure extends CatimaAppCompatActivity implements Lo
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
final RecyclerView cardList = binding.list;
|
|
||||||
GridLayoutManager layoutManager = (GridLayoutManager) cardList.getLayoutManager();
|
|
||||||
if (layoutManager != null) {
|
|
||||||
layoutManager.setSpanCount(getResources().getInteger(R.integer.main_view_card_columns));
|
|
||||||
}
|
|
||||||
|
|
||||||
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.All);
|
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.All);
|
||||||
mAdapter = new LoyaltyCardCursorAdapter(this, cardCursor, this, null);
|
mAdapter = new LoyaltyCardCursorAdapter(this, cardCursor, this, null);
|
||||||
cardList.setAdapter(mAdapter);
|
binding.list.setAdapter(mAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
var layoutManager = (GridLayoutManager) binding.list.getLayoutManager();
|
||||||
|
if (layoutManager != null) {
|
||||||
|
var settings = new Settings(this);
|
||||||
|
layoutManager.setSpanCount(settings.getPreferredColumnCount());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onClickAction(int position) {
|
private void onClickAction(int position) {
|
||||||
Cursor selected = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.All);
|
Cursor selected = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.All);
|
||||||
selected.moveToPosition(position);
|
selected.moveToPosition(position);
|
||||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
|
LoyaltyCard loyaltyCard = LoyaltyCard.fromCursor(CardShortcutConfigure.this, selected);
|
||||||
|
|
||||||
Log.d(TAG, "Creating shortcut for card " + loyaltyCard.store + "," + loyaltyCard.id);
|
Log.d(TAG, "Creating shortcut for card " + loyaltyCard.store + "," + loyaltyCard.id);
|
||||||
|
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
|
|||||||
Cursor loyaltyCardCursor = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.Unarchived);
|
Cursor loyaltyCardCursor = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.Unarchived);
|
||||||
return subscriber -> {
|
return subscriber -> {
|
||||||
while (loyaltyCardCursor.moveToNext()) {
|
while (loyaltyCardCursor.moveToNext()) {
|
||||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(loyaltyCardCursor);
|
LoyaltyCard card = LoyaltyCard.fromCursor(this, loyaltyCardCursor);
|
||||||
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
|
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
.putExtra("id", card.id);
|
.putExtra(LoyaltyCardViewActivity.BUNDLE_ID, card.id);
|
||||||
PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), card.id, openIntent, PendingIntent.FLAG_IMMUTABLE);
|
PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), card.id, openIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
subscriber.onNext(
|
subscriber.onNext(
|
||||||
new Control.StatelessBuilder(PREFIX + card.id, pendingIntent)
|
new Control.StatelessBuilder(PREFIX + card.id, pendingIntent)
|
||||||
@@ -69,11 +69,11 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
|
|||||||
for (String controlId : controlIds) {
|
for (String controlId : controlIds) {
|
||||||
Control control;
|
Control control;
|
||||||
Integer cardId = this.controlIdToCardId(controlId);
|
Integer cardId = this.controlIdToCardId(controlId);
|
||||||
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, cardId);
|
LoyaltyCard card = DBHelper.getLoyaltyCard(this, mDatabase, cardId);
|
||||||
if (card != null) {
|
if (card != null) {
|
||||||
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
|
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
.putExtra("id", card.id);
|
.putExtra(LoyaltyCardViewActivity.BUNDLE_ID, card.id);
|
||||||
PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), card.id, openIntent, PendingIntent.FLAG_IMMUTABLE);
|
PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), card.id, openIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
control = new Control.StatefulBuilder(controlId, pendingIntent)
|
control = new Control.StatefulBuilder(controlId, pendingIntent)
|
||||||
.setTitle(card.store)
|
.setTitle(card.store)
|
||||||
@@ -99,7 +99,7 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap getIcon(Context context, LoyaltyCard loyaltyCard) {
|
private Bitmap getIcon(Context context, LoyaltyCard loyaltyCard) {
|
||||||
Bitmap cardIcon = Utils.retrieveCardImage(context, loyaltyCard.id, ImageLocationType.icon);
|
Bitmap cardIcon = loyaltyCard.getImageThumbnail(context);
|
||||||
|
|
||||||
if (cardIcon != null) {
|
if (cardIcon != null) {
|
||||||
return cardIcon;
|
return cardIcon;
|
||||||
@@ -129,7 +129,7 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
|
|||||||
consumer.accept(ControlAction.RESPONSE_OK);
|
consumer.accept(ControlAction.RESPONSE_OK);
|
||||||
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
|
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
.putExtra("id", controlIdToCardId(controlId));
|
.putExtra(LoyaltyCardViewActivity.BUNDLE_ID, controlIdToCardId(controlId));
|
||||||
startActivity(openIntent);
|
startActivity(openIntent);
|
||||||
|
|
||||||
closePowerScreenOnAndroid11();
|
closePowerScreenOnAndroid11();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.os.Bundle;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
|
|
||||||
|
import androidx.activity.EdgeToEdge;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
@@ -24,6 +25,7 @@ public class CatimaAppCompatActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
EdgeToEdge.enable(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Utils.patchColors(this);
|
Utils.patchColors(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -45,15 +47,15 @@ public class CatimaBarcode {
|
|||||||
mBarcodeFormat = barcodeFormat;
|
mBarcodeFormat = barcodeFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CatimaBarcode fromBarcode(BarcodeFormat barcodeFormat) {
|
public static CatimaBarcode fromBarcode(@NonNull BarcodeFormat barcodeFormat) {
|
||||||
return new CatimaBarcode(barcodeFormat);
|
return new CatimaBarcode(barcodeFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CatimaBarcode fromName(String name) {
|
public static CatimaBarcode fromName(@NonNull String name) {
|
||||||
return new CatimaBarcode(BarcodeFormat.valueOf(name));
|
return new CatimaBarcode(BarcodeFormat.valueOf(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CatimaBarcode fromPrettyName(String prettyName) {
|
public static CatimaBarcode fromPrettyName(@NonNull String prettyName) {
|
||||||
try {
|
try {
|
||||||
return new CatimaBarcode(barcodeFormats.get(barcodePrettyNames.indexOf(prettyName)));
|
return new CatimaBarcode(barcodeFormats.get(barcodePrettyNames.indexOf(prettyName)));
|
||||||
} catch (IndexOutOfBoundsException e) {
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
|||||||
@@ -4,22 +4,24 @@ import android.app.Activity;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.core.util.Consumer;
|
||||||
|
|
||||||
import com.journeyapps.barcodescanner.CaptureManager;
|
import com.journeyapps.barcodescanner.CaptureManager;
|
||||||
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
||||||
|
|
||||||
public class CatimaCaptureManager extends CaptureManager {
|
public class CatimaCaptureManager extends CaptureManager {
|
||||||
private final Context mContext;
|
private final Consumer<String> mErrorCallback;
|
||||||
|
|
||||||
public CatimaCaptureManager(Activity activity, DecoratedBarcodeView barcodeView) {
|
public CatimaCaptureManager(Activity activity, DecoratedBarcodeView barcodeView, Consumer<String> errorCallback) {
|
||||||
super(activity, barcodeView);
|
super(activity, barcodeView);
|
||||||
|
|
||||||
mContext = activity.getApplicationContext();
|
mErrorCallback = errorCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void displayFrameworkBugMessageAndExit(String message) {
|
protected void displayFrameworkBugMessageAndExit(String message) {
|
||||||
// We don't want to exit, as we also have a enter from card image and add manually button here
|
// We don't want to exit, as we also have a enter from card image and add manually button here
|
||||||
// So we show a toast instead
|
// So, instead, we call our error callback
|
||||||
Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
|
mErrorCallback.accept(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ public class DBHelper extends SQLiteOpenHelper {
|
|||||||
public enum LoyaltyCardOrder {
|
public enum LoyaltyCardOrder {
|
||||||
Alpha,
|
Alpha,
|
||||||
LastUsed,
|
LastUsed,
|
||||||
|
ValidFrom,
|
||||||
Expiry
|
Expiry
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,10 +333,10 @@ public class DBHelper extends SQLiteOpenHelper {
|
|||||||
Set<String> files = new HashSet<>();
|
Set<String> files = new HashSet<>();
|
||||||
Cursor cardCursor = getLoyaltyCardCursor(database);
|
Cursor cardCursor = getLoyaltyCardCursor(database);
|
||||||
while (cardCursor.moveToNext()) {
|
while (cardCursor.moveToNext()) {
|
||||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
|
LoyaltyCard card = LoyaltyCard.fromCursor(context, cardCursor);
|
||||||
for (ImageLocationType imageLocationType : ImageLocationType.values()) {
|
for (ImageLocationType imageLocationType : ImageLocationType.values()) {
|
||||||
String name = Utils.getCardImageFileName(card.id, imageLocationType);
|
String name = Utils.getCardImageFileName(card.id, imageLocationType);
|
||||||
if (Utils.retrieveCardImageAsFile(context, name).exists()) {
|
if (card.getImageForImageLocationType(context, imageLocationType) != null) {
|
||||||
files.add(name);
|
files.add(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -535,14 +536,14 @@ public class DBHelper extends SQLiteOpenHelper {
|
|||||||
return (rowsUpdated == 1);
|
return (rowsUpdated == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoyaltyCard getLoyaltyCard(SQLiteDatabase database, final int id) {
|
public static LoyaltyCard getLoyaltyCard(Context context, SQLiteDatabase database, final int id) {
|
||||||
Cursor data = database.query(LoyaltyCardDbIds.TABLE, null, whereAttrs(LoyaltyCardDbIds.ID), withArgs(id), null, null, null);
|
Cursor data = database.query(LoyaltyCardDbIds.TABLE, null, whereAttrs(LoyaltyCardDbIds.ID), withArgs(id), null, null, null);
|
||||||
|
|
||||||
LoyaltyCard card = null;
|
LoyaltyCard card = null;
|
||||||
|
|
||||||
if (data.getCount() == 1) {
|
if (data.getCount() == 1) {
|
||||||
data.moveToFirst();
|
data.moveToFirst();
|
||||||
card = LoyaltyCard.toLoyaltyCard(data);
|
card = LoyaltyCard.fromCursor(context, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
data.close();
|
data.close();
|
||||||
@@ -916,6 +917,10 @@ public class DBHelper extends SQLiteOpenHelper {
|
|||||||
return LoyaltyCardDbIds.LAST_USED;
|
return LoyaltyCardDbIds.LAST_USED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (order == LoyaltyCardOrder.ValidFrom) {
|
||||||
|
return LoyaltyCardDbIds.VALID_FROM;
|
||||||
|
}
|
||||||
|
|
||||||
if (order == LoyaltyCardOrder.Expiry) {
|
if (order == LoyaltyCardOrder.Expiry) {
|
||||||
return LoyaltyCardDbIds.EXPIRY;
|
return LoyaltyCardDbIds.EXPIRY;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import android.text.InputType;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowManager;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
@@ -21,6 +23,7 @@ import androidx.appcompat.app.AlertDialog;
|
|||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -57,6 +60,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
|||||||
binding = ImportExportActivityBinding.inflate(getLayoutInflater());
|
binding = ImportExportActivityBinding.inflate(getLayoutInflater());
|
||||||
setTitle(R.string.importExport);
|
setTitle(R.string.importExport);
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
Utils.applyWindowInsets(binding.getRoot());
|
||||||
Toolbar toolbar = binding.toolbar;
|
Toolbar toolbar = binding.toolbar;
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
enableToolbarBackButton();
|
enableToolbarBackButton();
|
||||||
@@ -78,15 +82,21 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
|||||||
Log.e(TAG, "Activity returned NULL uri");
|
Log.e(TAG, "Activity returned NULL uri");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
// Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files
|
||||||
OutputStream writer = getContentResolver().openOutputStream(uri);
|
// FIXME: This is still suboptimal, because showing that the export started is delayed until the network request finishes
|
||||||
Log.e(TAG, "Starting file export with: " + result.toString());
|
new Thread() {
|
||||||
startExport(writer, uri, exportPassword.toCharArray(), true);
|
@Override
|
||||||
} catch (IOException e) {
|
public void run() {
|
||||||
Log.e(TAG, "Failed to export file: " + result.toString(), e);
|
try {
|
||||||
onExportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, result.toString()), uri);
|
OutputStream writer = getContentResolver().openOutputStream(uri);
|
||||||
}
|
Log.d(TAG, "Starting file export with: " + result);
|
||||||
|
startExport(writer, uri, exportPassword.toCharArray(), true);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to export file: " + result, e);
|
||||||
|
onExportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, result.toString()), uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
});
|
});
|
||||||
fileOpenLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), result -> {
|
fileOpenLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), result -> {
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
@@ -121,16 +131,19 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
|||||||
builder.setTitle(R.string.exportPassword);
|
builder.setTitle(R.string.exportPassword);
|
||||||
|
|
||||||
FrameLayout container = new FrameLayout(ImportExportActivity.this);
|
FrameLayout container = new FrameLayout(ImportExportActivity.this);
|
||||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
|
||||||
params.leftMargin = 50;
|
final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this);
|
||||||
params.rightMargin = 50;
|
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE);
|
||||||
|
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
params.setMargins(50, 10, 50, 0);
|
||||||
|
textInputLayout.setLayoutParams(params);
|
||||||
|
|
||||||
final EditText input = new EditText(ImportExportActivity.this);
|
final EditText input = new EditText(ImportExportActivity.this);
|
||||||
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
input.setLayoutParams(params);
|
|
||||||
input.setHint(R.string.exportPasswordHint);
|
input.setHint(R.string.exportPasswordHint);
|
||||||
|
|
||||||
container.addView(input);
|
textInputLayout.addView(input);
|
||||||
|
container.addView(textInputLayout);
|
||||||
builder.setView(container);
|
builder.setView(container);
|
||||||
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
||||||
exportPassword = input.getText().toString();
|
exportPassword = input.getText().toString();
|
||||||
@@ -143,7 +156,6 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
|||||||
});
|
});
|
||||||
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
|
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
|
||||||
builder.show();
|
builder.show();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check that there is a file manager available
|
// Check that there is a file manager available
|
||||||
@@ -153,17 +165,28 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
|||||||
// Check that there is an app that data can be imported from
|
// Check that there is an app that data can be imported from
|
||||||
Button importApplication = binding.importOptionApplicationButton;
|
Button importApplication = binding.importOptionApplicationButton;
|
||||||
importApplication.setOnClickListener(v -> chooseImportType(true, null));
|
importApplication.setOnClickListener(v -> chooseImportType(true, null));
|
||||||
|
|
||||||
|
// FIXME: The importer/exporter is currently quite broken
|
||||||
|
// To prevent the screen from turning off during import/export and some devices killing Catima as it's no longer foregrounded, force the screen to stay on here
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openFileForImport(Uri uri, char[] password) {
|
private void openFileForImport(Uri uri, char[] password) {
|
||||||
try {
|
// Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files
|
||||||
InputStream reader = getContentResolver().openInputStream(uri);
|
// FIXME: This is still suboptimal, because showing that the import started is delayed until the network request finishes
|
||||||
Log.e(TAG, "Starting file import with: " + uri.toString());
|
new Thread() {
|
||||||
startImport(reader, uri, importDataFormat, password, true);
|
@Override
|
||||||
} catch (IOException e) {
|
public void run() {
|
||||||
Log.e(TAG, "Failed to import file: " + uri.toString(), e);
|
try {
|
||||||
onImportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, e.toString()), uri, importDataFormat);
|
InputStream reader = getContentResolver().openInputStream(uri);
|
||||||
}
|
Log.d(TAG, "Starting file import with: " + uri);
|
||||||
|
startImport(reader, uri, importDataFormat, password, true);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to import file: " + uri, e);
|
||||||
|
onImportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, e.toString()), uri, importDataFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void chooseImportType(boolean choosePicker,
|
private void chooseImportType(boolean choosePicker,
|
||||||
@@ -315,9 +338,21 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
|||||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||||
builder.setTitle(R.string.passwordRequired);
|
builder.setTitle(R.string.passwordRequired);
|
||||||
|
|
||||||
final EditText input = new EditText(this);
|
FrameLayout container = new FrameLayout(ImportExportActivity.this);
|
||||||
|
|
||||||
|
final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this);
|
||||||
|
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE);
|
||||||
|
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
params.setMargins(50, 10, 50, 0);
|
||||||
|
textInputLayout.setLayoutParams(params);
|
||||||
|
|
||||||
|
final EditText input = new EditText(ImportExportActivity.this);
|
||||||
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
builder.setView(input);
|
input.setHint(R.string.exportPasswordHint);
|
||||||
|
|
||||||
|
textInputLayout.addView(input);
|
||||||
|
container.addView(textInputLayout);
|
||||||
|
builder.setView(container);
|
||||||
|
|
||||||
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
||||||
openFileForImport(uri, input.getText().toString().toCharArray());
|
openFileForImport(uri, input.getText().toString().toCharArray());
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.content.Context;
|
|||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -90,16 +91,16 @@ public class ImportExportTask implements CompatCallable<ImportExportResult> {
|
|||||||
progress = new ProgressDialog(activity);
|
progress = new ProgressDialog(activity);
|
||||||
progress.setTitle(doImport ? R.string.importing : R.string.exporting);
|
progress.setTitle(doImport ? R.string.importing : R.string.exporting);
|
||||||
|
|
||||||
progress.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
progress.setOnCancelListener(dialog -> cancel());
|
||||||
@Override
|
progress.setOnDismissListener(dialog -> cancel());
|
||||||
public void onDismiss(DialogInterface dialog) {
|
|
||||||
ImportExportTask.this.stop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
progress.show();
|
progress.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void cancel() {
|
||||||
|
ImportExportTask.this.stop();
|
||||||
|
}
|
||||||
|
|
||||||
protected ImportExportResult doInBackground(Void... nothing) {
|
protected ImportExportResult doInBackground(Void... nothing) {
|
||||||
final SQLiteDatabase database = new DBHelper(activity).getWritableDatabase();
|
final SQLiteDatabase database = new DBHelper(activity).getWritableDatabase();
|
||||||
ImportExportResult result;
|
ImportExportResult result;
|
||||||
|
|||||||
@@ -125,7 +125,29 @@ public class ImportURIHelper {
|
|||||||
headerColor = Integer.parseInt(unparsedHeaderColor);
|
headerColor = Integer.parseInt(unparsedHeaderColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new LoyaltyCard(-1, store, note, validFrom, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0, Utils.getUnixTime(), 100, 0);
|
return new LoyaltyCard(
|
||||||
|
-1,
|
||||||
|
store,
|
||||||
|
note,
|
||||||
|
validFrom,
|
||||||
|
expiry,
|
||||||
|
balance,
|
||||||
|
balanceType,
|
||||||
|
cardId,
|
||||||
|
barcodeId,
|
||||||
|
barcodeType,
|
||||||
|
headerColor,
|
||||||
|
0,
|
||||||
|
Utils.getUnixTime(),
|
||||||
|
100,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
} catch (NumberFormatException | UnsupportedEncodingException | ArrayIndexOutOfBoundsException ex) {
|
} catch (NumberFormatException | UnsupportedEncodingException | ArrayIndexOutOfBoundsException ex) {
|
||||||
throw new InvalidObjectException("Not a valid import URI");
|
throw new InvalidObjectException("Not a valid import URI");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Parcel;
|
import android.graphics.Bitmap;
|
||||||
import android.os.Parcelable;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -10,141 +11,551 @@ import androidx.annotation.Nullable;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Currency;
|
import java.util.Currency;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class LoyaltyCard implements Parcelable {
|
public class LoyaltyCard {
|
||||||
public final int id;
|
public int id;
|
||||||
public final String store;
|
public String store;
|
||||||
public final String note;
|
public String note;
|
||||||
@Nullable
|
@Nullable
|
||||||
public final Date validFrom;
|
public Date validFrom;
|
||||||
@Nullable
|
@Nullable
|
||||||
public final Date expiry;
|
public Date expiry;
|
||||||
public final BigDecimal balance;
|
public BigDecimal balance;
|
||||||
@Nullable
|
@Nullable
|
||||||
public final Currency balanceType;
|
public Currency balanceType;
|
||||||
public final String cardId;
|
public String cardId;
|
||||||
@Nullable
|
@Nullable
|
||||||
public final String barcodeId;
|
public String barcodeId;
|
||||||
@Nullable
|
@Nullable
|
||||||
public final CatimaBarcode barcodeType;
|
public CatimaBarcode barcodeType;
|
||||||
@Nullable
|
@Nullable
|
||||||
public final Integer headerColor;
|
public Integer headerColor;
|
||||||
public final int starStatus;
|
public int starStatus;
|
||||||
public final int archiveStatus;
|
public long lastUsed;
|
||||||
public final long lastUsed;
|
|
||||||
public int zoomLevel;
|
public int zoomLevel;
|
||||||
|
public int archiveStatus;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Bitmap imageThumbnail;
|
||||||
|
@Nullable
|
||||||
|
private String imageThumbnailPath;
|
||||||
|
@Nullable
|
||||||
|
private Bitmap imageFront;
|
||||||
|
@Nullable
|
||||||
|
private String imageFrontPath;
|
||||||
|
@Nullable
|
||||||
|
private Bitmap imageBack;
|
||||||
|
@Nullable
|
||||||
|
private String imageBackPath;
|
||||||
|
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_ID = "loyaltyCardId";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_STORE = "loyaltyCardStore";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_NOTE = "loyaltyCardNote";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_VALID_FROM = "loyaltyCardValidFrom";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_EXPIRY = "loyaltyCardExpiry";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_BALANCE = "loyaltyCardBalance";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_BALANCE_TYPE = "loyaltyCardBalanceType";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_CARD_ID = "loyaltyCardCardId";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_BARCODE_ID = "loyaltyCardBarcodeId";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_BARCODE_TYPE = "loyaltyCardBarcodeType";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_HEADER_COLOR = "loyaltyCardHeaderColor";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_STAR_STATUS = "loyaltyCardStarStatus";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_LAST_USED = "loyaltyCardLastUsed";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_ZOOM_LEVEL = "loyaltyCardZoomLevel";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_ARCHIVE_STATUS = "loyaltyCardArchiveStatus";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_IMAGE_THUMBNAIL = "loyaltyCardImageThumbnail";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_IMAGE_FRONT = "loyaltyCardImageFront";
|
||||||
|
public static final String BUNDLE_LOYALTY_CARD_IMAGE_BACK = "loyaltyCardImageBack";
|
||||||
|
|
||||||
|
private static final String TEMP_IMAGE_THUMBNAIL_FILE_NAME = "loyaltyCardTempImageThumbnailFileName";
|
||||||
|
private static final String TEMP_IMAGE_FRONT_FILE_NAME = "loyaltyCardTempImageFrontFileName";
|
||||||
|
private static final String TEMP_IMAGE_BACK_FILE_NAME = "loyaltyCardTempImageBackFileName";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a loyalty card object with default values
|
||||||
|
*/
|
||||||
|
public LoyaltyCard() {
|
||||||
|
setId(-1);
|
||||||
|
setStore("");
|
||||||
|
setNote("");
|
||||||
|
setValidFrom(null);
|
||||||
|
setExpiry(null);
|
||||||
|
setBalance(new BigDecimal("0"));
|
||||||
|
setBalanceType(null);
|
||||||
|
setCardId("");
|
||||||
|
setBarcodeId(null);
|
||||||
|
setBarcodeType(null);
|
||||||
|
setHeaderColor(null);
|
||||||
|
setStarStatus(0);
|
||||||
|
setLastUsed(Utils.getUnixTime());
|
||||||
|
setZoomLevel(100);
|
||||||
|
setArchiveStatus(0);
|
||||||
|
setImageThumbnail(null, null);
|
||||||
|
setImageFront(null, null);
|
||||||
|
setImageBack(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new loyalty card
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* @param store
|
||||||
|
* @param note
|
||||||
|
* @param validFrom
|
||||||
|
* @param expiry
|
||||||
|
* @param balance
|
||||||
|
* @param balanceType
|
||||||
|
* @param cardId
|
||||||
|
* @param barcodeId
|
||||||
|
* @param barcodeType
|
||||||
|
* @param headerColor
|
||||||
|
* @param starStatus
|
||||||
|
* @param lastUsed
|
||||||
|
* @param zoomLevel
|
||||||
|
* @param archiveStatus
|
||||||
|
*/
|
||||||
public LoyaltyCard(final int id, final String store, final String note, @Nullable final Date validFrom,
|
public LoyaltyCard(final int id, final String store, final String note, @Nullable final Date validFrom,
|
||||||
@Nullable final Date expiry, final BigDecimal balance, @Nullable final Currency balanceType,
|
@Nullable final Date expiry, final BigDecimal balance, @Nullable final Currency balanceType,
|
||||||
final String cardId, @Nullable final String barcodeId, @Nullable final CatimaBarcode barcodeType,
|
final String cardId, @Nullable final String barcodeId, @Nullable final CatimaBarcode barcodeType,
|
||||||
@Nullable final Integer headerColor, final int starStatus,
|
@Nullable final Integer headerColor, final int starStatus,
|
||||||
final long lastUsed, final int zoomLevel, final int archiveStatus) {
|
final long lastUsed, final int zoomLevel, final int archiveStatus,
|
||||||
|
@Nullable Bitmap imageThumbnail, @Nullable String imageThumbnailPath,
|
||||||
|
@Nullable Bitmap imageFront, @Nullable String imageFrontPath,
|
||||||
|
@Nullable Bitmap imageBack, @Nullable String imageBackPath) {
|
||||||
|
setId(id);
|
||||||
|
setStore(store);
|
||||||
|
setNote(note);
|
||||||
|
setValidFrom(validFrom);
|
||||||
|
setExpiry(expiry);
|
||||||
|
setBalance(balance);
|
||||||
|
setBalanceType(balanceType);
|
||||||
|
setCardId(cardId);
|
||||||
|
setBarcodeId(barcodeId);
|
||||||
|
setBarcodeType(barcodeType);
|
||||||
|
setHeaderColor(headerColor);
|
||||||
|
setStarStatus(starStatus);
|
||||||
|
setLastUsed(lastUsed);
|
||||||
|
setZoomLevel(zoomLevel);
|
||||||
|
setArchiveStatus(archiveStatus);
|
||||||
|
setImageThumbnail(imageThumbnail, imageThumbnailPath);
|
||||||
|
setImageFront(imageFront, imageFrontPath);
|
||||||
|
setImageBack(imageBack, imageBackPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Bitmap getImageThumbnail(Context context) {
|
||||||
|
if (imageThumbnailPath != null) {
|
||||||
|
if (imageThumbnailPath.equals(TEMP_IMAGE_THUMBNAIL_FILE_NAME)) {
|
||||||
|
imageThumbnail = Utils.loadTempImage(context, imageThumbnailPath);
|
||||||
|
} else {
|
||||||
|
imageThumbnail = Utils.retrieveCardImage(context, imageThumbnailPath);
|
||||||
|
}
|
||||||
|
imageThumbnailPath = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageThumbnail == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageThumbnail.copy(imageThumbnail.getConfig(), imageThumbnail.isMutable());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Bitmap getImageFront(Context context) {
|
||||||
|
if (imageFrontPath != null) {
|
||||||
|
if (imageFrontPath.equals(TEMP_IMAGE_FRONT_FILE_NAME)) {
|
||||||
|
imageFront = Utils.loadTempImage(context, imageFrontPath);
|
||||||
|
} else {
|
||||||
|
imageFront = Utils.retrieveCardImage(context, imageFrontPath);
|
||||||
|
}
|
||||||
|
imageFrontPath = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageFront == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageFront.copy(imageFront.getConfig(), imageFront.isMutable());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Bitmap getImageBack(Context context) {
|
||||||
|
if (imageBackPath != null) {
|
||||||
|
if (imageBackPath.equals(TEMP_IMAGE_BACK_FILE_NAME)) {
|
||||||
|
imageBack = Utils.loadTempImage(context, imageBackPath);
|
||||||
|
} else {
|
||||||
|
imageBack = Utils.retrieveCardImage(context, imageBackPath);
|
||||||
|
}
|
||||||
|
imageBackPath = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageBack == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageBack.copy(imageBack.getConfig(), imageBack.isMutable());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStore(@NonNull String store) {
|
||||||
this.store = store;
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNote(@NonNull String note) {
|
||||||
this.note = note;
|
this.note = note;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValidFrom(@Nullable Date validFrom) {
|
||||||
this.validFrom = validFrom;
|
this.validFrom = validFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpiry(@Nullable Date expiry) {
|
||||||
this.expiry = expiry;
|
this.expiry = expiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBalance(@NonNull BigDecimal balance) {
|
||||||
this.balance = balance;
|
this.balance = balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBalanceType(@Nullable Currency balanceType) {
|
||||||
this.balanceType = balanceType;
|
this.balanceType = balanceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCardId(@NonNull String cardId) {
|
||||||
this.cardId = cardId;
|
this.cardId = cardId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBarcodeId(@Nullable String barcodeId) {
|
||||||
this.barcodeId = barcodeId;
|
this.barcodeId = barcodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBarcodeType(@Nullable CatimaBarcode barcodeType) {
|
||||||
this.barcodeType = barcodeType;
|
this.barcodeType = barcodeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeaderColor(@Nullable Integer headerColor) {
|
||||||
this.headerColor = headerColor;
|
this.headerColor = headerColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStarStatus(int starStatus) {
|
||||||
|
if (starStatus != 0 && starStatus != 1) {
|
||||||
|
throw new IllegalArgumentException("starStatus must be 0 or 1");
|
||||||
|
}
|
||||||
|
|
||||||
this.starStatus = starStatus;
|
this.starStatus = starStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastUsed(long lastUsed) {
|
||||||
this.lastUsed = lastUsed;
|
this.lastUsed = lastUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZoomLevel(int zoomLevel) {
|
||||||
|
if (zoomLevel < 0 || zoomLevel > 100) {
|
||||||
|
throw new IllegalArgumentException("zoomLevel must be in range 0-100");
|
||||||
|
}
|
||||||
|
|
||||||
this.zoomLevel = zoomLevel;
|
this.zoomLevel = zoomLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setArchiveStatus(int archiveStatus) {
|
||||||
|
if (archiveStatus != 0 && archiveStatus != 1) {
|
||||||
|
throw new IllegalArgumentException("archiveStatus must be 0 or 1");
|
||||||
|
}
|
||||||
|
|
||||||
this.archiveStatus = archiveStatus;
|
this.archiveStatus = archiveStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected LoyaltyCard(Parcel in) {
|
public void setImageThumbnail(@Nullable Bitmap imageThumbnail, @Nullable String imageThumbnailPath) {
|
||||||
id = in.readInt();
|
if (imageThumbnail != null && imageThumbnailPath != null) {
|
||||||
store = in.readString();
|
throw new IllegalArgumentException("Cannot set both thumbnail and path");
|
||||||
note = in.readString();
|
}
|
||||||
long tmpValidFrom = in.readLong();
|
|
||||||
validFrom = tmpValidFrom != -1 ? new Date(tmpValidFrom) : null;
|
this.imageThumbnailPath = imageThumbnailPath;
|
||||||
long tmpExpiry = in.readLong();
|
this.imageThumbnail = imageThumbnail != null ? imageThumbnail.copy(imageThumbnail.getConfig(), imageThumbnail.isMutable()) : null;
|
||||||
expiry = tmpExpiry != -1 ? new Date(tmpExpiry) : null;
|
|
||||||
balance = (BigDecimal) in.readValue(BigDecimal.class.getClassLoader());
|
|
||||||
balanceType = (Currency) in.readValue(Currency.class.getClassLoader());
|
|
||||||
cardId = in.readString();
|
|
||||||
barcodeId = in.readString();
|
|
||||||
String tmpBarcodeType = in.readString();
|
|
||||||
barcodeType = !tmpBarcodeType.isEmpty() ? CatimaBarcode.fromName(tmpBarcodeType) : null;
|
|
||||||
int tmpHeaderColor = in.readInt();
|
|
||||||
headerColor = tmpHeaderColor != -1 ? tmpHeaderColor : null;
|
|
||||||
starStatus = in.readInt();
|
|
||||||
lastUsed = in.readLong();
|
|
||||||
zoomLevel = in.readInt();
|
|
||||||
archiveStatus = in.readInt();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void setImageFront(@Nullable Bitmap imageFront, @Nullable String imageFrontPath) {
|
||||||
public void writeToParcel(Parcel parcel, int i) {
|
if (imageFront != null && imageFrontPath != null) {
|
||||||
parcel.writeInt(id);
|
throw new IllegalArgumentException("Cannot set both thumbnail and path");
|
||||||
parcel.writeString(store);
|
}
|
||||||
parcel.writeString(note);
|
|
||||||
parcel.writeLong(validFrom != null ? validFrom.getTime() : -1);
|
this.imageFrontPath = imageFrontPath;
|
||||||
parcel.writeLong(expiry != null ? expiry.getTime() : -1);
|
this.imageFront = imageFront != null ? imageFront.copy(imageFront.getConfig(), imageFront.isMutable()) : null;
|
||||||
parcel.writeValue(balance);
|
|
||||||
parcel.writeValue(balanceType);
|
|
||||||
parcel.writeString(cardId);
|
|
||||||
parcel.writeString(barcodeId);
|
|
||||||
parcel.writeString(barcodeType != null ? barcodeType.name() : "");
|
|
||||||
parcel.writeInt(headerColor != null ? headerColor : -1);
|
|
||||||
parcel.writeInt(starStatus);
|
|
||||||
parcel.writeLong(lastUsed);
|
|
||||||
parcel.writeInt(zoomLevel);
|
|
||||||
parcel.writeInt(archiveStatus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoyaltyCard toLoyaltyCard(Cursor cursor) {
|
public void setImageBack(@Nullable Bitmap imageBack, @Nullable String imageBackPath) {
|
||||||
|
if (imageBack != null && imageBackPath != null) {
|
||||||
|
throw new IllegalArgumentException("Cannot set both thumbnail and path");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.imageBackPath = imageBackPath;
|
||||||
|
this.imageBack = imageBack != null ? imageBack.copy(imageBack.getConfig(), imageBack.isMutable()) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Bitmap getImageForImageLocationType(Context context, ImageLocationType imageLocationType) {
|
||||||
|
if (imageLocationType == ImageLocationType.icon) {
|
||||||
|
return getImageThumbnail(context);
|
||||||
|
} else if (imageLocationType == ImageLocationType.front) {
|
||||||
|
return getImageFront(context);
|
||||||
|
} else if (imageLocationType == ImageLocationType.back) {
|
||||||
|
return getImageBack(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Unknown image location type");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateFromBundle(@NonNull Bundle bundle, boolean requireFull) {
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_ID)) {
|
||||||
|
setId(bundle.getInt(BUNDLE_LOYALTY_CARD_ID));
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_ID);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_STORE)) {
|
||||||
|
setStore(Objects.requireNonNull(bundle.getString(BUNDLE_LOYALTY_CARD_STORE)));
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_STORE);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_NOTE)) {
|
||||||
|
setNote(Objects.requireNonNull(bundle.getString(BUNDLE_LOYALTY_CARD_NOTE)));
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_NOTE);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_VALID_FROM)) {
|
||||||
|
long tmpValidFrom = bundle.getLong(BUNDLE_LOYALTY_CARD_VALID_FROM);
|
||||||
|
setValidFrom(tmpValidFrom > 0 ? new Date(tmpValidFrom) : null);
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_VALID_FROM);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_EXPIRY)) {
|
||||||
|
long tmpExpiry = bundle.getLong(BUNDLE_LOYALTY_CARD_EXPIRY);
|
||||||
|
setExpiry(tmpExpiry > 0 ? new Date(tmpExpiry) : null);
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_EXPIRY);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_BALANCE)) {
|
||||||
|
setBalance(new BigDecimal(bundle.getString(BUNDLE_LOYALTY_CARD_BALANCE)));
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_BALANCE);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_BALANCE_TYPE)) {
|
||||||
|
String tmpBalanceType = bundle.getString(BUNDLE_LOYALTY_CARD_BALANCE_TYPE);
|
||||||
|
setBalanceType(tmpBalanceType != null ? Currency.getInstance(tmpBalanceType) : null);
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_BALANCE_TYPE);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_CARD_ID)) {
|
||||||
|
setCardId(Objects.requireNonNull(bundle.getString(BUNDLE_LOYALTY_CARD_CARD_ID)));
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_CARD_ID);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_BARCODE_ID)) {
|
||||||
|
setBarcodeId(bundle.getString(BUNDLE_LOYALTY_CARD_BARCODE_ID));
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_BARCODE_ID);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_BARCODE_TYPE)) {
|
||||||
|
String tmpBarcodeType = bundle.getString(BUNDLE_LOYALTY_CARD_BARCODE_TYPE);
|
||||||
|
setBarcodeType(tmpBarcodeType != null ? CatimaBarcode.fromName(tmpBarcodeType) : null);
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_BARCODE_TYPE);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_HEADER_COLOR)) {
|
||||||
|
int tmpHeaderColor = bundle.getInt(BUNDLE_LOYALTY_CARD_HEADER_COLOR);
|
||||||
|
setHeaderColor(tmpHeaderColor != -1 ? tmpHeaderColor : null);
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_HEADER_COLOR);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_STAR_STATUS)) {
|
||||||
|
setStarStatus(bundle.getInt(BUNDLE_LOYALTY_CARD_STAR_STATUS));
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_STAR_STATUS);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_LAST_USED)) {
|
||||||
|
setLastUsed(bundle.getLong(BUNDLE_LOYALTY_CARD_LAST_USED));
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_LAST_USED);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_ZOOM_LEVEL)) {
|
||||||
|
setZoomLevel(bundle.getInt(BUNDLE_LOYALTY_CARD_ZOOM_LEVEL));
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_ZOOM_LEVEL);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_ARCHIVE_STATUS)) {
|
||||||
|
setArchiveStatus(bundle.getInt(BUNDLE_LOYALTY_CARD_ARCHIVE_STATUS));
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_ARCHIVE_STATUS);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_IMAGE_THUMBNAIL)) {
|
||||||
|
setImageThumbnail(null, bundle.getString(BUNDLE_LOYALTY_CARD_IMAGE_THUMBNAIL));
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_IMAGE_THUMBNAIL);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_IMAGE_FRONT)) {
|
||||||
|
setImageFront(null, bundle.getString(BUNDLE_LOYALTY_CARD_IMAGE_FRONT));
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_IMAGE_FRONT);
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(BUNDLE_LOYALTY_CARD_IMAGE_BACK)) {
|
||||||
|
setImageBack(null, bundle.getString(BUNDLE_LOYALTY_CARD_IMAGE_BACK));
|
||||||
|
} else if (requireFull) {
|
||||||
|
throw new IllegalArgumentException("Missing key " + BUNDLE_LOYALTY_CARD_IMAGE_BACK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bundle toBundle(Context context, List<String> exportLimit) {
|
||||||
|
boolean exportIsLimited = !exportLimit.isEmpty();
|
||||||
|
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_ID)) {
|
||||||
|
bundle.putInt(BUNDLE_LOYALTY_CARD_ID, id);
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_STORE)) {
|
||||||
|
bundle.putString(BUNDLE_LOYALTY_CARD_STORE, store);
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_NOTE)) {
|
||||||
|
bundle.putString(BUNDLE_LOYALTY_CARD_NOTE, note);
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_VALID_FROM)) {
|
||||||
|
bundle.putLong(BUNDLE_LOYALTY_CARD_VALID_FROM, validFrom != null ? validFrom.getTime() : -1);
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_EXPIRY)) {
|
||||||
|
bundle.putLong(BUNDLE_LOYALTY_CARD_EXPIRY, expiry != null ? expiry.getTime() : -1);
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_BALANCE)) {
|
||||||
|
bundle.putString(BUNDLE_LOYALTY_CARD_BALANCE, balance.toString());
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_BALANCE_TYPE)) {
|
||||||
|
bundle.putString(BUNDLE_LOYALTY_CARD_BALANCE_TYPE, balanceType != null ? balanceType.toString() : null);
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_CARD_ID)) {
|
||||||
|
bundle.putString(BUNDLE_LOYALTY_CARD_CARD_ID, cardId);
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_BARCODE_ID)) {
|
||||||
|
bundle.putString(BUNDLE_LOYALTY_CARD_BARCODE_ID, barcodeId);
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_BARCODE_TYPE)) {
|
||||||
|
bundle.putString(BUNDLE_LOYALTY_CARD_BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null);
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_HEADER_COLOR)) {
|
||||||
|
bundle.putInt(BUNDLE_LOYALTY_CARD_HEADER_COLOR, headerColor != null ? headerColor : -1);
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_STAR_STATUS)) {
|
||||||
|
bundle.putInt(BUNDLE_LOYALTY_CARD_STAR_STATUS, starStatus);
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_LAST_USED)) {
|
||||||
|
bundle.putLong(BUNDLE_LOYALTY_CARD_LAST_USED, lastUsed);
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_ZOOM_LEVEL)) {
|
||||||
|
bundle.putInt(BUNDLE_LOYALTY_CARD_ZOOM_LEVEL, zoomLevel);
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_ARCHIVE_STATUS)) {
|
||||||
|
bundle.putInt(BUNDLE_LOYALTY_CARD_ARCHIVE_STATUS, archiveStatus);
|
||||||
|
}
|
||||||
|
// There is an (undocumented) size limit to bundles of around 2MB(?), when going over it you will experience a random crash
|
||||||
|
// So, instead of storing the bitmaps directly, we write the bitmap to a temp file and store the path
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_IMAGE_THUMBNAIL)) {
|
||||||
|
Bitmap thumbnail = getImageThumbnail(context);
|
||||||
|
if (thumbnail != null) {
|
||||||
|
Utils.saveTempImage(context, thumbnail, TEMP_IMAGE_THUMBNAIL_FILE_NAME, Bitmap.CompressFormat.PNG);
|
||||||
|
bundle.putString(BUNDLE_LOYALTY_CARD_IMAGE_THUMBNAIL, TEMP_IMAGE_THUMBNAIL_FILE_NAME);
|
||||||
|
} else {
|
||||||
|
bundle.putString(BUNDLE_LOYALTY_CARD_IMAGE_THUMBNAIL, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_IMAGE_FRONT)) {
|
||||||
|
Bitmap front = getImageFront(context);
|
||||||
|
if (front != null) {
|
||||||
|
Utils.saveTempImage(context, front, TEMP_IMAGE_FRONT_FILE_NAME, Bitmap.CompressFormat.PNG);
|
||||||
|
bundle.putString(BUNDLE_LOYALTY_CARD_IMAGE_FRONT, TEMP_IMAGE_FRONT_FILE_NAME);
|
||||||
|
} else {
|
||||||
|
bundle.putString(BUNDLE_LOYALTY_CARD_IMAGE_FRONT, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!exportIsLimited || exportLimit.contains(BUNDLE_LOYALTY_CARD_IMAGE_BACK)) {
|
||||||
|
Bitmap back = getImageBack(context);
|
||||||
|
if (back != null) {
|
||||||
|
Utils.saveTempImage(context, back, TEMP_IMAGE_BACK_FILE_NAME, Bitmap.CompressFormat.PNG);
|
||||||
|
bundle.putString(BUNDLE_LOYALTY_CARD_IMAGE_BACK, TEMP_IMAGE_BACK_FILE_NAME);
|
||||||
|
} else {
|
||||||
|
bundle.putString(BUNDLE_LOYALTY_CARD_IMAGE_BACK, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LoyaltyCard fromCursor(Context context, Cursor cursor) {
|
||||||
|
// id
|
||||||
int id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
|
int id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
|
||||||
|
// store
|
||||||
String store = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE));
|
String store = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE));
|
||||||
|
// note
|
||||||
String note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE));
|
String note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE));
|
||||||
|
// validFrom
|
||||||
long validFromLong = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.VALID_FROM));
|
long validFromLong = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.VALID_FROM));
|
||||||
|
Date validFrom = validFromLong > 0 ? new Date(validFromLong) : null;
|
||||||
|
// expiry
|
||||||
long expiryLong = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.EXPIRY));
|
long expiryLong = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.EXPIRY));
|
||||||
|
Date expiry = expiryLong > 0 ? new Date(expiryLong) : null;
|
||||||
|
// balance
|
||||||
BigDecimal balance = new BigDecimal(cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE)));
|
BigDecimal balance = new BigDecimal(cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE)));
|
||||||
String cardId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID));
|
// balanceType
|
||||||
String barcodeId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_ID));
|
|
||||||
int starred = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS));
|
|
||||||
long lastUsed = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.LAST_USED));
|
|
||||||
int zoomLevel = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ZOOM_LEVEL));
|
|
||||||
int archived = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS));
|
|
||||||
|
|
||||||
int barcodeTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
|
|
||||||
int balanceTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE);
|
int balanceTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE);
|
||||||
|
Currency balanceType = !cursor.isNull(balanceTypeColumn) ? Currency.getInstance(cursor.getString(balanceTypeColumn)) : null;
|
||||||
|
// cardId
|
||||||
|
String cardId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID));
|
||||||
|
// barcodeId
|
||||||
|
int barcodeIdColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_ID);
|
||||||
|
String barcodeId = !cursor.isNull(barcodeIdColumn) ? cursor.getString(barcodeIdColumn) : null;
|
||||||
|
// barcodeType
|
||||||
|
int barcodeTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
|
||||||
|
CatimaBarcode barcodeType = !cursor.isNull(barcodeTypeColumn) ? CatimaBarcode.fromName(cursor.getString(barcodeTypeColumn)) : null;
|
||||||
|
// headerColor
|
||||||
int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR);
|
int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR);
|
||||||
|
Integer headerColor = !cursor.isNull(headerColorColumn) ? cursor.getInt(headerColorColumn) : null;
|
||||||
|
// starStatus
|
||||||
|
int starStatus = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS));
|
||||||
|
// lastUsed
|
||||||
|
long lastUsed = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.LAST_USED));
|
||||||
|
// zoomLevel
|
||||||
|
int zoomLevel = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ZOOM_LEVEL));
|
||||||
|
// archiveStatus
|
||||||
|
int archiveStatus = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS));
|
||||||
|
|
||||||
CatimaBarcode barcodeType = null;
|
return new LoyaltyCard(
|
||||||
Currency balanceType = null;
|
id,
|
||||||
Date validFrom = null;
|
store,
|
||||||
Date expiry = null;
|
note,
|
||||||
Integer headerColor = null;
|
validFrom,
|
||||||
|
expiry,
|
||||||
if (cursor.isNull(barcodeTypeColumn) == false) {
|
balance,
|
||||||
barcodeType = CatimaBarcode.fromName(cursor.getString(barcodeTypeColumn));
|
balanceType,
|
||||||
}
|
cardId,
|
||||||
|
barcodeId,
|
||||||
if (cursor.isNull(balanceTypeColumn) == false) {
|
barcodeType,
|
||||||
balanceType = Currency.getInstance(cursor.getString(balanceTypeColumn));
|
headerColor,
|
||||||
}
|
starStatus,
|
||||||
|
lastUsed,
|
||||||
if (validFromLong > 0) {
|
zoomLevel,
|
||||||
validFrom = new Date(validFromLong);
|
archiveStatus,
|
||||||
}
|
null,
|
||||||
|
Utils.getCardImageFileName(id, ImageLocationType.icon),
|
||||||
if (expiryLong > 0) {
|
null,
|
||||||
expiry = new Date(expiryLong);
|
Utils.getCardImageFileName(id, ImageLocationType.front),
|
||||||
}
|
null,
|
||||||
|
Utils.getCardImageFileName(id, ImageLocationType.back)
|
||||||
if (cursor.isNull(headerColorColumn) == false) {
|
);
|
||||||
headerColor = cursor.getInt(headerColorColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LoyaltyCard(id, store, note, validFrom, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starred, lastUsed, zoomLevel, archived);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isDuplicate(final LoyaltyCard a, final LoyaltyCard b) {
|
public static boolean isDuplicate(Context context, final LoyaltyCard a, final LoyaltyCard b) {
|
||||||
|
// Note: Bitmap comparing is slow, be careful when calling this method
|
||||||
// Skip lastUsed & zoomLevel
|
// Skip lastUsed & zoomLevel
|
||||||
return a.id == b.id && // non-nullable int
|
return a.id == b.id && // non-nullable int
|
||||||
a.store.equals(b.store) && // non-nullable String
|
a.store.equals(b.store) && // non-nullable String
|
||||||
@@ -159,12 +570,23 @@ public class LoyaltyCard implements Parcelable {
|
|||||||
b.barcodeType == null ? null : b.barcodeType.format()) && // nullable CatimaBarcode with no overridden .equals(), so we need to check .format()
|
b.barcodeType == null ? null : b.barcodeType.format()) && // nullable CatimaBarcode with no overridden .equals(), so we need to check .format()
|
||||||
Utils.equals(a.headerColor, b.headerColor) && // nullable Integer
|
Utils.equals(a.headerColor, b.headerColor) && // nullable Integer
|
||||||
a.starStatus == b.starStatus && // non-nullable int
|
a.starStatus == b.starStatus && // non-nullable int
|
||||||
a.archiveStatus == b.archiveStatus; // non-nullable int
|
a.archiveStatus == b.archiveStatus && // non-nullable int
|
||||||
|
nullableBitmapsEqual(a.getImageThumbnail(context), b.getImageThumbnail(context)) && // nullable Bitmap
|
||||||
|
nullableBitmapsEqual(a.getImageFront(context), b.getImageFront(context)) && // nullable Bitmap
|
||||||
|
nullableBitmapsEqual(a.getImageBack(context), b.getImageBack(context)); // nullable Bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static boolean nullableBitmapsEqual(@Nullable Bitmap a, @Nullable Bitmap b) {
|
||||||
public int describeContents() {
|
if (a == null && b == null) {
|
||||||
return id;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a != null && b != null) {
|
||||||
|
return a.sameAs(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// One is null and the other isn't, so it's not equal
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -173,7 +595,8 @@ public class LoyaltyCard implements Parcelable {
|
|||||||
return String.format(
|
return String.format(
|
||||||
"LoyaltyCard{%n id=%s,%n store=%s,%n note=%s,%n validFrom=%s,%n expiry=%s,%n"
|
"LoyaltyCard{%n id=%s,%n store=%s,%n note=%s,%n validFrom=%s,%n expiry=%s,%n"
|
||||||
+ " balance=%s,%n balanceType=%s,%n cardId=%s,%n barcodeId=%s,%n barcodeType=%s,%n"
|
+ " balance=%s,%n balanceType=%s,%n cardId=%s,%n barcodeId=%s,%n barcodeType=%s,%n"
|
||||||
+ " headerColor=%s,%n starStatus=%s,%n lastUsed=%s,%n zoomLevel=%s,%n archiveStatus=%s%n}",
|
+ " headerColor=%s,%n starStatus=%s,%n lastUsed=%s,%n zoomLevel=%s,%n archiveStatus=%s%n"
|
||||||
|
+ " imageThumbnail=%s,%n imageThumbnailPath=%s,%n imageFront=%s,%n imageFrontPath=%s,%n imageBack=%s,%n imageBackPath=%s,%n}",
|
||||||
this.id,
|
this.id,
|
||||||
this.store,
|
this.store,
|
||||||
this.note,
|
this.note,
|
||||||
@@ -188,19 +611,13 @@ public class LoyaltyCard implements Parcelable {
|
|||||||
this.starStatus,
|
this.starStatus,
|
||||||
this.lastUsed,
|
this.lastUsed,
|
||||||
this.zoomLevel,
|
this.zoomLevel,
|
||||||
this.archiveStatus
|
this.archiveStatus,
|
||||||
|
this.imageThumbnail,
|
||||||
|
this.imageThumbnailPath,
|
||||||
|
this.imageFront,
|
||||||
|
this.imageFrontPath,
|
||||||
|
this.imageBack,
|
||||||
|
this.imageBackPath
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Creator<LoyaltyCard> CREATOR = new Creator<LoyaltyCard>() {
|
|
||||||
@Override
|
|
||||||
public LoyaltyCard createFromParcel(Parcel in) {
|
|
||||||
return new LoyaltyCard(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoyaltyCard[] newArray(int size) {
|
|
||||||
return new LoyaltyCard[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import java.text.DateFormat;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import protect.card_locker.databinding.LoyaltyCardLayoutBinding;
|
import protect.card_locker.databinding.LoyaltyCardLayoutBinding;
|
||||||
|
import protect.card_locker.preferences.Settings;
|
||||||
|
|
||||||
public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCursorAdapter.LoyaltyCardListItemViewHolder> {
|
public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCursorAdapter.LoyaltyCardListItemViewHolder> {
|
||||||
private int mCurrentSelectedIndex = -1;
|
private int mCurrentSelectedIndex = -1;
|
||||||
@@ -79,7 +80,7 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
|||||||
|
|
||||||
public LoyaltyCard getCard(int position) {
|
public LoyaltyCard getCard(int position) {
|
||||||
mCursor.moveToPosition(position);
|
mCursor.moveToPosition(position);
|
||||||
return LoyaltyCard.toLoyaltyCard(mCursor);
|
return LoyaltyCard.fromCursor(mContext, mCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onBindViewHolder(LoyaltyCardListItemViewHolder inputHolder, Cursor inputCursor) {
|
public void onBindViewHolder(LoyaltyCardListItemViewHolder inputHolder, Cursor inputCursor) {
|
||||||
@@ -87,8 +88,8 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
|||||||
boolean showDivider = false;
|
boolean showDivider = false;
|
||||||
inputHolder.mDivider.setVisibility(View.GONE);
|
inputHolder.mDivider.setVisibility(View.GONE);
|
||||||
|
|
||||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(inputCursor);
|
LoyaltyCard loyaltyCard = LoyaltyCard.fromCursor(mContext, inputCursor);
|
||||||
Bitmap icon = Utils.retrieveCardImage(mContext, loyaltyCard.id, ImageLocationType.icon);
|
Bitmap icon = loyaltyCard.getImageThumbnail(mContext);
|
||||||
|
|
||||||
if (mLoyaltyCardListDisplayOptions.showingNameBelowThumbnail() && icon != null) {
|
if (mLoyaltyCardListDisplayOptions.showingNameBelowThumbnail() && icon != null) {
|
||||||
showDivider = true;
|
showDivider = true;
|
||||||
@@ -111,22 +112,21 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mLoyaltyCardListDisplayOptions.showingValidity() && loyaltyCard.validFrom != null) {
|
if (mLoyaltyCardListDisplayOptions.showingValidity() && loyaltyCard.validFrom != null) {
|
||||||
inputHolder.setExtraField(inputHolder.mValidFromField, DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.validFrom), Utils.isNotYetValid(loyaltyCard.validFrom) ? Color.RED : null, showDivider);
|
inputHolder.setExtraField(inputHolder.mValidFromField, DateFormat.getDateInstance(DateFormat.MEDIUM).format(loyaltyCard.validFrom), Utils.isNotYetValid(loyaltyCard.validFrom) ? Color.RED : null, showDivider);
|
||||||
} else {
|
} else {
|
||||||
inputHolder.setExtraField(inputHolder.mValidFromField, null, null, false);
|
inputHolder.setExtraField(inputHolder.mValidFromField, null, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mLoyaltyCardListDisplayOptions.showingValidity() && loyaltyCard.expiry != null) {
|
if (mLoyaltyCardListDisplayOptions.showingValidity() && loyaltyCard.expiry != null) {
|
||||||
inputHolder.setExtraField(inputHolder.mExpiryField, DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.expiry), Utils.hasExpired(loyaltyCard.expiry) ? Color.RED : null, showDivider);
|
inputHolder.setExtraField(inputHolder.mExpiryField, DateFormat.getDateInstance(DateFormat.MEDIUM).format(loyaltyCard.expiry), Utils.hasExpired(loyaltyCard.expiry) ? Color.RED : null, showDivider);
|
||||||
} else {
|
} else {
|
||||||
inputHolder.setExtraField(inputHolder.mExpiryField, null, null, false);
|
inputHolder.setExtraField(inputHolder.mExpiryField, null, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
inputHolder.mCardIcon.setContentDescription(loyaltyCard.store);
|
inputHolder.mCardIcon.setContentDescription(loyaltyCard.store);
|
||||||
Utils.setIconOrTextWithBackground(mContext, loyaltyCard, icon, inputHolder.mCardIcon, inputHolder.mCardText);
|
Utils.setIconOrTextWithBackground(mContext, loyaltyCard, icon, inputHolder.mCardIcon, inputHolder.mCardText, new Settings(mContext).getPreferredColumnCount());
|
||||||
inputHolder.setIconBackgroundColor(Utils.getHeaderColor(mContext, loyaltyCard));
|
|
||||||
|
|
||||||
inputHolder.toggleCardStateIcon(loyaltyCard.starStatus != 0, loyaltyCard.archiveStatus != 0, itemSelected(inputCursor.getPosition()));
|
inputHolder.toggleCardStateIcon(loyaltyCard.starStatus != 0, loyaltyCard.archiveStatus != 0);
|
||||||
|
|
||||||
inputHolder.itemView.setActivated(mSelectedItems.get(inputCursor.getPosition(), false));
|
inputHolder.itemView.setActivated(mSelectedItems.get(inputCursor.getPosition(), false));
|
||||||
applyIconAnimation(inputHolder, inputCursor.getPosition());
|
applyIconAnimation(inputHolder, inputCursor.getPosition());
|
||||||
@@ -193,7 +193,7 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
|||||||
int i;
|
int i;
|
||||||
for (i = 0; i < mSelectedItems.size(); i++) {
|
for (i = 0; i < mSelectedItems.size(); i++) {
|
||||||
mCursor.moveToPosition(mSelectedItems.keyAt(i));
|
mCursor.moveToPosition(mSelectedItems.keyAt(i));
|
||||||
result.add(LoyaltyCard.toLoyaltyCard(mCursor));
|
result.add(LoyaltyCard.fromCursor(mContext, mCursor));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -212,13 +212,11 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
|||||||
public class LoyaltyCardListItemViewHolder extends RecyclerView.ViewHolder {
|
public class LoyaltyCardListItemViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
public TextView mCardText, mStoreField, mNoteField, mBalanceField, mValidFromField, mExpiryField;
|
public TextView mCardText, mStoreField, mNoteField, mBalanceField, mValidFromField, mExpiryField;
|
||||||
public ImageView mCardIcon, mStarBackground, mStarBorder, mTickIcon, mArchivedBackground;
|
public ImageView mCardIcon, mTickIcon;
|
||||||
public MaterialCardView mRow;
|
public MaterialCardView mRow;
|
||||||
public ConstraintLayout mStar, mArchived;
|
public ConstraintLayout mStar, mArchived;
|
||||||
public View mDivider;
|
public View mDivider;
|
||||||
|
|
||||||
private int mIconBackgroundColor;
|
|
||||||
|
|
||||||
protected LoyaltyCardListItemViewHolder(LoyaltyCardLayoutBinding loyaltyCardLayoutBinding, CardAdapterListener inputListener) {
|
protected LoyaltyCardListItemViewHolder(LoyaltyCardLayoutBinding loyaltyCardLayoutBinding, CardAdapterListener inputListener) {
|
||||||
super(loyaltyCardLayoutBinding.getRoot());
|
super(loyaltyCardLayoutBinding.getRoot());
|
||||||
View inputView = loyaltyCardLayoutBinding.getRoot();
|
View inputView = loyaltyCardLayoutBinding.getRoot();
|
||||||
@@ -232,10 +230,7 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
|||||||
mCardIcon = loyaltyCardLayoutBinding.thumbnail;
|
mCardIcon = loyaltyCardLayoutBinding.thumbnail;
|
||||||
mCardText = loyaltyCardLayoutBinding.thumbnailText;
|
mCardText = loyaltyCardLayoutBinding.thumbnailText;
|
||||||
mStar = loyaltyCardLayoutBinding.star;
|
mStar = loyaltyCardLayoutBinding.star;
|
||||||
mStarBackground = loyaltyCardLayoutBinding.starBackground;
|
|
||||||
mStarBorder = loyaltyCardLayoutBinding.starBorder;
|
|
||||||
mArchived = loyaltyCardLayoutBinding.archivedIcon;
|
mArchived = loyaltyCardLayoutBinding.archivedIcon;
|
||||||
mArchivedBackground = loyaltyCardLayoutBinding.archiveBackground;
|
|
||||||
mTickIcon = loyaltyCardLayoutBinding.selectedThumbnail;
|
mTickIcon = loyaltyCardLayoutBinding.selectedThumbnail;
|
||||||
inputView.setOnLongClickListener(view -> {
|
inputView.setOnLongClickListener(view -> {
|
||||||
inputListener.onRowClicked(getAdapterPosition());
|
inputListener.onRowClicked(getAdapterPosition());
|
||||||
@@ -297,31 +292,7 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
|||||||
mNoteField.requestLayout();
|
mNoteField.requestLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toggleCardStateIcon(boolean enableStar, boolean enableArchive, boolean colorByTheme) {
|
public void toggleCardStateIcon(boolean enableStar, boolean enableArchive) {
|
||||||
/* the below code does not work in android 5! hence the change of drawable instead
|
|
||||||
boolean needDarkForeground = Utils.needsDarkForeground(mIconBackgroundColor);
|
|
||||||
Drawable borderDrawable = mStarBorder.getDrawable().mutate();
|
|
||||||
Drawable backgroundDrawable = mStarBackground.getDrawable().mutate();
|
|
||||||
DrawableCompat.setTint(borderDrawable, needsDarkForeground ? Color.BLACK : Color.WHITE);
|
|
||||||
DrawableCompat.setTint(backgroundDrawable, needsDarkForeground ? Color.BLACK : Color.WHITE);
|
|
||||||
mStarBorder.setImageDrawable(borderDrawable);
|
|
||||||
mStarBackground.setImageDrawable(backgroundDrawable);
|
|
||||||
*/
|
|
||||||
boolean dark = Utils.needsDarkForeground(mIconBackgroundColor);
|
|
||||||
if (colorByTheme) {
|
|
||||||
dark = !mDarkModeEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dark) {
|
|
||||||
mStarBorder.setImageResource(R.drawable.ic_unstarred_white);
|
|
||||||
mStarBackground.setImageResource(R.drawable.ic_starred_black);
|
|
||||||
mArchivedBackground.setImageResource(R.drawable.ic_baseline_archive_24_black);
|
|
||||||
} else {
|
|
||||||
mStarBorder.setImageResource(R.drawable.ic_unstarred_black);
|
|
||||||
mStarBackground.setImageResource(R.drawable.ic_starred_white);
|
|
||||||
mArchivedBackground.setImageResource(R.drawable.ic_baseline_archive_24);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableStar) {
|
if (enableStar) {
|
||||||
mStar.setVisibility(View.VISIBLE);
|
mStar.setVisibility(View.VISIBLE);
|
||||||
} else{
|
} else{
|
||||||
@@ -333,22 +304,6 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
|||||||
} else{
|
} else{
|
||||||
mArchived.setVisibility(View.GONE);
|
mArchived.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
mStarBorder.invalidate();
|
|
||||||
mStarBackground.invalidate();
|
|
||||||
mArchivedBackground.invalidate();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIconBackgroundColor(int color) {
|
|
||||||
mIconBackgroundColor = color;
|
|
||||||
mCardIcon.setBackgroundColor(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int dpToPx(int dp, Context mContext) {
|
|
||||||
Resources r = mContext.getResources();
|
|
||||||
int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
|
|
||||||
return px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
@@ -20,6 +19,7 @@ import android.text.method.DigitsKeyListener;
|
|||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.text.util.Linkify;
|
import android.text.util.Linkify;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -60,7 +60,6 @@ import java.text.DateFormat;
|
|||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Currency;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
@@ -99,9 +98,35 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
static final String STATE_IMAGEINDEX = "imageIndex";
|
static final String STATE_IMAGEINDEX = "imageIndex";
|
||||||
static final String STATE_FULLSCREEN = "isFullscreen";
|
static final String STATE_FULLSCREEN = "isFullscreen";
|
||||||
|
|
||||||
|
static final String BUNDLE_ID = "id";
|
||||||
|
static final String BUNDLE_CARDLIST = "cardList";
|
||||||
|
static final String BUNDLE_TRANSITION_RIGHT = "transition_right";
|
||||||
|
|
||||||
final private TaskHandler mTasks = new TaskHandler();
|
final private TaskHandler mTasks = new TaskHandler();
|
||||||
Runnable barcodeImageGenerationFinishedCallback;
|
Runnable barcodeImageGenerationFinishedCallback;
|
||||||
|
|
||||||
|
private long initTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||||
|
if (settings.useVolumeKeysForNavigation()) {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||||
|
// Navigate to the previous card
|
||||||
|
if (initTime < (System.currentTimeMillis() - 1000)) {
|
||||||
|
prevNextCard(false);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||||
|
// Navigate to the next card
|
||||||
|
if (initTime < (System.currentTimeMillis() - 1000)) {
|
||||||
|
prevNextCard(true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
|
||||||
public void onMainImageTap() {
|
public void onMainImageTap() {
|
||||||
// If we're in fullscreen, leave fullscreen
|
// If we're in fullscreen, leave fullscreen
|
||||||
if (isFullscreen) {
|
if (isFullscreen) {
|
||||||
@@ -183,8 +208,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
|
|
||||||
private void extractIntentFields(Intent intent) {
|
private void extractIntentFields(Intent intent) {
|
||||||
final Bundle b = intent.getExtras();
|
final Bundle b = intent.getExtras();
|
||||||
loyaltyCardId = b != null ? b.getInt("id") : 0;
|
loyaltyCardId = b != null ? b.getInt(BUNDLE_ID) : 0;
|
||||||
cardList = b != null ? b.getIntegerArrayList("cardList") : null;
|
cardList = b != null ? b.getIntegerArrayList(BUNDLE_CARDLIST) : null;
|
||||||
Log.d(TAG, "View activity: id=" + loyaltyCardId);
|
Log.d(TAG, "View activity: id=" + loyaltyCardId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +235,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int transitionRight = incomingIntentExtras.getInt("transition_right", -1);
|
int transitionRight = incomingIntentExtras.getInt(BUNDLE_TRANSITION_RIGHT, -1);
|
||||||
if (transitionRight == 1) {
|
if (transitionRight == 1) {
|
||||||
// right side transition
|
// right side transition
|
||||||
overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
|
overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
|
||||||
@@ -223,6 +248,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
binding = LoyaltyCardViewLayoutBinding.inflate(getLayoutInflater());
|
binding = LoyaltyCardViewLayoutBinding.inflate(getLayoutInflater());
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
Utils.applyWindowInsets(binding.getRoot());
|
||||||
Toolbar toolbar = binding.toolbar;
|
Toolbar toolbar = binding.toolbar;
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
@@ -304,7 +330,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
binding.bottomAppBarUpdateBalanceButton.setOnClickListener(view -> showBalanceUpdateDialog());
|
binding.bottomAppBarUpdateBalanceButton.setOnClickListener(view -> showBalanceUpdateDialog());
|
||||||
|
|
||||||
binding.iconContainer.setOnClickListener(view -> {
|
binding.iconContainer.setOnClickListener(view -> {
|
||||||
if (Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.icon) != null) {
|
if (loyaltyCard.getImageThumbnail(this) != null) {
|
||||||
openImageInGallery(ImageType.ICON);
|
openImageInGallery(ImageType.ICON);
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(LoyaltyCardViewActivity.this, R.string.icon_header_click_text, Toast.LENGTH_LONG).show();
|
Toast.makeText(LoyaltyCardViewActivity.this, R.string.icon_header_click_text, Toast.LENGTH_LONG).show();
|
||||||
@@ -420,7 +446,11 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
|
|
||||||
private void showBalanceUpdateDialog() {
|
private void showBalanceUpdateDialog() {
|
||||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||||
|
|
||||||
|
// Header
|
||||||
builder.setTitle(R.string.updateBalanceTitle);
|
builder.setTitle(R.string.updateBalanceTitle);
|
||||||
|
|
||||||
|
// Layout
|
||||||
FrameLayout container = new FrameLayout(this);
|
FrameLayout container = new FrameLayout(this);
|
||||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
@@ -438,61 +468,91 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
currentTextview.setText(getString(R.string.currentBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)));
|
currentTextview.setText(getString(R.string.currentBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)));
|
||||||
layout.addView(currentTextview);
|
layout.addView(currentTextview);
|
||||||
|
|
||||||
TextView updateTextView = new TextView(this);
|
|
||||||
updateTextView.setText(getString(R.string.newBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)));
|
|
||||||
layout.addView(updateTextView);
|
|
||||||
|
|
||||||
final TextInputEditText input = new TextInputEditText(this);
|
final TextInputEditText input = new TextInputEditText(this);
|
||||||
Context dialogContext = this;
|
input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||||
input.setInputType(InputType.TYPE_CLASS_NUMBER);
|
|
||||||
input.setKeyListener(DigitsKeyListener.getInstance("0123456789,."));
|
input.setKeyListener(DigitsKeyListener.getInstance("0123456789,."));
|
||||||
input.setHint(R.string.updateBalanceHint);
|
input.setHint(R.string.updateBalanceHint);
|
||||||
input.addTextChangedListener(new SimpleTextWatcher() {
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
BigDecimal newBalance;
|
|
||||||
try {
|
|
||||||
newBalance = calculateNewBalance(loyaltyCard.balance, loyaltyCard.balanceType, s.toString());
|
|
||||||
} catch (ParseException e) {
|
|
||||||
input.setTag(null);
|
|
||||||
updateTextView.setText(getString(R.string.newBalanceSentence, Utils.formatBalance(dialogContext, loyaltyCard.balance, loyaltyCard.balanceType)));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save new balance into this element
|
|
||||||
input.setTag(newBalance);
|
|
||||||
updateTextView.setText(getString(R.string.newBalanceSentence, Utils.formatBalance(dialogContext, newBalance, loyaltyCard.balanceType)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
layout.addView(input);
|
layout.addView(input);
|
||||||
layout.setLayoutParams(params);
|
layout.setLayoutParams(params);
|
||||||
container.addView(layout);
|
container.addView(layout);
|
||||||
|
|
||||||
|
// Set layout
|
||||||
builder.setView(container);
|
builder.setView(container);
|
||||||
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
|
||||||
// Grab calculated balance from input field
|
// Buttons
|
||||||
BigDecimal newBalance = (BigDecimal) input.getTag();
|
builder.setPositiveButton(R.string.spend, (dialogInterface, i) -> {
|
||||||
if (newBalance == null) {
|
// Calculate and update balance
|
||||||
return;
|
try {
|
||||||
|
BigDecimal balanceChange = Utils.parseBalance(input.getText().toString(), loyaltyCard.balanceType);
|
||||||
|
BigDecimal newBalance = loyaltyCard.balance.subtract(balanceChange).max(new BigDecimal(0));
|
||||||
|
DBHelper.updateLoyaltyCardBalance(database, loyaltyCardId, newBalance);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
Toast.makeText(getApplicationContext(), R.string.amountParsingFailed, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actually update balance
|
// Reload state
|
||||||
DBHelper.updateLoyaltyCardBalance(database, loyaltyCardId, newBalance);
|
|
||||||
// Reload UI
|
|
||||||
this.onResume();
|
this.onResume();
|
||||||
|
|
||||||
|
// Show new balance
|
||||||
|
Toast.makeText(getApplicationContext(), getString(R.string.newBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)), Toast.LENGTH_LONG).show();
|
||||||
});
|
});
|
||||||
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
builder.setNegativeButton(R.string.receive, (dialogInterface, i) -> {
|
||||||
|
// Calculate and update balance
|
||||||
|
try {
|
||||||
|
BigDecimal balanceChange = Utils.parseBalance(input.getText().toString(), loyaltyCard.balanceType);
|
||||||
|
BigDecimal newBalance = loyaltyCard.balance.add(balanceChange);
|
||||||
|
DBHelper.updateLoyaltyCardBalance(database, loyaltyCardId, newBalance);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
Toast.makeText(getApplicationContext(), R.string.amountParsingFailed, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload state
|
||||||
|
this.onResume();
|
||||||
|
|
||||||
|
// Show new balance
|
||||||
|
Toast.makeText(getApplicationContext(), getString(R.string.newBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)), Toast.LENGTH_LONG).show();
|
||||||
|
});
|
||||||
|
builder.setNeutralButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
||||||
AlertDialog dialog = builder.create();
|
AlertDialog dialog = builder.create();
|
||||||
|
|
||||||
|
// Now that the dialog exists, we can bind something that affects the buttons
|
||||||
|
input.addTextChangedListener(new SimpleTextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
BigDecimal balanceChange;
|
||||||
|
|
||||||
|
try {
|
||||||
|
balanceChange = Utils.parseBalance(s.toString(), loyaltyCard.balanceType);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
input.setError(getString(R.string.amountParsingFailed));
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.setError(null);
|
||||||
|
if (balanceChange.equals(new BigDecimal(0))) {
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false);
|
||||||
|
} else {
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
dialog.show();
|
dialog.show();
|
||||||
|
|
||||||
|
// Disable buttons (must be done **after** dialog is shown to prevent crash
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false);
|
||||||
|
|
||||||
|
// Set focus on input field
|
||||||
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||||
input.requestFocus();
|
input.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal calculateNewBalance(BigDecimal currentBalance, Currency currency, String unparsedSubtraction) throws ParseException {
|
|
||||||
BigDecimal subtraction = Utils.parseBalance(unparsedSubtraction, currency);
|
|
||||||
return currentBalance.subtract(subtraction).max(new BigDecimal(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setBottomAppBarButtonState() {
|
private void setBottomAppBarButtonState() {
|
||||||
if (!loyaltyCard.note.isEmpty() || !loyaltyCardGroups.isEmpty() || hasBalance(loyaltyCard) || loyaltyCard.validFrom != null || loyaltyCard.expiry != null) {
|
if (!loyaltyCard.note.isEmpty() || !loyaltyCardGroups.isEmpty() || hasBalance(loyaltyCard) || loyaltyCard.validFrom != null || loyaltyCard.expiry != null) {
|
||||||
binding.bottomAppBarInfoButton.setVisibility(View.VISIBLE);
|
binding.bottomAppBarInfoButton.setVisibility(View.VISIBLE);
|
||||||
@@ -540,8 +600,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
// Restart activity with new card id and index
|
// Restart activity with new card id and index
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
Bundle b = intent.getExtras();
|
Bundle b = intent.getExtras();
|
||||||
b.putInt("id", loyaltyCardId);
|
b.putInt(BUNDLE_ID, loyaltyCardId);
|
||||||
b.putInt("transition_right", transitionRight ? 1 : 0);
|
b.putInt(BUNDLE_TRANSITION_RIGHT, transitionRight ? 1 : 0);
|
||||||
intent.putExtras(b);
|
intent.putExtras(b);
|
||||||
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
@@ -601,7 +661,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
window.setAttributes(attributes);
|
window.setAttributes(attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
loyaltyCard = DBHelper.getLoyaltyCard(database, loyaltyCardId);
|
loyaltyCard = DBHelper.getLoyaltyCard(this, database, loyaltyCardId);
|
||||||
if (loyaltyCard == null) {
|
if (loyaltyCard == null) {
|
||||||
Log.w(TAG, "Could not lookup loyalty card " + loyaltyCardId);
|
Log.w(TAG, "Could not lookup loyalty card " + loyaltyCardId);
|
||||||
Toast.makeText(this, R.string.noCardExistsError, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.noCardExistsError, Toast.LENGTH_LONG).show();
|
||||||
@@ -619,10 +679,15 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
cardIdString = loyaltyCard.cardId;
|
cardIdString = loyaltyCard.cardId;
|
||||||
barcodeIdString = loyaltyCard.barcodeId;
|
barcodeIdString = loyaltyCard.barcodeId;
|
||||||
|
|
||||||
binding.cardIdView.setText(loyaltyCard.cardId);
|
binding.mainImageDescription.setText(loyaltyCard.cardId);
|
||||||
|
|
||||||
// Display full text on click in case it doesn't fit in a single line
|
// Display full text on click in case it doesn't fit in a single line
|
||||||
binding.cardIdView.setOnClickListener(v -> {
|
binding.mainImageDescription.setOnClickListener(v -> {
|
||||||
|
if (mainImageIndex != 0) {
|
||||||
|
// Don't show cardId dialog, we're displaying something else
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TextView cardIdView = new TextView(LoyaltyCardViewActivity.this);
|
TextView cardIdView = new TextView(LoyaltyCardViewActivity.this);
|
||||||
cardIdView.setText(loyaltyCard.cardId);
|
cardIdView.setText(loyaltyCard.cardId);
|
||||||
cardIdView.setTextIsSelectable(true);
|
cardIdView.setTextIsSelectable(true);
|
||||||
@@ -655,8 +720,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
editButtonIcon.setTint(Utils.needsDarkForeground(complementaryColor) ? Color.BLACK : Color.WHITE);
|
editButtonIcon.setTint(Utils.needsDarkForeground(complementaryColor) ? Color.BLACK : Color.WHITE);
|
||||||
binding.fabEdit.setImageDrawable(editButtonIcon);
|
binding.fabEdit.setImageDrawable(editButtonIcon);
|
||||||
|
|
||||||
Bitmap icon = Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.icon);
|
Bitmap icon = loyaltyCard.getImageThumbnail(this);
|
||||||
Utils.setIconOrTextWithBackground(this, loyaltyCard, icon, binding.iconImage, binding.iconText);
|
Utils.setIconOrTextWithBackground(this, loyaltyCard, icon, binding.iconImage, binding.iconText, 1);
|
||||||
|
|
||||||
// If the background is very bright, we should use dark icons
|
// If the background is very bright, we should use dark icons
|
||||||
backgroundNeedsDarkIcons = Utils.needsDarkForeground(backgroundHeaderColor);
|
backgroundNeedsDarkIcons = Utils.needsDarkForeground(backgroundHeaderColor);
|
||||||
@@ -684,12 +749,12 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
imageTypes.add(ImageType.BARCODE);
|
imageTypes.add(ImageType.BARCODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
frontImageBitmap = Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.front);
|
frontImageBitmap = loyaltyCard.getImageFront(this);
|
||||||
if (frontImageBitmap != null) {
|
if (frontImageBitmap != null) {
|
||||||
imageTypes.add(ImageType.IMAGE_FRONT);
|
imageTypes.add(ImageType.IMAGE_FRONT);
|
||||||
}
|
}
|
||||||
|
|
||||||
backImageBitmap = Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.back);
|
backImageBitmap = loyaltyCard.getImageBack(this);
|
||||||
if (backImageBitmap != null) {
|
if (backImageBitmap != null) {
|
||||||
imageTypes.add(ImageType.IMAGE_BACK);
|
imageTypes.add(ImageType.IMAGE_BACK);
|
||||||
}
|
}
|
||||||
@@ -701,6 +766,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
DBHelper.updateLoyaltyCardLastUsed(database, loyaltyCard.id);
|
DBHelper.updateLoyaltyCardLastUsed(database, loyaltyCard.id);
|
||||||
|
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
|
|
||||||
|
ShortcutHelper.updateShortcuts(this, loyaltyCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setStateBasedOnImageTypes() {
|
private void setStateBasedOnImageTypes() {
|
||||||
@@ -800,6 +867,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
DBHelper.updateLoyaltyCardArchiveStatus(database, loyaltyCardId, 1);
|
DBHelper.updateLoyaltyCardArchiveStatus(database, loyaltyCardId, 1);
|
||||||
Toast.makeText(LoyaltyCardViewActivity.this, R.string.archived, Toast.LENGTH_LONG).show();
|
Toast.makeText(LoyaltyCardViewActivity.this, R.string.archived, Toast.LENGTH_LONG).show();
|
||||||
|
|
||||||
|
ShortcutHelper.removeShortcut(LoyaltyCardViewActivity.this, loyaltyCardId);
|
||||||
|
|
||||||
// Re-init loyaltyCard with new data from DB
|
// Re-init loyaltyCard with new data from DB
|
||||||
onResume();
|
onResume();
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
@@ -891,7 +960,9 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
if (imageTypes.isEmpty()) {
|
if (imageTypes.isEmpty()) {
|
||||||
barcodeRenderTarget.setVisibility(View.GONE);
|
barcodeRenderTarget.setVisibility(View.GONE);
|
||||||
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
||||||
binding.cardIdView.setTextColor(MaterialColors.getColor(binding.cardIdView, com.google.android.material.R.attr.colorOnSurfaceVariant));
|
binding.mainImageDescription.setTextColor(MaterialColors.getColor(binding.mainImageDescription, com.google.android.material.R.attr.colorOnSurfaceVariant));
|
||||||
|
|
||||||
|
binding.mainImageDescription.setText(loyaltyCard.cardId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,7 +971,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
if (wantedImageType == ImageType.BARCODE) {
|
if (wantedImageType == ImageType.BARCODE) {
|
||||||
barcodeRenderTarget.setBackgroundColor(Color.WHITE);
|
barcodeRenderTarget.setBackgroundColor(Color.WHITE);
|
||||||
binding.mainCardView.setCardBackgroundColor(Color.WHITE);
|
binding.mainCardView.setCardBackgroundColor(Color.WHITE);
|
||||||
binding.cardIdView.setTextColor(getResources().getColor(R.color.md_theme_light_onSurfaceVariant));
|
binding.mainImageDescription.setTextColor(getResources().getColor(R.color.md_theme_light_onSurfaceVariant));
|
||||||
|
|
||||||
if (waitForResize) {
|
if (waitForResize) {
|
||||||
redrawBarcodeAfterResize(!isFullscreen);
|
redrawBarcodeAfterResize(!isFullscreen);
|
||||||
@@ -908,18 +979,23 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
drawBarcode(!isFullscreen);
|
drawBarcode(!isFullscreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.mainImageDescription.setText(loyaltyCard.cardId);
|
||||||
barcodeRenderTarget.setContentDescription(getString(R.string.barcodeImageDescriptionWithType, format.prettyName()));
|
barcodeRenderTarget.setContentDescription(getString(R.string.barcodeImageDescriptionWithType, format.prettyName()));
|
||||||
} else if (wantedImageType == ImageType.IMAGE_FRONT) {
|
} else if (wantedImageType == ImageType.IMAGE_FRONT) {
|
||||||
barcodeRenderTarget.setImageBitmap(frontImageBitmap);
|
barcodeRenderTarget.setImageBitmap(frontImageBitmap);
|
||||||
barcodeRenderTarget.setBackgroundColor(Color.TRANSPARENT);
|
barcodeRenderTarget.setBackgroundColor(Color.TRANSPARENT);
|
||||||
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
||||||
binding.cardIdView.setTextColor(MaterialColors.getColor(binding.cardIdView, com.google.android.material.R.attr.colorOnSurfaceVariant));
|
binding.mainImageDescription.setTextColor(MaterialColors.getColor(binding.mainImageDescription, com.google.android.material.R.attr.colorOnSurfaceVariant));
|
||||||
|
|
||||||
|
binding.mainImageDescription.setText(getString(R.string.frontImageDescription));
|
||||||
barcodeRenderTarget.setContentDescription(getString(R.string.frontImageDescription));
|
barcodeRenderTarget.setContentDescription(getString(R.string.frontImageDescription));
|
||||||
} else if (wantedImageType == ImageType.IMAGE_BACK) {
|
} else if (wantedImageType == ImageType.IMAGE_BACK) {
|
||||||
barcodeRenderTarget.setImageBitmap(backImageBitmap);
|
barcodeRenderTarget.setImageBitmap(backImageBitmap);
|
||||||
barcodeRenderTarget.setBackgroundColor(Color.TRANSPARENT);
|
barcodeRenderTarget.setBackgroundColor(Color.TRANSPARENT);
|
||||||
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
||||||
binding.cardIdView.setTextColor(MaterialColors.getColor(binding.cardIdView, com.google.android.material.R.attr.colorOnSurfaceVariant));
|
binding.mainImageDescription.setTextColor(MaterialColors.getColor(binding.mainImageDescription, com.google.android.material.R.attr.colorOnSurfaceVariant));
|
||||||
|
|
||||||
|
binding.mainImageDescription.setText(getString(R.string.backImageDescription));
|
||||||
barcodeRenderTarget.setContentDescription(getString(R.string.backImageDescription));
|
barcodeRenderTarget.setContentDescription(getString(R.string.backImageDescription));
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Unknown image type: " + wantedImageType);
|
throw new IllegalArgumentException("Unknown image type: " + wantedImageType);
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import android.content.Intent;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.database.CursorIndexOutOfBoundsException;
|
import android.database.CursorIndexOutOfBoundsException;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -23,26 +23,30 @@ import android.widget.Toast;
|
|||||||
import androidx.activity.OnBackPressedCallback;
|
import androidx.activity.OnBackPressedCallback;
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.view.ActionMode;
|
import androidx.appcompat.view.ActionMode;
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView;
|
||||||
import androidx.core.splashscreen.SplashScreen;
|
import androidx.core.splashscreen.SplashScreen;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.File;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import protect.card_locker.databinding.ContentMainBinding;
|
import protect.card_locker.databinding.ContentMainBinding;
|
||||||
import protect.card_locker.databinding.MainActivityBinding;
|
import protect.card_locker.databinding.MainActivityBinding;
|
||||||
import protect.card_locker.databinding.SortingOptionBinding;
|
import protect.card_locker.databinding.SortingOptionBinding;
|
||||||
|
import protect.card_locker.preferences.Settings;
|
||||||
import protect.card_locker.preferences.SettingsActivity;
|
import protect.card_locker.preferences.SettingsActivity;
|
||||||
|
|
||||||
public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
|
public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
|
||||||
@@ -52,6 +56,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
public static final String RESTART_ACTIVITY_INTENT = "restart_activity_intent";
|
public static final String RESTART_ACTIVITY_INTENT = "restart_activity_intent";
|
||||||
|
|
||||||
private static final int MEDIUM_SCALE_FACTOR_DIP = 460;
|
private static final int MEDIUM_SCALE_FACTOR_DIP = 460;
|
||||||
|
static final String STATE_SEARCH_QUERY = "SEARCH_QUERY";
|
||||||
|
|
||||||
private SQLiteDatabase mDatabase;
|
private SQLiteDatabase mDatabase;
|
||||||
private LoyaltyCardCursorAdapter mAdapter;
|
private LoyaltyCardCursorAdapter mAdapter;
|
||||||
@@ -59,6 +64,8 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
private SearchView mSearchView;
|
private SearchView mSearchView;
|
||||||
private int mLoyaltyCardCount = 0;
|
private int mLoyaltyCardCount = 0;
|
||||||
protected String mFilter = "";
|
protected String mFilter = "";
|
||||||
|
private String currentQuery = "";
|
||||||
|
private String finalQuery = "";
|
||||||
protected Object mGroup = null;
|
protected Object mGroup = null;
|
||||||
protected DBHelper.LoyaltyCardOrder mOrder = DBHelper.LoyaltyCardOrder.Alpha;
|
protected DBHelper.LoyaltyCardOrder mOrder = DBHelper.LoyaltyCardOrder.Alpha;
|
||||||
protected DBHelper.LoyaltyCardOrderDirection mOrderDirection = DBHelper.LoyaltyCardOrderDirection.Ascending;
|
protected DBHelper.LoyaltyCardOrderDirection mOrderDirection = DBHelper.LoyaltyCardOrderDirection.Ascending;
|
||||||
@@ -68,9 +75,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
private View mNoMatchingCardsText;
|
private View mNoMatchingCardsText;
|
||||||
private View mNoGroupCardsText;
|
private View mNoGroupCardsText;
|
||||||
private TabLayout groupsTabLayout;
|
private TabLayout groupsTabLayout;
|
||||||
|
|
||||||
private Runnable mUpdateLoyaltyCardListRunnable;
|
private Runnable mUpdateLoyaltyCardListRunnable;
|
||||||
|
|
||||||
private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
|
private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
|
||||||
private ActivityResultLauncher<Intent> mSettingsLauncher;
|
private ActivityResultLauncher<Intent> mSettingsLauncher;
|
||||||
|
|
||||||
@@ -150,6 +155,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||||
Log.d(TAG, "Archiving card: " + loyaltyCard.id);
|
Log.d(TAG, "Archiving card: " + loyaltyCard.id);
|
||||||
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 1);
|
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 1);
|
||||||
|
ShortcutHelper.removeShortcut(MainActivity.this, loyaltyCard.id);
|
||||||
updateLoyaltyCardList(false);
|
updateLoyaltyCardList(false);
|
||||||
inputMode.finish();
|
inputMode.finish();
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
@@ -194,12 +200,36 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle inputSavedInstanceState) {
|
protected void onCreate(Bundle inputSavedInstanceState) {
|
||||||
extractIntentFields(getIntent());
|
|
||||||
SplashScreen.installSplashScreen(this);
|
SplashScreen.installSplashScreen(this);
|
||||||
super.onCreate(inputSavedInstanceState);
|
super.onCreate(inputSavedInstanceState);
|
||||||
|
|
||||||
|
// Delete old cache files
|
||||||
|
// These could be temporary images for the cropper, temporary images in LoyaltyCard toBundle/writeParcel/ etc.
|
||||||
|
new Thread(() -> {
|
||||||
|
long twentyFourHoursAgo = System.currentTimeMillis() - (1000 * 60 * 60 * 24);
|
||||||
|
|
||||||
|
File[] tempFiles = getCacheDir().listFiles();
|
||||||
|
|
||||||
|
if (tempFiles == null) {
|
||||||
|
Log.e(TAG, "getCacheDir().listFiles() somehow returned null, this should never happen... Skipping cache cleanup...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (File file : tempFiles) {
|
||||||
|
if (file.lastModified() < twentyFourHoursAgo) {
|
||||||
|
if (!file.delete()) {
|
||||||
|
Log.w(TAG, "Failed to delete cache file " + file.getPath());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
// We should extract the share intent after we called the super.onCreate as it may need to spawn a dialog window and the app needs to be initialized to not crash
|
||||||
|
extractIntentFields(getIntent());
|
||||||
|
|
||||||
binding = MainActivityBinding.inflate(getLayoutInflater());
|
binding = MainActivityBinding.inflate(getLayoutInflater());
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
Utils.applyWindowInsets(binding.getRoot());
|
||||||
setSupportActionBar(binding.toolbar);
|
setSupportActionBar(binding.toolbar);
|
||||||
groupsTabLayout = binding.groups;
|
groupsTabLayout = binding.groups;
|
||||||
contentMainBinding = ContentMainBinding.bind(binding.include.getRoot());
|
contentMainBinding = ContentMainBinding.bind(binding.include.getRoot());
|
||||||
@@ -246,52 +276,15 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
mCardList.setAdapter(mAdapter);
|
mCardList.setAdapter(mAdapter);
|
||||||
registerForContextMenu(mCardList);
|
registerForContextMenu(mCardList);
|
||||||
|
|
||||||
mGroup = null;
|
|
||||||
updateLoyaltyCardList(true);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This was added for Huawei, but Huawei is just too much of a fucking pain.
|
|
||||||
* Just leaving this commented out if needed for the future idk
|
|
||||||
* https://twitter.com/SylvieLorxu/status/1379437902741012483
|
|
||||||
*
|
|
||||||
|
|
||||||
// Show privacy policy on first run
|
|
||||||
SharedPreferences privacyPolicyShownPref = getApplicationContext().getSharedPreferences(
|
|
||||||
getString(R.string.sharedpreference_privacy_policy_shown),
|
|
||||||
Context.MODE_PRIVATE);
|
|
||||||
|
|
||||||
|
|
||||||
if (privacyPolicyShownPref.getInt(getString(R.string.sharedpreference_privacy_policy_shown), 0) == 0) {
|
|
||||||
SharedPreferences.Editor privacyPolicyShownPrefEditor = privacyPolicyShownPref.edit();
|
|
||||||
privacyPolicyShownPrefEditor.putInt(getString(R.string.sharedpreference_privacy_policy_shown), 1);
|
|
||||||
privacyPolicyShownPrefEditor.apply();
|
|
||||||
|
|
||||||
new AlertDialog.Builder(this)
|
|
||||||
.setTitle(R.string.privacy_policy)
|
|
||||||
.setMessage(R.string.privacy_policy_popup_text)
|
|
||||||
.setPositiveButton(R.string.accept, null)
|
|
||||||
.setNegativeButton(R.string.privacy_policy, new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int whichButton) {
|
|
||||||
openPrivacyPolicy();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setIcon(android.R.drawable.ic_dialog_info)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
mBarcodeScannerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
mBarcodeScannerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||||
// Exit early if the user cancelled the scan (pressed back/home)
|
// Exit early if the user cancelled the scan (pressed back/home)
|
||||||
if (result.getResultCode() != RESULT_OK) {
|
if (result.getResultCode() != RESULT_OK) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intent = result.getData();
|
Intent editIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||||
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, this);
|
editIntent.putExtras(result.getData().getExtras());
|
||||||
|
startActivity(editIntent);
|
||||||
Bundle inputBundle = intent.getExtras();
|
|
||||||
String group = inputBundle != null ? inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) : null;
|
|
||||||
processBarcodeValues(barcodeValues, group);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
mSettingsLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
mSettingsLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||||
@@ -327,7 +320,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
if (mSearchView != null && !mSearchView.isIconified()) {
|
if (mSearchView != null && !mSearchView.isIconified()) {
|
||||||
mFilter = mSearchView.getQuery().toString();
|
mFilter = mSearchView.getQuery().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start of active tab logic
|
// Start of active tab logic
|
||||||
updateTabGroups(groupsTabLayout);
|
updateTabGroups(groupsTabLayout);
|
||||||
|
|
||||||
@@ -385,6 +377,12 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
mBarcodeScannerLauncher.launch(intent);
|
mBarcodeScannerLauncher.launch(intent);
|
||||||
});
|
});
|
||||||
addButton.bringToFront();
|
addButton.bringToFront();
|
||||||
|
|
||||||
|
var layoutManager = (GridLayoutManager) mCardList.getLayoutManager();
|
||||||
|
if (layoutManager != null) {
|
||||||
|
var settings = new Settings(this);
|
||||||
|
layoutManager.setSpanCount(settings.getPreferredColumnCount());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayCardSetupOptions(Menu menu, boolean shouldShow) {
|
private void displayCardSetupOptions(Menu menu, boolean shouldShow) {
|
||||||
@@ -446,64 +444,83 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processBarcodeValues(BarcodeValues barcodeValues, String group) {
|
private void processParseResultList(List<ParseResult> parseResultList, String group, boolean closeAppOnNoBarcode) {
|
||||||
if (barcodeValues.isEmpty()) {
|
if (parseResultList.isEmpty()) {
|
||||||
throw new IllegalArgumentException("barcodesValues may not be empty");
|
throw new IllegalArgumentException("parseResultList may not be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
Utils.makeUserChooseParseResultFromList(MainActivity.this, parseResultList, new ParseResultListDisambiguatorCallback() {
|
||||||
Bundle newBundle = new Bundle();
|
@Override
|
||||||
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
|
public void onUserChoseParseResult(ParseResult parseResult) {
|
||||||
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
|
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||||
if (group != null) {
|
Bundle bundle = parseResult.toLoyaltyCardBundle(MainActivity.this);
|
||||||
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
|
if (group != null) {
|
||||||
}
|
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
|
||||||
newIntent.putExtras(newBundle);
|
}
|
||||||
startActivity(newIntent);
|
intent.putExtras(bundle);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUserDismissedSelector() {
|
||||||
|
if (closeAppOnNoBarcode) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSharedIntent(Intent intent) {
|
private void onSharedIntent(Intent intent) {
|
||||||
String receivedAction = intent.getAction();
|
String receivedAction = intent.getAction();
|
||||||
String receivedType = intent.getType();
|
String receivedType = intent.getType();
|
||||||
|
|
||||||
// Check if an image was shared to us
|
if (receivedAction == null || receivedType == null) {
|
||||||
if (Intent.ACTION_SEND.equals(receivedAction)) {
|
return;
|
||||||
if (!receivedType.startsWith("image/")) {
|
}
|
||||||
|
|
||||||
|
List<ParseResult> parseResultList;
|
||||||
|
|
||||||
|
// Check for shared text
|
||||||
|
if (receivedAction.equals(Intent.ACTION_SEND) && receivedType.equals("text/plain")) {
|
||||||
|
LoyaltyCard loyaltyCard = new LoyaltyCard();
|
||||||
|
loyaltyCard.setCardId(intent.getStringExtra(Intent.EXTRA_TEXT));
|
||||||
|
parseResultList = Collections.singletonList(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
|
||||||
|
} else {
|
||||||
|
// Parse whatever file was sent, regardless of opening or sharing
|
||||||
|
Uri data;
|
||||||
|
if (receivedAction.equals(Intent.ACTION_VIEW)) {
|
||||||
|
data = intent.getData();
|
||||||
|
} else if (receivedAction.equals(Intent.ACTION_SEND)) {
|
||||||
|
data = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Wrong action type to parse intent");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receivedType.startsWith("image/")) {
|
||||||
|
parseResultList = Utils.retrieveBarcodesFromImage(this, data);
|
||||||
|
} else if (receivedType.equals("application/pdf")) {
|
||||||
|
parseResultList = Utils.retrieveBarcodesFromPdf(this, data);
|
||||||
|
} else if (Arrays.asList("application/vnd.apple.pkpass", "application/vnd-com.apple.pkpass").contains(receivedType)) {
|
||||||
|
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data);
|
||||||
|
} else if (receivedType.equals("application/vnd.espass-espass")) {
|
||||||
|
// FIXME: espass is not pkpass
|
||||||
|
// However, several users stated in https://github.com/CatimaLoyalty/Android/issues/2197 that the formats are extremely similar to the point they could rename an .espass file to .pkpass and have it imported
|
||||||
|
// So it makes sense to "unofficially" treat it as a PKPASS for now, even though not completely correct
|
||||||
|
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data);
|
||||||
|
} else {
|
||||||
Log.e(TAG, "Wrong mime-type");
|
Log.e(TAG, "Wrong mime-type");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BarcodeValues barcodeValues;
|
|
||||||
Bitmap bitmap;
|
|
||||||
|
|
||||||
Uri data = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
|
||||||
if (data == null) {
|
|
||||||
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
bitmap = Utils.retrieveImageFromUri(this, data);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Error getting data from image file");
|
|
||||||
e.printStackTrace();
|
|
||||||
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
barcodeValues = Utils.getBarcodeFromBitmap(bitmap);
|
|
||||||
|
|
||||||
if (barcodeValues.isEmpty()) {
|
|
||||||
Log.i(TAG, "No barcode found in image file");
|
|
||||||
Toast.makeText(this, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
processBarcodeValues(barcodeValues, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Give up if we should parse but there is nothing to parse
|
||||||
|
if (parseResultList == null || parseResultList.isEmpty()) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
processParseResultList(parseResultList, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void extractIntentFields(Intent intent) {
|
private void extractIntentFields(Intent intent) {
|
||||||
@@ -537,6 +554,24 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
// Saving currentQuery to finalQuery for user, this will be used to restore search history, happens when user clicks a card from list
|
||||||
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
finalQuery = currentQuery;
|
||||||
|
// Putting the query also into outState for later use in onRestoreInstanceState when rotating screen
|
||||||
|
if (mSearchView != null) {
|
||||||
|
outState.putString(STATE_SEARCH_QUERY, finalQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
// Restoring instance state when rotation of screen happens with the goal to restore search query for user
|
||||||
|
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||||
|
super.onRestoreInstanceState(savedInstanceState);
|
||||||
|
finalQuery = savedInstanceState.getString(STATE_SEARCH_QUERY, "");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu inputMenu) {
|
public boolean onCreateOptionsMenu(Menu inputMenu) {
|
||||||
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
|
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
|
||||||
@@ -545,15 +580,42 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
|
|
||||||
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
|
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
|
||||||
if (searchManager != null) {
|
if (searchManager != null) {
|
||||||
mSearchView = (SearchView) inputMenu.findItem(R.id.action_search).getActionView();
|
MenuItem searchMenuItem = inputMenu.findItem(R.id.action_search);
|
||||||
|
mSearchView = (SearchView) searchMenuItem.getActionView();
|
||||||
mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
|
mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
|
||||||
mSearchView.setSubmitButtonEnabled(false);
|
mSearchView.setSubmitButtonEnabled(false);
|
||||||
|
|
||||||
mSearchView.setOnCloseListener(() -> {
|
mSearchView.setOnCloseListener(() -> {
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On Android 13 and later, pressing Back while the search view is open hides the keyboard
|
||||||
|
* and collapses the search view at the same time.
|
||||||
|
* This brings back the old behavior on Android 12 and lower: pressing Back once
|
||||||
|
* hides the keyboard, press again while keyboard is hidden to collapse the search view.
|
||||||
|
*/
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
searchMenuItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onMenuItemActionExpand(@NonNull MenuItem item) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMenuItemActionCollapse(@NonNull MenuItem item) {
|
||||||
|
if (mSearchView.hasFocus()) {
|
||||||
|
mSearchView.clearFocus();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentQuery = "";
|
||||||
|
mFilter = "";
|
||||||
|
updateLoyaltyCardList(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onQueryTextSubmit(String query) {
|
public boolean onQueryTextSubmit(String query) {
|
||||||
@@ -563,7 +625,21 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
@Override
|
@Override
|
||||||
public boolean onQueryTextChange(String newText) {
|
public boolean onQueryTextChange(String newText) {
|
||||||
mFilter = newText;
|
mFilter = newText;
|
||||||
|
// New logic to ensure search history after coming back from picked card - user will see the last search query
|
||||||
|
if (newText.isEmpty()) {
|
||||||
|
if(!finalQuery.isEmpty()){
|
||||||
|
// Setting the query text for user after coming back from picked card from finalQuery
|
||||||
|
mSearchView.setQuery(finalQuery, false);
|
||||||
|
}
|
||||||
|
else if(!currentQuery.isEmpty()){
|
||||||
|
// Else if is needed in case user deletes search - expected behaviour is to show all cards
|
||||||
|
currentQuery = "";
|
||||||
|
mSearchView.setQuery(currentQuery, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Setting search query each time user changes the text in search to temporary variable to be used later in finalQuery String which will be used to restore search history
|
||||||
|
currentQuery = newText;
|
||||||
|
}
|
||||||
TabLayout.Tab currentTab = groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition());
|
TabLayout.Tab currentTab = groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition());
|
||||||
mGroup = currentTab != null ? currentTab.getTag() : null;
|
mGroup = currentTab != null ? currentTab.getTag() : null;
|
||||||
|
|
||||||
@@ -572,6 +648,14 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Check if we came from a picked card back to search, in that case we want to show the search view with previous search query
|
||||||
|
if(!finalQuery.isEmpty()){
|
||||||
|
// Expand the search view to show the query
|
||||||
|
searchMenuItem.expandActionView();
|
||||||
|
// Setting the query text to empty String due to behaviour of onQueryTextChange after coming back from picked card - onQueryTextChange is called automatically without users interaction
|
||||||
|
finalQuery = "";
|
||||||
|
mSearchView.setQuery(currentQuery, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(inputMenu);
|
return super.onCreateOptionsMenu(inputMenu);
|
||||||
@@ -788,18 +872,16 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
Intent intent = new Intent(this, LoyaltyCardViewActivity.class);
|
Intent intent = new Intent(this, LoyaltyCardViewActivity.class);
|
||||||
intent.setAction("");
|
intent.setAction("");
|
||||||
final Bundle b = new Bundle();
|
final Bundle b = new Bundle();
|
||||||
b.putInt("id", loyaltyCard.id);
|
b.putInt(LoyaltyCardViewActivity.BUNDLE_ID, loyaltyCard.id);
|
||||||
|
|
||||||
ArrayList<Integer> cardList = new ArrayList<>();
|
ArrayList<Integer> cardList = new ArrayList<>();
|
||||||
for (int i = 0; i < mAdapter.getItemCount(); i++) {
|
for (int i = 0; i < mAdapter.getItemCount(); i++) {
|
||||||
cardList.add(mAdapter.getCard(i).id);
|
cardList.add(mAdapter.getCard(i).id);
|
||||||
}
|
}
|
||||||
|
|
||||||
b.putIntegerArrayList("cardList", cardList);
|
b.putIntegerArrayList(LoyaltyCardViewActivity.BUNDLE_CARDLIST, cardList);
|
||||||
intent.putExtras(b);
|
intent.putExtras(b);
|
||||||
|
|
||||||
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard);
|
|
||||||
|
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
|
|||||||
super.onCreate(inputSavedInstanceState);
|
super.onCreate(inputSavedInstanceState);
|
||||||
binding = ActivityManageGroupBinding.inflate(getLayoutInflater());
|
binding = ActivityManageGroupBinding.inflate(getLayoutInflater());
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
Utils.applyWindowInsets(binding.getRoot());
|
||||||
Toolbar toolbar = binding.toolbar;
|
Toolbar toolbar = binding.toolbar;
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class ManageGroupCursorAdapter extends LoyaltyCardCursorAdapter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(LoyaltyCardListItemViewHolder inputHolder, Cursor inputCursor) {
|
public void onBindViewHolder(LoyaltyCardListItemViewHolder inputHolder, Cursor inputCursor) {
|
||||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(inputCursor);
|
LoyaltyCard loyaltyCard = LoyaltyCard.fromCursor(mContext, inputCursor);
|
||||||
Boolean overlayValue = mInGroupOverlay.get(loyaltyCard.id);
|
Boolean overlayValue = mInGroupOverlay.get(loyaltyCard.id);
|
||||||
if ((overlayValue != null ? overlayValue : isLoyaltyCardInGroup(loyaltyCard.id))) {
|
if ((overlayValue != null ? overlayValue : isLoyaltyCardInGroup(loyaltyCard.id))) {
|
||||||
mAnimationItemsIndex.put(inputCursor.getPosition(), true);
|
mAnimationItemsIndex.put(inputCursor.getPosition(), true);
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
|
|||||||
binding = ManageGroupsActivityBinding.inflate(getLayoutInflater());
|
binding = ManageGroupsActivityBinding.inflate(getLayoutInflater());
|
||||||
setTitle(R.string.groups);
|
setTitle(R.string.groups);
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
Utils.applyWindowInsets(binding.getRoot());
|
||||||
Toolbar toolbar = binding.toolbar;
|
Toolbar toolbar = binding.toolbar;
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
enableToolbarBackButton();
|
enableToolbarBackButton();
|
||||||
|
|||||||
31
app/src/main/java/protect/card_locker/ParseResult.kt
Normal file
31
app/src/main/java/protect/card_locker/ParseResult.kt
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package protect.card_locker
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
|
||||||
|
class ParseResult(
|
||||||
|
val parseResultType: ParseResultType,
|
||||||
|
val loyaltyCard: LoyaltyCard) {
|
||||||
|
var note: String? = null
|
||||||
|
|
||||||
|
fun toLoyaltyCardBundle(context: Context): Bundle {
|
||||||
|
when (parseResultType) {
|
||||||
|
ParseResultType.FULL -> return loyaltyCard.toBundle(context, listOf())
|
||||||
|
ParseResultType.BARCODE_ONLY -> {
|
||||||
|
val defaultLoyaltyCard = LoyaltyCard()
|
||||||
|
defaultLoyaltyCard.setBarcodeId(null)
|
||||||
|
defaultLoyaltyCard.setBarcodeType(loyaltyCard.barcodeType)
|
||||||
|
defaultLoyaltyCard.setCardId(loyaltyCard.cardId)
|
||||||
|
|
||||||
|
return defaultLoyaltyCard.toBundle(
|
||||||
|
context,
|
||||||
|
listOf(
|
||||||
|
LoyaltyCard.BUNDLE_LOYALTY_CARD_BARCODE_ID,
|
||||||
|
LoyaltyCard.BUNDLE_LOYALTY_CARD_BARCODE_TYPE,
|
||||||
|
LoyaltyCard.BUNDLE_LOYALTY_CARD_CARD_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package protect.card_locker;
|
||||||
|
|
||||||
|
public interface ParseResultListDisambiguatorCallback {
|
||||||
|
void onUserChoseParseResult(ParseResult parseResult);
|
||||||
|
void onUserDismissedSelector();
|
||||||
|
}
|
||||||
6
app/src/main/java/protect/card_locker/ParseResultType.kt
Normal file
6
app/src/main/java/protect/card_locker/ParseResultType.kt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package protect.card_locker
|
||||||
|
|
||||||
|
enum class ParseResultType {
|
||||||
|
FULL,
|
||||||
|
BARCODE_ONLY
|
||||||
|
}
|
||||||
429
app/src/main/java/protect/card_locker/PkpassParser.kt
Normal file
429
app/src/main/java/protect/card_locker/PkpassParser.kt
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
package protect.card_locker
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.ArrayMap
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.zxing.BarcodeFormat
|
||||||
|
import net.lingala.zip4j.io.inputstream.ZipInputStream
|
||||||
|
import net.lingala.zip4j.model.LocalFileHeader
|
||||||
|
import org.json.JSONException
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.io.IOException
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.text.ParseException
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import java.time.format.DateTimeParseException
|
||||||
|
import java.util.Currency
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
class PkpassParser(context: Context, uri: Uri?) {
|
||||||
|
private var mContext = context
|
||||||
|
|
||||||
|
private var translations: ArrayMap<String, Map<String, String>> = ArrayMap()
|
||||||
|
|
||||||
|
private var passContent: JSONObject? = null
|
||||||
|
|
||||||
|
private var store: String? = null
|
||||||
|
private var note: String? = null
|
||||||
|
private var validFrom: Date? = null
|
||||||
|
private var expiry: Date? = null
|
||||||
|
private val balance: BigDecimal = BigDecimal(0)
|
||||||
|
private val balanceType: Currency? = null
|
||||||
|
private var cardId: String? = null
|
||||||
|
private var barcodeId: String? = null
|
||||||
|
private var barcodeType: CatimaBarcode? = null
|
||||||
|
private var headerColor: Int? = null
|
||||||
|
private val starStatus = 0
|
||||||
|
private val lastUsed: Long = 0
|
||||||
|
private val zoomLevel = DBHelper.DEFAULT_ZOOM_LEVEL
|
||||||
|
private var archiveStatus = 0
|
||||||
|
|
||||||
|
var image: Bitmap? = null
|
||||||
|
private set
|
||||||
|
private var logoSize = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (passContent != null) {
|
||||||
|
throw IllegalStateException("Pkpass instance already initialized!")
|
||||||
|
}
|
||||||
|
|
||||||
|
mContext = context
|
||||||
|
|
||||||
|
Log.i(TAG, "Received Pkpass file")
|
||||||
|
if (uri == null) {
|
||||||
|
Log.e(TAG, "Uri did not contain any data")
|
||||||
|
throw IOException(context.getString(R.string.errorReadingFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mContext.contentResolver.openInputStream(uri).use { inputStream ->
|
||||||
|
ZipInputStream(inputStream).use { zipInputStream ->
|
||||||
|
var localFileHeader: LocalFileHeader
|
||||||
|
while ((zipInputStream.nextEntry.also { localFileHeader = it }) != null) {
|
||||||
|
// Ignore directories
|
||||||
|
if (localFileHeader.isDirectory) continue
|
||||||
|
|
||||||
|
// We assume there are three options, as per spec:
|
||||||
|
// language.lproj/pass.strings
|
||||||
|
// file.extension
|
||||||
|
// More directories are ignored
|
||||||
|
val filenameParts = localFileHeader.fileName.split('/')
|
||||||
|
if (filenameParts.size > 2) {
|
||||||
|
continue
|
||||||
|
} else if (filenameParts.size == 2) {
|
||||||
|
// Doesn't seem like a language directory, ignore
|
||||||
|
if (!filenameParts[0].endsWith(".lproj")) continue
|
||||||
|
|
||||||
|
val locale = filenameParts[0].removeSuffix(".lproj")
|
||||||
|
|
||||||
|
translations[locale] = parseLanguageStrings(ZipUtils.read(zipInputStream))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a language, parse as normal files
|
||||||
|
when (localFileHeader.fileName) {
|
||||||
|
"logo.png" -> loadImageIfBiggerSize(1, zipInputStream)
|
||||||
|
"logo@2x.png" -> loadImageIfBiggerSize(2, zipInputStream)
|
||||||
|
"logo@3x.png" -> loadImageIfBiggerSize(3, zipInputStream)
|
||||||
|
"pass.json" -> passContent = ZipUtils.readJSON(zipInputStream) // Parse this last, so we're sure we have all language info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNotNull(passContent) { "File lacks pass.json" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: FileNotFoundException) {
|
||||||
|
throw IOException(mContext.getString(R.string.errorReadingFile))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun listLocales(): List<String> {
|
||||||
|
return translations.keys.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toLoyaltyCard(locale: String?): LoyaltyCard {
|
||||||
|
parsePassJSON(checkNotNull(passContent) { "Pkpass instance not yet initialized!" }, locale)
|
||||||
|
|
||||||
|
return LoyaltyCard(
|
||||||
|
-1,
|
||||||
|
store,
|
||||||
|
note,
|
||||||
|
validFrom,
|
||||||
|
expiry,
|
||||||
|
balance,
|
||||||
|
balanceType,
|
||||||
|
cardId,
|
||||||
|
barcodeId,
|
||||||
|
barcodeType,
|
||||||
|
headerColor,
|
||||||
|
starStatus,
|
||||||
|
lastUsed,
|
||||||
|
zoomLevel,
|
||||||
|
archiveStatus,
|
||||||
|
image,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getTranslation(string: String, locale: String?): String {
|
||||||
|
if (locale == null) {
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
val localeStrings = translations[locale]
|
||||||
|
|
||||||
|
return localeStrings?.get(string) ?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadImageIfBiggerSize(fileLogoSize: Int, zipInputStream: ZipInputStream) {
|
||||||
|
if (logoSize < fileLogoSize) {
|
||||||
|
image = ZipUtils.readImage(zipInputStream)
|
||||||
|
logoSize = fileLogoSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseColor(color: String): Int? {
|
||||||
|
// First, try formats supported by Android natively
|
||||||
|
try {
|
||||||
|
return Color.parseColor(color)
|
||||||
|
} catch (ignored: IllegalArgumentException) {}
|
||||||
|
|
||||||
|
// If that didn't work, try parsing it as a rbg(0,0,255) value
|
||||||
|
val red: Int;
|
||||||
|
val green: Int;
|
||||||
|
val blue: Int;
|
||||||
|
|
||||||
|
// Parse rgb(0,0,0) string
|
||||||
|
val rgbInfo = Regex("""^rgb\(\s*(?<red>\d+)\s*,\s*(?<green>\d+)\s*,\s*(?<blue>\d+)\s*\)$""").find(color)
|
||||||
|
if (rgbInfo == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to integers
|
||||||
|
try {
|
||||||
|
red = rgbInfo.groups[1]!!.value.toInt()
|
||||||
|
green = rgbInfo.groups[2]!!.value.toInt()
|
||||||
|
blue = rgbInfo.groups[3]!!.value.toInt()
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure everything is in a valid range as Color.rgb does not do range checks
|
||||||
|
if (red < 0 || red > 255) return null
|
||||||
|
if (green < 0 || green > 255) return null
|
||||||
|
if (blue < 0 || blue > 255) return null
|
||||||
|
|
||||||
|
return Color.rgb(red, green, blue)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseDateTime(dateTime: String): Date {
|
||||||
|
return Date.from(ZonedDateTime.parse(dateTime).toInstant())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseLanguageStrings(data: String): Map<String, String> {
|
||||||
|
val output = ArrayMap<String, String>()
|
||||||
|
|
||||||
|
// Translations look like this:
|
||||||
|
// "key_name" = "Translated value";
|
||||||
|
//
|
||||||
|
// However, "Translated value" may be multiple lines and may contain " (however, it'll be escaped)
|
||||||
|
var translationLine = StringBuilder()
|
||||||
|
|
||||||
|
for (line in data.lines()) {
|
||||||
|
translationLine.append(line)
|
||||||
|
|
||||||
|
// Make sure we don't have a false ending (this is the escaped double quote: \";)
|
||||||
|
if (!line.endsWith("\\\";") and line.endsWith("\";")) {
|
||||||
|
// We reached a translation ending, time to parse it
|
||||||
|
|
||||||
|
// 1. Split into key and value
|
||||||
|
// 2. Remove cruft of each
|
||||||
|
// 3. Clean up escape sequences
|
||||||
|
val keyValue = translationLine.toString().split("=", ignoreCase = false, limit = 2)
|
||||||
|
val key = keyValue[0].trim().removePrefix("\"").removeSuffix("\"")
|
||||||
|
val value = keyValue[1].trim().removePrefix("\"").removeSuffix("\";").replace("\\", "")
|
||||||
|
|
||||||
|
output[key] = value
|
||||||
|
|
||||||
|
translationLine = StringBuilder()
|
||||||
|
} else {
|
||||||
|
translationLine.append("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parsePassJSON(jsonObject: JSONObject, locale: String?) {
|
||||||
|
if (jsonObject.getInt("formatVersion") != 1) {
|
||||||
|
throw IllegalArgumentException(mContext.getString(R.string.unsupportedFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer logoText for store, it's generally shorter
|
||||||
|
try {
|
||||||
|
store = jsonObject.getString("logoText")
|
||||||
|
} catch (ignored: JSONException) {}
|
||||||
|
|
||||||
|
if (store.isNullOrEmpty()) {
|
||||||
|
store = jsonObject.getString("organizationName")
|
||||||
|
}
|
||||||
|
|
||||||
|
val noteText = StringBuilder()
|
||||||
|
noteText.append(getTranslation(jsonObject.getString("description"), locale))
|
||||||
|
|
||||||
|
try {
|
||||||
|
validFrom = parseDateTime(jsonObject.getString("relevantDate"))
|
||||||
|
} catch (ignored: JSONException) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
expiry = parseDateTime(jsonObject.getString("expirationDate"))
|
||||||
|
} catch (ignored: JSONException) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
headerColor = parseColor(jsonObject.getString("backgroundColor"))
|
||||||
|
} catch (ignored: JSONException) {}
|
||||||
|
|
||||||
|
var pkPassHasBarcodes = false
|
||||||
|
var validBarcodeFound = false
|
||||||
|
|
||||||
|
// Create a list of possible barcodes
|
||||||
|
val barcodes = ArrayList<JSONObject>()
|
||||||
|
|
||||||
|
// Append the non-deprecated entries
|
||||||
|
try {
|
||||||
|
val foundInBarcodesField = jsonObject.getJSONArray("barcodes")
|
||||||
|
|
||||||
|
for (i in 0 until foundInBarcodesField.length()) {
|
||||||
|
barcodes.add(foundInBarcodesField.getJSONObject(i))
|
||||||
|
}
|
||||||
|
} catch (ignored: JSONException) {}
|
||||||
|
|
||||||
|
// Append the deprecated entry if it exists
|
||||||
|
try {
|
||||||
|
barcodes.add(jsonObject.getJSONObject("barcode"))
|
||||||
|
} catch (ignored: JSONException) {}
|
||||||
|
|
||||||
|
for (barcode in barcodes) {
|
||||||
|
pkPassHasBarcodes = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
parsePassJSONBarcodeField(barcode)
|
||||||
|
|
||||||
|
validBarcodeFound = true
|
||||||
|
break
|
||||||
|
} catch (ignored: IllegalArgumentException) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pkPassHasBarcodes && !validBarcodeFound) {
|
||||||
|
throw FormatException(mContext.getString(R.string.errorReadingFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
// An used card being "archived" probably is the most sensible way to map "voided"
|
||||||
|
archiveStatus = try {
|
||||||
|
if (jsonObject.getBoolean("voided")) 1 else 0
|
||||||
|
} catch (ignored: JSONException) {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append type-specific info to the pass
|
||||||
|
|
||||||
|
// Find the relevant pass type and parse it
|
||||||
|
for (passType in listOf("boardingPass", "coupon", "eventTicket", "generic")) {
|
||||||
|
try {
|
||||||
|
var extraText = parsePassJSONPassFields(
|
||||||
|
jsonObject.getJSONObject(passType),
|
||||||
|
locale
|
||||||
|
)
|
||||||
|
|
||||||
|
noteText.append("\n\n")
|
||||||
|
noteText.append(extraText)
|
||||||
|
|
||||||
|
break
|
||||||
|
} catch (ignored: JSONException) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
note = noteText.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return success or failure */
|
||||||
|
private fun parsePassJSONBarcodeField(barcodeInfo: JSONObject) {
|
||||||
|
val format = barcodeInfo.getString("format")
|
||||||
|
|
||||||
|
// We only need to check these 4 formats as no other options are valid in the PkPass spec
|
||||||
|
barcodeType = when(format) {
|
||||||
|
"PKBarcodeFormatQR" -> CatimaBarcode.fromBarcode(BarcodeFormat.QR_CODE)
|
||||||
|
"PKBarcodeFormatPDF417" -> CatimaBarcode.fromBarcode(BarcodeFormat.PDF_417)
|
||||||
|
"PKBarcodeFormatAztec" -> CatimaBarcode.fromBarcode(BarcodeFormat.AZTEC)
|
||||||
|
"PKBarcodeFormatCode128" -> CatimaBarcode.fromBarcode(BarcodeFormat.CODE_128)
|
||||||
|
else -> throw IllegalArgumentException("No valid barcode type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: We probably need to do something with the messageEncoding field
|
||||||
|
try {
|
||||||
|
cardId = barcodeInfo.getString("altText")
|
||||||
|
barcodeId = barcodeInfo.getString("message")
|
||||||
|
} catch (ignored: JSONException) {
|
||||||
|
cardId = barcodeInfo.getString("message")
|
||||||
|
barcodeId = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't set barcodeId if it's the same as cardId
|
||||||
|
if (cardId == barcodeId) {
|
||||||
|
barcodeId = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parsePassJSONPassFields(fieldsParent: JSONObject, locale: String?): String {
|
||||||
|
// These fields contain a lot of info on where we're supposed to display them, but Catima doesn't really have anything for that
|
||||||
|
// So for now, throw them all into the description field in a logical order
|
||||||
|
val noteContents: MutableList<String> = ArrayList()
|
||||||
|
|
||||||
|
// Collect all the groups of fields that exist
|
||||||
|
for (fieldType in listOf("headerFields", "primaryFields", "secondaryFields", "auxiliaryFields", "backFields")) {
|
||||||
|
val content = StringBuilder()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val fieldArray = fieldsParent.getJSONArray(fieldType)
|
||||||
|
for (i in 0 until fieldArray.length()) {
|
||||||
|
val entry = fieldArray.getJSONObject(i)
|
||||||
|
|
||||||
|
content.append(parsePassJSONPassField(entry, locale))
|
||||||
|
|
||||||
|
// If this is not the last part, add spacing on the end
|
||||||
|
if (i < (fieldArray.length() - 1)) {
|
||||||
|
content.append("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ignore: JSONException) {
|
||||||
|
} catch (ignore: ParseException) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.isNotEmpty()) {
|
||||||
|
noteContents.add(content.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge all field groups together, one paragraph for field group
|
||||||
|
val output = StringBuilder()
|
||||||
|
|
||||||
|
for (i in 0 until noteContents.size) {
|
||||||
|
output.append(noteContents[i])
|
||||||
|
|
||||||
|
// If this is not the last part, add newlines to separate
|
||||||
|
if (i < (noteContents.size - 1)) {
|
||||||
|
output.append("\n\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parsePassJSONPassField(field: JSONObject, locale: String?): String {
|
||||||
|
// Value may be a localizable string, a date or a number. So let's try to parse it as a date first
|
||||||
|
|
||||||
|
var value = getTranslation(field.getString("value"), locale)
|
||||||
|
try {
|
||||||
|
value = DateFormat.getDateTimeInstance().format(parseDateTime(value))
|
||||||
|
} catch (ignored: DateTimeParseException) {
|
||||||
|
// It's fine if it's not a date
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Use the Android thing for formatted strings here
|
||||||
|
if (field.has("currencyCode")) {
|
||||||
|
val valueCurrency = Currency.getInstance(field.getString("currencyCode"))
|
||||||
|
|
||||||
|
value = Utils.formatBalance(
|
||||||
|
mContext,
|
||||||
|
Utils.parseBalance(value, valueCurrency),
|
||||||
|
valueCurrency
|
||||||
|
)
|
||||||
|
} else if (field.has("numberStyle")) {
|
||||||
|
if (field.getString("numberStyle") == "PKNumberStylePercent") {
|
||||||
|
// FIXME: Android formatting string
|
||||||
|
value = "${value}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val label = getTranslation(field.getString("label"), locale)
|
||||||
|
|
||||||
|
if (label.isNotEmpty()) {
|
||||||
|
return "$label: $value"
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "Catima"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,8 @@ import android.view.ViewGroup;
|
|||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ListAdapter;
|
||||||
|
import android.widget.SimpleAdapter;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@@ -42,6 +44,8 @@ import com.journeyapps.barcodescanner.BarcodeResult;
|
|||||||
import com.journeyapps.barcodescanner.CaptureManager;
|
import com.journeyapps.barcodescanner.CaptureManager;
|
||||||
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import protect.card_locker.databinding.CustomBarcodeScannerBinding;
|
import protect.card_locker.databinding.CustomBarcodeScannerBinding;
|
||||||
@@ -62,6 +66,8 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
private static final int COMPAT_SCALE_FACTOR_DIP = 320;
|
private static final int COMPAT_SCALE_FACTOR_DIP = 320;
|
||||||
|
|
||||||
private static final int PERMISSION_SCAN_ADD_FROM_IMAGE = 100;
|
private static final int PERMISSION_SCAN_ADD_FROM_IMAGE = 100;
|
||||||
|
private static final int PERMISSION_SCAN_ADD_FROM_PDF = 101;
|
||||||
|
private static final int PERMISSION_SCAN_ADD_FROM_PKPASS = 102;
|
||||||
|
|
||||||
private CaptureManager capture;
|
private CaptureManager capture;
|
||||||
private DecoratedBarcodeView barcodeScannerView;
|
private DecoratedBarcodeView barcodeScannerView;
|
||||||
@@ -73,13 +79,16 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
private ActivityResultLauncher<Intent> manualAddLauncher;
|
private ActivityResultLauncher<Intent> manualAddLauncher;
|
||||||
// can't use the pre-made contract because that launches the file manager for image type instead of gallery
|
// can't use the pre-made contract because that launches the file manager for image type instead of gallery
|
||||||
private ActivityResultLauncher<Intent> photoPickerLauncher;
|
private ActivityResultLauncher<Intent> photoPickerLauncher;
|
||||||
|
private ActivityResultLauncher<Intent> pdfPickerLauncher;
|
||||||
|
private ActivityResultLauncher<Intent> pkpassPickerLauncher;
|
||||||
|
|
||||||
static final String STATE_SCANNER_ACTIVE = "scannerActive";
|
static final String STATE_SCANNER_ACTIVE = "scannerActive";
|
||||||
private boolean mScannerActive = true;
|
private boolean mScannerActive = true;
|
||||||
|
private boolean mHasError = false;
|
||||||
|
|
||||||
private void extractIntentFields(Intent intent) {
|
private void extractIntentFields(Intent intent) {
|
||||||
final Bundle b = intent.getExtras();
|
final Bundle b = intent.getExtras();
|
||||||
cardId = b != null ? b.getString(LoyaltyCardEditActivity.BUNDLE_CARDID) : null;
|
cardId = b != null ? b.getString(LoyaltyCard.BUNDLE_LOYALTY_CARD_CARD_ID) : null;
|
||||||
addGroup = b != null ? b.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) : null;
|
addGroup = b != null ? b.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) : null;
|
||||||
Log.d(TAG, "Scan activity: id=" + cardId);
|
Log.d(TAG, "Scan activity: id=" + cardId);
|
||||||
}
|
}
|
||||||
@@ -91,6 +100,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
customBarcodeScannerBinding = CustomBarcodeScannerBinding.bind(binding.zxingBarcodeScanner);
|
customBarcodeScannerBinding = CustomBarcodeScannerBinding.bind(binding.zxingBarcodeScanner);
|
||||||
setTitle(R.string.scanCardBarcode);
|
setTitle(R.string.scanCardBarcode);
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
Utils.applyWindowInsets(binding.getRoot());
|
||||||
Toolbar toolbar = binding.toolbar;
|
Toolbar toolbar = binding.toolbar;
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
enableToolbarBackButton();
|
enableToolbarBackButton();
|
||||||
@@ -99,17 +109,47 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
|
|
||||||
manualAddLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.SELECT_BARCODE_REQUEST, result.getResultCode(), result.getData()));
|
manualAddLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.SELECT_BARCODE_REQUEST, result.getResultCode(), result.getData()));
|
||||||
photoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_IMAGE_FILE, result.getResultCode(), result.getData()));
|
photoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_IMAGE_FILE, result.getResultCode(), result.getData()));
|
||||||
|
pdfPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_PDF_FILE, result.getResultCode(), result.getData()));
|
||||||
|
pkpassPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_PKPASS_FILE, result.getResultCode(), result.getData()));
|
||||||
customBarcodeScannerBinding.fabOtherOptions.setOnClickListener(view -> {
|
customBarcodeScannerBinding.fabOtherOptions.setOnClickListener(view -> {
|
||||||
setScannerActive(false);
|
setScannerActive(false);
|
||||||
|
|
||||||
|
ArrayList<HashMap<String, Object>> list = new ArrayList<>();
|
||||||
|
String[] texts = new String[]{
|
||||||
|
getString(R.string.addWithoutBarcode),
|
||||||
|
getString(R.string.addManually),
|
||||||
|
getString(R.string.addFromImage),
|
||||||
|
getString(R.string.addFromPdfFile),
|
||||||
|
getString(R.string.addFromPkpass)
|
||||||
|
};
|
||||||
|
Object[] icons = new Object[]{
|
||||||
|
R.drawable.baseline_block_24,
|
||||||
|
R.drawable.ic_edit,
|
||||||
|
R.drawable.baseline_image_24,
|
||||||
|
R.drawable.baseline_picture_as_pdf_24,
|
||||||
|
R.drawable.local_activity_24px
|
||||||
|
};
|
||||||
|
String[] columns = new String[]{"text", "icon"};
|
||||||
|
|
||||||
|
for (int i = 0; i < texts.length; i++) {
|
||||||
|
HashMap<String, Object> map = new HashMap<>();
|
||||||
|
map.put(columns[0], texts[i]);
|
||||||
|
map.put(columns[1], icons[i]);
|
||||||
|
list.add(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListAdapter adapter = new SimpleAdapter(
|
||||||
|
ScanActivity.this,
|
||||||
|
list,
|
||||||
|
R.layout.alertdialog_row_with_icon,
|
||||||
|
columns,
|
||||||
|
new int[]{R.id.textView, R.id.imageView}
|
||||||
|
);
|
||||||
|
|
||||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ScanActivity.this);
|
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ScanActivity.this);
|
||||||
builder.setTitle(getString(R.string.add_a_card_in_a_different_way));
|
builder.setTitle(getString(R.string.add_a_card_in_a_different_way));
|
||||||
builder.setItems(
|
builder.setAdapter(
|
||||||
new CharSequence[]{
|
adapter,
|
||||||
getString(R.string.addWithoutBarcode),
|
|
||||||
getString(R.string.addManually),
|
|
||||||
getString(R.string.addFromImage)
|
|
||||||
},
|
|
||||||
(dialogInterface, i) -> {
|
(dialogInterface, i) -> {
|
||||||
switch (i) {
|
switch (i) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -121,6 +161,12 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
case 2:
|
case 2:
|
||||||
addFromImage();
|
addFromImage();
|
||||||
break;
|
break;
|
||||||
|
case 3:
|
||||||
|
addFromPdf();
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
addFromPkPass();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown 'Add a card in a different way' dialog option");
|
throw new IllegalArgumentException("Unknown 'Add a card in a different way' dialog option");
|
||||||
}
|
}
|
||||||
@@ -134,7 +180,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
|
|
||||||
// Even though we do the actual decoding with the barcodeScannerView
|
// Even though we do the actual decoding with the barcodeScannerView
|
||||||
// CaptureManager needs to be running to show the camera and scanning bar
|
// CaptureManager needs to be running to show the camera and scanning bar
|
||||||
capture = new CatimaCaptureManager(this, barcodeScannerView);
|
capture = new CatimaCaptureManager(this, barcodeScannerView, this::onCaptureManagerError);
|
||||||
Intent captureIntent = new Intent();
|
Intent captureIntent = new Intent();
|
||||||
Bundle captureIntentBundle = new Bundle();
|
Bundle captureIntentBundle = new Bundle();
|
||||||
captureIntentBundle.putBoolean(Intents.Scan.BEEP_ENABLED, false);
|
captureIntentBundle.putBoolean(Intents.Scan.BEEP_ENABLED, false);
|
||||||
@@ -144,16 +190,11 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
barcodeScannerView.decodeSingle(new BarcodeCallback() {
|
barcodeScannerView.decodeSingle(new BarcodeCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void barcodeResult(BarcodeResult result) {
|
public void barcodeResult(BarcodeResult result) {
|
||||||
Intent scanResult = new Intent();
|
LoyaltyCard loyaltyCard = new LoyaltyCard();
|
||||||
Bundle scanResultBundle = new Bundle();
|
loyaltyCard.setCardId(result.getText());
|
||||||
scanResultBundle.putString(BARCODE_CONTENTS, result.getText());
|
loyaltyCard.setBarcodeType(CatimaBarcode.fromBarcode(result.getBarcodeFormat()));
|
||||||
scanResultBundle.putString(BARCODE_FORMAT, result.getBarcodeFormat().name());
|
|
||||||
if (addGroup != null) {
|
returnResult(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
|
||||||
scanResultBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
|
|
||||||
}
|
|
||||||
scanResult.putExtras(scanResultBundle);
|
|
||||||
ScanActivity.this.setResult(RESULT_OK, scanResult);
|
|
||||||
finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -171,9 +212,14 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
capture.onResume();
|
capture.onResume();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
if (!Utils.deviceHasCamera(this)) {
|
||||||
showCameraPermissionMissingText(false);
|
showCameraError(getString(R.string.noCameraFoundGuideText), false);
|
||||||
|
} else if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
showCameraPermissionMissingText();
|
||||||
|
} else {
|
||||||
|
hideCameraError();
|
||||||
}
|
}
|
||||||
|
|
||||||
scaleScreen();
|
scaleScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,30 +298,38 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
mScannerActive = isActive;
|
mScannerActive = isActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void returnResult(String barcodeContents, String barcodeFormat) {
|
private void returnResult(ParseResult parseResult) {
|
||||||
Intent manualResult = new Intent();
|
Intent result = new Intent();
|
||||||
Bundle manualResultBundle = new Bundle();
|
Bundle bundle = parseResult.toLoyaltyCardBundle(ScanActivity.this);
|
||||||
manualResultBundle.putString(BARCODE_CONTENTS, barcodeContents);
|
|
||||||
manualResultBundle.putString(BARCODE_FORMAT, barcodeFormat);
|
|
||||||
if (addGroup != null) {
|
if (addGroup != null) {
|
||||||
manualResultBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
|
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
|
||||||
}
|
}
|
||||||
manualResult.putExtras(manualResultBundle);
|
result.putExtras(bundle);
|
||||||
ScanActivity.this.setResult(RESULT_OK, manualResult);
|
ScanActivity.this.setResult(RESULT_OK, result);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleActivityResult(int requestCode, int resultCode, Intent intent) {
|
private void handleActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
super.onActivityResult(requestCode, resultCode, intent);
|
super.onActivityResult(requestCode, resultCode, intent);
|
||||||
|
|
||||||
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
|
List<ParseResult> parseResultList = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
|
||||||
|
|
||||||
if (barcodeValues.isEmpty()) {
|
if (parseResultList.isEmpty()) {
|
||||||
setScannerActive(true);
|
setScannerActive(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
returnResult(barcodeValues.content(), barcodeValues.format());
|
Utils.makeUserChooseParseResultFromList(this, parseResultList, new ParseResultListDisambiguatorCallback() {
|
||||||
|
@Override
|
||||||
|
public void onUserChoseParseResult(ParseResult parseResult) {
|
||||||
|
returnResult(parseResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUserDismissedSelector() {
|
||||||
|
setScannerActive(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addWithoutBarcode() {
|
private void addWithoutBarcode() {
|
||||||
@@ -315,7 +369,9 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
|
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
|
||||||
returnResult(input.getText().toString(), "");
|
LoyaltyCard loyaltyCard = new LoyaltyCard();
|
||||||
|
loyaltyCard.setCardId(input.getText().toString());
|
||||||
|
returnResult(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
|
||||||
});
|
});
|
||||||
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
||||||
AlertDialog dialog = builder.create();
|
AlertDialog dialog = builder.create();
|
||||||
@@ -343,42 +399,83 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addManually() {
|
public void addManually() {
|
||||||
Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class);
|
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ScanActivity.this);
|
||||||
if (cardId != null) {
|
builder.setTitle(R.string.add_manually_warning_title);
|
||||||
final Bundle b = new Bundle();
|
builder.setMessage(R.string.add_manually_warning_message);
|
||||||
b.putString("initialCardId", cardId);
|
builder.setPositiveButton(R.string.continue_, (dialog, which) -> {
|
||||||
i.putExtras(b);
|
Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class);
|
||||||
}
|
if (cardId != null) {
|
||||||
manualAddLauncher.launch(i);
|
final Bundle b = new Bundle();
|
||||||
|
b.putString(LoyaltyCard.BUNDLE_LOYALTY_CARD_CARD_ID, cardId);
|
||||||
|
i.putExtras(b);
|
||||||
|
}
|
||||||
|
manualAddLauncher.launch(i);
|
||||||
|
});
|
||||||
|
builder.setNegativeButton(R.string.cancel, (dialog, which) -> setScannerActive(true));
|
||||||
|
builder.setOnCancelListener(dialog -> setScannerActive(true));
|
||||||
|
builder.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addFromImage() {
|
public void addFromImage() {
|
||||||
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_IMAGE);
|
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_IMAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addFromImageAfterPermission() {
|
public void addFromPdf() {
|
||||||
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
|
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PDF);
|
||||||
photoPickerIntent.setType("image/*");
|
}
|
||||||
Intent contentIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
||||||
contentIntent.setType("image/*");
|
|
||||||
|
|
||||||
Intent chooserIntent = Intent.createChooser(photoPickerIntent, getString(R.string.addFromImage));
|
public void addFromPkPass() {
|
||||||
|
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PKPASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFromImageOrFileAfterPermission(String mimeType, ActivityResultLauncher<Intent> launcher, int chooserText, int errorMessage) {
|
||||||
|
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
|
||||||
|
photoPickerIntent.setType(mimeType);
|
||||||
|
Intent contentIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
contentIntent.setType(mimeType);
|
||||||
|
|
||||||
|
Intent chooserIntent = Intent.createChooser(photoPickerIntent, getString(chooserText));
|
||||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { contentIntent });
|
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { contentIntent });
|
||||||
try {
|
try {
|
||||||
photoPickerLauncher.launch(chooserIntent);
|
launcher.launch(chooserIntent);
|
||||||
} catch (ActivityNotFoundException e) {
|
} catch (ActivityNotFoundException e) {
|
||||||
setScannerActive(true);
|
setScannerActive(true);
|
||||||
Toast.makeText(getApplicationContext(), R.string.failedLaunchingPhotoPicker, Toast.LENGTH_LONG).show();
|
Toast.makeText(getApplicationContext(), errorMessage, Toast.LENGTH_LONG).show();
|
||||||
Log.e(TAG, "No activity found to handle intent", e);
|
Log.e(TAG, "No activity found to handle intent", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showCameraPermissionMissingText(boolean show) {
|
public void onCaptureManagerError(String errorMessage) {
|
||||||
customBarcodeScannerBinding.cameraPermissionDeniedLayout.cameraPermissionDeniedClickableArea.setOnClickListener(show ? v -> {
|
if (mHasError) {
|
||||||
|
// We're already showing an error, ignore this new error
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showCameraError(errorMessage, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showCameraPermissionMissingText() {
|
||||||
|
showCameraError(getString(R.string.noCameraPermissionDirectToSystemSetting), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showCameraError(String message, boolean setOnClick) {
|
||||||
|
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorMessage.setText(message);
|
||||||
|
|
||||||
|
setCameraErrorState(true, setOnClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideCameraError() {
|
||||||
|
setCameraErrorState(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCameraErrorState(boolean visible, boolean setOnClick) {
|
||||||
|
mHasError = visible;
|
||||||
|
|
||||||
|
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorClickableArea.setOnClickListener(visible && setOnClick ? v -> {
|
||||||
navigateToSystemPermissionSetting();
|
navigateToSystemPermissionSetting();
|
||||||
} : null);
|
} : null);
|
||||||
customBarcodeScannerBinding.cardInputContainer.setBackgroundColor(show ? obtainThemeAttribute(com.google.android.material.R.attr.colorSurface) : Color.TRANSPARENT);
|
customBarcodeScannerBinding.cardInputContainer.setBackgroundColor(visible ? obtainThemeAttribute(com.google.android.material.R.attr.colorSurface) : Color.TRANSPARENT);
|
||||||
customBarcodeScannerBinding.cameraPermissionDeniedLayout.getRoot().setVisibility(show ? View.VISIBLE : View.GONE);
|
customBarcodeScannerBinding.cameraErrorLayout.getRoot().setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scaleScreen() {
|
private void scaleScreen() {
|
||||||
@@ -388,8 +485,8 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
float mediumSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,MEDIUM_SCALE_FACTOR_DIP,getResources().getDisplayMetrics());
|
float mediumSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,MEDIUM_SCALE_FACTOR_DIP,getResources().getDisplayMetrics());
|
||||||
boolean shouldScaleSmaller = screenHeight < mediumSizePx;
|
boolean shouldScaleSmaller = screenHeight < mediumSizePx;
|
||||||
|
|
||||||
customBarcodeScannerBinding.cameraPermissionDeniedLayout.cameraPermissionDeniedIcon.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
|
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorIcon.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
|
||||||
customBarcodeScannerBinding.cameraPermissionDeniedLayout.cameraPermissionDeniedTitle.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
|
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorTitle.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int obtainThemeAttribute(int attribute) {
|
private int obtainThemeAttribute(int attribute) {
|
||||||
@@ -415,10 +512,20 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
||||||
|
|
||||||
if (requestCode == CaptureManager.getCameraPermissionReqCode()) {
|
if (requestCode == CaptureManager.getCameraPermissionReqCode()) {
|
||||||
showCameraPermissionMissingText(!granted);
|
|
||||||
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
|
|
||||||
if (granted) {
|
if (granted) {
|
||||||
addFromImageAfterPermission();
|
hideCameraError();
|
||||||
|
} else {
|
||||||
|
showCameraPermissionMissingText();
|
||||||
|
}
|
||||||
|
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE || requestCode == PERMISSION_SCAN_ADD_FROM_PDF || requestCode == PERMISSION_SCAN_ADD_FROM_PKPASS) {
|
||||||
|
if (granted) {
|
||||||
|
if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
|
||||||
|
addFromImageOrFileAfterPermission("image/*", photoPickerLauncher, R.string.addFromImage, R.string.failedLaunchingPhotoPicker);
|
||||||
|
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_PDF) {
|
||||||
|
addFromImageOrFileAfterPermission("application/pdf", pdfPickerLauncher, R.string.addFromPdfFile, R.string.failedLaunchingFileManager);
|
||||||
|
} else {
|
||||||
|
addFromImageOrFileAfterPermission("application/*", pkpassPickerLauncher, R.string.addFromPkpass, R.string.failedLaunchingFileManager);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setScannerActive(true);
|
setScannerActive(true);
|
||||||
Toast.makeText(this, R.string.storageReadPermissionRequired, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.storageReadPermissionRequired, Toast.LENGTH_LONG).show();
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ class ShortcutHelper {
|
|||||||
private static final int ADAPTIVE_BITMAP_SIZE = 108 * ADAPTIVE_BITMAP_SCALE;
|
private static final int ADAPTIVE_BITMAP_SIZE = 108 * ADAPTIVE_BITMAP_SCALE;
|
||||||
private static final int ADAPTIVE_BITMAP_VISIBLE_SIZE = 72 * ADAPTIVE_BITMAP_SCALE;
|
private static final int ADAPTIVE_BITMAP_VISIBLE_SIZE = 72 * ADAPTIVE_BITMAP_SCALE;
|
||||||
private static final int ADAPTIVE_BITMAP_IMAGE_SIZE = ADAPTIVE_BITMAP_VISIBLE_SIZE + 5 * ADAPTIVE_BITMAP_SCALE;
|
private static final int ADAPTIVE_BITMAP_IMAGE_SIZE = ADAPTIVE_BITMAP_VISIBLE_SIZE + 5 * ADAPTIVE_BITMAP_SCALE;
|
||||||
private static final int PADDING_COLOR_OVERLAY = Color.argb(127, 0, 0, 0);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a card to the app shortcuts, and maintain a list of the most
|
* Add a card to the app shortcuts, and maintain a list of the most
|
||||||
@@ -43,6 +42,11 @@ class ShortcutHelper {
|
|||||||
* used card shortcut is discarded.
|
* used card shortcut is discarded.
|
||||||
*/
|
*/
|
||||||
static void updateShortcuts(Context context, LoyaltyCard card) {
|
static void updateShortcuts(Context context, LoyaltyCard card) {
|
||||||
|
if (card.archiveStatus == 1) {
|
||||||
|
// Don't add archived card to menu
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
LinkedList<ShortcutInfoCompat> list = new LinkedList<>(ShortcutManagerCompat.getDynamicShortcuts(context));
|
LinkedList<ShortcutInfoCompat> list = new LinkedList<>(ShortcutManagerCompat.getDynamicShortcuts(context));
|
||||||
|
|
||||||
SQLiteDatabase database = new DBHelper(context).getReadableDatabase();
|
SQLiteDatabase database = new DBHelper(context).getReadableDatabase();
|
||||||
@@ -82,7 +86,7 @@ class ShortcutHelper {
|
|||||||
for (int index = 0; index < list.size(); index++) {
|
for (int index = 0; index < list.size(); index++) {
|
||||||
ShortcutInfoCompat prevShortcut = list.get(index);
|
ShortcutInfoCompat prevShortcut = list.get(index);
|
||||||
|
|
||||||
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(database, Integer.parseInt(prevShortcut.getId()));
|
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(context, database, Integer.parseInt(prevShortcut.getId()));
|
||||||
|
|
||||||
// skip outdated cards that no longer exist
|
// skip outdated cards that no longer exist
|
||||||
if (loyaltyCard != null) {
|
if (loyaltyCard != null) {
|
||||||
@@ -108,25 +112,14 @@ class ShortcutHelper {
|
|||||||
* shortcut exists.
|
* shortcut exists.
|
||||||
*/
|
*/
|
||||||
static void removeShortcut(Context context, int cardId) {
|
static void removeShortcut(Context context, int cardId) {
|
||||||
List<ShortcutInfoCompat> list = ShortcutManagerCompat.getDynamicShortcuts(context);
|
ShortcutManagerCompat.removeDynamicShortcuts(context, Collections.singletonList(Integer.toString(cardId)));
|
||||||
|
|
||||||
String shortcutId = Integer.toString(cardId);
|
|
||||||
|
|
||||||
for (int index = 0; index < list.size(); index++) {
|
|
||||||
if (list.get(index).getId().equals(shortcutId)) {
|
|
||||||
list.remove(index);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShortcutManagerCompat.setDynamicShortcuts(context, list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static @NotNull
|
static @NotNull
|
||||||
Bitmap createAdaptiveBitmap(@NotNull Bitmap in, int paddingColor) {
|
Bitmap createAdaptiveBitmap(@NotNull Bitmap in, int paddingColor) {
|
||||||
Bitmap ret = Bitmap.createBitmap(ADAPTIVE_BITMAP_SIZE, ADAPTIVE_BITMAP_SIZE, Bitmap.Config.ARGB_8888);
|
Bitmap ret = Bitmap.createBitmap(ADAPTIVE_BITMAP_SIZE, ADAPTIVE_BITMAP_SIZE, Bitmap.Config.ARGB_8888);
|
||||||
Canvas output = new Canvas(ret);
|
Canvas output = new Canvas(ret);
|
||||||
output.drawColor(ColorUtils.compositeColors(PADDING_COLOR_OVERLAY, paddingColor));
|
output.drawColor(paddingColor);
|
||||||
Bitmap resized = Utils.resizeBitmap(in, ADAPTIVE_BITMAP_IMAGE_SIZE);
|
Bitmap resized = Utils.resizeBitmap(in, ADAPTIVE_BITMAP_IMAGE_SIZE);
|
||||||
output.drawBitmap(resized, (ADAPTIVE_BITMAP_SIZE - resized.getWidth()) / 2f, (ADAPTIVE_BITMAP_SIZE - resized.getHeight()) / 2f, null);
|
output.drawBitmap(resized, (ADAPTIVE_BITMAP_SIZE - resized.getWidth()) / 2f, (ADAPTIVE_BITMAP_SIZE - resized.getHeight()) / 2f, null);
|
||||||
return ret;
|
return ret;
|
||||||
@@ -139,15 +132,14 @@ class ShortcutHelper {
|
|||||||
// one replace it.
|
// one replace it.
|
||||||
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
final Bundle bundle = new Bundle();
|
final Bundle bundle = new Bundle();
|
||||||
bundle.putInt("id", loyaltyCard.id);
|
bundle.putInt(LoyaltyCardViewActivity.BUNDLE_ID, loyaltyCard.id);
|
||||||
bundle.putBoolean("view", true);
|
|
||||||
intent.putExtras(bundle);
|
intent.putExtras(bundle);
|
||||||
|
|
||||||
Bitmap iconBitmap = Utils.retrieveCardImage(context, loyaltyCard.id, ImageLocationType.icon);
|
Bitmap iconBitmap = loyaltyCard.getImageThumbnail(context);
|
||||||
if (iconBitmap == null) {
|
if (iconBitmap == null) {
|
||||||
iconBitmap = Utils.generateIcon(context, loyaltyCard, true).getLetterTile();
|
iconBitmap = Utils.generateIcon(context, loyaltyCard, true).getLetterTile();
|
||||||
} else {
|
} else {
|
||||||
iconBitmap = createAdaptiveBitmap(iconBitmap, Utils.getHeaderColor(context, loyaltyCard));
|
iconBitmap = createAdaptiveBitmap(iconBitmap, Utils.needsDarkForeground(Utils.getHeaderColor(context, loyaltyCard)) ? Color.BLACK : Color.WHITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
IconCompat icon = IconCompat.createWithAdaptiveBitmap(iconBitmap);
|
IconCompat icon = IconCompat.createWithAdaptiveBitmap(iconBitmap);
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ import com.yalantis.ucrop.UCropActivity;
|
|||||||
public class UCropWrapper extends UCropActivity {
|
public class UCropWrapper extends UCropActivity {
|
||||||
public static final String UCROP_TOOLBAR_TYPEFACE_STYLE = "ucop_toolbar_typeface_style";
|
public static final String UCROP_TOOLBAR_TYPEFACE_STYLE = "ucop_toolbar_typeface_style";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Utils.applyWindowInsets(findViewById(android.R.id.content));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
|
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onPostCreate(savedInstanceState);
|
super.onPostCreate(savedInstanceState);
|
||||||
|
|||||||
@@ -9,11 +9,16 @@ import android.content.res.Resources;
|
|||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.ImageDecoder;
|
import android.graphics.ImageDecoder;
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.pdf.PdfRenderer;
|
||||||
|
import android.hardware.camera2.CameraAccessException;
|
||||||
|
import android.hardware.camera2.CameraManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
@@ -22,6 +27,7 @@ import android.util.Log;
|
|||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -33,12 +39,17 @@ import androidx.annotation.RawRes;
|
|||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.core.graphics.ColorUtils;
|
import androidx.core.graphics.ColorUtils;
|
||||||
|
import androidx.core.graphics.Insets;
|
||||||
import androidx.core.os.LocaleListCompat;
|
import androidx.core.os.LocaleListCompat;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
import androidx.core.view.WindowInsetsControllerCompat;
|
import androidx.core.view.WindowInsetsControllerCompat;
|
||||||
|
import androidx.core.widget.TextViewCompat;
|
||||||
import androidx.exifinterface.media.ExifInterface;
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
import androidx.palette.graphics.Palette;
|
import androidx.palette.graphics.Palette;
|
||||||
|
|
||||||
import com.google.android.material.color.DynamicColors;
|
import com.google.android.material.color.DynamicColors;
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.google.zxing.BinaryBitmap;
|
import com.google.zxing.BinaryBitmap;
|
||||||
import com.google.zxing.LuminanceSource;
|
import com.google.zxing.LuminanceSource;
|
||||||
import com.google.zxing.MultiFormatReader;
|
import com.google.zxing.MultiFormatReader;
|
||||||
@@ -46,6 +57,8 @@ import com.google.zxing.NotFoundException;
|
|||||||
import com.google.zxing.RGBLuminanceSource;
|
import com.google.zxing.RGBLuminanceSource;
|
||||||
import com.google.zxing.Result;
|
import com.google.zxing.Result;
|
||||||
import com.google.zxing.common.HybridBinarizer;
|
import com.google.zxing.common.HybridBinarizer;
|
||||||
|
import com.google.zxing.multi.GenericMultipleBarcodeReader;
|
||||||
|
import com.google.zxing.multi.MultipleBarcodeReader;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -60,13 +73,17 @@ import java.math.BigDecimal;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.text.DecimalFormatSymbols;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Currency;
|
import java.util.Currency;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -83,12 +100,14 @@ public class Utils {
|
|||||||
public static final int SELECT_BARCODE_REQUEST = 2;
|
public static final int SELECT_BARCODE_REQUEST = 2;
|
||||||
public static final int BARCODE_SCAN = 3;
|
public static final int BARCODE_SCAN = 3;
|
||||||
public static final int BARCODE_IMPORT_FROM_IMAGE_FILE = 4;
|
public static final int BARCODE_IMPORT_FROM_IMAGE_FILE = 4;
|
||||||
public static final int CARD_IMAGE_FROM_CAMERA_FRONT = 5;
|
public static final int BARCODE_IMPORT_FROM_PDF_FILE = 5;
|
||||||
public static final int CARD_IMAGE_FROM_CAMERA_BACK = 6;
|
public static final int BARCODE_IMPORT_FROM_PKPASS_FILE = 6;
|
||||||
public static final int CARD_IMAGE_FROM_CAMERA_ICON = 7;
|
public static final int CARD_IMAGE_FROM_CAMERA_FRONT = 7;
|
||||||
public static final int CARD_IMAGE_FROM_FILE_FRONT = 8;
|
public static final int CARD_IMAGE_FROM_CAMERA_BACK = 8;
|
||||||
public static final int CARD_IMAGE_FROM_FILE_BACK = 9;
|
public static final int CARD_IMAGE_FROM_CAMERA_ICON = 9;
|
||||||
public static final int CARD_IMAGE_FROM_FILE_ICON = 10;
|
public static final int CARD_IMAGE_FROM_FILE_FRONT = 10;
|
||||||
|
public static final int CARD_IMAGE_FROM_FILE_BACK = 11;
|
||||||
|
public static final int CARD_IMAGE_FROM_FILE_ICON = 12;
|
||||||
|
|
||||||
public static final String CARD_IMAGE_FILENAME_REGEX = "^(card_)(\\d+)(_(?:front|back|icon)\\.png)$";
|
public static final String CARD_IMAGE_FILENAME_REGEX = "^(card_)(\\d+)(_(?:front|back|icon)\\.png)$";
|
||||||
|
|
||||||
@@ -131,56 +150,172 @@ public class Utils {
|
|||||||
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
|
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public List<ParseResult> retrieveBarcodesFromImage(Context context, Uri uri) {
|
||||||
|
Log.i(TAG, "Received image file with possible barcode");
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
|
Log.e(TAG, "Uri did not contain any data");
|
||||||
|
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap bitmap;
|
||||||
|
try {
|
||||||
|
bitmap = retrieveImageFromUri(context, uri);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error getting data from image file");
|
||||||
|
e.printStackTrace();
|
||||||
|
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ParseResult> barcodesFromBitmap = getBarcodesFromBitmap(bitmap);
|
||||||
|
|
||||||
|
if (barcodesFromBitmap.isEmpty()) {
|
||||||
|
Log.i(TAG, "No barcode found in image file");
|
||||||
|
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
return barcodesFromBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public List<ParseResult> retrieveBarcodesFromPkPass(Context context, Uri uri) {
|
||||||
|
Log.i(TAG, "Received Pkpass file with possible barcode");
|
||||||
|
if (uri == null) {
|
||||||
|
Log.e(TAG, "Pkpass did not contain any data");
|
||||||
|
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
PkpassParser pkpassParser;
|
||||||
|
try {
|
||||||
|
pkpassParser = new PkpassParser(context, uri);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error reading pkpass file", e);
|
||||||
|
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> locales = pkpassParser.listLocales();
|
||||||
|
if (locales.isEmpty()) {
|
||||||
|
try {
|
||||||
|
return Collections.singletonList(new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(null)));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error calling toLoyaltyCard on pkpass file", e);
|
||||||
|
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ParseResult> parseResultList = new ArrayList<>();
|
||||||
|
for (String locale : locales) {
|
||||||
|
ParseResult parseResult;
|
||||||
|
try {
|
||||||
|
parseResult = new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(locale));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error calling toLoyaltyCard on pkpass file", e);
|
||||||
|
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
parseResult.setNote(locale);
|
||||||
|
parseResultList.add(parseResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseResultList;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public List<ParseResult> retrieveBarcodesFromPdf(Context context, Uri uri) {
|
||||||
|
Log.i(TAG, "Received PDF file with possible barcode");
|
||||||
|
if (uri == null) {
|
||||||
|
Log.e(TAG, "Uri did not contain any data");
|
||||||
|
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ParcelFileDescriptor parcelFileDescriptor = null;
|
||||||
|
PdfRenderer renderer = null;
|
||||||
|
List<ParseResult> barcodesFromPdfPages = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, "r");
|
||||||
|
if (parcelFileDescriptor != null) {
|
||||||
|
renderer = new PdfRenderer(parcelFileDescriptor);
|
||||||
|
|
||||||
|
// Loop over all pages to find barcodes
|
||||||
|
Bitmap renderedPage;
|
||||||
|
for (int i = 0; i < renderer.getPageCount(); i++) {
|
||||||
|
PdfRenderer.Page page = renderer.openPage(i);
|
||||||
|
renderedPage = Bitmap.createBitmap(page.getWidth(), page.getHeight(), Bitmap.Config.ARGB_8888);
|
||||||
|
|
||||||
|
// Ensure the page has a background
|
||||||
|
// Fixes some transparent PDF files not being read well
|
||||||
|
Canvas canvas = new Canvas(renderedPage);
|
||||||
|
canvas.drawColor(Color.WHITE);
|
||||||
|
canvas.drawBitmap(renderedPage, 0, 0, null);
|
||||||
|
|
||||||
|
page.render(renderedPage, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
|
||||||
|
page.close();
|
||||||
|
|
||||||
|
List<ParseResult> barcodesFromPage = getBarcodesFromBitmap(renderedPage);
|
||||||
|
for (ParseResult parseResult : barcodesFromPage) {
|
||||||
|
parseResult.setNote(String.format(context.getString(R.string.pageWithNumber), i+1));
|
||||||
|
barcodesFromPdfPages.add(parseResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error reading PDF file", e);
|
||||||
|
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||||
|
} finally {
|
||||||
|
// Resource handling
|
||||||
|
if (renderer != null) {
|
||||||
|
renderer.close();
|
||||||
|
}
|
||||||
|
if (parcelFileDescriptor != null) {
|
||||||
|
try {
|
||||||
|
parcelFileDescriptor.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error closing ParcelFileDescriptor", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (barcodesFromPdfPages.isEmpty()) {
|
||||||
|
Log.i(TAG, "No barcode found in pdf file");
|
||||||
|
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
return barcodesFromPdfPages;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the Barcode format and content based on the result of an activity.
|
* Returns the ParseResult based on the result of an activity.
|
||||||
* It shows toasts to notify the end-user as needed itself and will return an empty
|
* It shows toasts to notify the end-user as needed itself and will return an empty list if the
|
||||||
* BarcodeValues object if the activity was cancelled or nothing could be found.
|
* activity was cancelled or nothing could be found.
|
||||||
*
|
*
|
||||||
* @param requestCode
|
* @param requestCode
|
||||||
* @param resultCode
|
* @param resultCode
|
||||||
* @param intent
|
* @param intent
|
||||||
* @param context
|
* @param context
|
||||||
* @return BarcodeValues
|
* @return List<ParseResult>
|
||||||
*/
|
*/
|
||||||
static public BarcodeValues parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) {
|
static public List<ParseResult> parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) {
|
||||||
String contents;
|
String contents;
|
||||||
String format;
|
String format;
|
||||||
|
|
||||||
if (resultCode != Activity.RESULT_OK) {
|
if (resultCode != Activity.RESULT_OK) {
|
||||||
return new BarcodeValues(null, null);
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) {
|
if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) {
|
||||||
Log.i(TAG, "Received image file with possible barcode");
|
return retrieveBarcodesFromImage(context, intent.getData());
|
||||||
|
}
|
||||||
|
|
||||||
Uri data = intent.getData();
|
if (requestCode == Utils.BARCODE_IMPORT_FROM_PDF_FILE) {
|
||||||
if (data == null) {
|
return retrieveBarcodesFromPdf(context, intent.getData());
|
||||||
Log.e(TAG, "Intent did not contain any data");
|
}
|
||||||
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
|
||||||
return new BarcodeValues(null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
Bitmap bitmap;
|
if (requestCode == Utils.BARCODE_IMPORT_FROM_PKPASS_FILE) {
|
||||||
try {
|
return retrieveBarcodesFromPkPass(context, intent.getData());
|
||||||
bitmap = retrieveImageFromUri(context, data);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Error getting data from image file");
|
|
||||||
e.printStackTrace();
|
|
||||||
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
|
||||||
return new BarcodeValues(null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
BarcodeValues barcodeFromBitmap = getBarcodeFromBitmap(bitmap);
|
|
||||||
|
|
||||||
if (barcodeFromBitmap.isEmpty()) {
|
|
||||||
Log.i(TAG, "No barcode found in image file");
|
|
||||||
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i(TAG, "Read barcode id: " + barcodeFromBitmap.content());
|
|
||||||
Log.i(TAG, "Read format: " + barcodeFromBitmap.format());
|
|
||||||
|
|
||||||
return barcodeFromBitmap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestCode == Utils.BARCODE_SCAN || requestCode == Utils.SELECT_BARCODE_REQUEST) {
|
if (requestCode == Utils.BARCODE_SCAN || requestCode == Utils.SELECT_BARCODE_REQUEST) {
|
||||||
@@ -196,7 +331,15 @@ public class Utils {
|
|||||||
Log.i(TAG, "Read barcode id: " + contents);
|
Log.i(TAG, "Read barcode id: " + contents);
|
||||||
Log.i(TAG, "Read format: " + format);
|
Log.i(TAG, "Read format: " + format);
|
||||||
|
|
||||||
return new BarcodeValues(format, contents);
|
LoyaltyCard loyaltyCard = new LoyaltyCard();
|
||||||
|
if (format != null) {
|
||||||
|
loyaltyCard.setBarcodeType(CatimaBarcode.fromName(format));
|
||||||
|
}
|
||||||
|
if (contents != null) {
|
||||||
|
loyaltyCard.setCardId(contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.singletonList(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new UnsupportedOperationException("Unknown request code for parseSetBarcodeActivityResult");
|
throw new UnsupportedOperationException("Unknown request code for parseSetBarcodeActivityResult");
|
||||||
@@ -216,22 +359,22 @@ public class Utils {
|
|||||||
return MediaStore.Images.Media.getBitmap(context.getContentResolver(), data);
|
return MediaStore.Images.Media.getBitmap(context.getContentResolver(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public BarcodeValues getBarcodeFromBitmap(Bitmap bitmap) {
|
static public List<ParseResult> getBarcodesFromBitmap(Bitmap bitmap) {
|
||||||
// This function is vulnerable to OOM, so we try again with a smaller bitmap is we get OOM
|
// This function is vulnerable to OOM, so we try again with a smaller bitmap is we get OOM
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
try {
|
try {
|
||||||
return Utils.getBarcodeFromBitmapReal(bitmap);
|
return Utils.getBarcodesFromBitmapReal(bitmap);
|
||||||
} catch (OutOfMemoryError e) {
|
} catch (OutOfMemoryError e) {
|
||||||
Log.w(TAG, "Ran OOM in getBarcodeFromBitmap! Trying again with smaller picture! Retry " + i + " of 10.");
|
Log.w(TAG, "Ran OOM in getBarcodesFromBitmap! Trying again with smaller picture! Retry " + i + " of 10.");
|
||||||
bitmap = Bitmap.createScaledBitmap(bitmap, (int) Math.round(0.75 * bitmap.getWidth()), (int) Math.round(0.75 * bitmap.getHeight()), false);
|
bitmap = Bitmap.createScaledBitmap(bitmap, (int) Math.round(0.75 * bitmap.getWidth()), (int) Math.round(0.75 * bitmap.getHeight()), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give up
|
// Give up
|
||||||
return new BarcodeValues(null, null);
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
static private BarcodeValues getBarcodeFromBitmapReal(Bitmap bitmap) {
|
static private List<ParseResult> getBarcodesFromBitmapReal(Bitmap bitmap) {
|
||||||
// In order to decode it, the Bitmap must first be converted into a pixel array...
|
// In order to decode it, the Bitmap must first be converted into a pixel array...
|
||||||
int[] intArray = new int[bitmap.getWidth() * bitmap.getHeight()];
|
int[] intArray = new int[bitmap.getWidth() * bitmap.getHeight()];
|
||||||
bitmap.getPixels(intArray, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
|
bitmap.getPixels(intArray, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
|
||||||
@@ -240,15 +383,68 @@ public class Utils {
|
|||||||
LuminanceSource source = new RGBLuminanceSource(bitmap.getWidth(), bitmap.getHeight(), intArray);
|
LuminanceSource source = new RGBLuminanceSource(bitmap.getWidth(), bitmap.getHeight(), intArray);
|
||||||
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
|
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||||
|
|
||||||
|
List<ParseResult> parseResultList = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
Result barcodeResult = new MultiFormatReader().decode(binaryBitmap);
|
MultiFormatReader multiFormatReader = new MultiFormatReader();
|
||||||
|
MultipleBarcodeReader multipleBarcodeReader = new GenericMultipleBarcodeReader(multiFormatReader);
|
||||||
|
|
||||||
return new BarcodeValues(barcodeResult.getBarcodeFormat().name(), barcodeResult.getText());
|
Result[] barcodeResults = multipleBarcodeReader.decodeMultiple(binaryBitmap);
|
||||||
|
|
||||||
|
for (Result barcodeResult : barcodeResults) {
|
||||||
|
Log.i(TAG, "Read barcode id: " + barcodeResult.getText());
|
||||||
|
Log.i(TAG, "Read format: " + barcodeResult.getBarcodeFormat().name());
|
||||||
|
|
||||||
|
LoyaltyCard loyaltyCard = new LoyaltyCard();
|
||||||
|
loyaltyCard.setCardId(barcodeResult.getText());
|
||||||
|
loyaltyCard.setBarcodeType(CatimaBarcode.fromBarcode(barcodeResult.getBarcodeFormat()));
|
||||||
|
parseResultList.add(new ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseResultList;
|
||||||
} catch (NotFoundException e) {
|
} catch (NotFoundException e) {
|
||||||
return new BarcodeValues(null, null);
|
return parseResultList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public void makeUserChooseParseResultFromList(Context context, List<ParseResult> parseResultList, ParseResultListDisambiguatorCallback callback) {
|
||||||
|
// If there is only one choice, consider it chosen
|
||||||
|
if (parseResultList.size() == 1) {
|
||||||
|
callback.onUserChoseParseResult(parseResultList.get(0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask user to choose a barcode
|
||||||
|
// TODO: This should contain an image of the barcode in question to help users understand the choice they're making
|
||||||
|
CharSequence[] barcodeDescriptions = new CharSequence[parseResultList.size()];
|
||||||
|
for (int i = 0; i < parseResultList.size(); i++) {
|
||||||
|
ParseResult parseResult = parseResultList.get(i);
|
||||||
|
CatimaBarcode catimaBarcode = parseResult.getLoyaltyCard().barcodeType;
|
||||||
|
|
||||||
|
String barcodeContent = parseResult.getLoyaltyCard().cardId;
|
||||||
|
// Shorten overly long barcodes
|
||||||
|
if (barcodeContent.length() > 22) {
|
||||||
|
barcodeContent = barcodeContent.substring(0, 20) + "…";
|
||||||
|
}
|
||||||
|
|
||||||
|
String parseResultNote = parseResult.getNote();
|
||||||
|
|
||||||
|
if (parseResultNote != null) {
|
||||||
|
barcodeDescriptions[i] = String.format("%s: %s (%s)", parseResultNote, catimaBarcode != null ? catimaBarcode.prettyName() : context.getString(R.string.noBarcode), barcodeContent);
|
||||||
|
} else {
|
||||||
|
barcodeDescriptions[i] = String.format("%s (%s)", catimaBarcode != null ? catimaBarcode.prettyName() : context.getString(R.string.noBarcode), barcodeContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
|
||||||
|
builder.setTitle(context.getString(R.string.multipleBarcodesFoundPleaseChooseOne));
|
||||||
|
builder.setItems(
|
||||||
|
barcodeDescriptions,
|
||||||
|
(dialogInterface, i) -> callback.onUserChoseParseResult(parseResultList.get(i))
|
||||||
|
);
|
||||||
|
builder.setOnCancelListener(dialogInterface -> callback.onUserDismissedSelector());
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
|
||||||
static public Boolean isNotYetValid(Date validFromDate) {
|
static public Boolean isNotYetValid(Date validFromDate) {
|
||||||
// The note in `hasExpired` does not apply here, since the bug was fixed before this feature was added.
|
// The note in `hasExpired` does not apply here, since the bug was fixed before this feature was added.
|
||||||
return validFromDate.after(getStartOfToday().getTime());
|
return validFromDate.after(getStartOfToday().getTime());
|
||||||
@@ -276,6 +472,7 @@ public class Utils {
|
|||||||
|
|
||||||
static public String formatBalance(Context context, BigDecimal value, Currency currency) {
|
static public String formatBalance(Context context, BigDecimal value, Currency currency) {
|
||||||
NumberFormat numberFormat = NumberFormat.getInstance();
|
NumberFormat numberFormat = NumberFormat.getInstance();
|
||||||
|
numberFormat.setGroupingUsed(false);
|
||||||
|
|
||||||
if (currency == null) {
|
if (currency == null) {
|
||||||
numberFormat.setMaximumFractionDigits(0);
|
numberFormat.setMaximumFractionDigits(0);
|
||||||
@@ -283,6 +480,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
|
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
|
||||||
|
currencyFormat.setGroupingUsed(false);
|
||||||
currencyFormat.setCurrency(currency);
|
currencyFormat.setCurrency(currency);
|
||||||
currencyFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
|
currencyFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
|
||||||
currencyFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
|
currencyFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
|
||||||
@@ -292,6 +490,7 @@ public class Utils {
|
|||||||
|
|
||||||
static public String formatBalanceWithoutCurrencySymbol(BigDecimal value, Currency currency) {
|
static public String formatBalanceWithoutCurrencySymbol(BigDecimal value, Currency currency) {
|
||||||
NumberFormat numberFormat = NumberFormat.getInstance();
|
NumberFormat numberFormat = NumberFormat.getInstance();
|
||||||
|
numberFormat.setGroupingUsed(false);
|
||||||
|
|
||||||
if (currency == null) {
|
if (currency == null) {
|
||||||
numberFormat.setMaximumFractionDigits(0);
|
numberFormat.setMaximumFractionDigits(0);
|
||||||
@@ -304,19 +503,72 @@ public class Utils {
|
|||||||
return numberFormat.format(value);
|
return numberFormat.format(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final double LargestPreciseDouble = (double) (1l << 53);
|
||||||
|
static{
|
||||||
|
assert (LargestPreciseDouble + 1.0) == LargestPreciseDouble;
|
||||||
|
assert (LargestPreciseDouble - 1.0) != LargestPreciseDouble;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigDecimal fromParsed(Number parsed){
|
||||||
|
if(parsed instanceof BigDecimal)
|
||||||
|
return (BigDecimal) parsed;
|
||||||
|
|
||||||
|
final double d = parsed.doubleValue();
|
||||||
|
if(d >= LargestPreciseDouble)
|
||||||
|
return new BigDecimal(parsed.longValue());
|
||||||
|
return new BigDecimal(d);
|
||||||
|
}
|
||||||
|
|
||||||
static public BigDecimal parseBalance(String value, Currency currency) throws ParseException {
|
static public BigDecimal parseBalance(String value, Currency currency) throws ParseException {
|
||||||
|
// This function expects the input string to not have any grouping (thousand separators).
|
||||||
|
// It will refuse to work otherwise
|
||||||
NumberFormat numberFormat = NumberFormat.getInstance();
|
NumberFormat numberFormat = NumberFormat.getInstance();
|
||||||
|
numberFormat.setGroupingUsed(false);
|
||||||
|
|
||||||
|
if (numberFormat instanceof DecimalFormat) {
|
||||||
|
((DecimalFormat) numberFormat).setParseBigDecimal(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (currency == null) {
|
if (currency == null) {
|
||||||
numberFormat.setMaximumFractionDigits(0);
|
numberFormat.setMaximumFractionDigits(0);
|
||||||
} else {
|
} else {
|
||||||
numberFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
|
int fractionDigits = currency.getDefaultFractionDigits();
|
||||||
numberFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
|
|
||||||
|
numberFormat.setMinimumFractionDigits(fractionDigits);
|
||||||
|
numberFormat.setMaximumFractionDigits(fractionDigits);
|
||||||
|
|
||||||
|
if (numberFormat instanceof DecimalFormat) {
|
||||||
|
// If the string contains both thousand separators and decimals separators, fail hard
|
||||||
|
DecimalFormatSymbols decimalFormatSymbols = ((DecimalFormat) numberFormat).getDecimalFormatSymbols();
|
||||||
|
char decimalSeparator = decimalFormatSymbols.getDecimalSeparator();
|
||||||
|
|
||||||
|
// Translate all non-digits to decimal separators, failing if we find more than 1.
|
||||||
|
// We loop over the codepoints to make sure eastern arabic numerals are not mistakenly
|
||||||
|
// treated as a separator.
|
||||||
|
boolean separatorFound = false;
|
||||||
|
StringBuilder translatedValue = new StringBuilder();
|
||||||
|
for (int i = 0; i < value.length();) {
|
||||||
|
int character = value.codePointAt(i);
|
||||||
|
|
||||||
|
if (Character.isDigit(character)) {
|
||||||
|
translatedValue.append(value.charAt(i));
|
||||||
|
} else {
|
||||||
|
if (separatorFound) {
|
||||||
|
throw new ParseException("Contains multiple separators", i);
|
||||||
|
}
|
||||||
|
|
||||||
|
separatorFound = true;
|
||||||
|
translatedValue.append(decimalSeparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
i += Character.charCount(character);
|
||||||
|
}
|
||||||
|
|
||||||
|
value = translatedValue.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, numberFormat.parse(value).toString());
|
return fromParsed(numberFormat.parse(value));
|
||||||
|
|
||||||
return new BigDecimal(numberFormat.parse(value).toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static public byte[] bitmapToByteArray(Bitmap bitmap) {
|
static public byte[] bitmapToByteArray(Bitmap bitmap) {
|
||||||
@@ -607,7 +859,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bitmap loadImage(String path) {
|
public static @Nullable Bitmap loadImage(String path) {
|
||||||
try {
|
try {
|
||||||
return BitmapFactory.decodeStream(new FileInputStream(path));
|
return BitmapFactory.decodeStream(new FileInputStream(path));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -616,7 +868,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bitmap loadTempImage(Context context, String name) {
|
public static @Nullable Bitmap loadTempImage(Context context, String name) {
|
||||||
return loadImage(context.getCacheDir() + "/" + name);
|
return loadImage(context.getCacheDir() + "/" + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,7 +945,7 @@ public class Utils {
|
|||||||
return typedValue.data;
|
return typedValue.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getHeaderColorFromImage(Bitmap image, int fallback) {
|
public static int getHeaderColorFromImage(@Nullable Bitmap image, int fallback) {
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
@@ -748,38 +1000,53 @@ public class Utils {
|
|||||||
.replaceAll("(?<!href=\")\\b(https?://[\\w@#%&+=:?/.-]*[\\w@#%&+=:?/-])", "<a href=\"$1\">$1</a>");
|
.replaceAll("(?<!href=\")\\b(https?://[\\w@#%&+=:?/.-]*[\\w@#%&+=:?/-])", "<a href=\"$1\">$1</a>");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setIconOrTextWithBackground(Context context, LoyaltyCard loyaltyCard, Bitmap icon, ImageView backgroundOrIcon, TextView textWhenNoImage) {
|
/**
|
||||||
|
* Sets an icon or text with background on the given ImageView and/or TextView, including background colour.
|
||||||
|
*
|
||||||
|
* @param context Android context
|
||||||
|
* @param loyaltyCard Loyalty Card
|
||||||
|
* @param icon Bitmap of the icon to set, or null
|
||||||
|
* @param backgroundOrIcon ImageView to draw the icon and background on to
|
||||||
|
* @param textWhenNoImage TextView to write the loyalty card name into if icon is null
|
||||||
|
* @return background colour
|
||||||
|
*/
|
||||||
|
public static int setIconOrTextWithBackground(Context context, LoyaltyCard loyaltyCard, Bitmap icon, ImageView backgroundOrIcon, TextView textWhenNoImage, int columnCount) {
|
||||||
|
int headerColor = getHeaderColor(context, loyaltyCard);
|
||||||
|
backgroundOrIcon.setImageBitmap(icon);
|
||||||
|
|
||||||
if (icon != null) {
|
if (icon != null) {
|
||||||
Log.d("onResume", "setting icon image");
|
// Use header colour to decide if this image will need a white or black background
|
||||||
|
backgroundOrIcon.setBackgroundColor(needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||||
|
|
||||||
textWhenNoImage.setVisibility(View.GONE);
|
textWhenNoImage.setVisibility(View.GONE);
|
||||||
|
|
||||||
backgroundOrIcon.setImageBitmap(icon);
|
|
||||||
backgroundOrIcon.setBackgroundColor(Color.TRANSPARENT);
|
|
||||||
} else {
|
} else {
|
||||||
textWhenNoImage.setVisibility(View.VISIBLE);
|
// Use header colour as background colour
|
||||||
|
|
||||||
int headerColor = getHeaderColor(context, loyaltyCard);
|
|
||||||
|
|
||||||
backgroundOrIcon.setImageBitmap(null);
|
|
||||||
backgroundOrIcon.setBackgroundColor(headerColor);
|
backgroundOrIcon.setBackgroundColor(headerColor);
|
||||||
|
|
||||||
|
// Manually calculate how many lines will be needed
|
||||||
|
// This is necessary because Android's auto sizing will split over lines way before reaching the minimum font size and store names split over multiple lines are harder to scan with a quick glance so we should try to prevent it
|
||||||
|
// Because we have to write the text before we can actually know the exact laid out size (trying to delay this causes bugs where the autosize fails) we have to take some... weird shortcuts
|
||||||
|
|
||||||
|
// At this point textWhenNoImage.getWidth() still returns 0, so we cheat by calculating the whole width of the screen and then dividing it by the amount of columns
|
||||||
|
int columnWidth = Resources.getSystem().getDisplayMetrics().widthPixels / columnCount;
|
||||||
|
|
||||||
|
// Calculate how wide a character is and calculate how many characters fit in a line
|
||||||
|
// text size is generally based on height, so setting 1:1 as width may be fishy
|
||||||
|
int characterWidth = TextViewCompat.getAutoSizeMinTextSize(textWhenNoImage);
|
||||||
|
int maxWidthPerLine = columnWidth - textWhenNoImage.getPaddingStart() - textWhenNoImage.getPaddingEnd();
|
||||||
|
|
||||||
|
// Set number of lines based on what could fit at most
|
||||||
|
int fullTextWidth = loyaltyCard.store.length() * characterWidth;
|
||||||
|
int maxLines = (fullTextWidth / maxWidthPerLine) + 1;
|
||||||
|
textWhenNoImage.setMaxLines(maxLines);
|
||||||
|
|
||||||
|
// Actually set the text and colour
|
||||||
|
textWhenNoImage.setVisibility(View.VISIBLE);
|
||||||
textWhenNoImage.setText(loyaltyCard.store);
|
textWhenNoImage.setText(loyaltyCard.store);
|
||||||
textWhenNoImage.setTextColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
textWhenNoImage.setTextColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean installedFromGooglePlay(Context context) {
|
return headerColor;
|
||||||
try {
|
|
||||||
String packageName = context.getPackageName();
|
|
||||||
String installer;
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
installer = context.getPackageManager().getInstallSourceInfo(packageName).getInstallingPackageName();
|
|
||||||
} else {
|
|
||||||
installer = context.getPackageManager().getInstallerPackageName(packageName);
|
|
||||||
}
|
|
||||||
return installer.equals("com.android.vending");
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getHeaderColor(Context context, LoyaltyCard loyaltyCard) {
|
public static int getHeaderColor(Context context, LoyaltyCard loyaltyCard) {
|
||||||
@@ -835,4 +1102,28 @@ public class Utils {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean deviceHasCamera(Context context) {
|
||||||
|
try {
|
||||||
|
return ((CameraManager) context.getSystemService(Context.CAMERA_SERVICE)).getCameraIdList().length > 0;
|
||||||
|
} catch (CameraAccessException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void applyWindowInsets(View root) {
|
||||||
|
/* This function basically fakes the activity being edge-to-edge. Useful for those activities that are really hard to get to behave well */
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(root, (view, windowInsets) -> {
|
||||||
|
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||||
|
|
||||||
|
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
|
||||||
|
layoutParams.leftMargin = insets.left;
|
||||||
|
layoutParams.bottomMargin = insets.bottom;
|
||||||
|
layoutParams.rightMargin = insets.right;
|
||||||
|
layoutParams.topMargin = insets.top;
|
||||||
|
view.setLayoutParams(layoutParams);
|
||||||
|
|
||||||
|
return WindowInsetsCompat.CONSUMED;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class ZipUtils {
|
|||||||
return new JSONObject(read(zipInputStream));
|
return new JSONObject(read(zipInputStream));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String read(ZipInputStream zipInputStream) throws IOException {
|
public static String read(ZipInputStream zipInputStream) throws IOException {
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
Reader reader = new BufferedReader(new InputStreamReader(zipInputStream, StandardCharsets.UTF_8));
|
Reader reader = new BufferedReader(new InputStreamReader(zipInputStream, StandardCharsets.UTF_8));
|
||||||
int c;
|
int c;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public class CatimaExporter implements Exporter {
|
|||||||
// Generate CSV
|
// Generate CSV
|
||||||
ByteArrayOutputStream catimaOutputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream catimaOutputStream = new ByteArrayOutputStream();
|
||||||
OutputStreamWriter catimaOutputStreamWriter = new OutputStreamWriter(catimaOutputStream, StandardCharsets.UTF_8);
|
OutputStreamWriter catimaOutputStreamWriter = new OutputStreamWriter(catimaOutputStream, StandardCharsets.UTF_8);
|
||||||
writeCSV(database, catimaOutputStreamWriter);
|
writeCSV(context, database, catimaOutputStreamWriter);
|
||||||
|
|
||||||
// Add CSV to zip file
|
// Add CSV to zip file
|
||||||
ZipParameters csvZipParameters = createZipParameters("catima.csv", password);
|
ZipParameters csvZipParameters = createZipParameters("catima.csv", password);
|
||||||
@@ -64,12 +64,12 @@ public class CatimaExporter implements Exporter {
|
|||||||
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(database);
|
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(database);
|
||||||
while (cardCursor.moveToNext()) {
|
while (cardCursor.moveToNext()) {
|
||||||
// For each card
|
// For each card
|
||||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
|
LoyaltyCard card = LoyaltyCard.fromCursor(context, cardCursor);
|
||||||
|
|
||||||
// For each image
|
// For each image
|
||||||
for (ImageLocationType imageLocationType : ImageLocationType.values()) {
|
for (ImageLocationType imageLocationType : ImageLocationType.values()) {
|
||||||
// If it exists, add to the .zip file
|
// If it exists, add to the .zip file
|
||||||
Bitmap image = Utils.retrieveCardImage(context, card.id, imageLocationType);
|
Bitmap image = card.getImageForImageLocationType(context, imageLocationType);
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
ZipParameters imageZipParameters = createZipParameters(Utils.getCardImageFileName(card.id, imageLocationType), password);
|
ZipParameters imageZipParameters = createZipParameters(Utils.getCardImageFileName(card.id, imageLocationType), password);
|
||||||
zipOutputStream.putNextEntry(imageZipParameters);
|
zipOutputStream.putNextEntry(imageZipParameters);
|
||||||
@@ -95,7 +95,7 @@ public class CatimaExporter implements Exporter {
|
|||||||
return zipParameters;
|
return zipParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeCSV(SQLiteDatabase database, OutputStreamWriter output) throws IOException, InterruptedException {
|
private void writeCSV(Context context, SQLiteDatabase database, OutputStreamWriter output) throws IOException, InterruptedException {
|
||||||
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
|
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
|
||||||
|
|
||||||
// Print the version
|
// Print the version
|
||||||
@@ -142,7 +142,7 @@ public class CatimaExporter implements Exporter {
|
|||||||
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(database);
|
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(database);
|
||||||
|
|
||||||
while (cardCursor.moveToNext()) {
|
while (cardCursor.moveToNext()) {
|
||||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
|
LoyaltyCard card = LoyaltyCard.fromCursor(context, cardCursor);
|
||||||
|
|
||||||
printer.printRecord(card.id,
|
printer.printRecord(card.id,
|
||||||
card.store,
|
card.store,
|
||||||
@@ -176,7 +176,7 @@ public class CatimaExporter implements Exporter {
|
|||||||
Cursor cardCursor2 = DBHelper.getLoyaltyCardCursor(database);
|
Cursor cardCursor2 = DBHelper.getLoyaltyCardCursor(database);
|
||||||
|
|
||||||
while (cardCursor2.moveToNext()) {
|
while (cardCursor2.moveToNext()) {
|
||||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor2);
|
LoyaltyCard card = LoyaltyCard.fromCursor(context, cardCursor2);
|
||||||
|
|
||||||
for (Group group : DBHelper.getLoyaltyCardGroups(database, card.id)) {
|
for (Group group : DBHelper.getLoyaltyCardGroups(database, card.id)) {
|
||||||
printer.printRecord(card.id, group._id);
|
printer.printRecord(card.id, group._id);
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ public class CatimaImporter implements Importer {
|
|||||||
Set<String> existingImages = DBHelper.imageFiles(context, database);
|
Set<String> existingImages = DBHelper.imageFiles(context, database);
|
||||||
|
|
||||||
for (LoyaltyCard card : data.cards) {
|
for (LoyaltyCard card : data.cards) {
|
||||||
LoyaltyCard existing = DBHelper.getLoyaltyCard(database, card.id);
|
LoyaltyCard existing = DBHelper.getLoyaltyCard(context, database, card.id);
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
DBHelper.insertLoyaltyCard(database, card.id, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType,
|
DBHelper.insertLoyaltyCard(database, card.id, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType,
|
||||||
card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
|
card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus);
|
||||||
@@ -152,7 +152,7 @@ public class CatimaImporter implements Importer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDuplicate(Context context, final LoyaltyCard existing, final LoyaltyCard card, final Set<String> existingImages, final Map<String, String> imageChecksums) throws IOException {
|
public boolean isDuplicate(Context context, final LoyaltyCard existing, final LoyaltyCard card, final Set<String> existingImages, final Map<String, String> imageChecksums) throws IOException {
|
||||||
if (!LoyaltyCard.isDuplicate(existing, card)) {
|
if (!LoyaltyCard.isDuplicate(context, existing, card)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (ImageLocationType imageLocationType : ImageLocationType.values()) {
|
for (ImageLocationType imageLocationType : ImageLocationType.values()) {
|
||||||
@@ -490,7 +490,29 @@ public class CatimaImporter implements Importer {
|
|||||||
// We catch this exception so we can still import old backups
|
// We catch this exception so we can still import old backups
|
||||||
}
|
}
|
||||||
|
|
||||||
return new LoyaltyCard(id, store, note, validFrom, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus, lastUsed, DBHelper.DEFAULT_ZOOM_LEVEL, archiveStatus);
|
return new LoyaltyCard(
|
||||||
|
id,
|
||||||
|
store,
|
||||||
|
note,
|
||||||
|
validFrom,
|
||||||
|
expiry,
|
||||||
|
balance,
|
||||||
|
balanceType,
|
||||||
|
cardId,
|
||||||
|
barcodeId,
|
||||||
|
barcodeType,
|
||||||
|
headerColor,
|
||||||
|
starStatus,
|
||||||
|
lastUsed,
|
||||||
|
DBHelper.DEFAULT_ZOOM_LEVEL,
|
||||||
|
archiveStatus,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -149,7 +149,29 @@ public class FidmeImporter implements Importer {
|
|||||||
// TODO: Front and back image
|
// TODO: Front and back image
|
||||||
|
|
||||||
// use -1 for the ID, it will be ignored when inserting the card into the DB
|
// use -1 for the ID, it will be ignored when inserting the card into the DB
|
||||||
return new LoyaltyCard(-1, store, note, null, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, starStatus, Utils.getUnixTime(), DBHelper.DEFAULT_ZOOM_LEVEL, archiveStatus);
|
return new LoyaltyCard(
|
||||||
|
-1,
|
||||||
|
store,
|
||||||
|
note,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
BigDecimal.valueOf(0),
|
||||||
|
null,
|
||||||
|
cardId,
|
||||||
|
null,
|
||||||
|
barcodeType,
|
||||||
|
headerColor,
|
||||||
|
starStatus,
|
||||||
|
Utils.getUnixTime(),
|
||||||
|
DBHelper.DEFAULT_ZOOM_LEVEL,
|
||||||
|
archiveStatus,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveAndDeduplicate(SQLiteDatabase database, final ImportedData data) {
|
public void saveAndDeduplicate(SQLiteDatabase database, final ImportedData data) {
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
|
import net.lingala.zip4j.ZipFile;
|
||||||
import net.lingala.zip4j.io.inputstream.ZipInputStream;
|
import net.lingala.zip4j.io.inputstream.ZipInputStream;
|
||||||
import net.lingala.zip4j.model.LocalFileHeader;
|
import net.lingala.zip4j.model.FileHeader;
|
||||||
|
|
||||||
import org.apache.commons.csv.CSVFormat;
|
import org.apache.commons.csv.CSVFormat;
|
||||||
import org.apache.commons.csv.CSVParser;
|
import org.apache.commons.csv.CSVParser;
|
||||||
@@ -20,9 +21,7 @@ import org.json.JSONException;
|
|||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -130,11 +129,9 @@ public class StocardImporter implements Importer {
|
|||||||
throw new FormatException("Issue parsing CSV data", e);
|
throw new FormatException("Issue parsing CSV data", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStream input = new FileInputStream(inputFile);
|
ZipFile zipFile = new ZipFile(inputFile, password);
|
||||||
ZipInputStream zipInputStream = new ZipInputStream(input, password);
|
zipData = importZIP(zipFile, zipData);
|
||||||
zipData = importZIP(zipInputStream, zipData);
|
zipFile.close();
|
||||||
zipInputStream.close();
|
|
||||||
input.close();
|
|
||||||
|
|
||||||
if (zipData.cards.keySet().size() == 0) {
|
if (zipData.cards.keySet().size() == 0) {
|
||||||
throw new FormatException("Couldn't find any loyalty cards in this Stocard export.");
|
throw new FormatException("Couldn't find any loyalty cards in this Stocard export.");
|
||||||
@@ -144,7 +141,7 @@ public class StocardImporter implements Importer {
|
|||||||
saveAndDeduplicate(context, database, importedData);
|
saveAndDeduplicate(context, database, importedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZIPData importZIP(ZipInputStream zipInputStream, final ZIPData zipData) throws IOException, FormatException, JSONException {
|
public ZIPData importZIP(ZipFile zipFile, final ZIPData zipData) throws IOException, FormatException, JSONException {
|
||||||
Map<String, StocardRecord> cards = zipData.cards;
|
Map<String, StocardRecord> cards = zipData.cards;
|
||||||
Map<String, StocardProvider> providers = zipData.providers;
|
Map<String, StocardProvider> providers = zipData.providers;
|
||||||
|
|
||||||
@@ -152,9 +149,8 @@ public class StocardImporter implements Importer {
|
|||||||
String[] cardBaseName = null;
|
String[] cardBaseName = null;
|
||||||
String customProviderId = "";
|
String customProviderId = "";
|
||||||
String cardName = "";
|
String cardName = "";
|
||||||
LocalFileHeader localFileHeader;
|
for (FileHeader fileHeader : zipFile.getFileHeaders()) {
|
||||||
while ((localFileHeader = zipInputStream.getNextEntry()) != null) {
|
String fileName = fileHeader.getFileName();
|
||||||
String fileName = localFileHeader.getFileName();
|
|
||||||
String[] nameParts = fileName.split("/");
|
String[] nameParts = fileName.split("/");
|
||||||
|
|
||||||
if (nameParts.length < 2) {
|
if (nameParts.length < 2) {
|
||||||
@@ -162,6 +158,7 @@ public class StocardImporter implements Importer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String userId = nameParts[1];
|
String userId = nameParts[1];
|
||||||
|
ZipInputStream zipInputStream = zipFile.getInputStream(fileHeader);
|
||||||
|
|
||||||
if (customProvidersBaseName == null) {
|
if (customProvidersBaseName == null) {
|
||||||
// FIXME: can we use the points-account/statement/content.json balance info somehow?
|
// FIXME: can we use the points-account/statement/content.json balance info somehow?
|
||||||
@@ -302,6 +299,8 @@ public class StocardImporter implements Importer {
|
|||||||
} else if (!fileName.endsWith("/")) {
|
} else if (!fileName.endsWith("/")) {
|
||||||
Log.d(TAG, "Unknown or unused file " + fileName + ", skipping...");
|
Log.d(TAG, "Unknown or unused file " + fileName + ", skipping...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zipInputStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ZIPData(cards, providers);
|
return new ZIPData(cards, providers);
|
||||||
@@ -355,7 +354,29 @@ public class StocardImporter implements Importer {
|
|||||||
|
|
||||||
long lastUsed = record.lastUsed != null ? record.lastUsed : Utils.getUnixTime();
|
long lastUsed = record.lastUsed != null ? record.lastUsed : Utils.getUnixTime();
|
||||||
|
|
||||||
LoyaltyCard card = new LoyaltyCard(tempID, store, note, null, null, BigDecimal.valueOf(0), null, record.cardId, null, barcodeType, headerColor, 0, lastUsed, DBHelper.DEFAULT_ZOOM_LEVEL, 0);
|
LoyaltyCard card = new LoyaltyCard(
|
||||||
|
tempID,
|
||||||
|
store,
|
||||||
|
note,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
BigDecimal.valueOf(0),
|
||||||
|
null,
|
||||||
|
record.cardId,
|
||||||
|
null,
|
||||||
|
barcodeType,
|
||||||
|
headerColor,
|
||||||
|
0,
|
||||||
|
lastUsed,
|
||||||
|
DBHelper.DEFAULT_ZOOM_LEVEL,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
importedData.cards.add(card);
|
importedData.cards.add(card);
|
||||||
|
|
||||||
Map<ImageLocationType, Bitmap> images = new HashMap<>();
|
Map<ImageLocationType, Bitmap> images = new HashMap<>();
|
||||||
|
|||||||
@@ -151,7 +151,29 @@ public class VoucherVaultImporter implements Importer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// use -1 for the ID, it will be ignored when inserting the card into the DB
|
// use -1 for the ID, it will be ignored when inserting the card into the DB
|
||||||
importedData.cards.add(new LoyaltyCard(-1, store, "", null, expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0, Utils.getUnixTime(), DBHelper.DEFAULT_ZOOM_LEVEL, 0));
|
importedData.cards.add(new LoyaltyCard(
|
||||||
|
-1,
|
||||||
|
store,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
expiry,
|
||||||
|
balance,
|
||||||
|
balanceType,
|
||||||
|
cardId,
|
||||||
|
null,
|
||||||
|
barcodeType,
|
||||||
|
headerColor,
|
||||||
|
0,
|
||||||
|
Utils.getUnixTime(),
|
||||||
|
DBHelper.DEFAULT_ZOOM_LEVEL,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return importedData;
|
return importedData;
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
package protect.card_locker.preferences;
|
package protect.card_locker.preferences;
|
||||||
|
|
||||||
|
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.IntegerRes;
|
import androidx.annotation.IntegerRes;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
@@ -14,8 +18,9 @@ import protect.card_locker.R;
|
|||||||
import protect.card_locker.Utils;
|
import protect.card_locker.Utils;
|
||||||
|
|
||||||
public class Settings {
|
public class Settings {
|
||||||
|
private static final String TAG = "Catima";
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private SharedPreferences mSettings;
|
private final SharedPreferences mSettings;
|
||||||
|
|
||||||
public Settings(Context context) {
|
public Settings(Context context) {
|
||||||
mContext = context.getApplicationContext();
|
mContext = context.getApplicationContext();
|
||||||
@@ -42,10 +47,11 @@ public class Settings {
|
|||||||
return mSettings.getBoolean(getResString(keyId), defaultValue);
|
return mSettings.getBoolean(getResString(keyId), defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public Locale getLocale() {
|
public Locale getLocale() {
|
||||||
String value = getString(R.string.settings_key_locale, "");
|
String value = getString(R.string.settings_key_locale, "");
|
||||||
|
|
||||||
if (value.length() == 0) {
|
if (value.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,4 +97,23 @@ public class Settings {
|
|||||||
public String getColor() {
|
public String getColor() {
|
||||||
return getString(R.string.setting_key_theme_color, mContext.getResources().getString(R.string.settings_key_system_theme));
|
return getString(R.string.setting_key_theme_color, mContext.getResources().getString(R.string.settings_key_system_theme));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getPreferredColumnCount() {
|
||||||
|
var defaultSymbol = mContext.getResources().getString(R.string.settings_key_automatic_column_count);
|
||||||
|
var defaultColumnCount = mContext.getResources().getInteger(R.integer.main_view_card_columns);
|
||||||
|
var orientation = mContext.getResources().getConfiguration().orientation;
|
||||||
|
var columnCountPrefKey = orientation == ORIENTATION_PORTRAIT ? R.string.setting_key_column_count_portrait : R.string.setting_key_column_count_landscape;
|
||||||
|
var columnCountSetting = getString(columnCountPrefKey, defaultSymbol);
|
||||||
|
try {
|
||||||
|
// the pref may be unset or explicitly set to default
|
||||||
|
return columnCountSetting.equals(defaultSymbol) ? defaultColumnCount : Integer.parseInt(columnCountSetting);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
Log.e(TAG, "Failed to parseInt the column count pref", nfe);
|
||||||
|
return defaultColumnCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean useVolumeKeysForNavigation() {
|
||||||
|
return getBoolean(R.string.settings_key_use_volume_keys_navigation, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public class SettingsActivity extends CatimaAppCompatActivity {
|
|||||||
binding = SettingsActivityBinding.inflate(getLayoutInflater());
|
binding = SettingsActivityBinding.inflate(getLayoutInflater());
|
||||||
setTitle(R.string.settings);
|
setTitle(R.string.settings);
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
Utils.applyWindowInsets(binding.getRoot());
|
||||||
Toolbar toolbar = binding.toolbar;
|
Toolbar toolbar = binding.toolbar;
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
enableToolbarBackButton();
|
enableToolbarBackButton();
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package protect.card_locker.viewmodels
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import protect.card_locker.LoyaltyCard
|
||||||
|
import protect.card_locker.LoyaltyCardField
|
||||||
|
import protect.card_locker.async.TaskHandler
|
||||||
|
|
||||||
|
class LoyaltyCardEditActivityViewModel : ViewModel() {
|
||||||
|
var initialized: Boolean = false
|
||||||
|
var hasChanged: Boolean = false
|
||||||
|
|
||||||
|
var taskHandler: TaskHandler = TaskHandler();
|
||||||
|
|
||||||
|
var addGroup: String? = null
|
||||||
|
var openSetIconMenu: Boolean = false
|
||||||
|
var loyaltyCardId: Int = 0
|
||||||
|
var updateLoyaltyCard: Boolean = false
|
||||||
|
var duplicateFromLoyaltyCardId: Boolean = false
|
||||||
|
var importLoyaltyCardUri: Uri? = null
|
||||||
|
|
||||||
|
var tabIndex: Int = 0
|
||||||
|
var requestedImageType: Int = 0
|
||||||
|
var tempLoyaltyCardField: LoyaltyCardField? = null
|
||||||
|
|
||||||
|
var loyaltyCard: LoyaltyCard = LoyaltyCard()
|
||||||
|
}
|
||||||
5
app/src/main/res/drawable/baseline_block_24.xml
Normal file
5
app/src/main/res/drawable/baseline_block_24.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?attr/colorControlNormal" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-4.42 3.58,-8 8,-8 1.85,0 3.55,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4,13.85 4,12zM12,20c-1.85,0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20,10.15 20,12c0,4.42 -3.58,8 -8,8z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/baseline_image_24.xml
Normal file
5
app/src/main/res/drawable/baseline_image_24.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?attr/colorControlNormal" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/baseline_picture_as_pdf_24.xml
Normal file
5
app/src/main/res/drawable/baseline_picture_as_pdf_24.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?attr/colorControlNormal" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM11.5,9.5c0,0.83 -0.67,1.5 -1.5,1.5L9,11v2L7.5,13L7.5,7L10,7c0.83,0 1.5,0.67 1.5,1.5v1zM16.5,11.5c0,0.83 -0.67,1.5 -1.5,1.5h-2.5L12.5,7L15,7c0.83,0 1.5,0.67 1.5,1.5v3zM20.5,8.5L19,8.5v1h1.5L20.5,11L19,11v2h-1.5L17.5,7h3v1.5zM9,9.5h1v-1L9,8.5v1zM4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM14,11.5h1v-3h-1v3z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="@android:color/black">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/black"
|
|
||||||
android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M7.41,18.59L8.83,20 12,16.83 15.17,20l1.41,-1.41L12,14l-4.59,4.59zM16.59,5.41L15.17,4 12,7.17 8.83,4 7.41,5.41 12,10l4.59,-4.59z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="#000000"
|
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="#000000"
|
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
|
|
||||||
</vector>
|
|
||||||
10
app/src/main/res/drawable/local_activity_24px.xml
Normal file
10
app/src/main/res/drawable/local_activity_24px.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M368,640L480,556L590,640L548,504L660,416L524,416L480,280L436,416L300,416L410,504L368,640ZM160,800Q127,800 103.5,776.5Q80,753 80,720L80,585Q80,574 87,566Q94,558 105,556Q129,548 144.5,527Q160,506 160,480Q160,454 144.5,433Q129,412 105,404Q94,402 87,394Q80,386 80,375L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,375Q880,386 873,394Q866,402 855,404Q831,412 815.5,433Q800,454 800,480Q800,506 815.5,527Q831,548 855,556Q866,558 873,566Q880,574 880,585L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,618Q763,596 741.5,559.5Q720,523 720,480Q720,437 741.5,400.5Q763,364 800,342L800,240Q800,240 800,240Q800,240 800,240L160,240Q160,240 160,240Q160,240 160,240L160,342Q197,364 218.5,400.5Q240,437 240,480Q240,523 218.5,559.5Q197,596 160,618L160,720Q160,720 160,720Q160,720 160,720ZM480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Z"/>
|
||||||
|
</vector>
|
||||||
@@ -2,9 +2,10 @@
|
|||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24"
|
android:viewportHeight="24">
|
||||||
android:tint="@android:color/white">
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="#D3D3D3"
|
||||||
android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z"/>
|
android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z"
|
||||||
|
android:strokeWidth="0.25"
|
||||||
|
android:strokeColor="#777777"/>
|
||||||
</vector>
|
</vector>
|
||||||
11
app/src/main/res/drawable/loyalty_card_icon_starred.xml
Normal file
11
app/src/main/res/drawable/loyalty_card_icon_starred.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#D3D3D3"
|
||||||
|
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"
|
||||||
|
android:strokeWidth="0.25"
|
||||||
|
android:strokeColor="#777777"/>
|
||||||
|
</vector>
|
||||||
@@ -10,7 +10,8 @@
|
|||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
@@ -20,11 +21,12 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<ScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="?attr/actionBarSize"
|
android:layout_marginTop="?attr/actionBarSize"
|
||||||
android:padding="10dp">
|
android:paddingVertical="8dp"
|
||||||
|
android:clipToPadding="false">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -35,14 +37,17 @@
|
|||||||
android:id="@+id/version_history"
|
android:id="@+id/version_history"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dp">
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:background="?android:selectableItemBackground">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/version_history_main"
|
android:id="@+id/version_history_main"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="sans-serif-medium"
|
android:fontFamily="sans-serif-medium"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:text="@string/version_history"
|
android:text="@string/version_history"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -52,7 +57,8 @@
|
|||||||
android:id="@+id/version_history_sub"
|
android:id="@+id/version_history_sub"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/version_history_main" />
|
app:layout_constraintTop_toBottomOf="@id/version_history_main" />
|
||||||
@@ -74,14 +80,17 @@
|
|||||||
android:id="@+id/credits"
|
android:id="@+id/credits"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dp">
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:background="?android:selectableItemBackground">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/credits_main"
|
android:id="@+id/credits_main"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="sans-serif-medium"
|
android:fontFamily="sans-serif-medium"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:text="@string/credits"
|
android:text="@string/credits"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -91,7 +100,8 @@
|
|||||||
android:id="@+id/credits_sub"
|
android:id="@+id/credits_sub"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/credits_main" />
|
app:layout_constraintTop_toBottomOf="@id/credits_main" />
|
||||||
@@ -113,14 +123,17 @@
|
|||||||
android:id="@+id/translate"
|
android:id="@+id/translate"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dp">
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:background="?android:selectableItemBackground">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/translate_main"
|
android:id="@+id/translate_main"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="sans-serif-medium"
|
android:fontFamily="sans-serif-medium"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:text="@string/help_translate_this_app"
|
android:text="@string/help_translate_this_app"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -130,7 +143,8 @@
|
|||||||
android:id="@+id/translate_sub"
|
android:id="@+id/translate_sub"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:text="@string/translate_platform"
|
android:text="@string/translate_platform"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -153,14 +167,17 @@
|
|||||||
android:id="@+id/license"
|
android:id="@+id/license"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dp">
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:background="?android:selectableItemBackground">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/license_main"
|
android:id="@+id/license_main"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="sans-serif-medium"
|
android:fontFamily="sans-serif-medium"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:text="@string/license"
|
android:text="@string/license"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -170,7 +187,8 @@
|
|||||||
android:id="@+id/license_sub"
|
android:id="@+id/license_sub"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:text="@string/app_license"
|
android:text="@string/app_license"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -193,14 +211,17 @@
|
|||||||
android:id="@+id/repo"
|
android:id="@+id/repo"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dp">
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:background="?android:selectableItemBackground">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/repo_main"
|
android:id="@+id/repo_main"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="sans-serif-medium"
|
android:fontFamily="sans-serif-medium"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:text="@string/source_repository"
|
android:text="@string/source_repository"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -210,7 +231,8 @@
|
|||||||
android:id="@+id/repo_sub"
|
android:id="@+id/repo_sub"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:text="@string/on_github"
|
android:text="@string/on_github"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -233,14 +255,17 @@
|
|||||||
android:id="@+id/privacy"
|
android:id="@+id/privacy"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dp">
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:background="?android:selectableItemBackground">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/privacy_main"
|
android:id="@+id/privacy_main"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="sans-serif-medium"
|
android:fontFamily="sans-serif-medium"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:text="@string/privacy_policy"
|
android:text="@string/privacy_policy"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -250,7 +275,8 @@
|
|||||||
android:id="@+id/privacy_sub"
|
android:id="@+id/privacy_sub"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:text="@string/and_data_usage"
|
android:text="@string/and_data_usage"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -273,17 +299,21 @@
|
|||||||
android:id="@+id/donate"
|
android:id="@+id/donate"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dp">
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:background="?android:selectableItemBackground">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/donate_main"
|
android:id="@+id/donate_main"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="sans-serif-medium"
|
android:fontFamily="sans-serif-medium"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:text="@string/donate"
|
android:text="@string/donate"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -303,14 +333,17 @@
|
|||||||
android:id="@+id/rate"
|
android:id="@+id/rate"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dp">
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:background="?android:selectableItemBackground">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/rate_main"
|
android:id="@+id/rate_main"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="sans-serif-medium"
|
android:fontFamily="sans-serif-medium"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:text="@string/rate_this_app"
|
android:text="@string/rate_this_app"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -320,7 +353,8 @@
|
|||||||
android:id="@+id/rate_sub"
|
android:id="@+id/rate_sub"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:text="@string/on_google_play"
|
android:text="@string/on_google_play"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -340,17 +374,20 @@
|
|||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:padding="8dp"
|
|
||||||
android:id="@+id/report_error"
|
android:id="@+id/report_error"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:background="?android:selectableItemBackground">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/report_error_main"
|
android:id="@+id/report_error_main"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="sans-serif-medium"
|
android:fontFamily="sans-serif-medium"
|
||||||
android:padding="2dp"
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:text="@string/report_error"
|
android:text="@string/report_error"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -362,9 +399,10 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintTop_toBottomOf="@id/report_error_main"
|
app:layout_constraintTop_toBottomOf="@id/report_error_main"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:text="@string/on_github"
|
android:text="@string/on_github" />
|
||||||
android:padding="2dp"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:importantForAccessibility="no"
|
android:importantForAccessibility="no"
|
||||||
@@ -379,5 +417,5 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
@@ -31,13 +31,6 @@
|
|||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
style="?attr/toolbarStyle" />
|
style="?attr/toolbarStyle" />
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
|
||||||
android:id="@+id/groups"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:tabMode="scrollable" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
31
app/src/main/res/layout/alertdialog_row_with_icon.xml
Normal file
31
app/src/main/res/layout/alertdialog_row_with_icon.xml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout 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">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/textView"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/imageView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -9,20 +9,20 @@
|
|||||||
tools:showIn="@layout/custom_barcode_scanner">
|
tools:showIn="@layout/custom_barcode_scanner">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/camera_permission_denied_clickable_area"
|
android:id="@+id/camera_error_clickable_area"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/camera_permission_denied_icon"
|
android:id="@+id/camera_error_icon"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="84dp"
|
android:layout_height="84dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:src="@drawable/camera_permission_denied" />
|
android:src="@drawable/camera_error" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/camera_permission_denied_title"
|
android:id="@+id/camera_error_title"
|
||||||
style="@style/TextAppearance.Material3.HeadlineLarge"
|
style="@style/TextAppearance.Material3.HeadlineLarge"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -30,12 +30,12 @@
|
|||||||
android:text="@string/cameraPermissionDeniedTitle" />
|
android:text="@string/cameraPermissionDeniedTitle" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/camera_permission_denied_message"
|
android:id="@+id/camera_error_message"
|
||||||
style="@style/AppTheme.TextView.NoData"
|
style="@style/AppTheme.TextView.NoData"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="@string/noCameraPermissionDirectToSystemSetting" />
|
android:text="@string/zxing_msg_camera_framework_bug" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/list"
|
android:id="@+id/list"
|
||||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
app:spanCount="1"
|
app:spanCount="@integer/main_view_card_columns"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_width="match_parent" />
|
android:layout_width="match_parent" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
@@ -34,8 +34,8 @@
|
|||||||
android:padding="@dimen/activity_scanner_padding">
|
android:padding="@dimen/activity_scanner_padding">
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/camera_permission_denied_layout"
|
android:id="@+id/camera_error_layout"
|
||||||
layout="@layout/camera_permission_failed_layout"
|
layout="@layout/camera_error_layout"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
tools:context="protect.card_locker.MainActivity"
|
tools:context="protect.card_locker.ManageGroupActivity"
|
||||||
tools:showIn="@layout/main_activity">
|
tools:showIn="@layout/main_activity">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
@@ -17,11 +18,11 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.core.widget.NestedScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fillViewport="true"
|
android:fillViewport="true"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
<LinearLayout android:orientation="vertical"
|
<LinearLayout android:orientation="vertical"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
@@ -127,7 +128,7 @@
|
|||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:text="@string/importOptionApplicationButton" />
|
android:text="@string/importOptionApplicationButton" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
@@ -6,4 +6,5 @@
|
|||||||
android:paddingRight="8dp"
|
android:paddingRight="8dp"
|
||||||
style="@style/Widget.MaterialComponents.Chip.Filter"
|
style="@style/Widget.MaterialComponents.Chip.Filter"
|
||||||
app:checkedIconVisible="true"
|
app:checkedIconVisible="true"
|
||||||
android:textAppearance="?android:attr/textAppearance" />
|
android:textAppearance="?android:attr/textAppearance"
|
||||||
|
app:checkedIconTint="?attr/colorOnBackground"/>
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
@@ -29,7 +30,8 @@
|
|||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/tabs"
|
android:id="@+id/tabs"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/transparent">
|
||||||
<com.google.android.material.tabs.TabItem
|
<com.google.android.material.tabs.TabItem
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -46,9 +48,10 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<ScrollView android:layout_width="fill_parent"
|
<androidx.core.widget.NestedScrollView
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="fill_parent"
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
||||||
<TableLayout
|
<TableLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -473,6 +476,6 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</TableLayout>
|
</TableLayout>
|
||||||
</TableLayout>
|
</TableLayout>
|
||||||
</ScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
@@ -41,13 +41,10 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
|
app:autoSizeMinTextSize="6sp"
|
||||||
app:autoSizeTextType="uniform"
|
app:autoSizeTextType="uniform"
|
||||||
app:autoSizeMinTextSize="12sp"
|
|
||||||
app:autoSizeMaxTextSize="100sp"
|
|
||||||
app:autoSizeStepGranularity="2sp"
|
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:maxLines="1"
|
android:padding="10dp" />
|
||||||
android:layout_margin="20dp" />
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:importantForAccessibility="no"
|
android:importantForAccessibility="no"
|
||||||
@@ -75,29 +72,15 @@
|
|||||||
android:layout_width="@dimen/cardThumbnailSize"
|
android:layout_width="@dimen/cardThumbnailSize"
|
||||||
android:layout_height="@dimen/cardThumbnailSize"
|
android:layout_height="@dimen/cardThumbnailSize"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:alpha="0.5"
|
android:alpha="0.8"
|
||||||
android:contentDescription="@string/starred"
|
android:contentDescription="@string/starred"
|
||||||
android:elevation="4dp"
|
android:elevation="4dp"
|
||||||
|
android:rotationX="2"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:srcCompat="@drawable/ic_starred_white"
|
app:srcCompat="@drawable/loyalty_card_icon_starred"
|
||||||
tools:ignore="ImageContrastCheck" />
|
tools:ignore="ImageContrastCheck"/>
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
android:id="@+id/star_border"
|
|
||||||
android:layout_width="@dimen/cardThumbnailSize"
|
|
||||||
android:layout_height="@dimen/cardThumbnailSize"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:alpha="0.5"
|
|
||||||
android:contentDescription="@string/starImage"
|
|
||||||
android:elevation="4dp"
|
|
||||||
android:visibility="visible"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:srcCompat="@drawable/ic_unstarred_black"
|
|
||||||
tools:ignore="ImageContrastCheck" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
@@ -113,18 +96,18 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/archive_background"
|
android:id="@+id/archive_background"
|
||||||
android:layout_width="41dp"
|
android:layout_width="@dimen/cardThumbnailSize"
|
||||||
android:layout_height="44dp"
|
android:layout_height="@dimen/cardThumbnailSize"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:alpha="0.5"
|
android:alpha="0.8"
|
||||||
android:contentDescription="@string/archived"
|
android:contentDescription="@string/archived"
|
||||||
android:elevation="4dp"
|
android:elevation="4dp"
|
||||||
android:rotationX="2"
|
android:rotationX="2"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
app:srcCompat="@drawable/ic_baseline_archive_24"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
tools:ignore="ImageContrastCheck,MissingConstraints"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:layout_editor_absoluteX="0dp"
|
app:srcCompat="@drawable/loyalty_card_icon_archived"
|
||||||
tools:layout_editor_absoluteY="-1dp" />
|
tools:ignore="ImageContrastCheck" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
@@ -154,6 +137,8 @@
|
|||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:layout_marginBottom="4dp"
|
android:layout_marginBottom="4dp"
|
||||||
android:textAppearance="?attr/textAppearanceBody2"
|
android:textAppearance="?attr/textAppearanceBody2"
|
||||||
|
android:maxLines="5"
|
||||||
|
android:ellipsize="end"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible"
|
tools:visibility="visible"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/store"
|
app:layout_constraintTop_toBottomOf="@+id/store"
|
||||||
|
|||||||
@@ -121,7 +121,7 @@
|
|||||||
android:layout_weight="1"/>
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/card_id_view"
|
android:id="@+id/main_image_description"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="@dimen/text_size_large"
|
android:textSize="@dimen/text_size_large"
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
android:id="@+id/groups"
|
android:id="@+id/groups"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
app:tabMode="scrollable"
|
app:tabMode="scrollable"
|
||||||
android:visibility="gone"/>
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
|
|||||||
@@ -1,56 +1,81 @@
|
|||||||
Sylvia van Os
|
Sylvia van Os
|
||||||
Branden Archer
|
Branden Archer
|
||||||
J. Lavoie
|
J. Lavoie
|
||||||
Allan Nordhøy
|
|
||||||
solokot
|
solokot
|
||||||
|
Allan Nordhøy
|
||||||
Heimen Stoffels
|
Heimen Stoffels
|
||||||
FC (Fay) Stegerman
|
|
||||||
Oğuz Ersen
|
Oğuz Ersen
|
||||||
|
FC (Fay) Stegerman
|
||||||
|
StoyanDimitrov
|
||||||
Katharine Chui
|
Katharine Chui
|
||||||
SlavekB
|
SlavekB
|
||||||
StoyanDimitrov
|
|
||||||
mondstern
|
mondstern
|
||||||
IllusiveMan196
|
IllusiveMan196
|
||||||
|
大王叫我来巡山
|
||||||
Altonss
|
Altonss
|
||||||
Michael Moroni
|
Michael Moroni
|
||||||
GM
|
|
||||||
Eric
|
Eric
|
||||||
|
B o d o
|
||||||
|
GM
|
||||||
|
laralem
|
||||||
Petr Novák
|
Petr Novák
|
||||||
Joel A
|
Joel A
|
||||||
laralem
|
Priit Jõerüüt
|
||||||
Taco
|
Taco
|
||||||
pfaffenrodt
|
pfaffenrodt
|
||||||
Aayush Gupta
|
Aayush Gupta
|
||||||
|
Scrambled777
|
||||||
|
Максим Горпиніч
|
||||||
|
ikanakova
|
||||||
HudobniVolk
|
HudobniVolk
|
||||||
|
Giovanni Donisi
|
||||||
|
Edgars Andersons
|
||||||
|
Jiri Grönroos
|
||||||
Nyatsuki
|
Nyatsuki
|
||||||
|
Silvério Santos
|
||||||
|
Balázs Meskó
|
||||||
|
Milo Ivir
|
||||||
Samantaz Fox
|
Samantaz Fox
|
||||||
arno-github
|
|
||||||
Cliff Heraldo
|
Cliff Heraldo
|
||||||
Sergio Paredes
|
Sergio Paredes
|
||||||
Ankit Tiwari
|
Ankit Tiwari
|
||||||
|
josé m
|
||||||
|
Arno-github
|
||||||
|
Jose Delvani
|
||||||
|
mdvhimself
|
||||||
Milan Šalka
|
Milan Šalka
|
||||||
Jiri Grönroos
|
Kachelkaiser
|
||||||
Balázs Meskó
|
|
||||||
Giovanni Donisi
|
|
||||||
Milo Ivir
|
|
||||||
Skrripy
|
Skrripy
|
||||||
huuhaa
|
huuhaa
|
||||||
Projjal Moitra
|
|
||||||
Quentin PAGÈS
|
|
||||||
waffshappen
|
waffshappen
|
||||||
Eryk Michalak
|
Marnick L'Eau
|
||||||
|
Maksim2005UA
|
||||||
|
ngocanhtve
|
||||||
|
Quentin PAGÈS
|
||||||
|
Projjal Moitra
|
||||||
|
Robin
|
||||||
|
JungHee Lee
|
||||||
|
தமிழ் நேரம்
|
||||||
Ziad OUALHADJ
|
Ziad OUALHADJ
|
||||||
|
Vasilis
|
||||||
Robin Liu
|
Robin Liu
|
||||||
mdvhimself
|
Renko
|
||||||
Denis Shilin
|
Denis Shilin
|
||||||
Miha Frangež
|
しいたけ
|
||||||
Silvério Santos
|
|
||||||
ikanakova
|
|
||||||
Virginie
|
|
||||||
Michael Gangolf
|
|
||||||
rudy3
|
|
||||||
Kim Seohyun
|
|
||||||
Govind S Nair
|
|
||||||
Freddo espresso
|
|
||||||
arshbeerSingh
|
|
||||||
Alexander Ivanov
|
Alexander Ivanov
|
||||||
|
Miha Frangež
|
||||||
|
Viet Nguyen Hoang
|
||||||
|
stavpup
|
||||||
|
ehrt74
|
||||||
|
Virginie
|
||||||
|
Tim Trek
|
||||||
|
MisterCosta96
|
||||||
|
arshbeerSingh
|
||||||
|
Augustin LAVILLE
|
||||||
|
Freddo espresso
|
||||||
|
Govind S Nair
|
||||||
|
Kim Seohyun
|
||||||
|
rudy3
|
||||||
|
Ricky Tigg
|
||||||
|
Michael Gangolf
|
||||||
|
Peter Dave Hello
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ _id,name,barcodeFormat
|
|||||||
015cf86e-c4b6-42b5-abed-5821492b2669,Campbells,ITF
|
015cf86e-c4b6-42b5-abed-5821492b2669,Campbells,ITF
|
||||||
016c8380-d433-4eb1-b7a0-df6fd9254ec6,Friendlies Pharmacy,CODE_128
|
016c8380-d433-4eb1-b7a0-df6fd9254ec6,Friendlies Pharmacy,CODE_128
|
||||||
0189b6a0-3f02-418f-872e-d5e354619a45,Mencke Gartencenter,EAN_8
|
0189b6a0-3f02-418f-872e-d5e354619a45,Mencke Gartencenter,EAN_8
|
||||||
|
01b239f4-d1db-4311-a33b-bc8bb9c71c19,McEwan,CODE_128
|
||||||
01ce8326-50e8-4787-9999-e509dfed15cb,Вигода Вопак,CODE_128
|
01ce8326-50e8-4787-9999-e509dfed15cb,Вигода Вопак,CODE_128
|
||||||
01eafcc6-ee41-447f-bbce-7a93ffb90b6c,Mario Mikke,EAN_13
|
01eafcc6-ee41-447f-bbce-7a93ffb90b6c,Mario Mikke,EAN_13
|
||||||
01f88e2d-3eb4-4242-a32b-1a847a28e140,Crodux,CODE_128
|
01f88e2d-3eb4-4242-a32b-1a847a28e140,Crodux,CODE_128
|
||||||
@@ -31,6 +32,7 @@ _id,name,barcodeFormat
|
|||||||
037f2420-273c-4ffe-9dd3-af22868b1b59,Al Pentolone,EAN_13
|
037f2420-273c-4ffe-9dd3-af22868b1b59,Al Pentolone,EAN_13
|
||||||
038516b8-3cdd-4f96-9582-97caf9dc3a47,Dier Specialist,CODE_39
|
038516b8-3cdd-4f96-9582-97caf9dc3a47,Dier Specialist,CODE_39
|
||||||
039784f4-4fef-497e-8f03-f026655394ef,террапевтика,EAN_13
|
039784f4-4fef-497e-8f03-f026655394ef,террапевтика,EAN_13
|
||||||
|
039932ff-caec-4d40-aa9a-0ed185b5cf5f,FNV,CODE_128
|
||||||
03b89b04-69cd-43cf-88eb-35760f092488,Мегаполис,CODE_128
|
03b89b04-69cd-43cf-88eb-35760f092488,Мегаполис,CODE_128
|
||||||
03d62f02-8266-493b-b4fd-95d5c853b87b,мта,EAN_13
|
03d62f02-8266-493b-b4fd-95d5c853b87b,мта,EAN_13
|
||||||
03fd0d65-b3dd-427b-9f7c-3554fe3dc99b,Happy Sport,EAN_13
|
03fd0d65-b3dd-427b-9f7c-3554fe3dc99b,Happy Sport,EAN_13
|
||||||
@@ -60,6 +62,7 @@ _id,name,barcodeFormat
|
|||||||
0777b427-2af5-4531-81c3-f7421dde9d63,Евразия Автозапчасти,EAN_13
|
0777b427-2af5-4531-81c3-f7421dde9d63,Евразия Автозапчасти,EAN_13
|
||||||
078a5228-818d-4a86-8726-c71dd27a3fdc,EU COVID-19 Certificado de Vacunacion,QR_CODE
|
078a5228-818d-4a86-8726-c71dd27a3fdc,EU COVID-19 Certificado de Vacunacion,QR_CODE
|
||||||
078fdcef-2e8a-4179-befe-5959cd588a7e,Клякса,EAN_13
|
078fdcef-2e8a-4179-befe-5959cd588a7e,Клякса,EAN_13
|
||||||
|
07a90343-0b80-4cb4-8571-b6a2419cff6e,Maracatú,CODE_128
|
||||||
07f645dc-3127-4050-94ac-41f42cacdb74,Cats & Dogs,EAN_8
|
07f645dc-3127-4050-94ac-41f42cacdb74,Cats & Dogs,EAN_8
|
||||||
081924f1-3eff-480a-a8a9-ec08eb4b75e7,Rossetti Market,EAN_13
|
081924f1-3eff-480a-a8a9-ec08eb4b75e7,Rossetti Market,EAN_13
|
||||||
0821c8d1-4556-4178-af1b-fe4d1977127d,Feedo,CODE_128
|
0821c8d1-4556-4178-af1b-fe4d1977127d,Feedo,CODE_128
|
||||||
@@ -84,11 +87,14 @@ _id,name,barcodeFormat
|
|||||||
09e1c670-eac2-4077-8a66-b990c3ba1ed8,Gamble & Brown Cafe,CODE_39
|
09e1c670-eac2-4077-8a66-b990c3ba1ed8,Gamble & Brown Cafe,CODE_39
|
||||||
09e38952-3559-4432-821a-84fdee4923f8,Стройка,EAN_13
|
09e38952-3559-4432-821a-84fdee4923f8,Стройка,EAN_13
|
||||||
0a047088-f9f9-47c5-a982-b307122f09fa,IGA Rewards,EAN_13
|
0a047088-f9f9-47c5-a982-b307122f09fa,IGA Rewards,EAN_13
|
||||||
|
0a058735-ecfd-4278-ae7a-9f6917193a3d,JBs Power Centre,CODE_128
|
||||||
|
0a124613-4513-4a4f-b89a-6c4b645e395b,BoniChoix,CODE_128
|
||||||
0a6c06b6-056d-4bf2-ae78-915a8c52d464,волгорост,EAN_13
|
0a6c06b6-056d-4bf2-ae78-915a8c52d464,волгорост,EAN_13
|
||||||
0a7c000b-39eb-4464-bc41-03d0e1f4a20f,Life Pharmacy ,CODE_128
|
0a7c000b-39eb-4464-bc41-03d0e1f4a20f,Life Pharmacy ,CODE_128
|
||||||
0ae08429-e2a2-4fe0-840a-e940ce9fd3e5,Zebra,EAN_13
|
0ae08429-e2a2-4fe0-840a-e940ce9fd3e5,Zebra,EAN_13
|
||||||
0b2502b7-f8d7-426e-b518-4482ee6115eb,Лоза,EAN_13
|
0b2502b7-f8d7-426e-b518-4482ee6115eb,Лоза,EAN_13
|
||||||
0b4c67fb-bf76-46e8-9a3b-cb0acfe47e71,Giocheria,CODE_39
|
0b4c67fb-bf76-46e8-9a3b-cb0acfe47e71,Giocheria,CODE_39
|
||||||
|
0b539afa-e6b5-42a0-8f03-50d5de9f4af0,MediaMarkt Club Karte,QR_CODE
|
||||||
0b600df8-f694-49d5-b5ee-56d0b47ab1bc,reima,EAN_13
|
0b600df8-f694-49d5-b5ee-56d0b47ab1bc,reima,EAN_13
|
||||||
0b82965b-29df-4c9e-ae5f-70a5d10f1d32,Fanølinjen,CODE_128
|
0b82965b-29df-4c9e-ae5f-70a5d10f1d32,Fanølinjen,CODE_128
|
||||||
0bb951c2-c644-4a0b-92c0-754d739a55be,ZALY,EAN_13
|
0bb951c2-c644-4a0b-92c0-754d739a55be,ZALY,EAN_13
|
||||||
@@ -128,8 +134,9 @@ _id,name,barcodeFormat
|
|||||||
0f650862-0a1c-4596-b2f9-30fc8d3bf8d3,Lila Bäcker,QR_CODE
|
0f650862-0a1c-4596-b2f9-30fc8d3bf8d3,Lila Bäcker,QR_CODE
|
||||||
0f69ba3f-6084-49a5-b959-24277008de45,CJ Express,CODE_128
|
0f69ba3f-6084-49a5-b959-24277008de45,CJ Express,CODE_128
|
||||||
0f936e1f-b3ac-4a34-aad7-a18bd76150f2,FOTOLAB,CODE_128
|
0f936e1f-b3ac-4a34-aad7-a18bd76150f2,FOTOLAB,CODE_128
|
||||||
|
0fafa67a-b4d2-4365-9f68-c167d43c7070,I TOURS,CODE_128
|
||||||
0fce03a0-6b7b-427c-a483-26a1169e73b0,EDMINS,EAN_13
|
0fce03a0-6b7b-427c-a483-26a1169e73b0,EDMINS,EAN_13
|
||||||
1,Accor Le Club,QR_CODE
|
1,Accor Live Limitless,QR_CODE
|
||||||
10,Aeroplan,CODE_128
|
10,Aeroplan,CODE_128
|
||||||
100,Esprit,ITF
|
100,Esprit,ITF
|
||||||
1000,Chemmart Pharmacy,CODE_128
|
1000,Chemmart Pharmacy,CODE_128
|
||||||
@@ -465,6 +472,7 @@ _id,name,barcodeFormat
|
|||||||
13,Amavita,EAN_13
|
13,Amavita,EAN_13
|
||||||
130,GNC,UPC_A
|
130,GNC,UPC_A
|
||||||
1300,IZOD,CODE_128
|
1300,IZOD,CODE_128
|
||||||
|
13004ca8-9095-40c2-aa98-1fcf6410efc7,Max Shop,CODE_128
|
||||||
1301,La Quinta Inns,CODE_128
|
1301,La Quinta Inns,CODE_128
|
||||||
1302,Pet Supplies Plus,UPC_A
|
1302,Pet Supplies Plus,UPC_A
|
||||||
1303,Piazza Italia,EAN_13
|
1303,Piazza Italia,EAN_13
|
||||||
@@ -501,8 +509,8 @@ _id,name,barcodeFormat
|
|||||||
1331,Bizzbee,QR_CODE
|
1331,Bizzbee,QR_CODE
|
||||||
1332,Blue Box,CODE_39
|
1332,Blue Box,CODE_39
|
||||||
1333,Brice,EAN_13
|
1333,Brice,EAN_13
|
||||||
1334,Bricoman,GS1_128
|
1334,Tecnomat,GS1_128
|
||||||
1335,Brico Marché,GS1_128
|
1335,Bricomarché,GS1_128
|
||||||
1336,Camaieu,CODE_128
|
1336,Camaieu,CODE_128
|
||||||
1337,Casino Supermarchés,EAN_13
|
1337,Casino Supermarchés,EAN_13
|
||||||
1338,Castorama,CODE_128
|
1338,Castorama,CODE_128
|
||||||
@@ -680,7 +688,7 @@ _id,name,barcodeFormat
|
|||||||
148f7495-e6f2-40b1-80cd-99b3632cb976,Slam,ITF
|
148f7495-e6f2-40b1-80cd-99b3632cb976,Slam,ITF
|
||||||
149,Höffner,ITF
|
149,Höffner,ITF
|
||||||
1490,Basko,EAN_13
|
1490,Basko,EAN_13
|
||||||
1491,Unes,EAN_13
|
1491,Unes,CODE_128
|
||||||
1492,Grande Cinema 3,EAN_13
|
1492,Grande Cinema 3,EAN_13
|
||||||
1493,Eurobrico,EAN_13
|
1493,Eurobrico,EAN_13
|
||||||
1494,Isola dei Tesori,EAN_13
|
1494,Isola dei Tesori,EAN_13
|
||||||
@@ -966,7 +974,7 @@ _id,name,barcodeFormat
|
|||||||
172,Jost,ITF
|
172,Jost,ITF
|
||||||
1720,Wheelup,CODE_39
|
1720,Wheelup,CODE_39
|
||||||
1721,BIG4,CODE_128
|
1721,BIG4,CODE_128
|
||||||
1722,Besson Chaussures,EAN_13
|
1722,Besson Chaussures,CODE_128
|
||||||
1723,Cactus,EAN_13
|
1723,Cactus,EAN_13
|
||||||
1724,Idea Bellezza,CODE_39
|
1724,Idea Bellezza,CODE_39
|
||||||
1725,Uyum,CODE_128
|
1725,Uyum,CODE_128
|
||||||
@@ -983,7 +991,7 @@ _id,name,barcodeFormat
|
|||||||
1733,Mondial Tissus,EAN_13
|
1733,Mondial Tissus,EAN_13
|
||||||
1734,Furet du nord,EAN_13
|
1734,Furet du nord,EAN_13
|
||||||
1735,Maxxess,EAN_13
|
1735,Maxxess,EAN_13
|
||||||
1736,Devianne,EAN_13
|
1736,Des Marques et Vous,EAN_13
|
||||||
1737,Colruyt,ITF
|
1737,Colruyt,ITF
|
||||||
1738,Paul,EAN_13
|
1738,Paul,EAN_13
|
||||||
1739,JouéClub,EAN_13
|
1739,JouéClub,EAN_13
|
||||||
@@ -1044,7 +1052,7 @@ _id,name,barcodeFormat
|
|||||||
179,Kastner & Öhler,EAN_13
|
179,Kastner & Öhler,EAN_13
|
||||||
1790,MY SIZE,CODE_39
|
1790,MY SIZE,CODE_39
|
||||||
1791,PetO,CODE_128
|
1791,PetO,CODE_128
|
||||||
1792,AVEVE,EAN_13
|
1792,Aveve,EAN_13
|
||||||
1793,BIO-Planet,ITF
|
1793,BIO-Planet,ITF
|
||||||
1794,Brico,EAN_13
|
1794,Brico,EAN_13
|
||||||
1795,Club,CODE_128
|
1795,Club,CODE_128
|
||||||
@@ -1343,9 +1351,10 @@ _id,name,barcodeFormat
|
|||||||
1e43877a-d4f1-4bff-bdb9-cd3346082a46,Scorpion Bay,EAN_13
|
1e43877a-d4f1-4bff-bdb9-cd3346082a46,Scorpion Bay,EAN_13
|
||||||
1e9469a4-8388-4ca9-a463-95ee73a0d953,FAMO,EAN_13
|
1e9469a4-8388-4ca9-a463-95ee73a0d953,FAMO,EAN_13
|
||||||
1e9a127a-0451-4565-9560-eaa097d3808b,Grill'd,CODE_128
|
1e9a127a-0451-4565-9560-eaa097d3808b,Grill'd,CODE_128
|
||||||
1ed46ee6-993a-4053-a016-a0d67e26b91b,Lidl SK,CODE_128
|
1ed46ee6-993a-4053-a016-a0d67e26b91b,Lidl,CODE_128
|
||||||
1f01c3b1-08f7-4365-a0f9-f1c9bcbdf58a,Fresco,CODE_128
|
1f01c3b1-08f7-4365-a0f9-f1c9bcbdf58a,Fresco,CODE_128
|
||||||
1f15d8f3-c35c-46d6-8038-4c9f91a18909,Покров,EAN_8
|
1f15d8f3-c35c-46d6-8038-4c9f91a18909,Покров,EAN_8
|
||||||
|
1f1ec99d-c8c6-42d3-ac6a-b9658a6e0a0d,xBarvy,EAN_13
|
||||||
1f661d7a-d355-4590-8d33-0d61630958cc,NDG,CODE_39
|
1f661d7a-d355-4590-8d33-0d61630958cc,NDG,CODE_39
|
||||||
1f6624c6-5acc-4983-ac17-31b9004232d7,Afvalpas Rijssen-Holten,QR_CODE
|
1f6624c6-5acc-4983-ac17-31b9004232d7,Afvalpas Rijssen-Holten,QR_CODE
|
||||||
1f69337f-7604-4e7a-9031-f0ab182e7cd7,Дешёвая Аптека Вита,CODE_128
|
1f69337f-7604-4e7a-9031-f0ab182e7cd7,Дешёвая Аптека Вита,CODE_128
|
||||||
@@ -1453,7 +1462,7 @@ _id,name,barcodeFormat
|
|||||||
2085,Billa,EAN_13
|
2085,Billa,EAN_13
|
||||||
2086,Billa,EAN_13
|
2086,Billa,EAN_13
|
||||||
2087,BIPA,EAN_13
|
2087,BIPA,EAN_13
|
||||||
2088,Penny,EAN_13
|
2088,PENNY,EAN_13
|
||||||
2089,Penny,EAN_13
|
2089,Penny,EAN_13
|
||||||
209,MCard,CODE_128
|
209,MCard,CODE_128
|
||||||
2090,Shoprite,CODE_128
|
2090,Shoprite,CODE_128
|
||||||
@@ -1492,6 +1501,7 @@ _id,name,barcodeFormat
|
|||||||
2112,Lindex,CODE_128
|
2112,Lindex,CODE_128
|
||||||
2113,Twilfit,CODE_128
|
2113,Twilfit,CODE_128
|
||||||
2114,aClass,CODE_128
|
2114,aClass,CODE_128
|
||||||
|
21143721-38a4-466f-b04d-a3e90cb62bad,L'angolo,CODE_128
|
||||||
2115,Clas Ohlson,CODE_128
|
2115,Clas Ohlson,CODE_128
|
||||||
2116,Agrimarket,CODE_128
|
2116,Agrimarket,CODE_128
|
||||||
2117,Starkki,CODE_128
|
2117,Starkki,CODE_128
|
||||||
@@ -1600,7 +1610,7 @@ _id,name,barcodeFormat
|
|||||||
2201,Avance,CODE_128
|
2201,Avance,CODE_128
|
||||||
2202,berca.be,EAN_13
|
2202,berca.be,EAN_13
|
||||||
2203,Brantano,EAN_13
|
2203,Brantano,EAN_13
|
||||||
2204,Brooklyn,EAN_13
|
2204,Brooklyn nv,EAN_13
|
||||||
2205,CAMELEON,CODE_128
|
2205,CAMELEON,CODE_128
|
||||||
2206,Carmi,CODE_39
|
2206,Carmi,CODE_39
|
||||||
2207,E5 mode,ITF
|
2207,E5 mode,ITF
|
||||||
@@ -1940,7 +1950,7 @@ _id,name,barcodeFormat
|
|||||||
2488,Proximus,CODE_128
|
2488,Proximus,CODE_128
|
||||||
2489,RS Bútor,CODE_128
|
2489,RS Bútor,CODE_128
|
||||||
248957ba-dbad-414e-86e4-009fc4e5beee,Самоцветы плюс,ITF
|
248957ba-dbad-414e-86e4-009fc4e5beee,Самоцветы плюс,ITF
|
||||||
249,Countdown,CODE_128
|
249,Woolworths,CODE_128
|
||||||
2490,SEIBU PRINCE CLUB,CODE_128
|
2490,SEIBU PRINCE CLUB,CODE_128
|
||||||
2491,サミット,EAN_13
|
2491,サミット,EAN_13
|
||||||
2492,The PUB,CODE_128
|
2492,The PUB,CODE_128
|
||||||
@@ -2024,6 +2034,7 @@ _id,name,barcodeFormat
|
|||||||
2557,Artex Fashion,EAN_13
|
2557,Artex Fashion,EAN_13
|
||||||
2558,Askot,CODE_128
|
2558,Askot,CODE_128
|
||||||
2559,BUTIK,EAN_8
|
2559,BUTIK,EAN_8
|
||||||
|
255d84f7-144d-4d63-b6fd-f00a8e94641f,HUK Autowelt,QR_CODE
|
||||||
256,Palmers,EAN_13
|
256,Palmers,EAN_13
|
||||||
2560,Dayli,EAN_13
|
2560,Dayli,EAN_13
|
||||||
2561,De Banier,CODE_128
|
2561,De Banier,CODE_128
|
||||||
@@ -2440,6 +2451,7 @@ _id,name,barcodeFormat
|
|||||||
28a46b11-8c45-4b2a-93dd-b7325a2fe013,Dialogues,CODE_128
|
28a46b11-8c45-4b2a-93dd-b7325a2fe013,Dialogues,CODE_128
|
||||||
28b5866e-f195-4d68-b8a0-02cdb611af4f,Да Здоров! аптека,EAN_13
|
28b5866e-f195-4d68-b8a0-02cdb611af4f,Да Здоров! аптека,EAN_13
|
||||||
28c5ee9a-cf66-4add-b71c-70b66be85570,Agraria,EAN_13
|
28c5ee9a-cf66-4add-b71c-70b66be85570,Agraria,EAN_13
|
||||||
|
28cc5dc7-61b4-4c95-a5a6-e125cc4bce9b,Aventurx,CODE_128
|
||||||
28d93baa-c331-4df8-a85d-65eb86199732,Solar Studio,CODE_128
|
28d93baa-c331-4df8-a85d-65eb86199732,Solar Studio,CODE_128
|
||||||
28fbdd64-8715-4cdc-8c3f-df7259b1ba65,NOHO,EAN_13
|
28fbdd64-8715-4cdc-8c3f-df7259b1ba65,NOHO,EAN_13
|
||||||
29,Heathrow Rewards,CODE_128
|
29,Heathrow Rewards,CODE_128
|
||||||
@@ -2582,6 +2594,7 @@ _id,name,barcodeFormat
|
|||||||
2b1eb78e-9684-4434-ba9b-41f00fc5beab,Sensation Profumerie,EAN_13
|
2b1eb78e-9684-4434-ba9b-41f00fc5beab,Sensation Profumerie,EAN_13
|
||||||
2b29bfc0-26a7-44cb-9d21-2a0bdb467320,Vertex Hotel,ITF
|
2b29bfc0-26a7-44cb-9d21-2a0bdb467320,Vertex Hotel,ITF
|
||||||
2b39b807-6375-404c-bfd7-7f3135654258,Планета Игрушек,EAN_13
|
2b39b807-6375-404c-bfd7-7f3135654258,Планета Игрушек,EAN_13
|
||||||
|
2b6062ec-39b1-4ac4-b6d6-cf19048c9f3f,Coripet,UPC_A
|
||||||
2b6992d5-615a-423a-b196-ab19a418686f,Mimco,CODE_128
|
2b6992d5-615a-423a-b196-ab19a418686f,Mimco,CODE_128
|
||||||
2b7d84ce-c573-44ea-8989-b23a13cf389b,Азбука Красоты,EAN_13
|
2b7d84ce-c573-44ea-8989-b23a13cf389b,Азбука Красоты,EAN_13
|
||||||
2bc9768c-56a2-4d7d-8f1c-0be9f208b71b,Profile,CODE_128
|
2bc9768c-56a2-4d7d-8f1c-0be9f208b71b,Profile,CODE_128
|
||||||
@@ -2852,6 +2865,7 @@ _id,name,barcodeFormat
|
|||||||
3199,Navyboot,EAN_13
|
3199,Navyboot,EAN_13
|
||||||
31d21202-2674-4c42-9a7e-a19b01d32b63,Vegetalis,EAN_13
|
31d21202-2674-4c42-9a7e-a19b01d32b63,Vegetalis,EAN_13
|
||||||
31d3cf0c-7522-4035-9256-7a712cb1a8b3,Канцелярия,EAN_13
|
31d3cf0c-7522-4035-9256-7a712cb1a8b3,Канцелярия,EAN_13
|
||||||
|
31db4e18-fb97-43d2-b026-c41f39d2faba,Bershka,CODE_128
|
||||||
31eccc6d-babd-4fee-9ae8-db9a00fc1c63,Pharmactiv,EAN_13
|
31eccc6d-babd-4fee-9ae8-db9a00fc1c63,Pharmactiv,EAN_13
|
||||||
31f60f6d-633f-42af-b387-e5d0b4e2f45f,SPINNS,EAN_13
|
31f60f6d-633f-42af-b387-e5d0b4e2f45f,SPINNS,EAN_13
|
||||||
32,Bauking,EAN_13
|
32,Bauking,EAN_13
|
||||||
@@ -3092,6 +3106,7 @@ _id,name,barcodeFormat
|
|||||||
3399,Taxi Jetax,CODE_128
|
3399,Taxi Jetax,CODE_128
|
||||||
339bb076-12fd-4e56-899f-3acb79f5da53,Hafenhotel Meereszeiten,CODE_128
|
339bb076-12fd-4e56-899f-3acb79f5da53,Hafenhotel Meereszeiten,CODE_128
|
||||||
33a430e4-35c7-43e7-98e8-5ce5d039ee70,VPZ,CODE_128
|
33a430e4-35c7-43e7-98e8-5ce5d039ee70,VPZ,CODE_128
|
||||||
|
33cb4886-5d06-473a-80b7-980ca2fb27c2,Bouwcenter Nobel,EAN_13
|
||||||
33d16d2d-f51e-44c3-92d8-2c3616af2d0f,Apotheke Peer Farmacia,CODE_128
|
33d16d2d-f51e-44c3-92d8-2c3616af2d0f,Apotheke Peer Farmacia,CODE_128
|
||||||
33dea27e-c7a4-4e40-8621-32da990f7d82,EU COVID-19 Vaccinationsintyg - Andra vaccination Skott,QR_CODE
|
33dea27e-c7a4-4e40-8621-32da990f7d82,EU COVID-19 Vaccinationsintyg - Andra vaccination Skott,QR_CODE
|
||||||
33e82e4f-5541-4be1-aa4c-0f2987cfd78f,Данди,EAN_13
|
33e82e4f-5541-4be1-aa4c-0f2987cfd78f,Данди,EAN_13
|
||||||
@@ -3452,6 +3467,7 @@ _id,name,barcodeFormat
|
|||||||
37,Bessmann,ITF
|
37,Bessmann,ITF
|
||||||
370,Virgin Atlantic,CODE_128
|
370,Virgin Atlantic,CODE_128
|
||||||
3700,Go Auto,CODE_128
|
3700,Go Auto,CODE_128
|
||||||
|
37003c25-7bc7-4dd9-8a3a-8406005d0dcf,Scouts en Gidsen Vlaanderen,CODE_128
|
||||||
3701,Good Earth,CODE_128
|
3701,Good Earth,CODE_128
|
||||||
3702,Hachem,CODE_128
|
3702,Hachem,CODE_128
|
||||||
3703,Le Magasin,CODE_128
|
3703,Le Magasin,CODE_128
|
||||||
@@ -3949,6 +3965,7 @@ _id,name,barcodeFormat
|
|||||||
4083,Каляев,EAN_13
|
4083,Каляев,EAN_13
|
||||||
4084,Shingle Inn,CODE_128
|
4084,Shingle Inn,CODE_128
|
||||||
4085,Golden Casket,CODE_128
|
4085,Golden Casket,CODE_128
|
||||||
|
40853977-7fdb-4815-a64e-85d2c70df347,OROCAJA,CODE_39
|
||||||
4086,Pet City,CODE_128
|
4086,Pet City,CODE_128
|
||||||
4087,chempro,EAN_13
|
4087,chempro,EAN_13
|
||||||
4088,merlo,CODE_39
|
4088,merlo,CODE_39
|
||||||
@@ -4313,7 +4330,7 @@ _id,name,barcodeFormat
|
|||||||
4387,Kremer,EAN_13
|
4387,Kremer,EAN_13
|
||||||
4388,Gartencenter Nickl,EAN_13
|
4388,Gartencenter Nickl,EAN_13
|
||||||
4389,Panarottis,QR_CODE
|
4389,Panarottis,QR_CODE
|
||||||
439,Alitalia,CODE_128
|
439,Volare ITA airways.,CODE_128
|
||||||
4390,Simply Asia,CODE_128
|
4390,Simply Asia,CODE_128
|
||||||
4391,Ultraliquors,CODE_128
|
4391,Ultraliquors,CODE_128
|
||||||
4392,Cum Books,CODE_128
|
4392,Cum Books,CODE_128
|
||||||
@@ -4565,6 +4582,7 @@ _id,name,barcodeFormat
|
|||||||
4599,Мокрый Нос,EAN_13
|
4599,Мокрый Нос,EAN_13
|
||||||
45b55fa2-835b-4ae5-a318-16a66b4ec85b,Євро Мікс,EAN_8
|
45b55fa2-835b-4ae5-a318-16a66b4ec85b,Євро Мікс,EAN_8
|
||||||
45cbba3f-f0d2-4837-8189-16b0ff2707f5,Барс,CODE_128
|
45cbba3f-f0d2-4837-8189-16b0ff2707f5,Барс,CODE_128
|
||||||
|
45e6b637-a991-45ce-b72d-8f4df03d9f6b,Tradition,CODE_128
|
||||||
45e6f6d3-e688-40f7-86e2-73e3803c86bd,KüstenCard mini/maxi,CODE_128
|
45e6f6d3-e688-40f7-86e2-73e3803c86bd,KüstenCard mini/maxi,CODE_128
|
||||||
45fa81a4-657e-414c-89ed-ebf1c49c0926,G'DAY REWARDS,CODE_128
|
45fa81a4-657e-414c-89ed-ebf1c49c0926,G'DAY REWARDS,CODE_128
|
||||||
45faf9e5-321c-44a7-b641-7acee8126349,EU COVID-19 Vaccinatiebewijs - Eerste vaccinatieschot,QR_CODE
|
45faf9e5-321c-44a7-b641-7acee8126349,EU COVID-19 Vaccinatiebewijs - Eerste vaccinatieschot,QR_CODE
|
||||||
@@ -4772,6 +4790,7 @@ _id,name,barcodeFormat
|
|||||||
4773,Maximiles,CODE_128
|
4773,Maximiles,CODE_128
|
||||||
4774,La Compagnie des Petits,CODE_128
|
4774,La Compagnie des Petits,CODE_128
|
||||||
4775,Totem Family,CODE_128
|
4775,Totem Family,CODE_128
|
||||||
|
477515a9-2257-4d19-af18-3dbcfeb4acd9,Omni,CODE_128
|
||||||
4776,La Jardinerie,CODE_128
|
4776,La Jardinerie,CODE_128
|
||||||
4777,La Plateforme du Bâtiment,EAN_13
|
4777,La Plateforme du Bâtiment,EAN_13
|
||||||
4778,Animal & Co,EAN_13
|
4778,Animal & Co,EAN_13
|
||||||
@@ -5049,6 +5068,7 @@ _id,name,barcodeFormat
|
|||||||
4adaa99b-282d-4abe-87c8-b16d3958f4c2,Тюменский ЦУМ,CODE_39
|
4adaa99b-282d-4abe-87c8-b16d3958f4c2,Тюменский ЦУМ,CODE_39
|
||||||
4ae5d40d-45ea-4188-bce8-eb3337733466,Garden Floridea,CODE_128
|
4ae5d40d-45ea-4188-bce8-eb3337733466,Garden Floridea,CODE_128
|
||||||
4b197111-0d79-4ac5-aecd-5dca6643e390,Евродом,EAN_13
|
4b197111-0d79-4ac5-aecd-5dca6643e390,Евродом,EAN_13
|
||||||
|
4b50787c-052c-48e9-8bae-b01373cef1b8,Fbo Clothing,CODE_128
|
||||||
4b511f9a-5c9c-4b9f-8c71-1631cb78456a,Семейная Аптека,EAN_13
|
4b511f9a-5c9c-4b9f-8c71-1631cb78456a,Семейная Аптека,EAN_13
|
||||||
4b8e7174-b85b-4b82-99ab-b1faee2dfb8f,Diper,EAN_13
|
4b8e7174-b85b-4b82-99ab-b1faee2dfb8f,Diper,EAN_13
|
||||||
4ba9de66-0015-49e1-a0d1-d24c2328eaa5,Witchery,EAN_13
|
4ba9de66-0015-49e1-a0d1-d24c2328eaa5,Witchery,EAN_13
|
||||||
@@ -5063,14 +5083,18 @@ _id,name,barcodeFormat
|
|||||||
4ccb26a9-3a58-487f-9bdf-5cc4b042c0b3,UNCS,CODE_128
|
4ccb26a9-3a58-487f-9bdf-5cc4b042c0b3,UNCS,CODE_128
|
||||||
4cd0da27-9a71-4eb0-88f4-23919b598828,Pins,CODE_128
|
4cd0da27-9a71-4eb0-88f4-23919b598828,Pins,CODE_128
|
||||||
4d28254f-9ec6-4262-aa28-ee0bd7620b00,Леонардо,EAN_13
|
4d28254f-9ec6-4262-aa28-ee0bd7620b00,Леонардо,EAN_13
|
||||||
|
4d4102e9-115a-4695-b764-c5534e1749a8,twd,EAN_13
|
||||||
4d7b0d6e-2680-4c6b-bdac-8985df7aa8a3,大昌,EAN_13
|
4d7b0d6e-2680-4c6b-bdac-8985df7aa8a3,大昌,EAN_13
|
||||||
|
4d8c62b4-b4c5-40b0-9117-6e5022cf7950,MilleMiglia,CODE_128
|
||||||
4dab7847-f728-4c34-80ea-a464238a3756,Волна,EAN_13
|
4dab7847-f728-4c34-80ea-a464238a3756,Волна,EAN_13
|
||||||
4db2f926-b58d-4821-8f85-b02d3e32fbcb,Дом посуды,EAN_13
|
4db2f926-b58d-4821-8f85-b02d3e32fbcb,Дом посуды,EAN_13
|
||||||
4dd50f0e-05a1-4a32-97c2-1e5b570d0d9b,MIA,EAN_13
|
4dd50f0e-05a1-4a32-97c2-1e5b570d0d9b,MIA,EAN_13
|
||||||
4dd586bf-d2ed-4357-898c-11b648bcb796,Детский парк,EAN_13
|
4dd586bf-d2ed-4357-898c-11b648bcb796,Детский парк,EAN_13
|
||||||
4dd5aa56-2f5c-4bb5-a281-211bb4e5463e,Joylab,CODE_128
|
4dd5aa56-2f5c-4bb5-a281-211bb4e5463e,Joylab,CODE_128
|
||||||
4e090085-f5bc-4f29-abcf-bb249dd3429d, SSENSE,CODE_128
|
4e090085-f5bc-4f29-abcf-bb249dd3429d, SSENSE,CODE_128
|
||||||
|
4e1001a2-a664-4d37-8b85-a71b02f9f6dc,xFarby,EAN_13
|
||||||
4e24761b-17a7-4b7d-b04a-16f54076d03b,Forum+,EAN_13
|
4e24761b-17a7-4b7d-b04a-16f54076d03b,Forum+,EAN_13
|
||||||
|
4e6622db-6fd3-405e-a60e-7157984da5ba,KiemKracht VZW,CODE_128
|
||||||
4e95cfa4-3011-41c2-ad87-0c560cbd218c,Lincolnshire Co-operative,DATA_MATRIX
|
4e95cfa4-3011-41c2-ad87-0c560cbd218c,Lincolnshire Co-operative,DATA_MATRIX
|
||||||
4eb5bcd8-9467-44ce-b54c-fc69521431be,Мир Обоев,CODE_128
|
4eb5bcd8-9467-44ce-b54c-fc69521431be,Мир Обоев,CODE_128
|
||||||
4ed66bc0-04ee-458b-aac7-6bb7bdd35e5c,Пивотека,CODE_39
|
4ed66bc0-04ee-458b-aac7-6bb7bdd35e5c,Пивотека,CODE_39
|
||||||
@@ -5300,6 +5324,7 @@ _id,name,barcodeFormat
|
|||||||
519,Alimerka,CODE_128
|
519,Alimerka,CODE_128
|
||||||
5190,Souris Mini,CODE_128
|
5190,Souris Mini,CODE_128
|
||||||
5191,Лакомка,EAN_13
|
5191,Лакомка,EAN_13
|
||||||
|
51917108-3469-4067-b1da-8697d60fcfa6,Kingston Frontenac Public Library,CODE_128
|
||||||
5192,AlphaZoo,CODE_128
|
5192,AlphaZoo,CODE_128
|
||||||
5193,БИГАМ,EAN_8
|
5193,БИГАМ,EAN_8
|
||||||
5194,Sebastiano,EAN_13
|
5194,Sebastiano,EAN_13
|
||||||
@@ -5406,6 +5431,7 @@ _id,name,barcodeFormat
|
|||||||
55cfc40e-469f-485f-ab26-823014fd8401,Seebauer,EAN_13
|
55cfc40e-469f-485f-ab26-823014fd8401,Seebauer,EAN_13
|
||||||
55db252f-70a8-4da7-b0c2-484c8445e750,Kreativmarkt Hamburg,EAN_13
|
55db252f-70a8-4da7-b0c2-484c8445e750,Kreativmarkt Hamburg,EAN_13
|
||||||
55e96a49-7157-43cc-aaa7-9867d37cb05f,Народная линия,EAN_13
|
55e96a49-7157-43cc-aaa7-9867d37cb05f,Народная линия,EAN_13
|
||||||
|
55eb9a72-cd1d-49f7-aec1-1f44f6207983,Lina Giorgi snc,CODE_39
|
||||||
55f414b7-b1a8-46f6-97ad-7f4f0867d8a9,EU COVID-19 Rokotustodistus - Toinen rokotus laukaus,QR_CODE
|
55f414b7-b1a8-46f6-97ad-7f4f0867d8a9,EU COVID-19 Rokotustodistus - Toinen rokotus laukaus,QR_CODE
|
||||||
56,Brax,CODE_128
|
56,Brax,CODE_128
|
||||||
560,Punt Roma,CODE_128
|
560,Punt Roma,CODE_128
|
||||||
@@ -5492,6 +5518,7 @@ _id,name,barcodeFormat
|
|||||||
5afc2de6-6129-43f5-9caf-be3572d65a90,Sisal,CODE_128
|
5afc2de6-6129-43f5-9caf-be3572d65a90,Sisal,CODE_128
|
||||||
5b01f59e-97db-4105-9aab-94f56099fc49,real,GS1_128
|
5b01f59e-97db-4105-9aab-94f56099fc49,real,GS1_128
|
||||||
5b1da0f0-143e-492d-83a9-ad22957a54c6,Metro Lifestyle,CODE_39
|
5b1da0f0-143e-492d-83a9-ad22957a54c6,Metro Lifestyle,CODE_39
|
||||||
|
5b502f6e-7c38-4708-ae56-04f97638692a,Баня Стил,CODE_128
|
||||||
5bb5ea85-8952-474e-be53-c5ac11f7428f,Farmec,EAN_13
|
5bb5ea85-8952-474e-be53-c5ac11f7428f,Farmec,EAN_13
|
||||||
5bb6dc04-3000-475f-a5d4-ba9427989809,Bimbostore Toys Center,EAN_13
|
5bb6dc04-3000-475f-a5d4-ba9427989809,Bimbostore Toys Center,EAN_13
|
||||||
5bf3f149-2217-45aa-b61b-eec9aeedf5d2,Werdich,CODE_39
|
5bf3f149-2217-45aa-b61b-eec9aeedf5d2,Werdich,CODE_39
|
||||||
@@ -5509,6 +5536,7 @@ _id,name,barcodeFormat
|
|||||||
5d3de23f-b72e-4920-9e3b-1a413979a779,CityCard,CODE_128
|
5d3de23f-b72e-4920-9e3b-1a413979a779,CityCard,CODE_128
|
||||||
5d426084-854e-493e-a10d-7ce5d34d31fe,Farmacie Comunali Firenze,CODE_128
|
5d426084-854e-493e-a10d-7ce5d34d31fe,Farmacie Comunali Firenze,CODE_128
|
||||||
5d51a06c-3af4-4400-9776-e3458190be87,Parisnail,EAN_13
|
5d51a06c-3af4-4400-9776-e3458190be87,Parisnail,EAN_13
|
||||||
|
5d5d4520-ee6c-45ea-b5f1-11282a0673f4,Arriva,CODE_128
|
||||||
5d695da3-f47b-4da8-b5ff-ea9d0fd9486b,Belaton,CODE_128
|
5d695da3-f47b-4da8-b5ff-ea9d0fd9486b,Belaton,CODE_128
|
||||||
5d866631-9858-4393-a5cf-eba96ca066cc,Kiwisun,CODE_128
|
5d866631-9858-4393-a5cf-eba96ca066cc,Kiwisun,CODE_128
|
||||||
5db03921-3703-40d3-ba27-f7d3ff5a40ba,Prodor Supermarché et Boucherie,EAN_13
|
5db03921-3703-40d3-ba27-f7d3ff5a40ba,Prodor Supermarché et Boucherie,EAN_13
|
||||||
@@ -5518,6 +5546,7 @@ _id,name,barcodeFormat
|
|||||||
5e18e98b-ad75-426a-a4ac-a80496906906,Beauty X,EAN_13
|
5e18e98b-ad75-426a-a4ac-a80496906906,Beauty X,EAN_13
|
||||||
5e27a7ae-ad95-4cce-b383-85a4eb822eaa,Supra Baby,EAN_13
|
5e27a7ae-ad95-4cce-b383-85a4eb822eaa,Supra Baby,EAN_13
|
||||||
5e402125-50f9-4de9-8769-ce4e0dc1d1a1,Romaest,CODE_128
|
5e402125-50f9-4de9-8769-ce4e0dc1d1a1,Romaest,CODE_128
|
||||||
|
5e46de16-6ebf-4d17-933f-2f782df8b3fb,Prima Company,CODE_128
|
||||||
5e6edac6-a458-4488-861c-f8f403f4b1e1,MABÙ,QR_CODE
|
5e6edac6-a458-4488-861c-f8f403f4b1e1,MABÙ,QR_CODE
|
||||||
5ee2ee34-5027-4535-a55f-657c1a092d5d,Lady Sharm,CODE_128
|
5ee2ee34-5027-4535-a55f-657c1a092d5d,Lady Sharm,CODE_128
|
||||||
5f01e866-3ef8-46e4-a40a-555594849eb7,ЦУМ,CODE_128
|
5f01e866-3ef8-46e4-a40a-555594849eb7,ЦУМ,CODE_128
|
||||||
@@ -5532,6 +5561,7 @@ _id,name,barcodeFormat
|
|||||||
6,ACS,CODE_128
|
6,ACS,CODE_128
|
||||||
60,Transgourmet,EAN_13
|
60,Transgourmet,EAN_13
|
||||||
600,Humanic,ITF
|
600,Humanic,ITF
|
||||||
|
60046ae3-b41c-4a08-a012-d8e921e8aab0,Multaparts,CODE_128
|
||||||
600bf563-b7b2-488a-9e21-0ccc63a67b1d,LAUF!,EAN_13
|
600bf563-b7b2-488a-9e21-0ccc63a67b1d,LAUF!,EAN_13
|
||||||
601,Beauty Alliance,CODE_128
|
601,Beauty Alliance,CODE_128
|
||||||
6014a435-c656-4bf7-bcd6-fa46ed28bac0,Окраина,EAN_13
|
6014a435-c656-4bf7-bcd6-fa46ed28bac0,Окраина,EAN_13
|
||||||
@@ -5554,10 +5584,12 @@ _id,name,barcodeFormat
|
|||||||
61,Centro,EAN_13
|
61,Centro,EAN_13
|
||||||
610,CAA,CODE_128
|
610,CAA,CODE_128
|
||||||
611,Calgary Co-op,EAN_13
|
611,Calgary Co-op,EAN_13
|
||||||
|
6110d522-b979-46ca-a313-ded4eac7db71,Telecomshop Twente,CODE_128
|
||||||
612,Canada Post,CODE_128
|
612,Canada Post,CODE_128
|
||||||
613,Canadian Tire,CODE_128
|
613,Canadian Tire,CODE_128
|
||||||
614,Change Lingerie,CODE_39
|
614,Change Lingerie,CODE_39
|
||||||
615,SCENE,CODE_128
|
615,SCENE,CODE_128
|
||||||
|
615a7629-0f60-4613-b41a-e1f571f5c20a,Goelia,CODE_128
|
||||||
615ddf35-4934-4442-b4df-54b065184476,Сигма,EAN_13
|
615ddf35-4934-4442-b4df-54b065184476,Сигма,EAN_13
|
||||||
616,Denny's,CODE_128
|
616,Denny's,CODE_128
|
||||||
617,DeSerres,CODE_128
|
617,DeSerres,CODE_128
|
||||||
@@ -5609,6 +5641,7 @@ _id,name,barcodeFormat
|
|||||||
639,National Car Rental,CODE_39
|
639,National Car Rental,CODE_39
|
||||||
63ace5b1-39bb-4486-87a8-692caab2c76b,куулклевер,QR_CODE
|
63ace5b1-39bb-4486-87a8-692caab2c76b,куулклевер,QR_CODE
|
||||||
63ad5b7e-ab54-45f2-9224-2da0122a21eb,Forum TC,EAN_13
|
63ad5b7e-ab54-45f2-9224-2da0122a21eb,Forum TC,EAN_13
|
||||||
|
63b32bf3-2e99-4487-bc45-7b70132fe53c,Checkers,CODE_128
|
||||||
63bcf094-bbc1-4caa-adfb-b6e015295f43,Парфюм Лидер,EAN_13
|
63bcf094-bbc1-4caa-adfb-b6e015295f43,Парфюм Лидер,EAN_13
|
||||||
63bee835-2e9d-4656-b7b6-4b9e9a024470,Арт-Квартал,EAN_13
|
63bee835-2e9d-4656-b7b6-4b9e9a024470,Арт-Квартал,EAN_13
|
||||||
63c87418-cb15-4294-a872-035a03da3a62,Belleplant,EAN_13
|
63c87418-cb15-4294-a872-035a03da3a62,Belleplant,EAN_13
|
||||||
@@ -5644,7 +5677,7 @@ _id,name,barcodeFormat
|
|||||||
657d61fe-7714-4aed-a3d5-6c718c6e9c2a,EU COVID-19 Vaccinationsattest - Første vaccinationsskud,QR_CODE
|
657d61fe-7714-4aed-a3d5-6c718c6e9c2a,EU COVID-19 Vaccinationsattest - Første vaccinationsskud,QR_CODE
|
||||||
658,Thrifty Foods,CODE_128
|
658,Thrifty Foods,CODE_128
|
||||||
659,Trade Secret,UPC_A
|
659,Trade Secret,UPC_A
|
||||||
659c40c9-f997-44a8-b6a8-a29df616c4b2, Alfa-Tec,EAN_13
|
659c40c9-f997-44a8-b6a8-a29df616c4b2,Alfa-Tec,EAN_13
|
||||||
65e6e477-57a3-41c1-88b2-330a6d0cf8bd,Nobis,PDF_417
|
65e6e477-57a3-41c1-88b2-330a6d0cf8bd,Nobis,PDF_417
|
||||||
65e848d6-edd5-401e-9b12-952a5c6fdf47,Джерела Здоров'я,CODE_39
|
65e848d6-edd5-401e-9b12-952a5c6fdf47,Джерела Здоров'я,CODE_39
|
||||||
66,BCF,CODE_128
|
66,BCF,CODE_128
|
||||||
@@ -5652,6 +5685,7 @@ _id,name,barcodeFormat
|
|||||||
661,WestJet Rewards,CODE_128
|
661,WestJet Rewards,CODE_128
|
||||||
66104d31-9ae9-440d-b316-0d07a4319af3,Farma Fedeltà,CODE_128
|
66104d31-9ae9-440d-b316-0d07a4319af3,Farma Fedeltà,CODE_128
|
||||||
662,Würzenbach Drogerie,EAN_13
|
662,Würzenbach Drogerie,EAN_13
|
||||||
|
662e6cc0-3ebe-47db-badf-b31b626ea70c,The Papanui Club,QR_CODE
|
||||||
66335d92-4622-4334-8384-4a6d5f61f239,Zinger,EAN_13
|
66335d92-4622-4334-8384-4a6d5f61f239,Zinger,EAN_13
|
||||||
664,American Eagle,ITF
|
664,American Eagle,ITF
|
||||||
665,TJX Style+,CODE_128
|
665,TJX Style+,CODE_128
|
||||||
@@ -5696,6 +5730,7 @@ _id,name,barcodeFormat
|
|||||||
687,Thai - Royal Orchid Plus,PDF_417
|
687,Thai - Royal Orchid Plus,PDF_417
|
||||||
688,SportIT,EAN_13
|
688,SportIT,EAN_13
|
||||||
689,Foster Calzature,EAN_13
|
689,Foster Calzature,EAN_13
|
||||||
|
68ac6315-08c6-471d-b2e0-ad42d1a091c8,100 Vetrine,UPC_A
|
||||||
68c2495e-937d-4e71-a4ad-85f066df0339,Jardival,EAN_13
|
68c2495e-937d-4e71-a4ad-85f066df0339,Jardival,EAN_13
|
||||||
68c69327-cce9-4de8-a062-b895c062ee60,Iden,EAN_13
|
68c69327-cce9-4de8-a062-b895c062ee60,Iden,EAN_13
|
||||||
68d4b527-e419-4346-8078-a4ef07a04f00,Lehner Versand,CODE_128
|
68d4b527-e419-4346-8078-a4ef07a04f00,Lehner Versand,CODE_128
|
||||||
@@ -5729,6 +5764,7 @@ _id,name,barcodeFormat
|
|||||||
6a5ac3f8-04cb-4d14-884f-1231b72228e8,Топаз,EAN_13
|
6a5ac3f8-04cb-4d14-884f-1231b72228e8,Топаз,EAN_13
|
||||||
6a7b1bc8-eca7-4323-9080-68af9414254f,CastoPro,CODE_128
|
6a7b1bc8-eca7-4323-9080-68af9414254f,CastoPro,CODE_128
|
||||||
6a85186a-bfd9-4078-a5da-db1b4e1fb526,Molders,CODE_128
|
6a85186a-bfd9-4078-a5da-db1b4e1fb526,Molders,CODE_128
|
||||||
|
6a8a8971-821c-46ce-a638-1a8585c9dedd,Booking.com,CODE_128
|
||||||
6aa89061-d0b5-46a2-9019-b1cb7146e485,Just Plastics,CODE_128
|
6aa89061-d0b5-46a2-9019-b1cb7146e485,Just Plastics,CODE_128
|
||||||
6aa9bd9a-b099-4997-9fa1-b0a7525c6ec7,AZ Casa,EAN_13
|
6aa9bd9a-b099-4997-9fa1-b0a7525c6ec7,AZ Casa,EAN_13
|
||||||
6ab113ff-77e9-4029-9b23-e420eda105e3,Ehrmann,CODE_39
|
6ab113ff-77e9-4029-9b23-e420eda105e3,Ehrmann,CODE_39
|
||||||
@@ -5767,6 +5803,7 @@ _id,name,barcodeFormat
|
|||||||
6faff0bd-9236-41f8-9c67-7b546c68085a,BVS,EAN_13
|
6faff0bd-9236-41f8-9c67-7b546c68085a,BVS,EAN_13
|
||||||
6fb31971-1cf0-468e-9f85-ebf6133ad3aa,у Палыча,CODE_128
|
6fb31971-1cf0-468e-9f85-ebf6133ad3aa,у Палыча,CODE_128
|
||||||
6fb45bab-d4be-49fd-8b58-d841110eb0cb,AL 48,EAN_13
|
6fb45bab-d4be-49fd-8b58-d841110eb0cb,AL 48,EAN_13
|
||||||
|
6fb4ec1e-c6b7-4597-82a3-5c8d4d69ad4f,Rachelle Béry,CODE_128
|
||||||
6fe38419-76d2-4b5c-983e-6dbed7822d62,GiorgioMare,CODE_128
|
6fe38419-76d2-4b5c-983e-6dbed7822d62,GiorgioMare,CODE_128
|
||||||
6fea059e-d9ec-4063-8ea4-cba5ac035942,L'arca di Noè,EAN_8
|
6fea059e-d9ec-4063-8ea4-cba5ac035942,L'arca di Noè,EAN_8
|
||||||
6ff46a57-e3c9-457e-bfb4-aa922c4c41b4,BENZ,CODE_128
|
6ff46a57-e3c9-457e-bfb4-aa922c4c41b4,BENZ,CODE_128
|
||||||
@@ -5855,6 +5892,7 @@ _id,name,barcodeFormat
|
|||||||
740308f3-fda8-4b83-9d86-d13592ef30ab,Dress Code,EAN_13
|
740308f3-fda8-4b83-9d86-d13592ef30ab,Dress Code,EAN_13
|
||||||
741,O'STIN,EAN_13
|
741,O'STIN,EAN_13
|
||||||
74135c63-c1ab-47b8-8d99-4d9dcf602eda,VOIX INTERIORS,CODE_128
|
74135c63-c1ab-47b8-8d99-4d9dcf602eda,VOIX INTERIORS,CODE_128
|
||||||
|
7415ddc5-3d77-410c-a6f8-ab399518a82c,Tradition,CODE_128
|
||||||
742,Reebok,CODE_128
|
742,Reebok,CODE_128
|
||||||
742069df-a468-45d5-8cf6-cc152b4aefaf,Bacher Garten-Center,EAN_13
|
742069df-a468-45d5-8cf6-cc152b4aefaf,Bacher Garten-Center,EAN_13
|
||||||
743,Savage,CODE_128
|
743,Savage,CODE_128
|
||||||
@@ -5902,6 +5940,7 @@ _id,name,barcodeFormat
|
|||||||
764,Васаби,CODE_128
|
764,Васаби,CODE_128
|
||||||
7648aaa6-671e-4396-9e4e-759aa66c9f4f,Bouwcenter,EAN_13
|
7648aaa6-671e-4396-9e4e-759aa66c9f4f,Bouwcenter,EAN_13
|
||||||
7649e44e-66e4-4af1-a913-87a40c8ae739,Office Centre,CODE_128
|
7649e44e-66e4-4af1-a913-87a40c8ae739,Office Centre,CODE_128
|
||||||
|
764a67a4-8087-41d1-b53a-d73b8380d5cf,Handy Home,CODE_128
|
||||||
765,Вестер,CODE_128
|
765,Вестер,CODE_128
|
||||||
766,Виктория,EAN_13
|
766,Виктория,EAN_13
|
||||||
767,Газпром АЗС,EAN_13
|
767,Газпром АЗС,EAN_13
|
||||||
@@ -5934,6 +5973,7 @@ _id,name,barcodeFormat
|
|||||||
780bd58f-acbb-493c-869d-63f7a93292f3,Schnitz,CODE_128
|
780bd58f-acbb-493c-869d-63f7a93292f3,Schnitz,CODE_128
|
||||||
781,Кофе Хауз,CODE_128
|
781,Кофе Хауз,CODE_128
|
||||||
782,Красный Куб,CODE_128
|
782,Красный Куб,CODE_128
|
||||||
|
78242148-6c07-4698-9ec1-56017dc687b6,Ideacasa Mercatone,EAN_13
|
||||||
782b0597-f7e4-4509-ba4b-a9fc35d72b4d,Рада,EAN_13
|
782b0597-f7e4-4509-ba4b-a9fc35d72b4d,Рада,EAN_13
|
||||||
782f7353-ec4c-49a8-9aac-1f7d28f4cab2,Remix Moda,EAN_13
|
782f7353-ec4c-49a8-9aac-1f7d28f4cab2,Remix Moda,EAN_13
|
||||||
783,Лукойл / Ликард,CODE_128
|
783,Лукойл / Ликард,CODE_128
|
||||||
@@ -5988,7 +6028,7 @@ _id,name,barcodeFormat
|
|||||||
7bd30784-434b-4d73-8dc1-5b5516723eda,Pascal Coste,EAN_13
|
7bd30784-434b-4d73-8dc1-5b5516723eda,Pascal Coste,EAN_13
|
||||||
7bd61c87-b62d-439a-92e9-cc435345cb53,Infinity Fashion,CODE_39
|
7bd61c87-b62d-439a-92e9-cc435345cb53,Infinity Fashion,CODE_39
|
||||||
7c138f2e-37f9-46d4-ac65-2b20ff90a629,Nai Harn Gym,CODE_39
|
7c138f2e-37f9-46d4-ac65-2b20ff90a629,Nai Harn Gym,CODE_39
|
||||||
7c1b39b5-b938-432e-b0be-3c196320bd37,Checkers,QR_CODE
|
7c1b39b5-b938-432e-b0be-3c196320bd37,Checkers,CODE_128
|
||||||
7c5a9dd0-28b0-4be1-b53f-cac4246990b4,Марафон Обувь,CODE_128
|
7c5a9dd0-28b0-4be1-b53f-cac4246990b4,Марафон Обувь,CODE_128
|
||||||
7c60823a-e9fc-447f-811d-589bf1f95342,Пчёлка маркет,UPC_A
|
7c60823a-e9fc-447f-811d-589bf1f95342,Пчёлка маркет,UPC_A
|
||||||
7c77ce3b-02ad-436b-a4aa-62a6d5d583e3,Plainview-Old Bethpage Public Library,CODABAR
|
7c77ce3b-02ad-436b-a4aa-62a6d5d583e3,Plainview-Old Bethpage Public Library,CODABAR
|
||||||
@@ -6004,6 +6044,7 @@ _id,name,barcodeFormat
|
|||||||
7ce87cdb-4c6b-437f-a693-dca518f7436a,Yo-get-it,CODE_39
|
7ce87cdb-4c6b-437f-a693-dca518f7436a,Yo-get-it,CODE_39
|
||||||
7d02542c-fac0-45b5-bc90-d74240715c56,Travis Perkins,CODE_128
|
7d02542c-fac0-45b5-bc90-d74240715c56,Travis Perkins,CODE_128
|
||||||
7d11f040-b0a2-4109-bdf1-25711d48d451,Consorzio Infarmacia,EAN_13
|
7d11f040-b0a2-4109-bdf1-25711d48d451,Consorzio Infarmacia,EAN_13
|
||||||
|
7d168ca5-9370-47bd-ac3e-bf1e1e26f1ec,RISPAWORLD,CODE_128
|
||||||
7d41888d-cd7d-42ef-bf93-9aeda5ae13f6,Kepro,EAN_13
|
7d41888d-cd7d-42ef-bf93-9aeda5ae13f6,Kepro,EAN_13
|
||||||
7d4345b8-448b-4e12-a1c5-c6e031de2352,Nove25,CODE_128
|
7d4345b8-448b-4e12-a1c5-c6e031de2352,Nove25,CODE_128
|
||||||
7d520d1c-611e-4e81-9937-41a9828e6b08,EU COVID-19 Vaccinatiebewijs,QR_CODE
|
7d520d1c-611e-4e81-9937-41a9828e6b08,EU COVID-19 Vaccinatiebewijs,QR_CODE
|
||||||
@@ -6014,6 +6055,7 @@ _id,name,barcodeFormat
|
|||||||
7da65ee3-d140-469c-b3ee-217272ac98d4,Kippie,QR_CODE
|
7da65ee3-d140-469c-b3ee-217272ac98d4,Kippie,QR_CODE
|
||||||
7db0f727-13b4-48c1-8618-550155a878a2,Imperial Games,CODE_128
|
7db0f727-13b4-48c1-8618-550155a878a2,Imperial Games,CODE_128
|
||||||
7db8a067-1c33-4cd9-9706-31a2592f719a,милый дом,GS1_128
|
7db8a067-1c33-4cd9-9706-31a2592f719a,милый дом,GS1_128
|
||||||
|
7dd14421-2fe6-494f-889b-dd8920f61091,Mastro Tortello,QR_CODE
|
||||||
7dd1b9ca-2a5b-4f3c-8c10-8bc216ff5d2f,Sokolov Jewelry,CODE_128
|
7dd1b9ca-2a5b-4f3c-8c10-8bc216ff5d2f,Sokolov Jewelry,CODE_128
|
||||||
7df2728d-3dc9-4724-8756-965e937674e2,Marriott Bonvoy,QR_CODE
|
7df2728d-3dc9-4724-8756-965e937674e2,Marriott Bonvoy,QR_CODE
|
||||||
7e3da299-047b-4981-8ff3-e5355c7289b2,GIROPHARM ,EAN_13
|
7e3da299-047b-4981-8ff3-e5355c7289b2,GIROPHARM ,EAN_13
|
||||||
@@ -6040,6 +6082,7 @@ _id,name,barcodeFormat
|
|||||||
8045996b-082d-4333-b631-54dc992ebef0,Coop,EAN_13
|
8045996b-082d-4333-b631-54dc992ebef0,Coop,EAN_13
|
||||||
805,Старик Хоттабыч,CODE_128
|
805,Старик Хоттабыч,CODE_128
|
||||||
806,Stockmann,CODE_128
|
806,Stockmann,CODE_128
|
||||||
|
8069f84c-3b04-4b0a-87fd-d89230547e8b,Happy Pets,QR_CODE
|
||||||
807,Сток-центр,EAN_13
|
807,Сток-центр,EAN_13
|
||||||
8070cf0a-9721-4fe7-b010-6fdca61349fc,Epping Plaza Hotel,CODE_128
|
8070cf0a-9721-4fe7-b010-6fdca61349fc,Epping Plaza Hotel,CODE_128
|
||||||
8077e001-6db6-4796-bd82-6716ea5e116e,Palace Cinemas,CODE_39
|
8077e001-6db6-4796-bd82-6716ea5e116e,Palace Cinemas,CODE_39
|
||||||
@@ -6061,6 +6104,7 @@ _id,name,barcodeFormat
|
|||||||
813f818a-e99d-49f2-af6e-653a9bcaab09,Bazar Avenue,EAN_13
|
813f818a-e99d-49f2-af6e-653a9bcaab09,Bazar Avenue,EAN_13
|
||||||
814,ФотоПлюс,CODE_128
|
814,ФотоПлюс,CODE_128
|
||||||
815,ЦентрОбувь,EAN_13
|
815,ЦентрОбувь,EAN_13
|
||||||
|
8153abb1-248f-4af9-a7f8-dd83cdacdc7f,TEKBIR MARKET,CODE_128
|
||||||
816,ЭКОНИКА,EAN_13
|
816,ЭКОНИКА,EAN_13
|
||||||
8166ded7-42b6-47b8-a5dc-032954e82db7,bugatti,EAN_13
|
8166ded7-42b6-47b8-a5dc-032954e82db7,bugatti,EAN_13
|
||||||
817,Эстель Адони,EAN_13
|
817,Эстель Адони,EAN_13
|
||||||
@@ -6071,7 +6115,7 @@ _id,name,barcodeFormat
|
|||||||
81c5ea7b-aa89-47f8-a22e-297207616f0b,Taurus Sports,CODE_128
|
81c5ea7b-aa89-47f8-a22e-297207616f0b,Taurus Sports,CODE_128
|
||||||
81dd0d8d-4613-400e-8cbd-b2189a88a22d,EULIVIA Apartments,CODE_128
|
81dd0d8d-4613-400e-8cbd-b2189a88a22d,EULIVIA Apartments,CODE_128
|
||||||
81e7b9b8-826c-4f9e-9c61-7568a454afa5,Industriya Krasoty,EAN_13
|
81e7b9b8-826c-4f9e-9c61-7568a454afa5,Industriya Krasoty,EAN_13
|
||||||
82,Desigual,CODE_39
|
82,Desigual,QR_CODE
|
||||||
820,Air Miles,EAN_13
|
820,Air Miles,EAN_13
|
||||||
820b5de7-a25a-4d30-ac74-3a70fe682bfd,Мир Электроники,CODE_128
|
820b5de7-a25a-4d30-ac74-3a70fe682bfd,Мир Электроники,CODE_128
|
||||||
821,Ajax Amsterdam,CODE_128
|
821,Ajax Amsterdam,CODE_128
|
||||||
@@ -6135,6 +6179,7 @@ _id,name,barcodeFormat
|
|||||||
848,Lake Side,ITF
|
848,Lake Side,ITF
|
||||||
848939e3-7e55-40af-a46a-a0b0b434bbcf,Планета ZOO,EAN_13
|
848939e3-7e55-40af-a46a-a0b0b434bbcf,Планета ZOO,EAN_13
|
||||||
849,Le Ballon,ITF
|
849,Le Ballon,ITF
|
||||||
|
8495d3db-8532-4bef-a58f-3a77479ff134,C&A,CODE_128
|
||||||
84a82d8b-1d4f-4673-b1e2-b115bbe5b618,Soul Origin,CODE_128
|
84a82d8b-1d4f-4673-b1e2-b115bbe5b618,Soul Origin,CODE_128
|
||||||
84faf272-0010-4f93-8aa1-154caaa11ac2,Pro-Duo Nur für Profis,EAN_8
|
84faf272-0010-4f93-8aa1-154caaa11ac2,Pro-Duo Nur für Profis,EAN_8
|
||||||
85,Diamond Club,CODE_128
|
85,Diamond Club,CODE_128
|
||||||
@@ -6194,6 +6239,7 @@ _id,name,barcodeFormat
|
|||||||
87737e38-8052-4fdc-a90a-3511b9157481,PETS&CO,CODE_128
|
87737e38-8052-4fdc-a90a-3511b9157481,PETS&CO,CODE_128
|
||||||
878,Jula,CODE_39
|
878,Jula,CODE_39
|
||||||
879,KappAhl,CODE_128
|
879,KappAhl,CODE_128
|
||||||
|
879a9dd3-45e3-4633-9376-9183fee6ab3e,Bernardi’s Marketplace,CODE_128
|
||||||
87b3f071-9af7-4163-b512-679717b696ac,Caucciu,EAN_13
|
87b3f071-9af7-4163-b512-679717b696ac,Caucciu,EAN_13
|
||||||
87b925d1-4d9a-47e3-9e54-deaef1981b77,Impfausweis,QR_CODE
|
87b925d1-4d9a-47e3-9e54-deaef1981b77,Impfausweis,QR_CODE
|
||||||
87d141a6-cac3-4d39-9357-a6365850e57f,Coeur de frais,CODE_128
|
87d141a6-cac3-4d39-9357-a6365850e57f,Coeur de frais,CODE_128
|
||||||
@@ -6247,13 +6293,14 @@ _id,name,barcodeFormat
|
|||||||
8a0dca6e-de83-4e48-a42d-a3009da56653,Park 'N Fly,CODE_39
|
8a0dca6e-de83-4e48-a42d-a3009da56653,Park 'N Fly,CODE_39
|
||||||
8a25357e-ebc3-4ae1-b7fc-a10ff3b1abd0,Конфил,CODE_128
|
8a25357e-ebc3-4ae1-b7fc-a10ff3b1abd0,Конфил,CODE_128
|
||||||
8a53dffe-df27-40f0-b2ff-58e53add0b3e,La Cartissima,EAN_13
|
8a53dffe-df27-40f0-b2ff-58e53add0b3e,La Cartissima,EAN_13
|
||||||
|
8a59226e-9895-4924-8616-345549a56aec,Munhowen Drinx,CODE_128
|
||||||
8a702666-368b-48a5-96fd-4e10aac5ae7f,Brooklyn Jeans,ITF
|
8a702666-368b-48a5-96fd-4e10aac5ae7f,Brooklyn Jeans,ITF
|
||||||
8a8095fe-f449-4242-83a1-0d3055874233,Little Sparrow,CODE_128
|
8a8095fe-f449-4242-83a1-0d3055874233,Little Sparrow,CODE_128
|
||||||
8a9c58f4-4db3-4aef-8cf0-d2caa0fcc4d1,EU COVID-19 Potrdilo o cepljenju,QR_CODE
|
8a9c58f4-4db3-4aef-8cf0-d2caa0fcc4d1,EU COVID-19 Potrdilo o cepljenju,QR_CODE
|
||||||
8aa58d48-ad60-4b6d-aa1d-054f94b6453b,Granola,PDF_417
|
8aa58d48-ad60-4b6d-aa1d-054f94b6453b,Granola,PDF_417
|
||||||
8ac5093b-8fc4-49d6-b271-dd845252b60c,Idea Verde Maschi,CODE_128
|
8ac5093b-8fc4-49d6-b271-dd845252b60c,Idea Verde Maschi,CODE_128
|
||||||
8ad83ece-2e55-4937-80c9-04584c598439,COM,EAN_13
|
8ad83ece-2e55-4937-80c9-04584c598439,COM,EAN_13
|
||||||
8b0f2db1-ae97-4af8-8e82-c4067a4ac322,Toyota,CODE_128
|
8b0f2db1-ae97-4af8-8e82-c4067a4ac322,Ma Toyota Extra,CODE_128
|
||||||
8b398aea-e5bd-484d-bdf2-5030bacf9157,Thèoria Milano,CODE_128
|
8b398aea-e5bd-484d-bdf2-5030bacf9157,Thèoria Milano,CODE_128
|
||||||
8b4c413c-effc-4912-9a34-6baea2972199,Karla,CODE_39
|
8b4c413c-effc-4912-9a34-6baea2972199,Karla,CODE_39
|
||||||
8b653178-4f49-4f73-9091-7763e039b539,Aléa Déco,CODE_128
|
8b653178-4f49-4f73-9091-7763e039b539,Aléa Déco,CODE_128
|
||||||
@@ -6304,6 +6351,7 @@ _id,name,barcodeFormat
|
|||||||
903,W.KRUK,CODE_128
|
903,W.KRUK,CODE_128
|
||||||
904,Galeria Wileńska,UPC_A
|
904,Galeria Wileńska,UPC_A
|
||||||
905,YES,EAN_13
|
905,YES,EAN_13
|
||||||
|
90574104-b485-489f-9872-3d32b7e07c59,America Today,CODE_128
|
||||||
906,ZiKO Klub,EAN_13
|
906,ZiKO Klub,EAN_13
|
||||||
9062c2a3-eeb1-4797-afb6-41a0394bb481,Městská knihovna - Česká Třebová,EAN_13
|
9062c2a3-eeb1-4797-afb6-41a0394bb481,Městská knihovna - Česká Třebová,EAN_13
|
||||||
90705634-f152-487c-97eb-27e1728285ef,Миртек,EAN_13
|
90705634-f152-487c-97eb-27e1728285ef,Миртек,EAN_13
|
||||||
@@ -6332,6 +6380,7 @@ _id,name,barcodeFormat
|
|||||||
91915513-4447-47b0-93ae-d489f6ee3a97,Chrome,EAN_8
|
91915513-4447-47b0-93ae-d489f6ee3a97,Chrome,EAN_8
|
||||||
92,Düsseldorf International,EAN_13
|
92,Düsseldorf International,EAN_13
|
||||||
920,Drummond Golf,CODE_39
|
920,Drummond Golf,CODE_39
|
||||||
|
92063e91-526a-4327-ba87-f487bfaec724,Rue du Commerce,CODE_128
|
||||||
920c9bd0-d85c-42c6-9301-fc1ddedd38c2,Idea Casa,CODE_128
|
920c9bd0-d85c-42c6-9301-fc1ddedd38c2,Idea Casa,CODE_128
|
||||||
920ce49c-9728-41f1-b9e9-9f9d06f53d92,Русские Самоцветы,EAN_13
|
920ce49c-9728-41f1-b9e9-9f9d06f53d92,Русские Самоцветы,EAN_13
|
||||||
921,NWZ,EAN_13
|
921,NWZ,EAN_13
|
||||||
@@ -6368,11 +6417,13 @@ _id,name,barcodeFormat
|
|||||||
935ef7c3-a93c-43e1-9abd-075bd05c3051,Форне,EAN_13
|
935ef7c3-a93c-43e1-9abd-075bd05c3051,Форне,EAN_13
|
||||||
936,Orlen - Vitay,CODE_128
|
936,Orlen - Vitay,CODE_128
|
||||||
937,Wojas,EAN_13
|
937,Wojas,EAN_13
|
||||||
|
937cef67-4a01-42fc-9f51-0a3f3210a686,Idea Città Company,GS1_128
|
||||||
938,Sizeer,CODE_128
|
938,Sizeer,CODE_128
|
||||||
939,T2 Tea,CODE_128
|
939,T2 Tea,CODE_128
|
||||||
93a8cca4-73cd-405c-8142-359a41127416,しまむらグループ,CODE_128
|
93a8cca4-73cd-405c-8142-359a41127416,しまむらグループ,CODE_128
|
||||||
93a9836f-0984-45ee-97c6-3e6675a34b11,Ludwig Beck,QR_CODE
|
93a9836f-0984-45ee-97c6-3e6675a34b11,Ludwig Beck,QR_CODE
|
||||||
93b76ad4-76f3-4132-8fe5-972f6ca5eb8a,Київфарм,EAN_13
|
93b76ad4-76f3-4132-8fe5-972f6ca5eb8a,Київфарм,EAN_13
|
||||||
|
93bda8ac-884e-4db0-ab72-09e12f86a3d2,Naturino Family Store,CODE_39
|
||||||
93c53a6b-2efb-4167-aa67-c4905f1692b1,ВелоДрайв,EAN_13
|
93c53a6b-2efb-4167-aa67-c4905f1692b1,ВелоДрайв,EAN_13
|
||||||
93d1d2d1-801d-4293-a1f1-cdf314ba341a,Nilufar,EAN_13
|
93d1d2d1-801d-4293-a1f1-cdf314ba341a,Nilufar,EAN_13
|
||||||
93d42408-df2a-42fd-a10c-9f9c725e8000,TuttintiMO,UPC_A
|
93d42408-df2a-42fd-a10c-9f9c725e8000,TuttintiMO,UPC_A
|
||||||
@@ -6420,6 +6471,7 @@ _id,name,barcodeFormat
|
|||||||
962,Монро,EAN_13
|
962,Монро,EAN_13
|
||||||
963,Jeans Symphony,EAN_13
|
963,Jeans Symphony,EAN_13
|
||||||
9630a33b-0869-4246-91db-80f928bd7b3a,Harfa Sport,EAN_13
|
9630a33b-0869-4246-91db-80f928bd7b3a,Harfa Sport,EAN_13
|
||||||
|
96394b6b-b91f-4fbd-991c-242b7189e0b0,Shoprite,CODE_128
|
||||||
963a19ff-687c-434a-a960-c5e9c6d27c1c,La Cage,CODE_128
|
963a19ff-687c-434a-a960-c5e9c6d27c1c,La Cage,CODE_128
|
||||||
964,Спектр,EAN_13
|
964,Спектр,EAN_13
|
||||||
964bee1b-84ac-42cb-ac20-b182e043a983,SIR,CODE_39
|
964bee1b-84ac-42cb-ac20-b182e043a983,SIR,CODE_39
|
||||||
@@ -6469,6 +6521,7 @@ _id,name,barcodeFormat
|
|||||||
989,Toys Center,EAN_13
|
989,Toys Center,EAN_13
|
||||||
98959593-9b79-4d3a-98bf-fd965d99825e,ташир пицца,PDF_417
|
98959593-9b79-4d3a-98bf-fd965d99825e,ташир пицца,PDF_417
|
||||||
98afc021-2350-4686-89de-03bc9bb686a4,Coeliac Australia,EAN_13
|
98afc021-2350-4686-89de-03bc9bb686a4,Coeliac Australia,EAN_13
|
||||||
|
98c597ea-20b1-4d9e-a6ae-0ed84e0f591d,Juttu,CODE_128
|
||||||
98d5694e-ee5e-4f60-9a32-0ac43d66f54f,Vaprio,CODE_128
|
98d5694e-ee5e-4f60-9a32-0ac43d66f54f,Vaprio,CODE_128
|
||||||
99,Ernsting's Family,ITF
|
99,Ernsting's Family,ITF
|
||||||
990,Nando's,CODE_128
|
990,Nando's,CODE_128
|
||||||
@@ -6530,6 +6583,7 @@ _id,name,barcodeFormat
|
|||||||
9dc29233-9613-4851-8630-15b7b39222c3,Kasztelan,CODE_128
|
9dc29233-9613-4851-8630-15b7b39222c3,Kasztelan,CODE_128
|
||||||
9dc3174d-0990-4d88-a4d6-3c7a6431160d,Янтарь,EAN_13
|
9dc3174d-0990-4d88-a4d6-3c7a6431160d,Янтарь,EAN_13
|
||||||
9dc63493-8062-498a-99be-db701dfc03a4,Farmacia,CODE_128
|
9dc63493-8062-498a-99be-db701dfc03a4,Farmacia,CODE_128
|
||||||
|
9dd46ad3-336b-4af2-9cbc-4526140558ef,Kiriel,EAN_13
|
||||||
9e02cf7a-da20-428d-a363-952f7a3fb25c,Kéddo,EAN_13
|
9e02cf7a-da20-428d-a363-952f7a3fb25c,Kéddo,EAN_13
|
||||||
9e82e20d-4da0-46c0-bb94-c2ba7b9b3d74,Индустрия красоты,EAN_13
|
9e82e20d-4da0-46c0-bb94-c2ba7b9b3d74,Индустрия красоты,EAN_13
|
||||||
9ec73fed-0974-4b7c-98e0-27aba810e8e1,Spielwarentraum,CODE_128
|
9ec73fed-0974-4b7c-98e0-27aba810e8e1,Spielwarentraum,CODE_128
|
||||||
@@ -6541,6 +6595,7 @@ _id,name,barcodeFormat
|
|||||||
9fd0773f-f0ee-476c-8351-c02fb65b9360,Plus Market,EAN_13
|
9fd0773f-f0ee-476c-8351-c02fb65b9360,Plus Market,EAN_13
|
||||||
a00761f0-abf1-4690-a95a-b18e41c527d2,Pet and Pool,CODE_128
|
a00761f0-abf1-4690-a95a-b18e41c527d2,Pet and Pool,CODE_128
|
||||||
a017f67b-3483-4587-97a0-2c5c4af6834e,SchuhMarke,CODE_128
|
a017f67b-3483-4587-97a0-2c5c4af6834e,SchuhMarke,CODE_128
|
||||||
|
a0284158-4eaf-4891-9768-f93e1049413a,Десятка,EAN_13
|
||||||
a04e9cdb-caec-4f4f-bf96-9e40fd90cb09,PharmaSave,CODE_128
|
a04e9cdb-caec-4f4f-bf96-9e40fd90cb09,PharmaSave,CODE_128
|
||||||
a05edd71-80dd-4e23-87cf-5df65a193281,Andre Tan,EAN_13
|
a05edd71-80dd-4e23-87cf-5df65a193281,Andre Tan,EAN_13
|
||||||
a08ccd9d-76ce-4245-8582-24d2840ff7b9,Chanel,CODABAR
|
a08ccd9d-76ce-4245-8582-24d2840ff7b9,Chanel,CODABAR
|
||||||
@@ -6562,6 +6617,7 @@ a238f465-ff8e-4077-b5fe-a1f250ed90d9,BJ's Wholesale,UPC_A
|
|||||||
a2756aea-2ca4-4870-811e-100871fdb73e,Pratiko,EAN_13
|
a2756aea-2ca4-4870-811e-100871fdb73e,Pratiko,EAN_13
|
||||||
a29668f6-dd2e-4281-917e-49e28ebff6a1,Koloria,CODE_128
|
a29668f6-dd2e-4281-917e-49e28ebff6a1,Koloria,CODE_128
|
||||||
a2b352d9-5d5d-4080-9f52-eb6a798aa6c6,Ferlenz,CODE_128
|
a2b352d9-5d5d-4080-9f52-eb6a798aa6c6,Ferlenz,CODE_128
|
||||||
|
a322cee9-b5c6-4384-a365-c970f335cc5c,Erdkorn,QR_CODE
|
||||||
a323e0ec-2b0b-4a82-a950-11f7516f2584,OnePass,EAN_13
|
a323e0ec-2b0b-4a82-a950-11f7516f2584,OnePass,EAN_13
|
||||||
a36556e0-433a-4b16-b72c-4751a386d707,EU COVID-19 Impfzertifikat - Erstimpfung,QR_CODE
|
a36556e0-433a-4b16-b72c-4751a386d707,EU COVID-19 Impfzertifikat - Erstimpfung,QR_CODE
|
||||||
a3828047-ff01-4eb4-be10-6e4d635ca029,Leffers,ITF
|
a3828047-ff01-4eb4-be10-6e4d635ca029,Leffers,ITF
|
||||||
@@ -6590,6 +6646,7 @@ a6060858-7d83-4f60-8318-b80635013f45,Detershop,EAN_13
|
|||||||
a645973d-7e87-46ab-8c77-0380ca06ae32,Perth Zoo,CODE_39
|
a645973d-7e87-46ab-8c77-0380ca06ae32,Perth Zoo,CODE_39
|
||||||
a65e3023-fa06-47c0-bfdc-4dc79f54c825,丁丁藥局,EAN_13
|
a65e3023-fa06-47c0-bfdc-4dc79f54c825,丁丁藥局,EAN_13
|
||||||
a69154f5-16a8-4543-bb49-b7a68bb3d301,EU COVID-19 Potvrda o cijepljenju,QR_CODE
|
a69154f5-16a8-4543-bb49-b7a68bb3d301,EU COVID-19 Potvrda o cijepljenju,QR_CODE
|
||||||
|
a69d8b79-a0e7-422b-a149-64c66b23aea4,Plus More,CODE_128
|
||||||
a6aa66ba-00b8-4922-b628-98cea029c9e2,Coop,EAN_13
|
a6aa66ba-00b8-4922-b628-98cea029c9e2,Coop,EAN_13
|
||||||
a6ab3df9-10bc-47df-bed4-839fe1e908be,零食物語,CODE_128
|
a6ab3df9-10bc-47df-bed4-839fe1e908be,零食物語,CODE_128
|
||||||
a6b2c527-afbc-4e71-ae24-e5e5e270d474,Pappert,PDF_417
|
a6b2c527-afbc-4e71-ae24-e5e5e270d474,Pappert,PDF_417
|
||||||
@@ -6599,6 +6656,7 @@ a7634961-1509-4902-9b25-714ef789e926,2HB,EAN_13
|
|||||||
a78ee36a-3682-404f-9c83-307c1a6b421e,Moda Lina,EAN_13
|
a78ee36a-3682-404f-9c83-307c1a6b421e,Moda Lina,EAN_13
|
||||||
a79b9a92-9821-4824-978e-1a257abfbaff,Wormland,CODE_128
|
a79b9a92-9821-4824-978e-1a257abfbaff,Wormland,CODE_128
|
||||||
a7b3e795-4746-45a4-9c80-d331fb051632,BonBon,EAN_13
|
a7b3e795-4746-45a4-9c80-d331fb051632,BonBon,EAN_13
|
||||||
|
a7e263c3-75fd-4ac2-98ea-0e7b3e425a74,SUPEREFECTIVO,CODE_39
|
||||||
a7f1c8c5-2895-4a74-98ac-9740e7c59922,Coffeelat,QR_CODE
|
a7f1c8c5-2895-4a74-98ac-9740e7c59922,Coffeelat,QR_CODE
|
||||||
a8090907-7e2e-4038-8831-0c72adaa0664,US FashionStore,EAN_13
|
a8090907-7e2e-4038-8831-0c72adaa0664,US FashionStore,EAN_13
|
||||||
a83b00dc-1bfd-41b6-9fee-3c7f5d33fef5,Baden,EAN_13
|
a83b00dc-1bfd-41b6-9fee-3c7f5d33fef5,Baden,EAN_13
|
||||||
@@ -6628,6 +6686,7 @@ aaa82398-d78f-46d6-bfb5-a40843e94cc8,CLEVER WEAR,EAN_13
|
|||||||
aabf2ea4-170c-42e4-906b-ea1253ebf580,Родные масла,EAN_13
|
aabf2ea4-170c-42e4-906b-ea1253ebf580,Родные масла,EAN_13
|
||||||
aac03de2-6c97-4bd9-8d72-a7bba15bea6d,La capsuleria,EAN_13
|
aac03de2-6c97-4bd9-8d72-a7bba15bea6d,La capsuleria,EAN_13
|
||||||
aae4f87d-ee8c-4ff0-9cb2-88c478b7a0dc,Bonjour,EAN_13
|
aae4f87d-ee8c-4ff0-9cb2-88c478b7a0dc,Bonjour,EAN_13
|
||||||
|
aae6aab3-e5fb-47c1-b6c1-c30c3f386793,Netto,CODE_128
|
||||||
aaf65c10-a78e-4b18-8c79-371d5cdef871,La Provençale,CODE_39
|
aaf65c10-a78e-4b18-8c79-371d5cdef871,La Provençale,CODE_39
|
||||||
ab0c09c4-d1cc-40a4-8b46-f101dc376655,Trade group SMIT,CODE_128
|
ab0c09c4-d1cc-40a4-8b46-f101dc376655,Trade group SMIT,CODE_128
|
||||||
ab0c5857-5b3d-4ac3-8910-ec6b8c49a0dc,Three,EAN_13
|
ab0c5857-5b3d-4ac3-8910-ec6b8c49a0dc,Three,EAN_13
|
||||||
@@ -6636,6 +6695,7 @@ ab245924-7af0-4996-84a2-f19a6b6a62fa,Hollister,CODE_128
|
|||||||
ab37459c-4368-4684-9ffa-3ac84c69e87a,ДомДоктор,EAN_13
|
ab37459c-4368-4684-9ffa-3ac84c69e87a,ДомДоктор,EAN_13
|
||||||
ab4a36d9-9a11-4575-a6cb-1bd053c6e00f,СБА,CODE_128
|
ab4a36d9-9a11-4575-a6cb-1bd053c6e00f,СБА,CODE_128
|
||||||
ab6de5de-ea68-47d6-87ad-884e63f63f48,EU COVID-19 Удостоверение за ваксинация - Първа ваксинация,QR_CODE
|
ab6de5de-ea68-47d6-87ad-884e63f63f48,EU COVID-19 Удостоверение за ваксинация - Първа ваксинация,QR_CODE
|
||||||
|
ab73cd57-b075-425f-afe6-868e56207a42,Rewe,QR_CODE
|
||||||
ab7a0e82-ad67-40fb-a85f-83cdd10fb44a,Depot,QR_CODE
|
ab7a0e82-ad67-40fb-a85f-83cdd10fb44a,Depot,QR_CODE
|
||||||
ab9d5459-25c3-4040-bff0-b7804375065f,Забіяка,CODE_128
|
ab9d5459-25c3-4040-bff0-b7804375065f,Забіяка,CODE_128
|
||||||
aba38815-1a55-456f-84b6-0321d8d34102,Андреич,EAN_13
|
aba38815-1a55-456f-84b6-0321d8d34102,Андреич,EAN_13
|
||||||
@@ -6676,11 +6736,13 @@ b000bec7-fe1d-4a01-8134-7e93c72fcf2c,фаэтон,EAN_13
|
|||||||
b00fc66a-460d-43c9-a5f1-86b0a92b125a,Дачник,CODE_128
|
b00fc66a-460d-43c9-a5f1-86b0a92b125a,Дачник,CODE_128
|
||||||
b0210273-794f-427b-bba1-c940a7aac7df,Helen,CODE_39
|
b0210273-794f-427b-bba1-c940a7aac7df,Helen,CODE_39
|
||||||
b0382f02-57d7-4d7a-a3f1-25ea85507c64,Laser Game Evolution,CODE_128
|
b0382f02-57d7-4d7a-a3f1-25ea85507c64,Laser Game Evolution,CODE_128
|
||||||
|
b059eafb-017b-49f0-9d74-62889d8ee777,City of Whitehorse,CODE_128
|
||||||
b063caac-e875-4475-8ae6-09a0f979fb85,CLUB SALUTE,CODE_39
|
b063caac-e875-4475-8ae6-09a0f979fb85,CLUB SALUTE,CODE_39
|
||||||
b07244fc-81d3-492b-a9e5-a813a57eea9c,Faciba,EAN_8
|
b07244fc-81d3-492b-a9e5-a813a57eea9c,Faciba,EAN_8
|
||||||
b07e5b4d-d658-4ba6-9305-d497af7a19ae,Nijhof Schoenen,ITF
|
b07e5b4d-d658-4ba6-9305-d497af7a19ae,Nijhof Schoenen,ITF
|
||||||
b086ef99-b8b8-45a9-80f5-33a4cb01aba8,spudshed,CODE_128
|
b086ef99-b8b8-45a9-80f5-33a4cb01aba8,spudshed,CODE_128
|
||||||
b0973d67-75d0-45e3-9f17-0f4cb80a4824,Motozem,CODE_128
|
b0973d67-75d0-45e3-9f17-0f4cb80a4824,Motozem,CODE_128
|
||||||
|
b0cfcd52-01a5-4533-8970-6e402e52bcb0,Brikon,UPC_A
|
||||||
b0e24b5a-4034-44b9-b22b-2a008d0bcde5,Eurodì,CODE_128
|
b0e24b5a-4034-44b9-b22b-2a008d0bcde5,Eurodì,CODE_128
|
||||||
b0efcdb1-872a-44f0-961a-a97ee45c7ba8,Porsche Group,QR_CODE
|
b0efcdb1-872a-44f0-961a-a97ee45c7ba8,Porsche Group,QR_CODE
|
||||||
b0f4291f-8d68-4071-8d10-cc212b4495cc,Iper d'Oriente,EAN_13
|
b0f4291f-8d68-4071-8d10-cc212b4495cc,Iper d'Oriente,EAN_13
|
||||||
@@ -6707,12 +6769,14 @@ b2ab5d25-1981-4120-be54-86ccda399861,Vitulano,UPC_A
|
|||||||
b2b50b52-83c6-43d3-bb13-008544e2cfa5,Turčianska knižnica,EAN_13
|
b2b50b52-83c6-43d3-bb13-008544e2cfa5,Turčianska knižnica,EAN_13
|
||||||
b2b7d24b-fdbc-468b-be59-b189d4d5fdf9,Het Certificaat B.V.,QR_CODE
|
b2b7d24b-fdbc-468b-be59-b189d4d5fdf9,Het Certificaat B.V.,QR_CODE
|
||||||
b2c03313-9621-4233-9b61-5faa8d2c66e0,JILL STUART,EAN_13
|
b2c03313-9621-4233-9b61-5faa8d2c66e0,JILL STUART,EAN_13
|
||||||
|
b2e520a4-c21a-4ba0-822b-c9ac5fe79f4d,BLUME2000,QR_CODE
|
||||||
b2f90e3a-4669-4cd4-8c31-65fbb91dc26e,Advantage Pharmacy,CODE_128
|
b2f90e3a-4669-4cd4-8c31-65fbb91dc26e,Advantage Pharmacy,CODE_128
|
||||||
b31982e9-7c22-4e92-8210-e08eaa123727,Linberg,EAN_13
|
b31982e9-7c22-4e92-8210-e08eaa123727,Linberg,EAN_13
|
||||||
b334927e-9574-457c-9a1f-1b7dd5928304,Farmanoi,CODE_128
|
b334927e-9574-457c-9a1f-1b7dd5928304,Farmanoi,CODE_128
|
||||||
b359db35-9be6-4369-b796-04b47b4044be,Signorizza,EAN_13
|
b359db35-9be6-4369-b796-04b47b4044be,Signorizza,EAN_13
|
||||||
b36ae43e-8a9c-41f7-8c54-d5ae673c94f5,Bio&Co,EAN_13
|
b36ae43e-8a9c-41f7-8c54-d5ae673c94f5,Bio&Co,EAN_13
|
||||||
b43d0b6b-db53-44a7-b518-30cace59c222,British Garden Centres,CODE_39
|
b43d0b6b-db53-44a7-b518-30cace59c222,British Garden Centres,CODE_39
|
||||||
|
b4606b36-853e-4014-9524-fc07fa6e1d4a,Cantina Rauscedo,EAN_13
|
||||||
b4663d4f-dd9f-43cc-ba0e-4ce9b0beccd2,Пивлавка,EAN_13
|
b4663d4f-dd9f-43cc-ba0e-4ce9b0beccd2,Пивлавка,EAN_13
|
||||||
b4725b6c-105f-4898-a8d5-ba426ddf9508,Yamazaki,CODE_128
|
b4725b6c-105f-4898-a8d5-ba426ddf9508,Yamazaki,CODE_128
|
||||||
b472df21-8f40-44ff-a11f-bbe1d76d6d58,Company Shop Group,CODE_39
|
b472df21-8f40-44ff-a11f-bbe1d76d6d58,Company Shop Group,CODE_39
|
||||||
@@ -6720,9 +6784,11 @@ b497667e-0c92-4db6-9579-63bbe35af881,Праздничный,QR_CODE
|
|||||||
b4b5583a-3d0e-458e-b800-3b43968a8421,Pirex,CODE_128
|
b4b5583a-3d0e-458e-b800-3b43968a8421,Pirex,CODE_128
|
||||||
b4c412d7-ad0b-4afd-aed8-0cf113f445ca,Аквафор,CODE_128
|
b4c412d7-ad0b-4afd-aed8-0cf113f445ca,Аквафор,CODE_128
|
||||||
b4e4e61f-8605-45b6-b672-fce67898ba4e,Schuhkay,EAN_8
|
b4e4e61f-8605-45b6-b672-fce67898ba4e,Schuhkay,EAN_8
|
||||||
|
b4f37441-b068-443f-bbfb-fca23c9f5eec,Tuttigiorni,EAN_13
|
||||||
b4f4c3c3-4ad3-4431-9048-1d6b0e47a649,Tezenis,CODE_128
|
b4f4c3c3-4ad3-4431-9048-1d6b0e47a649,Tezenis,CODE_128
|
||||||
b52836be-a999-4bf8-ba0b-5f2b9b96a509,Youth Hostels Luxembourg,CODE_128
|
b52836be-a999-4bf8-ba0b-5f2b9b96a509,Youth Hostels Luxembourg,CODE_128
|
||||||
b54963ea-a217-434b-b0fa-e8114fd6b999,Пинта,EAN_13
|
b54963ea-a217-434b-b0fa-e8114fd6b999,Пинта,EAN_13
|
||||||
|
b54ed01d-e46b-4f24-8ce9-e08f624f2ddb,IGA,CODE_128
|
||||||
b5656988-55fb-46c8-91ab-24a5b8422549,Moja Starówka,CODE_128
|
b5656988-55fb-46c8-91ab-24a5b8422549,Moja Starówka,CODE_128
|
||||||
b5695b84-a5cf-4286-87ab-afbe9368be1f,Tulipes,CODE_39
|
b5695b84-a5cf-4286-87ab-afbe9368be1f,Tulipes,CODE_39
|
||||||
b5dc4188-75d6-4cf1-b7f2-b0e85a57bc9a,Boulangeries Maison Toulorge,CODE_128
|
b5dc4188-75d6-4cf1-b7f2-b0e85a57bc9a,Boulangeries Maison Toulorge,CODE_128
|
||||||
@@ -6757,6 +6823,7 @@ b9c4e2bd-88ee-4345-b0c4-3828e076637c,Pro-Duo Exclusief Professionals,EAN_8
|
|||||||
b9f36613-ed74-441e-abce-66d465b83594,Accademia Italiana della Cucina,CODE_39
|
b9f36613-ed74-441e-abce-66d465b83594,Accademia Italiana della Cucina,CODE_39
|
||||||
b9f3eacc-e6d9-43e2-93f0-a1e63221b1fe,Più Medical,CODE_128
|
b9f3eacc-e6d9-43e2-93f0-a1e63221b1fe,Più Medical,CODE_128
|
||||||
b9fc9d9a-da0e-4fe2-82d8-5d6672263b4b,Kačka,CODE_128
|
b9fc9d9a-da0e-4fe2-82d8-5d6672263b4b,Kačka,CODE_128
|
||||||
|
ba063e76-f5be-4e98-a549-7040a825caf7,Trendevice,CODE_128
|
||||||
ba0d23c2-0030-4b68-9bec-6daf6c0db596,Zoomarket,CODE_128
|
ba0d23c2-0030-4b68-9bec-6daf6c0db596,Zoomarket,CODE_128
|
||||||
ba119be5-7382-453c-93be-625c555aec84,Vitaminas,CODE_128
|
ba119be5-7382-453c-93be-625c555aec84,Vitaminas,CODE_128
|
||||||
ba5aca20-b0fd-417d-8739-ba9b347c8fff,Клиника ЛМС,CODE_128
|
ba5aca20-b0fd-417d-8739-ba9b347c8fff,Клиника ЛМС,CODE_128
|
||||||
@@ -6822,6 +6889,7 @@ bfbe8661-ae7a-4338-bb37-fde8cd6c57a1,Хмель и Солод,EAN_13
|
|||||||
bfcd1bbc-3671-4a2b-99d4-8195c5246644,Metalmark,EAN_13
|
bfcd1bbc-3671-4a2b-99d4-8195c5246644,Metalmark,EAN_13
|
||||||
bfe5aac8-ea2d-41e0-ba15-af949e5437d7,Каприз,EAN_13
|
bfe5aac8-ea2d-41e0-ba15-af949e5437d7,Каприз,EAN_13
|
||||||
bff24292-b2e3-4322-9462-d5ecc80ce044,Halfords Motoring Club,QR_CODE
|
bff24292-b2e3-4322-9462-d5ecc80ce044,Halfords Motoring Club,QR_CODE
|
||||||
|
bricoman-it,Bricoman,CODE_128
|
||||||
c03f0f47-ce09-4bf1-95f8-c1d0c6f1a8ca,Coop,EAN_13
|
c03f0f47-ce09-4bf1-95f8-c1d0c6f1a8ca,Coop,EAN_13
|
||||||
c043ef0e-49a9-4f10-877f-974247cf0f16,IperBiobottega,EAN_13
|
c043ef0e-49a9-4f10-877f-974247cf0f16,IperBiobottega,EAN_13
|
||||||
c0712c54-a6a6-4695-b9ba-4f5a296b66cf,Apothical,EAN_13
|
c0712c54-a6a6-4695-b9ba-4f5a296b66cf,Apothical,EAN_13
|
||||||
@@ -6871,6 +6939,7 @@ c51b31d2-056b-41a0-9347-c4d02375df01,офисмаг,EAN_13
|
|||||||
c51c692c-9e90-48fb-9047-38d3bb7fec2d,Мясницкий Ряд,CODE_128
|
c51c692c-9e90-48fb-9047-38d3bb7fec2d,Мясницкий Ряд,CODE_128
|
||||||
c53f804f-29e6-4dc0-9f66-0b9b016cdade,Möbel Borst,CODE_39
|
c53f804f-29e6-4dc0-9f66-0b9b016cdade,Möbel Borst,CODE_39
|
||||||
c54a0027-fd79-457e-80eb-e73e1332e3e9,Ni Hao,CODE_128
|
c54a0027-fd79-457e-80eb-e73e1332e3e9,Ni Hao,CODE_128
|
||||||
|
c57001e2-db2b-4f15-8c49-29c6502a86e8,Underwood Meat Company,CODE_39
|
||||||
c5846a8f-687a-4de9-a5b5-b575488ac84b,Radhe Wholesale & Retail,EAN_13
|
c5846a8f-687a-4de9-a5b5-b575488ac84b,Radhe Wholesale & Retail,EAN_13
|
||||||
c59fc214-7895-40fc-8f94-9d1d800b66d2,Conradt,CODE_39
|
c59fc214-7895-40fc-8f94-9d1d800b66d2,Conradt,CODE_39
|
||||||
c5acc06c-0b7d-4e4d-bee3-2134e2fb3b9c,Belles Fleurs,EAN_13
|
c5acc06c-0b7d-4e4d-bee3-2134e2fb3b9c,Belles Fleurs,EAN_13
|
||||||
@@ -6910,6 +6979,7 @@ c9231cc7-92f2-447c-ad84-8d167c23e9cd,Zwitserse Apotheek,CODE_128
|
|||||||
c925f293-54ee-47ba-ba48-792945c5fa94,Смайл,UPC_A
|
c925f293-54ee-47ba-ba48-792945c5fa94,Смайл,UPC_A
|
||||||
c9295edb-4acf-4e21-b931-d07d1b97e9be,Weingärtner Gartencenter,EAN_13
|
c9295edb-4acf-4e21-b931-d07d1b97e9be,Weingärtner Gartencenter,EAN_13
|
||||||
c935a5b9-03f1-4194-8aa2-39545b376065,Alpina Intimo,CODE_39
|
c935a5b9-03f1-4194-8aa2-39545b376065,Alpina Intimo,CODE_39
|
||||||
|
c94a90ff-4118-4310-bcf2-588463110b83,knihovna Rosice,CODE_128
|
||||||
c964ff0f-5ac9-4976-967f-a55c7ec72e14,Mega Pet Warehouse,CODE_128
|
c964ff0f-5ac9-4976-967f-a55c7ec72e14,Mega Pet Warehouse,CODE_128
|
||||||
c998f7d2-6403-46c5-ba21-270195e61cd3,MAX & Co,EAN_13
|
c998f7d2-6403-46c5-ba21-270195e61cd3,MAX & Co,EAN_13
|
||||||
c9d387cb-7a0f-492f-a18d-f4d559ccbade,Информат,EAN_13
|
c9d387cb-7a0f-492f-a18d-f4d559ccbade,Информат,EAN_13
|
||||||
@@ -6921,10 +6991,13 @@ ca4944a1-3892-4803-8b04-b72cd996511f,Diadema,CODE_128
|
|||||||
ca650de4-55cc-4df6-8994-3378274bebf5,Moby Dick,CODE_128
|
ca650de4-55cc-4df6-8994-3378274bebf5,Moby Dick,CODE_128
|
||||||
caa55951-513c-4dca-b0bc-3cb80d85e4f2,PANORAMICO,EAN_13
|
caa55951-513c-4dca-b0bc-3cb80d85e4f2,PANORAMICO,EAN_13
|
||||||
cab2ae0e-10bc-4c58-b159-59f4e8566ca7,Hawkesbury Library Service,CODE_39
|
cab2ae0e-10bc-4c58-b159-59f4e8566ca7,Hawkesbury Library Service,CODE_39
|
||||||
|
cad853d8-b9fa-43d6-b37d-39274a571269,Harmony Beauty,EAN_8
|
||||||
caddfc56-1d2a-454c-bece-1516b13fa249,Millstream,EAN_13
|
caddfc56-1d2a-454c-bece-1516b13fa249,Millstream,EAN_13
|
||||||
|
cae4d233-caae-43ff-aaba-affdc99c2d98,ALTERNATURA d.o.o.,EAN_13
|
||||||
cae69560-d7e6-4cb7-9ac5-95199c15f9cc,Blumenmarkt Dietrich,CODE_128
|
cae69560-d7e6-4cb7-9ac5-95199c15f9cc,Blumenmarkt Dietrich,CODE_128
|
||||||
caff4297-2ae6-4315-9329-614c8510eb7f,Вместе Выгодно,CODE_128
|
caff4297-2ae6-4315-9329-614c8510eb7f,Вместе Выгодно,CODE_128
|
||||||
cb03988e-5063-4f48-aef2-9f959f9771a2,DVV,CODE_128
|
cb03988e-5063-4f48-aef2-9f959f9771a2,DVV,CODE_128
|
||||||
|
cb12d304-17dc-45ba-be1c-5602237320ce,Vero Moda,QR_CODE
|
||||||
cb1f1114-d1ea-4987-badc-7194d1ab1ca8,Zahradní Centrum,CODE_128
|
cb1f1114-d1ea-4987-badc-7194d1ab1ca8,Zahradní Centrum,CODE_128
|
||||||
cb4ead90-a2f7-41ba-80eb-d4970bed83bd,A-Kaart,CODABAR
|
cb4ead90-a2f7-41ba-80eb-d4970bed83bd,A-Kaart,CODABAR
|
||||||
cb7b9237-0c2d-437a-ba38-fa6decca977e,萊爾富,CODE_128
|
cb7b9237-0c2d-437a-ba38-fa6decca977e,萊爾富,CODE_128
|
||||||
@@ -6947,6 +7020,7 @@ cd121cb8-988c-454f-a4ac-10365bf4aa6c,Shop Santé,CODE_128
|
|||||||
cd26930f-c1ac-4543-a23c-0b90cfa0b1f7,36.6 Здоровье,EAN_13
|
cd26930f-c1ac-4543-a23c-0b90cfa0b1f7,36.6 Здоровье,EAN_13
|
||||||
cd38f71a-1a0a-4ba7-ac1d-43974fd42e1a,Gel Market,EAN_13
|
cd38f71a-1a0a-4ba7-ac1d-43974fd42e1a,Gel Market,EAN_13
|
||||||
cd73cbfb-68f5-4d67-9411-310695558c6b,NKC,CODE_128
|
cd73cbfb-68f5-4d67-9411-310695558c6b,NKC,CODE_128
|
||||||
|
cd840f28-f17c-44ed-9ec7-15b48aa2f0e1,Knihovna Matěje Josefa Sychry,EAN_13
|
||||||
cd9d6482-a7dd-4283-a776-f0982ade57a5,Biraghi,EAN_13
|
cd9d6482-a7dd-4283-a776-f0982ade57a5,Biraghi,EAN_13
|
||||||
cdd777ae-6fa4-458d-b7e5-f7c18fff857a,EU COVID-19 Vaccinationsintyg,QR_CODE
|
cdd777ae-6fa4-458d-b7e5-f7c18fff857a,EU COVID-19 Vaccinationsintyg,QR_CODE
|
||||||
cdd87d70-3e73-48a2-a88a-5e1083e41d0a,1000 мелочей,EAN_13
|
cdd87d70-3e73-48a2-a88a-5e1083e41d0a,1000 мелочей,EAN_13
|
||||||
@@ -6968,7 +7042,9 @@ cf4f5874-aef4-492c-ae9c-b47cb2f14224,Jardinerie Loiseau,EAN_13
|
|||||||
cfce4667-ff5d-44f0-8ba7-fbc44bbf2cb5,Orange Club,CODE_128
|
cfce4667-ff5d-44f0-8ba7-fbc44bbf2cb5,Orange Club,CODE_128
|
||||||
cfd15fb5-1bac-455b-a5f7-b808390fba06,Сакура Суши,EAN_8
|
cfd15fb5-1bac-455b-a5f7-b808390fba06,Сакура Суши,EAN_8
|
||||||
cff8ca3d-3620-4098-9b8b-e181f84f6ec8,365,CODE_128
|
cff8ca3d-3620-4098-9b8b-e181f84f6ec8,365,CODE_128
|
||||||
|
d0153291-afc6-4d0f-8120-74c0b321434a,SA Guild of Actors,CODE_128
|
||||||
d0540b51-9716-4d59-bc2f-1582b044c029,Wedding Price Card,CODE_128
|
d0540b51-9716-4d59-bc2f-1582b044c029,Wedding Price Card,CODE_128
|
||||||
|
d05b520c-091a-4a9b-84de-689484927109,Lotto Niedersachsen,DATA_MATRIX
|
||||||
d0a04b4f-df54-4fcd-b410-87ea5d0986aa,EU COVID-19 Očkovací preukaz - Záber na prvé očkovanie,QR_CODE
|
d0a04b4f-df54-4fcd-b410-87ea5d0986aa,EU COVID-19 Očkovací preukaz - Záber na prvé očkovanie,QR_CODE
|
||||||
d0b9a6b8-f724-4fe7-8195-e810297505af,Chocolaterie Albèrt,EAN_13
|
d0b9a6b8-f724-4fe7-8195-e810297505af,Chocolaterie Albèrt,EAN_13
|
||||||
d1018675-b1b2-44bc-91b6-a985d744836f,La Sirena,EAN_13
|
d1018675-b1b2-44bc-91b6-a985d744836f,La Sirena,EAN_13
|
||||||
@@ -7004,6 +7080,7 @@ d403852e-7683-49f0-9de5-6e1ec5ac842d,Andreas,CODE_128
|
|||||||
d4115422-7d2e-4001-9c49-4c1353c8b88d,Secom,EAN_13
|
d4115422-7d2e-4001-9c49-4c1353c8b88d,Secom,EAN_13
|
||||||
d44c1355-2941-4393-aeb8-1a7ad7122f67,HUALI MARKET,EAN_13
|
d44c1355-2941-4393-aeb8-1a7ad7122f67,HUALI MARKET,EAN_13
|
||||||
d4502068-af6b-43ab-b9a5-46dc1899e22a,Ябко,EAN_13
|
d4502068-af6b-43ab-b9a5-46dc1899e22a,Ябко,EAN_13
|
||||||
|
d4517693-3f1c-45a6-86f2-d60ad19d04e9,U Baristu,QR_CODE
|
||||||
d4934c41-3cae-40dd-bd5c-2ca88bdcf9f5,Bau-Buy,EAN_8
|
d4934c41-3cae-40dd-bd5c-2ca88bdcf9f5,Bau-Buy,EAN_8
|
||||||
d4b67cb7-cfbf-4bac-8711-2088b8592e5f,Wara,EAN_13
|
d4b67cb7-cfbf-4bac-8711-2088b8592e5f,Wara,EAN_13
|
||||||
d4e44512-0ac2-4d1f-8603-01cd0497416c,The co-operative,CODE_128
|
d4e44512-0ac2-4d1f-8603-01cd0497416c,The co-operative,CODE_128
|
||||||
@@ -7034,6 +7111,7 @@ d6eb202f-ba2f-4253-8f5d-1dce44d13bef,Канцлер,EAN_13
|
|||||||
d71e4888-dd0b-4aac-ae5b-937b17ee4149,FQCC,EAN_13
|
d71e4888-dd0b-4aac-ae5b-937b17ee4149,FQCC,EAN_13
|
||||||
d7893d3c-c704-4daa-955b-a97f061d0138,ВАБИ САБИ,CODE_128
|
d7893d3c-c704-4daa-955b-a97f061d0138,ВАБИ САБИ,CODE_128
|
||||||
d78fc335-cab2-40d7-a56c-333f568b36b4,социалочка,EAN_13
|
d78fc335-cab2-40d7-a56c-333f568b36b4,социалочка,EAN_13
|
||||||
|
d7959c14-98b1-4187-9088-494d1a7c5f9f,Canningvale,CODE_128
|
||||||
d79a1500-206d-407a-b111-724b898aa154,Sportsman's Warehouse,CODE_39
|
d79a1500-206d-407a-b111-724b898aa154,Sportsman's Warehouse,CODE_39
|
||||||
d7a18a8f-32b5-43f5-8290-5caf4297aaf8,Halfords Colleague Discount,CODE_128
|
d7a18a8f-32b5-43f5-8290-5caf4297aaf8,Halfords Colleague Discount,CODE_128
|
||||||
d7b8deb4-4006-4223-9600-331458fade3d,Пиватерра,EAN_13
|
d7b8deb4-4006-4223-9600-331458fade3d,Пиватерра,EAN_13
|
||||||
@@ -7100,6 +7178,7 @@ dema-be,DEMA,EAN_13
|
|||||||
df2f73ec-a3c1-4169-b47e-4742bcab704d,Digizenz,QR_CODE
|
df2f73ec-a3c1-4169-b47e-4742bcab704d,Digizenz,QR_CODE
|
||||||
df3228e8-78d0-42c7-8e45-30089e5267ea,Эдисон,EAN_13
|
df3228e8-78d0-42c7-8e45-30089e5267ea,Эдисон,EAN_13
|
||||||
df53a52a-320b-41ce-8ca0-92da86fcae0c,Koutný spol,CODE_93
|
df53a52a-320b-41ce-8ca0-92da86fcae0c,Koutný spol,CODE_93
|
||||||
|
df5ad302-ae2d-47db-b9c9-b5e030d3b553,ALDI,CODE_128
|
||||||
df62dc4f-b31a-4615-a289-94410da0ce7b,Melkior,CODE_128
|
df62dc4f-b31a-4615-a289-94410da0ce7b,Melkior,CODE_128
|
||||||
df668825-ed7c-4f05-b74b-47ec6daa69f0,Breakers,CODE_39
|
df668825-ed7c-4f05-b74b-47ec6daa69f0,Breakers,CODE_39
|
||||||
dfc5ba69-483e-46ab-8951-3afc7c6d7460,Chaussexpo,CODE_128
|
dfc5ba69-483e-46ab-8951-3afc7c6d7460,Chaussexpo,CODE_128
|
||||||
@@ -7112,6 +7191,7 @@ e0491f99-5f5b-4bfa-bb1d-f7cfe688ca26,Хмельная Миля,CODE_128
|
|||||||
e0663514-cb9c-413a-ad94-8b83dde796f8,Hommy,EAN_13
|
e0663514-cb9c-413a-ad94-8b83dde796f8,Hommy,EAN_13
|
||||||
e0b022eb-bc2b-4553-8345-5869e4f644e2,Life 2.0,CODE_39
|
e0b022eb-bc2b-4553-8345-5869e4f644e2,Life 2.0,CODE_39
|
||||||
e0b2fcbb-e302-4a5e-aa4b-3991fcee7831,KanclerCom,EAN_13
|
e0b2fcbb-e302-4a5e-aa4b-3991fcee7831,KanclerCom,EAN_13
|
||||||
|
e0d0863f-c345-4e3d-baf7-853414056795,Sport 2000,EAN_13
|
||||||
e0db8778-d9a2-4b6c-bece-1b2c4bef11c0,Everyone Fitness,CODE_39
|
e0db8778-d9a2-4b6c-bece-1b2c4bef11c0,Everyone Fitness,CODE_39
|
||||||
e0eadec9-539e-4316-b9bd-9e29d59c1abb,Containers for change,CODE_128
|
e0eadec9-539e-4316-b9bd-9e29d59c1abb,Containers for change,CODE_128
|
||||||
e132948b-f6a2-44cb-b0c1-d9366151a0e2, BSTRONG,CODE_128
|
e132948b-f6a2-44cb-b0c1-d9366151a0e2, BSTRONG,CODE_128
|
||||||
@@ -7146,11 +7226,13 @@ e435e3ee-a81f-40f8-86be-2def0a610ac1,Спорт-Марафон,EAN_13
|
|||||||
e4561f48-5c68-4c2e-88ea-7eeb531a8b41,Lubo,CODE_128
|
e4561f48-5c68-4c2e-88ea-7eeb531a8b41,Lubo,CODE_128
|
||||||
e456ceeb-d76a-4684-9e2a-54935e77daa5,Tendenze Calzature,EAN_13
|
e456ceeb-d76a-4684-9e2a-54935e77daa5,Tendenze Calzature,EAN_13
|
||||||
e4dfacd9-9513-4231-b09b-51af53151edd,Дворик,EAN_13
|
e4dfacd9-9513-4231-b09b-51af53151edd,Дворик,EAN_13
|
||||||
|
e4f5270b-5a69-41a3-a39e-e3e7e4460ddd,OSCARwash,QR_CODE
|
||||||
e4f54b47-0238-4fd6-9109-d5ce424981c6,Фламинго,EAN_13
|
e4f54b47-0238-4fd6-9109-d5ce424981c6,Фламинго,EAN_13
|
||||||
e5059f27-dc93-4296-b4d5-1162b692c5ec,Северная Звезда,CODE_128
|
e5059f27-dc93-4296-b4d5-1162b692c5ec,Северная Звезда,CODE_128
|
||||||
e550a9a1-c25b-4658-a9fa-38764c584693,Mon Grand Plaisir,QR_CODE
|
e550a9a1-c25b-4658-a9fa-38764c584693,Mon Grand Plaisir,QR_CODE
|
||||||
e55b3ee0-ac34-480c-8fd3-c63c3a6ae28c,Муниципальная Аптека,EAN_13
|
e55b3ee0-ac34-480c-8fd3-c63c3a6ae28c,Муниципальная Аптека,EAN_13
|
||||||
e55f98ef-9258-4eb7-97fb-7e97d2aacdaa,COOK Kitchen,QR_CODE
|
e55f98ef-9258-4eb7-97fb-7e97d2aacdaa,COOK Kitchen,QR_CODE
|
||||||
|
e5616ded-48e7-45d7-b706-a82ef5ab9667,OROCASH,CODE_39
|
||||||
e569e534-de02-4cde-a15e-ee5f3e70794e,Partyland,CODE_128
|
e569e534-de02-4cde-a15e-ee5f3e70794e,Partyland,CODE_128
|
||||||
e570f1ac-a109-4473-8644-9b6daf701d8d,najlepšia lekáreň,CODE_128
|
e570f1ac-a109-4473-8644-9b6daf701d8d,najlepšia lekáreň,CODE_128
|
||||||
e580263e-726d-4768-a756-1cec4966dbb6,Lower Plenty Hotel,CODE_128
|
e580263e-726d-4768-a756-1cec4966dbb6,Lower Plenty Hotel,CODE_128
|
||||||
@@ -7163,10 +7245,12 @@ e6b0d8c0-2e2b-4d2c-9c3d-4420ced94877,Багира,EAN_13
|
|||||||
e6b4a59b-4d9a-42c6-aae3-5baf468c1999,Evolution,EAN_13
|
e6b4a59b-4d9a-42c6-aae3-5baf468c1999,Evolution,EAN_13
|
||||||
e6c68ae5-12f0-4c8b-b5ca-8f725874c704,Полушка,EAN_13
|
e6c68ae5-12f0-4c8b-b5ca-8f725874c704,Полушка,EAN_13
|
||||||
e6e830c8-16b9-4382-9b84-93dca76ee66c,домаркет,CODE_128
|
e6e830c8-16b9-4382-9b84-93dca76ee66c,домаркет,CODE_128
|
||||||
e6edbb92-d988-4bf3-87f8-e9684b5a3983,UFS Dispensaries,CODE_39
|
e6ece7bc-ac39-45c6-b4f3-c225719c3a0e,Mikado,CODE_128
|
||||||
|
e6edbb92-d988-4bf3-87f8-e9684b5a3983,UFS Healthcare,CODE_39
|
||||||
e6efc01d-98bf-478e-a916-f51178a01690,Erborian,CODE_128
|
e6efc01d-98bf-478e-a916-f51178a01690,Erborian,CODE_128
|
||||||
e6f32c21-af1b-4da3-9c8e-36757cccde3b,Sally Beauty,CODE_128
|
e6f32c21-af1b-4da3-9c8e-36757cccde3b,Sally Beauty,CODE_128
|
||||||
e6f9e7a3-2b1f-4ec7-8c99-8c5d16988f56,Iндустрiя краси,EAN_13
|
e6f9e7a3-2b1f-4ec7-8c99-8c5d16988f56,Iндустрiя краси,EAN_13
|
||||||
|
e71a67d2-6898-4a05-91dd-7ae19095129f,FMBrikon,UPC_A
|
||||||
e71b01e0-cdf1-4f6b-bee6-d7e2fc9b3a81,Walder Schuhe,CODE_128
|
e71b01e0-cdf1-4f6b-bee6-d7e2fc9b3a81,Walder Schuhe,CODE_128
|
||||||
e760dd3f-aeb2-42a2-bf38-5866c061c2e9,Cash Piscines,CODE_128
|
e760dd3f-aeb2-42a2-bf38-5866c061c2e9,Cash Piscines,CODE_128
|
||||||
e79c474b-4ee0-4885-a9eb-7349bdc2bfc9,KIA,CODE_39
|
e79c474b-4ee0-4885-a9eb-7349bdc2bfc9,KIA,CODE_39
|
||||||
@@ -7200,6 +7284,7 @@ eaacfd6c-54dd-4bbd-81a2-0394b7b57496,Kmart,EAN_13
|
|||||||
eab09679-f885-46a1-8f96-3f82ea3b9d82,Niké,ITF
|
eab09679-f885-46a1-8f96-3f82ea3b9d82,Niké,ITF
|
||||||
eac387cc-ae67-4874-b420-12dae0150abc,Woss,EAN_13
|
eac387cc-ae67-4874-b420-12dae0150abc,Woss,EAN_13
|
||||||
eacb1c97-e7c2-4ed6-bf64-84db244fbdd5,Медтехника Ортосалон,EAN_13
|
eacb1c97-e7c2-4ed6-bf64-84db244fbdd5,Медтехника Ортосалон,EAN_13
|
||||||
|
eacdf92e-6601-437d-af01-15156a3ee199,Barossa Co-op,QR_CODE
|
||||||
eb01f161-6d42-4ae9-b381-2ca0be34cd6f,PiùMe,CODE_128
|
eb01f161-6d42-4ae9-b381-2ca0be34cd6f,PiùMe,CODE_128
|
||||||
eb2cfbfc-1d25-4ff7-9eb6-743a74c302c4,Клеопатра,EAN_13
|
eb2cfbfc-1d25-4ff7-9eb6-743a74c302c4,Клеопатра,EAN_13
|
||||||
eb32c9d7-80b8-4147-942f-3b94ad7dd8fd,Brico Pro,EAN_13
|
eb32c9d7-80b8-4147-942f-3b94ad7dd8fd,Brico Pro,EAN_13
|
||||||
@@ -7253,6 +7338,7 @@ ef56f2fe-b4b0-4639-a0dc-db4c6bd01d06,7я,QR_CODE
|
|||||||
ef8b1a62-353b-44e3-bfba-b1331b6509ab,Evoluphar,CODE_128
|
ef8b1a62-353b-44e3-bfba-b1331b6509ab,Evoluphar,CODE_128
|
||||||
ef8f92d7-a5a1-441e-8e91-133b64da57e5,Anabel Arto,CODE_128
|
ef8f92d7-a5a1-441e-8e91-133b64da57e5,Anabel Arto,CODE_128
|
||||||
efdfda06-b4ad-4bd6-ad00-41d6ab9aeaf8,Profi Center,CODE_128
|
efdfda06-b4ad-4bd6-ad00-41d6ab9aeaf8,Profi Center,CODE_128
|
||||||
|
effbec31-0ed6-4eb3-969b-17d99d340d78, Sedici Piadina,CODE_128
|
||||||
f01c0047-5952-4805-a48b-4d455d833777,ХозСити,EAN_13
|
f01c0047-5952-4805-a48b-4d455d833777,ХозСити,EAN_13
|
||||||
f032c0d2-9f71-47fa-9574-8970a917b63b,Brianza Biblioteche,EAN_13
|
f032c0d2-9f71-47fa-9574-8970a917b63b,Brianza Biblioteche,EAN_13
|
||||||
f0637a9d-47a8-44a0-8342-c409b6c55b6b,Baby,EAN_13
|
f0637a9d-47a8-44a0-8342-c409b6c55b6b,Baby,EAN_13
|
||||||
@@ -7269,6 +7355,7 @@ f1843eba-2bcd-49dc-be2c-1444ff5cfd91,EU COVID-19 Očkovací Průkaz,QR_CODE
|
|||||||
f1df75b9-1d7a-4cba-9e9d-f4411f4ea48b,Индейкин Дом,EAN_13
|
f1df75b9-1d7a-4cba-9e9d-f4411f4ea48b,Индейкин Дом,EAN_13
|
||||||
f1e508d1-b901-45ba-9ace-b98e96c8fd38,Dalbe,EAN_13
|
f1e508d1-b901-45ba-9ace-b98e96c8fd38,Dalbe,EAN_13
|
||||||
f1f1c15f-8a75-4a18-9b01-251778c8fb45,Optika Anda,CODE_128
|
f1f1c15f-8a75-4a18-9b01-251778c8fb45,Optika Anda,CODE_128
|
||||||
|
f1fe28ce-0c9a-4b64-a455-c9f14c3fa2be,PME Legend,CODE_128
|
||||||
f2153289-2b50-463f-91d4-37ceb62f304b,Колесо2,CODE_39
|
f2153289-2b50-463f-91d4-37ceb62f304b,Колесо2,CODE_39
|
||||||
f21a2eea-3a15-4765-8ea6-3f1ec10fdd87,EU COVID-19 Vaccinationsattest - Anden vaccination Skudt,QR_CODE
|
f21a2eea-3a15-4765-8ea6-3f1ec10fdd87,EU COVID-19 Vaccinationsattest - Anden vaccination Skudt,QR_CODE
|
||||||
f2292778-e0fe-4925-b939-b4716342fa44,Tread & Miller,CODE_128
|
f2292778-e0fe-4925-b939-b4716342fa44,Tread & Miller,CODE_128
|
||||||
@@ -7283,8 +7370,10 @@ f2a92584-5ef8-4220-b0ca-7aa48decd2e4,Artex Ieper,EAN_13
|
|||||||
f2b9fa76-c78f-4d2c-821f-70678bc8d4d5,Parfümerie Becker,EAN_8
|
f2b9fa76-c78f-4d2c-821f-70678bc8d4d5,Parfümerie Becker,EAN_8
|
||||||
f2c8f722-9c5f-423d-9989-deca7901aa11,Poetry,CODE_128
|
f2c8f722-9c5f-423d-9989-deca7901aa11,Poetry,CODE_128
|
||||||
f2d3f68c-7b77-4464-91d2-3162e74bea48,Neinver,EAN_13
|
f2d3f68c-7b77-4464-91d2-3162e74bea48,Neinver,EAN_13
|
||||||
|
f2dc6f84-01cc-4e13-aec2-2ce88367a27f,Ljekarne Prima Pharme,CODE_128
|
||||||
f3189d64-dd39-468b-872d-3bb70e4d416c,The Watergardens Hotel ,CODE_128
|
f3189d64-dd39-468b-872d-3bb70e4d416c,The Watergardens Hotel ,CODE_128
|
||||||
f3287ab2-0308-42f8-92dc-3147456a4a69,НУЖНО!,EAN_13
|
f3287ab2-0308-42f8-92dc-3147456a4a69,НУЖНО!,EAN_13
|
||||||
|
f359407e-234b-4fbb-af07-f3b293a51bbb,MaRinella,EAN_13
|
||||||
f35a3882-27b2-417d-8093-e87f8f25509a,Первый Семейный,CODE_128
|
f35a3882-27b2-417d-8093-e87f8f25509a,Первый Семейный,CODE_128
|
||||||
f3852d29-47fe-4528-83cd-5ae7b31fdb0e,Kraus,PDF_417
|
f3852d29-47fe-4528-83cd-5ae7b31fdb0e,Kraus,PDF_417
|
||||||
f3e63893-802b-4e40-9480-f3fbfda0a3e4,Аптека живика,EAN_13
|
f3e63893-802b-4e40-9480-f3fbfda0a3e4,Аптека живика,EAN_13
|
||||||
@@ -7297,6 +7386,7 @@ f49e49df-1b1c-4e19-994d-3a56c693d91c,GROSBASKET,EAN_13
|
|||||||
f4aefdf7-e66f-4980-a0ee-7e6f1afcc8df,Color Line,EAN_13
|
f4aefdf7-e66f-4980-a0ee-7e6f1afcc8df,Color Line,EAN_13
|
||||||
f4b16522-478d-4c84-bfa5-e0825ebf4917,bonVito,PDF_417
|
f4b16522-478d-4c84-bfa5-e0825ebf4917,bonVito,PDF_417
|
||||||
f4d0cac3-70a0-43dc-a204-fe5fd9ab428f,KüstenCard Flexi,CODE_128
|
f4d0cac3-70a0-43dc-a204-fe5fd9ab428f,KüstenCard Flexi,CODE_128
|
||||||
|
f4e09fa3-b712-4be5-915b-002082002246,Club VW Suisse,QR_CODE
|
||||||
f5002bd9-8e95-4c11-8a7c-e3d2fae42fe3,BCAA,CODE_128
|
f5002bd9-8e95-4c11-8a7c-e3d2fae42fe3,BCAA,CODE_128
|
||||||
f5356dd8-8762-4f36-8c50-f7383eccb840,Twój Market,EAN_13
|
f5356dd8-8762-4f36-8c50-f7383eccb840,Twój Market,EAN_13
|
||||||
f546e937-86b4-40eb-98cb-9a348d5dccec,МаксиФлора,EAN_13
|
f546e937-86b4-40eb-98cb-9a348d5dccec,МаксиФлора,EAN_13
|
||||||
@@ -7332,6 +7422,7 @@ f8f0bd64-d1ae-4560-9c22-0eed805f2016,Дивный Колибри,EAN_8
|
|||||||
f8fa2370-261e-4e19-ba9c-46cd33ead64d,Agri Sud Est,EAN_13
|
f8fa2370-261e-4e19-ba9c-46cd33ead64d,Agri Sud Est,EAN_13
|
||||||
f90691bf-2879-4424-b2d5-5c09ee9ff700,Кроха,CODE_128
|
f90691bf-2879-4424-b2d5-5c09ee9ff700,Кроха,CODE_128
|
||||||
f915ed01-85f9-4a61-921b-0d33eaf6fd23,ЗооОптТорг.Рф,EAN_13
|
f915ed01-85f9-4a61-921b-0d33eaf6fd23,ЗооОптТорг.Рф,EAN_13
|
||||||
|
f9223231-26b6-4f86-9d2e-5756488c2e74,Jack & Jones,QR_CODE
|
||||||
f93e7a30-4351-47e5-b8b2-3a9546ad9bb8,BOTICINAL POWERSANTÉ,EAN_13
|
f93e7a30-4351-47e5-b8b2-3a9546ad9bb8,BOTICINAL POWERSANTÉ,EAN_13
|
||||||
f940a1b8-c04b-4541-b307-7fdc1fa8eb91,Veggie Grill,CODE_128
|
f940a1b8-c04b-4541-b307-7fdc1fa8eb91,Veggie Grill,CODE_128
|
||||||
f9447f67-140e-402d-9a27-e7c11cefebda,Eleganza,CODE_128
|
f9447f67-140e-402d-9a27-e7c11cefebda,Eleganza,CODE_128
|
||||||
@@ -7350,6 +7441,7 @@ fa009005-250b-4994-a6ad-8043b28634fe,No One,CODE_128
|
|||||||
fa11b2c7-a768-4d4b-b03d-c845df6cb341,Terra Viva,CODE_128
|
fa11b2c7-a768-4d4b-b03d-c845df6cb341,Terra Viva,CODE_128
|
||||||
fa1670c0-1713-44f0-b57d-902b278ba741,нива,EAN_13
|
fa1670c0-1713-44f0-b57d-902b278ba741,нива,EAN_13
|
||||||
fa24b789-4774-41e1-8a52-216efc9de8ba,foodmaster,QR_CODE
|
fa24b789-4774-41e1-8a52-216efc9de8ba,foodmaster,QR_CODE
|
||||||
|
fa3bdecd-2216-4d2b-b39d-fb14681f62fc,Fusion Gyms,CODE_128
|
||||||
fa5593eb-2f35-4a7f-8c69-1c4a726759be,Форум,EAN_13
|
fa5593eb-2f35-4a7f-8c69-1c4a726759be,Форум,EAN_13
|
||||||
fa7407ee-0ddd-4727-bfc7-05c206c159d0,Toto,EAN_13
|
fa7407ee-0ddd-4727-bfc7-05c206c159d0,Toto,EAN_13
|
||||||
fa7f3968-0cba-4adb-b1bb-fb2083b98b2f,Der Bäcker Eifler,QR_CODE
|
fa7f3968-0cba-4adb-b1bb-fb2083b98b2f,Der Bäcker Eifler,QR_CODE
|
||||||
@@ -7360,6 +7452,7 @@ fac3cc98-d990-4106-b17a-e8b5afe1b843,Fidenza Village,CODE_128
|
|||||||
fadd868f-b34b-4604-8a24-c7fbcd8ea573,Big Marlin,EAN_13
|
fadd868f-b34b-4604-8a24-c7fbcd8ea573,Big Marlin,EAN_13
|
||||||
fae896a0-9c57-4ff8-be30-195fbf137a0b,Lotteria degli Scontrini,CODE_128
|
fae896a0-9c57-4ff8-be30-195fbf137a0b,Lotteria degli Scontrini,CODE_128
|
||||||
fafa23c9-5cda-4fb8-aab5-6faebc6386a8,NETTO,CODE_128
|
fafa23c9-5cda-4fb8-aab5-6faebc6386a8,NETTO,CODE_128
|
||||||
|
fb340faf-4fe5-4446-b811-217d615f5514, Abbonamento Musei,QR_CODE
|
||||||
fb507b68-ecf4-4397-969a-23e2427f76f2,Veritas,EAN_13
|
fb507b68-ecf4-4397-969a-23e2427f76f2,Veritas,EAN_13
|
||||||
fb5e84a1-5e9f-4fa5-ad36-c6060927c415,BIT BY BIT,CODE_128
|
fb5e84a1-5e9f-4fa5-ad36-c6060927c415,BIT BY BIT,CODE_128
|
||||||
fb6edc61-a282-4217-9b44-ac2611b5977c,Kierrätyskeskus,CODE_128
|
fb6edc61-a282-4217-9b44-ac2611b5977c,Kierrätyskeskus,CODE_128
|
||||||
@@ -7400,8 +7493,10 @@ fe488a32-17aa-4b93-8e88-b2df166b30b8,BIEMAR BOIS,CODE_39
|
|||||||
fe54303c-8e1c-4c62-8ee6-b9485e333419,Liverpool Library,CODE_128
|
fe54303c-8e1c-4c62-8ee6-b9485e333419,Liverpool Library,CODE_128
|
||||||
fe889ad0-ea52-4069-a051-b5ceb4c4b4e7,Аптека Гермес,EAN_13
|
fe889ad0-ea52-4069-a051-b5ceb4c4b4e7,Аптека Гермес,EAN_13
|
||||||
febc239e-ed07-45ac-905d-b6048a203784,Scarpamondo,EAN_13
|
febc239e-ed07-45ac-905d-b6048a203784,Scarpamondo,EAN_13
|
||||||
|
fed489b7-1d23-4b3f-b20f-52c229575de0,Autowaspark Kuzee,QR_CODE
|
||||||
fee32f93-2fe4-4fa1-ab62-159bdc375668,Покупочка,CODE_128
|
fee32f93-2fe4-4fa1-ab62-159bdc375668,Покупочка,CODE_128
|
||||||
fefcdd70-4aa8-4f78-b9e6-1dc18f9cd731,Button Blue,EAN_13
|
fefcdd70-4aa8-4f78-b9e6-1dc18f9cd731,Button Blue,EAN_13
|
||||||
|
ff50e5dc-1f3a-43a7-a55d-4a7d96b12757,Le Guidon Niortais,CODE_128
|
||||||
ff92fe3e-1b38-409f-9701-ee7665fccb5e,EU COVID-19 Certificado de Vacinação - Primeira injeção,QR_CODE
|
ff92fe3e-1b38-409f-9701-ee7665fccb5e,EU COVID-19 Certificado de Vacinação - Primeira injeção,QR_CODE
|
||||||
ff9fd337-4765-4ad1-90a3-62e4a78dc3ec,Нияма,QR_CODE
|
ff9fd337-4765-4ad1-90a3-62e4a78dc3ec,Нияма,QR_CODE
|
||||||
ffa57152-01bd-48bc-be45-46bac303c450,Мед Сервис,CODE_128
|
ffa57152-01bd-48bc-be45-46bac303c450,Мед Сервис,CODE_128
|
||||||
|
|||||||
|
@@ -50,11 +50,11 @@
|
|||||||
<string name="settings">اعدادات</string>
|
<string name="settings">اعدادات</string>
|
||||||
<string name="settings_light_theme">فاتح</string>
|
<string name="settings_light_theme">فاتح</string>
|
||||||
<string name="settings_dark_theme">داكن</string>
|
<string name="settings_dark_theme">داكن</string>
|
||||||
<string name="settings_card_orientation">اتجاه الباركود</string>
|
<string name="settings_card_orientation">اتجاه الشاشة</string>
|
||||||
<string name="settings_portrait_orientation">الوضع الرأسي</string>
|
<string name="settings_portrait_orientation">الوضع الرأسي</string>
|
||||||
<string name="settings_landscape_orientation">الوضع الأفقي</string>
|
<string name="settings_landscape_orientation">الوضع الأفقي</string>
|
||||||
<string name="settings_theme">مظهر</string>
|
<string name="settings_theme">مظهر</string>
|
||||||
<string name="settings_display_barcode_max_brightness">عرض مشرق علي الباركود</string>
|
<string name="settings_display_barcode_max_brightness">شاشة ساطعة</string>
|
||||||
<string name="importSuccessful">تم استيراد البيانات</string>
|
<string name="importSuccessful">تم استيراد البيانات</string>
|
||||||
<string name="exportSuccessful">تم تصدير البيانات</string>
|
<string name="exportSuccessful">تم تصدير البيانات</string>
|
||||||
<string name="enter_group_name">أدخل اسم المجموعة</string>
|
<string name="enter_group_name">أدخل اسم المجموعة</string>
|
||||||
@@ -119,7 +119,6 @@
|
|||||||
<string name="settings_blue_theme">أزرق</string>
|
<string name="settings_blue_theme">أزرق</string>
|
||||||
<string name="settings_sky_blue_theme">أزرق سماوي</string>
|
<string name="settings_sky_blue_theme">أزرق سماوي</string>
|
||||||
<string name="settings_green_theme">أخضر</string>
|
<string name="settings_green_theme">أخضر</string>
|
||||||
<string name="settings_grey_theme">رمادي</string>
|
|
||||||
<string name="settings_brown_theme">بني</string>
|
<string name="settings_brown_theme">بني</string>
|
||||||
<string name="app_contributors">أصبح ممكنًا بواسطة: <xliff:g id="app_contributors">%s</xliff:g></string>
|
<string name="app_contributors">أصبح ممكنًا بواسطة: <xliff:g id="app_contributors">%s</xliff:g></string>
|
||||||
<string name="sort">فرز</string>
|
<string name="sort">فرز</string>
|
||||||
@@ -238,9 +237,6 @@
|
|||||||
<string name="importLoyaltyCardKeychainMessage">حدد ملفك <i>LoyaltyCardKeychain.csv</i> التصدير من Loyalty Card Keychain للاستيراد.
|
<string name="importLoyaltyCardKeychainMessage">حدد ملفك <i>LoyaltyCardKeychain.csv</i> التصدير من Loyalty Card Keychain للاستيراد.
|
||||||
\nقم بإنشائه من قائمة الاستيراد / التصدير في Loyalty Card Keychain بالضغط على تصدير هناك أولاً.</string>
|
\nقم بإنشائه من قائمة الاستيراد / التصدير في Loyalty Card Keychain بالضغط على تصدير هناك أولاً.</string>
|
||||||
<string name="importStocard">الاستيراد من Stocard</string>
|
<string name="importStocard">الاستيراد من Stocard</string>
|
||||||
<string name="privacy_policy_popup_text">إشعار سياسة الخصوصية (مطلوب من قبل بعض متاجر التطبيقات):
|
|
||||||
\n
|
|
||||||
\nلا يتم جمع أي بيانات على الإطلاق ، والتي يمكن لأي شخص تأكيدها لأن تطبيقنا هو برنامج حر.</string>
|
|
||||||
<string name="failedGeneratingShareURL">تعذر إنشاء عنوان URL قابل للمشاركة. الرجاء الإبلاغ عن هذا.</string>
|
<string name="failedGeneratingShareURL">تعذر إنشاء عنوان URL قابل للمشاركة. الرجاء الإبلاغ عن هذا.</string>
|
||||||
<string name="help_translate_this_app">ساعد في ترجمة هذا التطبيق</string>
|
<string name="help_translate_this_app">ساعد في ترجمة هذا التطبيق</string>
|
||||||
<string name="on_google_play">على Google Play</string>
|
<string name="on_google_play">على Google Play</string>
|
||||||
@@ -251,23 +247,15 @@
|
|||||||
<string name="barcodeLongPressMessage">يمكن فتح صور فقط في تطبيق معرض الصور</string>
|
<string name="barcodeLongPressMessage">يمكن فتح صور فقط في تطبيق معرض الصور</string>
|
||||||
<string name="failedToOpenUrl">ثبت متصفح ويب أولاً</string>
|
<string name="failedToOpenUrl">ثبت متصفح ويب أولاً</string>
|
||||||
<string name="welcome">مرحبا بك في كاتيما</string>
|
<string name="welcome">مرحبا بك في كاتيما</string>
|
||||||
<string name="updateBalanceTitle">كم أنفقت؟</string>
|
<string name="updateBalanceTitle">كم أنفقت أو استلمت؟</string>
|
||||||
<string name="currentBalanceSentence">الرصيد الحالي: <xliff:g> %s </xliff:g></string>
|
<string name="currentBalanceSentence">الرصيد الحالي: <xliff:g> %s </xliff:g></string>
|
||||||
<plurals name="viewArchivedCardsWithCount">
|
|
||||||
<item quantity="zero">عرض الأرشيف (<xliff:g>%1$d</xliff:g> بطاقة)</item>
|
|
||||||
<item quantity="one">عرض الأرشيف (<xliff:g>%1$d</xliff:g> بطاقة)</item>
|
|
||||||
<item quantity="two">عرض الأرشيف (<xliff:g>%1$d</xliff:g> بطاقتين)</item>
|
|
||||||
<item quantity="few">عرض الأرشيف (<xliff:g>%1$d</xliff:g> بطاقات)</item>
|
|
||||||
<item quantity="many">عرض الأرشيف (<xliff:g>%1$d</xliff:g> بطاقات)</item>
|
|
||||||
<item quantity="other">عرض الأرشيف (<xliff:g>%1$d</xliff:g> بطاقات)</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="importCards">استيراد البطاقات</string>
|
<string name="importCards">استيراد البطاقات</string>
|
||||||
<string name="newBalanceSentence">الرصيد الجديد: <xliff:g>%s</xliff:g></string>
|
<string name="newBalanceSentence">الرصيد الجديد: <xliff:g>%s</xliff:g></string>
|
||||||
<string name="cameraPermissionDeniedTitle">تعذر الوصول إلى الكاميرا</string>
|
<string name="cameraPermissionDeniedTitle">تعذر الوصول إلى الكاميرا</string>
|
||||||
<string name="noCameraPermissionDirectToSystemSetting">لمسح الباركود، ستحتاج Catima إلى الوصول إلى الكاميرا. اضغط هنا لتغيير إعدادات الأذونات.</string>
|
<string name="noCameraPermissionDirectToSystemSetting">لمسح الباركود، ستحتاج Catima إلى الوصول إلى الكاميرا. اضغط هنا لتغيير إعدادات الأذونات.</string>
|
||||||
<string name="updateBalance">تحديث الرصيد</string>
|
<string name="updateBalance">تحديث الرصيد</string>
|
||||||
<string name="updateBalanceHint">أدخل المبلغ</string>
|
<string name="updateBalanceHint">أدخل المبلغ</string>
|
||||||
<string name="storageReadPermissionRequired">الصلاحيه للوصل للتخزين مطلوبة لهذا الاجراء</string>
|
<string name="storageReadPermissionRequired">الصلاحيه للوصل للتخزين مطلوبة لهذا الاجراء…</string>
|
||||||
<string name="validFromDate">عربيه</string>
|
<string name="validFromDate">عربيه</string>
|
||||||
<string name="cameraPermissionRequired">إذن للوصول إلى الكاميرا اللازمة لهذا الإجراء…</string>
|
<string name="cameraPermissionRequired">إذن للوصول إلى الكاميرا اللازمة لهذا الإجراء…</string>
|
||||||
<string name="anyDate">أي تاريخ</string>
|
<string name="anyDate">أي تاريخ</string>
|
||||||
@@ -289,7 +277,7 @@
|
|||||||
<string name="action_display_options">خيارات العرض</string>
|
<string name="action_display_options">خيارات العرض</string>
|
||||||
<string name="settings_oled_dark_summary">يقلل من استخدام البطارية على شاشات OLED</string>
|
<string name="settings_oled_dark_summary">يقلل من استخدام البطارية على شاشات OLED</string>
|
||||||
<string name="icon_header_click_text">اضغط لفترة طويلة لتحرير الصورة المصغرة</string>
|
<string name="icon_header_click_text">اضغط لفترة طويلة لتحرير الصورة المصغرة</string>
|
||||||
<string name="settings_category_title_cards">البطاقات</string>
|
<string name="settings_category_title_cards">البطاقات الظاهرة</string>
|
||||||
<string name="show_note">إظهار الملاحظة</string>
|
<string name="show_note">إظهار الملاحظة</string>
|
||||||
<string name="switchToBackImage">التبديل إلى الصورة الخلفية</string>
|
<string name="switchToBackImage">التبديل إلى الصورة الخلفية</string>
|
||||||
<string name="switchToFrontImage">التبديل إلى الصورة الأمامية</string>
|
<string name="switchToFrontImage">التبديل إلى الصورة الأمامية</string>
|
||||||
@@ -312,4 +300,37 @@
|
|||||||
<string name="field_must_not_be_empty">يجب ألا يكون الحقل فارغا</string>
|
<string name="field_must_not_be_empty">يجب ألا يكون الحقل فارغا</string>
|
||||||
<string name="app_name">كاتيما</string>
|
<string name="app_name">كاتيما</string>
|
||||||
<string name="settings_follow_sensor_orientation">التدوير دائمًا ( تجاهل إعدادات النظام)</string>
|
<string name="settings_follow_sensor_orientation">التدوير دائمًا ( تجاهل إعدادات النظام)</string>
|
||||||
</resources>
|
<string name="add_manually_warning_title">الفحص موصى به</string>
|
||||||
|
<string name="continue_">استمر</string>
|
||||||
|
<string name="spend">انفق</string>
|
||||||
|
<string name="receive">استلم</string>
|
||||||
|
<string name="amountParsingFailed">كمية غير صحيحة</string>
|
||||||
|
<string name="add_manually_warning_message">في بعض المتاجر قيمة الباركود تختلف عن الرقم الموجود على البطاقة. لهذا السبب إدخال الباركود يدوياً لن ينجح دائماً. من المستحسن فحص الباركود بكاميرا بدلا من ذالك. هل انت مُصِر على الاستكمال؟</string>
|
||||||
|
<string name="addFromPdfFile">تحديد ملف PDF</string>
|
||||||
|
<string name="errorReadingFile">لا يمكن قراءة الملف</string>
|
||||||
|
<string name="failedLaunchingFileManager">لم يتم العثور على مدير ملفات مدعوم</string>
|
||||||
|
<string name="multipleBarcodesFoundPleaseChooseOne">اي من الـbarcodes تريد استخدامه؟</string>
|
||||||
|
<string name="pageWithNumber">صفحة <xliff:g>%d</xliff:g></string>
|
||||||
|
<string name="noCameraFoundGuideText">يبدوا أن جهازك لا يمتلك كاميرا. إذا كان يمتلكها، أطفئ الجهاز وحاول مرة اخرى. اذا لم ينجح ذلك، أضغط على زر \"المزيد من الأختيارات\" في الأسفل لإضافة الباركود بطريقة أخرى.</string>
|
||||||
|
<string name="importCancelled">تم الغاء الاستيراد</string>
|
||||||
|
<string name="exportCancelled">تم الغاء الاستخراج</string>
|
||||||
|
<string name="useFrontImage">استخدام صورة أمامية</string>
|
||||||
|
<string name="useBackImage">استخدم صورة خلفية</string>
|
||||||
|
<string name="addFromPkpass">اختر ملف الدفتر(.pkpass)</string>
|
||||||
|
<string name="unsupportedFile">هذا الملف غير مدعوم</string>
|
||||||
|
<string name="generic_error_please_retry">نعتذر، حدث خطأ ما، حاول مرة أخرى...</string>
|
||||||
|
<string name="settings_use_volume_keys_navigation">تبديل البطاقات باستخدام أزرار الصوت</string>
|
||||||
|
<string name="settings_use_volume_keys_navigation_summary">تبديل البطاقات الظاهرة باستخدام أزرار الصوت</string>
|
||||||
|
<string name="settings_category_title_cards_overview">نظرة عامة على البطاقات</string>
|
||||||
|
<string name="settings_column_count_portrait">الأعمدة في الوضع الرأسي</string>
|
||||||
|
<string name="settings_column_count_landscape">الأعمدة في الوضع الأفقي</string>
|
||||||
|
<string name="settings_automatic_column_count">تلقائي</string>
|
||||||
|
<string name="settings_column_count_1">١</string>
|
||||||
|
<string name="settings_column_count_2">٢</string>
|
||||||
|
<string name="settings_column_count_3">٣</string>
|
||||||
|
<string name="settings_column_count_4">٤</string>
|
||||||
|
<string name="settings_column_count_5">٥</string>
|
||||||
|
<string name="settings_column_count_6">٦</string>
|
||||||
|
<string name="settings_column_count_7">٧</string>
|
||||||
|
<string name="sort_by_valid_from">صالح من</string>
|
||||||
|
</resources>
|
||||||
|
|||||||
@@ -24,10 +24,6 @@
|
|||||||
<string name="save">Guardar</string>
|
<string name="save">Guardar</string>
|
||||||
<string name="edit">Editar</string>
|
<string name="edit">Editar</string>
|
||||||
<string name="delete">Desaniciar</string>
|
<string name="delete">Desaniciar</string>
|
||||||
<plurals name="deleteCardsTitle">
|
|
||||||
<item quantity="one"></item>
|
|
||||||
<item quantity="other"></item>
|
|
||||||
</plurals>
|
|
||||||
<string name="unstar">Quitar de Favoritos</string>
|
<string name="unstar">Quitar de Favoritos</string>
|
||||||
<string name="cancel">Encaboxar</string>
|
<string name="cancel">Encaboxar</string>
|
||||||
<string name="importFailed">Nun se pudo facer la importación</string>
|
<string name="importFailed">Nun se pudo facer la importación</string>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<string name="removeImage">Премахване на изображение</string>
|
<string name="removeImage">Премахване на изображение</string>
|
||||||
<string name="takePhoto">Снимане</string>
|
<string name="takePhoto">Снимане</string>
|
||||||
<string name="intent_import_card_from_url_share_multiple_text">Искам да споделя тези карти с вас</string>
|
<string name="intent_import_card_from_url_share_multiple_text">Искам да споделя тези карти с вас</string>
|
||||||
<string name="wrongValueForBarcodeType">Стойноста е неприемлива за избрания щрихкод</string>
|
<string name="wrongValueForBarcodeType">Стойността е неприемлива за избрания вид щрихкод</string>
|
||||||
<string name="setBarcodeId">Задаване на стойност</string>
|
<string name="setBarcodeId">Задаване на стойност</string>
|
||||||
<string name="sameAsCardId">Като номера</string>
|
<string name="sameAsCardId">Като номера</string>
|
||||||
<string name="barcodeId">Стойност на щрихкода</string>
|
<string name="barcodeId">Стойност на щрихкода</string>
|
||||||
@@ -51,9 +51,6 @@
|
|||||||
<string name="importFidme">Внасяне от FidMe</string>
|
<string name="importFidme">Внасяне от FidMe</string>
|
||||||
<string name="exportOptionExplanation">Данните ще бъдат запазени на място по ваш избор.</string>
|
<string name="exportOptionExplanation">Данните ще бъдат запазени на място по ваш избор.</string>
|
||||||
<string name="accept">Приемане</string>
|
<string name="accept">Приемане</string>
|
||||||
<string name="privacy_policy_popup_text">Политика за личните данни (необходима от някои магазини за приложения):
|
|
||||||
\n
|
|
||||||
\nНЕ СЕ СЪБИРАТ ИЗОБЩО НИКАКВИ ДАННИ, което може да бъде потвърдено, защото приложението е със свободен код.</string>
|
|
||||||
<string name="privacy_policy">Политика за личните данни</string>
|
<string name="privacy_policy">Политика за личните данни</string>
|
||||||
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
|
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
|
||||||
<string name="turn_flashlight_off">Изключва светкавицата</string>
|
<string name="turn_flashlight_off">Изключва светкавицата</string>
|
||||||
@@ -78,7 +75,7 @@
|
|||||||
<string name="groups">Списъци</string>
|
<string name="groups">Списъци</string>
|
||||||
<string name="enter_group_name">Въведете име на списъка</string>
|
<string name="enter_group_name">Въведете име на списъка</string>
|
||||||
<string name="intent_import_card_from_url_share_text">Искам да споделя тази карта с вас</string>
|
<string name="intent_import_card_from_url_share_text">Искам да споделя тази карта с вас</string>
|
||||||
<string name="settings_display_barcode_max_brightness">Максимална яркост при видим щрихкод</string>
|
<string name="settings_display_barcode_max_brightness">Максимална яркост на екрана</string>
|
||||||
<string name="settings_keep_screen_on">Без изключване на екрана</string>
|
<string name="settings_keep_screen_on">Без изключване на екрана</string>
|
||||||
<string name="settings_disable_lockscreen_while_viewing_card">Без заключване на екрана</string>
|
<string name="settings_disable_lockscreen_while_viewing_card">Без заключване на екрана</string>
|
||||||
<string name="settings_dark_theme">Тъмна</string>
|
<string name="settings_dark_theme">Тъмна</string>
|
||||||
@@ -157,7 +154,6 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="app_contributors">Осъществено от: <xliff:g id="app_contributors">%s</xliff:g></string>
|
<string name="app_contributors">Осъществено от: <xliff:g id="app_contributors">%s</xliff:g></string>
|
||||||
<string name="settings_brown_theme">Кафяво</string>
|
<string name="settings_brown_theme">Кафяво</string>
|
||||||
<string name="settings_grey_theme">Сиво</string>
|
|
||||||
<string name="settings_green_theme">Зелено</string>
|
<string name="settings_green_theme">Зелено</string>
|
||||||
<string name="settings_sky_blue_theme">Небесносиньо</string>
|
<string name="settings_sky_blue_theme">Небесносиньо</string>
|
||||||
<string name="settings_blue_theme">Синьо</string>
|
<string name="settings_blue_theme">Синьо</string>
|
||||||
@@ -207,7 +203,7 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="settings_oled_dark">Черен фон за тъмната тема</string>
|
<string name="settings_oled_dark">Черен фон за тъмната тема</string>
|
||||||
<string name="include_if_asking_support">Ако искате да потърсите поддръжка, включете следната информация:</string>
|
<string name="include_if_asking_support">Ако искате да потърсите поддръжка, включете следната информация:</string>
|
||||||
<string name="settings_card_orientation">Завъртане на щрихкода</string>
|
<string name="settings_card_orientation">Завъртане на екрана</string>
|
||||||
<string name="settings_follow_system_orientation">Според системата</string>
|
<string name="settings_follow_system_orientation">Според системата</string>
|
||||||
<string name="settings_portrait_orientation">Портрет</string>
|
<string name="settings_portrait_orientation">Портрет</string>
|
||||||
<string name="settings_landscape_orientation">Пейзаж</string>
|
<string name="settings_landscape_orientation">Пейзаж</string>
|
||||||
@@ -226,14 +222,10 @@
|
|||||||
<string name="nextCard">Следваща</string>
|
<string name="nextCard">Следваща</string>
|
||||||
<string name="failedToOpenUrl">Първо инсталирайте уеб браузър</string>
|
<string name="failedToOpenUrl">Първо инсталирайте уеб браузър</string>
|
||||||
<string name="welcome">Добре дошли при Катима</string>
|
<string name="welcome">Добре дошли при Катима</string>
|
||||||
<plurals name="viewArchivedCardsWithCount">
|
|
||||||
<item quantity="one">Преглед на архива (<xliff:g>%1$d</xliff:g> карта)</item>
|
|
||||||
<item quantity="other">Преглед на архива (<xliff:g>%1$d</xliff:g> карти)</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="barcodeLongPressMessage">В приложението галерия могат да бъдат отваряни само изображения</string>
|
<string name="barcodeLongPressMessage">В приложението галерия могат да бъдат отваряни само изображения</string>
|
||||||
<string name="failedToRetrieveImageFile">Не е възможно извличане на изображение</string>
|
<string name="failedToRetrieveImageFile">Не е възможно извличане на изображение</string>
|
||||||
<string name="noCameraPermissionDirectToSystemSetting">За да сканирате щрихкодове с Catima е необходим достъп до камерата. За да промените разрешението докоснете тук.</string>
|
<string name="noCameraPermissionDirectToSystemSetting">За да сканирате щрихкодове с Catima е необходим достъп до камерата. За да промените разрешението докоснете тук.</string>
|
||||||
<string name="updateBalanceTitle">Каква е промяната\?</string>
|
<string name="updateBalanceTitle">Колко е похарчено или получено?</string>
|
||||||
<string name="updateBalanceHint">Въведете стойност</string>
|
<string name="updateBalanceHint">Въведете стойност</string>
|
||||||
<string name="newBalanceSentence">Нов баланс: <xliff:g>%s</xliff:g></string>
|
<string name="newBalanceSentence">Нов баланс: <xliff:g>%s</xliff:g></string>
|
||||||
<string name="cameraPermissionDeniedTitle">Камерата е недостъпна</string>
|
<string name="cameraPermissionDeniedTitle">Камерата е недостъпна</string>
|
||||||
@@ -266,7 +258,7 @@
|
|||||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Без заключване на екрана при преглед на карта</string>
|
<string name="settings_disable_lockscreen_while_viewing_card_summary">Без заключване на екрана при преглед на карта</string>
|
||||||
<string name="settings_allow_content_provider_read_summary">Приложенията ще искат разрешение, за да получат достъп</string>
|
<string name="settings_allow_content_provider_read_summary">Приложенията ще искат разрешение, за да получат достъп</string>
|
||||||
<string name="settings_oled_dark_summary">Намалява разхода на батерия от OLED дисплеи</string>
|
<string name="settings_oled_dark_summary">Намалява разхода на батерия от OLED дисплеи</string>
|
||||||
<string name="settings_category_title_cards">Карти</string>
|
<string name="settings_category_title_cards">Преглед на карта</string>
|
||||||
<string name="settings_category_title_general">Общи</string>
|
<string name="settings_category_title_general">Общи</string>
|
||||||
<string name="settings_category_title_privacy">Поверителност</string>
|
<string name="settings_category_title_privacy">Поверителност</string>
|
||||||
<string name="settings_keep_screen_on_summary">Спира автоматичното заключване на екрана при преглед на карти</string>
|
<string name="settings_keep_screen_on_summary">Спира автоматичното заключване на екрана при преглед на карти</string>
|
||||||
@@ -284,4 +276,37 @@
|
|||||||
<string name="field_must_not_be_empty">Полето не трябва да е празно</string>
|
<string name="field_must_not_be_empty">Полето не трябва да е празно</string>
|
||||||
<string name="app_name">Catima</string>
|
<string name="app_name">Catima</string>
|
||||||
<string name="settings_follow_sensor_orientation">Винаги да се завърта (пренебрегва системната настройка)</string>
|
<string name="settings_follow_sensor_orientation">Винаги да се завърта (пренебрегва системната настройка)</string>
|
||||||
</resources>
|
<string name="continue_">Продължаване</string>
|
||||||
|
<string name="add_manually_warning_title">Препоръчително е да сканирате</string>
|
||||||
|
<string name="add_manually_warning_message">Стойностите от щрихкода и отбелязаните на картата числа в някои случаи се различават. По тази причина е при ръчно въвеждане картата може да не работи. Силно препоръчително е да сканирате щрихкода с камерата. Желаете ли да продължите въпреки това?</string>
|
||||||
|
<string name="amountParsingFailed">Неприемлива сума</string>
|
||||||
|
<string name="spend">Похарчено</string>
|
||||||
|
<string name="receive">Получено</string>
|
||||||
|
<string name="pageWithNumber">Страница <xliff:g>%d</xliff:g></string>
|
||||||
|
<string name="addFromPdfFile">Изберете PDF</string>
|
||||||
|
<string name="errorReadingFile">Файлът не може да бъде прочетен</string>
|
||||||
|
<string name="failedLaunchingFileManager">Не е намерено приложение за управление на файлове</string>
|
||||||
|
<string name="multipleBarcodesFoundPleaseChooseOne">Кой от намерените щрихкодове желаете да бъде използван?</string>
|
||||||
|
<string name="noCameraFoundGuideText">Изглежда, устройството няма камера. Ако има опитайте да рестартирате. В противен случай използвайте бутона „Още“, за да добавите шрихкод по друг начин.</string>
|
||||||
|
<string name="importCancelled">Внасянето е спряно</string>
|
||||||
|
<string name="exportCancelled">Изнасянето е спряно</string>
|
||||||
|
<string name="useFrontImage">Използване на предната страна</string>
|
||||||
|
<string name="useBackImage">Използване на задната страна</string>
|
||||||
|
<string name="settings_use_volume_keys_navigation">Сменяне на картите с бутоните за звука</string>
|
||||||
|
<string name="settings_use_volume_keys_navigation_summary">Бутоните за силата на звука сменят видимата карта</string>
|
||||||
|
<string name="settings_automatic_column_count">Автоматично</string>
|
||||||
|
<string name="settings_column_count_2">2</string>
|
||||||
|
<string name="settings_column_count_4">4</string>
|
||||||
|
<string name="settings_column_count_5">5</string>
|
||||||
|
<string name="settings_column_count_1">1</string>
|
||||||
|
<string name="settings_column_count_3">3</string>
|
||||||
|
<string name="settings_column_count_6">6</string>
|
||||||
|
<string name="settings_column_count_7">7</string>
|
||||||
|
<string name="settings_column_count_landscape">Колони в пейзажен изглед</string>
|
||||||
|
<string name="settings_column_count_portrait">Колони в портретен изглед</string>
|
||||||
|
<string name="settings_category_title_cards_overview">Списък с карти</string>
|
||||||
|
<string name="generic_error_please_retry">Съжаляваме, нещо се обърка, опитайте отново…</string>
|
||||||
|
<string name="addFromPkpass">Изберете файл на Passbook (.pkpass)</string>
|
||||||
|
<string name="unsupportedFile">Този вид файлове не се поддържат</string>
|
||||||
|
<string name="sort_by_valid_from">Начало валидност</string>
|
||||||
|
</resources>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user