mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-10-24 03:13:45 +02:00
Compare commits
635 Commits
v0.13.21-b
...
v0.15.6-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e7137815b | ||
|
|
4226ee453f | ||
|
|
568a110443 | ||
|
|
7fd7a6ca8d | ||
|
|
3092c854ff | ||
|
|
a9566f4b23 | ||
|
|
7a5aa3c11d | ||
|
|
f2454bb028 | ||
|
|
2c1023a6fd | ||
|
|
9187ac6684 | ||
|
|
9640e43bc5 | ||
|
|
09b24b36f2 | ||
|
|
06a761c0d0 | ||
|
|
6ff7d7fcd7 | ||
|
|
0f27c79303 | ||
|
|
9961ad081f | ||
|
|
02056b4f78 | ||
|
|
0492588c25 | ||
|
|
ad8463ac8b | ||
|
|
7843de240a | ||
|
|
0037a08017 | ||
|
|
92ba432992 | ||
|
|
3a521f4014 | ||
|
|
e8bbc9799c | ||
|
|
96a48b4813 | ||
|
|
355450e82d | ||
|
|
99307c5b0c | ||
|
|
ace8be6514 | ||
|
|
1b538931b1 | ||
|
|
7ccd08ee2b | ||
|
|
104beed594 | ||
|
|
e8df594edb | ||
|
|
6e922a84f8 | ||
|
|
b74bf86277 | ||
|
|
6cbe50c9ef | ||
|
|
14fcca040b | ||
|
|
7bb545cff9 | ||
|
|
f52b1a246c | ||
|
|
273dfd89cc | ||
|
|
52d72d287b | ||
|
|
e8d8fcbe69 | ||
|
|
30c6c1afd3 | ||
|
|
c8c8093b3a | ||
|
|
c970b2cf2e | ||
|
|
0815283d31 | ||
|
|
55ee2d93b8 | ||
|
|
c9e17ed42b | ||
|
|
76a91b7fe0 | ||
|
|
f9b3169b6a | ||
|
|
4f42b5a3ee | ||
|
|
5d9645eaff | ||
|
|
c032808d82 | ||
|
|
282c94266e | ||
|
|
e2f7d52bee | ||
|
|
6317f0162a | ||
|
|
436a6310d7 | ||
|
|
0f74195255 | ||
|
|
5ba33786ab | ||
|
|
70fcfc1753 | ||
|
|
1e34048c0c | ||
|
|
45b1b23262 | ||
|
|
07842ace4e | ||
|
|
8e75280093 | ||
|
|
029b9ef498 | ||
|
|
51970abce7 | ||
|
|
f6faa19e5f | ||
|
|
dd7217ca54 | ||
|
|
6e13457eb2 | ||
|
|
3b319ee19b | ||
|
|
8ea8daa811 | ||
|
|
eb29b908c2 | ||
|
|
9935cb482e | ||
|
|
0d6e7181cf | ||
|
|
d225650e15 | ||
|
|
bfe09791d5 | ||
|
|
85d103f3f6 | ||
|
|
9e6dbe2465 | ||
|
|
355e5ccda6 | ||
|
|
a5f6f05e10 | ||
|
|
db0d35d80b | ||
|
|
6fca2a3931 | ||
|
|
0305a42b02 | ||
|
|
77d81716ed | ||
|
|
1fc8ee6fee | ||
|
|
3e54e80eb6 | ||
|
|
3c9bb63d32 | ||
|
|
617ab9efab | ||
|
|
bc574097e2 | ||
|
|
4cc64dc233 | ||
|
|
45fa0a165a | ||
|
|
0e5c07a078 | ||
|
|
601a742c71 | ||
|
|
c972401b6e | ||
|
|
024e81cf01 | ||
|
|
975ed402d5 | ||
|
|
b9e8083744 | ||
|
|
bb859708bc | ||
|
|
3cf2c221ac | ||
|
|
6edd7edcd2 | ||
|
|
4e26a02d78 | ||
|
|
bb36a57053 | ||
|
|
b291c800f1 | ||
|
|
b63a798d86 | ||
|
|
eacf3777a4 | ||
|
|
a5a7436bb1 | ||
|
|
2a4cc35df7 | ||
|
|
cdccf58b76 | ||
|
|
27300383a1 | ||
|
|
375b9bce30 | ||
|
|
b6b8db48df | ||
|
|
36e6c267b9 | ||
|
|
13066b3b4a | ||
|
|
ccbe9d00c8 | ||
|
|
ce291582cb | ||
|
|
bb37bc3b51 | ||
|
|
5a7747acd1 | ||
|
|
1bc2ec9461 | ||
|
|
2b977fc2b0 | ||
|
|
de60c4ee9e | ||
|
|
de67e40c00 | ||
|
|
cc4b016c64 | ||
|
|
f64f561d6f | ||
|
|
80bddf8a6b | ||
|
|
cbaaec961c | ||
|
|
5477b3f936 | ||
|
|
fd59a93ede | ||
|
|
cd316b7138 | ||
|
|
d1955192ed | ||
|
|
9beb839bf4 | ||
|
|
29ea303093 | ||
|
|
feff6751ca | ||
|
|
ca33fdf752 | ||
|
|
fdcdfe89d6 | ||
|
|
48ed2115a7 | ||
|
|
65988f4e08 | ||
|
|
ede65eda6c | ||
|
|
5da56acac8 | ||
|
|
5720c55301 | ||
|
|
ffefa4b30e | ||
|
|
80e4986b23 | ||
|
|
dc92ccda0a | ||
|
|
f9bab18076 | ||
|
|
2dec52e221 | ||
|
|
7413f693d7 | ||
|
|
415460df75 | ||
|
|
125a194468 | ||
|
|
32e9afbf36 | ||
|
|
3eca704f4a | ||
|
|
9c95129311 | ||
|
|
bf34c1bcdb | ||
|
|
284c687d77 | ||
|
|
09afb5a3f5 | ||
|
|
0138721451 | ||
|
|
2d5f610941 | ||
|
|
864fa7762b | ||
|
|
4fde38ee6a | ||
|
|
6cdf0f10d4 | ||
|
|
b66592c25f | ||
|
|
43616c566d | ||
|
|
62f1dc17a0 | ||
|
|
0e9a8a937a | ||
|
|
9a86b245ce | ||
|
|
64533f7a3f | ||
|
|
0b7de8d387 | ||
|
|
8eba4860fe | ||
|
|
b53e2f57e6 | ||
|
|
e1e834297b | ||
|
|
e37dc6e341 | ||
|
|
c91c896854 | ||
|
|
7e5dfa03d6 | ||
|
|
1a4ec3f049 | ||
|
|
756763fcbe | ||
|
|
93036c4e67 | ||
|
|
15bf972ef6 | ||
|
|
bcb4567382 | ||
|
|
3890c4ffb9 | ||
|
|
c8d82f24a8 | ||
|
|
367e740a9c | ||
|
|
5b5a51bdad | ||
|
|
7fc030a53e | ||
|
|
65087fb5f8 | ||
|
|
d5e789af3c | ||
|
|
45212c699f | ||
|
|
03106f3522 | ||
|
|
cd2857cb94 | ||
|
|
b3ddb8658f | ||
|
|
7ae80b6b49 | ||
|
|
34f27f9cd4 | ||
|
|
4c53b58fd8 | ||
|
|
6102ab4be8 | ||
|
|
6fcf565974 | ||
|
|
77bd16c7cf | ||
|
|
7d593080ec | ||
|
|
96ea74abeb | ||
|
|
e50e3957ec | ||
|
|
8cb9420939 | ||
|
|
bbb7b8caa2 | ||
|
|
cc284f3c9e | ||
|
|
bd8aae5fbf | ||
|
|
3d162f33df | ||
|
|
ff3c37f1b4 | ||
|
|
35160ce385 | ||
|
|
c8eef91813 | ||
|
|
6791b20121 | ||
|
|
3311c49236 | ||
|
|
5223e58f83 | ||
|
|
e0f9fc233d | ||
|
|
509bd21655 | ||
|
|
5be7229c4b | ||
|
|
efd3c461e0 | ||
|
|
13aed69f0e | ||
|
|
b03675811c | ||
|
|
e4beac185e | ||
|
|
bdc7d9dd84 | ||
|
|
b49d1e0529 | ||
|
|
3c0eb48d53 | ||
|
|
0d65531ec1 | ||
|
|
3848f9822b | ||
|
|
e1cdc002b9 | ||
|
|
168c1cf1ce | ||
|
|
5b142b4401 | ||
|
|
9be3d76590 | ||
|
|
26971aa109 | ||
|
|
18f9049bc6 | ||
|
|
53cf253b67 | ||
|
|
6d416f45a9 | ||
|
|
a34a447164 | ||
|
|
d3247a9ec1 | ||
|
|
dfb5a2b97f | ||
|
|
deae96e191 | ||
|
|
b8c0e18bb4 | ||
|
|
a083da1546 | ||
|
|
2518c70ec8 | ||
|
|
6ead8d261c | ||
|
|
b5f76a6ebc | ||
|
|
5e38bca535 | ||
|
|
c39142ee77 | ||
|
|
d0174479d7 | ||
|
|
aa23267d70 | ||
|
|
92f5da306d | ||
|
|
8601e2af7a | ||
|
|
5d9f31b99e | ||
|
|
8b29158d8b | ||
|
|
b68cf3c671 | ||
|
|
858a50a414 | ||
|
|
598df624df | ||
|
|
e6da2b73ed | ||
|
|
f44424d37a | ||
|
|
623804ae68 | ||
|
|
b7544939c2 | ||
|
|
63fa6d4660 | ||
|
|
9506c11951 | ||
|
|
ce5639bb98 | ||
|
|
99ef6ca861 | ||
|
|
43d5a3da27 | ||
|
|
30723110af | ||
|
|
a0ecb4e2bb | ||
|
|
a726b09f26 | ||
|
|
52b1d1fe33 | ||
|
|
8b4709c132 | ||
|
|
b4217a791e | ||
|
|
9b3a982d54 | ||
|
|
0a47d3eab5 | ||
|
|
7d7803247d | ||
|
|
aa8d45e636 | ||
|
|
7f3e87767c | ||
|
|
f76ea9bba5 | ||
|
|
1eefeae060 | ||
|
|
03447c33be | ||
|
|
f6c6cd5cb8 | ||
|
|
67b56cee50 | ||
|
|
2d7465cca6 | ||
|
|
d06f20690d | ||
|
|
e613a494ff | ||
|
|
503a01675e | ||
|
|
723f7e63e9 | ||
|
|
7ff6916e5f | ||
|
|
910ed26525 | ||
|
|
c0fffa6293 | ||
|
|
d800d3ba41 | ||
|
|
d267343c64 | ||
|
|
0f72e37272 | ||
|
|
89e44d581f | ||
|
|
c908a710de | ||
|
|
f0809cde72 | ||
|
|
e6da8c1c18 | ||
|
|
81ea67afa8 | ||
|
|
488049821e | ||
|
|
fc7408a8f8 | ||
|
|
6955191fdd | ||
|
|
ba59693d90 | ||
|
|
ce6e51128d | ||
|
|
a7a26037ae | ||
|
|
00854abd1a | ||
|
|
c26649d5f2 | ||
|
|
63b76a249f | ||
|
|
95f19208ad | ||
|
|
f4a902ee47 | ||
|
|
036823cbb6 | ||
|
|
547c2c8c0d | ||
|
|
7453d19d27 | ||
|
|
2a0b68d637 | ||
|
|
02f374fdf0 | ||
|
|
3536dcd775 | ||
|
|
6fa887e536 | ||
|
|
71755763b9 | ||
|
|
5aabcccfd4 | ||
|
|
1e009e2211 | ||
|
|
811bc6434b | ||
|
|
a5b2d06742 | ||
|
|
2c00674835 | ||
|
|
9bbff1f436 | ||
|
|
c75416f9be | ||
|
|
2cb750a7b0 | ||
|
|
abfec51964 | ||
|
|
e314717a3e | ||
|
|
290a78acb5 | ||
|
|
b40a4dd243 | ||
|
|
b38f6d0581 | ||
|
|
5c562a6bdb | ||
|
|
238d43f916 | ||
|
|
818dc23089 | ||
|
|
ddb50940ce | ||
|
|
96fb97f4ef | ||
|
|
6c0459c2ae | ||
|
|
30a9e1ee4b | ||
|
|
15a1f1e6e0 | ||
|
|
827e2b161c | ||
|
|
e95230859e | ||
|
|
95cb8ca493 | ||
|
|
5735293efb | ||
|
|
035df1f338 | ||
|
|
36a8b04c4f | ||
|
|
3d5411ef28 | ||
|
|
df7b74e727 | ||
|
|
0138599120 | ||
|
|
66446cbffc | ||
|
|
ef40f3900f | ||
|
|
ce259764a8 | ||
|
|
65187cb17b | ||
|
|
8f25245ad1 | ||
|
|
58b8baa019 | ||
|
|
ba1db88c8d | ||
|
|
c8f2f42a35 | ||
|
|
4e5a9b2af5 | ||
|
|
d1ab961c14 | ||
|
|
33caf4644e | ||
|
|
f34ffe18a8 | ||
|
|
4b1c5e111d | ||
|
|
dbd7c296c6 | ||
|
|
df93cbde8f | ||
|
|
d8cbc121a7 | ||
|
|
8163cd5c8f | ||
|
|
e9e9adb174 | ||
|
|
21fdfc1eef | ||
|
|
4304251e1e | ||
|
|
ae0cd74b0e | ||
|
|
6935bff244 | ||
|
|
102b9da617 | ||
|
|
8bd0d185ae | ||
|
|
7bfc5ae0a8 | ||
|
|
c72a41db9d | ||
|
|
5fbb1c2e32 | ||
|
|
850dd53c69 | ||
|
|
e3baf91037 | ||
|
|
4c811c9c04 | ||
|
|
4bc9f5826e | ||
|
|
9f19e4dc83 | ||
|
|
c9cb865c9c | ||
|
|
b8a7dd984d | ||
|
|
3ede46c445 | ||
|
|
87c4af94d3 | ||
|
|
6c30bd36d1 | ||
|
|
7e85ccaf28 | ||
|
|
db3a262410 | ||
|
|
70162da491 | ||
|
|
f3632a4033 | ||
|
|
2f4e176116 | ||
|
|
3ea8dc598c | ||
|
|
3f63dc0180 | ||
|
|
1f6701b183 | ||
|
|
3a170fbf90 | ||
|
|
b57e523a07 | ||
|
|
8c4509f255 | ||
|
|
3878c287be | ||
|
|
7789e3f3a7 | ||
|
|
94ac744f41 | ||
|
|
a83211f260 | ||
|
|
4f733affc5 | ||
|
|
ffb2a982bc | ||
|
|
9658a65661 | ||
|
|
68d4083708 | ||
|
|
af5377d2eb | ||
|
|
6ac8ad543c | ||
|
|
c10a63e76e | ||
|
|
b423a2af52 | ||
|
|
9b70289345 | ||
|
|
2a94c1f33a | ||
|
|
ea205c1acb | ||
|
|
6e1767730b | ||
|
|
3fc1ab37d7 | ||
|
|
c4a3f7333d | ||
|
|
fb820e01b1 | ||
|
|
504aca5287 | ||
|
|
ae7d5546ae | ||
|
|
8bcb10b281 | ||
|
|
845eaab73e | ||
|
|
298ff190ec | ||
|
|
734a548bc3 | ||
|
|
591973d97f | ||
|
|
a2f57ecd66 | ||
|
|
13dcfb479d | ||
|
|
3328d80130 | ||
|
|
f9df3ac0e2 | ||
|
|
61d66e80d8 | ||
|
|
93e0360116 | ||
|
|
2cec5f7934 | ||
|
|
8d4a09b007 | ||
|
|
6d8d2fa99d | ||
|
|
59ecd55530 | ||
|
|
77fcc89e37 | ||
|
|
7d63da4d9c | ||
|
|
8eea0bae99 | ||
|
|
f5dcd8f776 | ||
|
|
e1f2baeeda | ||
|
|
84d8ff8b0b | ||
|
|
9862c22fdb | ||
|
|
8c82f53dd2 | ||
|
|
5cf4c21ce3 | ||
|
|
4951f62d4a | ||
|
|
4c0c4b7010 | ||
|
|
96051e614c | ||
|
|
a2e494b2ba | ||
|
|
2d5a9bec84 | ||
|
|
7d9571cfdd | ||
|
|
acc6a780fa | ||
|
|
0e36d42a06 | ||
|
|
5cfddd807a | ||
|
|
17b5604f2a | ||
|
|
fdb6eed6d0 | ||
|
|
13de0437b8 | ||
|
|
a43c45f310 | ||
|
|
9c56a4d1fc | ||
|
|
2aea1d2631 | ||
|
|
118e05a0fa | ||
|
|
05f497787e | ||
|
|
53cf4d0234 | ||
|
|
6e735b1763 | ||
|
|
873a1a0683 | ||
|
|
27b1149d1c | ||
|
|
c1e64f111e | ||
|
|
b2af8448fd | ||
|
|
8f44338e76 | ||
|
|
e4a55abcb3 | ||
|
|
d7348b4973 | ||
|
|
09421230f2 | ||
|
|
4596e32258 | ||
|
|
4dc007a4f6 | ||
|
|
c53a156969 | ||
|
|
94bd0774fb | ||
|
|
b178b1d780 | ||
|
|
cbc840378c | ||
|
|
aa7989c16d | ||
|
|
85f9336804 | ||
|
|
d66be3ecda | ||
|
|
c08e05bd6c | ||
|
|
e08ab89fd4 | ||
|
|
8ba0a0a776 | ||
|
|
73ed0cea88 | ||
|
|
58a378d212 | ||
|
|
553307ba70 | ||
|
|
78f73a9049 | ||
|
|
abc69e7a0e | ||
|
|
503914dbce | ||
|
|
69680f8680 | ||
|
|
1a616aacb7 | ||
|
|
52d19f267e | ||
|
|
75a8dc9ee4 | ||
|
|
8c63920ef6 | ||
|
|
dda5619cdc | ||
|
|
9cc25fd931 | ||
|
|
f5a9eb4295 | ||
|
|
ebe256e482 | ||
|
|
e215585a64 | ||
|
|
8b01fc03ec | ||
|
|
9af2c8370d | ||
|
|
01f9003b8d | ||
|
|
73a3c7eb71 | ||
|
|
370ec1432e | ||
|
|
4b63c124bb | ||
|
|
3d7df604b1 | ||
|
|
59d1d275bc | ||
|
|
f7f0332b13 | ||
|
|
917c5f5083 | ||
|
|
370cf05615 | ||
|
|
101d892c7f | ||
|
|
db2faf6511 | ||
|
|
3a83d1bf79 | ||
|
|
070e309aa6 | ||
|
|
11ca7c3f7d | ||
|
|
7a0acaefb3 | ||
|
|
486f3664ef | ||
|
|
387b165314 | ||
|
|
4ba3f7996e | ||
|
|
7ce41dfb67 | ||
|
|
efd55c8d36 | ||
|
|
71543c64d4 | ||
|
|
408c1a541f | ||
|
|
8f739d9e0b | ||
|
|
bd8f608ee6 | ||
|
|
551643b11c | ||
|
|
075ecae540 | ||
|
|
dcb8807fa7 | ||
|
|
77ec2df31c | ||
|
|
d92e812554 | ||
|
|
0a6e1f9cc6 | ||
|
|
f3c3680382 | ||
|
|
f7e783a556 | ||
|
|
fd1b72563d | ||
|
|
5a94ef82dd | ||
|
|
3ad46b7e21 | ||
|
|
1e94d71665 | ||
|
|
0899a576ff | ||
|
|
db2476f3a5 | ||
|
|
8ba182870d | ||
|
|
d81085a9e8 | ||
|
|
ae92a459b7 | ||
|
|
e101c434d5 | ||
|
|
9686d0f0ca | ||
|
|
1dd0392b78 | ||
|
|
5e184d733b | ||
|
|
ef0b20887b | ||
|
|
5cb4bd998b | ||
|
|
57f499c6a5 | ||
|
|
c2ae6e19f9 | ||
|
|
16104fde03 | ||
|
|
13e10692b1 | ||
|
|
7fc93f23c0 | ||
|
|
d6ddf87365 | ||
|
|
05172744cd | ||
|
|
2504ae24fc | ||
|
|
5e41d5762b | ||
|
|
57d44c972f | ||
|
|
be0b57ac00 | ||
|
|
862bb2b276 | ||
|
|
5eac851f80 | ||
|
|
0deab8296f | ||
|
|
6785708661 | ||
|
|
5307fd0901 | ||
|
|
788c4c7917 | ||
|
|
3f6d96289d | ||
|
|
5cfd80e510 | ||
|
|
9eb32ae55a | ||
|
|
82e08150ab | ||
|
|
e956ee9254 | ||
|
|
bb4f34317b | ||
|
|
d08ff3fbae | ||
|
|
f5479ec82f | ||
|
|
3eb25c4060 | ||
|
|
03bb1ad9a6 | ||
|
|
b59d3e77f9 | ||
|
|
7c41692d5f | ||
|
|
ce89d456e1 | ||
|
|
a0c48fcca6 | ||
|
|
d0cba6d6bc | ||
|
|
6baf6ccf4b | ||
|
|
a2571e61a8 | ||
|
|
f61824ff0d | ||
|
|
734a1aeb01 | ||
|
|
5269aad90d | ||
|
|
eaeee188eb | ||
|
|
8c850e06ca | ||
|
|
0f754a8da8 | ||
|
|
81c4d4f393 | ||
|
|
5317aee18d | ||
|
|
b66eeba3b5 | ||
|
|
522ff1ddf7 | ||
|
|
7ef9c43ee3 | ||
|
|
05ac76e3e9 | ||
|
|
4838402797 | ||
|
|
0de12c7c07 | ||
|
|
0dadd8bffe | ||
|
|
75a0cb1189 | ||
|
|
a52d936b4d | ||
|
|
cfd04dc602 | ||
|
|
7622e63975 | ||
|
|
3e7172b9d1 | ||
|
|
7aa0294447 | ||
|
|
5b89c4d293 | ||
|
|
3bb991f57f | ||
|
|
1dd3aa0e8a | ||
|
|
189cecbc37 | ||
|
|
9680ba08e9 | ||
|
|
dcf5bd37ca | ||
|
|
d6a4b0a96d | ||
|
|
5f8638d349 | ||
|
|
1de274df39 | ||
|
|
3df801c54e | ||
|
|
d473cb49c5 | ||
|
|
2a5118a2cf | ||
|
|
347c56da55 | ||
|
|
8073723e1f | ||
|
|
c8e90a755d | ||
|
|
aeb0a5d8ea | ||
|
|
09fe7f3ecd | ||
|
|
a549411589 | ||
|
|
f426b5e118 | ||
|
|
12a8ef5e31 | ||
|
|
8210279a4c | ||
|
|
999d13b80d | ||
|
|
0573c0b270 | ||
|
|
5a36a7980c | ||
|
|
6bf9b5297f | ||
|
|
fdc6b0ff00 | ||
|
|
73c20a53d2 | ||
|
|
16ae8d8e4d | ||
|
|
3f8cfae64e | ||
|
|
6861a71efb | ||
|
|
2a603f410f | ||
|
|
6d22788f92 | ||
|
|
e36e6bbaca | ||
|
|
ac3a13ed73 | ||
|
|
202f7df5cb | ||
|
|
1b902b1a18 | ||
|
|
1765d399c8 | ||
|
|
5dd79707f1 | ||
|
|
14755134bf | ||
|
|
cccde7e135 | ||
|
|
3dafd643c0 | ||
|
|
76f8cd4102 | ||
|
|
a8bfb03f58 | ||
|
|
10ead4f3e0 | ||
|
|
af5a6857ba | ||
|
|
e94e195e73 | ||
|
|
869ebd90df |
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Something isn't working right.
|
||||||
|
title: ''
|
||||||
|
labels: bug, to check
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Prerequisites**
|
||||||
|
<!-- Please ensure your request is not part of an existing issue. -->
|
||||||
|
<!-- Please ensure you have checked the Obtainium Wiki. -->
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
<!-- Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Tap on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error -->
|
||||||
|
|
||||||
|
**Screenshots and Logs**
|
||||||
|
<!-- If applicable, add screenshots, logs, and any other artifacts (like some/all files under `/Android/data/dev.imranr.obtainium/`) that you think may help troubleshoot the issue. -->
|
||||||
|
|
||||||
|
**Please complete the following information:**
|
||||||
|
- Device: <!-- [e.g. Pixel 7] -->
|
||||||
|
- OS: <!-- [e.g. GrapheneOS] -->
|
||||||
|
- Obtainium Version: <!-- [e.g. 0.14.6-beta] -->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
<!-- Add any other context about the problem here. -->
|
||||||
30
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest a new Source, setting, or other feature.
|
||||||
|
title: ''
|
||||||
|
labels: enhancement, to check
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Prerequisites**
|
||||||
|
<!-- Please ensure your request is not part of an existing issue. -->
|
||||||
|
<!-- Please ensure you have checked the Obtainium Wiki. -->
|
||||||
|
|
||||||
|
**Describe the feature**
|
||||||
|
<!-- A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
For new Sources, it's preferable (not required) if you suggest how the following details can be extracted from the Source in a reliable way (like an API or through web scraping):
|
||||||
|
- The App version (or any release-specific identifier - a "pseudo-version") for the latest release
|
||||||
|
- One or more APK URL(s) for the latest release
|
||||||
|
- Above details for previous releases (optional)
|
||||||
|
|
||||||
|
Note that the Web scraper cannot deal with JavaScript-enabled content. -->
|
||||||
|
|
||||||
|
**Describe alternatives you've considered (if applicable)**
|
||||||
|
<!-- A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
Note that app-specific Sources are less likely to be added. In those cases, see if the HTML Source will work for you (if not, see if a generally-applicable enhancement to the HTML Source would work, and suggest that instead). -->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
<!-- Add any other context or screenshots about the feature request here. -->
|
||||||
71
.github/workflows/release.yml
vendored
Normal file
71
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
name: Build and Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
|
||||||
|
- name: Import GPG key
|
||||||
|
id: import_pgp_key
|
||||||
|
uses: crazy-max/ghaction-import-gpg@v6
|
||||||
|
with:
|
||||||
|
gpg_private_key: ${{ secrets.PGP_KEY_BASE64 }}
|
||||||
|
passphrase: ${{ secrets.PGP_PASSPHRASE }}
|
||||||
|
|
||||||
|
- name: Build APKs
|
||||||
|
run: |
|
||||||
|
sed -i 's/signingConfig signingConfigs.release//g' android/app/build.gradle
|
||||||
|
flutter build apk --flavor normal && flutter build apk --split-per-abi --flavor normal
|
||||||
|
for file in build/app/outputs/flutter-apk/app-*normal*.apk*; do mv "$file" "${file//-normal/}"; done
|
||||||
|
rm ./build/app/outputs/flutter-apk/*.sha1
|
||||||
|
ls -l ./build/app/outputs/flutter-apk/
|
||||||
|
|
||||||
|
- name: Sign APKs
|
||||||
|
env:
|
||||||
|
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
|
||||||
|
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||||
|
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
|
||||||
|
run: |
|
||||||
|
echo "${KEYSTORE_BASE64}" | base64 -d > apksign.keystore
|
||||||
|
for apk in ./build/app/outputs/flutter-apk/*-release*.apk; do
|
||||||
|
unsignedFn=${apk/-release/-unsigned}
|
||||||
|
mv "$apk" "$unsignedFn"
|
||||||
|
${ANDROID_HOME}/build-tools/30.0.2/apksigner sign --ks apksign.keystore --ks-pass pass:"${KEYSTORE_PASSWORD}" --out "${apk}" "${unsignedFn}"
|
||||||
|
sha256sum ${apk} | cut -d " " -f 1 > "$apk".sha256
|
||||||
|
gpg --batch --pinentry-mode loopback --passphrase "${PGP_PASSPHRASE}" --sign --detach-sig "$apk".sha256
|
||||||
|
done
|
||||||
|
rm apksign.keystore
|
||||||
|
PGP_KEY_FINGERPRINT="${{ steps.import_pgp_key.outputs.fingerprint }}"
|
||||||
|
|
||||||
|
- name: Extract Version
|
||||||
|
id: extract_version
|
||||||
|
run: |
|
||||||
|
VERSION=$(grep -oP "currentVersion = '\K[^']+" lib/main.dart)
|
||||||
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
TAG=$(grep -oP "'.*\\\$currentVersion.*'" lib/main.dart | head -c -2 | tail -c +2 | sed "s/\$currentVersion/$VERSION/g")
|
||||||
|
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||||
|
if [ -n "$(echo $TAG | grep -oP '\-beta$')" ]; then BETA=true; else BETA=false; fi
|
||||||
|
echo "beta=$BETA" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create Tag
|
||||||
|
uses: mathieudutour/github-tag-action@v6.1
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||||
|
custom_tag: "${{ steps.extract_version.outputs.tag }}"
|
||||||
|
tag_prefix: ""
|
||||||
|
|
||||||
|
- name: Create Release And Upload APKs
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||||
|
tag: "${{ steps.extract_version.outputs.tag }}"
|
||||||
|
prerelease: "${{ steps.extract_version.outputs.beta }}"
|
||||||
|
artifacts: ./build/app/outputs/flutter-apk/*-release*.apk*
|
||||||
|
generateReleaseNotes: true
|
||||||
53
README.md
53
README.md
@@ -1,31 +1,41 @@
|
|||||||
#  Obtainium
|
#  Obtainium
|
||||||
|
|
||||||
|
[](https://techforpalestine.org/learn-more)
|
||||||
|
|
||||||
Get Android App Updates Directly From the Source.
|
Get Android App Updates Directly From the Source.
|
||||||
|
|
||||||
Obtainium allows you to install and update Open-Source Apps directly from their releases pages, and receive notifications when new releases are made available.
|
Obtainium allows you to install and update Apps directly from their releases pages, and receive notifications when new releases are made available.
|
||||||
|
|
||||||
Motivation: [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0)
|
Motivation: [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0)
|
||||||
|
|
||||||
|
Read the Wiki: [https://github.com/ImranR98/Obtainium/wiki](https://github.com/ImranR98/Obtainium/wiki)
|
||||||
|
|
||||||
Currently supported App sources:
|
Currently supported App sources:
|
||||||
- [GitHub](https://github.com/)
|
- Open Source - General:
|
||||||
- [GitLab](https://gitlab.com/)
|
- [GitHub](https://github.com/)
|
||||||
- [Codeberg](https://codeberg.org/)
|
- [GitLab](https://gitlab.com/)
|
||||||
- [F-Droid](https://f-droid.org/)
|
- [Codeberg](https://codeberg.org/)
|
||||||
- [IzzyOnDroid](https://android.izzysoft.de/)
|
- [F-Droid](https://f-droid.org/)
|
||||||
- [Mullvad](https://mullvad.net/en/)
|
- Third Party F-Droid Repos
|
||||||
- [Signal](https://signal.org/)
|
- [IzzyOnDroid](https://android.izzysoft.de/)
|
||||||
- [SourceForge](https://sourceforge.net/)
|
- [SourceForge](https://sourceforge.net/)
|
||||||
- [SourceHut](https://git.sr.ht/)
|
- [SourceHut](https://git.sr.ht/)
|
||||||
- [APKMirror](https://apkmirror.com/) (Track-Only)
|
- Other - General:
|
||||||
- [APKPure](https://apkpure.com/)
|
- [APKPure](https://apkpure.com/)
|
||||||
- Third Party F-Droid Repos
|
- [Aptoide](https://aptoide.com/)
|
||||||
- Jenkins Jobs
|
- [Uptodown](https://uptodown.com/)
|
||||||
- [Steam](https://store.steampowered.com/mobile)
|
- [APKMirror](https://apkmirror.com/) (Track-Only)
|
||||||
- [Telegram App](https://telegram.org)
|
- [Huawei AppGallery](https://appgallery.huawei.com/)
|
||||||
- [VLC](https://www.videolan.org/vlc/download-android.html)
|
- Jenkins Jobs
|
||||||
- [Neutron Code](https://neutroncode.com)
|
- Open Source - App-Specific:
|
||||||
- "HTML" (Fallback)
|
- [Mullvad](https://mullvad.net/en/)
|
||||||
- Any other URL that returns an HTML page with links to APK files (if multiple, the last file alphabetically is picked)
|
- [Signal](https://signal.org/)
|
||||||
|
- [VLC](https://videolan.org/)
|
||||||
|
- Other - App-Specific:
|
||||||
|
- [WhatsApp](https://whatsapp.com)
|
||||||
|
- [Telegram App](https://telegram.org)
|
||||||
|
- [Neutron Code](https://neutroncode.com)
|
||||||
|
- "HTML" (Fallback): Any other URL that returns an HTML page with links to APK files
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -33,8 +43,9 @@ Currently supported App sources:
|
|||||||
alt="Get it on GitHub"
|
alt="Get it on GitHub"
|
||||||
height="80">](https://github.com/ImranR98/Obtainium/releases)
|
height="80">](https://github.com/ImranR98/Obtainium/releases)
|
||||||
|
|
||||||
|
[PGP Public Key](https://keyserver.ubuntu.com/pks/lookup?search=contact%40imranr.dev&fingerprint=on&op=index)
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
- Auto (unattended) updates are unsupported due to a lack of any capable Flutter plugin.
|
|
||||||
- For some sources, data is gathered using Web scraping and can easily break due to changes in website design. In such cases, more reliable methods may be unavailable.
|
- For some sources, data is gathered using Web scraping and can easily break due to changes in website design. In such cases, more reliable methods may be unavailable.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ if (flutterVersionName == null) {
|
|||||||
|
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'dev.rikka.tools.refine'
|
||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
def keystoreProperties = new Properties()
|
def keystoreProperties = new Properties()
|
||||||
@@ -32,7 +33,7 @@ if (keystorePropertiesFile.exists()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 33
|
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -49,16 +50,28 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId "dev.imranr.obtainium"
|
applicationId "dev.imranr.obtainium"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
||||||
minSdkVersion 23
|
minSdkVersion 24
|
||||||
targetSdkVersion 33
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flavorDimensions "flavor"
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
normal {
|
||||||
|
dimension "flavor"
|
||||||
|
applicationIdSuffix ""
|
||||||
|
}
|
||||||
|
fdroid {
|
||||||
|
dimension "flavor"
|
||||||
|
applicationIdSuffix ".fdroid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
release {
|
release {
|
||||||
keyAlias keystoreProperties['keyAlias']
|
keyAlias keystoreProperties['keyAlias']
|
||||||
@@ -78,6 +91,20 @@ flutter {
|
|||||||
source '../..'
|
source '../..'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
repositories {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
maven { url 'https://jitpack.io' }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
def shizuku_version = '13.1.5'
|
||||||
|
implementation "dev.rikka.shizuku:api:$shizuku_version"
|
||||||
|
implementation "dev.rikka.shizuku:provider:$shizuku_version"
|
||||||
|
|
||||||
|
def hidden_api_version = '4.3.1'
|
||||||
|
implementation "dev.rikka.tools.refine:runtime:$hidden_api_version"
|
||||||
|
implementation "dev.rikka.hidden:compat:$hidden_api_version"
|
||||||
|
compileOnly "dev.rikka.hidden:stub:$hidden_api_version"
|
||||||
|
implementation "org.lsposed.hiddenapibypass:hiddenapibypass:4.3"
|
||||||
|
|
||||||
|
implementation "com.github.topjohnwu.libsu:core:5.2.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="dev.imranr.obtainium">
|
package="dev.imranr.obtainium">
|
||||||
<application
|
<application
|
||||||
android:label="Obtainium"
|
android:label="Obtainium"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:requestLegacyExternalStorage="true">
|
android:requestLegacyExternalStorage="true"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -28,46 +30,47 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action
|
<action
|
||||||
android:name="com.android_package_installer.content.SESSION_API_PACKAGE_INSTALLED"
|
android:name="com.android_package_installer.content.SESSION_API_PACKAGE_INSTALLED"
|
||||||
android:exported="false"/>
|
android:exported="false" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="obtainium" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
<service
|
|
||||||
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmService"
|
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
|
||||||
android:exported="false"/>
|
|
||||||
<receiver
|
|
||||||
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmBroadcastReceiver"
|
|
||||||
android:exported="false"/>
|
|
||||||
<receiver
|
|
||||||
android:name="dev.fluttercommunity.plus.androidalarmmanager.RebootBroadcastReceiver"
|
|
||||||
android:enabled="false"
|
|
||||||
android:exported="false">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="dev.imranr.obtainium"
|
android:authorities="dev.imranr.obtainium"
|
||||||
android:grantUriPermissions="true">
|
android:grantUriPermissions="true">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/file_paths"/>
|
android:resource="@xml/file_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
<provider
|
||||||
|
android:name="rikka.shizuku.ShizukuProvider"
|
||||||
|
android:authorities="${applicationId}.shizuku"
|
||||||
|
android:multiprocess="false"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||||
</application>
|
</application>
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="29"/>
|
android:maxSdkVersion="29" />
|
||||||
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package dev.imranr.obtainium
|
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
|
||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package dev.imranr.obtainium
|
||||||
|
|
||||||
|
import android.util.Xml
|
||||||
|
import org.xmlpull.v1.XmlPullParser
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
|
class DefaultSystemFont {
|
||||||
|
fun get(): String {
|
||||||
|
return try {
|
||||||
|
val file = File("/system/etc/fonts.xml")
|
||||||
|
val fileStream = FileInputStream(file)
|
||||||
|
parseFontsFileStream(fileStream)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.message ?: "Unknown fonts.xml parsing exception"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseFontsFileStream(fileStream: FileInputStream): String {
|
||||||
|
fileStream.use { stream ->
|
||||||
|
val parser = Xml.newPullParser()
|
||||||
|
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||||
|
parser.setInput(stream, null)
|
||||||
|
parser.nextTag()
|
||||||
|
return parseFonts(parser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseFonts(parser: XmlPullParser): String {
|
||||||
|
while (!((parser.next() == XmlPullParser.END_TAG) && (parser.name == "family"))) {
|
||||||
|
if ((parser.eventType == XmlPullParser.START_TAG) && (parser.name == "font")
|
||||||
|
&& (parser.getAttributeValue(null, "style") == "normal")
|
||||||
|
&& (parser.getAttributeValue(null, "weight") == "400")) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parser.next()
|
||||||
|
val fontFile = parser.text.trim()
|
||||||
|
if (fontFile == "") {
|
||||||
|
throw NoSuchFieldException("The font filename couldn't be found in fonts.xml")
|
||||||
|
}
|
||||||
|
return "/system/fonts/$fontFile"
|
||||||
|
}
|
||||||
|
}
|
||||||
179
android/app/src/main/kotlin/dev/imranr/obtainium/MainActivity.kt
Normal file
179
android/app/src/main/kotlin/dev/imranr/obtainium/MainActivity.kt
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
package dev.imranr.obtainium
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentSender
|
||||||
|
import android.content.pm.IPackageInstaller
|
||||||
|
import android.content.pm.IPackageInstallerSession
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Process
|
||||||
|
import androidx.annotation.NonNull
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import dev.imranr.obtainium.util.IIntentSenderAdaptor
|
||||||
|
import dev.imranr.obtainium.util.IntentSenderUtils
|
||||||
|
import dev.imranr.obtainium.util.PackageInstallerUtils
|
||||||
|
import dev.imranr.obtainium.util.ShizukuSystemServerApi
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import io.flutter.plugin.common.MethodChannel.Result
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||||
|
import rikka.shizuku.Shizuku
|
||||||
|
import rikka.shizuku.Shizuku.OnRequestPermissionResultListener
|
||||||
|
import rikka.shizuku.ShizukuBinderWrapper
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity() {
|
||||||
|
private var nativeChannel: MethodChannel? = null
|
||||||
|
private val SHIZUKU_PERMISSION_REQUEST_CODE = (10..200).random()
|
||||||
|
|
||||||
|
private fun shizukuCheckPermission(result: Result) {
|
||||||
|
try {
|
||||||
|
if (Shizuku.isPreV11()) { // Unsupported
|
||||||
|
result.success(-1)
|
||||||
|
} else if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
result.success(1)
|
||||||
|
} else if (Shizuku.shouldShowRequestPermissionRationale()) { // Deny and don't ask again
|
||||||
|
result.success(0)
|
||||||
|
} else {
|
||||||
|
Shizuku.requestPermission(SHIZUKU_PERMISSION_REQUEST_CODE)
|
||||||
|
result.success(-2)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) { // If shizuku not running
|
||||||
|
result.success(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val shizukuRequestPermissionResultListener = OnRequestPermissionResultListener {
|
||||||
|
requestCode: Int, grantResult: Int ->
|
||||||
|
if (requestCode == SHIZUKU_PERMISSION_REQUEST_CODE) {
|
||||||
|
val res = if (grantResult == PackageManager.PERMISSION_GRANTED) 1 else 0
|
||||||
|
nativeChannel!!.invokeMethod("resPermShizuku", mapOf("res" to res))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shizukuInstallApk(apkFileUri: String, result: Result) {
|
||||||
|
val uri = Uri.parse(apkFileUri)
|
||||||
|
var res = false
|
||||||
|
var session: PackageInstaller.Session? = null
|
||||||
|
try {
|
||||||
|
val iPackageInstaller: IPackageInstaller =
|
||||||
|
ShizukuSystemServerApi.PackageManager_getPackageInstaller()
|
||||||
|
val isRoot = Shizuku.getUid() == 0
|
||||||
|
// The reason for use "com.android.shell" as installer package under adb
|
||||||
|
// is that getMySessions will check installer package's owner
|
||||||
|
val installerPackageName = if (isRoot) packageName else "com.android.shell"
|
||||||
|
var installerAttributionTag: String? = null
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
installerAttributionTag = attributionTag
|
||||||
|
}
|
||||||
|
val userId = if (isRoot) Process.myUserHandle().hashCode() else 0
|
||||||
|
val packageInstaller = PackageInstallerUtils.createPackageInstaller(
|
||||||
|
iPackageInstaller, installerPackageName, installerAttributionTag, userId)
|
||||||
|
val params =
|
||||||
|
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
||||||
|
var installFlags: Int = PackageInstallerUtils.getInstallFlags(params)
|
||||||
|
installFlags = installFlags or (0x00000002/*PackageManager.INSTALL_REPLACE_EXISTING*/
|
||||||
|
or 0x00000004 /*PackageManager.INSTALL_ALLOW_TEST*/)
|
||||||
|
PackageInstallerUtils.setInstallFlags(params, installFlags)
|
||||||
|
val sessionId = packageInstaller.createSession(params)
|
||||||
|
val iSession = IPackageInstallerSession.Stub.asInterface(
|
||||||
|
ShizukuBinderWrapper(iPackageInstaller.openSession(sessionId).asBinder()))
|
||||||
|
session = PackageInstallerUtils.createSession(iSession)
|
||||||
|
val inputStream = contentResolver.openInputStream(uri)
|
||||||
|
val openedSession = session.openWrite("apk.apk", 0, -1)
|
||||||
|
val buffer = ByteArray(8192)
|
||||||
|
var length: Int
|
||||||
|
try {
|
||||||
|
while (inputStream!!.read(buffer).also { length = it } > 0) {
|
||||||
|
openedSession.write(buffer, 0, length)
|
||||||
|
openedSession.flush()
|
||||||
|
session.fsync(openedSession)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
inputStream!!.close()
|
||||||
|
openedSession.close()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val results = arrayOf<Intent?>(null)
|
||||||
|
val countDownLatch = CountDownLatch(1)
|
||||||
|
val intentSender: IntentSender =
|
||||||
|
IntentSenderUtils.newInstance(object : IIntentSenderAdaptor() {
|
||||||
|
override fun send(intent: Intent?) {
|
||||||
|
results[0] = intent
|
||||||
|
countDownLatch.countDown()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
session.commit(intentSender)
|
||||||
|
countDownLatch.await()
|
||||||
|
res = results[0]!!.getIntExtra(
|
||||||
|
PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE) == 0
|
||||||
|
} catch (_: Exception) {
|
||||||
|
res = false
|
||||||
|
} finally {
|
||||||
|
if (session != null) {
|
||||||
|
try {
|
||||||
|
session.close()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
res = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.success(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rootCheckPermission(result: Result) {
|
||||||
|
Shell.getShell(Shell.GetShellCallback(
|
||||||
|
fun(shell: Shell) {
|
||||||
|
result.success(shell.isRoot)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rootInstallApk(apkFilePath: String, result: Result) {
|
||||||
|
Shell.sh("pm install -r -t " + apkFilePath).submit { out ->
|
||||||
|
val builder = StringBuilder()
|
||||||
|
for (data in out.getOut()) { builder.append(data) }
|
||||||
|
result.success(builder.toString().endsWith("Success"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||||
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
HiddenApiBypass.addHiddenApiExemptions("")
|
||||||
|
}
|
||||||
|
Shizuku.addRequestPermissionResultListener(shizukuRequestPermissionResultListener)
|
||||||
|
nativeChannel = MethodChannel(
|
||||||
|
flutterEngine.dartExecutor.binaryMessenger, "native")
|
||||||
|
nativeChannel!!.setMethodCallHandler {
|
||||||
|
call, result ->
|
||||||
|
if (call.method == "getSystemFont") {
|
||||||
|
val res = DefaultSystemFont().get()
|
||||||
|
result.success(res)
|
||||||
|
} else if (call.method == "checkPermissionShizuku") {
|
||||||
|
shizukuCheckPermission(result)
|
||||||
|
} else if (call.method == "checkPermissionRoot") {
|
||||||
|
rootCheckPermission(result)
|
||||||
|
} else if (call.method == "installWithShizuku") {
|
||||||
|
val apkFileUri: String? = call.argument("apkFileUri")
|
||||||
|
shizukuInstallApk(apkFileUri!!, result)
|
||||||
|
} else if (call.method == "installWithRoot") {
|
||||||
|
val apkFilePath: String? = call.argument("apkFilePath")
|
||||||
|
rootInstallApk(apkFilePath!!, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
Shizuku.removeRequestPermissionResultListener(shizukuRequestPermissionResultListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package dev.imranr.obtainium.util;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
public class ApplicationUtils {
|
||||||
|
|
||||||
|
private static Application application;
|
||||||
|
|
||||||
|
public static Application getApplication() {
|
||||||
|
return application;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setApplication(Application application) {
|
||||||
|
ApplicationUtils.application = application;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getProcessName() {
|
||||||
|
if (Build.VERSION.SDK_INT >= 28)
|
||||||
|
return Application.getProcessName();
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
@SuppressLint("PrivateApi")
|
||||||
|
Class<?> activityThread = Class.forName("android.app.ActivityThread");
|
||||||
|
@SuppressLint("DiscouragedPrivateApi")
|
||||||
|
Method method = activityThread.getDeclaredMethod("currentProcessName");
|
||||||
|
return (String) method.invoke(null);
|
||||||
|
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package dev.imranr.obtainium.util;
|
||||||
|
|
||||||
|
import android.content.IIntentReceiver;
|
||||||
|
import android.content.IIntentSender;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
public abstract class IIntentSenderAdaptor extends IIntentSender.Stub {
|
||||||
|
|
||||||
|
public abstract void send(Intent intent);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int send(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
|
||||||
|
send(intent);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
|
||||||
|
send(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package dev.imranr.obtainium.util;
|
||||||
|
|
||||||
|
import android.content.IIntentSender;
|
||||||
|
import android.content.IntentSender;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
public class IntentSenderUtils {
|
||||||
|
|
||||||
|
public static IntentSender newInstance(IIntentSender binder) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||||
|
//noinspection JavaReflectionMemberAccess
|
||||||
|
return IntentSender.class.getConstructor(IIntentSender.class).newInstance(binder);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package dev.imranr.obtainium.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.IPackageInstaller;
|
||||||
|
import android.content.pm.IPackageInstallerSession;
|
||||||
|
import android.content.pm.PackageInstaller;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
@SuppressWarnings({"JavaReflectionMemberAccess"})
|
||||||
|
public class PackageInstallerUtils {
|
||||||
|
|
||||||
|
public static PackageInstaller createPackageInstaller(IPackageInstaller installer, String installerPackageName, String installerAttributionTag, int userId) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
return PackageInstaller.class.getConstructor(IPackageInstaller.class, String.class, String.class, int.class)
|
||||||
|
.newInstance(installer, installerPackageName, installerAttributionTag, userId);
|
||||||
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
return PackageInstaller.class.getConstructor(IPackageInstaller.class, String.class, int.class)
|
||||||
|
.newInstance(installer, installerPackageName, userId);
|
||||||
|
} else {
|
||||||
|
return PackageInstaller.class.getConstructor(Context.class, PackageManager.class, IPackageInstaller.class, String.class, int.class)
|
||||||
|
.newInstance(ApplicationUtils.getApplication(), ApplicationUtils.getApplication().getPackageManager(), installer, installerPackageName, userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PackageInstaller.Session createSession(IPackageInstallerSession session) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||||
|
return PackageInstaller.Session.class.getConstructor(IPackageInstallerSession.class)
|
||||||
|
.newInstance(session);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getInstallFlags(PackageInstaller.SessionParams params) throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
return (int) PackageInstaller.SessionParams.class.getDeclaredField("installFlags").get(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setInstallFlags(PackageInstaller.SessionParams params, int newValue) throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
PackageInstaller.SessionParams.class.getDeclaredField("installFlags").set(params, newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package dev.imranr.obtainium.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.IPackageInstaller;
|
||||||
|
import android.content.pm.IPackageManager;
|
||||||
|
import android.content.pm.UserInfo;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.IUserManager;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import rikka.shizuku.ShizukuBinderWrapper;
|
||||||
|
import rikka.shizuku.SystemServiceHelper;
|
||||||
|
|
||||||
|
public class ShizukuSystemServerApi {
|
||||||
|
|
||||||
|
private static final Singleton<IPackageManager> PACKAGE_MANAGER = new Singleton<IPackageManager>() {
|
||||||
|
@Override
|
||||||
|
protected IPackageManager create() {
|
||||||
|
return IPackageManager.Stub.asInterface(new ShizukuBinderWrapper(SystemServiceHelper.getSystemService("package")));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final Singleton<IUserManager> USER_MANAGER = new Singleton<IUserManager>() {
|
||||||
|
@Override
|
||||||
|
protected IUserManager create() {
|
||||||
|
return IUserManager.Stub.asInterface(new ShizukuBinderWrapper(SystemServiceHelper.getSystemService(Context.USER_SERVICE)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static IPackageInstaller PackageManager_getPackageInstaller() throws RemoteException {
|
||||||
|
IPackageInstaller packageInstaller = PACKAGE_MANAGER.get().getPackageInstaller();
|
||||||
|
return IPackageInstaller.Stub.asInterface(new ShizukuBinderWrapper(packageInstaller.asBinder()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<UserInfo> UserManager_getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated) throws RemoteException {
|
||||||
|
if (Build.VERSION.SDK_INT >= 30) {
|
||||||
|
return USER_MANAGER.get().getUsers(excludePartial, excludeDying, excludePreCreated);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return USER_MANAGER.get().getUsers(excludeDying);
|
||||||
|
} catch (NoSuchFieldError e) {
|
||||||
|
return USER_MANAGER.get().getUsers(excludePartial, excludeDying, excludePreCreated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// method 2: use transactRemote directly
|
||||||
|
/*public static List<UserInfo> UserManager_getUsers(boolean excludeDying) {
|
||||||
|
Parcel data = SystemServiceHelper.obtainParcel(Context.USER_SERVICE, "android.os.IUserManager", "getUsers");
|
||||||
|
Parcel reply = Parcel.obtain();
|
||||||
|
data.writeInt(excludeDying ? 1 : 0);
|
||||||
|
|
||||||
|
List<UserInfo> res = null;
|
||||||
|
try {
|
||||||
|
ShizukuService.transactRemote(data, reply, 0);
|
||||||
|
reply.readException();
|
||||||
|
res = reply.createTypedArrayList(UserInfo.CREATOR);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e("ShizukuSample", "UserManager#getUsers", e);
|
||||||
|
} finally {
|
||||||
|
data.recycle();
|
||||||
|
reply.recycle();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package dev.imranr.obtainium.util;
|
||||||
|
|
||||||
|
public abstract class Singleton<T> {
|
||||||
|
|
||||||
|
private T mInstance;
|
||||||
|
|
||||||
|
protected abstract T create();
|
||||||
|
|
||||||
|
public final T get() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (mInstance == null) {
|
||||||
|
mInstance = create();
|
||||||
|
}
|
||||||
|
return mInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
android:viewportWidth="142.129"
|
android:viewportWidth="142.129"
|
||||||
android:viewportHeight="142.129"
|
android:viewportHeight="142.129"
|
||||||
android:width="503.6066dp"
|
android:width="108dp"
|
||||||
android:height="503.6066dp">
|
android:height="108dp">
|
||||||
<group
|
<group
|
||||||
android:translateX="-30.39437"
|
android:translateX="-30.39437"
|
||||||
android:translateY="-54.68043">
|
android:translateY="-54.68043">
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.7.10'
|
ext.kotlin_version = '1.8.10'
|
||||||
|
ext {
|
||||||
|
compileSdkVersion = 34 // or latest
|
||||||
|
targetSdkVersion = 34 // or latest
|
||||||
|
appCompatVersion = "1.4.2" // or latest
|
||||||
|
}
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.2.0'
|
classpath "com.android.tools.build:gradle:7.4.2"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
classpath "dev.rikka.tools.refine:gradle-plugin:4.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,6 +21,10 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven {
|
||||||
|
// [required] background_fetch
|
||||||
|
url "${project(':background_fetch').projectDir}/libs"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,291 +1,342 @@
|
|||||||
{
|
{
|
||||||
"invalidURLForSource": "Nije važeći URL aplikacije {}",
|
"invalidURLForSource": "Nije važeći URL aplikacije {}",
|
||||||
"noReleaseFound": "Nije moguće pronaći odgovarajuće izdanje",
|
"noReleaseFound": "Nije moguće pronaći odgovarajuće izdanje",
|
||||||
"noVersionFound": "Nije moguće odrediti verziju izdanja",
|
"noVersionFound": "Nije moguće odrediti verziju izdanja",
|
||||||
"urlMatchesNoSource": "URL se ne podudara s poznatim izvorom",
|
"urlMatchesNoSource": "URL se ne podudara s poznatim izvorom",
|
||||||
"cantInstallOlderVersion": "Nije moguće instalirati stariju verziju aplikacije",
|
"cantInstallOlderVersion": "Nije moguće instalirati stariju verziju aplikacije",
|
||||||
"appIdMismatch": "ID preuzetog paketa se ne podudara s postojećim ID-om aplikacije",
|
"appIdMismatch": "ID preuzetog paketa se ne podudara s postojećim ID-om aplikacije",
|
||||||
"functionNotImplemented": "Ova klasa nije implementirala ovu funkciju",
|
"functionNotImplemented": "Ova klasa nije implementirala ovu funkciju",
|
||||||
"placeholder": "Rezervirano mjesto",
|
"placeholder": "Rezervirano mjesto",
|
||||||
"someErrors": "Došlo je do nekih grešaka",
|
"someErrors": "Došlo je do nekih grešaka",
|
||||||
"unexpectedError": "Neočekivana greška",
|
"unexpectedError": "Neočekivana greška",
|
||||||
"ok": "Dobro",
|
"ok": "Dobro",
|
||||||
"and": "i",
|
"and": "i",
|
||||||
"startedBgUpdateTask": "Započeo je pozadinski zadatak provjere ažuriranja",
|
"githubPATLabel": "GitHub token za lični pristup (eng. PAT, povećava ograničenje stope)",
|
||||||
"bgUpdateIgnoreAfterIs": "ignoreAfter pozadinskog zadataka je {}",
|
"includePrereleases": "Uključi preliminarna izdanja",
|
||||||
"startedActualBGUpdateCheck": "Započela je stvarna provjera ažuriranja",
|
"fallbackToOlderReleases": "Povratak na starija izdanja",
|
||||||
"bgUpdateTaskFinished": "Završen zadatak provjere ažuriranja",
|
"filterReleaseTitlesByRegEx": "Filtrirajte naslove izdanja prema regularnom izrazu",
|
||||||
"firstRun": "Ovo je prvi put da pokrećete Obtainium",
|
"invalidRegEx": "Nevažeći regularni izraz",
|
||||||
"settingUpdateCheckIntervalTo": "Podešavanje intervala ažuriranja na {}",
|
"noDescription": "Bez opisa",
|
||||||
"githubPATLabel": "GitHub token za lični pristup (eng. PAT, povećava ograničenje stope)",
|
"cancel": "Otkaži",
|
||||||
"githubPATHint": "PAT mora biti u ovom formatu: korisničko_ime:token",
|
"continue": "Nastavite",
|
||||||
"githubPATFormat": "korisničko_ime:token",
|
"requiredInBrackets": "(obavezno)",
|
||||||
"includePrereleases": "Uključi preliminarna izdanja",
|
"dropdownNoOptsError": "GREŠKA: PADAJUĆI MENI MORA IMATI NAJMANJE JEDNU OPCIJU",
|
||||||
"fallbackToOlderReleases": "Povratak na starija izdanja",
|
"colour": "Boja",
|
||||||
"filterReleaseTitlesByRegEx": "Filtrirajte naslove izdanja prema regularnom izrazu",
|
"githubStarredRepos": "GitHub repo-i sa zvjezdicom",
|
||||||
"invalidRegEx": "Nevažeći regularni izraz",
|
"uname": "Korisničko ime",
|
||||||
"noDescription": "Bez opisa",
|
"wrongArgNum": "Naveden je pogrešan broj argumenata",
|
||||||
"cancel": "Otkaži",
|
"xIsTrackOnly": "{} je samo za praćenje",
|
||||||
"continue": "Nastavite",
|
"source": "Izvor",
|
||||||
"requiredInBrackets": "(obavezno)",
|
"app": "Aplikacija. ",
|
||||||
"dropdownNoOptsError": "GREŠKA: PADAJUĆI MENI MORA IMATI NAJMANJE JEDNU OPCIJU",
|
"appsFromSourceAreTrackOnly": "Aplikacije iz ovog izvora su 'Samo za praćenje'.",
|
||||||
"colour": "Boja",
|
"youPickedTrackOnly": "Odabrali ste opciju „Samo za praćenje”.",
|
||||||
"githubStarredRepos": "GitHub repo-i sa zvjezdicom",
|
"trackOnlyAppDescription": "Aplikacija će se pratiti radi ažuriranja, ali Obtainium neće moći da je preuzme ili instalira.",
|
||||||
"uname": "Korisničko ime",
|
"cancelled": "Otkazano",
|
||||||
"wrongArgNum": "Naveden je pogrešan broj argumenata",
|
"appAlreadyAdded": "Aplikacija je već dodana",
|
||||||
"xIsTrackOnly": "{} je samo za praćenje",
|
"alreadyUpToDateQuestion": "Aplikacija je već ažurirana?",
|
||||||
"source": "Izvor",
|
"addApp": "Dodaj aplikaciju",
|
||||||
"app": "Aplikacija. ",
|
"appSourceURL": "Izvorni URL aplikacije",
|
||||||
"appsFromSourceAreTrackOnly": "Aplikacije iz ovog izvora su 'Samo za praćenje'.",
|
"error": "Greška",
|
||||||
"youPickedTrackOnly": "Odabrali ste opciju „Samo za praćenje”.",
|
"add": "Dodaj",
|
||||||
"trackOnlyAppDescription": "Aplikacija će se pratiti radi ažuriranja, ali Obtainium neće moći da je preuzme ili instalira.",
|
"searchSomeSourcesLabel": "Pretraživanje (samo neki izvori)",
|
||||||
"cancelled": "Otkazano",
|
"search": "Pretraživanje",
|
||||||
"appAlreadyAdded": "Aplikacija je već dodana",
|
"additionalOptsFor": "Dodatne opcije za {}",
|
||||||
"alreadyUpToDateQuestion": "Aplikacija je već ažurirana?",
|
"supportedSources": "Podržani izvori",
|
||||||
"addApp": "Dodaj aplikaciju",
|
"trackOnlyInBrackets": "(Samo za praćenje)",
|
||||||
"appSourceURL": "Izvorni URL aplikacije",
|
"searchableInBrackets": "(Može se pretraživati)",
|
||||||
"error": "Greška",
|
"appsString": "Aplikacije",
|
||||||
"add": "Dodaj",
|
"noApps": "Nema aplikacija",
|
||||||
"searchSomeSourcesLabel": "Pretraživanje (samo neki izvori)",
|
"noAppsForFilter": "Nema aplikacija za filter",
|
||||||
"search": "Pretraživanje",
|
"byX": "Autor {}",
|
||||||
"additionalOptsFor": "Dodatne opcije za {}",
|
"percentProgress": "Napredak: {}%",
|
||||||
"supportedSourcesBelow": "Podržani izvori:",
|
"pleaseWait": "Molimo sačekajte",
|
||||||
"trackOnlyInBrackets": "(Samo za praćenje)",
|
"updateAvailable": "Ažuriranje dostupno",
|
||||||
"searchableInBrackets": "(Može se pretraživati)",
|
"estimateInBracketsShort": "(Procjena)",
|
||||||
"appsString": "Aplikacije",
|
"notInstalled": "Nije instalirano",
|
||||||
"noApps": "Nema aplikacija",
|
"estimateInBrackets": "(Procjena)",
|
||||||
"noAppsForFilter": "Nema aplikacija za filter",
|
"selectAll": "Označi sve",
|
||||||
"byX": "Autor {}",
|
"deselectX": "Poništi odabir {}",
|
||||||
"percentProgress": "Napredak: {}%",
|
"xWillBeRemovedButRemainInstalled": "{} će biti uklonjen iz Obtainiuma, ali će ostati instaliran na uređaju.",
|
||||||
"pleaseWait": "Molimo sačekajte",
|
"removeSelectedAppsQuestion": "Želite li ukloniti odabrane aplikacije?",
|
||||||
"updateAvailable": "Ažuriranje dostupno",
|
"removeSelectedApps": "Ukloni odabrane aplikacije",
|
||||||
"estimateInBracketsShort": "(Procjena)",
|
"updateX": "Nadogradi {}",
|
||||||
"notInstalled": "Nije instalirano",
|
"installX": "Instaliraj {}",
|
||||||
"estimateInBrackets": "(Procjena)",
|
"markXTrackOnlyAsUpdated": "Označi {}\n(samo za praćenje)\nkao ažurirano",
|
||||||
"selectAll": "Označi sve",
|
"changeX": "Promjena {}",
|
||||||
"deselectN": "Poništi odabir {}",
|
"installUpdateApps": "Instalirajte/ažurirajte aplikacije",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} će biti uklonjen iz Obtainiuma, ali će ostati instaliran na uređaju.",
|
"installUpdateSelectedApps": "Instalirajte/ažurirajte odabrane aplikacije",
|
||||||
"removeSelectedAppsQuestion": "Želite li ukloniti odabrane aplikacije?",
|
"markXSelectedAppsAsUpdated": "Označite {} odabrane aplikacije kao ažurirane?",
|
||||||
"removeSelectedApps": "Ukloni odabrane aplikacije",
|
"no": "Ne",
|
||||||
"updateX": "Nadogradi {}",
|
"yes": "Da",
|
||||||
"installX": "Instaliraj {}",
|
"markSelectedAppsUpdated": "Označi odabrane aplikacije kao ažurirane",
|
||||||
"markXTrackOnlyAsUpdated": "Označi {}\n(samo za praćenje)\nkao ažurirano",
|
"pinToTop": "Prikvači na vrh",
|
||||||
"changeX": "Promjena {}",
|
"unpinFromTop": "Otkvači sa vrha",
|
||||||
"installUpdateApps": "Instalirajte/ažurirajte aplikacije",
|
"resetInstallStatusForSelectedAppsQuestion": "Resetujte status instalacije za odabrane aplikacije?",
|
||||||
"installUpdateSelectedApps": "Instalirajte/ažurirajte odabrane aplikacije",
|
"installStatusOfXWillBeResetExplanation": "Status instalacije bilo koje odabrane aplikacije će se resetovati.\n\nTo može pomoći kada je verzija aplikacije prikazana u Obtainiumu netačna zbog neuspjelih ažuriranja ili drugih problema.",
|
||||||
"markXSelectedAppsAsUpdated": "Označite {} odabrane aplikacije kao ažurirane?",
|
"shareSelectedAppURLs": "Podijeli odabrane URL-ove aplikacija",
|
||||||
"no": "Ne",
|
"resetInstallStatus": "Resetujte status instalacije",
|
||||||
"yes": "Da",
|
"more": "Više",
|
||||||
"markSelectedAppsUpdated": "Označi odabrane aplikacije kao ažurirane",
|
"removeOutdatedFilter": "Uklonite zastarjeli filter aplikacija",
|
||||||
"pinToTop": "Prikvači na vrh",
|
"showOutdatedOnly": "Prikaži samo zastarjele aplikacije",
|
||||||
"unpinFromTop": "Otkvači sa vrha",
|
"filter": "Filtriranje",
|
||||||
"resetInstallStatusForSelectedAppsQuestion": "Resetujte status instalacije za odabrane aplikacije?",
|
"filterActive": "Filtriranje",
|
||||||
"installStatusOfXWillBeResetExplanation": "Status instalacije bilo koje odabrane aplikacije će se resetovati.\n\nTo može pomoći kada je verzija aplikacije prikazana u Obtainiumu netačna zbog neuspjelih ažuriranja ili drugih problema.",
|
"filterApps": "Filtriraj aplikacije",
|
||||||
"shareSelectedAppURLs": "Podijeli odabrane URL-ove aplikacija",
|
"appName": "Naziv aplikacije",
|
||||||
"resetInstallStatus": "Resetujte status instalacije",
|
"author": "Autor",
|
||||||
"more": "Više",
|
"upToDateApps": "Ažurirane aplikacije",
|
||||||
"removeOutdatedFilter": "Uklonite zastarjeli filter aplikacija",
|
"nonInstalledApps": "Neinstalirane aplikacije",
|
||||||
"showOutdatedOnly": "Prikaži samo zastarjele aplikacije",
|
"importExport": "Uvoz/izvoz",
|
||||||
"filter": "Filtriranje",
|
"settings": "Postavke",
|
||||||
"filterActive": "Filtriranje",
|
"exportedTo": "Izvezeno u {}",
|
||||||
"filterApps": "Filtriraj aplikacije",
|
"obtainiumExport": "Obtainium izvoz",
|
||||||
"appName": "Naziv aplikacije",
|
"invalidInput": "Neispravan unos.",
|
||||||
"author": "Autor",
|
"importedX": "Uvezeno {}",
|
||||||
"upToDateApps": "Ažurirane aplikacije",
|
"obtainiumImport": "Obtainium uvoz",
|
||||||
"nonInstalledApps": "Neinstalirane aplikacije",
|
"importFromURLList": "Uvoz iz URL liste",
|
||||||
"importExport": "Uvoz/izvoz",
|
"searchQuery": "Pretraga za: ",
|
||||||
"settings": "Postavke",
|
"appURLList": "Lista URL adresa aplikacija",
|
||||||
"exportedTo": "Izvezeno u {}",
|
"line": "Linija",
|
||||||
"obtainiumExport": "Obtainium Export",
|
"searchX": "Pretraživanje {}",
|
||||||
"invalidInput": "Neispravan unos.",
|
"noResults": "Nema rezultata",
|
||||||
"importedX": "Uvezeno {}",
|
"importX": "Uvoz {}",
|
||||||
"obtainiumImport": "Obtainium uvoz",
|
"importedAppsIdDisclaimer": "Uvezene aplikacije mogu se pogrešno prikazati kao „Nije instalirano”.\nDa biste to riješili, ponovo ih instalirajte putem aplikacije Obtainium.\nTo ne bi trebalo uticati na podatke aplikacije.\n\nUtječe samo na URL i metode uvoza treće strane.",
|
||||||
"importFromURLList": "Uvoz iz URL liste",
|
"importErrors": "Uvezi greške",
|
||||||
"searchQuery": "Pretraga za: ",
|
"importedXOfYApps": "{} od {} aplikacija uvezeno.",
|
||||||
"appURLList": "Lista URL adresa aplikacija",
|
"followingURLsHadErrors": "Sljedeći URL-ovi su imali greške:",
|
||||||
"line": "Linija",
|
"selectURL": "Odaberite URL",
|
||||||
"searchX": "Pretraživanje {}",
|
"selectURLs": "Odaberite URL-ove",
|
||||||
"noResults": "Nema rezultata",
|
"pick": "Odaberi",
|
||||||
"importX": "Uvoz {}",
|
"theme": "Tema",
|
||||||
"importedAppsIdDisclaimer": "Uvezene aplikacije mogu se pogrešno prikazati kao „Nije instalirano”.\nDa biste to riješili, ponovo ih instalirajte putem aplikacije Obtainium.\nTo ne bi trebalo uticati na podatke aplikacije.\n\nUtječe samo na URL i metode uvoza treće strane.",
|
"dark": "Tamna",
|
||||||
"importErrors": "Uvezi greške",
|
"light": "Svijetla",
|
||||||
"importedXOfYApps": "{} od {} aplikacija uvezeno.",
|
"followSystem": "Pratite sistem",
|
||||||
"followingURLsHadErrors": "Sljedeći URL-ovi su imali greške:",
|
"obtainium": "Obtainium",
|
||||||
"okay": "Dobro",
|
"materialYou": "Material You",
|
||||||
"selectURL": "Odaberite URL",
|
"useBlackTheme": "Koristite čisto crnu tamnu temu",
|
||||||
"selectURLs": "Odaberite URL-ove",
|
"appSortBy": "Aplikacije sortirane po",
|
||||||
"pick": "Odaberi",
|
"authorName": "Autor/Ime",
|
||||||
"theme": "Tema",
|
"nameAuthor": "Ime/Autor",
|
||||||
"dark": "Tamna",
|
"asAdded": "Kao što je dodano",
|
||||||
"light": "Svijetla",
|
"appSortOrder": "Redoslijed sortiranja aplikacija",
|
||||||
"followSystem": "Pratite sistem",
|
"ascending": "Uzlazno",
|
||||||
"obtainium": "Obtainium",
|
"descending": "Silazno",
|
||||||
"materialYou": "Material You",
|
"bgUpdateCheckInterval": "Interval provjere ažuriranja u pozadini",
|
||||||
"useBlackTheme": "Koristite čisto crnu tamnu temu",
|
"neverManualOnly": "Nikada - samo ručno",
|
||||||
"appSortBy": "Aplikacije sortirane po",
|
"appearance": "Izgled",
|
||||||
"authorName": "Autor/Ime",
|
"showWebInAppView": "Prikaži izvornu web stranicu u prikazu aplikacije",
|
||||||
"nameAuthor": "Ime/Autor",
|
"pinUpdates": "Prikvačite ažuriranja na vrh prikaza aplikacija",
|
||||||
"asAdded": "Kao što je dodano",
|
"updates": "Nadogradnje",
|
||||||
"appSortOrder": "Redoslijed sortiranja aplikacija",
|
"sourceSpecific": "Specifično za izvor",
|
||||||
"ascending": "Uzlazno",
|
"appSource": "Izvor aplikacije",
|
||||||
"descending": "Silazno",
|
"noLogs": "Nema evidencije",
|
||||||
"bgUpdateCheckInterval": "Interval provjere ažuriranja u pozadini",
|
"appLogs": "Evidencije aplikacija",
|
||||||
"neverManualOnly": "Nikada - samo ručno",
|
"close": "Zatvori",
|
||||||
"appearance": "Izgled",
|
"share": "Podijeli",
|
||||||
"showWebInAppView": "Prikaži izvornu web stranicu u prikazu aplikacije",
|
"appNotFound": "Aplikacija nije pronađena",
|
||||||
"pinUpdates": "Prikvačite ažuriranja na vrh prikaza aplikacija",
|
"obtainiumExportHyphenatedLowercase": "obtainium-izvoz",
|
||||||
"updates": "Nadogradnje",
|
"pickAnAPK": "Odaberite APK",
|
||||||
"sourceSpecific": "Specifično za izvor",
|
"appHasMoreThanOnePackage": "{} ima više od jednog paketa:",
|
||||||
"appSource": "Izvor aplikacije",
|
"deviceSupportsXArch": "Vaš uređaj podržava {} arhitekturu procesora.",
|
||||||
"noLogs": "Nema evidencije",
|
"deviceSupportsFollowingArchs": "Vaš uređaj podržava sljedeće arhitekture procesora:",
|
||||||
"appLogs": "Evidencije aplikacija",
|
"warning": "Upozorenje",
|
||||||
"close": "Zatvori",
|
"sourceIsXButPackageFromYPrompt": "Izvor aplikacije je '{}', ali paket za izdavanje dolazi iz '{}'. Želite li nastaviti?",
|
||||||
"share": "Podijeli",
|
"updatesAvailable": "Dostupna ažuriranja",
|
||||||
"appNotFound": "Aplikacija nije pronađena",
|
"updatesAvailableNotifDescription": "Obavještava korisnika da su ažuriranja dostupna za jednu ili više aplikacija koje prati Obtainium",
|
||||||
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
"noNewUpdates": "Nema novih ažuriranja.",
|
||||||
"pickAnAPK": "Odaberite APK",
|
"xHasAnUpdate": "{} ima ažuriranje.",
|
||||||
"appHasMoreThanOnePackage": "{} ima više od jednog paketa:",
|
"appsUpdated": "Aplikacije su ažurirane",
|
||||||
"deviceSupportsXArch": "Vaš uređaj podržava {} arhitekturu procesora.",
|
"appsUpdatedNotifDescription": "Obavještava korisnika da su u pozadini primijenjena ažuriranja na jednu ili više aplikacija",
|
||||||
"deviceSupportsFollowingArchs": "Vaš uređaj podržava sljedeće arhitekture procesora:",
|
"xWasUpdatedToY": "{} je ažuriran na {}.",
|
||||||
"warning": "Upozorenje",
|
"errorCheckingUpdates": "Greška pri provjeri ažuriranja",
|
||||||
"sourceIsXButPackageFromYPrompt": "Izvor aplikacije je '{}', ali paket za izdavanje dolazi iz '{}'. Želite li nastaviti?",
|
"errorCheckingUpdatesNotifDescription": "Obavijest koja se prikazuje kada provjera sigurnosnog ažuriranja ne uspije",
|
||||||
"updatesAvailable": "Dostupna ažuriranja",
|
"appsRemoved": "Aplikacije su uklonjene",
|
||||||
"updatesAvailableNotifDescription": "Obavještava korisnika da su ažuriranja dostupna za jednu ili više aplikacija koje prati Obtainium",
|
"appsRemovedNotifDescription": "Obavještava korisnika da je jedna ili više aplikacija uklonjeno zbog grešaka prilikom učitavanja",
|
||||||
"noNewUpdates": "Nema novih ažuriranja.",
|
"xWasRemovedDueToErrorY": "{} je uklonjen zbog ove greške: {}",
|
||||||
"xHasAnUpdate": "{} ima ažuriranje.",
|
"completeAppInstallation": "Dovršite instalaciju aplikacije",
|
||||||
"appsUpdated": "Aplikacije su ažurirane",
|
"obtainiumMustBeOpenToInstallApps": "Obtainium mora biti otvoren za instalaciju aplikacija",
|
||||||
"appsUpdatedNotifDescription": "Obavještava korisnika da su u pozadini primijenjena ažuriranja na jednu ili više aplikacija",
|
"completeAppInstallationNotifDescription": "Traži od korisnika da se vrati u Obtainium kako bi dovršio instalaciju aplikacije",
|
||||||
"xWasUpdatedToY": "{} je ažuriran na {}.",
|
"checkingForUpdates": "Tražim moguće nadogradnje",
|
||||||
"errorCheckingUpdates": "Greška pri provjeri ažuriranja",
|
"checkingForUpdatesNotifDescription": "Privremeno obavještenje koje se pojavljuje prilikom provjere ažuriranja",
|
||||||
"errorCheckingUpdatesNotifDescription": "Obavijest koja se prikazuje kada provjera sigurnosnog ažuriranja ne uspije",
|
"pleaseAllowInstallPerm": "Dozvolite Obtainiumu da instalira aplikacije",
|
||||||
"appsRemoved": "Aplikacije su uklonjene",
|
"trackOnly": "Samo za praćenje",
|
||||||
"appsRemovedNotifDescription": "Obavještava korisnika da je jedna ili više aplikacija uklonjeno zbog grešaka prilikom učitavanja",
|
"errorWithHttpStatusCode": "Greška {}",
|
||||||
"xWasRemovedDueToErrorY": "{} je uklonjen zbog ove greške: {}",
|
"versionCorrectionDisabled": "Ispravka verzije je onemogućena (izgleda da plugin ne radi)",
|
||||||
"completeAppInstallation": "Dovršite instalaciju aplikacije",
|
"unknown": "Nepoznato",
|
||||||
"obtainiumMustBeOpenToInstallApps": "Obtainium mora biti otvoren za instalaciju aplikacija",
|
"none": "Ništa",
|
||||||
"completeAppInstallationNotifDescription": "Traži od korisnika da se vrati u Obtainium kako bi dovršio instalaciju aplikacije",
|
"never": "Nikad",
|
||||||
"checkingForUpdates": "Tražim moguće nadogradnje",
|
"latestVersionX": "Najnovija verzija: {}",
|
||||||
"checkingForUpdatesNotifDescription": "Privremeno obavještenje koje se pojavljuje prilikom provjere ažuriranja",
|
"installedVersionX": "Instalirana verzija: {}",
|
||||||
"pleaseAllowInstallPerm": "Dozvolite Obtainiumu da instalira aplikacije",
|
"lastUpdateCheckX": "Posljednja provjera ažuriranja: {}",
|
||||||
"trackOnly": "Samo za praćenje",
|
"remove": "Izbriši",
|
||||||
"errorWithHttpStatusCode": "Greška {}",
|
"yesMarkUpdated": "Da, označi kao ažurirano",
|
||||||
"versionCorrectionDisabled": "Ispravka verzije je onemogućena (izgleda da plugin ne radi)",
|
"fdroid": "F-Droid Official",
|
||||||
"unknown": "Nepoznato",
|
"appIdOrName": "ID ili ime aplikacije",
|
||||||
"none": "Ništa",
|
"appId": "Apl ID",
|
||||||
"never": "Nikad",
|
"appWithIdOrNameNotFound": "Nije pronađena aplikacija s tim ID-om ili imenom",
|
||||||
"latestVersionX": "Najnovija verzija: {}",
|
"reposHaveMultipleApps": "Repo-i mogu sadržavati više aplikacija",
|
||||||
"installedVersionX": "Instalirana verzija: {}",
|
"fdroidThirdPartyRepo": "F-Droid Repo treće strane",
|
||||||
"lastUpdateCheckX": "Posljednja provjera ažuriranja: {}",
|
"steam": "Steam",
|
||||||
"remove": "Izbriši",
|
"steamMobile": "Steam Mobile",
|
||||||
"yesMarkUpdated": "Da, označi kao ažurirano",
|
"steamChat": "Razgovor na Steamu (chat)",
|
||||||
"fdroid": "F-Droid Official",
|
"install": "Instaliraj",
|
||||||
"appIdOrName": "ID ili ime aplikacije",
|
"markInstalled": "Označi kao instalirano",
|
||||||
"appId": "Apl ID",
|
"update": "Nadogradi",
|
||||||
"appWithIdOrNameNotFound": "Nije pronađena aplikacija s tim ID-om ili imenom",
|
"markUpdated": "Označi kao ažurirano",
|
||||||
"reposHaveMultipleApps": "Repo-i mogu sadržavati više aplikacija",
|
"additionalOptions": "Dodatne opcije",
|
||||||
"fdroidThirdPartyRepo": "F-Droid Repo treće strane",
|
"disableVersionDetection": "Onemogući detekciju verzije",
|
||||||
"steam": "Steam",
|
"noVersionDetectionExplanation": "Ova opcija bi se trebala koristiti samo za aplikacije gdje detekcija verzije ne radi ispravno.",
|
||||||
"steamMobile": "Steam Mobile",
|
"downloadingX": "Preuzimanje {}",
|
||||||
"steamChat": "Razgovor na Steamu (chat)",
|
"downloadNotifDescription": "Obavještava korisnika o napretku u preuzimanju aplikacije",
|
||||||
"install": "Instaliraj",
|
"noAPKFound": "APK nije pronađen",
|
||||||
"markInstalled": "Označi kao instalirano",
|
"noVersionDetection": "Nema detekcije verzije",
|
||||||
"update": "Nadogradi",
|
"categorize": "Kategoriziraj",
|
||||||
"markUpdated": "Označi kao ažurirano",
|
"categories": "Kategorije",
|
||||||
"additionalOptions": "Dodatne opcije",
|
"category": "Kategorija",
|
||||||
"disableVersionDetection": "Onemogući detekciju verzije",
|
"noCategory": "Nema kategorije",
|
||||||
"noVersionDetectionExplanation": "Ova opcija bi se trebala koristiti samo za aplikacije gdje detekcija verzije ne radi ispravno.",
|
"noCategories": "Nema kategorija",
|
||||||
"downloadingX": "Preuzimanje {}",
|
"deleteCategoriesQuestion": "Želite li izbrisati kategorije?",
|
||||||
"downloadNotifDescription": "Obavještava korisnika o napretku u preuzimanju aplikacije",
|
"categoryDeleteWarning": "Sve aplikacije u izbrisanim kategorijama će biti postavljene kao nekategorisane.",
|
||||||
"noAPKFound": "APK nije pronađen",
|
"addCategory": "Dodaj kategoriju",
|
||||||
"noVersionDetection": "Nema detekcije verzije",
|
"label": "Oznaka",
|
||||||
"categorize": "Kategoriziraj",
|
"language": "Jezik",
|
||||||
"categories": "Kategorije",
|
"copiedToClipboard": "Podaci kopirani u međuspremnik",
|
||||||
"category": "Kategorija",
|
"storagePermissionDenied": "Dozvola za pohranu je odbijena",
|
||||||
"noCategory": "Nema kategorije",
|
"selectedCategorizeWarning": "Ovo će zamijeniti sve postojeće postavke kategorije za odabrane aplikacije.",
|
||||||
"noCategories": "Nema kategorija",
|
"filterAPKsByRegEx": "Filtrirajte APK-ove prema regularnom izrazu",
|
||||||
"deleteCategoriesQuestion": "Želite li izbrisati kategorije?",
|
"removeFromObtainium": "Ukloni iz Obtainiuma",
|
||||||
"categoryDeleteWarning": "Sve aplikacije u izbrisanim kategorijama će biti postavljene kao nekategorisane.",
|
"uninstallFromDevice": "Deinstaliraj s uređaja",
|
||||||
"addCategory": "Dodaj kategoriju",
|
"onlyWorksWithNonVersionDetectApps": "Radi samo za aplikacije s onemogućenom detekcijom verzije.",
|
||||||
"label": "Oznaka",
|
"releaseDateAsVersion": "Koristi datum izdanja kao verziju",
|
||||||
"language": "Jezik",
|
"releaseDateAsVersionExplanation": "Ova opcija bi se trebala koristiti samo za aplikacije gdje detekcija verzije ne radi ispravno, ali je datum izdavanja dostupan.",
|
||||||
"copiedToClipboard": "Podaci kopirani u međuspremnik",
|
"changes": "Promjene",
|
||||||
"storagePermissionDenied": "Dozvola za pohranu je odbijena",
|
"releaseDate": "Datum izdavanja",
|
||||||
"selectedCategorizeWarning": "Ovo će zamijeniti sve postojeće postavke kategorije za odabrane aplikacije.",
|
"importFromURLsInFile": "Uvoz iz URL-ova u datoteci (kao što je OPML)",
|
||||||
"filterAPKsByRegEx": "Filtrirajte APK-ove prema regularnom izrazu",
|
"versionDetection": "Otkrivanje verzije",
|
||||||
"removeFromObtainium": "Ukloni iz Obtainiuma",
|
"standardVersionDetection": "Detekcija standardne verzije",
|
||||||
"uninstallFromDevice": "Deinstaliraj s uređaja",
|
"groupByCategory": "Grupiši po kategoriji",
|
||||||
"onlyWorksWithNonVersionDetectApps": "Radi samo za aplikacije s onemogućenom detekcijom verzije.",
|
"autoApkFilterByArch": "Pokušajte filtrirati APK-ove po arhitekturi procesora ako je moguće",
|
||||||
"releaseDateAsVersion": "Koristi datum izdanja kao verziju",
|
"overrideSource": "Premosti izvor",
|
||||||
"releaseDateAsVersionExplanation": "Ova opcija bi se trebala koristiti samo za aplikacije gdje detekcija verzije ne radi ispravno, ali je datum izdavanja dostupan.",
|
"dontShowAgain": "Ne prikazuj ovo ponovo",
|
||||||
"changes": "Promjene",
|
"dontShowTrackOnlyWarnings": "Ne prikazuj upozorenja „Samo za praćenje”",
|
||||||
"releaseDate": "Datum izdavanja",
|
"dontShowAPKOriginWarnings": "Ne prikazuj upozorenja o porijeklu APK-a",
|
||||||
"importFromURLsInFile": "Uvoz iz URL-ova u datoteci (kao što je OPML)",
|
"moveNonInstalledAppsToBottom": "Premjesti neinstalirane aplikacije na dno prikaza aplikacija",
|
||||||
"versionDetection": "Otkrivanje verzije",
|
"gitlabPATLabel": "GitLab token za lični pristup\n(Omogućava pretraživanje i bolje otkrivanje APK-a)",
|
||||||
"standardVersionDetection": "Detekcija standardne verzije",
|
"about": "O nama",
|
||||||
"groupByCategory": "Grupiši po kategoriji",
|
"requiresCredentialsInSettings": "{}: Za ovo su potrebni dodatni akreditivi (u Postavkama)",
|
||||||
"autoApkFilterByArch": "Pokušajte filtrirati APK-ove po arhitekturi procesora ako je moguće",
|
"checkOnStart": "Provjerite ima li novosti pri pokretanju",
|
||||||
"overrideSource": "Premosti izvor",
|
"tryInferAppIdFromCode": "Pokušati otkriti ID aplikacije iz izvornog koda",
|
||||||
"dontShowAgain": "Ne prikazuj ovo ponovo",
|
"removeOnExternalUninstall": "Automatski ukloni eksterno deinstalirane aplikacije",
|
||||||
"dontShowTrackOnlyWarnings": "Ne prikazuj upozorenja „Samo za praćenje”",
|
"pickHighestVersionCode": "Automatski odaberite najviši kôd verzije APK-a",
|
||||||
"dontShowAPKOriginWarnings": "Ne prikazuj upozorenja o porijeklu APK-a",
|
"checkUpdateOnDetailPage": "Provjerite ima li novosti pri otvaranju stranice s detaljima aplikacije",
|
||||||
"moveNonInstalledAppsToBottom": "Premjesti neinstalirane aplikacije na dno prikaza aplikacija",
|
"disablePageTransitions": "Ugasite animaciju prijelaza stranice",
|
||||||
"gitlabPATLabel": "GitLab token za lični pristup\n(Omogućava pretraživanje i bolje otkrivanje APK-a)",
|
"reversePageTransitions": "Reverzne animacije prijelaza stranice",
|
||||||
"about": "O nama",
|
"minStarCount": "Najmanji broj zvjezdica",
|
||||||
"requiresCredentialsInSettings": "Za ovo su potrebni dodatni akreditivi (u Postavkama)",
|
"addInfoBelow": "Dodajte ove informacije ispod.",
|
||||||
"checkOnStart": "Provjerite ima li novosti pri pokretanju",
|
"addInfoInSettings": "Dodajte ove informacije u Postavkama.",
|
||||||
"tryInferAppIdFromCode": "Pokušati otkriti ID aplikacije iz izvornog koda",
|
"githubSourceNote": "GitHub ograničavanje se može izbjeći korišćenjem tokena za lični pristup.",
|
||||||
"removeOnExternalUninstall": "Automatski ukloni eksterno deinstalirane aplikacije",
|
"gitlabSourceNote": "GitLab APK preuzimanje možda neće raditi bez tokena za lični pristup.",
|
||||||
"pickHighestVersionCode": "Automatski odaberite najviši kôd verzije APK-a",
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
"checkUpdateOnDetailPage": "Provjerite ima li novosti pri otvaranju stranice s detaljima aplikacije",
|
"filterReleaseNotesByRegEx": "Filtirajte promjene u izdanju po regularnom izrazu",
|
||||||
"disablePageTransitions": "Ugasite animaciju prijelaza stranice",
|
"customLinkFilterRegex": "Prilagođeni APK link filtrira se po regularnom izrazu (Zadano '.apk$')",
|
||||||
"reversePageTransitions": "Reverzne animacije prijelaza stranice",
|
"appsPossiblyUpdated": "Pokušano ažuriranje aplikacija",
|
||||||
"minStarCount": "Minimum Star Count",
|
"appsPossiblyUpdatedNotifDescription": "Obavještava korisnika da je ažuriranje jedne ili više aplikacija potencijalno izvršeno u pozadini",
|
||||||
"removeAppQuestion": {
|
"xWasPossiblyUpdatedToY": "{} aplikacija bi trebala biti ažurirana na {}.",
|
||||||
"one": "Želite li ukloniti aplikaciju?",
|
"enableBackgroundUpdates": "Dozvolite ažuriranja u pozadini",
|
||||||
"other": "Želite li ukloniti aplikacije?"
|
"backgroundUpdateReqsExplanation": "Ažuriranja u pozadini možda neće raditi za sve aplikacije.",
|
||||||
},
|
"backgroundUpdateLimitsExplanation": "Uspjeh ažuriranja u pozadini se može provjeriti tek kada otvorite Obtainium.",
|
||||||
"tooManyRequestsTryAgainInMinutes": {
|
"verifyLatestTag": "Provjerite 'posljednu' ('latest') oznaku",
|
||||||
"one": "Previše zahtjeva (ograničena broj zahteva) - pokušajte ponovo za {} minutu",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
"other": "Previše zahtjeva (ograničena cijena) - pokušajte ponovo za {} min."
|
"filterByLinkText": "Filter links by link text",
|
||||||
},
|
"intermediateLinkNotFound": "Intermediate link nije nađen",
|
||||||
"bgUpdateGotErrorRetryInMinutes": {
|
"intermediateLink": "Intermediate link",
|
||||||
"one": "Provjera ažuriranja u pozadini naišla je na {}, zakazuje se ponovni pokušaj za {} minutu",
|
"exemptFromBackgroundUpdates": "Izuzmi iz ažuriranja u pozadini (ako su uključeni)",
|
||||||
"other": "Provjera ažuriranja u pozadini naišla je na {}, zakazuje se ponovni pokušaj za {} min."
|
"bgUpdatesOnWiFiOnly": "Isključite ažuriranje u pozadini kada niste na WiFi-ju",
|
||||||
},
|
"autoSelectHighestVersionCode": "Automatski izaberite najveću (verziju) versionCode APK-a",
|
||||||
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
"versionExtractionRegEx": "RegEx ekstrakcija verzije",
|
||||||
"one": "Provjera ažuriranja u pozadini je pronašla {} ažuriranje - korisnik će biti obavješten ako je to potrebno",
|
"matchGroupToUse": "Podjesite grupu za upotebu",
|
||||||
"other": "Provjera ažuriranja u pozadini je pronašla {} ažuriranja - korisnik će biti obavješten ako je to potrebno"
|
"highlightTouchTargets": "Istaknite manje vidljive touch mete",
|
||||||
},
|
"pickExportDir": "Izaberite datoteku za izvoz",
|
||||||
"apps": {
|
"autoExportOnChanges": "Automatski izvezite pri promjenama",
|
||||||
"one": "{} aplikacija",
|
"includeSettings": "Include settings",
|
||||||
"other": "{} aplikacije"
|
"filterVersionsByRegEx": "Filtrirajte verzije po regulatnom izrazu",
|
||||||
},
|
"trySelectingSuggestedVersionCode": "Probajte izabrati preloženu (verziju) versionCode APK-a",
|
||||||
"url": {
|
"dontSortReleasesList": "Zadrži redosled izdanja iz API-a",
|
||||||
"one": "{} URL",
|
"reverseSort": "Obrni redosled",
|
||||||
"other": "{} URL-ovi"
|
"takeFirstLink": "Take first link",
|
||||||
},
|
"skipSort": "Skip sorting",
|
||||||
"minute": {
|
"debugMenu": "Meni za otkrivanje grešaka",
|
||||||
"one": "{} minuta",
|
"bgTaskStarted": "Rad u pozadini pokrenut - provjerite log-ove.",
|
||||||
"other": "min."
|
"runBgCheckNow": "Pokrenite pozadinsku provjeru ažuriranja sad",
|
||||||
},
|
"versionExtractWholePage": "Primjenite Regex ekstrakciju verzije na cijelu stranicu",
|
||||||
"hour": {
|
"installing": "Instaliranje",
|
||||||
"one": "{} sat",
|
"skipUpdateNotifications": "Ne prikazujte obavještenja ažuriranja",
|
||||||
"other": "{} sat/i"
|
"updatesAvailableNotifChannel": "Dostupna ažuriranja",
|
||||||
},
|
"appsUpdatedNotifChannel": "Aplikacije su ažurirane",
|
||||||
"day": {
|
"appsPossiblyUpdatedNotifChannel": "Pokušano ažuriranje aplikacija",
|
||||||
"one": "{} dan",
|
"errorCheckingUpdatesNotifChannel": "Greška pri provjeri ažuriranja",
|
||||||
"other": "{} dana"
|
"appsRemovedNotifChannel": "Aplikacije su uklonjene",
|
||||||
},
|
"downloadingXNotifChannel": "Preuzimanje {}",
|
||||||
"clearedNLogsBeforeXAfterY": {
|
"completeAppInstallationNotifChannel": "Dovršite instalaciju aplikacije",
|
||||||
"one": "Izbrisan {n} log (prije = {before}, nakon = {after})",
|
"checkingForUpdatesNotifChannel": "Tražim moguće nadogradnje",
|
||||||
"other": "Izbrisano {n} log-ova (prije = {before}, nakon = {after})"
|
"onlyCheckInstalledOrTrackOnlyApps": "Isključivo provjerite ažuriranje za instalirane i aplikacije 'samo za praćenje'",
|
||||||
},
|
"supportFixedAPKURL": "Podržite fiksne APK URL-ove",
|
||||||
"xAndNMoreUpdatesAvailable": {
|
"selectX": "Izaberite {}",
|
||||||
"one": "{} i još 1 aplikacija ima ažuriranja.",
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
"other": "{} i još {} aplikacija imaju ažuriranja."
|
"installMethod": "Installation method",
|
||||||
},
|
"normal": "Normal",
|
||||||
"xAndNMoreUpdatesInstalled": {
|
"shizuku": "Shizuku",
|
||||||
"one": "{} i još 1 aplikacija je ažurirana.",
|
"root": "Root",
|
||||||
"other": "{} i još {} aplikacija je ažurirano."
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
}
|
"removeAppQuestion": {
|
||||||
|
"one": "Želite li ukloniti aplikaciju?",
|
||||||
|
"other": "Želite li ukloniti aplikacije?"
|
||||||
|
},
|
||||||
|
"tooManyRequestsTryAgainInMinutes": {
|
||||||
|
"one": "Previše zahtjeva (ograničena broj zahteva) - pokušajte ponovo za {} minutu",
|
||||||
|
"other": "Previše zahtjeva (ograničena cijena) - pokušajte ponovo za {} min."
|
||||||
|
},
|
||||||
|
"bgUpdateGotErrorRetryInMinutes": {
|
||||||
|
"one": "Provjera ažuriranja u pozadini naišla je na {}, zakazuje se ponovni pokušaj za {} minutu",
|
||||||
|
"other": "Provjera ažuriranja u pozadini naišla je na {}, zakazuje se ponovni pokušaj za {} min."
|
||||||
|
},
|
||||||
|
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
||||||
|
"one": "Provjera ažuriranja u pozadini je pronašla {} ažuriranje - korisnik će biti obavješten ako je to potrebno",
|
||||||
|
"other": "Provjera ažuriranja u pozadini je pronašla {} ažuriranja - korisnik će biti obavješten ako je to potrebno"
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"one": "{} aplikacija",
|
||||||
|
"other": "{} aplikacije"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"one": "{} URL",
|
||||||
|
"other": "{} URL-ovi"
|
||||||
|
},
|
||||||
|
"minute": {
|
||||||
|
"one": "{} minuta",
|
||||||
|
"other": "min."
|
||||||
|
},
|
||||||
|
"hour": {
|
||||||
|
"one": "{} sat",
|
||||||
|
"other": "{} sat/i"
|
||||||
|
},
|
||||||
|
"day": {
|
||||||
|
"one": "{} dan",
|
||||||
|
"other": "{} dana"
|
||||||
|
},
|
||||||
|
"clearedNLogsBeforeXAfterY": {
|
||||||
|
"one": "Izbrisan {n} log (prije = {before}, nakon = {after})",
|
||||||
|
"other": "Izbrisano {n} log-ova (prije = {before}, nakon = {after})"
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesAvailable": {
|
||||||
|
"one": "{} i još 1 aplikacija ima ažuriranja.",
|
||||||
|
"other": "{} i još {} aplikacija imaju ažuriranja."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesInstalled": {
|
||||||
|
"one": "{} i još 1 aplikacija je ažurirana.",
|
||||||
|
"other": "{} i još {} aplikacija je ažurirano."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} i još jedna aplikacija je vjerovatno ažurirana.",
|
||||||
|
"other": "{} i još {} aplikacija su vjerovatno ažurirane."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
342
assets/translations/cs.json
Normal file
342
assets/translations/cs.json
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
{
|
||||||
|
"invalidURLForSource": "Žádná platná {} adresa URL aplikace",
|
||||||
|
"noReleaseFound": "Nebyla nalezena odpovídající verze",
|
||||||
|
"noVersionFound": "Nelze určit verzi vydání",
|
||||||
|
"urlMatchesNoSource": "URL neodpovídá žádnému známému zdroji",
|
||||||
|
"cantInstallOlderVersion": "Nelze nainstalovat starší verzi aplikace",
|
||||||
|
"appIdMismatch": "ID staženého balíčku neodpovídá ID existující aplikace",
|
||||||
|
"functionNotImplemented": "Tato třída nemá implementovánu tuto funkci",
|
||||||
|
"placeholder": "Zástupce",
|
||||||
|
"someErrors": "Vyskytly se nějaké chyby",
|
||||||
|
"unexpectedError": "Neočekávaná chyba",
|
||||||
|
"ok": "Ok",
|
||||||
|
"and": "a",
|
||||||
|
"githubPATLabel": "GitHub Personal Access Token (zvyšuje limit rychlosti)",
|
||||||
|
"includePrereleases": "Zahrnout předběžné verze",
|
||||||
|
"fallbackToOlderReleases": "Přechod na starší verze",
|
||||||
|
"filterReleaseTitlesByRegEx": "Filtrovat názvy verzí podle regulárního výrazu",
|
||||||
|
"invalidRegEx": "Neplatný regulární výraz",
|
||||||
|
"noDescription": "Žádný popis",
|
||||||
|
"cancel": "Zrušit",
|
||||||
|
"continue": "Pokračovat",
|
||||||
|
"requiredInBracets": "(Required)",
|
||||||
|
"dropdownNoOptsError": "ERROR: DROPDOWN MUSÍ MÍT AŽ JEDNU MOŽNOST",
|
||||||
|
"colour": "Barva",
|
||||||
|
"githubStarredRepos": "GitHub Starred Repos",
|
||||||
|
"uname": "Uživatelské jméno",
|
||||||
|
"wrongArgNum": "Nesprávný počet zadaných argumentů",
|
||||||
|
"xIsTrackOnly":"{} je určeno pouze pro sledování",
|
||||||
|
"source": "Zdroj",
|
||||||
|
"app": "App",
|
||||||
|
"appsFromSourceAreTrackOnly": "Aplikace z tohoto zdroje jsou Jen sledovány.",
|
||||||
|
"youPickedTrackOnly": "Vybrali jste možnost Jen sledovat.",
|
||||||
|
"trackOnlyAppDescription": "Aplikace je sledována kvůli aktualizacím, ale Obtainium ji nebude stahovat ani instalovat.",
|
||||||
|
"cancelled": "Zrušeno",
|
||||||
|
"appAlreadyAdded": "Aplikace již přidána",
|
||||||
|
"alreadyUpToDateQuestion": "App already up to date?",
|
||||||
|
"addApp": "Přidat aplikaci",
|
||||||
|
"appSourceURL": "Zdrojová adresa URL aplikace",
|
||||||
|
"error": "Chyba",
|
||||||
|
"add": "Přidat",
|
||||||
|
"searchSomeSourcesLabel": "Vyhledávání (pouze pro určité zdroje)",
|
||||||
|
"search": "Hledat",
|
||||||
|
"additionalOptsFor": "Další možnosti pro {}",
|
||||||
|
"supportedSources": "Podporované zdroje",
|
||||||
|
"trackOnlyInBrackets": "(Pouze stopy)",
|
||||||
|
"searchableInBrackets": "(s možností vyhledávání)",
|
||||||
|
"appsString": "Apky",
|
||||||
|
"noApps": "Žádné aplikace",
|
||||||
|
"noAppsForFilter": "Žádné aplikace pro vybraný filtr",
|
||||||
|
"byX": "Od {}",
|
||||||
|
"percentProgress": "Pokrok: {}%",
|
||||||
|
"pleaseWait": "Počkejte prosím",
|
||||||
|
"updateAvailable": "Aktualizace je k dispozici",
|
||||||
|
"estimateInBracketsShort": "(approx.)",
|
||||||
|
"notInstalled": "Není nainstalováno",
|
||||||
|
"estimateInBrackets": "(přibližně)",
|
||||||
|
"selectAll": "Vybrat vše",
|
||||||
|
"deselectX": "{} deselected",
|
||||||
|
"xWillBeRemovedButRemainInstalled": "{} bude odstraněn z Obtainium, ale zůstane nainstalován v zařízení.",
|
||||||
|
"removeSelectedAppsQuestion": "Odebrat vybrané aplikace?",
|
||||||
|
"removeSelectedApps": "Odebrat vybrané aplikace",
|
||||||
|
"updateX": "Aktualizovat {}",
|
||||||
|
"installX": "Instalovat {}",
|
||||||
|
"markXTrackOnlyAsUpdated": "Označit {}\n(Jen sledované)\njako aktualizované",
|
||||||
|
"changeX": "Změnit {}",
|
||||||
|
"installUpdateApps": "Instalovat/aktualizovat aplikace",
|
||||||
|
"installUpdateSelectedApps": "Instalovat/aktualizovat vybrané aplikace",
|
||||||
|
"markXSelectedAppsAsUpdated": "Označit {} vybrané aplikace jako aktuální?",
|
||||||
|
"no": "Ne",
|
||||||
|
"yes": "Ano",
|
||||||
|
"markSelectedAppsUpdated": "Označit vybrané aplikace jako aktuální",
|
||||||
|
"pinToTop": "Připnout nahoru",
|
||||||
|
"unpinFromTop": "Odepnout shora",
|
||||||
|
"resetInstallStatusForSelectedAppsQuestion": "Obnovit stav instalace vybraných aplikací?",
|
||||||
|
"installStatusOfXWillBeResetExplanation": "Stav instalace vybraných aplikací bude resetován. To může být užitečné, pokud je verze aplikace zobrazená v Obtainium nesprávná z důvodu neúspěšných aktualizací nebo jiných problémů.",
|
||||||
|
"shareSelectedAppURLs": "Sdílet adresy URL vybraných aplikací",
|
||||||
|
"resetInstallStatus": "Obnovit stav instalace",
|
||||||
|
"more": "Více",
|
||||||
|
"removeOutdatedFilter": "Odstranit filtr Neaktuální",
|
||||||
|
"showOutdatedOnly": "Zobrazovat pouze zastaralé aplikace",
|
||||||
|
"filter": "Filtr",
|
||||||
|
"filterActive": "Filtr *",
|
||||||
|
"filterApps": "Filtrovat aplikace",
|
||||||
|
"appName": "Název aplikace",
|
||||||
|
"author": "Autor",
|
||||||
|
"upToDateApps": "Aktuální apky",
|
||||||
|
"nonInstalledApps": "Neinstalované apky",
|
||||||
|
"importExport": "Import/Export",
|
||||||
|
"settings": "Nastavení",
|
||||||
|
"exportedTo": "Exportováno do {}",
|
||||||
|
"obtainiumExport": "Obtainium Export",
|
||||||
|
"invalidInput": "Neplatný vstup",
|
||||||
|
"importedX": "Importováno {}",
|
||||||
|
"obtainiumImport": "Obtainium Import",
|
||||||
|
"importFromURLList": "Import ze seznamu URL",
|
||||||
|
"searchQuery": "Vyhledávací dotaz",
|
||||||
|
"appURLList": "Seznam adres aplikací",
|
||||||
|
"line": "Linka",
|
||||||
|
"searchX": "Search {}",
|
||||||
|
"noResults": "Nebyly nalezeny žádné výsledky",
|
||||||
|
"importX": "Import {}",
|
||||||
|
"importedAppsIdDisclaimer": "Importované aplikace mohou být nesprávně zobrazeny jako \"Neinstalovány\". Chcete-li to opravit, nainstalujte je znovu prostřednictvím Obtainium. To nemá vliv na data aplikací. Ovlivňuje pouze metody importu URL a třetích stran.",
|
||||||
|
"importErrors": "Chyba importu",
|
||||||
|
"importedXOfYApps": "{}importováno z {} aplikací.",
|
||||||
|
"followingURLsHadErrors": "U následujících adres došlo k chybám:",
|
||||||
|
"selectURL": "Vybrat adresu",
|
||||||
|
"selectURLs": "Select adresy",
|
||||||
|
"pick": "Vybrat",
|
||||||
|
"theme": "Téma",
|
||||||
|
"dark": "Tmavé",
|
||||||
|
"light": "Světlé",
|
||||||
|
"followSystem": "Jako systém",
|
||||||
|
"obtainium": "Obtainium",
|
||||||
|
"materialYou": "Material You",
|
||||||
|
"useBlackTheme": "Použít čistě černé tmavé téma",
|
||||||
|
"appSortBy": "Seřadit podle",
|
||||||
|
"authorName": "Autor/Jméno",
|
||||||
|
"nameAuthor": "Jméno/Autor",
|
||||||
|
"asAdded": "Přidáno",
|
||||||
|
"appSortOrder": "Seřadit",
|
||||||
|
"ascending": "Vzestupně",
|
||||||
|
"descending": "Sestupně",
|
||||||
|
"bgUpdateCheckInterval": "Interval kontroly aktualizace na pozadí",
|
||||||
|
"neverManualOnly": "Nikdy - pouze ručně",
|
||||||
|
"appearance": "Vzhled",
|
||||||
|
"showWebInAppView": "Zobrazit zdrojové webové stránky v zobrazení aplikace",
|
||||||
|
"pinUpdates": "Připnout aplikace s aktualizacemi nahoru",
|
||||||
|
"updates": "Updates",
|
||||||
|
"sourceSpecific": "Specifické pro zdroj",
|
||||||
|
"appSource": "Zdroj aplikace",
|
||||||
|
"noLogs": "Žádné protokoly",
|
||||||
|
"appLogs": "Záznamy apky",
|
||||||
|
"close": "Zavřít",
|
||||||
|
"share": "Sdílet",
|
||||||
|
"appNotFound": "Aplikace nenalezena",
|
||||||
|
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||||
|
"pickAnAPK": "Vybrat APK",
|
||||||
|
"appHasMoreThanOnePackage": "{} má více než jeden balíček:",
|
||||||
|
"deviceSupportsXArch": "Vaše zařízení podporuje architekturu CPU {}.",
|
||||||
|
"deviceSupportsFollowingArchs": "Vaše zařízení podporuje následující architektury CPU:",
|
||||||
|
"warning": "Varování",
|
||||||
|
"sourceIsXButPackageFromYPrompt": "Zdroj aplikace je '{}', ale balíček pro vydání je z '{}'. Pokračovat?",
|
||||||
|
"updatesAvailable": "Dostupné aktualizace",
|
||||||
|
"updatesAvailableNotifDescription": "Upozorňuje uživatele, že jsou k dispozici aktualizace pro jednu nebo více aplikací sledovaných Obtainium",
|
||||||
|
"noNewUpdates": "Žádné nové aktualizace.",
|
||||||
|
"xHasAnUpdate": "{} má aktualizaci.",
|
||||||
|
"appsUpdated": "Aplikace aktualizovány",
|
||||||
|
"appsUpdatedNotifDescription": "Upozornit, že byly provedeny aktualizace jedné nebo více aplikací na pozadí",
|
||||||
|
"xWasUpdatedToY": "{} byla aktualizována na {}",
|
||||||
|
"errorCheckingUpdates": "Chyba kontroly aktualizací",
|
||||||
|
"errorCheckingUpdatesNotifDescription": "Zobrazit oznámení při neúspěšné kontrole aktualizací na pozadí",
|
||||||
|
"appsRemoved": "Odstraněné aplikace",
|
||||||
|
"appsRemovedNotifDescription": "Oznámit, že jedna nebo více aplikací bylo odstraněno z důvodu chyb při načítání",
|
||||||
|
"xWasRemovedDueToErrorY": "{} byla odstraněna z důvodu následující chyby: {}",
|
||||||
|
"completeAppInstallation": "Dokončit instalaci aplikace",
|
||||||
|
"obtainiumMustBeOpenToInstallApps": "Obtainium musí být otevřeno, aby bylo možné instalovat aplikace",
|
||||||
|
"completeAppInstallationNotifDescription": "Vyzvat k návratu do Obtainium pro dokončení instalace aplikací",
|
||||||
|
"checkingForUpdates": "Zkontrolovat aktualizace",
|
||||||
|
"checkingForUpdatesNotifDescription": "Dočasné oznámení zobrazené při kontrole aktualizací",
|
||||||
|
"pleaseAllowInstallPerm": "Povolte prosím Obtainium instalovat aplikace",
|
||||||
|
"trackOnly": "Jen sledovat",
|
||||||
|
"errorWithHttpStatusCode": "Chyba {}",
|
||||||
|
"versionCorrectionDisabled": "Oprava verze zakázána (zásuvný modul zřejmě nefunguje)",
|
||||||
|
"unknown": "Neznám",
|
||||||
|
"none": "None",
|
||||||
|
"never": "Nikdy",
|
||||||
|
"latestVersionX": "Nejnovější verze: {}",
|
||||||
|
"installedVersionX": "Nainstalovaná verze: {}",
|
||||||
|
"lastUpdateCheckX": "Poslední kontrola aktualizace: {}",
|
||||||
|
"remove": "Odebrat",
|
||||||
|
"yesMarkUpdated": "Ano, označit jako aktualizované",
|
||||||
|
"fdroid": "Oficiální repozitář F-Droid",
|
||||||
|
"appIdOrName": "ID nebo název apky",
|
||||||
|
"appId": "App ID",
|
||||||
|
"appWithIdOrNameNotFound": "Žádná aplikace s tímto ID nebo názvem nebyla nalezena",
|
||||||
|
"reposHaveMultipleApps": "Repozitáře mohou obsahovat více aplikací",
|
||||||
|
"fdroidThirdPartyRepo": "F-Droid repozitář třetí strany",
|
||||||
|
"steam": "Steam",
|
||||||
|
"steamMobile": "Steam Mobile",
|
||||||
|
"steamChat": "Steam Chat",
|
||||||
|
"install": "Install",
|
||||||
|
"markInstalled": "Označit jako nainstalovaný",
|
||||||
|
"update": "Aktualizovat",
|
||||||
|
"markUpdated": "Označit jako aktuální",
|
||||||
|
"additionalOptions": "Další možnosti",
|
||||||
|
"disableVersionDetection": "Deaktivovat detekci verze",
|
||||||
|
"noVersionDetectionExplanation": "Tato možnost by měla být použita pouze u aplikace, kde detekce verzí nefunguje správně.",
|
||||||
|
"downloadingX": "Stáhnout {}",
|
||||||
|
"downloadNotifDescription": "Informuje uživatele o průběhu stahování aplikace",
|
||||||
|
"noAPKFound": "Žádná APK nebyla nalezena",
|
||||||
|
"noVersionDetection": "Žádná detekce verze",
|
||||||
|
"categorize": "Kategorizovat",
|
||||||
|
"categories": "Kategorie",
|
||||||
|
"category": "Kategorie",
|
||||||
|
"noCategory": "Žádná kategorie",
|
||||||
|
"noCategories": "Žádné kategorie",
|
||||||
|
"deleteCategoriesQuestion": "Smazat kategorie?",
|
||||||
|
"categoryDeleteWarning": "Všechny aplikace v odstraněných kategoriích budou nastaveny na nekategorizované.",
|
||||||
|
"addCategory": "Přidat kategorii",
|
||||||
|
"label": "Štítek",
|
||||||
|
"language": "Jazyk",
|
||||||
|
"copiedToClipboard": "Zkopírováno do schránky",
|
||||||
|
"storagePermissionDenied": "Oprávnění k ukládání odepřeno",
|
||||||
|
"selectedCategorizeWarning": "Toto nahradí všechna stávající nastavení kategorií pro vybrané aplikace.",
|
||||||
|
"filterAPKsByRegEx": "Filtrovat APK podle regulárního výrazu",
|
||||||
|
"removeFromObtainium": "Odebrat z Obtainium",
|
||||||
|
"uninstallFromDevice": "Odinstalovat ze zařízení",
|
||||||
|
"onlyWorksWithNonVersionDetectApps": "Funguje pouze pro aplikace s vypnutou detekcí verze.",
|
||||||
|
"releaseDateAsVersion": "Použít datum vydání jako verzi",
|
||||||
|
"releaseDateAsVersionExplanation": "Tato možnost by měla být použita pouze u aplikace, kde detekce verzí nefunguje správně, ale je k dispozici datum vydání.",
|
||||||
|
"changes": "Změny",
|
||||||
|
"releaseDate": "Datum vydání",
|
||||||
|
"importFromURLsInFile": "Importovat adresy URL ze souboru (např. OPML)",
|
||||||
|
"versionDetection": "Detekce verze",
|
||||||
|
"standardVersionDetection": "Standardní detekce verze",
|
||||||
|
"groupByCategory": "Seskupit podle kategorie",
|
||||||
|
"autoApkFilterByArch": "Pokud je to možné, pokuste se filtrovat soubory APK podle architektury procesoru",
|
||||||
|
"overrideSource": "Přepsat zdroj",
|
||||||
|
"dontShowAgain": "Nezobrazovat znovu",
|
||||||
|
"dontShowTrackOnlyWarnings": "Nezobrazovat varování pro 'Jen sledované'",
|
||||||
|
"dontShowAPKOriginWarnings": "Nezobrazovat varování pro původ APK",
|
||||||
|
"moveNonInstalledAppsToBottom": "Přesunout nenainstalované aplikace na konec zobrazení Aplikace",
|
||||||
|
"gitlabPATLabel": "GitLab Personal Access Token\n(Umožňuje vyhledávání a lepší zjišťování APK)",
|
||||||
|
"about": "O",
|
||||||
|
"requiresCredentialsInSettings": "{}: Vyžaduje další pověření (v nastavení)",
|
||||||
|
"checkOnStart": "Zkontrolovat jednou při spuštění",
|
||||||
|
"tryInferAppIdFromCode": "Pokusit se určit ID aplikace ze zdrojového kódu",
|
||||||
|
"removeOnExternalUninstall": "Automaticky odstranit externě odinstalované aplikace",
|
||||||
|
"pickHighestVersionCode": "Automaticky vybrat nejvyšší verzi APK",
|
||||||
|
"checkUpdateOnDetailPage": "Zkontrolovat aktualizaci při otevření stránky s podrobnostmi aplikace",
|
||||||
|
"disablePageTransitions": "Zakázat animace pro přechody stránek",
|
||||||
|
"reversePageTransitions": "Obrátit animace pro přechody stránek",
|
||||||
|
"minStarCount": "Minimální počet hvězdiček",
|
||||||
|
"addInfoBelow": "Přidat tuto informaci na konec stránky.",
|
||||||
|
"addInfoInSettings": "Přidat tuto informaci do nastavení.",
|
||||||
|
"githubSourceNote": "Omezení rychlosti GitHub lze obejít pomocí klíče API.",
|
||||||
|
"gitlabSourceNote": "Extrakce GitLab APK nemusí fungovat bez klíče API",
|
||||||
|
"sortByLastLinkSegment": "Seřadit pouze podle poslední části odkazu",
|
||||||
|
"filterReleaseNotesByRegEx": "Filtrovat poznámky k vydání podle regulárního výrazu",
|
||||||
|
"customLinkFilterRegex": "Vlastní filtr odkazů APK podle regulárního výrazu (výchozí '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "Byly provedeny pokusy o aktualizaci aplikací",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Upozorňuje uživatele, že na pozadí mohly být provedeny aktualizace jedné nebo více aplikací",
|
||||||
|
"xWasPossiblyUpdatedToY":"{} mohlo být aktualizováno na {}.",
|
||||||
|
"enableBackgroundUpdates": "Povolit aktualizace na pozadí",
|
||||||
|
"backgroundUpdateReqsExplanation": "Aktualizace na pozadí nemusí být možná pro všechny aplikace.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "Úspěšnost instalace na pozadí lze určit pouze v případě, že je otevřeno Obtainium.",
|
||||||
|
"verifyLatestTag": "Zkontrolovat značku latest",
|
||||||
|
"intermediateLinkRegex": "Filtrovat mezipropojení, které by mělo být navštíveno jako první",
|
||||||
|
"filterByLinkText": "Filtrovat odkazy podle textu odkazu",
|
||||||
|
"intermediateLinkNotFound": "Připojený odkaz nenalezen",
|
||||||
|
"intermediateLink": "Připojený odkaz",
|
||||||
|
"exemptFromBackgroundUpdates": "Vyloučit z aktualizací na pozadí (je-li povoleno)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "Deaktivovat aktualizace na pozadí, pokud není k dispozici Wi-Fi",
|
||||||
|
"autoSelectHighestVersionCode": "Automaticky vybrat nejvyšší verzi APK",
|
||||||
|
"versionExtractionRegEx": "Extrakce verze pomocí RegEx",
|
||||||
|
"matchGroupToUse": "Odpovídá použité skupině",
|
||||||
|
"highlightTouchTargets": "Zvýraznit méně zjevné cíle dotyku",
|
||||||
|
"pickExportDir": "Vybrat adresář pro export",
|
||||||
|
"autoExportOnChanges": "Automatický export při změně",
|
||||||
|
"includeSettings": "Zahrnout nastavení",
|
||||||
|
"filterVersionsByRegEx": "Filtrovat verze podle regulárních výrazů",
|
||||||
|
"trySelectingSuggestedVersionCode": "Zkusit vybrat navrhovanou verzi APK",
|
||||||
|
"dontSortReleasesList": "Seřadit vydání z rozhraní API",
|
||||||
|
"reverseSort": "Obrácené třídění",
|
||||||
|
"takeFirstLink": "Použít první odkaz",
|
||||||
|
"skipSort": "Přeskočit třídění",
|
||||||
|
"debugMenu": "Nabídka ladění",
|
||||||
|
"bgTaskStarted": "Spuštěna úloha na pozadí - zkontrolujte protokoly.",
|
||||||
|
"runBgCheckNow": "Spustit kontrolu aktualizací na pozadí nyní",
|
||||||
|
"versionExtractWholePage": "Použít extrakci verze pomocí RegEx na celou stránku",
|
||||||
|
"installing": "Instaluji",
|
||||||
|
"skipUpdateNotifications": "Neposkytovat oznámení o aktualizaci",
|
||||||
|
"updatesAvailableNotifChannel": "Dostupné aktualizace",
|
||||||
|
"appsUpdatedNotifChannel": "Apky aktualizovány",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "Byly provedeny pokusy o aktualizace aplikací",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Chyba při kontrole aktualizací",
|
||||||
|
"appsRemovedNotifChannel": "Odstraněné apky",
|
||||||
|
"downloadingXNotifChannel": "Stáhnout {}",
|
||||||
|
"completeAppInstallationNotifChannel": "Dokončit instalaci aplikace",
|
||||||
|
"checkingForUpdatesNotifChannel": "Zkontrolovat aktualizace",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Na aktualizace kontrolovat pouze nainstalované aplikace a aplikace označené Track only",
|
||||||
|
"supportFixedAPKURL": "Odhadnout novější verzi na základě prvních třiceti číslic kontrolního součtu adresy URL APK, pokud není podporována jinak",
|
||||||
|
"selectX": "Vybrat {}",
|
||||||
|
"parallelDownloads": "Povolit souběžné stahování",
|
||||||
|
"installMethod": "Metoda instalace",
|
||||||
|
"normal": "Normální",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Správce",
|
||||||
|
"shizukuBinderNotFound": "Shizuku neběží",
|
||||||
|
"removeAppQuestion": {
|
||||||
|
"one": "Odstranit Apku?",
|
||||||
|
"other": "Odstranit Apky?"
|
||||||
|
},
|
||||||
|
"tooManyRequestsTryAgainInMinutes": {
|
||||||
|
"one": "Příliš mnoho požadavků (omezená rychlost) - zkuste to znovu za {} minutu",
|
||||||
|
"other": "Příliš mnoho požadavků (omezená rychlost) - zkuste to znovu za {} minut"
|
||||||
|
},
|
||||||
|
"bgUpdateGotErrorRetryInMinutes": {
|
||||||
|
"one": "Při kontrole aktualizace na pozadí byla zjištěna chyba {}, opakování bude naplánováno za {} minut",
|
||||||
|
"other": "Při kontrole aktualizací na pozadí byla zjištěna chyba {}, opakování bude naplánováno za {} minut"
|
||||||
|
},
|
||||||
|
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
||||||
|
"one": "Při kontrole aktualizací na pozadí nalezena {}aktualizace - v případě potřeby upozorní uživatele",
|
||||||
|
"other": "Kontrola aktualizací na pozadí nalezla {} aktualizací - v případě potřeby upozorní uživatele"
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"one": "{} Apka",
|
||||||
|
"other": "{} Apky"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"one": "{} Adresa",
|
||||||
|
"other": "{} Adres"
|
||||||
|
},
|
||||||
|
"minute": {
|
||||||
|
"one": "{} Minuta",
|
||||||
|
"other": "{} Minut"
|
||||||
|
},
|
||||||
|
"hour": {
|
||||||
|
"one": "{} Hodina",
|
||||||
|
"other": "{} Hodin"
|
||||||
|
},
|
||||||
|
"day": {
|
||||||
|
"one": "{} Den",
|
||||||
|
"other": "{} Dnů"
|
||||||
|
},
|
||||||
|
"clearedNLogsBeforeXAfterY": {
|
||||||
|
"one": "{n} Záznam vymazán (před = {before}, po = {after})",
|
||||||
|
"other": "{n} Záznamů vymazáno (před = {before}, po = {after})"
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesAvailable": {
|
||||||
|
"one": "{} a 1 další aplikace mají aktualizace.",
|
||||||
|
"other": "{} a {} další aplikace mají aktualizace."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesInstalled": {
|
||||||
|
"one": "{} a 1 další aplikace mají aktualizace.",
|
||||||
|
"other": "{} a {} další aplikace byly aktualizovány."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} a 1 další aplikace možno aktualizovat",
|
||||||
|
"other": "{} a {} další aplikace mohou být aktualizovány."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,15 +11,7 @@
|
|||||||
"unexpectedError": "Unerwarteter Fehler",
|
"unexpectedError": "Unerwarteter Fehler",
|
||||||
"ok": "Okay",
|
"ok": "Okay",
|
||||||
"and": "und",
|
"and": "und",
|
||||||
"startedBgUpdateTask": "Hintergrundaktualisierungsprüfung gestartet",
|
|
||||||
"bgUpdateIgnoreAfterIs": "Hintergrundaktualisierung 'ignoreAfter' ist {}",
|
|
||||||
"startedActualBGUpdateCheck": "Überprüfung der Hintergrundaktualisierung gestartet",
|
|
||||||
"bgUpdateTaskFinished": "Hintergrundaktualisierungsprüfung abgeschlossen",
|
|
||||||
"firstRun": "Dies ist der erste Start von Obtainium überhaupt",
|
|
||||||
"settingUpdateCheckIntervalTo": "Aktualisierungsintervall auf {} stellen",
|
|
||||||
"githubPATLabel": "GitHub Personal Access Token (Erhöht das Ratenlimit)",
|
"githubPATLabel": "GitHub Personal Access Token (Erhöht das Ratenlimit)",
|
||||||
"githubPATHint": "PAT muss in diesem Format sein: Benutzername:Token",
|
|
||||||
"githubPATFormat": "Benutzername:Token",
|
|
||||||
"includePrereleases": "Vorabversionen einbeziehen",
|
"includePrereleases": "Vorabversionen einbeziehen",
|
||||||
"fallbackToOlderReleases": "Fallback auf ältere Versionen",
|
"fallbackToOlderReleases": "Fallback auf ältere Versionen",
|
||||||
"filterReleaseTitlesByRegEx": "Release-Titel nach regulärem Ausdruck\nfiltern",
|
"filterReleaseTitlesByRegEx": "Release-Titel nach regulärem Ausdruck\nfiltern",
|
||||||
@@ -32,12 +24,12 @@
|
|||||||
"colour": "Farbe",
|
"colour": "Farbe",
|
||||||
"githubStarredRepos": "GitHub Starred Repos",
|
"githubStarredRepos": "GitHub Starred Repos",
|
||||||
"uname": "Benutzername",
|
"uname": "Benutzername",
|
||||||
"wrongArgNum": "Falsche Anzahl von Argumenten übermittelt",
|
"wrongArgNum": "Falsche Anzahl von Argumenten (Parametern) übermittelt",
|
||||||
"xIsTrackOnly": "{} ist nur zur Nachverfolgung",
|
"xIsTrackOnly": "{} ist nur zur Nachverfolgung",
|
||||||
"source": "Quelle",
|
"source": "Quelle",
|
||||||
"app": "App",
|
"app": "App",
|
||||||
"appsFromSourceAreTrackOnly": "Apps aus dieser Quelle sind 'Nur Nachverfolgen'.",
|
"appsFromSourceAreTrackOnly": "Apps aus dieser Quelle sind nur zum Nachverfolgen.",
|
||||||
"youPickedTrackOnly": "Sie haben die Option 'Nur Nachverfolgen' gewählt.",
|
"youPickedTrackOnly": "Sie haben die Option „Nur Nachverfolgen“ gewählt.",
|
||||||
"trackOnlyAppDescription": "Die App wird auf Updates überwacht, aber Obtainium wird sie nicht herunterladen oder installieren.",
|
"trackOnlyAppDescription": "Die App wird auf Updates überwacht, aber Obtainium wird sie nicht herunterladen oder installieren.",
|
||||||
"cancelled": "Abgebrochen",
|
"cancelled": "Abgebrochen",
|
||||||
"appAlreadyAdded": "App bereits hinzugefügt",
|
"appAlreadyAdded": "App bereits hinzugefügt",
|
||||||
@@ -46,10 +38,10 @@
|
|||||||
"appSourceURL": "Quell-URL der App",
|
"appSourceURL": "Quell-URL der App",
|
||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"add": "Hinzufügen",
|
"add": "Hinzufügen",
|
||||||
"searchSomeSourcesLabel": "Suche (nur bestimmte Quellen)",
|
"searchSomeSourcesLabel": "Suche (nur für bestimmte Quellen)",
|
||||||
"search": "Suchen",
|
"search": "Suchen",
|
||||||
"additionalOptsFor": "Zusatzoptionen für {}",
|
"additionalOptsFor": "Zusatzoptionen für {}",
|
||||||
"supportedSourcesBelow": "Unterstützte Quellen:",
|
"supportedSources": "Unterstützte Quellen",
|
||||||
"trackOnlyInBrackets": "(Nur Nachverfolgen)",
|
"trackOnlyInBrackets": "(Nur Nachverfolgen)",
|
||||||
"searchableInBrackets": "(Durchsuchbar)",
|
"searchableInBrackets": "(Durchsuchbar)",
|
||||||
"appsString": "Apps",
|
"appsString": "Apps",
|
||||||
@@ -63,7 +55,7 @@
|
|||||||
"notInstalled": "Nicht installiert",
|
"notInstalled": "Nicht installiert",
|
||||||
"estimateInBrackets": "(Ungefähr)",
|
"estimateInBrackets": "(Ungefähr)",
|
||||||
"selectAll": "Alle auswählen",
|
"selectAll": "Alle auswählen",
|
||||||
"deselectN": "{} abgewählt",
|
"deselectX": "{} abgewählt",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} wird aus Obtainium entfernt, bleibt aber auf dem Gerät installiert.",
|
"xWillBeRemovedButRemainInstalled": "{} wird aus Obtainium entfernt, bleibt aber auf dem Gerät installiert.",
|
||||||
"removeSelectedAppsQuestion": "Ausgewählte Apps entfernen?",
|
"removeSelectedAppsQuestion": "Ausgewählte Apps entfernen?",
|
||||||
"removeSelectedApps": "Ausgewählte Apps entfernen",
|
"removeSelectedApps": "Ausgewählte Apps entfernen",
|
||||||
@@ -78,13 +70,13 @@
|
|||||||
"yes": "Ja",
|
"yes": "Ja",
|
||||||
"markSelectedAppsUpdated": "Markiere ausgewählte Apps als aktuell",
|
"markSelectedAppsUpdated": "Markiere ausgewählte Apps als aktuell",
|
||||||
"pinToTop": "Oben anheften",
|
"pinToTop": "Oben anheften",
|
||||||
"unpinFromTop": "'Oben anheften' aufheben",
|
"unpinFromTop": "„Oben anheften“ aufheben",
|
||||||
"resetInstallStatusForSelectedAppsQuestion": "Installationsstatus für ausgewählte Apps zurücksetzen?",
|
"resetInstallStatusForSelectedAppsQuestion": "Installationsstatus für ausgewählte Apps zurücksetzen?",
|
||||||
"installStatusOfXWillBeResetExplanation": "Der Installationsstatus der ausgewählten Apps wird zurückgesetzt. Dies kann hilfreich sein, wenn die in Obtainium angezeigte App-Version aufgrund fehlgeschlagener Aktualisierungen oder anderer Probleme falsch ist.",
|
"installStatusOfXWillBeResetExplanation": "Der Installationsstatus der ausgewählten Apps wird zurückgesetzt. Dies kann hilfreich sein, wenn die in Obtainium angezeigte App-Version aufgrund fehlgeschlagener Aktualisierungen oder anderer Probleme falsch ist.",
|
||||||
"shareSelectedAppURLs": "Ausgewählte App-URLs teilen",
|
"shareSelectedAppURLs": "Ausgewählte App-URLs teilen",
|
||||||
"resetInstallStatus": "Installationsstatus zurücksetzen",
|
"resetInstallStatus": "Installationsstatus zurücksetzen",
|
||||||
"more": "Mehr",
|
"more": "Mehr",
|
||||||
"removeOutdatedFilter": "App-Filter 'Nicht aktuell' entfernen",
|
"removeOutdatedFilter": "App-Filter ‚Nicht aktuell‘ entfernen",
|
||||||
"showOutdatedOnly": "Nur nicht aktuelle Apps anzeigen",
|
"showOutdatedOnly": "Nur nicht aktuelle Apps anzeigen",
|
||||||
"filter": "Filter",
|
"filter": "Filter",
|
||||||
"filterActive": "Filter *",
|
"filterActive": "Filter *",
|
||||||
@@ -111,7 +103,6 @@
|
|||||||
"importErrors": "Importfehler",
|
"importErrors": "Importfehler",
|
||||||
"importedXOfYApps": "{} von {} Apps importiert.",
|
"importedXOfYApps": "{} von {} Apps importiert.",
|
||||||
"followingURLsHadErrors": "Bei folgenden URLs traten Fehler auf:",
|
"followingURLsHadErrors": "Bei folgenden URLs traten Fehler auf:",
|
||||||
"okay": "Okay",
|
|
||||||
"selectURL": "URL auswählen",
|
"selectURL": "URL auswählen",
|
||||||
"selectURLs": "URLs auswählen",
|
"selectURLs": "URLs auswählen",
|
||||||
"pick": "Auswählen",
|
"pick": "Auswählen",
|
||||||
@@ -130,15 +121,15 @@
|
|||||||
"ascending": "Aufsteigend",
|
"ascending": "Aufsteigend",
|
||||||
"descending": "Absteigend",
|
"descending": "Absteigend",
|
||||||
"bgUpdateCheckInterval": "Prüfintervall für Hintergrundaktualisierung",
|
"bgUpdateCheckInterval": "Prüfintervall für Hintergrundaktualisierung",
|
||||||
"neverManualOnly": "Nie - nur manuell",
|
"neverManualOnly": "Nie – nur manuell",
|
||||||
"appearance": "Aussehen",
|
"appearance": "Aussehen",
|
||||||
"showWebInAppView": "Quellwebseite in der App-Ansicht anzeigen",
|
"showWebInAppView": "Quellwebseite in der App-Ansicht anzeigen",
|
||||||
"pinUpdates": "Apps mit Aktualisierungen oben anheften",
|
"pinUpdates": "Apps mit Aktualisierungen oben anheften",
|
||||||
"updates": "Aktualisierungen",
|
"updates": "Aktualisierungen",
|
||||||
"sourceSpecific": "Quellenspezifisch",
|
"sourceSpecific": "Quellenspezifisch",
|
||||||
"appSource": "App-Quelle",
|
"appSource": "App-Quelle",
|
||||||
"noLogs": "Keine Protokolle",
|
"noLogs": "Keine Logs",
|
||||||
"appLogs": "App Protokolle",
|
"appLogs": "App-Logs",
|
||||||
"close": "Schließen",
|
"close": "Schließen",
|
||||||
"share": "Teilen",
|
"share": "Teilen",
|
||||||
"appNotFound": "App nicht gefunden",
|
"appNotFound": "App nicht gefunden",
|
||||||
@@ -178,12 +169,12 @@
|
|||||||
"lastUpdateCheckX": "Letzte Aktualisierungsprüfung: {}",
|
"lastUpdateCheckX": "Letzte Aktualisierungsprüfung: {}",
|
||||||
"remove": "Entfernen",
|
"remove": "Entfernen",
|
||||||
"yesMarkUpdated": "Ja, als aktualisiert markieren",
|
"yesMarkUpdated": "Ja, als aktualisiert markieren",
|
||||||
"fdroid": "F-Droid Official",
|
"fdroid": "offizielles F-Droid-Repo",
|
||||||
"appIdOrName": "App ID oder Name",
|
"appIdOrName": "App ID oder Name",
|
||||||
"appId": "App ID",
|
"appId": "App ID",
|
||||||
"appWithIdOrNameNotFound": "Es wurde keine App mit dieser ID oder diesem Namen gefunden",
|
"appWithIdOrNameNotFound": "Es wurde keine App mit dieser ID oder diesem Namen gefunden",
|
||||||
"reposHaveMultipleApps": "Repos können mehrere Apps enthalten",
|
"reposHaveMultipleApps": "Repos können mehrere Apps enthalten",
|
||||||
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
|
"fdroidThirdPartyRepo": "F-Droid Drittparteienrepo",
|
||||||
"steam": "Steam",
|
"steam": "Steam",
|
||||||
"steamMobile": "Steam Mobile",
|
"steamMobile": "Steam Mobile",
|
||||||
"steamChat": "Steam Chat",
|
"steamChat": "Steam Chat",
|
||||||
@@ -229,32 +220,88 @@
|
|||||||
"dontShowTrackOnlyWarnings": "Warnung für 'Nur Nachverfolgen' nicht anzeigen",
|
"dontShowTrackOnlyWarnings": "Warnung für 'Nur Nachverfolgen' nicht anzeigen",
|
||||||
"dontShowAPKOriginWarnings": "Warnung für APK-Herkunft nicht anzeigen",
|
"dontShowAPKOriginWarnings": "Warnung für APK-Herkunft nicht anzeigen",
|
||||||
"moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben",
|
"moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Aktiviert Suche and Better APK Discovery)",
|
"gitlabPATLabel": "GitLab Personal Access Token\n(Aktiviert Suche und bessere APK Entdeckung)",
|
||||||
"about": "Über",
|
"about": "Über",
|
||||||
"requiresCredentialsInSettings": "Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
|
"requiresCredentialsInSettings": "{}: Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
|
||||||
"checkOnStart": "Überprüfe einmalig beim Start",
|
"checkOnStart": "Überprüfe einmalig beim Start",
|
||||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
"tryInferAppIdFromCode": "Versuche, die App-ID aus dem Quellcode zu ermitteln",
|
||||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
"removeOnExternalUninstall": "Automatisches Entfernen von extern deinstallierten Apps",
|
||||||
"pickHighestVersionCode": "Auto-select highest version code APK",
|
"pickHighestVersionCode": "Automatische Auswahl des APK mit höchstem Versionscode",
|
||||||
"checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
|
"checkUpdateOnDetailPage": "Nach Updates suchen, wenn eine App-Detailseite geöffnet wird",
|
||||||
"disablePageTransitions": "Disable page transition animations",
|
"disablePageTransitions": "Animationen für Seitenübergänge deaktivieren",
|
||||||
"reversePageTransitions": "Reverse page transition animations",
|
"reversePageTransitions": "Umgekehrte Animationen für Seitenübergänge",
|
||||||
"minStarCount": "Minimum Star Count",
|
"minStarCount": "Minimale Anzahl von Sternen",
|
||||||
|
"addInfoBelow": "Fügen Sie diese Informationen unten hinzu.",
|
||||||
|
"addInfoInSettings": "Fügen Sie diese Info in den Einstellungen hinzu.",
|
||||||
|
"githubSourceNote": "Die GitHub-Ratenbegrenzung kann mit einem API-Schlüssel umgangen werden.",
|
||||||
|
"gitlabSourceNote": "GitLab APK-Extraktion funktioniert möglicherweise nicht ohne API-Schlüssel",
|
||||||
|
"sortByLastLinkSegment": "Sortiere nur nach dem letzten Teil des Links",
|
||||||
|
"filterReleaseNotesByRegEx": "Versionshinweise nach regulärem Ausdruck filtern",
|
||||||
|
"customLinkFilterRegex": "Benutzerdefinierter APK Link Filter nach Regulärem Ausdruck (Standard '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "App Aktualisierungen wurden versucht",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Benachrichtigt den Benutzer, dass Updates für eine oder mehrere Apps möglicherweise im Hintergrund durchgeführt wurden",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} wurde möglicherweise aktualisiert auf {}.",
|
||||||
|
"enableBackgroundUpdates": "Aktiviere Hintergrundaktualisierungen",
|
||||||
|
"backgroundUpdateReqsExplanation": "Die Hintergrundaktualisierung ist möglicherweise nicht für alle Apps möglich.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "Der Erfolg einer Hintergrundinstallation kann nur festgestellt werden, wenn Obtainium geöffnet wird.",
|
||||||
|
"verifyLatestTag": "Überprüfe das „latest“ Tag",
|
||||||
|
"intermediateLinkRegex": "Filter für einen „Zwischen“-Link, der zuerst besucht werden soll",
|
||||||
|
"filterByLinkText": "Filtere Links durch Linktext",
|
||||||
|
"intermediateLinkNotFound": "„Zwischen“-Link nicht gefunden",
|
||||||
|
"intermediateLink": "„Zwischen“-Link",
|
||||||
|
"exemptFromBackgroundUpdates": "Ausschluss von Hintergrundaktualisierungen (falls aktiviert)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "Hintergrundaktualisierungen deaktivieren, wenn kein WLAN vorhanden ist",
|
||||||
|
"autoSelectHighestVersionCode": "Automatisch höchste APK-Version auswählen",
|
||||||
|
"versionExtractionRegEx": "Versions-Extraktion per RegEx",
|
||||||
|
"matchGroupToUse": "zu verwendende Gruppe abgleichen",
|
||||||
|
"highlightTouchTargets": "Weniger offensichtliche Touch-Ziele hervorheben",
|
||||||
|
"pickExportDir": "Export-Verzeichnis wählen",
|
||||||
|
"autoExportOnChanges": "Automatischer Export bei Änderung(en)",
|
||||||
|
"includeSettings": "Einstellungen einbeziehen",
|
||||||
|
"filterVersionsByRegEx": "Versionen nach regulären Ausdrücken filtern",
|
||||||
|
"trySelectingSuggestedVersionCode": "Versuchen, den vorgeschlagenen APK-Versionscode auszuwählen",
|
||||||
|
"dontSortReleasesList": "Freigaberelease von der API ordern",
|
||||||
|
"reverseSort": "Umgekehrtes Sortieren",
|
||||||
|
"takeFirstLink": "Verwende den ersten Link",
|
||||||
|
"skipSort": "Überspringe Sortieren",
|
||||||
|
"debugMenu": "Debug-Menü",
|
||||||
|
"bgTaskStarted": "Hintergrundaufgabe gestartet – Logs prüfen.",
|
||||||
|
"runBgCheckNow": "Hintergrundaktualisierungsprüfung jetzt durchführen",
|
||||||
|
"versionExtractWholePage": "Versions-Extraktion per RegEx auf die gesamte Seite anwenden",
|
||||||
|
"installing": "Installiere",
|
||||||
|
"skipUpdateNotifications": "Keine Benachrichtigung zu App-Updates geben",
|
||||||
|
"updatesAvailableNotifChannel": "Aktualisierungen verfügbar",
|
||||||
|
"appsUpdatedNotifChannel": "Apps aktualisiert",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "App Aktualisierungen wurden versucht",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Fehler beim Prüfen auf Aktualisierungen",
|
||||||
|
"appsRemovedNotifChannel": "Apps entfernt",
|
||||||
|
"downloadingXNotifChannel": "Lade {} herunter",
|
||||||
|
"completeAppInstallationNotifChannel": "App Installation abschließen",
|
||||||
|
"checkingForUpdatesNotifChannel": "Nach Aktualisierungen suchen",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Überprüfe nur installierte und mit „nur Nachverfolgen“ markierte Apps auf Aktualisierungen",
|
||||||
|
"supportFixedAPKURL": "neuere Version anhand der ersten dreißig Zahlen der Checksumme der APK URL erraten, wenn anderweitig nicht unterstützt",
|
||||||
|
"selectX": "Wähle {}",
|
||||||
|
"parallelDownloads": "Erlaube parallele Downloads",
|
||||||
|
"installMethod": "Installationsmethode",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku läuft nicht",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "App entfernen?",
|
"one": "App entfernen?",
|
||||||
"other": "Apps entfernen?"
|
"other": "Apps entfernen?"
|
||||||
},
|
},
|
||||||
"tooManyRequestsTryAgainInMinutes": {
|
"tooManyRequestsTryAgainInMinutes": {
|
||||||
"one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut",
|
"one": "Zu viele Anfragen (Rate begrenzt) – versuchen Sie es in {} Minute erneut",
|
||||||
"other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut"
|
"other": "Zu viele Anfragen (Rate begrenzt) – versuchen Sie es in {} Minuten erneut"
|
||||||
},
|
},
|
||||||
"bgUpdateGotErrorRetryInMinutes": {
|
"bgUpdateGotErrorRetryInMinutes": {
|
||||||
"one": "Bei der Aktualisierungsprüfung im Hintergrund wurde ein {} festgestellt, eine erneute Prüfung wird in {} Minute geplant",
|
"one": "Bei der Aktualisierungsprüfung im Hintergrund wurde ein {} festgestellt, eine erneute Prüfung wird in {} Minute geplant",
|
||||||
"other": "Bei der Aktualisierungsprüfung im Hintergrund wurde ein {} festgestellt, eine erneute Prüfung wird in {} Minuten geplant"
|
"other": "Bei der Aktualisierungsprüfung im Hintergrund wurde ein {} festgestellt, eine erneute Prüfung wird in {} Minuten geplant"
|
||||||
},
|
},
|
||||||
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
||||||
"one": "Hintergrundaktualisierungsprüfung fand {} Aktualisierung - benachrichtigt den Benutzer, falls erforderlich",
|
"one": "Die Hintergrundaktualisierungsprüfung fand {} Aktualisierung – benachrichtigt den Benutzer, falls erforderlich",
|
||||||
"other": "Hintergrundaktualisierungsprüfung fand {} Aktualisierungen - benachrichtigt den Benutzer, falls erforderlich"
|
"other": "Die Hintergrundaktualisierungsprüfung fand {} Aktualisierungen – benachrichtigt den Benutzer, falls erforderlich"
|
||||||
},
|
},
|
||||||
"apps": {
|
"apps": {
|
||||||
"one": "{} App",
|
"one": "{} App",
|
||||||
@@ -277,8 +324,8 @@
|
|||||||
"other": "{} Tage"
|
"other": "{} Tage"
|
||||||
},
|
},
|
||||||
"clearedNLogsBeforeXAfterY": {
|
"clearedNLogsBeforeXAfterY": {
|
||||||
"one": "{n} Protokoll gelöscht (vorher = {vorher}, nachher = {nachher})",
|
"one": "{n} Log gelöscht (vorher = {before}, nachher = {after})",
|
||||||
"other": "{n} Protokolle gelöscht (vorher = {vorher}, nachher = {nachher})"
|
"other": "{n} Logs gelöscht (vorher = {before}, nachher = {after})"
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesAvailable": {
|
"xAndNMoreUpdatesAvailable": {
|
||||||
"one": "{} und 1 weitere App haben Aktualisierungen.",
|
"one": "{} und 1 weitere App haben Aktualisierungen.",
|
||||||
@@ -287,5 +334,9 @@
|
|||||||
"xAndNMoreUpdatesInstalled": {
|
"xAndNMoreUpdatesInstalled": {
|
||||||
"one": "{} und 1 weitere Anwendung wurden aktualisiert.",
|
"one": "{} und 1 weitere Anwendung wurden aktualisiert.",
|
||||||
"other": "{} und {} weitere Anwendungen wurden aktualisiert."
|
"other": "{} und {} weitere Anwendungen wurden aktualisiert."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} und 1 weitere Anwendung wurden möglicherweise aktualisiert.",
|
||||||
|
"other": "{} und {} weitere Anwendungen wurden möglicherweise aktualisiert."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,7 @@
|
|||||||
"unexpectedError": "Unexpected Error",
|
"unexpectedError": "Unexpected Error",
|
||||||
"ok": "Okay",
|
"ok": "Okay",
|
||||||
"and": "and",
|
"and": "and",
|
||||||
"startedBgUpdateTask": "Started BG update check task",
|
|
||||||
"bgUpdateIgnoreAfterIs": "Bg update ignoreAfter is {}",
|
|
||||||
"startedActualBGUpdateCheck": "Started actual BG update checking",
|
|
||||||
"bgUpdateTaskFinished": "Finished BG update check task",
|
|
||||||
"firstRun": "This is the first ever run of Obtainium",
|
|
||||||
"settingUpdateCheckIntervalTo": "Setting update interval to {}",
|
|
||||||
"githubPATLabel": "GitHub Personal Access Token (Increases Rate Limit)",
|
"githubPATLabel": "GitHub Personal Access Token (Increases Rate Limit)",
|
||||||
"githubPATHint": "PAT must be in this format: username:token",
|
|
||||||
"githubPATFormat": "username:token",
|
|
||||||
"includePrereleases": "Include prereleases",
|
"includePrereleases": "Include prereleases",
|
||||||
"fallbackToOlderReleases": "Fallback to older releases",
|
"fallbackToOlderReleases": "Fallback to older releases",
|
||||||
"filterReleaseTitlesByRegEx": "Filter Release Titles by Regular Expression",
|
"filterReleaseTitlesByRegEx": "Filter Release Titles by Regular Expression",
|
||||||
@@ -49,7 +41,7 @@
|
|||||||
"searchSomeSourcesLabel": "Search (Some Sources Only)",
|
"searchSomeSourcesLabel": "Search (Some Sources Only)",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"additionalOptsFor": "Additional Options for {}",
|
"additionalOptsFor": "Additional Options for {}",
|
||||||
"supportedSourcesBelow": "Supported Sources:",
|
"supportedSources": "Supported Sources",
|
||||||
"trackOnlyInBrackets": "(Track-Only)",
|
"trackOnlyInBrackets": "(Track-Only)",
|
||||||
"searchableInBrackets": "(Searchable)",
|
"searchableInBrackets": "(Searchable)",
|
||||||
"appsString": "Apps",
|
"appsString": "Apps",
|
||||||
@@ -63,7 +55,7 @@
|
|||||||
"notInstalled": "Not Installed",
|
"notInstalled": "Not Installed",
|
||||||
"estimateInBrackets": "(Estimate)",
|
"estimateInBrackets": "(Estimate)",
|
||||||
"selectAll": "Select All",
|
"selectAll": "Select All",
|
||||||
"deselectN": "Deselect {}",
|
"deselectX": "Deselect {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} will be removed from Obtainium but remain installed on device.",
|
"xWillBeRemovedButRemainInstalled": "{} will be removed from Obtainium but remain installed on device.",
|
||||||
"removeSelectedAppsQuestion": "Remove Selected Apps?",
|
"removeSelectedAppsQuestion": "Remove Selected Apps?",
|
||||||
"removeSelectedApps": "Remove Selected Apps",
|
"removeSelectedApps": "Remove Selected Apps",
|
||||||
@@ -111,7 +103,6 @@
|
|||||||
"importErrors": "Import Errors",
|
"importErrors": "Import Errors",
|
||||||
"importedXOfYApps": "{} of {} Apps imported.",
|
"importedXOfYApps": "{} of {} Apps imported.",
|
||||||
"followingURLsHadErrors": "The following URLs had errors:",
|
"followingURLsHadErrors": "The following URLs had errors:",
|
||||||
"okay": "Okay",
|
|
||||||
"selectURL": "Select URL",
|
"selectURL": "Select URL",
|
||||||
"selectURLs": "Select URLs",
|
"selectURLs": "Select URLs",
|
||||||
"pick": "Pick",
|
"pick": "Pick",
|
||||||
@@ -231,7 +222,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "{} needs additional credentials (in Settings)",
|
||||||
"checkOnStart": "Check for updates on startup",
|
"checkOnStart": "Check for updates on startup",
|
||||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
||||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
||||||
@@ -240,6 +231,64 @@
|
|||||||
"disablePageTransitions": "Disable page transition animations",
|
"disablePageTransitions": "Disable page transition animations",
|
||||||
"reversePageTransitions": "Reverse page transition animations",
|
"reversePageTransitions": "Reverse page transition animations",
|
||||||
"minStarCount": "Minimum Star Count",
|
"minStarCount": "Minimum Star Count",
|
||||||
|
"addInfoBelow": "Add this info below.",
|
||||||
|
"addInfoInSettings": "Add this info in the Settings.",
|
||||||
|
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
||||||
|
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
||||||
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
|
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
||||||
|
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "App Updates Attempted",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
|
||||||
|
"enableBackgroundUpdates": "Enable background updates",
|
||||||
|
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
||||||
|
"verifyLatestTag": "Verify the 'latest' tag",
|
||||||
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "Intermediate link not found",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
|
||||||
|
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
||||||
|
"versionExtractionRegEx": "Version Extraction RegEx",
|
||||||
|
"matchGroupToUse": "Match Group to Use for Version Extraction Regex",
|
||||||
|
"highlightTouchTargets": "Highlight less obvious touch targets",
|
||||||
|
"pickExportDir": "Pick Export Directory",
|
||||||
|
"autoExportOnChanges": "Auto-export on changes",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
|
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
|
||||||
|
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
|
||||||
|
"dontSortReleasesList": "Retain release order from API",
|
||||||
|
"reverseSort": "Reverse sorting",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
|
"debugMenu": "Debug Menu",
|
||||||
|
"bgTaskStarted": "Background task started - check logs.",
|
||||||
|
"runBgCheckNow": "Run Background Update Check Now",
|
||||||
|
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page",
|
||||||
|
"installing": "Installing",
|
||||||
|
"skipUpdateNotifications": "Skip update notifications",
|
||||||
|
"updatesAvailableNotifChannel": "Updates Available",
|
||||||
|
"appsUpdatedNotifChannel": "Apps Updated",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "App Updates Attempted",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Error Checking for Updates",
|
||||||
|
"appsRemovedNotifChannel": "Apps Removed",
|
||||||
|
"downloadingXNotifChannel": "Downloading {}",
|
||||||
|
"completeAppInstallationNotifChannel": "Complete App Installation",
|
||||||
|
"checkingForUpdatesNotifChannel": "Checking for Updates",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
||||||
|
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||||
|
"selectX": "Select {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
|
"installMethod": "Installation method",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Сompatible Shizuku service wasn't found",
|
||||||
|
"useSystemFont": "Use the system font",
|
||||||
|
"systemFontError": "Error loading the system font: {}",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Remove App?",
|
"one": "Remove App?",
|
||||||
"other": "Remove Apps?"
|
"other": "Remove Apps?"
|
||||||
@@ -285,7 +334,11 @@
|
|||||||
"other": "{} and {} more apps have updates."
|
"other": "{} and {} more apps have updates."
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesInstalled": {
|
"xAndNMoreUpdatesInstalled": {
|
||||||
"one": "{} and 1 more app were updated.",
|
"one": "{} and 1 more app was updated.",
|
||||||
"other": "{} and {} more apps were updated."
|
"other": "{} and {} more apps were updated."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} and 1 more app may have been updated.",
|
||||||
|
"other": "{} and {} more apps may have been updated."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,37 +8,29 @@
|
|||||||
"functionNotImplemented": "Esta clase no ha implementado esta función",
|
"functionNotImplemented": "Esta clase no ha implementado esta función",
|
||||||
"placeholder": "Espacio reservado",
|
"placeholder": "Espacio reservado",
|
||||||
"someErrors": "Han ocurrido algunos errores",
|
"someErrors": "Han ocurrido algunos errores",
|
||||||
"unexpectedError": "Error Inesperado",
|
"unexpectedError": "Error inesperado",
|
||||||
"ok": "Correcto",
|
"ok": "OK",
|
||||||
"and": "y",
|
"and": "y",
|
||||||
"startedBgUpdateTask": "Empezada la tarea de comprobación de actualizaciones en segundo plano",
|
"githubPATLabel": "Token GitHub de acceso personal\n(reduce tiempos de espera)",
|
||||||
"bgUpdateIgnoreAfterIs": "El parámetro ignoreAfter de la actualización en segundo plano es {}",
|
|
||||||
"startedActualBGUpdateCheck": "Ha comenzado la comprobación de actualizaciones en segundo plano",
|
|
||||||
"bgUpdateTaskFinished": "Ha finalizado la comprobación de actualizaciones en segundo plano",
|
|
||||||
"firstRun": "Esta es la primera ejecución de Obtainium",
|
|
||||||
"settingUpdateCheckIntervalTo": "Cambiando intervalo de actualización a {}",
|
|
||||||
"githubPATLabel": "Token de Acceso Personal de GitHub (Reduce tiempos de espera)",
|
|
||||||
"githubPATHint": "El TAP debe tener este formato: nombre_de_usuario:token",
|
|
||||||
"githubPATFormat": "nombre_de_usuario:token",
|
|
||||||
"includePrereleases": "Incluir versiones preliminares",
|
"includePrereleases": "Incluir versiones preliminares",
|
||||||
"fallbackToOlderReleases": "Retorceder a versiones previas",
|
"fallbackToOlderReleases": "Retroceder a versiones previas",
|
||||||
"filterReleaseTitlesByRegEx": "Filtra Títulos de Versiones mediantes Expresiones Regulares",
|
"filterReleaseTitlesByRegEx": "Filtrar por título de versión",
|
||||||
"invalidRegEx": "Expresión regular inválida",
|
"invalidRegEx": "Expresión inválida",
|
||||||
"noDescription": "Sin descripción",
|
"noDescription": "Sin descripción",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"continue": "Continuar",
|
"continue": "Continuar",
|
||||||
"requiredInBrackets": "(Requerido)",
|
"requiredInBrackets": "(Requerido)",
|
||||||
"dropdownNoOptsError": "ERROR: EL DESPLEGABLE DEBE TENER AL MENOS UNA OPCIÓN",
|
"dropdownNoOptsError": "ERROR: EL DESPLEGABLE DEBE TENER AL MENOS UNA OPCIÓN",
|
||||||
"colour": "Color",
|
"colour": "Color",
|
||||||
"githubStarredRepos": "Repositorios favoritos de GitHub",
|
"githubStarredRepos": "Repositorios favoritos GitHub",
|
||||||
"uname": "Nombre de usuario",
|
"uname": "Nombre de usuario",
|
||||||
"wrongArgNum": "Número de argumentos provistos inválido",
|
"wrongArgNum": "Número de argumentos provistos inválido",
|
||||||
"xIsTrackOnly": "{} es de 'Solo Seguimiento'",
|
"xIsTrackOnly": "{} es de 'Sólo seguimiento'",
|
||||||
"source": "Origen",
|
"source": "Origen",
|
||||||
"app": "Aplicación",
|
"app": "Aplicación",
|
||||||
"appsFromSourceAreTrackOnly": "Las aplicaciones de este origen son de 'Solo Seguimiento'.",
|
"appsFromSourceAreTrackOnly": "Las aplicaciones de este origen son de 'solo seguimiento'.",
|
||||||
"youPickedTrackOnly": "Debes seleccionar la opción de 'Solo Seguimiento'.",
|
"youPickedTrackOnly": "Debe seleccionar la opción de 'solo seguimiento'.",
|
||||||
"trackOnlyAppDescription": "Se monitorizará la aplicación en busca de actualizaciones, pero Obtainium no será capaz de descargarla o acutalizarla.",
|
"trackOnlyAppDescription": "Se hará el seguimiento de actualizaciones para la aplicación, pero Obtainium no será capaz de descargarla o actalizarla.",
|
||||||
"cancelled": "Cancelado",
|
"cancelled": "Cancelado",
|
||||||
"appAlreadyAdded": "Aplicación ya añadida",
|
"appAlreadyAdded": "Aplicación ya añadida",
|
||||||
"alreadyUpToDateQuestion": "¿Aplicación ya actualizada?",
|
"alreadyUpToDateQuestion": "¿Aplicación ya actualizada?",
|
||||||
@@ -46,16 +38,16 @@
|
|||||||
"appSourceURL": "URL de Origen de la Aplicación",
|
"appSourceURL": "URL de Origen de la Aplicación",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"add": "Añadir",
|
"add": "Añadir",
|
||||||
"searchSomeSourcesLabel": "Buscar (Solo Algunas Fuentes)",
|
"searchSomeSourcesLabel": "Buscar (solo algunas fuentes)",
|
||||||
"search": "Buscar",
|
"search": "Buscar",
|
||||||
"additionalOptsFor": "Opciones Adicionales para {}",
|
"additionalOptsFor": "Opciones Adicionales para {}",
|
||||||
"supportedSourcesBelow": "Fuentes Soportadas:",
|
"supportedSources": "Fuentes Soportadas",
|
||||||
"trackOnlyInBrackets": "(Solo Seguimiento)",
|
"trackOnlyInBrackets": "(Solo seguimiento)",
|
||||||
"searchableInBrackets": "(Soporta Búsquedas)",
|
"searchableInBrackets": "(soporta búsqueda)",
|
||||||
"appsString": "Aplicaciones",
|
"appsString": "Aplicaciones",
|
||||||
"noApps": "Sin Aplicaciones",
|
"noApps": "Sin Aplicaciones",
|
||||||
"noAppsForFilter": "Sin Aplicaciones para Filtrar",
|
"noAppsForFilter": "Sin aplicaciones para filtrar",
|
||||||
"byX": "Por {}",
|
"byX": "por: {}",
|
||||||
"percentProgress": "Progreso: {}%",
|
"percentProgress": "Progreso: {}%",
|
||||||
"pleaseWait": "Por favor, espere",
|
"pleaseWait": "Por favor, espere",
|
||||||
"updateAvailable": "Actualización Disponible",
|
"updateAvailable": "Actualización Disponible",
|
||||||
@@ -63,33 +55,33 @@
|
|||||||
"notInstalled": "No Instalado",
|
"notInstalled": "No Instalado",
|
||||||
"estimateInBrackets": "(Aproximado)",
|
"estimateInBrackets": "(Aproximado)",
|
||||||
"selectAll": "Seleccionar Todo",
|
"selectAll": "Seleccionar Todo",
|
||||||
"deselectN": "Deseleccionar {}",
|
"deselectX": "Deseleccionar {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} será borrada de Obtainium pero continuará instalada en el dispositivo.",
|
"xWillBeRemovedButRemainInstalled": "{} será eliminada de Obtainium pero continuará instalada en el dispositivo.",
|
||||||
"removeSelectedAppsQuestion": "¿Borrar aplicaciones seleccionadas?",
|
"removeSelectedAppsQuestion": "¿Eliminar aplicaciones seleccionadas?",
|
||||||
"removeSelectedApps": "Borrar Aplicaciones Seleccionadas",
|
"removeSelectedApps": "Eliminar Aplicaciones Seleccionadas",
|
||||||
"updateX": "Actualizar {}",
|
"updateX": "Actualizar {}",
|
||||||
"installX": "Instalar {}",
|
"installX": "Instalar {}",
|
||||||
"markXTrackOnlyAsUpdated": "Marcar {}\n(Solo Seguimient)\ncomo Actualizada",
|
"markXTrackOnlyAsUpdated": "Marcar {}\n(Solo seguimiento)\ncomo actualizada",
|
||||||
"changeX": "Cambiar {}",
|
"changeX": "Cambiar {}",
|
||||||
"installUpdateApps": "Instalar/Actualizar Aplicaciones",
|
"installUpdateApps": "Instalar/Actualizar aplicaciones",
|
||||||
"installUpdateSelectedApps": "Instalar/Actualizar Aplicaciones Seleccionadas",
|
"installUpdateSelectedApps": "Instalar/Actualizar aplicaciones seleccionadas",
|
||||||
"markXSelectedAppsAsUpdated": "¿Marcar {} Aplicaciones Seleccionadas como Actualizadas?",
|
"markXSelectedAppsAsUpdated": "¿Marcar {} aplicaciones seleccionadas como actualizadas?",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"yes": "Sí",
|
"yes": "Sí",
|
||||||
"markSelectedAppsUpdated": "Marcar Aplicaciones Seleccionadas como Actualizadas",
|
"markSelectedAppsUpdated": "Marcar aplicaciones seleccionadas como actualizadas",
|
||||||
"pinToTop": "Fijar arriba",
|
"pinToTop": "Fijar arriba",
|
||||||
"unpinFromTop": "Desfijar de arriba",
|
"unpinFromTop": "Desfijar de arriba",
|
||||||
"resetInstallStatusForSelectedAppsQuestion": "¿Restuarar Estado de Instalación para las Aplicaciones Seleccionadas?",
|
"resetInstallStatusForSelectedAppsQuestion": "¿Restuarar estado de instalación para las aplicaciones seleccionadas?",
|
||||||
"installStatusOfXWillBeResetExplanation": "El estado de instalación de las aplicaciones seleccionadas será restaurado.\n\nEsto puede ser de utilidad cuando la versión de la aplicación mostrada en Obtainium es incorrecta por actualizaciones fallidas u otros motivos.",
|
"installStatusOfXWillBeResetExplanation": "El estado de instalación de las aplicaciones seleccionadas será restaurado.\n\nEsto puede ser de útil cuando la versión de la aplicación mostrada en Obtainium es incorrecta por actualizaciones fallidas u otros motivos.",
|
||||||
"shareSelectedAppURLs": "Compartir URLs de las Aplicaciones Seleccionadas",
|
"shareSelectedAppURLs": "Compartir URLs de las aplicaciones seleccionadas",
|
||||||
"resetInstallStatus": "Restaurar Estado de Instalación",
|
"resetInstallStatus": "Restaurar Estado de Instalación",
|
||||||
"more": "Más",
|
"more": "Más",
|
||||||
"removeOutdatedFilter": "Elimiar Filtro de Aplicaciones Desactualizado",
|
"removeOutdatedFilter": "Elimiar filtro de aplicaciones desactualizado",
|
||||||
"showOutdatedOnly": "Mostrar solo Aplicaciones Desactualizadas",
|
"showOutdatedOnly": "Mostrar solo aplicaciones desactualizadas",
|
||||||
"filter": "Filtrar",
|
"filter": "Filtrar",
|
||||||
"filterActive": "Filtrar *",
|
"filterActive": "Filtrar *",
|
||||||
"filterApps": "Filtrar Actualizaciones",
|
"filterApps": "Filtrar Actualizaciones",
|
||||||
"appName": "Nombre de la Aplicación",
|
"appName": "Nombre de la aplicación",
|
||||||
"author": "Autor",
|
"author": "Autor",
|
||||||
"upToDateApps": "Aplicaciones Actualizadas",
|
"upToDateApps": "Aplicaciones Actualizadas",
|
||||||
"nonInstalledApps": "Aplicaciones No Instaladas",
|
"nonInstalledApps": "Aplicaciones No Instaladas",
|
||||||
@@ -106,47 +98,46 @@
|
|||||||
"line": "Línea",
|
"line": "Línea",
|
||||||
"searchX": "Buscar {}",
|
"searchX": "Buscar {}",
|
||||||
"noResults": "Resultados no encontrados",
|
"noResults": "Resultados no encontrados",
|
||||||
"importX": "Importar {}",
|
"importX": "Importar desde {}",
|
||||||
"importedAppsIdDisclaimer": "Las Aplicaciones Importadas pueden mostrarse incorrectamente como \"No Instalada\".\nPara arreglar esto, reinstálalas a través de Obtainium.\nEsto no debería afectar a los datos de las aplicaciones.\n\nSolo afecta a las URLs y a los métodos de importación mediante terceros.",
|
"importedAppsIdDisclaimer": "Las aplicaciones importadas podrían mostrarse incorrectamente como \"No Instalada\".\nPara solucionarlo, reinstálalas a través de Obtainium.\nEsto no debería afectar a los datos de las aplicaciones.\n\nSolo afecta a las URLs y a los métodos de importación mediante terceros.",
|
||||||
"importErrors": "Import Errors",
|
"importErrors": "Errores de Importación",
|
||||||
"importedXOfYApps": "{} de {} Aplicaciones importadas.",
|
"importedXOfYApps": "{} de {} Aplicaciones importadas.",
|
||||||
"followingURLsHadErrors": "Las siguientes URLs tuvieron problemas:",
|
"followingURLsHadErrors": "Las siguientes URLs han tenido problemas:",
|
||||||
"okay": "Correcto",
|
|
||||||
"selectURL": "Seleccionar URL",
|
"selectURL": "Seleccionar URL",
|
||||||
"selectURLs": "Seleccionar URLs",
|
"selectURLs": "Seleccionar URLs",
|
||||||
"pick": "Escoger",
|
"pick": "Escoger",
|
||||||
"theme": "Tema",
|
"theme": "Tema",
|
||||||
"dark": "Oscuro",
|
"dark": "Oscuro",
|
||||||
"light": "Claro",
|
"light": "Claro",
|
||||||
"followSystem": "Seguir al Sistema",
|
"followSystem": "Seguir al sistema",
|
||||||
"obtainium": "Obtainium",
|
"obtainium": "Obtainium",
|
||||||
"materialYou": "Material You",
|
"materialYou": "Material You",
|
||||||
"useBlackTheme": "Usar tema oscuro con negros puros",
|
"useBlackTheme": "Negro puro en tema Oscuro",
|
||||||
"appSortBy": "Ordenar Aplicaciones Por",
|
"appSortBy": "Ordenar Apps Por",
|
||||||
"authorName": "Autor/Nombre",
|
"authorName": "Autor/Nombre",
|
||||||
"nameAuthor": "Nombre/Autor",
|
"nameAuthor": "Nombre/Autor",
|
||||||
"asAdded": "Según se Añadieron",
|
"asAdded": "Según se Añadieron",
|
||||||
"appSortOrder": "Orden de Clasificación de Aplicaciones",
|
"appSortOrder": "Orden de Clasificación",
|
||||||
"ascending": "Ascendente",
|
"ascending": "Ascendente",
|
||||||
"descending": "Descendente",
|
"descending": "Descendente",
|
||||||
"bgUpdateCheckInterval": "Intervalo de Comprobación de Actualizaciones en Segundo Plano",
|
"bgUpdateCheckInterval": "Comprobación actualizaciones en segundo plano",
|
||||||
"neverManualOnly": "Nunca - Solo Manual",
|
"neverManualOnly": "Nunca, solo manual",
|
||||||
"appearance": "Apariencia",
|
"appearance": "Apariencia",
|
||||||
"showWebInAppView": "Mostrar Vista de la Web de Origen",
|
"showWebInAppView": "Mostrar vista de la web de origen",
|
||||||
"pinUpdates": "Fijar Actualizaciones en la Parte Superior de la Vista de Aplicaciones",
|
"pinUpdates": "Fijar Actualizaciones al principio",
|
||||||
"updates": "Actualizaciones",
|
"updates": "Actualizaciones",
|
||||||
"sourceSpecific": "Fuente Específica",
|
"sourceSpecific": "Fuente Específica",
|
||||||
"appSource": "Fuente de la Aplicación",
|
"appSource": "Obtainium en GitHub",
|
||||||
"noLogs": "Sin Logs",
|
"noLogs": "Sin Logs",
|
||||||
"appLogs": "Logs de la Aplicación",
|
"appLogs": "Logs",
|
||||||
"close": "Cerrar",
|
"close": "Cerrar",
|
||||||
"share": "Compartir",
|
"share": "Compartir",
|
||||||
"appNotFound": "Aplicación no encontrada",
|
"appNotFound": "Aplicación no encontrada",
|
||||||
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||||
"pickAnAPK": "Elige una APK",
|
"pickAnAPK": "Seleccione una APK",
|
||||||
"appHasMoreThanOnePackage": "{} tiene más de un paquete:",
|
"appHasMoreThanOnePackage": "{} tiene más de un paquete:",
|
||||||
"deviceSupportsXArch": "Tu dispositivo soporta las siguientes arquitecturas de procesador: {}.",
|
"deviceSupportsXArch": "Su dispositivo soporta las siguientes arquitecturas de procesador: {}.",
|
||||||
"deviceSupportsFollowingArchs": "Tu dispositivo soporta las siguientes arquitecturas de procesador:",
|
"deviceSupportsFollowingArchs": "Su dispositivo soporta las siguientes arquitecturas de procesador:",
|
||||||
"warning": "Aviso",
|
"warning": "Aviso",
|
||||||
"sourceIsXButPackageFromYPrompt": "La fuente de la aplicación es '{}' pero el paquete de la actualización viene de '{}'. ¿Desea continuar?",
|
"sourceIsXButPackageFromYPrompt": "La fuente de la aplicación es '{}' pero el paquete de la actualización viene de '{}'. ¿Desea continuar?",
|
||||||
"updatesAvailable": "Actualizaciones Disponibles",
|
"updatesAvailable": "Actualizaciones Disponibles",
|
||||||
@@ -162,11 +153,11 @@
|
|||||||
"appsRemovedNotifDescription": "Notifica al usuario que una o más aplicaciones fueron eliminadas por problemas al cargarlas",
|
"appsRemovedNotifDescription": "Notifica al usuario que una o más aplicaciones fueron eliminadas por problemas al cargarlas",
|
||||||
"xWasRemovedDueToErrorY": "{} ha sido eliminada por: {}",
|
"xWasRemovedDueToErrorY": "{} ha sido eliminada por: {}",
|
||||||
"completeAppInstallation": "Instalación Completa de la Aplicación",
|
"completeAppInstallation": "Instalación Completa de la Aplicación",
|
||||||
"obtainiumMustBeOpenToInstallApps": "Obtainium debe estar abierta para instalar aplicaciones",
|
"obtainiumMustBeOpenToInstallApps": "Obtainium debe estar abierto para instalar aplicaciones",
|
||||||
"completeAppInstallationNotifDescription": "Pide al usuario volver a Obtainium para teminar de instalar una aplicación",
|
"completeAppInstallationNotifDescription": "Pide al usuario volver a Obtainium para terminar de instalar una aplicación",
|
||||||
"checkingForUpdates": "Buscando Actualizaciones",
|
"checkingForUpdates": "Buscando Actualizaciones",
|
||||||
"checkingForUpdatesNotifDescription": "Notificación temporal que aparece al buscar actualizaciones",
|
"checkingForUpdatesNotifDescription": "Notificación temporal que aparece al buscar actualizaciones",
|
||||||
"pleaseAllowInstallPerm": "Por favor, permite a Obtainium instalar aplicaciones",
|
"pleaseAllowInstallPerm": "Por favor, permita que Obtainium instale aplicaciones",
|
||||||
"trackOnly": "Solo Seguimiento",
|
"trackOnly": "Solo Seguimiento",
|
||||||
"errorWithHttpStatusCode": "Error {}",
|
"errorWithHttpStatusCode": "Error {}",
|
||||||
"versionCorrectionDisabled": "Corrección de versiones desactivada (el plugin parece no funcionar)",
|
"versionCorrectionDisabled": "Corrección de versiones desactivada (el plugin parece no funcionar)",
|
||||||
@@ -178,24 +169,24 @@
|
|||||||
"lastUpdateCheckX": "Última Comprobación: {}",
|
"lastUpdateCheckX": "Última Comprobación: {}",
|
||||||
"remove": "Eliminar",
|
"remove": "Eliminar",
|
||||||
"yesMarkUpdated": "Sí, Marcar como Actualizada",
|
"yesMarkUpdated": "Sí, Marcar como Actualizada",
|
||||||
"fdroid": "Repositorio oficial de F-Droid",
|
"fdroid": "Repositorio oficial F-Droid",
|
||||||
"appIdOrName": "ID o Nombre de la Aplicación",
|
"appIdOrName": "ID o Nombre de la Aplicación",
|
||||||
"appId": "ID de la Aplicación",
|
"appId": "ID de la Aplicación",
|
||||||
"appWithIdOrNameNotFound": "No se han encontrado aplicaciones con esa ID o nombre",
|
"appWithIdOrNameNotFound": "No se han encontrado aplicaciones con esa ID o nombre",
|
||||||
"reposHaveMultipleApps": "Los repositorios pueden contener varias aplicaciones",
|
"reposHaveMultipleApps": "Los repositorios pueden contener varias aplicaciones",
|
||||||
"fdroidThirdPartyRepo": "Rpositorios de terceros de F-Droid",
|
"fdroidThirdPartyRepo": "Rpositorios de terceros F-Droid",
|
||||||
"steam": "Steam",
|
"steam": "Steam",
|
||||||
"steamMobile": "Steam Mobile",
|
"steamMobile": "Steam Mobile",
|
||||||
"steamChat": "Steam Chat",
|
"steamChat": "Steam Chat",
|
||||||
"install": "Instalar",
|
"install": "Instalar",
|
||||||
"markInstalled": "Marcar como Instalda",
|
"markInstalled": "Marcar como Instalada",
|
||||||
"update": "Actualizar",
|
"update": "Actualizar",
|
||||||
"markUpdated": "Marcar como Actualizada",
|
"markUpdated": "Marcar como Actualizada",
|
||||||
"additionalOptions": "Opciones Adicionales",
|
"additionalOptions": "Opciones Adicionales",
|
||||||
"disableVersionDetection": "Descativar Detección de Versiones",
|
"disableVersionDetection": "Descativar Detección de Versiones",
|
||||||
"noVersionDetectionExplanation": "Esta opción solo se debe usar en aplicaciones en las que la deteción de versiones pueda no funcionar correctamente.",
|
"noVersionDetectionExplanation": "Esta opción solo se debe usar en aplicaciones en las que la deteción de versiones pueda no funcionar correctamente.",
|
||||||
"downloadingX": "Descargando {}",
|
"downloadingX": "Descargando {}",
|
||||||
"downloadNotifDescription": "Notifica al usuario de progreso de descarga de una aplicación",
|
"downloadNotifDescription": "Notifica al usuario del progreso de descarga de una aplicación",
|
||||||
"noAPKFound": "APK no encontrada",
|
"noAPKFound": "APK no encontrada",
|
||||||
"noVersionDetection": "Sin detección de versiones",
|
"noVersionDetection": "Sin detección de versiones",
|
||||||
"categorize": "Catogorizar",
|
"categorize": "Catogorizar",
|
||||||
@@ -203,43 +194,99 @@
|
|||||||
"category": "Categoría",
|
"category": "Categoría",
|
||||||
"noCategory": "Sin Categoría",
|
"noCategory": "Sin Categoría",
|
||||||
"noCategories": "Sin Categorías",
|
"noCategories": "Sin Categorías",
|
||||||
"deleteCategoriesQuestion": "¿Borrar Categorías?",
|
"deleteCategoriesQuestion": "¿Eliminar Categorías?",
|
||||||
"categoryDeleteWarning": "Todas las aplicaciones en las categorías borradas serán margadas como 'Sin Categoría'.",
|
"categoryDeleteWarning": "Todas las aplicaciones en las categorías eliminadas serán marcadas como 'Sin Categoría'.",
|
||||||
"addCategory": "Añadir Categoría",
|
"addCategory": "Añadir Categoría",
|
||||||
"label": "Nombre",
|
"label": "Nombre",
|
||||||
"language": "Idioma",
|
"language": "Idioma",
|
||||||
"copiedToClipboard": "Copiado al Portapapeles",
|
"copiedToClipboard": "Copiado al Portapapeles",
|
||||||
"storagePermissionDenied": "Permiso de Almacenamiento rechazado",
|
"storagePermissionDenied": "Permiso de Almacenamiento rechazado",
|
||||||
"selectedCategorizeWarning": "Esto reemplazará cualquier ajuste de categoría para las aplicaicones seleccionadas.",
|
"selectedCategorizeWarning": "Esto reemplazará cualquier ajuste de categoría para las aplicaciones seleccionadas.",
|
||||||
"filterAPKsByRegEx": "Filtrar APKs mediante Expresiones Regulares",
|
"filterAPKsByRegEx": "Filtrar por APKs",
|
||||||
"removeFromObtainium": "Eliminar de Obtainium",
|
"removeFromObtainium": "Eliminar de Obtainium",
|
||||||
"uninstallFromDevice": "Desinstalar del Dispositivo",
|
"uninstallFromDevice": "Desinstalar del Dispositivo",
|
||||||
"onlyWorksWithNonVersionDetectApps": "Solo funciona para aplicaciones con la detección de versiones desactivada.",
|
"onlyWorksWithNonVersionDetectApps": "Solo funciona para aplicaciones con la detección de versiones desactivada.",
|
||||||
"releaseDateAsVersion": "Usar Fecha de Publicación como Versión",
|
"releaseDateAsVersion": "Por fecha de publicación",
|
||||||
"releaseDateAsVersionExplanation": "Esta opción solo se debería usar con aplicaciones en las que la detección de versiones no funciona pero hay disponible una fecha de publicación.",
|
"releaseDateAsVersionExplanation": "Esta opción solo se debería usar con aplicaciones en las que la detección de versiones no funciona pero hay disponible una fecha de publicación.",
|
||||||
"changes": "Cambios",
|
"changes": "Cambios",
|
||||||
"releaseDate": "Fecha de Publicación",
|
"releaseDate": "Fecha de Publicación",
|
||||||
"importFromURLsInFile": "Importar de URls en un Archivo (como OPML)",
|
"importFromURLsInFile": "Importar URLs desde archivo (como OPML)",
|
||||||
"versionDetection": "Detección de Versiones",
|
"versionDetection": "Detección de Versiones",
|
||||||
"standardVersionDetection": "Detección de versiones estándar",
|
"standardVersionDetection": "Por versión",
|
||||||
"groupByCategory": "Agrupar por Categoría",
|
"groupByCategory": "Agrupar por categoría",
|
||||||
"autoApkFilterByArch": "Tratar de filtrar las APKs mediante arquitecturas de procesador si es posible",
|
"autoApkFilterByArch": "Filtrar APKs por arquitectura del procesador (si es posible)",
|
||||||
"overrideSource": "Sobrescribir Fuente",
|
"overrideSource": "Sobrescribir Fuente",
|
||||||
"dontShowAgain": "No mostrar de nuevo",
|
"dontShowAgain": "No mostrar de nuevo",
|
||||||
"dontShowTrackOnlyWarnings": "No mostrar avisos de 'Solo Seguimiento'",
|
"dontShowTrackOnlyWarnings": "No mostrar avisos sobre apps en 'solo seguimiento'",
|
||||||
"dontShowAPKOriginWarnings": "No mostrar avisos de las fuentes de las APks",
|
"dontShowAPKOriginWarnings": "No mostrar avisos sobre las fuentes de las APKs",
|
||||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
"moveNonInstalledAppsToBottom": "Mover Apps no instaladas al final",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
"gitlabPATLabel": "Token GitLab de acceso personal\n(habilita la búsqueda y mejor detección de APKs)",
|
||||||
"about": "About",
|
"about": "Acerca",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "{}: Esto requiere credenciales adicionales (en ajustes)",
|
||||||
"checkOnStart": "Check for updates on startup",
|
"checkOnStart": "Comprobar actualizaciones al inicio",
|
||||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
"tryInferAppIdFromCode": "Intentar deducir la ID de la app por el código fuente",
|
||||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
"removeOnExternalUninstall": "Auto eliminar apps desinstaladas externamente",
|
||||||
"pickHighestVersionCode": "Auto-select highest version code APK",
|
"pickHighestVersionCode": "Auto selección versión superior del código APK",
|
||||||
"checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
|
"checkUpdateOnDetailPage": "Comprobar actualizaciones al abrir detalles de la app",
|
||||||
"disablePageTransitions": "Disable page transition animations",
|
"disablePageTransitions": "Deshabilitar animaciones de transición",
|
||||||
"reversePageTransitions": "Reverse page transition animations",
|
"reversePageTransitions": "Invertir animaciones de transición",
|
||||||
"minStarCount": "Minimum Star Count",
|
"minStarCount": "Número Mínimo de Estrellas",
|
||||||
|
"addInfoBelow": "Añadir esta información debajo.",
|
||||||
|
"addInfoInSettings": "Puede añadir esta información en Ajustes.",
|
||||||
|
"githubSourceNote": "La limitación de velocidad de GitHub puede evitarse con una clave API.",
|
||||||
|
"gitlabSourceNote": "La extracción de APK de GitLab podría no funcionar sin una clave API.",
|
||||||
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
|
"filterReleaseNotesByRegEx": "Filtrar por notas de versión (release notes)",
|
||||||
|
"customLinkFilterRegex": "Filtro personalizado de Enlace APK (por defecto '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "Actualización de Apps intentada",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Notifica al usuario que las actualizaciones en segundo plano podrían haberse realizado para una o más aplicaciones",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} podría estar actualizada a {}.",
|
||||||
|
"enableBackgroundUpdates": "Habilitar actualizaciones en segundo plano",
|
||||||
|
"backgroundUpdateReqsExplanation": "Las actualizaciones en segundo plano pueden no estar disponibles para todas las aplicaciones.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "El éxito de las instalaciones en segundo plano solo se puede comprobar con Obtainium abierto.",
|
||||||
|
"verifyLatestTag": "Comprobar la etiqueta 'Latest'",
|
||||||
|
"intermediateLinkRegex": "Filtrar por enlace 'intermedio' para visitar primero",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "Enlace intermedio no encontrado",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Exenta de actualizciones en segundo plano (si están habilitadas)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "Deshabilitar las actualizaciones en segundo plano sin WiFi",
|
||||||
|
"autoSelectHighestVersionCode": "Auto Selección de la versionCode APK superior",
|
||||||
|
"versionExtractionRegEx": "Versión de extracción regex",
|
||||||
|
"matchGroupToUse": "Grupo a usar para versión de extracción regex",
|
||||||
|
"highlightTouchTargets": "Resaltar objetivos menos obvios",
|
||||||
|
"pickExportDir": "Directorio para Exportar",
|
||||||
|
"autoExportOnChanges": "Auto Exportar cuando haya cambios",
|
||||||
|
"includeSettings": "Incluir ajustes",
|
||||||
|
"filterVersionsByRegEx": "Filtrar por Versiones",
|
||||||
|
"trySelectingSuggestedVersionCode": "Pruebe seleccionando la versionCode APK sugerida",
|
||||||
|
"dontSortReleasesList": "Mantener el order de publicación desde API",
|
||||||
|
"reverseSort": "Orden inverso",
|
||||||
|
"takeFirstLink": "Usar primer enlace",
|
||||||
|
"skipSort": "Omitir orden",
|
||||||
|
"debugMenu": "Menu Depurar",
|
||||||
|
"bgTaskStarted": "Iniciada tarea en segundo plano - revisa los logs.",
|
||||||
|
"runBgCheckNow": "Ejecutar verficiación de actualizaciones en segundo plano",
|
||||||
|
"versionExtractWholePage": "Aplicar la versión de extracción regex a la página entera",
|
||||||
|
"installing": "Instalando",
|
||||||
|
"skipUpdateNotifications": "No notificar sobre actualizaciones",
|
||||||
|
"updatesAvailableNotifChannel": "Actualizaciones disponibles",
|
||||||
|
"appsUpdatedNotifChannel": "Aplicaciones actualizadas",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "Se ha intentado actualizar la aplicación",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Error buscando actualizaciones",
|
||||||
|
"appsRemovedNotifChannel": "Aplicaciones eliminadas",
|
||||||
|
"downloadingXNotifChannel": "Descargando {}",
|
||||||
|
"completeAppInstallationNotifChannel": "Instalación completada",
|
||||||
|
"checkingForUpdatesNotifChannel": "Buscando actualizaciones",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Comprobar actualizaciones solo para apps instaladas o en seguimiento",
|
||||||
|
"supportFixedAPKURL": "Soporte para URLs fijas de APK",
|
||||||
|
"selectX": "Selecciona {}",
|
||||||
|
"parallelDownloads": "Permitir descargas paralelas",
|
||||||
|
"installMethod": "Método de instalación",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku no está operativo",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "¿Eliminar Aplicación?",
|
"one": "¿Eliminar Aplicación?",
|
||||||
"other": "¿Eliminar Aplicaciones?"
|
"other": "¿Eliminar Aplicaciones?"
|
||||||
@@ -265,27 +312,31 @@
|
|||||||
"other": "{} URLs"
|
"other": "{} URLs"
|
||||||
},
|
},
|
||||||
"minute": {
|
"minute": {
|
||||||
"one": "{} Minuto",
|
"one": "{} minuto",
|
||||||
"other": "{} Minutos"
|
"other": "{} minutos"
|
||||||
},
|
},
|
||||||
"hour": {
|
"hour": {
|
||||||
"one": "{} Hora",
|
"one": "{} hora",
|
||||||
"other": "{} Horas"
|
"other": "{} horas"
|
||||||
},
|
},
|
||||||
"day": {
|
"day": {
|
||||||
"one": "{} Día",
|
"one": "{} día",
|
||||||
"other": "{} Días"
|
"other": "{} días"
|
||||||
},
|
},
|
||||||
"clearedNLogsBeforeXAfterY": {
|
"clearedNLogsBeforeXAfterY": {
|
||||||
"one": "Borrado {n} log (previo a = {before}, posterior a = {after})",
|
"one": "Eliminado {n} log (previo a = {before}, posterior a = {after})",
|
||||||
"other": "Borrados {n} logs (previos a = {before}, posteriores a = {after})"
|
"other": "Eliminados {n} logs (previos a = {before}, posteriores a = {after})"
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesAvailable": {
|
"xAndNMoreUpdatesAvailable": {
|
||||||
"one": "{} y 1 aplicación más tiene actualizaciones.",
|
"one": "{} y 1 aplicación más tiene actualizaciones.",
|
||||||
"other": "{} y {} aplicaciones más tiene actualizaciones."
|
"other": "{} y {} aplicaciones más tienen actualizaciones."
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesInstalled": {
|
"xAndNMoreUpdatesInstalled": {
|
||||||
"one": "{} y 1 aplicación más han sido actualizadas.",
|
"one": "{} y 1 aplicación más han sido actualizadas.",
|
||||||
"other": "{} y {} aplicaciones más han sido actualizadas."
|
"other": "{} y {} aplicaciones más han sido actualizadas."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} y 1 aplicación más podría haber sido actualizada.",
|
||||||
|
"other": "{} y {} aplicaciones más podrían haber sido actualizadas."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,7 @@
|
|||||||
"unexpectedError": "خطای غیرمنتظره",
|
"unexpectedError": "خطای غیرمنتظره",
|
||||||
"ok": "باشه",
|
"ok": "باشه",
|
||||||
"and": "و",
|
"and": "و",
|
||||||
"startedBgUpdateTask": "شروع بررسی بروزرسانی BG",
|
|
||||||
"bgUpdateIgnoreAfterIs": "نادیده گرفتن بروزرسانی BG بعد از {} است",
|
|
||||||
"startedActualBGUpdateCheck": "بررسی بهروزرسانی واقعی BG آغاز شد",
|
|
||||||
"bgUpdateTaskFinished": "کار بررسی بهروزرسانی BG تمام شد",
|
|
||||||
"firstRun": "این اولین اجرای Obtainium است",
|
|
||||||
"settingUpdateCheckIntervalTo": "تنظیم فاصله بهروزرسانی روی {}",
|
|
||||||
"githubPATLabel": "توکن دسترسی شخصی گیت هاب(محدودیت نرخ را افزایش میدهد)",
|
"githubPATLabel": "توکن دسترسی شخصی گیت هاب(محدودیت نرخ را افزایش میدهد)",
|
||||||
"githubPATHint": "PAT باید در این قالب باشد: username:token",
|
|
||||||
"githubPATFormat": "username:token",
|
|
||||||
"includePrereleases": "شامل نسخه های اولیه",
|
"includePrereleases": "شامل نسخه های اولیه",
|
||||||
"fallbackToOlderReleases": "بازگشت به نسخه های قدیمی تر",
|
"fallbackToOlderReleases": "بازگشت به نسخه های قدیمی تر",
|
||||||
"filterReleaseTitlesByRegEx": "عناوین انتشار را با بیان منظم فیلتر کنید",
|
"filterReleaseTitlesByRegEx": "عناوین انتشار را با بیان منظم فیلتر کنید",
|
||||||
@@ -49,7 +41,7 @@
|
|||||||
"searchSomeSourcesLabel": "جستجو (فقط برخی منابع)",
|
"searchSomeSourcesLabel": "جستجو (فقط برخی منابع)",
|
||||||
"search": "جستجو کردن",
|
"search": "جستجو کردن",
|
||||||
"additionalOptsFor": "گزینه های اضافی برای {}",
|
"additionalOptsFor": "گزینه های اضافی برای {}",
|
||||||
"supportedSourcesBelow": "منابع پشتیبانی شده:",
|
"supportedSources": "منابع پشتیبانی شده",
|
||||||
"trackOnlyInBrackets": "«فقط ردیابی»",
|
"trackOnlyInBrackets": "«فقط ردیابی»",
|
||||||
"searchableInBrackets": "(قابل جستجو)",
|
"searchableInBrackets": "(قابل جستجو)",
|
||||||
"appsString": "برنامه ها",
|
"appsString": "برنامه ها",
|
||||||
@@ -63,7 +55,7 @@
|
|||||||
"notInstalled": "نصب نشده",
|
"notInstalled": "نصب نشده",
|
||||||
"estimateInBrackets": "(تخمین زدن)",
|
"estimateInBrackets": "(تخمین زدن)",
|
||||||
"selectAll": "انتخاب همه",
|
"selectAll": "انتخاب همه",
|
||||||
"deselectN": "لغو انتخاب {}",
|
"deselectX": "لغو انتخاب {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} از Obtainium حذف میشود اما روی دستگاه نصب میماند.",
|
"xWillBeRemovedButRemainInstalled": "{} از Obtainium حذف میشود اما روی دستگاه نصب میماند.",
|
||||||
"removeSelectedAppsQuestion": "برنامه های انتخابی حذف شود؟",
|
"removeSelectedAppsQuestion": "برنامه های انتخابی حذف شود؟",
|
||||||
"removeSelectedApps": "حذف برنامه های انتخاب شده",
|
"removeSelectedApps": "حذف برنامه های انتخاب شده",
|
||||||
@@ -111,7 +103,6 @@
|
|||||||
"importErrors": "خطاهای وارد کردن",
|
"importErrors": "خطاهای وارد کردن",
|
||||||
"importedXOfYApps": "{} از {} برنامه وارد شد.",
|
"importedXOfYApps": "{} از {} برنامه وارد شد.",
|
||||||
"followingURLsHadErrors": "آدرس های اینترنتی زیر دارای خطا بودند:",
|
"followingURLsHadErrors": "آدرس های اینترنتی زیر دارای خطا بودند:",
|
||||||
"okay": "باشه",
|
|
||||||
"selectURL": "آدرس اینترنتی انتخاب شده",
|
"selectURL": "آدرس اینترنتی انتخاب شده",
|
||||||
"selectURLs": "آدرس های اینترنتی انتخاب شده",
|
"selectURLs": "آدرس های اینترنتی انتخاب شده",
|
||||||
"pick": "انتخاب",
|
"pick": "انتخاب",
|
||||||
@@ -229,17 +220,73 @@
|
|||||||
"dontShowTrackOnlyWarnings": "هشدار 'فقط ردیابی' را نشان ندهید",
|
"dontShowTrackOnlyWarnings": "هشدار 'فقط ردیابی' را نشان ندهید",
|
||||||
"dontShowAPKOriginWarnings": "هشدارهای منبع APK را نشان ندهید",
|
"dontShowAPKOriginWarnings": "هشدارهای منبع APK را نشان ندهید",
|
||||||
"moveNonInstalledAppsToBottom": "برنامه های نصب نشده را به نمای پایین برنامه ها منتقل کنید",
|
"moveNonInstalledAppsToBottom": "برنامه های نصب نشده را به نمای پایین برنامه ها منتقل کنید",
|
||||||
"gitlabPATLabel": "رمز دسترسی شخصی GitLab\n(جستجو را فعال می کند and Better APK Discovery)",
|
"gitlabPATLabel": "رمز دسترسی شخصی GitLab\n(جستجو و کشف بهتر APK را فعال میکند)",
|
||||||
"about": "درباره",
|
"about": "درباره",
|
||||||
"requiresCredentialsInSettings": "این به اعتبارنامه های اضافی نیاز دارد (در تنظیمات)",
|
"requiresCredentialsInSettings": "{}: این به اعتبارنامه های اضافی نیاز دارد (در تنظیمات)",
|
||||||
"checkOnStart": "بررسی در شروع",
|
"checkOnStart": "بررسی در شروع",
|
||||||
"tryInferAppIdFromCode": "شناسه برنامه را از کد منبع استنباط کنید",
|
"tryInferAppIdFromCode": "شناسه برنامه را از کد منبع استنباط کنید",
|
||||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
"removeOnExternalUninstall": "حذف خودکار برنامه های حذف نصب شده خارجی",
|
||||||
"pickHighestVersionCode": "Auto-select highest version code APK",
|
"pickHighestVersionCode": "انتخاب خودکار بالاترین کد نسخه APK",
|
||||||
"checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
|
"checkUpdateOnDetailPage": "برای باز کردن صفحه جزئیات برنامه، بهروزرسانیها را بررسی کنید",
|
||||||
"disablePageTransitions": "Disable page transition animations",
|
"disablePageTransitions": "غیرفعال کردن انیمیشن های انتقال صفحه",
|
||||||
"reversePageTransitions": "Reverse page transition animations",
|
"reversePageTransitions": "انیمیشن های انتقال معکوس صفحه",
|
||||||
"minStarCount": "Minimum Star Count",
|
"minStarCount": "حداقل تعداد ستاره",
|
||||||
|
"addInfoBelow": "این اطلاعات را در زیر اضافه کنید",
|
||||||
|
"addInfoInSettings": "این اطلاعات را در تنظیمات اضافه کنید.",
|
||||||
|
"githubSourceNote": "با استفاده از کلید API می توان از محدودیت نرخ GitHub جلوگیری کرد.",
|
||||||
|
"gitlabSourceNote": "استخراج APK GitLab ممکن است بدون کلید API کار نکند.",
|
||||||
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
|
"filterReleaseNotesByRegEx": "یادداشت های انتشار را با بیان منظم فیلتر کنید",
|
||||||
|
"customLinkFilterRegex": "فیلتر پیوند سفارشی بر اساس عبارت منظم (پیشفرض '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "بهروزرسانی برنامه انجام شد",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "به کاربر اطلاع میدهد که بهروزرسانیهای یک یا چند برنامه به طور بالقوه در پسزمینه اعمال شده است",
|
||||||
|
"xWasPossiblyUpdatedToY": "ممکن است {} به {} به روز شده باشد.",
|
||||||
|
"enableBackgroundUpdates": "به روز رسانی پس زمینه را فعال کنید",
|
||||||
|
"backgroundUpdateReqsExplanation": "به روز رسانی پس زمینه ممکن است برای همه برنامه ها امکان پذیر نباشد.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "موفقیت نصب پسزمینه تنها زمانی مشخص میشود که Obtainium باز شود.",
|
||||||
|
"verifyLatestTag": "برچسب \"آخرین\" را تأیید کنید",
|
||||||
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "لینک میانی پیدا نشد",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "معاف از بهروزرسانیهای پسزمینه (در صورت فعال بودن)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "بهروزرسانیهای پسزمینه را در صورت عدم اتصال به WiFi غیرفعال کنید",
|
||||||
|
"autoSelectHighestVersionCode": "انتخاب خودکار بالاترین نسخه کد APK",
|
||||||
|
"versionExtractionRegEx": "نسخه استخراج RegEx",
|
||||||
|
"matchGroupToUse": "گروه مورد استفاده را مطابقت دهید",
|
||||||
|
"highlightTouchTargets": "اهداف لمسی کمتر واضح را برجسته کنید",
|
||||||
|
"pickExportDir": "فهرست صادرات را انتخاب کنید",
|
||||||
|
"autoExportOnChanges": "صادرات خودکار تغییرات",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
|
"filterVersionsByRegEx": "فیلتر کردن نسخه ها با RegEx",
|
||||||
|
"trySelectingSuggestedVersionCode": "نسخه پیشنهادی APK نسخه کد را انتخاب کنید",
|
||||||
|
"dontSortReleasesList": "حفظ سفارش انتشار از API",
|
||||||
|
"reverseSort": "مرتب سازی معکوس",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
|
"debugMenu": "منوی اشکال زدایی",
|
||||||
|
"bgTaskStarted": "کار پس زمینه شروع شد - لاگ های مربوط را بررسی کنید.",
|
||||||
|
"runBgCheckNow": "اکنون بهروزرسانی پسزمینه را بررسی کنید",
|
||||||
|
"versionExtractWholePage": "نسخه Extraction Regex را در کل صفحه اعمال کنید",
|
||||||
|
"installing": "در حال نصب",
|
||||||
|
"skipUpdateNotifications": "رد شدن از اعلان های به روز رسانی",
|
||||||
|
"updatesAvailableNotifChannel": "بروزرسانی در دسترس ",
|
||||||
|
"appsUpdatedNotifChannel": "برنامه ها به روز شدند",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "بهروزرسانی برنامه انجام شد",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "خطا در بررسی بهروزرسانیها",
|
||||||
|
"appsRemovedNotifChannel": "برنامه ها حذف شدند",
|
||||||
|
"downloadingXNotifChannel": "در حال دانلود {}",
|
||||||
|
"completeAppInstallationNotifChannel": "نصب کامل برنامه",
|
||||||
|
"checkingForUpdatesNotifChannel": "بررسی بهروزرسانیها",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "فقط برنامه های نصب شده و فقط ردیابی را برای به روز رسانی بررسی کنید",
|
||||||
|
"supportFixedAPKURL": "پشتیبانی از URL های APK ثابت",
|
||||||
|
"selectX": "انتخاب کنید {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
|
"installMethod": "Installation method",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "برنامه حذف شود؟",
|
"one": "برنامه حذف شود؟",
|
||||||
"other": "برنامه ها حذف شوند؟"
|
"other": "برنامه ها حذف شوند؟"
|
||||||
@@ -287,5 +334,9 @@
|
|||||||
"xAndNMoreUpdatesInstalled": {
|
"xAndNMoreUpdatesInstalled": {
|
||||||
"one": "{} و 1 برنامه دیگر به روز شدند.",
|
"one": "{} و 1 برنامه دیگر به روز شدند.",
|
||||||
"other": "{} و {} برنامه دیگر به روز شدند."
|
"other": "{} و {} برنامه دیگر به روز شدند."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} و 1 برنامه دیگر ممکن است به روز شده باشند.",
|
||||||
|
"other": "ممکن است {} و {} برنامه های دیگر به روز شده باشند."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,7 @@
|
|||||||
"unexpectedError": "Erreur inattendue",
|
"unexpectedError": "Erreur inattendue",
|
||||||
"ok": "Okay",
|
"ok": "Okay",
|
||||||
"and": "et",
|
"and": "et",
|
||||||
"startedBgUpdateTask": "Démarrage de la tâche de vérification de mise à jour en arrière-plan",
|
|
||||||
"bgUpdateIgnoreAfterIs": "Mise à jour en arrière-plan est ignoré après {}",
|
|
||||||
"startedActualBGUpdateCheck": "Démarrage de la vérification de la mise à jour en arrière-plan",
|
|
||||||
"bgUpdateTaskFinished": "Tâche de vérification de la mise à jour en arrière-plan terminée",
|
|
||||||
"firstRun": "Il s'agit de la toute première exécution d'Obtainium",
|
|
||||||
"settingUpdateCheckIntervalTo": "Définition de l'intervalle de mise à jour sur {}",
|
|
||||||
"githubPATLabel": "Jeton d'Accès Personnel GitHub (Augmente la limite de débit)",
|
"githubPATLabel": "Jeton d'Accès Personnel GitHub (Augmente la limite de débit)",
|
||||||
"githubPATHint": "Le JAP doit être dans ce format : username:token",
|
|
||||||
"githubPATFormat": "username:token",
|
|
||||||
"includePrereleases": "Inclure les avant-premières",
|
"includePrereleases": "Inclure les avant-premières",
|
||||||
"fallbackToOlderReleases": "Retour aux anciennes versions",
|
"fallbackToOlderReleases": "Retour aux anciennes versions",
|
||||||
"filterReleaseTitlesByRegEx": "Filtrer les titres de version par expression régulière",
|
"filterReleaseTitlesByRegEx": "Filtrer les titres de version par expression régulière",
|
||||||
@@ -45,11 +37,11 @@
|
|||||||
"addApp": "Ajouter une application",
|
"addApp": "Ajouter une application",
|
||||||
"appSourceURL": "URL de la source de l'application",
|
"appSourceURL": "URL de la source de l'application",
|
||||||
"error": "Erreur",
|
"error": "Erreur",
|
||||||
"add": "Ajoutée",
|
"add": "Ajouter",
|
||||||
"searchSomeSourcesLabel": "Rechercher (certaines sources uniquement)",
|
"searchSomeSourcesLabel": "Rechercher (certaines sources uniquement)",
|
||||||
"search": "Rechercher",
|
"search": "Rechercher",
|
||||||
"additionalOptsFor": "Options supplémentaires pour {}",
|
"additionalOptsFor": "Options supplémentaires pour {}",
|
||||||
"supportedSourcesBelow": "Sources prises en charge :",
|
"supportedSources": "Sources prises en charge ",
|
||||||
"trackOnlyInBrackets": "(Suivi uniquement)",
|
"trackOnlyInBrackets": "(Suivi uniquement)",
|
||||||
"searchableInBrackets": "(Recherchable)",
|
"searchableInBrackets": "(Recherchable)",
|
||||||
"appsString": "Applications",
|
"appsString": "Applications",
|
||||||
@@ -63,7 +55,7 @@
|
|||||||
"notInstalled": "Pas installé",
|
"notInstalled": "Pas installé",
|
||||||
"estimateInBrackets": "(Estimation)",
|
"estimateInBrackets": "(Estimation)",
|
||||||
"selectAll": "Tout sélectionner",
|
"selectAll": "Tout sélectionner",
|
||||||
"deselectN": "Déselectionner {}",
|
"deselectX": "Déselectionner {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} sera supprimé d'Obtainium mais restera installé sur l'appareil.",
|
"xWillBeRemovedButRemainInstalled": "{} sera supprimé d'Obtainium mais restera installé sur l'appareil.",
|
||||||
"removeSelectedAppsQuestion": "Supprimer les applications sélectionnées ?",
|
"removeSelectedAppsQuestion": "Supprimer les applications sélectionnées ?",
|
||||||
"removeSelectedApps": "Supprimer les applications sélectionnées",
|
"removeSelectedApps": "Supprimer les applications sélectionnées",
|
||||||
@@ -111,7 +103,6 @@
|
|||||||
"importErrors": "Erreurs d'importation",
|
"importErrors": "Erreurs d'importation",
|
||||||
"importedXOfYApps": "{} sur {} applications importées.",
|
"importedXOfYApps": "{} sur {} applications importées.",
|
||||||
"followingURLsHadErrors": "Les URL suivantes comportaient des erreurs :",
|
"followingURLsHadErrors": "Les URL suivantes comportaient des erreurs :",
|
||||||
"okay": "Okay",
|
|
||||||
"selectURL": "Sélectionnez l'URL",
|
"selectURL": "Sélectionnez l'URL",
|
||||||
"selectURLs": "Sélectionnez les URLs",
|
"selectURLs": "Sélectionnez les URLs",
|
||||||
"pick": "Prendre",
|
"pick": "Prendre",
|
||||||
@@ -231,7 +222,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
|
||||||
"checkOnStart": "Check for updates on startup",
|
"checkOnStart": "Check for updates on startup",
|
||||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
||||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
||||||
@@ -240,6 +231,62 @@
|
|||||||
"disablePageTransitions": "Disable page transition animations",
|
"disablePageTransitions": "Disable page transition animations",
|
||||||
"reversePageTransitions": "Reverse page transition animations",
|
"reversePageTransitions": "Reverse page transition animations",
|
||||||
"minStarCount": "Minimum Star Count",
|
"minStarCount": "Minimum Star Count",
|
||||||
|
"addInfoBelow": "Add this info below.",
|
||||||
|
"addInfoInSettings": "Add this info in the Settings.",
|
||||||
|
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
||||||
|
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
||||||
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
|
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
||||||
|
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "App Updates Attempted",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
|
||||||
|
"enableBackgroundUpdates": "Enable background updates",
|
||||||
|
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
||||||
|
"verifyLatestTag": "Verify the 'latest' tag",
|
||||||
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "Intermediate link not found",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
|
||||||
|
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
||||||
|
"versionExtractionRegEx": "Version Extraction RegEx",
|
||||||
|
"matchGroupToUse": "Match Group to Use",
|
||||||
|
"highlightTouchTargets": "Highlight less obvious touch targets",
|
||||||
|
"pickExportDir": "Pick Export Directory",
|
||||||
|
"autoExportOnChanges": "Auto-export on changes",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
|
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
|
||||||
|
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
|
||||||
|
"dontSortReleasesList": "Retain release order from API",
|
||||||
|
"reverseSort": "Reverse sorting",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
|
"debugMenu": "Debug Menu",
|
||||||
|
"bgTaskStarted": "Background task started - check logs.",
|
||||||
|
"runBgCheckNow": "Run Background Update Check Now",
|
||||||
|
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page",
|
||||||
|
"installing": "Installing",
|
||||||
|
"skipUpdateNotifications": "Skip update notifications",
|
||||||
|
"updatesAvailableNotifChannel": "Mises à jour disponibles",
|
||||||
|
"appsUpdatedNotifChannel": "Applications mises à jour",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "App Updates Attempted",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Erreur lors de la vérification des mises à jour",
|
||||||
|
"appsRemovedNotifChannel": "Applications supprimées",
|
||||||
|
"downloadingXNotifChannel": "Téléchargement {}",
|
||||||
|
"completeAppInstallationNotifChannel": "Installation complète de l'application",
|
||||||
|
"checkingForUpdatesNotifChannel": "Vérification des mises à jour",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
||||||
|
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||||
|
"selectX": "Select {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
|
"installMethod": "Installation method",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Supprimer l'application ?",
|
"one": "Supprimer l'application ?",
|
||||||
"other": "Supprimer les applications ?"
|
"other": "Supprimer les applications ?"
|
||||||
@@ -287,5 +334,9 @@
|
|||||||
"xAndNMoreUpdatesInstalled": {
|
"xAndNMoreUpdatesInstalled": {
|
||||||
"one": "{} et 1 autre application ont été mises à jour.",
|
"one": "{} et 1 autre application ont été mises à jour.",
|
||||||
"other": "{} et {} autres applications ont été mises à jour."
|
"other": "{} et {} autres applications ont été mises à jour."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} and 1 more app may have been updated.",
|
||||||
|
"other": "{} and {} more apps may have been updated."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,7 @@
|
|||||||
"unexpectedError": "Váratlan hiba",
|
"unexpectedError": "Váratlan hiba",
|
||||||
"ok": "Oké",
|
"ok": "Oké",
|
||||||
"and": "és",
|
"and": "és",
|
||||||
"startedBgUpdateTask": "Háttérfrissítés ellenőrzési feladat elindítva",
|
"githubPATLabel": "GitHub Personal Access Token (megnöveli a díjkorlátot)",
|
||||||
"bgUpdateIgnoreAfterIs": "Háttérfrissítés ignoreAfter a következő: {}",
|
|
||||||
"startedActualBGUpdateCheck": "Elkezdődött a tényleges háttérfrissítés ellenőrzése",
|
|
||||||
"bgUpdateTaskFinished": "A háttérfrissítés ellenőrzési feladat befejeződött",
|
|
||||||
"firstRun": "Ez az Obtainium első futása",
|
|
||||||
"settingUpdateCheckIntervalTo": "A frissítési intervallum beállítása erre: {}",
|
|
||||||
"githubPATLabel": "GitHub személyes hozzáférési token (megnöveli a díjkorlátot)",
|
|
||||||
"githubPATHint": "A PAT-nak a következő formátumban kell lennie: felhasználónév:token",
|
|
||||||
"githubPATFormat": "felhasználónév:token",
|
|
||||||
"includePrereleases": "Tartalmazza az előzetes kiadásokat",
|
"includePrereleases": "Tartalmazza az előzetes kiadásokat",
|
||||||
"fallbackToOlderReleases": "Visszatérés a régebbi kiadásokhoz",
|
"fallbackToOlderReleases": "Visszatérés a régebbi kiadásokhoz",
|
||||||
"filterReleaseTitlesByRegEx": "A kiadás címeinek szűrése reguláris kifejezéssel",
|
"filterReleaseTitlesByRegEx": "A kiadás címeinek szűrése reguláris kifejezéssel",
|
||||||
@@ -49,7 +41,7 @@
|
|||||||
"searchSomeSourcesLabel": "Keresés (csak egyes források)",
|
"searchSomeSourcesLabel": "Keresés (csak egyes források)",
|
||||||
"search": "Keresés",
|
"search": "Keresés",
|
||||||
"additionalOptsFor": "További lehetőségek a következőhöz: {}",
|
"additionalOptsFor": "További lehetőségek a következőhöz: {}",
|
||||||
"supportedSourcesBelow": "Támogatott források:",
|
"supportedSources": "Támogatott források",
|
||||||
"trackOnlyInBrackets": "(Csak nyomonkövetés)",
|
"trackOnlyInBrackets": "(Csak nyomonkövetés)",
|
||||||
"searchableInBrackets": "(Kereshető)",
|
"searchableInBrackets": "(Kereshető)",
|
||||||
"appsString": "Appok",
|
"appsString": "Appok",
|
||||||
@@ -63,7 +55,7 @@
|
|||||||
"notInstalled": "Nem telepített",
|
"notInstalled": "Nem telepített",
|
||||||
"estimateInBrackets": "(Becslés)",
|
"estimateInBrackets": "(Becslés)",
|
||||||
"selectAll": "Mindet kiválaszt",
|
"selectAll": "Mindet kiválaszt",
|
||||||
"deselectN": "Törölje {} kijelölését",
|
"deselectX": "Törölje {} kijelölését",
|
||||||
"xWillBeRemovedButRemainInstalled": "A(z) {} el lesz távolítva az Obtainiumból, de továbbra is telepítve marad az eszközön.",
|
"xWillBeRemovedButRemainInstalled": "A(z) {} el lesz távolítva az Obtainiumból, de továbbra is telepítve marad az eszközön.",
|
||||||
"removeSelectedAppsQuestion": "Eltávolítja a kiválasztott appokat?",
|
"removeSelectedAppsQuestion": "Eltávolítja a kiválasztott appokat?",
|
||||||
"removeSelectedApps": "Távolítsa el a kiválasztott appokat",
|
"removeSelectedApps": "Távolítsa el a kiválasztott appokat",
|
||||||
@@ -96,10 +88,10 @@
|
|||||||
"importExport": "Import/Export",
|
"importExport": "Import/Export",
|
||||||
"settings": "Beállítások",
|
"settings": "Beállítások",
|
||||||
"exportedTo": "Exportálva ide {}",
|
"exportedTo": "Exportálva ide {}",
|
||||||
"obtainiumExport": "Obtainium Export",
|
"obtainiumExport": "Obtainium Adat Exportálás",
|
||||||
"invalidInput": "Hibás bemenet",
|
"invalidInput": "Hibás bemenet",
|
||||||
"importedX": "Importálva innen {}",
|
"importedX": "Importálva innen {}",
|
||||||
"obtainiumImport": "Obtainium Import",
|
"obtainiumImport": "Obtainium Adat Importálás",
|
||||||
"importFromURLList": "Importálás URL listából",
|
"importFromURLList": "Importálás URL listából",
|
||||||
"searchQuery": "Keresési lekérdezés",
|
"searchQuery": "Keresési lekérdezés",
|
||||||
"appURLList": "App URL lista",
|
"appURLList": "App URL lista",
|
||||||
@@ -111,7 +103,6 @@
|
|||||||
"importErrors": "Importálási hibák",
|
"importErrors": "Importálási hibák",
|
||||||
"importedXOfYApps": "{}/{} app importálva.",
|
"importedXOfYApps": "{}/{} app importálva.",
|
||||||
"followingURLsHadErrors": "A következő URL-ek hibákat tartalmaztak:",
|
"followingURLsHadErrors": "A következő URL-ek hibákat tartalmaztak:",
|
||||||
"okay": "Oké",
|
|
||||||
"selectURL": "Válassza ki az URL-t",
|
"selectURL": "Válassza ki az URL-t",
|
||||||
"selectURLs": "Kiválasztott URL-ek",
|
"selectURLs": "Kiválasztott URL-ek",
|
||||||
"pick": "Válasszon",
|
"pick": "Válasszon",
|
||||||
@@ -121,7 +112,7 @@
|
|||||||
"followSystem": "Rendszer szerint",
|
"followSystem": "Rendszer szerint",
|
||||||
"obtainium": "Obtainium",
|
"obtainium": "Obtainium",
|
||||||
"materialYou": "Material You",
|
"materialYou": "Material You",
|
||||||
"useBlackTheme": "Használjon tiszta fekete sötét témát",
|
"useBlackTheme": "Használjon teljesen fekete sötét témát",
|
||||||
"appSortBy": "App rendezés...",
|
"appSortBy": "App rendezés...",
|
||||||
"authorName": "Szerző/Név",
|
"authorName": "Szerző/Név",
|
||||||
"nameAuthor": "Név/Szerző",
|
"nameAuthor": "Név/Szerző",
|
||||||
@@ -139,8 +130,8 @@
|
|||||||
"appSource": "App forrás",
|
"appSource": "App forrás",
|
||||||
"noLogs": "Nincsenek naplók",
|
"noLogs": "Nincsenek naplók",
|
||||||
"appLogs": "App naplók",
|
"appLogs": "App naplók",
|
||||||
"close": "Bezár",
|
"close": "Bezárás",
|
||||||
"share": "Megoszt",
|
"share": "Megosztás",
|
||||||
"appNotFound": "App nem található",
|
"appNotFound": "App nem található",
|
||||||
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||||
"pickAnAPK": "Válasszon egy APK-t",
|
"pickAnAPK": "Válasszon egy APK-t",
|
||||||
@@ -202,6 +193,7 @@
|
|||||||
"categories": "Kategóriák",
|
"categories": "Kategóriák",
|
||||||
"category": "Kategória",
|
"category": "Kategória",
|
||||||
"noCategory": "Nincs kategória",
|
"noCategory": "Nincs kategória",
|
||||||
|
"noCategories": "Nincsenek kategóriák",
|
||||||
"deleteCategoryQuestion": "Törli a kategóriát?",
|
"deleteCategoryQuestion": "Törli a kategóriát?",
|
||||||
"categoryDeleteWarning": "A(z) {} összes app kategorizálatlan állapotba kerül.",
|
"categoryDeleteWarning": "A(z) {} összes app kategorizálatlan állapotba kerül.",
|
||||||
"addCategory": "Új kategória",
|
"addCategory": "Új kategória",
|
||||||
@@ -228,17 +220,73 @@
|
|||||||
"dontShowTrackOnlyWarnings": "Ne jelenítsen meg 'Csak nyomon követés' figyelmeztetést",
|
"dontShowTrackOnlyWarnings": "Ne jelenítsen meg 'Csak nyomon követés' figyelmeztetést",
|
||||||
"dontShowAPKOriginWarnings": "Ne jelenítsen meg az APK eredetére vonatkozó figyelmeztetéseket",
|
"dontShowAPKOriginWarnings": "Ne jelenítsen meg az APK eredetére vonatkozó figyelmeztetéseket",
|
||||||
"moveNonInstalledAppsToBottom": "Helyezze át a nem telepített appokat az App nézet aljára",
|
"moveNonInstalledAppsToBottom": "Helyezze át a nem telepített appokat az App nézet aljára",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Engedélyezi a Keresést and Better APK Discovery)",
|
"gitlabPATLabel": "GitLab Personal Access Token\n(Engedélyezi a Keresést és jobb APK felfedezés)",
|
||||||
"about": "Rólunk",
|
"about": "Rólunk",
|
||||||
"requiresCredentialsInSettings": "Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
|
"requiresCredentialsInSettings": "{}: Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
|
||||||
"checkOnStart": "Egyszer az indításkor",
|
"checkOnStart": "Egyszer az alkalmazás indításakor is",
|
||||||
"tryInferAppIdFromCode": "Próbálja kikövetkeztetni az app azonosítót a forráskódból",
|
"tryInferAppIdFromCode": "Próbálja kikövetkeztetni az app azonosítót a forráskódból",
|
||||||
"removeOnExternalUninstall": "A külsőleg eltávolított appok auto. eltávolítása",
|
"removeOnExternalUninstall": "A külsőleg eltávolított appok auto. eltávolítása",
|
||||||
"pickHighestVersionCode": "A legmagasabb verziószámú APK auto. kiválasztása",
|
"pickHighestVersionCode": "A legmagasabb verziószámú APK auto. kiválasztása",
|
||||||
"checkUpdateOnDetailPage": "Frissítések keresése az app részleteit tartalmazó oldal megnyitásakor",
|
"checkUpdateOnDetailPage": "Frissítések keresése az app részleteit tartalmazó oldal megnyitásakor",
|
||||||
"disablePageTransitions": "Lap áttűnési animációk tiltása",
|
"disablePageTransitions": "Lap áttűnési animációk letiltása",
|
||||||
"reversePageTransitions": "Fordított lap áttűnési animációk",
|
"reversePageTransitions": "Fordított lap áttűnési animációk",
|
||||||
"minStarCount": "Minimális csillag szám",
|
"minStarCount": "Minimális csillag szám",
|
||||||
|
"addInfoBelow": "Adja hozzá ezt az infót alább.",
|
||||||
|
"addInfoInSettings": "Adja hozzá ezt az infót a Beállításokban.",
|
||||||
|
"githubSourceNote": "A GitHub sebességkorlátozás elkerülhető API-kulcs használatával.",
|
||||||
|
"gitlabSourceNote": "Előfordulhat, hogy a GitLab APK kibontása nem működik API-kulcs nélkül.",
|
||||||
|
"sortByLastLinkSegment": "Rendezés csak a link utolsó szegmense szerint",
|
||||||
|
"filterReleaseNotesByRegEx": "Kiadási megjegyzések szűrése reguláris kifejezéssel",
|
||||||
|
"customLinkFilterRegex": "Egyéni APK hivatkozásszűrő reguláris kifejezéssel (Alapérték '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "App frissítési kísérlet",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Értesíti a felhasználót, hogy egy vagy több alkalmazás frissítése lehetséges a háttérben",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} frissítve lehet erre {}.",
|
||||||
|
"backgroundUpdateReqsExplanation": "Előfordulhat, hogy nem minden appnál lehetséges a háttérbeli frissítés.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "A háttérben történő telepítés sikeressége csak az Obtainium megnyitásakor állapítható meg.",
|
||||||
|
"verifyLatestTag": "Ellenőrizze a „legújabb” címkét",
|
||||||
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "Közvetítő link nem található",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Mentes a háttérben történő frissítések alól (ha engedélyezett)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "Tiltsa le a háttérben frissítéseket, ha nincs Wi-Fi-n",
|
||||||
|
"autoSelectHighestVersionCode": "A legmagasabb verziószámú APK auto. kiválasztása",
|
||||||
|
"versionExtractionRegEx": "Verzió kibontása reguláris kifejezéssel",
|
||||||
|
"matchGroupToUse": "Párosítsa a csoportot a használathoz",
|
||||||
|
"highlightTouchTargets": "Emelje ki a kevésbé nyilvánvaló érintési célokat",
|
||||||
|
"pickExportDir": "Válassza az Exportálási könyvtárat",
|
||||||
|
"autoExportOnChanges": "Auto-exportálás a változások után",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
|
"filterVersionsByRegEx": "Verziók szűrése reguláris kifejezéssel",
|
||||||
|
"trySelectingSuggestedVersionCode": "Próbálja ki a javasolt verziókódú APK-t",
|
||||||
|
"dontSortReleasesList": "Az API-ból származó kiadási sorrend megőrzése",
|
||||||
|
"reverseSort": "Fordított rendezés",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
|
"debugMenu": "Hibakereső menü",
|
||||||
|
"bgTaskStarted": "A háttérfeladat elindult – ellenőrizze a naplókat.",
|
||||||
|
"enableBackgroundUpdates": "Frissítések a háttérben",
|
||||||
|
"runBgCheckNow": "Futtassa a Háttérben frissítés ellenőrzését most",
|
||||||
|
"versionExtractWholePage": "Alkalmazza a Version Extraction Regex-et az egész oldalra",
|
||||||
|
"installing": "Telepítés",
|
||||||
|
"skipUpdateNotifications": "A frissítési értesítések kihagyása",
|
||||||
|
"updatesAvailableNotifChannel": "Frissítések érhetők el",
|
||||||
|
"appsUpdatedNotifChannel": "Alkalmazások frissítve",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "App frissítési kísérlet",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Hiba a frissítések keresésekor",
|
||||||
|
"appsRemovedNotifChannel": "Alkalmazások eltávolítva",
|
||||||
|
"downloadingXNotifChannel": "{} letöltés",
|
||||||
|
"completeAppInstallationNotifChannel": "Teljes app telepítés",
|
||||||
|
"checkingForUpdatesNotifChannel": "Frissítések keresése",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Csak a telepített és a csak követhető appokat ellenőrizze frissítésekért",
|
||||||
|
"supportFixedAPKURL": "Támogatja a rögzített APK URL-eket",
|
||||||
|
"selectX": "Kiválaszt {}",
|
||||||
|
"parallelDownloads": "Párhuzamos letöltéseket enged",
|
||||||
|
"installMethod": "Installation method",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Eltávolítja az alkalmazást?",
|
"one": "Eltávolítja az alkalmazást?",
|
||||||
"other": "Eltávolítja az alkalmazást?"
|
"other": "Eltávolítja az alkalmazást?"
|
||||||
@@ -281,10 +329,14 @@
|
|||||||
},
|
},
|
||||||
"xAndNMoreUpdatesAvailable": {
|
"xAndNMoreUpdatesAvailable": {
|
||||||
"one": "A(z) {} és 1 további alkalmazás frissítéseket kapott.",
|
"one": "A(z) {} és 1 további alkalmazás frissítéseket kapott.",
|
||||||
"other": "{} és további {} alkalmazás frissítéseket kapott."
|
"other": "{} és {} további alkalmazás frissítéseket kapott."
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesInstalled": {
|
"xAndNMoreUpdatesInstalled": {
|
||||||
"one": "A(z) {} és 1 további alkalmazás frissítve.",
|
"one": "A(z) {} és 1 további alkalmazás frissítve.",
|
||||||
"other": "{} és további {} alkalmazás frissítve."
|
"other": "{} és {} további alkalmazás frissítve."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} és 1 további alkalmazás is frissült.",
|
||||||
|
"other": "{} és {} további alkalmazás is frissült."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,7 @@
|
|||||||
"unexpectedError": "Errore imprevisto",
|
"unexpectedError": "Errore imprevisto",
|
||||||
"ok": "Va bene",
|
"ok": "Va bene",
|
||||||
"and": "e",
|
"and": "e",
|
||||||
"startedBgUpdateTask": "Avviata l'attività di controllo degli aggiornamenti in secondo piano",
|
"githubPATLabel": "GitHub Personal Access Token (aumenta limite di traffico)",
|
||||||
"bgUpdateIgnoreAfterIs": "Il parametro di agg. in secondo piano 'ignoreAfter' è {}",
|
|
||||||
"startedActualBGUpdateCheck": "Avviato il controllo effettivo degli aggiornamenti in secondo piano",
|
|
||||||
"bgUpdateTaskFinished": "Terminata l'attività di controllo degli aggiornamenti in secondo piano",
|
|
||||||
"firstRun": "Questo è il primo avvio di sempre di Obtainium",
|
|
||||||
"settingUpdateCheckIntervalTo": "Fissato intervallo di aggiornamento a {}",
|
|
||||||
"githubPATLabel": "GitHub Personal Access Token (diminuisce limite di traffico)",
|
|
||||||
"githubPATHint": "PAT deve seguire questo formato: nomeutente:token",
|
|
||||||
"githubPATFormat": "nomeutente:token",
|
|
||||||
"includePrereleases": "Includi prerelease",
|
"includePrereleases": "Includi prerelease",
|
||||||
"fallbackToOlderReleases": "Ripiega su release precedenti",
|
"fallbackToOlderReleases": "Ripiega su release precedenti",
|
||||||
"filterReleaseTitlesByRegEx": "Filtra release con espressioni regolari",
|
"filterReleaseTitlesByRegEx": "Filtra release con espressioni regolari",
|
||||||
@@ -49,7 +41,7 @@
|
|||||||
"searchSomeSourcesLabel": "Cerca (solo per alcune fonti)",
|
"searchSomeSourcesLabel": "Cerca (solo per alcune fonti)",
|
||||||
"search": "Cerca",
|
"search": "Cerca",
|
||||||
"additionalOptsFor": "Opzioni aggiuntive per {}",
|
"additionalOptsFor": "Opzioni aggiuntive per {}",
|
||||||
"supportedSourcesBelow": "Fonti supportate:",
|
"supportedSources": "Fonti supportate",
|
||||||
"trackOnlyInBrackets": "(Solo-Monitoraggio)",
|
"trackOnlyInBrackets": "(Solo-Monitoraggio)",
|
||||||
"searchableInBrackets": "(ricercabile)",
|
"searchableInBrackets": "(ricercabile)",
|
||||||
"appsString": "App",
|
"appsString": "App",
|
||||||
@@ -59,17 +51,17 @@
|
|||||||
"percentProgress": "Avanzamento: {}%",
|
"percentProgress": "Avanzamento: {}%",
|
||||||
"pleaseWait": "In attesa",
|
"pleaseWait": "In attesa",
|
||||||
"updateAvailable": "Aggiornamento disponibile",
|
"updateAvailable": "Aggiornamento disponibile",
|
||||||
"estimateInBracketsShort": "(prev.)",
|
"estimateInBracketsShort": "(stim.)",
|
||||||
"notInstalled": "Non installato",
|
"notInstalled": "Non installato",
|
||||||
"estimateInBrackets": "(previsto)",
|
"estimateInBrackets": "(stimato)",
|
||||||
"selectAll": "Seleziona tutto",
|
"selectAll": "Seleziona tutto",
|
||||||
"deselectN": "Deseleziona {}",
|
"deselectX": "Deseleziona {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "Verà effettuata la rimozione di {}, ma non la disinstallazione.",
|
"xWillBeRemovedButRemainInstalled": "Verà effettuata la rimozione di {}, ma non la disinstallazione.",
|
||||||
"removeSelectedAppsQuestion": "Rimuovere le app selezionate?",
|
"removeSelectedAppsQuestion": "Rimuovere le app selezionate?",
|
||||||
"removeSelectedApps": "Rimuovi le app selezionate",
|
"removeSelectedApps": "Rimuovi le app selezionate",
|
||||||
"updateX": "Aggiorna {}",
|
"updateX": "Aggiorna {}",
|
||||||
"installX": "Installa {}",
|
"installX": "Installa {}",
|
||||||
"markXTrackOnlyAsUpdated": "Contrassegna {}\n(Solo-Monitoraggio)\ncome aggiornato",
|
"markXTrackOnlyAsUpdated": "Contrassegna {}\n(Solo-Monitoraggio)\ncome aggiornata",
|
||||||
"changeX": "Modifica {}",
|
"changeX": "Modifica {}",
|
||||||
"installUpdateApps": "Installa/Aggiorna app",
|
"installUpdateApps": "Installa/Aggiorna app",
|
||||||
"installUpdateSelectedApps": "Installa/Aggiorna le app selezionate",
|
"installUpdateSelectedApps": "Installa/Aggiorna le app selezionate",
|
||||||
@@ -111,7 +103,6 @@
|
|||||||
"importErrors": "Errori di importazione",
|
"importErrors": "Errori di importazione",
|
||||||
"importedXOfYApps": "{} app di {} importate.",
|
"importedXOfYApps": "{} app di {} importate.",
|
||||||
"followingURLsHadErrors": "I seguenti URL contengono errori:",
|
"followingURLsHadErrors": "I seguenti URL contengono errori:",
|
||||||
"okay": "Va bene",
|
|
||||||
"selectURL": "Seleziona l'URL",
|
"selectURL": "Seleziona l'URL",
|
||||||
"selectURLs": "Seleziona gli URL",
|
"selectURLs": "Seleziona gli URL",
|
||||||
"pick": "Seleziona",
|
"pick": "Seleziona",
|
||||||
@@ -136,7 +127,7 @@
|
|||||||
"pinUpdates": "Fissa aggiornamenti disponibili in alto",
|
"pinUpdates": "Fissa aggiornamenti disponibili in alto",
|
||||||
"updates": "Aggiornamenti",
|
"updates": "Aggiornamenti",
|
||||||
"sourceSpecific": "Specifiche per la fonte",
|
"sourceSpecific": "Specifiche per la fonte",
|
||||||
"appSource": "Sorgente dell'app",
|
"appSource": "Codice dell'app",
|
||||||
"noLogs": "Nessun log",
|
"noLogs": "Nessun log",
|
||||||
"appLogs": "Log dell'app",
|
"appLogs": "Log dell'app",
|
||||||
"close": "Chiudi",
|
"close": "Chiudi",
|
||||||
@@ -177,7 +168,7 @@
|
|||||||
"installedVersionX": "Versione installata: {}",
|
"installedVersionX": "Versione installata: {}",
|
||||||
"lastUpdateCheckX": "Ultimo controllo degli aggiornamenti: {}",
|
"lastUpdateCheckX": "Ultimo controllo degli aggiornamenti: {}",
|
||||||
"remove": "Rimuovi",
|
"remove": "Rimuovi",
|
||||||
"yesMarkUpdated": "Sì, contrassegna come aggiornato",
|
"yesMarkUpdated": "Sì, contrassegna come aggiornata",
|
||||||
"fdroid": "F-Droid ufficiale",
|
"fdroid": "F-Droid ufficiale",
|
||||||
"appIdOrName": "ID o nome dell'app",
|
"appIdOrName": "ID o nome dell'app",
|
||||||
"appId": "ID dell'app",
|
"appId": "ID dell'app",
|
||||||
@@ -188,9 +179,9 @@
|
|||||||
"steamMobile": "Steam Mobile",
|
"steamMobile": "Steam Mobile",
|
||||||
"steamChat": "Steam Chat",
|
"steamChat": "Steam Chat",
|
||||||
"install": "Installa",
|
"install": "Installa",
|
||||||
"markInstalled": "Contrassegna come installato",
|
"markInstalled": "Contrassegna come installata",
|
||||||
"update": "Aggiorna",
|
"update": "Aggiorna",
|
||||||
"markUpdated": "Contrassegna come aggiornato",
|
"markUpdated": "Contrassegna come aggiornata",
|
||||||
"additionalOptions": "Opzioni aggiuntive",
|
"additionalOptions": "Opzioni aggiuntive",
|
||||||
"disableVersionDetection": "Disattiva il rilevamento della versione",
|
"disableVersionDetection": "Disattiva il rilevamento della versione",
|
||||||
"noVersionDetectionExplanation": "Questa opzione dovrebbe essere usata solo per le app la cui versione non viene rilevata correttamente.",
|
"noVersionDetectionExplanation": "Questa opzione dovrebbe essere usata solo per le app la cui versione non viene rilevata correttamente.",
|
||||||
@@ -229,17 +220,73 @@
|
|||||||
"dontShowTrackOnlyWarnings": "Non mostrare gli avvisi 'Solo-Monitoraggio'",
|
"dontShowTrackOnlyWarnings": "Non mostrare gli avvisi 'Solo-Monitoraggio'",
|
||||||
"dontShowAPKOriginWarnings": "Non mostrare gli avvisi di origine dell'APK",
|
"dontShowAPKOriginWarnings": "Non mostrare gli avvisi di origine dell'APK",
|
||||||
"moveNonInstalledAppsToBottom": "Sposta le app non installate in fondo alla lista",
|
"moveNonInstalledAppsToBottom": "Sposta le app non installate in fondo alla lista",
|
||||||
"gitlabPATLabel": "GitLab Personal Access Token\n(attiva la ricerca and Better APK Discovery)",
|
"gitlabPATLabel": "GitLab Personal Access Token\n(attiva la ricerca e migliora la rilevazione di apk)",
|
||||||
"about": "Informazioni",
|
"about": "Informazioni",
|
||||||
"requiresCredentialsInSettings": "Servono credenziali aggiuntive (in Impostazioni)",
|
"requiresCredentialsInSettings": "{}: Servono credenziali aggiuntive (in Impostazioni)",
|
||||||
"checkOnStart": "Controlla una volta all'avvio",
|
"checkOnStart": "Controlla una volta all'avvio",
|
||||||
"tryInferAppIdFromCode": "Prova a dedurre l'ID dell'app dal codice sorgente",
|
"tryInferAppIdFromCode": "Prova a dedurre l'ID dell'app dal codice sorgente",
|
||||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
"removeOnExternalUninstall": "Rimuovi automaticamente app disinstallate esternamente",
|
||||||
"pickHighestVersionCode": "Auto-select highest version code APK",
|
"pickHighestVersionCode": "Auto-seleziona APK con version code più alto",
|
||||||
"checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
|
"checkUpdateOnDetailPage": "Controlla aggiornamenti all'apertura dei dettagli dell'app",
|
||||||
"disablePageTransitions": "Disable page transition animations",
|
"disablePageTransitions": "Disattiva animazioni di transizione pagina",
|
||||||
"reversePageTransitions": "Reverse page transition animations",
|
"reversePageTransitions": "Inverti animazioni di transizione pagina",
|
||||||
"minStarCount": "Minimum Star Count",
|
"minStarCount": "Numero minimo di stelle",
|
||||||
|
"addInfoBelow": "Aggiungi questa info sotto.",
|
||||||
|
"addInfoInSettings": "Aggiungi questa info nelle impostazioni.",
|
||||||
|
"githubSourceNote": "Il limite di ricerca GitHub può essere evitato usando una chiave API.",
|
||||||
|
"gitlabSourceNote": "L'estrazione di APK da GitLab potrebbe non funzionare senza chiave API.",
|
||||||
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
|
"filterReleaseNotesByRegEx": "Filtra le note di rilascio con espressione regolare",
|
||||||
|
"customLinkFilterRegex": "Filtra link APK personalizzato con espressione regolare (predefinito '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "Aggiornamenti app tentati",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Notifica all'utente che sono stati potenzialmente applicati in secondo piano aggiornamenti a una o più app",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} potrebbe essere stata aggiornata alla {}.",
|
||||||
|
"enableBackgroundUpdates": "Attiva aggiornamenti in secondo piano",
|
||||||
|
"backgroundUpdateReqsExplanation": "Gli aggiornamenti in secondo piano potrebbero non essere possibili per tutte le app.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "La riuscita di un'installazione in secondo piano può essere determinata solo quando viene aperto Obtainium.",
|
||||||
|
"verifyLatestTag": "Verifica l'etichetta 'Latest'",
|
||||||
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "Link intermedio non trovato",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Esente da aggiornamenti in secondo piano (se attivo)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "Disattiva aggiornamenti in secondo piano quando non si usa il WiFi",
|
||||||
|
"autoSelectHighestVersionCode": "Auto-seleziona APK con versionCode più alto",
|
||||||
|
"versionExtractionRegEx": "RegEx di estrazione versione",
|
||||||
|
"matchGroupToUse": "Gruppo da usare",
|
||||||
|
"highlightTouchTargets": "Evidenzia elementi toccabili meno ovvi",
|
||||||
|
"pickExportDir": "Scegli cartella esp.",
|
||||||
|
"autoExportOnChanges": "Auto-esporta dopo modifiche",
|
||||||
|
"includeSettings": "Includi impostazioni",
|
||||||
|
"filterVersionsByRegEx": "Filtra versioni con espressione regolare",
|
||||||
|
"trySelectingSuggestedVersionCode": "Prova a selezionare APK con versionCode suggerito",
|
||||||
|
"dontSortReleasesList": "Conserva l'ordine di release da API",
|
||||||
|
"reverseSort": "Ordine inverso",
|
||||||
|
"takeFirstLink": "Prendi il primo link",
|
||||||
|
"skipSort": "Salta ordinamento",
|
||||||
|
"debugMenu": "Menu di debug",
|
||||||
|
"bgTaskStarted": "Attività in secondo piano iniziata - controllo log.",
|
||||||
|
"runBgCheckNow": "Inizia aggiornamento in secondo piano ora",
|
||||||
|
"versionExtractWholePage": "Applica regex di estrazione versione a tutta la pagina",
|
||||||
|
"installing": "Installazione",
|
||||||
|
"skipUpdateNotifications": "Salta notifiche di aggiornamento",
|
||||||
|
"updatesAvailableNotifChannel": "Aggiornamenti disponibili",
|
||||||
|
"appsUpdatedNotifChannel": "App aggiornate",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "Aggiornamenti app tentati",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Controllo degli errori per gli aggiornamenti",
|
||||||
|
"appsRemovedNotifChannel": "App rimosse",
|
||||||
|
"downloadingXNotifChannel": "Scaricamento di {} in corso",
|
||||||
|
"completeAppInstallationNotifChannel": "Completa l'installazione dell'app",
|
||||||
|
"checkingForUpdatesNotifChannel": "Controllo degli aggiornamenti in corso",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Cerca aggiornamenti solo per app installate e app in Solo-Monitoraggio",
|
||||||
|
"supportFixedAPKURL": "Supporta URL fissi di APK",
|
||||||
|
"selectX": "Seleziona {}",
|
||||||
|
"parallelDownloads": "Permetti download paralleli",
|
||||||
|
"installMethod": "Metodo d'installazione",
|
||||||
|
"normal": "Normale",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku non è in esecuzione",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Rimuovere l'app?",
|
"one": "Rimuovere l'app?",
|
||||||
"other": "Rimuovere le app?"
|
"other": "Rimuovere le app?"
|
||||||
@@ -287,5 +334,9 @@
|
|||||||
"xAndNMoreUpdatesInstalled": {
|
"xAndNMoreUpdatesInstalled": {
|
||||||
"one": "{} e un'altra app sono state aggiornate.",
|
"one": "{} e un'altra app sono state aggiornate.",
|
||||||
"other": "{} e altre {} app sono state aggiornate."
|
"other": "{} e altre {} app sono state aggiornate."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} e un'altra app potrebbero essere state aggiornate.",
|
||||||
|
"other": "{} e altre {} app potrebbero essere state aggiornate."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,18 +11,10 @@
|
|||||||
"unexpectedError": "予期せぬエラーが発生しました",
|
"unexpectedError": "予期せぬエラーが発生しました",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"and": "と",
|
"and": "と",
|
||||||
"startedBgUpdateTask": "バックグラウンドのアップデート確認タスクを開始",
|
|
||||||
"bgUpdateIgnoreAfterIs": "Bg update ignoreAfter is {}",
|
|
||||||
"startedActualBGUpdateCheck": "実際のバックグラウンドのアップデート確認を開始",
|
|
||||||
"bgUpdateTaskFinished": "バックグラウンドのアップデート確認タスクを終了",
|
|
||||||
"firstRun": "これがObtainiumの最初の実行です",
|
|
||||||
"settingUpdateCheckIntervalTo": "確認間隔を{}に設定する",
|
|
||||||
"githubPATLabel": "GitHub パーソナルアクセストークン (レート制限の引き上げ)",
|
"githubPATLabel": "GitHub パーソナルアクセストークン (レート制限の引き上げ)",
|
||||||
"githubPATHint": "PATは次の形式でなければなりません: ユーザー名:トークン",
|
|
||||||
"githubPATFormat": "ユーザー名:トークン",
|
|
||||||
"includePrereleases": "プレリリースを含む",
|
"includePrereleases": "プレリリースを含む",
|
||||||
"fallbackToOlderReleases": "旧リリースへのフォールバック",
|
"fallbackToOlderReleases": "旧リリースへのフォールバック",
|
||||||
"filterReleaseTitlesByRegEx": "正規表現でリリースタイトルを絞り込む",
|
"filterReleaseTitlesByRegEx": "正規表現でリリースタイトルをフィルタリングする",
|
||||||
"invalidRegEx": "無効な正規表現",
|
"invalidRegEx": "無効な正規表現",
|
||||||
"noDescription": "説明はありません",
|
"noDescription": "説明はありません",
|
||||||
"cancel": "キャンセル",
|
"cancel": "キャンセル",
|
||||||
@@ -49,7 +41,7 @@
|
|||||||
"searchSomeSourcesLabel": "検索 (一部ソースのみ)",
|
"searchSomeSourcesLabel": "検索 (一部ソースのみ)",
|
||||||
"search": "検索",
|
"search": "検索",
|
||||||
"additionalOptsFor": "{}の追加オプション",
|
"additionalOptsFor": "{}の追加オプション",
|
||||||
"supportedSourcesBelow": "対応するソース:",
|
"supportedSources": "対応するソース",
|
||||||
"trackOnlyInBrackets": "(追跡のみ)",
|
"trackOnlyInBrackets": "(追跡のみ)",
|
||||||
"searchableInBrackets": "(検索可能)",
|
"searchableInBrackets": "(検索可能)",
|
||||||
"appsString": "アプリ",
|
"appsString": "アプリ",
|
||||||
@@ -63,7 +55,7 @@
|
|||||||
"notInstalled": "未インストール",
|
"notInstalled": "未インストール",
|
||||||
"estimateInBrackets": "(推定)",
|
"estimateInBrackets": "(推定)",
|
||||||
"selectAll": "すべて選択",
|
"selectAll": "すべて選択",
|
||||||
"deselectN": "{}件の選択を解除",
|
"deselectX": "{}件の選択を解除",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} はObtainiumから削除されますが、デバイスにはインストールされたままです。",
|
"xWillBeRemovedButRemainInstalled": "{} はObtainiumから削除されますが、デバイスにはインストールされたままです。",
|
||||||
"removeSelectedAppsQuestion": "選択したアプリを削除しますか?",
|
"removeSelectedAppsQuestion": "選択したアプリを削除しますか?",
|
||||||
"removeSelectedApps": "選択したアプリを削除する",
|
"removeSelectedApps": "選択したアプリを削除する",
|
||||||
@@ -88,7 +80,7 @@
|
|||||||
"showOutdatedOnly": "アップデートが存在するアプリのみ表示する",
|
"showOutdatedOnly": "アップデートが存在するアプリのみ表示する",
|
||||||
"filter": "フィルター",
|
"filter": "フィルター",
|
||||||
"filterActive": "フィルター *",
|
"filterActive": "フィルター *",
|
||||||
"filterApps": "アプリを絞り込む",
|
"filterApps": "アプリをフィルタリングする",
|
||||||
"appName": "アプリ名",
|
"appName": "アプリ名",
|
||||||
"author": "作者",
|
"author": "作者",
|
||||||
"upToDateApps": "最新のアプリ",
|
"upToDateApps": "最新のアプリ",
|
||||||
@@ -111,7 +103,6 @@
|
|||||||
"importErrors": "インポートエラー",
|
"importErrors": "インポートエラー",
|
||||||
"importedXOfYApps": "{} / {} アプリをインポートしました",
|
"importedXOfYApps": "{} / {} アプリをインポートしました",
|
||||||
"followingURLsHadErrors": "以下のURLでエラーが発生しました:",
|
"followingURLsHadErrors": "以下のURLでエラーが発生しました:",
|
||||||
"okay": "OK",
|
|
||||||
"selectURL": "URLを選択",
|
"selectURL": "URLを選択",
|
||||||
"selectURLs": "URLを選択",
|
"selectURLs": "URLを選択",
|
||||||
"pick": "選択",
|
"pick": "選択",
|
||||||
@@ -152,7 +143,7 @@
|
|||||||
"updatesAvailable": "アップデートが利用可能",
|
"updatesAvailable": "アップデートが利用可能",
|
||||||
"updatesAvailableNotifDescription": "Obtainiumが追跡している1つまたは複数のアプリのアップデートが利用可能であることをユーザーに通知する",
|
"updatesAvailableNotifDescription": "Obtainiumが追跡している1つまたは複数のアプリのアップデートが利用可能であることをユーザーに通知する",
|
||||||
"noNewUpdates": "新しいアップデートはありません",
|
"noNewUpdates": "新しいアップデートはありません",
|
||||||
"xHasAnUpdate": "{} のアップデートが利用可能です",
|
"xHasAnUpdate": "{} のアップデートが利用可能です。",
|
||||||
"appsUpdated": "アプリをアップデートしました",
|
"appsUpdated": "アプリをアップデートしました",
|
||||||
"appsUpdatedNotifDescription": "1つまたは複数のAppのアップデートがバックグラウンドで適用されたことをユーザーに通知する",
|
"appsUpdatedNotifDescription": "1つまたは複数のAppのアップデートがバックグラウンドで適用されたことをユーザーに通知する",
|
||||||
"xWasUpdatedToY": "{} が {} にアップデートされました",
|
"xWasUpdatedToY": "{} が {} にアップデートされました",
|
||||||
@@ -211,7 +202,7 @@
|
|||||||
"copiedToClipboard": "クリップボードにコピーしました",
|
"copiedToClipboard": "クリップボードにコピーしました",
|
||||||
"storagePermissionDenied": "ストレージ権限が拒否されました",
|
"storagePermissionDenied": "ストレージ権限が拒否されました",
|
||||||
"selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。",
|
"selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。",
|
||||||
"filterAPKsByRegEx": "正規表現でAPKを絞り込む",
|
"filterAPKsByRegEx": "正規表現でAPKをフィルタリングする",
|
||||||
"removeFromObtainium": "Obtainiumから削除する",
|
"removeFromObtainium": "Obtainiumから削除する",
|
||||||
"uninstallFromDevice": "デバイスからアンインストールする",
|
"uninstallFromDevice": "デバイスからアンインストールする",
|
||||||
"onlyWorksWithNonVersionDetectApps": "バージョン検出を無効にしているアプリにのみ動作します。",
|
"onlyWorksWithNonVersionDetectApps": "バージョン検出を無効にしているアプリにのみ動作します。",
|
||||||
@@ -231,7 +222,7 @@
|
|||||||
"moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる",
|
"moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる",
|
||||||
"gitlabPATLabel": "GitLab パーソナルアクセストークン\n(検索とより良いAPK検出の有効化)",
|
"gitlabPATLabel": "GitLab パーソナルアクセストークン\n(検索とより良いAPK検出の有効化)",
|
||||||
"about": "概要",
|
"about": "概要",
|
||||||
"requiresCredentialsInSettings": "これには追加の認証が必要です (設定にて)",
|
"requiresCredentialsInSettings": "{}: これには追加の認証が必要です (設定にて)",
|
||||||
"checkOnStart": "起動時にアップデートを確認する",
|
"checkOnStart": "起動時にアップデートを確認する",
|
||||||
"tryInferAppIdFromCode": "ソースコードからApp IDを推測する",
|
"tryInferAppIdFromCode": "ソースコードからApp IDを推測する",
|
||||||
"removeOnExternalUninstall": "外部でアンインストールされたアプリを自動的に削除する",
|
"removeOnExternalUninstall": "外部でアンインストールされたアプリを自動的に削除する",
|
||||||
@@ -239,14 +230,70 @@
|
|||||||
"checkUpdateOnDetailPage": "アプリの詳細ページを開く際にアップデートを確認する",
|
"checkUpdateOnDetailPage": "アプリの詳細ページを開く際にアップデートを確認する",
|
||||||
"disablePageTransitions": "ページ遷移アニメーションを無効化する",
|
"disablePageTransitions": "ページ遷移アニメーションを無効化する",
|
||||||
"reversePageTransitions": "ページ遷移アニメーションを反転する",
|
"reversePageTransitions": "ページ遷移アニメーションを反転する",
|
||||||
"minStarCount": "Minimum Star Count",
|
"minStarCount": "最小スター数",
|
||||||
|
"addInfoBelow": "下部でこの情報を追加してください。",
|
||||||
|
"addInfoInSettings": "設定でこの情報を追加してください。",
|
||||||
|
"githubSourceNote": "GitHubのレート制限はAPIキーを使うことで回避できます。",
|
||||||
|
"gitlabSourceNote": "GitLabのAPK抽出はAPIキーがないと動作しない場合があります。",
|
||||||
|
"sortByLastLinkSegment": "リンクの最後のセグメントのみでソートする",
|
||||||
|
"filterReleaseNotesByRegEx": "正規表現でリリースノートをフィルタリングする",
|
||||||
|
"customLinkFilterRegex": "正規表現によるカスタムリンクフィルター (デフォルト '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "アプリのアップデートを試行",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "1つまたは複数のアプリのアップデートがバックグラウンドで適用された可能性があることをユーザーに通知する",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} が {} にアップデートされた可能性があります。",
|
||||||
|
"enableBackgroundUpdates": "バックグラウンドアップデートを有効化する",
|
||||||
|
"backgroundUpdateReqsExplanation": "バックグラウンドアップデートは、すべてのアプリで可能とは限りません。",
|
||||||
|
"backgroundUpdateLimitsExplanation": "バックグラウンドアップデートが成功したかどうかは、Obtainiumを起動したときにしか判断できません。",
|
||||||
|
"verifyLatestTag": "'latest'タグを確認する",
|
||||||
|
"intermediateLinkRegex": "訪問する「中間」リンクのフィルター",
|
||||||
|
"filterByLinkText": "テキストでリンクをフィルタリングする",
|
||||||
|
"intermediateLinkNotFound": "中間リンクが見つかりませんでした",
|
||||||
|
"intermediateLink": "中間リンク",
|
||||||
|
"exemptFromBackgroundUpdates": "バックグラウンドアップデートを行わない (有効な場合)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "WiFiを使用していない場合,バックグラウンドアップデートを無効にする",
|
||||||
|
"autoSelectHighestVersionCode": "最も高いバージョンコードのAPKを自動で選択する",
|
||||||
|
"versionExtractionRegEx": "バージョン抽出の正規表現",
|
||||||
|
"matchGroupToUse": "使用するマッチしたグループ",
|
||||||
|
"highlightTouchTargets": "目立たないタップ可能な対象をハイライトする",
|
||||||
|
"pickExportDir": "エクスポートディレクトリを選択",
|
||||||
|
"autoExportOnChanges": "変更があった際に自動でエクスポートする",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
|
"filterVersionsByRegEx": "正規表現でバージョンをフィルタリングする",
|
||||||
|
"trySelectingSuggestedVersionCode": "提案されたバージョンコードのAPKを選択する",
|
||||||
|
"dontSortReleasesList": "APIからのリリース順を保持する",
|
||||||
|
"reverseSort": "逆順ソート",
|
||||||
|
"takeFirstLink": "最初のリンクを取得する",
|
||||||
|
"skipSort": "ソートをスキップする",
|
||||||
|
"debugMenu": "デバッグメニュー",
|
||||||
|
"bgTaskStarted": "バックグラウンドタスクが開始されました - ログを確認してください。",
|
||||||
|
"runBgCheckNow": "今すぐバックグラウンドでのアップデート確認を開始する",
|
||||||
|
"versionExtractWholePage": "バージョン抽出の正規表現をページ全体に適用する",
|
||||||
|
"installing": "インストール中",
|
||||||
|
"skipUpdateNotifications": "アップデート通知を行わない",
|
||||||
|
"updatesAvailableNotifChannel": "アップデートが利用可能",
|
||||||
|
"appsUpdatedNotifChannel": "アプリをアップデートしました",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "アプリのアップデートを試行",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "アップデート確認中のエラー",
|
||||||
|
"appsRemovedNotifChannel": "削除されたアプリ",
|
||||||
|
"downloadingXNotifChannel": "{} をダウンロード中",
|
||||||
|
"completeAppInstallationNotifChannel": "アプリのインストールを完了する",
|
||||||
|
"checkingForUpdatesNotifChannel": "アップデートを確認中",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "インストール済みのアプリと「追跡のみ」のアプリのアップデートのみを確認する",
|
||||||
|
"supportFixedAPKURL": "固定されたAPKのURLをサポートする",
|
||||||
|
"selectX": "{} 選択",
|
||||||
|
"parallelDownloads": "並行ダウンロードを許可する",
|
||||||
|
"installMethod": "インストール方法",
|
||||||
|
"normal": "通常",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizukuが起動していません",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "アプリを削除しますか?",
|
"one": "アプリを削除しますか?",
|
||||||
"other": "アプリを削除しますか?"
|
"other": "アプリを削除しますか?"
|
||||||
},
|
},
|
||||||
"tooManyRequestsTryAgainInMinutes": {
|
"tooManyRequestsTryAgainInMinutes": {
|
||||||
"one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください",
|
"one": "リクエストが多すぎます(レート制限)- {} 分後に再試行してください",
|
||||||
"other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください"
|
"other": "リクエストが多すぎます(レート制限)- {} 分後に再試行してください"
|
||||||
},
|
},
|
||||||
"bgUpdateGotErrorRetryInMinutes": {
|
"bgUpdateGotErrorRetryInMinutes": {
|
||||||
"one": "バックグラウンドでのアップデート確認で {} の問題が発生, {} 分後に再試行します",
|
"one": "バックグラウンドでのアップデート確認で {} の問題が発生, {} 分後に再試行します",
|
||||||
@@ -257,35 +304,39 @@
|
|||||||
"other": "バックグラウンドでのアップデート確認で {} 個のアップデートを発見 - 必要に応じてユーザーに通知します"
|
"other": "バックグラウンドでのアップデート確認で {} 個のアップデートを発見 - 必要に応じてユーザーに通知します"
|
||||||
},
|
},
|
||||||
"apps": {
|
"apps": {
|
||||||
"one": "{}個のアプリ",
|
"one": "{} 個のアプリ",
|
||||||
"other": "{}個のアプリ"
|
"other": "{} 個のアプリ"
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"one": "{}個のURL",
|
"one": "{} 個のURL",
|
||||||
"other": "{}個のURL"
|
"other": "{} 個のURL"
|
||||||
},
|
},
|
||||||
"minute": {
|
"minute": {
|
||||||
"one": "{}分",
|
"one": "{} 分",
|
||||||
"other": "{}分"
|
"other": "{} 分"
|
||||||
},
|
},
|
||||||
"hour": {
|
"hour": {
|
||||||
"one": "{}時間",
|
"one": "{} 時間",
|
||||||
"other": "{}時間"
|
"other": "{} 時間"
|
||||||
},
|
},
|
||||||
"day": {
|
"day": {
|
||||||
"one": "{}日",
|
"one": "{} 日",
|
||||||
"other": "{}日"
|
"other": "{} 日"
|
||||||
},
|
},
|
||||||
"clearedNLogsBeforeXAfterY": {
|
"clearedNLogsBeforeXAfterY": {
|
||||||
"one": "{n}個のログをクリアしました (前 = {before}, 後 = {after})",
|
"one": "{n} 個のログをクリアしました (前 = {before}, 後 = {after})",
|
||||||
"other": "{n}個のログをクリアしました (前 = {before}, 後 = {after})"
|
"other": "{n} 個のログをクリアしました (前 = {before}, 後 = {after})"
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesAvailable": {
|
"xAndNMoreUpdatesAvailable": {
|
||||||
"one": "{} とさらに {} 個のアプリのアップデートが利用可能です",
|
"one": "{} とさらに {} 個のアプリのアップデートが利用可能です。",
|
||||||
"other": "{} とさらに {} 個のアプリのアップデートが利用可能です"
|
"other": "{} とさらに {} 個のアプリのアップデートが利用可能です。"
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesInstalled": {
|
"xAndNMoreUpdatesInstalled": {
|
||||||
"one": "{} とさらに {} 個のアプリがアップデートされました",
|
"one": "{} とさらに {} 個のアプリがアップデートされました。",
|
||||||
"other": "{} とさらに {} 個のアプリがアップデートされました"
|
"other": "{} とさらに {} 個のアプリがアップデートされました。"
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} とさらに 1 個のアプリがアップデートされた可能性があります。",
|
||||||
|
"other": "{} とさらに {} 個のアプリがアップデートされた可能性があります。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
342
assets/translations/nl.json
Normal file
342
assets/translations/nl.json
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
{
|
||||||
|
"invalidURLForSource": "Geen valide {} app URL",
|
||||||
|
"noReleaseFound": "Kan geen geschikte release vinden",
|
||||||
|
"noVersionFound": "Kan de versie niet bepalen",
|
||||||
|
"urlMatchesNoSource": "URL komt niet overeen met bekende bron",
|
||||||
|
"cantInstallOlderVersion": "Kan geen oudere versie van de app installeren",
|
||||||
|
"appIdMismatch": "Gedownloade pakket-ID komt niet overeen met de bestaande app-ID",
|
||||||
|
"functionNotImplemented": "Deze class heeft deze functie niet geïmplementeerd.",
|
||||||
|
"placeholder": "Plaatshouder",
|
||||||
|
"someErrors": "Er zijn enkele fouten opgetreden",
|
||||||
|
"unexpectedError": "Onverwachte fout",
|
||||||
|
"ok": "Ok",
|
||||||
|
"and": "en",
|
||||||
|
"githubPATLabel": "GitHub Personal Access Token\n(Verhoogt limiet aantal verzoeken)",
|
||||||
|
"includePrereleases": "Bevat prereleases",
|
||||||
|
"fallbackToOlderReleases": "Terugvallen op oudere releases",
|
||||||
|
"filterReleaseTitlesByRegEx": "Filter release-titels met reguliere expressies.",
|
||||||
|
"invalidRegEx": "Ongeldige reguliere expressie",
|
||||||
|
"noDescription": "Geen omschrijving",
|
||||||
|
"cancel": "Annuleer",
|
||||||
|
"continue": "Ga verder",
|
||||||
|
"requiredInBrackets": "(Verplicht)",
|
||||||
|
"dropdownNoOptsError": "FOUTMELDING: DROPDOWN MOET TENMINSTE ÉÉN OPT HEBBEN",
|
||||||
|
"colour": "Kleur",
|
||||||
|
"githubStarredRepos": "GitHub Starred Repos",
|
||||||
|
"uname": "Gebruikersnaam",
|
||||||
|
"wrongArgNum": "Onjuist aantal argumenten verstrekt.",
|
||||||
|
"xIsTrackOnly": "{} is Track-Only",
|
||||||
|
"source": "Bron",
|
||||||
|
"app": "App",
|
||||||
|
"appsFromSourceAreTrackOnly": "Apps van deze bron zijn 'Track-Only'.",
|
||||||
|
"youPickedTrackOnly": "Je hebt de 'Track-Only' optie geselecteerd.",
|
||||||
|
"trackOnlyAppDescription": "De app zal worden gevolgd voor updates, maar Obtainium zal niet in staat zijn om deze te downloaden of te installeren.",
|
||||||
|
"cancelled": "Geannuleerd",
|
||||||
|
"appAlreadyAdded": "App al toegevoegd",
|
||||||
|
"alreadyUpToDateQuestion": "Is de app al up-to-date?",
|
||||||
|
"addApp": "App toevoegen",
|
||||||
|
"appSourceURL": "App bron URL",
|
||||||
|
"error": "Foutmelding",
|
||||||
|
"add": "Toevoegen",
|
||||||
|
"searchSomeSourcesLabel": "Zoeken (Alleen sommige bronnen)",
|
||||||
|
"search": "Zoeken",
|
||||||
|
"additionalOptsFor": "Aanvullende opties voor {}",
|
||||||
|
"supportedSources": "Ondersteunde bronnen",
|
||||||
|
"trackOnlyInBrackets": "(Track-Only)",
|
||||||
|
"searchableInBrackets": "(Doorzoekbaar)",
|
||||||
|
"appsString": "Apps",
|
||||||
|
"noApps": "Geen Apps",
|
||||||
|
"noAppsForFilter": "Geen Apps voor filter",
|
||||||
|
"byX": "Door {}",
|
||||||
|
"percentProgress": "Vooruitgang: {}%",
|
||||||
|
"pleaseWait": "Even geduld",
|
||||||
|
"updateAvailable": "Update beschikbaar",
|
||||||
|
"estimateInBracketsShort": "(Ong.)",
|
||||||
|
"notInstalled": "Niet geinstalleerd",
|
||||||
|
"estimateInBrackets": "(Ongeveer)",
|
||||||
|
"selectAll": "Selecteer alles",
|
||||||
|
"deselectX": "Deselecteer {}",
|
||||||
|
"xWillBeRemovedButRemainInstalled": "{} zal worden verwijderd uit Obtainium, maar blijft geïnstalleerd op het apparaat.",
|
||||||
|
"removeSelectedAppsQuestion": "Geselecteerde apps verwijderen??",
|
||||||
|
"removeSelectedApps": "Geselecteerde apps verwijderen",
|
||||||
|
"updateX": "Update {}",
|
||||||
|
"installX": "Installeer {}",
|
||||||
|
"markXTrackOnlyAsUpdated": "Markeer {}\n(Track-Only)\nals up-to-date",
|
||||||
|
"changeX": "Verander {}",
|
||||||
|
"installUpdateApps": "Installeer/Update apps",
|
||||||
|
"installUpdateSelectedApps": "Installeer/Update geselecteerde apps",
|
||||||
|
"markXSelectedAppsAsUpdated": "{} geselecteerde apps markeren als up-to-date?",
|
||||||
|
"no": "Nee",
|
||||||
|
"yes": "Ja",
|
||||||
|
"markSelectedAppsUpdated": "Markeer geselecteerde aps als up-to-date",
|
||||||
|
"pinToTop": "Vastzetten aan de bovenkant",
|
||||||
|
"unpinFromTop": "Losmaken van de bovenkant",
|
||||||
|
"resetInstallStatusForSelectedAppsQuestion": "Installatiestatus resetten voor geselecteerde apps?",
|
||||||
|
"installStatusOfXWillBeResetExplanation": "De installatiestatus van alle geselecteerde apps zal worden gereset.\n\nDit kan helpen wanneer de versie van de app die in Obtainium wordt weergegeven onjuist is vanwege mislukte updates of andere problemen.",
|
||||||
|
"shareSelectedAppURLs": "Deel geselecteerde app URL's",
|
||||||
|
"resetInstallStatus": "Reset installatiestatus",
|
||||||
|
"more": "Meer",
|
||||||
|
"removeOutdatedFilter": "Verwijder out-of-date app filter",
|
||||||
|
"showOutdatedOnly": "Toon alleen out-of-date apps",
|
||||||
|
"filter": "Filter",
|
||||||
|
"filterActive": "Filter *",
|
||||||
|
"filterApps": "Filter apps",
|
||||||
|
"appName": "App naam",
|
||||||
|
"author": "Auteur",
|
||||||
|
"upToDateApps": "Up-to-date apps",
|
||||||
|
"nonInstalledApps": "Niet-geïnstalleerde apps",
|
||||||
|
"importExport": "Import/Export",
|
||||||
|
"settings": "Instellingen",
|
||||||
|
"exportedTo": "Geëxporteerd naar {}",
|
||||||
|
"obtainiumExport": "Obtainium export",
|
||||||
|
"invalidInput": "Ongeldige invoer",
|
||||||
|
"importedX": "Geïmporteerd {}",
|
||||||
|
"obtainiumImport": "Obtainium import",
|
||||||
|
"importFromURLList": "Importeer van URL-lijsten",
|
||||||
|
"searchQuery": "Zoekopdracht",
|
||||||
|
"appURLList": "App URL-lijst",
|
||||||
|
"line": "Lijn",
|
||||||
|
"searchX": "Zoek {}",
|
||||||
|
"noResults": "Geen resultaten gevonden",
|
||||||
|
"importX": "Import {}",
|
||||||
|
"importedAppsIdDisclaimer": "Geïmporteerde apps kunnen mogelijk onjuist worden weergegeven als \"Niet geïnstalleerd\".\nOm dit op te lossen, herinstalleer ze via Obtainium.\nDit zou geen invloed moeten hebben op app-gegevens.\n\nDit heeft alleen invloed op URL- en importmethoden van derden.",
|
||||||
|
"importErrors": "Import foutmeldingen",
|
||||||
|
"importedXOfYApps": "{} van {} apps geïmporteerd.",
|
||||||
|
"followingURLsHadErrors": "De volgende URL's bevatten fouten:",
|
||||||
|
"selectURL": "Selecteer URL",
|
||||||
|
"selectURLs": "Selecteer URL's",
|
||||||
|
"pick": "Kies",
|
||||||
|
"theme": "Thema",
|
||||||
|
"dark": "Donker",
|
||||||
|
"light": "Licht",
|
||||||
|
"followSystem": "Volg systeem",
|
||||||
|
"obtainium": "Obtainium",
|
||||||
|
"materialYou": "Material You",
|
||||||
|
"useBlackTheme": "Gebruik zwart thema",
|
||||||
|
"appSortBy": "App sorteren op",
|
||||||
|
"authorName": "Auteur/Naam",
|
||||||
|
"nameAuthor": "Naam/Auteur",
|
||||||
|
"asAdded": "Zoals toegevoegd",
|
||||||
|
"appSortOrder": "App sorteervolgorde",
|
||||||
|
"ascending": "Oplopend",
|
||||||
|
"descending": "Aflopend",
|
||||||
|
"bgUpdateCheckInterval": "Frequentie voor achtergrondupdatecontrole",
|
||||||
|
"neverManualOnly": "Nooit - Alleen handmatig",
|
||||||
|
"appearance": "Weergave",
|
||||||
|
"showWebInAppView": "Toon de bronwebpagina in app-weergave",
|
||||||
|
"pinUpdates": "Updates bovenaan in de apps-weergave vastpinnen",
|
||||||
|
"updates": "Updates",
|
||||||
|
"sourceSpecific": "Bron-specifiek",
|
||||||
|
"appSource": "App bron",
|
||||||
|
"noLogs": "Geen logs",
|
||||||
|
"appLogs": "App logs",
|
||||||
|
"close": "Sluiten",
|
||||||
|
"share": "Delen",
|
||||||
|
"appNotFound": "App niet gevonden",
|
||||||
|
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||||
|
"pickAnAPK": "Kies een APK",
|
||||||
|
"appHasMoreThanOnePackage": "{} heeft meer dan één package:",
|
||||||
|
"deviceSupportsXArch": "Jouw apparaat support de {} CPU-architectuur.",
|
||||||
|
"deviceSupportsFollowingArchs": "Je apparaat ondersteunt de volgende CPU-architecturen:",
|
||||||
|
"warning": "Waarschuwing",
|
||||||
|
"sourceIsXButPackageFromYPrompt": "De appbron is '{}' maar de release package komt van '{}'. Doorgaan?",
|
||||||
|
"updatesAvailable": "Updates beschikbaar",
|
||||||
|
"updatesAvailableNotifDescription": "Stelt de gebruiker op de hoogte dat er updates beschikbaar zijn voor één of meer apps die worden bijgehouden door Obtainium.",
|
||||||
|
"noNewUpdates": "Geen nieuwe updates.",
|
||||||
|
"xHasAnUpdate": "{} heeft een update.",
|
||||||
|
"appsUpdated": "Apps bijgewerkt",
|
||||||
|
"appsUpdatedNotifDescription": "Stelt de gebruiker op de hoogte dat updates voor één of meer apps in de achtergrond zijn toegepast.",
|
||||||
|
"xWasUpdatedToY": "{} is bijgewerkt naar {}.",
|
||||||
|
"errorCheckingUpdates": "Fout bij het controleren op updates",
|
||||||
|
"errorCheckingUpdatesNotifDescription": "Een melding die verschijnt wanneer het controleren op updates in de achtergrond mislukt",
|
||||||
|
"appsRemoved": "Apps verwijderd",
|
||||||
|
"appsRemovedNotifDescription": "Stelt de gebruiker op de hoogte dat één of meer apps zijn verwijderd vanwege fouten tijdens het laden ervan",
|
||||||
|
"xWasRemovedDueToErrorY": "{} is verwijderd vanwege deze foutmelding: {}",
|
||||||
|
"completeAppInstallation": "Complete app installatie",
|
||||||
|
"obtainiumMustBeOpenToInstallApps": "Obtainium moet geopend zijn om apps te installeren",
|
||||||
|
"completeAppInstallationNotifDescription": "Vraagt de gebruiker om terug te keren naar Obtainium om de installatie van een app af te ronden",
|
||||||
|
"checkingForUpdates": "Controleren op updates",
|
||||||
|
"checkingForUpdatesNotifDescription": "Tijdelijke melding die verschijnt tijdens het controleren op updates",
|
||||||
|
"pleaseAllowInstallPerm": "Sta Obtainium toe om apps te installeren",
|
||||||
|
"trackOnly": "Track-Only",
|
||||||
|
"errorWithHttpStatusCode": "Foutmelding {}",
|
||||||
|
"versionCorrectionDisabled": "Versiecorrectie uitgeschakeld (de plug-in lijkt niet te werken)",
|
||||||
|
"unknown": "Onbekend",
|
||||||
|
"none": "Geen",
|
||||||
|
"never": "Nooit",
|
||||||
|
"latestVersionX": "Laatste versie: {}",
|
||||||
|
"installedVersionX": "Geïnstalleerde versie: {}",
|
||||||
|
"lastUpdateCheckX": "Laatste updatecontrole: {}",
|
||||||
|
"remove": "Verwijderen",
|
||||||
|
"yesMarkUpdated": "Ja, markeer als bijgewerkt",
|
||||||
|
"fdroid": "F-Droid Official",
|
||||||
|
"appIdOrName": "App ID of naam",
|
||||||
|
"appId": "App ID",
|
||||||
|
"appWithIdOrNameNotFound": "Er werd geen app gevonden met dat ID of die naam",
|
||||||
|
"reposHaveMultipleApps": "Repositories kunnen meerdere apps bevatten",
|
||||||
|
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
|
||||||
|
"steam": "Steam",
|
||||||
|
"steamMobile": "Steam Mobile",
|
||||||
|
"steamChat": "Steam Chat",
|
||||||
|
"install": "Installeren",
|
||||||
|
"markInstalled": "Als geïnstalleerd markere",
|
||||||
|
"update": "Update",
|
||||||
|
"markUpdated": "Markeren als bijgewerkt",
|
||||||
|
"additionalOptions": "Aanvullende opties",
|
||||||
|
"disableVersionDetection": "Versieherkenning uitschakelen",
|
||||||
|
"noVersionDetectionExplanation": "Deze optie moet alleen worden gebruikt voor apps waar versieherkenning niet correct werkt.",
|
||||||
|
"downloadingX": "Downloaden {}",
|
||||||
|
"downloadNotifDescription": "Stelt de gebruiker op de hoogte van de voortgang bij het downloaden van een app",
|
||||||
|
"noAPKFound": "Geen APK gevonden",
|
||||||
|
"noVersionDetection": "Geen versieherkenning",
|
||||||
|
"categorize": "Categoriseren",
|
||||||
|
"categories": "Categorieën",
|
||||||
|
"category": "Categorie",
|
||||||
|
"noCategory": "Geen categorie",
|
||||||
|
"noCategories": "Geen categorieën",
|
||||||
|
"deleteCategoriesQuestion": "Categorieën verwijderen?",
|
||||||
|
"categoryDeleteWarning": "Alle apps in verwijderde categorieën worden teruggezet naar 'ongecategoriseerd'.",
|
||||||
|
"addCategory": "Categorie toevoegen",
|
||||||
|
"label": "Label",
|
||||||
|
"language": "Taal",
|
||||||
|
"copiedToClipboard": "Gekopieerd naar klembord",
|
||||||
|
"storagePermissionDenied": "Toegang tot opslag geweigerd",
|
||||||
|
"selectedCategorizeWarning": "Dit zal eventuele bestaande categorie-instellingen voor de geselecteerde apps vervangen.",
|
||||||
|
"filterAPKsByRegEx": "Filter APK's op reguliere expressie",
|
||||||
|
"removeFromObtainium": "Verwijder van Obtainium",
|
||||||
|
"uninstallFromDevice": "Verwijder van apparaat",
|
||||||
|
"onlyWorksWithNonVersionDetectApps": "Werkt alleen voor apps waarbij versieherkenning is uitgeschakeld.",
|
||||||
|
"releaseDateAsVersion": "Gebruik de releasedatum als versie",
|
||||||
|
"releaseDateAsVersionExplanation": "Deze optie moet alleen worden gebruikt voor apps waar versieherkenning niet correct werkt, maar waar wel een releasedatum beschikbaar is.",
|
||||||
|
"changes": "Veranderingen",
|
||||||
|
"releaseDate": "Releasedatum",
|
||||||
|
"importFromURLsInFile": "Importeren vanaf URL's in een bestand (zoals OPML)",
|
||||||
|
"versionDetection": "Versieherkenning",
|
||||||
|
"standardVersionDetection": "Standaard versieherkenning",
|
||||||
|
"groupByCategory": "Groepeer op categorie",
|
||||||
|
"autoApkFilterByArch": "Poging om APK's te filteren op CPU-architectuur indien mogelijk",
|
||||||
|
"overrideSource": "Bron overschrijven",
|
||||||
|
"dontShowAgain": "Don't show this again",
|
||||||
|
"dontShowTrackOnlyWarnings": "Geen waarschuwingen voor 'Track-Only' weergeven",
|
||||||
|
"dontShowAPKOriginWarnings": "APK-herkomstwaarschuwingen niet weergeven",
|
||||||
|
"moveNonInstalledAppsToBottom": "Verplaats niet-geïnstalleerde apps naar de onderkant van de apps-weergave",
|
||||||
|
"gitlabPATLabel": "GitLab Personal Access Token\n(Maakt het mogelijk beter te zoeken naar APK's)",
|
||||||
|
"about": "Over",
|
||||||
|
"requiresCredentialsInSettings": "{}: Dit vereist aanvullende referenties (in Instellingen)",
|
||||||
|
"checkOnStart": "Controleren op updates bij opstarten",
|
||||||
|
"tryInferAppIdFromCode": "Probeer de app-ID af te leiden uit de broncode",
|
||||||
|
"removeOnExternalUninstall": "Automatisch extern verwijderde apps verwijderen",
|
||||||
|
"pickHighestVersionCode": "Automatisch de APK met de hoogste versiecode selecteren",
|
||||||
|
"checkUpdateOnDetailPage": "Controleren op updates bij het openen van een app-detailpagina",
|
||||||
|
"disablePageTransitions": "Schakel overgangsanimaties tussen pagina's uit",
|
||||||
|
"reversePageTransitions": "Omgekeerde overgangsanimaties tussen pagina's",
|
||||||
|
"minStarCount": "Minimale Github Stars",
|
||||||
|
"addInfoBelow": "Voeg deze informatie hieronder toe.",
|
||||||
|
"addInfoInSettings": "Voeg deze informatie toe in de instellingen.",
|
||||||
|
"githubSourceNote": "Beperkingen van GitHub kunnen worden vermeden door het gebruik van een API-sleutel.",
|
||||||
|
"gitlabSourceNote": "GitLab APK-extractie werkt mogelijk niet zonder een API-sleutel.",
|
||||||
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
|
"filterReleaseNotesByRegEx": "Filter release-opmerkingen met een reguliere expressie.",
|
||||||
|
"customLinkFilterRegex": "Aangepaste APK-linkfilter met een reguliere expressie (Standaard '.apk$').",
|
||||||
|
"appsPossiblyUpdated": "Poging tot app-updates",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Stelt de gebruiker op de hoogte dat updates voor één of meer apps mogelijk in de achtergrond zijn toegepast",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} mogelijk bijgewerkt naar {}.",
|
||||||
|
"enableBackgroundUpdates": "Achtergrondupdates inschakelen",
|
||||||
|
"backgroundUpdateReqsExplanation": "Achtergrondupdates zijn mogelijk niet voor alle apps mogelijk.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "Het succes van een installatie in de achtergrond kan alleen worden bepaald wanneer Obtainium is geopend.",
|
||||||
|
"verifyLatestTag": "Verifieer de 'Laatste'-tag",
|
||||||
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "Tussenliggende link niet gevonden",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Vrijgesteld van achtergrondupdates (indien ingeschakeld)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "Achtergrondupdates uitschakelen wanneer niet verbonden met WiFi",
|
||||||
|
"autoSelectHighestVersionCode": "Automatisch de APK met de hoogste versiecode selecteren",
|
||||||
|
"versionExtractionRegEx": "Reguliere expressie voor versie-extractie",
|
||||||
|
"matchGroupToUse": "Overeenkomende groep om te gebruiken voor de reguliere expressie voor versie-extractie",
|
||||||
|
"highlightTouchTargets": "Markeer minder voor de hand liggende aanraakdoelen.",
|
||||||
|
"pickExportDir": "Kies de exportmap",
|
||||||
|
"autoExportOnChanges": "Automatisch exporteren bij wijzigingen",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
|
"filterVersionsByRegEx": "Filter versies met een reguliere expressie",
|
||||||
|
"trySelectingSuggestedVersionCode": "Probeer de voorgestelde versiecode APK te selecteren",
|
||||||
|
"dontSortReleasesList": "Volgorde van releases behouden vanuit de API",
|
||||||
|
"reverseSort": "Sortering omkeren",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
|
"debugMenu": "Debug menu",
|
||||||
|
"bgTaskStarted": "Achtergrondtaak gestart - controleer de logs.",
|
||||||
|
"runBgCheckNow": "Voer nu een achtergrondupdatecontrole uit",
|
||||||
|
"versionExtractWholePage": "De reguliere expressie voor versie-extractie toepassen op de hele pagina",
|
||||||
|
"installing": "Installeren",
|
||||||
|
"skipUpdateNotifications": "Updatemeldingen overslaan",
|
||||||
|
"updatesAvailableNotifChannel": "Updates beschikbaar",
|
||||||
|
"appsUpdatedNotifChannel": "Apps bijgewerkt",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "Poging tot app-updates",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Foutcontrole bij het zoeken naar updates",
|
||||||
|
"appsRemovedNotifChannel": "Apps verwijderd",
|
||||||
|
"downloadingXNotifChannel": "{} downloaden",
|
||||||
|
"completeAppInstallationNotifChannel": "Voltooien van de app-installatie",
|
||||||
|
"checkingForUpdatesNotifChannel": "Controleren op updates",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Alleen geïnstalleerde en Track-Only apps controleren op updates",
|
||||||
|
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||||
|
"selectX": "Select {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
|
"installMethod": "Installation method",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
|
"removeAppQuestion": {
|
||||||
|
"one": "App verwijderen?",
|
||||||
|
"other": "Apps verwijderen?"
|
||||||
|
},
|
||||||
|
"tooManyRequestsTryAgainInMinutes": {
|
||||||
|
"one": "Te veel verzoeken (aantal beperkt) - probeer het opnieuw in {} minuut",
|
||||||
|
"other": "Te veel verzoeken (aantal beperkt) - probeer het opnieuw in {} minuten"
|
||||||
|
},
|
||||||
|
"bgUpdateGotErrorRetryInMinutes": {
|
||||||
|
"one": "Achtergrondupdatecontrole heeft een {}, zal een hercontrole plannen over {} minuut",
|
||||||
|
"other": "Achtergrondupdatecontrole heeft een {}, zal een hercontrole plannen over {} minuten"
|
||||||
|
},
|
||||||
|
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
||||||
|
"one": "Achtergrondupdatecontrole heeft {} update gevonden - zal de gebruiker op de hoogte stellen indien nodig",
|
||||||
|
"other": "Achtergrondupdatecontrole heeft {} updates gevonden - zal de gebruiker op de hoogte stellen indien nodig"
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"one": "{} app",
|
||||||
|
"other": "{} apps"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"one": "{} URL",
|
||||||
|
"other": "{} URLs"
|
||||||
|
},
|
||||||
|
"minute": {
|
||||||
|
"one": "{} minuut",
|
||||||
|
"other": "{} minuten"
|
||||||
|
},
|
||||||
|
"hour": {
|
||||||
|
"one": "{} uur",
|
||||||
|
"other": "{} uur"
|
||||||
|
},
|
||||||
|
"day": {
|
||||||
|
"one": "{} dag",
|
||||||
|
"other": "{} dagen"
|
||||||
|
},
|
||||||
|
"clearedNLogsBeforeXAfterY": {
|
||||||
|
"one": "{n} logboekitem gewist (voor = {before}, na = {after})",
|
||||||
|
"other": "{n} logboekitems gewist (voor = {before}, na = {after})"
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesAvailable": {
|
||||||
|
"one": "{} en nog 1 app hebben updates.",
|
||||||
|
"other": "{} en {} meer apps hebben updates."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesInstalled": {
|
||||||
|
"one": "{} en nog 1 app is bijgewerkt.",
|
||||||
|
"other": "{} en {} meer apps zijn bijgewerkt."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} en nog 1 app zijn mogelijk bijgewerkt.",
|
||||||
|
"other": "{} en {} meer apps zijn mogelijk bijgwerkt."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,4 @@
|
|||||||
{
|
{
|
||||||
"noDescription": "Brak opisu",
|
|
||||||
"no": "Nie",
|
|
||||||
"okay": "Okej",
|
|
||||||
"appId": "ID aplikacji",
|
|
||||||
"bgUpdateGotErrorRetryInMinutes": {
|
|
||||||
"one": "Sprawdzanie aktualizacji w tle napotkało {}, zaplanuje ponowne sprawdzenie za {} min.",
|
|
||||||
"other": "Sprawdzanie aktualizacji w tle napotkało {}, zaplanuje ponowne sprawdzenie za {} min."
|
|
||||||
},
|
|
||||||
"invalidURLForSource": "Nieprawidłowy adres URL aplikacji {}",
|
"invalidURLForSource": "Nieprawidłowy adres URL aplikacji {}",
|
||||||
"noReleaseFound": "Nie można znaleźć odpowiedniego wydania",
|
"noReleaseFound": "Nie można znaleźć odpowiedniego wydania",
|
||||||
"noVersionFound": "Nie można określić wersji wydania",
|
"noVersionFound": "Nie można określić wersji wydania",
|
||||||
@@ -19,19 +11,12 @@
|
|||||||
"unexpectedError": "Nieoczekiwany błąd",
|
"unexpectedError": "Nieoczekiwany błąd",
|
||||||
"ok": "Okej",
|
"ok": "Okej",
|
||||||
"and": "i",
|
"and": "i",
|
||||||
"startedBgUpdateTask": "Rozpoczęto zadanie sprawdzania aktualizacji w tle",
|
|
||||||
"bgUpdateIgnoreAfterIs": "Parametr ignoreAfter aktualizacji w tle to {}",
|
|
||||||
"startedActualBGUpdateCheck": "Rozpoczęto sprawdzanie aktualizacji w tle",
|
|
||||||
"bgUpdateTaskFinished": "Zakończono zadanie sprawdzania aktualizacji w tle",
|
|
||||||
"firstRun": "Jest to pierwsze uruchomienie Obtainium",
|
|
||||||
"settingUpdateCheckIntervalTo": "Ustawianie interwału aktualizacji na {}",
|
|
||||||
"githubPATLabel": "Osobisty token dostępu GitHub (zwiększa limit zapytań)",
|
"githubPATLabel": "Osobisty token dostępu GitHub (zwiększa limit zapytań)",
|
||||||
"githubPATHint": "Wymagany format: użytkownik:token",
|
|
||||||
"githubPATFormat": "użytkownik:token",
|
|
||||||
"includePrereleases": "Uwzględnij wersje wstępne",
|
"includePrereleases": "Uwzględnij wersje wstępne",
|
||||||
"fallbackToOlderReleases": "Powracaj do starszych wersji",
|
"fallbackToOlderReleases": "Powracaj do starszych wersji",
|
||||||
"filterReleaseTitlesByRegEx": "Filtruj tytuły wydań wg. wyrażeń regularnych",
|
"filterReleaseTitlesByRegEx": "Filtruj tytuły wydań wg. wyrażeń regularnych",
|
||||||
"invalidRegEx": "Nieprawidłowe wyrażenie regularne",
|
"invalidRegEx": "Nieprawidłowe wyrażenie regularne",
|
||||||
|
"noDescription": "Brak opisu",
|
||||||
"cancel": "Anuluj",
|
"cancel": "Anuluj",
|
||||||
"continue": "Kontynuuj",
|
"continue": "Kontynuuj",
|
||||||
"requiredInBrackets": "(Wymagane)",
|
"requiredInBrackets": "(Wymagane)",
|
||||||
@@ -40,11 +25,11 @@
|
|||||||
"githubStarredRepos": "Repozytoria GitHub oznaczone gwiazdką",
|
"githubStarredRepos": "Repozytoria GitHub oznaczone gwiazdką",
|
||||||
"uname": "Nazwa użytkownika",
|
"uname": "Nazwa użytkownika",
|
||||||
"wrongArgNum": "Nieprawidłowa liczba podanych argumentów",
|
"wrongArgNum": "Nieprawidłowa liczba podanych argumentów",
|
||||||
"xIsTrackOnly": "{} jest tylko obserwowana",
|
"xIsTrackOnly": "{} jest tylko obserwowane",
|
||||||
"source": "Źródło",
|
"source": "Źródło",
|
||||||
"app": "Aplikacja",
|
"app": "Aplikacja",
|
||||||
"appsFromSourceAreTrackOnly": "Aplikacje z tego źródła są „Obserwowane”.",
|
"appsFromSourceAreTrackOnly": "Aplikacje z tego źródła są tylko obserwowane.",
|
||||||
"youPickedTrackOnly": "Wybrano opcję „Tylko obserwuj”.",
|
"youPickedTrackOnly": "Wybrano opcję \"Tylko obserwuj\".",
|
||||||
"trackOnlyAppDescription": "Aplikacja będzie obserwowana pod kątem aktualizacji, ale Obtainium nie będzie w stanie jej pobrać ani zainstalować.",
|
"trackOnlyAppDescription": "Aplikacja będzie obserwowana pod kątem aktualizacji, ale Obtainium nie będzie w stanie jej pobrać ani zainstalować.",
|
||||||
"cancelled": "Anulowano",
|
"cancelled": "Anulowano",
|
||||||
"appAlreadyAdded": "Aplikacja już została dodana",
|
"appAlreadyAdded": "Aplikacja już została dodana",
|
||||||
@@ -56,9 +41,9 @@
|
|||||||
"searchSomeSourcesLabel": "Szukaj (tylko niektóre źródła)",
|
"searchSomeSourcesLabel": "Szukaj (tylko niektóre źródła)",
|
||||||
"search": "Szukaj",
|
"search": "Szukaj",
|
||||||
"additionalOptsFor": "Dodatkowe opcje dla {}",
|
"additionalOptsFor": "Dodatkowe opcje dla {}",
|
||||||
"supportedSourcesBelow": "Obsługiwane źródła:",
|
"supportedSources": "Obsługiwane źródła",
|
||||||
"trackOnlyInBrackets": "(tylko obserwowane)",
|
"trackOnlyInBrackets": "(tylko obserwowane)",
|
||||||
"searchableInBrackets": "(Wyszukiwalne)",
|
"searchableInBrackets": "(wyszukiwalne)",
|
||||||
"appsString": "Aplikacje",
|
"appsString": "Aplikacje",
|
||||||
"noApps": "Brak aplikacji",
|
"noApps": "Brak aplikacji",
|
||||||
"noAppsForFilter": "Brak aplikacji dla filtra",
|
"noAppsForFilter": "Brak aplikacji dla filtra",
|
||||||
@@ -70,17 +55,18 @@
|
|||||||
"notInstalled": "Nie zainstalowano",
|
"notInstalled": "Nie zainstalowano",
|
||||||
"estimateInBrackets": "(Szacunkowo)",
|
"estimateInBrackets": "(Szacunkowo)",
|
||||||
"selectAll": "Zaznacz wszystkie",
|
"selectAll": "Zaznacz wszystkie",
|
||||||
"deselectN": "Odznacz {}",
|
"deselectX": "Odznacz {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} zostanie usunięty z Obtainium, ale pozostanie zainstalowany na urządzeniu.",
|
"xWillBeRemovedButRemainInstalled": "{} zostanie usunięty z Obtainium, ale pozostanie zainstalowany na urządzeniu.",
|
||||||
"removeSelectedAppsQuestion": "Usunąć wybrane aplikacje?",
|
"removeSelectedAppsQuestion": "Usunąć wybrane aplikacje?",
|
||||||
"removeSelectedApps": "Usuń wybrane aplikacje",
|
"removeSelectedApps": "Usuń wybrane aplikacje",
|
||||||
"updateX": "Zaktualizuj {}",
|
"updateX": "Zaktualizuj {}",
|
||||||
"installX": "Zainstaluj {}",
|
"installX": "Zainstaluj {}",
|
||||||
"markXTrackOnlyAsUpdated": "Oznacz {}\n(Tylko obserwowana)\njako zaktualizowaną",
|
"markXTrackOnlyAsUpdated": "Oznacz {}\n(tylko obserwowana)\njako zaktualizowaną",
|
||||||
"changeX": "Zmień {}",
|
"changeX": "Zmień {}",
|
||||||
"installUpdateApps": "Instaluj/aktualizuj aplikacje",
|
"installUpdateApps": "Instaluj/aktualizuj aplikacje",
|
||||||
"installUpdateSelectedApps": "Zainstaluj/zaktualizuj wybrane aplikacje",
|
"installUpdateSelectedApps": "Zainstaluj/zaktualizuj wybrane aplikacje",
|
||||||
"markXSelectedAppsAsUpdated": "Oznaczyć {} wybranych aplikacji jako zaktualizowane?",
|
"markXSelectedAppsAsUpdated": "Oznaczyć {} wybranych aplikacji jako zaktualizowane?",
|
||||||
|
"no": "Nie",
|
||||||
"yes": "Tak",
|
"yes": "Tak",
|
||||||
"markSelectedAppsUpdated": "Oznacz wybrane aplikacje jako zaktualizowane",
|
"markSelectedAppsUpdated": "Oznacz wybrane aplikacje jako zaktualizowane",
|
||||||
"pinToTop": "Przypnij",
|
"pinToTop": "Przypnij",
|
||||||
@@ -113,7 +99,7 @@
|
|||||||
"searchX": "Przeszukaj {}",
|
"searchX": "Przeszukaj {}",
|
||||||
"noResults": "Nie znaleziono wyników",
|
"noResults": "Nie znaleziono wyników",
|
||||||
"importX": "Importuj {}",
|
"importX": "Importuj {}",
|
||||||
"importedAppsIdDisclaimer": "Zaimportowane aplikacje mogą być wyświetlane jako „Niezainstalowane”.\nAby to naprawić, zainstaluj je ponownie za pomocą Obtainium.\nNie powinno to mieć wpływu na dane aplikacji.\n\nDotyczy tylko adresów URL i metod importu innych aplikacji.",
|
"importedAppsIdDisclaimer": "Zaimportowane aplikacje mogą być wyświetlane jako niezainstalowane.\nAby to naprawić, przeinstaluj je za pomocą Obtainium.\nNie powinno to mieć wpływu na dane aplikacji.\n\nDotyczy tylko adresu URL i innych metod importu.",
|
||||||
"importErrors": "Błędy importowania",
|
"importErrors": "Błędy importowania",
|
||||||
"importedXOfYApps": "Zaimportowano {} z {} aplikacji.",
|
"importedXOfYApps": "Zaimportowano {} z {} aplikacji.",
|
||||||
"followingURLsHadErrors": "Następujące adresy URL zawierały błędy:",
|
"followingURLsHadErrors": "Następujące adresy URL zawierały błędy:",
|
||||||
@@ -155,20 +141,20 @@
|
|||||||
"warning": "Uwaga",
|
"warning": "Uwaga",
|
||||||
"sourceIsXButPackageFromYPrompt": "Źródłem aplikacji jest '{}', ale pakiet wydania pochodzi z '{}'. Kontynuować?",
|
"sourceIsXButPackageFromYPrompt": "Źródłem aplikacji jest '{}', ale pakiet wydania pochodzi z '{}'. Kontynuować?",
|
||||||
"updatesAvailable": "Dostępne aktualizacje",
|
"updatesAvailable": "Dostępne aktualizacje",
|
||||||
"updatesAvailableNotifDescription": "Powiadamia użytkownika o dostępności aktualizacji dla jednej lub więcej aplikacji obserwowanych przez Obtainium",
|
"updatesAvailableNotifDescription": "Informuje o dostępności aktualizacji dla jednej lub więcej aplikacji obserwowanych przez Obtainium",
|
||||||
"noNewUpdates": "Brak nowych aktualizacji.",
|
"noNewUpdates": "Brak nowych aktualizacji.",
|
||||||
"xHasAnUpdate": "{} ma aktualizację.",
|
"xHasAnUpdate": "{} ma aktualizację.",
|
||||||
"appsUpdated": "Zaktualizowane aplikacje",
|
"appsUpdated": "Zaktualizowano aplikacje",
|
||||||
"appsUpdatedNotifDescription": "Powiadamia użytkownika, gdy jedna lub więcej aplikacji zostało zaktualizowanych w tle",
|
"appsUpdatedNotifDescription": "Informuje, gdy co najmniej jedna aplikacja została zaktualizowana w tle",
|
||||||
"xWasUpdatedToY": "{} zaktualizowano do {}.",
|
"xWasUpdatedToY": "{} zaktualizowano do {}.",
|
||||||
"errorCheckingUpdates": "Sprawdzanie błędów aktualizacji",
|
"errorCheckingUpdates": "Błąd sprawdzania aktualizacji",
|
||||||
"errorCheckingUpdatesNotifDescription": "Powiadomienie wyświetlane, gdy sprawdzanie aktualizacji w tle nie powiedzie się",
|
"errorCheckingUpdatesNotifDescription": "Jest wyświetlane, gdy sprawdzanie aktualizacji w tle nie powiedzie się",
|
||||||
"appsRemoved": "Usunięte aplikacje",
|
"appsRemoved": "Usunięte aplikacje",
|
||||||
"appsRemovedNotifDescription": "Powiadamia użytkownika, gdy jedna lub więcej aplikacji zostało usuniętych z powodu błędów wczytywania",
|
"appsRemovedNotifDescription": "Informuje, gdy co najmniej jedna aplikacja została usunięta z powodu błędów podczas wczytywania",
|
||||||
"xWasRemovedDueToErrorY": "Usunięto {} z powodu błędu: {}",
|
"xWasRemovedDueToErrorY": "Usunięto {} z powodu błędu: {}",
|
||||||
"completeAppInstallation": "Ukończenie instalacji aplikacji",
|
"completeAppInstallation": "Ukończenie instalacji aplikacji",
|
||||||
"obtainiumMustBeOpenToInstallApps": "Aby zainstalować aplikacje, Obtainium musi być otwarte",
|
"obtainiumMustBeOpenToInstallApps": "Aby zainstalować aplikacje, Obtainium musi być otwarte",
|
||||||
"completeAppInstallationNotifDescription": "Prosi użytkownika o powrót do Obtainium w celu dokończenia instalacji aplikacji",
|
"completeAppInstallationNotifDescription": "Informuje o możliwości powrotu do Obtainium w celu dokończenia instalacji aplikacji",
|
||||||
"checkingForUpdates": "Sprawdzanie aktualizacji",
|
"checkingForUpdates": "Sprawdzanie aktualizacji",
|
||||||
"checkingForUpdatesNotifDescription": "Tymczasowe powiadomienie pojawiające się podczas sprawdzania aktualizacji",
|
"checkingForUpdatesNotifDescription": "Tymczasowe powiadomienie pojawiające się podczas sprawdzania aktualizacji",
|
||||||
"pleaseAllowInstallPerm": "Pozwól Obtainium instalować aplikacje",
|
"pleaseAllowInstallPerm": "Pozwól Obtainium instalować aplikacje",
|
||||||
@@ -185,6 +171,7 @@
|
|||||||
"yesMarkUpdated": "Tak, oznacz jako zaktualizowane",
|
"yesMarkUpdated": "Tak, oznacz jako zaktualizowane",
|
||||||
"fdroid": "Oficjalny F-Droid",
|
"fdroid": "Oficjalny F-Droid",
|
||||||
"appIdOrName": "ID aplikacji lub nazwa",
|
"appIdOrName": "ID aplikacji lub nazwa",
|
||||||
|
"appId": "ID aplikacji",
|
||||||
"appWithIdOrNameNotFound": "Nie znaleziono aplikacji o tym identyfikatorze lub nazwie",
|
"appWithIdOrNameNotFound": "Nie znaleziono aplikacji o tym identyfikatorze lub nazwie",
|
||||||
"reposHaveMultipleApps": "Repozytoria mogą zawierać wiele aplikacji",
|
"reposHaveMultipleApps": "Repozytoria mogą zawierać wiele aplikacji",
|
||||||
"fdroidThirdPartyRepo": "Zewnętrzne repo F-Droid",
|
"fdroidThirdPartyRepo": "Zewnętrzne repo F-Droid",
|
||||||
@@ -199,7 +186,7 @@
|
|||||||
"disableVersionDetection": "Wyłącz wykrywanie wersji",
|
"disableVersionDetection": "Wyłącz wykrywanie wersji",
|
||||||
"noVersionDetectionExplanation": "Opcja ta powinna być używana tylko w przypadku aplikacji, w których wykrywanie wersji nie działa poprawnie.",
|
"noVersionDetectionExplanation": "Opcja ta powinna być używana tylko w przypadku aplikacji, w których wykrywanie wersji nie działa poprawnie.",
|
||||||
"downloadingX": "Pobieranie {}",
|
"downloadingX": "Pobieranie {}",
|
||||||
"downloadNotifDescription": "Powiadamia użytkownika o postępach w pobieraniu aplikacji",
|
"downloadNotifDescription": "Informuje o postępach w pobieraniu aplikacji",
|
||||||
"noAPKFound": "Nie znaleziono pakietu APK",
|
"noAPKFound": "Nie znaleziono pakietu APK",
|
||||||
"noVersionDetection": "Bez wykrywania wersji",
|
"noVersionDetection": "Bez wykrywania wersji",
|
||||||
"categorize": "Kategoryzuj",
|
"categorize": "Kategoryzuj",
|
||||||
@@ -230,12 +217,12 @@
|
|||||||
"autoApkFilterByArch": "Spróbuj filtrować pliki APK według architektury procesora, jeśli to możliwe",
|
"autoApkFilterByArch": "Spróbuj filtrować pliki APK według architektury procesora, jeśli to możliwe",
|
||||||
"overrideSource": "Nadpisz źródło",
|
"overrideSource": "Nadpisz źródło",
|
||||||
"dontShowAgain": "Nie pokazuj tego ponownie",
|
"dontShowAgain": "Nie pokazuj tego ponownie",
|
||||||
"dontShowTrackOnlyWarnings": "Nie pokazuj ostrzeżeń „Tylko obserwowana”",
|
"dontShowTrackOnlyWarnings": "Nie pokazuj ostrzeżeń \"Tylko obserwowana\"",
|
||||||
"dontShowAPKOriginWarnings": "Nie pokazuj ostrzeżeń o pochodzeniu APK",
|
"dontShowAPKOriginWarnings": "Nie pokazuj ostrzeżeń o pochodzeniu APK",
|
||||||
"moveNonInstalledAppsToBottom": "Przenieś niezainstalowane aplikacje na dół widoku aplikacji",
|
"moveNonInstalledAppsToBottom": "Przenieś niezainstalowane aplikacje na dół widoku aplikacji",
|
||||||
"gitlabPATLabel": "Osobisty token dostępu GitLab\n(Umożliwia wyszukiwanie i lepsze wykrywanie APK)",
|
"gitlabPATLabel": "Osobisty token dostępu GitLab\n(Umożliwia wyszukiwanie i lepsze wykrywanie APK)",
|
||||||
"about": "Więcej informacji",
|
"about": "Więcej informacji",
|
||||||
"requiresCredentialsInSettings": "Wymaga to dodatkowych poświadczeń (w Ustawieniach)",
|
"requiresCredentialsInSettings": "{}: Wymaga to dodatkowych poświadczeń (w Ustawieniach)",
|
||||||
"checkOnStart": "Sprawdź aktualizacje przy uruchomieniu",
|
"checkOnStart": "Sprawdź aktualizacje przy uruchomieniu",
|
||||||
"tryInferAppIdFromCode": "Spróbuj wywnioskować identyfikator aplikacji z kodu źródłowego",
|
"tryInferAppIdFromCode": "Spróbuj wywnioskować identyfikator aplikacji z kodu źródłowego",
|
||||||
"removeOnExternalUninstall": "Automatyczne usuń odinstalowane zewnętrznie aplikacje",
|
"removeOnExternalUninstall": "Automatyczne usuń odinstalowane zewnętrznie aplikacje",
|
||||||
@@ -243,49 +230,139 @@
|
|||||||
"checkUpdateOnDetailPage": "Sprawdzaj aktualizacje podczas otwierania strony szczegółów aplikacji",
|
"checkUpdateOnDetailPage": "Sprawdzaj aktualizacje podczas otwierania strony szczegółów aplikacji",
|
||||||
"disablePageTransitions": "Wyłącz animacje przejścia między stronami",
|
"disablePageTransitions": "Wyłącz animacje przejścia między stronami",
|
||||||
"reversePageTransitions": "Odwróć animacje przejścia pomiędzy stronami",
|
"reversePageTransitions": "Odwróć animacje przejścia pomiędzy stronami",
|
||||||
"minStarCount": "Minimum Star Count",
|
"minStarCount": "Minimalna ilość gwiazdek",
|
||||||
|
"addInfoBelow": "Dodaj tę informację poniżej.",
|
||||||
|
"addInfoInSettings": "Dodaj tę informację w Ustawieniach.",
|
||||||
|
"githubSourceNote": "Limit żądań GitHub można ominąć za pomocą klucza API.",
|
||||||
|
"gitlabSourceNote": "Pozyskiwanie pliku APK z GitLab może nie działać bez klucza API.",
|
||||||
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
|
"filterReleaseNotesByRegEx": "Filtruj informacje o wersji według wyrażenia regularnego",
|
||||||
|
"customLinkFilterRegex": "Filtruj linki APK według wyrażenia regularnego (domyślnie \".apk$\")",
|
||||||
|
"appsPossiblyUpdated": "Aplikacje mogły zostać zaktualizowane",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Powiadamia, gdy co najmniej jedna aktualizacja aplikacji została potencjalnie zastosowana w tle",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} być może zaktualizowano do {}.",
|
||||||
|
"enableBackgroundUpdates": "Włącz aktualizacje w tle",
|
||||||
|
"backgroundUpdateReqsExplanation": "Aktualizacje w tle mogą nie być możliwe dla wszystkich aplikacji.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "Powodzenie instalacji w tle można określić dopiero po otwarciu Obtainium.",
|
||||||
|
"verifyLatestTag": "Zweryfikuj najnowszy tag",
|
||||||
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "Nie znaleziono linku pośredniego",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Wyklucz z uaktualnień w tle (jeśli są włączone)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "Wyłącz aktualizacje w tle, gdy nie ma połączenia z Wi-Fi",
|
||||||
|
"autoSelectHighestVersionCode": "Automatycznie wybierz najwyższy kod wersji APK",
|
||||||
|
"versionExtractionRegEx": "Wyrażenie regularne wyodrębniające wersję",
|
||||||
|
"matchGroupToUse": "Dopasuj grupę do użycia dla wyrażenia regularnego wyodrębniania wersji",
|
||||||
|
"highlightTouchTargets": "Wyróżnij mniej oczywiste elementy dotykowe",
|
||||||
|
"pickExportDir": "Wybierz katalog eksportu",
|
||||||
|
"autoExportOnChanges": "Automatyczny eksport po wprowadzeniu zmian",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
|
"filterVersionsByRegEx": "Filtruj wersje według wyrażenia regularnego",
|
||||||
|
"trySelectingSuggestedVersionCode": "Spróbuj wybierać sugerowany kod wersji APK",
|
||||||
|
"dontSortReleasesList": "Utrzymaj kolejność wydań z interfejsu API",
|
||||||
|
"reverseSort": "Odwrotne sortowanie",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
|
"debugMenu": "Menu debugowania",
|
||||||
|
"bgTaskStarted": "Uruchomiono zadanie w tle - sprawdź logi.",
|
||||||
|
"runBgCheckNow": "Wymuś sprawdzenie aktualizacji w tle",
|
||||||
|
"versionExtractWholePage": "Zastosuj wyrażenie regularne wyodrębniania wersji dla całej strony",
|
||||||
|
"installing": "Instalacja",
|
||||||
|
"skipUpdateNotifications": "Pomiń powiadomienia o aktualizacjach",
|
||||||
|
"updatesAvailableNotifChannel": "Dostępne aktualizacje aplikacji",
|
||||||
|
"appsUpdatedNotifChannel": "Zaktualizowane aplikacje",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "Informuj o próbach aktualizacji",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Błędy sprawdzania aktualizacji",
|
||||||
|
"appsRemovedNotifChannel": "Usunięte aplikacje",
|
||||||
|
"downloadingXNotifChannel": "Pobieranie aplikacji",
|
||||||
|
"completeAppInstallationNotifChannel": "Ukończenie instalacji aplikacji",
|
||||||
|
"checkingForUpdatesNotifChannel": "Sprawdzanie dostępności aktualizacji",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Sprawdzaj tylko zainstalowane i obserwowane aplikacje pod kątem aktualizacji",
|
||||||
|
"supportFixedAPKURL": "Obsługuj stałe adresy URL APK",
|
||||||
|
"selectX": "Wybierz {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
|
"installMethod": "Installation method",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Usunąć aplikację?",
|
"one": "Usunąć aplikację?",
|
||||||
|
"few": "Usunąć aplikacje?",
|
||||||
|
"many": "Usunąć aplikacje?",
|
||||||
"other": "Usunąć aplikacje?"
|
"other": "Usunąć aplikacje?"
|
||||||
},
|
},
|
||||||
"tooManyRequestsTryAgainInMinutes": {
|
"tooManyRequestsTryAgainInMinutes": {
|
||||||
"one": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} min.",
|
"one": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} minutę",
|
||||||
"other": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} min."
|
"few": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} minuty",
|
||||||
|
"many": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} minut",
|
||||||
|
"other": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} minuty"
|
||||||
|
},
|
||||||
|
"bgUpdateGotErrorRetryInMinutes": {
|
||||||
|
"one": "Sprawdzanie aktualizacji w tle napotkało {}, zaplanuje ponowne sprawdzenie za {} minutę",
|
||||||
|
"few": "Sprawdzanie aktualizacji w tle napotkało {}, zaplanuje ponowne sprawdzenie za {} minuty",
|
||||||
|
"many": "Sprawdzanie aktualizacji w tle napotkało {}, zaplanuje ponowne sprawdzenie za {} minut",
|
||||||
|
"other": "Sprawdzanie aktualizacji w tle napotkało {}, zaplanuje ponowne sprawdzenie za {} minuty"
|
||||||
},
|
},
|
||||||
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
||||||
"one": "Podczas sprawdzania aktualizacji w tle znaleziono {} aktualizację - w razie potrzeby użytkownik zostanie o tym powiadomiony",
|
"one": "W tle znaleziono {} aktualizację - w razie potrzeby użytkownik zostanie o tym powiadomiony",
|
||||||
"other": "Podczas sprawdzania aktualizacji w tle znaleziono {} akt. - w razie potrzeby użytkownik zostanie o tym powiadomiony"
|
"few": "W tle znaleziono {} aktualizacje - w razie potrzeby użytkownik zostanie o tym powiadomiony",
|
||||||
|
"many": "W tle znaleziono {} aktualizacji - w razie potrzeby użytkownik zostanie o tym powiadomiony",
|
||||||
|
"other": "W tle znaleziono {} aktualizacje - w razie potrzeby użytkownik zostanie o tym powiadomiony"
|
||||||
},
|
},
|
||||||
"apps": {
|
"apps": {
|
||||||
"one": "{} aplik.",
|
"one": "{} apkę",
|
||||||
"other": "{} aplik."
|
"few": "{} apki",
|
||||||
|
"many": "{} apek",
|
||||||
|
"other": "{} apki"
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"one": "{} adres URL",
|
"one": "{} adres URL",
|
||||||
"other": "{} adr. URL"
|
"few": "{} adresy URL",
|
||||||
|
"many": "{} adresów URL",
|
||||||
|
"other": "{} adresy URL"
|
||||||
},
|
},
|
||||||
"minute": {
|
"minute": {
|
||||||
"one": "{} min.",
|
"one": "{} minuta",
|
||||||
"other": "{} min."
|
"few": "{} minuty",
|
||||||
|
"many": "{} minut",
|
||||||
|
"other": "{} minuty"
|
||||||
},
|
},
|
||||||
"hour": {
|
"hour": {
|
||||||
"one": "{} godz.",
|
"one": "{} godzina",
|
||||||
"other": "{} godz."
|
"few": "{} godziny",
|
||||||
|
"many": "{} godzin",
|
||||||
|
"other": "{} godziny"
|
||||||
},
|
},
|
||||||
"day": {
|
"day": {
|
||||||
"one": "{} dzień",
|
"one": "{} dzień",
|
||||||
|
"few": "{} dni",
|
||||||
|
"many": "{} dni",
|
||||||
"other": "{} dni"
|
"other": "{} dni"
|
||||||
},
|
},
|
||||||
"clearedNLogsBeforeXAfterY": {
|
"clearedNLogsBeforeXAfterY": {
|
||||||
"one": "Wyczyszczono {n} log (przed = {before}, po = {after})",
|
"one": "Wyczyszczono {n} log (przed = {before}, po = {after})",
|
||||||
"other": "Wyczyszczono logi: {n} (przed = {before}, po = {after})"
|
"few": "Wyczyszczono {n} logi (przed = {before}, po = {after})",
|
||||||
|
"many": "Wyczyszczono {n} logów (przed = {before}, po = {after})",
|
||||||
|
"other": "Wyczyszczono {n} logi (przed = {before}, po = {after})"
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesAvailable": {
|
"xAndNMoreUpdatesAvailable": {
|
||||||
"one": "{} i jeszcze 1 aplikacja mają aktualizacje.",
|
"one": "{} i 1 inna apka mają aktualizacje.",
|
||||||
"other": "{} i {} aplik. otrzymało aktualizacje."
|
"few": "{} i {} inne apki mają aktualizacje.",
|
||||||
|
"many": "{} i {} innych apek ma aktualizacje.",
|
||||||
|
"other": "{} i {} inne apki mają aktualizacje."
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesInstalled": {
|
"xAndNMoreUpdatesInstalled": {
|
||||||
"one": "Zaktualizowano {} i jeszcze 1 aplikację.",
|
"one": "Zaktualizowano {} i 1 inną apkę.",
|
||||||
"other": "Zaktualizowano {} i {} aplik."
|
"few": "{} i {} inne apki zostały zaktualizowane.",
|
||||||
|
"many": "{} i {} innych apek zostało zaktualizowanych.",
|
||||||
|
"other": "{} i {} inne apki zostały zaktualizowane."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} i 1 inna apka mogły zostać zaktualizowane.",
|
||||||
|
"few": "{} i {} inne apki mogły zostać zaktualizowane.",
|
||||||
|
"many": "{} i {} innych apek mogło zostać zaktualizowanych.",
|
||||||
|
"other": "{} i {} inne apki mogły zostać zaktualizowane."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
343
assets/translations/pt.json
Normal file
343
assets/translations/pt.json
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
{
|
||||||
|
"invalidURLForSource": "URL {} inválida",
|
||||||
|
"noReleaseFound": "Não foi possível encontrar uma versão adequada",
|
||||||
|
"noVersionFound": "Não foi possível encontrar uma versão",
|
||||||
|
"urlMatchesNoSource": "URL não corresponde a uma fonte conhecida",
|
||||||
|
"cantInstallOlderVersion": "Não é permitido instalar uma versão anterior de um aplicativo",
|
||||||
|
"appIdMismatch": "ID do pacote baixado não é igual ao ID do aplicativo instalado",
|
||||||
|
"functionNotImplemented": "Esta classe não implementou essa função",
|
||||||
|
"placeholder": "Espaço reservado",
|
||||||
|
"someErrors": "Alguns erros ocorreram",
|
||||||
|
"unexpectedError": "Erro inesperado",
|
||||||
|
"ok": "OK",
|
||||||
|
"and": "e",
|
||||||
|
"githubPATLabel": "Token de acesso pessoal do GitHub (Reduz tempos de espera)",
|
||||||
|
"includePrereleases": "Incluir pré-lançamentos",
|
||||||
|
"fallbackToOlderReleases": "Retornar para versões anteriores",
|
||||||
|
"filterReleaseTitlesByRegEx": "Filtrar títulos de versões por expressão regular",
|
||||||
|
"invalidRegEx": "Expressão regular inválida",
|
||||||
|
"noDescription": "Sem descrição",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"continue": "Continuar",
|
||||||
|
"requiredInBrackets": "(Necessário)",
|
||||||
|
"dropdownNoOptsError": "ERRO: O DROPDOWN DEVE TER PELO MENOS UMA OPÇÃO",
|
||||||
|
"colour": "Cor",
|
||||||
|
"githubStarredRepos": "Favoritados no GitHub",
|
||||||
|
"uname": "Nome de usuário",
|
||||||
|
"wrongArgNum": "Número de argumentos errado",
|
||||||
|
"xIsTrackOnly": "{} é 'Apenas monitorar'",
|
||||||
|
"source": "Fonte",
|
||||||
|
"app": "Aplicativo",
|
||||||
|
"appsFromSourceAreTrackOnly": "Os aplicativos desta fonte são 'Apenas monitorar'.",
|
||||||
|
"youPickedTrackOnly": "Você selecionou a opção 'Apenas monitorar'.",
|
||||||
|
"trackOnlyAppDescription": "As atualizações desse aplicativo serão monitoradas, mas o Obtainium não poderá baixá-lo ou instalá-lo.",
|
||||||
|
"cancelled": "Cancelado",
|
||||||
|
"appAlreadyAdded": "Aplicativo já adicionado",
|
||||||
|
"alreadyUpToDateQuestion": "Aplicativo já foi atualizado?",
|
||||||
|
"addApp": "Adicionar aplicativo",
|
||||||
|
"appSourceURL": "URL de origem do aplicativo",
|
||||||
|
"error": "Erro",
|
||||||
|
"add": "Adicionar",
|
||||||
|
"searchSomeSourcesLabel": "Procurar (Apenas algumas fontes)",
|
||||||
|
"search": "Procurar",
|
||||||
|
"additionalOptsFor": "Opções adicionais para {}",
|
||||||
|
"supportedSources": "Fontes compatíveis",
|
||||||
|
"trackOnlyInBrackets": "(Apenas monitorar)",
|
||||||
|
"searchableInBrackets": "(Pesquisável)",
|
||||||
|
"appsString": "Aplicativos",
|
||||||
|
"noApps": "Não há aplicativos",
|
||||||
|
"noAppsForFilter": "Sem aplicativos para filtrar",
|
||||||
|
"byX": "Por {}",
|
||||||
|
"percentProgress": "Progresso: {}%",
|
||||||
|
"pleaseWait": "Por favor, espere",
|
||||||
|
"updateAvailable": "Atualização disponível",
|
||||||
|
"estimateInBracketsShort": "(Aprox.)",
|
||||||
|
"notInstalled": "Não instalado",
|
||||||
|
"estimateInBrackets": "(Aproximado)",
|
||||||
|
"selectAll": "Selecionar todos",
|
||||||
|
"deselectX": "Deselecionar {}",
|
||||||
|
"xWillBeRemovedButRemainInstalled": "{} será removido do Obtainium mais permanecerá instalado no dispositivo.",
|
||||||
|
"removeSelectedAppsQuestion": "Remover aplicativos selecionados?",
|
||||||
|
"removeSelectedApps": "Remover aplicativos selecionados",
|
||||||
|
"updateX": "Atualizar {}",
|
||||||
|
"installX": "Instalar {}",
|
||||||
|
"markXTrackOnlyAsUpdated": "Marcar {}\n(Apenas monitorar)\ncomo Atualizado",
|
||||||
|
"changeX": "Mudar {}",
|
||||||
|
"installUpdateApps": "Instalar/Atualizar aplicativos",
|
||||||
|
"installUpdateSelectedApps": "Instalar/Atualizar aplicativos selecionados",
|
||||||
|
"markXSelectedAppsAsUpdated": "Marcar {} aplicativos selecionados como atualizados?",
|
||||||
|
"no": "Não",
|
||||||
|
"yes": "Sim",
|
||||||
|
"markSelectedAppsUpdated": "Marcar aplicativos selecionados como Atualizados",
|
||||||
|
"pinToTop": "Fixar no topo",
|
||||||
|
"unpinFromTop": "Desafixar do topo",
|
||||||
|
"resetInstallStatusForSelectedAppsQuestion": "Reiniciar status de instalação para aplicativos selecionados?",
|
||||||
|
"installStatusOfXWillBeResetExplanation": "O status de instalação de qualquer aplicativo selecionado será reiniciado.\n\nIsso pode ajudar quando uma versão de um aplicativo mostrada no Obtainium é incorreta devido a falhas ao atualizar ou outros problemas.",
|
||||||
|
"shareSelectedAppURLs": "Compartilhar URLs de aplicativos selecionados",
|
||||||
|
"resetInstallStatus": "Reiniciar status de Iistalação",
|
||||||
|
"more": "Mais",
|
||||||
|
"removeOutdatedFilter": "Remover filtro de aplicativos desatualizados",
|
||||||
|
"showOutdatedOnly": "Mostrar apenas aplicativos desatualizados",
|
||||||
|
"filter": "Filtro",
|
||||||
|
"filterActive": "Filtro *",
|
||||||
|
"filterApps": "Filtrar aplicativos",
|
||||||
|
"appName": "Nome do aplicativo",
|
||||||
|
"author": "Autor",
|
||||||
|
"upToDateApps": "Aplicativos tualizados",
|
||||||
|
"nonInstalledApps": "Aplicativos não instalados",
|
||||||
|
"importExport": "Importar/Exportar",
|
||||||
|
"settings": "Configurações",
|
||||||
|
"exportedTo": "Exportado para {}",
|
||||||
|
"obtainiumExport": "Exportar Obtainium",
|
||||||
|
"invalidInput": "Input Inválido",
|
||||||
|
"importedX": "Importado {}",
|
||||||
|
"obtainiumImport": "Importar Obtainium",
|
||||||
|
"importFromURLList": "Importar de lista de URLs",
|
||||||
|
"searchQuery": "Pesquisa",
|
||||||
|
"appURLList": "Lista de URLs de aplicativos",
|
||||||
|
"line": "Linha",
|
||||||
|
"searchX": "Pesquisa {}",
|
||||||
|
"noResults": "Nenhum resultado encontrado",
|
||||||
|
"importX": "Importar {}",
|
||||||
|
"importedAppsIdDisclaimer": "Aplicativos Importados podem ser mostrados incorretamente como \"Não Instalado\".\nPara consertar, reinstale-os usando o Obtainium.\nIsso não deve afetar dados do aplicativo.\n\nAfeta apenas métodos de importação de URL e de terceiros.",
|
||||||
|
"importErrors": "Erros de importação",
|
||||||
|
"importedXOfYApps": "{} de {} aplicativos importados.",
|
||||||
|
"followingURLsHadErrors": "As seguintes URLs apresentaram erros:",
|
||||||
|
"selectURL": "Selecionar URL",
|
||||||
|
"selectURLs": "Selecionar URLs",
|
||||||
|
"pick": "Escolher",
|
||||||
|
"theme": "Tema",
|
||||||
|
"dark": "Escuro",
|
||||||
|
"light": "Claro",
|
||||||
|
"followSystem": "Seguir o sistema",
|
||||||
|
"obtainium": "Obtainium",
|
||||||
|
"materialYou": "Material You",
|
||||||
|
"useBlackTheme": "Usar tema preto completamente escuro",
|
||||||
|
"appSortBy": "Classificar aplicativo por",
|
||||||
|
"authorName": "Autor/Nome",
|
||||||
|
"nameAuthor": "Nome/Autor",
|
||||||
|
"asAdded": "Como adicionado",
|
||||||
|
"appSortOrder": "Ordem de classificação de aplicativos",
|
||||||
|
"ascending": "Ascendente",
|
||||||
|
"descending": "Descendente",
|
||||||
|
"bgUpdateCheckInterval": "Intervalo de verificação de atualizações em segundo-plano",
|
||||||
|
"neverManualOnly": "Nunca - apenas manual",
|
||||||
|
"appearance": "Aparência",
|
||||||
|
"showWebInAppView": "Mostrar página da internet em informações do aplicativo",
|
||||||
|
"pinUpdates": "Fixar atualizações no topo da janela de aplicativos",
|
||||||
|
"updates": "Atualizações",
|
||||||
|
"sourceSpecific": "Específico a fonte",
|
||||||
|
"appSource": "Fonte do aplicativo",
|
||||||
|
"noLogs": "Sem logs",
|
||||||
|
"appLogs": "Logs do aplicativo",
|
||||||
|
"close": "Fechar",
|
||||||
|
"share": "Compartilhar",
|
||||||
|
"appNotFound": "Aplicativo não encontrado",
|
||||||
|
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||||
|
"pickAnAPK": "Selecionar um APK",
|
||||||
|
"appHasMoreThanOnePackage": "{} tem mais de um pacote:",
|
||||||
|
"deviceSupportsXArch": "Seu dispositivo suporta a arquitetura de CPU {}.",
|
||||||
|
"deviceSupportsFollowingArchs": "Seu dispositivo suporta as seguintes arquiteturas de CPU:",
|
||||||
|
"warning": "Aviso",
|
||||||
|
"sourceIsXButPackageFromYPrompt": "A fonte do aplicativo é '{}' mas a origem do pacote é '{}'. Continuar?",
|
||||||
|
"updatesAvailable": "Atualizações disponíveis",
|
||||||
|
"updatesAvailableNotifDescription": "Notifica o usuário quando atualizações de um ou mais aplicativos monitorados pelo Obtainium estão disponíveis",
|
||||||
|
"noNewUpdates": "Sem novas atualizações.",
|
||||||
|
"xHasAnUpdate": "{} tem uma atualização.",
|
||||||
|
"appsUpdated": "Aplicativos atualizados",
|
||||||
|
"appsUpdatedNotifDescription": "Notifica o usuário quando atualizações foram aplicadas em segundo-plano para um ou mais aplicativos ",
|
||||||
|
"xWasUpdatedToY": "{} foi atualizado para {}.",
|
||||||
|
"errorCheckingUpdates": "Erro ao procurar por atualizações",
|
||||||
|
"errorCheckingUpdatesNotifDescription": "Uma notificação que mostra quando a checagem por atualizações em segundo-plano falha",
|
||||||
|
"appsRemoved": "Aplicativos removidos",
|
||||||
|
"appsRemovedNotifDescription": "Notifica o usuário quando um ou mais aplicativos foram removidos devido a erros de carregamento",
|
||||||
|
"xWasRemovedDueToErrorY": "{} foi removido devido a este erro: {}",
|
||||||
|
"completeAppInstallation": "Instalação do aplicativo completa",
|
||||||
|
"obtainiumMustBeOpenToInstallApps": "Obtainium deve estar aberto para instalar os aplicativos",
|
||||||
|
"completeAppInstallationNotifDescription": "Pede ao usuário que retorne ao Obtainium para finalizar a instalação de um aplicativo",
|
||||||
|
"checkingForUpdates": "Verificando atualizações",
|
||||||
|
"checkingForUpdatesNotifDescription": "Notificação transiente que aparece quando o Obtainium está verificando se há atualizações",
|
||||||
|
"pleaseAllowInstallPerm": "Por favor, permita que o Obtainium possa instalar aplicativos",
|
||||||
|
"trackOnly": "Apenas monitorar",
|
||||||
|
"errorWithHttpStatusCode": "Erro {}",
|
||||||
|
"versionCorrectionDisabled": "Correção de versão desativada (plugin parece não funcionar)",
|
||||||
|
"unknown": "Desconhecido",
|
||||||
|
"none": "Nenhum",
|
||||||
|
"never": "Nunca",
|
||||||
|
"latestVersionX": "Última versão: {}",
|
||||||
|
"installedVersionX": "Versão instalada: {}",
|
||||||
|
"lastUpdateCheckX": "Última verificação de atualizações: {}",
|
||||||
|
"remove": "Remover",
|
||||||
|
"yesMarkUpdated": "Sim, marcar como Atualizado",
|
||||||
|
"fdroid": "F-Droid Official",
|
||||||
|
"appIdOrName": "ID do aplicativo ou nome",
|
||||||
|
"appId": "ID do aplicativo",
|
||||||
|
"appWithIdOrNameNotFound": "Nenhum aplicativo foi encontrado com esse ID ou nome",
|
||||||
|
"reposHaveMultipleApps": "Repositórios podem conter multiplos aplicativos",
|
||||||
|
"fdroidThirdPartyRepo": "Repositórios de terceiros F-Droid",
|
||||||
|
"steam": "Steam",
|
||||||
|
"steamMobile": "Steam Mobile",
|
||||||
|
"steamChat": "Steam Chat",
|
||||||
|
"install": "Instalar",
|
||||||
|
"markInstalled": "Marcar instalado",
|
||||||
|
"update": "Atualizar",
|
||||||
|
"markUpdated": "Marcar como atualizado",
|
||||||
|
"additionalOptions": "Opções adicionais",
|
||||||
|
"disableVersionDetection": "Desativar detecção de versão",
|
||||||
|
"noVersionDetectionExplanation": "Essa opção deve apenas ser usada por aplicativos onde a detecção de versão não funciona corretamente.",
|
||||||
|
"downloadingX": "Baixando {}",
|
||||||
|
"downloadNotifDescription": "Notifica o usuário o progresso do download de um aplicativo",
|
||||||
|
"noAPKFound": "APK não encontrado",
|
||||||
|
"noVersionDetection": "Sem detecção de versão",
|
||||||
|
"categorize": "Categorizar",
|
||||||
|
"categories": "Categorias",
|
||||||
|
"category": "Categoria",
|
||||||
|
"noCategory": "Sem categoria",
|
||||||
|
"noCategories": "Sem categoria",
|
||||||
|
"deleteCategoriesQuestion": "Deletar categorias?",
|
||||||
|
"categoryDeleteWarning": "Todos os aplicativos em categorias removidas serão descategorizados.",
|
||||||
|
"addCategory": "Adicionar categoria",
|
||||||
|
"label": "Etiqueta",
|
||||||
|
"language": "Linguagem",
|
||||||
|
"copiedToClipboard": "Copiado para a área de transferência",
|
||||||
|
"storagePermissionDenied": "Permissão de armazenamento negada",
|
||||||
|
"selectedCategorizeWarning": "Isso vai substituir qualquer configuração de categoria para os aplicativos selecionados.",
|
||||||
|
"filterAPKsByRegEx": "Filtrar APKs por expressão regular",
|
||||||
|
"removeFromObtainium": "Remover do Obtainium",
|
||||||
|
"uninstallFromDevice": "Desinstalar do dispositivo",
|
||||||
|
"onlyWorksWithNonVersionDetectApps": "Apenas funciona para aplicativos com detecção de versão desativada.",
|
||||||
|
"releaseDateAsVersion": "Usar data de lançamento como versão",
|
||||||
|
"releaseDateAsVersionExplanation": "Esta opção só deve ser usada para aplicativos onde a detecção de versão não funciona corretamente, mas há uma data de lançamento disponível.",
|
||||||
|
"changes": "Mudanças",
|
||||||
|
"releaseDate": "Data de lançamento",
|
||||||
|
"importFromURLsInFile": "Importar de URLs em arquivo (como OPML)",
|
||||||
|
"versionDetection": "Detecção de Versão",
|
||||||
|
"standardVersionDetection": "Detecção de versão padrão",
|
||||||
|
"groupByCategory": "Agroupar por categoria",
|
||||||
|
"autoApkFilterByArch": "Tente filtrar APKs por arquitetura de CPU, se possível",
|
||||||
|
"overrideSource": "Substituir fonte",
|
||||||
|
"dontShowAgain": "Não mostrar isso novamente",
|
||||||
|
"dontShowTrackOnlyWarnings": "Não mostrar avisos 'Apenas Monitorar'",
|
||||||
|
"dontShowAPKOriginWarnings": "Não mostrar avisos de origem da APK",
|
||||||
|
"moveNonInstalledAppsToBottom": "Mover aplicativos não instalados para o fundo da lista de aplicativos",
|
||||||
|
AQUI
|
||||||
|
"gitlabPATLabel": "Token de Acceso Pessoal do Gitlab\n(Ativa Pesquisa e Melhor Descoberta de APKs)",
|
||||||
|
"about": "Sobre",
|
||||||
|
"requiresCredentialsInSettings": "{}: Isso requer credenciais adicionais (em Configurações)",
|
||||||
|
"checkOnStart": "Checar por atualizações ao iniciar ",
|
||||||
|
"tryInferAppIdFromCode": "Tente inferir o ID do aplicativo pelo código-fonte",
|
||||||
|
"removeOnExternalUninstall": "Remover automaticamente aplicativos desinstalados externamente",
|
||||||
|
"pickHighestVersionCode": "Auto-selecionar o maior numero de versão do APK",
|
||||||
|
"checkUpdateOnDetailPage": "Checar por atualizações ao abrir a página de detalhes de um aplicativo",
|
||||||
|
"disablePageTransitions": "Desativar animações de transição de pagina",
|
||||||
|
"reversePageTransitions": "Reverter animações de transição de pagina",
|
||||||
|
"minStarCount": "Contagem Minima de Estrelas",
|
||||||
|
"addInfoBelow": "Adicionar essa informação abaixo.",
|
||||||
|
"addInfoInSettings": "Adicionar essa informação nas configurações.",
|
||||||
|
"githubSourceNote": "A limitação de taxa do GitHub pode ser evitada usando uma chave de API.",
|
||||||
|
"gitlabSourceNote": "A extração de APK do GitLab pode não funcionar sem uma chave de API.",
|
||||||
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
|
"filterReleaseNotesByRegEx": "Filtrar Notas de Lançamento por Expressão Regular",
|
||||||
|
"customLinkFilterRegex": "Filtro de Link Personalizado por Expressão Regular (Padrão '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "Tentativas de atualização de aplicativos",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Notifica o usuário de que atualizações de um ou mais aplicativos foram potencialmente aplicadas em segundo-plano",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} pode ter sido atualizado para {}.",
|
||||||
|
"enableBackgroundUpdates": "Ativar atualizações em segundo-plano",
|
||||||
|
"backgroundUpdateReqsExplanation": "Atualizações em segundo-plano podem não ser possíveis para todos os aplicativos.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "O sucesso de uma instalação em segundo-plano só pode ser determinado quando o Obtainium é aberto.",
|
||||||
|
"verifyLatestTag": "Verifique a 'ultima' etiqueta",
|
||||||
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "Link intermediário não encontrado",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Isento de atualizações em segundo-plano (se ativadas)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "Desative atualizações em segundo-plano quando não estiver em WiFi",
|
||||||
|
"autoSelectHighestVersionCode": "Auto-selecionar o maior codigo de versão",
|
||||||
|
"versionExtractionRegEx": "RegEx para Extração de Versão",
|
||||||
|
"matchGroupToUse": "Grupo de Seleção para Usar",
|
||||||
|
"highlightTouchTargets": "Destaque areas de toque menos óbvias",
|
||||||
|
"pickExportDir": "Escolher Diretorio de Exportação",
|
||||||
|
"autoExportOnChanges": "Auto-exportar em mudanças",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
|
"filterVersionsByRegEx": "Filtrar Versões por Expressão Regular",
|
||||||
|
"trySelectingSuggestedVersionCode": "Tente selecionar a versão sugerida",
|
||||||
|
"dontSortReleasesList": "Reter a ordem de lançamento da API",
|
||||||
|
"reverseSort": "Ordenação reversa",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
|
"debugMenu": "Menu Debug",
|
||||||
|
"bgTaskStarted": "Tarefa em segundo-plano iniciada - verifique os logs.",
|
||||||
|
"runBgCheckNow": "Execute a verificação de atualização em segundo-plano agora",
|
||||||
|
"versionExtractWholePage": "Aplicar Regex de Extração de Versão à Página Inteira",
|
||||||
|
"installing": "Instalando",
|
||||||
|
"skipUpdateNotifications": "Pular notificações de update",
|
||||||
|
"updatesAvailableNotifChannel": "Atualizações Disponíveis",
|
||||||
|
"appsUpdatedNotifChannel": "Aplicativos Atualizados",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "Tentativas de atualização de aplicativos",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Erro ao Procurar por Atualizações",
|
||||||
|
"appsRemovedNotifChannel": "Aplicativos Removidos",
|
||||||
|
"downloadingXNotifChannel": "Baixando {}",
|
||||||
|
"completeAppInstallationNotifChannel": "Instalação completa do aplicativo",
|
||||||
|
"checkingForUpdatesNotifChannel": "Checando por Atualizações",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Apenas checar apps instalados e 'Apenas Seguir' por updates",
|
||||||
|
"supportFixedAPKURL": "Suporte APK com URLs fixas",
|
||||||
|
"selectX": "Selecionar {}",
|
||||||
|
"parallelDownloads": "Permitir downloads paralelos",
|
||||||
|
"installMethod": "Método de instalação",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku não está rodando",
|
||||||
|
"removeAppQuestion": {
|
||||||
|
"one": "Remover aplicativo?",
|
||||||
|
"other": "Remover aplicativos?"
|
||||||
|
},
|
||||||
|
"tooManyRequestsTryAgainInMinutes": {
|
||||||
|
"one": "Muitas solicitações (taxa limitada) - tente novamente em {} minuto",
|
||||||
|
"other": "Muitas solicitações (taxa limitada) - tente novamente em {} minutos"
|
||||||
|
},
|
||||||
|
"bgUpdateGotErrorRetryInMinutes": {
|
||||||
|
"one": "A verificação de atualizações em segundo-plano encontrou um {}, agendada uma nova verificação em {} minuto",
|
||||||
|
"other": "A verificação de atualizações em segundo-plano encontrou um {}, agendada uma nova verificação em {} minutos"
|
||||||
|
},
|
||||||
|
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
||||||
|
"one": "A verificação de atualizações em segundo-plano encontrou {} atualização, o usuário sera notificado caso necessário",
|
||||||
|
"other": "A verificação de atualizações em segundo-plano encontrou {} atualizações, o usuário sera notificado caso necessário"
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"one": "{} Aplicativo",
|
||||||
|
"other": "{} Aplicativos"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"one": "{} URL",
|
||||||
|
"other": "{} URLs"
|
||||||
|
},
|
||||||
|
"minute": {
|
||||||
|
"one": "{} Minuto",
|
||||||
|
"other": "{} Minutos"
|
||||||
|
},
|
||||||
|
"hour": {
|
||||||
|
"one": "{} Hora",
|
||||||
|
"other": "{} Horas"
|
||||||
|
},
|
||||||
|
"day": {
|
||||||
|
"one": "{} Dia",
|
||||||
|
"other": "{} Dias"
|
||||||
|
},
|
||||||
|
"clearedNLogsBeforeXAfterY": {
|
||||||
|
"one": "Limpo {n} log (antes = {antes}, depois = {depois})",
|
||||||
|
"other": "Limpados {n} logs (antes = {antes}, depois = {depois})"
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesAvailable": {
|
||||||
|
"one": "{} e 1 outro app tem atualizações.",
|
||||||
|
"other": "{} e {} outros apps tem atualizações."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesInstalled": {
|
||||||
|
"one": "{} e 1 outro app foi atualizado.",
|
||||||
|
"other": "{} e {} outros apps foram atualizados."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} e 1 outro app pode ter sido atualizado.",
|
||||||
|
"other": "{} e {} outros apps podem ter sido atualizados."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"invalidURLForSource": "Неверный URL-адрес {} приложения",
|
"invalidURLForSource": "Неверный URL-адрес приложения: {}",
|
||||||
"noReleaseFound": "Не удалось найти подходящий релиз",
|
"noReleaseFound": "Не удалось найти подходящий релиз",
|
||||||
"noVersionFound": "Не удалось определить версию релиза",
|
"noVersionFound": "Не удалось определить версию релиза",
|
||||||
"urlMatchesNoSource": "URL-адрес не соответствует известному источнику",
|
"urlMatchesNoSource": "URL-адрес не соответствует известному источнику",
|
||||||
@@ -9,49 +9,41 @@
|
|||||||
"placeholder": "Заполнитель",
|
"placeholder": "Заполнитель",
|
||||||
"someErrors": "Возникли некоторые ошибки",
|
"someErrors": "Возникли некоторые ошибки",
|
||||||
"unexpectedError": "Неожиданная ошибка",
|
"unexpectedError": "Неожиданная ошибка",
|
||||||
"ok": "Окей",
|
"ok": "Ok",
|
||||||
"and": "и",
|
"and": "и",
|
||||||
"startedBgUpdateTask": "Запущена задача фоновой проверки обновлений",
|
"githubPATLabel": "Персональный токен доступа GitHub\n(увеличивает лимит запросов)",
|
||||||
"bgUpdateIgnoreAfterIs": "Параметр игнорирования фоновых обновлений: {}",
|
|
||||||
"startedActualBGUpdateCheck": "Запущена фактическая проверка фоновых обновлений",
|
|
||||||
"bgUpdateTaskFinished": "Завершена задача фоновой проверки обновлений",
|
|
||||||
"firstRun": "Это первый запуск Obtainium",
|
|
||||||
"settingUpdateCheckIntervalTo": "Установка интервала проверки обновлений: {}",
|
|
||||||
"githubPATLabel": "Персональный токен доступа GitHub (увеличивает лимит запросов)",
|
|
||||||
"githubPATHint": "Токен доступа должен быть в формате: имя_пользователя:токен",
|
|
||||||
"githubPATFormat": "имя_пользователя:токен",
|
|
||||||
"includePrereleases": "Включить предварительные релизы",
|
"includePrereleases": "Включить предварительные релизы",
|
||||||
"fallbackToOlderReleases": "Откатиться к более старым версиям",
|
"fallbackToOlderReleases": "Откатываться к предыдущей версии",
|
||||||
"filterReleaseTitlesByRegEx": "Фильтровать заголовки релизов\nс помощью регулярного выражения",
|
"filterReleaseTitlesByRegEx": "Фильтровать заголовки релизов\n(регулярное выражение)",
|
||||||
"invalidRegEx": "Неверное регулярное выражение",
|
"invalidRegEx": "Неверное регулярное выражение",
|
||||||
"noDescription": "Нет описания",
|
"noDescription": "Нет описания",
|
||||||
"cancel": "Отмена",
|
"cancel": "Отмена",
|
||||||
"continue": "Продолжить",
|
"continue": "Продолжить",
|
||||||
"requiredInBrackets": "(Обязательно)",
|
"requiredInBrackets": "(обязательно)",
|
||||||
"dropdownNoOptsError": "Ошибка: Выпадающий список должен содержать хотя бы одну опцию",
|
"dropdownNoOptsError": "Ошибка: в выпадающем списке должна быть выбрана хотя бы одна настройка",
|
||||||
"colour": "Цвет",
|
"colour": "Цвет",
|
||||||
"githubStarredRepos": "Помеченные звездочкой репозитории на GitHub",
|
"githubStarredRepos": "Избранные репозитории GitHub",
|
||||||
"uname": "Имя пользователя",
|
"uname": "Имя пользователя",
|
||||||
"wrongArgNum": "Неправильное количество предоставленных аргументов",
|
"wrongArgNum": "Неправильное количество предоставленных аргументов",
|
||||||
"xIsTrackOnly": "{} только для отслеживания",
|
"xIsTrackOnly": "{} только для отслеживания",
|
||||||
"source": "Источник",
|
"source": "Источник",
|
||||||
"app": "Приложение",
|
"app": "Приложение",
|
||||||
"appsFromSourceAreTrackOnly": "Приложения из этого источника являются 'только для отслеживания'.",
|
"appsFromSourceAreTrackOnly": "Приложения из этого источника настроены только для отслеживания",
|
||||||
"youPickedTrackOnly": "Вы выбрали опцию 'Только для отслеживания'.",
|
"youPickedTrackOnly": "Вы выбрали опцию 'Только для отслеживания'",
|
||||||
"trackOnlyAppDescription": "Приложение будет отслеживаться на предмет обновлений, но Obtainium не сможет загрузить или установить его.",
|
"trackOnlyAppDescription": "Приложение будет отслеживаться на предмет обновлений, но Obtainium не сможет загрузить или установить его",
|
||||||
"cancelled": "Отменено",
|
"cancelled": "Отменено",
|
||||||
"appAlreadyAdded": "Приложение уже добавлено",
|
"appAlreadyAdded": "Приложение уже добавлено",
|
||||||
"alreadyUpToDateQuestion": "Приложение уже обновлено?",
|
"alreadyUpToDateQuestion": "Приложение уже обновлено?",
|
||||||
"addApp": "Добавить приложение",
|
"addApp": "Добавить",
|
||||||
"appSourceURL": "URL-источник приложения",
|
"appSourceURL": "URL-источник приложения",
|
||||||
"error": "Ошибка",
|
"error": "Ошибка",
|
||||||
"add": "Добавить",
|
"add": "Добавить",
|
||||||
"searchSomeSourcesLabel": "Поиск (только в некоторых источниках)",
|
"searchSomeSourcesLabel": "Поиск (в некоторых источниках)",
|
||||||
"search": "Поиск",
|
"search": "Поиск",
|
||||||
"additionalOptsFor": "Дополнительные опции для {}",
|
"additionalOptsFor": "Дополнительные настройки для {}",
|
||||||
"supportedSourcesBelow": "Поддерживаемые источники:",
|
"supportedSources": "Поддерживаемые источники",
|
||||||
"trackOnlyInBrackets": "(Только для отслеживания)",
|
"trackOnlyInBrackets": "(только отслеживание)",
|
||||||
"searchableInBrackets": "(Поиск)",
|
"searchableInBrackets": "(поиск)",
|
||||||
"appsString": "Приложения",
|
"appsString": "Приложения",
|
||||||
"noApps": "Нет приложений",
|
"noApps": "Нет приложений",
|
||||||
"noAppsForFilter": "Нет приложений для фильтра",
|
"noAppsForFilter": "Нет приложений для фильтра",
|
||||||
@@ -62,9 +54,9 @@
|
|||||||
"estimateInBracketsShort": "(Оценка)",
|
"estimateInBracketsShort": "(Оценка)",
|
||||||
"notInstalled": "Не установлено",
|
"notInstalled": "Не установлено",
|
||||||
"estimateInBrackets": "(Оценка)",
|
"estimateInBrackets": "(Оценка)",
|
||||||
"selectAll": "Выбрать все",
|
"selectAll": "Выбрать всё",
|
||||||
"deselectN": "Отменить выбор {}",
|
"deselectX": "Отменить выбор {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} будет удалено из Obtainium, но останется установленным на устройстве.",
|
"xWillBeRemovedButRemainInstalled": "{} будет удалено из Obtainium, но останется на устройстве",
|
||||||
"removeSelectedAppsQuestion": "Удалить выбранные приложения?",
|
"removeSelectedAppsQuestion": "Удалить выбранные приложения?",
|
||||||
"removeSelectedApps": "Удалить выбранные приложения",
|
"removeSelectedApps": "Удалить выбранные приложения",
|
||||||
"updateX": "Обновить {}",
|
"updateX": "Обновить {}",
|
||||||
@@ -73,17 +65,17 @@
|
|||||||
"changeX": "Изменить {}",
|
"changeX": "Изменить {}",
|
||||||
"installUpdateApps": "Установить/Обновить приложения",
|
"installUpdateApps": "Установить/Обновить приложения",
|
||||||
"installUpdateSelectedApps": "Установить/Обновить выбранные приложения",
|
"installUpdateSelectedApps": "Установить/Обновить выбранные приложения",
|
||||||
"markXSelectedAppsAsUpdated": "Отметить {} выбранные приложения как обновленные?",
|
"markXSelectedAppsAsUpdated": "Выбрано приложений: {}. Отметить как обновлённые?",
|
||||||
"no": "Нет",
|
"no": "Нет",
|
||||||
"yes": "Да",
|
"yes": "Да",
|
||||||
"markSelectedAppsUpdated": "Отметить выбранные приложения как обновленные",
|
"markSelectedAppsUpdated": "Отметить выбранные приложения как обновлённые",
|
||||||
"pinToTop": "Закрепить сверху",
|
"pinToTop": "Закрепить сверху",
|
||||||
"unpinFromTop": "Открепить",
|
"unpinFromTop": "Открепить",
|
||||||
"resetInstallStatusForSelectedAppsQuestion": "Сбросить статус установки для выбранных приложений?",
|
"resetInstallStatusForSelectedAppsQuestion": "Сбросить статус установки для выбранных приложений?",
|
||||||
"installStatusOfXWillBeResetExplanation": "Статус установки для выбранных приложений будет сброшен.\n\nЭто может помочь, если версия приложения, отображаемая в Obtainium, неправильная из-за неудачных обновлений или других проблем.",
|
"installStatusOfXWillBeResetExplanation": "Статус установки для выбранных приложений будет сброшен.\n\nЭто может помочь, если версия приложения, отображаемая в Obtainium, некорректная — из-за неудачных обновлений или других проблем",
|
||||||
"shareSelectedAppURLs": "Поделиться выбранными URL-адресами приложений",
|
"shareSelectedAppURLs": "Поделиться выбранными URL-адресами приложений",
|
||||||
"resetInstallStatus": "Сбросить статус установки",
|
"resetInstallStatus": "Сбросить статус установки",
|
||||||
"more": "Еще",
|
"more": "Ещё",
|
||||||
"removeOutdatedFilter": "Удалить фильтр для устаревших приложений",
|
"removeOutdatedFilter": "Удалить фильтр для устаревших приложений",
|
||||||
"showOutdatedOnly": "Показывать только устаревшие приложения",
|
"showOutdatedOnly": "Показывать только устаревшие приложения",
|
||||||
"filter": "Фильтр",
|
"filter": "Фильтр",
|
||||||
@@ -93,7 +85,7 @@
|
|||||||
"author": "Автор",
|
"author": "Автор",
|
||||||
"upToDateApps": "Приложения со свежими обновлениями",
|
"upToDateApps": "Приложения со свежими обновлениями",
|
||||||
"nonInstalledApps": "Неустановленные приложения",
|
"nonInstalledApps": "Неустановленные приложения",
|
||||||
"importExport": "Импорт/экспорт",
|
"importExport": "Данные",
|
||||||
"settings": "Настройки",
|
"settings": "Настройки",
|
||||||
"exportedTo": "Экспортировано в {}",
|
"exportedTo": "Экспортировано в {}",
|
||||||
"obtainiumExport": "Экспорт из Obtainium",
|
"obtainiumExport": "Экспорт из Obtainium",
|
||||||
@@ -107,63 +99,62 @@
|
|||||||
"searchX": "Поиск {}",
|
"searchX": "Поиск {}",
|
||||||
"noResults": "Результатов не найдено",
|
"noResults": "Результатов не найдено",
|
||||||
"importX": "Импорт {}",
|
"importX": "Импорт {}",
|
||||||
"importedAppsIdDisclaimer": "Импортированные приложения могут неверно отображаться как 'Не установлены'.\nДля исправления этой проблемы повторно установите их через Obtainium.\nЭто не должно повлиять на данные приложения.\n\nПроблемы возникают только при импорте из URL-адреса и сторонних источников.",
|
"importedAppsIdDisclaimer": "Импортированные приложения могут неверно отображаться как неустановленные.\nДля исправления этой проблемы повторно установите их через Obtainium.\nЭто не должно повлиять на данные приложения.\n\nПроблемы возникают только при импорте из URL-адреса и сторонних источников",
|
||||||
"importErrors": "Ошибка импорта",
|
"importErrors": "Ошибка импорта",
|
||||||
"importedXOfYApps": "Импортировано {} из {} приложений.",
|
"importedXOfYApps": "Импортировано приложений: {} из {}",
|
||||||
"followingURLsHadErrors": "При импорте следующие URL-адреса содержали ошибки:",
|
"followingURLsHadErrors": "При импорте следующие URL-адреса содержали ошибки:",
|
||||||
"okay": "Окей",
|
|
||||||
"selectURL": "Выбрать URL-адрес",
|
"selectURL": "Выбрать URL-адрес",
|
||||||
"selectURLs": "Выбрать URL-адреса",
|
"selectURLs": "Выбрать URL-адреса",
|
||||||
"pick": "Выбрать",
|
"pick": "Выбрать",
|
||||||
"theme": "Тема",
|
"theme": "Тема",
|
||||||
"dark": "Темная",
|
"dark": "Тёмная",
|
||||||
"light": "Светлая",
|
"light": "Светлая",
|
||||||
"followSystem": "Как в системе",
|
"followSystem": "Системная",
|
||||||
"obtainium": "Obtainium",
|
"obtainium": "Obtainium",
|
||||||
"materialYou": "Material You",
|
"materialYou": "Material You",
|
||||||
"useBlackTheme": "Использовать чёрную тему",
|
"useBlackTheme": "Использовать чёрную тему",
|
||||||
"appSortBy": "Сортировка приложений по",
|
"appSortBy": "Сортировка приложений",
|
||||||
"authorName": "Автор/Название",
|
"authorName": "Автор/Название",
|
||||||
"nameAuthor": "Название/Автор",
|
"nameAuthor": "Название/Автор",
|
||||||
"asAdded": "В порядке добавления",
|
"asAdded": "В порядке добавления",
|
||||||
"appSortOrder": "Порядок сортировки приложений",
|
"appSortOrder": "Порядок",
|
||||||
"ascending": "По возрастанию",
|
"ascending": "По возрастанию",
|
||||||
"descending": "По убыванию",
|
"descending": "По убыванию",
|
||||||
"bgUpdateCheckInterval": "Интервал проверки обновлений в фоновом режиме",
|
"bgUpdateCheckInterval": "Интервал проверки обновлений в фоновом режиме",
|
||||||
"neverManualOnly": "Никогда - Только вручную",
|
"neverManualOnly": "Никогда — только вручную",
|
||||||
"appearance": "Внешний вид",
|
"appearance": "Внешний вид",
|
||||||
"showWebInAppView": "Показывать исходную веб-страницу в представлении приложения",
|
"showWebInAppView": "Показывать исходную веб-страницу на странице приложения",
|
||||||
"pinUpdates": "Закрепить обновления сверху списка приложений",
|
"pinUpdates": "Отображать обновления приложений сверху списка",
|
||||||
"updates": "Обновления",
|
"updates": "Обновления",
|
||||||
"sourceSpecific": "Специфика источника",
|
"sourceSpecific": "Настройки источников",
|
||||||
"appSource": "Источник приложения",
|
"appSource": "Исходный код",
|
||||||
"noLogs": "Нет журналов",
|
"noLogs": "Нет журналов",
|
||||||
"appLogs": "Журналы приложений",
|
"appLogs": "Логи",
|
||||||
"close": "Закрыть",
|
"close": "Закрыть",
|
||||||
"share": "Поделиться",
|
"share": "Поделиться",
|
||||||
"appNotFound": "Приложение не найдено",
|
"appNotFound": "Приложение не найдено",
|
||||||
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||||
"pickAnAPK": "Выберите APK-файл",
|
"pickAnAPK": "Выберите APK-файл",
|
||||||
"appHasMoreThanOnePackage": "{} имеет более одного пакета:",
|
"appHasMoreThanOnePackage": "{} имеет более одного пакета:",
|
||||||
"deviceSupportsXArch": "Ваше устройство поддерживает архитектуру процессора {}.",
|
"deviceSupportsXArch": "Ваше устройство поддерживает архитектуру процессора {}",
|
||||||
"deviceSupportsFollowingArchs": "Ваше устройство поддерживает следующие архитектуры процессора:",
|
"deviceSupportsFollowingArchs": "Ваше устройство поддерживает следующие архитектуры процессора:",
|
||||||
"warning": "Предупреждение",
|
"warning": "Предупреждение",
|
||||||
"sourceIsXButPackageFromYPrompt": "Источник приложения - '{}', но пакет для установки получен из '{}'. Продолжить?",
|
"sourceIsXButPackageFromYPrompt": "Источник приложения — '{}', но пакет для установки получен из '{}'. Продолжить?",
|
||||||
"updatesAvailable": "Доступны обновления",
|
"updatesAvailable": "Доступны обновления",
|
||||||
"updatesAvailableNotifDescription": "Уведомляет пользователя о наличии обновлений для одного или нескольких приложений, отслеживаемых Obtainium",
|
"updatesAvailableNotifDescription": "Уведомляет о наличии обновлений для одного или нескольких приложений в Obtainium",
|
||||||
"noNewUpdates": "Нет новых обновлений.",
|
"noNewUpdates": "Нет новых обновлений",
|
||||||
"xHasAnUpdate": "{} есть обновление.",
|
"xHasAnUpdate": "{} есть обновление",
|
||||||
"appsUpdated": "Приложения обновлены",
|
"appsUpdated": "Приложения обновлены",
|
||||||
"appsUpdatedNotifDescription": "Уведомляет пользователя о том, что обновления для одного или нескольких приложений были применены в фоновом режиме",
|
"appsUpdatedNotifDescription": "Уведомляет об обновлении одного или нескольких приложений в фоновом режиме",
|
||||||
"xWasUpdatedToY": "{} была обновлена до версии {}.",
|
"xWasUpdatedToY": "{} была обновлена до версии {}",
|
||||||
"errorCheckingUpdates": "Ошибка при проверке обновлений",
|
"errorCheckingUpdates": "Ошибка при проверке обновлений",
|
||||||
"errorCheckingUpdatesNotifDescription": "Уведомление, которое появляется, когда проверка обновлений в фоновом режиме завершилась с ошибкой",
|
"errorCheckingUpdatesNotifDescription": "Уведомление о завершении проверки обновлений в фоновом режиме с ошибкой",
|
||||||
"appsRemoved": "Приложение удалено",
|
"appsRemoved": "Приложение удалено",
|
||||||
"appsRemovedNotifDescription": "Уведомляет пользователя о том, что одно или несколько приложений было удалено из-за ошибок при их загрузке",
|
"appsRemovedNotifDescription": "Уведомление об удалении одного или несколько приложений из-за ошибок при их загрузке",
|
||||||
"xWasRemovedDueToErrorY": "{} был удален из-за ошибки: {}",
|
"xWasRemovedDueToErrorY": "{} был удален из-за ошибки: {}",
|
||||||
"completeAppInstallation": "Завершение установки приложения",
|
"completeAppInstallation": "Завершение установки приложения",
|
||||||
"obtainiumMustBeOpenToInstallApps": "Для установки приложений Obtainium должен быть открыт",
|
"obtainiumMustBeOpenToInstallApps": "Obtainium должен быть открыт для установки приложений",
|
||||||
"completeAppInstallationNotifDescription": "Просит пользователя вернуться в Obtainium, чтобы завершить установку приложения",
|
"completeAppInstallationNotifDescription": "Уведомление о необходимости открыть Obtainium для завершения установки приложения",
|
||||||
"checkingForUpdates": "Проверка обновлений",
|
"checkingForUpdates": "Проверка обновлений",
|
||||||
"checkingForUpdatesNotifDescription": "Временное уведомление, которое появляется при проверке обновлений",
|
"checkingForUpdatesNotifDescription": "Временное уведомление, которое появляется при проверке обновлений",
|
||||||
"pleaseAllowInstallPerm": "Пожалуйста, разрешите Obtainium устанавливать приложения",
|
"pleaseAllowInstallPerm": "Пожалуйста, разрешите Obtainium устанавливать приложения",
|
||||||
@@ -175,14 +166,14 @@
|
|||||||
"never": "Никогда",
|
"never": "Никогда",
|
||||||
"latestVersionX": "Последняя версия: {}",
|
"latestVersionX": "Последняя версия: {}",
|
||||||
"installedVersionX": "Установленная версия: {}",
|
"installedVersionX": "Установленная версия: {}",
|
||||||
"lastUpdateCheckX": "Последняя проверка обновлений: {}",
|
"lastUpdateCheckX": "Последняя проверка: {}",
|
||||||
"remove": "Удалить",
|
"remove": "Удалить",
|
||||||
"yesMarkUpdated": "Да, отметить как обновленное",
|
"yesMarkUpdated": "Да, отметить как обновленное",
|
||||||
"fdroid": "Официальный F-Droid",
|
"fdroid": "Официальные репозитории F-Droid",
|
||||||
"appIdOrName": "ID или название приложения",
|
"appIdOrName": "ID или название приложения",
|
||||||
"appId": "ID приложения",
|
"appId": "ID приложения",
|
||||||
"appWithIdOrNameNotFound": "Приложение с таким ID или названием не было найдено",
|
"appWithIdOrNameNotFound": "Приложение с таким ID или названием не было найдено",
|
||||||
"reposHaveMultipleApps": "В хранилище может быть несколько приложений",
|
"reposHaveMultipleApps": "В хранилище несколько приложений",
|
||||||
"fdroidThirdPartyRepo": "Сторонние репозитории F-Droid",
|
"fdroidThirdPartyRepo": "Сторонние репозитории F-Droid",
|
||||||
"steam": "Steam",
|
"steam": "Steam",
|
||||||
"steamMobile": "Steam Mobile",
|
"steamMobile": "Steam Mobile",
|
||||||
@@ -190,102 +181,164 @@
|
|||||||
"install": "Установить",
|
"install": "Установить",
|
||||||
"markInstalled": "Пометить как установленное",
|
"markInstalled": "Пометить как установленное",
|
||||||
"update": "Обновить",
|
"update": "Обновить",
|
||||||
"markUpdated": "Отметить обновленным",
|
"markUpdated": "Отметить обновлённым",
|
||||||
"additionalOptions": "Дополнительные опции",
|
"additionalOptions": "Дополнительные настройки",
|
||||||
"disableVersionDetection": "Отключить обнаружение версии",
|
"disableVersionDetection": "Отключить обнаружение версии",
|
||||||
"noVersionDetectionExplanation": "Эта опция должна использоваться только для приложений, где обнаружение версии не работает корректно.",
|
"noVersionDetectionExplanation": "Эта настройка должна использоваться только для приложений, где обнаружение версии не работает корректно",
|
||||||
"downloadingX": "Загрузка {}",
|
"downloadingX": "Загрузка {}",
|
||||||
"downloadNotifDescription": "Уведомляет пользователя о прогрессе загрузки приложения",
|
"downloadNotifDescription": "Уведомляет пользователя о прогрессе загрузки приложения",
|
||||||
"noAPKFound": "APK не найден",
|
"noAPKFound": "APK не найден",
|
||||||
"noVersionDetection": "Версий не обнаружено",
|
"noVersionDetection": "Обнаружение версий отключено",
|
||||||
"categorize": "Категоризировать",
|
"categorize": "Категоризировать",
|
||||||
"categories": "Категории",
|
"categories": "Категории",
|
||||||
"category": "Категория",
|
"category": "Категория",
|
||||||
"noCategory": "Без категории",
|
"noCategory": "Без категории",
|
||||||
"noCategories": "Без категорий",
|
"noCategories": "Без категорий",
|
||||||
"deleteCategoriesQuestion": "Удалить категории?",
|
"deleteCategoriesQuestion": "Удалить категории?",
|
||||||
"categoryDeleteWarning": "Все приложения в удаленных категориях будут помечены как без категории.",
|
"categoryDeleteWarning": "Все приложения в удаленных категориях будут помечены как без категории",
|
||||||
"addCategory": "Добавить категорию",
|
"addCategory": "Добавить категорию",
|
||||||
"label": "Метка",
|
"label": "Метка",
|
||||||
"language": "Язык",
|
"language": "Язык",
|
||||||
"copiedToClipboard": "Скопировано в буфер обмена",
|
"copiedToClipboard": "Скопировано в буфер обмена",
|
||||||
"storagePermissionDenied": "Отказано в доступе к хранилищу",
|
"storagePermissionDenied": "Отказано в доступе к хранилищу",
|
||||||
"selectedCategorizeWarning": "Это заменит все текущие настройки категорий для выбранных приложений.",
|
"selectedCategorizeWarning": "Это заменит все текущие настройки категорий для выбранных приложений",
|
||||||
"filterAPKsByRegEx": "Фильтровать APK-файлы с помощью\nрегулярного выражения",
|
"filterAPKsByRegEx": "Отфильтровать APK-файлы\n(регулярное выражение)",
|
||||||
"removeFromObtainium": "Удалить из Obtainium",
|
"removeFromObtainium": "Удалить из Obtainium",
|
||||||
"uninstallFromDevice": "Удалить с устройства",
|
"uninstallFromDevice": "Удалить с устройства",
|
||||||
"onlyWorksWithNonVersionDetectApps": "Работает только для приложений с отключенным определением версии.",
|
"onlyWorksWithNonVersionDetectApps": "Работает только для приложений с отключенным определением версии",
|
||||||
"releaseDateAsVersion": "Использовать дату выпуска в качестве версии",
|
"releaseDateAsVersion": "Дата выпуска вместо версии",
|
||||||
"releaseDateAsVersionExplanation": "Этот параметр следует использовать только для приложений, в которых определение версии не работает правильно, но имеется дата выпуска.",
|
"releaseDateAsVersionExplanation": "Этот параметр следует использовать только для приложений, в которых определение версии не работает правильно, но имеется дата выпуска",
|
||||||
"changes": "Изменения",
|
"changes": "Изменения",
|
||||||
"releaseDate": "Дата выпуска",
|
"releaseDate": "Дата выпуска",
|
||||||
"importFromURLsInFile": "Импорт URL-адресов из файла (например, OPML)",
|
"importFromURLsInFile": "Импорт из файла URL-адресов (например: OPML)",
|
||||||
"versionDetection": "Определение версии",
|
"versionDetection": "Определение версии",
|
||||||
"standardVersionDetection": "Стандартное определение версии",
|
"standardVersionDetection": "Стандартное",
|
||||||
"groupByCategory": "Группировать по категориям",
|
"groupByCategory": "Группировать по категориям",
|
||||||
"autoApkFilterByArch": "Попытка фильтрации APK-файлов по архитектуре процессора, если это возможно",
|
"autoApkFilterByArch": "Попытаться отфильтровать APK-файлы по архитектуре процессора",
|
||||||
"overrideSource": "Переопределить источник",
|
"overrideSource": "Переопределить источник",
|
||||||
"dontShowAgain": "Не показывать снова",
|
"dontShowAgain": "Не показывать снова",
|
||||||
"dontShowTrackOnlyWarnings": "Не показывать предупреждения о только отслеживаемых приложениях",
|
"dontShowTrackOnlyWarnings": "Не показывать предупреждения о только отслеживаемых приложениях",
|
||||||
"dontShowAPKOriginWarnings": "Не показывать предупреждения об источнике APK-файлов",
|
"dontShowAPKOriginWarnings": "Не показывать предупреждения об отличающемся источнике APK-файлов",
|
||||||
"moveNonInstalledAppsToBottom": "Переместить неустановленные приложения вниз списка",
|
"moveNonInstalledAppsToBottom": "Отображать неустановленные приложения внизу списка",
|
||||||
"gitlabPATLabel": "Персональный токен доступа GitLab\n(Включает поиск и улучшает обнаружение APK)",
|
"gitlabPATLabel": "Персональный токен доступа GitLab\n(включает поиск и улучшает обнаружение APK)",
|
||||||
"about": "О приложении",
|
"about": "Описание",
|
||||||
"requiresCredentialsInSettings": "Для этого требуются дополнительные учетные данные (в настройках)",
|
"requiresCredentialsInSettings": "{}: Для этого требуются дополнительные учетные данные (в настройках)",
|
||||||
"checkOnStart": "Проверять наличие обновлений при запуске",
|
"checkOnStart": "Проверять наличие обновлений при запуске",
|
||||||
"tryInferAppIdFromCode": "Попытаться определить ID приложения из исходного кода",
|
"tryInferAppIdFromCode": "Попытаться определить ID приложения из исходного кода",
|
||||||
"removeOnExternalUninstall": "Автоматически убирать из списка удаленные извне приложения",
|
"removeOnExternalUninstall": "Автоматически убирать из списка удаленные извне приложения",
|
||||||
"pickHighestVersionCode": "Автовыбор кода наивысшей версии APK",
|
"pickHighestVersionCode": "Автовыбор актуальной версии кода APK",
|
||||||
"checkUpdateOnDetailPage": "Проверять наличие обновлений при открытии страницы представления приложения",
|
"checkUpdateOnDetailPage": "Проверять наличие обновлений при открытии страницы приложения",
|
||||||
"disablePageTransitions": "Отключить анимацию перехода между страницами",
|
"disablePageTransitions": "Отключить анимацию перехода между страницами",
|
||||||
"reversePageTransitions": "Реверс анимации перехода между страницами",
|
"reversePageTransitions": "Реверс анимации перехода между страницами",
|
||||||
"minStarCount": "Минимальное количество звёзд",
|
"minStarCount": "Минимальное количество звёзд",
|
||||||
|
"addInfoBelow": "Добавьте эту информацию ниже",
|
||||||
|
"addInfoInSettings": "Добавьте эту информацию в Настройки",
|
||||||
|
"githubSourceNote": "Используя ключ API можно обойти лимит запросов GitHub",
|
||||||
|
"gitlabSourceNote": "Без ключа API может не работать извлечение APK с GitLab",
|
||||||
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
|
"filterReleaseNotesByRegEx": "Фильтровать примечания к выпуску\n(регулярное выражение)",
|
||||||
|
"customLinkFilterRegex": "Пользовательский фильтр ссылок APK\n(регулярное выражение, по умолчанию: '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "Попытки обновления приложений",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Уведомление о возможных обновлениях одного или нескольких приложений в фоновом режиме",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} возможно был обновлен до {}",
|
||||||
|
"enableBackgroundUpdates": "Включить обновления в фоне",
|
||||||
|
"backgroundUpdateReqsExplanation": "Фоновые обновления могут быть возможны не для всех приложений",
|
||||||
|
"backgroundUpdateLimitsExplanation": "Успешность фоновой установки можно определить только после открытия Obtainium",
|
||||||
|
"verifyLatestTag": "Проверять тег 'latest'",
|
||||||
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "Промежуточная ссылка не найдена",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Исключить из фоновых обновлений (если включено)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "Отключить фоновые обновления, если нет соединения с Wi-Fi",
|
||||||
|
"autoSelectHighestVersionCode": "Автоматически выбирать APK с актуальной версией кода",
|
||||||
|
"versionExtractionRegEx": "Регулярное выражение для извлечения версии",
|
||||||
|
"matchGroupToUse": "Выберите группу для использования",
|
||||||
|
"highlightTouchTargets": "Выделить менее очевидные элементы управления касанием",
|
||||||
|
"pickExportDir": "Выбрать каталог для экспорта",
|
||||||
|
"autoExportOnChanges": "Автоэкспорт при изменениях",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
|
"filterVersionsByRegEx": "Фильтровать версии по регулярному выражению",
|
||||||
|
"trySelectingSuggestedVersionCode": "Попробуйте выбрать предложенный код версии APK",
|
||||||
|
"dontSortReleasesList": "Сохранить порядок релизов от API",
|
||||||
|
"reverseSort": "Обратная сортировка",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
|
"debugMenu": "Меню отладки",
|
||||||
|
"bgTaskStarted": "Фоновая задача начата — проверьте журналы",
|
||||||
|
"runBgCheckNow": "Запустить проверку фонового обновления сейчас",
|
||||||
|
"versionExtractWholePage": "Применить регулярное выражение версии ко всей странице",
|
||||||
|
"installing": "Устанавливается",
|
||||||
|
"skipUpdateNotifications": "Не оповещать об обновлениях",
|
||||||
|
"updatesAvailableNotifChannel": "Доступны обновления",
|
||||||
|
"appsUpdatedNotifChannel": "Приложения обновлены",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "Попытки обновления приложений",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Ошибка при проверке обновлений",
|
||||||
|
"appsRemovedNotifChannel": "Приложение удалено",
|
||||||
|
"downloadingXNotifChannel": "Загрузка {}",
|
||||||
|
"completeAppInstallationNotifChannel": "Завершение установки приложения",
|
||||||
|
"checkingForUpdatesNotifChannel": "Проверка обновлений",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Проверять обновления только у установленных или отслеживаемых приложений",
|
||||||
|
"supportFixedAPKURL": "Поддержка фиксированных URL-адресов APK",
|
||||||
|
"selectX": "Выбрать {}",
|
||||||
|
"parallelDownloads": "Разрешить параллельные загрузки",
|
||||||
|
"installMethod": "Метод установки",
|
||||||
|
"normal": "Нормальный",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Суперпользователь",
|
||||||
|
"shizukuBinderNotFound": "Совместимый сервис Shizuku не найден",
|
||||||
|
"useSystemFont": "Использовать системный шрифт",
|
||||||
|
"systemFontError": "Ошибка загрузки системного шрифта: {}",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Удалить приложение?",
|
"one": "Удалить приложение?",
|
||||||
"other": "Удалить приложения?"
|
"other": "Удалить приложения?"
|
||||||
},
|
},
|
||||||
"tooManyRequestsTryAgainInMinutes": {
|
"tooManyRequestsTryAgainInMinutes": {
|
||||||
"one": "Слишком много запросов (ограничение скорости) - попробуйте снова через {} минуту",
|
"one": "Слишком много запросов (ограничение скорости) — попробуйте снова через {} минуту",
|
||||||
"other": "Слишком много запросов (ограничение скорости) - попробуйте снова через {} минуты"
|
"other": "Слишком много запросов (ограничение скорости) — попробуйте снова через {} минуты"
|
||||||
},
|
},
|
||||||
"bgUpdateGotErrorRetryInMinutes": {
|
"bgUpdateGotErrorRetryInMinutes": {
|
||||||
"one": "При проверке обновлений в фоновом режиме возникла ошибка {}, повторная проверка будет запланирована через {} минуту",
|
"one": "При проверке обновлений в фоновом режиме возникла ошибка {}, повторная проверка будет запланирована через {} минуту",
|
||||||
"other": "При проверке обновлений в фоновом режиме возникла ошибка {}, повторная проверка будет запланирована через {} минуты"
|
"other": "При проверке обновлений в фоновом режиме возникла ошибка {}, повторная проверка будет запланирована через {} минуты"
|
||||||
},
|
},
|
||||||
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
||||||
"one": "В ходе проверки обновления в фоновом режиме было обнаружено {} обновление - Пользователю будет отправлено уведомление, если это необходимо",
|
"one": "В ходе проверки обновления в фоновом режиме было обнаружено {} обновление — Пользователю будет отправлено уведомление, если это необходимо",
|
||||||
"other": "В ходе проверки обновления в фоновом режиме было обнаружено {} обновлений - Пользователю будет отправлено уведомление, если это необходимо"
|
"other": "В ходе проверки обновления в фоновом режиме было обнаружено {} обновлений — Пользователю будет отправлено уведомление, если это необходимо"
|
||||||
},
|
},
|
||||||
"apps": {
|
"apps": {
|
||||||
"one": "{} Приложение",
|
"one": "{} приложение",
|
||||||
"other": "{} Приложений"
|
"other": "{} приложений"
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"one": "{} URL-адрес",
|
"one": "{} URL-адрес",
|
||||||
"other": "{} URL-адреса"
|
"other": "{} URL-адреса"
|
||||||
},
|
},
|
||||||
"minute": {
|
"minute": {
|
||||||
"one": "{} Минута",
|
"one": "{} минута",
|
||||||
"other": "{} Минуты"
|
"other": "{} минуты"
|
||||||
},
|
},
|
||||||
"hour": {
|
"hour": {
|
||||||
"one": "{} Час",
|
"one": "{} час",
|
||||||
"other": "{} Часов"
|
"other": "{} часов"
|
||||||
},
|
},
|
||||||
"day": {
|
"day": {
|
||||||
"one": "{} День",
|
"one": "{} день",
|
||||||
"other": "{} Дней"
|
"other": "{} дней"
|
||||||
},
|
},
|
||||||
"clearedNLogsBeforeXAfterY": {
|
"clearedNLogsBeforeXAfterY": {
|
||||||
"one": "Очищен {n} журнал (до = {before}, после = {after})",
|
"one": "Очищен {n} журнал (до = {before}, после = {after})",
|
||||||
"other": "Очищено {n} журналов (до = {before}, после = {after})"
|
"other": "Очищено {n} журналов (до = {before}, после = {after})"
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesAvailable": {
|
"xAndNMoreUpdatesAvailable": {
|
||||||
"one": "У {} и еще 1 приложения есть обновление.",
|
"one": "У {} и еще 1 приложения есть обновление",
|
||||||
"other": "У {} и ещё {} приложений есть обновления."
|
"other": "У {} и ещё {} приложений есть обновления"
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesInstalled": {
|
"xAndNMoreUpdatesInstalled": {
|
||||||
"one": "{} и еще 1 приложение были обновлены.",
|
"one": "{} и ещё 1 приложение были обновлены",
|
||||||
"other": "{} и еще {} приложений были обновлены."
|
"other": "{} и ещё {} приложений были обновлены"
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} и ещё 1 приложение могли быть обновлены",
|
||||||
|
"other": "{} и ещё {} приложений могли быть обновлены"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
328
assets/translations/sv.json
Normal file
328
assets/translations/sv.json
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
{
|
||||||
|
"invalidURLForSource": "Inte giltig {} App-URL",
|
||||||
|
"noReleaseFound": "Kunde inte hitta en lämplig releaseversion",
|
||||||
|
"noVersionFound": "Kunde inte bestämma releaseversion",
|
||||||
|
"urlMatchesNoSource": "URL matchar inte känd källa",
|
||||||
|
"cantInstallOlderVersion": "Kan inte installera en äldre version av en app",
|
||||||
|
"appIdMismatch": "Nerladdat paket-ID matchar inte nuvarande App-ID",
|
||||||
|
"functionNotImplemented": "This class has not implemented this function",
|
||||||
|
"placeholder": "Platshållare",
|
||||||
|
"someErrors": "Några fel uppstod",
|
||||||
|
"unexpectedError": "Oväntat fel",
|
||||||
|
"ok": "Okej",
|
||||||
|
"and": "och",
|
||||||
|
"githubPATLabel": "GitHub Personal Access Token (Increases Rate Limit)",
|
||||||
|
"includePrereleases": "Inkludera förreleaser",
|
||||||
|
"fallbackToOlderReleases": "Fall tillbaka till äldre releaser",
|
||||||
|
"filterReleaseTitlesByRegEx": "Filter Release Titles by Regular Expression",
|
||||||
|
"invalidRegEx": "Invalid regular expression",
|
||||||
|
"noDescription": "Ingen beskrivning",
|
||||||
|
"cancel": "Avbryt",
|
||||||
|
"continue": "Fortsätt",
|
||||||
|
"requiredInBrackets": "(Kräver)",
|
||||||
|
"dropdownNoOptsError": "ERROR: DROPDOWN MUST HAVE AT LEAST ONE OPT",
|
||||||
|
"colour": "Färg",
|
||||||
|
"githubStarredRepos": "GitHub Stjärnmärkta Förråd",
|
||||||
|
"uname": "Användarnamn",
|
||||||
|
"wrongArgNum": "Wrong number of arguments provided",
|
||||||
|
"xIsTrackOnly": "{} är 'Följ-Endast'",
|
||||||
|
"source": "Källa",
|
||||||
|
"app": "App",
|
||||||
|
"appsFromSourceAreTrackOnly": "Apparna från denna källa är 'Följ-Endast'.",
|
||||||
|
"youPickedTrackOnly": "Du har markerat 'Följ-Endast'-alternativet",
|
||||||
|
"trackOnlyAppDescription": "Appen kommer följas för uppdateringar men Obtainium kommer inte ladda ner eller installera den.",
|
||||||
|
"cancelled": "Avbruten",
|
||||||
|
"appAlreadyAdded": "App redan tillagd",
|
||||||
|
"alreadyUpToDateQuestion": "App redan uppdaterad?",
|
||||||
|
"addApp": "Lägg till App",
|
||||||
|
"appSourceURL": "URL till Appkälla",
|
||||||
|
"error": "Fel",
|
||||||
|
"add": "Lägg till",
|
||||||
|
"searchSomeSourcesLabel": "Sök (Bara några källor)",
|
||||||
|
"search": "Sök",
|
||||||
|
"additionalOptsFor": "Ytterligare Alternativ för {}",
|
||||||
|
"supportedSources": "Stödda Källor",
|
||||||
|
"trackOnlyInBrackets": "(Följ-Endast)",
|
||||||
|
"searchableInBrackets": "(Sökbar)",
|
||||||
|
"appsString": "Appar",
|
||||||
|
"noApps": "Inga Appar",
|
||||||
|
"noAppsForFilter": "Inga Appar för Filter",
|
||||||
|
"byX": "Av {}",
|
||||||
|
"percentProgress": "Progress: {}%",
|
||||||
|
"pleaseWait": "Vänta",
|
||||||
|
"updateAvailable": "Uppdatering Tillgänglig",
|
||||||
|
"estimateInBracketsShort": "(Est.)",
|
||||||
|
"notInstalled": "Inte Installerad",
|
||||||
|
"estimateInBrackets": "(Uppskattning)",
|
||||||
|
"selectAll": "Välj Alla",
|
||||||
|
"deselectX": "Avmarkera {}",
|
||||||
|
"xWillBeRemovedButRemainInstalled": "{} kommer tas bort från Obtainium men kommer vara fortsatt installerad på enheten.",
|
||||||
|
"removeSelectedAppsQuestion": "Ta bort markerade Appar?",
|
||||||
|
"removeSelectedApps": "Ta bort markerade Appar",
|
||||||
|
"updateX": "Uppdatera {}",
|
||||||
|
"installX": "Installera {}",
|
||||||
|
"markXTrackOnlyAsUpdated": "Märk {}\n(Följ-Endast)\nsom Uppdaterad",
|
||||||
|
"changeX": "Byt {}",
|
||||||
|
"installUpdateApps": "Installera/Uppdatera Appar",
|
||||||
|
"installUpdateSelectedApps": "Installera/Uppdatera Markerade Appar",
|
||||||
|
"markXSelectedAppsAsUpdated": "Märk {} markerade Appar som Uppdaterade?",
|
||||||
|
"no": "Nej",
|
||||||
|
"yes": "Ja",
|
||||||
|
"markSelectedAppsUpdated": "Märk Valda Appar som Uppdaterade",
|
||||||
|
"pinToTop": "Nåla fast högst upp",
|
||||||
|
"unpinFromTop": "Avnåla",
|
||||||
|
"resetInstallStatusForSelectedAppsQuestion": "Återställ Installationsstatus för valda Appar?",
|
||||||
|
"installStatusOfXWillBeResetExplanation": "Installationsstatusen för de markerade apparna kommer återställas.\n\n Detta kan hjälpa när appversionen visad i Obtanium är fel på grund av misslyckade uppdateringar eller andra orsaker.",
|
||||||
|
"shareSelectedAppURLs": "Dela Valda Appars URL:er",
|
||||||
|
"resetInstallStatus": "Återställ Installationstatus",
|
||||||
|
"more": "Mer",
|
||||||
|
"removeOutdatedFilter": "Ta bort Utgånga App-filtret",
|
||||||
|
"showOutdatedOnly": "Visa Endast Utgånga Appar",
|
||||||
|
"filter": "Filter",
|
||||||
|
"filterActive": "Filter *",
|
||||||
|
"filterApps": "Filtrera Appar",
|
||||||
|
"appName": "Appnamn",
|
||||||
|
"author": "Utvecklare",
|
||||||
|
"upToDateApps": "Uppdaterade Appar",
|
||||||
|
"nonInstalledApps": "Icke-Installerade Appar",
|
||||||
|
"importExport": "Importera/Exportera",
|
||||||
|
"settings": "Inställningar",
|
||||||
|
"exportedTo": "Exporterad till {}",
|
||||||
|
"obtainiumExport": "Obtainiumexport",
|
||||||
|
"invalidInput": "Ogiltig inmatning",
|
||||||
|
"importedX": "Importerad {}",
|
||||||
|
"obtainiumImport": "Obtainium Import",
|
||||||
|
"importFromURLList": "Importera från URL-lista",
|
||||||
|
"searchQuery": "Sökförfrågan",
|
||||||
|
"appURLList": "App URL List",
|
||||||
|
"line": "Linje",
|
||||||
|
"searchX": "Sök {}",
|
||||||
|
"noResults": "Inga resultat",
|
||||||
|
"importX": "Importera {}",
|
||||||
|
"importedAppsIdDisclaimer": "Importerade Appar kan felaktigt visas som \"Inte Installerad\".\nFör att fixa detta återinstallera dem genom Obtainium.\nDetta skall inte påverka appdata.\n\n Påverkar endast URL:en och tredjepartsimportermetoder.",
|
||||||
|
"importErrors": "Importfel",
|
||||||
|
"importedXOfYApps": "{} av {} Appar importerade.",
|
||||||
|
"followingURLsHadErrors": "Följande URL:er hade fel:",
|
||||||
|
"selectURL": "Välj URL",
|
||||||
|
"selectURLs": "Välj URL:er",
|
||||||
|
"pick": "Välj",
|
||||||
|
"theme": "Tema",
|
||||||
|
"dark": "Mörkt",
|
||||||
|
"light": "Ljust",
|
||||||
|
"followSystem": "Följ System",
|
||||||
|
"obtainium": "Obtainium",
|
||||||
|
"materialYou": "Material You",
|
||||||
|
"useBlackTheme": "Använd svart tema",
|
||||||
|
"appSortBy": "Sortera Appar via",
|
||||||
|
"authorName": "Utvecklare/Namn",
|
||||||
|
"nameAuthor": "Namn/Utvecklare",
|
||||||
|
"asAdded": "As Added",
|
||||||
|
"appSortOrder": "App Sort Order",
|
||||||
|
"ascending": "Stigande",
|
||||||
|
"descending": "Fallande",
|
||||||
|
"bgUpdateCheckInterval": "Bakgrundsuppdateringskollfrekvens",
|
||||||
|
"neverManualOnly": "Never - Manual Only",
|
||||||
|
"appearance": "Utseende",
|
||||||
|
"showWebInAppView": "Visa källans hemsida i appvyn",
|
||||||
|
"pinUpdates": "Fäst uppdateringar högst upp i appvyn",
|
||||||
|
"updates": "Uppdateringar",
|
||||||
|
"sourceSpecific": "Källspecifik",
|
||||||
|
"appSource": "Appkälla",
|
||||||
|
"noLogs": "Inga Loggar",
|
||||||
|
"appLogs": "Apploggar",
|
||||||
|
"close": "Stäng",
|
||||||
|
"share": "Dela",
|
||||||
|
"appNotFound": "App ej funnen",
|
||||||
|
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||||
|
"pickAnAPK": "Välj en APK",
|
||||||
|
"appHasMoreThanOnePackage": "{} har fler än ett paket:",
|
||||||
|
"deviceSupportsXArch": "Din enhet stödjer {} CPU-arkiktektur.",
|
||||||
|
"deviceSupportsFollowingArchs": "YDin enhet stödjer följande CPU-arkitekturer:",
|
||||||
|
"warning": "Varning",
|
||||||
|
"sourceIsXButPackageFromYPrompt": "Appens källa är '{}' men releasepaketet kommer från '{}'. Vill du fortsätta?",
|
||||||
|
"updatesAvailable": "Uppdateringar Tillgängliga",
|
||||||
|
"updatesAvailableNotifDescription": "Aviserar användaren att det finns uppdateringar tillgängaliga för en eller fler Appar som följs av Obtainium",
|
||||||
|
"noNewUpdates": "Inga nya uppdateringar.",
|
||||||
|
"xHasAnUpdate": "{} har en uppdatering.",
|
||||||
|
"appsUpdated": "Appar Uppdaterade",
|
||||||
|
"appsUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were applied in the background",
|
||||||
|
"xWasUpdatedToY": "{} uppdaterades till {}.",
|
||||||
|
"errorCheckingUpdates": "Fel vid uppdateringskoll",
|
||||||
|
"errorCheckingUpdatesNotifDescription": "En aviserings som visar när bakgrundsuppdateringarkollar misslyckas",
|
||||||
|
"appsRemoved": "Appar borttagna",
|
||||||
|
"appsRemovedNotifDescription": "Aviserar användaren när en eller fler Appar togs bort på grund av fel när de laddades",
|
||||||
|
"xWasRemovedDueToErrorY": "{} togs bort på grund av detta felet: {}",
|
||||||
|
"completeAppInstallation": "Gör klar appinstallation",
|
||||||
|
"obtainiumMustBeOpenToInstallApps": "Obtainium måste vara öppet för att installera Appar",
|
||||||
|
"completeAppInstallationNotifDescription": "Frågar användaren att återvända till Obtaiunium när appinstallation är klar",
|
||||||
|
"checkingForUpdates": "Kollar efter Uppdateringar",
|
||||||
|
"checkingForUpdatesNotifDescription": "Transient notification that appears when checking for updates",
|
||||||
|
"pleaseAllowInstallPerm": "Tillåt Obtanium att installera Appar",
|
||||||
|
"trackOnly": "Följ-Endast",
|
||||||
|
"errorWithHttpStatusCode": "Fel {}",
|
||||||
|
"versionCorrectionDisabled": "Versionskorrigering inaktiverat (plugin verkar inte fungera)",
|
||||||
|
"unknown": "Okänd",
|
||||||
|
"none": "None",
|
||||||
|
"never": "Aldrig",
|
||||||
|
"latestVersionX": "Senaste Version: {}",
|
||||||
|
"installedVersionX": "Installerad Version: {}",
|
||||||
|
"lastUpdateCheckX": "Senaste uppdateringskoll: {}",
|
||||||
|
"remove": "Ta bort",
|
||||||
|
"yesMarkUpdated": "Ja, Märk som Uppdaterad",
|
||||||
|
"fdroid": "F-Droid Officiell",
|
||||||
|
"appIdOrName": "App-ID eller Namn",
|
||||||
|
"appId": "App-ID",
|
||||||
|
"appWithIdOrNameNotFound": "Ingen App funnen med det namnet eller ID",
|
||||||
|
"reposHaveMultipleApps": "Förråd kan innehålla flera ApparR",
|
||||||
|
"fdroidThirdPartyRepo": "F-Droid Tredjeparts Förråd",
|
||||||
|
"steam": "Steam",
|
||||||
|
"steamMobile": "Steam Mobile",
|
||||||
|
"steamChat": "Steam Chat",
|
||||||
|
"install": "Installera",
|
||||||
|
"markInstalled": "Märk Installerad",
|
||||||
|
"update": "Uppdatera",
|
||||||
|
"markUpdated": "Märk Uppdaterad",
|
||||||
|
"additionalOptions": "Ytterligare Alternativ",
|
||||||
|
"disableVersionDetection": "Inaktivera versionsdetektering",
|
||||||
|
"noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.",
|
||||||
|
"downloadingX": "Laddar ner {}",
|
||||||
|
"downloadNotifDescription": "Notifies the user of the progress in downloading an App",
|
||||||
|
"noAPKFound": "Ingen APK funnen",
|
||||||
|
"noVersionDetection": "Ingen versiondetektering",
|
||||||
|
"categorize": "Kategorisera",
|
||||||
|
"categories": "Kategorier",
|
||||||
|
"category": "Kategori",
|
||||||
|
"noCategory": "Ingen Kategori",
|
||||||
|
"noCategories": "Inga Kategorier",
|
||||||
|
"deleteCategoriesQuestion": "Ta Bort Kategorier?",
|
||||||
|
"categoryDeleteWarning": "Alla Appar i de borttagna kategorierna kommer att märkas som okategoriserade.",
|
||||||
|
"addCategory": "Lägg till Kategori",
|
||||||
|
"label": "Label",
|
||||||
|
"language": "Språk",
|
||||||
|
"copiedToClipboard": "Kopierat till Urklipp",
|
||||||
|
"storagePermissionDenied": "Lagringsbehörighet nekad",
|
||||||
|
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
|
||||||
|
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
|
||||||
|
"removeFromObtainium": "Ta bort från Obtainium",
|
||||||
|
"uninstallFromDevice": "Avinstallera från Enheten",
|
||||||
|
"onlyWorksWithNonVersionDetectApps": "Fungerar bara för Appar med versionsdetektering inaktiverat..",
|
||||||
|
"releaseDateAsVersion": "Använd releasedatum som version",
|
||||||
|
"releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.",
|
||||||
|
"changes": "Ändringar",
|
||||||
|
"releaseDate": "Releasedatum",
|
||||||
|
"importFromURLsInFile": "Importera från URL:er i fil (som OPML)",
|
||||||
|
"versionDetection": "Versionsdetektering",
|
||||||
|
"standardVersionDetection": "Standardversionsdetektering",
|
||||||
|
"groupByCategory": "Gruppera via Kategori",
|
||||||
|
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
|
||||||
|
"overrideSource": "Överskrid Källa",
|
||||||
|
"dontShowAgain": "Visa inte detta igen",
|
||||||
|
"dontShowTrackOnlyWarnings": "Visa inte 'Följ-Endast' varningar",
|
||||||
|
"dontShowAPKOriginWarnings": "Visa inte APK-ursprung varningar",
|
||||||
|
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
||||||
|
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
||||||
|
"about": "Om",
|
||||||
|
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
|
||||||
|
"checkOnStart": "Kolla efter uppdateringar vid start",
|
||||||
|
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
||||||
|
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
||||||
|
"pickHighestVersionCode": "Auto-select highest version code APK",
|
||||||
|
"checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
|
||||||
|
"disablePageTransitions": "Disable page transition animations",
|
||||||
|
"reversePageTransitions": "Reverse page transition animations",
|
||||||
|
"minStarCount": "Minsta antal stjärnmarkeringar",
|
||||||
|
"addInfoBelow": "Lägg till denna information nedanför.",
|
||||||
|
"addInfoInSettings": "Lägg till denna information i Inställningar.",
|
||||||
|
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
|
||||||
|
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
|
||||||
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
|
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
|
||||||
|
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "App Updates Attempted",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
|
||||||
|
"enableBackgroundUpdates": "Aktivera Bakgrundsuppdateringar",
|
||||||
|
"backgroundUpdateReqsExplanation": "Bakgrundsuppdateringar är inte möjligt för alla appar.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
|
||||||
|
"verifyLatestTag": "Verifiera 'senaste'-taggen",
|
||||||
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "Intermediate link not found",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Undta från bakgrundsuppdateringar (om aktiverad)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "Inaktivera Bakgrundsuppdateringar utan WiFi",
|
||||||
|
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
|
||||||
|
"versionExtractionRegEx": "Version Extraction RegEx",
|
||||||
|
"matchGroupToUse": "Match Group to Use",
|
||||||
|
"highlightTouchTargets": "Highlight less obvious touch targets",
|
||||||
|
"pickExportDir": "Välj Exportsökväg",
|
||||||
|
"autoExportOnChanges": "Automatisk export vid ändringar",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
|
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
|
||||||
|
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
|
||||||
|
"dontSortReleasesList": "Retain release order from API",
|
||||||
|
"reverseSort": "Omvänd sortering",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
|
"debugMenu": "Felsökningsmeny",
|
||||||
|
"bgTaskStarted": "Background task started - check logs.",
|
||||||
|
"runBgCheckNow": "Kör Bakgrundsuppdateringskoll Nu",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
|
"installMethod": "Installation method",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
|
"removeAppQuestion": {
|
||||||
|
"one": "Ta Bort App?",
|
||||||
|
"other": "Ta Bort Appar?"
|
||||||
|
},
|
||||||
|
"tooManyRequestsTryAgainInMinutes": {
|
||||||
|
"one": "Too many requests (rate limited) - try again in {} minute",
|
||||||
|
"other": "Too many requests (rate limited) - try again in {} minutes"
|
||||||
|
},
|
||||||
|
"bgUpdateGotErrorRetryInMinutes": {
|
||||||
|
"one": "BG update checking encountered a {}, will schedule a retry check in {} minute",
|
||||||
|
"other": "BG update checking encountered a {}, will schedule a retry check in {} minutes"
|
||||||
|
},
|
||||||
|
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
||||||
|
"one": "BG update checking found {} update - will notify user if needed",
|
||||||
|
"other": "BG update checking found {} updates - will notify user if needed"
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"one": "{} App",
|
||||||
|
"other": "{} Appar"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"one": "{} URL",
|
||||||
|
"other": "{} URL:er"
|
||||||
|
},
|
||||||
|
"minute": {
|
||||||
|
"one": "{} Minut",
|
||||||
|
"other": "{} Minuter"
|
||||||
|
},
|
||||||
|
"hour": {
|
||||||
|
"one": "{} Timme",
|
||||||
|
"other": "{} Timmar"
|
||||||
|
},
|
||||||
|
"day": {
|
||||||
|
"one": "{} Dag",
|
||||||
|
"other": "{} Dagar"
|
||||||
|
},
|
||||||
|
"clearedNLogsBeforeXAfterY": {
|
||||||
|
"one": "Rensade {n} logg (före = {before}, efter = {after})",
|
||||||
|
"other": "Rensade {n} loggar (före = {before}, efter = {after})"
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesAvailable": {
|
||||||
|
"one": "{} och 1 app till har tillgängliga uppdateringar.",
|
||||||
|
"other": "{} och {} appar till har tillgängliga uppdateringar."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesInstalled": {
|
||||||
|
"one": "{} och 1 till app uppdaterades.",
|
||||||
|
"other": "{} och {} appar till uppdaterades."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} och 1 till app kan ha uppdaterats.",
|
||||||
|
"other": "{} och {} appar till kan ha uppdaterats."
|
||||||
|
}
|
||||||
|
}
|
||||||
342
assets/translations/tr.json
Normal file
342
assets/translations/tr.json
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
{
|
||||||
|
"invalidURLForSource": "Geçerli bir {} Uygulama URL'si değil",
|
||||||
|
"noReleaseFound": "Uygun bir sürüm bulunamadı",
|
||||||
|
"noVersionFound": "Sürüm bulunamadı",
|
||||||
|
"urlMatchesNoSource": "URL, bilinen bir kaynağa uymuyor",
|
||||||
|
"cantInstallOlderVersion": "Eski bir sürümü yükleyemem",
|
||||||
|
"appIdMismatch": "İndirilen paket kimliği mevcut Uygulama kimliği ile eşleşmiyor",
|
||||||
|
"functionNotImplemented": "Bu sınıf bu işlevi uygulamamıştır",
|
||||||
|
"placeholder": "Yer Tutucu",
|
||||||
|
"someErrors": "Bazı Hatalar Oluştu",
|
||||||
|
"unexpectedError": "Beklenmeyen Hata",
|
||||||
|
"ok": "Tamam",
|
||||||
|
"and": "ve",
|
||||||
|
"githubPATLabel": "GitHub Kişisel Erişim Anahtarı (Sınırlamayı Artırır)",
|
||||||
|
"includePrereleases": "Ön sürümleri dahil et",
|
||||||
|
"fallbackToOlderReleases": "Daha eski sürümlere geri dön",
|
||||||
|
"filterReleaseTitlesByRegEx": "Düzenli İfadelerle Sürüm Başlıklarını Filtrele",
|
||||||
|
"invalidRegEx": "Geçersiz düzenli ifade",
|
||||||
|
"noDescription": "Açıklama yok",
|
||||||
|
"cancel": "İptal",
|
||||||
|
"continue": "Devam Et",
|
||||||
|
"requiredInBrackets": "(Gerekli)",
|
||||||
|
"dropdownNoOptsError": "HATA: DİPLOMADA EN AZ BİR SEÇENEK OLMALI",
|
||||||
|
"colour": "Renk",
|
||||||
|
"githubStarredRepos": "GitHub'a Yıldızlı Depolar",
|
||||||
|
"uname": "Kullanıcı Adı",
|
||||||
|
"wrongArgNum": "Hatalı argüman sayısı sağlandı",
|
||||||
|
"xIsTrackOnly": "{} yalnızca Takip Edilen",
|
||||||
|
"source": "Kaynak",
|
||||||
|
"app": "Uygulama",
|
||||||
|
"appsFromSourceAreTrackOnly": "Bu kaynaktan gelen uygulamalar 'Yalnızca Takip Edilen'dir.",
|
||||||
|
"youPickedTrackOnly": "'Yalnızca Takip Edilen' seçeneğini seçtiniz.",
|
||||||
|
"trackOnlyAppDescription": "Uygulama güncellemeleri için takip edilecek, ancak Obtainium onu indiremeyecek veya kuramayacaktır.",
|
||||||
|
"cancelled": "İptal Edildi",
|
||||||
|
"appAlreadyAdded": "Uygulama zaten eklenmiş",
|
||||||
|
"alreadyUpToDateQuestion": "Uygulama Zaten Güncel mi?",
|
||||||
|
"addApp": "Uygulama Ekle",
|
||||||
|
"appSourceURL": "Uygulama Kaynak URL'si",
|
||||||
|
"error": "Hata",
|
||||||
|
"add": "Ekle",
|
||||||
|
"searchSomeSourcesLabel": "Ara (Bazı Kaynaklar Yalnızca)",
|
||||||
|
"search": "Ara",
|
||||||
|
"additionalOptsFor": "{} İçin Ek Seçenekler",
|
||||||
|
"supportedSources": "Desteklenen Kaynaklar",
|
||||||
|
"trackOnlyInBrackets": "(Yalnızca Takip)",
|
||||||
|
"searchableInBrackets": "(Aranabilir)",
|
||||||
|
"appsString": "Uygulamalar",
|
||||||
|
"noApps": "Uygulama Yok",
|
||||||
|
"noAppsForFilter": "Filtre İçin Uygulama Yok",
|
||||||
|
"byX": "{} Tarafından",
|
||||||
|
"percentProgress": "İlerleme: {}%",
|
||||||
|
"pleaseWait": "Lütfen Bekleyin",
|
||||||
|
"updateAvailable": "Güncelleme Var",
|
||||||
|
"estimateInBracketsShort": "(Est.)",
|
||||||
|
"notInstalled": "Yüklenmedi",
|
||||||
|
"estimateInBrackets": "(Tahmini)",
|
||||||
|
"selectAll": "Hepsini Seç",
|
||||||
|
"deselectX": "{}'yi Seçimden Kaldır",
|
||||||
|
"xWillBeRemovedButRemainInstalled": "{} Obtainium'dan kaldırılacak ancak cihazınızda yüklü kalacaktır.",
|
||||||
|
"removeSelectedAppsQuestion": "Seçilen Uygulamaları Kaldırmak İstiyor musunuz?",
|
||||||
|
"removeSelectedApps": "Seçilen Uygulamaları Kaldır",
|
||||||
|
"updateX": "{}'yi Güncelle",
|
||||||
|
"installX": "{}'yi Yükle",
|
||||||
|
"markXTrackOnlyAsUpdated": "{}(Takip Edilen) olarak Güncellendi olarak İşaretle",
|
||||||
|
"changeX": "{}'yi Değiştir",
|
||||||
|
"installUpdateApps": "Uygulamaları Yükle/Güncelle",
|
||||||
|
"installUpdateSelectedApps": "Seçilen Uygulamaları Yükle/Güncelle",
|
||||||
|
"markXSelectedAppsAsUpdated": "Seçilen Uygulamaları {} olarak Güncellendi olarak İşaretle?",
|
||||||
|
"no": "Hayır",
|
||||||
|
"yes": "Evet",
|
||||||
|
"markSelectedAppsUpdated": "Seçilen Uygulamaları Güncellendi olarak İşaretle",
|
||||||
|
"pinToTop": "Üstte Tut",
|
||||||
|
"unpinFromTop": "Üstten Kaldır",
|
||||||
|
"resetInstallStatusForSelectedAppsQuestion": "Seçilen Uygulamaların Yükleme Durumunu Sıfırlamak İstiyor musunuz?",
|
||||||
|
"installStatusOfXWillBeResetExplanation": "Seçilen Uygulamaların yükleme durumu sıfırlanacak.\n\nBu, Obtainium'da gösterilen uygulama sürümünün başarısız güncellemeler veya diğer sorunlar nedeniyle yanlış olması durumunda yardımcı olabilir.",
|
||||||
|
"shareSelectedAppURLs": "Seçilen Uygulama URL'larını Paylaş",
|
||||||
|
"resetInstallStatus": "Yükleme Durumunu Sıfırla",
|
||||||
|
"more": "Daha Fazla",
|
||||||
|
"removeOutdatedFilter": "Güncel Olmayan Uygulama Filtresini Kaldır",
|
||||||
|
"showOutdatedOnly": "Yalnızca Güncel Olmayan Uygulamaları Göster",
|
||||||
|
"filter": "Filtre",
|
||||||
|
"filterActive": "Filtre *",
|
||||||
|
"filterApps": "Uygulamaları Filtrele",
|
||||||
|
"appName": "Uygulama Adı",
|
||||||
|
"author": "Yazar",
|
||||||
|
"upToDateApps": "Güncel Uygulamalar",
|
||||||
|
"nonInstalledApps": "Yüklenmemiş Uygulamalar",
|
||||||
|
"importExport": "İçe/Dışa Aktar",
|
||||||
|
"settings": "Ayarlar",
|
||||||
|
"exportedTo": "{}'e Dışa Aktarıldı",
|
||||||
|
"obtainiumExport": "Obtainium Dışa Aktar",
|
||||||
|
"invalidInput": "Geçersiz Giriş",
|
||||||
|
"importedX": "{} İçe Aktarıldı",
|
||||||
|
"obtainiumImport": "Obtainium İçe Aktar",
|
||||||
|
"importFromURLList": "URL Listesinden İçe Aktar (Örneğin OPML)",
|
||||||
|
"searchQuery": "Arama Sorgusu",
|
||||||
|
"appURLList": "Uygulama URL Listesi",
|
||||||
|
"line": "Satır",
|
||||||
|
"searchX": "{}'yi Ara",
|
||||||
|
"noResults": "Sonuç Bulunamadı",
|
||||||
|
"importX": "{} İçe Aktar",
|
||||||
|
"importedAppsIdDisclaimer": "İçe Aktarılan Uygulamalar yanlışlıkla \"Yüklenmedi\" olarak gösterilebilir.\nBunu düzeltmek için bunları Obtainium üzerinden yeniden yükleyin.\nBu, yalnızca URL ve üçüncü taraf içe aktarma yöntemlerini etkiler.\n\nYalnızca URL ve üçüncü taraf içe aktarma yöntemlerini etkiler.",
|
||||||
|
"importErrors": "İçe Aktarma Hataları",
|
||||||
|
"importedXOfYApps": "{}'den {} Uygulama İçe Aktarıldı.",
|
||||||
|
"followingURLsHadErrors": "Aşağıdaki URL'lerde hatalar oluştu:",
|
||||||
|
"selectURL": "URL Seç",
|
||||||
|
"selectURLs": "URL'leri Seç",
|
||||||
|
"pick": "Seç",
|
||||||
|
"theme": "Tema",
|
||||||
|
"dark": "Koyu",
|
||||||
|
"light": "Aydınlık",
|
||||||
|
"followSystem": "Sistemi Takip Et",
|
||||||
|
"obtainium": "Obtainium",
|
||||||
|
"materialYou": "Material You",
|
||||||
|
"useBlackTheme": "Saf siyah koyu temasını kullan",
|
||||||
|
"appSortBy": "Uygulama Sıralama Ölçütü",
|
||||||
|
"authorName": "Yazar/Ad",
|
||||||
|
"nameAuthor": "Ad/Yazar",
|
||||||
|
"asAdded": "Eklendiği Gibi",
|
||||||
|
"appSortOrder": "Uygulama Sıralama Sırası",
|
||||||
|
"ascending": "Artan",
|
||||||
|
"descending": "Azalan",
|
||||||
|
"bgUpdateCheckInterval": "Arka Planda Güncelleme Kontrol Aralığı",
|
||||||
|
"neverManualOnly": "Asla - Yalnızca El ile",
|
||||||
|
"appearance": "Görünüm",
|
||||||
|
"showWebInAppView": "Kaynağı Uygulama Görünümünde Göster",
|
||||||
|
"pinUpdates": "Güncellemeleri Uygulamalar Görünümünün Üstüne Sabitle",
|
||||||
|
"updates": "Güncellemeler",
|
||||||
|
"sourceSpecific": "Kaynak Özel",
|
||||||
|
"appSource": "Uygulama Kaynağı",
|
||||||
|
"noLogs": "Günlük Yok",
|
||||||
|
"appLogs": "Uygulama Günlükleri",
|
||||||
|
"close": "Kapat",
|
||||||
|
"share": "Paylaş",
|
||||||
|
"appNotFound": "Uygulama Bulunamadı",
|
||||||
|
"obtainiumExportHyphenatedLowercase": "obtainium-ihracat",
|
||||||
|
"pickAnAPK": "APK Seç",
|
||||||
|
"appHasMoreThanOnePackage": "{}'nin birden fazla paketi var:",
|
||||||
|
"deviceSupportsXArch": "Cihazınız {} CPU mimarisini destekliyor.",
|
||||||
|
"deviceSupportsFollowingArchs": "Cihazınız aşağıdaki CPU mimarilerini destekliyor:",
|
||||||
|
"warning": "Uyarı",
|
||||||
|
"sourceIsXButPackageFromYPrompt": "Uygulama kaynağı '{}', ancak dağıtım paketi '{}'. Devam etmek istiyor musunuz?",
|
||||||
|
"updatesAvailable": "Güncellemeler Var",
|
||||||
|
"updatesAvailableNotifDescription": "Kullanıcıya Obtainium tarafından takip edilen bir veya daha fazla uygulama için güncelleme bulunduğuna dair bilgi verir",
|
||||||
|
"noNewUpdates": "Yeni güncelleme yok.",
|
||||||
|
"xHasAnUpdate": "{} güncelleme alıyor.",
|
||||||
|
"appsUpdated": "Uygulamalar Güncellendi",
|
||||||
|
"appsUpdatedNotifDescription": "Kullanıcıya bir veya daha fazla uygulamanın arka planda güncellendiğine dair bilgi verir",
|
||||||
|
"xWasUpdatedToY": "{} şu sürüme güncellendi: {}.",
|
||||||
|
"errorCheckingUpdates": "Güncellemeler Kontrol Edilirken Hata Oluştu",
|
||||||
|
"errorCheckingUpdatesNotifDescription": "Arka planda güncelleme kontrolü sırasında hata oluştuğunda görünen bir bildirim",
|
||||||
|
"appsRemoved": "Uygulamalar Kaldırıldı",
|
||||||
|
"appsRemovedNotifDescription": "Bir veya daha fazla uygulamanın yüklenirken hata nedeniyle kaldırıldığını bildiren bir bildirim",
|
||||||
|
"xWasRemovedDueToErrorY": "{} şu hatadan dolayı kaldırıldı: {}",
|
||||||
|
"completeAppInstallation": "Uygulama Yüklemeyi Tamamla",
|
||||||
|
"obtainiumMustBeOpenToInstallApps": "Uygulamaları yüklemek için Obtainium'un açık olması gerekiyor",
|
||||||
|
"completeAppInstallationNotifDescription": "Kullanıcıdan Obtainium'a geri dönüp bir uygulama yüklemeyi tamamlamasını isteyen bir bildirim",
|
||||||
|
"checkingForUpdates": "Güncellemeler Kontrol Ediliyor",
|
||||||
|
"checkingForUpdatesNotifDescription": "Güncellemeler kontrol edildiğinde görünen geçici bir bildirim",
|
||||||
|
"pleaseAllowInstallPerm": "Lütfen Obtainium'un uygulama yüklemesine izin verin",
|
||||||
|
"trackOnly": "Sadece Takip Et",
|
||||||
|
"errorWithHttpStatusCode": "Hata {}",
|
||||||
|
"versionCorrectionDisabled": "Sürüm düzeltme devre dışı bırakıldı (eklenti çalışmıyor gibi görünüyor)",
|
||||||
|
"unknown": "Bilinmiyor",
|
||||||
|
"none": "Hiçbiri",
|
||||||
|
"never": "Asla",
|
||||||
|
"latestVersionX": "En Son Sürüm: {}",
|
||||||
|
"installedVersionX": "Yüklenen Sürüm: {}",
|
||||||
|
"lastUpdateCheckX": "Son Güncelleme Kontrolü: {}",
|
||||||
|
"remove": "Kaldır",
|
||||||
|
"yesMarkUpdated": "Evet, Güncellendi olarak İşaretle",
|
||||||
|
"fdroid": "F-Droid Resmi",
|
||||||
|
"appIdOrName": "Uygulama Kimliği veya Adı",
|
||||||
|
"appId": "Uygulama Kimliği",
|
||||||
|
"appWithIdOrNameNotFound": "Bu kimlik veya ada sahip bir uygulama bulunamadı",
|
||||||
|
"reposHaveMultipleApps": "Depolar birden fazla uygulama içerebilir",
|
||||||
|
"fdroidThirdPartyRepo": "F-Droid Üçüncü Taraf Depo",
|
||||||
|
"steam": "Steam",
|
||||||
|
"steamMobile": "Steam Mobile",
|
||||||
|
"steamChat": "Steam Sohbet",
|
||||||
|
"install": "Yükle",
|
||||||
|
"markInstalled": "Yüklendi olarak İşaretle",
|
||||||
|
"update": "Güncelle",
|
||||||
|
"markUpdated": "Güncellendi olarak İşaretle",
|
||||||
|
"additionalOptions": "Ek Seçenekler",
|
||||||
|
"disableVersionDetection": "Sürüm Algılama Devre Dışı",
|
||||||
|
"noVersionDetectionExplanation": "Bu seçenek, sürüm algılamanın doğru çalışmadığı uygulamalar için kullanılmalıdır.",
|
||||||
|
"downloadingX": "{} İndiriliyor",
|
||||||
|
"downloadNotifDescription": "Bir uygulamanın indirme sürecinde ilerlemeyi bildiren bir bildirim",
|
||||||
|
"noAPKFound": "APK bulunamadı",
|
||||||
|
"noVersionDetection": "Sürüm Algılanamıyor",
|
||||||
|
"categorize": "Kategorize Et",
|
||||||
|
"categories": "Kategoriler",
|
||||||
|
"category": "Kategori",
|
||||||
|
"noCategory": "Kategori Yok",
|
||||||
|
"noCategories": "Kategoriler Yok",
|
||||||
|
"deleteCategoriesQuestion": "Kategorileri Silmek İstiyor musunuz?",
|
||||||
|
"categoryDeleteWarning": "Silinen kategorilerdeki tüm uygulamalar kategorisiz olarak ayarlanacaktır.",
|
||||||
|
"addCategory": "Kategori Ekle",
|
||||||
|
"label": "Etiket",
|
||||||
|
"language": "Dil",
|
||||||
|
"copiedToClipboard": "Panoya Kopyalandı",
|
||||||
|
"storagePermissionDenied": "Depolama izni reddedildi",
|
||||||
|
"selectedCategorizeWarning": "Bu, seçilen uygulamalar için mevcut kategori ayarlarını değiştirecektir.",
|
||||||
|
"filterAPKsByRegEx": "APK'leri Düzenli İfade ile Filtrele",
|
||||||
|
"removeFromObtainium": "Obtainium'dan Kaldır",
|
||||||
|
"uninstallFromDevice": "Cihazdan Kaldır",
|
||||||
|
"onlyWorksWithNonVersionDetectApps": "Yalnızca Sürüm Algılaması Devre Dışı Uygulamalar İçin Çalışır.",
|
||||||
|
"releaseDateAsVersion": "Sürüm Olarak Yayın Tarihi Kullan",
|
||||||
|
"releaseDateAsVersionExplanation": "Bu seçenek, sürüm algılamanın doğru çalışmadığı ancak bir sürüm tarihinin mevcut olduğu uygulamalar için kullanılmalıdır.",
|
||||||
|
"changes": "Değişiklikler",
|
||||||
|
"releaseDate": "Yayın Tarihi",
|
||||||
|
"importFromURLsInFile": "Dosyadaki URL'lerden İçe Aktar",
|
||||||
|
"versionDetection": "Sürüm Tespiti",
|
||||||
|
"standardVersionDetection": "Standart sürüm tespiti",
|
||||||
|
"groupByCategory": "Kategoriye Göre Grupla",
|
||||||
|
"autoApkFilterByArch": "Mümkünse APK'leri CPU mimarisi ile filtreleme girişimi",
|
||||||
|
"overrideSource": "Kaynağı Geçersiz Kıl",
|
||||||
|
"dontShowAgain": "Bunu tekrar gösterme",
|
||||||
|
"dontShowTrackOnlyWarnings": "'Yalnızca Takip Edilen' uyarılarını gösterme",
|
||||||
|
"dontShowAPKOriginWarnings": "APK kaynağı uyarılarını gösterme",
|
||||||
|
"moveNonInstalledAppsToBottom": "Yüklenmemiş Uygulamaları Uygulamalar Görünümünün Altına Taşı",
|
||||||
|
"gitlabPATLabel": "GitLab Kişisel Erişim Belirteci\n(Arama ve Daha İyi APK Keşfi İçin)",
|
||||||
|
"about": "Hakkında",
|
||||||
|
"requiresCredentialsInSettings": "{}: Bu, ek kimlik bilgilerine ihtiyaç duyar (Ayarlar'da)",
|
||||||
|
"checkOnStart": "Başlangıçta güncellemeleri kontrol et",
|
||||||
|
"tryInferAppIdFromCode": "Uygulama kimliğini kaynak kodundan çıkarma girişimi",
|
||||||
|
"removeOnExternalUninstall": "Harici kaldırmada otomatik olarak kaldırılan uygulamalar",
|
||||||
|
"pickHighestVersionCode": "Otomatik olarak en yüksek sürüm kodlu APK'yi seç",
|
||||||
|
"checkUpdateOnDetailPage": "Uygulama detay sayfasını açarken güncellemeleri kontrol et",
|
||||||
|
"disablePageTransitions": "Sayfa geçiş animasyonlarını devre dışı bırak",
|
||||||
|
"reversePageTransitions": "Sayfa geçiş animasyonlarını tersine çevir",
|
||||||
|
"minStarCount": "Minimum Yıldız Sayısı",
|
||||||
|
"addInfoBelow": "Bu bilgiyi aşağıya ekle.",
|
||||||
|
"addInfoInSettings": "Bu bilgiyi Ayarlar'da ekleyin.",
|
||||||
|
"githubSourceNote": "GitHub hız sınırlaması bir API anahtarı kullanılarak atlanabilir.",
|
||||||
|
"gitlabSourceNote": "GitLab APK çıkarma işlemi bir API anahtarı olmadan çalışmayabilir.",
|
||||||
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
|
"filterReleaseNotesByRegEx": "Sürüm Notlarını Düzenli İfade ile Filtrele",
|
||||||
|
"customLinkFilterRegex": "Özel APK Bağlantı Filtresi Düzenli İfade ile (Varsayılan '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "Uygulama Güncellemeleri Denendi",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Kullanıcıya bir veya daha fazla uygulamanın arka planda potansiyel olarak güncellendiğini bildiren bildirim",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} muhtemelen {} sürümüne güncellendi.",
|
||||||
|
"enableBackgroundUpdates": "Arka plan güncellemelerini etkinleştir",
|
||||||
|
"backgroundUpdateReqsExplanation": "Arka plan güncellemeleri tüm uygulamalar için mümkün olmayabilir.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "Arka plan kurulumunun başarısı, Obtainium'un açıldığında ancak belirlenebilir.",
|
||||||
|
"verifyLatestTag": "'latest' etiketini doğrula",
|
||||||
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "Ara bağlantı bulunamadı",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Arka plan güncellemelerinden muaf tut (etkinse)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "WiFi olmadığında arka plan güncellemelerini devre dışı bırak",
|
||||||
|
"autoSelectHighestVersionCode": "Otomatik olarak en yüksek sürüm kodunu seç",
|
||||||
|
"versionExtractionRegEx": "Sürüm Çıkarma Düzenli İfade",
|
||||||
|
"matchGroupToUse": "Sürüm Çıkarma Regex için Kullanılacak Eşleşme Grubu",
|
||||||
|
"highlightTouchTargets": "Daha az belirgin dokunma hedeflerini vurgula",
|
||||||
|
"pickExportDir": "Dışa Aktarılacak Klasörü Seç",
|
||||||
|
"autoExportOnChanges": "Değişikliklerde otomatik olarak dışa aktar",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
|
"filterVersionsByRegEx": "Sürümleri Düzenli İfade ile Filtrele",
|
||||||
|
"trySelectingSuggestedVersionCode": "Önerilen sürüm kodunu seçmeyi dene",
|
||||||
|
"dontSortReleasesList": "API'den sıralama düzenini koru",
|
||||||
|
"reverseSort": "Ters sıralama",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
|
"debugMenu": "Hata Ayıklama Menüsü",
|
||||||
|
"bgTaskStarted": "Arka plan görevi başladı - günlükleri kontrol et.",
|
||||||
|
"runBgCheckNow": "Arka Plan Güncelleme Kontrolünü Şimdi Çalıştır",
|
||||||
|
"versionExtractWholePage": "Tüm Sayfaya Sürüm Çıkarma Regex'i Uygula",
|
||||||
|
"installing": "Yükleniyor",
|
||||||
|
"skipUpdateNotifications": "Güncelleme bildirimlerini atla",
|
||||||
|
"updatesAvailableNotifChannel": "Güncellemeler Mevcut",
|
||||||
|
"appsUpdatedNotifChannel": "Güncellenen Uygulamalar",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "Uygulama Güncellemeleri Denendi",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Güncellemeler Kontrol Edilirken Hata",
|
||||||
|
"appsRemovedNotifChannel": "Kaldırılan Uygulamalar",
|
||||||
|
"downloadingXNotifChannel": "{} İndiriliyor",
|
||||||
|
"completeAppInstallationNotifChannel": "Uygulama Kurulumu Tamamlandı",
|
||||||
|
"checkingForUpdatesNotifChannel": "Güncellemeler Kontrol Ediliyor",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Yalnızca yüklü ve Yalnızca İzleme Uygulamalarını güncelleme",
|
||||||
|
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||||
|
"selectX": "Select {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
|
"installMethod": "Installation method",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
|
"removeAppQuestion": {
|
||||||
|
"one": "Uygulamayı Kaldır?",
|
||||||
|
"other": "Uygulamaları Kaldır?"
|
||||||
|
},
|
||||||
|
"tooManyRequestsTryAgainInMinutes": {
|
||||||
|
"one": "Çok fazla istek (hız sınırlı) - {} dakika sonra tekrar deneyin",
|
||||||
|
"other": "Çok fazla istek (hız sınırlı) - {} dakika sonra tekrar deneyin"
|
||||||
|
},
|
||||||
|
"bgUpdateGotErrorRetryInMinutes": {
|
||||||
|
"one": "Arka plan güncelleme kontrolü bir hatayla karşılaştı, {} dakika sonra tekrar kontrol edilecek",
|
||||||
|
"other": "Arka plan güncelleme kontrolü bir hatayla karşılaştı, {} dakika sonra tekrar kontrol edilecek"
|
||||||
|
},
|
||||||
|
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
||||||
|
"one": "Arka plan güncelleme kontrolü {} güncelleme buldu - gerektiğinde kullanıcıyı bilgilendirecek",
|
||||||
|
"other": "Arka plan güncelleme kontrolü {} güncelleme buldu - gerektiğinde kullanıcıyı bilgilendirecek"
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"one": "{} Uygulama",
|
||||||
|
"other": "{} Uygulamalar"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"one": "{} URL",
|
||||||
|
"other": "{} URL'ler"
|
||||||
|
},
|
||||||
|
"minute": {
|
||||||
|
"one": "{} Dakika",
|
||||||
|
"other": "{} Dakika"
|
||||||
|
},
|
||||||
|
"hour": {
|
||||||
|
"one": "{} Saat",
|
||||||
|
"other": "{} Saat"
|
||||||
|
},
|
||||||
|
"day": {
|
||||||
|
"one": "{} Gün",
|
||||||
|
"other": "{} Gün"
|
||||||
|
},
|
||||||
|
"clearedNLogsBeforeXAfterY": {
|
||||||
|
"one": "{n} log temizlendi (önce = {before}, sonra = {after})",
|
||||||
|
"other": "{n} log temizlendi (önce = {before}, sonra = {after})"
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesAvailable": {
|
||||||
|
"one": "{} ve 1 diğer uygulama güncellemeye sahip.",
|
||||||
|
"other": "{} ve {} daha fazla uygulama güncellemeye sahip."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesInstalled": {
|
||||||
|
"one": "{} ve 1 diğer uygulama güncellendi.",
|
||||||
|
"other": "{} ve {} daha fazla uygulama güncellendi."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} ve 1 diğer uygulama muhtemelen güncellendi.",
|
||||||
|
"other": "{} ve {} daha fazla uygulama muhtemelen güncellendi."
|
||||||
|
}
|
||||||
|
}
|
||||||
342
assets/translations/vi.json
Normal file
342
assets/translations/vi.json
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
{
|
||||||
|
"invalidURLForSource": "URL ứng dụng {} không hợp lệ",
|
||||||
|
"noReleaseFound": "Không thể tìm thấy bản phát hành phù hợp",
|
||||||
|
"noVersionFound": "Không thể xác định phiên bản phát hành",
|
||||||
|
"urlMatchesNoSource": "URL không khớp với nguồn đã biết",
|
||||||
|
"cantInstallOlderVersion": "Không thể cài đặt phiên bản cũ hơn của Ứng dụng",
|
||||||
|
"appIdMismatch": "ID gói đã tải xuống không khớp với ID ứng dụng hiện tại",
|
||||||
|
"functionNotImplemented": "Lớp này chưa triển khai chức năng này",
|
||||||
|
"placeholder": "Giữ chỗ",
|
||||||
|
"someErrors": "Đã xảy ra một số lỗi",
|
||||||
|
"unexpectedError": "Lỗi không mong đợi",
|
||||||
|
"ok": "OK",
|
||||||
|
"and": "và",
|
||||||
|
"githubPATLabel": "Mã thông báo truy cập cá nhân GitHub (Tăng tốc độ giới hạn)",
|
||||||
|
"includePrereleases": "Bao gồm các bản phát hành trước",
|
||||||
|
"fallbackToOlderReleases": "Dự phòng về bản phát hành cũ hơn",
|
||||||
|
"filterReleaseTitlesByRegEx": "Lọc tiêu đề bản phát hành theo biểu thức chính quy",
|
||||||
|
"invalidRegEx": "Biểu thức chính quy không hợp lệ",
|
||||||
|
"noDescription": "Không có mô tả",
|
||||||
|
"cancel": "Hủy",
|
||||||
|
"continue": "Tiếp tục",
|
||||||
|
"requiredInBrackets": "(Yêu cầu)",
|
||||||
|
"dropdownNoOptsError": "LỖI: TẢI XUỐNG PHẢI CÓ ÍT NHẤT MỘT LỰA CHỌN",
|
||||||
|
"colour": "Màu sắc",
|
||||||
|
"githubStarredRepos": "Kho lưu trữ có gắn dấu sao GitHub",
|
||||||
|
"uname": "Tên người dùng",
|
||||||
|
"wrongArgNum": "Số lượng đối số được cung cấp sai",
|
||||||
|
"xIsTrackOnly": "{}là Chỉ-Theo dõi",
|
||||||
|
"source": "Nguồn",
|
||||||
|
"app": "Ứng dụng",
|
||||||
|
"appsFromSourceAreTrackOnly": "Các ứng dụng từ nguồn này là 'Chỉ-Theo dõi'.",
|
||||||
|
"youPickedTrackOnly": "Bạn đã chọn tùy chọn 'Chỉ-Theo dõi'.",
|
||||||
|
"trackOnlyAppDescription": "Ứng dụng sẽ được theo dõi để cập nhật, nhưng Obtainium sẽ không thể tải xuống hoặc cài đặt nó.",
|
||||||
|
"cancelled": "Đã hủy",
|
||||||
|
"appAlreadyAdded": "Ứng dụng được thêm rồi",
|
||||||
|
"alreadyUpToDateQuestion": "Ứng dụng đã được cập nhật?",
|
||||||
|
"addApp": "Thêm ứng dụng",
|
||||||
|
"appSourceURL": "URL nguồn ứng dụng",
|
||||||
|
"error": "Lỗi",
|
||||||
|
"add": "Thêm",
|
||||||
|
"searchSomeSourcesLabel": "Tìm kiếm (Chỉ một số nguồn)",
|
||||||
|
"search": "Tìm kiếm",
|
||||||
|
"additionalOptsFor": "Tùy chọn bổ sung cho {}",
|
||||||
|
"supportedSources": "Nguồn được hỗ trợ",
|
||||||
|
"trackOnlyInBrackets": "(Chỉ-Theo dõi)",
|
||||||
|
"searchableInBrackets": "(Có thể tìm kiếm)",
|
||||||
|
"appsString": "Ứng dụng",
|
||||||
|
"noApps": "Không có ứng dụng",
|
||||||
|
"noAppsForFilter": "Không có ứng dụng cho bộ lọc",
|
||||||
|
"byX": "Bởi {}",
|
||||||
|
"percentProgress": "Tiến triển: {}%",
|
||||||
|
"pleaseWait": "Vui lòng chờ",
|
||||||
|
"updateAvailable": "Có sẵn bản cập nhật",
|
||||||
|
"estimateInBracketsShort": "(Ước lượng.)",
|
||||||
|
"notInstalled": "Chưa cài đặt",
|
||||||
|
"estimateInBrackets": "(Ước lượng)",
|
||||||
|
"selectAll": "Chọn tất cả",
|
||||||
|
"deselectX": "Bỏ chọn {}",
|
||||||
|
"xWillBeRemovedButRemainInstalled": "{} sẽ bị xóa khỏi Obtainium nhưng vẫn còn cài đặt trên thiết bị.",
|
||||||
|
"removeSelectedAppsQuestion": "Xóa ứng dụng đã chọn?",
|
||||||
|
"removeSelectedApps": "Xóa ứng dụng đã chọn",
|
||||||
|
"updateX": "Cập nhật {}",
|
||||||
|
"installX": "Cài đặt {}",
|
||||||
|
"markXTrackOnlyAsUpdated": "Đánh dấu {}\n(Chỉ-Theo dõi)\nnhư là đã cập nhật",
|
||||||
|
"changeX": "Thay đổi {}",
|
||||||
|
"installUpdateApps": "Cài đặt/Cập nhật ứng dụng",
|
||||||
|
"installUpdateSelectedApps": "Cài đặt/Cập nhật ứng dụng đã chọn",
|
||||||
|
"markXSelectedAppsAsUpdated": "Đánh dấu {} ứng dụng đã chọn là đã cập nhật?",
|
||||||
|
"no": "Không",
|
||||||
|
"yes": "Đúng",
|
||||||
|
"markSelectedAppsUpdated": "Đánh dấu các ứng dụng đã chọn là đã cập nhật",
|
||||||
|
"pinToTop": "Ghim đầu trang",
|
||||||
|
"unpinFromTop": "Bỏ ghim khỏi đầu trang",
|
||||||
|
"resetInstallStatusForSelectedAppsQuestion": "Đặt lại trạng thái cài đặt cho ứng dụng đã chọn?",
|
||||||
|
"installStatusOfXWillBeResetExplanation": "Trạng thái cài đặt của mọi Ứng dụng đã chọn sẽ được đặt lại.\n\nĐiều này có thể hữu ích khi phiên bản Ứng dụng hiển thị trong Obtainium không chính xác do cập nhật không thành công hoặc các sự cố khác.",
|
||||||
|
"shareSelectedAppURLs": "Chia sẻ URL ứng dụng đã chọn",
|
||||||
|
"resetInstallStatus": "Đặt lại trạng thái cài đặt",
|
||||||
|
"more": "Nhiều hơn",
|
||||||
|
"removeOutdatedFilter": "Xóa bộ lọc ứng dụng lỗi thời",
|
||||||
|
"showOutdatedOnly": "Chỉ hiển thị các ứng dụng lỗi thời",
|
||||||
|
"filter": "Lọc",
|
||||||
|
"filterActive": "Lọc *",
|
||||||
|
"filterApps": "Lọc ứng dụng",
|
||||||
|
"appName": "Tên ứng dụng",
|
||||||
|
"author": "Tác giả",
|
||||||
|
"upToDateApps": "Ứng dụng cập nhật",
|
||||||
|
"nonInstalledApps": "Ứng dụng chưa được cài đặt",
|
||||||
|
"importExport": "Nhập/Xuất",
|
||||||
|
"settings": "Cài đặt",
|
||||||
|
"exportedTo": "Đã xuất sang {}",
|
||||||
|
"obtainiumExport": "Xuất Obtainium",
|
||||||
|
"invalidInput": "Đầu vào không hợp lệ",
|
||||||
|
"importedX": "Đã nhập {}",
|
||||||
|
"obtainiumImport": "Nhập Obtainium",
|
||||||
|
"importFromURLList": "Nhập từ danh sách URL",
|
||||||
|
"searchQuery": "Truy vấn tìm kiếm",
|
||||||
|
"appURLList": "Danh sách URL ứng dụng",
|
||||||
|
"line": "Hàng",
|
||||||
|
"searchX": "Tìm kiếm {}",
|
||||||
|
"noResults": "Không có kết quả nào được tìm thấy",
|
||||||
|
"importX": "Nhập {}",
|
||||||
|
"importedAppsIdDisclaimer": "Ứng dụng đã nhập có thể hiển thị không chính xác là \"Chưa được cài đặt\".\nĐể khắc phục sự cố này, hãy cài đặt lại chúng thông qua Obtainium.\nĐiều này sẽ không ảnh hưởng đến dữ liệu Ứng dụng.\n\nChỉ ảnh hưởng đến URL và phương thức nhập của bên thứ ba.",
|
||||||
|
"importErrors": "Lỗi nhập",
|
||||||
|
"importedXOfYApps": "{} trong số {} Ứng dụng đã được nhập.",
|
||||||
|
"followingURLsHadErrors": "Các URL sau có lỗi:",
|
||||||
|
"selectURL": "Chọn URL",
|
||||||
|
"selectURLs": "Chọn URL",
|
||||||
|
"pick": "Chọn",
|
||||||
|
"theme": "Chủ đề",
|
||||||
|
"dark": "Tối",
|
||||||
|
"light": "Sáng",
|
||||||
|
"followSystem": "Theo hệ thống",
|
||||||
|
"obtainium": "Obtainium",
|
||||||
|
"materialYou": "Material You",
|
||||||
|
"useBlackTheme": "Nền đen",
|
||||||
|
"appSortBy": "Sắp xếp ứng dụng",
|
||||||
|
"authorName": "Tác giả",
|
||||||
|
"nameAuthor": "Tên",
|
||||||
|
"asAdded": "Như đã thêm",
|
||||||
|
"appSortOrder": "Thứ tự sắp xếp",
|
||||||
|
"ascending": "Tăng dần",
|
||||||
|
"descending": "Giảm dần",
|
||||||
|
"bgUpdateCheckInterval": "Khoảng thời gian kiểm tra cập nhật nền",
|
||||||
|
"neverManualOnly": "Không bao giờ - Chỉ thủ công",
|
||||||
|
"appearance": "Hiển thị",
|
||||||
|
"showWebInAppView": "Hiển thị trang web Nguồn trong chế độ xem Ứng dụng",
|
||||||
|
"pinUpdates": "Ghim nội dung cập nhật lên đầu chế độ xem Ứng dụng",
|
||||||
|
"updates": "Cập nhật",
|
||||||
|
"sourceSpecific": "Nguồn cụ thể",
|
||||||
|
"appSource": "Nguồn ứng dụng",
|
||||||
|
"noLogs": "Không có nhật ký",
|
||||||
|
"appLogs": "Nhật ký ứng dụng",
|
||||||
|
"close": "Đóng",
|
||||||
|
"share": "Chia sẻ",
|
||||||
|
"appNotFound": "Không tìm thấy ứng dụng",
|
||||||
|
"obtainiumExportHyphenatedLowercase": "xuất khẩu-obtainium",
|
||||||
|
"pickAnAPK": "Chọn một APK",
|
||||||
|
"appHasMoreThanOnePackage": "{} có nhiều gói:",
|
||||||
|
"deviceSupportsXArch": "Thiết bị của bạn hỗ trợ kiến trúc CPU {}.",
|
||||||
|
"deviceSupportsFollowingArchs": "Thiết bị của bạn hỗ trợ các kiến trúc CPU sau:",
|
||||||
|
"warning": "Cảnh báo",
|
||||||
|
"sourceIsXButPackageFromYPrompt": "Nguồn ứng dụng là '{}' nhưng gói phát hành đến từ '{}'. Tiếp tục?",
|
||||||
|
"updatesAvailable": "Cập nhật có sẵn",
|
||||||
|
"updatesAvailableNotifDescription": "Thông báo cho người dùng rằng có bản cập nhật cho một hoặc nhiều Ứng dụng được theo dõi bởi Obtainium",
|
||||||
|
"noNewUpdates": "Không có bản cập nhật mới.",
|
||||||
|
"xHasAnUpdate": "{} có bản cập nhật.",
|
||||||
|
"appsUpdated": "Ứng dụng đã cập nhật ",
|
||||||
|
"appsUpdatedNotifDescription": "Thông báo cho người dùng rằng các bản cập nhật cho một hoặc nhiều Ứng dụng đã được áp dụng trong nền",
|
||||||
|
"xWasUpdatedToY": "{} đã được cập nhật thành {}.",
|
||||||
|
"errorCheckingUpdates": "Lỗi kiểm tra bản cập nhật",
|
||||||
|
"errorCheckingUpdatesNotifDescription": "Thông báo hiển thị khi kiểm tra cập nhật nền không thành công",
|
||||||
|
"appsRemoved": "Ứng dụng đã loại bỏ",
|
||||||
|
"appsRemovedNotifDescription": "Thông báo cho người dùng rằng một hoặc nhiều Ứng dụng đã bị loại bỏ do lỗi khi tải chúng",
|
||||||
|
"xWasRemovedDueToErrorY": "{} đã bị loại bỏ do lỗi này: {}",
|
||||||
|
"completeAppInstallation": "Hoàn tất cài đặt ứng dụng",
|
||||||
|
"obtainiumMustBeOpenToInstallApps": "Obtainium phải được mở để cài đặt Ứng dụng",
|
||||||
|
"completeAppInstallationNotifDescription": "Yêu cầu người dùng quay lại Obtainium để hoàn tất cài đặt Ứng dụng",
|
||||||
|
"checkingForUpdates": "Đang kiểm tra cập nhật",
|
||||||
|
"checkingForUpdatesNotifDescription": "Thông báo tạm thời xuất hiện khi kiểm tra bản cập nhật",
|
||||||
|
"pleaseAllowInstallPerm": "Vui lòng cho phép Obtainium cài đặt Ứng dụng",
|
||||||
|
"trackOnly": "Chỉ-Theo dõi",
|
||||||
|
"errorWithHttpStatusCode": "Lỗi {}",
|
||||||
|
"versionCorrectionDisabled": "Tính năng sửa phiên bản bị vô hiệu hóa (plugin dường như không hoạt động)",
|
||||||
|
"unknown": "Không xác định",
|
||||||
|
"none": "Không",
|
||||||
|
"never": "Không bao giờ",
|
||||||
|
"latestVersionX": "Phiên bản mới nhất: {}",
|
||||||
|
"installedVersionX": "Phiên bản đã cài đặt: {}",
|
||||||
|
"lastUpdateCheckX": "Kiểm tra cập nhật lần cuối: {}",
|
||||||
|
"remove": "Loại bỏ",
|
||||||
|
"yesMarkUpdated": "Có, Đánh dấu là đã cập nhật",
|
||||||
|
"fdroid": "Chính thức của F-Droid",
|
||||||
|
"appIdOrName": "ID hoặc tên ứng dụng",
|
||||||
|
"appId": "ID ứng dụng",
|
||||||
|
"appWithIdOrNameNotFound": "Không tìm thấy ứng dụng nào có ID hoặc tên đó",
|
||||||
|
"reposHaveMultipleApps": "Kho có thể chứa nhiều Ứng dụng",
|
||||||
|
"fdroidThirdPartyRepo": "Kho lưu trữ bên thứ ba F-Droid",
|
||||||
|
"steam": "Steam",
|
||||||
|
"steamMobile": "Steam Mobile",
|
||||||
|
"steamChat": "Steam Chat",
|
||||||
|
"install": "Cài đặt",
|
||||||
|
"markInstalled": "Đánh dấu là đã cài đặt",
|
||||||
|
"update": "Cập nhật",
|
||||||
|
"markUpdated": "Đánh dấu đã cập nhật",
|
||||||
|
"additionalOptions": "Tùy chọn bổ sung",
|
||||||
|
"disableVersionDetection": "Tắt tính năng phát hiện phiên bản",
|
||||||
|
"noVersionDetectionExplanation": "Chỉ nên sử dụng tùy chọn này cho Ứng dụng mà tính năng phát hiện phiên bản không hoạt động chính xác.",
|
||||||
|
"downloadingX": "Đang tải xuống {}",
|
||||||
|
"downloadNotifDescription": "Thông báo cho người dùng về tiến trình tải xuống Ứng dụng",
|
||||||
|
"noAPKFound": "Không tìm thấy APK",
|
||||||
|
"noVersionDetection": "Không phát hiện phiên bản",
|
||||||
|
"categorize": "Phân loại",
|
||||||
|
"categories": "Thể loại",
|
||||||
|
"category": "Thể loại",
|
||||||
|
"noCategory": "Không thể loại",
|
||||||
|
"noCategories": "Không thể loại",
|
||||||
|
"deleteCategoriesQuestion": "Xóa thể loại?",
|
||||||
|
"categoryDeleteWarning": "Tất cả ứng dụng trong thể loại đã xóa sẽ được đặt thành chưa được phân loại.",
|
||||||
|
"addCategory": "Thêm thể loại",
|
||||||
|
"label": "Nhãn",
|
||||||
|
"language": "Ngôn ngữ",
|
||||||
|
"copiedToClipboard": "Sao chép vào clipboard",
|
||||||
|
"storagePermissionDenied": "Quyền lưu trữ bị từ chối",
|
||||||
|
"selectedCategorizeWarning": "Điều này sẽ thay thế mọi cài đặt thể loại hiện có cho Ứng dụng đã chọn.",
|
||||||
|
"filterAPKsByRegEx": "Lọc APK theo biểu thức chính quy",
|
||||||
|
"removeFromObtainium": "Loại khỏi Obtainium",
|
||||||
|
"uninstallFromDevice": "Gỡ cài đặt khỏi thiết bị",
|
||||||
|
"onlyWorksWithNonVersionDetectApps": "Chỉ hoạt động với Ứng dụng đã tắt tính năng phát hiện phiên bản.",
|
||||||
|
"releaseDateAsVersion": "Sử dụng ngày phát hành làm phiên bản",
|
||||||
|
"releaseDateAsVersionExplanation": "Chỉ nên sử dụng tùy chọn này cho Ứng dụng trong đó tính năng phát hiện phiên bản không hoạt động chính xác nhưng đã có ngày phát hành.",
|
||||||
|
"changes": "Thay đổi",
|
||||||
|
"releaseDate": "Ngày phát hành",
|
||||||
|
"importFromURLsInFile": "Nhập từ URL trong Tệp (như OPML)",
|
||||||
|
"versionDetection": "Phát hiện phiên bản",
|
||||||
|
"standardVersionDetection": "Phát hiện phiên bản tiêu chuẩn",
|
||||||
|
"groupByCategory": "Nhóm theo thể loại",
|
||||||
|
"autoApkFilterByArch": "Cố gắng lọc APK theo kiến trúc CPU nếu có thể",
|
||||||
|
"overrideSource": "Ghi đè nguồn",
|
||||||
|
"dontShowAgain": "Đừng hiển thị thông tin này nữa",
|
||||||
|
"dontShowTrackOnlyWarnings": "Không hiển thị cảnh báo 'Chỉ-Theo dõi'",
|
||||||
|
"dontShowAPKOriginWarnings": "Không hiển thị cảnh báo nguồn gốc APK",
|
||||||
|
"moveNonInstalledAppsToBottom": "Di chuyển Ứng dụng chưa được cài đặt xuống cuối chế độ xem Ứng dụng",
|
||||||
|
"gitlabPATLabel": "Mã thông báo truy cập cá nhân GitLab\n(Cho phép tìm kiếm và khám phá APK tốt hơn)",
|
||||||
|
"about": "Giới thiệu",
|
||||||
|
"requiresCredentialsInSettings": "{}: Điều này cần thông tin xác thực bổ sung (trong Cài đặt)",
|
||||||
|
"checkOnStart": "Kiểm tra các bản cập nhật khi khởi động",
|
||||||
|
"tryInferAppIdFromCode": "Thử suy ra ID ứng dụng từ mã nguồn",
|
||||||
|
"removeOnExternalUninstall": "Tự động xóa ứng dụng đã gỡ cài đặt bên ngoài",
|
||||||
|
"pickHighestVersionCode": "Tự động chọn APK mã phiên bản cao nhất",
|
||||||
|
"checkUpdateOnDetailPage": "Kiểm tra các bản cập nhật khi mở trang chi tiết Ứng dụng",
|
||||||
|
"disablePageTransitions": "Tắt hiệu ứng chuyển trang",
|
||||||
|
"reversePageTransitions": "Hoạt ảnh chuyển đổi trang đảo ngược",
|
||||||
|
"minStarCount": "Số lượng sao tối thiểu",
|
||||||
|
"addInfoBelow": "Thêm thông tin này vào bên dưới.",
|
||||||
|
"addInfoInSettings": "Thêm thông tin này vào Cài đặt.",
|
||||||
|
"githubSourceNote": "Có thể tránh được việc giới hạn tốc độ GitHub bằng cách sử dụng khóa API.",
|
||||||
|
"gitlabSourceNote": "Trích xuất APK GitLab có thể không hoạt động nếu không có khóa API.",
|
||||||
|
"sortByLastLinkSegment": "Sort by only the last segment of the link",
|
||||||
|
"filterReleaseNotesByRegEx": "Lọc ghi chú phát hành theo biểu thức chính quy",
|
||||||
|
"customLinkFilterRegex": "Bộ lọc liên kết APK tùy chỉnh theo biểu thức chính quy (Mặc định '.apk$')",
|
||||||
|
"appsPossiblyUpdated": "Đã cố gắng cập nhật ứng dụng",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "Thông báo cho người dùng rằng các bản cập nhật cho một hoặc nhiều Ứng dụng có khả năng được áp dụng trong nền",
|
||||||
|
"xWasPossiblyUpdatedToY": "{} có thể đã được cập nhật thành {}.",
|
||||||
|
"enableBackgroundUpdates": "Bật cập nhật nền",
|
||||||
|
"backgroundUpdateReqsExplanation": "Có thể không thực hiện được cập nhật nền cho tất cả ứng dụng.",
|
||||||
|
"backgroundUpdateLimitsExplanation": "Sự thành công của cài đặt nền chỉ có thể được xác định khi mở Obtainium.",
|
||||||
|
"verifyLatestTag": "Xác minh thẻ 'mới nhất'",
|
||||||
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
|
"filterByLinkText": "Filter links by link text",
|
||||||
|
"intermediateLinkNotFound": "Không tìm thấy liên kết trung gian",
|
||||||
|
"intermediateLink": "Intermediate link",
|
||||||
|
"exemptFromBackgroundUpdates": "Miễn cập nhật nền (nếu được bật)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "Tắt cập nhật nền khi không có WiFi",
|
||||||
|
"autoSelectHighestVersionCode": "Tự động chọn APK mã phiên bản cao nhất",
|
||||||
|
"versionExtractionRegEx": "Trích xuất phiên bản RegEx",
|
||||||
|
"matchGroupToUse": "Nhóm đối sánh để sử dụng cho Regex trích xuất phiên bản",
|
||||||
|
"highlightTouchTargets": "Đánh dấu các mục tiêu cảm ứng ít rõ ràng hơn",
|
||||||
|
"pickExportDir": "Chọn thư mục xuất",
|
||||||
|
"autoExportOnChanges": "Tự động xuất khi thay đổi",
|
||||||
|
"includeSettings": "Include settings",
|
||||||
|
"filterVersionsByRegEx": "Lọc phiên bản theo biểu thức chính quy",
|
||||||
|
"trySelectingSuggestedVersionCode": "Thử chọn APK Mã phiên bản được đề xuất",
|
||||||
|
"dontSortReleasesList": "Giữ lại thứ tự phát hành từ API",
|
||||||
|
"reverseSort": "Sắp xếp ngược",
|
||||||
|
"takeFirstLink": "Take first link",
|
||||||
|
"skipSort": "Skip sorting",
|
||||||
|
"debugMenu": "Danh sách gỡ lỗi",
|
||||||
|
"bgTaskStarted": "Tác vụ nền đã bắt đầu - kiểm tra nhật ký.",
|
||||||
|
"runBgCheckNow": "Chạy kiểm tra cập nhật nền ngay bây giờ",
|
||||||
|
"versionExtractWholePage": "Áp dụng Regex trích xuất phiên bản cho toàn bộ trang",
|
||||||
|
"installing": "Đang cài đặt",
|
||||||
|
"skipUpdateNotifications": "Bỏ qua thông báo cập nhật",
|
||||||
|
"updatesAvailableNotifChannel": "Cập nhật có sẵn",
|
||||||
|
"appsUpdatedNotifChannel": "Đã cập nhật ứng dụng",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "Đã cố gắng cập nhật ứng dụng",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "Lỗi kiểm tra bản cập nhật",
|
||||||
|
"appsRemovedNotifChannel": "Ứng dụng đã bị loại bỏ",
|
||||||
|
"downloadingXNotifChannel": "Đang tải xuống {}",
|
||||||
|
"completeAppInstallationNotifChannel": "Hoàn tất cài đặt ứng dụng",
|
||||||
|
"checkingForUpdatesNotifChannel": "Đang kiểm tra cập nhật",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "Chỉ kiểm tra các ứng dụng đã cài đặt và Chỉ-Theo dõi để biết các bản cập nhật",
|
||||||
|
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||||
|
"selectX": "Select {}",
|
||||||
|
"parallelDownloads": "Allow parallel downloads",
|
||||||
|
"installMethod": "Installation method",
|
||||||
|
"normal": "Normal",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "Shizuku chưa khởi động",
|
||||||
|
"removeAppQuestion":{
|
||||||
|
"one": "Gỡ ứng dụng?",
|
||||||
|
"other": "Gỡ ứng dụng?"
|
||||||
|
},
|
||||||
|
"tooManyRequestsTryAgainInMinutes":{
|
||||||
|
"one": "Quá nhiều yêu cầu (tốc độ giới hạn) - hãy thử lại sau {} phút",
|
||||||
|
"other": "Quá nhiều yêu cầu (tốc độ giới hạn) - hãy thử lại sau {} phút"
|
||||||
|
},
|
||||||
|
"bgUpdateGotErrorRetryInMinutes":{
|
||||||
|
"one": "Việc kiểm tra bản cập nhật BG gặp phải {}, sẽ lên lịch kiểm tra lại sau {} phút",
|
||||||
|
"other": "Việc kiểm tra bản cập nhật BG gặp phải {}, sẽ lên lịch kiểm tra lại sau {} phút"
|
||||||
|
},
|
||||||
|
"bgCheckFoundUpdatesWillNotifyIfNeeded":{
|
||||||
|
"one": "Đang kiểm tra bản cập nhật BG tìm thấy {} bản cập nhật - sẽ thông báo cho người dùng nếu cần",
|
||||||
|
"other": "Đang kiểm tra bản cập nhật BG tìm thấy {} bản cập nhật - sẽ thông báo cho người dùng nếu cần"
|
||||||
|
},
|
||||||
|
"apps":{
|
||||||
|
"one": "{} Ứng dụng",
|
||||||
|
"other": "{} Ứng dụng"
|
||||||
|
},
|
||||||
|
"url":{
|
||||||
|
"one": "{} URL",
|
||||||
|
"other": "{} URL"
|
||||||
|
},
|
||||||
|
"minute":{
|
||||||
|
"one": "{} Phút",
|
||||||
|
"other": "{} Phút"
|
||||||
|
},
|
||||||
|
"hour":{
|
||||||
|
"one": "{} Giờ",
|
||||||
|
"other": "{} Giờ"
|
||||||
|
},
|
||||||
|
"day":{
|
||||||
|
"one": "{} Ngày",
|
||||||
|
"other": "{} ngày"
|
||||||
|
},
|
||||||
|
"clearedNLogsBeforeXAfterY":{
|
||||||
|
"one": "Đã xóa {n} nhật ký (trước = {trước}, sau = {sau})",
|
||||||
|
"other": "Đã xóa {n} nhật ký (trước = {trước}, sau = {sau})"
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesAvailable":{
|
||||||
|
"one": "{} và 1 ứng dụng khác có bản cập nhật.",
|
||||||
|
"other": "{} và {} ứng dụng khác có bản cập nhật."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesInstalled":{
|
||||||
|
"one": "{} và 1 ứng dụng khác đã được cập nhật.",
|
||||||
|
"other": "{} và {} ứng dụng khác đã được cập nhật."
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled":{
|
||||||
|
"one": "{} và 1 ứng dụng khác có thể đã được cập nhật.",
|
||||||
|
"other": "{} và {} ứng dụng khác có thể đã được cập nhật."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,18 +11,10 @@
|
|||||||
"unexpectedError": "意外错误",
|
"unexpectedError": "意外错误",
|
||||||
"ok": "好的",
|
"ok": "好的",
|
||||||
"and": "和",
|
"and": "和",
|
||||||
"startedBgUpdateTask": "后台更新检查任务已启动",
|
|
||||||
"bgUpdateIgnoreAfterIs": "后台更新检查间隔为 {}",
|
|
||||||
"startedActualBGUpdateCheck": "开始后台更新检查",
|
|
||||||
"bgUpdateTaskFinished": "后台更新检查任务已完成",
|
|
||||||
"firstRun": "这是 Obtainium 首次启动",
|
|
||||||
"settingUpdateCheckIntervalTo": "更新检查间隔设置为 {}",
|
|
||||||
"githubPATLabel": "GitHub 个人访问令牌(提升 API 请求限额)",
|
"githubPATLabel": "GitHub 个人访问令牌(提升 API 请求限额)",
|
||||||
"githubPATHint": "个人访问令牌必须为“username:token”的格式",
|
|
||||||
"githubPATFormat": "username:token",
|
|
||||||
"includePrereleases": "包含预发行版",
|
"includePrereleases": "包含预发行版",
|
||||||
"fallbackToOlderReleases": "将旧发行版作为备选",
|
"fallbackToOlderReleases": "将旧发行版作为备选",
|
||||||
"filterReleaseTitlesByRegEx": "使用正则表达式筛选发行标题",
|
"filterReleaseTitlesByRegEx": "筛选发行标题(正则表达式)",
|
||||||
"invalidRegEx": "无效的正则表达式",
|
"invalidRegEx": "无效的正则表达式",
|
||||||
"noDescription": "无描述",
|
"noDescription": "无描述",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
@@ -30,11 +22,11 @@
|
|||||||
"requiredInBrackets": "(必填)",
|
"requiredInBrackets": "(必填)",
|
||||||
"dropdownNoOptsError": "错误:下拉菜单必须包含至少一个选项",
|
"dropdownNoOptsError": "错误:下拉菜单必须包含至少一个选项",
|
||||||
"colour": "配色",
|
"colour": "配色",
|
||||||
"githubStarredRepos": "GitHub 已星标仓库",
|
"githubStarredRepos": "已星标的 GitHub 仓库",
|
||||||
"uname": "用户名",
|
"uname": "用户名",
|
||||||
"wrongArgNum": "参数数量错误",
|
"wrongArgNum": "参数数量错误",
|
||||||
"xIsTrackOnly": "{}为“仅追踪”模式",
|
"xIsTrackOnly": "“{}”为“仅追踪”模式",
|
||||||
"source": "源代码",
|
"source": "来源",
|
||||||
"app": "应用",
|
"app": "应用",
|
||||||
"appsFromSourceAreTrackOnly": "此来源的应用为“仅追踪”模式。",
|
"appsFromSourceAreTrackOnly": "此来源的应用为“仅追踪”模式。",
|
||||||
"youPickedTrackOnly": "您选择了“仅追踪”。",
|
"youPickedTrackOnly": "您选择了“仅追踪”。",
|
||||||
@@ -46,10 +38,10 @@
|
|||||||
"appSourceURL": "来源 URL",
|
"appSourceURL": "来源 URL",
|
||||||
"error": "错误",
|
"error": "错误",
|
||||||
"add": "添加",
|
"add": "添加",
|
||||||
"searchSomeSourcesLabel": "搜索(仅部分来源)",
|
"searchSomeSourcesLabel": "搜索(仅支持部分来源)",
|
||||||
"search": "搜索",
|
"search": "搜索",
|
||||||
"additionalOptsFor": "{} 的更多选项",
|
"additionalOptsFor": "{} 的更多选项",
|
||||||
"supportedSourcesBelow": "支持的来源:",
|
"supportedSources": "支持的来源",
|
||||||
"trackOnlyInBrackets": "(仅追踪)",
|
"trackOnlyInBrackets": "(仅追踪)",
|
||||||
"searchableInBrackets": "(可搜索)",
|
"searchableInBrackets": "(可搜索)",
|
||||||
"appsString": "应用列表",
|
"appsString": "应用列表",
|
||||||
@@ -59,17 +51,17 @@
|
|||||||
"percentProgress": "进度:{}%",
|
"percentProgress": "进度:{}%",
|
||||||
"pleaseWait": "请稍候",
|
"pleaseWait": "请稍候",
|
||||||
"updateAvailable": "更新可用",
|
"updateAvailable": "更新可用",
|
||||||
"estimateInBracketsShort": "(推测)",
|
"estimateInBracketsShort": "(推测)",
|
||||||
"notInstalled": "未安装",
|
"notInstalled": "未安装",
|
||||||
"estimateInBrackets": "(推测)",
|
"estimateInBrackets": "(推测)",
|
||||||
"selectAll": "全选",
|
"selectAll": "全选",
|
||||||
"deselectN": "取消选择 {}",
|
"deselectX": "取消选择 {}",
|
||||||
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。",
|
"xWillBeRemovedButRemainInstalled": "“{}”将从 Obtainium 中删除,但仍安装在您的设备中。",
|
||||||
"removeSelectedAppsQuestion": "是否删除选中的应用?",
|
"removeSelectedAppsQuestion": "是否删除选中的应用?",
|
||||||
"removeSelectedApps": "删除选中的应用",
|
"removeSelectedApps": "删除选中的应用",
|
||||||
"updateX": "更新 {}",
|
"updateX": "更新 {}",
|
||||||
"installX": "安装 {}",
|
"installX": "安装 {}",
|
||||||
"markXTrackOnlyAsUpdated": "将 {}\n(仅追踪)\n标记为已更新",
|
"markXTrackOnlyAsUpdated": "将“{}”\n(仅追踪)\n标记为已更新",
|
||||||
"changeX": "更改 {}",
|
"changeX": "更改 {}",
|
||||||
"installUpdateApps": "安装/更新应用",
|
"installUpdateApps": "安装/更新应用",
|
||||||
"installUpdateSelectedApps": "安装/更新选中的应用",
|
"installUpdateSelectedApps": "安装/更新选中的应用",
|
||||||
@@ -96,22 +88,21 @@
|
|||||||
"importExport": "导入/导出",
|
"importExport": "导入/导出",
|
||||||
"settings": "设置",
|
"settings": "设置",
|
||||||
"exportedTo": "已导出至 {}",
|
"exportedTo": "已导出至 {}",
|
||||||
"obtainiumExport": "Obtainium 导出",
|
"obtainiumExport": "导出 Obtainium",
|
||||||
"invalidInput": "无效的输入",
|
"invalidInput": "无效的输入",
|
||||||
"importedX": "已导入 {}",
|
"importedX": "已导入 {}",
|
||||||
"obtainiumImport": "Obtainium 导入",
|
"obtainiumImport": "导入 Obtainium",
|
||||||
"importFromURLList": "从 URL 列表导入",
|
"importFromURLList": "从 URL 列表导入",
|
||||||
"searchQuery": "搜索查询",
|
"searchQuery": "搜索查询",
|
||||||
"appURLList": "应用 URL 列表",
|
"appURLList": "应用 URL 列表",
|
||||||
"line": "行",
|
"line": "行",
|
||||||
"searchX": "搜索 {}",
|
"searchX": "搜索{}",
|
||||||
"noResults": "无结果",
|
"noResults": "无结果",
|
||||||
"importX": "导入 {}",
|
"importX": "导入{}",
|
||||||
"importedAppsIdDisclaimer": "导入的应用可能错误地显示为“未安装”。\n请通过 Obtainium 重新安装这些应用来解决此问题。",
|
"importedAppsIdDisclaimer": "导入的应用可能会错误地显示为“未安装”状态。\n请通过 Obtainium 重新安装这些应用来解决此问题。",
|
||||||
"importErrors": "导入错误",
|
"importErrors": "导入错误",
|
||||||
"importedXOfYApps": "已导入 {} 中的 {} 个应用。",
|
"importedXOfYApps": "已导入 {} 中的 {} 个应用。",
|
||||||
"followingURLsHadErrors": "下列 URL 存在错误:",
|
"followingURLsHadErrors": "下列 URL 存在错误:",
|
||||||
"okay": "好的",
|
|
||||||
"selectURL": "选择 URL",
|
"selectURL": "选择 URL",
|
||||||
"selectURLs": "选择 URL",
|
"selectURLs": "选择 URL",
|
||||||
"pick": "选择",
|
"pick": "选择",
|
||||||
@@ -132,7 +123,7 @@
|
|||||||
"bgUpdateCheckInterval": "后台更新检查间隔",
|
"bgUpdateCheckInterval": "后台更新检查间隔",
|
||||||
"neverManualOnly": "手动",
|
"neverManualOnly": "手动",
|
||||||
"appearance": "外观",
|
"appearance": "外观",
|
||||||
"showWebInAppView": "在应用详情页显示来源网页",
|
"showWebInAppView": "应用详情页显示来源网页",
|
||||||
"pinUpdates": "将待更新应用置顶",
|
"pinUpdates": "将待更新应用置顶",
|
||||||
"updates": "更新",
|
"updates": "更新",
|
||||||
"sourceSpecific": "来源",
|
"sourceSpecific": "来源",
|
||||||
@@ -144,23 +135,23 @@
|
|||||||
"appNotFound": "未找到应用",
|
"appNotFound": "未找到应用",
|
||||||
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||||
"pickAnAPK": "选择一个 APK 文件",
|
"pickAnAPK": "选择一个 APK 文件",
|
||||||
"appHasMoreThanOnePackage": "{} 有多个架构可用:",
|
"appHasMoreThanOnePackage": "“{}”有多个架构可用:",
|
||||||
"deviceSupportsXArch": "您的设备支持 {} 架构。",
|
"deviceSupportsXArch": "您的设备支持 {} 架构。",
|
||||||
"deviceSupportsFollowingArchs": "您的设备支持下列架构:",
|
"deviceSupportsFollowingArchs": "您的设备支持下列架构:",
|
||||||
"warning": "警告",
|
"warning": "警告",
|
||||||
"sourceIsXButPackageFromYPrompt": "此应用的来源是“{}”,但 APK 文件来自“{}”。是否继续?",
|
"sourceIsXButPackageFromYPrompt": "此应用的来源是“{}”,但 APK 文件来自“{}”。是否继续?",
|
||||||
"updatesAvailable": "更新可用",
|
"updatesAvailable": "更新可用",
|
||||||
"updatesAvailableNotifDescription": "Obtainium 追踪的应用有更新时发出通知",
|
"updatesAvailableNotifDescription": "Obtainium 追踪的应用有更新时发送通知",
|
||||||
"noNewUpdates": "全部应用已是最新。",
|
"noNewUpdates": "全部应用已是最新。",
|
||||||
"xHasAnUpdate": "{} 可以更新了。",
|
"xHasAnUpdate": "“{}”可以更新了。",
|
||||||
"appsUpdated": "应用已更新",
|
"appsUpdated": "应用已更新",
|
||||||
"appsUpdatedNotifDescription": "当应用在后台安装更新时发出通知",
|
"appsUpdatedNotifDescription": "当应用在后台安装更新时发送通知",
|
||||||
"xWasUpdatedToY": "{} 已更新至 {}。",
|
"xWasUpdatedToY": "“{}”已更新至 {}。",
|
||||||
"errorCheckingUpdates": "检查更新出错",
|
"errorCheckingUpdates": "检查更新出错",
|
||||||
"errorCheckingUpdatesNotifDescription": "当后台检查更新失败时显示的通知",
|
"errorCheckingUpdatesNotifDescription": "当后台检查更新失败时显示的通知",
|
||||||
"appsRemoved": "应用已删除",
|
"appsRemoved": "应用已删除",
|
||||||
"appsRemovedNotifDescription": "当应用因加载出错而被删除时发出通知",
|
"appsRemovedNotifDescription": "当应用因加载出错而被删除时发送通知",
|
||||||
"xWasRemovedDueToErrorY": "{} 由于以下错误被删除:{}",
|
"xWasRemovedDueToErrorY": "“{}”由于以下错误被删除:{}",
|
||||||
"completeAppInstallation": "完成应用安装",
|
"completeAppInstallation": "完成应用安装",
|
||||||
"obtainiumMustBeOpenToInstallApps": "必须启动 Obtainium 才能安装应用",
|
"obtainiumMustBeOpenToInstallApps": "必须启动 Obtainium 才能安装应用",
|
||||||
"completeAppInstallationNotifDescription": "提示返回 Obtainium 以完成应用的安装",
|
"completeAppInstallationNotifDescription": "提示返回 Obtainium 以完成应用的安装",
|
||||||
@@ -168,7 +159,7 @@
|
|||||||
"checkingForUpdatesNotifDescription": "检查更新时短暂显示的通知",
|
"checkingForUpdatesNotifDescription": "检查更新时短暂显示的通知",
|
||||||
"pleaseAllowInstallPerm": "请授予 Obtainium 安装应用的权限",
|
"pleaseAllowInstallPerm": "请授予 Obtainium 安装应用的权限",
|
||||||
"trackOnly": "仅追踪",
|
"trackOnly": "仅追踪",
|
||||||
"errorWithHttpStatusCode": "错误 {}",
|
"errorWithHttpStatusCode": "{} 错误",
|
||||||
"versionCorrectionDisabled": "禁用版本号更正(插件似乎未起作用)",
|
"versionCorrectionDisabled": "禁用版本号更正(插件似乎未起作用)",
|
||||||
"unknown": "未知",
|
"unknown": "未知",
|
||||||
"none": "无",
|
"none": "无",
|
||||||
@@ -180,7 +171,7 @@
|
|||||||
"yesMarkUpdated": "是,标记为已更新",
|
"yesMarkUpdated": "是,标记为已更新",
|
||||||
"fdroid": "F-Droid 官方存储库",
|
"fdroid": "F-Droid 官方存储库",
|
||||||
"appIdOrName": "应用 ID 或名称",
|
"appIdOrName": "应用 ID 或名称",
|
||||||
"appId": "App ID",
|
"appId": "应用 ID",
|
||||||
"appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用",
|
"appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用",
|
||||||
"reposHaveMultipleApps": "存储库中可能包含多个应用",
|
"reposHaveMultipleApps": "存储库中可能包含多个应用",
|
||||||
"fdroidThirdPartyRepo": "F-Droid 第三方存储库",
|
"fdroidThirdPartyRepo": "F-Droid 第三方存储库",
|
||||||
@@ -194,11 +185,11 @@
|
|||||||
"additionalOptions": "附加选项",
|
"additionalOptions": "附加选项",
|
||||||
"disableVersionDetection": "禁用版本检测",
|
"disableVersionDetection": "禁用版本检测",
|
||||||
"noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。",
|
"noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。",
|
||||||
"downloadingX": "正在下载{}",
|
"downloadingX": "正在下载“{}”",
|
||||||
"downloadNotifDescription": "提示应用的下载进度",
|
"downloadNotifDescription": "提示应用的下载进度",
|
||||||
"noAPKFound": "未找到 APK 文件",
|
"noAPKFound": "未找到 APK 文件",
|
||||||
"noVersionDetection": "禁用版本检测",
|
"noVersionDetection": "禁用版本检测",
|
||||||
"categorize": "分类",
|
"categorize": "归类",
|
||||||
"categories": "类别",
|
"categories": "类别",
|
||||||
"category": "类别",
|
"category": "类别",
|
||||||
"noCategory": "无类别",
|
"noCategory": "无类别",
|
||||||
@@ -211,7 +202,7 @@
|
|||||||
"copiedToClipboard": "已复制至剪贴板",
|
"copiedToClipboard": "已复制至剪贴板",
|
||||||
"storagePermissionDenied": "已拒绝授予存储权限",
|
"storagePermissionDenied": "已拒绝授予存储权限",
|
||||||
"selectedCategorizeWarning": "这将覆盖选中应用当前的类别设置。",
|
"selectedCategorizeWarning": "这将覆盖选中应用当前的类别设置。",
|
||||||
"filterAPKsByRegEx": "使用正则表达式筛选 APK 文件",
|
"filterAPKsByRegEx": "筛选 APK 文件(正则表达式)",
|
||||||
"removeFromObtainium": "从 Obtainium 中删除",
|
"removeFromObtainium": "从 Obtainium 中删除",
|
||||||
"uninstallFromDevice": "从设备中卸载",
|
"uninstallFromDevice": "从设备中卸载",
|
||||||
"onlyWorksWithNonVersionDetectApps": "仅适用于禁用版本检测的应用。",
|
"onlyWorksWithNonVersionDetectApps": "仅适用于禁用版本检测的应用。",
|
||||||
@@ -226,20 +217,78 @@
|
|||||||
"autoApkFilterByArch": "如果可能,尝试按设备支持的 CPU 架构筛选 APK 文件",
|
"autoApkFilterByArch": "如果可能,尝试按设备支持的 CPU 架构筛选 APK 文件",
|
||||||
"overrideSource": "覆盖来源",
|
"overrideSource": "覆盖来源",
|
||||||
"dontShowAgain": "不再显示",
|
"dontShowAgain": "不再显示",
|
||||||
"dontShowTrackOnlyWarnings": "不显示“仅追踪”模式警告",
|
"dontShowTrackOnlyWarnings": "忽略“仅追踪”模式警告",
|
||||||
"dontShowAPKOriginWarnings": "不显示 APK 文件来源警告",
|
"dontShowAPKOriginWarnings": "忽略 APK 文件来源警告",
|
||||||
"moveNonInstalledAppsToBottom": "将未安装应用置底",
|
"moveNonInstalledAppsToBottom": "将未安装应用置底",
|
||||||
"gitlabPATLabel": "GitLab 个人访问令牌\n(用于搜索应用 and Better APK Discovery)",
|
"gitlabPATLabel": "GitLab 个人访问令牌(启用搜索功能并增强 APK 发现)",
|
||||||
"about": "相关文档",
|
"about": "相关文档",
|
||||||
"requiresCredentialsInSettings": "此功能需要额外的凭据(在“设置”中添加)",
|
"requiresCredentialsInSettings": "{}:此功能需要额外的凭据(在“设置”中添加)",
|
||||||
"checkOnStart": "启动时进行一次检查",
|
"checkOnStart": "启动时进行一次检查",
|
||||||
"tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
|
"tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
|
||||||
"removeOnExternalUninstall": "自动删除已卸载的外部应用",
|
"removeOnExternalUninstall": "自动删除列表中已卸载的应用",
|
||||||
"pickHighestVersionCode": "自动选择版本号最高的 APK 文件",
|
"pickHighestVersionCode": "自动选择版本号最高的 APK 文件",
|
||||||
"checkUpdateOnDetailPage": "打开应用详情页时检查更新",
|
"checkUpdateOnDetailPage": "打开应用详情页时进行检查",
|
||||||
"disablePageTransitions": "禁用页面过渡动画效果",
|
"disablePageTransitions": "禁用页面过渡动画效果",
|
||||||
"reversePageTransitions": "反转页面过渡动画效果",
|
"reversePageTransitions": "反转页面过渡动画效果",
|
||||||
"minStarCount": "最小星标数",
|
"minStarCount": "最小星标数",
|
||||||
|
"addInfoBelow": "在下方添加此凭据。",
|
||||||
|
"addInfoInSettings": "在“设置”中添加此凭据。",
|
||||||
|
"githubSourceNote": "使用访问令牌可避免触发 GitHub 的 API 请求限制。",
|
||||||
|
"gitlabSourceNote": "未使用访问令牌时可能无法从 GitLab 获取 APK 文件。",
|
||||||
|
"sortByLastLinkSegment": "仅根据链接的末尾部分进行筛选",
|
||||||
|
"filterReleaseNotesByRegEx": "筛选发行说明(正则表达式)",
|
||||||
|
"customLinkFilterRegex": "筛选自定义来源的 APK 文件链接\n(正则表达式,默认匹配模式为“.apk$”)",
|
||||||
|
"appsPossiblyUpdated": "已尝试更新应用",
|
||||||
|
"appsPossiblyUpdatedNotifDescription": "当应用已尝试在后台更新时发送通知",
|
||||||
|
"xWasPossiblyUpdatedToY": "已尝试将“{}”更新至 {}。",
|
||||||
|
"enableBackgroundUpdates": "启用后台更新",
|
||||||
|
"backgroundUpdateReqsExplanation": "后台更新未必适用于所有的应用。",
|
||||||
|
"backgroundUpdateLimitsExplanation": "只有在启动 Obtainium 时才能确认安装是否成功。",
|
||||||
|
"verifyLatestTag": "验证“Latest”标签",
|
||||||
|
"intermediateLinkRegex": "筛选中转链接(正则表达式)",
|
||||||
|
"filterByLinkText": "根据链接文本进行筛选",
|
||||||
|
"intermediateLinkNotFound": "未找到中转链接",
|
||||||
|
"intermediateLink": "中转链接",
|
||||||
|
"exemptFromBackgroundUpdates": "禁用后台更新(如果已经全局启用)",
|
||||||
|
"bgUpdatesOnWiFiOnly": "未连接 Wi-Fi 时禁用后台更新",
|
||||||
|
"autoSelectHighestVersionCode": "自动选择版本号最高的 APK 文件",
|
||||||
|
"versionExtractionRegEx": "版本号提取规则(正则表达式)",
|
||||||
|
"matchGroupToUse": "引用的捕获组",
|
||||||
|
"highlightTouchTargets": "突出展示不明显的触摸区域",
|
||||||
|
"pickExportDir": "选择导出文件夹",
|
||||||
|
"autoExportOnChanges": "数据变更时自动导出",
|
||||||
|
"includeSettings": "同时导出应用设置",
|
||||||
|
"filterVersionsByRegEx": "筛选版本号(正则表达式)",
|
||||||
|
"trySelectingSuggestedVersionCode": "尝试选择推荐版本的 APK 文件",
|
||||||
|
"dontSortReleasesList": "保持来自 API 的发行顺序",
|
||||||
|
"reverseSort": "反转排序",
|
||||||
|
"takeFirstLink": "选取第一个链接",
|
||||||
|
"skipSort": "不进行排序",
|
||||||
|
"debugMenu": "调试选项",
|
||||||
|
"bgTaskStarted": "后台任务已启动 - 详见日志",
|
||||||
|
"runBgCheckNow": "立即进行后台更新检查",
|
||||||
|
"versionExtractWholePage": "将版本号提取规则应用于完整页面",
|
||||||
|
"installing": "正在安装",
|
||||||
|
"skipUpdateNotifications": "忽略更新通知",
|
||||||
|
"updatesAvailableNotifChannel": "更新可用",
|
||||||
|
"appsUpdatedNotifChannel": "应用已更新",
|
||||||
|
"appsPossiblyUpdatedNotifChannel": "已尝试更新应用",
|
||||||
|
"errorCheckingUpdatesNotifChannel": "检查更新出错",
|
||||||
|
"appsRemovedNotifChannel": "应用已删除",
|
||||||
|
"downloadingXNotifChannel": "正在下载“{}”",
|
||||||
|
"completeAppInstallationNotifChannel": "完成应用安装",
|
||||||
|
"checkingForUpdatesNotifChannel": "正在检查更新",
|
||||||
|
"onlyCheckInstalledOrTrackOnlyApps": "只检查已安装和“仅追踪”的应用",
|
||||||
|
"supportFixedAPKURL": "支持固定的 APK 文件链接",
|
||||||
|
"selectX": "选择{}",
|
||||||
|
"parallelDownloads": "启用并行下载",
|
||||||
|
"installMethod": "安装方式",
|
||||||
|
"normal": "常规",
|
||||||
|
"shizuku": "Shizuku",
|
||||||
|
"root": "Root",
|
||||||
|
"shizukuBinderNotFound": "未发现兼容的 Shizuku 服务",
|
||||||
|
"useSystemFont": "使用系统字体",
|
||||||
|
"systemFontError": "加载系统字体出错:{}",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "是否删除应用?",
|
"one": "是否删除应用?",
|
||||||
"other": "是否删除应用?"
|
"other": "是否删除应用?"
|
||||||
@@ -253,8 +302,8 @@
|
|||||||
"other": "后台更新检查遇到了“{}”问题,预定于 {} 分钟后重试"
|
"other": "后台更新检查遇到了“{}”问题,预定于 {} 分钟后重试"
|
||||||
},
|
},
|
||||||
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
|
||||||
"one": "后台检查发现 {} 个应用更新 - 如有需要将发出通知",
|
"one": "后台检查发现 {} 个应用更新 - 如有需要将发送通知",
|
||||||
"other": "后台检查发现 {} 个应用更新 - 如有需要将发出通知"
|
"other": "后台检查发现 {} 个应用更新 - 如有需要将发送通知"
|
||||||
},
|
},
|
||||||
"apps": {
|
"apps": {
|
||||||
"one": "{} 个应用",
|
"one": "{} 个应用",
|
||||||
@@ -281,11 +330,15 @@
|
|||||||
"other": "清除了 {n} 个日志({before} 之前,{after} 之后)"
|
"other": "清除了 {n} 个日志({before} 之前,{after} 之后)"
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesAvailable": {
|
"xAndNMoreUpdatesAvailable": {
|
||||||
"one": "{} 和另外 1 个应用可以更新了。",
|
"one": "“{}”和另外 1 个应用可以更新了。",
|
||||||
"other": "{} 和另外 {} 个应用可以更新了。"
|
"other": "“{}”和另外 {} 个应用可以更新了。"
|
||||||
},
|
},
|
||||||
"xAndNMoreUpdatesInstalled": {
|
"xAndNMoreUpdatesInstalled": {
|
||||||
"one": "{} 和另外 1 个应用已更新。",
|
"one": "{} 和另外 1 个应用已更新。",
|
||||||
"other": "{} 和另外 {} 个应用已更新。"
|
"other": "“{}”和另外 {} 个应用已更新。"
|
||||||
|
},
|
||||||
|
"xAndNMoreUpdatesPossiblyInstalled": {
|
||||||
|
"one": "{} 和另外 1 个应用已尝试更新。",
|
||||||
|
"other": "“{}”和另外 {} 个应用已尝试更新。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
23
build.sh
Executable file
23
build.sh
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Convenience script
|
||||||
|
|
||||||
|
CURR_DIR="$(pwd)"
|
||||||
|
trap "cd "$CURR_DIR"" EXIT
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
git fetch && git merge origin/main && git push # Typically run after a PR to main, so bring dev up to date
|
||||||
|
fi
|
||||||
|
rm ./build/app/outputs/flutter-apk/* 2>/dev/null # Get rid of older builds if any
|
||||||
|
flutter build apk --flavor normal && flutter build apk --split-per-abi --flavor normal # Build (both split and combined APKs)
|
||||||
|
for file in ./build/app/outputs/flutter-apk/app-*normal*.apk*; do mv "$file" "${file//-normal/}"; done
|
||||||
|
flutter build apk --flavor fdroid -t lib/main_fdroid.dart && # Do the same for the F-Droid flavour
|
||||||
|
flutter build apk --split-per-abi --flavor fdroid -t lib/main_fdroid.dart
|
||||||
|
for file in ./build/app/outputs/flutter-apk/*.sha1; do gpg --sign --detach-sig "$file"; done # Generate PGP signatures
|
||||||
|
rsync -r ./build/app/outputs/flutter-apk/ ~/Downloads/Obtainium-build/ # Dropoff in Downloads to allow for drag-drop into Flatpak Firefox
|
||||||
|
cd ~/Downloads/Obtainium-build/ # Make zips just in case (for in-comment uploads)
|
||||||
|
for apk in *.apk; do
|
||||||
|
PREFIX="$(echo "$apk" | head -c -5)"
|
||||||
|
zip "$PREFIX" "$PREFIX"*
|
||||||
|
done
|
||||||
|
mkdir -p zips
|
||||||
|
mv *.zip zips/
|
||||||
@@ -10,7 +10,7 @@ class APKCombo extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx = RegExp('^https?://$host/+[^/]+/+[^/]+');
|
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/+[^/]+/+[^/]+');
|
||||||
var match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
var match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
@@ -25,12 +25,16 @@ class APKCombo extends AppSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, String> get requestHeaders => {
|
Future<Map<String, String>?> getRequestHeaders(
|
||||||
"User-Agent": "curl/8.0.1",
|
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
|
||||||
"Accept": "*/*",
|
bool forAPKDownload = false}) async {
|
||||||
"Connection": "keep-alive",
|
return {
|
||||||
"Host": "$host"
|
"User-Agent": "curl/8.0.1",
|
||||||
};
|
"Accept": "*/*",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"Host": "$host"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<MapEntry<String, String>>> getApkUrls(String standardUrl) async {
|
Future<List<MapEntry<String, String>>> getApkUrls(String standardUrl) async {
|
||||||
var res = await sourceRequest('$standardUrl/download/apk');
|
var res = await sourceRequest('$standardUrl/download/apk');
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ class APKMirror extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx = RegExp('^https?://$host/apk/[^/]+/[^/]+');
|
RegExp standardUrlRegEx =
|
||||||
|
RegExp('^https?://(www\\.)?$host/apk/[^/]+/[^/]+');
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
|
|||||||
@@ -3,19 +3,38 @@ import 'package:html/parser.dart';
|
|||||||
import 'package:obtainium/custom_errors.dart';
|
import 'package:obtainium/custom_errors.dart';
|
||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
|
parseDateTimeMMMddCommayyyy(String? dateString) {
|
||||||
|
DateTime? releaseDate;
|
||||||
|
try {
|
||||||
|
releaseDate = dateString != null
|
||||||
|
? DateFormat('MMM dd, yyyy').parse(dateString)
|
||||||
|
: null;
|
||||||
|
releaseDate = dateString != null && releaseDate == null
|
||||||
|
? DateFormat('MMMM dd, yyyy').parse(dateString)
|
||||||
|
: releaseDate;
|
||||||
|
} catch (err) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return releaseDate;
|
||||||
|
}
|
||||||
|
|
||||||
class APKPure extends AppSource {
|
class APKPure extends AppSource {
|
||||||
APKPure() {
|
APKPure() {
|
||||||
host = 'apkpure.com';
|
host = 'apkpure.com';
|
||||||
|
allowSubDomains = true;
|
||||||
|
naiveStandardVersionDetection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegExB = RegExp('^https?://m.$host/+[^/]+/+[^/]+');
|
RegExp standardUrlRegExB =
|
||||||
|
RegExp('^https?://m.$host/+[^/]+/+[^/]+(/+[^/]+)?');
|
||||||
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
|
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
url = 'https://$host/${Uri.parse(url).path}';
|
url = 'https://$host${Uri.parse(url).path}';
|
||||||
}
|
}
|
||||||
RegExp standardUrlRegExA = RegExp('^https?://$host/+[^/]+/+[^/]+');
|
RegExp standardUrlRegExA =
|
||||||
|
RegExp('^https?://(www\\.)?$host/+[^/]+/+[^/]+(/+[^/]+)?');
|
||||||
match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
@@ -47,17 +66,7 @@ class APKPure extends AppSource {
|
|||||||
}
|
}
|
||||||
String? dateString =
|
String? dateString =
|
||||||
html.querySelector('span.info-other span.date')?.text.trim();
|
html.querySelector('span.info-other span.date')?.text.trim();
|
||||||
DateTime? releaseDate;
|
DateTime? releaseDate = parseDateTimeMMMddCommayyyy(dateString);
|
||||||
try {
|
|
||||||
releaseDate = dateString != null
|
|
||||||
? DateFormat('MMM dd, yyyy').parse(dateString)
|
|
||||||
: null;
|
|
||||||
releaseDate = dateString != null && releaseDate == null
|
|
||||||
? DateFormat('MMMM dd, yyyy').parse(dateString)
|
|
||||||
: null;
|
|
||||||
} catch (err) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK';
|
String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK';
|
||||||
List<MapEntry<String, String>> apkUrls = [
|
List<MapEntry<String, String>> apkUrls = [
|
||||||
MapEntry('$appId.apk', 'https://d.$host/b/$type/$appId?version=latest')
|
MapEntry('$appId.apk', 'https://d.$host/b/$type/$appId?version=latest')
|
||||||
@@ -70,11 +79,13 @@ class APKPure extends AppSource {
|
|||||||
Uri.parse(standardUrl).pathSegments.reversed.last;
|
Uri.parse(standardUrl).pathSegments.reversed.last;
|
||||||
String appName =
|
String appName =
|
||||||
html.querySelector('h1.info-title')?.text.trim() ?? appId;
|
html.querySelector('h1.info-title')?.text.trim() ?? appId;
|
||||||
String? changeLog = htmlChangelog.querySelector("div.whats-new-info p:not(.date)")?.innerHtml
|
String? changeLog = htmlChangelog
|
||||||
.trim().replaceAll("<br>", " \n");
|
.querySelector("div.whats-new-info p:not(.date)")
|
||||||
|
?.innerHtml
|
||||||
|
.trim()
|
||||||
|
.replaceAll("<br>", " \n");
|
||||||
return APKDetails(version, apkUrls, AppNames(author, appName),
|
return APKDetails(version, apkUrls, AppNames(author, appName),
|
||||||
releaseDate: releaseDate,
|
releaseDate: releaseDate, changeLog: changeLog);
|
||||||
changeLog: changeLog);
|
|
||||||
} else {
|
} else {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
|
|||||||
77
lib/app_sources/aptoide.dart
Normal file
77
lib/app_sources/aptoide.dart
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:obtainium/custom_errors.dart';
|
||||||
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
|
class Aptoide extends AppSource {
|
||||||
|
Aptoide() {
|
||||||
|
host = 'aptoide.com';
|
||||||
|
name = 'Aptoide';
|
||||||
|
allowSubDomains = true;
|
||||||
|
naiveStandardVersionDetection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
|
RegExp standardUrlRegEx = RegExp('^https?://([^\\.]+\\.){2,}$host');
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
|
if (match == null) {
|
||||||
|
throw InvalidURLError(name);
|
||||||
|
}
|
||||||
|
return url.substring(0, match.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> tryInferringAppId(String standardUrl,
|
||||||
|
{Map<String, dynamic> additionalSettings = const {}}) async {
|
||||||
|
return (await getAppDetailsJSON(standardUrl))['package'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> getAppDetailsJSON(String standardUrl) async {
|
||||||
|
var res = await sourceRequest(standardUrl);
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
var idMatch = RegExp('"app":{"id":[0-9]+').firstMatch(res.body);
|
||||||
|
String? id;
|
||||||
|
if (idMatch != null) {
|
||||||
|
id = res.body.substring(idMatch.start + 12, idMatch.end);
|
||||||
|
} else {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
var res2 =
|
||||||
|
await sourceRequest('https://ws2.aptoide.com/api/7/getApp/app_id/$id');
|
||||||
|
if (res2.statusCode != 200) {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
return jsonDecode(res2.body)?['nodes']?['meta']?['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
|
String standardUrl,
|
||||||
|
Map<String, dynamic> additionalSettings,
|
||||||
|
) async {
|
||||||
|
var appDetails = await getAppDetailsJSON(standardUrl);
|
||||||
|
String appName = appDetails['name'] ?? tr('app');
|
||||||
|
String author = appDetails['developer']?['name'] ?? name;
|
||||||
|
String? dateStr = appDetails['updated'];
|
||||||
|
String? version = appDetails['file']?['vername'];
|
||||||
|
String? apkUrl = appDetails['file']?['path'];
|
||||||
|
if (version == null) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
if (apkUrl == null) {
|
||||||
|
throw NoAPKError();
|
||||||
|
}
|
||||||
|
DateTime? relDate;
|
||||||
|
if (dateStr != null) {
|
||||||
|
relDate = DateTime.parse(dateStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return APKDetails(
|
||||||
|
version, getApkUrlsFromUrls([apkUrl]), AppNames(author, appName),
|
||||||
|
releaseDate: relDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:obtainium/app_sources/github.dart';
|
import 'package:obtainium/app_sources/github.dart';
|
||||||
import 'package:obtainium/components/generated_form.dart';
|
|
||||||
import 'package:obtainium/custom_errors.dart';
|
import 'package:obtainium/custom_errors.dart';
|
||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
@@ -9,28 +7,8 @@ class Codeberg extends AppSource {
|
|||||||
Codeberg() {
|
Codeberg() {
|
||||||
host = 'codeberg.org';
|
host = 'codeberg.org';
|
||||||
|
|
||||||
additionalSourceSpecificSettingFormItems = [];
|
additionalSourceAppSpecificSettingFormItems =
|
||||||
|
gh.additionalSourceAppSpecificSettingFormItems;
|
||||||
additionalSourceAppSpecificSettingFormItems = [
|
|
||||||
[
|
|
||||||
GeneratedFormSwitch('includePrereleases',
|
|
||||||
label: tr('includePrereleases'), defaultValue: false)
|
|
||||||
],
|
|
||||||
[
|
|
||||||
GeneratedFormSwitch('fallbackToOlderReleases',
|
|
||||||
label: tr('fallbackToOlderReleases'), defaultValue: true)
|
|
||||||
],
|
|
||||||
[
|
|
||||||
GeneratedFormTextField('filterReleaseTitlesByRegEx',
|
|
||||||
label: tr('filterReleaseTitlesByRegEx'),
|
|
||||||
required: false,
|
|
||||||
additionalValidators: [
|
|
||||||
(value) {
|
|
||||||
return regExValidator(value);
|
|
||||||
}
|
|
||||||
])
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
canSearch = true;
|
canSearch = true;
|
||||||
searchQuerySettingFormItems = gh.searchQuerySettingFormItems;
|
searchQuerySettingFormItems = gh.searchQuerySettingFormItems;
|
||||||
@@ -38,7 +16,7 @@ class Codeberg extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
|
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:html/parser.dart';
|
import 'package:html/parser.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
|
import 'package:obtainium/components/generated_form.dart';
|
||||||
import 'package:obtainium/custom_errors.dart';
|
import 'package:obtainium/custom_errors.dart';
|
||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
@@ -10,19 +11,41 @@ class FDroid extends AppSource {
|
|||||||
FDroid() {
|
FDroid() {
|
||||||
host = 'f-droid.org';
|
host = 'f-droid.org';
|
||||||
name = tr('fdroid');
|
name = tr('fdroid');
|
||||||
|
naiveStandardVersionDetection = true;
|
||||||
canSearch = true;
|
canSearch = true;
|
||||||
|
additionalSourceAppSpecificSettingFormItems = [
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('filterVersionsByRegEx',
|
||||||
|
label: tr('filterVersionsByRegEx'),
|
||||||
|
required: false,
|
||||||
|
additionalValidators: [
|
||||||
|
(value) {
|
||||||
|
return regExValidator(value);
|
||||||
|
}
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('trySelectingSuggestedVersionCode',
|
||||||
|
label: tr('trySelectingSuggestedVersionCode'))
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('autoSelectHighestVersionCode',
|
||||||
|
label: tr('autoSelectHighestVersionCode'))
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegExB =
|
RegExp standardUrlRegExB =
|
||||||
RegExp('^https?://$host/+[^/]+/+packages/+[^/]+');
|
RegExp('^https?://(www\\.)?$host/+[^/]+/+packages/+[^/]+');
|
||||||
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
|
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
url =
|
url =
|
||||||
'https://${Uri.parse(url.substring(0, match.end)).host}/packages/${Uri.parse(url).pathSegments.last}';
|
'https://${Uri.parse(url.substring(0, match.end)).host}/packages/${Uri.parse(url).pathSegments.last}';
|
||||||
}
|
}
|
||||||
RegExp standardUrlRegExA = RegExp('^https?://$host/+packages/+[^/]+');
|
RegExp standardUrlRegExA =
|
||||||
|
RegExp('^https?://(www\\.)?$host/+packages/+[^/]+');
|
||||||
match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
@@ -36,28 +59,6 @@ class FDroid extends AppSource {
|
|||||||
return Uri.parse(standardUrl).pathSegments.last;
|
return Uri.parse(standardUrl).pathSegments.last;
|
||||||
}
|
}
|
||||||
|
|
||||||
APKDetails getAPKUrlsFromFDroidPackagesAPIResponse(
|
|
||||||
Response res, String apkUrlPrefix, String standardUrl) {
|
|
||||||
if (res.statusCode == 200) {
|
|
||||||
List<dynamic> releases = jsonDecode(res.body)['packages'] ?? [];
|
|
||||||
if (releases.isEmpty) {
|
|
||||||
throw NoReleasesError();
|
|
||||||
}
|
|
||||||
String? latestVersion = releases[0]['versionName'];
|
|
||||||
if (latestVersion == null) {
|
|
||||||
throw NoVersionError();
|
|
||||||
}
|
|
||||||
List<String> apkUrls = releases
|
|
||||||
.where((element) => element['versionName'] == latestVersion)
|
|
||||||
.map((e) => '${apkUrlPrefix}_${e['versionCode']}.apk')
|
|
||||||
.toList();
|
|
||||||
return APKDetails(latestVersion, getApkUrlsFromUrls(apkUrls),
|
|
||||||
AppNames(name, Uri.parse(standardUrl).pathSegments.last));
|
|
||||||
} else {
|
|
||||||
throw getObtainiumHttpError(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl,
|
String standardUrl,
|
||||||
@@ -65,10 +66,38 @@ class FDroid extends AppSource {
|
|||||||
) async {
|
) async {
|
||||||
String? appId = await tryInferringAppId(standardUrl);
|
String? appId = await tryInferringAppId(standardUrl);
|
||||||
String host = Uri.parse(standardUrl).host;
|
String host = Uri.parse(standardUrl).host;
|
||||||
return getAPKUrlsFromFDroidPackagesAPIResponse(
|
var details = getAPKUrlsFromFDroidPackagesAPIResponse(
|
||||||
await sourceRequest('https://$host/api/v1/packages/$appId'),
|
await sourceRequest('https://$host/api/v1/packages/$appId'),
|
||||||
'https://$host/repo/$appId',
|
'https://$host/repo/$appId',
|
||||||
standardUrl);
|
standardUrl,
|
||||||
|
name,
|
||||||
|
autoSelectHighestVersionCode:
|
||||||
|
additionalSettings['autoSelectHighestVersionCode'] == true,
|
||||||
|
trySelectingSuggestedVersionCode:
|
||||||
|
additionalSettings['trySelectingSuggestedVersionCode'] == true,
|
||||||
|
filterVersionsByRegEx:
|
||||||
|
(additionalSettings['filterVersionsByRegEx'] as String?)
|
||||||
|
?.isNotEmpty ==
|
||||||
|
true
|
||||||
|
? additionalSettings['filterVersionsByRegEx']
|
||||||
|
: null);
|
||||||
|
if (!hostChanged) {
|
||||||
|
try {
|
||||||
|
var res = await sourceRequest(
|
||||||
|
'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml');
|
||||||
|
String author = res.body
|
||||||
|
.split('\n')
|
||||||
|
.where((l) => l.startsWith('AuthorName: '))
|
||||||
|
.first
|
||||||
|
.split(': ')
|
||||||
|
.sublist(1)
|
||||||
|
.join(': ');
|
||||||
|
details.names.author = author;
|
||||||
|
} catch (e) {
|
||||||
|
// Fail silently
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -100,4 +129,79 @@ class FDroid extends AppSource {
|
|||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
APKDetails getAPKUrlsFromFDroidPackagesAPIResponse(
|
||||||
|
Response res, String apkUrlPrefix, String standardUrl, String sourceName,
|
||||||
|
{bool autoSelectHighestVersionCode = false,
|
||||||
|
bool trySelectingSuggestedVersionCode = false,
|
||||||
|
String? filterVersionsByRegEx}) {
|
||||||
|
if (res.statusCode == 200) {
|
||||||
|
var response = jsonDecode(res.body);
|
||||||
|
List<dynamic> releases = response['packages'] ?? [];
|
||||||
|
if (releases.isEmpty) {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
String? version;
|
||||||
|
Iterable<dynamic> releaseChoices = [];
|
||||||
|
// Grab the versionCode suggested if the user chose to do that
|
||||||
|
// Only do so at this stage if the user has no release filter
|
||||||
|
if (trySelectingSuggestedVersionCode &&
|
||||||
|
response['suggestedVersionCode'] != null &&
|
||||||
|
filterVersionsByRegEx == null) {
|
||||||
|
var suggestedReleases = releases.where((element) =>
|
||||||
|
element['versionCode'] == response['suggestedVersionCode']);
|
||||||
|
if (suggestedReleases.isNotEmpty) {
|
||||||
|
releaseChoices = suggestedReleases;
|
||||||
|
version = suggestedReleases.first['versionName'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Apply the release filter if any
|
||||||
|
if (filterVersionsByRegEx?.isNotEmpty == true) {
|
||||||
|
version = null;
|
||||||
|
releaseChoices = [];
|
||||||
|
for (var i = 0; i < releases.length; i++) {
|
||||||
|
if (RegExp(filterVersionsByRegEx!)
|
||||||
|
.hasMatch(releases[i]['versionName'])) {
|
||||||
|
version = releases[i]['versionName'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (version == null) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Default to the highest version
|
||||||
|
version ??= releases[0]['versionName'];
|
||||||
|
if (version == null) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
// If a suggested release was not already picked, pick all those with the selected version
|
||||||
|
if (releaseChoices.isEmpty) {
|
||||||
|
releaseChoices =
|
||||||
|
releases.where((element) => element['versionName'] == version);
|
||||||
|
}
|
||||||
|
// For the remaining releases, use the toggles to auto-select one if possible
|
||||||
|
if (releaseChoices.length > 1) {
|
||||||
|
if (autoSelectHighestVersionCode) {
|
||||||
|
releaseChoices = [releaseChoices.first];
|
||||||
|
} else if (trySelectingSuggestedVersionCode &&
|
||||||
|
response['suggestedVersionCode'] != null) {
|
||||||
|
var suggestedReleases = releaseChoices.where((element) =>
|
||||||
|
element['versionCode'] == response['suggestedVersionCode']);
|
||||||
|
if (suggestedReleases.isNotEmpty) {
|
||||||
|
releaseChoices = suggestedReleases;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (releaseChoices.isEmpty) {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
List<String> apkUrls = releaseChoices
|
||||||
|
.map((e) => '${apkUrlPrefix}_${e['versionCode']}.apk')
|
||||||
|
.toList();
|
||||||
|
return APKDetails(version, getApkUrlsFromUrls(apkUrls.toSet().toList()),
|
||||||
|
AppNames(sourceName, Uri.parse(standardUrl).pathSegments.last));
|
||||||
|
} else {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import 'package:obtainium/providers/source_provider.dart';
|
|||||||
class FDroidRepo extends AppSource {
|
class FDroidRepo extends AppSource {
|
||||||
FDroidRepo() {
|
FDroidRepo() {
|
||||||
name = tr('fdroidThirdPartyRepo');
|
name = tr('fdroidThirdPartyRepo');
|
||||||
|
canSearch = true;
|
||||||
|
excludeFromMassSearch = true;
|
||||||
|
neverAutoSelect = true;
|
||||||
|
|
||||||
additionalSourceAppSpecificSettingFormItems = [
|
additionalSourceAppSpecificSettingFormItems = [
|
||||||
[
|
[
|
||||||
@@ -22,17 +25,99 @@ class FDroidRepo extends AppSource {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String removeQueryParamsFromUrl(String url, {List<String> keep = const []}) {
|
||||||
|
var uri = Uri.parse(url);
|
||||||
|
Map<String, dynamic> resultParams = {};
|
||||||
|
uri.queryParameters.forEach((key, value) {
|
||||||
|
if (keep.contains(key)) {
|
||||||
|
resultParams[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
url = uri.replace(queryParameters: resultParams).toString();
|
||||||
|
if (url.endsWith('?')) {
|
||||||
|
url = url.substring(0, url.length - 1);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
|
var standardUri = Uri.parse(url);
|
||||||
|
var pathSegments = standardUri.pathSegments;
|
||||||
|
if (pathSegments.last == 'index.xml') {
|
||||||
|
pathSegments.removeLast();
|
||||||
|
standardUri = standardUri.replace(path: pathSegments.join('/'));
|
||||||
|
}
|
||||||
|
return removeQueryParamsFromUrl(standardUri.toString(), keep: ['appId']);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, List<String>>> search(String query,
|
||||||
|
{Map<String, dynamic> querySettings = const {}}) async {
|
||||||
|
String? url = querySettings['url'];
|
||||||
|
if (url == null) {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
url = removeQueryParamsFromUrl(standardizeUrl(url));
|
||||||
|
var res = await sourceRequest('$url/index.xml');
|
||||||
|
if (res.statusCode == 200) {
|
||||||
|
var body = parse(res.body);
|
||||||
|
Map<String, List<String>> results = {};
|
||||||
|
body.querySelectorAll('application').toList().forEach((app) {
|
||||||
|
String appId = app.attributes['id']!;
|
||||||
|
String appName = app.querySelector('name')?.innerHtml ?? appId;
|
||||||
|
String appDesc = app.querySelector('desc')?.innerHtml ?? '';
|
||||||
|
if (query.isEmpty ||
|
||||||
|
appId.contains(query) ||
|
||||||
|
appName.contains(query) ||
|
||||||
|
appDesc.contains(query)) {
|
||||||
|
results['$url?appId=$appId'] = [appName, appDesc];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return results;
|
||||||
|
} else {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
App endOfGetAppChanges(App app) {
|
||||||
|
var uri = Uri.parse(app.url);
|
||||||
|
String? appId;
|
||||||
|
if (!isTempId(app)) {
|
||||||
|
appId = app.id;
|
||||||
|
} else if (uri.queryParameters['appId'] != null) {
|
||||||
|
appId = uri.queryParameters['appId'];
|
||||||
|
}
|
||||||
|
if (appId != null) {
|
||||||
|
app.url = uri
|
||||||
|
.replace(
|
||||||
|
queryParameters: Map.fromEntries(
|
||||||
|
[...uri.queryParameters.entries, MapEntry('appId', appId)]))
|
||||||
|
.toString();
|
||||||
|
app.additionalSettings['appIdOrName'] = appId;
|
||||||
|
app.id = appId;
|
||||||
|
}
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
String? appIdOrName = additionalSettings['appIdOrName'];
|
String? appIdOrName = additionalSettings['appIdOrName'];
|
||||||
|
var standardUri = Uri.parse(standardUrl);
|
||||||
|
if (standardUri.queryParameters['appId'] != null) {
|
||||||
|
appIdOrName = standardUri.queryParameters['appId'];
|
||||||
|
}
|
||||||
|
standardUrl = removeQueryParamsFromUrl(standardUrl);
|
||||||
bool pickHighestVersionCode = additionalSettings['pickHighestVersionCode'];
|
bool pickHighestVersionCode = additionalSettings['pickHighestVersionCode'];
|
||||||
if (appIdOrName == null) {
|
if (appIdOrName == null) {
|
||||||
throw NoReleasesError();
|
throw NoReleasesError();
|
||||||
}
|
}
|
||||||
var res = await sourceRequest('$standardUrl/index.xml');
|
var res = await sourceRequest(
|
||||||
|
'$standardUrl${standardUrl.endsWith('/index.xml') ? '' : '/index.xml'}');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var body = parse(res.body);
|
var body = parse(res.body);
|
||||||
var foundApps = body.querySelectorAll('application').where((element) {
|
var foundApps = body.querySelectorAll('application').where((element) {
|
||||||
@@ -41,7 +126,7 @@ class FDroidRepo extends AppSource {
|
|||||||
if (foundApps.isEmpty) {
|
if (foundApps.isEmpty) {
|
||||||
foundApps = body.querySelectorAll('application').where((element) {
|
foundApps = body.querySelectorAll('application').where((element) {
|
||||||
return element.querySelector('name')?.innerHtml.toLowerCase() ==
|
return element.querySelector('name')?.innerHtml.toLowerCase() ==
|
||||||
appIdOrName.toLowerCase();
|
appIdOrName!.toLowerCase();
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
if (foundApps.isEmpty) {
|
if (foundApps.isEmpty) {
|
||||||
@@ -50,7 +135,7 @@ class FDroidRepo extends AppSource {
|
|||||||
.querySelector('name')
|
.querySelector('name')
|
||||||
?.innerHtml
|
?.innerHtml
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(appIdOrName.toLowerCase()) ??
|
.contains(appIdOrName!.toLowerCase()) ??
|
||||||
false;
|
false;
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
@@ -58,8 +143,9 @@ class FDroidRepo extends AppSource {
|
|||||||
throw ObtainiumError(tr('appWithIdOrNameNotFound'));
|
throw ObtainiumError(tr('appWithIdOrNameNotFound'));
|
||||||
}
|
}
|
||||||
var authorName = body.querySelector('repo')?.attributes['name'] ?? name;
|
var authorName = body.querySelector('repo')?.attributes['name'] ?? name;
|
||||||
var appName =
|
String appId = foundApps[0].attributes['id']!;
|
||||||
foundApps[0].querySelector('name')?.innerHtml ?? appIdOrName;
|
foundApps[0].querySelector('name')?.innerHtml ?? appId;
|
||||||
|
var appName = foundApps[0].querySelector('name')?.innerHtml ?? appId;
|
||||||
var releases = foundApps[0].querySelectorAll('package');
|
var releases = foundApps[0].querySelectorAll('package');
|
||||||
String? latestVersion = releases[0].querySelector('version')?.innerHtml;
|
String? latestVersion = releases[0].querySelector('version')?.innerHtml;
|
||||||
String? added = releases[0].querySelector('added')?.innerHtml;
|
String? added = releases[0].querySelector('added')?.innerHtml;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
@@ -16,26 +17,11 @@ class GitHub extends AppSource {
|
|||||||
host = 'github.com';
|
host = 'github.com';
|
||||||
appIdInferIsOptional = true;
|
appIdInferIsOptional = true;
|
||||||
|
|
||||||
additionalSourceSpecificSettingFormItems = [
|
sourceConfigSettingFormItems = [
|
||||||
GeneratedFormTextField('github-creds',
|
GeneratedFormTextField('github-creds',
|
||||||
label: tr('githubPATLabel'),
|
label: tr('githubPATLabel'),
|
||||||
password: true,
|
password: true,
|
||||||
required: false,
|
required: false,
|
||||||
additionalValidators: [
|
|
||||||
(value) {
|
|
||||||
if (value != null && value.trim().isNotEmpty) {
|
|
||||||
if (value
|
|
||||||
.split(':')
|
|
||||||
.where((element) => element.trim().isNotEmpty)
|
|
||||||
.length !=
|
|
||||||
2) {
|
|
||||||
return tr('githubPATHint');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
],
|
|
||||||
hint: tr('githubPATFormat'),
|
|
||||||
belowWidgets: [
|
belowWidgets: [
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 4,
|
height: 4,
|
||||||
@@ -75,6 +61,21 @@ class GitHub extends AppSource {
|
|||||||
return regExValidator(value);
|
return regExValidator(value);
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('filterReleaseNotesByRegEx',
|
||||||
|
label: tr('filterReleaseNotesByRegEx'),
|
||||||
|
required: false,
|
||||||
|
additionalValidators: [
|
||||||
|
(value) {
|
||||||
|
return regExValidator(value);
|
||||||
|
}
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[GeneratedFormSwitch('verifyLatestTag', label: tr('verifyLatestTag'))],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('dontSortReleasesList',
|
||||||
|
label: tr('dontSortReleasesList'))
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -107,7 +108,7 @@ class GitHub extends AppSource {
|
|||||||
for (var path in possibleBuildGradleLocations) {
|
for (var path in possibleBuildGradleLocations) {
|
||||||
try {
|
try {
|
||||||
var res = await sourceRequest(
|
var res = await sourceRequest(
|
||||||
'${await convertStandardUrlToAPIUrl(standardUrl)}/contents/$path');
|
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
try {
|
try {
|
||||||
var body = jsonDecode(res.body);
|
var body = jsonDecode(res.body);
|
||||||
@@ -116,22 +117,23 @@ class GitHub extends AppSource {
|
|||||||
.decode(body['content'].toString().split('\n').join('')))
|
.decode(body['content'].toString().split('\n').join('')))
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((e) => e.trim());
|
.map((e) => e.trim());
|
||||||
var appId = trimmedLines
|
var appIds = trimmedLines.where((l) =>
|
||||||
.where((l) =>
|
l.startsWith('applicationId "') ||
|
||||||
l.startsWith('applicationId "') ||
|
l.startsWith('applicationId \''));
|
||||||
l.startsWith('applicationId \''))
|
appIds = appIds.map((appId) => appId
|
||||||
.first;
|
.split(appId.startsWith('applicationId "') ? '"' : '\'')[1]);
|
||||||
appId = appId
|
appIds = appIds.map((appId) {
|
||||||
.split(appId.startsWith('applicationId "') ? '"' : '\'')[1];
|
if (appId.startsWith('\${') && appId.endsWith('}')) {
|
||||||
if (appId.startsWith('\${') && appId.endsWith('}')) {
|
appId = trimmedLines
|
||||||
appId = trimmedLines
|
.where((l) => l.startsWith(
|
||||||
.where((l) => l.startsWith(
|
'def ${appId.substring(2, appId.length - 1)}'))
|
||||||
'def ${appId.substring(2, appId.length - 1)}'))
|
.first;
|
||||||
.first;
|
appId = appId.split(appId.contains('"') ? '"' : '\'')[1];
|
||||||
appId = appId.split(appId.contains('"') ? '"' : '\'')[1];
|
}
|
||||||
}
|
|
||||||
if (appId.isNotEmpty) {
|
|
||||||
return appId;
|
return appId;
|
||||||
|
}).where((appId) => appId.isNotEmpty);
|
||||||
|
if (appIds.length == 1) {
|
||||||
|
return appIds.first;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
LogsProvider().add(
|
LogsProvider().add(
|
||||||
@@ -147,7 +149,7 @@ class GitHub extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
|
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
@@ -155,19 +157,57 @@ class GitHub extends AppSource {
|
|||||||
return url.substring(0, match.end);
|
return url.substring(0, match.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getCredentialPrefixIfAny() async {
|
@override
|
||||||
SettingsProvider settingsProvider = SettingsProvider();
|
Future<Map<String, String>?> getRequestHeaders(
|
||||||
await settingsProvider.initializeSettings();
|
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
|
||||||
String? creds = settingsProvider
|
bool forAPKDownload = false}) async {
|
||||||
.getSettingString(additionalSourceSpecificSettingFormItems[0].key);
|
var token = await getTokenIfAny(additionalSettings);
|
||||||
return creds != null && creds.isNotEmpty ? '$creds@' : '';
|
var headers = <String, String>{};
|
||||||
|
if (token != null) {
|
||||||
|
headers[HttpHeaders.authorizationHeader] = 'Token $token';
|
||||||
|
}
|
||||||
|
if (forAPKDownload == true) {
|
||||||
|
headers[HttpHeaders.acceptHeader] = 'application/octet-stream';
|
||||||
|
}
|
||||||
|
if (headers.isNotEmpty) {
|
||||||
|
return headers;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getAPIHost() async =>
|
Future<String?> getTokenIfAny(Map<String, dynamic> additionalSettings) async {
|
||||||
'https://${await getCredentialPrefixIfAny()}api.$host';
|
SettingsProvider settingsProvider = SettingsProvider();
|
||||||
|
await settingsProvider.initializeSettings();
|
||||||
|
var sourceConfig =
|
||||||
|
await getSourceConfigValues(additionalSettings, settingsProvider);
|
||||||
|
String? creds = sourceConfig['github-creds'];
|
||||||
|
if (creds != null) {
|
||||||
|
var userNameEndIndex = creds.indexOf(':');
|
||||||
|
if (userNameEndIndex > 0) {
|
||||||
|
creds = creds.substring(
|
||||||
|
userNameEndIndex + 1); // For old username-included token inputs
|
||||||
|
}
|
||||||
|
return creds;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> convertStandardUrlToAPIUrl(String standardUrl) async =>
|
@override
|
||||||
'${await getAPIHost()}/repos${standardUrl.substring('https://$host'.length)}';
|
Future<String?> getSourceNote() async {
|
||||||
|
if (!hostChanged && (await getTokenIfAny({})) == null) {
|
||||||
|
return '${tr('githubSourceNote')} ${hostChanged ? tr('addInfoBelow') : tr('addInfoInSettings')}';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> getAPIHost(Map<String, dynamic> additionalSettings) async =>
|
||||||
|
'https://api.$host';
|
||||||
|
|
||||||
|
Future<String> convertStandardUrlToAPIUrl(
|
||||||
|
String standardUrl, Map<String, dynamic> additionalSettings) async =>
|
||||||
|
'${await getAPIHost(additionalSettings)}/repos${standardUrl.substring('https://$host'.length)}';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? changeLogPageFromStandardUrl(String standardUrl) =>
|
String? changeLogPageFromStandardUrl(String standardUrl) =>
|
||||||
@@ -185,16 +225,48 @@ class GitHub extends AppSource {
|
|||||||
true
|
true
|
||||||
? additionalSettings['filterReleaseTitlesByRegEx']
|
? additionalSettings['filterReleaseTitlesByRegEx']
|
||||||
: null;
|
: null;
|
||||||
|
String? regexNotesFilter =
|
||||||
|
(additionalSettings['filterReleaseNotesByRegEx'] as String?)
|
||||||
|
?.isNotEmpty ==
|
||||||
|
true
|
||||||
|
? additionalSettings['filterReleaseNotesByRegEx']
|
||||||
|
: null;
|
||||||
|
bool verifyLatestTag = additionalSettings['verifyLatestTag'] == true;
|
||||||
|
bool dontSortReleasesList =
|
||||||
|
additionalSettings['dontSortReleasesList'] == true;
|
||||||
|
dynamic latestRelease;
|
||||||
|
if (verifyLatestTag) {
|
||||||
|
var temp = requestUrl.split('?');
|
||||||
|
Response res = await sourceRequest(
|
||||||
|
'${temp[0]}/latest${temp.length > 1 ? '?${temp.sublist(1).join('?')}' : ''}');
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
if (onHttpErrorCode != null) {
|
||||||
|
onHttpErrorCode(res);
|
||||||
|
}
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
latestRelease = jsonDecode(res.body);
|
||||||
|
}
|
||||||
Response res = await sourceRequest(requestUrl);
|
Response res = await sourceRequest(requestUrl);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var releases = jsonDecode(res.body) as List<dynamic>;
|
var releases = jsonDecode(res.body) as List<dynamic>;
|
||||||
|
if (latestRelease != null) {
|
||||||
|
var latestTag = latestRelease['tag_name'] ?? latestRelease['name'];
|
||||||
|
if (releases
|
||||||
|
.where((element) =>
|
||||||
|
(element['tag_name'] ?? element['name']) == latestTag)
|
||||||
|
.isEmpty) {
|
||||||
|
releases = [latestRelease, ...releases];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<MapEntry<String, String>> getReleaseAPKUrls(dynamic release) =>
|
List<MapEntry<String, String>> getReleaseAPKUrls(dynamic release) =>
|
||||||
(release['assets'] as List<dynamic>?)
|
(release['assets'] as List<dynamic>?)
|
||||||
?.map((e) {
|
?.map((e) {
|
||||||
return e['name'] != null && e['browser_download_url'] != null
|
return (e['name'] != null) &&
|
||||||
|
((e['url'] ?? e['browser_download_url']) != null)
|
||||||
? MapEntry(e['name'] as String,
|
? MapEntry(e['name'] as String,
|
||||||
e['browser_download_url'] as String)
|
(e['url'] ?? e['browser_download_url']) as String)
|
||||||
: const MapEntry('', '');
|
: const MapEntry('', '');
|
||||||
})
|
})
|
||||||
.where((element) => element.key.toLowerCase().endsWith('.apk'))
|
.where((element) => element.key.toLowerCase().endsWith('.apk'))
|
||||||
@@ -205,32 +277,47 @@ class GitHub extends AppSource {
|
|||||||
rel?['published_at'] != null
|
rel?['published_at'] != null
|
||||||
? DateTime.parse(rel['published_at'])
|
? DateTime.parse(rel['published_at'])
|
||||||
: null;
|
: null;
|
||||||
releases.sort((a, b) {
|
if (dontSortReleasesList) {
|
||||||
// See #478 and #534
|
releases = releases.reversed.toList();
|
||||||
if (a == b) {
|
} else {
|
||||||
return 0;
|
releases.sort((a, b) {
|
||||||
} else if (a == null) {
|
// See #478 and #534
|
||||||
return -1;
|
if (a == b) {
|
||||||
} else if (b == null) {
|
return 0;
|
||||||
return 1;
|
} else if (a == null) {
|
||||||
} else {
|
return -1;
|
||||||
var nameA = a['tag_name'] ?? a['name'];
|
} else if (b == null) {
|
||||||
var nameB = b['tag_name'] ?? b['name'];
|
return 1;
|
||||||
var stdFormats = findStandardFormatsForVersion(nameA, true)
|
|
||||||
.intersection(findStandardFormatsForVersion(nameB, true));
|
|
||||||
if (stdFormats.isNotEmpty) {
|
|
||||||
var reg = RegExp(stdFormats.first);
|
|
||||||
var matchA = reg.firstMatch(nameA);
|
|
||||||
var matchB = reg.firstMatch(nameB);
|
|
||||||
return compareAlphaNumeric(
|
|
||||||
(nameA as String).substring(matchA!.start, matchA.end),
|
|
||||||
(nameB as String).substring(matchB!.start, matchB.end));
|
|
||||||
} else {
|
} else {
|
||||||
return (getReleaseDateFromRelease(a) ?? DateTime(1))
|
var nameA = a['tag_name'] ?? a['name'];
|
||||||
.compareTo(getReleaseDateFromRelease(b) ?? DateTime(0));
|
var nameB = b['tag_name'] ?? b['name'];
|
||||||
|
var stdFormats = findStandardFormatsForVersion(nameA, true)
|
||||||
|
.intersection(findStandardFormatsForVersion(nameB, true));
|
||||||
|
if (stdFormats.isNotEmpty) {
|
||||||
|
var reg = RegExp(stdFormats.first);
|
||||||
|
var matchA = reg.firstMatch(nameA);
|
||||||
|
var matchB = reg.firstMatch(nameB);
|
||||||
|
return compareAlphaNumeric(
|
||||||
|
(nameA as String).substring(matchA!.start, matchA.end),
|
||||||
|
(nameB as String).substring(matchB!.start, matchB.end));
|
||||||
|
} else {
|
||||||
|
return (getReleaseDateFromRelease(a) ?? DateTime(1))
|
||||||
|
.compareTo(getReleaseDateFromRelease(b) ?? DateTime(0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (latestRelease != null &&
|
||||||
|
releases.isNotEmpty &&
|
||||||
|
latestRelease !=
|
||||||
|
(releases[releases.length - 1]['tag_name'] ??
|
||||||
|
releases[0]['name'])) {
|
||||||
|
var ind = releases.indexWhere((element) =>
|
||||||
|
latestRelease == (element['tag_name'] ?? element['name']));
|
||||||
|
if (ind >= 0) {
|
||||||
|
releases.add(releases.removeAt(ind));
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
releases = releases.reversed.toList();
|
releases = releases.reversed.toList();
|
||||||
dynamic targetRelease;
|
dynamic targetRelease;
|
||||||
var prerrelsSkipped = 0;
|
var prerrelsSkipped = 0;
|
||||||
@@ -253,7 +340,17 @@ class GitHub extends AppSource {
|
|||||||
!RegExp(regexFilter).hasMatch(nameToFilter.trim())) {
|
!RegExp(regexFilter).hasMatch(nameToFilter.trim())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (regexNotesFilter != null &&
|
||||||
|
!RegExp(regexNotesFilter)
|
||||||
|
.hasMatch(((releases[i]['body'] as String?) ?? '').trim())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
var apkUrls = getReleaseAPKUrls(releases[i]);
|
var apkUrls = getReleaseAPKUrls(releases[i]);
|
||||||
|
if (additionalSettings['apkFilterRegEx'] != null) {
|
||||||
|
var reg = RegExp(additionalSettings['apkFilterRegEx']);
|
||||||
|
apkUrls =
|
||||||
|
apkUrls.where((element) => reg.hasMatch(element.key)).toList();
|
||||||
|
}
|
||||||
if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) {
|
if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -311,7 +408,7 @@ class GitHub extends AppSource {
|
|||||||
) async {
|
) async {
|
||||||
return await getLatestAPKDetailsCommon2(standardUrl, additionalSettings,
|
return await getLatestAPKDetailsCommon2(standardUrl, additionalSettings,
|
||||||
(bool useTagUrl) async {
|
(bool useTagUrl) async {
|
||||||
return '${await convertStandardUrlToAPIUrl(standardUrl)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
|
return '${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
|
||||||
}, (Response res) {
|
}, (Response res) {
|
||||||
rateLimitErrorCheck(res);
|
rateLimitErrorCheck(res);
|
||||||
});
|
});
|
||||||
@@ -360,7 +457,7 @@ class GitHub extends AppSource {
|
|||||||
{Map<String, dynamic> querySettings = const {}}) async {
|
{Map<String, dynamic> querySettings = const {}}) async {
|
||||||
return searchCommon(
|
return searchCommon(
|
||||||
query,
|
query,
|
||||||
'${await getAPIHost()}/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100',
|
'${await getAPIHost({})}/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100',
|
||||||
'items', onHttpErrorCode: (Response res) {
|
'items', onHttpErrorCode: (Response res) {
|
||||||
rateLimitErrorCheck(res);
|
rateLimitErrorCheck(res);
|
||||||
}, querySettings: querySettings);
|
}, querySettings: querySettings);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class GitLab extends AppSource {
|
|||||||
host = 'gitlab.com';
|
host = 'gitlab.com';
|
||||||
canSearch = true;
|
canSearch = true;
|
||||||
|
|
||||||
additionalSourceSpecificSettingFormItems = [
|
sourceConfigSettingFormItems = [
|
||||||
GeneratedFormTextField('gitlab-creds',
|
GeneratedFormTextField('gitlab-creds',
|
||||||
label: tr('gitlabPATLabel'),
|
label: tr('gitlabPATLabel'),
|
||||||
password: true,
|
password: true,
|
||||||
@@ -52,7 +52,7 @@ class GitLab extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
|
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
@@ -60,23 +60,28 @@ class GitLab extends AppSource {
|
|||||||
return url.substring(0, match.end);
|
return url.substring(0, match.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getPATIfAny() async {
|
Future<String?> getPATIfAny(Map<String, dynamic> additionalSettings) async {
|
||||||
SettingsProvider settingsProvider = SettingsProvider();
|
SettingsProvider settingsProvider = SettingsProvider();
|
||||||
await settingsProvider.initializeSettings();
|
await settingsProvider.initializeSettings();
|
||||||
String? creds = settingsProvider
|
var sourceConfig =
|
||||||
.getSettingString(additionalSourceSpecificSettingFormItems[0].key);
|
await getSourceConfigValues(additionalSettings, settingsProvider);
|
||||||
|
String? creds = sourceConfig['gitlab-creds'];
|
||||||
return creds != null && creds.isNotEmpty ? creds : null;
|
return creds != null && creds.isNotEmpty ? creds : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> getSourceNote() async {
|
||||||
|
if ((await getPATIfAny({})) == null) {
|
||||||
|
return '${tr('gitlabSourceNote')} ${hostChanged ? tr('addInfoBelow') : tr('addInfoInSettings')}';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, List<String>>> search(String query,
|
Future<Map<String, List<String>>> search(String query,
|
||||||
{Map<String, dynamic> querySettings = const {}}) async {
|
{Map<String, dynamic> querySettings = const {}}) async {
|
||||||
String? PAT = await getPATIfAny();
|
|
||||||
if (PAT == null) {
|
|
||||||
throw CredsNeededError(name);
|
|
||||||
}
|
|
||||||
var url =
|
var url =
|
||||||
'https://$host/api/v4/search?private_token=$PAT&scope=projects&search=${Uri.encodeQueryComponent(query)}';
|
'https://$host/api/v4/projects?search=${Uri.encodeQueryComponent(query)}';
|
||||||
var res = await sourceRequest(url);
|
var res = await sourceRequest(url);
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
@@ -103,7 +108,7 @@ class GitLab extends AppSource {
|
|||||||
) async {
|
) async {
|
||||||
bool fallbackToOlderReleases =
|
bool fallbackToOlderReleases =
|
||||||
additionalSettings['fallbackToOlderReleases'] == true;
|
additionalSettings['fallbackToOlderReleases'] == true;
|
||||||
String? PAT = await getPATIfAny();
|
String? PAT = await getPATIfAny(hostChanged ? additionalSettings : {});
|
||||||
Iterable<APKDetails> apkDetailsList = [];
|
Iterable<APKDetails> apkDetailsList = [];
|
||||||
if (PAT != null) {
|
if (PAT != null) {
|
||||||
var names = GitHub().getAppNames(standardUrl);
|
var names = GitHub().getAppNames(standardUrl);
|
||||||
@@ -165,7 +170,6 @@ class GitLab extends AppSource {
|
|||||||
...getLinksFromParsedHTML(entryContent,
|
...getLinksFromParsedHTML(entryContent,
|
||||||
RegExp('/[^/]+\\.apk\$', caseSensitive: false), '')
|
RegExp('/[^/]+\\.apk\$', caseSensitive: false), '')
|
||||||
.where((element) => Uri.parse(element).host != '')
|
.where((element) => Uri.parse(element).host != '')
|
||||||
.toList()
|
|
||||||
];
|
];
|
||||||
var entryId = entry.querySelector('id')?.innerHtml;
|
var entryId = entry.querySelector('id')?.innerHtml;
|
||||||
var version =
|
var version =
|
||||||
@@ -183,7 +187,7 @@ class GitLab extends AppSource {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (apkDetailsList.isEmpty) {
|
if (apkDetailsList.isEmpty) {
|
||||||
throw NoReleasesError();
|
throw NoReleasesError(note: tr('gitlabSourceNote'));
|
||||||
}
|
}
|
||||||
if (fallbackToOlderReleases) {
|
if (fallbackToOlderReleases) {
|
||||||
if (additionalSettings['trackOnly'] != true) {
|
if (additionalSettings['trackOnly'] != true) {
|
||||||
@@ -191,7 +195,7 @@ class GitLab extends AppSource {
|
|||||||
apkDetailsList.where((e) => e.apkUrls.isNotEmpty).toList();
|
apkDetailsList.where((e) => e.apkUrls.isNotEmpty).toList();
|
||||||
}
|
}
|
||||||
if (apkDetailsList.isEmpty) {
|
if (apkDetailsList.isEmpty) {
|
||||||
throw NoReleasesError();
|
throw NoReleasesError(note: tr('gitlabSourceNote'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return apkDetailsList.first;
|
return apkDetailsList.first;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:html/parser.dart';
|
import 'package:html/parser.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
|
import 'package:obtainium/components/generated_form.dart';
|
||||||
import 'package:obtainium/custom_errors.dart';
|
import 'package:obtainium/custom_errors.dart';
|
||||||
|
import 'package:obtainium/providers/apps_provider.dart';
|
||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
|
String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
|
||||||
@@ -15,13 +17,17 @@ String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
|
|||||||
.split('/')
|
.split('/')
|
||||||
.where((element) => element.trim().isNotEmpty)
|
.where((element) => element.trim().isNotEmpty)
|
||||||
.toList();
|
.toList();
|
||||||
|
String absoluteUrl;
|
||||||
if (ambiguousUrl.startsWith('/') || currPathSegments.isEmpty) {
|
if (ambiguousUrl.startsWith('/') || currPathSegments.isEmpty) {
|
||||||
return '${referenceAbsoluteUrl.origin}/$ambiguousUrl';
|
absoluteUrl = '${referenceAbsoluteUrl.origin}/$ambiguousUrl';
|
||||||
} else if (ambiguousUrl.split('/').length == 1) {
|
} else if (ambiguousUrl.split('/').where((e) => e.isNotEmpty).length == 1) {
|
||||||
return '${referenceAbsoluteUrl.origin}/${currPathSegments.join('/')}/$ambiguousUrl';
|
absoluteUrl =
|
||||||
|
'${referenceAbsoluteUrl.origin}/${currPathSegments.join('/')}/$ambiguousUrl';
|
||||||
} else {
|
} else {
|
||||||
return '${referenceAbsoluteUrl.origin}/${currPathSegments.sublist(0, currPathSegments.length - 1).join('/')}/$ambiguousUrl';
|
absoluteUrl =
|
||||||
|
'${referenceAbsoluteUrl.origin}/${currPathSegments.sublist(0, currPathSegments.length - (currPathSegments.last.contains('.') ? 1 : 0)).join('/')}/$ambiguousUrl';
|
||||||
}
|
}
|
||||||
|
return Uri.parse(absoluteUrl).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
int compareAlphaNumeric(String a, String b) {
|
int compareAlphaNumeric(String a, String b) {
|
||||||
@@ -85,48 +91,181 @@ bool _isNumeric(String s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class HTML extends AppSource {
|
class HTML extends AppSource {
|
||||||
|
var finalStepFormitems = [
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('customLinkFilterRegex',
|
||||||
|
label: tr('customLinkFilterRegex'),
|
||||||
|
hint: 'download/(.*/)?(android|apk|mobile)',
|
||||||
|
required: false,
|
||||||
|
additionalValidators: [
|
||||||
|
(value) {
|
||||||
|
return regExValidator(value);
|
||||||
|
}
|
||||||
|
])
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('versionExtractWholePage',
|
||||||
|
label: tr('versionExtractWholePage'))
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('supportFixedAPKURL',
|
||||||
|
defaultValue: true, label: tr('supportFixedAPKURL')),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
var commonFormItems = [
|
||||||
|
[GeneratedFormSwitch('filterByLinkText', label: tr('filterByLinkText'))],
|
||||||
|
[GeneratedFormSwitch('skipSort', label: tr('skipSort'))],
|
||||||
|
[GeneratedFormSwitch('reverseSort', label: tr('takeFirstLink'))],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('sortByLastLinkSegment',
|
||||||
|
label: tr('sortByLastLinkSegment'))
|
||||||
|
],
|
||||||
|
];
|
||||||
|
var intermediateFormItems = [
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('customLinkFilterRegex',
|
||||||
|
label: tr('intermediateLinkRegex'),
|
||||||
|
hint: '([0-9]+.)*[0-9]+/\$',
|
||||||
|
required: true,
|
||||||
|
additionalValidators: [(value) => regExValidator(value)])
|
||||||
|
],
|
||||||
|
];
|
||||||
|
HTML() {
|
||||||
|
additionalSourceAppSpecificSettingFormItems = [
|
||||||
|
[
|
||||||
|
GeneratedFormSubForm(
|
||||||
|
'intermediateLink', [...intermediateFormItems, ...commonFormItems],
|
||||||
|
label: tr('intermediateLink'))
|
||||||
|
],
|
||||||
|
finalStepFormitems[0],
|
||||||
|
...commonFormItems,
|
||||||
|
...finalStepFormitems.sublist(1)
|
||||||
|
];
|
||||||
|
overrideVersionDetectionFormDefault('noVersionDetection',
|
||||||
|
disableStandard: false, disableRelDate: true);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// TODO: implement requestHeaders choice, hardcoded for now
|
Future<Map<String, String>?> getRequestHeaders(
|
||||||
Map<String, String>? get requestHeaders => {
|
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
|
||||||
"User-Agent":
|
bool forAPKDownload = false}) async {
|
||||||
"Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"
|
return {
|
||||||
};
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given an HTTP response, grab some links according to the common additional settings
|
||||||
|
// (those that apply to intermediate and final steps)
|
||||||
|
Future<List<MapEntry<String, String>>> grabLinksCommon(
|
||||||
|
Response res, Map<String, dynamic> additionalSettings) async {
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
var html = parse(res.body);
|
||||||
|
List<MapEntry<String, String>> allLinks = html
|
||||||
|
.querySelectorAll('a')
|
||||||
|
.map((element) => MapEntry(
|
||||||
|
element.attributes['href'] ?? '',
|
||||||
|
element.text.isNotEmpty
|
||||||
|
? element.text
|
||||||
|
: (element.attributes['href'] ?? '').split('/').last))
|
||||||
|
.where((element) => element.key.isNotEmpty)
|
||||||
|
.map((e) =>
|
||||||
|
MapEntry(ensureAbsoluteUrl(e.key, res.request!.url), e.value))
|
||||||
|
.toList();
|
||||||
|
if (allLinks.isEmpty) {
|
||||||
|
allLinks = RegExp(
|
||||||
|
r'(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?')
|
||||||
|
.allMatches(res.body)
|
||||||
|
.map((match) =>
|
||||||
|
MapEntry(match.group(0)!, match.group(0)?.split('/').last ?? ''))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
List<MapEntry<String, String>> links = [];
|
||||||
|
bool skipSort = additionalSettings['skipSort'] == true;
|
||||||
|
bool filterLinkByText = additionalSettings['filterByLinkText'] == true;
|
||||||
|
if ((additionalSettings['customLinkFilterRegex'] as String?)?.isNotEmpty ==
|
||||||
|
true) {
|
||||||
|
var reg = RegExp(additionalSettings['customLinkFilterRegex']);
|
||||||
|
links = allLinks
|
||||||
|
.where((element) =>
|
||||||
|
reg.hasMatch(filterLinkByText ? element.value : element.key))
|
||||||
|
.toList();
|
||||||
|
} else {
|
||||||
|
links = allLinks
|
||||||
|
.where((element) =>
|
||||||
|
Uri.parse(filterLinkByText ? element.value : element.key)
|
||||||
|
.path
|
||||||
|
.toLowerCase()
|
||||||
|
.endsWith('.apk'))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
if (!skipSort) {
|
||||||
|
links.sort((a, b) => additionalSettings['sortByLastLinkSegment'] == true
|
||||||
|
? compareAlphaNumeric(
|
||||||
|
a.key.split('/').where((e) => e.isNotEmpty).last,
|
||||||
|
b.key.split('/').where((e) => e.isNotEmpty).last)
|
||||||
|
: compareAlphaNumeric(a.key, b.key));
|
||||||
|
}
|
||||||
|
if (additionalSettings['reverseSort'] == true) {
|
||||||
|
links = links.reversed.toList();
|
||||||
|
}
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
var uri = Uri.parse(standardUrl);
|
var currentUrl = standardUrl;
|
||||||
Response res = await sourceRequest(standardUrl);
|
if (additionalSettings['intermediateLink']?.isNotEmpty != true) {
|
||||||
if (res.statusCode == 200) {
|
additionalSettings['intermediateLink'] = [];
|
||||||
List<String> links = parse(res.body)
|
|
||||||
.querySelectorAll('a')
|
|
||||||
.map((element) => element.attributes['href'] ?? '')
|
|
||||||
.where((element) =>
|
|
||||||
Uri.parse(element).path.toLowerCase().endsWith('.apk'))
|
|
||||||
.toList();
|
|
||||||
links.sort((a, b) => compareAlphaNumeric(a, b));
|
|
||||||
if (additionalSettings['apkFilterRegEx'] != null) {
|
|
||||||
var reg = RegExp(additionalSettings['apkFilterRegEx']);
|
|
||||||
links = links.where((element) => reg.hasMatch(element)).toList();
|
|
||||||
}
|
|
||||||
if (links.isEmpty) {
|
|
||||||
throw NoReleasesError();
|
|
||||||
}
|
|
||||||
var rel = links.last;
|
|
||||||
var version = rel.hashCode.toString();
|
|
||||||
List<String> apkUrls =
|
|
||||||
[rel].map((e) => ensureAbsoluteUrl(e, uri)).toList();
|
|
||||||
return APKDetails(version, apkUrls.map((e) => MapEntry(e, e)).toList(),
|
|
||||||
AppNames(uri.host, tr('app')));
|
|
||||||
} else {
|
|
||||||
throw getObtainiumHttpError(res);
|
|
||||||
}
|
}
|
||||||
|
additionalSettings['intermediateLink'] =
|
||||||
|
additionalSettings['intermediateLink']
|
||||||
|
.where((l) => l['customLinkFilterRegex'].isNotEmpty == true)
|
||||||
|
.toList();
|
||||||
|
for (int i = 0; i < (additionalSettings['intermediateLink'].length); i++) {
|
||||||
|
var intLinks = await grabLinksCommon(await sourceRequest(currentUrl),
|
||||||
|
additionalSettings['intermediateLink'][i]);
|
||||||
|
if (intLinks.isEmpty) {
|
||||||
|
throw NoReleasesError();
|
||||||
|
} else {
|
||||||
|
currentUrl = intLinks.last.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var uri = Uri.parse(currentUrl);
|
||||||
|
Response res = await sourceRequest(currentUrl);
|
||||||
|
var links = await grabLinksCommon(res, additionalSettings);
|
||||||
|
|
||||||
|
if ((additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty == true) {
|
||||||
|
var reg = RegExp(additionalSettings['apkFilterRegEx']);
|
||||||
|
links = links.where((element) => reg.hasMatch(element.key)).toList();
|
||||||
|
}
|
||||||
|
if (links.isEmpty) {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
var rel = links.last.key;
|
||||||
|
String? version;
|
||||||
|
if (additionalSettings['supportFixedAPKURL'] != true) {
|
||||||
|
version = rel.hashCode.toString();
|
||||||
|
}
|
||||||
|
version = extractVersion(
|
||||||
|
additionalSettings['versionExtractionRegEx'] as String?,
|
||||||
|
additionalSettings['matchGroupToUse'] as String?,
|
||||||
|
additionalSettings['versionExtractWholePage'] == true
|
||||||
|
? res.body.split('\r\n').join('\n').split('\n').join('\\n')
|
||||||
|
: rel);
|
||||||
|
version ??= (await checkDownloadHash(rel)).toString();
|
||||||
|
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
|
||||||
|
AppNames(uri.host, tr('app')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
91
lib/app_sources/huaweiappgallery.dart
Normal file
91
lib/app_sources/huaweiappgallery.dart
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:obtainium/custom_errors.dart';
|
||||||
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
|
class HuaweiAppGallery extends AppSource {
|
||||||
|
HuaweiAppGallery() {
|
||||||
|
name = 'Huawei AppGallery';
|
||||||
|
host = 'appgallery.huawei.com';
|
||||||
|
overrideVersionDetectionFormDefault('releaseDateAsVersion',
|
||||||
|
disableStandard: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
|
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/app/[^/]+');
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
|
if (match == null) {
|
||||||
|
throw InvalidURLError(name);
|
||||||
|
}
|
||||||
|
return url.substring(0, match.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDlUrl(String standardUrl) =>
|
||||||
|
'https://${host!.replaceAll('appgallery.', 'appgallery.cloud.')}/appdl/${standardUrl.split('/').last}';
|
||||||
|
|
||||||
|
requestAppdlRedirect(String dlUrl) async {
|
||||||
|
Response res = await sourceRequest(dlUrl, followRedirects: false);
|
||||||
|
if (res.statusCode == 200 ||
|
||||||
|
res.statusCode == 302 ||
|
||||||
|
res.statusCode == 304) {
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appIdFromRedirectDlUrl(String redirectDlUrl) {
|
||||||
|
var parts = redirectDlUrl
|
||||||
|
.split('?')[0]
|
||||||
|
.split('/')
|
||||||
|
.last
|
||||||
|
.split('.')
|
||||||
|
.reversed
|
||||||
|
.toList();
|
||||||
|
parts.removeAt(0);
|
||||||
|
parts.removeAt(0);
|
||||||
|
return parts.reversed.join('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> tryInferringAppId(String standardUrl,
|
||||||
|
{Map<String, dynamic> additionalSettings = const {}}) async {
|
||||||
|
String dlUrl = getDlUrl(standardUrl);
|
||||||
|
Response res = await requestAppdlRedirect(dlUrl);
|
||||||
|
return res.headers['location'] != null
|
||||||
|
? appIdFromRedirectDlUrl(res.headers['location']!)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
|
String standardUrl,
|
||||||
|
Map<String, dynamic> additionalSettings,
|
||||||
|
) async {
|
||||||
|
String dlUrl = getDlUrl(standardUrl);
|
||||||
|
Response res = await requestAppdlRedirect(dlUrl);
|
||||||
|
if (res.headers['location'] == null) {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
String appId = appIdFromRedirectDlUrl(res.headers['location']!);
|
||||||
|
var relDateStr =
|
||||||
|
res.headers['location']?.split('?')[0].split('.').reversed.toList()[1];
|
||||||
|
var relDateStrAdj = relDateStr?.split('');
|
||||||
|
var tempLen = relDateStrAdj?.length ?? 0;
|
||||||
|
var i = 2;
|
||||||
|
while (i < tempLen) {
|
||||||
|
relDateStrAdj?.insert((i + i ~/ 2 - 1), '-');
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
var relDate = relDateStrAdj == null
|
||||||
|
? null
|
||||||
|
: DateFormat('yy-MM-dd-HH-mm').parse(relDateStrAdj.join(''));
|
||||||
|
if (relDateStr == null) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
return APKDetails(
|
||||||
|
relDateStr, [MapEntry('$appId.apk', dlUrl)], AppNames(name, appId),
|
||||||
|
releaseDate: relDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,14 +3,25 @@ import 'package:obtainium/custom_errors.dart';
|
|||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
class IzzyOnDroid extends AppSource {
|
class IzzyOnDroid extends AppSource {
|
||||||
|
late FDroid fd;
|
||||||
|
|
||||||
IzzyOnDroid() {
|
IzzyOnDroid() {
|
||||||
host = 'android.izzysoft.de';
|
host = 'izzysoft.de';
|
||||||
|
fd = FDroid();
|
||||||
|
additionalSourceAppSpecificSettingFormItems =
|
||||||
|
fd.additionalSourceAppSpecificSettingFormItems;
|
||||||
|
allowSubDomains = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx = RegExp('^https?://$host/repo/apk/[^/]+');
|
RegExp standardUrlRegExA = RegExp('^https?://android.$host/repo/apk/[^/]+');
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
RegExpMatch? match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
||||||
|
if (match == null) {
|
||||||
|
RegExp standardUrlRegExB =
|
||||||
|
RegExp('^https?://apt.$host/fdroid/index/apk/[^/]+');
|
||||||
|
match = standardUrlRegExB.firstMatch(url.toLowerCase());
|
||||||
|
}
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
@@ -20,7 +31,7 @@ class IzzyOnDroid extends AppSource {
|
|||||||
@override
|
@override
|
||||||
Future<String?> tryInferringAppId(String standardUrl,
|
Future<String?> tryInferringAppId(String standardUrl,
|
||||||
{Map<String, dynamic> additionalSettings = const {}}) async {
|
{Map<String, dynamic> additionalSettings = const {}}) async {
|
||||||
return FDroid().tryInferringAppId(standardUrl);
|
return fd.tryInferringAppId(standardUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -29,10 +40,16 @@ class IzzyOnDroid extends AppSource {
|
|||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
String? appId = await tryInferringAppId(standardUrl);
|
String? appId = await tryInferringAppId(standardUrl);
|
||||||
return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse(
|
return fd.getAPKUrlsFromFDroidPackagesAPIResponse(
|
||||||
await sourceRequest(
|
await sourceRequest(
|
||||||
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'),
|
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'),
|
||||||
'https://android.izzysoft.de/frepo/$appId',
|
'https://android.izzysoft.de/frepo/$appId',
|
||||||
standardUrl);
|
standardUrl,
|
||||||
|
name,
|
||||||
|
autoSelectHighestVersionCode:
|
||||||
|
additionalSettings['autoSelectHighestVersionCode'] == true,
|
||||||
|
trySelectingSuggestedVersionCode:
|
||||||
|
additionalSettings['trySelectingSuggestedVersionCode'] == true,
|
||||||
|
filterVersionsByRegEx: additionalSettings['filterVersionsByRegEx']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import 'package:obtainium/providers/source_provider.dart';
|
|||||||
|
|
||||||
class Jenkins extends AppSource {
|
class Jenkins extends AppSource {
|
||||||
Jenkins() {
|
Jenkins() {
|
||||||
overrideVersionDetectionFormDefault('releaseDateAsVersion', true);
|
overrideVersionDetectionFormDefault('releaseDateAsVersion',
|
||||||
|
disableStandard: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
String trimJobUrl(String url) {
|
String trimJobUrl(String url) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class Mullvad extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx = RegExp('^https?://$host');
|
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host');
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ class NeutronCode extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx = RegExp('^https?://$host/downloads/file/[^/]+');
|
RegExp standardUrlRegEx =
|
||||||
|
RegExp('^https?://(www\\.)?$host/downloads/file/[^/]+');
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
|
|||||||
@@ -10,13 +10,14 @@ class SourceForge extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegExB = RegExp('^https?://$host/p/[^/]+');
|
RegExp standardUrlRegExB = RegExp('^https?://(www\\.)?$host/p/[^/]+');
|
||||||
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
|
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
url =
|
url =
|
||||||
'https://${Uri.parse(url.substring(0, match.end)).host}/projects/${url.substring(Uri.parse(url.substring(0, match.end)).host.length + '/projects/'.length + 1)}';
|
'https://${Uri.parse(url.substring(0, match.end)).host}/projects/${url.substring(Uri.parse(url.substring(0, match.end)).host.length + '/projects/'.length + 1)}';
|
||||||
}
|
}
|
||||||
RegExp standardUrlRegExA = RegExp('^https?://$host/projects/[^/]+');
|
RegExp standardUrlRegExA =
|
||||||
|
RegExp('^https?://(www\\.)?$host/projects/[^/]+');
|
||||||
match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class SourceHut extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
|
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
|
|||||||
99
lib/app_sources/uptodown.dart
Normal file
99
lib/app_sources/uptodown.dart
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:html/parser.dart';
|
||||||
|
import 'package:obtainium/app_sources/apkpure.dart';
|
||||||
|
import 'package:obtainium/custom_errors.dart';
|
||||||
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
|
class Uptodown extends AppSource {
|
||||||
|
Uptodown() {
|
||||||
|
host = 'uptodown.com';
|
||||||
|
allowSubDomains = true;
|
||||||
|
naiveStandardVersionDetection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
|
RegExp standardUrlRegEx = RegExp('^https?://([^\\.]+\\.){2,}$host');
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
|
if (match == null) {
|
||||||
|
throw InvalidURLError(name);
|
||||||
|
}
|
||||||
|
return '${url.substring(0, match.end)}/android/download';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> tryInferringAppId(String standardUrl,
|
||||||
|
{Map<String, dynamic> additionalSettings = const {}}) async {
|
||||||
|
return (await getAppDetailsFromPage(standardUrl))['appId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String?>> getAppDetailsFromPage(String standardUrl) async {
|
||||||
|
var res = await sourceRequest(standardUrl);
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
var html = parse(res.body);
|
||||||
|
String? version = html.querySelector('div.version')?.innerHtml;
|
||||||
|
String? apkUrl =
|
||||||
|
'${standardUrl.split('/').reversed.toList().sublist(1).reversed.join('/')}/post-download';
|
||||||
|
String? name = html.querySelector('#detail-app-name')?.innerHtml.trim();
|
||||||
|
String? author = html.querySelector('#author-link')?.innerHtml.trim();
|
||||||
|
var detailElements = html.querySelectorAll('#technical-information td');
|
||||||
|
String? appId = (detailElements.elementAtOrNull(2))?.innerHtml.trim();
|
||||||
|
String? dateStr = (detailElements.elementAtOrNull(29))?.innerHtml.trim();
|
||||||
|
return Map.fromEntries([
|
||||||
|
MapEntry('version', version),
|
||||||
|
MapEntry('apkUrl', apkUrl),
|
||||||
|
MapEntry('appId', appId),
|
||||||
|
MapEntry('name', name),
|
||||||
|
MapEntry('author', author),
|
||||||
|
MapEntry('dateStr', dateStr)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
|
String standardUrl,
|
||||||
|
Map<String, dynamic> additionalSettings,
|
||||||
|
) async {
|
||||||
|
var appDetails = await getAppDetailsFromPage(standardUrl);
|
||||||
|
var version = appDetails['version'];
|
||||||
|
var apkUrl = appDetails['apkUrl'];
|
||||||
|
var appId = appDetails['appId'];
|
||||||
|
if (version == null) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
if (apkUrl == null) {
|
||||||
|
throw NoAPKError();
|
||||||
|
}
|
||||||
|
if (appId == null) {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
String appName = appDetails['name'] ?? tr('app');
|
||||||
|
String author = appDetails['author'] ?? name;
|
||||||
|
String? dateStr = appDetails['dateStr'];
|
||||||
|
DateTime? relDate;
|
||||||
|
if (dateStr != null) {
|
||||||
|
relDate = parseDateTimeMMMddCommayyyy(dateStr);
|
||||||
|
}
|
||||||
|
return APKDetails(
|
||||||
|
version, getApkUrlsFromUrls([apkUrl]), AppNames(author, appName),
|
||||||
|
releaseDate: relDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> apkUrlPrefetchModifier(
|
||||||
|
String apkUrl, String standardUrl) async {
|
||||||
|
var res = await sourceRequest(apkUrl);
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
var html = parse(res.body);
|
||||||
|
var finalUrlKey =
|
||||||
|
html.querySelector('.post-download')?.attributes['data-url'];
|
||||||
|
if (finalUrlKey == null) {
|
||||||
|
throw NoAPKError();
|
||||||
|
}
|
||||||
|
return 'https://dw.$host/dwn/$finalUrlKey';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:html/parser.dart';
|
import 'package:html/parser.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
|
import 'package:obtainium/app_sources/html.dart';
|
||||||
import 'package:obtainium/custom_errors.dart';
|
import 'package:obtainium/custom_errors.dart';
|
||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
@@ -7,19 +9,46 @@ class VLC extends AppSource {
|
|||||||
VLC() {
|
VLC() {
|
||||||
host = 'videolan.org';
|
host = 'videolan.org';
|
||||||
}
|
}
|
||||||
|
get dwUrlBase => 'https://get.$host/vlc-android/';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, String>?> getRequestHeaders(
|
||||||
|
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
|
||||||
|
bool forAPKDownload = false}) =>
|
||||||
|
HTML().getRequestHeaders(
|
||||||
|
additionalSettings: additionalSettings,
|
||||||
|
forAPKDownload: forAPKDownload);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
return 'https://$host';
|
return 'https://$host';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> getLatestVersion(String standardUrl) async {
|
||||||
|
Response res = await sourceRequest(dwUrlBase);
|
||||||
|
if (res.statusCode == 200) {
|
||||||
|
var dwLinks = parse(res.body)
|
||||||
|
.querySelectorAll('a')
|
||||||
|
.where((element) => element.attributes['href'] != 'last/')
|
||||||
|
.map((e) => e.attributes['href']?.split('/')[0])
|
||||||
|
.toList();
|
||||||
|
String? version = dwLinks.isNotEmpty ? dwLinks.last : null;
|
||||||
|
if (version == null) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
} else {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res = await sourceRequest(
|
Response res = await get(
|
||||||
'https://www.videolan.org/vlc/download-android.html');
|
Uri.parse('https://www.videolan.org/vlc/download-android.html'));
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var dwUrlBase = 'get.videolan.org/vlc-android';
|
var dwUrlBase = 'get.videolan.org/vlc-android';
|
||||||
var dwLinks = parse(res.body)
|
var dwLinks = parse(res.body)
|
||||||
@@ -37,26 +66,43 @@ class VLC extends AppSource {
|
|||||||
throw NoVersionError();
|
throw NoVersionError();
|
||||||
}
|
}
|
||||||
String? targetUrl = 'https://$dwUrlBase/$version/';
|
String? targetUrl = 'https://$dwUrlBase/$version/';
|
||||||
Response res2 = await sourceRequest(targetUrl);
|
var apkUrls = ['arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64']
|
||||||
String mirrorDwBase =
|
.map((e) => '${targetUrl}VLC-Android-$version-$e.apk')
|
||||||
'https://plug-mirror.rcac.purdue.edu/vlc/vlc-android/$version/';
|
.toList();
|
||||||
List<String> apkUrls = [];
|
|
||||||
if (res2.statusCode == 200) {
|
|
||||||
apkUrls = parse(res2.body)
|
|
||||||
.querySelectorAll('a')
|
|
||||||
.map((e) => e.attributes['href'])
|
|
||||||
.where((h) =>
|
|
||||||
h != null && h.isNotEmpty && h.toLowerCase().endsWith('.apk'))
|
|
||||||
.map((e) => mirrorDwBase + e!)
|
|
||||||
.toList();
|
|
||||||
} else {
|
|
||||||
throw getObtainiumHttpError(res2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return APKDetails(
|
return APKDetails(
|
||||||
version, getApkUrlsFromUrls(apkUrls), AppNames('VideoLAN', 'VLC'));
|
version, getApkUrlsFromUrls(apkUrls), AppNames('VideoLAN', 'VLC'));
|
||||||
} else {
|
} else {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> apkUrlPrefetchModifier(
|
||||||
|
String apkUrl, String standardUrl) async {
|
||||||
|
Response res = await sourceRequest(apkUrl);
|
||||||
|
if (res.statusCode == 200) {
|
||||||
|
String? apkUrl =
|
||||||
|
parse(res.body).querySelector('#alt_link')?.attributes['href'];
|
||||||
|
if (apkUrl == null) {
|
||||||
|
throw NoAPKError();
|
||||||
|
}
|
||||||
|
return apkUrl;
|
||||||
|
} else if (res.statusCode == 500 &&
|
||||||
|
res.body.toLowerCase().indexOf('mirror') > 0) {
|
||||||
|
var html = parse(res.body);
|
||||||
|
var err = '';
|
||||||
|
html.body?.nodes.forEach((element) {
|
||||||
|
if (element.text != null) {
|
||||||
|
err += '${element.text}\n';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
err = err.trim();
|
||||||
|
if (err.isEmpty) {
|
||||||
|
err = tr('err');
|
||||||
|
}
|
||||||
|
throw ObtainiumError(err);
|
||||||
|
} else {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import 'package:obtainium/providers/source_provider.dart';
|
|||||||
class WhatsApp extends AppSource {
|
class WhatsApp extends AppSource {
|
||||||
WhatsApp() {
|
WhatsApp() {
|
||||||
host = 'whatsapp.com';
|
host = 'whatsapp.com';
|
||||||
|
overrideVersionDetectionFormDefault('noVersionDetection',
|
||||||
|
disableStandard: true, disableRelDate: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -16,14 +18,13 @@ class WhatsApp extends AppSource {
|
|||||||
@override
|
@override
|
||||||
Future<String> apkUrlPrefetchModifier(
|
Future<String> apkUrlPrefetchModifier(
|
||||||
String apkUrl, String standardUrl) async {
|
String apkUrl, String standardUrl) async {
|
||||||
Response res = await sourceRequest('https://www.whatsapp.com/android');
|
Response res = await sourceRequest('$standardUrl/android');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var targetLinks = parse(res.body)
|
var targetLinks = parse(res.body)
|
||||||
.querySelectorAll('a')
|
.querySelectorAll('a')
|
||||||
.map((e) => e.attributes['href'] ?? '')
|
.map((e) => e.attributes['href'] ?? '')
|
||||||
.where((e) => e.isNotEmpty)
|
.where((e) => e.isNotEmpty)
|
||||||
.where((e) =>
|
.where((e) => e.contains('WhatsApp.apk'))
|
||||||
e.contains('content.whatsapp.net') && e.contains('WhatsApp.apk'))
|
|
||||||
.toList();
|
.toList();
|
||||||
if (targetLinks.isEmpty) {
|
if (targetLinks.isEmpty) {
|
||||||
throw NoAPKError();
|
throw NoAPKError();
|
||||||
@@ -39,37 +40,16 @@ class WhatsApp extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res = await sourceRequest('https://www.whatsapp.com/android');
|
// This is a CDN link that is consistent per version
|
||||||
if (res.statusCode == 200) {
|
// But it has query params that change constantly
|
||||||
var targetElements = parse(res.body)
|
Uri apkUri =
|
||||||
.querySelectorAll('p')
|
Uri.parse(await apkUrlPrefetchModifier(standardUrl, standardUrl));
|
||||||
.where((element) => element.innerHtml.contains('Version '))
|
var unusableApkUrl = '${apkUri.origin}/${apkUri.path}';
|
||||||
.toList();
|
// So we use the param-less URL is a pseudo-version to add the app and check for updates
|
||||||
if (targetElements.isEmpty) {
|
// See #357 for why we can't scrape the version number directly
|
||||||
throw NoVersionError();
|
// But we re-fetch the URL again with its latest query params at the actual download time
|
||||||
}
|
String version = unusableApkUrl.hashCode.toString();
|
||||||
var vLines = targetElements[0]
|
return APKDetails(version, getApkUrlsFromUrls([unusableApkUrl]),
|
||||||
.innerHtml
|
AppNames('Meta', 'WhatsApp'));
|
||||||
.split('\n')
|
|
||||||
.where((element) => element.contains('Version '))
|
|
||||||
.toList();
|
|
||||||
if (vLines.isEmpty) {
|
|
||||||
throw NoVersionError();
|
|
||||||
}
|
|
||||||
var versionMatch = RegExp('[0-9]+(\\.[0-9]+)+').firstMatch(vLines[0]);
|
|
||||||
if (versionMatch == null) {
|
|
||||||
throw NoVersionError();
|
|
||||||
}
|
|
||||||
String version =
|
|
||||||
vLines[0].substring(versionMatch.start, versionMatch.end);
|
|
||||||
return APKDetails(
|
|
||||||
version,
|
|
||||||
getApkUrlsFromUrls([
|
|
||||||
'https://www.whatsapp.com/android?v=$version&=thisIsaPlaceholder&a=realURLPrefetchedAtDownloadTime'
|
|
||||||
]),
|
|
||||||
AppNames('Meta', 'WhatsApp'));
|
|
||||||
} else {
|
|
||||||
throw getObtainiumHttpError(res);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:hsluv/hsluv.dart';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:obtainium/components/generated_form_modal.dart';
|
import 'package:obtainium/components/generated_form_modal.dart';
|
||||||
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
abstract class GeneratedFormItem {
|
abstract class GeneratedFormItem {
|
||||||
late String key;
|
late String key;
|
||||||
@@ -25,21 +26,19 @@ class GeneratedFormTextField extends GeneratedFormItem {
|
|||||||
late int max;
|
late int max;
|
||||||
late String? hint;
|
late String? hint;
|
||||||
late bool password;
|
late bool password;
|
||||||
|
late TextInputType? textInputType;
|
||||||
|
|
||||||
GeneratedFormTextField(String key,
|
GeneratedFormTextField(super.key,
|
||||||
{String label = 'Input',
|
{super.label,
|
||||||
List<Widget> belowWidgets = const [],
|
super.belowWidgets,
|
||||||
String defaultValue = '',
|
String super.defaultValue = '',
|
||||||
List<String? Function(String? value)> additionalValidators = const [],
|
List<String? Function(String? value)> super.additionalValidators =
|
||||||
|
const [],
|
||||||
this.required = true,
|
this.required = true,
|
||||||
this.max = 1,
|
this.max = 1,
|
||||||
this.hint,
|
this.hint,
|
||||||
this.password = false})
|
this.password = false,
|
||||||
: super(key,
|
this.textInputType});
|
||||||
label: label,
|
|
||||||
belowWidgets: belowWidgets,
|
|
||||||
defaultValue: defaultValue,
|
|
||||||
additionalValidators: additionalValidators);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String ensureType(val) {
|
String ensureType(val) {
|
||||||
@@ -52,18 +51,14 @@ class GeneratedFormDropdown extends GeneratedFormItem {
|
|||||||
List<String>? disabledOptKeys;
|
List<String>? disabledOptKeys;
|
||||||
|
|
||||||
GeneratedFormDropdown(
|
GeneratedFormDropdown(
|
||||||
String key,
|
super.key,
|
||||||
this.opts, {
|
this.opts, {
|
||||||
String label = 'Input',
|
super.label,
|
||||||
List<Widget> belowWidgets = const [],
|
super.belowWidgets,
|
||||||
String defaultValue = '',
|
String super.defaultValue = '',
|
||||||
this.disabledOptKeys,
|
this.disabledOptKeys,
|
||||||
List<String? Function(String? value)> additionalValidators = const [],
|
List<String? Function(String? value)> super.additionalValidators = const [],
|
||||||
}) : super(key,
|
});
|
||||||
label: label,
|
|
||||||
belowWidgets: belowWidgets,
|
|
||||||
defaultValue: defaultValue,
|
|
||||||
additionalValidators: additionalValidators);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String ensureType(val) {
|
String ensureType(val) {
|
||||||
@@ -73,16 +68,12 @@ class GeneratedFormDropdown extends GeneratedFormItem {
|
|||||||
|
|
||||||
class GeneratedFormSwitch extends GeneratedFormItem {
|
class GeneratedFormSwitch extends GeneratedFormItem {
|
||||||
GeneratedFormSwitch(
|
GeneratedFormSwitch(
|
||||||
String key, {
|
super.key, {
|
||||||
String label = 'Input',
|
super.label,
|
||||||
List<Widget> belowWidgets = const [],
|
super.belowWidgets,
|
||||||
bool defaultValue = false,
|
bool super.defaultValue = false,
|
||||||
List<String? Function(bool value)> additionalValidators = const [],
|
List<String? Function(bool value)> super.additionalValidators = const [],
|
||||||
}) : super(key,
|
});
|
||||||
label: label,
|
|
||||||
belowWidgets: belowWidgets,
|
|
||||||
defaultValue: defaultValue,
|
|
||||||
additionalValidators: additionalValidators);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool ensureType(val) {
|
bool ensureType(val) {
|
||||||
@@ -96,22 +87,17 @@ class GeneratedFormTagInput extends GeneratedFormItem {
|
|||||||
late WrapAlignment alignment;
|
late WrapAlignment alignment;
|
||||||
late String emptyMessage;
|
late String emptyMessage;
|
||||||
late bool showLabelWhenNotEmpty;
|
late bool showLabelWhenNotEmpty;
|
||||||
GeneratedFormTagInput(String key,
|
GeneratedFormTagInput(super.key,
|
||||||
{String label = 'Input',
|
{super.label,
|
||||||
List<Widget> belowWidgets = const [],
|
super.belowWidgets,
|
||||||
Map<String, MapEntry<int, bool>> defaultValue = const {},
|
Map<String, MapEntry<int, bool>> super.defaultValue = const {},
|
||||||
List<String? Function(Map<String, MapEntry<int, bool>> value)>
|
List<String? Function(Map<String, MapEntry<int, bool>> value)>
|
||||||
additionalValidators = const [],
|
super.additionalValidators = const [],
|
||||||
this.deleteConfirmationMessage,
|
this.deleteConfirmationMessage,
|
||||||
this.singleSelect = false,
|
this.singleSelect = false,
|
||||||
this.alignment = WrapAlignment.start,
|
this.alignment = WrapAlignment.start,
|
||||||
this.emptyMessage = 'Input',
|
this.emptyMessage = 'Input',
|
||||||
this.showLabelWhenNotEmpty = true})
|
this.showLabelWhenNotEmpty = true});
|
||||||
: super(key,
|
|
||||||
label: label,
|
|
||||||
belowWidgets: belowWidgets,
|
|
||||||
defaultValue: defaultValue,
|
|
||||||
additionalValidators: additionalValidators);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, MapEntry<int, bool>> ensureType(val) {
|
Map<String, MapEntry<int, bool>> ensureType(val) {
|
||||||
@@ -133,6 +119,18 @@ class GeneratedForm extends StatefulWidget {
|
|||||||
State<GeneratedForm> createState() => _GeneratedFormState();
|
State<GeneratedForm> createState() => _GeneratedFormState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GeneratedFormSubForm extends GeneratedFormItem {
|
||||||
|
final List<List<GeneratedFormItem>> items;
|
||||||
|
|
||||||
|
GeneratedFormSubForm(super.key, this.items,
|
||||||
|
{super.label, super.belowWidgets, super.defaultValue = const []});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ensureType(val) {
|
||||||
|
return val; // Not easy to validate List<Map<String, dynamic>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generates a color in the HSLuv (Pastel) color space
|
// Generates a color in the HSLuv (Pastel) color space
|
||||||
// https://pub.dev/documentation/hsluv/latest/hsluv/Hsluv/hpluvToRgb.html
|
// https://pub.dev/documentation/hsluv/latest/hsluv/Hsluv/hpluvToRgb.html
|
||||||
Color generateRandomLightColor() {
|
Color generateRandomLightColor() {
|
||||||
@@ -144,32 +142,44 @@ Color generateRandomLightColor() {
|
|||||||
// Map from HPLuv color space to RGB, use constant saturation=100, lightness=70
|
// Map from HPLuv color space to RGB, use constant saturation=100, lightness=70
|
||||||
final List<double> rgbValuesDbl = Hsluv.hpluvToRgb([hue, 100, 70]);
|
final List<double> rgbValuesDbl = Hsluv.hpluvToRgb([hue, 100, 70]);
|
||||||
// Map RBG values from 0-1 to 0-255:
|
// Map RBG values from 0-1 to 0-255:
|
||||||
final List<int> rgbValues = rgbValuesDbl.map((rgb) => (rgb * 255).toInt()).toList();
|
final List<int> rgbValues =
|
||||||
|
rgbValuesDbl.map((rgb) => (rgb * 255).toInt()).toList();
|
||||||
return Color.fromARGB(255, rgbValues[0], rgbValues[1], rgbValues[2]);
|
return Color.fromARGB(255, rgbValues[0], rgbValues[1], rgbValues[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int generateRandomNumber(int seed1,
|
||||||
|
{int seed2 = 0, int seed3 = 0, max = 10000}) {
|
||||||
|
int combinedSeed = seed1.hashCode ^ seed2.hashCode ^ seed3.hashCode;
|
||||||
|
Random random = Random(combinedSeed);
|
||||||
|
int randomNumber = random.nextInt(max);
|
||||||
|
return randomNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateTextField(TextFormField tf) =>
|
||||||
|
(tf.key as GlobalKey<FormFieldState>).currentState?.isValid == true;
|
||||||
|
|
||||||
class _GeneratedFormState extends State<GeneratedForm> {
|
class _GeneratedFormState extends State<GeneratedForm> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
Map<String, dynamic> values = {};
|
Map<String, dynamic> values = {};
|
||||||
late List<List<Widget>> formInputs;
|
late List<List<Widget>> formInputs;
|
||||||
List<List<Widget>> rows = [];
|
List<List<Widget>> rows = [];
|
||||||
String? initKey;
|
String? initKey;
|
||||||
|
int forceUpdateKeyCount = 0;
|
||||||
|
|
||||||
// If any value changes, call this to update the parent with value and validity
|
// If any value changes, call this to update the parent with value and validity
|
||||||
void someValueChanged({bool isBuilding = false}) {
|
void someValueChanged({bool isBuilding = false, bool forceInvalid = false}) {
|
||||||
Map<String, dynamic> returnValues = values;
|
Map<String, dynamic> returnValues = values;
|
||||||
var valid = true;
|
var valid = true;
|
||||||
for (int r = 0; r < widget.items.length; r++) {
|
for (int r = 0; r < widget.items.length; r++) {
|
||||||
for (int i = 0; i < widget.items[r].length; i++) {
|
for (int i = 0; i < widget.items[r].length; i++) {
|
||||||
if (formInputs[r][i] is TextFormField) {
|
if (formInputs[r][i] is TextFormField) {
|
||||||
var fieldState =
|
valid = valid && validateTextField(formInputs[r][i] as TextFormField);
|
||||||
(formInputs[r][i].key as GlobalKey<FormFieldState>).currentState;
|
|
||||||
if (fieldState != null) {
|
|
||||||
valid = valid && fieldState.isValid;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (forceInvalid) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
widget.onValueChanges(returnValues, valid, isBuilding);
|
widget.onValueChanges(returnValues, valid, isBuilding);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,6 +200,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
if (formItem is GeneratedFormTextField) {
|
if (formItem is GeneratedFormTextField) {
|
||||||
final formFieldKey = GlobalKey<FormFieldState>();
|
final formFieldKey = GlobalKey<FormFieldState>();
|
||||||
return TextFormField(
|
return TextFormField(
|
||||||
|
keyboardType: formItem.textInputType,
|
||||||
obscureText: formItem.password,
|
obscureText: formItem.password,
|
||||||
autocorrect: !formItem.password,
|
autocorrect: !formItem.password,
|
||||||
enableSuggestions: !formItem.password,
|
enableSuggestions: !formItem.password,
|
||||||
@@ -243,6 +254,17 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
someValueChanged();
|
someValueChanged();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
} else if (formItem is GeneratedFormSubForm) {
|
||||||
|
values[formItem.key] = [];
|
||||||
|
for (Map<String, dynamic> v
|
||||||
|
in ((formItem.defaultValue ?? []) as List<dynamic>)) {
|
||||||
|
var fullDefaults = getDefaultValuesFromFormItems(formItem.items);
|
||||||
|
for (var element in v.entries) {
|
||||||
|
fullDefaults[element.key] = element.value;
|
||||||
|
}
|
||||||
|
values[formItem.key].add(fullDefaults);
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
} else {
|
} else {
|
||||||
return Container(); // Some input types added in build
|
return Container(); // Some input types added in build
|
||||||
}
|
}
|
||||||
@@ -264,6 +286,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
}
|
}
|
||||||
for (var r = 0; r < formInputs.length; r++) {
|
for (var r = 0; r < formInputs.length; r++) {
|
||||||
for (var e = 0; e < formInputs[r].length; e++) {
|
for (var e = 0; e < formInputs[r].length; e++) {
|
||||||
|
String fieldKey = widget.items[r][e].key;
|
||||||
if (widget.items[r][e] is GeneratedFormSwitch) {
|
if (widget.items[r][e] is GeneratedFormSwitch) {
|
||||||
formInputs[r][e] = Row(
|
formInputs[r][e] = Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@@ -273,10 +296,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
width: 8,
|
width: 8,
|
||||||
),
|
),
|
||||||
Switch(
|
Switch(
|
||||||
value: values[widget.items[r][e].key],
|
value: values[fieldKey],
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
values[widget.items[r][e].key] = value;
|
values[fieldKey] = value;
|
||||||
someValueChanged();
|
someValueChanged();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -285,8 +308,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
} else if (widget.items[r][e] is GeneratedFormTagInput) {
|
} else if (widget.items[r][e] is GeneratedFormTagInput) {
|
||||||
formInputs[r][e] =
|
formInputs[r][e] =
|
||||||
Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||||
if ((values[widget.items[r][e].key]
|
if ((values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
|
||||||
?.isNotEmpty ==
|
?.isNotEmpty ==
|
||||||
true &&
|
true &&
|
||||||
(widget.items[r][e] as GeneratedFormTagInput)
|
(widget.items[r][e] as GeneratedFormTagInput)
|
||||||
@@ -309,8 +331,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
(widget.items[r][e] as GeneratedFormTagInput).alignment,
|
(widget.items[r][e] as GeneratedFormTagInput).alignment,
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
(values[widget.items[r][e].key]
|
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
|
||||||
?.isEmpty ==
|
?.isEmpty ==
|
||||||
true
|
true
|
||||||
? Text(
|
? Text(
|
||||||
@@ -318,8 +339,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
.emptyMessage,
|
.emptyMessage,
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
...(values[widget.items[r][e].key]
|
...(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
|
||||||
?.entries
|
?.entries
|
||||||
.map((e2) {
|
.map((e2) {
|
||||||
return Padding(
|
return Padding(
|
||||||
@@ -332,11 +352,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
selected: e2.value.value,
|
selected: e2.value.value,
|
||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
(values[widget.items[r][e].key] as Map<String,
|
(values[fieldKey] as Map<String,
|
||||||
MapEntry<int, bool>>)[e2.key] =
|
MapEntry<int, bool>>)[e2.key] =
|
||||||
MapEntry(
|
MapEntry(
|
||||||
(values[widget.items[r][e].key] as Map<
|
(values[fieldKey] as Map<String,
|
||||||
String,
|
|
||||||
MapEntry<int, bool>>)[e2.key]!
|
MapEntry<int, bool>>)[e2.key]!
|
||||||
.key,
|
.key,
|
||||||
value);
|
value);
|
||||||
@@ -344,22 +363,18 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
as GeneratedFormTagInput)
|
as GeneratedFormTagInput)
|
||||||
.singleSelect &&
|
.singleSelect &&
|
||||||
value == true) {
|
value == true) {
|
||||||
for (var key in (values[
|
for (var key in (values[fieldKey]
|
||||||
widget.items[r][e].key]
|
|
||||||
as Map<String, MapEntry<int, bool>>)
|
as Map<String, MapEntry<int, bool>>)
|
||||||
.keys) {
|
.keys) {
|
||||||
if (key != e2.key) {
|
if (key != e2.key) {
|
||||||
(values[widget.items[r][e].key] as Map<
|
(values[fieldKey] as Map<
|
||||||
String,
|
String,
|
||||||
MapEntry<int, bool>>)[key] =
|
MapEntry<int,
|
||||||
MapEntry(
|
bool>>)[key] = MapEntry(
|
||||||
(values[widget.items[r][e].key]
|
(values[fieldKey] as Map<String,
|
||||||
as Map<
|
MapEntry<int, bool>>)[key]!
|
||||||
String,
|
.key,
|
||||||
MapEntry<int,
|
false);
|
||||||
bool>>)[key]!
|
|
||||||
.key,
|
|
||||||
false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,38 +384,39 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
));
|
));
|
||||||
}) ??
|
}) ??
|
||||||
[const SizedBox.shrink()],
|
[const SizedBox.shrink()],
|
||||||
(values[widget.items[r][e].key]
|
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
?.values
|
||||||
?.values
|
.where((e) => e.value)
|
||||||
.where((e) => e.value)
|
.length ==
|
||||||
.length == 1
|
1
|
||||||
? Padding(
|
? Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
var temp = values[widget.items[r][e].key]
|
var temp = values[fieldKey]
|
||||||
as Map<String, MapEntry<int, bool>>;
|
as Map<String, MapEntry<int, bool>>;
|
||||||
// get selected category str where bool is true
|
// get selected category str where bool is true
|
||||||
final oldEntry = temp.entries.firstWhere((entry) => entry.value.value);
|
final oldEntry = temp.entries
|
||||||
// generate new color, ensure it is not the same
|
.firstWhere((entry) => entry.value.value);
|
||||||
int newColor = oldEntry.value.key;
|
// generate new color, ensure it is not the same
|
||||||
while(oldEntry.value.key == newColor) {
|
int newColor = oldEntry.value.key;
|
||||||
newColor = generateRandomLightColor().value;
|
while (oldEntry.value.key == newColor) {
|
||||||
}
|
newColor = generateRandomLightColor().value;
|
||||||
// Update entry with new color, remain selected
|
}
|
||||||
temp.update(oldEntry.key, (old) => MapEntry(newColor, old.value));
|
// Update entry with new color, remain selected
|
||||||
values[widget.items[r][e].key] = temp;
|
temp.update(oldEntry.key,
|
||||||
someValueChanged();
|
(old) => MapEntry(newColor, old.value));
|
||||||
});
|
values[fieldKey] = temp;
|
||||||
},
|
someValueChanged();
|
||||||
icon: const Icon(Icons.format_color_fill_rounded),
|
});
|
||||||
visualDensity: VisualDensity.compact,
|
},
|
||||||
tooltip: tr('colour'),
|
icon: const Icon(Icons.format_color_fill_rounded),
|
||||||
))
|
visualDensity: VisualDensity.compact,
|
||||||
|
tooltip: tr('colour'),
|
||||||
|
))
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
(values[widget.items[r][e].key]
|
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
|
||||||
as Map<String, MapEntry<int, bool>>?)
|
|
||||||
?.values
|
?.values
|
||||||
.where((e) => e.value)
|
.where((e) => e.value)
|
||||||
.isNotEmpty ==
|
.isNotEmpty ==
|
||||||
@@ -411,10 +427,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
fn() {
|
fn() {
|
||||||
setState(() {
|
setState(() {
|
||||||
var temp = values[widget.items[r][e].key]
|
var temp = values[fieldKey]
|
||||||
as Map<String, MapEntry<int, bool>>;
|
as Map<String, MapEntry<int, bool>>;
|
||||||
temp.removeWhere((key, value) => value.value);
|
temp.removeWhere((key, value) => value.value);
|
||||||
values[widget.items[r][e].key] = temp;
|
values[fieldKey] = temp;
|
||||||
someValueChanged();
|
someValueChanged();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -465,7 +481,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
String? label = value?['label'];
|
String? label = value?['label'];
|
||||||
if (label != null) {
|
if (label != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
var temp = values[widget.items[r][e].key]
|
var temp = values[fieldKey]
|
||||||
as Map<String, MapEntry<int, bool>>?;
|
as Map<String, MapEntry<int, bool>>?;
|
||||||
temp ??= {};
|
temp ??= {};
|
||||||
if (temp[label] == null) {
|
if (temp[label] == null) {
|
||||||
@@ -478,7 +494,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
temp[label] = MapEntry(
|
temp[label] = MapEntry(
|
||||||
generateRandomLightColor().value,
|
generateRandomLightColor().value,
|
||||||
!(someSelected && singleSelect));
|
!(someSelected && singleSelect));
|
||||||
values[widget.items[r][e].key] = temp;
|
values[fieldKey] = temp;
|
||||||
someValueChanged();
|
someValueChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -492,6 +508,93 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
|
} else if (widget.items[r][e] is GeneratedFormSubForm) {
|
||||||
|
List<Widget> subformColumn = [];
|
||||||
|
for (int i = 0; i < values[fieldKey].length; i++) {
|
||||||
|
var items = (widget.items[r][e] as GeneratedFormSubForm)
|
||||||
|
.items
|
||||||
|
.map((x) => x.map((y) {
|
||||||
|
y.defaultValue = values[fieldKey]?[i]?[y.key];
|
||||||
|
return y;
|
||||||
|
}).toList())
|
||||||
|
.toList();
|
||||||
|
var internalFormKey = ValueKey(generateRandomNumber(
|
||||||
|
values[fieldKey].length,
|
||||||
|
seed2: i,
|
||||||
|
seed3: forceUpdateKeyCount));
|
||||||
|
subformColumn.add(Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})',
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
GeneratedForm(
|
||||||
|
key: internalFormKey,
|
||||||
|
items: items,
|
||||||
|
onValueChanges: (values, valid, isBuilding) {
|
||||||
|
if (valid) {
|
||||||
|
this.values[fieldKey]?[i] = values;
|
||||||
|
}
|
||||||
|
someValueChanged(
|
||||||
|
isBuilding: isBuilding, forceInvalid: !valid);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton.icon(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor:
|
||||||
|
Theme.of(context).colorScheme.error),
|
||||||
|
onPressed: (values[fieldKey].length > 0)
|
||||||
|
? () {
|
||||||
|
var temp = List.from(values[fieldKey]);
|
||||||
|
temp.removeAt(i);
|
||||||
|
values[fieldKey] = List.from(temp);
|
||||||
|
forceUpdateKeyCount++;
|
||||||
|
someValueChanged();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
label: Text(
|
||||||
|
'${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})',
|
||||||
|
),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.delete_outline_rounded,
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
subformColumn.add(Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: values[fieldKey].length > 0 ? 24 : 0, top: 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
values[fieldKey].add(getDefaultValuesFromFormItems(
|
||||||
|
(widget.items[r][e] as GeneratedFormSubForm)
|
||||||
|
.items));
|
||||||
|
forceUpdateKeyCount++;
|
||||||
|
someValueChanged();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: Text((widget.items[r][e] as GeneratedFormSubForm)
|
||||||
|
.label))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
if (values[fieldKey].length > 0) {
|
||||||
|
subformColumn.add(const Divider());
|
||||||
|
}
|
||||||
|
formInputs[r][e] = Column(children: subformColumn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ class GeneratedFormModal extends StatefulWidget {
|
|||||||
this.initValid = false,
|
this.initValid = false,
|
||||||
this.message = '',
|
this.message = '',
|
||||||
this.additionalWidgets = const [],
|
this.additionalWidgets = const [],
|
||||||
this.singleNullReturnButton});
|
this.singleNullReturnButton,
|
||||||
|
this.primaryActionColour});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final String message;
|
final String message;
|
||||||
@@ -19,6 +20,7 @@ class GeneratedFormModal extends StatefulWidget {
|
|||||||
final bool initValid;
|
final bool initValid;
|
||||||
final List<Widget> additionalWidgets;
|
final List<Widget> additionalWidgets;
|
||||||
final String? singleNullReturnButton;
|
final String? singleNullReturnButton;
|
||||||
|
final Color? primaryActionColour;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<GeneratedFormModal> createState() => _GeneratedFormModalState();
|
State<GeneratedFormModal> createState() => _GeneratedFormModalState();
|
||||||
@@ -71,6 +73,10 @@ class _GeneratedFormModalState extends State<GeneratedFormModal> {
|
|||||||
: widget.singleNullReturnButton!)),
|
: widget.singleNullReturnButton!)),
|
||||||
widget.singleNullReturnButton == null
|
widget.singleNullReturnButton == null
|
||||||
? TextButton(
|
? TextButton(
|
||||||
|
style: widget.primaryActionColour == null
|
||||||
|
? null
|
||||||
|
: TextButton.styleFrom(
|
||||||
|
foregroundColor: widget.primaryActionColour),
|
||||||
onPressed: !valid
|
onPressed: !valid
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:android_package_installer/android_package_installer.dart';
|
import 'package:android_package_installer/android_package_installer.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -32,7 +34,9 @@ class CredsNeededError extends ObtainiumError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class NoReleasesError extends ObtainiumError {
|
class NoReleasesError extends ObtainiumError {
|
||||||
NoReleasesError() : super(tr('noReleaseFound'));
|
NoReleasesError({String? note})
|
||||||
|
: super(
|
||||||
|
'${tr('noReleaseFound')}${note?.isNotEmpty == true ? '\n\n$note' : ''}');
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoAPKError extends ObtainiumError {
|
class NoAPKError extends ObtainiumError {
|
||||||
@@ -65,30 +69,43 @@ class NotImplementedError extends ObtainiumError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MultiAppMultiError extends ObtainiumError {
|
class MultiAppMultiError extends ObtainiumError {
|
||||||
Map<String, List<String>> content = {};
|
Map<String, dynamic> rawErrors = {};
|
||||||
|
Map<String, List<String>> idsByErrorString = {};
|
||||||
|
Map<String, String> appIdNames = {};
|
||||||
|
|
||||||
MultiAppMultiError() : super(tr('placeholder'), unexpected: true);
|
MultiAppMultiError() : super(tr('placeholder'), unexpected: true);
|
||||||
|
|
||||||
add(String appId, String string) {
|
add(String appId, dynamic error, {String? appName}) {
|
||||||
var tempIds = content.remove(string);
|
if (error is SocketException) {
|
||||||
|
error = error.message;
|
||||||
|
}
|
||||||
|
rawErrors[appId] = error;
|
||||||
|
var string = error.toString();
|
||||||
|
var tempIds = idsByErrorString.remove(string);
|
||||||
tempIds ??= [];
|
tempIds ??= [];
|
||||||
tempIds.add(appId);
|
tempIds.add(appId);
|
||||||
content.putIfAbsent(string, () => tempIds!);
|
idsByErrorString.putIfAbsent(string, () => tempIds!);
|
||||||
|
if (appName != null) {
|
||||||
|
appIdNames[appId] = appName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String errorString(String appId, {bool includeIdsWithNames = false}) =>
|
||||||
|
'${appIdNames.containsKey(appId) ? '${appIdNames[appId]}${includeIdsWithNames ? ' ($appId)' : ''}' : appId}: ${rawErrors[appId].toString()}';
|
||||||
|
|
||||||
|
String errorsAppsString(String errString, List<String> appIds,
|
||||||
|
{bool includeIdsWithNames = false}) =>
|
||||||
|
'$errString [${list2FriendlyString(appIds.map((id) => appIdNames.containsKey(id) == true ? '${appIdNames[id]}${includeIdsWithNames ? ' ($id)' : ''}' : id).toList())}]';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() => idsByErrorString.entries
|
||||||
String finalString = '';
|
.map((e) => errorsAppsString(e.key, e.value))
|
||||||
for (var e in content.keys) {
|
.join('\n\n');
|
||||||
finalString += '$e: ${content[e].toString()}\n\n';
|
|
||||||
}
|
|
||||||
return finalString;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showError(dynamic e, BuildContext context) {
|
showMessage(dynamic e, BuildContext context, {bool isError = false}) {
|
||||||
Provider.of<LogsProvider>(context, listen: false)
|
Provider.of<LogsProvider>(context, listen: false)
|
||||||
.add(e.toString(), level: LogLevels.error);
|
.add(e.toString(), level: isError ? LogLevels.error : LogLevels.info);
|
||||||
if (e is String || (e is ObtainiumError && !e.unexpected)) {
|
if (e is String || (e is ObtainiumError && !e.unexpected)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(e.toString())),
|
SnackBar(content: Text(e.toString())),
|
||||||
@@ -100,8 +117,8 @@ showError(dynamic e, BuildContext context) {
|
|||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
title: Text(e is MultiAppMultiError
|
title: Text(e is MultiAppMultiError
|
||||||
? tr('someErrors')
|
? tr(isError ? 'someErrors' : 'updates')
|
||||||
: tr('unexpectedError')),
|
: tr(isError ? 'unexpectedError' : 'unknown')),
|
||||||
content: GestureDetector(
|
content: GestureDetector(
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
Clipboard.setData(ClipboardData(text: e.toString()));
|
Clipboard.setData(ClipboardData(text: e.toString()));
|
||||||
@@ -122,6 +139,10 @@ showError(dynamic e, BuildContext context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showError(dynamic e, BuildContext context) {
|
||||||
|
showMessage(e, context, isError: true);
|
||||||
|
}
|
||||||
|
|
||||||
String list2FriendlyString(List<String> list) {
|
String list2FriendlyString(List<String> list) {
|
||||||
return list.length == 2
|
return list.length == 2
|
||||||
? '${list[0]} ${tr('and')} ${list[1]}'
|
? '${list[0]} ${tr('and')} ${list[1]}'
|
||||||
|
|||||||
188
lib/main.dart
188
lib/main.dart
@@ -1,9 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:obtainium/custom_errors.dart';
|
|
||||||
import 'package:obtainium/pages/home.dart';
|
import 'package:obtainium/pages/home.dart';
|
||||||
import 'package:obtainium/providers/apps_provider.dart';
|
import 'package:obtainium/providers/apps_provider.dart';
|
||||||
import 'package:obtainium/providers/logs_provider.dart';
|
import 'package:obtainium/providers/logs_provider.dart';
|
||||||
@@ -14,14 +12,14 @@ import 'package:permission_handler/permission_handler.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
|
import 'package:background_fetch/background_fetch.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:easy_localization/src/easy_localization_controller.dart';
|
import 'package:easy_localization/src/easy_localization_controller.dart';
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:easy_localization/src/localization.dart';
|
import 'package:easy_localization/src/localization.dart';
|
||||||
|
|
||||||
const String currentVersion = '0.13.21';
|
const String currentVersion = '0.15.6';
|
||||||
const String currentReleaseTag =
|
const String currentReleaseTag =
|
||||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||||
|
|
||||||
@@ -29,7 +27,7 @@ const int bgUpdateCheckAlarmId = 666;
|
|||||||
|
|
||||||
List<MapEntry<Locale, String>> supportedLocales = const [
|
List<MapEntry<Locale, String>> supportedLocales = const [
|
||||||
MapEntry(Locale('en'), 'English'),
|
MapEntry(Locale('en'), 'English'),
|
||||||
MapEntry(Locale('zh'), '汉语'),
|
MapEntry(Locale('zh'), '简体中文'),
|
||||||
MapEntry(Locale('it'), 'Italiano'),
|
MapEntry(Locale('it'), 'Italiano'),
|
||||||
MapEntry(Locale('ja'), '日本語'),
|
MapEntry(Locale('ja'), '日本語'),
|
||||||
MapEntry(Locale('hu'), 'Magyar'),
|
MapEntry(Locale('hu'), 'Magyar'),
|
||||||
@@ -38,11 +36,18 @@ List<MapEntry<Locale, String>> supportedLocales = const [
|
|||||||
MapEntry(Locale('fr'), 'Français'),
|
MapEntry(Locale('fr'), 'Français'),
|
||||||
MapEntry(Locale('es'), 'Español'),
|
MapEntry(Locale('es'), 'Español'),
|
||||||
MapEntry(Locale('pl'), 'Polski'),
|
MapEntry(Locale('pl'), 'Polski'),
|
||||||
MapEntry(Locale('ru'), 'Русский язык'),
|
MapEntry(Locale('ru'), 'Русский'),
|
||||||
MapEntry(Locale('bs'), 'Bosanski'),
|
MapEntry(Locale('bs'), 'Bosanski'),
|
||||||
|
MapEntry(Locale('pt'), 'Brasileiro'),
|
||||||
|
MapEntry(Locale('cs'), 'Česky'),
|
||||||
|
MapEntry(Locale('sv'), 'Svenska'),
|
||||||
|
MapEntry(Locale('nl'), 'Nederlands'),
|
||||||
|
MapEntry(Locale('vi'), 'Tiếng Việt'),
|
||||||
|
MapEntry(Locale('tr'), 'Türkçe'),
|
||||||
];
|
];
|
||||||
const fallbackLocale = Locale('en');
|
const fallbackLocale = Locale('en');
|
||||||
const localeDir = 'assets/translations';
|
const localeDir = 'assets/translations';
|
||||||
|
var fdroid = false;
|
||||||
|
|
||||||
final globalNavigatorKey = GlobalKey<NavigatorState>();
|
final globalNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
@@ -72,81 +77,16 @@ Future<void> loadTranslations() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
void backgroundFetchHeadlessTask(HeadlessTask task) async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
String taskId = task.taskId;
|
||||||
await EasyLocalization.ensureInitialized();
|
bool isTimeout = task.timeout;
|
||||||
|
if (isTimeout) {
|
||||||
await loadTranslations();
|
print('BG update task timed out.');
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
LogsProvider logs = LogsProvider();
|
return;
|
||||||
logs.add(tr('startedBgUpdateTask'));
|
|
||||||
int? ignoreAfterMicroseconds = params?['ignoreAfterMicroseconds'];
|
|
||||||
await AndroidAlarmManager.initialize();
|
|
||||||
DateTime? ignoreAfter = ignoreAfterMicroseconds != null
|
|
||||||
? DateTime.fromMicrosecondsSinceEpoch(ignoreAfterMicroseconds)
|
|
||||||
: null;
|
|
||||||
logs.add(tr('bgUpdateIgnoreAfterIs', args: [ignoreAfter.toString()]));
|
|
||||||
var notificationsProvider = NotificationsProvider();
|
|
||||||
await notificationsProvider.notify(checkingUpdatesNotification);
|
|
||||||
try {
|
|
||||||
var appsProvider = AppsProvider();
|
|
||||||
await notificationsProvider.cancel(ErrorCheckingUpdatesNotification('').id);
|
|
||||||
await appsProvider.loadApps();
|
|
||||||
List<String> existingUpdateIds =
|
|
||||||
appsProvider.findExistingUpdates(installedOnly: true);
|
|
||||||
DateTime nextIgnoreAfter = DateTime.now();
|
|
||||||
String? err;
|
|
||||||
try {
|
|
||||||
logs.add(tr('startedActualBGUpdateCheck'));
|
|
||||||
await appsProvider.checkUpdates(
|
|
||||||
ignoreAppsCheckedAfter: ignoreAfter, throwErrorsForRetry: true);
|
|
||||||
} catch (e) {
|
|
||||||
if (e is RateLimitError || e is SocketException) {
|
|
||||||
var remainingMinutes = e is RateLimitError ? e.remainingMinutes : 15;
|
|
||||||
logs.add(plural('bgUpdateGotErrorRetryInMinutes', remainingMinutes,
|
|
||||||
args: [e.toString(), remainingMinutes.toString()]));
|
|
||||||
AndroidAlarmManager.oneShot(Duration(minutes: remainingMinutes),
|
|
||||||
Random().nextInt(pow(2, 31) as int), bgUpdateCheck, params: {
|
|
||||||
'ignoreAfterMicroseconds': nextIgnoreAfter.microsecondsSinceEpoch
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
err = e.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<App> newUpdates = appsProvider
|
|
||||||
.findExistingUpdates(installedOnly: true)
|
|
||||||
.where((id) => !existingUpdateIds.contains(id))
|
|
||||||
.map((e) => appsProvider.apps[e]!.app)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
// TODO: This silent update code doesn't work yet
|
|
||||||
// List<String> silentlyUpdated = await appsProvider
|
|
||||||
// .downloadAndInstallLatestApp(
|
|
||||||
// [...newUpdates.map((e) => e.id), ...existingUpdateIds], null);
|
|
||||||
// if (silentlyUpdated.isNotEmpty) {
|
|
||||||
// newUpdates = newUpdates
|
|
||||||
// .where((element) => !silentlyUpdated.contains(element.id))
|
|
||||||
// .toList();
|
|
||||||
// notificationsProvider.notify(
|
|
||||||
// SilentUpdateNotification(
|
|
||||||
// silentlyUpdated.map((e) => appsProvider.apps[e]!.app).toList()),
|
|
||||||
// cancelExisting: true);
|
|
||||||
// }
|
|
||||||
logs.add(
|
|
||||||
plural('bgCheckFoundUpdatesWillNotifyIfNeeded', newUpdates.length));
|
|
||||||
if (newUpdates.isNotEmpty) {
|
|
||||||
notificationsProvider.notify(UpdateNotification(newUpdates));
|
|
||||||
}
|
|
||||||
if (err != null) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
notificationsProvider
|
|
||||||
.notify(ErrorCheckingUpdatesNotification(e.toString()));
|
|
||||||
} finally {
|
|
||||||
logs.add(tr('bgUpdateTaskFinished'));
|
|
||||||
await notificationsProvider.cancel(checkingUpdatesNotification.id);
|
|
||||||
}
|
}
|
||||||
|
await bgUpdateCheck(taskId, null);
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
@@ -166,7 +106,6 @@ void main() async {
|
|||||||
);
|
);
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
}
|
}
|
||||||
await AndroidAlarmManager.initialize();
|
|
||||||
runApp(MultiProvider(
|
runApp(MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (context) => AppsProvider()),
|
ChangeNotifierProvider(create: (context) => AppsProvider()),
|
||||||
@@ -181,6 +120,7 @@ void main() async {
|
|||||||
useOnlyLangCode: true,
|
useOnlyLangCode: true,
|
||||||
child: const Obtainium()),
|
child: const Obtainium()),
|
||||||
));
|
));
|
||||||
|
BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultThemeColour = Colors.deepPurple;
|
var defaultThemeColour = Colors.deepPurple;
|
||||||
@@ -195,6 +135,32 @@ class Obtainium extends StatefulWidget {
|
|||||||
class _ObtainiumState extends State<Obtainium> {
|
class _ObtainiumState extends State<Obtainium> {
|
||||||
var existingUpdateInterval = -1;
|
var existingUpdateInterval = -1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
initPlatformState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initPlatformState() async {
|
||||||
|
await BackgroundFetch.configure(
|
||||||
|
BackgroundFetchConfig(
|
||||||
|
minimumFetchInterval: 15,
|
||||||
|
stopOnTerminate: false,
|
||||||
|
enableHeadless: true,
|
||||||
|
requiresBatteryNotLow: false,
|
||||||
|
requiresCharging: false,
|
||||||
|
requiresStorageNotLow: false,
|
||||||
|
requiresDeviceIdle: false,
|
||||||
|
requiredNetworkType: NetworkType.ANY), (String taskId) async {
|
||||||
|
await bgUpdateCheck(taskId, null);
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
|
}, (String taskId) async {
|
||||||
|
context.read<LogsProvider>().add('BG update task timed out.');
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
|
});
|
||||||
|
if (!mounted) return;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
|
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
|
||||||
@@ -206,23 +172,25 @@ class _ObtainiumState extends State<Obtainium> {
|
|||||||
} else {
|
} else {
|
||||||
bool isFirstRun = settingsProvider.checkAndFlipFirstRun();
|
bool isFirstRun = settingsProvider.checkAndFlipFirstRun();
|
||||||
if (isFirstRun) {
|
if (isFirstRun) {
|
||||||
logs.add(tr('firstRun'));
|
logs.add('This is the first ever run of Obtainium.');
|
||||||
// If this is the first run, ask for notification permissions and add Obtainium to the Apps list
|
// If this is the first run, ask for notification permissions and add Obtainium to the Apps list
|
||||||
Permission.notification.request();
|
Permission.notification.request();
|
||||||
appsProvider.saveApps([
|
if (!fdroid) {
|
||||||
App(
|
appsProvider.saveApps([
|
||||||
obtainiumId,
|
App(
|
||||||
'https://github.com/ImranR98/Obtainium',
|
obtainiumId,
|
||||||
'ImranR98',
|
'https://github.com/ImranR98/Obtainium',
|
||||||
'Obtainium',
|
'ImranR98',
|
||||||
currentReleaseTag,
|
'Obtainium',
|
||||||
currentReleaseTag,
|
currentReleaseTag,
|
||||||
[],
|
currentReleaseTag,
|
||||||
0,
|
[],
|
||||||
{'includePrereleases': true},
|
0,
|
||||||
null,
|
{'includePrereleases': true},
|
||||||
false)
|
null,
|
||||||
], onlyIfExists: false);
|
false)
|
||||||
|
], onlyIfExists: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!supportedLocales
|
if (!supportedLocales
|
||||||
.map((e) => e.key.languageCode)
|
.map((e) => e.key.languageCode)
|
||||||
@@ -232,24 +200,6 @@ class _ObtainiumState extends State<Obtainium> {
|
|||||||
context.locale.languageCode)) {
|
context.locale.languageCode)) {
|
||||||
settingsProvider.resetLocaleSafe(context);
|
settingsProvider.resetLocaleSafe(context);
|
||||||
}
|
}
|
||||||
// Register the background update task according to the user's setting
|
|
||||||
if (existingUpdateInterval != settingsProvider.updateInterval) {
|
|
||||||
if (existingUpdateInterval != -1) {
|
|
||||||
logs.add(tr('settingUpdateCheckIntervalTo',
|
|
||||||
args: [settingsProvider.updateInterval.toString()]));
|
|
||||||
}
|
|
||||||
existingUpdateInterval = settingsProvider.updateInterval;
|
|
||||||
if (existingUpdateInterval == 0) {
|
|
||||||
AndroidAlarmManager.cancel(bgUpdateCheckAlarmId);
|
|
||||||
} else {
|
|
||||||
AndroidAlarmManager.periodic(
|
|
||||||
Duration(minutes: existingUpdateInterval),
|
|
||||||
bgUpdateCheckAlarmId,
|
|
||||||
bgUpdateCheck,
|
|
||||||
rescheduleOnReboot: true,
|
|
||||||
wakeup: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DynamicColorBuilder(
|
return DynamicColorBuilder(
|
||||||
@@ -286,14 +236,18 @@ class _ObtainiumState extends State<Obtainium> {
|
|||||||
colorScheme: settingsProvider.theme == ThemeSettings.dark
|
colorScheme: settingsProvider.theme == ThemeSettings.dark
|
||||||
? darkColorScheme
|
? darkColorScheme
|
||||||
: lightColorScheme,
|
: lightColorScheme,
|
||||||
fontFamily: 'Metropolis'),
|
fontFamily:
|
||||||
|
settingsProvider.useSystemFont ? 'SystemFont' : 'Metropolis'),
|
||||||
darkTheme: ThemeData(
|
darkTheme: ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
colorScheme: settingsProvider.theme == ThemeSettings.light
|
colorScheme: settingsProvider.theme == ThemeSettings.light
|
||||||
? lightColorScheme
|
? lightColorScheme
|
||||||
: darkColorScheme,
|
: darkColorScheme,
|
||||||
fontFamily: 'Metropolis'),
|
fontFamily:
|
||||||
home: const HomePage());
|
settingsProvider.useSystemFont ? 'SystemFont' : 'Metropolis'),
|
||||||
|
home: Shortcuts(shortcuts: <LogicalKeySet, Intent>{
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
|
||||||
|
}, child: const HomePage()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
lib/main_fdroid.dart
Normal file
6
lib/main_fdroid.dart
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import 'main.dart' as m;
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
m.fdroid = true;
|
||||||
|
m.main();
|
||||||
|
}
|
||||||
@@ -15,8 +15,10 @@ class GitHubStars implements MassAppUrlSource {
|
|||||||
|
|
||||||
Future<Map<String, List<String>>> getOnePageOfUserStarredUrlsWithDescriptions(
|
Future<Map<String, List<String>>> getOnePageOfUserStarredUrlsWithDescriptions(
|
||||||
String username, int page) async {
|
String username, int page) async {
|
||||||
Response res = await get(Uri.parse(
|
Response res = await get(
|
||||||
'https://${await GitHub().getCredentialPrefixIfAny()}api.github.com/users/$username/starred?per_page=100&page=$page'));
|
Uri.parse(
|
||||||
|
'https://api.github.com/users/$username/starred?per_page=100&page=$page'),
|
||||||
|
headers: await GitHub().getRequestHeaders());
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
Map<String, List<String>> urlsWithDescriptions = {};
|
Map<String, List<String>> urlsWithDescriptions = {};
|
||||||
for (var e in (jsonDecode(res.body) as List<dynamic>)) {
|
for (var e in (jsonDecode(res.body) as List<dynamic>)) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'package:obtainium/pages/app.dart';
|
|||||||
import 'package:obtainium/pages/import_export.dart';
|
import 'package:obtainium/pages/import_export.dart';
|
||||||
import 'package:obtainium/pages/settings.dart';
|
import 'package:obtainium/pages/settings.dart';
|
||||||
import 'package:obtainium/providers/apps_provider.dart';
|
import 'package:obtainium/providers/apps_provider.dart';
|
||||||
|
import 'package:obtainium/providers/notifications_provider.dart';
|
||||||
import 'package:obtainium/providers/settings_provider.dart';
|
import 'package:obtainium/providers/settings_provider.dart';
|
||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@@ -20,10 +21,10 @@ class AddAppPage extends StatefulWidget {
|
|||||||
const AddAppPage({super.key});
|
const AddAppPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AddAppPage> createState() => _AddAppPageState();
|
State<AddAppPage> createState() => AddAppPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AddAppPageState extends State<AddAppPage> {
|
class AddAppPageState extends State<AddAppPage> {
|
||||||
bool gettingAppInfo = false;
|
bool gettingAppInfo = false;
|
||||||
bool searching = false;
|
bool searching = false;
|
||||||
|
|
||||||
@@ -35,58 +36,72 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
bool additionalSettingsValid = true;
|
bool additionalSettingsValid = true;
|
||||||
bool inferAppIdIfOptional = true;
|
bool inferAppIdIfOptional = true;
|
||||||
List<String> pickedCategories = [];
|
List<String> pickedCategories = [];
|
||||||
int searchnum = 0;
|
int urlInputKey = 0;
|
||||||
SourceProvider sourceProvider = SourceProvider();
|
SourceProvider sourceProvider = SourceProvider();
|
||||||
|
|
||||||
|
linkFn(String input) {
|
||||||
|
try {
|
||||||
|
if (input.isEmpty) {
|
||||||
|
throw UnsupportedURLError();
|
||||||
|
}
|
||||||
|
sourceProvider.getSource(input);
|
||||||
|
changeUserInput(input, true, false, updateUrlInput: true);
|
||||||
|
} catch (e) {
|
||||||
|
showError(e, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeUserInput(String input, bool valid, bool isBuilding,
|
||||||
|
{bool updateUrlInput = false}) {
|
||||||
|
userInput = input;
|
||||||
|
if (!isBuilding) {
|
||||||
|
setState(() {
|
||||||
|
if (updateUrlInput) {
|
||||||
|
urlInputKey++;
|
||||||
|
}
|
||||||
|
var prevHost = pickedSource?.host;
|
||||||
|
try {
|
||||||
|
var naturalSource =
|
||||||
|
valid ? sourceProvider.getSource(userInput) : null;
|
||||||
|
if (naturalSource != null &&
|
||||||
|
naturalSource.runtimeType.toString() !=
|
||||||
|
HTML().runtimeType.toString()) {
|
||||||
|
// If input has changed to match a regular source, reset the override
|
||||||
|
pickedSourceOverride = null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
var source = valid
|
||||||
|
? sourceProvider.getSource(userInput,
|
||||||
|
overrideSource: pickedSourceOverride)
|
||||||
|
: null;
|
||||||
|
if (pickedSource.runtimeType != source.runtimeType ||
|
||||||
|
(prevHost != null && prevHost != source?.host)) {
|
||||||
|
pickedSource = source;
|
||||||
|
additionalSettings = source != null
|
||||||
|
? getDefaultValuesFromFormItems(
|
||||||
|
source.combinedAppSpecificSettingFormItems)
|
||||||
|
: {};
|
||||||
|
additionalSettingsValid = source != null
|
||||||
|
? !sourceProvider.ifRequiredAppSpecificSettingsExist(source)
|
||||||
|
: true;
|
||||||
|
inferAppIdIfOptional = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
AppsProvider appsProvider = context.read<AppsProvider>();
|
AppsProvider appsProvider = context.read<AppsProvider>();
|
||||||
|
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
|
||||||
|
NotificationsProvider notificationsProvider =
|
||||||
|
context.read<NotificationsProvider>();
|
||||||
|
|
||||||
bool doingSomething = gettingAppInfo || searching;
|
bool doingSomething = gettingAppInfo || searching;
|
||||||
|
|
||||||
changeUserInput(String input, bool valid, bool isBuilding,
|
Future<bool> getTrackOnlyConfirmationIfNeeded(bool userPickedTrackOnly,
|
||||||
{bool isSearch = false}) {
|
|
||||||
userInput = input;
|
|
||||||
if (!isBuilding) {
|
|
||||||
setState(() {
|
|
||||||
if (isSearch) {
|
|
||||||
searchnum++;
|
|
||||||
}
|
|
||||||
var prevHost = pickedSource?.host;
|
|
||||||
try {
|
|
||||||
var naturalSource =
|
|
||||||
valid ? sourceProvider.getSource(userInput) : null;
|
|
||||||
if (naturalSource != null &&
|
|
||||||
naturalSource.runtimeType.toString() !=
|
|
||||||
HTML().runtimeType.toString()) {
|
|
||||||
// If input has changed to match a regular source, reset the override
|
|
||||||
pickedSourceOverride = null;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
var source = valid
|
|
||||||
? sourceProvider.getSource(userInput,
|
|
||||||
overrideSource: pickedSourceOverride)
|
|
||||||
: null;
|
|
||||||
if (pickedSource.runtimeType != source.runtimeType ||
|
|
||||||
(prevHost != null && prevHost != source?.host)) {
|
|
||||||
pickedSource = source;
|
|
||||||
additionalSettings = source != null
|
|
||||||
? getDefaultValuesFromFormItems(
|
|
||||||
source.combinedAppSpecificSettingFormItems)
|
|
||||||
: {};
|
|
||||||
additionalSettingsValid = source != null
|
|
||||||
? !sourceProvider.ifRequiredAppSpecificSettingsExist(source)
|
|
||||||
: true;
|
|
||||||
inferAppIdIfOptional = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> getTrackOnlyConfirmationIfNeeded(
|
|
||||||
bool userPickedTrackOnly, SettingsProvider settingsProvider,
|
|
||||||
{bool ignoreHideSetting = false}) async {
|
{bool ignoreHideSetting = false}) async {
|
||||||
var useTrackOnly = userPickedTrackOnly || pickedSource!.enforceTrackOnly;
|
var useTrackOnly = userPickedTrackOnly || pickedSource!.enforceTrackOnly;
|
||||||
if (useTrackOnly &&
|
if (useTrackOnly &&
|
||||||
@@ -138,22 +153,19 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
gettingAppInfo = true;
|
gettingAppInfo = true;
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
var settingsProvider = context.read<SettingsProvider>();
|
|
||||||
var userPickedTrackOnly = additionalSettings['trackOnly'] == true;
|
var userPickedTrackOnly = additionalSettings['trackOnly'] == true;
|
||||||
App? app;
|
App? app;
|
||||||
if ((await getTrackOnlyConfirmationIfNeeded(
|
if ((await getTrackOnlyConfirmationIfNeeded(userPickedTrackOnly)) &&
|
||||||
userPickedTrackOnly, settingsProvider)) &&
|
|
||||||
(await getReleaseDateAsVersionConfirmationIfNeeded(
|
(await getReleaseDateAsVersionConfirmationIfNeeded(
|
||||||
userPickedTrackOnly))) {
|
userPickedTrackOnly))) {
|
||||||
var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly;
|
var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly;
|
||||||
app = await sourceProvider.getApp(
|
app = await sourceProvider.getApp(
|
||||||
pickedSource!, userInput, additionalSettings,
|
pickedSource!, userInput.trim(), additionalSettings,
|
||||||
trackOnlyOverride: trackOnly,
|
trackOnlyOverride: trackOnly,
|
||||||
overrideSource: pickedSourceOverride,
|
overrideSource: pickedSourceOverride,
|
||||||
inferAppIdIfOptional: inferAppIdIfOptional);
|
inferAppIdIfOptional: inferAppIdIfOptional);
|
||||||
// Only download the APK here if you need to for the package ID
|
// Only download the APK here if you need to for the package ID
|
||||||
if (sourceProvider.isTempId(app) &&
|
if (isTempId(app) && app.additionalSettings['trackOnly'] != true) {
|
||||||
app.additionalSettings['trackOnly'] != true) {
|
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
var apkUrl = await appsProvider.confirmApkUrl(app, context);
|
var apkUrl = await appsProvider.confirmApkUrl(app, context);
|
||||||
if (apkUrl == null) {
|
if (apkUrl == null) {
|
||||||
@@ -163,7 +175,8 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
app.apkUrls.map((e) => e.value).toList().indexOf(apkUrl.value);
|
app.apkUrls.map((e) => e.value).toList().indexOf(apkUrl.value);
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
var downloadedArtifact = await appsProvider.downloadApp(
|
var downloadedArtifact = await appsProvider.downloadApp(
|
||||||
app, globalNavigatorKey.currentContext);
|
app, globalNavigatorKey.currentContext,
|
||||||
|
notificationsProvider: notificationsProvider);
|
||||||
DownloadedApk? downloadedFile;
|
DownloadedApk? downloadedFile;
|
||||||
DownloadedXApkDir? downloadedDir;
|
DownloadedXApkDir? downloadedDir;
|
||||||
if (downloadedArtifact is DownloadedApk) {
|
if (downloadedArtifact is DownloadedApk) {
|
||||||
@@ -204,7 +217,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GeneratedForm(
|
child: GeneratedForm(
|
||||||
key: Key(searchnum.toString()),
|
key: Key(urlInputKey.toString()),
|
||||||
items: [
|
items: [
|
||||||
[
|
[
|
||||||
GeneratedFormTextField('appSourceURL',
|
GeneratedFormTextField('appSourceURL',
|
||||||
@@ -253,56 +266,82 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
runSearch() async {
|
runSearch({bool filtered = true}) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
searching = true;
|
searching = true;
|
||||||
});
|
});
|
||||||
|
var sourceStrings = <String, List<String>>{};
|
||||||
|
sourceProvider.sources
|
||||||
|
.where((e) => e.canSearch && !e.excludeFromMassSearch)
|
||||||
|
.forEach((s) {
|
||||||
|
sourceStrings[s.name] = [s.name];
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
var results = await Future.wait(
|
var searchSources = await showDialog<List<String>?>(
|
||||||
sourceProvider.sources.where((e) => e.canSearch).map((e) async {
|
|
||||||
try {
|
|
||||||
return await e.search(searchQuery);
|
|
||||||
} catch (err) {
|
|
||||||
if (err is! CredsNeededError) {
|
|
||||||
rethrow;
|
|
||||||
} else {
|
|
||||||
return <String, List<String>>{};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
// .then((results) async {
|
|
||||||
// Interleave results instead of simple reduce
|
|
||||||
Map<String, List<String>> res = {};
|
|
||||||
var si = 0;
|
|
||||||
var done = false;
|
|
||||||
while (!done) {
|
|
||||||
done = true;
|
|
||||||
for (var r in results) {
|
|
||||||
if (r.length > si) {
|
|
||||||
done = false;
|
|
||||||
res.addEntries([r.entries.elementAt(si)]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
si++;
|
|
||||||
}
|
|
||||||
if (res.isEmpty) {
|
|
||||||
throw ObtainiumError(tr('noResults'));
|
|
||||||
}
|
|
||||||
List<String>? selectedUrls = res.isEmpty
|
|
||||||
? []
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
: await showDialog<List<String>?>(
|
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext ctx) {
|
builder: (BuildContext ctx) {
|
||||||
return UrlSelectionModal(
|
return SelectionModal(
|
||||||
urlsWithDescriptions: res,
|
title: tr('selectX', args: [plural('source', 2)]),
|
||||||
selectedByDefault: false,
|
entries: sourceStrings,
|
||||||
onlyOneSelectionAllowed: true,
|
selectedByDefault: true,
|
||||||
|
onlyOneSelectionAllowed: false,
|
||||||
|
titlesAreLinks: false,
|
||||||
|
deselectThese: settingsProvider.searchDeselected,
|
||||||
);
|
);
|
||||||
});
|
}) ??
|
||||||
if (selectedUrls != null && selectedUrls.isNotEmpty) {
|
[];
|
||||||
changeUserInput(selectedUrls[0], true, false, isSearch: true);
|
if (searchSources.isNotEmpty) {
|
||||||
|
settingsProvider.searchDeselected = sourceStrings.keys
|
||||||
|
.where((s) => !searchSources.contains(s))
|
||||||
|
.toList();
|
||||||
|
var results = await Future.wait(sourceProvider.sources
|
||||||
|
.where((e) => searchSources.contains(e.name))
|
||||||
|
.map((e) async {
|
||||||
|
try {
|
||||||
|
return await e.search(searchQuery);
|
||||||
|
} catch (err) {
|
||||||
|
if (err is! CredsNeededError) {
|
||||||
|
rethrow;
|
||||||
|
} else {
|
||||||
|
err.unexpected = true;
|
||||||
|
showError(err, context);
|
||||||
|
return <String, List<String>>{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Interleave results instead of simple reduce
|
||||||
|
Map<String, List<String>> res = {};
|
||||||
|
var si = 0;
|
||||||
|
var done = false;
|
||||||
|
while (!done) {
|
||||||
|
done = true;
|
||||||
|
for (var r in results) {
|
||||||
|
if (r.length > si) {
|
||||||
|
done = false;
|
||||||
|
res.addEntries([r.entries.elementAt(si)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
si++;
|
||||||
|
}
|
||||||
|
if (res.isEmpty) {
|
||||||
|
throw ObtainiumError(tr('noResults'));
|
||||||
|
}
|
||||||
|
List<String>? selectedUrls = res.isEmpty
|
||||||
|
? []
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
: await showDialog<List<String>?>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext ctx) {
|
||||||
|
return SelectionModal(
|
||||||
|
entries: res,
|
||||||
|
selectedByDefault: false,
|
||||||
|
onlyOneSelectionAllowed: true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (selectedUrls != null && selectedUrls.isNotEmpty) {
|
||||||
|
changeUserInput(selectedUrls[0], true, false, updateUrlInput: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showError(e, context);
|
showError(e, context);
|
||||||
@@ -410,7 +449,13 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
),
|
),
|
||||||
GeneratedForm(
|
GeneratedForm(
|
||||||
key: Key(pickedSource.runtimeType.toString()),
|
key: Key(pickedSource.runtimeType.toString()),
|
||||||
items: pickedSource!.combinedAppSpecificSettingFormItems,
|
items: [
|
||||||
|
...pickedSource!.combinedAppSpecificSettingFormItems,
|
||||||
|
...(pickedSourceOverride != null
|
||||||
|
? pickedSource!.sourceConfigSettingFormItems
|
||||||
|
.map((e) => [e])
|
||||||
|
: [])
|
||||||
|
],
|
||||||
onValueChanges: (values, valid, isBuilding) {
|
onValueChanges: (values, valid, isBuilding) {
|
||||||
if (!isBuilding) {
|
if (!isBuilding) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -455,32 +500,28 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(
|
|
||||||
height: 48,
|
|
||||||
),
|
|
||||||
Text(
|
Text(
|
||||||
tr('supportedSourcesBelow'),
|
tr('supportedSources'),
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 16,
|
||||||
),
|
),
|
||||||
...sourceProvider.sources
|
...sourceProvider.sources.map((e) => GestureDetector(
|
||||||
.map((e) => GestureDetector(
|
onTap: e.host != null
|
||||||
onTap: e.host != null
|
? () {
|
||||||
? () {
|
launchUrlString('https://${e.host}',
|
||||||
launchUrlString('https://${e.host}',
|
mode: LaunchMode.externalApplication);
|
||||||
mode: LaunchMode.externalApplication);
|
}
|
||||||
}
|
: null,
|
||||||
: null,
|
child: Text(
|
||||||
child: Text(
|
'${e.name}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
|
||||||
'${e.name}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
|
style: TextStyle(
|
||||||
style: TextStyle(
|
decoration: e.host != null
|
||||||
decoration: e.host != null
|
? TextDecoration.underline
|
||||||
? TextDecoration.underline
|
: TextDecoration.none,
|
||||||
: TextDecoration.none,
|
fontStyle: FontStyle.italic),
|
||||||
fontStyle: FontStyle.italic),
|
)))
|
||||||
)))
|
|
||||||
.toList()
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -504,15 +545,29 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
HTML().runtimeType.toString()))
|
HTML().runtimeType.toString()))
|
||||||
getHTMLSourceOverrideDropdown(),
|
getHTMLSourceOverrideDropdown(),
|
||||||
if (shouldShowSearchBar()) getSearchBarRow(),
|
if (shouldShowSearchBar()) getSearchBarRow(),
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
if (pickedSource != null)
|
if (pickedSource != null)
|
||||||
getAdditionalOptsCol()
|
FutureBuilder(
|
||||||
else
|
builder: (ctx, val) {
|
||||||
getSourcesListWidget(),
|
return val.data != null && val.data!.isNotEmpty
|
||||||
const SizedBox(
|
? Text(
|
||||||
height: 8,
|
val.data!,
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.bodySmall,
|
||||||
|
)
|
||||||
|
: const SizedBox();
|
||||||
|
},
|
||||||
|
future: pickedSource?.getSourceNote()),
|
||||||
|
SizedBox(
|
||||||
|
height: pickedSource != null ? 16 : 96,
|
||||||
|
),
|
||||||
|
if (pickedSource != null) getAdditionalOptsCol(),
|
||||||
|
if (pickedSource == null)
|
||||||
|
const Divider(
|
||||||
|
height: 48,
|
||||||
|
),
|
||||||
|
if (pickedSource == null) getSourcesListWidget(),
|
||||||
|
SizedBox(
|
||||||
|
height: pickedSource != null ? 8 : 2,
|
||||||
),
|
),
|
||||||
])),
|
])),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -145,6 +145,29 @@ class _AppPageState extends State<AppPage> {
|
|||||||
appsProvider.saveApps([app.app]);
|
appsProvider.saveApps([app.app]);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
if (app?.app.additionalSettings['about'] is String &&
|
||||||
|
app?.app.additionalSettings['about'].isNotEmpty)
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 48,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onLongPress: () {
|
||||||
|
Clipboard.setData(ClipboardData(
|
||||||
|
text: app?.app.additionalSettings['about'] ?? ''));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
|
content: Text(tr('copiedToClipboard')),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
app?.app.additionalSettings['about'],
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -152,13 +175,16 @@ class _AppPageState extends State<AppPage> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 125),
|
const SizedBox(height: 20),
|
||||||
app?.installedInfo != null
|
app?.icon != null
|
||||||
? Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
? Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
Image.memory(
|
GestureDetector(
|
||||||
app!.installedInfo!.icon!,
|
child: Image.memory(
|
||||||
height: 150,
|
app!.icon!,
|
||||||
gaplessPlayback: true,
|
height: 150,
|
||||||
|
gaplessPlayback: true,
|
||||||
|
),
|
||||||
|
onTap: () => pm.openApp(app.app.id),
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
: Container(),
|
: Container(),
|
||||||
@@ -292,7 +318,7 @@ class _AppPageState extends State<AppPage> {
|
|||||||
if (source?.enforceTrackOnly == true) {
|
if (source?.enforceTrackOnly == true) {
|
||||||
app.app.additionalSettings['trackOnly'] = true;
|
app.app.additionalSettings['trackOnly'] = true;
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
showError(tr('appsFromSourceAreTrackOnly'), context);
|
showMessage(tr('appsFromSourceAreTrackOnly'), context);
|
||||||
}
|
}
|
||||||
if (app.app.additionalSettings['versionDetection'] ==
|
if (app.app.additionalSettings['versionDetection'] ==
|
||||||
'releaseDateAsVersion') {
|
'releaseDateAsVersion') {
|
||||||
@@ -338,12 +364,18 @@ class _AppPageState extends State<AppPage> {
|
|||||||
try {
|
try {
|
||||||
HapticFeedback.heavyImpact();
|
HapticFeedback.heavyImpact();
|
||||||
var res = await appsProvider.downloadAndInstallLatestApps(
|
var res = await appsProvider.downloadAndInstallLatestApps(
|
||||||
app?.app.id != null ? [app!.app.id] : [],
|
app?.app.id != null ? [app!.app.id] : [],
|
||||||
globalNavigatorKey.currentContext);
|
globalNavigatorKey.currentContext,
|
||||||
|
);
|
||||||
|
if (app?.app.installedVersion != null && !trackOnly) {
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
showMessage(tr('appsUpdated'), context);
|
||||||
|
}
|
||||||
if (res.isNotEmpty && mounted) {
|
if (res.isNotEmpty && mounted) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
showError(e, context);
|
showError(e, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,8 +463,7 @@ class _AppPageState extends State<AppPage> {
|
|||||||
? getResetInstallStatusButton()
|
? getResetInstallStatusButton()
|
||||||
: getInstallOrUpdateButton()),
|
: getInstallOrUpdateButton()),
|
||||||
const SizedBox(width: 16.0),
|
const SizedBox(width: 16.0),
|
||||||
Expanded(
|
IconButton(
|
||||||
child: TextButton(
|
|
||||||
onPressed: app?.downloadProgress != null
|
onPressed: app?.downloadProgress != null
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
@@ -445,13 +476,9 @@ class _AppPageState extends State<AppPage> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(
|
tooltip: tr('remove'),
|
||||||
foregroundColor:
|
icon: const Icon(Icons.delete_outline),
|
||||||
Theme.of(context).colorScheme.error,
|
),
|
||||||
surfaceTintColor:
|
|
||||||
Theme.of(context).colorScheme.error),
|
|
||||||
child: Text(tr('remove')),
|
|
||||||
)),
|
|
||||||
])),
|
])),
|
||||||
if (app?.downloadProgress != null)
|
if (app?.downloadProgress != null)
|
||||||
Padding(
|
Padding(
|
||||||
@@ -463,8 +490,17 @@ class _AppPageState extends State<AppPage> {
|
|||||||
],
|
],
|
||||||
));
|
));
|
||||||
|
|
||||||
|
appScreenAppBar() => AppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: settingsProvider.showAppWebpage ? AppBar() : null,
|
appBar: settingsProvider.showAppWebpage ? AppBar() : appScreenAppBar(),
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
child: settingsProvider.showAppWebpage
|
child: settingsProvider.showAppWebpage
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
refreshingSince = DateTime.now();
|
refreshingSince = DateTime.now();
|
||||||
});
|
});
|
||||||
return appsProvider.checkUpdates().catchError((e) {
|
return appsProvider.checkUpdates().catchError((e) {
|
||||||
showError(e, context);
|
showError(e is Map ? e['errors'] : e, context);
|
||||||
return <App>[];
|
return <App>[];
|
||||||
}).whenComplete(() {
|
}).whenComplete(() {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -393,9 +393,9 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAppIcon(int appIndex) {
|
getAppIcon(int appIndex) {
|
||||||
return listedApps[appIndex].installedInfo != null
|
return listedApps[appIndex].icon != null
|
||||||
? Image.memory(
|
? Image.memory(
|
||||||
listedApps[appIndex].installedInfo!.icon!,
|
listedApps[appIndex].icon!,
|
||||||
gaplessPlayback: true,
|
gaplessPlayback: true,
|
||||||
)
|
)
|
||||||
: Row(
|
: Row(
|
||||||
@@ -448,47 +448,56 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: showChangesFn,
|
onTap: showChangesFn,
|
||||||
child: Column(
|
child: Container(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
decoration: BoxDecoration(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
borderRadius: BorderRadius.circular(12),
|
||||||
children: [
|
color: settingsProvider.highlightTouchTargets &&
|
||||||
Row(mainAxisSize: MainAxisSize.min, children: [
|
showChangesFn != null
|
||||||
Container(
|
? (Theme.of(context).brightness == Brightness.light
|
||||||
constraints: BoxConstraints(
|
? Theme.of(context).primaryColor
|
||||||
maxWidth: MediaQuery.of(context).size.width / 4),
|
: Theme.of(context).primaryColorLight)
|
||||||
child: Text(getVersionText(index),
|
.withAlpha(20)
|
||||||
overflow: TextOverflow.ellipsis,
|
: null),
|
||||||
textAlign: TextAlign.end)),
|
padding: settingsProvider.highlightTouchTargets
|
||||||
]),
|
? const EdgeInsetsDirectional.fromSTEB(12, 0, 12, 0)
|
||||||
Row(
|
: const EdgeInsetsDirectional.fromSTEB(24, 0, 0, 0),
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
getChangesButtonString(index, showChangesFn != null),
|
Container(
|
||||||
style: TextStyle(
|
constraints: BoxConstraints(
|
||||||
fontStyle: FontStyle.italic,
|
maxWidth:
|
||||||
decoration: showChangesFn != null
|
MediaQuery.of(context).size.width / 4),
|
||||||
? TextDecoration.underline
|
child: Text(getVersionText(index),
|
||||||
: TextDecoration.none),
|
overflow: TextOverflow.ellipsis,
|
||||||
)
|
textAlign: TextAlign.end)),
|
||||||
|
]),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
getChangesButtonString(
|
||||||
|
index, showChangesFn != null),
|
||||||
|
style: TextStyle(
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
decoration: showChangesFn != null
|
||||||
|
? TextDecoration.underline
|
||||||
|
: TextDecoration.none),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
)))
|
||||||
],
|
|
||||||
))
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
var transparent =
|
var transparent =
|
||||||
Theme.of(context).colorScheme.background.withAlpha(0).value;
|
Theme.of(context).colorScheme.background.withAlpha(0).value;
|
||||||
List<double> stops = [
|
List<double> stops = [
|
||||||
...listedApps[index]
|
...listedApps[index].app.categories.asMap().entries.map(
|
||||||
.app
|
(e) => ((e.key / (listedApps[index].app.categories.length - 1)))),
|
||||||
.categories
|
|
||||||
.asMap()
|
|
||||||
.entries
|
|
||||||
.map((e) =>
|
|
||||||
((e.key / (listedApps[index].app.categories.length - 1))))
|
|
||||||
.toList(),
|
|
||||||
1
|
1
|
||||||
];
|
];
|
||||||
if (stops.length == 2) {
|
if (stops.length == 2) {
|
||||||
@@ -501,13 +510,9 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
begin: const Alignment(-1, 0),
|
begin: const Alignment(-1, 0),
|
||||||
end: const Alignment(-0.97, 0),
|
end: const Alignment(-0.97, 0),
|
||||||
colors: [
|
colors: [
|
||||||
...listedApps[index]
|
...listedApps[index].app.categories.map((e) =>
|
||||||
.app
|
Color(settingsProvider.categories[e] ?? transparent)
|
||||||
.categories
|
.withAlpha(255)),
|
||||||
.map((e) =>
|
|
||||||
Color(settingsProvider.categories[e] ?? transparent)
|
|
||||||
.withAlpha(255))
|
|
||||||
.toList(),
|
|
||||||
Color(transparent)
|
Color(transparent)
|
||||||
])),
|
])),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
@@ -543,20 +548,19 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
: FontWeight.normal)),
|
: FontWeight.normal)),
|
||||||
trailing: listedApps[index].downloadProgress != null
|
trailing: listedApps[index].downloadProgress != null
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
width: 90,
|
|
||||||
child: Text(
|
child: Text(
|
||||||
listedApps[index].downloadProgress! >= 0
|
listedApps[index].downloadProgress! >= 0
|
||||||
? tr('percentProgress', args: [
|
? tr('percentProgress', args: [
|
||||||
listedApps[index]
|
listedApps[index]
|
||||||
.downloadProgress!
|
.downloadProgress!
|
||||||
.toInt()
|
.toInt()
|
||||||
.toString()
|
.toString()
|
||||||
])
|
])
|
||||||
: tr('pleaseWait'),
|
: tr('installing'),
|
||||||
textAlign: (listedApps[index].downloadProgress! >= 0)
|
textAlign: (listedApps[index].downloadProgress! >= 0)
|
||||||
? TextAlign.start
|
? TextAlign.start
|
||||||
: TextAlign.end,
|
: TextAlign.end,
|
||||||
))
|
))
|
||||||
: trailingRow,
|
: trailingRow,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (selectedAppIds.isNotEmpty) {
|
if (selectedAppIds.isNotEmpty) {
|
||||||
@@ -644,15 +648,15 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
label: tr('installX', args: [
|
label: tr('installX', args: [
|
||||||
plural('apps', newInstallIdsAllOrSelected.length)
|
plural('apps', newInstallIdsAllOrSelected.length)
|
||||||
]),
|
]),
|
||||||
defaultValue: existingUpdateIdsAllOrSelected.isNotEmpty));
|
defaultValue: existingUpdateIdsAllOrSelected.isEmpty));
|
||||||
}
|
}
|
||||||
if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) {
|
if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) {
|
||||||
formItems.add(GeneratedFormSwitch('trackonlies',
|
formItems.add(GeneratedFormSwitch('trackonlies',
|
||||||
label: tr('markXTrackOnlyAsUpdated', args: [
|
label: tr('markXTrackOnlyAsUpdated', args: [
|
||||||
plural('apps', trackOnlyUpdateIdsAllOrSelected.length)
|
plural('apps', trackOnlyUpdateIdsAllOrSelected.length)
|
||||||
]),
|
]),
|
||||||
defaultValue: existingUpdateIdsAllOrSelected.isNotEmpty ||
|
defaultValue: existingUpdateIdsAllOrSelected.isEmpty &&
|
||||||
newInstallIdsAllOrSelected.isNotEmpty));
|
newInstallIdsAllOrSelected.isEmpty));
|
||||||
}
|
}
|
||||||
showDialog<Map<String, dynamic>?>(
|
showDialog<Map<String, dynamic>?>(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -685,11 +689,14 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
}
|
}
|
||||||
appsProvider
|
appsProvider
|
||||||
.downloadAndInstallLatestApps(
|
.downloadAndInstallLatestApps(
|
||||||
toInstall, globalNavigatorKey.currentContext,
|
toInstall, globalNavigatorKey.currentContext)
|
||||||
settingsProvider: settingsProvider)
|
|
||||||
.catchError((e) {
|
.catchError((e) {
|
||||||
showError(e, context);
|
showError(e, context);
|
||||||
return <String>[];
|
return <String>[];
|
||||||
|
}).then((value) {
|
||||||
|
if (shouldInstallUpdates) {
|
||||||
|
showMessage(tr('appsUpdated'), context);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -816,7 +823,7 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
items: const [],
|
items: const [],
|
||||||
initValid: true,
|
initValid: true,
|
||||||
message: tr('installStatusOfXWillBeResetExplanation',
|
message: tr('installStatusOfXWillBeResetExplanation',
|
||||||
args: [plural('app', selectedAppIds.length)]),
|
args: [plural('apps', selectedAppIds.length)]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (values != null) {
|
if (values != null) {
|
||||||
@@ -864,7 +871,7 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
String urls = '';
|
String urls = '';
|
||||||
for (var a in selectedApps) {
|
for (var a in selectedApps) {
|
||||||
urls += '${a.url}\n';
|
urls += 'obtainium://add/${a.url}\n';
|
||||||
}
|
}
|
||||||
urls = urls.substring(0, urls.length - 1);
|
urls = urls.substring(0, urls.length - 1);
|
||||||
Share.share(urls,
|
Share.share(urls,
|
||||||
@@ -964,10 +971,8 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
defaultValue: filter.sourceFilter,
|
defaultValue: filter.sourceFilter,
|
||||||
[
|
[
|
||||||
MapEntry('', tr('none')),
|
MapEntry('', tr('none')),
|
||||||
...sourceProvider.sources
|
...sourceProvider.sources.map(
|
||||||
.map((e) =>
|
(e) => MapEntry(e.runtimeType.toString(), e.name))
|
||||||
MapEntry(e.runtimeType.toString(), e.name))
|
|
||||||
.toList()
|
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
|
import 'package:app_links/app_links.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:obtainium/components/generated_form_modal.dart';
|
||||||
|
import 'package:obtainium/custom_errors.dart';
|
||||||
import 'package:obtainium/pages/add_app.dart';
|
import 'package:obtainium/pages/add_app.dart';
|
||||||
import 'package:obtainium/pages/apps.dart';
|
import 'package:obtainium/pages/apps.dart';
|
||||||
import 'package:obtainium/pages/import_export.dart';
|
import 'package:obtainium/pages/import_export.dart';
|
||||||
@@ -30,58 +35,144 @@ class _HomePageState extends State<HomePage> {
|
|||||||
bool isReversing = false;
|
bool isReversing = false;
|
||||||
int prevAppCount = -1;
|
int prevAppCount = -1;
|
||||||
bool prevIsLoading = true;
|
bool prevIsLoading = true;
|
||||||
|
late AppLinks _appLinks;
|
||||||
|
StreamSubscription<Uri>? _linkSubscription;
|
||||||
|
bool isLinkActivity = false;
|
||||||
|
|
||||||
List<NavigationPageItem> pages = [
|
List<NavigationPageItem> pages = [
|
||||||
NavigationPageItem(tr('appsString'), Icons.apps,
|
NavigationPageItem(tr('appsString'), Icons.apps,
|
||||||
AppsPage(key: GlobalKey<AppsPageState>())),
|
AppsPage(key: GlobalKey<AppsPageState>())),
|
||||||
NavigationPageItem(tr('addApp'), Icons.add, const AddAppPage()),
|
NavigationPageItem(
|
||||||
|
tr('addApp'), Icons.add, AddAppPage(key: GlobalKey<AddAppPageState>())),
|
||||||
NavigationPageItem(
|
NavigationPageItem(
|
||||||
tr('importExport'), Icons.import_export, const ImportExportPage()),
|
tr('importExport'), Icons.import_export, const ImportExportPage()),
|
||||||
NavigationPageItem(tr('settings'), Icons.settings, const SettingsPage())
|
NavigationPageItem(tr('settings'), Icons.settings, const SettingsPage())
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
initDeepLinks();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initDeepLinks() async {
|
||||||
|
_appLinks = AppLinks();
|
||||||
|
|
||||||
|
goToAddApp(String data) async {
|
||||||
|
switchToPage(1);
|
||||||
|
while (
|
||||||
|
(pages[1].widget.key as GlobalKey<AddAppPageState>?)?.currentState ==
|
||||||
|
null) {
|
||||||
|
await Future.delayed(const Duration(microseconds: 1));
|
||||||
|
}
|
||||||
|
(pages[1].widget.key as GlobalKey<AddAppPageState>?)
|
||||||
|
?.currentState
|
||||||
|
?.linkFn(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
interpretLink(Uri uri) async {
|
||||||
|
isLinkActivity = true;
|
||||||
|
var action = uri.host;
|
||||||
|
var data = uri.path.length > 1 ? uri.path.substring(1) : "";
|
||||||
|
try {
|
||||||
|
if (action == 'add') {
|
||||||
|
await goToAddApp(data);
|
||||||
|
} else if (action == 'app' || action == 'apps') {
|
||||||
|
var dataStr = Uri.decodeComponent(data);
|
||||||
|
if (await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext ctx) {
|
||||||
|
return GeneratedFormModal(
|
||||||
|
title: tr('importX', args: [
|
||||||
|
action == 'app' ? tr('app') : tr('appsString')
|
||||||
|
]),
|
||||||
|
items: const [],
|
||||||
|
additionalWidgets: [
|
||||||
|
ExpansionTile(
|
||||||
|
title: const Text('Raw JSON'),
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
dataStr,
|
||||||
|
style: const TextStyle(fontFamily: 'monospace'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}) !=
|
||||||
|
null) {
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
var result = await context.read<AppsProvider>().import(
|
||||||
|
action == 'app'
|
||||||
|
? '{ "apps": [$dataStr] }'
|
||||||
|
: '{ "apps": $dataStr }');
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
showMessage(
|
||||||
|
tr('importedX', args: [plural('apps', result.key)]), context);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw ObtainiumError(tr('unknown'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showError(e, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check initial link if app was in cold state (terminated)
|
||||||
|
final appLink = await _appLinks.getInitialAppLink();
|
||||||
|
if (appLink != null) {
|
||||||
|
await interpretLink(appLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle link when app is in warm state (front or background)
|
||||||
|
_linkSubscription = _appLinks.uriLinkStream.listen((uri) async {
|
||||||
|
await interpretLink(uri);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsReversing(int targetIndex) {
|
||||||
|
bool reversing = selectedIndexHistory.isNotEmpty &&
|
||||||
|
selectedIndexHistory.last > targetIndex;
|
||||||
|
setState(() {
|
||||||
|
isReversing = reversing;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToPage(int index) async {
|
||||||
|
setIsReversing(index);
|
||||||
|
if (index == 0) {
|
||||||
|
while ((pages[0].widget.key as GlobalKey<AppsPageState>).currentState !=
|
||||||
|
null) {
|
||||||
|
// Avoid duplicate GlobalKey error
|
||||||
|
await Future.delayed(const Duration(microseconds: 1));
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
selectedIndexHistory.clear();
|
||||||
|
});
|
||||||
|
} else if (selectedIndexHistory.isEmpty ||
|
||||||
|
(selectedIndexHistory.isNotEmpty &&
|
||||||
|
selectedIndexHistory.last != index)) {
|
||||||
|
setState(() {
|
||||||
|
int existingInd = selectedIndexHistory.indexOf(index);
|
||||||
|
if (existingInd >= 0) {
|
||||||
|
selectedIndexHistory.removeAt(existingInd);
|
||||||
|
}
|
||||||
|
selectedIndexHistory.add(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
AppsProvider appsProvider = context.watch<AppsProvider>();
|
AppsProvider appsProvider = context.watch<AppsProvider>();
|
||||||
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
|
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
|
||||||
|
|
||||||
setIsReversing(int targetIndex) {
|
|
||||||
bool reversing = selectedIndexHistory.isNotEmpty &&
|
|
||||||
selectedIndexHistory.last > targetIndex;
|
|
||||||
setState(() {
|
|
||||||
isReversing = reversing;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
switchToPage(int index) async {
|
|
||||||
setIsReversing(index);
|
|
||||||
if (index == 0) {
|
|
||||||
while ((pages[0].widget.key as GlobalKey<AppsPageState>).currentState !=
|
|
||||||
null) {
|
|
||||||
// Avoid duplicate GlobalKey error
|
|
||||||
await Future.delayed(const Duration(microseconds: 1));
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
selectedIndexHistory.clear();
|
|
||||||
});
|
|
||||||
} else if (selectedIndexHistory.isEmpty ||
|
|
||||||
(selectedIndexHistory.isNotEmpty &&
|
|
||||||
selectedIndexHistory.last != index)) {
|
|
||||||
setState(() {
|
|
||||||
int existingInd = selectedIndexHistory.indexOf(index);
|
|
||||||
if (existingInd >= 0) {
|
|
||||||
selectedIndexHistory.removeAt(existingInd);
|
|
||||||
}
|
|
||||||
selectedIndexHistory.add(index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!prevIsLoading &&
|
if (!prevIsLoading &&
|
||||||
prevAppCount >= 0 &&
|
prevAppCount >= 0 &&
|
||||||
appsProvider.apps.length > prevAppCount &&
|
appsProvider.apps.length > prevAppCount &&
|
||||||
selectedIndexHistory.isNotEmpty &&
|
selectedIndexHistory.isNotEmpty &&
|
||||||
selectedIndexHistory.last == 1) {
|
selectedIndexHistory.last == 1 &&
|
||||||
|
!isLinkActivity) {
|
||||||
switchToPage(0);
|
switchToPage(0);
|
||||||
}
|
}
|
||||||
prevAppCount = appsProvider.apps.length;
|
prevAppCount = appsProvider.apps.length;
|
||||||
@@ -129,6 +220,11 @@ class _HomePageState extends State<HomePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
|
if (isLinkActivity &&
|
||||||
|
selectedIndexHistory.length == 1 &&
|
||||||
|
selectedIndexHistory.last == 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
setIsReversing(selectedIndexHistory.length >= 2
|
setIsReversing(selectedIndexHistory.length >= 2
|
||||||
? selectedIndexHistory.reversed.toList()[1]
|
? selectedIndexHistory.reversed.toList()[1]
|
||||||
: 0);
|
: 0);
|
||||||
@@ -143,4 +239,10 @@ class _HomePageState extends State<HomePage> {
|
|||||||
?.clearSelected();
|
?.clearSelected();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
_linkSubscription?.cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:obtainium/app_sources/fdroidrepo.dart';
|
||||||
import 'package:obtainium/components/custom_app_bar.dart';
|
import 'package:obtainium/components/custom_app_bar.dart';
|
||||||
import 'package:obtainium/components/generated_form.dart';
|
import 'package:obtainium/components/generated_form.dart';
|
||||||
import 'package:obtainium/components/generated_form_modal.dart';
|
import 'package:obtainium/components/generated_form_modal.dart';
|
||||||
@@ -28,8 +29,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
SourceProvider sourceProvider = SourceProvider();
|
SourceProvider sourceProvider = SourceProvider();
|
||||||
var appsProvider = context.read<AppsProvider>();
|
var appsProvider = context.watch<AppsProvider>();
|
||||||
var settingsProvider = context.read<SettingsProvider>();
|
var settingsProvider = context.watch<SettingsProvider>();
|
||||||
|
|
||||||
var outlineButtonStyle = ButtonStyle(
|
var outlineButtonStyle = ButtonStyle(
|
||||||
shape: MaterialStateProperty.all(
|
shape: MaterialStateProperty.all(
|
||||||
@@ -81,7 +82,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
});
|
});
|
||||||
appsProvider.addAppsByURL(urls).then((errors) {
|
appsProvider.addAppsByURL(urls).then((errors) {
|
||||||
if (errors.isEmpty) {
|
if (errors.isEmpty) {
|
||||||
showError(tr('importedX', args: [plural('apps', urls.length)]),
|
showMessage(tr('importedX', args: [plural('apps', urls.length)]),
|
||||||
context);
|
context);
|
||||||
} else {
|
} else {
|
||||||
showDialog(
|
showDialog(
|
||||||
@@ -102,10 +103,17 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
runObtainiumExport() {
|
runObtainiumExport({bool pickOnly = false}) async {
|
||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
appsProvider.exportApps().then((String path) {
|
appsProvider
|
||||||
showError(tr('exportedTo', args: [path]), context);
|
.export(
|
||||||
|
pickOnly:
|
||||||
|
pickOnly || (await settingsProvider.getExportDir()) == null,
|
||||||
|
sp: settingsProvider)
|
||||||
|
.then((String? result) {
|
||||||
|
if (result != null) {
|
||||||
|
showMessage(tr('exportedTo', args: [result]), context);
|
||||||
|
}
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
showError(e, context);
|
showError(e, context);
|
||||||
});
|
});
|
||||||
@@ -124,7 +132,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw ObtainiumError(tr('invalidInput'));
|
throw ObtainiumError(tr('invalidInput'));
|
||||||
}
|
}
|
||||||
appsProvider.importApps(data).then((value) {
|
appsProvider.import(data).then((value) {
|
||||||
var cats = settingsProvider.categories;
|
var cats = settingsProvider.categories;
|
||||||
appsProvider.apps.forEach((key, value) {
|
appsProvider.apps.forEach((key, value) {
|
||||||
for (var c in value.app.categories) {
|
for (var c in value.app.categories) {
|
||||||
@@ -134,7 +142,11 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
appsProvider.addMissingCategories(settingsProvider);
|
appsProvider.addMissingCategories(settingsProvider);
|
||||||
showError(tr('importedX', args: [plural('apps', value)]), context);
|
showMessage(
|
||||||
|
'${tr('importedX', args: [
|
||||||
|
plural('apps', value.key)
|
||||||
|
])}${value.value ? ' + ${tr('settings')}' : ''}',
|
||||||
|
context);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// User canceled the picker
|
// User canceled the picker
|
||||||
@@ -181,17 +193,29 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
items: [
|
items: [
|
||||||
[
|
[
|
||||||
GeneratedFormTextField('searchQuery',
|
GeneratedFormTextField('searchQuery',
|
||||||
label: tr('searchQuery'))
|
label: tr('searchQuery'),
|
||||||
|
required: source.name != FDroidRepo().name)
|
||||||
|
],
|
||||||
|
...source.searchQuerySettingFormItems.map((e) => [e]),
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('url',
|
||||||
|
label: source.host != null
|
||||||
|
? tr('overrideSource')
|
||||||
|
: plural('url', 1).substring(2),
|
||||||
|
defaultValue: source.host ?? '',
|
||||||
|
required: true)
|
||||||
],
|
],
|
||||||
...source.searchQuerySettingFormItems.map((e) => [e])
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (values != null &&
|
if (values != null) {
|
||||||
(values['searchQuery'] as String?)?.isNotEmpty == true) {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
importInProgress = true;
|
importInProgress = true;
|
||||||
});
|
});
|
||||||
|
if (values['url'] != source.host) {
|
||||||
|
source = sourceProvider.getSource(values['url'],
|
||||||
|
overrideSource: source.runtimeType.toString());
|
||||||
|
}
|
||||||
var urlsWithDescriptions = await source
|
var urlsWithDescriptions = await source
|
||||||
.search(values['searchQuery'] as String, querySettings: values);
|
.search(values['searchQuery'] as String, querySettings: values);
|
||||||
if (urlsWithDescriptions.isNotEmpty) {
|
if (urlsWithDescriptions.isNotEmpty) {
|
||||||
@@ -200,17 +224,19 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
await showDialog<List<String>?>(
|
await showDialog<List<String>?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext ctx) {
|
builder: (BuildContext ctx) {
|
||||||
return UrlSelectionModal(
|
return SelectionModal(
|
||||||
urlsWithDescriptions: urlsWithDescriptions,
|
entries: urlsWithDescriptions,
|
||||||
selectedByDefault: false,
|
selectedByDefault: false,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (selectedUrls != null && selectedUrls.isNotEmpty) {
|
if (selectedUrls != null && selectedUrls.isNotEmpty) {
|
||||||
var errors = await appsProvider.addAppsByURL(selectedUrls);
|
var errors = await appsProvider.addAppsByURL(selectedUrls,
|
||||||
|
sourceOverride: source);
|
||||||
if (errors.isEmpty) {
|
if (errors.isEmpty) {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
showError(
|
showMessage(
|
||||||
tr('importedX', args: [plural('app', selectedUrls.length)]),
|
tr('importedX',
|
||||||
|
args: [plural('apps', selectedUrls.length)]),
|
||||||
context);
|
context);
|
||||||
} else {
|
} else {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
@@ -259,15 +285,14 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
await showDialog<List<String>?>(
|
await showDialog<List<String>?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext ctx) {
|
builder: (BuildContext ctx) {
|
||||||
return UrlSelectionModal(
|
return SelectionModal(entries: urlsWithDescriptions);
|
||||||
urlsWithDescriptions: urlsWithDescriptions);
|
|
||||||
});
|
});
|
||||||
if (selectedUrls != null) {
|
if (selectedUrls != null) {
|
||||||
var errors = await appsProvider.addAppsByURL(selectedUrls);
|
var errors = await appsProvider.addAppsByURL(selectedUrls);
|
||||||
if (errors.isEmpty) {
|
if (errors.isEmpty) {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
showError(
|
showMessage(
|
||||||
tr('importedX', args: [plural('app', selectedUrls.length)]),
|
tr('importedX', args: [plural('apps', selectedUrls.length)]),
|
||||||
context);
|
context);
|
||||||
} else {
|
} else {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
@@ -290,6 +315,11 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sourceStrings = <String, List<String>>{};
|
||||||
|
sourceProvider.sources.where((e) => e.canSearch).forEach((s) {
|
||||||
|
sourceStrings[s.name] = [s.name];
|
||||||
|
});
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
body: CustomScrollView(slivers: <Widget>[
|
body: CustomScrollView(slivers: <Widget>[
|
||||||
@@ -301,27 +331,102 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
FutureBuilder(
|
||||||
children: [
|
future: settingsProvider.getExportDir(),
|
||||||
Expanded(
|
builder: (context, snapshot) {
|
||||||
child: TextButton(
|
return Column(
|
||||||
style: outlineButtonStyle,
|
children: [
|
||||||
onPressed: appsProvider.apps.isEmpty ||
|
Row(
|
||||||
importInProgress
|
children: [
|
||||||
? null
|
Expanded(
|
||||||
: runObtainiumExport,
|
child: TextButton(
|
||||||
child: Text(tr('obtainiumExport')))),
|
style: outlineButtonStyle,
|
||||||
const SizedBox(
|
onPressed: appsProvider.apps.isEmpty ||
|
||||||
width: 16,
|
importInProgress
|
||||||
),
|
? null
|
||||||
Expanded(
|
: () {
|
||||||
child: TextButton(
|
runObtainiumExport(pickOnly: true);
|
||||||
style: outlineButtonStyle,
|
},
|
||||||
onPressed: importInProgress
|
child: Text(tr('pickExportDir'),
|
||||||
? null
|
textAlign: TextAlign.center),
|
||||||
: runObtainiumImport,
|
)),
|
||||||
child: Text(tr('obtainiumImport'))))
|
const SizedBox(
|
||||||
],
|
width: 16,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TextButton(
|
||||||
|
style: outlineButtonStyle,
|
||||||
|
onPressed: appsProvider.apps.isEmpty ||
|
||||||
|
importInProgress ||
|
||||||
|
snapshot.data == null
|
||||||
|
? null
|
||||||
|
: runObtainiumExport,
|
||||||
|
child: Text(tr('obtainiumExport'),
|
||||||
|
textAlign: TextAlign.center),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextButton(
|
||||||
|
style: outlineButtonStyle,
|
||||||
|
onPressed: importInProgress
|
||||||
|
? null
|
||||||
|
: runObtainiumImport,
|
||||||
|
child: Text(tr('obtainiumImport'),
|
||||||
|
textAlign: TextAlign.center))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (snapshot.data != null)
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
GeneratedForm(
|
||||||
|
items: [
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch(
|
||||||
|
'autoExportOnChanges',
|
||||||
|
label: tr('autoExportOnChanges'),
|
||||||
|
defaultValue: settingsProvider
|
||||||
|
.autoExportOnChanges,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch(
|
||||||
|
'exportSettings',
|
||||||
|
label: tr('includeSettings'),
|
||||||
|
defaultValue: settingsProvider
|
||||||
|
.exportSettings,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
onValueChanges:
|
||||||
|
(value, valid, isBuilding) {
|
||||||
|
if (valid && !isBuilding) {
|
||||||
|
if (value['autoExportOnChanges'] !=
|
||||||
|
null) {
|
||||||
|
settingsProvider
|
||||||
|
.autoExportOnChanges = value[
|
||||||
|
'autoExportOnChanges'] ==
|
||||||
|
true;
|
||||||
|
}
|
||||||
|
if (value['exportSettings'] !=
|
||||||
|
null) {
|
||||||
|
settingsProvider.exportSettings =
|
||||||
|
value['exportSettings'] ==
|
||||||
|
true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (importInProgress)
|
if (importInProgress)
|
||||||
const Column(
|
const Column(
|
||||||
@@ -341,6 +446,54 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
const Divider(
|
const Divider(
|
||||||
height: 32,
|
height: 32,
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: importInProgress
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
var searchSourceName =
|
||||||
|
await showDialog<
|
||||||
|
List<String>?>(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(BuildContext
|
||||||
|
ctx) {
|
||||||
|
return SelectionModal(
|
||||||
|
title: tr(
|
||||||
|
'selectX',
|
||||||
|
args: [
|
||||||
|
tr('source')
|
||||||
|
]),
|
||||||
|
entries:
|
||||||
|
sourceStrings,
|
||||||
|
selectedByDefault:
|
||||||
|
false,
|
||||||
|
onlyOneSelectionAllowed:
|
||||||
|
true,
|
||||||
|
titlesAreLinks:
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}) ??
|
||||||
|
[];
|
||||||
|
var searchSource =
|
||||||
|
sourceProvider.sources
|
||||||
|
.where((e) =>
|
||||||
|
searchSourceName
|
||||||
|
.contains(
|
||||||
|
e.name))
|
||||||
|
.toList();
|
||||||
|
if (searchSource.isNotEmpty) {
|
||||||
|
runSourceSearch(
|
||||||
|
searchSource[0]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(tr('searchX',
|
||||||
|
args: [tr('source')])))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed:
|
onPressed:
|
||||||
importInProgress ? null : urlListImport,
|
importInProgress ? null : urlListImport,
|
||||||
@@ -356,39 +509,19 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
...sourceProvider.sources
|
...sourceProvider.massUrlSources.map((source) => Column(
|
||||||
.where((element) => element.canSearch)
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
.map((source) => Column(
|
children: [
|
||||||
crossAxisAlignment:
|
const SizedBox(height: 8),
|
||||||
CrossAxisAlignment.stretch,
|
TextButton(
|
||||||
children: [
|
onPressed: importInProgress
|
||||||
const SizedBox(height: 8),
|
? null
|
||||||
TextButton(
|
: () {
|
||||||
onPressed: importInProgress
|
runMassSourceImport(source);
|
||||||
? null
|
},
|
||||||
: () {
|
child: Text(
|
||||||
runSourceSearch(source);
|
tr('importX', args: [source.name])))
|
||||||
},
|
])),
|
||||||
child: Text(
|
|
||||||
tr('searchX', args: [source.name])))
|
|
||||||
]))
|
|
||||||
.toList(),
|
|
||||||
...sourceProvider.massUrlSources
|
|
||||||
.map((source) => Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
TextButton(
|
|
||||||
onPressed: importInProgress
|
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
runMassSourceImport(source);
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
tr('importX', args: [source.name])))
|
|
||||||
]))
|
|
||||||
.toList(),
|
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
const Divider(
|
const Divider(
|
||||||
height: 32,
|
height: 32,
|
||||||
@@ -399,7 +532,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
|||||||
fontStyle: FontStyle.italic, fontSize: 12)),
|
fontStyle: FontStyle.italic, fontSize: 12)),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
)))
|
)))
|
||||||
]));
|
]));
|
||||||
@@ -450,126 +583,191 @@ class _ImportErrorDialogState extends State<ImportErrorDialog> {
|
|||||||
style: const TextStyle(fontStyle: FontStyle.italic),
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
}).toList()
|
})
|
||||||
]),
|
]),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop(null);
|
Navigator.of(context).pop(null);
|
||||||
},
|
},
|
||||||
child: Text(tr('okay')))
|
child: Text(tr('ok')))
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class UrlSelectionModal extends StatefulWidget {
|
class SelectionModal extends StatefulWidget {
|
||||||
UrlSelectionModal(
|
SelectionModal(
|
||||||
{super.key,
|
{super.key,
|
||||||
required this.urlsWithDescriptions,
|
required this.entries,
|
||||||
this.selectedByDefault = true,
|
this.selectedByDefault = true,
|
||||||
this.onlyOneSelectionAllowed = false});
|
this.onlyOneSelectionAllowed = false,
|
||||||
|
this.titlesAreLinks = true,
|
||||||
|
this.title,
|
||||||
|
this.deselectThese = const []});
|
||||||
|
|
||||||
Map<String, List<String>> urlsWithDescriptions;
|
String? title;
|
||||||
|
Map<String, List<String>> entries;
|
||||||
bool selectedByDefault;
|
bool selectedByDefault;
|
||||||
|
List<String> deselectThese;
|
||||||
bool onlyOneSelectionAllowed;
|
bool onlyOneSelectionAllowed;
|
||||||
|
bool titlesAreLinks;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<UrlSelectionModal> createState() => _UrlSelectionModalState();
|
State<SelectionModal> createState() => _SelectionModalState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
class _SelectionModalState extends State<SelectionModal> {
|
||||||
Map<MapEntry<String, List<String>>, bool> urlWithDescriptionSelections = {};
|
Map<MapEntry<String, List<String>>, bool> entrySelections = {};
|
||||||
|
String filterRegex = '';
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
for (var url in widget.urlsWithDescriptions.entries) {
|
for (var entry in widget.entries.entries) {
|
||||||
urlWithDescriptionSelections.putIfAbsent(url,
|
entrySelections.putIfAbsent(
|
||||||
() => widget.selectedByDefault && !widget.onlyOneSelectionAllowed);
|
entry,
|
||||||
|
() =>
|
||||||
|
widget.selectedByDefault &&
|
||||||
|
!widget.onlyOneSelectionAllowed &&
|
||||||
|
!widget.deselectThese.contains(entry.key));
|
||||||
}
|
}
|
||||||
if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) {
|
if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) {
|
||||||
selectOnlyOne(widget.urlsWithDescriptions.entries.first.key);
|
selectOnlyOne(widget.entries.entries.first.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectOnlyOne(String url) {
|
selectOnlyOne(String url) {
|
||||||
for (var uwd in urlWithDescriptionSelections.keys) {
|
for (var e in entrySelections.keys) {
|
||||||
urlWithDescriptionSelections[uwd] = uwd.key == url;
|
entrySelections[e] = e.key == url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
Map<MapEntry<String, List<String>>, bool> filteredEntrySelections = {};
|
||||||
|
entrySelections.forEach((key, value) {
|
||||||
|
var searchableText = key.value.isEmpty ? key.key : key.value[0];
|
||||||
|
if (filterRegex.isEmpty || RegExp(filterRegex).hasMatch(searchableText)) {
|
||||||
|
filteredEntrySelections.putIfAbsent(key, () => value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (filterRegex.isNotEmpty && filteredEntrySelections.isEmpty) {
|
||||||
|
entrySelections.forEach((key, value) {
|
||||||
|
var searchableText = key.value.isEmpty ? key.key : key.value[0];
|
||||||
|
if (filterRegex.isEmpty ||
|
||||||
|
RegExp(filterRegex, caseSensitive: false)
|
||||||
|
.hasMatch(searchableText)) {
|
||||||
|
filteredEntrySelections.putIfAbsent(key, () => value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
title: Text(
|
title: Text(widget.title ?? tr('pick')),
|
||||||
widget.onlyOneSelectionAllowed ? tr('selectURL') : tr('selectURLs')),
|
|
||||||
content: Column(children: [
|
content: Column(children: [
|
||||||
...urlWithDescriptionSelections.keys.map((urlWithD) {
|
GeneratedForm(
|
||||||
|
items: [
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('filter',
|
||||||
|
label: tr('filter'),
|
||||||
|
required: false,
|
||||||
|
additionalValidators: [
|
||||||
|
(value) {
|
||||||
|
return regExValidator(value);
|
||||||
|
}
|
||||||
|
])
|
||||||
|
]
|
||||||
|
],
|
||||||
|
onValueChanges: (value, valid, isBuilding) {
|
||||||
|
if (valid && !isBuilding) {
|
||||||
|
if (value['filter'] != null) {
|
||||||
|
setState(() {
|
||||||
|
filterRegex = value['filter'];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
...filteredEntrySelections.keys.map((entry) {
|
||||||
selectThis(bool? value) {
|
selectThis(bool? value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
value ??= false;
|
value ??= false;
|
||||||
if (value! && widget.onlyOneSelectionAllowed) {
|
if (value! && widget.onlyOneSelectionAllowed) {
|
||||||
selectOnlyOne(urlWithD.key);
|
selectOnlyOne(entry.key);
|
||||||
} else {
|
} else {
|
||||||
urlWithDescriptionSelections[urlWithD] = value!;
|
entrySelections[entry] = value!;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var urlLink = GestureDetector(
|
var urlLink = GestureDetector(
|
||||||
onTap: () {
|
onTap: !widget.titlesAreLinks
|
||||||
launchUrlString(urlWithD.key,
|
? null
|
||||||
mode: LaunchMode.externalApplication);
|
: () {
|
||||||
},
|
launchUrlString(entry.key,
|
||||||
|
mode: LaunchMode.externalApplication);
|
||||||
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
urlWithD.value[0],
|
entry.value.isEmpty ? entry.key : entry.value[0],
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
decoration: TextDecoration.underline,
|
decoration: widget.titlesAreLinks
|
||||||
|
? TextDecoration.underline
|
||||||
|
: null,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
),
|
),
|
||||||
Text(
|
if (widget.titlesAreLinks)
|
||||||
Uri.parse(urlWithD.key).host,
|
Text(
|
||||||
style: const TextStyle(
|
Uri.parse(entry.key).host,
|
||||||
decoration: TextDecoration.underline, fontSize: 12),
|
style: const TextStyle(
|
||||||
)
|
decoration: TextDecoration.underline, fontSize: 12),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
|
|
||||||
var descriptionText = Text(
|
var descriptionText = entry.value.length <= 1
|
||||||
urlWithD.value[1].length > 128
|
? const SizedBox.shrink()
|
||||||
? '${urlWithD.value[1].substring(0, 128)}...'
|
: Text(
|
||||||
: urlWithD.value[1],
|
entry.value[1].length > 128
|
||||||
style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12),
|
? '${entry.value[1].substring(0, 128)}...'
|
||||||
);
|
: entry.value[1],
|
||||||
|
style: const TextStyle(
|
||||||
|
fontStyle: FontStyle.italic, fontSize: 12),
|
||||||
|
);
|
||||||
|
|
||||||
var selectedUrlsWithDs = urlWithDescriptionSelections.entries
|
var selectedEntries =
|
||||||
.where((e) => e.value)
|
entrySelections.entries.where((e) => e.value).toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
var singleSelectTile = ListTile(
|
var singleSelectTile = ListTile(
|
||||||
title: urlLink,
|
title: GestureDetector(
|
||||||
subtitle: GestureDetector(
|
onTap: widget.titlesAreLinks
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
selectOnlyOne(urlWithD.key);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: descriptionText,
|
|
||||||
),
|
|
||||||
leading: Radio<String>(
|
|
||||||
value: urlWithD.key,
|
|
||||||
groupValue: selectedUrlsWithDs.isEmpty
|
|
||||||
? null
|
? null
|
||||||
: selectedUrlsWithDs.first.key.key,
|
: () {
|
||||||
|
selectThis(!(entrySelections[entry] ?? false));
|
||||||
|
},
|
||||||
|
child: urlLink,
|
||||||
|
),
|
||||||
|
subtitle: entry.value.length <= 1
|
||||||
|
? null
|
||||||
|
: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
selectOnlyOne(entry.key);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: descriptionText,
|
||||||
|
),
|
||||||
|
leading: Radio<String>(
|
||||||
|
value: entry.key,
|
||||||
|
groupValue: selectedEntries.isEmpty
|
||||||
|
? null
|
||||||
|
: selectedEntries.first.key.key,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectOnlyOne(urlWithD.key);
|
selectOnlyOne(entry.key);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -577,7 +775,7 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
|||||||
|
|
||||||
var multiSelectTile = Row(children: [
|
var multiSelectTile = Row(children: [
|
||||||
Checkbox(
|
Checkbox(
|
||||||
value: urlWithDescriptionSelections[urlWithD],
|
value: entrySelections[entry],
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
selectThis(value);
|
selectThis(value);
|
||||||
}),
|
}),
|
||||||
@@ -592,14 +790,22 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
urlLink,
|
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: widget.titlesAreLinks
|
||||||
selectThis(
|
? null
|
||||||
!(urlWithDescriptionSelections[urlWithD] ?? false));
|
: () {
|
||||||
},
|
selectThis(!(entrySelections[entry] ?? false));
|
||||||
child: descriptionText,
|
},
|
||||||
|
child: urlLink,
|
||||||
),
|
),
|
||||||
|
entry.value.length <= 1
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
selectThis(!(entrySelections[entry] ?? false));
|
||||||
|
},
|
||||||
|
child: descriptionText,
|
||||||
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
)
|
)
|
||||||
@@ -619,24 +825,18 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
|||||||
},
|
},
|
||||||
child: Text(tr('cancel'))),
|
child: Text(tr('cancel'))),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed:
|
onPressed: entrySelections.values.where((b) => b).isEmpty
|
||||||
urlWithDescriptionSelections.values.where((b) => b).isEmpty
|
? null
|
||||||
? null
|
: () {
|
||||||
: () {
|
Navigator.of(context).pop(entrySelections.entries
|
||||||
Navigator.of(context).pop(urlWithDescriptionSelections
|
.where((entry) => entry.value)
|
||||||
.entries
|
.map((e) => e.key.key)
|
||||||
.where((entry) => entry.value)
|
.toList());
|
||||||
.map((e) => e.key.key)
|
},
|
||||||
.toList());
|
|
||||||
},
|
|
||||||
child: Text(widget.onlyOneSelectionAllowed
|
child: Text(widget.onlyOneSelectionAllowed
|
||||||
? tr('pick')
|
? tr('pick')
|
||||||
: tr('importX', args: [
|
: tr('selectX', args: [
|
||||||
plural(
|
entrySelections.values.where((b) => b).length.toString()
|
||||||
'url',
|
|
||||||
urlWithDescriptionSelections.values
|
|
||||||
.where((b) => b)
|
|
||||||
.length)
|
|
||||||
])))
|
])))
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:obtainium/components/custom_app_bar.dart';
|
import 'package:obtainium/components/custom_app_bar.dart';
|
||||||
@@ -6,6 +7,7 @@ import 'package:obtainium/custom_errors.dart';
|
|||||||
import 'package:obtainium/main.dart';
|
import 'package:obtainium/main.dart';
|
||||||
import 'package:obtainium/providers/apps_provider.dart';
|
import 'package:obtainium/providers/apps_provider.dart';
|
||||||
import 'package:obtainium/providers/logs_provider.dart';
|
import 'package:obtainium/providers/logs_provider.dart';
|
||||||
|
import 'package:obtainium/providers/native_provider.dart';
|
||||||
import 'package:obtainium/providers/settings_provider.dart';
|
import 'package:obtainium/providers/settings_provider.dart';
|
||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@@ -28,6 +30,29 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
settingsProvider.initializeSettings();
|
settingsProvider.initializeSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var installMethodDropdown = DropdownButtonFormField(
|
||||||
|
decoration: InputDecoration(labelText: tr('installMethod')),
|
||||||
|
value: settingsProvider.installMethod,
|
||||||
|
items: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: InstallMethodSettings.normal,
|
||||||
|
child: Text(tr('normal')),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: InstallMethodSettings.shizuku,
|
||||||
|
child: Text(tr('shizuku')),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: InstallMethodSettings.root,
|
||||||
|
child: Text(tr('root')),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
settingsProvider.installMethod = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var themeDropdown = DropdownButtonFormField(
|
var themeDropdown = DropdownButtonFormField(
|
||||||
decoration: InputDecoration(labelText: tr('theme')),
|
decoration: InputDecoration(labelText: tr('theme')),
|
||||||
value: settingsProvider.theme,
|
value: settingsProvider.theme,
|
||||||
@@ -166,9 +191,9 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
var sourceSpecificFields = sourceProvider.sources.map((e) {
|
var sourceSpecificFields = sourceProvider.sources.map((e) {
|
||||||
if (e.additionalSourceSpecificSettingFormItems.isNotEmpty) {
|
if (e.sourceConfigSettingFormItems.isNotEmpty) {
|
||||||
return GeneratedForm(
|
return GeneratedForm(
|
||||||
items: e.additionalSourceSpecificSettingFormItems.map((e) {
|
items: e.sourceConfigSettingFormItems.map((e) {
|
||||||
e.defaultValue = settingsProvider.getSettingString(e.key);
|
e.defaultValue = settingsProvider.getSettingString(e.key);
|
||||||
return [e];
|
return [e];
|
||||||
}).toList(),
|
}).toList(),
|
||||||
@@ -184,6 +209,10 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const height8 = SizedBox(
|
||||||
|
height: 8,
|
||||||
|
);
|
||||||
|
|
||||||
const height16 = SizedBox(
|
const height16 = SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
);
|
);
|
||||||
@@ -211,6 +240,72 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
color: Theme.of(context).colorScheme.primary),
|
color: Theme.of(context).colorScheme.primary),
|
||||||
),
|
),
|
||||||
intervalDropdown,
|
intervalDropdown,
|
||||||
|
FutureBuilder(
|
||||||
|
builder: (ctx, val) {
|
||||||
|
return (val.data?.version.sdkInt ?? 0) >= 30
|
||||||
|
? Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
height16,
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment
|
||||||
|
.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(tr(
|
||||||
|
'enableBackgroundUpdates'))),
|
||||||
|
Switch(
|
||||||
|
value: settingsProvider
|
||||||
|
.enableBackgroundUpdates,
|
||||||
|
onChanged: (value) {
|
||||||
|
settingsProvider
|
||||||
|
.enableBackgroundUpdates =
|
||||||
|
value;
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
|
height8,
|
||||||
|
Text(tr('backgroundUpdateReqsExplanation'),
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelSmall),
|
||||||
|
Text(tr('backgroundUpdateLimitsExplanation'),
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelSmall),
|
||||||
|
height8,
|
||||||
|
if (settingsProvider
|
||||||
|
.enableBackgroundUpdates)
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
height16,
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment
|
||||||
|
.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(tr(
|
||||||
|
'bgUpdatesOnWiFiOnly'))),
|
||||||
|
Switch(
|
||||||
|
value: settingsProvider
|
||||||
|
.bgUpdatesOnWiFiOnly,
|
||||||
|
onChanged: (value) {
|
||||||
|
settingsProvider
|
||||||
|
.bgUpdatesOnWiFiOnly =
|
||||||
|
value;
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
future: DeviceInfoPlugin().androidInfo),
|
||||||
height16,
|
height16,
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@@ -238,6 +333,37 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
})
|
})
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
height16,
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(tr(
|
||||||
|
'onlyCheckInstalledOrTrackOnlyApps'))),
|
||||||
|
Switch(
|
||||||
|
value: settingsProvider
|
||||||
|
.onlyCheckInstalledOrTrackOnlyApps,
|
||||||
|
onChanged: (value) {
|
||||||
|
settingsProvider
|
||||||
|
.onlyCheckInstalledOrTrackOnlyApps =
|
||||||
|
value;
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
|
height16,
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(child: Text(tr('parallelDownloads'))),
|
||||||
|
Switch(
|
||||||
|
value: settingsProvider.parallelDownloads,
|
||||||
|
onChanged: (value) {
|
||||||
|
settingsProvider.parallelDownloads =
|
||||||
|
value;
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
|
installMethodDropdown,
|
||||||
height32,
|
height32,
|
||||||
Text(
|
Text(
|
||||||
tr('sourceSpecific'),
|
tr('sourceSpecific'),
|
||||||
@@ -282,6 +408,30 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
height16,
|
height16,
|
||||||
localeDropdown,
|
localeDropdown,
|
||||||
height16,
|
height16,
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(child: Text(tr('useSystemFont'))),
|
||||||
|
Switch(
|
||||||
|
value: settingsProvider.useSystemFont,
|
||||||
|
onChanged: (useSystemFont) {
|
||||||
|
if (useSystemFont) {
|
||||||
|
NativeFeatures.loadSystemFont().then((fontLoadRes) {
|
||||||
|
if (fontLoadRes == 'ok') {
|
||||||
|
settingsProvider.useSystemFont = true;
|
||||||
|
} else {
|
||||||
|
showError(ObtainiumError(
|
||||||
|
tr('systemFontError', args: [fontLoadRes])
|
||||||
|
), context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
settingsProvider.useSystemFont = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
|
height16,
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@@ -403,8 +553,26 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
Switch(
|
Switch(
|
||||||
value:
|
value:
|
||||||
settingsProvider.reversePageTransitions,
|
settingsProvider.reversePageTransitions,
|
||||||
|
onChanged: settingsProvider
|
||||||
|
.disablePageTransitions
|
||||||
|
? null
|
||||||
|
: (value) {
|
||||||
|
settingsProvider
|
||||||
|
.reversePageTransitions = value;
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
|
height16,
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(tr('highlightTouchTargets'))),
|
||||||
|
Switch(
|
||||||
|
value:
|
||||||
|
settingsProvider.highlightTouchTargets,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
settingsProvider.reversePageTransitions =
|
settingsProvider.highlightTouchTargets =
|
||||||
value;
|
value;
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
@@ -445,7 +613,8 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<LogsProvider>().get().then((logs) {
|
context.read<LogsProvider>().get().then((logs) {
|
||||||
if (logs.isEmpty) {
|
if (logs.isEmpty) {
|
||||||
showError(ObtainiumError(tr('noLogs')), context);
|
showMessage(
|
||||||
|
ObtainiumError(tr('noLogs')), context);
|
||||||
} else {
|
} else {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -459,7 +628,38 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
label: Text(tr('appLogs'))),
|
label: Text(tr('appLogs'))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
height16,
|
const Divider(
|
||||||
|
height: 32,
|
||||||
|
),
|
||||||
|
// Padding(
|
||||||
|
// padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||||
|
// child: Column(children: [
|
||||||
|
// Row(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
// children: [
|
||||||
|
// Flexible(child: Text(tr('debugMenu'))),
|
||||||
|
// Switch(
|
||||||
|
// value: settingsProvider.showDebugOpts,
|
||||||
|
// onChanged: (value) {
|
||||||
|
// settingsProvider.showDebugOpts = value;
|
||||||
|
// })
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// if (settingsProvider.showDebugOpts)
|
||||||
|
// Column(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
// children: [
|
||||||
|
// height16,
|
||||||
|
// TextButton(
|
||||||
|
// onPressed: () {
|
||||||
|
// bgUpdateCheck('taskId', null);
|
||||||
|
// showMessage(tr('bgTaskStarted'), context);
|
||||||
|
// },
|
||||||
|
// child: Text(tr('runBgCheckNow')))
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ]),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
75
lib/providers/native_provider.dart
Normal file
75
lib/providers/native_provider.dart
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class NativeFeatures {
|
||||||
|
static const MethodChannel _channel = MethodChannel('native');
|
||||||
|
static bool _systemFontLoaded = false;
|
||||||
|
static bool _callbacksApplied = false;
|
||||||
|
static int _resPermShizuku = -2; // not set
|
||||||
|
|
||||||
|
static Future<ByteData> _readFileBytes(String path) async {
|
||||||
|
var file = File(path);
|
||||||
|
var bytes = await file.readAsBytes();
|
||||||
|
return ByteData.view(bytes.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future _handleCalls(MethodCall call) async {
|
||||||
|
if (call.method == 'resPermShizuku') {
|
||||||
|
_resPermShizuku = call.arguments['res'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future _waitWhile(bool Function() test,
|
||||||
|
[Duration pollInterval = const Duration(milliseconds: 250)]) {
|
||||||
|
var completer = Completer();
|
||||||
|
check() {
|
||||||
|
if (test()) {
|
||||||
|
Timer(pollInterval, check);
|
||||||
|
} else {
|
||||||
|
completer.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check();
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<String> loadSystemFont() async {
|
||||||
|
if (_systemFontLoaded) { return "ok"; }
|
||||||
|
var getFontRes = await _channel.invokeMethod('getSystemFont');
|
||||||
|
if (getFontRes[0] != '/') { return getFontRes; } // Error
|
||||||
|
var fontLoader = FontLoader('SystemFont');
|
||||||
|
fontLoader.addFont(_readFileBytes(getFontRes));
|
||||||
|
await fontLoader.load();
|
||||||
|
_systemFontLoaded = true;
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<int> checkPermissionShizuku() async {
|
||||||
|
if (!_callbacksApplied) {
|
||||||
|
_channel.setMethodCallHandler(_handleCalls);
|
||||||
|
_callbacksApplied = true;
|
||||||
|
}
|
||||||
|
int res = await _channel.invokeMethod('checkPermissionShizuku');
|
||||||
|
if (res == -2) {
|
||||||
|
await _waitWhile(() => _resPermShizuku == -2);
|
||||||
|
res = _resPermShizuku;
|
||||||
|
_resPermShizuku = -2;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> checkPermissionRoot() async {
|
||||||
|
return await _channel.invokeMethod('checkPermissionRoot');
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> installWithShizuku({required String apkFileUri}) async {
|
||||||
|
return await _channel.invokeMethod(
|
||||||
|
'installWithShizuku', {'apkFileUri': apkFileUri});
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> installWithRoot({required String apkFilePath}) async {
|
||||||
|
return await _channel.invokeMethod(
|
||||||
|
'installWithRoot', {'apkFilePath': apkFilePath});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,13 +22,13 @@ class ObtainiumNotification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class UpdateNotification extends ObtainiumNotification {
|
class UpdateNotification extends ObtainiumNotification {
|
||||||
UpdateNotification(List<App> updates)
|
UpdateNotification(List<App> updates, {int? id})
|
||||||
: super(
|
: super(
|
||||||
2,
|
id ?? 2,
|
||||||
tr('updatesAvailable'),
|
tr('updatesAvailable'),
|
||||||
'',
|
'',
|
||||||
'UPDATES_AVAILABLE',
|
'UPDATES_AVAILABLE',
|
||||||
tr('updatesAvailable'),
|
tr('updatesAvailableNotifChannel'),
|
||||||
tr('updatesAvailableNotifDescription'),
|
tr('updatesAvailableNotifDescription'),
|
||||||
Importance.max) {
|
Importance.max) {
|
||||||
message = updates.isEmpty
|
message = updates.isEmpty
|
||||||
@@ -41,9 +41,15 @@ class UpdateNotification extends ObtainiumNotification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SilentUpdateNotification extends ObtainiumNotification {
|
class SilentUpdateNotification extends ObtainiumNotification {
|
||||||
SilentUpdateNotification(List<App> updates)
|
SilentUpdateNotification(List<App> updates, {int? id})
|
||||||
: super(3, tr('appsUpdated'), '', 'APPS_UPDATED', tr('appsUpdated'),
|
: super(
|
||||||
tr('appsUpdatedNotifDescription'), Importance.defaultImportance) {
|
id ?? 3,
|
||||||
|
tr('appsUpdated'),
|
||||||
|
'',
|
||||||
|
'APPS_UPDATED',
|
||||||
|
tr('appsUpdatedNotifChannel'),
|
||||||
|
tr('appsUpdatedNotifDescription'),
|
||||||
|
Importance.defaultImportance) {
|
||||||
message = updates.length == 1
|
message = updates.length == 1
|
||||||
? tr('xWasUpdatedToY',
|
? tr('xWasUpdatedToY',
|
||||||
args: [updates[0].finalName, updates[0].latestVersion])
|
args: [updates[0].finalName, updates[0].latestVersion])
|
||||||
@@ -52,22 +58,46 @@ class SilentUpdateNotification extends ObtainiumNotification {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ErrorCheckingUpdatesNotification extends ObtainiumNotification {
|
class SilentUpdateAttemptNotification extends ObtainiumNotification {
|
||||||
ErrorCheckingUpdatesNotification(String error)
|
SilentUpdateAttemptNotification(List<App> updates, {int? id})
|
||||||
: super(
|
: super(
|
||||||
5,
|
id ?? 3,
|
||||||
|
tr('appsPossiblyUpdated'),
|
||||||
|
'',
|
||||||
|
'APPS_POSSIBLY_UPDATED',
|
||||||
|
tr('appsPossiblyUpdatedNotifChannel'),
|
||||||
|
tr('appsPossiblyUpdatedNotifDescription'),
|
||||||
|
Importance.defaultImportance) {
|
||||||
|
message = updates.length == 1
|
||||||
|
? tr('xWasPossiblyUpdatedToY',
|
||||||
|
args: [updates[0].finalName, updates[0].latestVersion])
|
||||||
|
: plural('xAndNMoreUpdatesPossiblyInstalled', updates.length - 1,
|
||||||
|
args: [updates[0].finalName, (updates.length - 1).toString()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorCheckingUpdatesNotification extends ObtainiumNotification {
|
||||||
|
ErrorCheckingUpdatesNotification(String error, {int? id})
|
||||||
|
: super(
|
||||||
|
id ?? 5,
|
||||||
tr('errorCheckingUpdates'),
|
tr('errorCheckingUpdates'),
|
||||||
error,
|
error,
|
||||||
'BG_UPDATE_CHECK_ERROR',
|
'BG_UPDATE_CHECK_ERROR',
|
||||||
tr('errorCheckingUpdates'),
|
tr('errorCheckingUpdatesNotifChannel'),
|
||||||
tr('errorCheckingUpdatesNotifDescription'),
|
tr('errorCheckingUpdatesNotifDescription'),
|
||||||
Importance.high);
|
Importance.high);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppsRemovedNotification extends ObtainiumNotification {
|
class AppsRemovedNotification extends ObtainiumNotification {
|
||||||
AppsRemovedNotification(List<List<String>> namedReasons)
|
AppsRemovedNotification(List<List<String>> namedReasons)
|
||||||
: super(6, tr('appsRemoved'), '', 'APPS_REMOVED', tr('appsRemoved'),
|
: super(
|
||||||
tr('appsRemovedNotifDescription'), Importance.max) {
|
6,
|
||||||
|
tr('appsRemoved'),
|
||||||
|
'',
|
||||||
|
'APPS_REMOVED',
|
||||||
|
tr('appsRemovedNotifChannel'),
|
||||||
|
tr('appsRemovedNotifDescription'),
|
||||||
|
Importance.max) {
|
||||||
message = '';
|
message = '';
|
||||||
for (var r in namedReasons) {
|
for (var r in namedReasons) {
|
||||||
message += '${tr('xWasRemovedDueToErrorY', args: [r[0], r[1]])} \n';
|
message += '${tr('xWasRemovedDueToErrorY', args: [r[0], r[1]])} \n';
|
||||||
@@ -83,7 +113,7 @@ class DownloadNotification extends ObtainiumNotification {
|
|||||||
tr('downloadingX', args: [appName]),
|
tr('downloadingX', args: [appName]),
|
||||||
'',
|
'',
|
||||||
'APP_DOWNLOADING',
|
'APP_DOWNLOADING',
|
||||||
tr('downloadingX', args: [tr('app')]),
|
tr('downloadingXNotifChannel', args: [tr('app')]),
|
||||||
tr('downloadNotifDescription'),
|
tr('downloadNotifDescription'),
|
||||||
Importance.low,
|
Importance.low,
|
||||||
onlyAlertOnce: true,
|
onlyAlertOnce: true,
|
||||||
@@ -95,18 +125,21 @@ final completeInstallationNotification = ObtainiumNotification(
|
|||||||
tr('completeAppInstallation'),
|
tr('completeAppInstallation'),
|
||||||
tr('obtainiumMustBeOpenToInstallApps'),
|
tr('obtainiumMustBeOpenToInstallApps'),
|
||||||
'COMPLETE_INSTALL',
|
'COMPLETE_INSTALL',
|
||||||
tr('completeAppInstallation'),
|
tr('completeAppInstallationNotifChannel'),
|
||||||
tr('completeAppInstallationNotifDescription'),
|
tr('completeAppInstallationNotifDescription'),
|
||||||
Importance.max);
|
Importance.max);
|
||||||
|
|
||||||
final checkingUpdatesNotification = ObtainiumNotification(
|
class CheckingUpdatesNotification extends ObtainiumNotification {
|
||||||
4,
|
CheckingUpdatesNotification(String appName)
|
||||||
tr('checkingForUpdates'),
|
: super(
|
||||||
'',
|
4,
|
||||||
'BG_UPDATE_CHECK',
|
tr('checkingForUpdates'),
|
||||||
tr('checkingForUpdates'),
|
appName,
|
||||||
tr('checkingForUpdatesNotifDescription'),
|
'BG_UPDATE_CHECK',
|
||||||
Importance.min);
|
tr('checkingForUpdatesNotifChannel'),
|
||||||
|
tr('checkingForUpdatesNotifDescription'),
|
||||||
|
Importance.min);
|
||||||
|
}
|
||||||
|
|
||||||
class NotificationsProvider {
|
class NotificationsProvider {
|
||||||
FlutterLocalNotificationsPlugin notifications =
|
FlutterLocalNotificationsPlugin notifications =
|
||||||
|
|||||||
@@ -9,12 +9,16 @@ import 'package:obtainium/app_sources/github.dart';
|
|||||||
import 'package:obtainium/main.dart';
|
import 'package:obtainium/main.dart';
|
||||||
import 'package:obtainium/providers/apps_provider.dart';
|
import 'package:obtainium/providers/apps_provider.dart';
|
||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:shared_storage/shared_storage.dart' as saf;
|
||||||
|
|
||||||
String obtainiumTempId = 'imranr98_obtainium_${GitHub().host}';
|
String obtainiumTempId = 'imranr98_obtainium_${GitHub().host}';
|
||||||
String obtainiumId = 'dev.imranr.obtainium';
|
String obtainiumId = 'dev.imranr.obtainium';
|
||||||
|
|
||||||
|
enum InstallMethodSettings { normal, shizuku, root }
|
||||||
|
|
||||||
enum ThemeSettings { system, light, dark }
|
enum ThemeSettings { system, light, dark }
|
||||||
|
|
||||||
enum ColourSettings { basic, materialYou }
|
enum ColourSettings { basic, materialYou }
|
||||||
@@ -35,6 +39,7 @@ List<int> updateIntervals = [15, 30, 60, 120, 180, 360, 720, 1440, 4320, 0]
|
|||||||
|
|
||||||
class SettingsProvider with ChangeNotifier {
|
class SettingsProvider with ChangeNotifier {
|
||||||
SharedPreferences? prefs;
|
SharedPreferences? prefs;
|
||||||
|
String? defaultAppDir;
|
||||||
bool justStarted = true;
|
bool justStarted = true;
|
||||||
|
|
||||||
String sourceUrl = 'https://github.com/ImranR98/Obtainium';
|
String sourceUrl = 'https://github.com/ImranR98/Obtainium';
|
||||||
@@ -42,6 +47,26 @@ class SettingsProvider with ChangeNotifier {
|
|||||||
// Not done in constructor as we want to be able to await it
|
// Not done in constructor as we want to be able to await it
|
||||||
Future<void> initializeSettings() async {
|
Future<void> initializeSettings() async {
|
||||||
prefs = await SharedPreferences.getInstance();
|
prefs = await SharedPreferences.getInstance();
|
||||||
|
defaultAppDir = (await getExternalStorageDirectory())!.path;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get useSystemFont {
|
||||||
|
return prefs?.getBool('useSystemFont') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set useSystemFont(bool useSystemFont) {
|
||||||
|
prefs?.setBool('useSystemFont', useSystemFont);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallMethodSettings get installMethod {
|
||||||
|
return InstallMethodSettings.values[
|
||||||
|
prefs?.getInt('installMethod') ?? InstallMethodSettings.normal.index];
|
||||||
|
}
|
||||||
|
|
||||||
|
set installMethod(InstallMethodSettings t) {
|
||||||
|
prefs?.setInt('installMethod', t.index);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +234,8 @@ class SettingsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String? getSettingString(String settingId) {
|
String? getSettingString(String settingId) {
|
||||||
return prefs?.getString(settingId);
|
String? str = prefs?.getString(settingId);
|
||||||
|
return str?.isNotEmpty == true ? str : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSettingString(String settingId, String value) {
|
void setSettingString(String settingId, String value) {
|
||||||
@@ -309,4 +335,133 @@ class SettingsProvider with ChangeNotifier {
|
|||||||
prefs?.setBool('reversePageTransitions', show);
|
prefs?.setBool('reversePageTransitions', show);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get enableBackgroundUpdates {
|
||||||
|
return prefs?.getBool('enableBackgroundUpdates') ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
set enableBackgroundUpdates(bool val) {
|
||||||
|
prefs?.setBool('enableBackgroundUpdates', val);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get bgUpdatesOnWiFiOnly {
|
||||||
|
return prefs?.getBool('bgUpdatesOnWiFiOnly') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set bgUpdatesOnWiFiOnly(bool val) {
|
||||||
|
prefs?.setBool('bgUpdatesOnWiFiOnly', val);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime get lastCompletedBGCheckTime {
|
||||||
|
int? temp = prefs?.getInt('lastCompletedBGCheckTime');
|
||||||
|
return temp != null
|
||||||
|
? DateTime.fromMillisecondsSinceEpoch(temp)
|
||||||
|
: DateTime.fromMillisecondsSinceEpoch(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
set lastCompletedBGCheckTime(DateTime val) {
|
||||||
|
prefs?.setInt('lastCompletedBGCheckTime', val.millisecondsSinceEpoch);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get showDebugOpts {
|
||||||
|
return prefs?.getBool('showDebugOpts') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set showDebugOpts(bool val) {
|
||||||
|
prefs?.setBool('showDebugOpts', val);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get highlightTouchTargets {
|
||||||
|
return prefs?.getBool('highlightTouchTargets') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set highlightTouchTargets(bool val) {
|
||||||
|
prefs?.setBool('highlightTouchTargets', val);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Uri?> getExportDir() async {
|
||||||
|
var uriString = prefs?.getString('exportDir');
|
||||||
|
if (uriString != null) {
|
||||||
|
Uri? uri = Uri.parse(uriString);
|
||||||
|
if (!(await saf.canRead(uri) ?? false) ||
|
||||||
|
!(await saf.canWrite(uri) ?? false)) {
|
||||||
|
uri = null;
|
||||||
|
prefs?.remove('exportDir');
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> pickExportDir({bool remove = false}) async {
|
||||||
|
var existingSAFPerms = (await saf.persistedUriPermissions()) ?? [];
|
||||||
|
var currentOneWayDataSyncDir = await getExportDir();
|
||||||
|
Uri? newOneWayDataSyncDir;
|
||||||
|
if (!remove) {
|
||||||
|
newOneWayDataSyncDir = (await saf.openDocumentTree());
|
||||||
|
}
|
||||||
|
if (currentOneWayDataSyncDir?.path != newOneWayDataSyncDir?.path) {
|
||||||
|
if (newOneWayDataSyncDir == null) {
|
||||||
|
prefs?.remove('exportDir');
|
||||||
|
} else {
|
||||||
|
prefs?.setString('exportDir', newOneWayDataSyncDir.toString());
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
for (var e in existingSAFPerms) {
|
||||||
|
await saf.releasePersistableUriPermission(e.uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get autoExportOnChanges {
|
||||||
|
return prefs?.getBool('autoExportOnChanges') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set autoExportOnChanges(bool val) {
|
||||||
|
prefs?.setBool('autoExportOnChanges', val);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get onlyCheckInstalledOrTrackOnlyApps {
|
||||||
|
return prefs?.getBool('onlyCheckInstalledOrTrackOnlyApps') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set onlyCheckInstalledOrTrackOnlyApps(bool val) {
|
||||||
|
prefs?.setBool('onlyCheckInstalledOrTrackOnlyApps', val);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get exportSettings {
|
||||||
|
return prefs?.getBool('exportSettings') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set exportSettings(bool val) {
|
||||||
|
prefs?.setBool('exportSettings', val);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get parallelDownloads {
|
||||||
|
return prefs?.getBool('parallelDownloads') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set parallelDownloads(bool val) {
|
||||||
|
prefs?.setBool('parallelDownloads', val);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> get searchDeselected {
|
||||||
|
return prefs?.getStringList('searchDeselected') ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
set searchDeselected(List<String> list) {
|
||||||
|
prefs?.setStringList('searchDeselected', list);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ import 'package:html/dom.dart';
|
|||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:obtainium/app_sources/apkmirror.dart';
|
import 'package:obtainium/app_sources/apkmirror.dart';
|
||||||
import 'package:obtainium/app_sources/apkpure.dart';
|
import 'package:obtainium/app_sources/apkpure.dart';
|
||||||
|
import 'package:obtainium/app_sources/aptoide.dart';
|
||||||
import 'package:obtainium/app_sources/codeberg.dart';
|
import 'package:obtainium/app_sources/codeberg.dart';
|
||||||
import 'package:obtainium/app_sources/fdroid.dart';
|
import 'package:obtainium/app_sources/fdroid.dart';
|
||||||
import 'package:obtainium/app_sources/fdroidrepo.dart';
|
import 'package:obtainium/app_sources/fdroidrepo.dart';
|
||||||
import 'package:obtainium/app_sources/github.dart';
|
import 'package:obtainium/app_sources/github.dart';
|
||||||
import 'package:obtainium/app_sources/gitlab.dart';
|
import 'package:obtainium/app_sources/gitlab.dart';
|
||||||
|
import 'package:obtainium/app_sources/huaweiappgallery.dart';
|
||||||
import 'package:obtainium/app_sources/izzyondroid.dart';
|
import 'package:obtainium/app_sources/izzyondroid.dart';
|
||||||
import 'package:obtainium/app_sources/html.dart';
|
import 'package:obtainium/app_sources/html.dart';
|
||||||
import 'package:obtainium/app_sources/jenkins.dart';
|
import 'package:obtainium/app_sources/jenkins.dart';
|
||||||
@@ -24,10 +26,13 @@ import 'package:obtainium/app_sources/sourceforge.dart';
|
|||||||
import 'package:obtainium/app_sources/sourcehut.dart';
|
import 'package:obtainium/app_sources/sourcehut.dart';
|
||||||
import 'package:obtainium/app_sources/steammobile.dart';
|
import 'package:obtainium/app_sources/steammobile.dart';
|
||||||
import 'package:obtainium/app_sources/telegramapp.dart';
|
import 'package:obtainium/app_sources/telegramapp.dart';
|
||||||
|
import 'package:obtainium/app_sources/uptodown.dart';
|
||||||
import 'package:obtainium/app_sources/vlc.dart';
|
import 'package:obtainium/app_sources/vlc.dart';
|
||||||
|
import 'package:obtainium/app_sources/whatsapp.dart';
|
||||||
import 'package:obtainium/components/generated_form.dart';
|
import 'package:obtainium/components/generated_form.dart';
|
||||||
import 'package:obtainium/custom_errors.dart';
|
import 'package:obtainium/custom_errors.dart';
|
||||||
import 'package:obtainium/mass_app_sources/githubstars.dart';
|
import 'package:obtainium/mass_app_sources/githubstars.dart';
|
||||||
|
import 'package:obtainium/providers/settings_provider.dart';
|
||||||
|
|
||||||
class AppNames {
|
class AppNames {
|
||||||
late String author;
|
late String author;
|
||||||
@@ -62,10 +67,11 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) {
|
|||||||
.reduce((value, element) => [...value, ...element]);
|
.reduce((value, element) => [...value, ...element]);
|
||||||
Map<String, dynamic> additionalSettings =
|
Map<String, dynamic> additionalSettings =
|
||||||
getDefaultValuesFromFormItems([formItems]);
|
getDefaultValuesFromFormItems([formItems]);
|
||||||
|
Map<String, dynamic> originalAdditionalSettings = {};
|
||||||
if (json['additionalSettings'] != null) {
|
if (json['additionalSettings'] != null) {
|
||||||
additionalSettings.addEntries(
|
originalAdditionalSettings =
|
||||||
Map<String, dynamic>.from(jsonDecode(json['additionalSettings']))
|
Map<String, dynamic>.from(jsonDecode(json['additionalSettings']));
|
||||||
.entries);
|
additionalSettings.addEntries(originalAdditionalSettings.entries);
|
||||||
}
|
}
|
||||||
// If needed, migrate old-style additionalData to newer-style additionalSettings (V1)
|
// If needed, migrate old-style additionalData to newer-style additionalSettings (V1)
|
||||||
if (json['additionalData'] != null) {
|
if (json['additionalData'] != null) {
|
||||||
@@ -129,6 +135,53 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) {
|
|||||||
if (additionalSettings['autoApkFilterByArch'] == null) {
|
if (additionalSettings['autoApkFilterByArch'] == null) {
|
||||||
additionalSettings['autoApkFilterByArch'] = false;
|
additionalSettings['autoApkFilterByArch'] = false;
|
||||||
}
|
}
|
||||||
|
if (source.runtimeType == HTML().runtimeType) {
|
||||||
|
// HTML 'fixed URL' support should be disabled if it previously did not exist
|
||||||
|
if (originalAdditionalSettings['supportFixedAPKURL'] == null) {
|
||||||
|
additionalSettings['supportFixedAPKURL'] = false;
|
||||||
|
}
|
||||||
|
// HTML key rename
|
||||||
|
if (originalAdditionalSettings['sortByFileNamesNotLinks'] != null) {
|
||||||
|
additionalSettings['sortByLastLinkSegment'] =
|
||||||
|
originalAdditionalSettings['sortByFileNamesNotLinks'];
|
||||||
|
}
|
||||||
|
// HTML single 'intermediate link' should be converted to multi-support version
|
||||||
|
if (originalAdditionalSettings['intermediateLinkRegex'] != null &&
|
||||||
|
additionalSettings['intermediateLinkRegex']?.isNotEmpty != true) {
|
||||||
|
additionalSettings['intermediateLink'] = [
|
||||||
|
{
|
||||||
|
'customLinkFilterRegex':
|
||||||
|
originalAdditionalSettings['intermediateLinkRegex'],
|
||||||
|
'filterByLinkText':
|
||||||
|
originalAdditionalSettings['intermediateLinkByText']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ((additionalSettings['intermediateLink']?.length ?? 0) > 0) {
|
||||||
|
additionalSettings['intermediateLink'] =
|
||||||
|
additionalSettings['intermediateLink'].where((e) {
|
||||||
|
return e['customLinkFilterRegex']?.isNotEmpty == true;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
// Steam source apps should be converted to HTML (#1244)
|
||||||
|
var legacySteamSourceApps = SteamMobile().apks.keys;
|
||||||
|
if (legacySteamSourceApps.contains(additionalSettings['app'] ?? '')) {
|
||||||
|
json['url'] = '${json['url']}/mobile';
|
||||||
|
var replacementAdditionalSettings = getDefaultValuesFromFormItems(
|
||||||
|
HTML().combinedAppSpecificSettingFormItems);
|
||||||
|
for (var s in replacementAdditionalSettings.keys) {
|
||||||
|
if (additionalSettings.containsKey(s)) {
|
||||||
|
replacementAdditionalSettings[s] = additionalSettings[s];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replacementAdditionalSettings['customLinkFilterRegex'] =
|
||||||
|
'/${additionalSettings['app']}-(([0-9]+\\.?){1,})\\.apk';
|
||||||
|
replacementAdditionalSettings['versionExtractionRegEx'] =
|
||||||
|
replacementAdditionalSettings['customLinkFilterRegex'];
|
||||||
|
replacementAdditionalSettings['matchGroupToUse'] = '\$1';
|
||||||
|
additionalSettings = replacementAdditionalSettings;
|
||||||
|
}
|
||||||
|
}
|
||||||
json['additionalSettings'] = jsonEncode(additionalSettings);
|
json['additionalSettings'] = jsonEncode(additionalSettings);
|
||||||
// F-Droid no longer needs cloudflare exception since override can be used - migrate apps appropriately
|
// F-Droid no longer needs cloudflare exception since override can be used - migrate apps appropriately
|
||||||
// This allows us to reverse the changes made for issue #418 (support cloudflare.f-droid)
|
// This allows us to reverse the changes made for issue #418 (support cloudflare.f-droid)
|
||||||
@@ -278,9 +331,6 @@ preStandardizeUrl(String url) {
|
|||||||
url.toLowerCase().indexOf('https://') != 0) {
|
url.toLowerCase().indexOf('https://') != 0) {
|
||||||
url = 'https://$url';
|
url = 'https://$url';
|
||||||
}
|
}
|
||||||
if (url.toLowerCase().indexOf('https://www.') == 0) {
|
|
||||||
url = 'https://${url.substring(12)}';
|
|
||||||
}
|
|
||||||
url = url
|
url = url
|
||||||
.split('/')
|
.split('/')
|
||||||
.where((e) => e.isNotEmpty)
|
.where((e) => e.isNotEmpty)
|
||||||
@@ -323,21 +373,31 @@ abstract class AppSource {
|
|||||||
bool enforceTrackOnly = false;
|
bool enforceTrackOnly = false;
|
||||||
bool changeLogIfAnyIsMarkDown = true;
|
bool changeLogIfAnyIsMarkDown = true;
|
||||||
bool appIdInferIsOptional = false;
|
bool appIdInferIsOptional = false;
|
||||||
|
bool allowSubDomains = false;
|
||||||
|
bool naiveStandardVersionDetection = false;
|
||||||
|
bool neverAutoSelect = false;
|
||||||
|
|
||||||
AppSource() {
|
AppSource() {
|
||||||
name = runtimeType.toString();
|
name = runtimeType.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
overrideVersionDetectionFormDefault(String vd, bool disableStandard) {
|
overrideVersionDetectionFormDefault(String vd,
|
||||||
|
{bool disableStandard = false, bool disableRelDate = false}) {
|
||||||
additionalAppSpecificSourceAgnosticSettingFormItems =
|
additionalAppSpecificSourceAgnosticSettingFormItems =
|
||||||
additionalAppSpecificSourceAgnosticSettingFormItems.map((e) {
|
additionalAppSpecificSourceAgnosticSettingFormItems.map((e) {
|
||||||
return e.map((e2) {
|
return e.map((e2) {
|
||||||
if (e2.key == 'versionDetection') {
|
if (e2.key == 'versionDetection') {
|
||||||
var item = e2 as GeneratedFormDropdown;
|
var item = e2 as GeneratedFormDropdown;
|
||||||
item.defaultValue = vd;
|
item.defaultValue = vd;
|
||||||
|
item.disabledOptKeys = [];
|
||||||
if (disableStandard) {
|
if (disableStandard) {
|
||||||
item.disabledOptKeys = ['standardVersionDetection'];
|
item.disabledOptKeys?.add('standardVersionDetection');
|
||||||
}
|
}
|
||||||
|
if (disableRelDate) {
|
||||||
|
item.disabledOptKeys?.add('releaseDateAsVersion');
|
||||||
|
}
|
||||||
|
item.disabledOptKeys =
|
||||||
|
item.disabledOptKeys?.where((element) => element != vd).toList();
|
||||||
}
|
}
|
||||||
return e2;
|
return e2;
|
||||||
}).toList();
|
}).toList();
|
||||||
@@ -352,12 +412,28 @@ abstract class AppSource {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String>? get requestHeaders => null;
|
Future<Map<String, String>?> getRequestHeaders(
|
||||||
|
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
|
||||||
|
bool forAPKDownload = false}) async {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Future<Response> sourceRequest(String url) async {
|
App endOfGetAppChanges(App app) {
|
||||||
if (requestHeaders != null) {
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> sourceRequest(String url,
|
||||||
|
{bool followRedirects = true,
|
||||||
|
Map<String, dynamic> additionalSettings =
|
||||||
|
const <String, dynamic>{}}) async {
|
||||||
|
var requestHeaders =
|
||||||
|
await getRequestHeaders(additionalSettings: additionalSettings);
|
||||||
|
if (requestHeaders != null || followRedirects == false) {
|
||||||
var req = Request('GET', Uri.parse(url));
|
var req = Request('GET', Uri.parse(url));
|
||||||
req.headers.addAll(requestHeaders!);
|
req.followRedirects = followRedirects;
|
||||||
|
if (requestHeaders != null) {
|
||||||
|
req.headers.addAll(requestHeaders);
|
||||||
|
}
|
||||||
return Response.fromStream(await Client().send(req));
|
return Response.fromStream(await Client().send(req));
|
||||||
} else {
|
} else {
|
||||||
return get(Uri.parse(url));
|
return get(Uri.parse(url));
|
||||||
@@ -386,6 +462,16 @@ abstract class AppSource {
|
|||||||
label: tr('trackOnly'),
|
label: tr('trackOnly'),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('versionExtractionRegEx',
|
||||||
|
label: tr('versionExtractionRegEx'),
|
||||||
|
required: false,
|
||||||
|
additionalValidators: [(value) => regExValidator(value)]),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('matchGroupToUse',
|
||||||
|
label: tr('matchGroupToUse'), required: false, hint: '\$0')
|
||||||
|
],
|
||||||
[
|
[
|
||||||
GeneratedFormDropdown(
|
GeneratedFormDropdown(
|
||||||
'versionDetection',
|
'versionDetection',
|
||||||
@@ -412,7 +498,16 @@ abstract class AppSource {
|
|||||||
GeneratedFormSwitch('autoApkFilterByArch',
|
GeneratedFormSwitch('autoApkFilterByArch',
|
||||||
label: tr('autoApkFilterByArch'), defaultValue: true)
|
label: tr('autoApkFilterByArch'), defaultValue: true)
|
||||||
],
|
],
|
||||||
[GeneratedFormTextField('appName', label: tr('appName'), required: false)]
|
[GeneratedFormTextField('appName', label: tr('appName'), required: false)],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('exemptFromBackgroundUpdates',
|
||||||
|
label: tr('exemptFromBackgroundUpdates'))
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('skipUpdateNotifications',
|
||||||
|
label: tr('skipUpdateNotifications'))
|
||||||
|
],
|
||||||
|
[GeneratedFormTextField('about', label: tr('about'), required: false)]
|
||||||
];
|
];
|
||||||
|
|
||||||
// Previous 2 variables combined into one at runtime for convenient usage
|
// Previous 2 variables combined into one at runtime for convenient usage
|
||||||
@@ -424,18 +519,38 @@ abstract class AppSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Some Sources may have additional settings at the Source level (not specific to Apps) - these use SettingsProvider
|
// Some Sources may have additional settings at the Source level (not specific to Apps) - these use SettingsProvider
|
||||||
List<GeneratedFormItem> additionalSourceSpecificSettingFormItems = [];
|
// If the source has been overridden, we expect the user to define one-time values as additional settings - don't use the stored values
|
||||||
|
List<GeneratedFormItem> sourceConfigSettingFormItems = [];
|
||||||
|
Future<Map<String, String>> getSourceConfigValues(
|
||||||
|
Map<String, dynamic> additionalSettings,
|
||||||
|
SettingsProvider settingsProvider) async {
|
||||||
|
Map<String, String> results = {};
|
||||||
|
for (var e in sourceConfigSettingFormItems) {
|
||||||
|
var val = hostChanged
|
||||||
|
? additionalSettings[e.key]
|
||||||
|
: settingsProvider.getSettingString(e.key);
|
||||||
|
if (val != null) {
|
||||||
|
results[e.key] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
String? changeLogPageFromStandardUrl(String standardUrl) {
|
String? changeLogPageFromStandardUrl(String standardUrl) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> getSourceNote() async {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> apkUrlPrefetchModifier(
|
Future<String> apkUrlPrefetchModifier(
|
||||||
String apkUrl, String standardUrl) async {
|
String apkUrl, String standardUrl) async {
|
||||||
return apkUrl;
|
return apkUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool canSearch = false;
|
bool canSearch = false;
|
||||||
|
bool excludeFromMassSearch = false;
|
||||||
List<GeneratedFormItem> searchQuerySettingFormItems = [];
|
List<GeneratedFormItem> searchQuerySettingFormItems = [];
|
||||||
Future<Map<String, List<String>>> search(String query,
|
Future<Map<String, List<String>>> search(String query,
|
||||||
{Map<String, dynamic> querySettings = const {}}) {
|
{Map<String, dynamic> querySettings = const {}}) {
|
||||||
@@ -474,6 +589,76 @@ regExValidator(String? value) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
intValidator(String? value, {bool positive = false}) {
|
||||||
|
if (value == null) {
|
||||||
|
return tr('invalidInput');
|
||||||
|
}
|
||||||
|
var num = int.tryParse(value);
|
||||||
|
if (num == null) {
|
||||||
|
return tr('invalidInput');
|
||||||
|
}
|
||||||
|
if (positive && num <= 0) {
|
||||||
|
return tr('invalidInput');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isTempId(App app) {
|
||||||
|
// return app.id == generateTempID(app.url, app.additionalSettings);
|
||||||
|
return RegExp('^[0-9]+\$').hasMatch(app.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceMatchGroupsInString(RegExpMatch match, String matchGroupString) {
|
||||||
|
if (RegExp('^\\d+\$').hasMatch(matchGroupString)) {
|
||||||
|
matchGroupString = '\$$matchGroupString';
|
||||||
|
}
|
||||||
|
// Regular expression to match numbers in the input string
|
||||||
|
final numberRegex = RegExp(r'\$\d+');
|
||||||
|
// Extract all numbers from the input string
|
||||||
|
final numbers = numberRegex.allMatches(matchGroupString);
|
||||||
|
if (numbers.isEmpty) {
|
||||||
|
// If no numbers found, return the original string
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Replace numbers with corresponding match groups
|
||||||
|
var outputString = matchGroupString;
|
||||||
|
for (final numberMatch in numbers) {
|
||||||
|
final number = numberMatch.group(0)!;
|
||||||
|
final matchGroup = match.group(int.parse(number.substring(1))) ?? '';
|
||||||
|
// Check if the number is preceded by a single backslash
|
||||||
|
final isEscaped = outputString.contains('\\$number');
|
||||||
|
// Replace the number with the corresponding match group
|
||||||
|
if (!isEscaped) {
|
||||||
|
outputString = outputString.replaceAll(number, matchGroup);
|
||||||
|
} else {
|
||||||
|
outputString = outputString.replaceAll('\\$number', number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outputString;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? extractVersion(String? versionExtractionRegEx, String? matchGroupString,
|
||||||
|
String stringToCheck) {
|
||||||
|
if (versionExtractionRegEx?.isNotEmpty == true) {
|
||||||
|
String? version = stringToCheck;
|
||||||
|
var match = RegExp(versionExtractionRegEx!).allMatches(version);
|
||||||
|
if (match.isEmpty) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
matchGroupString = matchGroupString?.trim() ?? '';
|
||||||
|
if (matchGroupString.isEmpty) {
|
||||||
|
matchGroupString = "0";
|
||||||
|
}
|
||||||
|
version = replaceMatchGroupsInString(match.last, matchGroupString);
|
||||||
|
if (version?.isNotEmpty != true) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
return version!;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SourceProvider {
|
class SourceProvider {
|
||||||
// Add more source classes here so they are available via the service
|
// Add more source classes here so they are available via the service
|
||||||
List<AppSource> get sources => [
|
List<AppSource> get sources => [
|
||||||
@@ -481,20 +666,21 @@ class SourceProvider {
|
|||||||
GitLab(),
|
GitLab(),
|
||||||
Codeberg(),
|
Codeberg(),
|
||||||
FDroid(),
|
FDroid(),
|
||||||
IzzyOnDroid(),
|
|
||||||
FDroidRepo(),
|
FDroidRepo(),
|
||||||
Jenkins(),
|
IzzyOnDroid(),
|
||||||
SourceForge(),
|
SourceForge(),
|
||||||
SourceHut(),
|
SourceHut(),
|
||||||
APKMirror(),
|
|
||||||
APKPure(),
|
APKPure(),
|
||||||
// APKCombo(), // Can't get past their scraping blocking yet (get 403 Forbidden)
|
Aptoide(),
|
||||||
|
Uptodown(),
|
||||||
|
APKMirror(),
|
||||||
|
HuaweiAppGallery(),
|
||||||
|
Jenkins(),
|
||||||
Mullvad(),
|
Mullvad(),
|
||||||
Signal(),
|
Signal(),
|
||||||
VLC(),
|
VLC(),
|
||||||
// WhatsApp(), // As of 2023-03-20 this is unusable as the version on the webpage is months out of date
|
WhatsApp(),
|
||||||
TelegramApp(),
|
TelegramApp(),
|
||||||
SteamMobile(),
|
|
||||||
NeutronCode(),
|
NeutronCode(),
|
||||||
HTML() // This should ALWAYS be the last option as they are tried in order
|
HTML() // This should ALWAYS be the last option as they are tried in order
|
||||||
];
|
];
|
||||||
@@ -517,13 +703,16 @@ class SourceProvider {
|
|||||||
}
|
}
|
||||||
AppSource? source;
|
AppSource? source;
|
||||||
for (var s in sources.where((element) => element.host != null)) {
|
for (var s in sources.where((element) => element.host != null)) {
|
||||||
if (RegExp('://${s.host}(/|\\z)?').hasMatch(url)) {
|
if (RegExp(
|
||||||
|
'://${s.allowSubDomains ? '([^\\.]+\\.)*' : '(www\\.)?'}${s.host}(/|\\z)?')
|
||||||
|
.hasMatch(url)) {
|
||||||
source = s;
|
source = s;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (source == null) {
|
if (source == null) {
|
||||||
for (var s in sources.where((element) => element.host == null)) {
|
for (var s in sources.where(
|
||||||
|
(element) => element.host == null && !element.neverAutoSelect)) {
|
||||||
try {
|
try {
|
||||||
s.sourceSpecificStandardizeURL(url);
|
s.sourceSpecificStandardizeURL(url);
|
||||||
source = s;
|
source = s;
|
||||||
@@ -554,11 +743,6 @@ class SourceProvider {
|
|||||||
String standardUrl, Map<String, dynamic> additionalSettings) =>
|
String standardUrl, Map<String, dynamic> additionalSettings) =>
|
||||||
(standardUrl + additionalSettings.toString()).hashCode.toString();
|
(standardUrl + additionalSettings.toString()).hashCode.toString();
|
||||||
|
|
||||||
bool isTempId(App app) {
|
|
||||||
// return app.id == generateTempID(app.url, app.additionalSettings);
|
|
||||||
return RegExp('^[0-9]+\$').hasMatch(app.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<App> getApp(
|
Future<App> getApp(
|
||||||
AppSource source, String url, Map<String, dynamic> additionalSettings,
|
AppSource source, String url, Map<String, dynamic> additionalSettings,
|
||||||
{App? currentApp,
|
{App? currentApp,
|
||||||
@@ -572,6 +756,18 @@ class SourceProvider {
|
|||||||
String standardUrl = source.standardizeUrl(url);
|
String standardUrl = source.standardizeUrl(url);
|
||||||
APKDetails apk =
|
APKDetails apk =
|
||||||
await source.getLatestAPKDetails(standardUrl, additionalSettings);
|
await source.getLatestAPKDetails(standardUrl, additionalSettings);
|
||||||
|
|
||||||
|
if (source.runtimeType != HTML().runtimeType) {
|
||||||
|
// HTML does it separately
|
||||||
|
String? extractedVersion = extractVersion(
|
||||||
|
additionalSettings['versionExtractionRegEx'] as String?,
|
||||||
|
additionalSettings['matchGroupToUse'] as String?,
|
||||||
|
apk.version);
|
||||||
|
if (extractedVersion != null) {
|
||||||
|
apk.version = extractedVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (additionalSettings['versionDetection'] == 'releaseDateAsVersion' &&
|
if (additionalSettings['versionDetection'] == 'releaseDateAsVersion' &&
|
||||||
apk.releaseDate != null) {
|
apk.releaseDate != null) {
|
||||||
apk.version = apk.releaseDate!.microsecondsSinceEpoch.toString();
|
apk.version = apk.releaseDate!.microsecondsSinceEpoch.toString();
|
||||||
@@ -597,13 +793,13 @@ class SourceProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String apkVersion = apk.version.replaceAll('/', '-');
|
|
||||||
var name = currentApp != null ? currentApp.name.trim() : '';
|
var name = currentApp != null ? currentApp.name.trim() : '';
|
||||||
name = name.isNotEmpty ? name : apk.names.name;
|
name = name.isNotEmpty ? name : apk.names.name;
|
||||||
return App(
|
App finalApp = App(
|
||||||
currentApp?.id ??
|
currentApp?.id ??
|
||||||
((!source.appIdInferIsOptional ||
|
(!trackOnly &&
|
||||||
(source.appIdInferIsOptional && inferAppIdIfOptional))
|
(!source.appIdInferIsOptional ||
|
||||||
|
(source.appIdInferIsOptional && inferAppIdIfOptional))
|
||||||
? await source.tryInferringAppId(standardUrl,
|
? await source.tryInferringAppId(standardUrl,
|
||||||
additionalSettings: additionalSettings)
|
additionalSettings: additionalSettings)
|
||||||
: null) ??
|
: null) ??
|
||||||
@@ -612,7 +808,7 @@ class SourceProvider {
|
|||||||
apk.names.author,
|
apk.names.author,
|
||||||
name,
|
name,
|
||||||
currentApp?.installedVersion,
|
currentApp?.installedVersion,
|
||||||
apkVersion,
|
apk.version,
|
||||||
apk.apkUrls,
|
apk.apkUrls,
|
||||||
apk.apkUrls.length - 1 >= 0 ? apk.apkUrls.length - 1 : 0,
|
apk.apkUrls.length - 1 >= 0 ? apk.apkUrls.length - 1 : 0,
|
||||||
additionalSettings,
|
additionalSettings,
|
||||||
@@ -623,14 +819,17 @@ class SourceProvider {
|
|||||||
changeLog: apk.changeLog,
|
changeLog: apk.changeLog,
|
||||||
overrideSource: overrideSource ?? currentApp?.overrideSource,
|
overrideSource: overrideSource ?? currentApp?.overrideSource,
|
||||||
allowIdChange: currentApp?.allowIdChange ??
|
allowIdChange: currentApp?.allowIdChange ??
|
||||||
source.appIdInferIsOptional &&
|
trackOnly ||
|
||||||
inferAppIdIfOptional // Optional ID inferring may be incorrect - allow correction on first install
|
(source.appIdInferIsOptional &&
|
||||||
|
inferAppIdIfOptional) // Optional ID inferring may be incorrect - allow correction on first install
|
||||||
);
|
);
|
||||||
|
return source.endOfGetAppChanges(finalApp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns errors in [results, errors] instead of throwing them
|
// Returns errors in [results, errors] instead of throwing them
|
||||||
Future<List<dynamic>> getAppsByURLNaive(List<String> urls,
|
Future<List<dynamic>> getAppsByURLNaive(List<String> urls,
|
||||||
{List<String> alreadyAddedUrls = const []}) async {
|
{List<String> alreadyAddedUrls = const [],
|
||||||
|
AppSource? sourceOverride}) async {
|
||||||
List<App> apps = [];
|
List<App> apps = [];
|
||||||
Map<String, dynamic> errors = {};
|
Map<String, dynamic> errors = {};
|
||||||
for (var url in urls) {
|
for (var url in urls) {
|
||||||
@@ -638,7 +837,7 @@ class SourceProvider {
|
|||||||
if (alreadyAddedUrls.contains(url)) {
|
if (alreadyAddedUrls.contains(url)) {
|
||||||
throw ObtainiumError(tr('appAlreadyAdded'));
|
throw ObtainiumError(tr('appAlreadyAdded'));
|
||||||
}
|
}
|
||||||
var source = getSource(url);
|
var source = sourceOverride ?? getSource(url);
|
||||||
apps.add(await getApp(
|
apps.add(await getApp(
|
||||||
source,
|
source,
|
||||||
url,
|
url,
|
||||||
|
|||||||
420
pubspec.lock
420
pubspec.lock
@@ -1,47 +1,55 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
android_alarm_manager_plus:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: android_alarm_manager_plus
|
|
||||||
sha256: "80f963d47cb7ab0818144c7b0668aea4c038f9cb8626626e89a4ea77375defb7"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.1"
|
|
||||||
android_intent_plus:
|
android_intent_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: android_intent_plus
|
name: android_intent_plus
|
||||||
sha256: "2c87d8330ba5deef5fe20e77f4d178190b3b24531dce08368030ab4be40a9d4e"
|
sha256: e1c62bb41c90e15083b7fb84dc327fe90396cc9c1445b55ff1082144fabfb4d9
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.1"
|
version: "4.0.3"
|
||||||
android_package_installer:
|
android_package_installer:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: f09c79eee5be3c60b04760143eb954a13fdd07f1
|
resolved-ref: ba2aa7a11edc2649d1d80c25ed9291521262f714
|
||||||
url: "https://github.com/ImranR98/android_package_installer"
|
url: "https://github.com/ImranR98/android_package_installer"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
android_package_manager:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: android_package_manager
|
||||||
|
sha256: b873fe5856f7c442aca9751dac05d117285be9e4de08eb15d1ffb811fd1b688d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.0"
|
||||||
animations:
|
animations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: animations
|
name: animations
|
||||||
sha256: fe8a6bdca435f718bb1dc8a11661b2c22504c6da40ef934cee8327ed77934164
|
sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.7"
|
version: "2.0.11"
|
||||||
|
app_links:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: app_links
|
||||||
|
sha256: "4e392b5eba997df356ca6021f28431ce1cfeb16758699553a94b13add874a3bb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.5.0"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: archive
|
name: archive
|
||||||
sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
|
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.7"
|
version: "3.4.10"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -58,6 +66,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
version: "2.11.0"
|
||||||
|
background_fetch:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: background_fetch
|
||||||
|
sha256: f70b28a0f7a3156195e9742229696f004ea3bf10f74039b7bf4c78a74fbda8a4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -86,10 +102,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cli_util
|
name: cli_util
|
||||||
sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7
|
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.0"
|
version: "0.4.1"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -102,10 +118,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.1"
|
version: "1.18.0"
|
||||||
|
connectivity_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: connectivity_plus
|
||||||
|
sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.2"
|
||||||
|
connectivity_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: connectivity_plus_platform_interface
|
||||||
|
sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.4"
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -118,12 +150,12 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cross_file
|
name: cross_file
|
||||||
sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9"
|
sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.3+4"
|
version: "0.3.3+8"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: crypto
|
name: crypto
|
||||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||||
@@ -142,26 +174,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: cupertino_icons
|
name: cupertino_icons
|
||||||
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
|
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.6"
|
||||||
dbus:
|
dbus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dbus
|
name: dbus
|
||||||
sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263"
|
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.8"
|
version: "0.7.10"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: device_info_plus
|
name: device_info_plus
|
||||||
sha256: "2c35b6d1682b028e42d07b3aee4b98fa62996c10bc12cb651ec856a80d6a761b"
|
sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.0.2"
|
version: "9.1.1"
|
||||||
device_info_plus_platform_interface:
|
device_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -174,18 +206,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dynamic_color
|
name: dynamic_color
|
||||||
sha256: de4798a7069121aee12d5895315680258415de9b00e717723a1bd73d58f0126d
|
sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.6"
|
version: "1.6.9"
|
||||||
easy_localization:
|
easy_localization:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: easy_localization
|
name: easy_localization
|
||||||
sha256: "30ebf25448ffe169e0bd9bc4b5da94faa8398967a2ad2ca09f438be8b6953645"
|
sha256: de63e3b422adfc97f256cbb3f8cf12739b6a4993d390f3cadb3f51837afaefe5
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.3"
|
||||||
easy_logger:
|
easy_logger:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -206,26 +238,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
|
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.1.0"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file
|
name: file
|
||||||
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.4"
|
version: "7.0.0"
|
||||||
file_picker:
|
file_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: "21145c9c268d54b1f771d8380c195d2d6f655e0567dc1ca2f9c134c02c819e0a"
|
sha256: "4e42aacde3b993c5947467ab640882c56947d9d27342a5b6f2895b23956954a6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.3.3"
|
version: "6.1.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -235,10 +267,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_archive
|
name: flutter_archive
|
||||||
sha256: aec85d1da65e5b33a529db00a86df0b8e92bda78088a7cfaeeba5187701d0d85
|
sha256: "004132780d382df5171589ab793e2efc9c3eef570fe72d78b4ccfbfbe52762ae"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "6.0.0"
|
||||||
flutter_fgbg:
|
flutter_fgbg:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -259,18 +291,18 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
|
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "3.0.1"
|
||||||
flutter_local_notifications:
|
flutter_local_notifications:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_local_notifications
|
name: flutter_local_notifications
|
||||||
sha256: "3cc40fe8c50ab8383f3e053a499f00f975636622ecdc8e20a77418ece3b1e975"
|
sha256: "892ada16046d641263f30c72e7432397088810a84f34479f6677494802a2b535"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.1.0+1"
|
version: "16.3.0"
|
||||||
flutter_local_notifications_linux:
|
flutter_local_notifications_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -296,18 +328,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_markdown
|
name: flutter_markdown
|
||||||
sha256: "4b1bfbb802d76320a1a46d9ce984106135093efd9d969765d07c2125af107bdf"
|
sha256: "30088ce826b5b9cfbf9e8bece34c716c8a59fa54461dcae1e4ac01a94639e762"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.17"
|
version: "0.6.18+3"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_plugin_android_lifecycle
|
name: flutter_plugin_android_lifecycle
|
||||||
sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360"
|
sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.15"
|
version: "2.0.17"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -322,10 +354,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: fluttertoast
|
name: fluttertoast
|
||||||
sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c"
|
sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.2.2"
|
version: "8.2.4"
|
||||||
|
gtk:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: gtk
|
||||||
|
sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
hsluv:
|
hsluv:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -346,10 +386,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.2"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -362,26 +402,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf
|
sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.17"
|
version: "4.1.3"
|
||||||
installed_apps:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: installed_apps
|
|
||||||
sha256: "145af8eb6e4e7c830e9888d6de0573ae5c139e8e0742a3e67316e1db21ab6fe0"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.1"
|
|
||||||
intl:
|
intl:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6
|
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.18.0"
|
version: "0.18.1"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -402,10 +434,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
|
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "3.0.0"
|
||||||
markdown:
|
markdown:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -418,26 +450,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.15"
|
version: "0.12.16"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
|
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.5.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.10.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -454,22 +486,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
package_archive_info:
|
nm:
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: package_archive_info
|
|
||||||
sha256: "8f671a29b79d15f192e5e2b0dab9d0bad66b9ee93fb58d4e0afdb62f91a259be"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.1.0"
|
|
||||||
package_info:
|
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: package_info
|
name: nm
|
||||||
sha256: "6c07d9d82c69e16afeeeeb6866fe43985a20b3b50df243091bfc4a4ad2b03b75"
|
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "0.5.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -482,114 +506,122 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
|
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.15"
|
version: "2.1.1"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86"
|
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.27"
|
version: "2.2.2"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297"
|
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.4"
|
version: "2.3.1"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_linux
|
name: path_provider_linux
|
||||||
sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.11"
|
version: "2.2.1"
|
||||||
path_provider_platform_interface:
|
path_provider_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
|
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.6"
|
version: "2.1.1"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
|
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.7"
|
version: "2.2.1"
|
||||||
permission_handler:
|
permission_handler:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: permission_handler
|
name: permission_handler
|
||||||
sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81"
|
sha256: "860c6b871c94c78e202dc69546d4d8fd84bd59faeb36f8fb9888668a53ff4f78"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.4.3"
|
version: "11.1.0"
|
||||||
permission_handler_android:
|
permission_handler_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_android
|
name: permission_handler_android
|
||||||
sha256: "2ffaf52a21f64ac9b35fe7369bb9533edbd4f698e5604db8645b1064ff4cf221"
|
sha256: "2f1bec180ee2f5665c22faada971a8f024761f632e93ddc23310487df52dcfa6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.3.3"
|
version: "12.0.1"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_apple
|
name: permission_handler_apple
|
||||||
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
sha256: "1a816084338ada8d574b1cb48390e6e8b19305d5120fe3a37c98825bacc78306"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.4"
|
version: "9.2.0"
|
||||||
|
permission_handler_html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_html
|
||||||
|
sha256: "11b762a8c123dced6461933a88ea1edbbe036078c3f9f41b08886e678e7864df"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.0+2"
|
||||||
permission_handler_platform_interface:
|
permission_handler_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_platform_interface
|
name: permission_handler_platform_interface
|
||||||
sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9"
|
sha256: d87349312f7eaf6ce0adaf668daf700ac5b06af84338bd8b8574dfbd93ffe1a1
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.11.3"
|
version: "4.0.2"
|
||||||
permission_handler_windows:
|
permission_handler_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_windows
|
name: permission_handler_windows
|
||||||
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
sha256: "1e8640c1e39121128da6b816d236e714d2cf17fac5a105dd6acdd3403a628004"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3"
|
version: "0.2.0"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: petitparser
|
name: petitparser
|
||||||
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
|
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.4.0"
|
version: "6.0.2"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
|
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.4"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd"
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.1.8"
|
||||||
pointycastle:
|
pointycastle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -602,82 +634,90 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: provider
|
name: provider
|
||||||
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
|
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.5"
|
version: "6.1.1"
|
||||||
share_plus:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: share_plus
|
name: share_plus
|
||||||
sha256: ed3fcea4f789ed95913328e629c0c53e69e80e08b6c24542f1b3576046c614e8
|
sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.2"
|
version: "7.2.1"
|
||||||
share_plus_platform_interface:
|
share_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: share_plus_platform_interface
|
name: share_plus_platform_interface
|
||||||
sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981"
|
sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.1"
|
version: "3.3.1"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1"
|
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.2"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076
|
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.1"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_foundation
|
name: shared_preferences_foundation
|
||||||
sha256: f39696b83e844923b642ce9dd4bd31736c17e697f6731a5adf445b1274cf3cd4
|
sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.4"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_linux
|
name: shared_preferences_linux
|
||||||
sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1"
|
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.2"
|
||||||
shared_preferences_platform_interface:
|
shared_preferences_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_platform_interface
|
name: shared_preferences_platform_interface
|
||||||
sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1"
|
sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.1"
|
||||||
shared_preferences_web:
|
shared_preferences_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_web
|
name: shared_preferences_web
|
||||||
sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a"
|
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.2"
|
||||||
shared_preferences_windows:
|
shared_preferences_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_windows
|
name: shared_preferences_windows
|
||||||
sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d
|
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.2"
|
||||||
|
shared_storage:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shared_storage
|
||||||
|
sha256: "7c65a9d64f0f5521256be974cfd74010af12196657cec9f9fb7b03b2f11bcaf6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.0"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -687,42 +727,50 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_span
|
name: source_span
|
||||||
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
|
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.10.0"
|
||||||
|
sprintf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sprintf
|
||||||
|
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
sqflite:
|
sqflite:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqflite
|
name: sqflite
|
||||||
sha256: b4d6710e1200e96845747e37338ea8a819a12b51689a3bcf31eff0003b37a0b9
|
sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.8+4"
|
version: "2.3.0"
|
||||||
sqflite_common:
|
sqflite_common:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite_common
|
name: sqflite_common
|
||||||
sha256: "8f7603f3f8f126740bc55c4ca2d1027aab4b74a1267a3e31ce51fe40e3b65b8f"
|
sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.5+1"
|
version: "2.5.0+2"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.0"
|
version: "1.11.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stream_channel
|
name: stream_channel
|
||||||
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
|
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -735,10 +783,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: synchronized
|
name: synchronized
|
||||||
sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
|
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.0+1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -751,10 +799,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.1"
|
version: "0.6.1"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -775,74 +823,74 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e"
|
sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.12"
|
version: "6.2.2"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: "78cb6dea3e93148615109e58e42c35d1ffbf5ef66c44add673d0ab75f12ff3af"
|
sha256: c0766a55ab42cefaa728cabc951e82919ab41a3a4fee0aaa96176ca82da8cc51
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.37"
|
version: "6.2.1"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
|
sha256: "46b81e3109cbb2d6b81702ad3077540789a3e74e22795eb9f0b7d494dbaa72ea"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.4"
|
version: "6.2.2"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_linux
|
name: url_launcher_linux
|
||||||
sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
|
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.5"
|
version: "3.1.1"
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_macos
|
name: url_launcher_macos
|
||||||
sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1"
|
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.6"
|
version: "3.1.0"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_platform_interface
|
name: url_launcher_platform_interface
|
||||||
sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea
|
sha256: "4aca1e060978e19b2998ee28503f40b5ba6226819c2b5e3e4d1821e8ccd92198"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.3.0"
|
||||||
url_launcher_web:
|
url_launcher_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4
|
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.18"
|
version: "2.2.3"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_windows
|
name: url_launcher_windows
|
||||||
sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422"
|
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.7"
|
version: "3.1.1"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
|
sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.7"
|
version: "4.2.2"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -851,70 +899,78 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.0"
|
||||||
webview_flutter:
|
webview_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: webview_flutter
|
name: webview_flutter
|
||||||
sha256: "789d52bd789373cc1e100fb634af2127e86c99cf9abde09499743270c5de8d00"
|
sha256: "60e23976834e995c404c0b21d3b9db37ecd77d3303ef74f8b8d7a7b19947fc04"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.2"
|
version: "4.4.3"
|
||||||
webview_flutter_android:
|
webview_flutter_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_android
|
name: webview_flutter_android
|
||||||
sha256: "8587d0b4991bd0f223f4b4957101c2c7449f905601571315f4967072498dd3fb"
|
sha256: "161af93c2abaf94ef2192bffb53a3658b2d721a3bf99b69aa1e47814ee18cc96"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.9.1"
|
version: "3.13.2"
|
||||||
webview_flutter_platform_interface:
|
webview_flutter_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_platform_interface
|
name: webview_flutter_platform_interface
|
||||||
sha256: "564ef378cafc1a0e29f1d76ce175ef517a0a6115875dff7b43fccbef2b0aeb30"
|
sha256: dbe745ee459a16b6fec296f7565a8ef430d0d681001d8ae521898b9361854943
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0"
|
version: "2.9.0"
|
||||||
webview_flutter_wkwebview:
|
webview_flutter_wkwebview:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_wkwebview
|
name: webview_flutter_wkwebview
|
||||||
sha256: "3e36a8f564809cb7c257ff4278502b185e2191349df0ddee98837f91805c74b8"
|
sha256: "02d8f3ebbc842704b2b662377b3ee11c0f8f1bbaa8eab6398262f40049819160"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.7.1"
|
version: "3.10.1"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee
|
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.5"
|
version: "5.2.0"
|
||||||
win32_registry:
|
win32_registry:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32_registry
|
name: win32_registry
|
||||||
sha256: e4506d60b7244251bc59df15656a3093501c37fb5af02105a944d73eb95be4c9
|
sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.2"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xdg_directories
|
name: xdg_directories
|
||||||
sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff
|
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.4"
|
||||||
xml:
|
xml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xml
|
name: xml
|
||||||
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
|
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.5.0"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -924,5 +980,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.0.0 <4.0.0"
|
dart: ">=3.2.0 <4.0.0"
|
||||||
flutter: ">=3.10.0"
|
flutter: ">=3.16.0"
|
||||||
|
|||||||
25
pubspec.yaml
25
pubspec.yaml
@@ -1,5 +1,5 @@
|
|||||||
name: obtainium
|
name: obtainium
|
||||||
description: A new Flutter project.
|
description: Get Android App Updates Directly From the Source.
|
||||||
|
|
||||||
# The following line prevents the package from being accidentally published to
|
# The following line prevents the package from being accidentally published to
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||||
@@ -17,10 +17,10 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 0.13.21+185 # When changing this, update the tag in main() accordingly
|
version: 0.15.6+242 # When changing this, update the tag in main() accordingly
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.18.2 <3.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
# Dependencies specify other packages that your package needs in order to work.
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
# To automatically upgrade your package dependencies to the latest versions
|
||||||
@@ -38,7 +38,7 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.5
|
cupertino_icons: ^1.0.5
|
||||||
path_provider: ^2.0.11
|
path_provider: ^2.0.11
|
||||||
flutter_fgbg: ^0.3.0 # Try removing reliance on this
|
flutter_fgbg: ^0.3.0 # Try removing reliance on this
|
||||||
flutter_local_notifications: ^15.1.0+1
|
flutter_local_notifications: ^16.1.0
|
||||||
provider: ^6.0.3
|
provider: ^6.0.3
|
||||||
http: ^1.0.0
|
http: ^1.0.0
|
||||||
webview_flutter: ^4.0.0
|
webview_flutter: ^4.0.0
|
||||||
@@ -46,25 +46,28 @@ dependencies:
|
|||||||
html: ^0.15.0
|
html: ^0.15.0
|
||||||
shared_preferences: ^2.0.15
|
shared_preferences: ^2.0.15
|
||||||
url_launcher: ^6.1.5
|
url_launcher: ^6.1.5
|
||||||
permission_handler: ^10.0.0
|
permission_handler: ^11.0.0
|
||||||
fluttertoast: ^8.0.9
|
fluttertoast: ^8.0.9
|
||||||
device_info_plus: ^9.0.0
|
device_info_plus: ^9.0.0
|
||||||
file_picker: ^5.2.10
|
file_picker: ^6.0.0
|
||||||
animations: ^2.0.4
|
animations: ^2.0.4
|
||||||
android_package_installer:
|
android_package_installer:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/ImranR98/android_package_installer
|
url: https://github.com/ImranR98/android_package_installer
|
||||||
ref: main
|
ref: main
|
||||||
|
android_package_manager: ^0.6.0
|
||||||
share_plus: ^7.0.0
|
share_plus: ^7.0.0
|
||||||
installed_apps: ^1.3.1
|
|
||||||
package_archive_info: ^0.1.0
|
|
||||||
android_alarm_manager_plus: ^3.0.0
|
|
||||||
sqflite: ^2.2.0+3
|
sqflite: ^2.2.0+3
|
||||||
easy_localization: ^3.0.1
|
easy_localization: ^3.0.1
|
||||||
android_intent_plus: ^4.0.0
|
android_intent_plus: ^4.0.0
|
||||||
flutter_markdown: ^0.6.14
|
flutter_markdown: ^0.6.14
|
||||||
flutter_archive: ^5.0.0
|
flutter_archive: ^6.0.0
|
||||||
hsluv: ^1.1.3
|
hsluv: ^1.1.3
|
||||||
|
connectivity_plus: ^5.0.0
|
||||||
|
shared_storage: ^0.8.0
|
||||||
|
crypto: ^3.0.3
|
||||||
|
app_links: ^3.5.0
|
||||||
|
background_fetch: ^1.2.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -76,7 +79,7 @@ dev_dependencies:
|
|||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^2.0.1
|
flutter_lints: ^3.0.0
|
||||||
|
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
android: "ic_launcher"
|
android: "ic_launcher"
|
||||||
|
|||||||
Reference in New Issue
Block a user