Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22193b1872 | ||
|
|
306e7b5201 | ||
|
|
0c8104af96 | ||
|
|
3b06f7a105 | ||
|
|
0869623c1d | ||
|
|
bfa79a6f68 | ||
|
|
cda842485f | ||
|
|
db35306a26 | ||
|
|
2363861a62 | ||
|
|
2960585250 | ||
|
|
ff830ee50d | ||
|
|
3c917da56a | ||
|
|
6ea1d9ec73 | ||
|
|
5ebd7cbb63 | ||
|
|
0d91444ee2 | ||
|
|
9459c78e7c | ||
|
|
b326a7148d | ||
|
|
5ae850dbe8 | ||
|
|
2ef9c9c68e | ||
|
|
613bd000d2 | ||
|
|
f7ea5bcb7b | ||
|
|
b8b5b8ae9b | ||
|
|
e6085a64be | ||
|
|
e77acf9b1e | ||
|
|
dc4299d315 | ||
|
|
1c8d2ba19e | ||
|
|
d46455c021 | ||
|
|
abd4c24138 | ||
|
|
1e922ae074 | ||
|
|
335059a681 | ||
|
|
a530991119 | ||
|
|
45130b5f85 | ||
|
|
70fd411640 | ||
|
|
4d47cd4a4a | ||
|
|
bb694ca9cb | ||
|
|
7ede8424d0 | ||
|
|
1167cf51b5 | ||
|
|
13d2c81c5c | ||
|
|
102a34a3f3 | ||
|
|
0c490f2bdb | ||
|
|
8f69f9cef9 | ||
|
|
00e65a39de | ||
|
|
8698c6099c | ||
|
|
da3fbdb401 | ||
|
|
b28052c46c | ||
|
|
b438a7f03d | ||
|
|
c144035d95 | ||
|
|
7658c3980a | ||
|
|
c11297a733 | ||
|
|
c89e60adf6 | ||
|
|
fa33f61183 | ||
|
|
f3e57d816c | ||
|
|
bf2e1eface | ||
|
|
a69fff9b53 | ||
|
|
9fc0038bd5 | ||
|
|
ffd8dd5633 | ||
|
|
c3947b0f6a | ||
|
|
84b17b17fe | ||
|
|
3956a1123e | ||
|
|
f04185f6fd | ||
|
|
10d188ee40 | ||
|
|
63e2d3740f | ||
|
|
672173437f | ||
|
|
2c851158f5 | ||
|
|
367733676a | ||
|
|
538cd2078b | ||
|
|
731833d9be | ||
|
|
7ebd71283f | ||
|
|
5eb1942ad2 | ||
|
|
999222f3a4 | ||
|
|
e82d726898 | ||
|
|
0f48c75512 | ||
|
|
183d3ea666 | ||
|
|
4e4e9123f2 | ||
|
|
c09566d38a | ||
|
|
750ac6f2ec | ||
|
|
1ed78fa680 | ||
|
|
48a26cf1f7 | ||
|
|
bc6a63a1f0 | ||
|
|
45f360904f | ||
|
|
bdf39a1dd7 | ||
|
|
abff198d01 | ||
|
|
6721ecf113 | ||
|
|
3899c71317 | ||
|
|
b8e4ec9c9f | ||
|
|
92cb120dd0 | ||
|
|
7a8f9c38eb | ||
|
|
dc7498e505 | ||
|
|
02e925f77a | ||
|
|
7dc2a479b6 | ||
|
|
1913d629e7 | ||
|
|
59a750f41c | ||
|
|
46dfafe0b2 | ||
|
|
1faab05032 | ||
|
|
c895f0db56 | ||
|
|
c622df96f0 | ||
|
|
30ac13ec73 | ||
|
|
efbe809982 | ||
|
|
92df5dd36a | ||
|
|
ebdf13678a | ||
|
|
0a0d46033c | ||
|
|
7f72565e24 | ||
|
|
cfcf1e2fcb | ||
|
|
faf2f7a38c | ||
|
|
ace7e5abdd | ||
|
|
f50e547980 | ||
|
|
c05a303568 | ||
|
|
4fcd41d3cc | ||
|
|
3e6c4c1b13 | ||
|
|
55d356ea40 | ||
|
|
a752421efe | ||
|
|
aa4b41612f | ||
|
|
f576228c2d | ||
|
|
c93add3b83 | ||
|
|
117446cebb | ||
|
|
4bee5ec6c8 | ||
|
|
92776c9e8d | ||
|
|
eea3fb5c54 | ||
|
|
dbd0727af2 | ||
|
|
a4cf49754a | ||
|
|
587c5991f1 | ||
|
|
10234ffcef | ||
|
|
2a2710e391 | ||
|
|
f1b5423d94 | ||
|
|
1cb916d16a | ||
|
|
6396120fb0 | ||
|
|
2fe5157519 | ||
|
|
4ce480defb | ||
|
|
3ea5d16f19 | ||
|
|
31fc58576c | ||
|
|
9573799eba | ||
|
|
df94e94b31 | ||
|
|
98b561b186 | ||
|
|
8ee469343c | ||
|
|
e54d129577 | ||
|
|
a7a3237f4f | ||
|
|
4b17c3963f | ||
|
|
93925a03b9 | ||
|
|
a74707ee82 | ||
|
|
909034d4f3 | ||
|
|
c261d33980 | ||
|
|
e9654c9b04 | ||
|
|
4742a290c8 | ||
|
|
bbae3351a5 | ||
|
|
db149cd3aa | ||
|
|
1a94748958 | ||
|
|
a20ad88532 | ||
|
|
93b4ba1167 | ||
|
|
693d6d6663 | ||
|
|
67bb1a2627 | ||
|
|
d20f91018d | ||
|
|
9414627772 | ||
|
|
6134fb26c6 | ||
|
|
6445df4d2f | ||
|
|
1069803681 | ||
|
|
60f9d19c6c | ||
|
|
b2d001d238 | ||
|
|
ea26cdbb9f | ||
|
|
f3154081f4 | ||
|
|
5388bbfc16 | ||
|
|
48284acf06 | ||
|
|
852830c894 | ||
|
|
97b2c8f080 | ||
|
|
888869d5fc | ||
|
|
9452c65438 | ||
|
|
08db580076 | ||
|
|
649135e6d4 | ||
|
|
fb255fd46a | ||
|
|
ba01e9f0fe | ||
|
|
c6b6a4012e | ||
|
|
964c1a82db | ||
|
|
6eb2a8db45 | ||
|
|
abf5466883 | ||
|
|
c40fcdee9e | ||
|
|
ca66205493 | ||
|
|
8bdea4a3cd | ||
|
|
d11797be41 | ||
|
|
332a5df506 | ||
|
|
024125cf52 | ||
|
|
5b747d6fea | ||
|
|
f4b2597d95 | ||
|
|
4a24b98868 | ||
|
|
932f82a08b | ||
|
|
c20709b377 | ||
|
|
f1fd813ddc | ||
|
|
dfb8089db3 | ||
|
|
cf6cd74e8f | ||
|
|
5b93bd79a9 | ||
|
|
5831adbcc6 | ||
|
|
133b6df382 | ||
|
|
0e9abac8a2 | ||
|
|
b6b172ebfd | ||
|
|
561f508e02 | ||
|
|
b36a29a5c7 | ||
|
|
938f17b559 | ||
|
|
9feec7f6e3 | ||
|
|
b1f0e7bbfd | ||
|
|
24335cb2fc | ||
|
|
4d41ae9448 | ||
|
|
d380560ff0 | ||
|
|
aa649ca9a9 | ||
|
|
754f9c51ea | ||
|
|
c6a2f79946 | ||
|
|
3e6a735217 | ||
|
|
d0d9d70445 | ||
|
|
9db715b916 | ||
|
|
7c88c004b6 | ||
|
|
6215d77b72 | ||
|
|
c947f53d2c | ||
|
|
d9029284c3 | ||
|
|
48b099e182 | ||
|
|
cbac258a6a | ||
|
|
256a39643b | ||
|
|
9dc21315b8 | ||
|
|
d27ada63b1 | ||
|
|
97a2874218 | ||
|
|
1c3471f7b9 | ||
|
|
b8b677e7a8 | ||
|
|
986873e945 | ||
|
|
7e90a439a6 | ||
|
|
ba2dab02a6 | ||
|
|
5aad361ebd | ||
|
|
418443300c | ||
|
|
3958a48137 | ||
|
|
c829b2d9bf | ||
|
|
12d78786f0 | ||
|
|
87655c9e28 | ||
|
|
eb54d7c250 | ||
|
|
78a7b93c21 | ||
|
|
4da8378aef | ||
|
|
2f6687ec90 | ||
|
|
38b2584153 | ||
|
|
a25d36da29 | ||
|
|
6f7c3ec39c | ||
|
|
d9e94d52f5 | ||
|
|
035f651b9f | ||
|
|
98d2c69640 | ||
|
|
0dfeb2dcc8 | ||
|
|
645e7780f3 | ||
|
|
d635e31afc | ||
|
|
91d0882ad8 | ||
|
|
4ab4ac7668 | ||
|
|
48115b3f2d | ||
|
|
f55559d6d6 | ||
|
|
0bf1afd7af | ||
|
|
1ff4f47102 | ||
|
|
70dbd7eac1 | ||
|
|
ff7ebe0629 | ||
|
|
0d847607a2 | ||
|
|
dd53862d37 | ||
|
|
9df5366fc6 | ||
|
|
69f888fe29 | ||
|
|
bdc08ef95c | ||
|
|
7b098bb6dd | ||
|
|
43085d0c13 | ||
|
|
89885f5e9d | ||
|
|
3d48bdeee6 | ||
|
|
e331f2e293 | ||
|
|
0a605d518b | ||
|
|
72f02bd16d | ||
|
|
ff9a6884a9 | ||
|
|
9fd6c89c5b | ||
|
|
73d2644e96 | ||
|
|
e8de07bdb8 | ||
|
|
3003acdc81 | ||
|
|
350241f517 | ||
|
|
ed6289341a | ||
|
|
0811eb75df | ||
|
|
619408e272 | ||
|
|
c43ff9dc95 | ||
|
|
7fd461ef61 | ||
|
|
3b17a24f29 | ||
|
|
95358bf16b | ||
|
|
d8de262111 | ||
|
|
5f78ee7f95 | ||
|
|
c3cbdf5a24 | ||
|
|
ac1f7d158a | ||
|
|
bdf8823855 | ||
|
|
17693ec3f2 | ||
|
|
1afea8f39c | ||
|
|
f0068144dc | ||
|
|
90a4de1343 | ||
|
|
e83a8ef7e7 | ||
|
|
2526cb65ba | ||
|
|
4f419b86e2 | ||
|
|
6c1baa9ec1 | ||
|
|
b499d540dc | ||
|
|
522bd5d4b0 | ||
|
|
d3d6d1a565 | ||
|
|
e6b639040b | ||
|
|
64259502db | ||
|
|
a5a9c1330b | ||
|
|
c9fb6ad2ac | ||
|
|
3ecc752c6c | ||
|
|
db0f97542f | ||
|
|
c9d372223e | ||
|
|
d5be79198c | ||
|
|
b75a5b56d2 | ||
|
|
a1da000157 | ||
|
|
965d1fd2b7 | ||
|
|
bc34cd9b5e | ||
|
|
d3d2368be0 | ||
|
|
afb78ac8b8 | ||
|
|
b675ad785d | ||
|
|
649bd85fd5 | ||
|
|
f302fb7da5 | ||
|
|
a9ad35a379 | ||
|
|
ed412c221a | ||
|
|
e46da24e03 | ||
|
|
c9ef64b81f | ||
|
|
18a6235844 | ||
|
|
357566805b | ||
|
|
f89a2b8447 | ||
|
|
2528b9ff2a | ||
|
|
3a8b3689fb | ||
|
|
74d2a17867 | ||
|
|
e77c90debe | ||
|
|
8364e86980 | ||
|
|
fc5206456e | ||
|
|
216e3afec7 | ||
|
|
70b86277bd | ||
|
|
488163c945 | ||
|
|
5c8916d32e | ||
|
|
9f1fa7601b | ||
|
|
a3f59ed7a2 | ||
|
|
20ad4003b7 | ||
|
|
c059dd916e | ||
|
|
61b582f676 | ||
|
|
4bf6ae897f | ||
|
|
74b15ce766 | ||
|
|
cea4a5431e | ||
|
|
12e0105b5a | ||
|
|
17be43d0d2 | ||
|
|
dd071c912a | ||
|
|
32cdacb4be | ||
|
|
c1cbebd782 | ||
|
|
2a334445dc | ||
|
|
34bba5a636 | ||
|
|
3c18ac0d96 | ||
|
|
9b9005087e | ||
|
|
88f0bc4c28 | ||
|
|
c8d368e86f | ||
|
|
318d6956fa | ||
|
|
5443214033 | ||
|
|
2bdabbcd4c | ||
|
|
38eaa59518 | ||
|
|
ed6fa92f51 | ||
|
|
7f0b4bccb3 | ||
|
|
f4edfb870b | ||
|
|
9a07256a70 | ||
|
|
b744754949 | ||
|
|
f08859b377 | ||
|
|
fb5475b724 | ||
|
|
798d26ddae | ||
|
|
cd2eb2e0d9 | ||
|
|
dfa1c29fa0 | ||
|
|
090e85ea1c | ||
|
|
d1fd7c1eb0 | ||
|
|
775cba5667 | ||
|
|
6f40bc5b1b | ||
|
|
e14fb974b4 | ||
|
|
dbf29c6b83 | ||
|
|
ad44fd3382 | ||
|
|
61df41d5fe | ||
|
|
942a795c2c | ||
|
|
5f948badd1 | ||
|
|
f07f32b169 | ||
|
|
905257d039 | ||
|
|
4af7500e42 | ||
|
|
a9a3a0037b | ||
|
|
111d16319b | ||
|
|
269a74da52 | ||
|
|
51a094a502 | ||
|
|
b048c017bf | ||
|
|
d725756498 | ||
|
|
65649bbb72 | ||
|
|
7e246e9d78 | ||
|
|
cdf965f4da | ||
|
|
b2e19bb89a | ||
|
|
ba6cfb0a55 | ||
|
|
8a24dbfdaf | ||
|
|
d84e13e2ef | ||
|
|
c4727aa3d8 | ||
|
|
110b5d4c87 | ||
|
|
b8c834794b | ||
|
|
bcc1cc412e | ||
|
|
ff96880cc2 | ||
|
|
6f4e9662db | ||
|
|
5da99fc5f0 | ||
|
|
ec3fc8737a | ||
|
|
5fd3dcda36 | ||
|
|
938aaba898 | ||
|
|
cb7e6c96b0 | ||
|
|
49b128a20c | ||
|
|
7f683eb920 | ||
|
|
489c89205d | ||
|
|
4b0b91de98 | ||
|
|
c54b1ab4a0 | ||
|
|
8055a2b651 | ||
|
|
71ababc4d1 | ||
|
|
498b885398 | ||
|
|
774fcfd497 | ||
|
|
0967e475d5 | ||
|
|
2bd6fc444e | ||
|
|
dfa025952b | ||
|
|
30918675bf | ||
|
|
91f6c11821 | ||
|
|
bf16fbe84b | ||
|
|
ffb8003245 | ||
|
|
1645b5f362 | ||
|
|
aa03cb5f89 | ||
|
|
1d8025b0cf | ||
|
|
3ac8998377 | ||
|
|
5160f61523 | ||
|
|
68c5fa10f4 | ||
|
|
620f564ae5 | ||
|
|
b840d481a8 | ||
|
|
7872a59a89 | ||
|
|
62c7f92563 | ||
|
|
283f237180 | ||
|
|
6dcd1c43fe | ||
|
|
805bad437c | ||
|
|
f476fb1cc0 | ||
|
|
f35f8b1b29 | ||
|
|
061ed7e765 | ||
|
|
fbbfe78828 | ||
|
|
557132b4c5 | ||
|
|
5bc7a15c15 | ||
|
|
0fa34e3eeb | ||
|
|
eeef8bd468 | ||
|
|
6b5eeea3b7 | ||
|
|
6e9fd540c5 | ||
|
|
c4da8ce9df | ||
|
|
5cc50e6e06 | ||
|
|
5088ea7b80 | ||
|
|
d7ae175a00 | ||
|
|
60e47f2ffc | ||
|
|
850c2d3b8a | ||
|
|
cdddd5eacf | ||
|
|
fd4793db99 | ||
|
|
9006dbee87 | ||
|
|
073923d072 | ||
|
|
dfb006584e | ||
|
|
c00383ce81 | ||
|
|
ca9d61d955 | ||
|
|
37a8549d21 | ||
|
|
6c753b5058 | ||
|
|
7395ed0c2b | ||
|
|
2b55c2b5e0 | ||
|
|
41d9aa488f | ||
|
|
3e46d2ad84 | ||
|
|
dac990716d | ||
|
|
ed56e14574 | ||
|
|
acf72397b6 | ||
|
|
02d97e440f | ||
|
|
f2e67d3727 | ||
|
|
ae11b24c7a | ||
|
|
6f2b77fbdc | ||
|
|
11d4df8c36 | ||
|
|
f221b01811 | ||
|
|
236e9e60da | ||
|
|
40cfb3d111 | ||
|
|
05190e6384 | ||
|
|
77ab8ed85b | ||
|
|
059cf5e631 | ||
|
|
b304c7ebe8 | ||
|
|
9b097d3a42 | ||
|
|
4dc2e1f262 | ||
|
|
6a10cdd8cd | ||
|
|
7febdf13b7 | ||
|
|
47b0b678e4 | ||
|
|
4e21c606ef | ||
|
|
aef143256d | ||
|
|
7c76bd91b4 | ||
|
|
2ddce7b69b | ||
|
|
73df916b86 | ||
|
|
85285a2005 | ||
|
|
5a3b90ec12 | ||
|
|
13bc0b7d46 | ||
|
|
814e11a153 | ||
|
|
262988a0f8 | ||
|
|
4376bf069c | ||
|
|
995d75c95a | ||
|
|
f9773de1d8 | ||
|
|
c46cc41bc4 | ||
|
|
bcef06a1f1 | ||
|
|
45941784a8 | ||
|
|
d646853c2e | ||
|
|
eedc3ccc69 | ||
|
|
036a9a12cc | ||
|
|
59d8746cb3 | ||
|
|
cf8e266c4c | ||
|
|
363c53ede2 | ||
|
|
f9175ffd03 | ||
|
|
041b2538d3 | ||
|
|
58daf0993b | ||
|
|
2be75b28d9 | ||
|
|
3e9549088a | ||
|
|
0428a87c71 | ||
|
|
9121df192d | ||
|
|
b63d85d341 | ||
|
|
423a762cad | ||
|
|
2c68d395f7 | ||
|
|
b1132c901a | ||
|
|
db1fd83c8b | ||
|
|
4592ba8d38 | ||
|
|
05951c0776 | ||
|
|
070c010156 | ||
|
|
3e0741b6d8 | ||
|
|
8ec0e3348b | ||
|
|
e81a63c695 | ||
|
|
d41976829a | ||
|
|
09589ec899 | ||
|
|
9553f1ca74 | ||
|
|
2d91e87ca0 | ||
|
|
1cd09bace7 | ||
|
|
a896464e4c | ||
|
|
052d492f59 | ||
|
|
d0fc8c3f73 | ||
|
|
02322e50ab | ||
|
|
029aa6d466 | ||
|
|
9fbbb43ec1 | ||
|
|
8429539527 | ||
|
|
935f878688 | ||
|
|
5596d1f8a6 | ||
|
|
00028248c0 | ||
|
|
75feb7b409 | ||
|
|
5fefe96963 | ||
|
|
7572cdb25d | ||
|
|
77834809f6 | ||
|
|
b72edac261 | ||
|
|
4c4623d92d | ||
|
|
162b51f1fa | ||
|
|
f667c4f3ad | ||
|
|
b291f6b26c | ||
|
|
a679acfb6a | ||
|
|
c546294cd9 | ||
|
|
474c6e2112 | ||
|
|
dbce80b4fa | ||
|
|
4399cdadb0 | ||
|
|
14e7f5bbb8 | ||
|
|
3440017db8 | ||
|
|
1223b2b4eb | ||
|
|
731fbc3083 | ||
|
|
bb9487b35e | ||
|
|
43ea2a0f84 | ||
|
|
f5606aea09 | ||
|
|
2d10d080a4 | ||
|
|
e36c007176 | ||
|
|
60c2784643 | ||
|
|
49d91caafd | ||
|
|
6c4e9fc29b | ||
|
|
e49fa835a8 | ||
|
|
ab1e7c623f | ||
|
|
725ce14ed6 | ||
|
|
a154445723 | ||
|
|
b09e49ddae | ||
|
|
f94f39a31c | ||
|
|
a4172acdf2 | ||
|
|
8823762b16 | ||
|
|
5b9f44e73d | ||
|
|
af1b5f135b | ||
|
|
14399f46cf | ||
|
|
eccd154046 | ||
|
|
e7e789aadb | ||
|
|
1f171427fc | ||
|
|
1cf054f50c | ||
|
|
e824c3830e | ||
|
|
b93b0eaef7 | ||
|
|
be4c4e7455 | ||
|
|
5dbb53f053 | ||
|
|
550946be5e | ||
|
|
8819e2dddc | ||
|
|
ae14412385 | ||
|
|
276a731f53 | ||
|
|
6873c8bc72 | ||
|
|
13a3a85916 | ||
|
|
79c25e5dd4 | ||
|
|
05e1710953 | ||
|
|
756698bda6 | ||
|
|
8965acb17c | ||
|
|
87e6fc3d03 | ||
|
|
438959e42b | ||
|
|
c5713c6079 | ||
|
|
2251df5a6f | ||
|
|
4474aee7fa | ||
|
|
4dcc637065 | ||
|
|
d793eec2e0 | ||
|
|
3ece1c3636 | ||
|
|
c135cad669 | ||
|
|
3d9e636cc3 | ||
|
|
b64c4966e0 | ||
|
|
7695fe8659 | ||
|
|
ad7e021928 | ||
|
|
73233ad6de | ||
|
|
3e96c793b1 | ||
|
|
6d10a87efc | ||
|
|
0228b7d0a4 | ||
|
|
798377416d | ||
|
|
9b64c65816 | ||
|
|
2030fe4461 | ||
|
|
c8f9fb8bac | ||
|
|
cce698578e | ||
|
|
2bf8146489 | ||
|
|
7148477209 | ||
|
|
fe0e68ab37 | ||
|
|
24420db4c4 | ||
|
|
2a0e067472 | ||
|
|
8126ffc980 | ||
|
|
9d530e095a | ||
|
|
8dd8ed45b4 | ||
|
|
c825d91779 | ||
|
|
38a2c3f26b | ||
|
|
3b8aba29d5 | ||
|
|
3aa77e5022 | ||
|
|
543bacf448 |
No files matched your search
@@ -4,11 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- staging
|
||||
- trying
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
permissions:
|
||||
actions: none
|
||||
checks: none
|
||||
@@ -24,47 +20,66 @@ permissions:
|
||||
security-events: none
|
||||
statuses: none
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-21-openjdk-amd64
|
||||
JAVA_HOME: /usr/lib/jvm/java-25-openjdk-amd64
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
flavor: [Foss, Gplay]
|
||||
flavor: [fdroidLegacy, foss, gplay]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: gradle/actions/wrapper-validation@v5
|
||||
- name: set up OpenJDK 21
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Ensure sentence-cased flavor variables
|
||||
run: |
|
||||
flavor="$(echo ${{ matrix.flavor }})"
|
||||
echo "sentenceCasedFlavor=${flavor^}" >> "$GITHUB_ENV"
|
||||
- uses: gradle/actions/wrapper-validation@v6.2.0
|
||||
- name: set up OpenJDK 25
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y openjdk-21-jdk-headless
|
||||
sudo apt-get install -y openjdk-25-jdk-headless
|
||||
sudo update-alternatives --auto java
|
||||
- name: Build
|
||||
run: ./gradlew assemble${{ matrix.flavor }}Release
|
||||
run: ./gradlew assemble${sentenceCasedFlavor}Release
|
||||
- name: Check for non-free libraries
|
||||
run: |
|
||||
# Get Katastima APK scanner
|
||||
wget -q https://codeberg.org/Katastima/apkscanner/releases/download/v0.0.9/apk-scanner_v0.0.9.jar
|
||||
# Print pretty result
|
||||
java -jar apk-scanner_v0.0.9.jar scan-apk "$(echo 'app/build/outputs/apk/${{ matrix.flavor }}/release/app-${{ matrix.flavor }}-release-unsigned.apk')"
|
||||
# Check for offending libraries
|
||||
# Exit code 4 = no result, which is what we want
|
||||
set +e
|
||||
java -jar apk-scanner_v0.0.9.jar scan-apk --json=yes "$(echo 'app/build/outputs/apk/${{ matrix.flavor }}/release/app-${{ matrix.flavor }}-release-unsigned.apk')" | jq --exit-status '.libraryCheckResult.offendingLibraries[] | select((.libraryId == "/org/acra" and .license == "Apache-2.0")|not)'
|
||||
if [ "$?" -ne 4 ]; then
|
||||
echo "Bad libraries found!"
|
||||
exit 1
|
||||
fi
|
||||
set -e
|
||||
- name: Check lint
|
||||
run: ./gradlew lint${{ matrix.flavor }}Release
|
||||
run: ./gradlew lint${sentenceCasedFlavor}Release
|
||||
- name: Run unit tests
|
||||
run: timeout 5m ./gradlew test${{ matrix.flavor }}ReleaseUnitTest || { ./gradlew --stop && timeout 5m ./gradlew test${{ matrix.flavor }}ReleaseUnitTest; }
|
||||
run: timeout 5m ./gradlew test${sentenceCasedFlavor}DebugUnitTest || { ./gradlew --stop && timeout 5m ./gradlew test${sentenceCasedFlavor}DebugUnitTest; }
|
||||
- name: Enable KVM
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
- name: Run instrumented tests (API 21)
|
||||
uses: ReactiveCircus/android-emulator-runner@v2
|
||||
- name: Run instrumented tests (API 23)
|
||||
uses: ReactiveCircus/android-emulator-runner@v2.38.0
|
||||
with:
|
||||
api-level: 21
|
||||
api-level: 23
|
||||
arch: x86_64
|
||||
script: ./gradlew connected${{ matrix.flavor }}DebugAndroidTest
|
||||
script: ./gradlew connected${sentenceCasedFlavor}DebugAndroidTest
|
||||
- name: Run instrumented tests (API 35)
|
||||
uses: ReactiveCircus/android-emulator-runner@v2
|
||||
uses: ReactiveCircus/android-emulator-runner@v2.38.0
|
||||
with:
|
||||
api-level: 35
|
||||
arch: x86_64
|
||||
script: ./gradlew connected${{ matrix.flavor }}DebugAndroidTest
|
||||
script: ./gradlew connected${sentenceCasedFlavor}DebugAndroidTest
|
||||
- name: Archive test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6.0.0
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: test-results-flavor${{ matrix.flavor }}
|
||||
name: test-results-flavor-${{ matrix.flavor }}
|
||||
path: app/build/reports
|
||||
@@ -19,15 +19,15 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6.1.0
|
||||
uses: actions/setup-python@v7.0.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Run converter script
|
||||
run: python .scripts/changelog_to_fastlane.py
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v8.0.0
|
||||
uses: peter-evans/create-pull-request@v8.1.1
|
||||
with:
|
||||
title: "Update Fastlane changelogs"
|
||||
commit-message: "Update Fastlane changelogs"
|
||||
|
||||
@@ -17,15 +17,17 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Update contributors
|
||||
id: update_contributors
|
||||
uses: TheLastProject/contributors-to-file-action@v3.2.0
|
||||
with:
|
||||
file_in_repo: app/src/main/res/raw/contributors.txt
|
||||
min_commit_count: 5
|
||||
- name: Remove gradle-update-robot from contributors (not a person)
|
||||
run: "sed -i '/^gradle-update-robot$/d' app/src/main/res/raw/contributors.txt"
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v8.0.0
|
||||
uses: peter-evans/create-pull-request@v8.1.1
|
||||
with:
|
||||
title: "Update contributors"
|
||||
commit-message: "Update contributors"
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
generate-feature-graphic:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Install requirements
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Generate featureGraphic.png for each language
|
||||
run: .scripts/generate_feature_graphic/generate_feature_graphic.sh
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v8.0.0
|
||||
uses: peter-evans/create-pull-request@v8.1.1
|
||||
with:
|
||||
title: "Update feature graphic"
|
||||
commit-message: "Update feature graphic"
|
||||
|
||||
@@ -4,11 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- staging
|
||||
- trying
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
permissions:
|
||||
actions: none
|
||||
checks: none
|
||||
@@ -27,7 +23,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- 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
|
||||
- name: Check app_name consistency
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
name: Update Gradle Wrapper
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-gradle-wrapper:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Update Gradle Wrapper
|
||||
uses: gradle-update/update-gradle-wrapper-action@v2
|
||||
@@ -17,13 +17,13 @@ jobs:
|
||||
update-locales:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Add new locales
|
||||
run: .scripts/new-locales.py
|
||||
- name: Update locales
|
||||
run: .scripts/locales.py
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v8.0.0
|
||||
uses: peter-evans/create-pull-request@v8.1.1
|
||||
with:
|
||||
title: "Update locales"
|
||||
commit-message: "Update locales"
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Android Wear CI
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
permissions:
|
||||
actions: none
|
||||
checks: none
|
||||
contents: read
|
||||
deployments: none
|
||||
discussions: none
|
||||
id-token: none
|
||||
issues: none
|
||||
packages: none
|
||||
pages: none
|
||||
pull-requests: none
|
||||
repository-projects: none
|
||||
security-events: none
|
||||
statuses: none
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-25-openjdk-amd64
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7.0.0
|
||||
- uses: gradle/actions/wrapper-validation@v6.2.0
|
||||
- name: set up OpenJDK 25
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y openjdk-25-jdk-headless
|
||||
sudo update-alternatives --auto java
|
||||
- name: Build
|
||||
run: ./gradlew :wear:assembleRelease
|
||||
- name: Check for non-free libraries
|
||||
run: |
|
||||
# Get Katastima APK scanner
|
||||
wget -q https://codeberg.org/Katastima/apkscanner/releases/download/v0.0.9/apk-scanner_v0.0.9.jar
|
||||
# Print pretty result
|
||||
java -jar apk-scanner_v0.0.9.jar scan-apk "wear/build/outputs/apk/release/wear-release-unsigned.apk"
|
||||
# Check for offending libraries
|
||||
# Exit code 4 = no result, which is what we want
|
||||
set +e
|
||||
java -jar apk-scanner_v0.0.9.jar scan-apk --json=yes "wear/build/outputs/apk/release/wear-release-unsigned.apk" | jq --exit-status '.libraryCheckResult.offendingLibraries[]'
|
||||
if [ "$?" -ne 4 ]; then
|
||||
echo "Bad libraries found!"
|
||||
exit 1
|
||||
fi
|
||||
set -e
|
||||
- name: Check lint
|
||||
run: ./gradlew :wear:lintRelease
|
||||
@@ -19,6 +19,8 @@
|
||||
/app/*.log
|
||||
/app/build
|
||||
/app/release
|
||||
/wear/build
|
||||
/wear/release
|
||||
/.idea/*
|
||||
!/.idea/icon.svg
|
||||
# Bundle
|
||||
@@ -28,3 +30,4 @@
|
||||
|
||||
# Catima-specific
|
||||
SHA256SUMS
|
||||
/shared/build
|
||||
@@ -0,0 +1 @@
|
||||
3.3.11
|
||||
@@ -15,7 +15,7 @@ with open('CHANGELOG.md') as changelog:
|
||||
changelogs[version_code] = text
|
||||
|
||||
text = []
|
||||
match = re.match("## \S* - (\d*).*", line)
|
||||
match = re.match("## .+ - (\d*).*", line)
|
||||
if not match:
|
||||
raise ValueError(f"Invalid version line: {line}")
|
||||
version_code = match.group(1)
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## v2.45.0 (Android) - 1002
|
||||
|
||||
- Add a Bluetooth server for Wear OS companion app support
|
||||
- Remove embedded CHANGELOG
|
||||
|
||||
## v1.0.0 (Wear OS) - 200
|
||||
|
||||
- Initial release of Wear OS companion app
|
||||
|
||||
## v2.44.0 - 168 (2026-07-28)
|
||||
|
||||
- Add option to open image in internal image viewer in gallery
|
||||
|
||||
## v2.43.0 - 167 (2026-07-12)
|
||||
|
||||
- Add support for showing the card ID in the card list
|
||||
- Add support to sort by most recently added
|
||||
- Add internal image viewer for zooming card front/back/icon images
|
||||
|
||||
## v2.42.1 - 166 (2026-05-16)
|
||||
|
||||
- Prevent crash on devices with misbehaving NFC
|
||||
|
||||
## v2.42.0 - 165 (2026-05-06)
|
||||
|
||||
- Disable NFC while showing a card by default
|
||||
- Ask keyboard to capitalize each word in store name in edit screen
|
||||
- Put date in export file name
|
||||
|
||||
## v2.41.7 - 164 (2026-03-06)
|
||||
|
||||
Android 5.0 and 5.1 are no longer supported starting with this release. If you want to use Catima on these versions, please use version 2.41.6.
|
||||
|
||||
- Fix crash when switching cards using volume buttons if only one card is available
|
||||
- Fix shortcut returning to previous card after rotation
|
||||
|
||||
## v2.41.6 - 163 (2026-01-23)
|
||||
|
||||
- Auto-detect URLs in card ID and make the clickable in card ID details pop-up
|
||||
|
||||
@@ -23,7 +23,9 @@ for good reason.
|
||||
|
||||
## Code Changes
|
||||
|
||||
Note: submitting LLM ("AI") generated code is strongly discouraged, as such
|
||||
### LLM ("AI") and coding agents
|
||||
|
||||
**Note:** submitting LLM ("AI") generated code is strongly discouraged, as such
|
||||
code is often (subtly) incorrect or overcomplicated (for example: unnecessarily
|
||||
pulling in extra libraries for functionality already covered by existing
|
||||
libraries). It also often makes unrelated changes that increase the risk of
|
||||
@@ -47,6 +49,14 @@ risk.
|
||||
|
||||
Please never submit LLM-generated code as-is.
|
||||
|
||||
### Kotlin and Compose migration
|
||||
|
||||
Catima uses a lot of "legacy" tech, Java, Android XML. This will be ported to Kotlin,
|
||||
then Jetpack Compose; for further details, see
|
||||
<https://github.com/CatimaLoyalty/Android/issues/2483>.
|
||||
|
||||
Thus, prefer Kotlin and Jetpack Compose for new code/UI where practical, especially new files/features.
|
||||
|
||||
### Test Your Code
|
||||
|
||||
There are four possible tests you can run to verify your code. The first
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "fastlane"
|
||||
|
||||
# https://github.com/fastlane/fastlane/issues/29183
|
||||
gem "abbrev"
|
||||
gem "mutex_m"
|
||||
gem "ostruct"
|
||||
@@ -1,48 +1,50 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.7)
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
CFPropertyList (3.0.8)
|
||||
abbrev (0.1.2)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
addressable (2.9.0)
|
||||
public_suffix (>= 2.0.2, < 8.0)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1117.0)
|
||||
aws-sdk-core (3.226.0)
|
||||
aws-partitions (1.1269.0)
|
||||
aws-sdk-core (3.254.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
base64
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.105.0)
|
||||
aws-sdk-core (~> 3, >= 3.225.0)
|
||||
aws-sdk-kms (1.130.0)
|
||||
aws-sdk-core (~> 3, >= 3.254.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.189.1)
|
||||
aws-sdk-core (~> 3, >= 3.225.0)
|
||||
aws-sdk-s3 (1.227.0)
|
||||
aws-sdk-core (~> 3, >= 3.254.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.12.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.3.0)
|
||||
benchmark (0.5.0)
|
||||
bigdecimal (4.1.2)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
csv (3.3.5)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.7.0)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.6.20240107)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.112.0)
|
||||
faraday (1.10.4)
|
||||
excon (1.5.0)
|
||||
logger
|
||||
faraday (1.10.6)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
@@ -54,53 +56,62 @@ GEM
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday-cookie_jar (0.0.8)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
http-cookie (>= 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.1)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.1.1)
|
||||
faraday-multipart (1.2.0)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday-retry (1.0.4)
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.4.0)
|
||||
fastlane (2.228.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
fastimage (2.4.1)
|
||||
fastlane (2.237.0)
|
||||
CFPropertyList (>= 2.3, < 5.0.0)
|
||||
abbrev (~> 0.1)
|
||||
addressable (>= 2.9.0, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
aws-sdk-s3 (~> 1.197)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
base64 (~> 0.2)
|
||||
benchmark (>= 0.1.0)
|
||||
bundler (>= 2.4.0, < 5.0.0)
|
||||
colored (~> 1.2)
|
||||
commander (~> 4.6)
|
||||
csv (~> 3.3)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
excon (>= 0.71.0, < 2.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
fastlane-sirp (>= 1.0.0)
|
||||
fastlane-sirp (>= 1.1.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||
google-cloud-env (>= 1.6.0, < 2.3.0)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
jwt (>= 2.10.3, < 4)
|
||||
logger (>= 1.6, < 2.0)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multi_json (~> 1.12)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
mutex_m (~> 0.3)
|
||||
naturally (~> 2.2)
|
||||
nkf (~> 0.2)
|
||||
optparse (>= 0.1.1, < 1.0.0)
|
||||
ostruct (>= 0.1.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.5)
|
||||
@@ -113,44 +124,48 @@ GEM
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.4.1)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-sirp (1.0.0)
|
||||
sysrandom (~> 1.0)
|
||||
fastlane-sirp (1.1.0)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.54.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.3)
|
||||
google-apis-androidpublisher_v3 (0.104.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-core (0.18.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
googleauth (~> 1.9)
|
||||
httpclient (>= 2.8.3, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
mutex_m
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.13.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.31.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-cloud-core (1.8.0)
|
||||
google-apis-iamcredentials_v1 (0.28.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.18.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-storage_v1 (0.64.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-cloud-core (1.9.0)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.5.0)
|
||||
google-cloud-storage (1.47.0)
|
||||
google-cloud-env (2.2.2)
|
||||
base64 (~> 0.2)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-errors (1.7.0)
|
||||
google-cloud-storage (1.62.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.31.0)
|
||||
google-apis-core (>= 0.18, < 2)
|
||||
google-apis-iamcredentials_v1 (~> 0.18)
|
||||
google-apis-storage_v1 (>= 0.42)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
googleauth (~> 1.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.8.1)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
google-logging-utils (0.2.0)
|
||||
googleauth (1.17.1)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-env (~> 2.2)
|
||||
google-logging-utils (~> 0.1)
|
||||
jwt (>= 1.4, < 4.0)
|
||||
os (>= 0.9, < 2.0)
|
||||
pstore (~> 0.1)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.8)
|
||||
@@ -158,43 +173,42 @@ GEM
|
||||
httpclient (2.9.0)
|
||||
mutex_m
|
||||
jmespath (1.6.2)
|
||||
json (2.12.2)
|
||||
jwt (2.10.1)
|
||||
json (2.21.1)
|
||||
jwt (3.2.0)
|
||||
base64
|
||||
logger (1.7.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
multi_json (1.15.0)
|
||||
multi_json (1.21.1)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.4.0)
|
||||
naturally (2.3.0)
|
||||
nkf (0.2.0)
|
||||
optparse (0.6.0)
|
||||
nkf (0.3.0)
|
||||
optparse (0.8.1)
|
||||
os (1.1.4)
|
||||
ostruct (0.6.1)
|
||||
ostruct (0.6.3)
|
||||
plist (3.7.2)
|
||||
public_suffix (6.0.2)
|
||||
rake (13.3.0)
|
||||
pstore (0.2.1)
|
||||
public_suffix (7.0.5)
|
||||
rake (13.4.2)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.4.1)
|
||||
retriable (3.8.0)
|
||||
rexml (3.4.4)
|
||||
rouge (3.28.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.4.1)
|
||||
security (0.1.5)
|
||||
signet (0.20.0)
|
||||
signet (0.22.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
jwt (>= 1.5, < 4.0)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
sysrandom (1.0.5)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
@@ -206,12 +220,14 @@ GEM
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.27.0)
|
||||
xcodeproj (1.28.1)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
base64
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.4.0)
|
||||
nkf
|
||||
rexml (>= 3.3.6, < 4.0)
|
||||
xcpretty (0.4.1)
|
||||
rouge (~> 3.28.0)
|
||||
@@ -222,10 +238,7 @@ PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
abbrev
|
||||
fastlane
|
||||
mutex_m
|
||||
ostruct
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.22
|
||||
@@ -1,12 +1,14 @@
|
||||
**Last updated**
|
||||
September 30 2025
|
||||
August 7 2026
|
||||
|
||||
# Privacy Policy
|
||||
Catima does not collect or transmit any personal information.
|
||||
|
||||
To ensure correct app functionality, we require access to the following:
|
||||
|
||||
- Bluetooth: When using Catima together with the Wear OS companion app, Bluetooth access is needed to be able to communicate to Wear OS. This may show up as "find, connect to, and determine the relative position of nearby devices" because Android considers Bluetooth as part of location, but location information is never collected or used. The app can still be used when Bluetooth permission is denied, but you will not be able to use the Wear OS companion app.
|
||||
- Camera: We need access to your camera to be able to scan barcodes. The app can still be used when camera access is denied, but you will have to manually type the barcode information.
|
||||
- NFC: We need NFC access to temporarily disable NFC while showing a barcode to prevent NFC payment apps from triggering (such as when using a check-in gates which support both barcodes and NFC payments). This functionality can be disabled in settings.
|
||||
- Storage (Android 5 and 6 only): We need access to your device storage to create or import backups. The app can still be used when storage access is denied, but you will not be able to create or import backups.
|
||||
|
||||
Catima offers a feature to share cards with other users. All the relevant data is in the generated shareable URLs and never transmitted to our servers. When viewed through catima.app, the data in the URL is rendered using client-side Javascript to further ensure no data is ever transmitted to us.
|
||||
|
||||
@@ -17,10 +17,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "me.hackerchick.catima"
|
||||
minSdk = 21
|
||||
minSdk = 23
|
||||
targetSdk = 36
|
||||
versionCode = 163
|
||||
versionName = "2.41.6"
|
||||
versionCode = 1002
|
||||
versionName = "2.45.0"
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
multiDexEnabled = true
|
||||
@@ -38,7 +38,7 @@ android {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android.txt"),
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
@@ -59,8 +59,14 @@ android {
|
||||
dimension = "type"
|
||||
isDefault = true
|
||||
}
|
||||
create("fdroidLegacy") {
|
||||
dimension = "type"
|
||||
versionNameSuffix = " (F-Droid)"
|
||||
versionCode = defaultConfig.versionCode!! - 1
|
||||
}
|
||||
create("gplay") {
|
||||
dimension = "type"
|
||||
versionNameSuffix = " (Google)"
|
||||
|
||||
// Google doesn't allow donation links
|
||||
buildConfigField("boolean", "showDonate", "false")
|
||||
@@ -98,7 +104,7 @@ android {
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
jvmTarget = JvmTarget.JVM_21
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
@@ -107,13 +113,14 @@ android {
|
||||
// Flag to enable support for the new language APIs
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":shared"))
|
||||
|
||||
// AndroidX
|
||||
implementation(libs.androidx.appcompat.appcompat)
|
||||
implementation(libs.androidx.constraintlayout.constraintlayout)
|
||||
@@ -166,7 +173,6 @@ dependencies {
|
||||
|
||||
tasks.register("copyRawResFiles", Copy::class) {
|
||||
from(
|
||||
layout.projectDirectory.file("../CHANGELOG.md"),
|
||||
layout.projectDirectory.file("../PRIVACY.md")
|
||||
)
|
||||
into(layout.projectDirectory.dir("src/main/res/raw"))
|
||||
@@ -174,7 +180,7 @@ tasks.register("copyRawResFiles", Copy::class) {
|
||||
}.also {
|
||||
tasks.preBuild.dependsOn(it)
|
||||
tasks.getByName<Delete>("clean") {
|
||||
val filesNamesToDelete = listOf("CHANGELOG", "PRIVACY")
|
||||
val filesNamesToDelete = listOf("PRIVACY")
|
||||
filesNamesToDelete.forEach { fileName ->
|
||||
delete(layout.projectDirectory.file("src/main/res/raw/${fileName.lowercase()}.md"))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
<string name="app_name">Debug di Catima</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Depuração do Catima</string>
|
||||
</resources>
|
||||
@@ -12,8 +12,18 @@
|
||||
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="23" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.nfc"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
@@ -94,9 +104,12 @@
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize"/>
|
||||
<activity
|
||||
android:name=".LoyaltyCardViewActivity"
|
||||
android:name=".cardview.LoyaltyCardViewActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/AppTheme.NoActionBar" />
|
||||
<activity
|
||||
android:name=".cardimageview.LoyaltyCardImageViewActivity"
|
||||
android:theme="@style/AppTheme.NoActionBar" />
|
||||
<activity
|
||||
android:name=".LoyaltyCardEditActivity"
|
||||
android:exported="true"
|
||||
@@ -219,5 +232,26 @@
|
||||
<action android:name="android.service.controls.ControlsProviderService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".wearos.BluetoothServerService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="connectedDevice" />
|
||||
|
||||
<activity
|
||||
android:name=".wearos.BluetoothPairingActivity"
|
||||
android:exported="false"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/AppTheme.Transparent" />
|
||||
|
||||
<receiver
|
||||
android:name=".wearos.BluetoothPairingReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="protect.card_locker.wearos.action.ALLOW_DEVICE" />
|
||||
<action android:name="protect.card_locker.wearos.action.BLOCK_DEVICE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -69,16 +69,7 @@ fun AboutScreenContent(
|
||||
stringResource(R.string.version_history),
|
||||
content.versionHistory,
|
||||
modifier = Modifier.testTag("card_version_history"),
|
||||
onClickUrl = "https://catima.app/changelog/",
|
||||
onClickDialogText = AnnotatedString.fromHtml(
|
||||
htmlString = content.historyHtml,
|
||||
linkStyles = TextLinkStyles(
|
||||
style = SpanStyle(
|
||||
textDecoration = TextDecoration.Underline,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
)
|
||||
)
|
||||
onClickUrl = "https://catima.app/changelog/"
|
||||
)
|
||||
CatimaAboutSection(
|
||||
stringResource(R.string.credits),
|
||||
|
||||
@@ -62,18 +62,6 @@ public class AboutContent {
|
||||
return contributors.replace("\n", "<br />");
|
||||
}
|
||||
|
||||
public String getHistoryHtml() {
|
||||
String versionHistory;
|
||||
try {
|
||||
versionHistory = Utils.readTextFile(context, R.raw.changelog)
|
||||
.replace("# Changelog\n\n", "");
|
||||
} catch (IOException ignored) {
|
||||
return "";
|
||||
}
|
||||
return Utils.linkify(Utils.basicMDToHTML(versionHistory))
|
||||
.replace("\n", "<br />");
|
||||
}
|
||||
|
||||
public String getLicenseHtml() {
|
||||
try {
|
||||
return Utils.readTextFile(context, R.raw.license);
|
||||
|
||||
@@ -57,7 +57,7 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
|
||||
private final boolean showFallback;
|
||||
private final BarcodeImageWriterResultCallback callback;
|
||||
|
||||
BarcodeImageWriterTask(
|
||||
public BarcodeImageWriterTask(
|
||||
Context context, ImageView imageView, String cardIdString,
|
||||
CatimaBarcode barcodeFormat, @NonNull Charset barcodeEncoding, TextView textView,
|
||||
boolean showFallback, BarcodeImageWriterResultCallback callback, boolean roundCornerPadding, boolean isFullscreen
|
||||
|
||||
@@ -22,6 +22,8 @@ import java.util.List;
|
||||
import java.util.concurrent.Flow;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import protect.card_locker.cardview.LoyaltyCardViewActivity;
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
public class CardsOnPowerScreenService extends ControlsProviderService {
|
||||
|
||||
|
||||
@@ -38,15 +38,10 @@ public class CatimaAppCompatActivity extends AppCompatActivity {
|
||||
Window window = getWindow();
|
||||
if (window != null) {
|
||||
boolean darkMode = Utils.isDarkModeEnabled(this);
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
View decorView = window.getDecorView();
|
||||
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(window, decorView);
|
||||
wic.setAppearanceLightStatusBars(!darkMode);
|
||||
window.setStatusBarColor(Color.TRANSPARENT);
|
||||
} else {
|
||||
// icons are always white back then
|
||||
window.setStatusBarColor(darkMode ? Color.TRANSPARENT : Color.argb(127, 0, 0, 0));
|
||||
}
|
||||
View decorView = window.getDecorView();
|
||||
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(window, decorView);
|
||||
wic.setAppearanceLightStatusBars(!darkMode);
|
||||
window.setStatusBarColor(Color.TRANSPARENT);
|
||||
}
|
||||
// XXX android 9 and below has a nasty rendering bug if the theme was patched earlier
|
||||
Utils.postPatchColors(this);
|
||||
@@ -66,7 +61,4 @@ public class CatimaAppCompatActivity extends AppCompatActivity {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,7 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
|
||||
public enum LoyaltyCardOrder {
|
||||
Alpha,
|
||||
LastAdded,
|
||||
LastUsed,
|
||||
ValidFrom,
|
||||
Expiry
|
||||
@@ -1089,6 +1090,10 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
return LoyaltyCardDbIds.STORE;
|
||||
}
|
||||
|
||||
if (order == LoyaltyCardOrder.LastAdded) {
|
||||
return LoyaltyCardDbIds.ID;
|
||||
}
|
||||
|
||||
if (order == LoyaltyCardOrder.LastUsed) {
|
||||
return LoyaltyCardDbIds.LAST_USED;
|
||||
}
|
||||
@@ -1105,8 +1110,8 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
|
||||
private static String getDbDirection(LoyaltyCardOrder order, LoyaltyCardOrderDirection direction) {
|
||||
if (order == LoyaltyCardOrder.LastUsed) {
|
||||
// We want the default sorting to put the most recently used first
|
||||
if (order == LoyaltyCardOrder.LastAdded || order == LoyaltyCardOrder.LastUsed) {
|
||||
// We want the default sorting to put the most recently added/used first
|
||||
return direction == LoyaltyCardOrderDirection.Descending ? "ASC" : "DESC";
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ import protect.card_locker.importexport.ImportExportResultType
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
class ImportExportActivity : CatimaAppCompatActivity() {
|
||||
private lateinit var binding: ImportExportActivityBinding
|
||||
@@ -45,6 +48,11 @@ class ImportExportActivity : CatimaAppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "Catima"
|
||||
|
||||
internal fun getExportFilename(): String {
|
||||
val sdf = SimpleDateFormat("yyyyMMdd", Locale.US)
|
||||
return "catima_${sdf.format(Date())}.zip"
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -107,7 +115,7 @@ class ImportExportActivity : CatimaAppCompatActivity() {
|
||||
val intentCreateDocumentAction = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/zip"
|
||||
putExtra(Intent.EXTRA_TITLE, "catima.zip")
|
||||
putExtra(Intent.EXTRA_TITLE, getExportFilename())
|
||||
}
|
||||
|
||||
val exportButton: Button = binding.exportButton
|
||||
|
||||
@@ -13,6 +13,8 @@ import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.widget.RemoteViewsCompat
|
||||
import protect.card_locker.DBHelper.LoyaltyCardArchiveFilter
|
||||
import protect.card_locker.cardview.LoyaltyCardViewActivity
|
||||
import protect.card_locker.shared.ForegroundColorHelper
|
||||
|
||||
class ListWidget : AppWidgetProvider() {
|
||||
fun updateAll(context: Context) {
|
||||
@@ -99,11 +101,10 @@ class ListWidget : AppWidgetProvider() {
|
||||
// Note: Android 5 will not use bitmaps
|
||||
val remoteViews = RemoteViews(context.packageName, R.layout.list_widget_item).apply {
|
||||
val headerColor = Utils.getHeaderColor(context, loyaltyCard)
|
||||
val foreground = if (Utils.needsDarkForeground(headerColor)) Color.BLACK else Color.WHITE
|
||||
val foreground = if (ForegroundColorHelper.needsDarkForeground(headerColor)) Color.BLACK else Color.WHITE
|
||||
setInt(R.id.item_container_foreground, "setBackgroundColor", headerColor)
|
||||
val icon = loyaltyCard.getImageThumbnail(context)
|
||||
// setImageViewIcon is not supported on Android 5, so force Android 5 down the text path
|
||||
// FIXME: The icon flow causes a crash up to Android 12L, so SDK_INT is forced up from 23 to 33
|
||||
// FIXME: The icon flow causes a crash up to Android 12L, so force anything below 33 down this path
|
||||
if (icon != null && Build.VERSION.SDK_INT >= 32) {
|
||||
setInt(R.id.item_container_foreground, "setBackgroundColor", foreground)
|
||||
setImageViewIcon(R.id.item_image, Icon.createWithBitmap(icon))
|
||||
@@ -130,4 +131,4 @@ class ListWidget : AppWidgetProvider() {
|
||||
|
||||
return remoteViews
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,12 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
||||
inputHolder.setNoteField(null);
|
||||
}
|
||||
|
||||
if (mLoyaltyCardListDisplayOptions.showingCardId() && !loyaltyCard.cardId.isEmpty()) {
|
||||
inputHolder.setExtraField(inputHolder.mCardIdField, loyaltyCard.cardId, null, showDivider);
|
||||
} else {
|
||||
inputHolder.setExtraField(inputHolder.mCardIdField, null, null, false);
|
||||
}
|
||||
|
||||
if (mLoyaltyCardListDisplayOptions.showingBalance() && !loyaltyCard.balance.equals(new BigDecimal("0"))) {
|
||||
inputHolder.setExtraField(inputHolder.mBalanceField, Utils.formatBalance(mContext, loyaltyCard.balance, loyaltyCard.balanceType), null, showDivider);
|
||||
} else {
|
||||
@@ -211,7 +217,7 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
||||
|
||||
public class LoyaltyCardListItemViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public TextView mCardText, mStoreField, mNoteField, mBalanceField, mValidFromField, mExpiryField;
|
||||
public TextView mCardText, mStoreField, mNoteField, mCardIdField, mBalanceField, mValidFromField, mExpiryField;
|
||||
public ImageView mCardIcon, mTickIcon;
|
||||
public MaterialCardView mRow;
|
||||
public ConstraintLayout mStar, mArchived;
|
||||
@@ -224,6 +230,7 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
||||
mDivider = loyaltyCardLayoutBinding.infoDivider;
|
||||
mStoreField = loyaltyCardLayoutBinding.store;
|
||||
mNoteField = loyaltyCardLayoutBinding.note;
|
||||
mCardIdField = loyaltyCardLayoutBinding.cardId;
|
||||
mBalanceField = loyaltyCardLayoutBinding.balance;
|
||||
mValidFromField = loyaltyCardLayoutBinding.validFrom;
|
||||
mExpiryField = loyaltyCardLayoutBinding.expiry;
|
||||
|
||||
@@ -91,6 +91,7 @@ import java.util.concurrent.Callable;
|
||||
import protect.card_locker.async.TaskHandler;
|
||||
import protect.card_locker.databinding.LayoutChipChoiceBinding;
|
||||
import protect.card_locker.databinding.LoyaltyCardEditActivityBinding;
|
||||
import protect.card_locker.shared.ForegroundColorHelper;
|
||||
import protect.card_locker.viewmodels.LoyaltyCardEditActivityViewModel;
|
||||
|
||||
public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements BarcodeImageWriterResultCallback, ColorPickerDialogListener {
|
||||
@@ -719,6 +720,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (mDatabase != null && mDatabase.isOpen()) {
|
||||
mDatabase.close();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void selectTab(int index) {
|
||||
binding.tabs.selectTab(binding.tabs.getTabAt(index));
|
||||
viewModel.setTabIndex(index);
|
||||
@@ -888,8 +897,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
Integer headerColor = viewModel.getLoyaltyCard().headerColor;
|
||||
if (headerColor != null) {
|
||||
thumbnail.setOnClickListener(new ChooseCardImage());
|
||||
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
|
||||
thumbnailEditIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
thumbnailEditIcon.setColorFilter(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
|
||||
}
|
||||
|
||||
onResuming = false;
|
||||
@@ -911,18 +920,18 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
setLoyaltyCardHeaderColor(headerColor);
|
||||
|
||||
thumbnail.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
thumbnail.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
|
||||
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
|
||||
thumbnailEditIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
thumbnailEditIcon.setColorFilter(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
|
||||
} else {
|
||||
generateIcon(storeFieldEdit.getText().toString().trim());
|
||||
|
||||
Integer headerColor = viewModel.getLoyaltyCard().headerColor;
|
||||
|
||||
if (headerColor != null) {
|
||||
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
|
||||
thumbnailEditIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
thumbnailEditIcon.setColorFilter(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ public class LoyaltyCardListDisplayOptionsManager {
|
||||
|
||||
private boolean mShowNameBelowThumbnail;
|
||||
private boolean mShowNote;
|
||||
private boolean mShowCardId;
|
||||
private boolean mShowBalance;
|
||||
private boolean mShowValidity;
|
||||
private boolean mShowArchivedCards;
|
||||
@@ -50,6 +51,7 @@ public class LoyaltyCardListDisplayOptionsManager {
|
||||
Context.MODE_PRIVATE);
|
||||
mShowNameBelowThumbnail = mCardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_name_below_thumbnail), false);
|
||||
mShowNote = mCardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_note), true);
|
||||
mShowCardId = mCardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_card_id), false);
|
||||
mShowBalance = mCardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_balance), true);
|
||||
mShowValidity = mCardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_validity), true);
|
||||
mShowArchivedCards = mCardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_archived_cards), true);
|
||||
@@ -83,6 +85,17 @@ public class LoyaltyCardListDisplayOptionsManager {
|
||||
return mShowNote;
|
||||
}
|
||||
|
||||
public void showCardId(boolean show) {
|
||||
mShowCardId = show;
|
||||
mRefreshCardsCallback.run();
|
||||
|
||||
saveDetailState(R.string.sharedpreference_card_details_show_card_id, show);
|
||||
}
|
||||
|
||||
public boolean showingCardId() {
|
||||
return mShowCardId;
|
||||
}
|
||||
|
||||
public void showBalance(boolean show) {
|
||||
mShowBalance = show;
|
||||
mRefreshCardsCallback.run();
|
||||
@@ -137,6 +150,11 @@ public class LoyaltyCardListDisplayOptionsManager {
|
||||
showingNote(),
|
||||
this::showNote
|
||||
));
|
||||
displayOptions.add(new LoyaltyCardDisplayOption(
|
||||
mContext.getString(R.string.show_card_id),
|
||||
showingCardId(),
|
||||
this::showCardId
|
||||
));
|
||||
displayOptions.add(new LoyaltyCardDisplayOption(
|
||||
mContext.getString(R.string.show_balance),
|
||||
showingBalance(),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
import org.acra.ACRA;
|
||||
import org.acra.config.CoreConfigurationBuilder;
|
||||
import org.acra.config.DialogConfigurationBuilder;
|
||||
@@ -11,6 +11,7 @@ import org.acra.config.MailSenderConfigurationBuilder;
|
||||
import org.acra.data.StringFormat;
|
||||
|
||||
import protect.card_locker.preferences.Settings;
|
||||
import protect.card_locker.wearos.WearSyncServiceManager;
|
||||
|
||||
public class LoyaltyCardLockerApplication extends Application {
|
||||
|
||||
@@ -41,5 +42,10 @@ public class LoyaltyCardLockerApplication extends Application {
|
||||
// Set theme
|
||||
Settings settings = new Settings(this);
|
||||
AppCompatDelegate.setDefaultNightMode(settings.getTheme());
|
||||
|
||||
// Start Bluetooth server for Wear OS companion if enabled.
|
||||
// The service checks BLUETOOTH_CONNECT itself and stops if the permission is missing.
|
||||
// The permission is requested from the launcher Activity when the UI resumes.
|
||||
WearSyncServiceManager.INSTANCE.synchronize(this, null);
|
||||
}
|
||||
}
|
||||
@@ -31,11 +31,13 @@ import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
|
||||
import protect.card_locker.DBHelper.LoyaltyCardOrder
|
||||
import protect.card_locker.DBHelper.LoyaltyCardOrderDirection
|
||||
import protect.card_locker.LoyaltyCardCursorAdapter.CardAdapterListener
|
||||
import protect.card_locker.cardview.LoyaltyCardViewActivity
|
||||
import protect.card_locker.databinding.ContentMainBinding
|
||||
import protect.card_locker.databinding.MainActivityBinding
|
||||
import protect.card_locker.databinding.SortingOptionBinding
|
||||
import protect.card_locker.preferences.Settings
|
||||
import protect.card_locker.preferences.SettingsActivity
|
||||
import protect.card_locker.wearos.WearSyncPermissionRequester
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import androidx.core.content.edit
|
||||
@@ -60,6 +62,7 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
private lateinit var mUpdateLoyaltyCardListRunnable: Runnable
|
||||
private lateinit var mBarcodeScannerLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var mSettingsLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var mWearSyncPermissionRequester: WearSyncPermissionRequester
|
||||
|
||||
private val mCurrentActionModeCallback: ActionMode.Callback = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(inputMode: ActionMode, inputMenu: Menu?): Boolean {
|
||||
@@ -317,6 +320,8 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
}
|
||||
}
|
||||
|
||||
mWearSyncPermissionRequester = WearSyncPermissionRequester(this, this)
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (mSearchView != null && !mSearchView!!.isIconified) {
|
||||
@@ -393,6 +398,8 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
val settings = Settings(this)
|
||||
layoutManager.setSpanCount(settings.getPreferredColumnCount())
|
||||
}
|
||||
|
||||
mWearSyncPermissionRequester.synchronize()
|
||||
}
|
||||
|
||||
private fun displayCardSetupOptions(menu: Menu, shouldShow: Boolean) {
|
||||
@@ -466,11 +473,7 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
ShortcutHelper.updateShortcuts(mAdapter.mContext)
|
||||
}
|
||||
|
||||
private fun processParseResultList(
|
||||
parseResultList: MutableList<ParseResult?>,
|
||||
group: String?,
|
||||
closeAppOnNoBarcode: Boolean
|
||||
) {
|
||||
private fun processParseResultList(parseResultList: MutableList<ParseResult?>) {
|
||||
require(!parseResultList.isEmpty()) { "parseResultList may not be empty" }
|
||||
|
||||
Utils.makeUserChooseParseResultFromList(
|
||||
@@ -481,17 +484,12 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
val intent =
|
||||
Intent(applicationContext, LoyaltyCardEditActivity::class.java)
|
||||
val bundle = parseResult.toLoyaltyCardBundle(this@MainActivity)
|
||||
if (group != null) {
|
||||
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group)
|
||||
}
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
override fun onUserDismissedSelector() {
|
||||
if (closeAppOnNoBarcode) {
|
||||
finish()
|
||||
}
|
||||
finish()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -555,7 +553,7 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
return
|
||||
}
|
||||
|
||||
processParseResultList(parseResultList, null, true)
|
||||
processParseResultList(parseResultList)
|
||||
}
|
||||
|
||||
private fun extractIntentFields(intent: Intent) {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package protect.card_locker
|
||||
|
||||
object NotificationInfo {
|
||||
object WearBluetooth {
|
||||
const val NOTIFICATION_ID = 1001
|
||||
const val CHANNEL_ID = "catima_wear_bt"
|
||||
const val PAIRING_CHANNEL_ID = "catima_wear_bt_pairing"
|
||||
const val CRITICAL_ERROR_NOTIFICATION_ID = 1002
|
||||
const val CRITICAL_ERROR_CHANNEL_ID = "catima_wear_bt_critical_error"
|
||||
}
|
||||
}
|
||||
@@ -34,11 +34,6 @@ public class PermissionUtils {
|
||||
* @return
|
||||
*/
|
||||
public static boolean needsCameraPermission(Activity activity) {
|
||||
// Android only introduced the runtime permission system in Marshmallow (Android 6.0)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ContextCompat.checkSelfPermission(activity, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
@@ -49,21 +44,14 @@ public class PermissionUtils {
|
||||
* @param activity
|
||||
* @param requestCode
|
||||
*/
|
||||
public static void requestStorageReadPermission(CatimaAppCompatActivity activity, int requestCode) {
|
||||
public static void requestStorageReadPermission(Activity activity, int requestCode) {
|
||||
String[] permissions = new String[]{ android.Manifest.permission.READ_EXTERNAL_STORAGE };
|
||||
int[] mockedResults = new int[]{ PackageManager.PERMISSION_GRANTED };
|
||||
|
||||
if (needsStorageReadPermission(activity)) {
|
||||
ActivityCompat.requestPermissions(activity, permissions, requestCode);
|
||||
} else {
|
||||
// FIXME: This points to onMockedRequestPermissionResult instead of to
|
||||
// onRequestPermissionResult because onRequestPermissionResult was only introduced in
|
||||
// Android 6.0 (SDK 23) and we and to support Android 5.0 (SDK 21) too.
|
||||
//
|
||||
// When minSdk becomes 23, this should point to onRequestPermissionResult directly and
|
||||
// the activity input variable should be changed from CatimaAppCompatActivity to
|
||||
// Activity.
|
||||
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
|
||||
activity.onRequestPermissionsResult(requestCode, permissions, mockedResults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,21 +62,14 @@ public class PermissionUtils {
|
||||
* @param activity
|
||||
* @param requestCode
|
||||
*/
|
||||
public static void requestCameraPermission(CatimaAppCompatActivity activity, int requestCode) {
|
||||
public static void requestCameraPermission(Activity activity, int requestCode) {
|
||||
String[] permissions = new String[]{ Manifest.permission.CAMERA };
|
||||
int[] mockedResults = new int[]{ PackageManager.PERMISSION_GRANTED };
|
||||
|
||||
if (needsCameraPermission(activity)) {
|
||||
ActivityCompat.requestPermissions(activity, permissions, requestCode);
|
||||
} else {
|
||||
// FIXME: This points to onMockedRequestPermissionResult instead of to
|
||||
// onRequestPermissionResult because onRequestPermissionResult was only introduced in
|
||||
// Android 6.0 (SDK 23) and we and to support Android 5.0 (SDK 21) too.
|
||||
//
|
||||
// When minSdk becomes 23, this should point to onRequestPermissionResult directly and
|
||||
// the activity input variable should be changed from CatimaAppCompatActivity to
|
||||
// Activity.
|
||||
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
|
||||
activity.onRequestPermissionsResult(requestCode, permissions, mockedResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -543,14 +543,6 @@ class ScanActivity : CatimaAppCompatActivity() {
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
|
||||
onMockedRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
override fun onMockedRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
val granted =
|
||||
grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
|
||||
@@ -15,11 +15,12 @@ import androidx.core.content.pm.ShortcutManagerCompat;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
|
||||
class ShortcutHelper {
|
||||
import protect.card_locker.cardview.LoyaltyCardViewActivity;
|
||||
import protect.card_locker.shared.ForegroundColorHelper;
|
||||
|
||||
public class ShortcutHelper {
|
||||
/**
|
||||
* This variable controls the maximum number of shortcuts available.
|
||||
* It is made public only to make testing easier and should not be
|
||||
@@ -43,33 +44,36 @@ class ShortcutHelper {
|
||||
* based on the lastUsed field. Archived cards are excluded from the shortcuts
|
||||
* list. The list keeps at most maxShortcuts number of elements.
|
||||
*/
|
||||
static void updateShortcuts(Context context) {
|
||||
public static void updateShortcuts(Context context) {
|
||||
if (maxShortcuts == -1) {
|
||||
maxShortcuts = ShortcutManagerCompat.getMaxShortcutCountPerActivity(context);
|
||||
}
|
||||
LinkedList<ShortcutInfoCompat> finalList = new LinkedList<>();
|
||||
SQLiteDatabase database = new DBHelper(context).getReadableDatabase();
|
||||
Cursor loyaltyCardCursor = DBHelper.getLoyaltyCardCursor(
|
||||
DBHelper dbHelper = new DBHelper(context);
|
||||
SQLiteDatabase database = dbHelper.getReadableDatabase();
|
||||
try (Cursor loyaltyCardCursor = DBHelper.getLoyaltyCardCursor(
|
||||
database,
|
||||
"",
|
||||
null,
|
||||
DBHelper.LoyaltyCardOrder.LastUsed,
|
||||
DBHelper.LoyaltyCardOrderDirection.Ascending,
|
||||
DBHelper.LoyaltyCardArchiveFilter.Unarchived
|
||||
);
|
||||
)) {
|
||||
int rank = 0;
|
||||
|
||||
int rank = 0;
|
||||
while (rank < maxShortcuts && loyaltyCardCursor.moveToNext()) {
|
||||
int id = loyaltyCardCursor.getInt(loyaltyCardCursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
|
||||
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(context, database, id);
|
||||
|
||||
while (rank < maxShortcuts && loyaltyCardCursor.moveToNext()) {
|
||||
int id = loyaltyCardCursor.getInt(loyaltyCardCursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
|
||||
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(context, database, id);
|
||||
ShortcutInfoCompat updatedShortcut = createShortcutBuilder(context, loyaltyCard)
|
||||
.setRank(rank)
|
||||
.build();
|
||||
|
||||
ShortcutInfoCompat updatedShortcut = createShortcutBuilder(context, loyaltyCard)
|
||||
.setRank(rank)
|
||||
.build();
|
||||
|
||||
finalList.addLast(updatedShortcut);
|
||||
rank++;
|
||||
finalList.addLast(updatedShortcut);
|
||||
rank++;
|
||||
}
|
||||
} finally {
|
||||
dbHelper.close();
|
||||
}
|
||||
|
||||
ShortcutManagerCompat.setDynamicShortcuts(context, finalList);
|
||||
@@ -90,7 +94,7 @@ class ShortcutHelper {
|
||||
intent.setAction(Intent.ACTION_MAIN);
|
||||
// Prevent instances of the view activity from piling up; if one exists let this
|
||||
// one replace it.
|
||||
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
final Bundle bundle = new Bundle();
|
||||
bundle.putInt(LoyaltyCardViewActivity.BUNDLE_ID, loyaltyCard.id);
|
||||
intent.putExtras(bundle);
|
||||
@@ -99,7 +103,7 @@ class ShortcutHelper {
|
||||
if (iconBitmap == null) {
|
||||
iconBitmap = Utils.generateIcon(context, loyaltyCard, true).getLetterTile();
|
||||
} else {
|
||||
iconBitmap = createAdaptiveBitmap(iconBitmap, Utils.needsDarkForeground(Utils.getHeaderColor(context, loyaltyCard)) ? Color.BLACK : Color.WHITE);
|
||||
iconBitmap = createAdaptiveBitmap(iconBitmap, ForegroundColorHelper.Companion.needsDarkForeground(Utils.getHeaderColor(context, loyaltyCard)) ? Color.BLACK : Color.WHITE);
|
||||
}
|
||||
|
||||
IconCompat icon = IconCompat.createWithAdaptiveBitmap(iconBitmap);
|
||||
@@ -110,4 +114,4 @@ class ShortcutHelper {
|
||||
.setIntent(intent)
|
||||
.setIcon(icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,16 +40,9 @@ class UCropWrapper : UCropActivity() {
|
||||
return
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
val decorView = window.decorView
|
||||
val wic = WindowInsetsControllerCompat(window, decorView)
|
||||
wic.isAppearanceLightStatusBars = !darkMode
|
||||
} else if (!darkMode) {
|
||||
window.statusBarColor = ColorUtils.compositeColors(
|
||||
Color.argb(127, 0, 0, 0),
|
||||
window.statusBarColor
|
||||
)
|
||||
}
|
||||
val decorView = window.decorView
|
||||
val wic = WindowInsetsControllerCompat(window, decorView)
|
||||
wic.isAppearanceLightStatusBars = !darkMode
|
||||
}
|
||||
|
||||
private fun checkViews(darkMode: Boolean) {
|
||||
|
||||
@@ -39,7 +39,6 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RawRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
@@ -95,6 +94,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import protect.card_locker.preferences.Settings;
|
||||
import protect.card_locker.shared.ForegroundColorHelper;
|
||||
|
||||
public class Utils {
|
||||
private static final String TAG = "Catima";
|
||||
@@ -115,8 +115,6 @@ public class Utils {
|
||||
|
||||
public static final String CARD_IMAGE_FILENAME_REGEX = "^(card_)(\\d+)(_(?:front|back|icon)\\.png)$";
|
||||
|
||||
static final double LUMINANCE_MIDPOINT = 0.5;
|
||||
|
||||
static final int BITMAP_SIZE_SMALL = 512;
|
||||
static final int BITMAP_SIZE_BIG = 1600;
|
||||
|
||||
@@ -147,11 +145,7 @@ public class Utils {
|
||||
}
|
||||
|
||||
return new LetterBitmap(context, store, store,
|
||||
tileLetterFontSize, pixelSize, pixelSize, backgroundColor, needsDarkForeground(backgroundColor) ? Color.BLACK : Color.WHITE);
|
||||
}
|
||||
|
||||
static public boolean needsDarkForeground(Integer backgroundColor) {
|
||||
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
|
||||
tileLetterFontSize, pixelSize, pixelSize, backgroundColor, ForegroundColorHelper.Companion.needsDarkForeground(backgroundColor) ? Color.BLACK : Color.WHITE);
|
||||
}
|
||||
|
||||
static public List<ParseResult> retrieveBarcodesFromImage(Context context, Uri uri) {
|
||||
@@ -1070,7 +1064,7 @@ public class Utils {
|
||||
|
||||
if (icon != null) {
|
||||
// Use header colour to decide if this image will need a white or black background
|
||||
backgroundOrIcon.setBackgroundColor(needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
backgroundOrIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
|
||||
// Ensure correct cropping style
|
||||
backgroundOrIcon.setScaleType(Utils.getRecommendedScaleTypeForThumbnailImage(icon));
|
||||
@@ -1100,7 +1094,7 @@ public class Utils {
|
||||
// Actually set the text and colour
|
||||
textWhenNoImage.setVisibility(View.VISIBLE);
|
||||
textWhenNoImage.setText(loyaltyCard.store);
|
||||
textWhenNoImage.setTextColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
textWhenNoImage.setTextColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
}
|
||||
|
||||
return headerColor;
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
package protect.card_locker.cardimageview
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedDispatcher
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.gestures.rememberTransformableState
|
||||
import androidx.compose.foundation.gestures.transformable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.core.content.FileProvider
|
||||
import protect.card_locker.CatimaComponentActivity
|
||||
import protect.card_locker.ImageLocationType
|
||||
import protect.card_locker.R
|
||||
import protect.card_locker.Utils
|
||||
import protect.card_locker.compose.CatimaTopAppBar
|
||||
import protect.card_locker.compose.theme.CatimaTheme
|
||||
import protect.card_locker.preferences.Settings
|
||||
|
||||
class LoyaltyCardImageViewActivity : CatimaComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
fixedEdgeToEdge()
|
||||
applyWindowPreferences()
|
||||
|
||||
val imageLocationType = intent.imageLocationType()
|
||||
val loyaltyCardId = intent.getIntExtra(BUNDLE_ID, 0)
|
||||
val bitmap = imageLocationType?.let {
|
||||
Utils.retrieveCardImage(this, loyaltyCardId, it)
|
||||
}
|
||||
|
||||
if (loyaltyCardId == 0 || imageLocationType == null || bitmap == null) {
|
||||
Toast.makeText(this, R.string.failedToRetrieveImageFile, Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
title = getString(imageLocationType.descriptionRes())
|
||||
|
||||
setContent {
|
||||
CatimaTheme {
|
||||
LoyaltyCardImageViewScreen(
|
||||
bitmap = bitmap,
|
||||
imageLocationType = imageLocationType,
|
||||
cardId = loyaltyCardId,
|
||||
contentDescriptionRes = imageLocationType.descriptionRes(),
|
||||
onBackPressedDispatcher = onBackPressedDispatcher
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyWindowPreferences() {
|
||||
val settings = Settings(this)
|
||||
val currentWindow = window ?: return
|
||||
|
||||
val attributes = currentWindow.attributes
|
||||
if (settings.useMaxBrightnessDisplayingBarcode()) {
|
||||
attributes.screenBrightness = 1F
|
||||
}
|
||||
|
||||
if (settings.keepScreenOn) {
|
||||
currentWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
|
||||
currentWindow.attributes = attributes
|
||||
}
|
||||
|
||||
private fun Intent.imageLocationType(): ImageLocationType? {
|
||||
val name = getStringExtra(BUNDLE_IMAGE_LOCATION_TYPE) ?: return null
|
||||
return runCatching { ImageLocationType.valueOf(name) }.getOrNull()
|
||||
}
|
||||
|
||||
private fun ImageLocationType.descriptionRes(): Int {
|
||||
return when (this) {
|
||||
ImageLocationType.icon -> R.string.thumbnailDescription
|
||||
ImageLocationType.front -> R.string.frontImageDescription
|
||||
ImageLocationType.back -> R.string.backImageDescription
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BUNDLE_ID = "id"
|
||||
const val BUNDLE_IMAGE_LOCATION_TYPE = "imageLocationType"
|
||||
|
||||
@JvmStatic
|
||||
fun createIntent(
|
||||
context: Context,
|
||||
loyaltyCardId: Int,
|
||||
imageLocationType: ImageLocationType
|
||||
): Intent {
|
||||
return Intent(context, LoyaltyCardImageViewActivity::class.java)
|
||||
.putExtra(BUNDLE_ID, loyaltyCardId)
|
||||
.putExtra(BUNDLE_IMAGE_LOCATION_TYPE, imageLocationType.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LoyaltyCardImageViewScreen(
|
||||
bitmap: Bitmap,
|
||||
imageLocationType: ImageLocationType,
|
||||
cardId: Int,
|
||||
@StringRes contentDescriptionRes: Int,
|
||||
onBackPressedDispatcher: OnBackPressedDispatcher,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var scale by remember { mutableFloatStateOf(1F) }
|
||||
var offset by remember { mutableStateOf(Offset.Zero) }
|
||||
var size by remember { mutableStateOf(IntSize.Zero) }
|
||||
val zoomState = rememberTransformableState { _, zoomChange, panChange, _ ->
|
||||
val nextScale = (scale * zoomChange).coerceIn(1F, MAX_IMAGE_SCALE)
|
||||
scale = nextScale
|
||||
offset = if (nextScale == 1F) {
|
||||
Offset.Zero
|
||||
} else {
|
||||
(offset + panChange).clampTo(size, nextScale)
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
CatimaTopAppBar(
|
||||
title = stringResource(contentDescriptionRes),
|
||||
onBackPressedDispatcher = onBackPressedDispatcher,
|
||||
actions = {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
IconButton(onClick = { expanded = !expanded }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.MoreVert,
|
||||
contentDescription = stringResource(R.string.action_more_options)
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false }
|
||||
) {
|
||||
DropdownMenuItem(onClick = { openInImageGallery(context, cardId, imageLocationType) },
|
||||
text = { Text(stringResource(R.string.open_in_gallery)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.testTag("card_image_zoom")
|
||||
) {
|
||||
Image(
|
||||
bitmap = bitmap.asImageBitmap(),
|
||||
contentDescription = stringResource(contentDescriptionRes),
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.onSizeChanged { size = it }
|
||||
.pointerInput(bitmap) {
|
||||
detectTapGestures(
|
||||
onDoubleTap = {
|
||||
scale = 1F
|
||||
offset = Offset.Zero
|
||||
}
|
||||
)
|
||||
}
|
||||
.transformable(zoomState)
|
||||
.graphicsLayer {
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
translationX = offset.x
|
||||
translationY = offset.y
|
||||
}
|
||||
.testTag("card_image")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Offset.clampTo(size: IntSize, scale: Float): Offset {
|
||||
if (size.width == 0 || size.height == 0) {
|
||||
return this
|
||||
}
|
||||
|
||||
val maxX = size.width * (scale - 1F) / 2F
|
||||
val maxY = size.height * (scale - 1F) / 2F
|
||||
return Offset(
|
||||
x = x.coerceIn(-maxX, maxX),
|
||||
y = y.coerceIn(-maxY, maxY)
|
||||
)
|
||||
}
|
||||
|
||||
private fun openInImageGallery(context: Context, cardId: Int, imageLocationType: ImageLocationType){
|
||||
val imagePath = Utils.getCardImageFileName(cardId, imageLocationType)
|
||||
val viewInGalleryIntent = Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(FileProvider.getUriForFile(context, context.packageName, context.getFileStreamPath(imagePath)), "image/*")
|
||||
flags = FLAG_GRANT_READ_URI_PERMISSION
|
||||
}
|
||||
context.startActivity(viewInGalleryIntent)
|
||||
}
|
||||
|
||||
private const val MAX_IMAGE_SCALE = 5F
|
||||
@@ -0,0 +1,103 @@
|
||||
package protect.card_locker.cardview;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
final class LoyaltyCardImageNavigator {
|
||||
private final List<LoyaltyCardImageType> imageTypes;
|
||||
private int currentIndex;
|
||||
|
||||
LoyaltyCardImageNavigator(List<LoyaltyCardImageType> imageTypes, int currentIndex) {
|
||||
this.imageTypes = new ArrayList<>(imageTypes);
|
||||
this.currentIndex = clampIndex(currentIndex);
|
||||
}
|
||||
|
||||
LoyaltyCardImageType getCurrent() {
|
||||
if (isEmpty()) {
|
||||
return LoyaltyCardImageType.NONE;
|
||||
}
|
||||
return imageTypes.get(currentIndex);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return imageTypes.isEmpty();
|
||||
}
|
||||
|
||||
int size() {
|
||||
return imageTypes.size();
|
||||
}
|
||||
|
||||
boolean remove(LoyaltyCardImageType type) {
|
||||
int removedIndex = imageTypes.indexOf(type);
|
||||
if (removedIndex == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
imageTypes.remove(removedIndex);
|
||||
currentIndex = clampIndex(currentIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean canGoPrevious() {
|
||||
return currentIndex > 0;
|
||||
}
|
||||
|
||||
boolean canGoNext() {
|
||||
return currentIndex < imageTypes.size() - 1;
|
||||
}
|
||||
|
||||
boolean movePrevious() {
|
||||
if (!canGoPrevious()) {
|
||||
return false;
|
||||
}
|
||||
currentIndex--;
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean moveNext(boolean overflow) {
|
||||
if (isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (canGoNext()) {
|
||||
currentIndex++;
|
||||
return true;
|
||||
}
|
||||
if (overflow) {
|
||||
currentIndex = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int getCurrentIndex() {
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
LoyaltyCardImageType peekNext(boolean overflow) {
|
||||
if (isEmpty()) {
|
||||
return LoyaltyCardImageType.NONE;
|
||||
}
|
||||
|
||||
if (canGoNext()) {
|
||||
return imageTypes.get(currentIndex + 1);
|
||||
}
|
||||
|
||||
return overflow ? imageTypes.get(0) : getCurrent();
|
||||
}
|
||||
|
||||
private int clampIndex(int index) {
|
||||
if (imageTypes.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.max(0, Math.min(index, imageTypes.size() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
enum LoyaltyCardImageType {
|
||||
NONE,
|
||||
ICON,
|
||||
BARCODE,
|
||||
IMAGE_FRONT,
|
||||
IMAGE_BACK
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package protect.card_locker.cardview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.google.android.material.color.MaterialColors;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import protect.card_locker.*;
|
||||
import protect.card_locker.async.TaskHandler;
|
||||
|
||||
final class LoyaltyCardMainImageRenderer {
|
||||
private final Context context;
|
||||
private final TaskHandler tasks = new TaskHandler();
|
||||
private final BarcodeImageWriterResultCallback barcodeCallback;
|
||||
|
||||
LoyaltyCardMainImageRenderer(
|
||||
Context context,
|
||||
BarcodeImageWriterResultCallback barcodeCallback
|
||||
) {
|
||||
this.context = context;
|
||||
this.barcodeCallback = barcodeCallback;
|
||||
}
|
||||
|
||||
void renderCurrent(
|
||||
LoyaltyCardImageType imageType,
|
||||
Bitmap frontImageBitmap,
|
||||
Bitmap backImageBitmap,
|
||||
CatimaBarcode format,
|
||||
Charset barcodeEncoding,
|
||||
String cardIdString,
|
||||
String barcodeIdString,
|
||||
ImageView barcodeRenderTarget,
|
||||
TextView mainImageDescription,
|
||||
MaterialCardView mainCardView,
|
||||
boolean isFullscreen,
|
||||
boolean waitForResize
|
||||
) {
|
||||
if (imageType == LoyaltyCardImageType.NONE) {
|
||||
// With no renderable media left, show the raw card ID instead of an empty card area.
|
||||
barcodeRenderTarget.setVisibility(View.GONE);
|
||||
mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
||||
mainImageDescription.setTextColor(
|
||||
MaterialColors.getColor(
|
||||
mainImageDescription,
|
||||
com.google.android.material.R.attr.colorOnSurfaceVariant
|
||||
)
|
||||
);
|
||||
mainImageDescription.setText(cardIdString);
|
||||
return;
|
||||
}
|
||||
|
||||
if (imageType == LoyaltyCardImageType.BARCODE) {
|
||||
barcodeRenderTarget.setBackgroundColor(Color.WHITE);
|
||||
mainCardView.setCardBackgroundColor(Color.WHITE);
|
||||
mainImageDescription.setTextColor(context.getResources().getColor(R.color.md_theme_light_onSurfaceVariant));
|
||||
|
||||
if (waitForResize) {
|
||||
redrawBarcodeAfterResize(
|
||||
barcodeRenderTarget,
|
||||
barcodeIdString,
|
||||
cardIdString,
|
||||
format,
|
||||
barcodeEncoding,
|
||||
!isFullscreen,
|
||||
isFullscreen
|
||||
);
|
||||
} else {
|
||||
drawBarcode(
|
||||
barcodeRenderTarget,
|
||||
barcodeIdString,
|
||||
cardIdString,
|
||||
format,
|
||||
barcodeEncoding,
|
||||
!isFullscreen,
|
||||
isFullscreen
|
||||
);
|
||||
}
|
||||
|
||||
mainImageDescription.setText(cardIdString);
|
||||
barcodeRenderTarget.setContentDescription(
|
||||
context.getString(R.string.barcodeImageDescriptionWithType, format.prettyName())
|
||||
);
|
||||
} else if (imageType == LoyaltyCardImageType.IMAGE_FRONT) {
|
||||
barcodeRenderTarget.setImageBitmap(frontImageBitmap);
|
||||
barcodeRenderTarget.setBackgroundColor(Color.TRANSPARENT);
|
||||
mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
||||
mainImageDescription.setTextColor(
|
||||
MaterialColors.getColor(
|
||||
mainImageDescription,
|
||||
com.google.android.material.R.attr.colorOnSurfaceVariant
|
||||
)
|
||||
);
|
||||
mainImageDescription.setText(context.getString(R.string.frontImageDescription));
|
||||
barcodeRenderTarget.setContentDescription(context.getString(R.string.frontImageDescription));
|
||||
} else if (imageType == LoyaltyCardImageType.IMAGE_BACK) {
|
||||
barcodeRenderTarget.setImageBitmap(backImageBitmap);
|
||||
barcodeRenderTarget.setBackgroundColor(Color.TRANSPARENT);
|
||||
mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
||||
mainImageDescription.setTextColor(
|
||||
MaterialColors.getColor(
|
||||
mainImageDescription,
|
||||
com.google.android.material.R.attr.colorOnSurfaceVariant
|
||||
)
|
||||
);
|
||||
mainImageDescription.setText(context.getString(R.string.backImageDescription));
|
||||
barcodeRenderTarget.setContentDescription(context.getString(R.string.backImageDescription));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown image type: " + imageType);
|
||||
}
|
||||
|
||||
barcodeRenderTarget.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void redrawBarcodeAfterResize(
|
||||
ImageView barcodeRenderTarget,
|
||||
String barcodeIdString,
|
||||
String cardIdString,
|
||||
CatimaBarcode format,
|
||||
Charset barcodeEncoding,
|
||||
boolean addPadding,
|
||||
boolean isFullscreen
|
||||
) {
|
||||
if (format == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Barcode dimensions depend on the final ImageView size, so wait for layout before rendering.
|
||||
barcodeRenderTarget.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
barcodeRenderTarget.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
drawBarcode(
|
||||
barcodeRenderTarget,
|
||||
barcodeIdString,
|
||||
cardIdString,
|
||||
format,
|
||||
barcodeEncoding,
|
||||
addPadding,
|
||||
isFullscreen
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void drawBarcode(
|
||||
ImageView barcodeRenderTarget,
|
||||
String barcodeIdString,
|
||||
String cardIdString,
|
||||
CatimaBarcode format,
|
||||
Charset barcodeEncoding,
|
||||
boolean addPadding,
|
||||
boolean isFullscreen
|
||||
) {
|
||||
// Barcodes are regenerated eagerly because zoom/fullscreen changes affect the output bitmap.
|
||||
tasks.flushTaskList(TaskHandler.TYPE.BARCODE, true, false, false);
|
||||
if (format == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(
|
||||
context.getApplicationContext(),
|
||||
barcodeRenderTarget,
|
||||
barcodeIdString != null ? barcodeIdString : cardIdString,
|
||||
format,
|
||||
barcodeEncoding,
|
||||
null,
|
||||
false,
|
||||
barcodeCallback,
|
||||
addPadding,
|
||||
isFullscreen
|
||||
);
|
||||
tasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package protect.card_locker.cardview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.text.InputType;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.DigitsKeyListener;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import protect.card_locker.*;
|
||||
|
||||
final class LoyaltyCardViewDialogs {
|
||||
interface BalanceUpdateListener {
|
||||
void onBalanceUpdated(BigDecimal newBalance);
|
||||
}
|
||||
|
||||
void showInfoDialog(Context context, LoyaltyCard loyaltyCard, List<Group> loyaltyCardGroups) {
|
||||
AlertDialog.Builder infoDialog = new MaterialAlertDialogBuilder(context);
|
||||
|
||||
int dialogContentPadding = context.getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
|
||||
infoDialog.setTitle(loyaltyCard.store);
|
||||
|
||||
TextView infoTextview = new TextView(context);
|
||||
infoTextview.setPadding(
|
||||
dialogContentPadding,
|
||||
dialogContentPadding / 2,
|
||||
dialogContentPadding,
|
||||
0
|
||||
);
|
||||
infoTextview.setAutoLinkMask(Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS | Linkify.WEB_URLS);
|
||||
infoTextview.setTextIsSelectable(true);
|
||||
|
||||
SpannableStringBuilder infoText = new SpannableStringBuilder();
|
||||
if (!loyaltyCard.note.isEmpty()) {
|
||||
infoText.append(loyaltyCard.note);
|
||||
}
|
||||
|
||||
if (!loyaltyCardGroups.isEmpty()) {
|
||||
List<String> groupNames = new ArrayList<>();
|
||||
for (Group group : loyaltyCardGroups) {
|
||||
groupNames.add(group._id);
|
||||
}
|
||||
|
||||
padSpannableString(infoText);
|
||||
infoText.append(context.getString(R.string.groupsList, TextUtils.join(", ", groupNames)));
|
||||
}
|
||||
|
||||
if (hasBalance(loyaltyCard)) {
|
||||
padSpannableString(infoText);
|
||||
infoText.append(context.getString(
|
||||
R.string.balanceSentence,
|
||||
Utils.formatBalance(context, loyaltyCard.balance, loyaltyCard.balanceType)
|
||||
));
|
||||
}
|
||||
|
||||
appendDateInfo(
|
||||
context,
|
||||
infoText,
|
||||
loyaltyCard.validFrom,
|
||||
Utils::isNotYetValid,
|
||||
R.string.validFromSentence,
|
||||
R.string.validFromSentence
|
||||
);
|
||||
appendDateInfo(
|
||||
context,
|
||||
infoText,
|
||||
loyaltyCard.expiry,
|
||||
Utils::hasExpired,
|
||||
R.string.expiryStateSentenceExpired,
|
||||
R.string.expiryStateSentence
|
||||
);
|
||||
|
||||
infoTextview.setText(infoText);
|
||||
infoDialog.setView(infoTextview);
|
||||
infoDialog.setPositiveButton(R.string.ok, (dialogInterface, i) -> dialogInterface.dismiss());
|
||||
infoDialog.create().show();
|
||||
}
|
||||
|
||||
void showBalanceUpdateDialog(
|
||||
Context context,
|
||||
LoyaltyCard loyaltyCard,
|
||||
BalanceUpdateListener balanceUpdateListener
|
||||
) {
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context);
|
||||
builder.setTitle(R.string.updateBalanceTitle);
|
||||
|
||||
FrameLayout container = new FrameLayout(context);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
int contentPadding = context.getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
|
||||
params.leftMargin = contentPadding;
|
||||
params.topMargin = contentPadding / 2;
|
||||
params.rightMargin = contentPadding;
|
||||
|
||||
LinearLayout layout = new LinearLayout(context);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
TextView currentTextview = new TextView(context);
|
||||
currentTextview.setText(context.getString(
|
||||
R.string.currentBalanceSentence,
|
||||
Utils.formatBalance(context, loyaltyCard.balance, loyaltyCard.balanceType)
|
||||
));
|
||||
layout.addView(currentTextview);
|
||||
|
||||
final TextInputEditText input = new TextInputEditText(context);
|
||||
input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
input.setKeyListener(DigitsKeyListener.getInstance("0123456789,."));
|
||||
input.setHint(R.string.updateBalanceHint);
|
||||
|
||||
layout.addView(input);
|
||||
layout.setLayoutParams(params);
|
||||
container.addView(layout);
|
||||
builder.setView(container);
|
||||
|
||||
builder.setPositiveButton(R.string.spend, (dialogInterface, i) -> {
|
||||
try {
|
||||
BigDecimal balanceChange = Utils.parseBalance(input.getText().toString(), loyaltyCard.balanceType);
|
||||
BigDecimal newBalance = loyaltyCard.balance.subtract(balanceChange).max(new BigDecimal(0));
|
||||
balanceUpdateListener.onBalanceUpdated(newBalance);
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(
|
||||
R.string.newBalanceSentence,
|
||||
Utils.formatBalance(context, newBalance, loyaltyCard.balanceType)
|
||||
),
|
||||
Toast.LENGTH_LONG
|
||||
).show();
|
||||
} catch (ParseException e) {
|
||||
Toast.makeText(context, R.string.amountParsingFailed, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.receive, (dialogInterface, i) -> {
|
||||
try {
|
||||
BigDecimal balanceChange = Utils.parseBalance(input.getText().toString(), loyaltyCard.balanceType);
|
||||
BigDecimal newBalance = loyaltyCard.balance.add(balanceChange);
|
||||
balanceUpdateListener.onBalanceUpdated(newBalance);
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(
|
||||
R.string.newBalanceSentence,
|
||||
Utils.formatBalance(context, newBalance, loyaltyCard.balanceType)
|
||||
),
|
||||
Toast.LENGTH_LONG
|
||||
).show();
|
||||
} catch (ParseException e) {
|
||||
Toast.makeText(context, R.string.amountParsingFailed, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
builder.setNeutralButton(context.getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
// Button state depends on the parsed input, so listeners must be bound after the dialog exists.
|
||||
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(context.getString(R.string.amountParsingFailed));
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
input.setError(null);
|
||||
boolean hasNonZeroValue = !balanceChange.equals(new BigDecimal(0));
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(hasNonZeroValue);
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(hasNonZeroValue);
|
||||
}
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
// Touching dialog buttons before show() can crash because they are not created yet.
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false);
|
||||
|
||||
if (dialog.getWindow() != null) {
|
||||
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||
}
|
||||
// Focus immediately so the keyboard opens on the amount field.
|
||||
input.requestFocus();
|
||||
}
|
||||
|
||||
private boolean hasBalance(LoyaltyCard loyaltyCard) {
|
||||
return !loyaltyCard.balance.equals(new BigDecimal(0));
|
||||
}
|
||||
|
||||
private SpannableStringBuilder padSpannableString(SpannableStringBuilder spannableStringBuilder) {
|
||||
if (spannableStringBuilder.length() > 0) {
|
||||
spannableStringBuilder.append("\n\n");
|
||||
}
|
||||
|
||||
return spannableStringBuilder;
|
||||
}
|
||||
|
||||
private void appendDateInfo(
|
||||
Context context,
|
||||
SpannableStringBuilder infoText,
|
||||
Date date,
|
||||
Predicate<Date> dateCheck,
|
||||
@StringRes int dateCheckTrueString,
|
||||
@StringRes int dateCheckFalseString
|
||||
) {
|
||||
if (date == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String formattedDate = DateFormat.getDateInstance(DateFormat.LONG).format(date);
|
||||
|
||||
padSpannableString(infoText);
|
||||
if (dateCheck.test(date)) {
|
||||
int start = infoText.length();
|
||||
infoText.append(context.getString(dateCheckTrueString, formattedDate));
|
||||
infoText.setSpan(new ForegroundColorSpan(Color.RED), start, infoText.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
} else {
|
||||
infoText.append(context.getString(dateCheckFalseString, formattedDate));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package protect.card_locker.compose
|
||||
import androidx.activity.OnBackPressedDispatcher
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -22,7 +23,9 @@ import protect.card_locker.preferences.Settings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CatimaTopAppBar(title: String, onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
||||
fun CatimaTopAppBar(title: String,
|
||||
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
||||
actions : @Composable RowScope.() -> Unit = {}) {
|
||||
// Use pure black in OLED theme
|
||||
val context = LocalContext.current
|
||||
val settings = Settings(context)
|
||||
@@ -51,6 +54,7 @@ fun CatimaTopAppBar(title: String, onBackPressedDispatcher: OnBackPressedDispatc
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
actions = actions
|
||||
)
|
||||
}
|
||||
@@ -77,13 +77,6 @@ public class CardsContentProvider extends ContentProvider {
|
||||
@Nullable final String selection,
|
||||
@Nullable final String[] selectionArgs,
|
||||
@Nullable final String sortOrder) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
// Disable the content provider on SDK < 23 since it grants dangerous
|
||||
// permissions at install-time
|
||||
Log.w(TAG, "Content provider read is only available for SDK >= 23");
|
||||
return null;
|
||||
}
|
||||
|
||||
final Settings settings = new Settings(getContext());
|
||||
if (!settings.getAllowContentProviderRead()) {
|
||||
Log.w(TAG, "Content provider read is disabled");
|
||||
|
||||
@@ -82,6 +82,10 @@ public class Settings {
|
||||
return getBoolean(R.string.settings_key_disable_lockscreen_while_viewing_card, true);
|
||||
}
|
||||
|
||||
public boolean getDisableNfcWhileViewingCard() {
|
||||
return getBoolean(R.string.settings_key_disable_nfc_while_viewing_card, true);
|
||||
}
|
||||
|
||||
public boolean getAllowContentProviderRead() {
|
||||
return getBoolean(R.string.settings_key_allow_content_provider_read, true);
|
||||
}
|
||||
@@ -108,4 +112,8 @@ public class Settings {
|
||||
public boolean useVolumeKeysForNavigation() {
|
||||
return getBoolean(R.string.settings_key_use_volume_keys_navigation, false);
|
||||
}
|
||||
|
||||
public boolean getWearSyncEnabled() {
|
||||
return getBoolean(R.string.settings_key_wear_sync, false);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,42 @@
|
||||
package protect.card_locker.preferences
|
||||
|
||||
import android.app.Activity
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.MenuItem
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import protect.card_locker.BuildConfig
|
||||
import protect.card_locker.CatimaAppCompatActivity
|
||||
import protect.card_locker.MainActivity
|
||||
import protect.card_locker.R
|
||||
import protect.card_locker.Utils
|
||||
import protect.card_locker.databinding.SettingsActivityBinding
|
||||
import protect.card_locker.shared.BluetoothPermissionHelper
|
||||
import protect.card_locker.shared.WearBluetoothSecurity
|
||||
import protect.card_locker.wearos.BluetoothPairingNotificationManager
|
||||
import protect.card_locker.wearos.WearSyncPermissionRequester
|
||||
|
||||
class SettingsActivity : CatimaAppCompatActivity() {
|
||||
|
||||
@@ -81,11 +101,62 @@ class SettingsActivity : CatimaAppCompatActivity() {
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
var mReloadMain: Boolean = false
|
||||
|
||||
private lateinit var wearSyncPermissionRequester: WearSyncPermissionRequester
|
||||
private var currentDevicesDialog: AlertDialog? = null
|
||||
private var currentDevicesDialogIsBlocked: Boolean = false
|
||||
private val devicesChangedReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != BluetoothPairingNotificationManager.ACTION_DEVICES_CHANGED) {
|
||||
return
|
||||
}
|
||||
findPreference<Preference>(getString(R.string.settings_key_wear_sync_devices))?.let { updateWearSyncAllowedDevicesSummary(it) }
|
||||
findPreference<Preference>(getString(R.string.settings_key_wear_sync_blocked_devices))?.let { updateWearSyncBlockedDevicesSummary(it) }
|
||||
if (currentDevicesDialog?.isShowing != true) {
|
||||
return
|
||||
}
|
||||
val isBlocked = currentDevicesDialogIsBlocked
|
||||
currentDevicesDialog?.dismiss()
|
||||
if (isBlocked) {
|
||||
showWearSyncBlockedDevicesDialog()
|
||||
} else {
|
||||
showWearSyncAllowedDevicesDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
wearSyncPermissionRequester.synchronize()
|
||||
findPreference<Preference>(getString(R.string.settings_key_wear_sync_devices))?.let { updateWearSyncAllowedDevicesSummary(it) }
|
||||
findPreference<Preference>(getString(R.string.settings_key_wear_sync_blocked_devices))?.let { updateWearSyncBlockedDevicesSummary(it) }
|
||||
ContextCompat.registerReceiver(
|
||||
requireContext(),
|
||||
devicesChangedReceiver,
|
||||
IntentFilter(BluetoothPairingNotificationManager.ACTION_DEVICES_CHANGED),
|
||||
ContextCompat.RECEIVER_NOT_EXPORTED
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
requireContext().unregisterReceiver(devicesChangedReceiver)
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
wearSyncPermissionRequester = WearSyncPermissionRequester(this, requireContext())
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.preferences)
|
||||
|
||||
// Show pretty names and summaries
|
||||
setupThemePreference()
|
||||
setupOledDarkPreference()
|
||||
setupLocalePreference()
|
||||
setupCrashReporterPreference()
|
||||
setupWearSyncPreference()
|
||||
setupWearSyncDevicePreferences()
|
||||
}
|
||||
|
||||
private fun setupThemePreference() {
|
||||
val themePreference = findPreference<ListPreference>(getString(R.string.settings_key_theme))
|
||||
themePreference!!.setOnPreferenceChangeListener { _, o ->
|
||||
when (o.toString()) {
|
||||
@@ -101,13 +172,17 @@ class SettingsActivity : CatimaAppCompatActivity() {
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupOledDarkPreference() {
|
||||
val oledDarkPreference = findPreference<Preference>(getString(R.string.settings_key_oled_dark))
|
||||
oledDarkPreference!!.setOnPreferenceChangeListener { _, _ ->
|
||||
refreshActivity(true)
|
||||
refreshActivity()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupLocalePreference() {
|
||||
val localePreference =
|
||||
findPreference<ListPreference>(getString(R.string.settings_key_locale))!!
|
||||
localePreference.let {
|
||||
@@ -148,7 +223,7 @@ class SettingsActivity : CatimaAppCompatActivity() {
|
||||
localePreference.setOnPreferenceChangeListener { _, newValue ->
|
||||
// See corresponding comment in Utils.updateBaseContextLocale for Android 6- notes
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
refreshActivity(true)
|
||||
refreshActivity()
|
||||
return@setOnPreferenceChangeListener true
|
||||
}
|
||||
val newLocale = newValue as String
|
||||
@@ -156,20 +231,205 @@ class SettingsActivity : CatimaAppCompatActivity() {
|
||||
AppCompatDelegate.setApplicationLocales(if (newLocale.isEmpty()) LocaleListCompat.getEmptyLocaleList() else LocaleListCompat.create(Utils.stringToLocale(newLocale)))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// Disable content provider on SDK < 23 since dangerous permissions
|
||||
// are granted at install-time
|
||||
val contentProviderReadPreference = findPreference<Preference>(getString(R.string.settings_key_allow_content_provider_read))
|
||||
contentProviderReadPreference!!.isVisible =
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
|
||||
private fun setupCrashReporterPreference() {
|
||||
// Hide crash reporter settings on builds it's not enabled on
|
||||
val crashReporterPreference = findPreference<Preference>("acra.enable")
|
||||
crashReporterPreference!!.isVisible = BuildConfig.useAcraCrashReporter
|
||||
}
|
||||
|
||||
private fun refreshActivity(reloadMain: Boolean) {
|
||||
mReloadMain = reloadMain || mReloadMain
|
||||
private fun setupWearSyncPreference() {
|
||||
val wearSyncPreference = findPreference<SwitchPreferenceCompat>(getString(R.string.settings_key_wear_sync))
|
||||
wearSyncPreference!!.setOnPreferenceChangeListener { preference, newValue ->
|
||||
val enabled = newValue as Boolean
|
||||
if (enabled) {
|
||||
wearSyncPermissionRequester.onWearSyncChanged(true) { granted ->
|
||||
if (granted) {
|
||||
(preference as? SwitchPreferenceCompat)?.isChecked = true
|
||||
} else {
|
||||
showWearSyncPermissionDeniedSnackbar()
|
||||
}
|
||||
}
|
||||
false
|
||||
} else {
|
||||
wearSyncPermissionRequester.onWearSyncChanged(false)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWearSyncDevicePreferences() {
|
||||
val allowedPreference = findPreference<Preference>(getString(R.string.settings_key_wear_sync_devices))
|
||||
allowedPreference!!.setOnPreferenceClickListener {
|
||||
showWearSyncAllowedDevicesDialog()
|
||||
true
|
||||
}
|
||||
updateWearSyncAllowedDevicesSummary(allowedPreference)
|
||||
|
||||
val blockedPreference = findPreference<Preference>(getString(R.string.settings_key_wear_sync_blocked_devices))
|
||||
blockedPreference!!.setOnPreferenceClickListener {
|
||||
showWearSyncBlockedDevicesDialog()
|
||||
true
|
||||
}
|
||||
updateWearSyncBlockedDevicesSummary(blockedPreference)
|
||||
}
|
||||
|
||||
private fun updateWearSyncAllowedDevicesSummary(preference: Preference) {
|
||||
val count = WearBluetoothSecurity.listTrustedDevices(requireContext()).size
|
||||
preference.summary = if (count == 0) {
|
||||
getString(R.string.settings_wear_sync_no_devices)
|
||||
} else {
|
||||
resources.getQuantityString(R.plurals.settings_wear_sync_devices_summary_count, count, count)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateWearSyncBlockedDevicesSummary(preference: Preference) {
|
||||
val count = WearBluetoothSecurity.listBlockedDevices(requireContext()).size
|
||||
preference.summary = if (count == 0) {
|
||||
getString(R.string.settings_wear_sync_no_blocked_devices)
|
||||
} else {
|
||||
resources.getQuantityString(R.plurals.settings_wear_sync_blocked_devices_summary_count, count, count)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun showWearSyncDeviceListDialog(
|
||||
titleRes: Int,
|
||||
noDevicesRes: Int,
|
||||
messageRes: Int,
|
||||
addresses: Set<String>,
|
||||
isBlocked: Boolean,
|
||||
onRemove: (String, String) -> Unit
|
||||
) {
|
||||
val context = requireContext()
|
||||
if (!BluetoothPermissionHelper.isBluetoothConnectGranted(context)) {
|
||||
showWearSyncPermissionDeniedSnackbar()
|
||||
return
|
||||
}
|
||||
val knownAddresses = addresses.toList()
|
||||
currentDevicesDialog?.dismiss()
|
||||
currentDevicesDialogIsBlocked = isBlocked
|
||||
if (knownAddresses.isEmpty()) {
|
||||
currentDevicesDialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(titleRes)
|
||||
.setMessage(noDevicesRes)
|
||||
.setOnDismissListener {
|
||||
currentDevicesDialog = null
|
||||
currentDevicesDialogIsBlocked = false
|
||||
}
|
||||
.setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||
.show()
|
||||
return
|
||||
}
|
||||
|
||||
val adapter = context.getSystemService(BLUETOOTH_SERVICE) as? BluetoothManager
|
||||
val bondedDevices = try {
|
||||
adapter?.adapter?.bondedDevices ?: emptySet()
|
||||
} catch (_: SecurityException) {
|
||||
emptySet<BluetoothDevice>()
|
||||
}
|
||||
val deviceMap = bondedDevices.associateBy { it.address }
|
||||
|
||||
val content = View.inflate(context, R.layout.wear_sync_device_list, null)
|
||||
content.findViewById<TextView>(R.id.wear_sync_device_list_message).text = getString(messageRes)
|
||||
val container = content.findViewById<LinearLayout>(R.id.wear_sync_device_list_container)
|
||||
|
||||
knownAddresses.forEach { address ->
|
||||
val name = deviceName(deviceMap[address], address)
|
||||
val itemView = LayoutInflater.from(context).inflate(R.layout.wear_sync_device_list_item, container, false) as TextView
|
||||
itemView.text = getString(R.string.settings_wear_sync_device_name, name, address)
|
||||
itemView.setOnClickListener { onRemove(address, name) }
|
||||
container.addView(itemView)
|
||||
}
|
||||
|
||||
currentDevicesDialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(titleRes)
|
||||
.setView(content)
|
||||
.setOnDismissListener {
|
||||
currentDevicesDialog = null
|
||||
currentDevicesDialogIsBlocked = false
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.dismiss() }
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun showWearSyncAllowedDevicesDialog() {
|
||||
showWearSyncDeviceListDialog(
|
||||
R.string.settings_wear_sync_devices,
|
||||
R.string.settings_wear_sync_no_devices,
|
||||
R.string.settings_wear_sync_tap_to_remove,
|
||||
WearBluetoothSecurity.listTrustedDevices(requireContext()),
|
||||
isBlocked = false
|
||||
) { address, name -> confirmRemoveAllowedDevice(address, name) }
|
||||
}
|
||||
|
||||
private fun showWearSyncBlockedDevicesDialog() {
|
||||
showWearSyncDeviceListDialog(
|
||||
R.string.settings_wear_sync_blocked_devices,
|
||||
R.string.settings_wear_sync_no_blocked_devices,
|
||||
R.string.settings_wear_sync_tap_to_remove,
|
||||
WearBluetoothSecurity.listBlockedDevices(requireContext()),
|
||||
isBlocked = true
|
||||
) { address, name -> confirmRemoveBlockedDevice(address, name) }
|
||||
}
|
||||
|
||||
private fun confirmRemoveAllowedDevice(address: String, name: String) {
|
||||
confirmRemoveDevice(
|
||||
address,
|
||||
name,
|
||||
R.string.settings_wear_sync_unpair_message,
|
||||
onRemove = { WearBluetoothSecurity.untrustDevice(requireContext(), address) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun confirmRemoveBlockedDevice(address: String, name: String) {
|
||||
confirmRemoveDevice(
|
||||
address,
|
||||
name,
|
||||
R.string.settings_wear_sync_unblock_message,
|
||||
onRemove = { WearBluetoothSecurity.unblockDevice(requireContext(), address) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun confirmRemoveDevice(address: String, name: String, messageRes: Int, onRemove: () -> Unit) {
|
||||
val context = requireContext()
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.settings_wear_sync_unpair_title)
|
||||
.setMessage(getString(messageRes, name))
|
||||
.setPositiveButton(R.string.settings_wear_sync_unpair) { dialog, _ ->
|
||||
onRemove()
|
||||
BluetoothPairingNotificationManager.cancel(context, address)
|
||||
BluetoothPairingNotificationManager.notifyDevicesChanged(context)
|
||||
findPreference<Preference>(getString(R.string.settings_key_wear_sync_devices))?.let { updateWearSyncAllowedDevicesSummary(it) }
|
||||
findPreference<Preference>(getString(R.string.settings_key_wear_sync_blocked_devices))?.let { updateWearSyncBlockedDevicesSummary(it) }
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.dismiss() }
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun deviceName(device: BluetoothDevice?, fallback: String): String {
|
||||
return try { device?.name } catch (_: SecurityException) { null } ?: fallback
|
||||
}
|
||||
|
||||
private fun showWearSyncPermissionDeniedSnackbar() {
|
||||
Snackbar.make(
|
||||
requireView(),
|
||||
R.string.wear_sync_permission_required,
|
||||
Snackbar.LENGTH_LONG
|
||||
).setAction(R.string.open_settings) {
|
||||
startActivity(
|
||||
Intent(
|
||||
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
||||
Uri.fromParts("package", requireContext().packageName, null)
|
||||
)
|
||||
)
|
||||
}.show()
|
||||
}
|
||||
|
||||
private fun refreshActivity() {
|
||||
mReloadMain = true
|
||||
activity?.recreate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package protect.card_locker.wearos
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import protect.card_locker.R
|
||||
import protect.card_locker.shared.WearBluetoothSecurity
|
||||
|
||||
class BluetoothPairingActivity : AppCompatActivity() {
|
||||
|
||||
private var currentDialog: AlertDialog? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
showPairingDialog(intent)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
showPairingDialog(intent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
currentDialog?.dismiss()
|
||||
currentDialog = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun showPairingDialog(intent: Intent) {
|
||||
val address = intent.getStringExtra(BluetoothPairingConstants.DEVICE_ADDRESS) ?: return finish()
|
||||
val deviceName = intent.getStringExtra(BluetoothPairingConstants.DEVICE_NAME) ?: address
|
||||
val token = intent.getStringExtra(BluetoothPairingConstants.DEVICE_TOKEN)
|
||||
val isMismatch = intent.getBooleanExtra(BluetoothPairingConstants.IS_MISMATCH, false)
|
||||
|
||||
val titleRes = if (isMismatch) {
|
||||
R.string.wear_bt_pairing_mismatch_title
|
||||
} else {
|
||||
R.string.wear_bt_pairing_title
|
||||
}
|
||||
val messageRes = if (isMismatch) {
|
||||
R.string.wear_bt_pairing_mismatch_message
|
||||
} else {
|
||||
R.string.wear_bt_pairing_message
|
||||
}
|
||||
|
||||
currentDialog?.dismiss()
|
||||
currentDialog = MaterialAlertDialogBuilder(this)
|
||||
.setTitle(getString(titleRes, deviceName))
|
||||
.setMessage(getString(messageRes, deviceName))
|
||||
.setPositiveButton(R.string.wear_bt_pairing_allow) { _, _ ->
|
||||
WearBluetoothSecurity.trustDevice(this, address, token)
|
||||
BluetoothPairingNotificationManager.updateResultNotification(this, address, deviceName, true)
|
||||
BluetoothPairingNotificationManager.notifyDevicesChanged(this)
|
||||
finish()
|
||||
}
|
||||
.setNegativeButton(R.string.wear_bt_pairing_block) { _, _ ->
|
||||
WearBluetoothSecurity.blockDevice(this, address)
|
||||
BluetoothPairingNotificationManager.updateResultNotification(this, address, deviceName, false)
|
||||
BluetoothPairingNotificationManager.notifyDevicesChanged(this)
|
||||
finish()
|
||||
}
|
||||
.setOnCancelListener { finish() }
|
||||
.setCancelable(true)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package protect.card_locker.wearos
|
||||
|
||||
object BluetoothPairingConstants {
|
||||
const val DEVICE_ADDRESS = "device_address"
|
||||
const val DEVICE_NAME = "device_name"
|
||||
const val DEVICE_TOKEN = "device_token"
|
||||
const val IS_MISMATCH = "is_mismatch"
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package protect.card_locker.wearos
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import protect.card_locker.NotificationInfo
|
||||
import protect.card_locker.R
|
||||
|
||||
object BluetoothPairingNotificationManager {
|
||||
|
||||
const val ACTION_DEVICES_CHANGED = "protect.card_locker.wearos.ACTION_DEVICES_CHANGED"
|
||||
|
||||
fun notifyDevicesChanged(context: Context) {
|
||||
context.sendBroadcast(Intent(ACTION_DEVICES_CHANGED).apply { setPackage(context.packageName) })
|
||||
}
|
||||
|
||||
fun showAuthorizationNotification(
|
||||
context: Context,
|
||||
deviceName: String,
|
||||
address: String,
|
||||
token: String,
|
||||
isMismatch: Boolean = false
|
||||
) {
|
||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
NotificationInfo.WearBluetooth.PAIRING_CHANNEL_ID,
|
||||
context.getString(R.string.wear_bt_pairing_channel_name),
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
)
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
val titleRes = if (isMismatch) {
|
||||
R.string.wear_bt_pairing_mismatch_title
|
||||
} else {
|
||||
R.string.wear_bt_pairing_title
|
||||
}
|
||||
val messageRes = if (isMismatch) {
|
||||
R.string.wear_bt_pairing_mismatch_message
|
||||
} else {
|
||||
R.string.wear_bt_pairing_message
|
||||
}
|
||||
|
||||
val allowIntent = Intent(context, BluetoothPairingReceiver::class.java).apply {
|
||||
action = BluetoothPairingReceiver.ACTION_ALLOW
|
||||
putExtra(BluetoothPairingConstants.DEVICE_ADDRESS, address)
|
||||
putExtra(BluetoothPairingConstants.DEVICE_NAME, deviceName)
|
||||
putExtra(BluetoothPairingConstants.DEVICE_TOKEN, token)
|
||||
}
|
||||
val blockIntent = Intent(context, BluetoothPairingReceiver::class.java).apply {
|
||||
action = BluetoothPairingReceiver.ACTION_BLOCK
|
||||
putExtra(BluetoothPairingConstants.DEVICE_ADDRESS, address)
|
||||
putExtra(BluetoothPairingConstants.DEVICE_NAME, deviceName)
|
||||
putExtra(BluetoothPairingConstants.IS_MISMATCH, isMismatch)
|
||||
}
|
||||
|
||||
val allowPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
address.hashCode(),
|
||||
allowIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val blockPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
address.hashCode() + 1,
|
||||
blockIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val contentIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
address.hashCode() + 2,
|
||||
Intent(context, BluetoothPairingActivity::class.java).apply {
|
||||
putExtra(BluetoothPairingConstants.DEVICE_ADDRESS, address)
|
||||
putExtra(BluetoothPairingConstants.DEVICE_NAME, deviceName)
|
||||
putExtra(BluetoothPairingConstants.DEVICE_TOKEN, token)
|
||||
putExtra(BluetoothPairingConstants.IS_MISMATCH, isMismatch)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val notification = NotificationCompat.Builder(context, NotificationInfo.WearBluetooth.PAIRING_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification_catima)
|
||||
.setContentTitle(context.getString(titleRes, deviceName))
|
||||
.setContentText(context.getString(messageRes, deviceName))
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(context.getString(messageRes, deviceName)))
|
||||
.addAction(0, context.getString(R.string.wear_bt_pairing_allow), allowPendingIntent)
|
||||
.addAction(0, context.getString(R.string.wear_bt_pairing_block), blockPendingIntent)
|
||||
.setContentIntent(contentIntent)
|
||||
.setAutoCancel(false)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_STATUS)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(notificationId(address), notification)
|
||||
}
|
||||
|
||||
fun cancel(context: Context, address: String) {
|
||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.cancel(notificationId(address))
|
||||
}
|
||||
|
||||
fun updateResultNotification(context: Context, address: String, deviceName: String, allowed: Boolean) {
|
||||
cancel(context, address)
|
||||
val message = if (allowed) {
|
||||
context.getString(R.string.wear_bt_pairing_allowed, deviceName)
|
||||
} else {
|
||||
context.getString(R.string.wear_bt_pairing_blocked, deviceName)
|
||||
}
|
||||
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
fun notificationId(address: String): Int = address.hashCode()
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package protect.card_locker.wearos
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import protect.card_locker.shared.WearBluetoothSecurity
|
||||
|
||||
class BluetoothPairingReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val address = intent.getStringExtra(BluetoothPairingConstants.DEVICE_ADDRESS) ?: return
|
||||
val deviceName = intent.getStringExtra(BluetoothPairingConstants.DEVICE_NAME) ?: address
|
||||
val token = intent.getStringExtra(BluetoothPairingConstants.DEVICE_TOKEN)
|
||||
|
||||
when (intent.action) {
|
||||
ACTION_ALLOW -> {
|
||||
WearBluetoothSecurity.trustDevice(context, address, token)
|
||||
BluetoothPairingNotificationManager.updateResultNotification(context, address, deviceName, true)
|
||||
BluetoothPairingNotificationManager.notifyDevicesChanged(context)
|
||||
}
|
||||
ACTION_BLOCK -> {
|
||||
WearBluetoothSecurity.blockDevice(context, address)
|
||||
BluetoothPairingNotificationManager.updateResultNotification(context, address, deviceName, false)
|
||||
BluetoothPairingNotificationManager.notifyDevicesChanged(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ACTION_ALLOW = "protect.card_locker.wearos.action.ALLOW_DEVICE"
|
||||
const val ACTION_BLOCK = "protect.card_locker.wearos.action.BLOCK_DEVICE"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
package protect.card_locker.wearos
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.BluetoothServerSocket
|
||||
import android.bluetooth.BluetoothSocket
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import protect.card_locker.DBHelper
|
||||
import protect.card_locker.NotificationInfo
|
||||
import protect.card_locker.R
|
||||
import protect.card_locker.Utils
|
||||
import protect.card_locker.shared.BluetoothPermissionHelper
|
||||
import protect.card_locker.shared.WearBluetoothProtocol
|
||||
import protect.card_locker.shared.WearBluetoothSecurity
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.io.OutputStreamWriter
|
||||
import java.io.PrintWriter
|
||||
|
||||
class BluetoothServerService : Service() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "CatimaBtServer"
|
||||
private const val NOTIFICATION_ID = NotificationInfo.WearBluetooth.NOTIFICATION_ID
|
||||
private const val CHANNEL_ID = NotificationInfo.WearBluetooth.CHANNEL_ID
|
||||
}
|
||||
|
||||
private var serverThread: AcceptThread? = null
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (!BluetoothPermissionHelper.isBluetoothConnectGranted(this)) {
|
||||
Log.w(TAG, "BLUETOOTH_CONNECT permission not granted, stopping")
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
val adapter = (getSystemService(BLUETOOTH_SERVICE) as? BluetoothManager)?.adapter
|
||||
if (adapter == null || !adapter.isEnabled) {
|
||||
Log.w(TAG, "Bluetooth not available or disabled")
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
// Avoid tearing down a working accept socket every time Settings is resumed.
|
||||
if (serverThread?.isAlive == true) {
|
||||
Log.d(TAG, "Bluetooth server already listening")
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
startForegroundWithNotification()
|
||||
serverThread?.cancel()
|
||||
serverThread = AcceptThread(adapter).also { it.start() }
|
||||
Log.d(TAG, "Bluetooth server started")
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
private fun startForegroundWithNotification() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
getString(R.string.wear_bt_channel_name),
|
||||
NotificationManager.IMPORTANCE_MIN
|
||||
).apply { setShowBadge(false) }
|
||||
getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
|
||||
}
|
||||
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle(getString(R.string.wear_bt_notification_title))
|
||||
.setSmallIcon(R.drawable.ic_notification_sync)
|
||||
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||
.setSilent(true)
|
||||
.build()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(NOTIFICATION_ID, notification,
|
||||
android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE)
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
serverThread?.cancel()
|
||||
serverThread = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
private inner class AcceptThread(adapter: BluetoothAdapter) : Thread() {
|
||||
private var serverSocket: BluetoothServerSocket? = null
|
||||
@Volatile private var running = true
|
||||
|
||||
init {
|
||||
try {
|
||||
if (BluetoothPermissionHelper.isBluetoothConnectGranted(this@BluetoothServerService)) {
|
||||
serverSocket = adapter.listenUsingRfcommWithServiceRecord(
|
||||
WearBluetoothProtocol.BT_SERVICE_NAME,
|
||||
WearBluetoothProtocol.BT_SERVICE_UUID
|
||||
)
|
||||
} else {
|
||||
Log.w(TAG, "BLUETOOTH_CONNECT permission missing, cannot open server socket")
|
||||
running = false
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "BLUETOOTH_CONNECT permission missing, cannot create server socket", e)
|
||||
running = false
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to create server socket", e)
|
||||
running = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
Log.d(TAG, "Listening for Bluetooth connections")
|
||||
while (running) {
|
||||
val socket: BluetoothSocket = try {
|
||||
serverSocket?.accept() ?: break
|
||||
} catch (e: Exception) {
|
||||
if (running) Log.e(TAG, "Accept failed", e)
|
||||
break
|
||||
}
|
||||
// Handle each client on its own thread so a slow/stuck client
|
||||
// cannot block the accept loop.
|
||||
Thread { handleConnection(socket) }.start()
|
||||
}
|
||||
Log.d(TAG, "Accept loop ended")
|
||||
if (running) {
|
||||
Log.w(TAG, "Accept loop exited unexpectedly, restarting service")
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleConnection(socket: BluetoothSocket) {
|
||||
val address = try { socket.remoteDevice.address } catch (_: SecurityException) { null } ?: return
|
||||
val deviceName = try { socket.remoteDevice.name } catch (_: SecurityException) { address }
|
||||
Log.d(TAG, "Connected to $deviceName ($address)")
|
||||
try {
|
||||
val reader = BufferedReader(InputStreamReader(socket.inputStream, "UTF-8"))
|
||||
val writer = PrintWriter(OutputStreamWriter(socket.outputStream, "UTF-8"), false)
|
||||
val firstLine = reader.readLine()?.trim() ?: return
|
||||
|
||||
if (firstLine == WearBluetoothProtocol.BT_CMD_VERSIONS) {
|
||||
val versions = JSONArray().put("${WearBluetoothProtocol.PROTOCOL_VERSION}.${WearBluetoothProtocol.PROTOCOL_MINOR_VERSION}").toString()
|
||||
writer.println(versions)
|
||||
writer.flush()
|
||||
Log.d(TAG, "Sent supported versions to $deviceName: $versions")
|
||||
return
|
||||
}
|
||||
|
||||
var commandLine: String? = firstLine
|
||||
while (commandLine != null) {
|
||||
val command = commandLine
|
||||
Log.d(TAG, "Received command: $command from $deviceName")
|
||||
|
||||
if (!command.startsWith(WearBluetoothProtocol.BT_CMD_VERSION_PREFIX)) {
|
||||
notAuthorized(writer)
|
||||
Log.w(TAG, "Unsupported command from $deviceName: $command")
|
||||
break
|
||||
}
|
||||
|
||||
val tokenLine = reader.readLine()?.trim()
|
||||
if (tokenLine == null || !tokenLine.startsWith(WearBluetoothProtocol.BT_CMD_TOKEN_PREFIX)) {
|
||||
notAuthorized(writer)
|
||||
Log.w(TAG, "Malformed token line from $deviceName: $tokenLine")
|
||||
break
|
||||
}
|
||||
val token = tokenLine.removePrefix(WearBluetoothProtocol.BT_CMD_TOKEN_PREFIX)
|
||||
|
||||
val isTrusted = WearBluetoothSecurity.isDeviceTrusted(this@BluetoothServerService, address)
|
||||
val storedToken = WearBluetoothSecurity.getDeviceToken(this@BluetoothServerService, address)
|
||||
val tokenMatches = storedToken != null && storedToken == token
|
||||
|
||||
when {
|
||||
WearBluetoothSecurity.isDeviceBlocked(this@BluetoothServerService, address) -> {
|
||||
notAuthorized(writer)
|
||||
Log.w(TAG, "Rejected blocked device $deviceName")
|
||||
break
|
||||
}
|
||||
isTrusted && tokenMatches -> {
|
||||
if (!serveCommand(writer, deviceName, command)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
isTrusted && !tokenMatches -> {
|
||||
BluetoothPairingNotificationManager.showAuthorizationNotification(
|
||||
this@BluetoothServerService,
|
||||
deviceName,
|
||||
address,
|
||||
token,
|
||||
isMismatch = true
|
||||
)
|
||||
notAuthorized(writer)
|
||||
Log.w(TAG, "Token mismatch for $deviceName")
|
||||
break
|
||||
}
|
||||
else -> {
|
||||
BluetoothPairingNotificationManager.showAuthorizationNotification(
|
||||
this@BluetoothServerService,
|
||||
deviceName,
|
||||
address,
|
||||
token,
|
||||
isMismatch = false
|
||||
)
|
||||
notAuthorized(writer)
|
||||
Log.d(TAG, "Requested authorization for $deviceName")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
commandLine = reader.readLine()?.trim()
|
||||
}
|
||||
} catch (_: IOException) {
|
||||
// Client closed the socket; this is a normal end-of-stream after
|
||||
// the watch has received the pages it needs.
|
||||
Log.d(TAG, "Connection closed by $deviceName")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error handling connection from $deviceName", e)
|
||||
} finally {
|
||||
try { socket.close() } catch (_: Exception) { }
|
||||
}
|
||||
}
|
||||
|
||||
private fun notAuthorized(writer: PrintWriter) {
|
||||
writer.println(WearBluetoothProtocol.BT_RESPONSE_NOT_AUTHORIZED)
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
private fun serveCommand(
|
||||
writer: PrintWriter,
|
||||
deviceName: String,
|
||||
command: String
|
||||
): Boolean {
|
||||
when {
|
||||
command.startsWith(WearBluetoothProtocol.BT_CMD_CARDS_PAGE_PREFIX) -> {
|
||||
val pageIndex = command.removePrefix(WearBluetoothProtocol.BT_CMD_CARDS_PAGE_PREFIX).toIntOrNull()
|
||||
if (pageIndex == null || pageIndex < 0) {
|
||||
Log.w(TAG, "Invalid page index from $deviceName: $command")
|
||||
notAuthorized(writer)
|
||||
return false
|
||||
}
|
||||
val json = buildCardsPageJson(pageIndex)
|
||||
writer.println(json)
|
||||
writer.flush()
|
||||
Log.d(TAG, "Sent page $pageIndex to $deviceName")
|
||||
return true
|
||||
}
|
||||
else -> {
|
||||
Log.w(TAG, "Unsupported command from $deviceName: $command")
|
||||
notAuthorized(writer)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildCardsPageJson(pageIndex: Int): String {
|
||||
val dbHelper = DBHelper(this@BluetoothServerService)
|
||||
val db = dbHelper.readableDatabase
|
||||
val order = Utils.getLoyaltyCardOrder(this@BluetoothServerService)
|
||||
val orderDirection = Utils.getLoyaltyCardOrderDirection(this@BluetoothServerService)
|
||||
val cursor = DBHelper.getLoyaltyCardCursor(
|
||||
db, "", null, order, orderDirection,
|
||||
DBHelper.LoyaltyCardArchiveFilter.Unarchived
|
||||
)
|
||||
val totalCards = cursor.count
|
||||
val totalPages = if (totalCards == 0) 1 else (totalCards + WearBluetoothProtocol.PAGE_SIZE - 1) / WearBluetoothProtocol.PAGE_SIZE
|
||||
val offset = pageIndex * WearBluetoothProtocol.PAGE_SIZE
|
||||
val array = JSONArray()
|
||||
cursor.use {
|
||||
if (it.move(offset + 1)) {
|
||||
val idIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID)
|
||||
val storeIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE)
|
||||
val cardIdIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID)
|
||||
val barcodeIdIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_ID)
|
||||
val barcodeTypeIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE)
|
||||
val headerColorIdx = it.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR)
|
||||
var count = 0
|
||||
do {
|
||||
array.put(JSONObject().apply {
|
||||
put("id", it.getInt(idIdx))
|
||||
put("store", it.getString(storeIdx) ?: "")
|
||||
put("cardId", it.getString(cardIdIdx) ?: "")
|
||||
put("barcodeId", if (it.isNull(barcodeIdIdx)) JSONObject.NULL else it.getString(barcodeIdIdx))
|
||||
put("barcodeType", if (it.isNull(barcodeTypeIdx)) JSONObject.NULL else it.getString(barcodeTypeIdx))
|
||||
put("headerColor", if (it.isNull(headerColorIdx)) JSONObject.NULL else it.getInt(headerColorIdx))
|
||||
})
|
||||
count++
|
||||
} while (count < WearBluetoothProtocol.PAGE_SIZE && it.moveToNext())
|
||||
}
|
||||
}
|
||||
return JSONObject().apply {
|
||||
put("page", pageIndex)
|
||||
put("totalPages", totalPages)
|
||||
put("cards", array)
|
||||
}.toString()
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
running = false
|
||||
try { serverSocket?.close() } catch (_: Exception) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package protect.card_locker.wearos
|
||||
|
||||
import android.Manifest
|
||||
import android.app.ForegroundServiceStartNotAllowedException
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.activity.result.ActivityResultCaller
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import protect.card_locker.NotificationInfo
|
||||
import protect.card_locker.R
|
||||
import protect.card_locker.preferences.Settings
|
||||
import protect.card_locker.shared.BluetoothPermissionHelper
|
||||
|
||||
class WearSyncPermissionRequester(
|
||||
caller: ActivityResultCaller,
|
||||
context: Context
|
||||
) {
|
||||
private val context = context.applicationContext
|
||||
private var pendingResultCallback: ((Boolean) -> Unit)? = null
|
||||
|
||||
private val permissionLauncher = caller.registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { results ->
|
||||
val allGranted = results.all { it.value }
|
||||
pendingResultCallback?.invoke(allGranted)
|
||||
WearSyncServiceManager.onPermissionResult(this.context, allGranted)
|
||||
pendingResultCallback = null
|
||||
}
|
||||
|
||||
fun synchronize() {
|
||||
WearSyncServiceManager.synchronize(context) {
|
||||
permissionLauncher.launch(getRequiredPermissions())
|
||||
}
|
||||
}
|
||||
|
||||
fun onWearSyncChanged(enabled: Boolean, onPermissionResult: ((Boolean) -> Unit)? = null) {
|
||||
val applied = WearSyncServiceManager.onWearSyncChanged(context, enabled) {
|
||||
pendingResultCallback = onPermissionResult
|
||||
permissionLauncher.launch(getRequiredPermissions())
|
||||
}
|
||||
if (applied) {
|
||||
onPermissionResult?.invoke(true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRequiredPermissions(): Array<String> = buildList {
|
||||
if (BluetoothPermissionHelper.isBluetoothConnectRequired()) {
|
||||
add(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
}
|
||||
if (BluetoothPermissionHelper.isPostNotificationsRequired()) {
|
||||
add(Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
object WearSyncServiceManager {
|
||||
private const val TAG = "CatimaWearSyncServiceManager"
|
||||
|
||||
fun synchronize(context: Context, requestPermission: (() -> Unit)? = null) {
|
||||
if (!Settings(context).wearSyncEnabled) {
|
||||
stop(context)
|
||||
} else if (arePermissionsGranted(context)) {
|
||||
start(context)
|
||||
} else if (requestPermission != null) {
|
||||
requestPermission()
|
||||
} else {
|
||||
stop(context)
|
||||
}
|
||||
}
|
||||
|
||||
fun onWearSyncChanged(
|
||||
context: Context,
|
||||
enabled: Boolean,
|
||||
requestPermission: () -> Unit
|
||||
): Boolean {
|
||||
return if (enabled) {
|
||||
if (arePermissionsGranted(context)) {
|
||||
start(context)
|
||||
true
|
||||
} else {
|
||||
requestPermission()
|
||||
false
|
||||
}
|
||||
} else {
|
||||
stop(context)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun arePermissionsGranted(context: Context): Boolean {
|
||||
return BluetoothPermissionHelper.isBluetoothConnectGranted(context) &&
|
||||
BluetoothPermissionHelper.isPostNotificationsGranted(context)
|
||||
}
|
||||
|
||||
fun onPermissionResult(context: Context, granted: Boolean) {
|
||||
if (granted && Settings(context).wearSyncEnabled) {
|
||||
start(context)
|
||||
} else {
|
||||
stop(context)
|
||||
if (!granted) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
putBoolean(context.getString(R.string.settings_key_wear_sync), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun start(context: Context) {
|
||||
// Exception can only throw on API level 31 and up
|
||||
if (Build.VERSION.SDK_INT >= 31) {
|
||||
try {
|
||||
ContextCompat.startForegroundService(
|
||||
context,
|
||||
Intent(context, BluetoothServerService::class.java)
|
||||
)
|
||||
} catch (e: ForegroundServiceStartNotAllowedException) {
|
||||
// We naively assume we have enough permissions to show a notification as the service should never try to start
|
||||
// before the user gave notification access anyway
|
||||
Log.d(TAG, "Foreground service failed to launch: $e")
|
||||
val channel = NotificationChannel(
|
||||
NotificationInfo.WearBluetooth.CRITICAL_ERROR_CHANNEL_ID,
|
||||
context.getString(R.string.wear_bt_critical_error_channel_name),
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply { setShowBadge(false) }
|
||||
context.getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
|
||||
val notification = NotificationCompat.Builder(context, NotificationInfo.WearBluetooth.CRITICAL_ERROR_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification_error)
|
||||
.setContentTitle(context.getString(R.string.wear_bt_failed_foreground_notification_title))
|
||||
.setContentText(e.message)
|
||||
.build()
|
||||
with(NotificationManagerCompat.from(context)) {
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return@with
|
||||
}
|
||||
notify(
|
||||
NotificationInfo.WearBluetooth.CRITICAL_ERROR_NOTIFICATION_ID,
|
||||
notification
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ContextCompat.startForegroundService(
|
||||
context,
|
||||
Intent(context, BluetoothServerService::class.java)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stop(context: Context) {
|
||||
context.stopService(Intent(context, BluetoothServerService::class.java))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
~ Copyright (C) 2026 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="#FFFFFF">
|
||||
<group android:scaleX="1.104"
|
||||
android:scaleY="1.104"
|
||||
android:translateX="-49.92"
|
||||
android:translateY="-49.92">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M508.5,668.5Q520,657 520,640Q520,623 508.5,611.5Q497,600 480,600Q463,600 451.5,611.5Q440,623 440,640Q440,657 451.5,668.5Q463,680 480,680Q497,680 508.5,668.5ZM440,520L520,520L520,280L440,280L440,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
~ Copyright (C) 2026 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="#FFFFFF">
|
||||
<group android:scaleX="0.92"
|
||||
android:scaleY="0.92"
|
||||
android:translateX="38.4"
|
||||
android:translateY="38.4">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M160,800L160,720L270,720L254,706Q202,660 181,601Q160,542 160,482Q160,371 226.5,284.5Q293,198 400,170L400,254Q328,280 284,342.5Q240,405 240,482Q240,527 257,569.5Q274,612 310,648L320,658L320,560L400,560L400,800L160,800ZM560,790L560,706Q632,680 676,617.5Q720,555 720,478Q720,433 703,390.5Q686,348 650,312L640,302L640,400L560,400L560,160L800,160L800,240L690,240L706,254Q755,303 777.5,360.5Q800,418 800,478Q800,589 733.5,675.5Q667,762 560,790Z"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
After Width: | Height: | Size: 790 B |
|
After Width: | Height: | Size: 752 B |
|
After Width: | Height: | Size: 499 B |
|
After Width: | Height: | Size: 509 B |
|
After Width: | Height: | Size: 478 B |
|
After Width: | Height: | Size: 346 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 919 B |
|
After Width: | Height: | Size: 656 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 918 B |
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M2,6h2v12L2,18L2,6zM5,6h1v12L5,18L5,6zM7,6h3v12L7,18L7,6zM11,6h1v12h-1L11,6zM13,6h2v12h-2L13,6zM16,6h2v12h-2L16,6zM19,6h1v12h-1L19,6zM21,6h1v12h-1L21,6z"/>
|
||||
</vector>
|
||||
@@ -109,7 +109,8 @@
|
||||
android:id="@+id/storeNameEdit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true" />
|
||||
android:singleLine="true"
|
||||
android:inputType="textCapWords" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -158,10 +158,31 @@
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
app:layout_constraintTop_toBottomOf="@+id/note"
|
||||
app:layout_constraintBottom_toTopOf="@+id/balance"
|
||||
app:layout_constraintBottom_toTopOf="@+id/cardId"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cardId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:textAppearance="?attr/textAppearanceBody2"
|
||||
app:drawableLeftCompat="@drawable/ic_card_id_24dp"
|
||||
android:drawablePadding="4dp"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@+id/info_divider"
|
||||
app:layout_constraintBottom_toTopOf="@+id/balance"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible"
|
||||
tools:text="1234567890"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/balance"
|
||||
android:layout_width="match_parent"
|
||||
@@ -174,7 +195,7 @@
|
||||
app:drawableLeftCompat="@drawable/ic_baseline_payments_24"
|
||||
android:drawablePadding="4dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@+id/info_divider"
|
||||
app:layout_constraintTop_toBottomOf="@+id/cardId"
|
||||
app:layout_constraintBottom_toTopOf="@+id/validFrom"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/wear_sync_device_list_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wear_sync_device_list_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="?attr/dialogPreferredPadding"
|
||||
android:paddingEnd="?attr/dialogPreferredPadding"
|
||||
style="?attr/materialAlertDialogBodyTextStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/listPreferredItemHeightSmall"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingLeft"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingRight"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:ellipsize="marquee"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
style="?attr/materialAlertDialogBodyTextStyle" />
|
||||
@@ -3,100 +3,109 @@ Branden Archer
|
||||
J. Lavoie
|
||||
solokot
|
||||
Allan Nordhøy
|
||||
Heimen Stoffels
|
||||
Oğuz Ersen
|
||||
FC (Fay) Stegerman
|
||||
Heimen Stoffels
|
||||
B o d o
|
||||
StoyanDimitrov
|
||||
FC (Fay) Stegerman
|
||||
大王叫我来巡山
|
||||
StoyanDimitrov
|
||||
SlavekB
|
||||
Katharine Chui
|
||||
Katharine Chui / Katie
|
||||
Silvério Santos
|
||||
mondstern
|
||||
IllusiveMan196
|
||||
Silvério Santos
|
||||
Максим Горпиніч
|
||||
Edgars Andersons
|
||||
Altonss
|
||||
Joel A
|
||||
Michael Moroni
|
||||
Liner Seven
|
||||
Priit Jõerüüt
|
||||
Fjuro
|
||||
GitSpoon
|
||||
Joel A
|
||||
Altonss
|
||||
Priit Jõerüüt
|
||||
Michael Moroni
|
||||
Eric
|
||||
Максим Горпиніч
|
||||
GM
|
||||
Petr Novák
|
||||
laralem
|
||||
Vasilis
|
||||
Taco
|
||||
Milo Ivir
|
||||
nadiafekihahmed
|
||||
Горпиніч Максим Олександрович
|
||||
pfaffenrodt
|
||||
Aayush Gupta
|
||||
damjang
|
||||
Scrambled777
|
||||
josé m
|
||||
Vasilis
|
||||
ikanakova
|
||||
Nyatsuki
|
||||
Kachelkaiser
|
||||
Giovanni Donisi
|
||||
Milo Ivir
|
||||
HudobniVolk
|
||||
Jiri Grönroos
|
||||
Warder
|
||||
Samantaz Fox
|
||||
Balázs Meskó
|
||||
Giovanni Donisi
|
||||
Kachelkaiser
|
||||
109247019824
|
||||
Feike Donia
|
||||
Arno-github
|
||||
Ankit Tiwari
|
||||
HudobniVolk
|
||||
Warder
|
||||
Balázs Meskó
|
||||
Traductor
|
||||
Samantaz Fox
|
||||
mdvhimself
|
||||
AMIR-G98
|
||||
Cliff Heraldo
|
||||
Sergio Paredes
|
||||
Ankit Tiwari
|
||||
Feike
|
||||
Arno-github
|
||||
Jose Delvani
|
||||
damjang
|
||||
mdvhimself
|
||||
Milan Šalka
|
||||
AMIR-G98
|
||||
Robin
|
||||
தமிழ்நேரம்
|
||||
huuhaa
|
||||
Skrripy
|
||||
NooB9496
|
||||
Govindgopalyadav
|
||||
Gideon
|
||||
waffshappen
|
||||
Marnick L'Eau
|
||||
JungHee Lee
|
||||
pacavarn
|
||||
ngocanhtve
|
||||
aradxxx
|
||||
Watchman89
|
||||
StellarSand
|
||||
Quentin PAGÈS
|
||||
Projjal Moitra
|
||||
Traductor
|
||||
Aliaksandr Trush
|
||||
e-michalak
|
||||
JungHee Lee
|
||||
தமிழ் நேரம்
|
||||
josé m.
|
||||
hajertabbane
|
||||
inavleb
|
||||
Ziad OUALHADJ
|
||||
Robin Liu
|
||||
Ricky Tigg
|
||||
Renko
|
||||
Gideon
|
||||
Denis Shilin
|
||||
しいたけ
|
||||
Alexander Ivanov
|
||||
Miha Frangež
|
||||
stavpup
|
||||
mrestivill
|
||||
ehrt74
|
||||
Virginie
|
||||
Thu-Trang Pham
|
||||
Tim Trek
|
||||
Peter Dave Hello
|
||||
Virginie
|
||||
dukezas
|
||||
ehrt74
|
||||
ezn24
|
||||
mrestivill
|
||||
stavpup
|
||||
Miha Frangež
|
||||
Alì Mortacci
|
||||
Andre Costa
|
||||
arshbeerSingh
|
||||
Augustin LAVILLE
|
||||
Freddo espresso
|
||||
Vasudev R.
|
||||
Govind S Nair
|
||||
Kim Seohyun
|
||||
Libre
|
||||
echo r"0xX4H" | rev
|
||||
rudy3
|
||||
Michael Gangolf
|
||||
PRATHAMESH BHAGAT
|
||||
Peter Dave Hello
|
||||
Thomas Bertels
|
||||
@@ -1,39 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="action_search">بحث</string>
|
||||
<string name="action_add">أضف</string>
|
||||
<string name="noGiftCards">اضغط على زر الإضافة + لإضافة بطاقة، أو استورد من خلال⋮ القائمة</string>
|
||||
<string name="noMatchingGiftCards">لا نتائج. حاول تغيير كلمات البحث.</string>
|
||||
<string name="storeName">اسم</string>
|
||||
<string name="note">مذكرة</string>
|
||||
<string name="action_add">إضافة</string>
|
||||
<string name="noGiftCards">انقر على زر + لإضافة بطاقة، أو استوردها من قائمة ⋮</string>
|
||||
<string name="noMatchingGiftCards">لا توجد نتائج. حاول تغيير بحثك.</string>
|
||||
<string name="storeName">الاسم</string>
|
||||
<string name="note">ملاحظة</string>
|
||||
<string name="cardId">معرّف البطاقة</string>
|
||||
<string name="barcodeType">نوع الباركود</string>
|
||||
<string name="noBarcode">لا يوجد باركود</string>
|
||||
<string name="star">أضف الى المفضلة</string>
|
||||
<string name="unstar">حذف من المفضلة</string>
|
||||
<string name="star">إضافة إلى المفضلة</string>
|
||||
<string name="unstar">إزالة من المفضلة</string>
|
||||
<string name="cancel">إلغاء</string>
|
||||
<string name="save">حفظ</string>
|
||||
<string name="edit">تعديل</string>
|
||||
<string name="delete">إحذف</string>
|
||||
<string name="delete">حذف</string>
|
||||
<string name="confirm">تأكيد</string>
|
||||
<string name="deleteConfirmation">إحذف هذه البطاقة نهائيا؟</string>
|
||||
<string name="ok">حسنا</string>
|
||||
<string name="share">شارك</string>
|
||||
<string name="sendLabel">ارسل…</string>
|
||||
<string name="editCardTitle">عدل البطاقة</string>
|
||||
<string name="addCardTitle">اضف بطاقة</string>
|
||||
<string name="scanCardBarcode">مسح باركود</string>
|
||||
<string name="deleteConfirmation">هل ترغب في حذف هذه البطاقة نهائيًّا؟</string>
|
||||
<string name="ok">حسنًا</string>
|
||||
<string name="share">مشاركة</string>
|
||||
<string name="sendLabel">إرسال…</string>
|
||||
<string name="editCardTitle">تعديل البطاقة</string>
|
||||
<string name="addCardTitle">إضافة بطاقة</string>
|
||||
<string name="scanCardBarcode">مسح الباركود</string>
|
||||
<string name="cardShortcut">اختصار البطاقة</string>
|
||||
<string name="noCardsMessage">أضف بطاقة أولا</string>
|
||||
<string name="noCardsMessage">أضف بطاقة أولًا</string>
|
||||
<string name="barcodeImageDescriptionWithType">صورة <xliff:g>%s</xliff:g> الباركود</string>
|
||||
<string name="noCardExistsError">لم يعثر على هذه البطاقة</string>
|
||||
<string name="failedParsingImportUriError">لا يمكن تحليل الرابط المستورد</string>
|
||||
<string name="noCardExistsError">لم يتمّ العثور على تلك البطاقة</string>
|
||||
<string name="failedParsingImportUriError">لا يمكن تحليل عنوان URL المستورد</string>
|
||||
<string name="importExport">استيراد/تصدير</string>
|
||||
<string name="importExportHelp">إنشاء نسخة احتياطية من بياناتك تمكنك من نقلها إلى جهاز آخر</string>
|
||||
<string name="importFailed">تعذر إجراء الاستيراد</string>
|
||||
<string name="exportSuccessfulTitle">مصدّر</string>
|
||||
<string name="importExportHelp">تتيح لك عملية النسخ الاحتياطي لبياناتك نقلها إلى جهاز آخر</string>
|
||||
<string name="importFailed">تعذّر إجراء عملية الاستيراد</string>
|
||||
<string name="exportSuccessfulTitle">تمّ التصدير</string>
|
||||
<string name="exportFailedTitle">فشل التصدير</string>
|
||||
<string name="exportFailed">تعذر التصدير</string>
|
||||
<string name="exportFailed">تعذّر إجراء عملية التصدير</string>
|
||||
<string name="exportOptionExplanation">ستكون البيانات مكتوبة في المسار الذي تختاره</string>
|
||||
<string name="importOptionFilesystemButton">من نظام الملفات</string>
|
||||
<string name="about">حول</string>
|
||||
@@ -122,21 +122,21 @@
|
||||
<string name="archived">تمت أرشفة البطاقة</string>
|
||||
<string name="unarchived">البطاقة غير مؤرشفة</string>
|
||||
<string name="failedLaunchingPhotoPicker">تعذر العثور على تطبيق معرض مدعوم</string>
|
||||
<string name="noGiftCardsGroup">أنشئ بعض البطاقات، ثم عيّنهم لهذه المجموعة</string>
|
||||
<string name="deleteTitle">إحذف بطاقة</string>
|
||||
<string name="noGiftCardsGroup">أنشئ بعض البطاقات، ثم عيّنها للمجموعة هنا</string>
|
||||
<string name="deleteTitle">حذف البطاقة</string>
|
||||
<plurals name="selectedCardCount">
|
||||
<item quantity="zero"><xliff:g>%d</xliff:g> محدد</item>
|
||||
<item quantity="zero"><xliff:g>%d</xliff:g> محددة</item>
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> محددة</item>
|
||||
<item quantity="two"><xliff:g>%d</xliff:g> محدد</item>
|
||||
<item quantity="few"><xliff:g>%d</xliff:g> محدد</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> محدد</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> محدد</item>
|
||||
<item quantity="two"><xliff:g>%d</xliff:g> محددتين</item>
|
||||
<item quantity="few"><xliff:g>%d</xliff:g> محددة</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> محددة</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> محددة</item>
|
||||
</plurals>
|
||||
<string name="importing">جار الاستيراد…</string>
|
||||
<string name="importing">جارٍ الاستيراد…</string>
|
||||
<string name="exportName">تصدير</string>
|
||||
<string name="exporting">جار التصدير…</string>
|
||||
<string name="importSuccessfulTitle">مستورد</string>
|
||||
<string name="importFailedTitle">فشل بالاستيراد</string>
|
||||
<string name="importSuccessfulTitle">تمّ الاستيراد</string>
|
||||
<string name="importFailedTitle">فشل الاستيراد</string>
|
||||
<string name="sort_by_most_recently_used">الأكثر أستعمالا مؤخرا</string>
|
||||
<string name="license">رخصة</string>
|
||||
<string name="sort_by_name">اسم</string>
|
||||
@@ -150,20 +150,20 @@
|
||||
<item quantity="other"><xliff:g>%1$d</xliff:g> بطاقات ( <xliff:g id="archivedCount">%2$d</xliff:g> مؤرشفة)</item>
|
||||
</plurals>
|
||||
<plurals name="deleteCardsTitle">
|
||||
<item quantity="zero">إحذف <xliff:g>%d</xliff:g> بطاقة</item>
|
||||
<item quantity="one">إحذف <xliff:g>%d</xliff:g> بطاقة</item>
|
||||
<item quantity="two">إحذف <xliff:g>%d</xliff:g> بطاقتين</item>
|
||||
<item quantity="few">إحذف <xliff:g>%d</xliff:g> بطائق</item>
|
||||
<item quantity="many">إحذف <xliff:g>%d</xliff:g> بطاقة</item>
|
||||
<item quantity="other">إحذف <xliff:g>%d</xliff:g> بطاقة</item>
|
||||
<item quantity="zero">حذف <xliff:g>%d</xliff:g> بطاقة</item>
|
||||
<item quantity="one">حذف <xliff:g>%d</xliff:g> بطاقة</item>
|
||||
<item quantity="two">حذف <xliff:g>%d</xliff:g> بطاقتَين</item>
|
||||
<item quantity="few">حذف <xliff:g>%d</xliff:g> من البطاقات</item>
|
||||
<item quantity="many">حذف <xliff:g>%d</xliff:g> من البطاقات</item>
|
||||
<item quantity="other">حذف <xliff:g>%d</xliff:g> من البطاقات</item>
|
||||
</plurals>
|
||||
<plurals name="deleteCardsConfirmation">
|
||||
<item quantity="zero">إحذف هذه <xliff:g>%d</xliff:g> البطاقة نهائيا؟</item>
|
||||
<item quantity="one">إحذف هذه <xliff:g>%d</xliff:g> البطاقة نهائيا؟</item>
|
||||
<item quantity="two">إحذف هذه <xliff:g>%d</xliff:g> البطاقتين نهائيا؟</item>
|
||||
<item quantity="few">إحذف هذه <xliff:g>%d</xliff:g> البطاقات نهائيا؟</item>
|
||||
<item quantity="many">إحذف هذه <xliff:g>%d</xliff:g> البطاقات نهائيا؟</item>
|
||||
<item quantity="other">إحذف هذه <xliff:g>%d</xliff:g> البطاقات نهائيا؟</item>
|
||||
<item quantity="zero">حذف هذه <xliff:g>%d</xliff:g> البطاقة نهائيًا؟</item>
|
||||
<item quantity="one">حذف هذه <xliff:g>%d</xliff:g> البطاقة نهائيًا؟</item>
|
||||
<item quantity="two">حذف هذه <xliff:g>%d</xliff:g> البطاقتين نهائيًا؟</item>
|
||||
<item quantity="few">هل ترغب في حذف هذه البطاقات الـ <xliff:g>%d</xliff:g> نهائيًّا؟</item>
|
||||
<item quantity="many">هل ترغب في حذف هذه البطاقات الـ <xliff:g>%d</xliff:g> نهائيًّا؟</item>
|
||||
<item quantity="other">هل ترغب في حذف هذه البطاقات الـ <xliff:g>%d</xliff:g> نهائيًا؟</item>
|
||||
</plurals>
|
||||
<string name="importOptionFilesystemTitle">الاستيراد من نظام الملفات</string>
|
||||
<string name="importOptionFilesystemExplanation">اختر ملفًا محددًا من نظام الملفات</string>
|
||||
@@ -220,7 +220,6 @@
|
||||
<string name="previousCard">السابق</string>
|
||||
<string name="nextCard">التالي</string>
|
||||
<string name="failedToRetrieveImageFile">فشل في استخراج ملف الصورة</string>
|
||||
<string name="barcodeLongPressMessage">يمكن فتح صور فقط في تطبيق معرض الصور</string>
|
||||
<string name="failedToOpenUrl">ثبت متصفح ويب أولاً</string>
|
||||
<string name="welcome">مرحبًا بك في كاتيما</string>
|
||||
<string name="updateBalanceTitle">كم أنفقت أو استلمت؟</string>
|
||||
@@ -243,7 +242,6 @@
|
||||
<string name="permissionReadCardsLabel">اقرأ بطاقات كاتيما</string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">حقوق النشر © 2019–<xliff:g>%d</xliff:g> Sylvia van Os والمساهمون</string>
|
||||
<string name="app_copyright_short">حقوق النشر © Sylvia van Os والمساهمون</string>
|
||||
<string name="openFrontImageInGalleryApp">افتح الصورة الأمامية في تطبيق المعرض</string>
|
||||
<string name="show_name_below_image_thumbnail">إظهار الاسم أسفل الصورة المصغرة</string>
|
||||
<string name="show_validity">إظهار الصلاحية</string>
|
||||
<string name="view_online">مشاهدة حية</string>
|
||||
@@ -259,7 +257,6 @@
|
||||
<string name="switchToFrontImage">التبديل إلى الصورة الأمامية</string>
|
||||
<string name="settings_allow_content_provider_read_summary">سيظل يتعين على التطبيقات طلب الإذن لمنحها حق الوصول</string>
|
||||
<string name="setBarcodeHeight">ضبط ارتفاع الباركود</string>
|
||||
<string name="openBackImageInGalleryApp">فتح الصورة الخلفية في تطبيق المعرض</string>
|
||||
<string name="settings_allow_content_provider_read_title">السماح للتطبيقات الأخرى بالوصول إلى بياناتي</string>
|
||||
<string name="donate">تبرّع</string>
|
||||
<string name="show_archived_cards">عرض البطاقات المؤرشفة</string>
|
||||
@@ -325,4 +322,9 @@
|
||||
<string name="back">رجوع</string>
|
||||
<string name="pref_enable_acra">إخطار بإرسال تقارير الأخطاء</string>
|
||||
<string name="pref_enable_acra_summary">عند التفعيل, سيتم سؤالك للابلاغ بالاعطال عندما تحدث. لا ولن يتم الابلاغ بالاعطال تلفائيا.</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">وقف NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">وقف NFC لمنع NFC من العمل أثناء عرض بطاقة</string>
|
||||
<string name="nfc_blocked_while_viewing_card">NFC موقوف</string>
|
||||
<string name="change_settings">تغير الاعدادات</string>
|
||||
<string name="nfc_block_system_error">فشل في ايقاف NFC</string>
|
||||
</resources>
|
||||
@@ -163,7 +163,6 @@
|
||||
<string name="showMoreInfo">Паказаць інфармацыю</string>
|
||||
<string name="updateBalance">Абнавіць баланс</string>
|
||||
<string name="failedToRetrieveImageFile">Не ўдалося атрымаць файл відарысу</string>
|
||||
<string name="barcodeLongPressMessage">У галерэі можна адкрываць толькі відарысы</string>
|
||||
<string name="sort_by_name">Назва</string>
|
||||
<string name="sort_by_most_recently_used">Частата выкарыстання</string>
|
||||
<string name="sort_by_valid_from">Дзейнічае ад</string>
|
||||
@@ -203,7 +202,6 @@
|
||||
<string name="validFromSentence">Дзейнічае з: <xliff:g>%s</xliff:g></string>
|
||||
<string name="height">Вышыня</string>
|
||||
<string name="switchToFrontImage">Пераключыцца на пярэдні відарыс</string>
|
||||
<string name="openBackImageInGalleryApp">Адкрыць задні відарыс у галерэі</string>
|
||||
<string name="setBarcodeHeight">Задаць вышыню штрыхкода</string>
|
||||
<string name="donate">Ахвяраваць</string>
|
||||
<string name="icon_header_click_text">Доўгі націск для рэдагавання мініяцюры</string>
|
||||
@@ -288,7 +286,6 @@
|
||||
<string name="report_error">Паведаміць пра памылку</string>
|
||||
<string name="failedLaunchingPhotoPicker">Не атрымалася знайсці праграму для галерэі, якая падтрымліваецца</string>
|
||||
<string name="unsupportedFile">Гэты файл не падтрымліваецца</string>
|
||||
<string name="openFrontImageInGalleryApp">Адкрыць пярэдні відарыс у галерэі</string>
|
||||
<string name="field_must_not_be_empty">Поле не павінна быць пустым</string>
|
||||
<string name="add_manually_warning_title">Рэкамендуецца сканаванне</string>
|
||||
<string name="errorReadingFile">Не атрымалася прачытаць файл</string>
|
||||
|
||||
@@ -198,7 +198,6 @@
|
||||
<string name="nextCard">Следваща</string>
|
||||
<string name="failedToOpenUrl">Първо инсталирайте уеб браузър</string>
|
||||
<string name="welcome">Добре дошли при Катима</string>
|
||||
<string name="barcodeLongPressMessage">В приложението галерия могат да бъдат отваряни само изображения</string>
|
||||
<string name="failedToRetrieveImageFile">Не е възможно извличане на изображение</string>
|
||||
<string name="noCameraPermissionDirectToSystemSetting">За да сканирате щрихкодове с Catima е необходим достъп до камерата. За да промените разрешението докоснете тук.</string>
|
||||
<string name="updateBalanceTitle">Колко е похарчено или получено?</string>
|
||||
@@ -218,8 +217,6 @@
|
||||
<string name="switchToFrontImage">Показване на предната страна</string>
|
||||
<string name="switchToBackImage">Показване на задната страна</string>
|
||||
<string name="switchToBarcode">Показване на щрихкода</string>
|
||||
<string name="openFrontImageInGalleryApp">Отваряне на изображението на предната страна в приложение за преглед за изображения</string>
|
||||
<string name="openBackImageInGalleryApp">Отваряне на изображението на задната страна в приложение за преглед за изображения</string>
|
||||
<string name="setBarcodeHeight">Задаване на височина на щрихкода</string>
|
||||
<string name="donate">Даряване</string>
|
||||
<string name="icon_header_click_text">Задръжте, за да промените миниатюрата</string>
|
||||
@@ -301,4 +298,14 @@
|
||||
<string name="nothing_to_copy">Няма стойност</string>
|
||||
<string name="back">Назад</string>
|
||||
<string name="barcodeEncoding">Кодиране на щрихкода</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">Пауза на NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Спира NFC, за да бъдат предотвратени безконтактни плащания при преглед на карта</string>
|
||||
<string name="nfc_blocked_while_viewing_card">NFC е на пауза</string>
|
||||
<string name="change_settings">Настройки</string>
|
||||
<string name="nfc_block_system_error">Грешка при спиране на NFC</string>
|
||||
<string name="sort_by_most_recently_added">Поредност на добавяне</string>
|
||||
<string name="zoomFrontImage">Увеличаване на предното изображение</string>
|
||||
<string name="zoomBackImage">Увеличаване на задното изображение</string>
|
||||
<string name="show_card_id">Показване номера на картата</string>
|
||||
<string name="open_in_gallery">Отваряне в галерията</string>
|
||||
</resources>
|
||||
@@ -3,13 +3,13 @@
|
||||
<string name="save">সংরক্ষণ</string>
|
||||
<string name="cancel">বাতিল</string>
|
||||
<string name="unstar">তারা মুক্ত</string>
|
||||
<string name="star">তারা</string>
|
||||
<string name="barcodeType">বারকোড ধরন</string>
|
||||
<string name="note">বিঃদ্রঃ</string>
|
||||
<string name="storeName">দোকানের নাম</string>
|
||||
<string name="star">পছন্দের তালিকায় যোগ করুন</string>
|
||||
<string name="barcodeType">বারকোডের ধরন</string>
|
||||
<string name="note">উল্লেখ্য</string>
|
||||
<string name="storeName">নাম</string>
|
||||
<string name="noMatchingGiftCards">কোনো ফলাফল পাওয়া যায়নি। অনুসন্ধানের বাক্যটি বদলে দেখুন।</string>
|
||||
<string name="noGiftCards">প্লাস বোতামটি টিপে একটি কার্ড যোগ করুন বা ⋮ মেনু থেকে কিছু নিয়ে আসুন।</string>
|
||||
<string name="action_add">কর্ম যোগ</string>
|
||||
<string name="action_add">যোগ করুন</string>
|
||||
<string name="all">সব</string>
|
||||
<string name="noGroupCards">এই গ্রুপটি খালি</string>
|
||||
<string name="noGroups">+ যোগ বোতামটি টিপে গ্রুপ যোগ করুন যাতে তাদের শ্রেণীকরণ করা যায়।</string>
|
||||
@@ -193,7 +193,6 @@
|
||||
<string name="settings_oled_dark">অন্ধকার থিমের জন্য খাঁটি কালো পটভূমি</string>
|
||||
<string name="setIcon">আইকন সেট করুন</string>
|
||||
<string name="updateBalance">ব্যালেন্স আপডেট করুন</string>
|
||||
<string name="barcodeLongPressMessage">গ্যালারি অ্যাপে শুধুমাত্র ছবি খোলা যাবে</string>
|
||||
<string name="translate_platform">Weblate-এ</string>
|
||||
<string name="on_google_play">Google Play-তে</string>
|
||||
<string name="shortcutSelectCard">একটি কার্ড নির্বাচন করুন</string>
|
||||
@@ -225,8 +224,6 @@
|
||||
<string name="switchToBackImage">পিছনের ছবিটিতে সুইচ করুন</string>
|
||||
<string name="switchToFrontImage">সামনের ছবিটিতে সুইচ করুন</string>
|
||||
<string name="switchToBarcode">বারকোডে সুইচ করুন</string>
|
||||
<string name="openFrontImageInGalleryApp">সামনের ছবিটি গ্যালারি অ্যাপে খুলুন</string>
|
||||
<string name="openBackImageInGalleryApp">পিছনের ছবিটি গ্যালারি অ্যাপে খুলুন</string>
|
||||
<string name="setBarcodeHeight">বারকোডের উচ্চতা সেট করুন</string>
|
||||
<string name="icon_header_click_text">দীর্ঘক্ষন টাচ করে থাম্বনেইল এডিট করবেন</string>
|
||||
<string name="show_balance">ব্যালান্স দেখান</string>
|
||||
|
||||
@@ -174,7 +174,6 @@
|
||||
<string name="importLoyaltyCardKeychainMessage">Odaberite svoj izvoz iz Privjeska za ključeve s karticom vjernosti za uvoz.\nKreirajte ga iz menija Uvoz/Izvoz u Privjesku za ključeve s karticom vjernosti pritiskom na Izvoz.</string>
|
||||
<string name="importVoucherVaultMessage">Odaberite izvoz iz Voucher Vaulta za uvoz. \nKreirajte ga pritiskom na Izvoz u Voucher Vaultu.</string>
|
||||
<string name="switchToBarcode">Prebaci na barkod</string>
|
||||
<string name="openFrontImageInGalleryApp">Otvorite prednju sliku u aplikaciji za pregled slika</string>
|
||||
<string name="switchToBackImage">Prebaci na sliku iz pozadine</string>
|
||||
<string name="switchToFrontImage">Prebaci na prednju sliku</string>
|
||||
<string name="height">Visina</string>
|
||||
@@ -213,7 +212,6 @@
|
||||
<string name="credits">Zasluge</string>
|
||||
<string name="version_history">Historija verzija</string>
|
||||
<string name="sort_by_valid_from">Vrijedi od</string>
|
||||
<string name="barcodeLongPressMessage">U aplikaciji galerija mogu se otvoriti samo slike</string>
|
||||
<string name="failedToRetrieveImageFile">Neuspjevam preuzimanje slikovne datoteke</string>
|
||||
<string name="updateBalance">Ažuriraj stanje</string>
|
||||
<string name="showMoreInfo">Prikaži informacije</string>
|
||||
@@ -241,7 +239,6 @@
|
||||
<string name="group_name_is_empty">Naziv grupe ne smije biti prazan</string>
|
||||
<string name="group_updated">Grupa ažurirana</string>
|
||||
<string name="deleteConfirmationGroup">Izbrisati grupu?</string>
|
||||
<string name="openBackImageInGalleryApp">Otvori sliku pozadi u aplikaciji za pregled slika</string>
|
||||
<string name="setBarcodeHeight">Postavi visinu barkoda</string>
|
||||
<string name="donate">Donirajte</string>
|
||||
<string name="icon_header_click_text">Dugo pritisnite za uređivanje sličice</string>
|
||||
@@ -306,4 +303,5 @@
|
||||
<string name="pref_enable_acra_summary">Kada je omogućeno, bit ćete upitani da prijavite pad sistema kada se dogodi. Izvještaji o padu sistema se nikada ne šalju automatski.</string>
|
||||
<string name="copy_value">Kopiraj vrijednost</string>
|
||||
<string name="copied_to_clipboard">Kopirano u međuspremnik</string>
|
||||
<string name="importCatimaMessage">Odaberite izvoz iz Catima za uvoz. \nKreirajte ga iz menija Uvoz/izvoz druge Catima aplikacije pritiskom na Izvoz.</string>
|
||||
</resources>
|
||||
@@ -72,7 +72,6 @@
|
||||
<string name="no">No</string>
|
||||
<string name="sort">Ordenar</string>
|
||||
<string name="failedToRetrieveImageFile">Ha fallat l\'obtenció del fitxer d\'imatge</string>
|
||||
<string name="barcodeLongPressMessage">Les imatges només es poden obrir desde la app galeria</string>
|
||||
<string name="settings_category_title_privacy">Privacitat</string>
|
||||
<plurals name="deleteCardsTitle">
|
||||
<item quantity="one">Eliminar <xliff:g>%d</xliff:g> targeta</item>
|
||||
@@ -167,7 +166,6 @@
|
||||
<string name="noCardsMessage">Afegeix primer una targeta</string>
|
||||
<string name="noCardExistsError">No s\'ha pogut trobar aquesta targeta</string>
|
||||
<string name="failedParsingImportUriError">No s\'ha pogut analitzar l\'URI d\'importació</string>
|
||||
<string name="openFrontImageInGalleryApp">Obrir la imatge frontal a l\'app de galeria</string>
|
||||
<string name="settings_use_volume_keys_navigation_summary">Utilitza els botons de volum per canviar la targeta que es mostra</string>
|
||||
<string name="updateBarcodeQuestionText">Ha canviat el valor ID. Vol actualitzar també el codi de barres per uter utilitzar el mateix valor?</string>
|
||||
<string name="starred">Preferides</string>
|
||||
@@ -183,7 +181,6 @@
|
||||
<string name="on_github">al Github</string>
|
||||
<string name="include_if_asking_support">Si vol demanar suport tècnic, inclogui la següent informació:</string>
|
||||
<string name="newBalanceSentence">Nou balanç: <xliff:g>%s</xliff:g></string>
|
||||
<string name="openBackImageInGalleryApp">Obrir la imatge posterior a l\'app de la galeria</string>
|
||||
<string name="generic_error_please_retry">Perdoni, alguna cosa ha anat malament, siusplau torni-ho a provar...</string>
|
||||
<string name="settings_column_count_6">6</string>
|
||||
<string name="useBackImage">Utilitzar la imatge posterior</string>
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
<string name="nextCard">Následující</string>
|
||||
<string name="failedToOpenUrl">Nejprve nainstalujte webový prohlížeč</string>
|
||||
<string name="welcome">Vítejte v Catima</string>
|
||||
<string name="barcodeLongPressMessage">V aplikaci pro galerii mohou být otevírány pouze obrázky</string>
|
||||
<string name="failedToRetrieveImageFile">Nepodařilo se získat soubor obrázku</string>
|
||||
<string name="cameraPermissionDeniedTitle">Nepodařilo se získat přístup k fotoaparátu</string>
|
||||
<string name="importCards">Importovat karty</string>
|
||||
@@ -223,8 +222,6 @@
|
||||
<string name="switchToFrontImage">Přepnout na přední obrázek</string>
|
||||
<string name="switchToBackImage">Přepnout na zadní obrázek</string>
|
||||
<string name="switchToBarcode">Přepnout na čárový kód</string>
|
||||
<string name="openFrontImageInGalleryApp">Otevřít přední obrázek v aplikaci prohlížeče obrázků</string>
|
||||
<string name="openBackImageInGalleryApp">Otevřít zadní obrázek v aplikaci prohlížeče obrázků</string>
|
||||
<string name="setBarcodeHeight">Nastavit výšku čárového kódu</string>
|
||||
<string name="donate">Přispět</string>
|
||||
<string name="icon_header_click_text">Dlouhým stisknutím miniaturu upravíte</string>
|
||||
@@ -307,4 +304,14 @@
|
||||
<string name="nothing_to_copy">Nenalezena žádná hodnota</string>
|
||||
<string name="barcodeEncoding">Kódování čárového kódu</string>
|
||||
<string name="back">Zpět</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">Pozastavit NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Pozastaví NFC pro zabránění spuštění NFC plateb při zobrazování karty</string>
|
||||
<string name="nfc_blocked_while_viewing_card">NFC je pozastaveno</string>
|
||||
<string name="change_settings">Změnit nastavení</string>
|
||||
<string name="nfc_block_system_error">Nepodařilo se pozastavit NFC</string>
|
||||
<string name="sort_by_most_recently_added">Nedávno přidáno</string>
|
||||
<string name="zoomFrontImage">Přiblížit přední obrázek</string>
|
||||
<string name="zoomBackImage">Přiblížit zadní obrázek</string>
|
||||
<string name="show_card_id">Zobrazit ID karty</string>
|
||||
<string name="open_in_gallery">Otevřít v galerii</string>
|
||||
</resources>
|
||||
@@ -197,7 +197,6 @@
|
||||
<string name="nextCard">Nächste</string>
|
||||
<string name="failedToOpenUrl">Bitte installiere zuerst einen Webbrowser</string>
|
||||
<string name="welcome">Willkommen bei Catima</string>
|
||||
<string name="barcodeLongPressMessage">In der Galerie können nur Bilder geöffnet werden</string>
|
||||
<string name="failedToRetrieveImageFile">Bilddatei konnte nicht abgerufen werden</string>
|
||||
<string name="updateBalanceTitle">Wie viel hast du ausgegeben oder erhalten?</string>
|
||||
<string name="cameraPermissionDeniedTitle">Kein Zugriff auf die Kamera</string>
|
||||
@@ -215,8 +214,6 @@
|
||||
<string name="anyDate">Beliebiges Datum</string>
|
||||
<string name="icon_header_click_text">Zum Bearbeiten des Vorschaubildes lang drücken</string>
|
||||
<string name="switchToBarcode">Zum Barcode wechseln</string>
|
||||
<string name="openFrontImageInGalleryApp">Vorderseite in Bildbetrachter öffnen</string>
|
||||
<string name="openBackImageInGalleryApp">Rückseite in Bildbetrachter öffnen</string>
|
||||
<string name="height">Höhe</string>
|
||||
<string name="switchToFrontImage">Zur Vorderseite wechseln</string>
|
||||
<string name="switchToBackImage">Zur Rückseite wechseln</string>
|
||||
@@ -301,4 +298,14 @@
|
||||
<string name="nothing_to_copy">Keinen Betrag gefunden</string>
|
||||
<string name="barcodeEncoding">Barcode-Kodierung</string>
|
||||
<string name="back">Zurück</string>
|
||||
<string name="change_settings">Einstellungen ändern</string>
|
||||
<string name="nfc_blocked_while_viewing_card">NFC ist vorübergehend deaktiviert</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Deaktiviert vorübergehend NFC, sodass keine NFC-Zahlungen ausgelöst werden können, während eine Karte angezeigt wird</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">NFC vorübergehend deaktivieren</string>
|
||||
<string name="nfc_block_system_error">Pausieren von NFC ist fehlgeschlagen</string>
|
||||
<string name="show_card_id">Karten-ID anzeigen</string>
|
||||
<string name="zoomBackImage">Rückseite vergrößern</string>
|
||||
<string name="zoomFrontImage">Vorderseite vergrößern</string>
|
||||
<string name="sort_by_most_recently_added">Zuletzt hinzugefügt</string>
|
||||
<string name="open_in_gallery">In Bildergalerie öffnen</string>
|
||||
</resources>
|
||||
@@ -200,7 +200,6 @@
|
||||
<string name="previousCard">Προηγούμενη</string>
|
||||
<string name="nextCard">Επόμενη</string>
|
||||
<string name="updateBalance">Ενημέρωση υπολοίπου</string>
|
||||
<string name="barcodeLongPressMessage">Μόνο εικόνες μπορούν να ανοιχτούν στην εφαρμογή φωτογραφιών</string>
|
||||
<string name="noCameraPermissionDirectToSystemSetting">Για να σκανάρετε γραμμωτούς κώδικες, θα χρειαστεί πρόσβαση στην κάμερα από το Catima. Πατήστε εδώ για να δώσετε πρόσβαση.</string>
|
||||
<string name="importCards">Εισαγωγή καρτών</string>
|
||||
<string name="updateBalanceHint">Εισάγετε ποσό</string>
|
||||
@@ -219,11 +218,9 @@
|
||||
<string name="switchToBarcode">Μετάβαση σε γραμμωτό κώδικα</string>
|
||||
<string name="validFromSentence">Ισχύει από: <xliff:g>%s</xliff:g></string>
|
||||
<string name="permissionReadCardsLabel">Διαβάστε τις κάρτες Catima</string>
|
||||
<string name="openBackImageInGalleryApp">Ανοίξτε την οπίσθια εικόνα στη συλλογή εικόνων</string>
|
||||
<string name="permissionReadCardsDescription">Διάβασε τις Κάρτες σου Catima και όλες τους τις λεπτομέρειες, συμπεριλαμβανομένων των σημειώσεων και των εικόνων</string>
|
||||
<string name="donate">Δωρεά</string>
|
||||
<string name="icon_header_click_text">Πατήστε παρατεταμένα για επεξεργασία του εικονιδίου</string>
|
||||
<string name="openFrontImageInGalleryApp">Ανοίξτε την εμπρόσθια εικόνα στη συλλογή εικόνων</string>
|
||||
<string name="storageReadPermissionRequired">Δικαίωμα ανάγνωσης του χώρου αποθήκευσης απαραίτητο για αυτήν την ενέργεια…</string>
|
||||
<string name="validFromDate">Ισχύει από</string>
|
||||
<string name="anyDate">Οποιαδήποτε ημερομηνία</string>
|
||||
@@ -301,4 +298,14 @@
|
||||
<string name="nothing_to_copy">Δεν βρέθηκε τιμή</string>
|
||||
<string name="barcodeEncoding">Κωδικοποίηση γραμμωτού κώδικα</string>
|
||||
<string name="back">Πίσω</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">Παύση NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Παύση του NFC για την αποτροπή ενεργοποίησης πληρωμών NFC κατά την προβολή μιας κάρτας</string>
|
||||
<string name="nfc_blocked_while_viewing_card">Το NFC είναι σε παύση</string>
|
||||
<string name="change_settings">Αλλαγή ρυθμίσεων</string>
|
||||
<string name="nfc_block_system_error">Αποτυχία παύσης NFC</string>
|
||||
<string name="sort_by_most_recently_added">Πρόσφατα προστεθέντα</string>
|
||||
<string name="zoomFrontImage">Μεγέθυνση μπροστινής εικόνας</string>
|
||||
<string name="zoomBackImage">Μεγέθυνση πίσω εικόνας</string>
|
||||
<string name="show_card_id">Εμφάνιση ταυτότητας κάρτας</string>
|
||||
<string name="open_in_gallery">Άνοιγμα στη συλλογή εικόνων</string>
|
||||
</resources>
|
||||
@@ -165,7 +165,6 @@
|
||||
<string name="chooseImportType">Importi datumojn de</string>
|
||||
<string name="importCatima">Importi el Catima</string>
|
||||
<string name="updateBalance">Ĝisdatigi saldon</string>
|
||||
<string name="barcodeLongPressMessage">Nur bildoj povas esti malfermitaj en la galeria apo</string>
|
||||
<string name="sort_by_name">Nomo</string>
|
||||
<string name="sort_by_most_recently_used">Plej laste uzitaj</string>
|
||||
<string name="sort_by_expiry">Dato de eksvalidiĝo</string>
|
||||
@@ -180,8 +179,6 @@
|
||||
<string name="newBalanceSentence">Nova saldo: <xliff:g>%s</xliff:g></string>
|
||||
<string name="anyDate">Iam ajn</string>
|
||||
<string name="switchToFrontImage">Ŝanĝi al antaŭa bildo</string>
|
||||
<string name="openFrontImageInGalleryApp">Malfermi la antaŭan bildon en galeria apo</string>
|
||||
<string name="openBackImageInGalleryApp">Malfermi la malantaŭan bildon en galeria apo</string>
|
||||
<string name="show_name_below_image_thumbnail">Montri nomon sub la bildeto</string>
|
||||
<string name="show_note">Montri noton</string>
|
||||
<string name="settings_category_title_cards">Kartoj</string>
|
||||
|
||||
@@ -182,7 +182,6 @@
|
||||
<string name="selectColor">Seleccionar color</string>
|
||||
<string name="sort">Ordenar</string>
|
||||
<string name="app_contributors">Hecho posible por: <xliff:g id="app_contributors">%s</xliff:g></string>
|
||||
<string name="barcodeLongPressMessage">Solo se puede abrir imágenes en la aplicación de galería</string>
|
||||
<string name="yes">Si</string>
|
||||
<string name="no">No</string>
|
||||
<string name="passwordRequired">Ingresa la contraseña</string>
|
||||
@@ -192,7 +191,7 @@
|
||||
<string name="reverse">en orden inverso</string>
|
||||
<string name="sort_by_most_recently_used">Más recientemente usado</string>
|
||||
<string name="settings_use_volume_keys_navigation">Cambiar tarjetas usando los botones de volumen</string>
|
||||
<string name="sort_by_valid_from">Válido Desde</string>
|
||||
<string name="sort_by_valid_from">Válido desde</string>
|
||||
<string name="sort_by_expiry">Vencimiento</string>
|
||||
<string name="settings_use_volume_keys_navigation_summary">Usá los botones de volumen para cambiar la tarjeta que se muestra</string>
|
||||
<string name="cardWithNumberAndLocale">Tarjeta <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
@@ -227,4 +226,39 @@
|
||||
<string name="source_repository">Fuente de repositorio</string>
|
||||
<string name="and_data_usage">y uso de datos</string>
|
||||
<string name="include_if_asking_support">Si queres solicitar soporte, incluya la siguiente información:</string>
|
||||
<string name="starred">Favoritos</string>
|
||||
<string name="unarchive">Desarchivar</string>
|
||||
<string name="unarchived">Carta desarchivada</string>
|
||||
<string name="updateBalanceTitle">¿Cuánto gastó o ganó?</string>
|
||||
<string name="anyDate">Cualquier fecha</string>
|
||||
<string name="chooseValidFromDate">Elija fecha válida desde</string>
|
||||
<string name="switchToFrontImage">Cambiar a imagen frontal</string>
|
||||
<string name="switchToBackImage">Cambiar a imagen trasera</string>
|
||||
<string name="switchToBarcode">Cambiar a código de barras</string>
|
||||
<string name="setBarcodeHeight">Definir altura de código de barras</string>
|
||||
<string name="show_name_below_image_thumbnail">Mostrar nombre de la imagen bajo su miniatura</string>
|
||||
<string name="show_balance">Mostrar saldo</string>
|
||||
<string name="show_validity">Mostrar validez</string>
|
||||
<string name="settings_category_title_cards">Ver tarjeta</string>
|
||||
<string name="settings_category_title_cards_overview">Ver todas las tarjetas</string>
|
||||
<string name="settings_column_count_portrait">Columna en modo retrato</string>
|
||||
<string name="settings_column_count_landscape">Columna en modo horizontal</string>
|
||||
<string name="settings_automatic_column_count">Automático</string>
|
||||
<string name="settings_column_count_1">1</string>
|
||||
<string name="settings_column_count_2">2</string>
|
||||
<string name="settings_column_count_3">3</string>
|
||||
<string name="settings_column_count_4">4</string>
|
||||
<string name="settings_column_count_5">5</string>
|
||||
<string name="currentBalanceSentence">Balance actual: <xliff:g>%s</xliff:g></string>
|
||||
<string name="newBalanceSentence">Nuevo balance: <xliff:g>%s</xliff:g></string>
|
||||
<string name="validFromSentence">Válido desde <xliff:g>%s</xliff:g></string>
|
||||
<string name="settings_column_count_6">6</string>
|
||||
<string name="settings_column_count_7">7</string>
|
||||
<string name="settings_category_title_general">General</string>
|
||||
<string name="settings_category_title_privacy">Privacidad</string>
|
||||
<string name="action_display_options">Mostrar Opciones</string>
|
||||
<string name="show_archived_cards">Mostrar tarjetas archivadas</string>
|
||||
<string name="view_online">Ver Online</string>
|
||||
<string name="action_more_options">Más Opciones</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">Pause NFC</string>
|
||||
</resources>
|
||||
@@ -16,7 +16,7 @@
|
||||
<string name="addCardTitle">Añadir tarjeta</string>
|
||||
<string name="scanCardBarcode">Escanear código de barras</string>
|
||||
<string name="cardShortcut">Atajo de tarjeta</string>
|
||||
<string name="noCardsMessage">Añada una tarjeta primero</string>
|
||||
<string name="noCardsMessage">Añada una primera tarjeta</string>
|
||||
<string name="noCardExistsError">No se ha podido encontrar esa tarjeta</string>
|
||||
<string name="importExport">Importar/Exportar</string>
|
||||
<string name="exportName">Exportar</string>
|
||||
@@ -25,7 +25,7 @@
|
||||
<string name="importFailedTitle">Falló la importación</string>
|
||||
<string name="importFailed">No se ha podido realizar la importación</string>
|
||||
<string name="exportSuccessfulTitle">Exportado</string>
|
||||
<string name="exportFailedTitle">Falló la exportación</string>
|
||||
<string name="exportFailedTitle">Exportación incorrecta</string>
|
||||
<string name="exportFailed">No se han podido exportar</string>
|
||||
<string name="importing">Importando…</string>
|
||||
<string name="exporting">Exportando…</string>
|
||||
@@ -47,7 +47,7 @@
|
||||
<string name="settings_theme">Tema</string>
|
||||
<string name="app_copyright_old">Basado en Loyalty Card Keychain
|
||||
\nderechos de autor © 2016-2020 de Branden Archer</string>
|
||||
<string name="exportOptionExplanation">Los datos se guardarán en la ubicación que elija</string>
|
||||
<string name="exportOptionExplanation">Los datos serán escritos en el lugar que elija</string>
|
||||
<string name="failedParsingImportUriError">No se pudo procesar el URI de importación</string>
|
||||
<string name="share">Compartir</string>
|
||||
<string name="barcodeType">Tipo de código de barras</string>
|
||||
@@ -205,13 +205,12 @@
|
||||
</plurals>
|
||||
<string name="starred">Estrellado</string>
|
||||
<string name="failedToRetrieveImageFile">No se pudo recuperar el archivo de imagen</string>
|
||||
<string name="barcodeLongPressMessage">Solo se pueden abrir imágenes en la aplicación de galería</string>
|
||||
<string name="updateBalanceTitle">¿Cuánto has gastado o recibido?</string>
|
||||
<string name="currentBalanceSentence">Saldo actual: <xliff:g>%s</xliff:g></string>
|
||||
<string name="noCameraPermissionDirectToSystemSetting">Para escanear códigos de barras, Catima necesitará acceso a su cámara. Toque aquí para cambiar la configuración de sus permisos.</string>
|
||||
<string name="noCameraPermissionDirectToSystemSetting">Para escanear códigos de barras, Catima necesitará acceso a su cámara. Toque aquí para cambiar los ajustes de su permiso.</string>
|
||||
<string name="updateBalanceHint">Introduzca el importe</string>
|
||||
<string name="newBalanceSentence">Nuevo balance: <xliff:g>%s</xliff:g></string>
|
||||
<string name="cameraPermissionDeniedTitle">No se pudo acceder a la cámara</string>
|
||||
<string name="cameraPermissionDeniedTitle">No pudo acceder a la cámara</string>
|
||||
<string name="updateBalance">Actualizar el balance</string>
|
||||
<string name="importCards">Importar tarjetas</string>
|
||||
<string name="storageReadPermissionRequired">Se necesita permiso para leer el almacenamiento para esta acción…</string>
|
||||
@@ -222,8 +221,6 @@
|
||||
<string name="validFromSentence">Válido desde: <xliff:g>%s</xliff:g></string>
|
||||
<string name="height">Alto</string>
|
||||
<string name="switchToFrontImage">Cambiar a imagen frontal</string>
|
||||
<string name="openFrontImageInGalleryApp">Abrir imagen frontal en la aplicación de la galería</string>
|
||||
<string name="openBackImageInGalleryApp">Abrir imagen trasera en la aplicación de visor de imagen</string>
|
||||
<string name="setBarcodeHeight">Ajustar la altura del código de barras</string>
|
||||
<string name="donate">Donar</string>
|
||||
<string name="switchToBarcode">Cambiar a código de barras</string>
|
||||
@@ -234,7 +231,7 @@
|
||||
<string name="show_validity">Mostrar la validez</string>
|
||||
<string name="show_balance">Mostrar el saldo</string>
|
||||
<string name="permissionReadCardsLabel">Leer Tarjetas Catima</string>
|
||||
<string name="permissionReadCardsDescription">leer tus tarjetas Catima y todos sus detalles, incluidas notas e imágenes</string>
|
||||
<string name="permissionReadCardsDescription">lea sus tarjetas Catima y todos sus detalles, incluyendo notas e imágenes</string>
|
||||
<string name="settings_allow_content_provider_read_title">Permite a otras aplicaciones acceder a mis datos</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Necesario para que funcione en algunos escáneres</string>
|
||||
<string name="settings_keep_screen_on_summary">Deshabilita el tiempo de espera de la pantalla mientras se ve una tarjeta</string>
|
||||
@@ -307,4 +304,13 @@
|
||||
<string name="nothing_to_copy">Ningún valor encontrado</string>
|
||||
<string name="barcodeEncoding">Codificación de barra de código</string>
|
||||
<string name="back">Atrás</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">Pausar NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Pausa NFC para prevenir abonaciones NFC desde disparador mientras vea una tarjeta</string>
|
||||
<string name="nfc_blocked_while_viewing_card">NFC está pausado</string>
|
||||
<string name="change_settings">Cambiar ajustes</string>
|
||||
<string name="nfc_block_system_error">Incorrecto al pausar NFC</string>
|
||||
<string name="sort_by_most_recently_added">Más recientemente añadido</string>
|
||||
<string name="zoomFrontImage">Imagen frontal de zoom</string>
|
||||
<string name="zoomBackImage">Imagen trasera de zoom</string>
|
||||
<string name="show_card_id">Mostrar ID de tarjeta</string>
|
||||
</resources>
|
||||
@@ -118,7 +118,6 @@
|
||||
<string name="updateBarcodeQuestionTitle">Kas uuendame triipkoodi väärtust?</string>
|
||||
<string name="yes">Jah</string>
|
||||
<string name="no">Ei</string>
|
||||
<string name="barcodeLongPressMessage">Galeriirakenduses saad avada vaid pilte</string>
|
||||
<string name="sort_by_most_recently_used">Viimati kasutatud</string>
|
||||
<string name="sort_by_expiry">Aegumine</string>
|
||||
<string name="reverse">…tagurpidi järjekorras</string>
|
||||
@@ -230,8 +229,6 @@
|
||||
<string name="switchToFrontImage">Vaata esikülje pilti</string>
|
||||
<string name="switchToBackImage">Vaata tagakülje pilti</string>
|
||||
<string name="switchToBarcode">Vaata triipkoodi</string>
|
||||
<string name="openFrontImageInGalleryApp">Ava esikülje pilt pildivalijas</string>
|
||||
<string name="openBackImageInGalleryApp">Ava tagakülje pilt pildivalijas</string>
|
||||
<string name="setBarcodeHeight">Määra triipkoodi kõrgus</string>
|
||||
<string name="donate">Toeta rahaliselt</string>
|
||||
<string name="icon_header_click_text">Pisipildi muutmiseks vajuta pikalt</string>
|
||||
@@ -301,4 +298,14 @@
|
||||
<string name="nothing_to_copy">Ühtegi väärtust ei leidu</string>
|
||||
<string name="barcodeEncoding">Triipkoodi kodeerimine</string>
|
||||
<string name="back">Tagasi</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">Peata lähiväljaside (NFC)</string>
|
||||
<string name="nfc_blocked_while_viewing_card">Lähiväljaside (NFC) on peatatud</string>
|
||||
<string name="change_settings">Muuda seadistusi</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Vältimaks, et lähiväljaside (NFC) abil toimiv makseliides käivitub, lülitatakse kaardi vaatamisel lähiväljaside (NFC) välja</string>
|
||||
<string name="nfc_block_system_error">Lähiväljaside (NFC) peatamine ei õnnestunud</string>
|
||||
<string name="sort_by_most_recently_added">Viimasena lisatud</string>
|
||||
<string name="zoomFrontImage">Suumi esikaane pilti</string>
|
||||
<string name="zoomBackImage">Suumi tagakaane pilti</string>
|
||||
<string name="show_card_id">Näita kaardi tunnust</string>
|
||||
<string name="open_in_gallery">Ava pildigaleriis</string>
|
||||
</resources>
|
||||
@@ -3,8 +3,8 @@
|
||||
<string name="app_name">کاتیما</string>
|
||||
<string name="action_search">جستجو</string>
|
||||
<string name="action_add">افزودن</string>
|
||||
<string name="noGiftCards">برای افزودن کارت دکمه + پلاس را بزنید, یا از ⋮ منو ایمپورت کنید.</string>
|
||||
<string name="noGiftCardsGroup">تعدادی کارت ایجاد کنید، و سپس آنها را اینجا به گروه بیافزایید.</string>
|
||||
<string name="noGiftCards">برای افزودن کارت، روی دکمه + کلیک کنید یا از منوی ⋮ کارتها را وارد کنید.</string>
|
||||
<string name="noGiftCardsGroup">چند کارت ایجاد کنید و سپس آنها را در اینجا به این گروه اختصاص دهید.</string>
|
||||
<string name="noMatchingGiftCards">بدون نتیجه. سعی کنید جستجویتان را تغییر دهید.</string>
|
||||
<string name="storeName">نام</string>
|
||||
<string name="note">یادداشت</string>
|
||||
@@ -139,7 +139,7 @@
|
||||
<string name="group_updated">گروه به روز شد</string>
|
||||
<string name="all">همه</string>
|
||||
<string name="deleteConfirmationGroup">حذف گروه؟</string>
|
||||
<string name="failedOpeningFileManager">ابتدا یک مدیریت فایل نصب کنید.</string>
|
||||
<string name="failedOpeningFileManager">نمی توان مدیر پرونده را باز کرد</string>
|
||||
<string name="moveUp">به بالا حرکت کن</string>
|
||||
<string name="moveDown">به پایین حرکت کن</string>
|
||||
<string name="leaveWithoutSaveConfirmation">بدون ذخیره ترک میکنید؟</string>
|
||||
@@ -179,7 +179,7 @@
|
||||
<string name="importCards">واردات کارت ها</string>
|
||||
<string name="height">ارتفاع</string>
|
||||
<string name="add_manually_warning_message">برای برخی از فروشگاهها، مقدار بارکد با عدد نوشته شده روی کارت متفاوت است. به همین دلیل، وارد کردن دستی بارکد ممکن است همیشه کار نکند. اکیداً توصیه میشود که به جای آن، بارکد را با دوربین خود اسکن کنید. آیا هنوز میخواهید ادامه دهید؟</string>
|
||||
<string name="generic_error_please_retry">ببخشید، مشکلی پیش آمده، لطفا دوباره امتحان کنید...</string>
|
||||
<string name="generic_error_please_retry">مشکلی پیش آمده</string>
|
||||
<string name="welcome">یه کتیما خوش آمدید</string>
|
||||
<string name="chooseValidFromDate">مقداری درست از تاریخ برگزینید</string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">میخواهم چند کارت به شما بدهم</string>
|
||||
@@ -191,7 +191,7 @@
|
||||
<string name="no">نه</string>
|
||||
<string name="updateBarcodeQuestionTitle">مقدار بارکد را بهروز میکنید؟</string>
|
||||
<string name="updateBarcodeQuestionText">شما شناسه را تغییر دادید. میخواهید بارکد را هم بهروز کنید تا همین مقدار را بهکار ببرید؟</string>
|
||||
<string name="failedGeneratingShareURL">نتوانستم آدرس همرسانیپذیر بسازم. لطفن این را گزارش کنید.</string>
|
||||
<string name="failedGeneratingShareURL">نتوانستم نشانی همرسانیپذیر بسازم.</string>
|
||||
<string name="turn_flashlight_on">چراغقوه را روشن کنید</string>
|
||||
<string name="settings_locale">زبان</string>
|
||||
<string name="settings_oled_dark">پسزمینهی یکدست سیاه برای حالت تاریک</string>
|
||||
@@ -209,11 +209,10 @@
|
||||
<string name="wrongValueForBarcodeType">مقدار برای نوع بارکد برگزیده درست نیست</string>
|
||||
<string name="setBackImage">عکس پشتِسر را بگذارید</string>
|
||||
<string name="removeImage">عکس را برچینید</string>
|
||||
<string name="passwordRequired">لطفا گذرواژه را وارد کنید</string>
|
||||
<string name="passwordRequired">گذرواژه را وارد کنید</string>
|
||||
<string name="previousCard">پیشین</string>
|
||||
<string name="turn_flashlight_off">چراغقوه را خاموش کنید</string>
|
||||
<string name="updateBalance">بهروزرسانی موجودی</string>
|
||||
<string name="barcodeLongPressMessage">تنها عکس میتواند در گالری برنامه باز شود</string>
|
||||
<string name="sort_by_name">نام</string>
|
||||
<string name="sort_by_most_recently_used">بهتازگی استفادهشده</string>
|
||||
<string name="sort_by_expiry">انقضا</string>
|
||||
@@ -221,7 +220,6 @@
|
||||
<string name="include_if_asking_support">اگر میخواهید پشتیبانی درخواست کنید، اطلاعات زیر را پر کنید:</string>
|
||||
<string name="unarchive">برچیدن بایگانی</string>
|
||||
<string name="switchToBackImage">به عکس پشتِسر جابهجا کنید</string>
|
||||
<string name="openBackImageInGalleryApp">عکس پشتِسر را در برنامهی گالری باز کنید</string>
|
||||
<string name="setBarcodeHeight">ارتفاع بارگد را بگمارید</string>
|
||||
<string name="donate">کمک مالی کنید</string>
|
||||
<string name="icon_header_click_text">برای ویرایش قاب پیشنمایه فشار داده و نگهدارید</string>
|
||||
@@ -235,7 +233,7 @@
|
||||
<string name="settings_column_count_4">۴</string>
|
||||
<string name="settings_column_count_5">۵</string>
|
||||
<string name="settings_column_count_6">۶</string>
|
||||
<string name="failedLaunchingPhotoPicker">نتوانستم یک برنامهی گالری پشتیبانیشده بیابم</string>
|
||||
<string name="failedLaunchingPhotoPicker">نتوانستم یک برنامهی انتخاب تصویر پشتیبانیشده بیابم</string>
|
||||
<string name="show_balance">موجودی را نشان دهید</string>
|
||||
<string name="settings_column_count_landscape">ستونها در حالت لنداسکیپ</string>
|
||||
<string name="settings_column_count_1">۱</string>
|
||||
@@ -265,7 +263,6 @@
|
||||
<string name="failedToOpenUrl">نخست یک مرورگر وب نصب کنید</string>
|
||||
<string name="updateBalanceTitle">چقدر خرج یا دریافت کردید؟</string>
|
||||
<string name="switchToBarcode">به بارکد جابهجا کنید</string>
|
||||
<string name="openFrontImageInGalleryApp">عکس روبهرو را در برنامهی گالری باز کنید</string>
|
||||
<string name="add_a_card_in_a_different_way">یک کارت به روشی متفاوت بیافزایید</string>
|
||||
<string name="takePhoto">عکس بگیرید</string>
|
||||
<string name="settings_column_count_7">۷</string>
|
||||
@@ -286,4 +283,8 @@
|
||||
<string name="addFromPkpass">یک فایل دفترچه حساب (.pkpass) انتخاب کنید</string>
|
||||
<string name="noCameraFoundGuideText">به نظر نمیرسد دستگاه شما دوربین داشته باشد. اگر دارد، دستگاه را مجدداً راهاندازی کنید. در غیر این صورت، از دکمه گزینههای بیشتر در زیر برای افزودن بارکد به روش دیگری استفاده کنید.</string>
|
||||
<string name="card_list_widget_empty">بعد از اینکه چند کارت وفاداری در کاتیما اضافه کردید، آنها اینجا ظاهر میشوند. اگر کارت دارید، مطمئن شوید که همه آنها بایگانی نشدهاند.</string>
|
||||
<string name="card_list_widget_name">فهرست کارت</string>
|
||||
<string name="cardWithNumber">کارت <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">کارت <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">لطفا دستگاهتان را نچرخوانید، زیرا کار در حال انجام را لغو میکند</string>
|
||||
</resources>
|
||||
@@ -10,17 +10,14 @@
|
||||
<string name="setBarcodeId">Aseta viivakoodin arvo</string>
|
||||
<string name="sameAsCardId">Sama kuin ID-tunnus</string>
|
||||
<string name="barcodeId">Viivakoodin arvo</string>
|
||||
<string name="importVoucherVaultMessage">Valitse tuotava <i>vouchervault.json</i>-vienti Voucher Vaultista.
|
||||
\nLuo se painamalla ensin Vie Voucher Vaultissa.</string>
|
||||
<string name="importVoucherVaultMessage">Valitse vienti Voucher Vaultista tuotavaksi. \nLuo se painamalla \"Export\" Voucher Vaultissa.</string>
|
||||
<string name="importVoucherVault">Tuo Voucher Vault -varmuuskopiotiedostosta</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Valitse tuotava <i>LoyaltyCardKeychain.csv</i>-vientitiedostosi Loyalty Card Keychainista.
|
||||
\nTai luo se Loyalty Card Keychainin Import/Export-valikosta painamalla ensin Export (Vie).</string>
|
||||
<string name="importLoyaltyCardKeychain">Tuo Loyalty Card Keychain varmuuskopiotiedostosta</string>
|
||||
<string name="importFidmeMessage">Valitse tuotava <i>fidme-export-request-xxxxxxxx.zip</i>-vientitiedostosi FidMe:stä ja valitse viivakoodityypit manuaalisesti sen jälkeen.
|
||||
\nLuo se FidMe-profiilistasi valitsemalla Tietosuoja ja painamalla ensin Extract my data.</string>
|
||||
<string name="importFidmeMessage">Valitse vienti FidMe:stä tuotavaksi ja valitse viivakoodityypit manuaalisesti sen jälkeen. \nLuo se FidMe-profiilistasi valitsemalla \"Data Protection\" ja painamalla \"Extract my data\".</string>
|
||||
<string name="importFidme">Tuo FidMe-varmuuskopiotiedostosta</string>
|
||||
<string name="importCatimaMessage">Valitse tuotava <i>catima.zip</i>-vientitiedostosi Catimasta.
|
||||
\nLuo se Catima-sovelluksen Tuo/vie-valikosta painamalla siellä ensin Vie.</string>
|
||||
<string name="importCatimaMessage">Valitse tuotava vientitiedosto Catimasta. \nLuo se Catima-sovelluksen Tuo/vie-valikosta painamalla siellä ensin Vie.</string>
|
||||
<string name="importCatima">Tuo Catima varmuuskopiotiedostosta</string>
|
||||
<string name="accept">Hyväksy</string>
|
||||
<string name="privacy_policy">Tietosuojakäytäntö</string>
|
||||
@@ -47,14 +44,14 @@
|
||||
<string name="leaveWithoutSaveTitle">Poistu</string>
|
||||
<string name="moveDown">Siirrä alaspäin</string>
|
||||
<string name="moveUp">Siirrä ylöspäin</string>
|
||||
<string name="failedOpeningFileManager">Asenna ensin tiedostonhallintaohjelma.</string>
|
||||
<string name="failedOpeningFileManager">Tiedostonhallintaohjelman avaaminen epäonnistui</string>
|
||||
<string name="deleteConfirmationGroup">Poistetaanko ryhmä\?</string>
|
||||
<string name="all">Kaikki</string>
|
||||
<plurals name="groupCardCount">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> kortti</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> korttia</item>
|
||||
</plurals>
|
||||
<string name="noGroups">Napsauta plus-painiketta (+) lisätäksesi ryhmät luokittelua varten.</string>
|
||||
<string name="noGroups">Napsauta plus-painiketta (+) lisätäksesi ryhmät luokittelua varten</string>
|
||||
<string name="groups">Ryhmät</string>
|
||||
<string name="enter_group_name">Anna ryhmän nimi</string>
|
||||
<string name="exportSuccessful">Tiedot viety</string>
|
||||
@@ -71,8 +68,8 @@
|
||||
<string name="starImage">Suosikkitähti</string>
|
||||
<string name="thumbnailDescription">Pienoiskuva</string>
|
||||
<string name="selectBarcodeTitle">Valitse viivakoodi</string>
|
||||
<string name="app_resources">Vapaat kolmannen osapuolen resurssit: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Vapaat kolmannen osapuolen kirjastot: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Kolmannen osapuolen resurssit: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Kolmannen osapuolen kirjastot: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="debug_version_fmt">Versio: <xliff:g id="version">%s</xliff:g></string>
|
||||
<string name="about_title_fmt">Tietoja <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="app_license">Copyleft (käyttäjänoikeus) - vapaa ohjelmisto, lisenssi GPLv3+</string>
|
||||
@@ -81,9 +78,9 @@
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Tekijänoikeus © 2019–<xliff:g>%d</xliff:g> Sylvia van Os ja avustajat</string>
|
||||
<string name="about">Tietoja</string>
|
||||
<string name="importOptionFilesystemButton">Tiedostojärjestelmästä</string>
|
||||
<string name="importOptionFilesystemExplanation">Valitse tietty tiedosto tiedostojärjestelmästä.</string>
|
||||
<string name="importOptionFilesystemExplanation">Valitse tietty tiedosto tiedostojärjestelmästä</string>
|
||||
<string name="importOptionFilesystemTitle">Tuo tiedostojärjestelmästä</string>
|
||||
<string name="exportOptionExplanation">Tiedot kirjoitetaan valitsemaasi sijaintiin.</string>
|
||||
<string name="exportOptionExplanation">Tiedot kirjoitetaan valitsemaasi sijaintiin</string>
|
||||
<string name="exporting">Viedään…</string>
|
||||
<string name="importing">Tuodaan…</string>
|
||||
<string name="exportFailed">Vienti epäonnistui</string>
|
||||
@@ -92,10 +89,10 @@
|
||||
<string name="importFailed">Tuonti epäonnistui</string>
|
||||
<string name="importFailedTitle">Tuonti epäonnistui</string>
|
||||
<string name="importSuccessfulTitle">Tuonti valmis</string>
|
||||
<string name="importExportHelp">Tietojesi varmuuskopiointi mahdollistaa niiden siirtämisen toiseen laitteeseen.</string>
|
||||
<string name="importExportHelp">Tietojesi varmuuskopiointi mahdollistaa niiden siirtämisen toiseen laitteeseen</string>
|
||||
<string name="exportName">Vie</string>
|
||||
<string name="importExport">Tuo/vie</string>
|
||||
<string name="failedParsingImportUriError">Tuonnin URI: n jäsentäminen epäonnistui</string>
|
||||
<string name="failedParsingImportUriError">Tuonnin URI:n jäsentäminen epäonnistui</string>
|
||||
<string name="noCardExistsError">Tätä korttia ei löytynyt</string>
|
||||
<string name="noCardsMessage">Lisää ensin kortti</string>
|
||||
<string name="cardShortcut">Kortin pikakuvake</string>
|
||||
@@ -118,7 +115,7 @@
|
||||
<string name="note">Lisätieto</string>
|
||||
<string name="storeName">Nimi</string>
|
||||
<string name="noMatchingGiftCards">Ei hakutuloksia, kokeile toisella hakutermillä.</string>
|
||||
<string name="noGiftCards">Lisää kortti napsauttamalla plus-painiketta (+), tai mene ⋮-valikkoon tuodaksesi.</string>
|
||||
<string name="noGiftCards">Lisää kortti napsauttamalla plus-painiketta (+), tai mene ⋮-valikkoon tuodaksesi</string>
|
||||
<string name="action_add">Lisää</string>
|
||||
<string name="action_search">Hae</string>
|
||||
<string name="takePhoto">Ota valokuva</string>
|
||||
@@ -142,8 +139,8 @@
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> valittu</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> valitut</item>
|
||||
</plurals>
|
||||
<string name="passwordRequired">Ole hyvä ja syötä salasana</string>
|
||||
<string name="failedGeneratingShareURL">Jaettavaa URL-osoitetta ei voitu luoda. Ilmoita tästä.</string>
|
||||
<string name="passwordRequired">Syötä salasana</string>
|
||||
<string name="failedGeneratingShareURL">Jaettavaa URL-osoitetta ei voitu luoda</string>
|
||||
<string name="turn_flashlight_on">Käytä taskulamppua</string>
|
||||
<string name="turn_flashlight_off">Sammuta salamavalo</string>
|
||||
<string name="app_contributors">Mahdollistanut: <xliff:g id="app_contributors">%s</xliff:g></string>
|
||||
@@ -173,7 +170,7 @@
|
||||
<string name="on_github">GitHubissa</string>
|
||||
<string name="and_data_usage">ja datan käyttö</string>
|
||||
<string name="on_google_play">Google Playssa</string>
|
||||
<string name="report_error">Ilmoita virheestä</string>
|
||||
<string name="report_error">Ilmoita ongelmasta</string>
|
||||
<string name="shortcutSelectCard">Valitse kortti</string>
|
||||
<string name="starred">Tähdellä merkityt</string>
|
||||
<string name="options">Vaihtoehdot</string>
|
||||
@@ -186,12 +183,12 @@
|
||||
<string name="translate_platform">Weblatessa</string>
|
||||
<string name="selectColor">Valitse väri</string>
|
||||
<string name="rate_this_app">Arvostele tämä sovellus</string>
|
||||
<string name="noGiftCardsGroup">Lisää kortteja ja lisää ne ryhmään täällä.</string>
|
||||
<string name="noGiftCardsGroup">Lisää kortteja ja lisää ne ryhmään täällä</string>
|
||||
<string name="barcodeImageDescriptionWithType">Kuva <xliff:g>%s</xliff:g> viivakoodi</string>
|
||||
<string name="unarchived">Kortti on poistettu arkistosta</string>
|
||||
<string name="unarchive">Poista arkistosta</string>
|
||||
<string name="archived">Kortti arkistoitu</string>
|
||||
<string name="failedLaunchingPhotoPicker">Tuettua galleriasovellusta ei löytynyt</string>
|
||||
<string name="failedLaunchingPhotoPicker">Tuettua kuvavalitsinta ei löytynyt</string>
|
||||
<plurals name="groupCardCountWithArchived">
|
||||
<item quantity="one"><xliff:g>%1$d</xliff:g> kortti (<xliff:g id="archivedCount">%2$d</xliff:g> arkistoitu)</item>
|
||||
<item quantity="other"><xliff:g>%1$d</xliff:g> korttia (<xliff:g id="archivedCount">%2$d</xliff:g> arkistoitu)</item>
|
||||
@@ -203,7 +200,6 @@
|
||||
<string name="welcome">Tervetuloa Catimaan</string>
|
||||
<string name="updateBalanceTitle">Kuinka paljon kulutit tai tienasit?</string>
|
||||
<string name="updateBalanceHint">Syötä summa</string>
|
||||
<string name="barcodeLongPressMessage">Vain kuvia on mahdollista avata galleriasovelluksessa</string>
|
||||
<string name="archive">Arkistoi</string>
|
||||
<string name="importCards">Tuo kortteja</string>
|
||||
<string name="failedToRetrieveImageFile">Kuvatiedoston noutaminen epäonnistui</string>
|
||||
@@ -226,14 +222,12 @@
|
||||
<string name="height">Korkeus</string>
|
||||
<string name="switchToFrontImage">Vaihda etukuvaan</string>
|
||||
<string name="switchToBarcode">Vaihda viivakoodiin</string>
|
||||
<string name="openFrontImageInGalleryApp">Avaa etukuva galleriasovelluksessa</string>
|
||||
<string name="settings_display_barcode_max_brightness_summary">Välttämätön, jotta jotkin skannerit toimivat</string>
|
||||
<string name="settings_keep_screen_on_summary">Poistaa näytön aikakatkaisun korttia katsellessa</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Poistaa näyttölukituksen käytöstä korttia katsellessa</string>
|
||||
<string name="settings_allow_content_provider_read_title">Salli muiden sovellusten käyttää omaa dataa</string>
|
||||
<string name="settings_oled_dark_summary">Vähentää akun käyttöä OLED-näytöillä</string>
|
||||
<string name="switchToBackImage">Vaihda takakuvaan</string>
|
||||
<string name="openBackImageInGalleryApp">Avaa takakuva galleriasovelluksessa</string>
|
||||
<string name="setBarcodeHeight">Aseta viivakoodin korkeus</string>
|
||||
<string name="icon_header_click_text">Pitkä painallus pikkukuvan muokkaamiseksi</string>
|
||||
<string name="show_name_below_image_thumbnail">Näytä nimi pikkukuvan alapuolella</string>
|
||||
@@ -284,12 +278,35 @@
|
||||
<string name="settings_column_count_4">4</string>
|
||||
<string name="settings_column_count_5">5</string>
|
||||
<string name="settings_column_count_7">7</string>
|
||||
<string name="addFromPkpass">Valitse Passbook-tiedosto (.pkpass)</string>
|
||||
<string name="addFromPkpass">Valitse Passbook-tiedosto (.pkpass / .pkpasses)</string>
|
||||
<string name="unsupportedFile">Tämä tiedosto ei ole tuettu</string>
|
||||
<string name="generic_error_please_retry">Pahoittelut, jokin meni pieleen. Yritä uudelleen...</string>
|
||||
<string name="generic_error_please_retry">Tapahtui virhe</string>
|
||||
<string name="sort_by_valid_from">Voimassa alkaen</string>
|
||||
<string name="width">Leveys</string>
|
||||
<string name="setBarcodeWidth">Aseta viivakoodin leveys</string>
|
||||
<string name="card_list_widget_name">Korttiluettelo</string>
|
||||
<string name="card_list_widget_empty">Kun olet lisännyt kanta-asiakaskortteja Catimaan, ne näkyvät täällä. Jos sinulla on kortteja, varmista, etteivät ne kaikki ole arkistoituja.</string>
|
||||
<string name="cardWithNumber">Kortti <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">Kortti <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Älä kierrä laitetta, koska se peruu toiminnon</string>
|
||||
<string name="acra_catima_has_crashed">Valitettavasti <xliff:g id="app_name">%s</xliff:g> kaatui. Auta korjaamaan ongelma lähettämällä virheraportti.</string>
|
||||
<string name="pref_enable_acra">Kysy kaatumisraporttien lähettämisestä</string>
|
||||
<string name="pref_enable_acra_summary">Kun käytössä, sinulta kysytään lupa lähettää raportti kaatumisen yhteydessä. Kaatumisraportteja ei koskaan lähetetä automaattisesti.</string>
|
||||
<string name="copy_value">Kopioi arvo</string>
|
||||
<string name="copied_to_clipboard">Kopioitu leikepöydälle</string>
|
||||
<string name="nothing_to_copy">Arvoa ei löytynyt</string>
|
||||
<string name="barcodeEncoding">Viivakoodin koodaus</string>
|
||||
<string name="back">Takaisin</string>
|
||||
<string name="acra_explain_crash">Jos mahdollista lisää tietoja toiminnastasi täällä:</string>
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> pysähtymisraportti</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">Keskeytä NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Keskeyttää NFC:n, jotta NFC-maksut eivät aktivoituisi korttia katsoessa</string>
|
||||
<string name="sort_by_most_recently_added">Viimeksi lisätty</string>
|
||||
<string name="zoomFrontImage">Suurenna etukuvaa</string>
|
||||
<string name="zoomBackImage">Suurenna takakuvaa</string>
|
||||
<string name="show_card_id">Näytä kortin tunniste</string>
|
||||
<string name="nfc_blocked_while_viewing_card">NFC on keskeytetty</string>
|
||||
<string name="change_settings">Muuta asetuksia</string>
|
||||
<string name="nfc_block_system_error">NFC:n keskeyttäminen epäonnistui</string>
|
||||
<string name="open_in_gallery">Avaa kuvagalleriassa</string>
|
||||
</resources>
|
||||
@@ -203,7 +203,6 @@
|
||||
<string name="previousCard">Précédent</string>
|
||||
<string name="failedToOpenUrl">Installez d\'abord un navigateur web</string>
|
||||
<string name="welcome">Bienvenue dans Catima</string>
|
||||
<string name="barcodeLongPressMessage">Seules les images peuvent être ouvertes dans l’application galerie</string>
|
||||
<string name="failedToRetrieveImageFile">Impossible de récupérer le fichier image</string>
|
||||
<string name="currentBalanceSentence">Solde actuel : <xliff:g>%s</xliff:g></string>
|
||||
<string name="updateBalanceHint">Entrez le montant</string>
|
||||
@@ -223,8 +222,6 @@
|
||||
<string name="switchToFrontImage">Passer à l\'image avant</string>
|
||||
<string name="switchToBackImage">Passer à l\'image arrière</string>
|
||||
<string name="switchToBarcode">Passer au code barre</string>
|
||||
<string name="openFrontImageInGalleryApp">Ouvrir l\'image du recto dans l\'application de visualisation des images</string>
|
||||
<string name="openBackImageInGalleryApp">Ouvrir l\'image du verso dans l\'application de visualisation des images</string>
|
||||
<string name="setBarcodeHeight">Définir la hauteur du code-barres</string>
|
||||
<string name="donate">Faire un don</string>
|
||||
<string name="icon_header_click_text">Appuyez longuement pour modifier la vignette</string>
|
||||
@@ -307,4 +304,14 @@
|
||||
<string name="nothing_to_copy">Aucune valeur trouvée</string>
|
||||
<string name="barcodeEncoding">Encodage du code-barres</string>
|
||||
<string name="back">Retour</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">Mettre en pause le NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Mettre en pause le NFC pour éviter le déclenchement de paiements NFC lors de la consultation d\'une carte</string>
|
||||
<string name="nfc_blocked_while_viewing_card">Le NFC est en pause</string>
|
||||
<string name="change_settings">Modifier les paramètres</string>
|
||||
<string name="nfc_block_system_error">Erreur lors de la mise en pause du NFC</string>
|
||||
<string name="sort_by_most_recently_added">Récemment ajoutés</string>
|
||||
<string name="zoomFrontImage">Grossir l\'image avant</string>
|
||||
<string name="zoomBackImage">Grossir l\'image arrière</string>
|
||||
<string name="show_card_id">Afficher l\'ID de carte</string>
|
||||
<string name="open_in_gallery">Ouvrir dans la galerie d\'images</string>
|
||||
</resources>
|
||||
@@ -150,7 +150,6 @@
|
||||
<string name="showMoreInfo">Ver info</string>
|
||||
<string name="updateBalance">Actualizar saldo</string>
|
||||
<string name="failedToRetrieveImageFile">Fallou a obtención do ficheiro de imaxe</string>
|
||||
<string name="barcodeLongPressMessage">Só imaxes que se abren na app de galería</string>
|
||||
<string name="sort_by_name">Nome</string>
|
||||
<string name="sort_by_most_recently_used">Usadas recentemente</string>
|
||||
<string name="sort_by_expiry">Caducidade</string>
|
||||
@@ -194,8 +193,6 @@
|
||||
<string name="height">Altura</string>
|
||||
<string name="switchToFrontImage">Cambiar á imaxe frontal</string>
|
||||
<string name="switchToBackImage">Cambiar á imaxe posterior</string>
|
||||
<string name="openFrontImageInGalleryApp">Abrir imaxe frontal no visor de imaxes</string>
|
||||
<string name="openBackImageInGalleryApp">Abrir imaxe posterior no visor de imaxes</string>
|
||||
<string name="setBarcodeHeight">Establecer altura do código de barras</string>
|
||||
<string name="icon_header_click_text">Pulsación longa para editar a miniatura</string>
|
||||
<string name="show_name_below_image_thumbnail">Mostrar nome debaixo da miniatura</string>
|
||||
@@ -300,4 +297,14 @@
|
||||
<string name="nothing_to_copy">Non hai ningún valor</string>
|
||||
<string name="barcodeEncoding">Códificación do código de barras</string>
|
||||
<string name="back">Volver</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">NFC en pausa</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Detén o NFC para evitar a activación de pago con NFC cando se mira unha tarxeta</string>
|
||||
<string name="nfc_blocked_while_viewing_card">NFC está en pausa</string>
|
||||
<string name="change_settings">Cambiar axustes</string>
|
||||
<string name="nfc_block_system_error">Non se detivo NFC</string>
|
||||
<string name="sort_by_most_recently_added">Máis recentes</string>
|
||||
<string name="zoomFrontImage">Zoom na imaxe frontal</string>
|
||||
<string name="zoomBackImage">Zoom na imaxe posterior</string>
|
||||
<string name="show_card_id">Mostrar ID da tarxeta</string>
|
||||
<string name="open_in_gallery">Abrir na galería de imaxes</string>
|
||||
</resources>
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="action_add">הוספה</string>
|
||||
<string name="cardId">מזהה כרטיס</string>
|
||||
<string name="cancel">ביטול</string>
|
||||
<string name="save">שמור</string>
|
||||
<string name="ok">אישור</string>
|
||||
<string name="action_search">חיפוש</string>
|
||||
<string name="noGiftCards">לחץ על לחצן ה + להוספת כרטיס, או ייבא מספר כרטיסים באמצעות התפריט ⋮.</string>
|
||||
<string name="noGiftCards">לחץ על כפתור הפלוס (+) כדי להוסיף כרטיס, או ייבא כרטיס מהתפריט ⋮.</string>
|
||||
<string name="share">שיתוף</string>
|
||||
<string name="addCardTitle">הוספת כרטיס</string>
|
||||
<string name="star">הוספה למועדפים</string>
|
||||
@@ -16,7 +16,7 @@
|
||||
<string name="noBarcode">אין ברקוד</string>
|
||||
<string name="edit">עריכה</string>
|
||||
<string name="exportFailed">לא ניתן לייצא</string>
|
||||
<string name="noGiftCardsGroup">צור מספר כרטיסים, ואז שייך אותם לקבוצה פה.</string>
|
||||
<string name="noGiftCardsGroup">צור מספר כרטיסים, ולאחר מכן שייך אותם לקבוצה.</string>
|
||||
<string name="failedParsingImportUriError">נתיב לא נמצא</string>
|
||||
<string name="noMatchingGiftCards">אין תוצאות. נסה חיפוש אחר.</string>
|
||||
<string name="storeName">שם חנות</string>
|
||||
@@ -51,4 +51,19 @@
|
||||
<string name="multipleBarcodesFoundPleaseChooseOne">באיזה מהברקודים שנמצאו אתה רוצה להשתמש?</string>
|
||||
<string name="useFrontImage">השתמש בתמונה קדמית</string>
|
||||
<string name="useBackImage">השתמש בתמונה אחורית</string>
|
||||
<plurals name="selectedCardCount">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> נבחר</item>
|
||||
<item quantity="two"><xliff:g>%d</xliff:g> נבחרו</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> נבחרו</item>
|
||||
</plurals>
|
||||
<plurals name="deleteCardsTitle">
|
||||
<item quantity="one">הסר <xliff:g>%d</xliff:g> כרטיסייה</item>
|
||||
<item quantity="two">הסר <xliff:g>%d</xliff:g> כרטיסיות</item>
|
||||
<item quantity="other">הסר <xliff:g>%d</xliff:g> כרטיסיות</item>
|
||||
</plurals>
|
||||
<plurals name="deleteCardsConfirmation">
|
||||
<item quantity="one">להסיר את <xliff:g>%d</xliff:g> הכרטיסייה לצמיתות?</item>
|
||||
<item quantity="two">להסיר את <xliff:g>%d</xliff:g> הכרטיסיות לצמיתות?</item>
|
||||
<item quantity="other">להסיר את <xliff:g>%d</xliff:g> הכרטיסיות לצמיתות?</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -212,7 +212,6 @@
|
||||
<string name="version_history">वर्जन तिहास</string>
|
||||
<string name="archive">पुरालेख/संग्रहित (Archive)</string>
|
||||
<string name="archived">कार्ड संग्रहीत (Archived)</string>
|
||||
<string name="barcodeLongPressMessage">गैलरी ऐप में केवल छवियां ही खोली जा सकती हैं</string>
|
||||
<string name="license">अनुज्ञप्ति (लाइसेंस)</string>
|
||||
<string name="source_repository">स्रोत रिपोजिटरी</string>
|
||||
<string name="include_if_asking_support">यदि आप सहायता का अनुरोध करना चाहते हैं, तो निम्नलिखित जानकारी शामिल करें:</string>
|
||||
@@ -245,8 +244,6 @@
|
||||
<string name="switchToFrontImage">सामने वाली छवि पर स्विच करें</string>
|
||||
<string name="switchToBackImage">पिछली छवि पर स्विच करें</string>
|
||||
<string name="switchToBarcode">बारकोड पर स्विच करें</string>
|
||||
<string name="openFrontImageInGalleryApp">इमेज दर्शक सामने वाली इमेज खोलें</string>
|
||||
<string name="openBackImageInGalleryApp">पिछली इमेज खोलें</string>
|
||||
<string name="setBarcodeHeight">बारकोड ऊंचाई सेट करें</string>
|
||||
<string name="icon_header_click_text">छोटी छवि (थंबनेल) संपादित करने के लिए देर तक दबाएँ</string>
|
||||
<string name="enter_card_id">अपने कार्ड पर आईडी नंबर या टेक्स्ट दर्ज करें</string>
|
||||
@@ -298,4 +295,6 @@
|
||||
<string name="copy_value">मान कॉपी करें</string>
|
||||
<string name="copied_to_clipboard">क्लिपबोर्ड पर कॉपी किया गया</string>
|
||||
<string name="nothing_to_copy">कोई मूल्य नहीं मिला</string>
|
||||
<string name="barcodeEncoding">बारकोड एन्कोडिंग</string>
|
||||
<string name="back">वापस</string>
|
||||
</resources>
|
||||
@@ -128,8 +128,6 @@
|
||||
<string name="switchToFrontImage">Prebaci na prednju sliku</string>
|
||||
<string name="switchToBackImage">Prebaci na stražnju sliku</string>
|
||||
<string name="switchToBarcode">Prebaci na crtični kod</string>
|
||||
<string name="openFrontImageInGalleryApp">Otvori prednju sliku u aplikaciji prikazivača slika</string>
|
||||
<string name="openBackImageInGalleryApp">Otvori stražnju sliku u aplikaciji prikazivača slika</string>
|
||||
<string name="setBarcodeHeight">Postavi visinu crtičnog koda</string>
|
||||
<plurals name="selectedCardCount">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> odabrana</item>
|
||||
@@ -159,7 +157,6 @@
|
||||
<string name="importVoucherVaultMessage">Odaberi tvoj izvoz iz Voucher Vault za uvoz. \nStvori ga u aplikaciji Voucher Vault pritiskom na „Izvoz”.</string>
|
||||
<string name="failedToRetrieveImageFile">Neuspjelo dohvaćanje slikovne datoteke</string>
|
||||
<string name="license">Licenca</string>
|
||||
<string name="barcodeLongPressMessage">U aplikaciji galerije se mogu otvoriiti samo slike</string>
|
||||
<string name="source_repository">Izvorni repozitorij</string>
|
||||
<string name="on_github">na GitHubu</string>
|
||||
<string name="on_google_play">na stranici Google Play</string>
|
||||
@@ -305,4 +302,10 @@
|
||||
<string name="copied_to_clipboard">Kopirano u međuspremnik</string>
|
||||
<string name="back">Natrag</string>
|
||||
<string name="copy_value">Kopiraj vrijednost</string>
|
||||
<string name="nothing_to_copy">Nije pronađena vrijednost</string>
|
||||
<string name="barcodeEncoding">Kodiranje barkoda</string>
|
||||
<string name="change_settings">Promijeni postavke</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">Pauziraj NFC</string>
|
||||
<string name="nfc_blocked_while_viewing_card">NFC je pauziran</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Pauzira NFC kako bi se spriječilo aktiviranje NFC plaćanja tijekom prikazivanja kartice</string>
|
||||
</resources>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="noGiftCards">Kattintson a + plusz gombra egy kártya hozzáadásához, vagy importáljon a ⋮ menüből.</string>
|
||||
<string name="noGiftCards">Koppintson a + plusz gombra egy kártya hozzáadásához, vagy importáljon a ⋮ menüből</string>
|
||||
<string name="action_add">Hozzáadás</string>
|
||||
<string name="action_search">Keresés</string>
|
||||
<plurals name="selectedCardCount">
|
||||
@@ -8,7 +8,7 @@
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> kiválasztva</item>
|
||||
</plurals>
|
||||
<string name="noCardsMessage">Először adjon hozzá egy kártyát</string>
|
||||
<string name="noGiftCardsGroup">Hozzon létre kártyákat, és rendelje hozzá a megfelelő csoporthoz.</string>
|
||||
<string name="noGiftCardsGroup">Hozzon létre kártyákat, és rendelje hozzá a megfelelő csoporthoz</string>
|
||||
<string name="noMatchingGiftCards">Nincs találat. Próbálja módosítani a keresést.</string>
|
||||
<string name="storeName">Név</string>
|
||||
<string name="note">Feljegyzés</string>
|
||||
@@ -32,12 +32,12 @@
|
||||
<string name="sendLabel">Küldés…</string>
|
||||
<string name="editCardTitle">Kártya szerkesztése</string>
|
||||
<string name="addCardTitle">Kártya hozzáadása</string>
|
||||
<string name="scanCardBarcode">Vonalkód leolvasása</string>
|
||||
<string name="cardShortcut">Kártya parancsikon</string>
|
||||
<string name="scanCardBarcode">Vonalkód beolvasása</string>
|
||||
<string name="cardShortcut">Kártya indítóikon</string>
|
||||
<string name="failedParsingImportUriError">Az importálási URI nem értelmezhető</string>
|
||||
<string name="importExport">Importálás/exportálás</string>
|
||||
<string name="exportName">Exportálás</string>
|
||||
<string name="importExportHelp">Az adatok mentése lehetővé teszi, hogy egy másik eszközre helyezze át.</string>
|
||||
<string name="importExportHelp">Az adatok mentése lehetővé teszi, hogy egy másik eszközre helyezze át</string>
|
||||
<string name="importSuccessfulTitle">Importálva</string>
|
||||
<string name="importFailedTitle">Az importálás sikertelen</string>
|
||||
<string name="importFailed">Nem sikerült az importálás végrehajtása</string>
|
||||
@@ -49,14 +49,14 @@
|
||||
</plurals>
|
||||
<string name="deleteTitle">Kártya törlése</string>
|
||||
<string name="barcodeImageDescriptionWithType">A(z) <xliff:g>%s</xliff:g> vonalkód képe</string>
|
||||
<string name="noCardExistsError">A kártya nem található</string>
|
||||
<string name="noCardExistsError">Ez a kártya nem található</string>
|
||||
<string name="importVoucherVault">Importálás a Voucher Vaultból</string>
|
||||
<string name="wrongValueForBarcodeType">Ez az érték meg megfelelő a kiválasztott vonalkódtípushoz</string>
|
||||
<string name="wrongValueForBarcodeType">Ez az érték nem felel meg a kiválasztott vonalkódtípushoz</string>
|
||||
<string name="setBackImage">Hátlapi kép beállítása</string>
|
||||
<string name="no">Nem</string>
|
||||
<string name="passwordRequired">Adja meg a jelszót</string>
|
||||
<string name="exportPasswordHint">Kód beírása</string>
|
||||
<string name="failedGeneratingShareURL">Nem lehetett megosztható webcímet előállítani. Kérjük, ezt jelentse.</string>
|
||||
<string name="failedGeneratingShareURL">Nem lehetett megosztható webcímet előállítani</string>
|
||||
<string name="sort">Rendezés</string>
|
||||
<string name="on_google_play">a Google Playen</string>
|
||||
<string name="and_data_usage">és adathasználat</string>
|
||||
@@ -68,13 +68,13 @@
|
||||
<string name="exportFailed">Nem sikerült az exportálás végrehajtása</string>
|
||||
<string name="importing">Importálás…</string>
|
||||
<string name="exporting">Exportálás…</string>
|
||||
<string name="exportOptionExplanation">Az adatok a kiválasztott helyre lesznek mentve.</string>
|
||||
<string name="exportOptionExplanation">Az adatok a kiválasztott helyre kerülnek mentésre</string>
|
||||
<string name="importOptionFilesystemTitle">Importálás fájlrendszerből</string>
|
||||
<string name="importOptionFilesystemButton">A fájlrendszerből</string>
|
||||
<string name="app_license">Copyleft licencű szabad szoftver, GPLv3+ alatt közzétéve</string>
|
||||
<string name="about_title_fmt">A <xliff:g id="app_name">%s</xliff:g> névjegye</string>
|
||||
<string name="debug_version_fmt">Verzió: <xliff:g id="version">%s</xliff:g></string>
|
||||
<string name="app_resources">Szabad külső erőforrások: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_resources">Külső erőforrások: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="selectBarcodeTitle">Vonalkód kiválasztása</string>
|
||||
<string name="starImage">Kedvencek csillag</string>
|
||||
<string name="settings">Beállítások</string>
|
||||
@@ -91,14 +91,14 @@
|
||||
<string name="groups">Csoportok</string>
|
||||
<string name="group_edit">Csoport szerkesztése</string>
|
||||
<string name="group_name_is_empty">A csoportnév nem lehet üres</string>
|
||||
<string name="failedOpeningFileManager">Előbb telepítsen egy fájlkezelőt.</string>
|
||||
<string name="failedOpeningFileManager">Nem sikerült a fájlkezelő megnyitása</string>
|
||||
<string name="moveUp">Mozgatás felfelé</string>
|
||||
<string name="moveDown">Mozgatás lefelé</string>
|
||||
<string name="leaveWithoutSaveTitle">Kilépés</string>
|
||||
<string name="leaveWithoutSaveConfirmation">Kilépés mentés nélkül\?</string>
|
||||
<string name="addManually">A vonalkód kézi beírása</string>
|
||||
<string name="addFromImage">Kép kiválasztása a galériából</string>
|
||||
<string name="editGroup">Csoportok szerkesztése: <xliff:g>%s</xliff:g></string>
|
||||
<string name="editGroup">Csoport szerkesztése: <xliff:g>%s</xliff:g></string>
|
||||
<string name="expiryStateSentence">Lejárat: <xliff:g>%s</xliff:g></string>
|
||||
<string name="balanceSentence">Egyenleg: <xliff:g>%s</xliff:g></string>
|
||||
<string name="card">Kártya</string>
|
||||
@@ -108,7 +108,7 @@
|
||||
<string name="chooseExpiryDate">Lejárati dátum kiválasztása</string>
|
||||
<string name="moveBarcodeToTopOfScreen">Vonalkód feltolása a képernyő tetejére</string>
|
||||
<string name="noBarcodeFound">Nem található vonalkód</string>
|
||||
<string name="errorReadingImage">Nem lehet olvasni a képet</string>
|
||||
<string name="errorReadingImage">A kép nem olvasható</string>
|
||||
<string name="balance">Egyenleg</string>
|
||||
<string name="currency">Pénznem</string>
|
||||
<string name="points">Pontok</string>
|
||||
@@ -118,10 +118,8 @@
|
||||
<string name="importCatima">Importálás a Catimából</string>
|
||||
<string name="importFidme">Importálás a FidMeből</string>
|
||||
<string name="importLoyaltyCardKeychain">Importálás a Loyalty Card Keychainből</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Válassza ki a Loyalty Card Keychainből importálandó <i>LoyaltyCardKeychain.csv</i> export fájlt.
|
||||
\nEzt hozza létre a Loyalty Card Keychainben az Importálás/exportálás menüben, az Exportálás megnyomásával.</string>
|
||||
<string name="importVoucherVaultMessage">Válassza ki az importálandó <i>vouchervault.json</i> Voucher Vault export fájlt.
|
||||
\nEzt hozza létre a Voucher Vaultban az Exportálás megnyomásával.</string>
|
||||
<string name="importLoyaltyCardKeychainMessage">Jelölje ki az importálandó Loyalty Card Keychain exportot. \nA Loyalty Card Keychain Importálás/Exportálás menüjéből hozhatja létre, az Exportálásra koppintva.</string>
|
||||
<string name="importVoucherVaultMessage">Jelölje ki az importálandó Voucher Vault exportot. \nA Voucher Vault-on belül az Exportálás gombra koppintva hozhatja létre.</string>
|
||||
<string name="barcodeId">Vonalkód értéke</string>
|
||||
<string name="sameAsCardId">Ugyanaz, mint az azonosító</string>
|
||||
<string name="setBarcodeId">Vonalkód értékének beállítása</string>
|
||||
@@ -167,30 +165,28 @@
|
||||
</plurals>
|
||||
<string name="chooseImportType">Adatok importálása innen</string>
|
||||
<string name="thumbnailDescription">Előkép</string>
|
||||
<string name="importOptionFilesystemExplanation">Válassza ki a kívánt fájlt a fájlrendszerből.</string>
|
||||
<string name="importOptionFilesystemExplanation">Válassza ki a kívánt fájlt a fájlrendszerből</string>
|
||||
<string name="about">Névjegy</string>
|
||||
<string name="app_copyright_old">Készült a Loyalty Card Keychain alapján
|
||||
\nCopyright © 2016–2020 Branden Archer</string>
|
||||
<string name="app_libraries">Szabad külső programkönyvtárak: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Külső programkönyvtárak: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="settings_dark_theme">Sötét</string>
|
||||
<string name="deleteConfirmationGroup">Csoport törlése\?</string>
|
||||
<string name="noGroups">Nyomja meg a + gombot a csoportok kategorizálásához.</string>
|
||||
<string name="noGroups">Koppintson a + gombra a csoportok kategorizálásához</string>
|
||||
<string name="group_name_already_in_use">A csoportnév már létezik</string>
|
||||
<string name="all">Összes</string>
|
||||
<string name="noGroupCards">Ez a csoport üres</string>
|
||||
<string name="group_updated">Csoport frissítve</string>
|
||||
<string name="expiryStateSentenceExpired">Lejárt: <xliff:g>%s</xliff:g></string>
|
||||
<string name="groupsList">Csoportok: <xliff:g>%s</xliff:g></string>
|
||||
<string name="importCatimaMessage">Válassza ki az exportált <i>catima.zip</i> fájlt az importáláshoz.
|
||||
\nLétrehozhatja az Importálás/exportálás menüből az Exportálást megnyomva egy másik Catima alkalmazásban.</string>
|
||||
<string name="importFidmeMessage">Válassza ki a FidMeből exportált <i>fidme-export-request-xxxxxx.zip</i> fájl majd importálja be, és utána válassza a kézi vonalkódbeírást.
|
||||
\nEzt hozza létre a FidMe-profiljában az Adatvédelem rész választásával, majd a Saját adatok kinyerése megnyomásával.</string>
|
||||
<string name="importCatimaMessage">Válassza ki a megfelelő exportált Catima elemet importálásra. \nAz Importálás/exportálás menüelemmel importálhat egy korábbi Catima exportot.</string>
|
||||
<string name="importFidmeMessage">Jelölje ki az importálandó FidMe exportot, ezt követően adja meg kézileg a vonalkód típusát. \nA FidMe profiljából tudja létrehozni, az Adatvédelem, majd a Saját adatok kinyerése lehetőséggel.</string>
|
||||
<plurals name="groupCardCountWithArchived">
|
||||
<item quantity="one"><xliff:g>%1$d</xliff:g> kártya (<xliff:g id="archivedCount">%2$d</xliff:g> archiválva)</item>
|
||||
<item quantity="other"><xliff:g>%1$d</xliff:g> kártya (<xliff:g id="archivedCount">%2$d</xliff:g> archiválva)</item>
|
||||
</plurals>
|
||||
<string name="failedToOpenUrl">Először telepítsen egy webböngészőt</string>
|
||||
<string name="failedLaunchingPhotoPicker">Nem található támogatott galéria alkalmazás</string>
|
||||
<string name="failedLaunchingPhotoPicker">Nem található támogatott képválasztó</string>
|
||||
<string name="previousCard">Előző</string>
|
||||
<string name="nextCard">Következő</string>
|
||||
<string name="settings_oled_dark">Teljesen fekete háttér a sötét témánál</string>
|
||||
@@ -206,7 +202,6 @@
|
||||
<string name="newBalanceSentence">Új egyenleg: <xliff:g>%s</xliff:g></string>
|
||||
<string name="cameraPermissionDeniedTitle">Nem sikerült hozzáférni a kamerához</string>
|
||||
<string name="failedToRetrieveImageFile">Nem sikerült lekérni a képfájlt</string>
|
||||
<string name="barcodeLongPressMessage">Csak képek nyithatók meg a galéria alkalmazásban</string>
|
||||
<string name="unarchived">Kártya archiválása megszüntetve</string>
|
||||
<string name="welcome">Üdvözöljük a Catimában</string>
|
||||
<string name="updateBalance">Egyenleg frissítése</string>
|
||||
@@ -222,13 +217,11 @@
|
||||
<string name="switchToBackImage">Hátulnézeti képre váltás</string>
|
||||
<string name="switchToBarcode">Vonalkódra váltás</string>
|
||||
<string name="donate">Adományozás</string>
|
||||
<string name="openBackImageInGalleryApp">Hátulnézeti kép megnyitása a galéria alkalmazásban</string>
|
||||
<string name="openFrontImageInGalleryApp">Elölnézeti kép megnyitása a galéria alkalmazásban</string>
|
||||
<string name="setBarcodeHeight">Vonalkód magasságának megadása</string>
|
||||
<string name="icon_header_click_text">Hosszan nyomja meg a miniatűr szerkesztéséhez</string>
|
||||
<string name="show_note">Megjegyzés megjelenítése</string>
|
||||
<string name="show_balance">Egyenleg megjelenítése</string>
|
||||
<string name="permissionReadCardsLabel">Catima kártyák olvasása</string>
|
||||
<string name="permissionReadCardsLabel">Catima kártyák beolvasása</string>
|
||||
<string name="permissionReadCardsDescription">olvassa el a Catima-kártyáit, és annak minden részletét, beleértve a jegyzeteket és a képeket is</string>
|
||||
<string name="show_validity">Érvényesség megjelenítése</string>
|
||||
<string name="show_name_below_image_thumbnail">Név megjelenítése a kép miniatűrje alatt</string>
|
||||
@@ -257,7 +250,7 @@
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="amountParsingFailed">Hibás érték</string>
|
||||
<string name="add_manually_warning_title">Szkennelés ajánlott</string>
|
||||
<string name="add_manually_warning_message">Egyes boltoknál a kártyán levő számsor különbözik a vonalkódtól. Emiatt a manuális szám beírás nem minden esetben fog működni. Erősen ajánlott inkább a vonalkód szkennelése kamerával. Biztosan folytatja?</string>
|
||||
<string name="add_manually_warning_message">Az egyes kártyákon levő számsor különbözik a vonalkód számától. Emiatt a kézi számbeírás nem minden esetben fog működni. Ajánlott tehát a vonalkód kamerával történő beolvasása. Biztos, hogy folytatja?</string>
|
||||
<string name="continue_">Tovább</string>
|
||||
<string name="spend">Költött</string>
|
||||
<string name="receive">Megkapott</string>
|
||||
@@ -282,12 +275,32 @@
|
||||
<string name="settings_column_count_6">6</string>
|
||||
<string name="settings_column_count_3">3</string>
|
||||
<string name="settings_column_count_1">1</string>
|
||||
<string name="addFromPkpass">Válassz egy Passbook fájlt (.pkpass)</string>
|
||||
<string name="addFromPkpass">Válasszon egy Passbook fájlt (.pkpass / .pkpasses)</string>
|
||||
<string name="unsupportedFile">Ez a fájl nem támogatott</string>
|
||||
<string name="generic_error_please_retry">Sajnálom, valami rosszul ment, próbálkozz újra...</string>
|
||||
<string name="generic_error_please_retry">Hiba lépett fel</string>
|
||||
<string name="settings_column_count_4">4</string>
|
||||
<string name="settings_column_count_7">7</string>
|
||||
<string name="sort_by_valid_from">Érvényesség kezdete</string>
|
||||
<string name="width">Szélesség</string>
|
||||
<string name="setBarcodeWidth">Vonalkód szélességének beállítása</string>
|
||||
<string name="setBarcodeWidth">Vonalkódszélesség beállítása</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">NFC szüneteltetése</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Az NFC szüneteltetése a kártya megtekintése idejére, hogy ne indítsa be az NFC-s fizetést</string>
|
||||
<string name="card_list_widget_name">Kártyalista</string>
|
||||
<string name="card_list_widget_empty">Miután hozzáadja a kártyákat a Catimához, itt fognak megjelenni. Ha vannak kártyái, győződjön meg arról, hogy nincsenek-e archiválva.</string>
|
||||
<string name="cardWithNumber"><xliff:g>%d</xliff:g> kártya</string>
|
||||
<string name="cardWithNumberAndLocale"><xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>) kártya</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">Ne forgassa el az eszközt, mert az megszakítja a műveletet</string>
|
||||
<string name="acra_catima_has_crashed">Sajnos a <xliff:g id="app_name">%s</xliff:g> összeomlott. Segítsen a hiba megoldásában azzal, hogy küld nekünk egy jelentést.</string>
|
||||
<string name="acra_explain_crash">Ha lehetséges, adja meg, hogy éppen milyen műveletet végzett:</string>
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> összeomlási jelentés</string>
|
||||
<string name="pref_enable_acra">Rákérdezés az összeomlási jelentések elküldésére</string>
|
||||
<string name="pref_enable_acra_summary">Ha be van kapcsolva, akkor mindig rákérdez az összeomlási jelentés elküldésére. A jelentések sosem kerülnek küldésre automatikusan.</string>
|
||||
<string name="copy_value">Érték másolása</string>
|
||||
<string name="copied_to_clipboard">Vágólapra másolva</string>
|
||||
<string name="nothing_to_copy">Nem található érték</string>
|
||||
<string name="barcodeEncoding">Vonalkód kódolása</string>
|
||||
<string name="back">Vissza</string>
|
||||
<string name="nfc_blocked_while_viewing_card">NFC szüneteltetve</string>
|
||||
<string name="change_settings">Beállítások változtatása</string>
|
||||
<string name="nfc_block_system_error">Nem sikerült szüneteltetni az NFC-t</string>
|
||||
</resources>
|
||||
@@ -189,7 +189,6 @@
|
||||
<string name="archive">Arsip</string>
|
||||
<string name="archived">Kartu diarsipkan</string>
|
||||
<string name="failedToRetrieveImageFile">Gagal mengambil file gambar</string>
|
||||
<string name="barcodeLongPressMessage">Hanya gambar yang dapat dibuka di aplikasi galeri</string>
|
||||
<string name="unarchive">Ekstrak</string>
|
||||
<string name="unarchived">Kartu tidak diarsipkan</string>
|
||||
<string name="options">Opsi</string>
|
||||
@@ -212,8 +211,6 @@
|
||||
<string name="switchToFrontImage">Ubah ke depan gambar</string>
|
||||
<string name="switchToBackImage">Ubah ke belakang gambar</string>
|
||||
<string name="switchToBarcode">Ubah ke kode batang</string>
|
||||
<string name="openFrontImageInGalleryApp">Buka gambar depan di aplikasi penampil gambar</string>
|
||||
<string name="openBackImageInGalleryApp">Buka gambar di aplikasi penampil gambar</string>
|
||||
<string name="setBarcodeHeight">Atur tinggi kode batang</string>
|
||||
<string name="donate">Donasi</string>
|
||||
<string name="show_validity">Tunjukkan validitas</string>
|
||||
@@ -292,4 +289,16 @@
|
||||
<string name="pref_enable_acra_summary">Ketika diaktifkan, Anda akan diminta untuk melaporkan crash saat terjadi. Laporan crash tidak pernah dikirim secara otomatis.</string>
|
||||
<string name="copy_value">Salin nilai</string>
|
||||
<string name="copied_to_clipboard">Disalin ke papan klip</string>
|
||||
<string name="nothing_to_copy">Tidak ditemukan nilai</string>
|
||||
<string name="barcodeEncoding">Pengkodean barcode</string>
|
||||
<string name="back">Kembali</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">Jeda NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Menjeda NFC untuk mencegah pembayaran NFC terpicu saat melihat kartu</string>
|
||||
<string name="nfc_blocked_while_viewing_card">NFC dijeda</string>
|
||||
<string name="change_settings">Ubah pengaturan</string>
|
||||
<string name="nfc_block_system_error">Gagal menjeda NFC</string>
|
||||
<string name="show_card_id">Tampilkan ID Kartu</string>
|
||||
<string name="sort_by_most_recently_added">Terakhir ditambahkan</string>
|
||||
<string name="zoomFrontImage">Perbesar gambar depan</string>
|
||||
<string name="zoomBackImage">Perbesar gambar belakang</string>
|
||||
</resources>
|
||||
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="action_add">Bæta</string>
|
||||
<string name="noBarcode">Nei strikamerkið</string>
|
||||
<string name="noBarcode">Ekkert strikamerki</string>
|
||||
<string name="action_search">Leita að</string>
|
||||
<string name="delete">Eyða</string>
|
||||
<string name="noGiftCards">Smelltu á + plús takka til að bæta korti, eða að flytja inn frá ⋮ Valmynd.</string>
|
||||
<string name="noGiftCards">Smelltu á + plús takka til að bæta korti, eða að flytja inn frá ⋮ Valmynd</string>
|
||||
<string name="note">Athugið</string>
|
||||
<string name="barcodeType">Strikamerkið tegund</string>
|
||||
<string name="cancel">Hætta</string>
|
||||
@@ -12,61 +12,61 @@
|
||||
<string name="storeName">Nafnið</string>
|
||||
<string name="star">Bæta við eftirlæti</string>
|
||||
<string name="unstar">Fjarlægja frá eftirlæti</string>
|
||||
<string name="save">Sparaðu</string>
|
||||
<string name="save">Vista</string>
|
||||
<string name="edit">Breyta</string>
|
||||
<string name="confirm">Staðfesta</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="sendLabel">Sendu…</string>
|
||||
<string name="deleteConfirmation">Eyða þetta kort til frambúðar\?</string>
|
||||
<string name="deleteConfirmation">Eyða þessu korti til frambúðar?</string>
|
||||
<string name="share">Deila</string>
|
||||
<string name="editCardTitle">Breyta Kort</string>
|
||||
<string name="addCardTitle">Bæta Kort</string>
|
||||
<string name="scanCardBarcode">Skanna Kort Strikamerkið</string>
|
||||
<string name="noCardsMessage">Bæta kort fyrstu</string>
|
||||
<string name="exportFailedTitle">Flytja mistókst</string>
|
||||
<string name="exportSuccessfulTitle">Flutt</string>
|
||||
<string name="noCardExistsError">Gæti ekki fundið kort</string>
|
||||
<string name="failedParsingImportUriError">Get ekki lesið inn URI</string>
|
||||
<string name="exportName">Flytja</string>
|
||||
<string name="importExportHelp">Stuðningur upp spil gerir þér kleift að færa þá til annar tæki.</string>
|
||||
<string name="importSuccessfulTitle">Flutt</string>
|
||||
<string name="editCardTitle">Breyta Korti</string>
|
||||
<string name="addCardTitle">Bæta við korti</string>
|
||||
<string name="scanCardBarcode">Skanna Strikamerkið</string>
|
||||
<string name="noCardsMessage">Bæta fyrst við korti</string>
|
||||
<string name="exportFailedTitle">Útflutningur mistókst</string>
|
||||
<string name="exportSuccessfulTitle">Flutt út</string>
|
||||
<string name="noCardExistsError">Gat ekki fundið þetta kort</string>
|
||||
<string name="failedParsingImportUriError">Get ekki flutt inn URI</string>
|
||||
<string name="exportName">Flytja út</string>
|
||||
<string name="importExportHelp">Afritun gagna gerir þér kleift að færa það á annað tæki</string>
|
||||
<string name="importSuccessfulTitle">Flutt inn</string>
|
||||
<string name="importFailedTitle">Innflutningur mistókst</string>
|
||||
<string name="exportOptionExplanation">Gögnum verður skrifað á stað af eigin vali.</string>
|
||||
<string name="exportOptionExplanation">Gögnin verðs skrifuð á stað sem þú velur</string>
|
||||
<string name="importOptionFilesystemTitle">Innflutningur frá möppuna</string>
|
||||
<string name="importOptionFilesystemExplanation">Velja ákveðna skrá frá möppuna.</string>
|
||||
<string name="importOptionFilesystemExplanation">Velja ákveðna skrá úr skráakerfinu</string>
|
||||
<string name="starImage">Uppáhalds stjörnu</string>
|
||||
<string name="never">Aldrei</string>
|
||||
<string name="currency">Mynt</string>
|
||||
<string name="moveBarcodeToTopOfScreen">Færa strikamerkið að efst á skjánum</string>
|
||||
<string name="errorReadingImage">Gat ekki lesið ímynd</string>
|
||||
<string name="errorReadingImage">Gat ekki lesið mynd</string>
|
||||
<string name="about">Um</string>
|
||||
<string name="settings">Stillingar</string>
|
||||
<string name="settings_keep_screen_on">Halda á skjánum</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card">Að koma í veg fyrir að læsa skjánum</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card">Koma í veg fyrir skjálæsingu</string>
|
||||
<string name="editBarcode">Breyta strikamerkið</string>
|
||||
<string name="settings_light_theme">Ljós</string>
|
||||
<string name="importOptionFilesystemButton">Frá möppuna</string>
|
||||
<string name="chooseImportType">Flytja gögn úr\?</string>
|
||||
<string name="chooseImportType">Flytja gögn úr</string>
|
||||
<string name="balance">Jafnvægi</string>
|
||||
<string name="points">Stig</string>
|
||||
<string name="selectBarcodeTitle">Veldu Strikamerkið</string>
|
||||
<string name="thumbnailDescription">Þumalinn fyrir kort</string>
|
||||
<string name="selectBarcodeTitle">Velja strikamerki</string>
|
||||
<string name="thumbnailDescription">Táknmynd</string>
|
||||
<string name="settings_theme">Þema</string>
|
||||
<string name="app_license">Copylefted frítt hugbúnaður, leyfi GPLv3+</string>
|
||||
<string name="noBarcodeFound">Nei strikamerkið var komist</string>
|
||||
<string name="settings_system_theme">Kerfi</string>
|
||||
<string name="settings_dark_theme">Dökk</string>
|
||||
<string name="settings_display_barcode_max_brightness">Bjartari strikamerkið skoða</string>
|
||||
<string name="settings_display_barcode_max_brightness">Bjartari skjá</string>
|
||||
<string name="enter_group_name">Inn nafn hópur</string>
|
||||
<string name="intent_import_card_from_url_share_text">Ég vil deila kort með þér</string>
|
||||
<string name="importSuccessful">Kortið gögn flutt</string>
|
||||
<string name="importSuccessful">Gögn flutt inn</string>
|
||||
<string name="groups">Tekur</string>
|
||||
<string name="exportSuccessful">Kortagögn flutt út</string>
|
||||
<string name="failedOpeningFileManager">Setja skráasafn fyrst.</string>
|
||||
<string name="exportSuccessful">Gögn flutt út</string>
|
||||
<string name="failedOpeningFileManager">Gat ekki opnað skráakerfistjóra</string>
|
||||
<string name="moveUp">Fara upp</string>
|
||||
<string name="leaveWithoutSaveTitle">Brottför</string>
|
||||
<string name="leaveWithoutSaveConfirmation">Fara án þess að bjarga\?</string>
|
||||
<string name="addFromImage">Veldu mynd frá gallerí</string>
|
||||
<string name="addFromImage">Veldu mynd úr gallerí</string>
|
||||
<string name="card">Kort</string>
|
||||
<string name="expiryDate">Fyrningu</string>
|
||||
<string name="moveDown">Fara neðar</string>
|
||||
@@ -75,8 +75,32 @@
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> valið</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> valin</item>
|
||||
</plurals>
|
||||
<string name="noGiftCardsGroup">Búðu til nokkur kort og settu þau síðan í hópinn hér.</string>
|
||||
<string name="noGiftCardsGroup">Búðu til nokkur kort og settu þau síðan í hópinn hér</string>
|
||||
<string name="sort">flokka</string>
|
||||
<string name="sort_by">flokka Eftir</string>
|
||||
<string name="nextCard">Næsta</string>
|
||||
</resources>
|
||||
<plurals name="deleteCardsConfirmation">
|
||||
<item quantity="one">Eyða þessu <xliff:g>%d</xliff:g> korti varanlega?</item>
|
||||
<item quantity="other">Eyða þessum <xliff:g>%d</xliff:g> kortum varanlega?</item>
|
||||
</plurals>
|
||||
<string name="barcodeImageDescriptionWithType">Mynd <xliff:g>%s</xliff:g> strikamerki</string>
|
||||
<string name="importExport">Flytja inn/út</string>
|
||||
<string name="importFailed">Gat ekki flutt inn</string>
|
||||
<plurals name="deleteCardsTitle">
|
||||
<item quantity="one">Eyða <xliff:g>%d</xliff:g> korti</item>
|
||||
<item quantity="other">Eyða <xliff:g>%d</xliff:g> kortum</item>
|
||||
</plurals>
|
||||
<plurals name="groupCardCount">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> kort</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> kort</item>
|
||||
</plurals>
|
||||
<plurals name="balancePoints">
|
||||
<item quantity="one"><xliff:g>%s</xliff:g> punktur</item>
|
||||
<item quantity="other"><xliff:g>%s</xliff:g> punktar</item>
|
||||
</plurals>
|
||||
<string name="previousCard">Fyrra</string>
|
||||
<string name="failedToOpenUrl">Settu inn vef-vafra fyrst</string>
|
||||
<string name="welcome">Velkomin að Catima</string>
|
||||
<string name="importCards">Flytja inn kort</string>
|
||||
<string name="app_name">Catima</string>
|
||||
</resources>
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="action_search">Cerca</string>
|
||||
<string name="action_add">Aggiungi</string>
|
||||
<string name="noGiftCards">Premi il pulsante + per aggiungere una carta oppure importala dal menù ⋮</string>
|
||||
<string name="noMatchingGiftCards">Nessun risultato. Prova a cambiare la tua ricerca.</string>
|
||||
<string name="noMatchingGiftCards">Nessun risultato. Prova a modificare la ricerca.</string>
|
||||
<string name="storeName">Nome</string>
|
||||
<string name="note">Note</string>
|
||||
<string name="cardId">Codice</string>
|
||||
@@ -110,8 +110,8 @@
|
||||
<string name="addFromImage">Seleziona un\'immagine dalla galleria</string>
|
||||
<string name="unsupportedBarcodeType">Questo tipo di codice a barre non può ancora essere visualizzato. Potrebbe essere supportato in una versione successiva dell\'applicazione.</string>
|
||||
<string name="wrongValueForBarcodeType">Il valore non è valido per il tipo di codice a barre selezionato</string>
|
||||
<string name="app_resources">Risorse di terze parti: <xliff:g id="app_resources_list"> %s </xliff:g></string>
|
||||
<string name="app_libraries">Librerie di terze parti: <xliff:g id="app_libraries_list"> %s </xliff:g></string>
|
||||
<string name="app_resources">Risorse di terze parti: <xliff:g id="app_resources_list">%s</xliff:g></string>
|
||||
<string name="app_libraries">Librerie di terze parti: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">Voglio condividere alcune carte con te</string>
|
||||
<string name="no">No</string>
|
||||
<string name="yes">Sì</string>
|
||||
@@ -204,7 +204,6 @@
|
||||
<string name="welcome">Benvenuti su Catima</string>
|
||||
<string name="failedToOpenUrl">Installa prima un browser web</string>
|
||||
<string name="failedToRetrieveImageFile">Impossibile ottenere il file dell\'immagine</string>
|
||||
<string name="barcodeLongPressMessage">Si possono aprire solo immagini dell\'app della galleria</string>
|
||||
<string name="cameraPermissionDeniedTitle">Impossibile accedere alla fotocamera</string>
|
||||
<string name="noCameraPermissionDirectToSystemSetting">Per poter scansionare i codici a barre, Catima ha bisogno di accedere alla tua fotocamera. Premi qui per poter cambiare le impostazioni dei permessi.</string>
|
||||
<string name="updateBalance">Aggiorna bilancio</string>
|
||||
@@ -223,9 +222,7 @@
|
||||
<string name="switchToFrontImage">Passa all\'immagine frontale</string>
|
||||
<string name="switchToBackImage">Passa all\'immagine dietro</string>
|
||||
<string name="switchToBarcode">Passa al codice a barre</string>
|
||||
<string name="openFrontImageInGalleryApp">Apri immagine frontale nell\'app per la visualizzazione di immagini</string>
|
||||
<string name="donate">Dona</string>
|
||||
<string name="openBackImageInGalleryApp">Apri l\'immagine retro nell\'app per la visualizzazione di immagini</string>
|
||||
<string name="setBarcodeHeight">Imposta l\'altezza del codice a barre</string>
|
||||
<string name="icon_header_click_text">Premi a lungo per modificare l\'immagine</string>
|
||||
<string name="show_note">Mostra nota</string>
|
||||
@@ -307,4 +304,14 @@
|
||||
<string name="nothing_to_copy">Nessun valore trovato</string>
|
||||
<string name="barcodeEncoding">Codifica codici a barre</string>
|
||||
<string name="back">Indietro</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">Pausa NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">Sospende la funzione NFC per impedire che i pagamenti NFC vengano attivati durante la visualizzazione di una carta</string>
|
||||
<string name="nfc_blocked_while_viewing_card">La funzione NFC è disattivata</string>
|
||||
<string name="change_settings">Modifica impostazioni</string>
|
||||
<string name="nfc_block_system_error">Impossibile mettere in pausa l\'NFC</string>
|
||||
<string name="sort_by_most_recently_added">Aggiunte più di recente</string>
|
||||
<string name="show_card_id">Mostra ID carta</string>
|
||||
<string name="zoomFrontImage">Zoom immagine fronte</string>
|
||||
<string name="zoomBackImage">Zoom immagine retro</string>
|
||||
<string name="open_in_gallery">Apri nella galleria immagini</string>
|
||||
</resources>
|
||||
@@ -8,4 +8,9 @@
|
||||
</plurals>
|
||||
<string name="action_add">הוסף</string>
|
||||
<string name="noGiftCards">לחץ על כפתור הפלוס (+) כדי להוסיף כרטיס, או ייבא מתפריט ⋮.</string>
|
||||
<string name="noMatchingGiftCards">אין תוצאות. נסה לשנות את החיפוש.</string>
|
||||
<string name="storeName">שם החנות</string>
|
||||
<string name="note">הערות</string>
|
||||
<string name="barcodeType">סוג ברקוד</string>
|
||||
<string name="noBarcode">אין ברקוד</string>
|
||||
</resources>
|
||||