Compare commits

...

746 Commits

Author SHA1 Message Date
badf32ff11 Merge pull request #1349 from ImranR98/dev
- Allow existing '/refs' in SourceHut URLs (#1347)
- Revert to showing App URL separately again (#1336)
- F-Droid: Don't pull changelog text if it isn't a raw file from GitHub/GitLab (#1340)
- Add a note on self-hosted instances of sources (#1342)
- versionCode changes
2024-01-27 22:00:54 -05:00
5e40f3264e Merge pull request #1337 from jont4/main
Translation: pt update
2024-01-27 21:58:41 -05:00
71bb6d9410 Merge pull request #1338 from teaminh/main
Update vi.json
2024-01-27 21:58:14 -05:00
731b682fc9 Merge pull request #1339 from DwainZwerg/patch-11
Update de.json
2024-01-27 21:57:03 -05:00
c9751227a5 Flutter upgrade + versionCode changes + increment version 2024-01-27 21:56:26 -05:00
6ef2a26e94 Add a note on self-hosted instances of sources (#1342) 2024-01-27 21:26:32 -05:00
5a8efa2388 F-Droid: Don't pull changelog text if it isn't a raw file from GitHub/GitLab (#1340) 2024-01-27 21:01:44 -05:00
d25895fa28 Revert to showing App URL separately again (#1336) 2024-01-27 20:13:01 -05:00
de09f3ece2 Allow existing '/refs' in SourceHut URLs (#1347) 2024-01-27 20:08:05 -05:00
1135ffb30f Update de.json
Better Translation of "latest"
2024-01-25 13:49:57 +00:00
5379cb31e8 Update vi.json 2024-01-25 18:31:46 +07:00
7d41ab44b7 Minor update 2024-01-24 23:21:42 -03:00
b239f9bd05 Merge pull request #1335 from ImranR98/dev
Don't include prereleases in default app entry
2024-01-24 20:15:35 -05:00
1acc923ec2 Don't include prereleases in default app entry 2024-01-24 20:15:13 -05:00
8d6edfb91f Update release.yml 2024-01-24 20:12:56 -05:00
491b62bb0d Update release.yml 2024-01-24 20:10:02 -05:00
d1413fc478 Update release.yml 2024-01-24 20:05:47 -05:00
761f7039e9 Merge pull request #1334 from ImranR98/dev
- De-emphasize 'reset install status' button to avoid confusion
- Minor bugfix (#1331)
2024-01-24 19:54:16 -05:00
59783a341d Upgrade packages, increment version (1.0.0) 2024-01-24 19:53:31 -05:00
0100fa4236 Merge remote-tracking branch 'origin/main' into dev 2024-01-24 19:52:42 -05:00
f6f9ed68dc Merge pull request #1332 from palopezv/patch-1
Update es.json
2024-01-24 19:50:46 -05:00
8adb5e84d1 Reordered a button for consistency 2024-01-24 19:50:11 -05:00
3940b65156 De-emphasize 'reset install status' button to avoid confusion 2024-01-24 19:49:07 -05:00
98e7f27eab Merge remote-tracking branch 'origin/main' into dev 2024-01-24 19:33:45 -05:00
7cf5cafd75 Minor bugfix (#1331) 2024-01-24 19:28:40 -05:00
d750e35273 Update release.yml 2024-01-24 19:22:56 -05:00
1f12e12130 Update es.json
I'm submitting a revision (call it heartless copy editing :😉:) of the Spanish translation file. This revision corrects several grammatical errors, most are syntactical and semantic, some stylistic in order to correct literal (and intellegible) translations and typos.
2024-01-24 16:11:54 -05:00
4a4fb4716e Merge pull request #1328 from ImranR98/dev
Updated modules
2024-01-23 11:37:29 -05:00
efe6ec0d43 Updated modules 2024-01-23 11:37:08 -05:00
00d605e195 Merge pull request #1325 from ImranR98/dev
Update Flutter submodule and make GitHub actions error out if it doesn't match latest/stable
2024-01-22 12:22:15 -05:00
f06de8d19f Update Flutter submodule and make GitHub actions error out if it doesn't match latest/stable 2024-01-22 12:21:38 -05:00
fac335c849 Merge pull request #1324 from p1gp1g/reproducible-build
Add flutter as a submodule (again)
2024-01-22 12:07:43 -05:00
5bef546ae4 Merge pull request #1323 from DwainZwerg/patch-10
Update de.json
2024-01-21 14:13:07 -05:00
b690cd6a67 Update de.json
Edit german translation
2024-01-21 18:59:22 +00:00
dbee4a2657 Merge pull request #1316 from bluefly000/japanese-translation
Update ja.json
2024-01-21 12:35:30 -05:00
67420f293b Merge pull request #1319 from teaminh/main
Update vi.json
2024-01-21 12:35:23 -05:00
d1d6b0792b Merge pull request #1322 from ygxbnet/patch-1
Update zh.json
2024-01-21 12:35:17 -05:00
2ec039ba0f Update zh.json 2024-01-21 23:38:20 +08:00
4b9b9a10ad Update vi.json 2024-01-21 10:00:00 +07:00
4193d8ada1 Update ja.json 2024-01-21 10:19:35 +09:00
4701b090b0 Update ja.json 2024-01-21 10:18:45 +09:00
8b0c85827c Merge pull request #1315 from ClockGen/fastlane
Added fastlane structure for F-Droid
2024-01-20 19:26:31 -05:00
57e02d1c80 Added initial fastlane structure 2024-01-21 01:51:57 +03:00
4446929b10 Merge pull request #1314 from ImranR98/dev
Remove unnecessary padding from badge graphic
2024-01-20 16:42:25 -05:00
c31f65b0e4 Remove unnecessary padding from badge graphic 2024-01-20 16:42:01 -05:00
f6c3b8dceb Merge pull request #1313 from ImranR98/dev
Increment version
2024-01-20 16:31:49 -05:00
10bd825972 Increment version 2024-01-20 16:31:30 -05:00
303bee7c57 Merge pull request #1312 from ImranR98/dev
Bugfix: don't show 'Apps Updated' toast when nothing was updated (#1307), Disable app page buttons while app info is being updated
2024-01-20 16:29:47 -05:00
369ae43062 Disable app page buttons while app info is being updated 2024-01-20 16:28:54 -05:00
62dc7fd88b Bugfix: don't show 'Apps Updated' toast when nothing was updated (#1307) 2024-01-20 16:18:19 -05:00
4aace63cd7 Merge pull request #1311 from ImranR98/dev
Removed unnecessary READ_EXTERNAL_STORAGE permission (#1309)
2024-01-20 16:05:33 -05:00
5d588a05a7 Removed unnecessary READ_EXTERNAL_STORAGE permission (#1309) 2024-01-20 16:04:17 -05:00
cab6f9d026 Merge pull request #1310 from ImranR98/dev
"Standardize" translation files + use machine-translations as placeholders for missing translations
2024-01-20 15:42:24 -05:00
fc94d553eb Merge remote-tracking branch 'origin/main' into dev 2024-01-20 15:41:44 -05:00
2952345585 Use machine-translations as placeholders for missing translations 2024-01-20 15:39:10 -05:00
becc3828b7 Remove some unused translations 2024-01-20 14:58:37 -05:00
2a62248991 "Standardize" translation files 2024-01-20 14:45:28 -05:00
eb2b31219c Merge pull request #1306 from gidano/main
Update hu.json
2024-01-20 14:31:51 -05:00
55053f9627 Merge pull request #1304 from bluefly000/japanese-translation
Update ja.json
2024-01-20 14:31:19 -05:00
5ff6c64938 Update hu.json 2024-01-20 10:35:18 +01:00
c1c517cf38 Update ja.json 2024-01-20 18:21:00 +09:00
b3de2e5d6b Merge pull request #1303 from ImranR98/dev
- Better partial APK hash as pseudo-version (related to #1101)
- Support Direct APK Links (#1292)
- Easier to understand version detection settings
- Bugfix in automatic source selection for URL input
- Various UI improvements and wording changes
- Add invert option to apk filter regex 
- Filter out fdroid APKs from default Obtainium entry (for later)
2024-01-19 23:41:27 -05:00
0183070b2b Merge remote-tracking branch 'origin/main' into dev 2024-01-19 23:38:23 -05:00
089bbc3ee9 Merge pull request #1291 from Bardesss/patch-1
Update nl.json
2024-01-19 23:29:49 -05:00
b085801c2c Merge pull request #1298 from jont4/main
Translation: Some fixes and updates in pt.json
2024-01-19 23:29:40 -05:00
ad4cae4288 Add F-Droid build to GitHub action 2024-01-19 23:29:22 -05:00
95c285e1f7 Filter out fdroid APKs from default Obtainium entry 2024-01-19 23:25:00 -05:00
c31a1912a5 Add invert option to apk filter regex 2024-01-19 23:22:00 -05:00
99da1f8481 Upgrade packages, increment version 2024-01-19 20:41:07 -05:00
bf6b3a7da0 UI bug 2024-01-19 20:36:40 -05:00
4ab4f58f65 Cleaner 'add app' page 2024-01-19 20:35:44 -05:00
29a76ac8e6 Removed unused divider and commented code on settings page 2024-01-19 19:59:30 -05:00
e2761ce284 UI improvements on app page 2024-01-19 19:57:28 -05:00
dc45334cb9 Support Direct APK Links (#1292) 2024-01-19 18:54:01 -05:00
56052bfd79 Bugfix in version detection toggles 2024-01-19 00:43:02 -05:00
f5067d636a More UI tweaks 2024-01-19 00:21:57 -05:00
b173b1300a Clearer wording 2024-01-19 00:12:43 -05:00
15a34e53bf Bugfix in automatic source selection for URL input 2024-01-19 00:00:47 -05:00
183fb26988 Clearer version detection settings 2024-01-18 23:46:23 -05:00
2da9a8cd59 Remove unnecessary print() 2024-01-18 20:45:34 -05:00
bb07000280 Better partial APK hash as pseudo-version (related to #1101) 2024-01-18 20:45:06 -05:00
3a46ad2957 Some fixes and updates 2024-01-16 15:48:59 -03:00
6b631d119c Merge branch 'ImranR98:main' into main 2024-01-16 15:24:02 -03:00
051762fcc1 Update nl.json
Added new strings.
2024-01-15 11:38:00 +00:00
595d5dc283 Merge pull request #1289 from ImranR98/dev
Added badge graphic (#1287)
2024-01-14 15:14:41 -05:00
1d328f07e2 Merge remote-tracking branch 'origin/main' into dev 2024-01-14 15:14:01 -05:00
b1c8ac6f2a Added badge graphic (#1287) 2024-01-14 15:09:16 -05:00
da619d37f7 Merge pull request #1288 from iDazai/main
Update de.json
2024-01-14 14:56:54 -05:00
236c4722e5 Update de.json
added 2 missing strings, translated new strings
2024-01-14 15:36:08 +01:00
92f59116a0 Merge remote-tracking branch 'origin/main' into dev 2024-01-14 01:37:17 -05:00
c28040f0cb Merge remote-tracking branch 'origin/main' into dev 2024-01-14 01:36:43 -05:00
e8b9654320 Update release.yml 2024-01-14 01:36:35 -05:00
be1a793a37 Update release.yml 2024-01-14 01:34:32 -05:00
440720afb6 Update release.yml 2024-01-14 01:31:42 -05:00
900d3e734e Update release.yml 2024-01-14 01:27:13 -05:00
0bf096abb5 Temporary GitHub action change 2024-01-14 01:26:17 -05:00
632efb9e22 Merge remote-tracking branch 'origin/main' into dev 2024-01-14 01:24:50 -05:00
bfc506e450 Merge pull request #1285 from ImranR98/dev
- Improve loading time (parallelize version detection)
- Newest asset upload date as release date for GitHub (#1284)
- Enable 'start on boot' for BG task (#1234)
- Remove the need to hardcode Obtainium's version number
2024-01-14 01:24:25 -05:00
ffe612708c Remove the need to hardcode Obtainium's version number 2024-01-14 01:22:35 -05:00
5d161160aa Increment version 2024-01-14 00:56:25 -05:00
eadf3e5a29 Enable 'start on boot' for BG task (#1234) 2024-01-14 00:22:25 -05:00
6b6b4084a0 Newest asset upload date as release date for GitHub (#1284) 2024-01-14 00:14:44 -05:00
30a4633f72 Improve loading time (parallelize version detection) 2024-01-13 23:26:57 -05:00
d44139dd72 Merge pull request #1281 from ImranR98/dev
Allow entire app config to be shared as link (see https://github.com/ImranR98/apps.obtainium.imranr.dev/issues/5)
2024-01-12 23:47:31 -05:00
60869a0490 Allow entire app config to be shared as link (see https://github.com/ImranR98/apps.obtainium.imranr.dev/issues/5) 2024-01-12 23:46:50 -05:00
102be5fa2e Merge pull request #1280 from ImranR98/dev
- Fix form input issues (related to #1272)
- HTML Source bugfix (related to #1259)
2024-01-12 22:32:49 -05:00
a25c04b390 Update packages, increment version, dart fix 2024-01-12 22:32:33 -05:00
d46f0a1c33 HTML Source bugfix (related to #1259) 2024-01-12 22:16:06 -05:00
5c7f5a99e1 Attempt to fix form input issues for recursive forms (NOT FULLY TESTED) 2024-01-12 21:51:48 -05:00
d949a80f19 Merge pull request #1279 from gidano/main
Update hu.json
2024-01-12 20:29:33 -05:00
6b5d4e2027 Merge pull request #1278 from mehdeej/main
Update fa.json
2024-01-12 20:29:25 -05:00
199d071ee8 Update hu.json 2024-01-12 15:45:26 +01:00
5b78de5d61 Merge branch 'ImranR98:main' into main 2024-01-12 05:46:33 -03:00
db4f13e2e1 Update fa.json 2024-01-12 07:32:52 +03:30
5e869b7e03 Update release.yml 2024-01-11 21:58:48 -05:00
34fee36132 Merge pull request #1277 from ImranR98/dev
- Fix APKPure downloads (#1250)
- F-Droid bugfix (#1264)
- Allow versionCode to be used as the version for comparison (#1269)
- Add custom header support to HTML (#709, #1272)
- Disabled Mullvad source (#1276)
- Bugfixes: Don't make URLs lowercase, never auto-select Jenkins
2024-01-11 21:44:36 -05:00
ea09bc36a5 Merge branch 'main' into dev 2024-01-11 21:44:24 -05:00
f7e0678cb2 Remove unused variable 2024-01-11 21:39:41 -05:00
223ae378a9 Update packages, increment version 2024-01-11 21:39:26 -05:00
6f52a48991 Merge remote-tracking branch 'origin/main' into dev 2024-01-11 21:36:15 -05:00
0280935955 Merge pull request #1268 from bluefly000/japanese-translation
Update ja.json
2024-01-11 21:36:03 -05:00
eebc7d9ab3 Merge pull request #1271 from pmtpro/main
update vi.json
2024-01-11 21:35:53 -05:00
aa7b5652ff Fix APKPure downloads (#1250) 2024-01-11 21:34:46 -05:00
ec6683a198 Bugfixes: Don't make URLs lowercase, never auto-select Jenkins 2024-01-11 21:28:15 -05:00
05372924a0 F-Droid bugfix (#1264) 2024-01-11 21:16:25 -05:00
e63c1399dd Allow versionCode to be used as the version for comparison (#1269) 2024-01-11 20:10:20 -05:00
4fcad92e1d Disabled Mullvad source (#1276) 2024-01-11 19:50:51 -05:00
e18e7298bc GeneratedFormSubForm UI improvements 2024-01-11 19:43:17 -05:00
76a6a509cd Header usage bugfix 2024-01-11 19:23:59 -05:00
7a03561ff6 Add custom header support to HTML (#709, #1272) 2024-01-11 19:01:17 -05:00
70e54ce14a F-Droid small fix 2024-01-10 18:51:22 -05:00
b5f86f0e79 update vi.json 2024-01-10 10:20:59 +07:00
4ebca49ef7 Update ja.json 2024-01-09 22:26:11 +09:00
49cfa95463 Merge pull request #1265 from ImranR98/dev
- Multi-host support + add '.net' host to APKPure source (#1250)
- HTML link parsing bugfix (#1259)
- APKMirror version extraction bugfix (#1264)
2024-01-08 19:28:31 -05:00
4600ab0593 Increment version 2024-01-08 19:20:08 -05:00
7f2ca98bde Multi-host support + add '.net' host to APKPure source (#1250) 2024-01-08 19:17:50 -05:00
6511485bcf APKMirror version extraction bugfix (#1264) 2024-01-08 18:33:24 -05:00
fd22113e44 HTML link parsing bugfix (#1259) 2024-01-08 18:18:57 -05:00
f2ae078723 Merge branch 'ImranR98:main' into main 2024-01-08 18:20:27 -03:00
c3dacf2b9b Translation: tiny bug 2024-01-08 18:18:57 -03:00
e8580dc1d5 Merge pull request #1257 from ImranR98/dev
Add F-Droid changelogs (#1255), Fix corrupt pt.json (#1256)
2024-01-07 21:59:17 -05:00
daffff7eb0 Add F-Droid changelogs (#1255) 2024-01-07 21:57:23 -05:00
751fda5e37 Increment version 2024-01-07 21:45:41 -05:00
1e38abc500 Fix corrupt pt.json (#1256) 2024-01-07 21:40:56 -05:00
8e7137815b Merge pull request #1254 from ImranR98/dev
URL parsing bugfix for HTML source (#1253)
2024-01-07 12:37:51 -05:00
4226ee453f Merge remote-tracking branch 'origin/main' into dev 2024-01-07 12:37:14 -05:00
568a110443 Merge pull request #1249 from LilligantMatsuri/main
Update Chinese translation
2024-01-07 12:37:00 -05:00
7fd7a6ca8d Merge pull request #1252 from jont4/main
Translation: many fixes in PT translation
2024-01-07 12:36:47 -05:00
3092c854ff Increment version 2024-01-07 12:36:20 -05:00
a9566f4b23 Remove redundant function call 2024-01-07 12:34:09 -05:00
7a5aa3c11d URL parsing bugfix for HTML source (#1253) 2024-01-07 12:33:06 -05:00
f2454bb028 Translation: many fixes in PT translation 2024-01-07 12:28:24 -03:00
2c1023a6fd Update zh.json
- Add & translate new strings
- Correct inaccurate translations

Signed-off-by: Matsuri <matsuri@vmoe.info>
2024-01-07 16:55:00 +08:00
9187ac6684 Merge pull request #1248 from ImranR98/dev
Removed Steam Source (existing apps auto-switched to HTML) (#1244) + Bugfixes
2024-01-06 23:39:11 -05:00
9640e43bc5 Upgrade packages, increment version 2024-01-06 23:38:36 -05:00
09b24b36f2 Removed Steam Source (existing apps auto-switched to HTML) (#1244) + bugfixes 2024-01-06 23:37:27 -05:00
06a761c0d0 Merge pull request #1247 from CertainBot/main
Update Spanish
2024-01-06 22:46:06 -05:00
6ff7d7fcd7 Merge pull request #1246 from bluefly000/japanese-translation
Update ja.json
2024-01-06 22:45:47 -05:00
0f27c79303 Merge pull request #1243 from pmtpro/patch-1
Update vi.json
2024-01-06 22:45:25 -05:00
9961ad081f Update Spanish
- English: new strings, typos, minor changes
- Español: strings nuevos, correcciones, algunas mejoras
2024-01-07 02:55:18 +01:00
02056b4f78 Update ja.json 2024-01-06 01:05:52 +09:00
0492588c25 Update vi.json 2024-01-04 09:26:37 +07:00
ad8463ac8b Update README.md 2024-01-03 20:28:49 -05:00
7843de240a Merge pull request #1241 from ImranR98/dev
Don't use numpad for version extraction match group
2024-01-03 20:19:18 -05:00
0037a08017 Don't use numpad for version extraction match group 2024-01-03 20:18:59 -05:00
92ba432992 Merge pull request #1240 from ImranR98/dev
- Remove reference to old plugin to avoid crashes (#1221)
- Confirmation box for link-based imports (#1232)
- Construct extracted versions from match groups (#1235)
- Fix the WebView 'cleartext' error caused by HTTP sites (#1235)
- Make GitHub's (and Codeberg's) "fallback" work with the APK filter (#1236)
- Make version extraction universal (#1237)
- HTML Bugfix (#1238)
2024-01-03 20:16:19 -05:00
3a521f4014 Update packages, increment version 2024-01-03 20:13:47 -05:00
e8bbc9799c Fix the WebView 'cleartext' error caused by HTTP sites 2024-01-03 20:11:52 -05:00
96a48b4813 Construct extracted versions from match groups (#1235) 2024-01-03 20:02:44 -05:00
355450e82d Merge duplicate version extraction code into one function 2024-01-03 19:28:45 -05:00
99307c5b0c Make version extraction universal (#1237) 2024-01-03 19:20:03 -05:00
ace8be6514 Remove reference to old plugin to avoid crashes (#1221) 2024-01-03 19:04:36 -05:00
1b538931b1 Confirmation box for link-based imports (#1232) 2024-01-03 19:03:05 -05:00
7ccd08ee2b Make GitHub's (and Codeberg's) "fallback" work with the APK filter (#1236) 2024-01-03 18:32:09 -05:00
104beed594 HTML Bugfix (#1238) 2024-01-03 18:25:02 -05:00
e8df594edb Merge pull request #1233 from iDazai/main
added missing strings in some languages, translated for DE
2024-01-03 18:06:29 -05:00
6e922a84f8 Merge pull request #1230 from re7gog/re7gog
Fixes and system font
2024-01-03 18:06:04 -05:00
b74bf86277 Corrections 2024-01-03 14:39:33 +03:00
6cbe50c9ef Update vi.json 2024-01-02 07:57:32 +01:00
14fcca040b Update tr.json 2024-01-02 07:57:21 +01:00
7bb545cff9 Update sv.json 2024-01-02 07:57:07 +01:00
f52b1a246c Update pl.json 2024-01-02 07:56:41 +01:00
273dfd89cc Update nl.json 2024-01-02 07:56:17 +01:00
52d72d287b Update ja.json 2024-01-02 07:56:01 +01:00
e8d8fcbe69 Update hu.json 2024-01-02 07:55:44 +01:00
30c6c1afd3 Update fr.json 2024-01-02 07:55:16 +01:00
c8c8093b3a Update fa.json 2024-01-02 07:55:03 +01:00
c970b2cf2e Update es.json 2024-01-02 07:54:23 +01:00
0815283d31 Update bs.json 2024-01-02 07:53:32 +01:00
55ee2d93b8 Update de.json
added missing strings and translated
2024-01-02 07:53:07 +01:00
c9e17ed42b Merge branch 'main' into re7gog 2024-01-01 18:50:24 +03:00
76a91b7fe0 Don't load font if loaded 2024-01-01 18:42:38 +03:00
f9b3169b6a Update README.md 2024-01-01 07:55:00 -05:00
4f42b5a3ee Merge pull request #1228 from ImranR98/dev
Save Search Preferences (#1226)
2023-12-31 23:42:22 -05:00
5d9645eaff Save Search Preferences (#1226) 2023-12-31 23:41:46 -05:00
c032808d82 Merge pull request #1227 from ImranR98/dev
- Add www subdomain support for various sources (#1222)
- Disable standard version detection for WhatsApp (should never have been enabled)
2023-12-31 23:29:31 -05:00
282c94266e Merge remote-tracking branch 'origin/main' into dev 2023-12-31 23:28:09 -05:00
e2f7d52bee Increment version 2023-12-31 23:27:23 -05:00
6317f0162a Disable standard version detection for WhatsApp (should never have been enabled) 2023-12-31 22:05:50 -05:00
436a6310d7 Add www subdomain support for various sources (#1222) 2023-12-31 15:59:44 -05:00
0f74195255 Replace okay with ok 2023-12-31 19:42:28 +03:00
5ba33786ab System font and newer dependencies 2023-12-31 19:33:33 +03:00
70fcfc1753 Merge remote-tracking branch 'origin/main' into dev 2023-12-30 10:50:22 -05:00
1e34048c0c Merge pull request #1218 from LilligantMatsuri/main
Update Chinese translation
2023-12-30 10:50:12 -05:00
45b1b23262 Merge pull request #1215 from oSoWoSo/cz
Update cs.json
2023-12-30 10:50:04 -05:00
07842ace4e Merge pull request #1217 from gidano/main
Update hu.json
2023-12-30 10:49:34 -05:00
8e75280093 Merge pull request #1220 from ImranR98/dev
Fix intermediate link bug (#1219)
2023-12-30 10:49:21 -05:00
029b9ef498 Fix intermediate link bug (#1219) 2023-12-30 10:49:05 -05:00
51970abce7 Update zh.json
- Add & translate new strings
- Minor improvements

Signed-off-by: Matsuri <matsuri@vmoe.info>
2023-12-30 16:55:01 +08:00
f6faa19e5f Update hu.json 2023-12-30 09:45:46 +01:00
dd7217ca54 Update cs.json 2023-12-30 04:31:05 +01:00
6e13457eb2 Merge pull request #1210 from iDazai/main
Update de.json
2023-12-29 12:44:09 -06:00
3b319ee19b Merge pull request #1206 from gidano/main
Update hu.json
2023-12-29 12:44:02 -06:00
8ea8daa811 Merge pull request #1212 from ImranR98/dev
HTML intermediate link bugfix (#1211)
2023-12-29 12:43:49 -06:00
eb29b908c2 Increment version 2023-12-29 13:43:06 -05:00
9935cb482e HTML intermediate link bugfix (#1211) 2023-12-29 13:39:56 -05:00
0d6e7181cf Update de.json 2023-12-29 17:35:25 +01:00
d225650e15 Update hu.json 2023-12-29 10:12:13 +01:00
bfe09791d5 Merge pull request #1205 from ImranR98/dev
Bugfix in HTML intermediate link UI (incomplete)
2023-12-28 18:24:03 -06:00
85d103f3f6 Bugfix in HTML intermediate link UI (incomplete) 2023-12-28 19:16:16 -05:00
9e6dbe2465 Merge pull request #1189 from iDazai/main
Update de.json
2023-12-28 16:09:12 -06:00
355e5ccda6 Merge branch 'main' into main 2023-12-28 16:09:03 -06:00
a5f6f05e10 Merge pull request #1190 from unbranched/patch-4
Update it.json
2023-12-28 16:08:29 -06:00
db0d35d80b Merge pull request #1196 from LucasTavaresA/main
Update pt.json
2023-12-28 16:08:21 -06:00
6fca2a3931 Merge pull request #1197 from CertainBot/main
Update Spanish 3rd pass
2023-12-28 16:08:11 -06:00
0305a42b02 Merge branch 'main' into main 2023-12-28 16:08:04 -06:00
77d81716ed Switch to a New Background Task Plugin (#608), Add Link Text Filter for HTML Links (#1182), Add Support for Multiple Intermediate Links to HTML Source (#1204)
- Switch to a New Background Task Plugin (#608)
- Add Link Text Filter for HTML Links (#1182)
- Add Support for Multiple Intermediate Links to HTML Source
2023-12-28 17:06:11 -05:00
1fc8ee6fee Fix all 2023-12-28 17:50:12 +03:00
3e54e80eb6 Update it.json
Added missing strings.
2023-12-27 09:15:22 +00:00
3c9bb63d32 Update es.json
- English: updated new strings, few minor corrections.
- Español: actualizacion de frases nuevas, correcciones menores.
2023-12-25 16:04:51 +01:00
617ab9efab Update pt.json 2023-12-25 10:45:12 -03:00
bc574097e2 Update it.json 2023-12-25 09:40:29 +00:00
4cc64dc233 Update de.json
translated three new strings, fixed on with hyphen missing
2023-12-25 10:30:44 +01:00
45fa0a165a Merge pull request #1188 from ImranR98/dev
Typo, minor error reporting bug
2023-12-24 21:08:06 -06:00
0e5c07a078 Update packages, increment version 2023-12-24 22:07:27 -05:00
601a742c71 Merge remote-tracking branch 'origin/main' into dev 2023-12-24 22:04:51 -05:00
c972401b6e Merge pull request #1186 from CertainBot/main
Update Spanish 2nd pass
2023-12-24 21:04:35 -06:00
024e81cf01 bug 2023-12-24 22:03:29 -05:00
975ed402d5 Merge remote-tracking branch 'origin/main' into dev 2023-12-24 22:03:05 -05:00
b9e8083744 Merge pull request #1184 from re7gog/re7gog
Shizuku and Root
2023-12-24 21:02:48 -06:00
bb859708bc Typo 2023-12-24 21:14:18 -05:00
3cf2c221ac Update Spanish 2nd pass
- English: update new strings, homogenize expressions.
-Español: actualizar frases nuevas, homogeneizar expresiones.
2023-12-24 21:00:37 +01:00
6edd7edcd2 No delay when already has permission 2023-12-24 19:43:34 +03:00
4e26a02d78 Merge 2023-12-24 18:49:33 +03:00
bb36a57053 Merge branch 'main' into re7gog 2023-12-24 18:37:11 +03:00
b291c800f1 Now it looks good 2023-12-24 18:26:11 +03:00
b63a798d86 Merge pull request #1180 from ImranR98/dev
- Fix installed version bug when importing apps (#1179)
- Share button now shares Obtainium "protocol" links
2023-12-22 20:14:07 -06:00
eacf3777a4 Update packages, increment version 2023-12-22 21:13:15 -05:00
a5a7436bb1 Share button now shares Obtainium "protocol" links 2023-12-22 21:12:07 -05:00
2a4cc35df7 Fix installed version bug when importing apps (#1179) 2023-12-22 21:10:11 -05:00
cdccf58b76 Merge remote-tracking branch 'origin/dev' 2023-12-22 21:00:09 -05:00
27300383a1 Add Wiki link 2023-12-22 20:59:49 -05:00
375b9bce30 Working shizuku installer, need refactor 2023-12-22 16:27:54 +03:00
b6b8db48df request Shizuku permission 2023-12-22 16:08:41 +03:00
36e6c267b9 Shizuku dependencies 2023-12-22 14:21:18 +03:00
13066b3b4a Merge pull request #1177 from ImranR98/dev
- Add "Don't Sort" option to the HTML Source (#1168 )
- Fix syntax error in translation file (#1169)
- Parallel download option (#1175)
2023-12-21 15:02:58 -06:00
ccbe9d00c8 Update packages, increment version 2023-12-21 16:01:42 -05:00
ce291582cb Add "Don't Sort" option to the HTML Source (#1168 ) 2023-12-21 15:57:59 -05:00
bb37bc3b51 Fix syntax error in translation file (#1169) 2023-12-21 15:46:38 -05:00
5a7747acd1 Merge remote-tracking branch 'origin/main' into dev 2023-12-21 15:45:03 -05:00
1bc2ec9461 Parallel download option (#1169) 2023-12-21 15:44:38 -05:00
2b977fc2b0 Merge pull request #1173 from drizzt/fix_1142
Resize adaptative icon to 108x108dp (#1142)
2023-12-21 14:24:21 -06:00
de60c4ee9e Root install 2023-12-20 16:21:12 +03:00
de67e40c00 Add installMethod in settings 2023-12-20 11:57:56 +03:00
cc4b016c64 Resize adaptative icon to 108x108dp (#1142)
Android documentation [1] explicitly specify that adaptive icons must be
108x108dp.
Having bigger Adaptive icons triggers a bug in Android 14 QPR1 that
prevent to install apps from apk directly when the icons are bigger.

[1] https://developer.android.com/develop/ui/views/launch/icon_design_adaptive
2023-12-20 00:00:45 +01:00
f64f561d6f Merge pull request #1167 from ImranR98/dev
Update issue templates
2023-12-17 16:00:25 -06:00
80bddf8a6b Update issue templates 2023-12-17 16:59:23 -05:00
cbaaec961c Merge pull request #1166 from ImranR98/dev
- Get real author name for F-Droid (#1076)
- Always add Track-only apps with random ID (#1138)
- Minor UI fix (#1162)
- Fix 'verify latest' option for GitHub (#1163)
2023-12-17 13:57:34 -06:00
5477b3f936 Update packages, increment version, run dart fix 2023-12-17 14:54:54 -05:00
fd59a93ede Get real author name for F-Droid (#1076) 2023-12-17 14:47:22 -05:00
cd316b7138 Always add Track-only apps with random ID (#1138) 2023-12-17 14:36:06 -05:00
d1955192ed Minor UI fix (#1162) 2023-12-17 14:14:46 -05:00
9beb839bf4 Fix 'verify latest' option for GitHub (#1163) 2023-12-17 14:13:26 -05:00
29ea303093 Merge pull request #1161 from ImranR98/dev
- Custom link support (#368, #918)
- Export settings (#1157)
- Use public GitLab search API (#1147)
- Fix unauthorized error (#1153)
2023-12-16 03:12:10 -06:00
feff6751ca Merge remote-tracking branch 'origin/main' into dev 2023-12-16 04:08:56 -05:00
ca33fdf752 Merge pull request #1133 from CertainBot/patch-1
Update Spanish translation
2023-12-16 03:07:03 -06:00
fdcdfe89d6 Merge pull request #1149 from gidano/main
Update hu.json
2023-12-16 03:06:53 -06:00
48ed2115a7 Merge pull request #1150 from Erudaro/main
Update bs.json
2023-12-16 03:06:44 -06:00
65988f4e08 Link UI bugfix 2023-12-16 04:02:10 -05:00
ede65eda6c bug 2023-12-16 03:47:29 -05:00
5da56acac8 bug 2023-12-16 03:45:15 -05:00
5720c55301 bugs 2023-12-16 03:43:55 -05:00
ffefa4b30e Update packages, increment version 2023-12-16 02:57:08 -05:00
80e4986b23 Export settings (#1157) 2023-12-16 02:55:05 -05:00
dc92ccda0a Use public GitLab search API (#1147) 2023-12-16 02:01:43 -05:00
f9bab18076 Fix unauthorized error (#1153) 2023-12-16 01:44:02 -05:00
2dec52e221 Bugfix 2023-12-16 01:24:43 -05:00
7413f693d7 JSON import link support (#368) 2023-12-16 00:31:32 -05:00
415460df75 Custom link support (#918) 2023-12-15 23:37:04 -05:00
125a194468 Update bs.json 2023-12-10 22:07:14 +01:00
32e9afbf36 Update hu.json 2023-12-10 17:16:08 +01:00
3eca704f4a Merge pull request #1137 from ImranR98/dev
- Add regex filter to all select dialogs (#1110)
- Let users store a custom note per app (#1126)
- Better GitLab error message (#1106)
2023-12-07 20:57:48 -06:00
9c95129311 Better GitLab error message (#1106) 2023-12-07 21:55:35 -05:00
bf34c1bcdb Let users store a custom note per app (#1126) 2023-12-07 21:47:56 -05:00
284c687d77 Add regex filter to all select dialogs (#1110) 2023-12-07 21:38:16 -05:00
09afb5a3f5 Upgrade packages, increment version 2023-12-07 21:00:51 -05:00
0138721451 Merge pull request #1118 from DwainZwerg/patch-9
Update de.json
2023-12-07 19:56:13 -06:00
2d5f610941 Merge branch 'main' into patch-9 2023-12-07 19:56:07 -06:00
864fa7762b Merge pull request #1119 from iDazai/patch-2
Update de.json
2023-12-07 19:55:16 -06:00
4fde38ee6a Merge pull request #1125 from Daviteusz/weblate-obtainium-translate
Update Polish translation
2023-12-07 19:55:05 -06:00
6cdf0f10d4 Merge pull request #1136 from mehdeej/main
Update (Persian) fa.json
2023-12-07 19:54:56 -06:00
b66592c25f Update (Persian) fa.json 2023-12-07 20:58:26 +03:30
43616c566d Update es.json
- Spanish translation updated. Typos corrected.
- Traducción al castellano actualizada. Corrección de erratas.
2023-12-06 00:35:30 +01:00
62f1dc17a0 Update Polish translations 2023-11-28 18:08:57 +01:00
0e9a8a937a Update de.json
translated newly added strings
2023-11-25 14:06:50 +01:00
9a86b245ce Update de.json 2023-11-25 12:52:32 +00:00
64533f7a3f Fix GitHub actions build error 2023-11-24 20:27:01 -05:00
0b7de8d387 Merge pull request #1115 from ImranR98/dev
Support for fixed APK URL in HTML source (#1101), Fix IzzyDroid release finding bug (#1104), More search options (#1107), Third party GitLab search (#1108), GitHub: Don't infer AppID if many found (#1112), Add www support back to APKMirror (#1114)
2023-11-24 19:16:28 -06:00
8eba4860fe Don't apply #1101 feature to existing apps 2023-11-24 20:14:16 -05:00
b53e2f57e6 Fix local build error 2023-11-24 19:54:03 -05:00
e1e834297b Merge remote-tracking branch 'origin/main' into dev 2023-11-24 19:35:23 -05:00
e37dc6e341 Update packages, increment version 2023-11-24 19:34:22 -05:00
c91c896854 Fix IzzyDroid release finding bug (#1104) 2023-11-24 19:32:46 -05:00
7e5dfa03d6 GitHub: Don't infer AppID if many found (#1112) 2023-11-24 19:24:19 -05:00
1a4ec3f049 Add www support back to APKMirror (#1114) 2023-11-24 19:12:42 -05:00
756763fcbe Third party GitLab search (#1108) 2023-11-24 18:58:10 -05:00
93036c4e67 Make fixed APK URL support (HTML) default (#1101) 2023-11-24 18:15:04 -05:00
15bf972ef6 UI fixes for previous commit (#1107) 2023-11-24 18:12:27 -05:00
bcb4567382 More search options (#1107) 2023-11-24 17:59:59 -05:00
3890c4ffb9 Support for fixed APK URL in HTML source (#1101) 2023-11-24 16:39:44 -05:00
c8d82f24a8 Merge pull request #1103 from ImranR98/dev
Increment versionCode (#1098) + ran 'dart fix'
2023-11-19 15:02:42 -06:00
367e740a9c Increment versionCode (#1098) + ran 'dart fix' 2023-11-19 16:02:16 -05:00
5b5a51bdad Merge pull request #1098 from gidano/main
Update hu.json
2023-11-19 14:58:46 -06:00
7fc030a53e Merge pull request #1099 from DwainZwerg/patch-8
Update de.json
2023-11-19 14:58:38 -06:00
65087fb5f8 Update de.json
Small improvements
2023-11-19 14:11:05 +00:00
d5e789af3c Update hu.json 2023-11-19 10:32:57 +01:00
45212c699f Update release.yml 2023-11-18 16:05:56 -05:00
03106f3522 Merge pull request #1096 from ImranR98/dev
Dev
2023-11-18 15:57:58 -05:00
cd2857cb94 Add language menu entries (+ tr fix), increment version 2023-11-18 15:56:44 -05:00
b3ddb8658f F-Droid flavour updates + update packages 2023-11-18 15:42:32 -05:00
7ae80b6b49 Merge pull request #236 from p1gp1g/flavors
Add fdroid flavor
2023-11-18 15:01:37 -05:00
34f27f9cd4 Merge branch 'main' into flavors 2023-11-18 14:56:30 -05:00
4c53b58fd8 Fix broken thrid-party f-droid search (#1094) 2023-11-18 14:53:43 -05:00
6102ab4be8 Merge remote-tracking branch 'origin/main' into dev 2023-11-18 14:42:32 -05:00
6fcf565974 Bug 2023-11-18 14:42:24 -05:00
77bd16c7cf Merge pull request #1081 from JohnBetaro/main
Small optimization
2023-11-18 14:41:23 -05:00
7d593080ec Merge pull request #1068 from LilligantMatsuri/main
Update Chinese translation
2023-11-18 14:41:05 -05:00
96ea74abeb Merge pull request #1072 from Bardesss/main
Update nl.json
2023-11-18 14:40:58 -05:00
e50e3957ec Merge pull request #1078 from Daviteusz/weblate-obtainium-translate
Update Polish translation
2023-11-18 14:40:51 -05:00
8cb9420939 Merge pull request #1083 from ngocanhtve/main
Create vn.json
2023-11-18 14:40:41 -05:00
bbb7b8caa2 Merge pull request #1089 from mamiyaso/patch-1
Create tr.json
2023-11-18 14:40:34 -05:00
cc284f3c9e Merge pull request #1092 from bluefly000/japanese-translation
Update ja.json
2023-11-18 14:40:22 -05:00
bd8aae5fbf Rename vn.json to vi.json
Correct the country code.
2023-11-18 13:57:28 +07:00
3d162f33df Update ja.json 2023-11-17 20:35:33 +09:00
ff3c37f1b4 Update ja.json 2023-11-17 14:34:30 +09:00
35160ce385 Create tr.json
Added Turkish translation
2023-11-14 17:48:59 +03:00
c8eef91813 Create vn.json
Translation for Vietnamese language!
2023-11-11 15:44:56 +07:00
6791b20121 Update release.yml 2023-11-10 21:20:21 -10:00
3311c49236 Update release.yml 2023-11-10 21:18:35 -10:00
5223e58f83 Update build.gradle 2023-11-10 21:17:40 -10:00
e0f9fc233d Update release.yml 2023-11-10 21:15:57 -10:00
509bd21655 Update Polish translations 2023-11-09 15:52:30 +01:00
5be7229c4b Update nl.json 2023-11-07 08:49:35 +00:00
efd3c461e0 Update zh.json
- Translate new strings
- Minor improvements

Signed-off-by: Matsuri <matsuri@vmoe.info>
2023-11-05 14:56:41 +08:00
13aed69f0e Merge pull request #1063 from ImranR98/dev
Various bugfixes and minor enhancements (#454, #1026, #1050, #1052, #1060)
2023-11-03 19:44:28 -04:00
b03675811c Added new language menu entries 2023-11-03 19:40:19 -04:00
e4beac185e Merge remote-tracking branch 'origin/main' into dev 2023-11-03 19:38:23 -04:00
bdc7d9dd84 Merge pull request #1058 from Bardesss/main
Add nl.json
2023-11-03 19:38:01 -04:00
b49d1e0529 Merge pull request #998 from Mr-Bajs/main
Adding swedish translation
2023-11-03 19:37:55 -04:00
3c0eb48d53 Merge pull request #1027 from unbranched/patch-3
Update it.json
2023-11-03 19:37:36 -04:00
0d65531ec1 Merge pull request #1028 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-11-03 19:37:31 -04:00
3848f9822b Merge pull request #1036 from iDazai/main
Update de.json
2023-11-03 19:37:24 -04:00
e1cdc002b9 Merge pull request #1043 from gidano/main
Update hu.json
2023-11-03 19:37:15 -04:00
168c1cf1ce Upgrade packages, increment version 2023-11-03 19:36:39 -04:00
5b142b4401 Various fixes and improvements (#454, #1026, #1050, #1051, #1052, #1060) 2023-11-03 19:35:42 -04:00
9be3d76590 Add nl.json
Adding Dutch translation to Obtanium
2023-11-01 10:01:44 +00:00
26971aa109 Upgrade packages 2023-10-25 18:22:58 -04:00
18f9049bc6 locale(pl): Update Polish translations 2023-10-24 11:51:19 +02:00
53cf253b67 Update hu.json 2023-10-23 10:20:51 +02:00
6d416f45a9 Higher versionCode after revert 2023-10-22 12:51:30 -04:00
a34a447164 Revert "Switched to WorkManager for reliability (#608)"
This reverts commit d3247a9ec1.
2023-10-22 12:50:54 -04:00
d3247a9ec1 Switched to WorkManager for reliability (#608) 2023-10-22 00:59:51 -04:00
dfb5a2b97f Update de.json
translated latest addition
2023-10-21 17:19:20 +02:00
deae96e191 Update it.json
Missed one string & fixed one
2023-10-19 08:56:18 +00:00
b8c0e18bb4 Merge pull request #1024 from ImranR98/dev
Don't replace / in versions (#1023), Fix HTML version extraction (#1021), Update check only for installed apps (#1012)
2023-10-18 19:47:34 -04:00
a083da1546 Upgrade packages, increment version 2023-10-18 19:46:11 -04:00
2518c70ec8 Bugfixes
Don't replace / in versions (#1023)
Fix HTML version extraction (#1021)
Update check only for installed apps (#1012)
2023-10-18 19:42:15 -04:00
6ead8d261c Merge pull request #1011 from LucasTavaresA/main
Update pt.json
2023-10-18 19:12:46 -04:00
b5f76a6ebc Merge pull request #1014 from DwainZwerg/patch-4
Update de.json (small improvements)
2023-10-18 19:12:40 -04:00
5e38bca535 Merge pull request #1015 from unbranched/patch-2
Update it.json
2023-10-18 19:12:34 -04:00
c39142ee77 Merge pull request #1016 from k03mad/ru_locale
Update ru translation
2023-10-18 19:12:23 -04:00
d0174479d7 update ru translation 2023-10-17 17:50:10 +03:00
aa23267d70 Update it.json 2023-10-17 09:58:12 +00:00
92f5da306d Update de.json (small improvements 2023-10-17 09:10:18 +00:00
8601e2af7a Update sv.json 2023-10-16 14:22:38 +00:00
5d9f31b99e Update sv.json 2023-10-16 14:21:50 +00:00
8b29158d8b Update sv.json 2023-10-16 14:17:13 +00:00
b68cf3c671 Update sv.json 2023-10-16 14:12:09 +00:00
858a50a414 Update pt.json 2023-10-15 20:13:45 -03:00
598df624df Update sv.json 2023-10-14 19:31:08 +00:00
e6da2b73ed Update sv.json 2023-10-14 19:16:26 +00:00
f44424d37a Update sv.json 2023-10-14 19:11:25 +00:00
623804ae68 Update sv.json 2023-10-14 18:35:37 +00:00
b7544939c2 Merge pull request #1008 from ImranR98/dev
Fix broken search (#1006)
2023-10-14 13:27:01 -04:00
63fa6d4660 Increment version 2023-10-14 13:25:49 -04:00
9506c11951 Fix broken search (#1006) 2023-10-14 13:25:26 -04:00
ce5639bb98 Update sv.json 2023-10-14 12:48:03 +00:00
99ef6ca861 Update sv.json 2023-10-14 12:32:28 +00:00
43d5a3da27 Update sv.json
updated
2023-10-14 12:30:55 +00:00
30723110af Merge branch 'ImranR98:main' into main 2023-10-14 12:07:11 +00:00
a0ecb4e2bb Merge pull request #1003 from ImranR98/dev
Fix Source selection (HTML) which broke in the last release
2023-10-14 05:24:49 -04:00
a726b09f26 Fix Source selection (HTML) which broke in the last release 2023-10-14 05:24:08 -04:00
52b1d1fe33 Merge pull request #1001 from ImranR98/dev
Fixed syntax errors in de.json
2023-10-14 01:36:49 -04:00
8b4709c132 Fixed syntax errors in de.json 2023-10-14 01:36:26 -04:00
b4217a791e Merge pull request #994 from DwainZwerg/patch-3
Update de.json
2023-10-14 01:33:33 -04:00
9b3a982d54 Merge pull request #997 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-10-14 01:33:11 -04:00
0a47d3eab5 Merge pull request #1000 from ImranR98/dev
Switch back to parallelized BG task (#963), Make Third Party F-Droid Repos Searchable (#995)
2023-10-14 01:31:50 -04:00
7d7803247d Update packages, increment version 2023-10-14 01:30:37 -04:00
aa8d45e636 Make Third Party F-Droid Repos Searchable (#995) 2023-10-14 01:29:02 -04:00
7f3e87767c Switch back to parallelized BG task (#963) + better logging 2023-10-13 23:39:24 -04:00
f76ea9bba5 Merge branch 'ImranR98:main' into main 2023-10-13 12:52:23 +00:00
1eefeae060 locale(pl): Update Polish translations 2023-10-13 13:03:33 +02:00
03447c33be Update de.json 2023-10-13 07:30:23 +00:00
f6c6cd5cb8 Merge pull request #993 from ImranR98/dev
Separate notif channel title translations (#979), Add 'skip update notification' per-app toggle (#980), Better error for some incompatible APK cases (#981), Add whole-page version extraction for HTML (#989), Better relative link resolution (#989), Clearer wording (#991), Fixed issues in some translation files (#992)
2023-10-12 19:54:12 -04:00
67b56cee50 Upgrade packages, increment version 2023-10-12 19:51:55 -04:00
2d7465cca6 Separate notif channel title translations (#979) 2023-10-12 19:51:04 -04:00
d06f20690d Add 'skip update notification' per-app toggle (#980) 2023-10-12 19:33:38 -04:00
e613a494ff Better error for some incompatible APK cases (#981) 2023-10-12 19:24:23 -04:00
503a01675e Clearer wording (#991) 2023-10-12 19:10:02 -04:00
723f7e63e9 Add whole-page version extraction for HTML (#989) 2023-10-12 19:06:59 -04:00
7ff6916e5f Better relative link resolution (#989) 2023-10-12 18:57:47 -04:00
910ed26525 Fixed issues in some translation files 2023-10-12 18:31:31 -04:00
c0fffa6293 Merge pull request #987 from ImranR98/dev
Fix issues with last conflit resolution commit
2023-10-11 21:07:18 -04:00
d800d3ba41 Fix issues with last conflit resolution commit 2023-10-11 21:03:23 -04:00
d267343c64 Merge pull request #986 from ImranR98/dev
Switch back to serial BG installs (#984), Fix UpToDown again (#982)
2023-10-11 21:01:09 -04:00
0f72e37272 Merge branch 'main' into dev 2023-10-11 21:00:37 -04:00
89e44d581f Update packages, increment version 2023-10-11 20:56:26 -04:00
c908a710de Fix UpToDown again (#982) 2023-10-11 20:55:09 -04:00
f0809cde72 Merge pull request #977 from iDazai/main
updated translations
2023-10-11 20:44:12 -04:00
e6da8c1c18 Merge pull request #978 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-10-11 20:44:06 -04:00
81ea67afa8 Remove unnecessary log 2023-10-11 20:43:27 -04:00
488049821e Switch back to serial BG installs (#984) 2023-10-11 20:42:33 -04:00
fc7408a8f8 locale(pl): Update Polish translations 2023-10-09 20:14:22 +02:00
6955191fdd Update pl.json
fixed line breaks
2023-10-09 15:22:32 +02:00
ba59693d90 Update pl.json
Changed order of the lines so it's the same order for every language and better to compare
2023-10-09 15:17:52 +02:00
ce6e51128d Update it.json
added three missing lines, no translation
2023-10-09 15:04:45 +02:00
a7a26037ae Update hu.json
added four missing lines, no translation
2023-10-09 15:02:51 +02:00
00854abd1a Update fr.json
added three missing lines, no translation
2023-10-09 14:59:14 +02:00
c26649d5f2 Update fa.json
added three missing lines, no translation
2023-10-09 14:58:42 +02:00
63b76a249f Update es.json
added three missing lines, no translation
2023-10-09 14:57:52 +02:00
95f19208ad Update bs.json
added three missing lines, no translation
2023-10-09 14:54:24 +02:00
f4a902ee47 Update de.json
added three missing lines and translated them, reverted translation of variables so log function can work again.
2023-10-09 14:52:50 +02:00
036823cbb6 Merge remote-tracking branch 'origin/main' into dev 2023-10-08 17:33:12 -04:00
547c2c8c0d Merge pull request #975 from ImranR98/dev
Less notif spam, no check in aeroplane mode (#974)
2023-10-08 17:33:05 -04:00
7453d19d27 Increment version, update packages 2023-10-08 17:32:41 -04:00
2a0b68d637 Less notif spam, no check in aeroplane mode (#974) 2023-10-08 17:30:08 -04:00
02f374fdf0 Merge pull request #969 from ImranR98/dev
Enabled WhatsApp Source (#357)
2023-10-07 13:31:45 -04:00
3536dcd775 Increment version 2023-10-07 13:31:02 -04:00
6fa887e536 Merge pull request #967 from JohnBetaro/main
Better version detection for F-Droid
2023-10-07 13:16:39 -04:00
71755763b9 Enabled WhatsApp Source (#357) 2023-10-07 13:15:22 -04:00
5aabcccfd4 Update fdroid.dart 2023-10-07 09:53:34 +01:00
1e009e2211 Update release.yml 2023-10-07 02:03:18 -04:00
811bc6434b Update release.yml 2023-10-07 02:00:13 -04:00
a5b2d06742 Update release.yml 2023-10-07 01:33:34 -04:00
2c00674835 Update release.yml 2023-10-07 01:20:03 -04:00
9bbff1f436 Update release.yml 2023-10-07 01:08:43 -04:00
c75416f9be Update release.yml 2023-10-07 01:02:53 -04:00
2cb750a7b0 Update release.yml 2023-10-07 00:56:27 -04:00
abfec51964 Update release.yml 2023-10-07 00:40:34 -04:00
e314717a3e Update release.yml 2023-10-07 00:33:50 -04:00
290a78acb5 Update release.yml 2023-10-07 00:30:09 -04:00
b40a4dd243 Update release.yml 2023-10-07 00:09:18 -04:00
b38f6d0581 Update release.yml 2023-10-06 23:58:33 -04:00
5c562a6bdb Update release.yml 2023-10-06 23:45:00 -04:00
238d43f916 Update release.yml 2023-10-06 23:18:25 -04:00
818dc23089 Update release.yml 2023-10-06 22:55:59 -04:00
ddb50940ce Update release.yml 2023-10-06 22:55:50 -04:00
96fb97f4ef Update release.yml 2023-10-06 22:53:13 -04:00
6c0459c2ae Update release.yml 2023-10-06 22:51:30 -04:00
30a9e1ee4b Update release.yml 2023-10-06 22:48:21 -04:00
15a1f1e6e0 Update release.yml 2023-10-06 22:46:11 -04:00
827e2b161c Update release.yml 2023-10-06 22:45:20 -04:00
e95230859e Update release.yml 2023-10-06 22:43:41 -04:00
95cb8ca493 Update release.yml 2023-10-06 22:41:47 -04:00
5735293efb Update release.yml 2023-10-06 22:40:27 -04:00
035df1f338 Update release.yml 2023-10-06 22:38:01 -04:00
36a8b04c4f Update release.yml 2023-10-06 22:35:40 -04:00
3d5411ef28 Update release.yml 2023-10-06 22:32:23 -04:00
df7b74e727 Update release.yml 2023-10-06 22:28:02 -04:00
0138599120 Update release.yml 2023-10-06 22:23:45 -04:00
66446cbffc Update release.yml 2023-10-06 22:16:41 -04:00
ef40f3900f Update release.yml 2023-10-06 22:11:25 -04:00
ce259764a8 Update release.yml 2023-10-06 22:09:33 -04:00
65187cb17b Update release.yml 2023-10-06 22:04:55 -04:00
8f25245ad1 Update release.yml 2023-10-06 21:47:25 -04:00
58b8baa019 Merge pull request #966 from ImranR98/dev
Run GH action manually only
2023-10-06 21:21:44 -04:00
ba1db88c8d Run GH action manually only 2023-10-06 21:21:26 -04:00
c8f2f42a35 Merge pull request #965 from ImranR98/dev
Parallelize Update Checking (#963), Bugfixes (#954, #962)
2023-10-06 21:20:19 -04:00
4e5a9b2af5 Updated GitHub actions 2023-10-06 21:19:46 -04:00
d1ab961c14 Merge remote-tracking branch 'origin/main' into dev 2023-10-06 20:45:02 -04:00
33caf4644e Ran dart fix 2023-10-06 20:43:06 -04:00
f34ffe18a8 Update packages, increment version 2023-10-06 20:42:09 -04:00
4b1c5e111d Merge pull request #958 from JohnBetaro/main
Workflow to build and release
2023-10-06 20:40:48 -04:00
dbd7c296c6 Merge pull request #951 from DwainZwerg/patch-2
Update de.json
2023-10-06 20:38:08 -04:00
df93cbde8f Fix UpToDown (#962) 2023-10-06 20:37:14 -04:00
d8cbc121a7 Merge pull request #964 from ImranR98/963
Parallelize Update Checking (#963)
2023-10-06 20:07:18 -04:00
8163cd5c8f Improvements, bugfixes 2023-10-06 19:58:46 -04:00
e9e9adb174 Tweaks 2023-10-06 19:34:08 -04:00
21fdfc1eef Attempting to parallelize update checks 2023-10-06 19:23:18 -04:00
4304251e1e Remove checking for APKs
This is because I couldn't check for prereleases because I don't know how, maybe you can.
2023-10-05 11:31:42 +01:00
ae0cd74b0e Update android.yml 2023-10-05 11:29:03 +01:00
6935bff244 Update android.yml 2023-10-05 11:22:29 +01:00
102b9da617 Update android.yml 2023-10-05 11:19:46 +01:00
8bd0d185ae Create android.yml 2023-10-05 11:16:42 +01:00
7bfc5ae0a8 Bugfix (#954) 2023-10-03 21:34:24 -04:00
c72a41db9d Update de.json
and small improvements
2023-10-01 12:05:49 +00:00
5fbb1c2e32 Merge pull request #947 from LilligantMatsuri/main
Update Chinese translation
2023-09-30 10:16:52 -04:00
850dd53c69 Merge pull request #949 from ImranR98/dev
"Naive" version detection for some Sources (#946)
2023-09-30 10:16:36 -04:00
e3baf91037 Increment version, upgrade packages 2023-09-30 10:16:10 -04:00
4c811c9c04 "Naive" version detection for some Sources (#946) 2023-09-30 10:12:49 -04:00
4bc9f5826e Update zh.json
- Translate new strings
- Slight improvements

Signed-off-by: Matsuri <matsuri@vmoe.info>
2023-09-30 20:30:00 +08:00
9f19e4dc83 Merge pull request #945 from ImranR98/dev
Add export dir pick button + update packages
2023-09-29 12:13:38 -04:00
c9cb865c9c Add export dir pick button + update packages 2023-09-29 12:13:11 -04:00
b8a7dd984d Merge pull request #934 from LucasTavaresA/main
Update pt.json
2023-09-29 12:12:55 -04:00
3ede46c445 Merge pull request #937 from TangyWrecker/main
Update ru.json
2023-09-29 12:12:39 -04:00
87c4af94d3 Merge pull request #944 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-09-29 12:12:32 -04:00
6c30bd36d1 Create sv.json 2023-09-29 14:54:39 +00:00
7e85ccaf28 locale(pl): Update Polish translations 2023-09-29 02:39:18 +02:00
db3a262410 Update ru.json
oops
2023-09-26 01:13:01 +03:00
70162da491 Update ru.json 2023-09-26 01:09:19 +03:00
f3632a4033 Update pt.json 2023-09-24 15:22:56 -03:00
2f4e176116 Merge pull request #933 from ImranR98/dev
Downgrade permission handler to fix #932 + APKPure improvement
2023-09-23 17:19:52 -04:00
3ea8dc598c Downgrade permission handler to fix #932 + APKPure improvement 2023-09-23 17:18:12 -04:00
3f63dc0180 Merge pull request #930 from gidano/main
Update hu.json
2023-09-23 14:37:19 -04:00
1f6701b183 Update hu.json 2023-09-23 20:36:00 +02:00
3a170fbf90 Merge pull request #931 from ImranR98/dev
Fix major GitHub bug from previous release (#929)
2023-09-23 14:32:46 -04:00
b57e523a07 Increment version 2023-09-23 14:32:13 -04:00
8c4509f255 Fix major GitHub bug (#929) 2023-09-23 14:31:52 -04:00
3878c287be Update hu.json 2023-09-23 20:23:25 +02:00
7789e3f3a7 Removed excessive logs 2023-09-23 14:02:55 -04:00
94ac744f41 Merge pull request #928 from ImranR98/dev
Fix version number
2023-09-23 13:43:25 -04:00
a83211f260 Fix version number 2023-09-23 13:43:04 -04:00
4f733affc5 Merge pull request #927 from ImranR98/dev
- Change a default F-Droid Source option
- Add GitHub "don't sort" toggle (#925)
- Better IzzyOnDroid support (#926)
- Allow 'm.' subdomain for APKPure
- Added HTML reverse sorting (#915)
2023-09-23 13:41:08 -04:00
ffb2a982bc Merge branch 'main' into dev 2023-09-23 13:40:32 -04:00
9658a65661 Upgrade packages, increment version 2023-09-23 13:36:46 -04:00
68d4083708 Added HTML reverse sorting (#915)
+ translatable strings (#916)
2023-09-23 13:31:27 -04:00
af5377d2eb Allow 'm.' subdomain for APKPure 2023-09-23 13:21:48 -04:00
6ac8ad543c Better IzzyOnDroid support (#926) 2023-09-23 13:16:51 -04:00
c10a63e76e Add GitHub "don't sort" toggle (#925) 2023-09-23 12:48:21 -04:00
b423a2af52 Change a default F-Droid Source option 2023-09-22 00:22:54 -04:00
9b70289345 Merge pull request #914 from ygxbnet/main
Update zh.json
2023-09-18 20:11:08 -04:00
2a94c1f33a Update main.dart 2023-09-19 00:51:58 +08:00
ea205c1acb Update zh.json 2023-09-19 00:32:20 +08:00
6e1767730b Update zh.json 2023-09-18 00:45:45 +08:00
3fc1ab37d7 Merge pull request #911 from ImranR98/dev
Enable Brazil translation (#830)
2023-09-16 17:41:58 -04:00
c4a3f7333d Enable Brazil translation (#830) 2023-09-16 16:45:06 -04:00
fb820e01b1 Merge pull request #910 from ImranR98/dev
Remove nonsense hint
2023-09-16 16:35:37 -04:00
504aca5287 Merge pull request #908 from oSoWoSo/cz
Add Czech translation
2023-09-16 16:35:25 -04:00
ae7d5546ae Remove nonsense hint 2023-09-16 16:34:15 -04:00
8bcb10b281 Add Czech translation 2023-09-16 22:32:42 +02:00
845eaab73e Merge pull request #909 from ImranR98/dev
Bugfix: HTML Source was broken (could not add apps)
2023-09-16 15:27:11 -04:00
298ff190ec Bugfix: HTML Source was broken (could not add apps) 2023-09-16 15:26:35 -04:00
734a548bc3 Merge pull request #906 from ImranR98/dev
Increment version
2023-09-16 09:28:57 -04:00
591973d97f Increment version 2023-09-16 09:28:45 -04:00
a2f57ecd66 Merge pull request #903 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-09-16 09:26:34 -04:00
13dcfb479d Merge pull request #902 from gidano/main
Update hu.json
2023-09-16 09:26:28 -04:00
3328d80130 Merge pull request #905 from saymanrifat/saymanrifat-721
Add back button to app detail page (#721)
2023-09-16 09:26:03 -04:00
f9df3ac0e2 Reduce top padding as we no longer need it 2023-09-16 09:25:26 -04:00
61d66e80d8 Add back button to app detail page (#721) 2023-09-16 18:31:57 +06:00
93e0360116 locale(pl): Update Polish translations 2023-09-16 12:45:50 +02:00
2cec5f7934 Update hu.json 2023-09-16 11:28:36 +02:00
8d4a09b007 Merge pull request #894 from markus-gitdev/main
Update de.json
2023-09-16 04:39:06 -04:00
6d8d2fa99d Merge branch 'main' into main 2023-09-16 04:38:48 -04:00
59ecd55530 Merge pull request #895 from DwainZwerg/patch-1
edit de.json
2023-09-16 04:38:05 -04:00
77fcc89e37 Merge branch 'main' into patch-1 2023-09-16 04:35:12 -04:00
7d63da4d9c Merge pull request #893 from bluefly000/japanese-translation
Update ja.json
2023-09-16 04:32:42 -04:00
8eea0bae99 Merge pull request #892 from iDazai/main
Update de.json
2023-09-16 04:32:35 -04:00
f5dcd8f776 Merge pull request #901 from ImranR98/dev
Fix background update issue (#896)
2023-09-16 04:31:29 -04:00
e1f2baeeda Fix background update issue (#896) 2023-09-16 04:31:03 -04:00
84d8ff8b0b Small improvement of the previous translation (especially "" instead of the English ''; my translations of the new features to be translated are mostly based on iDazai (https://github.com/ImranR98/Obtainium/pull/892/files) and markus-gitdev (https://github.com/ImranR98/Obtainium/pull/894/files)) 2023-09-14 08:46:51 +00:00
9862c22fdb Update de.json 2023-09-14 09:27:36 +02:00
8c82f53dd2 Update ja.json 2023-09-14 15:44:27 +09:00
5cf4c21ce3 Update de.json
Translated latest strings
2023-09-14 08:37:46 +02:00
4951f62d4a Merge pull request #890 from ImranR98/dev
- Fix export encoding (#887)
- Fix repeating background install for some apps (#886)
- More version filtering options for F-Droid (#885)
- Trim user URL input
2023-09-13 22:20:49 -04:00
4c0c4b7010 Trim user URL input 2023-09-13 22:19:09 -04:00
96051e614c Merge remote-tracking branch 'origin/main' into dev 2023-09-13 22:17:11 -04:00
a2e494b2ba More version filtering options for F-Droid (#885) 2023-09-13 22:15:10 -04:00
2d5a9bec84 Increment version, update packages 2023-09-13 21:27:18 -04:00
7d9571cfdd Fix repeating background install for some apps (#886) 2023-09-13 21:24:25 -04:00
acc6a780fa Fix export encoding (#887) 2023-09-13 21:01:39 -04:00
0e36d42a06 Merge pull request #863 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-09-13 19:22:57 -04:00
5cfddd807a locale(pl): Update Polish translations 2023-09-11 22:05:32 +02:00
17b5604f2a Merge pull request #882 from ImranR98/dev
Bugfix
2023-09-10 23:29:38 -04:00
fdb6eed6d0 Slight tweak 2023-09-10 23:28:37 -04:00
13de0437b8 Bugfix 2023-09-10 23:23:35 -04:00
a43c45f310 Merge pull request #881 from ImranR98/dev
Pick export dir + auto-export (#283, #600) + UI fix
2023-09-10 23:15:36 -04:00
9c56a4d1fc Bugs 2023-09-10 23:15:16 -04:00
2aea1d2631 Bugfixes 2023-09-10 23:11:06 -04:00
118e05a0fa Bugfix 2023-09-10 22:47:04 -04:00
05f497787e Build script tweak 2023-09-10 22:37:32 -04:00
53cf4d0234 Pick export dir + auto-export (#283, #600) 2023-09-10 22:35:28 -04:00
6e735b1763 Enable auto-export on update checks 2023-09-10 22:24:18 -04:00
873a1a0683 Allow for alternative app dirs (unfinished) 2023-09-10 15:55:34 -04:00
27b1149d1c Fix padding when touch targets not highlighted 2023-09-10 14:16:19 -04:00
c1e64f111e Merge pull request #878 from ImranR98/dev
Fix Codeberg not finding APKs (#877), Make touch highlight visible in dark theme (#860)
2023-09-10 14:03:48 -04:00
b2af8448fd Increment version 2023-09-10 14:02:26 -04:00
8f44338e76 Make touch highlight visible in dark theme (#860) 2023-09-10 13:56:39 -04:00
e4a55abcb3 Fix Codeberg not finding APKs (#877) 2023-09-10 13:49:04 -04:00
d7348b4973 Merge pull request #871 from ImranR98/dev
Make less obvious target highlighting optional
2023-09-09 07:30:31 -04:00
09421230f2 Make less obvious target highlighting optional 2023-09-09 07:29:35 -04:00
4596e32258 Merge pull request #870 from ImranR98/dev
- Add Support for Private GitHub Repos (#857)
- Add Uptodown Source (#853)
- Add User-Defined Version Extraction for HTML Source (#861)
- Make release notes tap target more obvious (#860)
- Fix Missing Download Notification on App Add (#858)
- Prevent Access Token from Showing in Logs (#714)
2023-09-09 06:54:12 -04:00
4dc007a4f6 Update packages, increment version 2023-09-09 06:50:13 -04:00
c53a156969 Slight UI tweak 2023-09-09 06:48:55 -04:00
94bd0774fb Make release notes tap target more obvious (#860) 2023-09-09 06:00:36 -04:00
b178b1d780 Bugfix 2023-09-09 05:46:56 -04:00
cbc840378c Add Uptodown (#853) 2023-09-09 05:31:04 -04:00
aa7989c16d Custom version extraction for HTML (#861) 2023-09-09 04:41:44 -04:00
85f9336804 Re-add dl notification when adding app (#858) 2023-09-09 03:58:54 -04:00
d66be3ecda Private GitHub repos now work (#857) 2023-09-09 03:55:52 -04:00
c08e05bd6c Trying to use header-based HTTP auth (not working) 2023-09-06 21:30:45 -04:00
e08ab89fd4 Update issue templates 2023-09-06 18:33:04 -04:00
8ba0a0a776 Merge pull request #852 from ImranR98/dev
Fix Aptoide App ID inferral error (#851)
2023-09-04 03:57:56 -04:00
73ed0cea88 Increment version 2023-09-04 03:56:41 -04:00
58a378d212 Fix Aptoide App ID inferral error (#851) 2023-09-04 03:55:13 -04:00
553307ba70 Merge pull request #850 from ImranR98/dev
Auto Select Highest VersionCode APK (F-Droid) (#846)
2023-09-03 16:12:24 -04:00
78f73a9049 Auto Select Highest VersionCode APK (F-Droid) (#846) 2023-09-03 16:11:17 -04:00
abc69e7a0e Merge pull request #849 from ImranR98/dev
Bugfix in HTML Source (#848)
2023-09-03 15:37:43 -04:00
503914dbce Bugfix in HTML Source (#848) 2023-09-03 15:37:26 -04:00
69680f8680 Merge pull request #844 from gidano/main
Update hu.json
2023-09-03 00:15:41 -04:00
1a616aacb7 Merge pull request #847 from ImranR98/dev
Add Aptoide as a Source (#843)
2023-09-03 00:15:01 -04:00
52d19f267e Upgrade packages, increment version 2023-09-03 00:14:23 -04:00
75a8dc9ee4 Add Aptoide as a Source (#843) 2023-09-02 21:28:30 -04:00
8c63920ef6 Update hu.json 2023-09-02 15:41:10 +02:00
dda5619cdc Update issue templates 2023-09-01 11:25:53 -04:00
9cc25fd931 Update issue templates 2023-09-01 11:18:42 -04:00
f5a9eb4295 Update issue templates 2023-09-01 11:13:04 -04:00
ebe256e482 Update issue templates 2023-09-01 11:05:49 -04:00
e215585a64 Merge pull request #840 from ImranR98/dev
Bugfix: BG update fail leads to infinite retries (#838), Group update notifications into one (#829), Temporarily exclude Obtainium from BG updates (#836)
2023-08-30 22:36:31 -04:00
8b01fc03ec Increment version 2023-08-30 22:35:11 -04:00
9af2c8370d Bugfix: BG update fail leads to infinite retries (#838) 2023-08-30 22:34:12 -04:00
01f9003b8d Group update notifications into one (#829) 2023-08-30 22:07:30 -04:00
73a3c7eb71 Temporarily exclude Obtainium from BG updates (#836) 2023-08-30 21:41:26 -04:00
370ec1432e Fix BG update OS requirement 2023-08-30 21:40:05 -04:00
4b63c124bb Merge pull request #835 from ImranR98/dev
VLC: Postpone mirror-dependent request until download time (#821)
2023-08-30 18:43:47 -04:00
3d7df604b1 Upgrade packages 2023-08-30 18:43:03 -04:00
59d1d275bc Increment version 2023-08-30 18:41:55 -04:00
f7f0332b13 VLC: Postpone mirror-dependent request to download time (#821) 2023-08-30 18:41:34 -04:00
917c5f5083 Merge pull request #833 from iDazai/main
Update de.json
2023-08-30 18:14:06 -04:00
370cf05615 Merge pull request #834 from bluefly000/japanese-translation
Update ja.json
2023-08-30 18:13:58 -04:00
101d892c7f Update ja.json 2023-08-30 14:09:11 +09:00
db2faf6511 Update de.json
updated newly added strings, added "APK" for the line "customLinkFilterRegex" which was added in other languages too.
2023-08-30 07:01:06 +02:00
3a83d1bf79 Merge pull request #831 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-08-29 13:06:16 -04:00
070e309aa6 Merge pull request #832 from ImranR98/dev
Disable Brazil language to unblock stuck users (temporary - until fix is found) (#830)
2023-08-29 13:06:05 -04:00
11ca7c3f7d Increment version 2023-08-29 13:05:17 -04:00
7a0acaefb3 Disable BR lang. to unblock users (temp.) (#830) 2023-08-29 13:04:58 -04:00
486f3664ef locale(pl): Update Polish translations 2023-08-29 18:59:54 +02:00
387b165314 Merge pull request #825 from ImranR98/dev
Increment version
2023-08-28 22:15:51 -04:00
4ba3f7996e Increment version 2023-08-28 22:08:37 -04:00
7ce41dfb67 Merge pull request #824 from LucasTavaresA/main
Fix and update brasilian translation
2023-08-28 22:07:44 -04:00
efd55c8d36 Fix and update brasilian translation 2023-08-28 22:10:54 -03:00
71543c64d4 Merge pull request #823 from ImranR98/dev
Various Enhancements and Bugfixes
2023-08-28 20:27:03 -04:00
408c1a541f Increment version 2023-08-28 20:26:21 -04:00
8f739d9e0b WiFi only BG update toggle (#819) + typo fix 2023-08-28 20:24:15 -04:00
bd8f608ee6 Allow apps to be exempted from BG updates (#822) 2023-08-28 20:02:09 -04:00
551643b11c More UI feedback when app updated (#814) 2023-08-28 19:51:49 -04:00
075ecae540 Re-add VLC with better error messaging when no mirror available (#821) 2023-08-28 19:36:02 -04:00
dcb8807fa7 Add "Intermediate Link" option to HTML Source (#820) 2023-08-28 19:11:22 -04:00
77ec2df31c Merge pull request #818 from LucasTavaresA/main
Add brasilian translation
2023-08-27 17:45:19 -04:00
d92e812554 Add brasilian translation 2023-08-27 18:27:09 -03:00
0a6e1f9cc6 Switch back to upstream package manager (from custom branch) 2023-08-27 15:43:17 -04:00
f3c3680382 Merge pull request #817 from ImranR98/dev
Increment version
2023-08-27 14:27:13 -04:00
f7e783a556 Increment version 2023-08-27 14:26:37 -04:00
fd1b72563d Merge pull request #816 from ImranR98/dev
Remove VLC (#801)
2023-08-27 14:25:11 -04:00
5a94ef82dd Merge remote-tracking branch 'origin/main' into dev 2023-08-27 14:24:59 -04:00
3ad46b7e21 Merge pull request #803 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-08-27 14:23:59 -04:00
1e94d71665 locale(pl): Update Polish translations 2023-08-27 19:59:19 +02:00
0899a576ff Merge pull request #815 from bluefly000/japanese-translation
Update ja.json
2023-08-27 13:21:42 -04:00
db2476f3a5 Update ja.json 2023-08-28 01:36:14 +09:00
8ba182870d Merge pull request #812 from LilligantMatsuri/main
Update Chinese translation
2023-08-27 07:48:04 -04:00
d81085a9e8 Update zh.json
- Translate new strings
- Slight improvements

Signed-off-by: Matsuri <matsuri@vmoe.info>
2023-08-27 15:30:01 +08:00
ae92a459b7 Remove VLC (#801) 2023-08-26 15:35:08 -04:00
e101c434d5 Merge pull request #809 from bluefly000/japanese-translation
Update ja.json
2023-08-26 14:54:48 -04:00
9686d0f0ca Added missing enableBackgroundUpdates text 2023-08-27 01:20:51 +09:00
1dd0392b78 Update ja.json 2023-08-27 01:15:08 +09:00
5e184d733b Merge pull request #806 from ImranR98/dev
Update README
2023-08-25 21:01:29 -04:00
ef0b20887b Update README 2023-08-25 21:00:57 -04:00
5cb4bd998b Merge pull request #805 from ImranR98/dev
Update packages, increment version
2023-08-25 20:54:18 -04:00
57f499c6a5 Update packages, increment version 2023-08-25 20:53:23 -04:00
c2ae6e19f9 Merge pull request #804 from ImranR98/dev
'Verify latest tag' toggle (#798, #740)
2023-08-25 20:41:59 -04:00
16104fde03 Merge branch 'main' into dev 2023-08-25 20:41:52 -04:00
13e10692b1 'Verify latest tag' toggle (#798, #740) 2023-08-25 20:40:35 -04:00
7fc93f23c0 Merge pull request #802 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-08-25 17:01:09 -04:00
d6ddf87365 locale(pl): Update Polish translations
Polish translation has been updated

Signed-off-by: Daviteusz <daviteusz0@gmail.com>
2023-08-25 22:49:54 +02:00
05172744cd Merge pull request #800 from ImranR98/dev
Enable Background Updates (#25)
2023-08-25 10:54:38 -04:00
2504ae24fc Fix bgcheck error reporting (per-app), move code around 2023-08-24 15:05:07 -04:00
5e41d5762b BG task: notif, retry, log tweaks 2023-08-24 10:46:11 -04:00
57d44c972f Update build script to create zips 2023-08-23 20:05:08 -04:00
be0b57ac00 Added logs, BG install cooldown 2023-08-23 19:51:52 -04:00
862bb2b276 chmod +x build.sh 2023-08-23 19:39:23 -04:00
5eac851f80 Added convenience build script 2023-08-23 19:36:24 -04:00
0deab8296f bug 2023-08-23 18:28:16 -04:00
6785708661 BG update toggle has an effect 2023-08-22 19:57:42 -04:00
5307fd0901 bug 2023-08-22 19:43:23 -04:00
788c4c7917 Try less messy bg update method 2023-08-22 19:14:56 -04:00
3f6d96289d Bugfix, logging 2023-08-22 18:13:28 -04:00
5cfd80e510 Added debug menu with on-demand bg task 2023-08-22 17:36:13 -04:00
9eb32ae55a Don't reschedule bg checks if app is restarted 2023-08-22 17:13:15 -04:00
82e08150ab bugs 2023-08-22 16:28:22 -04:00
e956ee9254 Trying a new recursive BG update task due2 mem limits 2023-08-22 12:51:55 -04:00
bb4f34317b Bugfix + version increment + update packages 2023-08-21 23:55:54 -04:00
d08ff3fbae Add BG update toggle 2023-08-21 20:32:41 -04:00
f5479ec82f Max 5 attempts for bg check fail due to rate/net 2023-08-21 20:15:57 -04:00
3eb25c4060 Switch to per-app BG update tasks 2023-08-21 20:10:30 -04:00
03bb1ad9a6 bugfix 2023-08-21 09:48:35 -04:00
b59d3e77f9 Enable experimental BG update support (not well tested) (#25) 2023-08-20 22:32:33 -04:00
7c41692d5f Enable version correction in background (replace plugin) 2023-08-20 16:03:41 -04:00
ce89d456e1 Merge pull request #788 from ImranR98/dev
Pub update
2023-08-19 01:44:36 -04:00
a0c48fcca6 Pub update 2023-08-19 01:44:02 -04:00
d0cba6d6bc Merge pull request #787 from ImranR98/dev
Enable Android TV 'OK' Button (#281), Add Huawei AppGallery (#756), Fix VLC Source (#758)
2023-08-19 01:41:31 -04:00
6baf6ccf4b Slightly better error reporting for failed xapk install 2023-08-19 01:40:14 -04:00
a2571e61a8 Fix VLC Source (#758) 2023-08-19 01:28:25 -04:00
f61824ff0d Add Huawei AppGallery (#756) 2023-08-19 00:19:52 -04:00
734a1aeb01 Enable Android TV 'OK' Button (#281) 2023-08-18 22:12:16 -04:00
5269aad90d Merge pull request #786 from ImranR98/dev
Don't Send "Foreground" Notification if Not Needed + Small UI Fix
2023-08-18 20:11:34 -04:00
eaeee188eb Upgrade packages, increment version 2023-08-18 19:58:54 -04:00
8c850e06ca Merge pull request #774 from iDazai/main
Update de.json
2023-08-18 19:56:59 -04:00
0f754a8da8 Merge pull request #775 from bluefly000/japanese-translation
Update ja.json
2023-08-18 19:56:31 -04:00
81c4d4f393 Fix default multi-"app change" selection bug 2023-08-18 19:55:31 -04:00
5317aee18d Merge pull request #776 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-08-18 19:54:44 -04:00
b66eeba3b5 Merge pull request #781 from mehdeej/main
Update fa.json
2023-08-18 19:54:22 -04:00
522ff1ddf7 Merge pull request #784 from Nriver/task/update-chinese-translation
update chinese translation
2023-08-18 19:54:06 -04:00
7ef9c43ee3 Don't wait for foreground if install is silent 2023-08-18 19:36:56 -04:00
05ac76e3e9 update chinese translation 2023-08-18 16:56:20 +08:00
4838402797 Update fa.json 2023-08-16 18:54:58 +00:00
0de12c7c07 locale(pl): Update Polish translations
Polish translation has been updated

Signed-off-by: Daviteusz <daviteusz0@gmail.com>
2023-08-14 23:24:16 +02:00
0dadd8bffe Update ja.json 2023-08-15 02:57:31 +09:00
75a0cb1189 Update de.json
translated newly added line
2023-08-14 14:38:38 +02:00
a52d936b4d Merge pull request #773 from ImranR98/dev
Increment version
2023-08-13 21:28:02 -04:00
cfd04dc602 Increment version 2023-08-13 21:27:41 -04:00
7622e63975 Merge pull request #754 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-08-13 21:26:51 -04:00
3e7172b9d1 Merge branch 'main' into weblate-obtainium-translate 2023-08-13 21:26:44 -04:00
7aa0294447 Merge pull request #760 from iDazai/main
Update de.json
2023-08-13 21:26:14 -04:00
5b89c4d293 Merge branch 'main' into main 2023-08-13 21:26:08 -04:00
3bb991f57f Merge pull request #765 from Marocco2/main-1
Update html user agent with a believable one
2023-08-13 21:25:47 -04:00
1dd3aa0e8a Merge pull request #766 from Octopus1348/main
Make Hungarian translation more understandable
2023-08-13 21:25:41 -04:00
189cecbc37 Merge branch 'main' into main 2023-08-13 21:25:35 -04:00
9680ba08e9 Merge pull request #772 from ImranR98/dev
Use custom link filters for HTML, very basic foreground-only silent updates when able
2023-08-13 21:24:35 -04:00
dcf5bd37ca Foreground-only silent install support 2023-08-13 21:18:53 -04:00
d6a4b0a96d Make Hungarian translation more understandable 2023-08-11 18:56:25 +02:00
5f8638d349 Update html.dart
Swap user agent with a chrome browser on Android
2023-08-11 17:27:12 +02:00
1de274df39 Update de.json
translated the two new added lines, and one yet to be translated string.
2023-08-09 08:15:07 +02:00
3df801c54e locale(pl): Update Polish translations
Polish translation has been updated

Signed-off-by: Daviteusz <daviteusz0@gmail.com>
2023-08-06 22:49:52 +02:00
d473cb49c5 Add custom link filter for HTML 2023-08-06 13:14:48 -04:00
sim
7987083555 Add flutter as a submodule 2023-02-02 21:56:32 +01:00
sim
e94e195e73 Rename main flavor ('no') to 'github' 2023-01-29 22:42:22 +01:00
sim
869ebd90df Add fdroid flavor 2023-01-24 19:46:48 +01:00
93 changed files with 9526 additions and 3096 deletions

1
.flutter Submodule

Submodule .flutter added at 41456452f2

33
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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. -->

View 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. -->

88
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,88 @@
name: Build and Release
on:
workflow_dispatch:
inputs:
beta:
type: boolean
description: Is beta?
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: Check submodule
id: check_submodule
run: |
SUBMODULE_COMMIT_LONG="$(git submodule status | head -1 | tail -c +2 | awk '{print $1}')"
FLUTTER_COMMIT_SHORT="$(flutter --version | head -2 | tail -1 | awk '{print $4}')"
echo "SUBMODULE_COMMIT_LONG=$SUBMODULE_COMMIT_LONG, FLUTTER_COMMIT_SHORT=$FLUTTER_COMMIT_SHORT"
if ! [[ "$SUBMODULE_COMMIT_LONG" =~ ^$FLUTTER_COMMIT_SHORT ]]; then
echo "Your submodule has not been updated!"
exit 1
fi
- name: Extract Version
id: extract_version
run: |
VERSION=$(grep -oP "^version: [^\+]+" pubspec.yaml | tail -c +10)
echo "version=$VERSION" >> $GITHUB_OUTPUT
if [ ${{ inputs.beta }} == true ]; then BETA=true; else BETA=false; fi
echo "beta=$BETA" >> $GITHUB_OUTPUT
TAG="v$VERSION"
if [ $BETA == true ]; then TAG="$TAG"-beta; fi
echo "tag=$TAG" >> $GITHUB_OUTPUT
- 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
flutter build apk --flavor fdroid -t lib/main_fdroid.dart && flutter build apk --split-per-abi --flavor fdroid -t lib/main_fdroid.dart
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/$(ls ${ANDROID_HOME}/build-tools/ | tail -1)/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: 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

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule ".flutter"]
path = .flutter
url = https://github.com/flutter/flutter/

View File

@ -1,31 +1,41 @@
# ![Obtainium Icon](./assets/graphics/icon_small.png) Obtainium
[![Ceasefire Now](https://badge.techforpalestine.org/default)](https://techforpalestine.org/learn-more)
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)
Read the Wiki: [https://github.com/ImranR98/Obtainium/wiki](https://github.com/ImranR98/Obtainium/wiki)
Currently supported App sources:
- [GitHub](https://github.com/)
- [GitLab](https://gitlab.com/)
- [Codeberg](https://codeberg.org/)
- [F-Droid](https://f-droid.org/)
- [IzzyOnDroid](https://android.izzysoft.de/)
- [Mullvad](https://mullvad.net/en/)
- [Signal](https://signal.org/)
- [SourceForge](https://sourceforge.net/)
- [SourceHut](https://git.sr.ht/)
- [APKMirror](https://apkmirror.com/) (Track-Only)
- [APKPure](https://apkpure.com/)
- Third Party F-Droid Repos
- Jenkins Jobs
- [Steam](https://store.steampowered.com/mobile)
- [Telegram App](https://telegram.org)
- [VLC](https://www.videolan.org/vlc/download-android.html)
- [Neutron Code](https://neutroncode.com)
- "HTML" (Fallback)
- Any other URL that returns an HTML page with links to APK files (if multiple, the last file alphabetically is picked)
- Open Source - General:
- [GitHub](https://github.com/)
- [GitLab](https://gitlab.com/)
- [Codeberg](https://codeberg.org/)
- [F-Droid](https://f-droid.org/)
- Third Party F-Droid Repos
- [IzzyOnDroid](https://android.izzysoft.de/)
- [SourceForge](https://sourceforge.net/)
- [SourceHut](https://git.sr.ht/)
- Other - General:
- [APKPure](https://apkpure.net/)
- [Aptoide](https://aptoide.com/)
- [Uptodown](https://uptodown.com/)
- [APKMirror](https://apkmirror.com/) (Track-Only)
- [Huawei AppGallery](https://appgallery.huawei.com/)
- Jenkins Jobs
- Open Source - App-Specific:
- [Mullvad](https://mullvad.net/en/)
- [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
@ -33,8 +43,9 @@ Currently supported App sources:
alt="Get it on GitHub"
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
- 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.
## Screenshots

View File

@ -23,6 +23,7 @@ if (flutterVersionName == null) {
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'dev.rikka.tools.refine'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
@ -32,7 +33,7 @@ if (keystorePropertiesFile.exists()) {
}
android {
compileSdkVersion 33
compileSdkVersion rootProject.ext.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
@ -49,16 +50,28 @@ android {
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "dev.imranr.obtainium"
// 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.
minSdkVersion 23
targetSdkVersion 33
minSdkVersion 24
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
flavorDimensions "flavor"
productFlavors {
normal {
dimension "flavor"
applicationIdSuffix ""
}
fdroid {
dimension "flavor"
applicationIdSuffix ".fdroid"
}
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
@ -78,6 +91,33 @@ flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
repositories {
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"
}
ext.abiCodes = ["x86_64": 1, "armeabi-v7a": 2, "arm64-v8a": 3]
import com.android.build.OutputFile
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def abiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
if (abiVersionCode != null) {
output.versionCodeOverride = variant.versionCode * 10 + abiVersionCode
} else {
output.versionCodeOverride = variant.versionCode * 10
}
}
}

View File

@ -1,10 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="dev.imranr.obtainium">
<application
android:label="Obtainium"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true">
android:requestLegacyExternalStorage="true"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true"
@ -28,46 +30,46 @@
<intent-filter>
<action
android:name="com.android_package_installer.content.SESSION_API_PACKAGE_INSTALLED"
android:exported="false"/>
android:exported="false" />
</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>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
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
android:name="androidx.core.content.FileProvider"
android:authorities="dev.imranr.obtainium"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
android:name="androidx.core.content.FileProvider"
android:authorities="dev.imranr.obtainium"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</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>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<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.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.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"/>
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
</manifest>

View File

@ -1,6 +0,0 @@
package dev.imranr.obtainium
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -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"
}
}

View 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)
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}*/
}

View File

@ -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;
}
}
}

View File

@ -1,8 +1,8 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="142.129"
android:viewportHeight="142.129"
android:width="503.6066dp"
android:height="503.6066dp">
android:width="108dp"
android:height="108dp">
<group
android:translateX="-30.39437"
android:translateY="-54.68043">

View File

@ -1,13 +1,19 @@
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 {
google()
mavenCentral()
}
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 "dev.rikka.tools.refine:gradle-plugin:4.3.1"
}
}
@ -15,6 +21,10 @@ allprojects {
repositories {
google()
mavenCentral()
maven {
// [required] background_fetch
url "${project(':background_fetch').projectDir}/libs"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,297 +1,355 @@
{
"invalidURLForSource": "Nije važeći URL aplikacije {}",
"noReleaseFound": "Nije moguće pronaći odgovarajuće izdanje",
"noVersionFound": "Nije moguće odrediti verziju izdanja",
"urlMatchesNoSource": "URL se ne podudara s poznatim izvorom",
"cantInstallOlderVersion": "Nije moguće instalirati stariju verziju aplikacije",
"appIdMismatch": "ID preuzetog paketa se ne podudara s postojećim ID-om aplikacije",
"functionNotImplemented": "Ova klasa nije implementirala ovu funkciju",
"placeholder": "Rezervirano mjesto",
"someErrors": "Došlo je do nekih grešaka",
"unexpectedError": "Neočekivana greška",
"ok": "Dobro",
"and": "i",
"startedBgUpdateTask": "Započeo je pozadinski zadatak provjere ažuriranja",
"bgUpdateIgnoreAfterIs": "ignoreAfter pozadinskog zadataka je {}",
"startedActualBGUpdateCheck": "Započela je stvarna provjera ažuriranja",
"bgUpdateTaskFinished": "Završen zadatak provjere ažuriranja",
"firstRun": "Ovo je prvi put da pokrećete Obtainium",
"settingUpdateCheckIntervalTo": "Podešavanje intervala ažuriranja na {}",
"githubPATLabel": "GitHub token za lični pristup (eng. PAT, povećava ograničenje stope)",
"githubPATHint": "PAT mora biti u ovom formatu: korisničko_ime:token",
"githubPATFormat": "korisničko_ime:token",
"includePrereleases": "Uključi preliminarna izdanja",
"fallbackToOlderReleases": "Povratak na starija izdanja",
"filterReleaseTitlesByRegEx": "Filtrirajte naslove izdanja prema regularnom izrazu",
"invalidRegEx": "Nevažeći regularni izraz",
"noDescription": "Bez opisa",
"cancel": "Otkaži",
"continue": "Nastavite",
"requiredInBrackets": "(obavezno)",
"dropdownNoOptsError": "GREŠKA: PADAJUĆI MENI MORA IMATI NAJMANJE JEDNU OPCIJU",
"colour": "Boja",
"githubStarredRepos": "GitHub repo-i sa zvjezdicom",
"uname": "Korisničko ime",
"wrongArgNum": "Naveden je pogrešan broj argumenata",
"xIsTrackOnly": "{} je samo za praćenje",
"source": "Izvor",
"app": "Aplikacija. ",
"appsFromSourceAreTrackOnly": "Aplikacije iz ovog izvora su 'Samo za praćenje'.",
"youPickedTrackOnly": "Odabrali ste opciju „Samo za praćenje”.",
"trackOnlyAppDescription": "Aplikacija će se pratiti radi ažuriranja, ali Obtainium neće moći da je preuzme ili instalira.",
"cancelled": "Otkazano",
"appAlreadyAdded": "Aplikacija je već dodana",
"alreadyUpToDateQuestion": "Aplikacija je već ažurirana?",
"addApp": "Dodaj aplikaciju",
"appSourceURL": "Izvorni URL aplikacije",
"error": "Greška",
"add": "Dodaj",
"searchSomeSourcesLabel": "Pretraživanje (samo neki izvori)",
"search": "Pretraživanje",
"additionalOptsFor": "Dodatne opcije za {}",
"supportedSourcesBelow": "Podržani izvori:",
"trackOnlyInBrackets": "(Samo za praćenje)",
"searchableInBrackets": "(Može se pretraživati)",
"appsString": "Aplikacije",
"noApps": "Nema aplikacija",
"noAppsForFilter": "Nema aplikacija za filter",
"byX": "Autor {}",
"percentProgress": "Napredak: {}%",
"pleaseWait": "Molimo sačekajte",
"updateAvailable": "Ažuriranje dostupno",
"estimateInBracketsShort": "(Procjena)",
"notInstalled": "Nije instalirano",
"estimateInBrackets": "(Procjena)",
"selectAll": "Označi sve",
"deselectN": "Poništi odabir {}",
"xWillBeRemovedButRemainInstalled": "{} će biti uklonjen iz Obtainiuma, ali će ostati instaliran na uređaju.",
"removeSelectedAppsQuestion": "Želite li ukloniti odabrane aplikacije?",
"removeSelectedApps": "Ukloni odabrane aplikacije",
"updateX": "Nadogradi {}",
"installX": "Instaliraj {}",
"markXTrackOnlyAsUpdated": "Označi {}\n(samo za praćenje)\nkao ažurirano",
"changeX": "Promjena {}",
"installUpdateApps": "Instalirajte/ažurirajte aplikacije",
"installUpdateSelectedApps": "Instalirajte/ažurirajte odabrane aplikacije",
"markXSelectedAppsAsUpdated": "Označite {} odabrane aplikacije kao ažurirane?",
"no": "Ne",
"yes": "Da",
"markSelectedAppsUpdated": "Označi odabrane aplikacije kao ažurirane",
"pinToTop": "Prikvači na vrh",
"unpinFromTop": "Otkvači sa vrha",
"resetInstallStatusForSelectedAppsQuestion": "Resetujte status instalacije za 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.",
"shareSelectedAppURLs": "Podijeli odabrane URL-ove aplikacija",
"resetInstallStatus": "Resetujte status instalacije",
"more": "Više",
"removeOutdatedFilter": "Uklonite zastarjeli filter aplikacija",
"showOutdatedOnly": "Prikaži samo zastarjele aplikacije",
"filter": "Filtriranje",
"filterActive": "Filtriranje",
"filterApps": "Filtriraj aplikacije",
"appName": "Naziv aplikacije",
"author": "Autor",
"upToDateApps": "Ažurirane aplikacije",
"nonInstalledApps": "Neinstalirane aplikacije",
"importExport": "Uvoz/izvoz",
"settings": "Postavke",
"exportedTo": "Izvezeno u {}",
"obtainiumExport": "Obtainium Export",
"invalidInput": "Neispravan unos.",
"importedX": "Uvezeno {}",
"obtainiumImport": "Obtainium uvoz",
"importFromURLList": "Uvoz iz URL liste",
"searchQuery": "Pretraga za: ",
"appURLList": "Lista URL adresa aplikacija",
"line": "Linija",
"searchX": "Pretraživanje {}",
"noResults": "Nema rezultata",
"importX": "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.",
"importErrors": "Uvezi greške",
"importedXOfYApps": "{} od {} aplikacija uvezeno.",
"followingURLsHadErrors": "Sljedeći URL-ovi su imali greške:",
"okay": "Dobro",
"selectURL": "Odaberite URL",
"selectURLs": "Odaberite URL-ove",
"pick": "Odaberi",
"theme": "Tema",
"dark": "Tamna",
"light": "Svijetla",
"followSystem": "Pratite sistem",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Koristite čisto crnu tamnu temu",
"appSortBy": "Aplikacije sortirane po",
"authorName": "Autor/Ime",
"nameAuthor": "Ime/Autor",
"asAdded": "Kao što je dodano",
"appSortOrder": "Redoslijed sortiranja aplikacija",
"ascending": "Uzlazno",
"descending": "Silazno",
"bgUpdateCheckInterval": "Interval provjere ažuriranja u pozadini",
"neverManualOnly": "Nikada - samo ručno",
"appearance": "Izgled",
"showWebInAppView": "Prikaži izvornu web stranicu u prikazu aplikacije",
"pinUpdates": "Prikvačite ažuriranja na vrh prikaza aplikacija",
"updates": "Nadogradnje",
"sourceSpecific": "Specifično za izvor",
"appSource": "Izvor aplikacije",
"noLogs": "Nema evidencije",
"appLogs": "Evidencije aplikacija",
"close": "Zatvori",
"share": "Podijeli",
"appNotFound": "Aplikacija nije pronađena",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "Odaberite APK",
"appHasMoreThanOnePackage": "{} ima više od jednog paketa:",
"deviceSupportsXArch": "Vaš uređaj podržava {} arhitekturu procesora.",
"deviceSupportsFollowingArchs": "Vaš uređaj podržava sljedeće arhitekture procesora:",
"warning": "Upozorenje",
"sourceIsXButPackageFromYPrompt": "Izvor aplikacije je '{}', ali paket za izdavanje dolazi iz '{}'. Želite li nastaviti?",
"updatesAvailable": "Dostupna ažuriranja",
"updatesAvailableNotifDescription": "Obavještava korisnika da su ažuriranja dostupna za jednu ili više aplikacija koje prati Obtainium",
"noNewUpdates": "Nema novih ažuriranja.",
"xHasAnUpdate": "{} ima ažuriranje.",
"appsUpdated": "Aplikacije su ažurirane",
"appsUpdatedNotifDescription": "Obavještava korisnika da su u pozadini primijenjena ažuriranja na jednu ili više aplikacija",
"xWasUpdatedToY": "{} je ažuriran na {}.",
"errorCheckingUpdates": "Greška pri provjeri ažuriranja",
"errorCheckingUpdatesNotifDescription": "Obavijest koja se prikazuje kada provjera sigurnosnog ažuriranja ne uspije",
"appsRemoved": "Aplikacije su uklonjene",
"appsRemovedNotifDescription": "Obavještava korisnika da je jedna ili više aplikacija uklonjeno zbog grešaka prilikom učitavanja",
"xWasRemovedDueToErrorY": "{} je uklonjen zbog ove greške: {}",
"completeAppInstallation": "Dovršite instalaciju aplikacije",
"obtainiumMustBeOpenToInstallApps": "Obtainium mora biti otvoren za instalaciju aplikacija",
"completeAppInstallationNotifDescription": "Traži od korisnika da se vrati u Obtainium kako bi dovršio instalaciju aplikacije",
"checkingForUpdates": "Tražim moguće nadogradnje",
"checkingForUpdatesNotifDescription": "Privremeno obavještenje koje se pojavljuje prilikom provjere ažuriranja",
"pleaseAllowInstallPerm": "Dozvolite Obtainiumu da instalira aplikacije",
"trackOnly": "Samo za praćenje",
"errorWithHttpStatusCode": "Greška {}",
"versionCorrectionDisabled": "Ispravka verzije je onemogućena (izgleda da plugin ne radi)",
"unknown": "Nepoznato",
"none": "Ništa",
"never": "Nikad",
"latestVersionX": "Najnovija verzija: {}",
"installedVersionX": "Instalirana verzija: {}",
"lastUpdateCheckX": "Posljednja provjera ažuriranja: {}",
"remove": "Izbriši",
"yesMarkUpdated": "Da, označi kao ažurirano",
"fdroid": "F-Droid Official",
"appIdOrName": "ID ili ime aplikacije",
"appId": "Apl ID",
"appWithIdOrNameNotFound": "Nije pronađena aplikacija s tim ID-om ili imenom",
"reposHaveMultipleApps": "Repo-i mogu sadržavati više aplikacija",
"fdroidThirdPartyRepo": "F-Droid Repo treće strane",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Razgovor na Steamu (chat)",
"install": "Instaliraj",
"markInstalled": "Označi kao instalirano",
"update": "Nadogradi",
"markUpdated": "Označi kao ažurirano",
"additionalOptions": "Dodatne opcije",
"disableVersionDetection": "Onemogući detekciju verzije",
"noVersionDetectionExplanation": "Ova opcija bi se trebala koristiti samo za aplikacije gdje detekcija verzije ne radi ispravno.",
"downloadingX": "Preuzimanje {}",
"downloadNotifDescription": "Obavještava korisnika o napretku u preuzimanju aplikacije",
"noAPKFound": "APK nije pronađen",
"noVersionDetection": "Nema detekcije verzije",
"categorize": "Kategoriziraj",
"categories": "Kategorije",
"category": "Kategorija",
"noCategory": "Nema kategorije",
"noCategories": "Nema kategorija",
"deleteCategoriesQuestion": "Želite li izbrisati kategorije?",
"categoryDeleteWarning": "Sve aplikacije u izbrisanim kategorijama će biti postavljene kao nekategorisane.",
"addCategory": "Dodaj kategoriju",
"label": "Oznaka",
"language": "Jezik",
"copiedToClipboard": "Podaci kopirani u međuspremnik",
"storagePermissionDenied": "Dozvola za pohranu je odbijena",
"selectedCategorizeWarning": "Ovo će zamijeniti sve postojeće postavke kategorije za odabrane aplikacije.",
"filterAPKsByRegEx": "Filtrirajte APK-ove prema regularnom izrazu",
"removeFromObtainium": "Ukloni iz Obtainiuma",
"uninstallFromDevice": "Deinstaliraj s uređaja",
"onlyWorksWithNonVersionDetectApps": "Radi samo za aplikacije s onemogućenom detekcijom verzije.",
"releaseDateAsVersion": "Koristi datum izdanja kao verziju",
"releaseDateAsVersionExplanation": "Ova opcija bi se trebala koristiti samo za aplikacije gdje detekcija verzije ne radi ispravno, ali je datum izdavanja dostupan.",
"changes": "Promjene",
"releaseDate": "Datum izdavanja",
"importFromURLsInFile": "Uvoz iz URL-ova u datoteci (kao što je OPML)",
"versionDetection": "Otkrivanje verzije",
"standardVersionDetection": "Detekcija standardne verzije",
"groupByCategory": "Grupiši po kategoriji",
"autoApkFilterByArch": "Pokušajte filtrirati APK-ove po arhitekturi procesora ako je moguće",
"overrideSource": "Premosti izvor",
"dontShowAgain": "Ne prikazuj ovo ponovo",
"dontShowTrackOnlyWarnings": "Ne prikazuj upozorenja „Samo za praćenje",
"dontShowAPKOriginWarnings": "Ne prikazuj upozorenja o porijeklu APK-a",
"moveNonInstalledAppsToBottom": "Premjesti neinstalirane aplikacije na dno prikaza aplikacija",
"gitlabPATLabel": "GitLab token za lični pristup\n(Omogućava pretraživanje i bolje otkrivanje APK-a)",
"about": "O nama",
"requiresCredentialsInSettings": "Za ovo su potrebni dodatni akreditivi (u Postavkama)",
"checkOnStart": "Provjerite ima li novosti pri pokretanju",
"tryInferAppIdFromCode": "Pokušati otkriti ID aplikacije iz izvornog koda",
"removeOnExternalUninstall": "Automatski ukloni eksterno deinstalirane aplikacije",
"pickHighestVersionCode": "Automatski odaberite najviši kôd verzije APK-a",
"checkUpdateOnDetailPage": "Provjerite ima li novosti pri otvaranju stranice s detaljima aplikacije",
"disablePageTransitions": "Ugasite animaciju prijelaza stranice",
"reversePageTransitions": "Reverzne animacije prijelaza stranice",
"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.",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"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."
}
{
"invalidURLForSource": "Nije važeći URL aplikacije {}",
"noReleaseFound": "Nije moguće pronaći odgovarajuće izdanje",
"noVersionFound": "Nije moguće odrediti verziju izdanja",
"urlMatchesNoSource": "URL se ne podudara s poznatim izvorom",
"cantInstallOlderVersion": "Nije moguće instalirati stariju verziju aplikacije",
"appIdMismatch": "ID preuzetog paketa se ne podudara s postojećim ID-om aplikacije",
"functionNotImplemented": "Ova klasa nije implementirala ovu funkciju",
"placeholder": "Rezervirano mjesto",
"someErrors": "Došlo je do nekih grešaka",
"unexpectedError": "Neočekivana greška",
"ok": "Dobro",
"and": "i",
"githubPATLabel": "GitHub token za lični pristup (eng. PAT, povećava ograničenje stope)",
"includePrereleases": "Uključi preliminarna izdanja",
"fallbackToOlderReleases": "Povratak na starija izdanja",
"filterReleaseTitlesByRegEx": "Filtrirajte naslove izdanja prema regularnom izrazu",
"invalidRegEx": "Nevažeći regularni izraz",
"noDescription": "Bez opisa",
"cancel": "Otkaži",
"continue": "Nastavite",
"requiredInBrackets": "(obavezno)",
"dropdownNoOptsError": "GREŠKA: PADAJUĆI MENI MORA IMATI NAJMANJE JEDNU OPCIJU",
"colour": "Boja",
"githubStarredRepos": "GitHub repo-i sa zvjezdicom",
"uname": "Korisničko ime",
"wrongArgNum": "Naveden je pogrešan broj argumenata",
"xIsTrackOnly": "{} je samo za praćenje",
"source": "Izvor",
"app": "Aplikacija. ",
"appsFromSourceAreTrackOnly": "Aplikacije iz ovog izvora su 'Samo za praćenje'.",
"youPickedTrackOnly": "Odabrali ste opciju „Samo za praćenje”.",
"trackOnlyAppDescription": "Aplikacija će se pratiti radi ažuriranja, ali Obtainium neće moći da je preuzme ili instalira.",
"cancelled": "Otkazano",
"appAlreadyAdded": "Aplikacija je već dodana",
"alreadyUpToDateQuestion": "Aplikacija je već ažurirana?",
"addApp": "Dodaj aplikaciju",
"appSourceURL": "Izvorni URL aplikacije",
"error": "Greška",
"add": "Dodaj",
"searchSomeSourcesLabel": "Pretraživanje (samo neki izvori)",
"search": "Pretraživanje",
"additionalOptsFor": "Dodatne opcije za {}",
"supportedSources": "Podržani izvori",
"trackOnlyInBrackets": "(Samo za praćenje)",
"searchableInBrackets": "(Može se pretraživati)",
"appsString": "Aplikacije",
"noApps": "Nema aplikacija",
"noAppsForFilter": "Nema aplikacija za filter",
"byX": "Autor {}",
"percentProgress": "Napredak: {}%",
"pleaseWait": "Molimo sačekajte",
"updateAvailable": "Ažuriranje dostupno",
"notInstalled": "Nije instalirano",
"pseudoVersion": "pseudo-verzija",
"selectAll": "Označi sve",
"deselectX": "Poništi odabir {}",
"xWillBeRemovedButRemainInstalled": "{} će biti uklonjen iz Obtainiuma, ali će ostati instaliran na uređaju.",
"removeSelectedAppsQuestion": "Želite li ukloniti odabrane aplikacije?",
"removeSelectedApps": "Ukloni odabrane aplikacije",
"updateX": "Nadogradi {}",
"installX": "Instaliraj {}",
"markXTrackOnlyAsUpdated": "Označi {}\n(samo za praćenje)\nkao ažurirano",
"changeX": "Promjena {}",
"installUpdateApps": "Instalirajte/ažurirajte aplikacije",
"installUpdateSelectedApps": "Instalirajte/ažurirajte odabrane aplikacije",
"markXSelectedAppsAsUpdated": "Označite {} odabrane aplikacije kao ažurirane?",
"no": "Ne",
"yes": "Da",
"markSelectedAppsUpdated": "Označi odabrane aplikacije kao ažurirane",
"pinToTop": "Prikvači na vrh",
"unpinFromTop": "Otkvači sa vrha",
"resetInstallStatusForSelectedAppsQuestion": "Resetujte status instalacije za 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.",
"customLinkMessage": "Ove veze rade na uređajima s instaliranim Obtainiumom",
"shareAppConfigLinks": "Podijelite konfiguraciju aplikacije kao HTML vezu",
"shareSelectedAppURLs": "Podijeli odabrane URL-ove aplikacija",
"resetInstallStatus": "Resetujte status instalacije",
"more": "Više",
"removeOutdatedFilter": "Uklonite zastarjeli filter aplikacija",
"showOutdatedOnly": "Prikaži samo zastarjele aplikacije",
"filter": "Filtriranje",
"filterActive": "Filtriranje",
"filterApps": "Filtriraj aplikacije",
"appName": "Naziv aplikacije",
"author": "Autor",
"upToDateApps": "Ažurirane aplikacije",
"nonInstalledApps": "Neinstalirane aplikacije",
"importExport": "Uvoz/izvoz",
"settings": "Postavke",
"exportedTo": "Izvezeno u {}",
"obtainiumExport": "Obtainium izvoz",
"invalidInput": "Neispravan unos.",
"importedX": "Uvezeno {}",
"obtainiumImport": "Obtainium uvoz",
"importFromURLList": "Uvoz iz URL liste",
"searchQuery": "Pretraga za: ",
"appURLList": "Lista URL adresa aplikacija",
"line": "Linija",
"searchX": "Pretraživanje {}",
"noResults": "Nema rezultata",
"importX": "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.",
"importErrors": "Uvezi greške",
"importedXOfYApps": "{} od {} aplikacija uvezeno.",
"followingURLsHadErrors": "Sljedeći URL-ovi su imali greške:",
"selectURL": "Odaberite URL",
"selectURLs": "Odaberite URL-ove",
"pick": "Odaberi",
"theme": "Tema",
"dark": "Tamna",
"light": "Svijetla",
"followSystem": "Pratite sistem",
"useBlackTheme": "Koristite čisto crnu tamnu temu",
"appSortBy": "Aplikacije sortirane po",
"authorName": "Autor/Ime",
"nameAuthor": "Ime/Autor",
"asAdded": "Kao što je dodano",
"appSortOrder": "Redoslijed sortiranja aplikacija",
"ascending": "Uzlazno",
"descending": "Silazno",
"bgUpdateCheckInterval": "Interval provjere ažuriranja u pozadini",
"neverManualOnly": "Nikada - samo ručno",
"appearance": "Izgled",
"showWebInAppView": "Prikaži izvornu web stranicu u prikazu aplikacije",
"pinUpdates": "Prikvačite ažuriranja na vrh prikaza aplikacija",
"updates": "Nadogradnje",
"sourceSpecific": "Specifično za izvor",
"appSource": "Izvor aplikacije",
"noLogs": "Nema evidencije",
"appLogs": "Evidencije aplikacija",
"close": "Zatvori",
"share": "Podijeli",
"appNotFound": "Aplikacija nije pronađena",
"obtainiumExportHyphenatedLowercase": "obtainium-izvoz",
"pickAnAPK": "Odaberite APK",
"appHasMoreThanOnePackage": "{} ima više od jednog paketa:",
"deviceSupportsXArch": "Vaš uređaj podržava {} arhitekturu procesora.",
"deviceSupportsFollowingArchs": "Vaš uređaj podržava sljedeće arhitekture procesora:",
"warning": "Upozorenje",
"sourceIsXButPackageFromYPrompt": "Izvor aplikacije je '{}', ali paket za izdavanje dolazi iz '{}'. Želite li nastaviti?",
"updatesAvailable": "Dostupna ažuriranja",
"updatesAvailableNotifDescription": "Obavještava korisnika da su ažuriranja dostupna za jednu ili više aplikacija koje prati Obtainium",
"noNewUpdates": "Nema novih ažuriranja.",
"xHasAnUpdate": "{} ima ažuriranje.",
"appsUpdated": "Aplikacije su ažurirane",
"appsUpdatedNotifDescription": "Obavještava korisnika da su u pozadini primijenjena ažuriranja na jednu ili više aplikacija",
"xWasUpdatedToY": "{} je ažuriran na {}.",
"errorCheckingUpdates": "Greška pri provjeri ažuriranja",
"errorCheckingUpdatesNotifDescription": "Obavijest koja se prikazuje kada provjera sigurnosnog ažuriranja ne uspije",
"appsRemoved": "Aplikacije su uklonjene",
"appsRemovedNotifDescription": "Obavještava korisnika da je jedna ili više aplikacija uklonjeno zbog grešaka prilikom učitavanja",
"xWasRemovedDueToErrorY": "{} je uklonjen zbog ove greške: {}",
"completeAppInstallation": "Dovršite instalaciju aplikacije",
"obtainiumMustBeOpenToInstallApps": "Obtainium mora biti otvoren za instalaciju aplikacija",
"completeAppInstallationNotifDescription": "Traži od korisnika da se vrati u Obtainium kako bi dovršio instalaciju aplikacije",
"checkingForUpdates": "Tražim moguće nadogradnje",
"checkingForUpdatesNotifDescription": "Privremeno obavještenje koje se pojavljuje prilikom provjere ažuriranja",
"pleaseAllowInstallPerm": "Dozvolite Obtainiumu da instalira aplikacije",
"trackOnly": "Samo za praćenje",
"errorWithHttpStatusCode": "Greška {}",
"versionCorrectionDisabled": "Ispravka verzije je onemogućena (izgleda da plugin ne radi)",
"unknown": "Nepoznato",
"none": "Ništa",
"never": "Nikad",
"latestVersionX": "Najnovija verzija: {}",
"installedVersionX": "Instalirana verzija: {}",
"lastUpdateCheckX": "Posljednja provjera ažuriranja: {}",
"remove": "Izbriši",
"yesMarkUpdated": "Da, označi kao ažurirano",
"fdroid": "Službeni F-Droid",
"appIdOrName": "ID ili ime aplikacije",
"appId": "Apl ID",
"appWithIdOrNameNotFound": "Nije pronađena aplikacija s tim ID-om ili imenom",
"reposHaveMultipleApps": "Repo-i mogu sadržavati više aplikacija",
"fdroidThirdPartyRepo": "F-Droid Repo treće strane",
"steamMobile": "Steam Mobile",
"steamChat": "Razgovor na Steamu (chat)",
"install": "Instaliraj",
"markInstalled": "Označi kao instalirano",
"update": "Nadogradi",
"markUpdated": "Označi kao ažurirano",
"additionalOptions": "Dodatne opcije",
"disableVersionDetection": "Onemogući detekciju verzije",
"noVersionDetectionExplanation": "Ova opcija bi se trebala koristiti samo za aplikacije gdje detekcija verzije ne radi ispravno.",
"downloadingX": "Preuzimanje {}",
"downloadNotifDescription": "Obavještava korisnika o napretku u preuzimanju aplikacije",
"noAPKFound": "APK nije pronađen",
"noVersionDetection": "Nema detekcije verzije",
"categorize": "Kategoriziraj",
"categories": "Kategorije",
"category": "Kategorija",
"noCategory": "Nema kategorije",
"noCategories": "Nema kategorija",
"deleteCategoriesQuestion": "Želite li izbrisati kategorije?",
"categoryDeleteWarning": "Sve aplikacije u izbrisanim kategorijama će biti postavljene kao nekategorisane.",
"addCategory": "Dodaj kategoriju",
"label": "Oznaka",
"language": "Jezik",
"copiedToClipboard": "Podaci kopirani u međuspremnik",
"storagePermissionDenied": "Dozvola za pohranu je odbijena",
"selectedCategorizeWarning": "Ovo će zamijeniti sve postojeće postavke kategorije za odabrane aplikacije.",
"filterAPKsByRegEx": "Filtrirajte APK-ove prema regularnom izrazu",
"removeFromObtainium": "Ukloni iz Obtainiuma",
"uninstallFromDevice": "Deinstaliraj s uređaja",
"onlyWorksWithNonVersionDetectApps": "Radi samo za aplikacije s onemogućenom detekcijom verzije.",
"releaseDateAsVersion": "Koristi datum izdanja kao verziju",
"releaseDateAsVersionExplanation": "Ova opcija bi se trebala koristiti samo za aplikacije gdje detekcija verzije ne radi ispravno, ali je datum izdavanja dostupan.",
"changes": "Promjene",
"releaseDate": "Datum izdavanja",
"importFromURLsInFile": "Uvoz iz URL-ova u datoteci (kao što je OPML)",
"versionDetectionExplanation": "Pomiri niz verzije sa verzijom otkrivenom iz OS-a",
"versionDetection": "Otkrivanje verzije",
"standardVersionDetection": "Detekcija standardne verzije",
"groupByCategory": "Grupiši po kategoriji",
"autoApkFilterByArch": "Pokušajte filtrirati APK-ove po arhitekturi procesora ako je moguće",
"overrideSource": "Premosti izvor",
"dontShowAgain": "Ne prikazuj ovo ponovo",
"dontShowTrackOnlyWarnings": "Ne prikazuj upozorenja „Samo za praćenje”",
"dontShowAPKOriginWarnings": "Ne prikazuj upozorenja o porijeklu APK-a",
"moveNonInstalledAppsToBottom": "Premjesti neinstalirane aplikacije na dno prikaza aplikacija",
"gitlabPATLabel": "GitLab token za lični pristup\n(Omogućava pretraživanje i bolje otkrivanje APK-a)",
"about": "O nama",
"requiresCredentialsInSettings": "{}: Za ovo su potrebni dodatni akreditivi (u Postavkama)",
"checkOnStart": "Provjerite ima li novosti pri pokretanju",
"tryInferAppIdFromCode": "Pokušati otkriti ID aplikacije iz izvornog koda",
"removeOnExternalUninstall": "Automatski ukloni eksterno deinstalirane aplikacije",
"pickHighestVersionCode": "Automatski odaberite najviši kôd verzije APK-a",
"checkUpdateOnDetailPage": "Provjerite ima li novosti pri otvaranju stranice s detaljima aplikacije",
"disablePageTransitions": "Ugasite animaciju prijelaza stranice",
"reversePageTransitions": "Reverzne animacije prijelaza stranice",
"minStarCount": "Najmanji broj zvjezdica",
"addInfoBelow": "Dodajte ove informacije ispod.",
"addInfoInSettings": "Dodajte ove informacije u Postavkama.",
"githubSourceNote": "GitHub ograničavanje se može izbjeći korišćenjem tokena za lični pristup.",
"gitlabSourceNote": "GitLab APK preuzimanje možda neće raditi bez tokena za lični pristup.",
"sortByLastLinkSegment": "Sortiraj samo po zadnjem segmentu veze",
"filterReleaseNotesByRegEx": "Filtirajte promjene u izdanju po regularnom izrazu",
"customLinkFilterRegex": "Prilagođeni APK link filtrira se po regularnom izrazu (Zadano '.apk$')",
"appsPossiblyUpdated": "Pokušano ažuriranje aplikacija",
"appsPossiblyUpdatedNotifDescription": "Obavještava korisnika da je ažuriranje jedne ili više aplikacija potencijalno izvršeno u pozadini",
"xWasPossiblyUpdatedToY": "{} aplikacija bi trebala biti ažurirana na {}.",
"enableBackgroundUpdates": "Dozvolite ažuriranja u pozadini",
"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.",
"verifyLatestTag": "Provjerite 'posljednu' ('latest') oznaku",
"intermediateLinkRegex": "Filter za 'srednju' vezu za posjetu",
"filterByLinkText": "Filtriraj linkove prema tekstu linka",
"intermediateLinkNotFound": "Intermediate link nije nađen",
"intermediateLink": "srednja karika",
"exemptFromBackgroundUpdates": "Izuzmi iz ažuriranja u pozadini (ako su uključeni)",
"bgUpdatesOnWiFiOnly": "Isključite ažuriranje u pozadini kada niste na WiFi-ju",
"autoSelectHighestVersionCode": "Automatski izaberite najveću (verziju) versionCode APK-a",
"versionExtractionRegEx": "RegEx ekstrakcija verzije",
"matchGroupToUse": "Podjesite grupu za upotebu",
"highlightTouchTargets": "Istaknite manje vidljive touch mete",
"pickExportDir": "Izaberite datoteku za izvoz",
"autoExportOnChanges": "Automatski izvezite pri promjenama",
"includeSettings": "Uključi postavke",
"filterVersionsByRegEx": "Filtrirajte verzije po regulatnom izrazu",
"trySelectingSuggestedVersionCode": "Probajte izabrati preloženu (verziju) versionCode APK-a",
"dontSortReleasesList": "Zadrži redosled izdanja iz API-a",
"reverseSort": "Obrni redosled",
"takeFirstLink": "Uzmi prvi link",
"skipSort": "Preskoči sortiranje",
"debugMenu": "Meni za otkrivanje grešaka",
"bgTaskStarted": "Rad u pozadini pokrenut - provjerite log-ove.",
"runBgCheckNow": "Pokrenite pozadinsku provjeru ažuriranja sad",
"versionExtractWholePage": "Primjenite Regex ekstrakciju verzije na cijelu stranicu",
"installing": "Instaliranje",
"skipUpdateNotifications": "Ne prikazujte obavještenja ažuriranja",
"updatesAvailableNotifChannel": "Dostupna ažuriranja",
"appsUpdatedNotifChannel": "Aplikacije su ažurirane",
"appsPossiblyUpdatedNotifChannel": "Pokušano ažuriranje aplikacija",
"errorCheckingUpdatesNotifChannel": "Greška pri provjeri ažuriranja",
"appsRemovedNotifChannel": "Aplikacije su uklonjene",
"downloadingXNotifChannel": "Preuzimanje {}",
"completeAppInstallationNotifChannel": "Dovršite instalaciju aplikacije",
"checkingForUpdatesNotifChannel": "Tražim moguće nadogradnje",
"onlyCheckInstalledOrTrackOnlyApps": "Isključivo provjerite ažuriranje za instalirane i aplikacije 'samo za praćenje'",
"supportFixedAPKURL": "Podržite fiksne APK URL-ove",
"selectX": "Izaberite {}",
"parallelDownloads": "Dozvoli paralelna preuzimanja",
"installMethod": "Način instalacije",
"normal": "normalno",
"root": "korijen",
"shizukuBinderNotFound": "Shizuku is not running",
"useSystemFont": "Koristite sistemski font",
"systemFontError": "Greška pri učitavanju sistemskog fonta: {}",
"useVersionCodeAsOSVersion": "Koristite kod verzije aplikacije kao verziju koju je otkrio OS",
"requestHeader": "Zaglavlje zahtjeva",
"useLatestAssetDateAsReleaseDate": "Koristite najnovije otpremanje materijala kao datum izdavanja",
"defaultPseudoVersioningMethod": "Zadana metoda pseudo-verzija",
"partialAPKHash": "Djelomični APK Hash",
"APKLinkHash": "APK Link Hash",
"directAPKLink": "Direktna APK veza",
"pseudoVersionInUse": "Pseudo-verzija je u upotrebi",
"installed": "Instalirano",
"latest": "Najnoviji",
"invertRegEx": "Obrni regularni izraz",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"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."
}
}

355
assets/translations/cs.json Normal file
View File

@ -0,0 +1,355 @@
{
"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",
"requiredInBrackets": "(Požadované)",
"dropdownNoOptsError": "ERROR: DROPDOWN MUSÍ MÍT AŽ JEDNU MOŽNOST",
"colour": "Barva",
"githubStarredRepos": "GitHub označená hvězdičkou",
"uname": "Uživatelské jméno",
"wrongArgNum": "Nesprávný počet zadaných argumentů",
"xIsTrackOnly": "{} je určeno pouze pro sledování",
"source": "Zdroj",
"app": "Aplikace",
"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",
"notInstalled": "Není nainstalováno",
"pseudoVersion": "pseudoverze",
"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ů.",
"customLinkMessage": "Tyto odkazy fungují na zařízeních s nainstalovaným Obtainium",
"shareAppConfigLinks": "Sdílet konfiguraci aplikace jako odkaz HTML",
"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": "Export obtainia",
"invalidInput": "Neplatný vstup",
"importedX": "Importováno {}",
"obtainiumImport": "Dovoz obtainia",
"importFromURLList": "Import ze seznamu URL",
"searchQuery": "Vyhledávací dotaz",
"appURLList": "Seznam adres aplikací",
"line": "Linka",
"searchX": "Vyhledávání {}",
"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",
"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": "Aktualizace",
"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": "Žádný",
"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": "ID aplikace",
"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",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"install": "Nainstalujte",
"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)",
"versionDetectionExplanation": "Sloučit řetězec verze s verzí zjištěnou z OS",
"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í",
"root": "Správce",
"shizukuBinderNotFound": "Shizuku neběží",
"useSystemFont": "Použít systémové písmo",
"systemFontError": "Chyba při načítání systémového písma: {}",
"useVersionCodeAsOSVersion": "Použít kód verze aplikace jako verzi zjištěnou OS",
"requestHeader": "Hlavička požadavku",
"useLatestAssetDateAsReleaseDate": "Použít poslední nahrané dílo jako datum vydání",
"defaultPseudoVersioningMethod": "Výchozí metoda pseudoverze",
"partialAPKHash": "Částečný hash APK",
"APKLinkHash": "APK Link Hash",
"directAPKLink": "Přímý odkaz APK",
"pseudoVersionInUse": "Pseudoverze se používá",
"installed": "Instalováno",
"latest": "Nejnovější",
"invertRegEx": "Invertovat regulární výraz",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"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."
}
}

View File

@ -11,15 +11,7 @@
"unexpectedError": "Unerwarteter Fehler",
"ok": "Okay",
"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)",
"githubPATHint": "PAT muss in diesem Format sein: Benutzername:Token",
"githubPATFormat": "Benutzername:Token",
"includePrereleases": "Vorabversionen einbeziehen",
"fallbackToOlderReleases": "Fallback auf ältere Versionen",
"filterReleaseTitlesByRegEx": "Release-Titel nach regulärem Ausdruck\nfiltern",
@ -32,12 +24,12 @@
"colour": "Farbe",
"githubStarredRepos": "GitHub Starred Repos",
"uname": "Benutzername",
"wrongArgNum": "Falsche Anzahl von Argumenten übermittelt",
"wrongArgNum": "Falsche Anzahl von Argumenten (Parametern) übermittelt",
"xIsTrackOnly": "{} ist nur zur Nachverfolgung",
"source": "Quelle",
"app": "App",
"appsFromSourceAreTrackOnly": "Apps aus dieser Quelle sind 'Nur Nachverfolgen'.",
"youPickedTrackOnly": "Sie haben die Option 'Nur Nachverfolgen' gewählt.",
"appsFromSourceAreTrackOnly": "Apps aus dieser Quelle sind nur zum Nachverfolgen.",
"youPickedTrackOnly": "Sie haben die Option Nur Nachverfolgen gewählt.",
"trackOnlyAppDescription": "Die App wird auf Updates überwacht, aber Obtainium wird sie nicht herunterladen oder installieren.",
"cancelled": "Abgebrochen",
"appAlreadyAdded": "App bereits hinzugefügt",
@ -46,10 +38,10 @@
"appSourceURL": "Quell-URL der App",
"error": "Fehler",
"add": "Hinzufügen",
"searchSomeSourcesLabel": "Suche (nur bestimmte Quellen)",
"searchSomeSourcesLabel": "Suche (nur für bestimmte Quellen)",
"search": "Suchen",
"additionalOptsFor": "Zusatzoptionen für {}",
"supportedSourcesBelow": "Unterstützte Quellen:",
"supportedSources": "Unterstützte Quellen",
"trackOnlyInBrackets": "(Nur Nachverfolgen)",
"searchableInBrackets": "(Durchsuchbar)",
"appsString": "Apps",
@ -59,11 +51,10 @@
"percentProgress": "Fortschritt: {}%",
"pleaseWait": "Bitte warten",
"updateAvailable": "Aktualisierung verfügbar",
"estimateInBracketsShort": "(ca.)",
"notInstalled": "Nicht installiert",
"estimateInBrackets": "(Ungefähr)",
"pseudoVersion": "Pseudoversion",
"selectAll": "Alle auswählen",
"deselectN": "{} abgewählt",
"deselectX": "{} abgewählt",
"xWillBeRemovedButRemainInstalled": "{} wird aus Obtainium entfernt, bleibt aber auf dem Gerät installiert.",
"removeSelectedAppsQuestion": "Ausgewählte Apps entfernen?",
"removeSelectedApps": "Ausgewählte Apps entfernen",
@ -78,13 +69,15 @@
"yes": "Ja",
"markSelectedAppsUpdated": "Markiere ausgewählte Apps als aktuell",
"pinToTop": "Oben anheften",
"unpinFromTop": "'Oben anheften' aufheben",
"unpinFromTop": "Oben anheften aufheben",
"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.",
"customLinkMessage": "Diese Links funktionieren auf Geräten, wo Obtainium installiert ist",
"shareAppConfigLinks": "Teile die Appkonfiguration als HTML-Link",
"shareSelectedAppURLs": "Ausgewählte App-URLs teilen",
"resetInstallStatus": "Installationsstatus zurücksetzen",
"more": "Mehr",
"removeOutdatedFilter": "App-Filter 'Nicht aktuell' entfernen",
"removeOutdatedFilter": "App-Filter Nicht aktuell entfernen",
"showOutdatedOnly": "Nur nicht aktuelle Apps anzeigen",
"filter": "Filter",
"filterActive": "Filter *",
@ -96,22 +89,21 @@
"importExport": "Import/Export",
"settings": "Einstellungen",
"exportedTo": "Exportiert zu {}",
"obtainiumExport": "Obtainium Export",
"obtainiumExport": "Obtainium-Export",
"invalidInput": "Ungültige Eingabe",
"importedX": "Importiert {}",
"obtainiumImport": "Obtainium Import",
"obtainiumImport": "Obtainium-Import",
"importFromURLList": "Importieren aus URL-Liste",
"searchQuery": "Suchanfrage",
"appURLList": "App URL-Liste",
"line": "Linie",
"searchX": "Suche {}",
"noResults": "Keine Ergebnisse gefunden",
"importX": "Import {}",
"importX": "Importieren {}",
"importedAppsIdDisclaimer": "Importierte Apps werden möglicherweise fälschlicherweise als \"Nicht installiert\" angezeigt. Um dies zu beheben, installieren Sie sie erneut über Obtainium. Dies hat keine Auswirkungen auf App-Daten. Es betrifft nur URL- und Drittanbieter-Importmethoden.",
"importErrors": "Importfehler",
"importedXOfYApps": "{} von {} Apps importiert.",
"followingURLsHadErrors": "Bei folgenden URLs traten Fehler auf:",
"okay": "Okay",
"selectURL": "URL auswählen",
"selectURLs": "URLs auswählen",
"pick": "Auswählen",
@ -119,8 +111,6 @@
"dark": "Dunkel",
"light": "Hell",
"followSystem": "System folgen",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Verwende Pure Black Dark Theme",
"appSortBy": "App sortieren nach",
"authorName": "Autor/Name",
@ -130,19 +120,19 @@
"ascending": "Aufsteigend",
"descending": "Absteigend",
"bgUpdateCheckInterval": "Prüfintervall für Hintergrundaktualisierung",
"neverManualOnly": "Nie - nur manuell",
"neverManualOnly": "Nie nur manuell",
"appearance": "Aussehen",
"showWebInAppView": "Quellwebseite in der App-Ansicht anzeigen",
"pinUpdates": "Apps mit Aktualisierungen oben anheften",
"updates": "Aktualisierungen",
"sourceSpecific": "Quellenspezifisch",
"appSource": "App-Quelle",
"noLogs": "Keine Protokolle",
"appLogs": "App Protokolle",
"noLogs": "Keine Logs",
"appLogs": "App-Logs",
"close": "Schließen",
"share": "Teilen",
"appNotFound": "App nicht gefunden",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"obtainiumExportHyphenatedLowercase": "Obtainium-Export",
"pickAnAPK": "APK auswählen",
"appHasMoreThanOnePackage": "{} verfügt über mehr als ein Paket:",
"deviceSupportsXArch": "Ihr Gerät unterstützt die CPU-Architektur {}.",
@ -178,15 +168,14 @@
"lastUpdateCheckX": "Letzte Aktualisierungsprüfung: {}",
"remove": "Entfernen",
"yesMarkUpdated": "Ja, als aktualisiert markieren",
"fdroid": "F-Droid Official",
"fdroid": "offizielles F-Droid-Repo",
"appIdOrName": "App ID oder Name",
"appId": "App ID",
"appWithIdOrNameNotFound": "Es wurde keine App mit dieser ID oder diesem Namen gefunden",
"reposHaveMultipleApps": "Repos können mehrere Apps enthalten",
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
"steam": "Steam",
"fdroidThirdPartyRepo": "F-Droid Drittparteienrepo",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamChat": "Steam-Chat",
"install": "Installieren",
"markInstalled": "Als Installiert markieren",
"update": "Aktualisieren",
@ -220,6 +209,7 @@
"changes": "Änderungen",
"releaseDate": "Veröffentlichungsdatum",
"importFromURLsInFile": "Importieren von URLs aus Datei (z. B. OPML)",
"versionDetectionExplanation": "Abgleich der Versionsnummer mit der vom Betriebssystem erkannten Version",
"versionDetection": "Versionserkennung",
"standardVersionDetection": "Standardversionserkennung",
"groupByCategory": "Nach Kategorie gruppieren",
@ -229,9 +219,9 @@
"dontShowTrackOnlyWarnings": "Warnung für 'Nur Nachverfolgen' nicht anzeigen",
"dontShowAPKOriginWarnings": "Warnung für APK-Herkunft nicht anzeigen",
"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",
"requiresCredentialsInSettings": "Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
"requiresCredentialsInSettings": "{}: Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
"checkOnStart": "Überprüfe einmalig beim Start",
"tryInferAppIdFromCode": "Versuche, die App-ID aus dem Quellcode zu ermitteln",
"removeOnExternalUninstall": "Automatisches Entfernen von extern deinstallierten Apps",
@ -244,31 +234,95 @@
"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",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"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",
"root": "Root",
"shizukuBinderNotFound": "Kompatibler Shizukudienst wurde nicht gefunden",
"useSystemFont": "Verwende die Systemschriftart",
"systemFontError": "Fehler beim Laden der Systemschriftart: {}",
"useVersionCodeAsOSVersion": "Verwende die Appversion als erkannte Version vom Betriebssystem",
"requestHeader": "Request Header",
"useLatestAssetDateAsReleaseDate": "Den letzten Asset-Upload als Veröffentlichungsdatum verwenden",
"defaultPseudoVersioningMethod": "Standardmäßiges Verfahren zur Pseudo-Versionierung",
"partialAPKHash": "partieller APK-Hash",
"APKLinkHash": "APK-Link-Hash",
"directAPKLink": "Direkter APK-Link",
"pseudoVersionInUse": "Pseudoversionen sind in Benutzung",
"installed": "Installiert",
"latest": "Neueste Version",
"invertRegEx": "Regulären Ausdruck invertieren",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"removeAppQuestion": {
"one": "App entfernen?",
"other": "Apps entfernen?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut",
"other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut"
"one": "Zu viele Anfragen (Rate begrenzt) versuche es in {} Minute erneut",
"other": "Zu viele Anfragen (Rate begrenzt) versuche es in {} Minuten erneut"
},
"bgUpdateGotErrorRetryInMinutes": {
"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"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "Hintergrundaktualisierungsprüfung fand {} Aktualisierung - benachrichtigt den Benutzer, falls erforderlich",
"other": "Hintergrundaktualisierungsprüfung fand {} Aktualisierungen - benachrichtigt den Benutzer, falls erforderlich"
"one": "Die Hintergrundaktualisierungsprüfung fand {} Aktualisierung benachrichtigt den Benutzer, falls erforderlich",
"other": "Die Hintergrundaktualisierungsprüfung fand {} Aktualisierungen benachrichtigt den Benutzer, falls erforderlich"
},
"apps": {
"one": "{} App",
"other": "{} Apps"
"eine": "{} App",
"andere": "{} Apps"
},
"url": {
"one": "{} URL",
"other": "{} URLs"
"eine": "{} URL",
"andere": "{} URLs"
},
"minute": {
"one": "{} Minute",
@ -283,8 +337,8 @@
"other": "{} Tage"
},
"clearedNLogsBeforeXAfterY": {
"one": "{n} Protokoll gelöscht (vorher = {vorher}, nachher = {nachher})",
"other": "{n} Protokolle gelöscht (vorher = {vorher}, nachher = {nachher})"
"one": "{n} Log gelöscht (vorher = {before}, nachher = {after})",
"other": "{n} Logs gelöscht (vorher = {before}, nachher = {after})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} und 1 weitere App haben Aktualisierungen.",
@ -293,5 +347,9 @@
"xAndNMoreUpdatesInstalled": {
"one": "{} und 1 weitere Anwendung 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."
}
}

View File

@ -11,15 +11,7 @@
"unexpectedError": "Unexpected Error",
"ok": "Okay",
"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)",
"githubPATHint": "PAT must be in this format: username:token",
"githubPATFormat": "username:token",
"includePrereleases": "Include prereleases",
"fallbackToOlderReleases": "Fallback to older releases",
"filterReleaseTitlesByRegEx": "Filter Release Titles by Regular Expression",
@ -49,7 +41,7 @@
"searchSomeSourcesLabel": "Search (Some Sources Only)",
"search": "Search",
"additionalOptsFor": "Additional Options for {}",
"supportedSourcesBelow": "Supported Sources:",
"supportedSources": "Supported Sources",
"trackOnlyInBrackets": "(Track-Only)",
"searchableInBrackets": "(Searchable)",
"appsString": "Apps",
@ -59,11 +51,10 @@
"percentProgress": "Progress: {}%",
"pleaseWait": "Please Wait",
"updateAvailable": "Update Available",
"estimateInBracketsShort": "(Est.)",
"notInstalled": "Not Installed",
"estimateInBrackets": "(Estimate)",
"pseudoVersion": "pseudo-version",
"selectAll": "Select All",
"deselectN": "Deselect {}",
"deselectX": "Deselect {}",
"xWillBeRemovedButRemainInstalled": "{} will be removed from Obtainium but remain installed on device.",
"removeSelectedAppsQuestion": "Remove Selected Apps?",
"removeSelectedApps": "Remove Selected Apps",
@ -81,6 +72,8 @@
"unpinFromTop": "Unpin from top",
"resetInstallStatusForSelectedAppsQuestion": "Reset Install Status for Selected Apps?",
"installStatusOfXWillBeResetExplanation": "The install status of any selected Apps will be reset.\n\nThis can help when the App version shown in Obtainium is incorrect due to failed updates or other issues.",
"customLinkMessage": "These links work on devices with Obtainium installed",
"shareAppConfigLinks": "Share app configuration as HTML link",
"shareSelectedAppURLs": "Share Selected App URLs",
"resetInstallStatus": "Reset Install Status",
"more": "More",
@ -111,7 +104,6 @@
"importErrors": "Import Errors",
"importedXOfYApps": "{} of {} Apps imported.",
"followingURLsHadErrors": "The following URLs had errors:",
"okay": "Okay",
"selectURL": "Select URL",
"selectURLs": "Select URLs",
"pick": "Pick",
@ -119,8 +111,6 @@
"dark": "Dark",
"light": "Light",
"followSystem": "Follow System",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Use pure black dark theme",
"appSortBy": "App Sort By",
"authorName": "Author/Name",
@ -173,8 +163,8 @@
"unknown": "Unknown",
"none": "None",
"never": "Never",
"latestVersionX": "Latest Version: {}",
"installedVersionX": "Installed Version: {}",
"latestVersionX": "Latest: {}",
"installedVersionX": "Installed: {}",
"lastUpdateCheckX": "Last Update Check: {}",
"remove": "Remove",
"yesMarkUpdated": "Yes, Mark as Updated",
@ -184,7 +174,6 @@
"appWithIdOrNameNotFound": "No App was found with that ID or Name",
"reposHaveMultipleApps": "Repos may contain multiple Apps",
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"install": "Install",
@ -215,11 +204,12 @@
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.",
"releaseDateAsVersion": "Use Release Date as Version",
"releaseDateAsVersion": "Use release date as version string",
"releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.",
"changes": "Changes",
"releaseDate": "Release Date",
"importFromURLsInFile": "Import from URLs in File (like OPML)",
"versionDetectionExplanation": "Reconcile version string with version detected from OS",
"versionDetection": "Version Detection",
"standardVersionDetection": "Standard version detection",
"groupByCategory": "Group by Category",
@ -231,7 +221,7 @@
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
"about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"requiresCredentialsInSettings": "{} needs additional credentials (in Settings)",
"checkOnStart": "Check for updates on startup",
"tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
@ -244,8 +234,72 @@
"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.",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"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 String Extraction RegEx",
"matchGroupToUse": "Match Group to Use for Version String 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 string 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",
"root": "Root",
"shizukuBinderNotFound": "Сompatible Shizuku service wasn't found",
"useSystemFont": "Use the system font",
"systemFontError": "Error loading the system font: {}",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"useLatestAssetDateAsReleaseDate": "Use latest asset upload as release date",
"defaultPseudoVersioningMethod": "Default Pseudo-Versioning Method",
"partialAPKHash": "Partial APK Hash",
"APKLinkHash": "APK Link Hash",
"directAPKLink": "Direct APK Link",
"pseudoVersionInUse": "A Pseudo-Version is in Use",
"installed": "Installed",
"latest": "Latest",
"invertRegEx": "Invert regular expression",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"removeAppQuestion": {
"one": "Remove App?",
"other": "Remove Apps?"
@ -291,7 +345,11 @@
"other": "{} and {} more apps have updates."
},
"xAndNMoreUpdatesInstalled": {
"one": "{} and 1 more app were updated.",
"one": "{} and 1 more app was 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."
}
}
}

View File

@ -1,95 +1,88 @@
{
"invalidURLForSource": "URL de la aplicación {} no válida",
"invalidURLForSource": "URL de la aplicación {} no es válida",
"noReleaseFound": "No se ha podido encontrar una versión válida",
"noVersionFound": "No se ha podido determinar la versión de la publicación",
"noVersionFound": "No se ha podido determinar la versión",
"urlMatchesNoSource": "La URL no coincide con ninguna fuente conocida",
"cantInstallOlderVersion": "No se puede instalar una versión previa de la aplicación",
"appIdMismatch": "La ID del paquete descargado no coincide con la ID de la aplicación instalada",
"functionNotImplemented": "Esta clase no ha implementado esta función",
"placeholder": "Espacio reservado",
"someErrors": "Han ocurrido algunos errores",
"unexpectedError": "Error Inesperado",
"ok": "Correcto",
"unexpectedError": "Error inesperado",
"ok": "OK",
"and": "y",
"startedBgUpdateTask": "Empezada la tarea de comprobación de actualizaciones en segundo plano",
"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",
"githubPATLabel": "Token de acceso personal a GitHub\n(reduce tiempos de espera)",
"includePrereleases": "Incluir versiones preliminares",
"fallbackToOlderReleases": "Retorceder a versiones previas",
"filterReleaseTitlesByRegEx": "Filtra Títulos de Versiones mediantes Expresiones Regulares",
"invalidRegEx": "Expresión regular inválida",
"fallbackToOlderReleases": "Retroceder a versiones previas",
"filterReleaseTitlesByRegEx": "Filtrar por título de versión",
"invalidRegEx": "Expresión inválida",
"noDescription": "Sin descripción",
"cancel": "Cancelar",
"continue": "Continuar",
"requiredInBrackets": "(Requerido)",
"dropdownNoOptsError": "ERROR: EL DESPLEGABLE DEBE TENER AL MENOS UNA OPCIÓN",
"colour": "Color",
"githubStarredRepos": "Repositorios favoritos de GitHub",
"githubStarredRepos": "Repositorios favoritos en GitHub",
"uname": "Nombre de usuario",
"wrongArgNum": "Número de argumentos provistos inválido",
"xIsTrackOnly": "{} es de 'Solo Seguimiento'",
"xIsTrackOnly": "{} es de 'lo seguimiento'",
"source": "Origen",
"app": "Aplicación",
"appsFromSourceAreTrackOnly": "Las aplicaciones de este origen son de 'Solo Seguimiento'.",
"youPickedTrackOnly": "Debes 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.",
"appsFromSourceAreTrackOnly": "Las aplicaciones de este origen son de 'solo seguimiento'.",
"youPickedTrackOnly": "Debe seleccionar la opción de 'solo seguimiento'.",
"trackOnlyAppDescription": "Se hará el seguimiento de actualizaciones para la aplicación, pero Obtainium no será capaz de descargar o actualizarla.",
"cancelled": "Cancelado",
"appAlreadyAdded": "Aplicación ya añadida",
"alreadyUpToDateQuestion": "¿Aplicación ya actualizada?",
"appAlreadyAdded": "Aplicación añadida anteriormente",
"alreadyUpToDateQuestion": "¿Aplicación actualizada previamente?",
"addApp": "Añadir Aplicación",
"appSourceURL": "URL de Origen de la Aplicación",
"appSourceURL": "URL de origen de la aplicación",
"error": "Error",
"add": "Añadir",
"searchSomeSourcesLabel": "Buscar (Solo Algunas Fuentes)",
"searchSomeSourcesLabel": "Buscar (solo algunas fuentes)",
"search": "Buscar",
"additionalOptsFor": "Opciones Adicionales para {}",
"supportedSourcesBelow": "Fuentes Soportadas:",
"trackOnlyInBrackets": "(Solo Seguimiento)",
"searchableInBrackets": "(Soporta Búsquedas)",
"supportedSources": "Fuentes Soportadas",
"trackOnlyInBrackets": "(Solo seguimiento)",
"searchableInBrackets": "(soporta búsqueda)",
"appsString": "Aplicaciones",
"noApps": "Sin Aplicaciones",
"noAppsForFilter": "Sin Aplicaciones para Filtrar",
"byX": "Por {}",
"noAppsForFilter": "Sin aplicaciones para filtrar",
"byX": "por: {}",
"percentProgress": "Progreso: {}%",
"pleaseWait": "Por favor, espere",
"updateAvailable": "Actualización Disponible",
"estimateInBracketsShort": "(Aprox.)",
"notInstalled": "No Instalado",
"estimateInBrackets": "(Aproximado)",
"pseudoVersion": "pseudoversión",
"selectAll": "Seleccionar Todo",
"deselectN": "Deseleccionar {}",
"xWillBeRemovedButRemainInstalled": "{} será borrada de Obtainium pero continuará instalada en el dispositivo.",
"removeSelectedAppsQuestion": "¿Borrar aplicaciones seleccionadas?",
"removeSelectedApps": "Borrar Aplicaciones Seleccionadas",
"deselectX": "Deseleccionar {}",
"xWillBeRemovedButRemainInstalled": "{} será eliminada de Obtainium pero continuará instalada en el dispositivo.",
"removeSelectedAppsQuestion": "¿Eliminar aplicaciones seleccionadas?",
"removeSelectedApps": "Eliminar Aplicaciones Seleccionadas",
"updateX": "Actualizar {}",
"installX": "Instalar {}",
"markXTrackOnlyAsUpdated": "Marcar {}\n(Solo Seguimient)\ncomo Actualizada",
"markXTrackOnlyAsUpdated": "Marcar {}\n(Solo seguimiento)\ncomo actualizada",
"changeX": "Cambiar {}",
"installUpdateApps": "Instalar/Actualizar Aplicaciones",
"installUpdateSelectedApps": "Instalar/Actualizar Aplicaciones Seleccionadas",
"markXSelectedAppsAsUpdated": "¿Marcar {} Aplicaciones Seleccionadas como Actualizadas?",
"installUpdateApps": "Instalar/Actualizar aplicaciones",
"installUpdateSelectedApps": "Instalar/Actualizar aplicaciones seleccionadas",
"markXSelectedAppsAsUpdated": "¿Marcar {} aplicaciones seleccionadas como actualizadas?",
"no": "No",
"yes": "Sí",
"markSelectedAppsUpdated": "Marcar Aplicaciones Seleccionadas como Actualizadas",
"markSelectedAppsUpdated": "Marcar aplicaciones seleccionadas como actualizadas",
"pinToTop": "Fijar arriba",
"unpinFromTop": "Desfijar de arriba",
"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.",
"shareSelectedAppURLs": "Compartir URLs de las Aplicaciones Seleccionadas",
"resetInstallStatus": "Restaurar Estado de Instalación",
"resetInstallStatusForSelectedAppsQuestion": "¿Restuarar estado de instalación para las aplicaciones seleccionadas?",
"installStatusOfXWillBeResetExplanation": "Se restaurará el estado de instalación de las aplicaciones seleccionadas.\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.",
"customLinkMessage": "Estos enlaces funcionan en dispositivos con Obtainium instalado",
"shareAppConfigLinks": "Compartir la configuración de la aplicación como enlace HTML",
"shareSelectedAppURLs": "Compartir URLs de las aplicaciones seleccionadas",
"resetInstallStatus": "Restaurar estado de instalación",
"more": "Más",
"removeOutdatedFilter": "Elimiar Filtro de Aplicaciones Desactualizado",
"showOutdatedOnly": "Mostrar solo Aplicaciones Desactualizadas",
"removeOutdatedFilter": "Elimiar filtro de aplicaciones desactualizado",
"showOutdatedOnly": "Mostrar solo aplicaciones desactualizadas",
"filter": "Filtrar",
"filterActive": "Filtrar *",
"filterApps": "Filtrar Actualizaciones",
"appName": "Nombre de la Aplicación",
"appName": "Nombre de la aplicación",
"author": "Autor",
"upToDateApps": "Aplicaciones Actualizadas",
"nonInstalledApps": "Aplicaciones No Instaladas",
@ -106,146 +99,207 @@
"line": "Línea",
"searchX": "Buscar {}",
"noResults": "Resultados no encontrados",
"importX": "Importar {}",
"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.",
"importErrors": "Import Errors",
"importX": "Importar desde {}",
"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": "Errores de Importación",
"importedXOfYApps": "{} de {} Aplicaciones importadas.",
"followingURLsHadErrors": "Las siguientes URLs tuvieron problemas:",
"okay": "Correcto",
"followingURLsHadErrors": "Las siguientes URLs han tenido problemas:",
"selectURL": "Seleccionar URL",
"selectURLs": "Seleccionar URLs",
"pick": "Escoger",
"theme": "Tema",
"dark": "Oscuro",
"light": "Claro",
"followSystem": "Seguir al Sistema",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Usar tema oscuro con negros puros",
"appSortBy": "Ordenar Aplicaciones Por",
"followSystem": "Seguir al sistema",
"useBlackTheme": "Negro puro en tema Oscuro",
"appSortBy": "Ordenar Apps Por",
"authorName": "Autor/Nombre",
"nameAuthor": "Nombre/Autor",
"asAdded": "Según se Añadieron",
"appSortOrder": "Orden de Clasificación de Aplicaciones",
"appSortOrder": "Orden de Clasificación",
"ascending": "Ascendente",
"descending": "Descendente",
"bgUpdateCheckInterval": "Intervalo de Comprobación de Actualizaciones en Segundo Plano",
"neverManualOnly": "Nunca - Solo Manual",
"bgUpdateCheckInterval": "Comprobación actualizaciones en segundo plano",
"neverManualOnly": "Nunca, solo manual",
"appearance": "Apariencia",
"showWebInAppView": "Mostrar Vista de la Web de Origen",
"pinUpdates": "Fijar Actualizaciones en la Parte Superior de la Vista de Aplicaciones",
"showWebInAppView": "Mostrar vista de la web de origen",
"pinUpdates": "Fijar actualizaciones al principio",
"updates": "Actualizaciones",
"sourceSpecific": "Fuente Específica",
"appSource": "Fuente de la Aplicación",
"appSource": "Obtainium en GitHub",
"noLogs": "Sin Logs",
"appLogs": "Logs de la Aplicación",
"appLogs": "Logs",
"close": "Cerrar",
"share": "Compartir",
"appNotFound": "Aplicación no encontrada",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "Elige una APK",
"obtainiumExportHyphenatedLowercase": "obtainium-exportación",
"pickAnAPK": "Seleccione una APK",
"appHasMoreThanOnePackage": "{} tiene más de un paquete:",
"deviceSupportsXArch": "Tu dispositivo soporta las siguientes arquitecturas de procesador: {}.",
"deviceSupportsFollowingArchs": "Tu dispositivo soporta las siguientes arquitecturas de procesador:",
"deviceSupportsXArch": "Su dispositivo soporta las siguientes arquitecturas de procesador: {}.",
"deviceSupportsFollowingArchs": "Su dispositivo soporta las siguientes arquitecturas de procesador:",
"warning": "Aviso",
"sourceIsXButPackageFromYPrompt": "La fuente de la aplicación es '{}' pero el paquete de la actualización viene de '{}'. ¿Desea continuar?",
"updatesAvailable": "Actualizaciones Disponibles",
"updatesAvailableNotifDescription": "Notifica al usuario de que hay actualizaciones para una o más aplicaciones monitorizadas por Obtainium",
"updatesAvailableNotifDescription": "Notifica al usuario de que hay actualizaciones para una o más aplicaciones monitoreadas por Obtainium",
"noNewUpdates": "No hay nuevas actualizaciones.",
"xHasAnUpdate": "{} tiene una actualización.",
"appsUpdated": "Aplicaciones Actualizadas",
"appsUpdated": "Aplicaciones actualizadas",
"appsUpdatedNotifDescription": "Notifica al usuario de que una o más aplicaciones han sido actualizadas en segundo plano",
"xWasUpdatedToY": "{} ha sido actualizada a {}.",
"errorCheckingUpdates": "Error Buscando Actualizaciones",
"errorCheckingUpdates": "Error buscando ectualizaciones",
"errorCheckingUpdatesNotifDescription": "Una notificación que muestra cuándo la comprobación de actualizaciones en segundo plano falla",
"appsRemoved": "Aplicaciones Eliminadas",
"appsRemoved": "Aplicaciones eliminadas",
"appsRemovedNotifDescription": "Notifica al usuario que una o más aplicaciones fueron eliminadas por problemas al cargarlas",
"xWasRemovedDueToErrorY": "{} ha sido eliminada por: {}",
"completeAppInstallation": "Instalación Completa de la Aplicación",
"obtainiumMustBeOpenToInstallApps": "Obtainium debe estar abierta para instalar aplicaciones",
"completeAppInstallationNotifDescription": "Pide al usuario volver a Obtainium para teminar de instalar una aplicación",
"checkingForUpdates": "Buscando Actualizaciones",
"completeAppInstallation": "Instalación completa de la aplicación",
"obtainiumMustBeOpenToInstallApps": "Obtainium debe estar abierto para instalar aplicaciones",
"completeAppInstallationNotifDescription": "Pide al usuario volver a Obtainium para terminar de instalar una aplicación",
"checkingForUpdates": "Buscando actualizaciones...",
"checkingForUpdatesNotifDescription": "Notificación temporal que aparece al buscar actualizaciones",
"pleaseAllowInstallPerm": "Por favor, permite a Obtainium instalar aplicaciones",
"trackOnly": "Solo Seguimiento",
"pleaseAllowInstallPerm": "Por favor, permita que Obtainium instale aplicaciones",
"trackOnly": "Solo para seguimiento",
"errorWithHttpStatusCode": "Error {}",
"versionCorrectionDisabled": "Corrección de versiones desactivada (el plugin parece no funcionar)",
"unknown": "Desconocido",
"none": "Ninguno",
"never": "Nunca",
"latestVersionX": "Última Versión: {}",
"installedVersionX": "Versión Instalada: {}",
"lastUpdateCheckX": "Última Comprobación: {}",
"latestVersionX": "Última versión: {}",
"installedVersionX": "Versión instalada: {}",
"lastUpdateCheckX": "Última comprobación: {}",
"remove": "Eliminar",
"yesMarkUpdated": "Sí, Marcar como Actualizada",
"fdroid": "Repositorio oficial de F-Droid",
"yesMarkUpdated": "Sí, marcar como actualizada",
"fdroid": "Repositorio oficial F-Droid",
"appIdOrName": "ID o Nombre de la Aplicación",
"appId": "ID de la Aplicación",
"appWithIdOrNameNotFound": "No se han encontrado aplicaciones con esa ID o nombre",
"reposHaveMultipleApps": "Los repositorios pueden contener varias aplicaciones",
"fdroidThirdPartyRepo": "Rpositorios de terceros de F-Droid",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"fdroidThirdPartyRepo": "Repositorio de tercera parte F-Droid",
"steamMobile": "Móvil de vapor",
"steamChat": "Chat de vapor",
"install": "Instalar",
"markInstalled": "Marcar como Instalda",
"markInstalled": "Marcar como instalada",
"update": "Actualizar",
"markUpdated": "Marcar como Actualizada",
"additionalOptions": "Opciones Adicionales",
"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.",
"markUpdated": "Marcar como actualizada",
"additionalOptions": "Opciones adicionales",
"disableVersionDetection": "Desactivar la detección de versiones",
"noVersionDetectionExplanation": "Esta opción solo se debe usar en aplicaciones en las que la deteción de versiones pueda que no funcionar correctamente.",
"downloadingX": "Descargando {}",
"downloadNotifDescription": "Notifica al usuario de progreso de descarga de una aplicación",
"noAPKFound": "APK no encontrada",
"downloadNotifDescription": "Notifica al usuario del progreso de descarga de una aplicación",
"noAPKFound": "No se encontró el paquete de instalación APK",
"noVersionDetection": "Sin detección de versiones",
"categorize": "Catogorizar",
"categories": "Categorías",
"category": "Categoría",
"noCategory": "Sin Categoría",
"noCategories": "Sin Categorías",
"deleteCategoriesQuestion": "¿Borrar Categorías?",
"categoryDeleteWarning": "Todas las aplicaciones en las categorías borradas serán margadas como 'Sin Categoría'.",
"addCategory": "Añadir Categoría",
"noCategory": "Sin categoría",
"noCategories": "Sin categorías",
"deleteCategoriesQuestion": "¿Eliminar categorías?",
"categoryDeleteWarning": "Todas las aplicaciones en las categorías eliminadas serán marcadas como 'Sin categoría'.",
"addCategory": "Añadir categoría",
"label": "Nombre",
"language": "Idioma",
"copiedToClipboard": "Copiado al Portapapeles",
"storagePermissionDenied": "Permiso de Almacenamiento rechazado",
"selectedCategorizeWarning": "Esto reemplazará cualquier ajuste de categoría para las aplicaicones seleccionadas.",
"filterAPKsByRegEx": "Filtrar APKs mediante Expresiones Regulares",
"copiedToClipboard": "Copiado al portapapeles",
"storagePermissionDenied": "Permiso de almacenamiento rechazado",
"selectedCategorizeWarning": "Esto reemplazará cualquier ajuste de categoría para las aplicaciones seleccionadas.",
"filterAPKsByRegEx": "Filtrar por APKs",
"removeFromObtainium": "Eliminar de Obtainium",
"uninstallFromDevice": "Desinstalar del Dispositivo",
"uninstallFromDevice": "Desinstalar del dispositivo",
"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.",
"changes": "Cambios",
"releaseDate": "Fecha de Publicación",
"importFromURLsInFile": "Importar de URls en un Archivo (como OPML)",
"versionDetection": "Detección de Versiones",
"standardVersionDetection": "Detección de versiones estándar",
"groupByCategory": "Agrupar por Categoría",
"autoApkFilterByArch": "Tratar de filtrar las APKs mediante arquitecturas de procesador si es posible",
"releaseDate": "Fecha de publicación",
"importFromURLsInFile": "Importar URLs desde archivo (como OPML)",
"versionDetectionExplanation": "Conciliar la cadena de versión con la versión detectada desde el sistema operativo",
"versionDetection": "Detección de versiones",
"standardVersionDetection": "Por versión",
"groupByCategory": "Agrupar por categoría",
"autoApkFilterByArch": "Filtrar APKs por arquitectura del procesador (si es posible)",
"overrideSource": "Sobrescribir Fuente",
"dontShowAgain": "No mostrar de nuevo",
"dontShowTrackOnlyWarnings": "No mostrar avisos de 'Solo Seguimiento'",
"dontShowAPKOriginWarnings": "No mostrar avisos de las fuentes de las APks",
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
"about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check for updates on startup",
"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": "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.",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"dontShowTrackOnlyWarnings": "No mostrar avisos sobre apps en 'solo seguimiento'",
"dontShowAPKOriginWarnings": "No mostrar avisos sobre las fuentes de las APKs",
"moveNonInstalledAppsToBottom": "Mover Apps no instaladas al final",
"gitlabPATLabel": "Token de acceso personal a GitLab\n(habilita la búsqueda y mejor detección de APKs)",
"about": "Acerca",
"requiresCredentialsInSettings": "{}: Esto requiere credenciales adicionales (en ajustes)",
"checkOnStart": "Comprobar actualizaciones al inicio",
"tryInferAppIdFromCode": "Intentar deducir la ID de la app por el código fuente",
"removeOnExternalUninstall": "Auto eliminar apps desinstaladas externamente",
"pickHighestVersionCode": "Auto selección de versión superior del paquete APK",
"checkUpdateOnDetailPage": "Comprobar actualizaciones al abrir detalles de la app",
"disablePageTransitions": "Deshabilitar animaciones de transición",
"reversePageTransitions": "Invertir animaciones de transición",
"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 un 'token de acceso personal'.",
"gitlabSourceNote": "La extracción de APK de GitLab podría no funcionar sin un 'token de acceso personal'.",
"sortByLastLinkSegment": "Ordenar sólo por el último segmento del enlace",
"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": "Filtrar enlaces por texto del enlace",
"intermediateLinkNotFound": "Enlace intermedio no encontrado",
"intermediateLink": "Enlace intermedio",
"exemptFromBackgroundUpdates": "Exenta de actualizciones en segundo plano (si están habilitadas)",
"bgUpdatesOnWiFiOnly": "Deshabilitar las actualizaciones en segundo plano sin WiFi",
"autoSelectHighestVersionCode": "Auto selección del paquete APK con versión más reciente",
"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 versión del paquete APK sugerida",
"dontSortReleasesList": "Mantener el order de publicación de la fuente original",
"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",
"root": "Raíz",
"shizukuBinderNotFound": "Shizuku no funciona",
"useSystemFont": "Usar la fuente de impresión del sistema",
"systemFontError": "Error al cargar la fuente de impresión del sistema: {}",
"useVersionCodeAsOSVersion": "Usar la versión de la aplicación como versión detectada por el sistema operativo",
"requestHeader": "Encabezado de solicitud",
"useLatestAssetDateAsReleaseDate": "Usar la última carga de recursos como fecha de lanzamiento",
"defaultPseudoVersioningMethod": "Método de pseudoversionado predeterminado",
"partialAPKHash": "Hash de APK parcial",
"APKLinkHash": "Hash de enlace APK",
"directAPKLink": "Enlace APK directo",
"pseudoVersionInUse": "Se está utilizando una pseudoversión",
"installed": "Instalado",
"latest": "Versión más reciente",
"invertRegEx": "Invertir expresión regular",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"removeAppQuestion": {
"one": "¿Eliminar Aplicación?",
"other": "¿Eliminar Aplicaciones?"
@ -267,31 +321,35 @@
"other": "{} Aplicaciones"
},
"url": {
"one": "{} URL",
"other": "{} URLs"
"uno": "{} URL",
"otro": "{} URL"
},
"minute": {
"one": "{} Minuto",
"other": "{} Minutos"
"one": "{} minuto",
"other": "{} minutos"
},
"hour": {
"one": "{} Hora",
"other": "{} Horas"
"one": "{} hora",
"other": "{} horas"
},
"day": {
"one": "{} Día",
"other": "{} Días"
"one": "{} día",
"other": "{} días"
},
"clearedNLogsBeforeXAfterY": {
"one": "Borrado {n} log (previo a = {before}, posterior a = {after})",
"other": "Borrados {n} logs (previos a = {before}, posteriores a = {after})"
"one": "Eliminado {n} log (previo a = {before}, posterior a = {after})",
"other": "Eliminados {n} logs (previos a = {before}, posteriores a = {after})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} y 1 aplicación más tiene actualizaciones.",
"other": "{} y {} aplicaciones más tiene actualizaciones."
"other": "{} y {} aplicaciones más tienen actualizaciones."
},
"xAndNMoreUpdatesInstalled": {
"one": "{} y 1 aplicación 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."
}
}

View File

@ -11,15 +11,7 @@
"unexpectedError": "خطای غیرمنتظره",
"ok": "باشه",
"and": "و",
"startedBgUpdateTask": "شروع بررسی بروزرسانی BG",
"bgUpdateIgnoreAfterIs": "نادیده گرفتن بروزرسانی BG بعد از {} است",
"startedActualBGUpdateCheck": "بررسی به‌روزرسانی واقعی BG آغاز شد",
"bgUpdateTaskFinished": "کار بررسی به‌روزرسانی BG تمام شد",
"firstRun": "این اولین اجرای Obtainium است",
"settingUpdateCheckIntervalTo": "تنظیم فاصله به‌روزرسانی روی {}",
"githubPATLabel": "توکن دسترسی شخصی گیت هاب(محدودیت نرخ را افزایش میدهد)",
"githubPATHint": "PAT باید در این قالب باشد: username:token",
"githubPATFormat": "username:token",
"includePrereleases": "شامل نسخه های اولیه",
"fallbackToOlderReleases": "بازگشت به نسخه های قدیمی تر",
"filterReleaseTitlesByRegEx": "عناوین انتشار را با بیان منظم فیلتر کنید",
@ -49,7 +41,7 @@
"searchSomeSourcesLabel": "جستجو (فقط برخی منابع)",
"search": "جستجو کردن",
"additionalOptsFor": "گزینه های اضافی برای {}",
"supportedSourcesBelow": "منابع پشتیبانی شده:",
"supportedSources": "منابع پشتیبانی شده",
"trackOnlyInBrackets": "«فقط ردیابی»",
"searchableInBrackets": "(قابل جستجو)",
"appsString": "برنامه ها",
@ -59,11 +51,10 @@
"percentProgress": "پیش رفتن: {}%",
"pleaseWait": "لطفا صبر کنید",
"updateAvailable": "بروزرسانی در دسترس",
"estimateInBracketsShort": "(تخمین)",
"notInstalled": "نصب نشده",
"estimateInBrackets": "(تخمین زدن)",
"pseudoVersion": "نسخه شبه",
"selectAll": "انتخاب همه",
"deselectN": "لغو انتخاب {}",
"deselectX": "لغو انتخاب {}",
"xWillBeRemovedButRemainInstalled": "{} از Obtainium حذف می‌شود اما روی دستگاه نصب می‌ماند.",
"removeSelectedAppsQuestion": "برنامه های انتخابی حذف شود؟",
"removeSelectedApps": "حذف برنامه های انتخاب شده",
@ -81,6 +72,8 @@
"unpinFromTop": "برداشتن پین از بالا",
"resetInstallStatusForSelectedAppsQuestion": "وضعیت نصب برنامه‌های انتخابی بازنشانی شود؟",
"installStatusOfXWillBeResetExplanation": "وضعیت نصب برنامه‌های انتخاب‌شده بازنشانی می‌شود.\n\nاگر نسخه برنامه نشان‌داده‌شده در Obtainium به دلیل به‌روزرسانی‌های ناموفق یا مشکلات دیگر نادرست باشد، می‌تواند کمک کند.",
"customLinkMessage": "این پیوندها بر روی دستگاه هایی با Obtainium نصب شده کار می کنند",
"shareAppConfigLinks": "اشتراک گذاری پیکربندی برنامه به عنوان پیوند HTML",
"shareSelectedAppURLs": "اشتراک گذاری آدرس اینترنتی برنامه های انتخاب شده",
"resetInstallStatus": "بازنشانی وضعیت نصب",
"more": "بیشتر",
@ -93,25 +86,24 @@
"author": "سازنده",
"upToDateApps": "برنامه های به روز",
"nonInstalledApps": "برنامه های نصب نشده",
"importExport": "وادر کردن/صادر کردن",
"importExport": "درون ریزی/برون ریزی",
"settings": "تنظیمات",
"exportedTo": "صادر کردن به{}",
"exportedTo": "برون ریزی به{}",
"obtainiumExport": "صادرکردن Obtainium",
"invalidInput": "ورودی نامعتبر",
"importedX": "وارد شده {}",
"obtainiumImport": "واردکردن Obtainium",
"importFromURLList": "وارد کردن از فهرست آدرس اینترنتی",
"importFromURLList": "درون ریزی از فهرست آدرس اینترنتی",
"searchQuery": "جستجوی سوال",
"appURLList": "فهرست آدرس اینترنتی برنامه",
"line": "خط",
"searchX": "جستجو {}",
"noResults": "نتیجه ای پیدا نشد",
"importX": "وارد کردن {}",
"importedAppsIdDisclaimer": "ممکن است برنامه‌های وارد شده به اشتباه به‌عنوان \"نصب نشده\" نشان داده شوند.\nبرای رفع این مشکل، آنها را دوباره از طریق Obtainium نصب کنید.\nاین نباید روی داده‌های برنامه تأثیر بگذارد.\n\nفقط بر روی آدرس اینترنتی و روش‌های وارد کردن شخص ثالث تأثیر می‌گذارد.",
"importErrors": "خطاهای وارد کردن",
"importX": "درون ریزی {}",
"importedAppsIdDisclaimer": "ممکن است برنامه‌های وارد شده به اشتباه به‌عنوان \"نصب نشده\" نشان داده شوند.\nبرای رفع این مشکل، آنها را دوباره از طریق Obtainium نصب کنید.\nاین نباید روی داده‌های برنامه تأثیر بگذارد.\n\nفقط بر روی آدرس اینترنتی و روش‌های درون ریزی شخص ثالث تأثیر می‌گذارد.",
"importErrors": "خطاهای درون ریزی",
"importedXOfYApps": "{} از {} برنامه وارد شد.",
"followingURLsHadErrors": "آدرس های اینترنتی زیر دارای خطا بودند:",
"okay": "باشه",
"selectURL": "آدرس اینترنتی انتخاب شده",
"selectURLs": "آدرس های اینترنتی انتخاب شده",
"pick": "انتخاب",
@ -119,8 +111,6 @@
"dark": "تاریک",
"light": "روشن",
"followSystem": "هماهنگ با سیستم",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "استفاده از تم تیره سیاه خالص",
"appSortBy": "مرتب سازی برنامه بر اساس",
"authorName": "سازنده/اسم",
@ -142,7 +132,7 @@
"close": "بستن",
"share": "اشتراک گذاری",
"appNotFound": "برنامه پیدا نشد",
"obtainiumExportHyphenatedLowercase": "صادر کردن-obtainium",
"obtainiumExportHyphenatedLowercase": "برون ریزی-obtainium",
"pickAnAPK": "یک APK انتخاب کنید",
"appHasMoreThanOnePackage": "{} بیش از یک بسته دارد:",
"deviceSupportsXArch": "دستگاه شما از معماری پردازنده {} پشتیبانی میکند",
@ -180,13 +170,12 @@
"yesMarkUpdated": "بله، علامت گذاری به عنوان به روز شده",
"fdroid": "F-Droid Official",
"appIdOrName": "شناسه یا نام برنامه",
"appId": "App ID",
"appId": "شناسه برنامه",
"appWithIdOrNameNotFound": "هیچ برنامه ای با آن شناسه یا نام یافت نشد",
"reposHaveMultipleApps": "مخازن ممکن است شامل چندین برنامه باشد",
"fdroidThirdPartyRepo": "مخازن شخص ثالث F-Droid",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "استیم موبایل",
"steamChat": "چت استیم",
"install": "نصب",
"markInstalled": "علامت گذاری به عنوان نصب شده",
"update": "به روز رسانی",
@ -219,7 +208,8 @@
"releaseDateAsVersionExplanation": "این گزینه فقط باید برای برنامه هایی استفاده شود که تشخیص نسخه به درستی کار نمی کند، اما تاریخ انتشار در دسترس است.",
"changes": "تغییرات",
"releaseDate": "تاریخ انتشار",
"importFromURLsInFile": "وارد کردن از آدرس های اینترنتی موجود در فایل (مانند OPML)",
"importFromURLsInFile": "درون ریزی از آدرس های اینترنتی موجود در فایل (مانند OPML)",
"versionDetectionExplanation": "تطبیق رشته نسخه با نسخه شناسایی شده از سیستم عامل",
"versionDetection": "تشخیص نسخه",
"standardVersionDetection": "تشخیص نسخه استاندارد",
"groupByCategory": "گروه بر اساس دسته",
@ -229,23 +219,87 @@
"dontShowTrackOnlyWarnings": "هشدار 'فقط ردیابی' را نشان ندهید",
"dontShowAPKOriginWarnings": "هشدارهای منبع APK را نشان ندهید",
"moveNonInstalledAppsToBottom": "برنامه های نصب نشده را به نمای پایین برنامه ها منتقل کنید",
"gitlabPATLabel": "رمز دسترسی شخصی GitLab\n(جستجو را فعال می کند and Better APK Discovery)",
"gitlabPATLabel": "رمز دسترسی شخصی GitLab\n(جستجو و کشف بهتر APK را فعال میکند)",
"about": "درباره",
"requiresCredentialsInSettings": "این به اعتبارنامه های اضافی نیاز دارد (در تنظیمات)",
"requiresCredentialsInSettings": "{}: این به اعتبارنامه های اضافی نیاز دارد (در تنظیمات)",
"checkOnStart": "بررسی در شروع",
"tryInferAppIdFromCode": "شناسه برنامه را از کد منبع استنباط کنید",
"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": "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.",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"removeOnExternalUninstall": "حذف خودکار برنامه های حذف نصب شده خارجی",
"pickHighestVersionCode": "انتخاب خودکار بالاترین کد نسخه APK",
"checkUpdateOnDetailPage": "برای باز کردن صفحه جزئیات برنامه، به‌روزرسانی‌ها را بررسی کنید",
"disablePageTransitions": "غیرفعال کردن انیمیشن های انتقال صفحه",
"reversePageTransitions": "انیمیشن های انتقال معکوس صفحه",
"minStarCount": "حداقل تعداد ستاره",
"addInfoBelow": "این اطلاعات را در زیر اضافه کنید",
"addInfoInSettings": "این اطلاعات را در تنظیمات اضافه کنید.",
"githubSourceNote": "با استفاده از کلید API می توان از محدودیت نرخ GitHub جلوگیری کرد.",
"gitlabSourceNote": "استخراج APK GitLab ممکن است بدون کلید API کار نکند.",
"sortByLastLinkSegment": "فقط بر اساس آخرین بخش پیوند مرتب کنید",
"filterReleaseNotesByRegEx": "یادداشت های انتشار را با بیان منظم فیلتر کنید",
"customLinkFilterRegex": "فیلتر پیوند سفارشی بر اساس عبارت منظم (پیش‌فرض '.apk$')",
"appsPossiblyUpdated": "به‌روزرسانی برنامه انجام شد",
"appsPossiblyUpdatedNotifDescription": "به کاربر اطلاع می‌دهد که به‌روزرسانی‌های یک یا چند برنامه به طور بالقوه در پس‌زمینه اعمال شده است",
"xWasPossiblyUpdatedToY": "ممکن است {} به {} به روز شده باشد.",
"enableBackgroundUpdates": "به روز رسانی پس زمینه را فعال کنید",
"backgroundUpdateReqsExplanation": "به روز رسانی پس زمینه ممکن است برای همه برنامه ها امکان پذیر نباشد.",
"backgroundUpdateLimitsExplanation": "موفقیت نصب پس‌زمینه تنها زمانی مشخص می‌شود که Obtainium باز شود.",
"verifyLatestTag": "برچسب \"آخرین\" را تأیید کنید",
"intermediateLinkRegex": "برای بازدید از پیوند «میانگین» فیلتر کنید",
"filterByLinkText": "لینک ها را بر اساس متن پیوند فیلتر کنید",
"intermediateLinkNotFound": "لینک میانی پیدا نشد",
"intermediateLink": "پیوند میانی",
"exemptFromBackgroundUpdates": "معاف از به‌روزرسانی‌های پس‌زمینه (در صورت فعال بودن)",
"bgUpdatesOnWiFiOnly": "به‌روزرسانی‌های پس‌زمینه را در صورت عدم اتصال به WiFi غیرفعال کنید",
"autoSelectHighestVersionCode": "انتخاب خودکار بالاترین نسخه کد APK",
"versionExtractionRegEx": "نسخه استخراج RegEx",
"matchGroupToUse": "گروه مورد استفاده را مطابقت دهید",
"highlightTouchTargets": "اهداف لمسی کمتر واضح را برجسته کنید",
"pickExportDir": "فهرست برون ریزی را انتخاب کنید",
"autoExportOnChanges": "برون ریزی خودکار تغییرات",
"includeSettings": "شامل تنظیمات",
"filterVersionsByRegEx": "فیلتر کردن نسخه ها با RegEx",
"trySelectingSuggestedVersionCode": "نسخه پیشنهادی APK نسخه کد را انتخاب کنید",
"dontSortReleasesList": "حفظ سفارش انتشار از API",
"reverseSort": "مرتب سازی معکوس",
"takeFirstLink": "لینک اول را بگیرید",
"skipSort": "از مرتب سازی صرف نظر کنید",
"debugMenu": "منوی اشکال زدایی",
"bgTaskStarted": "کار پس زمینه شروع شد - لاگ های مربوط را بررسی کنید.",
"runBgCheckNow": "اکنون به‌روزرسانی پس‌زمینه را بررسی کنید",
"versionExtractWholePage": "نسخه Extraction Regex را در کل صفحه اعمال کنید",
"installing": "در حال نصب",
"skipUpdateNotifications": "رد شدن از اعلان های به روز رسانی",
"updatesAvailableNotifChannel": "بروزرسانی در دسترس ",
"appsUpdatedNotifChannel": "برنامه ها به روز شدند",
"appsPossiblyUpdatedNotifChannel": "به‌روزرسانی برنامه انجام شد",
"errorCheckingUpdatesNotifChannel": "خطا در بررسی به‌روزرسانی‌ها",
"appsRemovedNotifChannel": "برنامه ها حذف شدند",
"downloadingXNotifChannel": "در حال دانلود {}",
"completeAppInstallationNotifChannel": "نصب کامل برنامه",
"checkingForUpdatesNotifChannel": "بررسی به‌روزرسانی‌ها",
"onlyCheckInstalledOrTrackOnlyApps": "فقط برنامه های نصب شده و فقط ردیابی را برای به روز رسانی بررسی کنید",
"supportFixedAPKURL": "پشتیبانی از URL های APK ثابت",
"selectX": "انتخاب کنید {}",
"parallelDownloads": "اجازه دانلود موازی",
"installMethod": "روش نصب",
"normal": "طبیعی",
"root": "ریشه",
"shizukuBinderNotFound": "Shizuku در حال اجرا نیست",
"useSystemFont": "استفاده از فونت سیستم",
"systemFontError": "خطا در بارگیری فونت سیستم: {}",
"useVersionCodeAsOSVersion": "استفاده کد نسخه برنامه به جای نسخه شناسایی شده توسط سیستم عامل استفاده کنید",
"requestHeader": "درخواست سطر بالایی",
"useLatestAssetDateAsReleaseDate": "استفاده از آخرین بارگذاری دارایی به عنوان تاریخ انتشار",
"defaultPseudoVersioningMethod": "روش پیش فرض شبه نسخه",
"partialAPKHash": "هش جزئی APK",
"APKLinkHash": "هش پیوند APK",
"directAPKLink": "لینک مستقیم APK",
"pseudoVersionInUse": "یک نسخه شبه در حال استفاده است",
"installed": "نصب شده است",
"latest": "آخرین",
"invertRegEx": "معکوس کردن عبارت منظم",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"removeAppQuestion": {
"one": "برنامه حذف شود؟",
"other": "برنامه ها حذف شوند؟"
@ -293,5 +347,9 @@
"xAndNMoreUpdatesInstalled": {
"one": "{} و 1 برنامه دیگر به روز شدند.",
"other": "{} و {} برنامه دیگر به روز شدند."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} و 1 برنامه دیگر ممکن است به روز شده باشند.",
"other": "ممکن است {} و {} برنامه های دیگر به روز شده باشند."
}
}

View File

@ -9,17 +9,9 @@
"placeholder": "Espace réservé",
"someErrors": "Des erreurs se sont produites",
"unexpectedError": "Erreur inattendue",
"ok": "Okay",
"ok": "D'accord",
"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)",
"githubPATHint": "Le JAP doit être dans ce format : username:token",
"githubPATFormat": "username:token",
"includePrereleases": "Inclure les avant-premières",
"fallbackToOlderReleases": "Retour aux anciennes versions",
"filterReleaseTitlesByRegEx": "Filtrer les titres de version par expression régulière",
@ -49,7 +41,7 @@
"searchSomeSourcesLabel": "Rechercher (certaines sources uniquement)",
"search": "Rechercher",
"additionalOptsFor": "Options supplémentaires pour {}",
"supportedSourcesBelow": "Sources prises en charge :",
"supportedSources": "Sources prises en charge ",
"trackOnlyInBrackets": "(Suivi uniquement)",
"searchableInBrackets": "(Recherchable)",
"appsString": "Applications",
@ -59,11 +51,10 @@
"percentProgress": "Progrès: {}%",
"pleaseWait": "Veuillez patienter",
"updateAvailable": "Mise à jour disponible",
"estimateInBracketsShort": "(Est.)",
"notInstalled": "Pas installé",
"estimateInBrackets": "(Estimation)",
"pseudoVersion": "pseudo-version",
"selectAll": "Tout sélectionner",
"deselectN": "Déselectionner {}",
"deselectX": "Déselectionner {}",
"xWillBeRemovedButRemainInstalled": "{} sera supprimé d'Obtainium mais restera installé sur l'appareil.",
"removeSelectedAppsQuestion": "Supprimer les applications sélectionnées ?",
"removeSelectedApps": "Supprimer les applications sélectionnées",
@ -81,6 +72,8 @@
"unpinFromTop": "Détacher du haut",
"resetInstallStatusForSelectedAppsQuestion": "Réinitialiser l'état d'installation des applications sélectionnées ?",
"installStatusOfXWillBeResetExplanation": "L'état d'installation de toutes les applications sélectionnées sera réinitialisé.\n\nCela peut aider lorsque la version de l'application affichée dans Obtainium est incorrecte en raison d'échecs de mises à jour ou d'autres problèmes.",
"customLinkMessage": "Ces liens fonctionnent sur les appareils sur lesquels Obtenirium est installé",
"shareAppConfigLinks": "Partager la configuration de l'application sous forme de lien HTML",
"shareSelectedAppURLs": "Partager les URL d'application sélectionnées",
"resetInstallStatus": "Réinitialiser le statut d'installation",
"more": "Plus",
@ -111,7 +104,6 @@
"importErrors": "Erreurs d'importation",
"importedXOfYApps": "{} sur {} applications importées.",
"followingURLsHadErrors": "Les URL suivantes comportaient des erreurs :",
"okay": "Okay",
"selectURL": "Sélectionnez l'URL",
"selectURLs": "Sélectionnez les URLs",
"pick": "Prendre",
@ -119,9 +111,7 @@
"dark": "Sombre",
"light": "Clair",
"followSystem": "Suivre le système",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Use pure black dark theme",
"useBlackTheme": "Utilisez le thème noir pur et sombre",
"appSortBy": "Applications triées par",
"authorName": "Auteur/Nom",
"nameAuthor": "Nom/Auteur",
@ -142,7 +132,7 @@
"close": "Fermer",
"share": "Partager",
"appNotFound": "Application introuvable",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"obtainiumExportHyphenatedLowercase": "exportation d'obtainium",
"pickAnAPK": "Choisissez un APK",
"appHasMoreThanOnePackage": "{} a plus d'un paquet :",
"deviceSupportsXArch": "Votre appareil prend en charge l'architecture de processeur {}.",
@ -178,15 +168,14 @@
"lastUpdateCheckX": "Vérification de la dernière mise à jour : {}",
"remove": "Retirer",
"yesMarkUpdated": "Oui, marquer comme mis à jour",
"fdroid": "F-Droid Official",
"fdroid": "F-Droïde Officiel",
"appIdOrName": "ID ou nom de l'application",
"appId": "ID de l'application",
"appWithIdOrNameNotFound": "Aucune application n'a été trouvée avec cet identifiant ou ce nom",
"reposHaveMultipleApps": "Les dépôts peuvent contenir plusieurs applications",
"fdroidThirdPartyRepo": "Dépôt tiers F-Droid",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "Vapeur Mobile",
"steamChat": "Chat sur Steam",
"install": "Installer",
"markInstalled": "Marquer installée",
"update": "Mettre à jour",
@ -201,14 +190,14 @@
"categorize": "Catégoriser",
"categories": "Catégories",
"category": "Catégorie",
"noCategory": "No Category",
"noCategory": "Aucune catégorie",
"noCategories": "Aucune catégorie",
"deleteCategoriesQuestion": "Supprimer les catégories ?",
"categoryDeleteWarning": "Toutes les applications dans les catégories supprimées seront définies sur non catégorisées.",
"addCategory": "Ajouter une catégorie",
"label": "Étiquette",
"language": "Langue",
"copiedToClipboard": "Copied to Clipboard",
"copiedToClipboard": "Copié dans le presse-papier",
"storagePermissionDenied": "Autorisation de stockage refusée",
"selectedCategorizeWarning": "Cela remplacera tous les paramètres de catégorie existants pour les applications sélectionnées.",
"filterAPKsByRegEx": "Filtrer les APK par expression régulière",
@ -220,32 +209,97 @@
"changes": "Changements",
"releaseDate": "Date de sortie",
"importFromURLsInFile": "Importer à partir d'URL dans un fichier (comme OPML)",
"versionDetectionExplanation": "Réconcilier la chaîne de version avec la version détectée à partir du système d'exploitation",
"versionDetection": "Détection des versions",
"standardVersionDetection": "Détection de version standard",
"groupByCategory": "Group by Category",
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
"overrideSource": "Override Source",
"dontShowAgain": "Don't show this again",
"groupByCategory": "Regrouper par catégorie",
"autoApkFilterByArch": "Essayez de filtrer les APK par architecture CPU si possible",
"overrideSource": "Remplacer la source",
"dontShowAgain": "Ne montre plus ça",
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning",
"dontShowAPKOriginWarnings": "Don't show APK origin warnings",
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
"about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check for updates on startup",
"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": "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.",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"dontShowAPKOriginWarnings": "Ne pas afficher les avertissements sur l'origine de l'APK",
"moveNonInstalledAppsToBottom": "Déplacer les applications non installées vers le bas de la vue Applications",
"gitlabPATLabel": "Jeton d'accès personnel GitLab\\n (permet la recherche et une meilleure découverte d'APK)",
"about": "À propos de",
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
"checkOnStart": "Vérifier les mises à jour au démarrage",
"tryInferAppIdFromCode": "Essayez de déduire l'ID de l'application à partir du code source",
"removeOnExternalUninstall": "Supprimer automatiquement les applications désinstallées en externe",
"pickHighestVersionCode": "Sélectionner automatiquement le code APK de la version la plus élevée",
"checkUpdateOnDetailPage": "Vérifier les mises à jour lors de l'ouverture d'une page de détails d'application",
"disablePageTransitions": "Désactiver les animations de transition de page",
"reversePageTransitions": "Animations de transition de page inversée",
"minStarCount": "Nombre minimum d'étoiles",
"addInfoBelow": "Ajoutez ces informations ci-dessous.",
"addInfoInSettings": "Ajoutez ces informations dans les paramètres.",
"githubSourceNote": "La limitation du débit GitHub peut être évitée à l'aide d'une clé API.",
"gitlabSourceNote": "L'extraction d'APK GitLab peut ne pas fonctionner sans clé API.",
"sortByLastLinkSegment": "Trier uniquement sur le dernier segment du lien",
"filterReleaseNotesByRegEx": "Filtrer les notes de version par expression régulière",
"customLinkFilterRegex": "Filtre de lien APK personnalisé par expression régulière (par défaut '.apk$')",
"appsPossiblyUpdated": "Tentative de mise à jour de l'application",
"appsPossiblyUpdatedNotifDescription": "Avertit l'utilisateur que des mises à jour d'une ou plusieurs applications ont été potentiellement appliquées en arrière-plan",
"xWasPossiblyUpdatedToY": "{} a peut-être été mis à jour vers {}.",
"enableBackgroundUpdates": "Activer les mises à jour en arrière-plan",
"backgroundUpdateReqsExplanation": "Les mises à jour en arrière-plan peuvent ne pas être possibles pour toutes les applications.",
"backgroundUpdateLimitsExplanation": "Le succès d'une installation en arrière-plan ne peut être déterminé qu'à l'ouverture d'Obetium.",
"verifyLatestTag": "Vérifiez la balise 'dernière'",
"intermediateLinkRegex": " Filtrer un lien \" intermédiaire \" à visiter ",
"filterByLinkText": "Filtrer les liens par texte de lien",
"intermediateLinkNotFound": "Lien intermédiaire introuvable",
"intermediateLink": "Lien intermédiaire",
"exemptFromBackgroundUpdates": "Exempt des mises à jour en arrière-plan (si activé)",
"bgUpdatesOnWiFiOnly": "Désactiver les mises à jour en arrière-plan lorsque vous n'êtes pas connecté au WiFi",
"autoSelectHighestVersionCode": "Sélection automatique de la version la plus élevéeCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Mettez en évidence les cibles tactiles moins évidentes",
"pickExportDir": "Choisir le répertoire d'exportation",
"autoExportOnChanges": "Exportation automatique sur modifications",
"includeSettings": "Inclure les paramètres",
"filterVersionsByRegEx": "Filtrer les versions par expression régulière",
"trySelectingSuggestedVersionCode": "Essayez de sélectionner la version suggéréeCode APK",
"dontSortReleasesList": "Conserver la commande de version de l'API",
"reverseSort": "Tri inversé",
"takeFirstLink": "Prendre le premier lien",
"skipSort": "Sauter le tri",
"debugMenu": "Menu de débogage",
"bgTaskStarted": "Tâche en arrière-plan démarrée - vérifier les journaux.",
"runBgCheckNow": "Exécuter la vérification de la mise à jour en arrière-plan maintenant",
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page",
"installing": "Installation",
"skipUpdateNotifications": "Ignorer les notifications de mise à jour",
"updatesAvailableNotifChannel": "Mises à jour disponibles",
"appsUpdatedNotifChannel": "Applications mises à jour",
"appsPossiblyUpdatedNotifChannel": "Tentative de mise à jour de l'application",
"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": "Vérifiez uniquement les mises à jour des applications installées et de suivi uniquement",
"supportFixedAPKURL": "Prise en charge des URL APK fixes",
"selectX": "Sélectionner {}",
"parallelDownloads": "Autoriser les téléchargements parallèles",
"installMethod": "Méthode d'installation",
"normal": "Normale",
"root": "Racine",
"shizukuBinderNotFound": "Shizuku is not running",
"useSystemFont": "Utiliser la police système",
"systemFontError": "Erreur de chargement de la police système : {}",
"useVersionCodeAsOSVersion": "Utiliser le code de version de l'application comme version détectée par le système d'exploitation",
"requestHeader": "En-tête de demande",
"useLatestAssetDateAsReleaseDate": "Utiliser le dernier téléchargement d'élément comme date de sortie",
"defaultPseudoVersioningMethod": "Méthode de pseudo-version par défaut",
"partialAPKHash": "Hash APK partiel",
"APKLinkHash": "Hash de lien APK",
"directAPKLink": "Lien APK direct",
"pseudoVersionInUse": "Une pseudo-version est utilisée",
"installed": "Installée",
"latest": "Dernier",
"invertRegEx": "Inverser l'expression régulière",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"removeAppQuestion": {
"one": "Supprimer l'application ?",
"other": "Supprimer les applications ?"
@ -268,10 +322,10 @@
},
"url": {
"one": "{} URL",
"other": "{} URLs"
"other": "{} URL"
},
"minute": {
"one": "{} Minute",
"one": "{} Minutes",
"other": "{} Minutes"
},
"hour": {
@ -293,5 +347,9 @@
"xAndNMoreUpdatesInstalled": {
"one": "{} et 1 autre application ont été mises à jour.",
"other": "{} et {} autres applications ont été mises à jour."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"une": "{} et 1 application supplémentaire ont peut-être été mises à jour.",
"other": "{} et {} autres applications peuvent avoir été mises à jour."
}
}

View File

@ -1,296 +1,355 @@
{
"invalidURLForSource": "Érvénytelen a(z) {} app URL-je",
"noReleaseFound": "Nem található megfelelő kiadás",
"noVersionFound": "Nem sikerült meghatározni a kiadás verzióját",
"urlMatchesNoSource": "Az URL nem egyezik ismert forrással",
"cantInstallOlderVersion": "Nem telepíthető egy app régebbi verziója",
"appIdMismatch": "A letöltött csomagazonosító nem egyezik a meglévő app azonosítóval",
"functionNotImplemented": "Ez az osztály nem valósította meg ezt a függvényt",
"placeholder": "Helykitöltő",
"someErrors": "Néhány hiba történt",
"unexpectedError": "Váratlan hiba",
"ok": "Oké",
"and": "és",
"startedBgUpdateTask": "Háttérfrissítés ellenőrzési feladat elindítva",
"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 Personal Access 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",
"fallbackToOlderReleases": "Visszatérés a régebbi kiadásokhoz",
"filterReleaseTitlesByRegEx": "A kiadás címeinek szűrése reguláris kifejezéssel",
"invalidRegEx": "Érvénytelen reguláris kifejezés",
"noDescription": "Nincs leírás",
"cancel": "Mégse",
"continue": "Tovább",
"requiredInBrackets": "(Kötelező)",
"dropdownNoOptsError": "HIBA: A LEDOBÁST LEGALÁBB EGY OPCIÓHOZ KELL RENDELNI",
"colour": "Szín",
"githubStarredRepos": "GitHub Csillagos Repo-k",
"uname": "Felh.név",
"wrongArgNum": "Rossz számú argumentumot adott meg",
"xIsTrackOnly": "A(z) {} csak nyomonkövethető",
"source": "Forrás",
"app": "App",
"appsFromSourceAreTrackOnly": "Az ebből a forrásból származó alkalmazások 'Csak nyomon követhetőek'.",
"youPickedTrackOnly": "A 'Csak követés' opciót választotta.",
"trackOnlyAppDescription": "Az alkalmazás frissítéseit nyomon követi, de az Obtainium nem tudja letölteni vagy telepíteni.",
"cancelled": "Törölve",
"appAlreadyAdded": "Az app már hozzáadva",
"alreadyUpToDateQuestion": "Az app már naprakész?",
"addApp": "App hozzáadás",
"appSourceURL": "App forrás URL",
"error": "Hiba",
"add": "Hozzáadás",
"searchSomeSourcesLabel": "Keresés (csak egyes források)",
"search": "Keresés",
"additionalOptsFor": "További lehetőségek a következőhöz: {}",
"supportedSourcesBelow": "Támogatott források:",
"trackOnlyInBrackets": "(Csak nyomonkövetés)",
"searchableInBrackets": "(Kereshető)",
"appsString": "Appok",
"noApps": "Nincs App",
"noAppsForFilter": "Nincsenek appok a szűrőhöz",
"byX": "Fejlesztő: {}",
"percentProgress": "Folyamat: {}%",
"pleaseWait": "Kis türelmet",
"updateAvailable": "Frissítés érhető el",
"estimateInBracketsShort": "(Becsült)",
"notInstalled": "Nem telepített",
"estimateInBrackets": "(Becslés)",
"selectAll": "Mindet kiválaszt",
"deselectN": "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.",
"removeSelectedAppsQuestion": "Eltávolítja a kiválasztott appokat?",
"removeSelectedApps": "Távolítsa el a kiválasztott appokat",
"updateX": "Frissítés: {}",
"installX": "Telepítés: {}",
"markXTrackOnlyAsUpdated": "Jelölje meg: {}\n(Csak nyomon követhető)\nmint Frissített",
"changeX": "Változás {}",
"installUpdateApps": "Appok telepítése/frissítése",
"installUpdateSelectedApps": "Telepítse/frissítse a kiválasztott appokat",
"markXSelectedAppsAsUpdated": "Megjelöl {} kiválasztott alkalmazást frissítettként?",
"no": "Nem",
"yes": "Igen",
"markSelectedAppsUpdated": "Jelölje meg a kiválasztott appokat frissítettként",
"pinToTop": "Rögzítés felülre",
"unpinFromTop": "Eltávolít felülről",
"resetInstallStatusForSelectedAppsQuestion": "Visszaállítja a kiválasztott appok telepítési állapotát?",
"installStatusOfXWillBeResetExplanation": "A kiválasztott appok telepítési állapota visszaáll.\n\nEz akkor segíthet, ha az Obtainiumban megjelenített app verzió hibás, frissítések vagy egyéb problémák miatt.",
"shareSelectedAppURLs": "Ossza meg a kiválasztott app URL címeit",
"resetInstallStatus": "Telepítési állapot visszaállítása",
"more": "További",
"removeOutdatedFilter": "Távolítsa el az elavult app szűrőt",
"showOutdatedOnly": "Csak az elavult appok megjelenítése",
"filter": "Szűrő",
"filterActive": "Szűrő *",
"filterApps": "Appok szűrése",
"appName": "App név",
"author": "Szerző",
"upToDateApps": "Naprakész appok",
"nonInstalledApps": "Nem telepített appok",
"importExport": "Import/Export",
"settings": "Beállítások",
"exportedTo": "Exportálva ide {}",
"obtainiumExport": "Obtainium Export",
"invalidInput": "Hibás bemenet",
"importedX": "Importálva innen {}",
"obtainiumImport": "Obtainium Import",
"importFromURLList": "Importálás URL listából",
"searchQuery": "Keresési lekérdezés",
"appURLList": "App URL lista",
"line": "Sor",
"searchX": "Keresés {}",
"noResults": "Nincs találat",
"importX": "Import {}",
"importedAppsIdDisclaimer": "Előfordulhat, hogy az importált appok helytelenül \"Nincs telepítve\" jelzéssel jelennek meg.\nA probléma megoldásához telepítse újra őket az Obtainiumon keresztül.\nEz nem érinti az alkalmazásadatokat.\n\nCsak az URL-ekre és a harmadik féltől származó importálási módszerekre vonatkozik..",
"importErrors": "Importálási hibák",
"importedXOfYApps": "{}/{} app importálva.",
"followingURLsHadErrors": "A következő URL-ek hibákat tartalmaztak:",
"okay": "Oké",
"selectURL": "Válassza ki az URL-t",
"selectURLs": "Kiválasztott URL-ek",
"pick": "Válasszon",
"theme": "Téma",
"dark": "Sötét",
"light": "Világos",
"followSystem": "Rendszer szerint",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Használjon tiszta fekete sötét témát",
"appSortBy": "App rendezés...",
"authorName": "Szerző/Név",
"nameAuthor": "Név/Szerző",
"asAdded": "Mint Hozzáadott",
"appSortOrder": "Appok rendezése",
"ascending": "Emelkedő",
"descending": "Csökkenő",
"bgUpdateCheckInterval": "Háttérfrissítés ellenőrzés időköze",
"neverManualOnly": "Soha csak manuális",
"appearance": "Megjelenés",
"showWebInAppView": "Forrás megjelenítése az Appok nézetben",
"pinUpdates": "Frissítések kitűzése az App nézet tetejére",
"updates": "Frissítések",
"sourceSpecific": "Forrás-specifikus",
"appSource": "App forrás",
"noLogs": "Nincsenek naplók",
"appLogs": "App naplók",
"close": "Bezár",
"share": "Megoszt",
"appNotFound": "App nem található",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "Válasszon egy APK-t",
"appHasMoreThanOnePackage": "A(z) {} egynél több csomaggal rendelkezik:",
"deviceSupportsXArch": "Eszköze támogatja a {} CPU architektúrát.",
"deviceSupportsFollowingArchs": "Az eszköze a következő CPU architektúrákat támogatja:",
"warning": "Figyelem",
"sourceIsXButPackageFromYPrompt": "Az alkalmazás forrása „{}”, de a kiadási csomag innen származik: „{}”. Folytatja?",
"updatesAvailable": "Frissítések érhetők el",
"updatesAvailableNotifDescription": "Értesíti a felhasználót, hogy frissítések állnak rendelkezésre egy vagy több, az Obtainium által nyomon követett alkalmazáshoz",
"noNewUpdates": "Nincsenek új frissítések.",
"xHasAnUpdate": "A(z) {} frissítést kapott.",
"appsUpdated": "Alkalmazások frissítve",
"appsUpdatedNotifDescription": "Értesíti a felhasználót, hogy egy/több app frissítése megtörtént a háttérben",
"xWasUpdatedToY": "{} frissítve a következőre: {}.",
"errorCheckingUpdates": "Hiba a frissítések keresésekor",
"errorCheckingUpdatesNotifDescription": "Értesítés, amely akkor jelenik meg, ha a háttérbeli frissítések ellenőrzése sikertelen",
"appsRemoved": "Alkalmazások eltávolítva",
"appsRemovedNotifDescription": "Értesíti a felhasználót egy vagy több alkalmazás eltávolításáról a betöltésük során fellépő hibák miatt",
"xWasRemovedDueToErrorY": "A(z) {} a következő hiba miatt lett eltávolítva: {}",
"completeAppInstallation": "Teljes app telepítés",
"obtainiumMustBeOpenToInstallApps": "Az Obtainiumnak megnyitva kell lennie az alkalmazások telepítéséhez",
"completeAppInstallationNotifDescription": "Megkéri a felhasználót, hogy térjen vissza az Obtainiumhoz, hogy befejezze az alkalmazás telepítését",
"checkingForUpdates": "Frissítések keresése",
"checkingForUpdatesNotifDescription": "Átmeneti értesítés, amely a frissítések keresésekor jelenik meg",
"pleaseAllowInstallPerm": "Kérjük, engedélyezze az Obtainiumnak az alkalmazások telepítését",
"trackOnly": "Csak követés",
"errorWithHttpStatusCode": "Hiba {}",
"versionCorrectionDisabled": "Verzió korrekció letiltva (úgy tűnik, a beépülő modul nem működik)",
"unknown": "Ismeretlen",
"none": "Egyik sem",
"never": "Soha",
"latestVersionX": "Legújabb verzió: {}",
"installedVersionX": "Telepített verzió: {}",
"lastUpdateCheckX": "Frissítés ellenőrizve: {}",
"remove": "Eltávolítás",
"yesMarkUpdated": "Igen, megjelölés frissítettként",
"fdroid": "F-Droid Official",
"appIdOrName": "App ID vagy név",
"appId": "App ID",
"appWithIdOrNameNotFound": "Nem található app ezzel az azonosítóval vagy névvel",
"reposHaveMultipleApps": "A repók több alkalmazást is tartalmazhatnak",
"fdroidThirdPartyRepo": "F-Droid Harmadik-fél Repo",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"install": "Telepít",
"markInstalled": "Telepítettnek jelöl",
"update": "Frissít",
"markUpdated": "Frissítettnek jelöl",
"additionalOptions": "További lehetőségek",
"disableVersionDetection": "Verzió érzékelés letiltása",
"noVersionDetectionExplanation": "Ezt a beállítást csak olyan alkalmazásoknál szabad használni, ahol a verzióérzékelés nem működik megfelelően.",
"downloadingX": "{} letöltés",
"downloadNotifDescription": "Értesíti a felhasználót az app letöltésének előrehaladásáról",
"noAPKFound": "Nem található APK",
"noVersionDetection": "Nincs verzió érzékelés",
"categorize": "Kategorizálás",
"categories": "Kategóriák",
"category": "Kategória",
"noCategory": "Nincs kategória",
"deleteCategoryQuestion": "Törli a kategóriát?",
"categoryDeleteWarning": "A(z) {} összes app kategorizálatlan állapotba kerül.",
"addCategory": "Új kategória",
"label": "Címke",
"language": "Nyelv",
"copiedToClipboard": "Másolva a vágólapra",
"storagePermissionDenied": "Tárhely engedély megtagadva",
"selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.",
"filterAPKsByRegEx": "Az APK-k szűrése reguláris kifejezéssel",
"removeFromObtainium": "Eltávolítás az Obtainiumból",
"uninstallFromDevice": "Eltávolítás a készülékről",
"onlyWorksWithNonVersionDetectApps": "Csak azoknál az alkalmazásoknál működik, amelyeknél a verzióérzékelés le van tiltva.",
"releaseDateAsVersion": "Használja a Kiadás dátumát, mint verziót",
"releaseDateAsVersionExplanation": "Ezt a beállítást csak olyan alkalmazásoknál szabad használni, ahol a verzió érzékelése nem működik megfelelően, de elérhető a kiadás dátuma.",
"changes": "Változtatások",
"releaseDate": "Kiadás dátuma",
"importFromURLsInFile": "Importálás fájlban található URL-ből (mint pl. OPML)",
"versionDetection": "Verzió érzékelés",
"standardVersionDetection": "Alapért. verzió érzékelés",
"groupByCategory": "Csoportosítás Kategória alapján",
"autoApkFilterByArch": "Ha lehetséges, próbálja CPU architektúra szerint szűrni az APK-kat",
"overrideSource": "Forrás felülbírálása",
"dontShowAgain": "Ne mutassa ezt újra",
"dontShowTrackOnlyWarnings": "Ne jelenítsen meg 'Csak nyomon követés' figyelmeztetést",
"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",
"gitlabPATLabel": "GitLab Personal Access Token\n(Engedélyezi a Keresést és jobb APK felfedezés)",
"about": "Rólunk",
"requiresCredentialsInSettings": "Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
"checkOnStart": "Egyszer az indításkor",
"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",
"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",
"disablePageTransitions": "Lap áttűnési animációk tiltása",
"reversePageTransitions": "Fordított lap áttűnési animációk",
"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 értékelési korlátozása 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.",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazást?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva",
"other": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "A háttérfrissítések ellenőrzése {}-t észlelt, {} perc múlva ütemezi az újrapróbálkozást",
"other": "A háttérfrissítések ellenőrzése {}-t észlelt, {} perc múlva ütemezi az újrapróbálkozást"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "A háttérfrissítés ellenőrzése {} frissítést talált szükség esetén értesíti a felhasználót",
"other": "A háttérfrissítés ellenőrzése {} frissítést talált szükség esetén értesíti a felhasználót"
},
"apps": {
"one": "{} app",
"other": "{} app"
},
"url": {
"one": "{} URL",
"other": "{} URL"
},
"minute": {
"one": "{} perc",
"other": "{} perc"
},
"hour": {
"one": "{} óra",
"other": "{} óra"
},
"day": {
"one": "{} nap",
"other": "{} nap"
},
"clearedNLogsBeforeXAfterY": {
"one": "{n} napló törölve (előtte = {előtte}, utána = {utána})",
"other": "{n} napló törölve (előtte = {előtte}, utána = {utána})"
},
"xAndNMoreUpdatesAvailable": {
"one": "A(z) {} és 1 további alkalmazás frissítéseket kapott.",
"other": "{} és további {} alkalmazás frissítéseket kapott."
},
"xAndNMoreUpdatesInstalled": {
"one": "A(z) {} és 1 további alkalmazás frissítve.",
"other": "{} és további {} alkalmazás frissítve."
}
}
{
"invalidURLForSource": "Érvénytelen a(z) {} app URL-je",
"noReleaseFound": "Nem található megfelelő kiadás",
"noVersionFound": "Nem sikerült meghatározni a kiadás verzióját",
"urlMatchesNoSource": "Az URL nem egyezik ismert forrással",
"cantInstallOlderVersion": "Nem telepíthető egy app régebbi verziója",
"appIdMismatch": "A letöltött csomagazonosító nem egyezik a meglévő app azonosítóval",
"functionNotImplemented": "Ez az osztály nem valósította meg ezt a függvényt",
"placeholder": "Helykitöltő",
"someErrors": "Néhány hiba történt",
"unexpectedError": "Váratlan hiba",
"ok": "Oké",
"and": "és",
"githubPATLabel": "GitHub Personal Access Token (megnöveli a díjkorlátot)",
"includePrereleases": "Tartalmazza az előzetes kiadásokat",
"fallbackToOlderReleases": "Visszatérés a régebbi kiadásokhoz",
"filterReleaseTitlesByRegEx": "A kiadás címeinek szűrése reguláris kifejezéssel",
"invalidRegEx": "Érvénytelen reguláris kifejezés",
"noDescription": "Nincs leírás",
"cancel": "Mégse",
"continue": "Tovább",
"requiredInBrackets": "(Kötelező)",
"dropdownNoOptsError": "HIBA: A LEDOBÁST LEGALÁBB EGY OPCIÓHOZ KELL RENDELNI",
"colour": "Szín",
"githubStarredRepos": "GitHub Csillagos Repo-k",
"uname": "Felh.név",
"wrongArgNum": "Rossz számú argumentumot adott meg",
"xIsTrackOnly": "A(z) {} csak nyomonkövethető",
"source": "Forrás",
"app": "Alkalmazás",
"appsFromSourceAreTrackOnly": "Az ebből a forrásból származó alkalmazások 'Csak nyomon követhetőek'.",
"youPickedTrackOnly": "A 'Csak követés' opciót választotta.",
"trackOnlyAppDescription": "Az alkalmazás frissítéseit nyomon követi, de az Obtainium nem tudja letölteni vagy telepíteni.",
"cancelled": "Törölve",
"appAlreadyAdded": "Az app már hozzáadva",
"alreadyUpToDateQuestion": "Az app már naprakész?",
"addApp": "App hozzáadás",
"appSourceURL": "App forrás URL",
"error": "Hiba",
"add": "Hozzáadás",
"searchSomeSourcesLabel": "Keresés (csak egyes források)",
"search": "Keresés",
"additionalOptsFor": "További lehetőségek a következőhöz: {}",
"supportedSources": "Támogatott források",
"trackOnlyInBrackets": "(Csak nyomonkövetés)",
"searchableInBrackets": "(Kereshető)",
"appsString": "Appok",
"noApps": "Nincs App",
"noAppsForFilter": "Nincsenek appok a szűrőhöz",
"byX": "Fejlesztő: {}",
"percentProgress": "Folyamat: {}%",
"pleaseWait": "Kis türelmet",
"updateAvailable": "Frissítés érhető el",
"notInstalled": "Nem telepített",
"pseudoVersion": "ál-verzió",
"selectAll": "Mindet kiválaszt",
"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.",
"removeSelectedAppsQuestion": "Eltávolítja a kiválasztott appokat?",
"removeSelectedApps": "Távolítsa el a kiválasztott appokat",
"updateX": "Frissítés: {}",
"installX": "Telepítés: {}",
"markXTrackOnlyAsUpdated": "Jelölje meg: {}\n(Csak nyomon követhető)\nmint Frissített",
"changeX": "Változás {}",
"installUpdateApps": "Appok telepítése/frissítése",
"installUpdateSelectedApps": "Telepítse/frissítse a kiválasztott appokat",
"markXSelectedAppsAsUpdated": "Megjelöl {} kiválasztott alkalmazást frissítettként?",
"no": "Nem",
"yes": "Igen",
"markSelectedAppsUpdated": "Jelölje meg a kiválasztott appokat frissítettként",
"pinToTop": "Rögzítés felülre",
"unpinFromTop": "Eltávolít felülről",
"resetInstallStatusForSelectedAppsQuestion": "Visszaállítja a kiválasztott appok telepítési állapotát?",
"installStatusOfXWillBeResetExplanation": "A kiválasztott appok telepítési állapota visszaáll.\n\nEz akkor segíthet, ha az Obtainiumban megjelenített app verzió hibás, frissítések vagy egyéb problémák miatt.",
"customLinkMessage": "Ezek a hivatkozások a telepített Obtainium-mal rendelkező eszközökön működnek",
"shareAppConfigLinks": "App konfiguráció megosztása HTML linkként",
"shareSelectedAppURLs": "Ossza meg a kiválasztott app URL címeit",
"resetInstallStatus": "Telepítési állapot visszaállítása",
"more": "További",
"removeOutdatedFilter": "Távolítsa el az elavult app szűrőt",
"showOutdatedOnly": "Csak az elavult appok megjelenítése",
"filter": "Szűrő",
"filterActive": "Szűrő *",
"filterApps": "Appok szűrése",
"appName": "App név",
"author": "Szerző",
"upToDateApps": "Naprakész appok",
"nonInstalledApps": "Nem telepített appok",
"importExport": "Import Export",
"settings": "Beállítások",
"exportedTo": "Exportálva ide {}",
"obtainiumExport": "Obtainium Adat Exportálás",
"invalidInput": "Hibás bemenet",
"importedX": "Importálva innen {}",
"obtainiumImport": "Obtainium Adat Importálás",
"importFromURLList": "Importálás URL listából",
"searchQuery": "Keresési lekérdezés",
"appURLList": "App URL lista",
"line": "Sor",
"searchX": "Keresés {}",
"noResults": "Nincs találat",
"importX": "Importálás: {}",
"importedAppsIdDisclaimer": "Előfordulhat, hogy az importált appok helytelenül \"Nincs telepítve\" jelzéssel jelennek meg.\nA probléma megoldásához telepítse újra őket az Obtainiumon keresztül.\nEz nem érinti az alkalmazásadatokat.\n\nCsak az URL-ekre és a harmadik féltől származó importálási módszerekre vonatkozik..",
"importErrors": "Importálási hibák",
"importedXOfYApps": "{}/{} app importálva.",
"followingURLsHadErrors": "A következő URL-ek hibákat tartalmaztak:",
"selectURL": "Válassza ki az URL-t",
"selectURLs": "Kiválasztott URL-ek",
"pick": "Válasszon",
"theme": "Téma",
"dark": "Sötét",
"light": "Világos",
"followSystem": "Rendszer szerint",
"useBlackTheme": "Használjon teljesen fekete sötét témát",
"appSortBy": "App rendezés...",
"authorName": "Szerző/Név",
"nameAuthor": "Név/Szerző",
"asAdded": "Mint Hozzáadott",
"appSortOrder": "Appok rendezése",
"ascending": "Emelkedő",
"descending": "Csökkenő",
"bgUpdateCheckInterval": "Háttérfrissítés ellenőrzés időköze",
"neverManualOnly": "Soha csak manuális",
"appearance": "Megjelenés",
"showWebInAppView": "Forrás megjelenítése az Appok nézetben",
"pinUpdates": "Frissítések kitűzése az App nézet tetejére",
"updates": "Frissítések",
"sourceSpecific": "Forrás-specifikus",
"appSource": "App forrás",
"noLogs": "Nincsenek naplók",
"appLogs": "App naplók",
"close": "Bezárás",
"share": "Megosztás",
"appNotFound": "App nem található",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "Válasszon egy APK-t",
"appHasMoreThanOnePackage": "A(z) {} egynél több csomaggal rendelkezik:",
"deviceSupportsXArch": "Eszköze támogatja a {} CPU architektúrát.",
"deviceSupportsFollowingArchs": "Az eszköze a következő CPU architektúrákat támogatja:",
"warning": "Figyelem",
"sourceIsXButPackageFromYPrompt": "Az alkalmazás forrása „{}”, de a kiadási csomag innen származik: „{}”. Folytatja?",
"updatesAvailable": "Frissítések érhetők el",
"updatesAvailableNotifDescription": "Értesíti a felhasználót, hogy frissítések állnak rendelkezésre egy vagy több, az Obtainium által nyomon követett alkalmazáshoz",
"noNewUpdates": "Nincsenek új frissítések.",
"xHasAnUpdate": "A(z) {} frissítést kapott.",
"appsUpdated": "Alkalmazások frissítve",
"appsUpdatedNotifDescription": "Értesíti a felhasználót, hogy egy/több app frissítése megtörtént a háttérben",
"xWasUpdatedToY": "{} frissítve a következőre: {}.",
"errorCheckingUpdates": "Hiba a frissítések keresésekor",
"errorCheckingUpdatesNotifDescription": "Értesítés, amely akkor jelenik meg, ha a háttérbeli frissítések ellenőrzése sikertelen",
"appsRemoved": "Alkalmazások eltávolítva",
"appsRemovedNotifDescription": "Értesíti a felhasználót egy vagy több alkalmazás eltávolításáról a betöltésük során fellépő hibák miatt",
"xWasRemovedDueToErrorY": "A(z) {} a következő hiba miatt lett eltávolítva: {}",
"completeAppInstallation": "Teljes app telepítés",
"obtainiumMustBeOpenToInstallApps": "Az Obtainiumnak megnyitva kell lennie az alkalmazások telepítéséhez",
"completeAppInstallationNotifDescription": "Megkéri a felhasználót, hogy térjen vissza az Obtainiumhoz, hogy befejezze az alkalmazás telepítését",
"checkingForUpdates": "Frissítések keresése",
"checkingForUpdatesNotifDescription": "Átmeneti értesítés, amely a frissítések keresésekor jelenik meg",
"pleaseAllowInstallPerm": "Kérjük, engedélyezze az Obtainiumnak az alkalmazások telepítését",
"trackOnly": "Csak követés",
"errorWithHttpStatusCode": "Hiba {}",
"versionCorrectionDisabled": "Verzió korrekció letiltva (úgy tűnik, a beépülő modul nem működik)",
"unknown": "Ismeretlen",
"none": "Egyik sem",
"never": "Soha",
"latestVersionX": "Legújabb verzió: {}",
"installedVersionX": "Telepített verzió: {}",
"lastUpdateCheckX": "Frissítés ellenőrizve: {}",
"remove": "Eltávolítás",
"yesMarkUpdated": "Igen, megjelölés frissítettként",
"fdroid": "F-Droid hivatalos",
"appIdOrName": "App ID vagy név",
"appId": "Alkalmazásazonosító",
"appWithIdOrNameNotFound": "Nem található app ezzel az azonosítóval vagy névvel",
"reposHaveMultipleApps": "A repók több alkalmazást is tartalmazhatnak",
"fdroidThirdPartyRepo": "F-Droid Harmadik-fél Repo",
"steamMobile": "Steam mobil",
"steamChat": "Steam Chat",
"install": "Telepít",
"markInstalled": "Telepítettnek jelöl",
"update": "Frissít",
"markUpdated": "Frissítettnek jelöl",
"additionalOptions": "További lehetőségek",
"disableVersionDetection": "Verzió érzékelés letiltása",
"noVersionDetectionExplanation": "Ezt a beállítást csak olyan alkalmazásoknál szabad használni, ahol a verzióérzékelés nem működik megfelelően.",
"downloadingX": "{} letöltés",
"downloadNotifDescription": "Értesíti a felhasználót az app letöltésének előrehaladásáról",
"noAPKFound": "Nem található APK",
"noVersionDetection": "Nincs verzió érzékelés",
"categorize": "Kategorizálás",
"categories": "Kategóriák",
"category": "Kategória",
"noCategory": "Nincs kategória",
"noCategories": "Nincsenek kategóriák",
"deleteCategoriesQuestion": "Törli a kategóriákat?",
"categoryDeleteWarning": "A(z) {} összes app kategorizálatlan állapotba kerül.",
"addCategory": "Új kategória",
"label": "Címke",
"language": "Nyelv",
"copiedToClipboard": "Másolva a vágólapra",
"storagePermissionDenied": "Tárhely engedély megtagadva",
"selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.",
"filterAPKsByRegEx": "Az APK-k szűrése reguláris kifejezéssel",
"removeFromObtainium": "Eltávolítás az Obtainiumból",
"uninstallFromDevice": "Eltávolítás a készülékről",
"onlyWorksWithNonVersionDetectApps": "Csak azoknál az alkalmazásoknál működik, amelyeknél a verzióérzékelés le van tiltva.",
"releaseDateAsVersion": "Használja a Kiadás dátumát, mint verziót",
"releaseDateAsVersionExplanation": "Ezt a beállítást csak olyan alkalmazásoknál szabad használni, ahol a verzió érzékelése nem működik megfelelően, de elérhető a kiadás dátuma.",
"changes": "Változtatások",
"releaseDate": "Kiadás dátuma",
"importFromURLsInFile": "Importálás fájlban található URL-ből (mint pl. OPML)",
"versionDetectionExplanation": "A verzió karakterlánc egyeztetése az OS által észlelt verzióval",
"versionDetection": "Verzió érzékelés",
"standardVersionDetection": "Alapért. verzió érzékelés",
"groupByCategory": "Csoportosítás Kategória alapján",
"autoApkFilterByArch": "Ha lehetséges, próbálja CPU architektúra szerint szűrni az APK-kat",
"overrideSource": "Forrás felülbírálása",
"dontShowAgain": "Ne mutassa ezt újra",
"dontShowTrackOnlyWarnings": "Ne jelenítsen meg 'Csak nyomon követés' figyelmeztetést",
"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",
"gitlabPATLabel": "GitLab Personal Access Token\n(Engedélyezi a Keresést és jobb APK felfedezés)",
"about": "Rólunk",
"requiresCredentialsInSettings": "{}: Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
"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",
"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",
"checkUpdateOnDetailPage": "Frissítések keresése az app részleteit tartalmazó oldal megnyitásakor",
"disablePageTransitions": "Lap áttűnési animációk letiltása",
"reversePageTransitions": "Fordított lap áttűnési animációk",
"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 {}.",
"enableBackgroundUpdates": "Frissítések a háttérben",
"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": "Szűrés egy 'köztes' látogatási linkre",
"filterByLinkText": "A hivatkozások szűrése linkszöveg alapján",
"intermediateLinkNotFound": "Köztes link nem található",
"intermediateLink": "Köztes 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": "Tartalmazza a beállításokat",
"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": "Vegye az első linket",
"skipSort": "A válogatás kihagyása",
"debugMenu": "Hibakereső menü",
"bgTaskStarted": "A háttérfeladat elindult ellenőrizze a naplókat.",
"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": "Telepítési mód",
"normal": "Normál",
"root": "Gyökér",
"shizukuBinderNotFound": "A Shizuku nem fut",
"useSystemFont": "Használja a rendszer betűtípusát",
"systemFontError": "Hiba a rendszer betűtípusának betöltésekor: {}",
"useVersionCodeAsOSVersion": "Az app versionCode használata a rendszer által észlelt verzióként",
"requestHeader": "Kérelem fejléc",
"useLatestAssetDateAsReleaseDate": "Használja a legújabb tartalomfeltöltést megjelenési dátumként",
"defaultPseudoVersioningMethod": "Alapértelmezett álversziós módszer",
"partialAPKHash": "Részleges APK Hash",
"APKLinkHash": "APK Link Hash",
"directAPKLink": "Közvetlen APK Link",
"pseudoVersionInUse": "Egy álverzió van használatban",
"installed": "Telepített",
"latest": "Legújabb",
"invertRegEx": "Invertált reguláris kifejezés",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazást?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva",
"other": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "A háttérfrissítések ellenőrzése {}-t észlelt, {} perc múlva ütemezi az újrapróbálkozást",
"other": "A háttérfrissítések ellenőrzése {}-t észlelt, {} perc múlva ütemezi az újrapróbálkozást"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "A háttérfrissítés ellenőrzése {} frissítést talált szükség esetén értesíti a felhasználót",
"other": "A háttérfrissítés ellenőrzése {} frissítést talált szükség esetén értesíti a felhasználót"
},
"apps": {
"one": "{} app",
"other": "{} app"
},
"url": {
"one": "{} URL",
"other": "{} URL"
},
"minute": {
"one": "{} perc",
"other": "{} perc"
},
"hour": {
"one": "{} óra",
"other": "{} óra"
},
"day": {
"one": "{} nap",
"other": "{} nap"
},
"clearedNLogsBeforeXAfterY": {
"one": "{n} napló törölve (előtte = {előtte}, utána = {utána})",
"other": "{n} napló törölve (előtte = {előtte}, utána = {utána})"
},
"xAndNMoreUpdatesAvailable": {
"one": "A(z) {} és 1 további alkalmazás frissítéseket kapott.",
"other": "{} és {} további alkalmazás frissítéseket kapott."
},
"xAndNMoreUpdatesInstalled": {
"one": "A(z) {} és 1 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."
}
}

View File

@ -11,15 +11,7 @@
"unexpectedError": "Errore imprevisto",
"ok": "Va bene",
"and": "e",
"startedBgUpdateTask": "Avviata l'attività di controllo degli aggiornamenti in secondo piano",
"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",
"githubPATLabel": "GitHub Personal Access Token (aumenta limite di traffico)",
"includePrereleases": "Includi prerelease",
"fallbackToOlderReleases": "Ripiega su release precedenti",
"filterReleaseTitlesByRegEx": "Filtra release con espressioni regolari",
@ -49,7 +41,7 @@
"searchSomeSourcesLabel": "Cerca (solo per alcune fonti)",
"search": "Cerca",
"additionalOptsFor": "Opzioni aggiuntive per {}",
"supportedSourcesBelow": "Fonti supportate:",
"supportedSources": "Fonti supportate",
"trackOnlyInBrackets": "(Solo-Monitoraggio)",
"searchableInBrackets": "(ricercabile)",
"appsString": "App",
@ -59,28 +51,29 @@
"percentProgress": "Avanzamento: {}%",
"pleaseWait": "In attesa",
"updateAvailable": "Aggiornamento disponibile",
"estimateInBracketsShort": "(prev.)",
"notInstalled": "Non installato",
"estimateInBrackets": "(previsto)",
"pseudoVersion": "pseudo-versione",
"selectAll": "Seleziona tutto",
"deselectN": "Deseleziona {}",
"deselectX": "Deseleziona {}",
"xWillBeRemovedButRemainInstalled": "Verà effettuata la rimozione di {}, ma non la disinstallazione.",
"removeSelectedAppsQuestion": "Rimuovere le app selezionate?",
"removeSelectedApps": "Rimuovi le app selezionate",
"updateX": "Aggiorna {}",
"installX": "Installa {}",
"markXTrackOnlyAsUpdated": "Contrassegna {}\n(Solo-Monitoraggio)\ncome aggiornato",
"markXTrackOnlyAsUpdated": "Contrassegna {}\n(Solo-Monitoraggio)\ncome aggiornata",
"changeX": "Modifica {}",
"installUpdateApps": "Installa/Aggiorna app",
"installUpdateSelectedApps": "Installa/Aggiorna le app selezionate",
"markXSelectedAppsAsUpdated": "Contrassegnare le {} app selezionate come aggiornate?",
"no": "No",
"no": "NO",
"yes": "Sì",
"markSelectedAppsUpdated": "Contrassegna le app selezionate come aggiornate",
"pinToTop": "Fissa in alto",
"unpinFromTop": "Rimuovi dall'alto",
"resetInstallStatusForSelectedAppsQuestion": "Ripristinare lo stato d'installazione delle app selezionate?",
"installStatusOfXWillBeResetExplanation": "Lo stato d'installazione di ogni app selezionata sarà ripristinato.\n\nCiò può essere d'aiuto nel caso in cui la versione mostrata dell'app in Obtainium non sia corretta a causa di un aggiornamento fallito o di altri problemi.",
"customLinkMessage": "Questi collegamenti funzionano sui dispositivi con Obtainium installato",
"shareAppConfigLinks": "Condividi la configurazione dell'app come collegamento HTML",
"shareSelectedAppURLs": "Condividi gli URL delle app selezionate",
"resetInstallStatus": "Ripristina lo stato d'installazione",
"more": "Altro",
@ -111,7 +104,6 @@
"importErrors": "Errori di importazione",
"importedXOfYApps": "{} app di {} importate.",
"followingURLsHadErrors": "I seguenti URL contengono errori:",
"okay": "Va bene",
"selectURL": "Seleziona l'URL",
"selectURLs": "Seleziona gli URL",
"pick": "Seleziona",
@ -119,8 +111,6 @@
"dark": "Scuro",
"light": "Chiaro",
"followSystem": "Segui il sistema",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Usa il tema Nero puro",
"appSortBy": "App ordinate per",
"authorName": "Autore/Nome",
@ -136,7 +126,7 @@
"pinUpdates": "Fissa aggiornamenti disponibili in alto",
"updates": "Aggiornamenti",
"sourceSpecific": "Specifiche per la fonte",
"appSource": "Sorgente dell'app",
"appSource": "Codice dell'app",
"noLogs": "Nessun log",
"appLogs": "Log dell'app",
"close": "Chiudi",
@ -177,20 +167,19 @@
"installedVersionX": "Versione installata: {}",
"lastUpdateCheckX": "Ultimo controllo degli aggiornamenti: {}",
"remove": "Rimuovi",
"yesMarkUpdated": "Sì, contrassegna come aggiornato",
"yesMarkUpdated": "Sì, contrassegna come aggiornata",
"fdroid": "F-Droid ufficiale",
"appIdOrName": "ID o nome dell'app",
"appId": "ID dell'app",
"appWithIdOrNameNotFound": "Non è stata trovata alcuna app con quell'ID o nome",
"reposHaveMultipleApps": "I repository possono contenere più app",
"fdroidThirdPartyRepo": "Repository F-Droid di terze parti",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "Mobile a vapore",
"steamChat": "Chat di vapore",
"install": "Installa",
"markInstalled": "Contrassegna come installato",
"markInstalled": "Contrassegna come installata",
"update": "Aggiorna",
"markUpdated": "Contrassegna come aggiornato",
"markUpdated": "Contrassegna come aggiornata",
"additionalOptions": "Opzioni aggiuntive",
"disableVersionDetection": "Disattiva il rilevamento della versione",
"noVersionDetectionExplanation": "Questa opzione dovrebbe essere usata solo per le app la cui versione non viene rilevata correttamente.",
@ -220,6 +209,7 @@
"changes": "Novità",
"releaseDate": "Data di rilascio",
"importFromURLsInFile": "Importa da URL in file (come OPML)",
"versionDetectionExplanation": "Riconcilia la stringa della versione con la versione rilevata dal sistema operativo",
"versionDetection": "Rilevamento di versione",
"standardVersionDetection": "Rilevamento di versione standard",
"groupByCategory": "Raggruppa per categoria",
@ -229,23 +219,87 @@
"dontShowTrackOnlyWarnings": "Non mostrare gli avvisi 'Solo-Monitoraggio'",
"dontShowAPKOriginWarnings": "Non mostrare gli avvisi di origine dell'APK",
"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",
"requiresCredentialsInSettings": "Servono credenziali aggiuntive (in Impostazioni)",
"requiresCredentialsInSettings": "{}: Servono credenziali aggiuntive (in Impostazioni)",
"checkOnStart": "Controlla una volta all'avvio",
"tryInferAppIdFromCode": "Prova a dedurre l'ID dell'app dal codice sorgente",
"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": "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.",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"removeOnExternalUninstall": "Rimuovi automaticamente app disinstallate esternamente",
"pickHighestVersionCode": "Auto-seleziona APK con version code più alto",
"checkUpdateOnDetailPage": "Controlla aggiornamenti all'apertura dei dettagli dell'app",
"disablePageTransitions": "Disattiva animazioni di transizione pagina",
"reversePageTransitions": "Inverti animazioni di transizione pagina",
"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": "Ordina solo in base all'ultimo segmento del collegamento",
"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": "Filtro per un collegamento 'intermedio' da visitare",
"filterByLinkText": "Filtra i collegamenti in base al testo del collegamento",
"intermediateLinkNotFound": "Link intermedio non trovato",
"intermediateLink": "Collegamento intermedio",
"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",
"root": "Radice",
"shizukuBinderNotFound": "Shizuku non è in esecuzione",
"useSystemFont": "Utilizza il carattere di sistema",
"systemFontError": "Errore durante il caricamento del carattere di sistema: {}",
"useVersionCodeAsOSVersion": "Utilizza il codice versione dell'app come versione rilevata dal sistema operativo",
"requestHeader": "Intestazione della richiesta",
"useLatestAssetDateAsReleaseDate": "Utilizza l'ultimo caricamento della risorsa come data di rilascio",
"defaultPseudoVersioningMethod": "Metodo di pseudoversione predefinito",
"partialAPKHash": "Hash APK parziale",
"APKLinkHash": "Hash collegamento APK",
"directAPKLink": "Collegamento APK diretto",
"pseudoVersionInUse": "È in uso una pseudo-versione",
"installed": "Installato",
"latest": "Ultimo",
"invertRegEx": "Inverti espressione regolare",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"removeAppQuestion": {
"one": "Rimuovere l'app?",
"other": "Rimuovere le app?"
@ -293,5 +347,9 @@
"xAndNMoreUpdatesInstalled": {
"one": "{} e un'altra 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."
}
}

View File

@ -11,18 +11,10 @@
"unexpectedError": "予期せぬエラーが発生しました",
"ok": "OK",
"and": "と",
"startedBgUpdateTask": "バックグラウンドのアップデート確認タスクを開始",
"bgUpdateIgnoreAfterIs": "Bg update ignoreAfter is {}",
"startedActualBGUpdateCheck": "実際のバックグラウンドのアップデート確認を開始",
"bgUpdateTaskFinished": "バックグラウンドのアップデート確認タスクを終了",
"firstRun": "これがObtainiumの最初の実行です",
"settingUpdateCheckIntervalTo": "確認間隔を{}に設定する",
"githubPATLabel": "GitHub パーソナルアクセストークン (レート制限の引き上げ)",
"githubPATHint": "PATは次の形式でなければなりません: ユーザー名:トークン",
"githubPATFormat": "ユーザー名:トークン",
"includePrereleases": "プレリリースを含む",
"fallbackToOlderReleases": "旧リリースへのフォールバック",
"filterReleaseTitlesByRegEx": "正規表現でリリースタイトルを絞り込む",
"filterReleaseTitlesByRegEx": "正規表現でリリースタイトルをフィルタリングする",
"invalidRegEx": "無効な正規表現",
"noDescription": "説明はありません",
"cancel": "キャンセル",
@ -49,7 +41,7 @@
"searchSomeSourcesLabel": "検索 (一部ソースのみ)",
"search": "検索",
"additionalOptsFor": "{}の追加オプション",
"supportedSourcesBelow": "対応するソース:",
"supportedSources": "対応するソース",
"trackOnlyInBrackets": "(追跡のみ)",
"searchableInBrackets": "(検索可能)",
"appsString": "アプリ",
@ -59,11 +51,10 @@
"percentProgress": "ダウンロード中: {}%",
"pleaseWait": "しばらくお待ちください",
"updateAvailable": "アップデートが利用可能",
"estimateInBracketsShort": "(推定)",
"notInstalled": "未インストール",
"estimateInBrackets": "(推定)",
"pseudoVersion": "疑似バージョン",
"selectAll": "すべて選択",
"deselectN": "{}件の選択を解除",
"deselectX": "{}件の選択を解除",
"xWillBeRemovedButRemainInstalled": "{} はObtainiumから削除されますが、デバイスにはインストールされたままです。",
"removeSelectedAppsQuestion": "選択したアプリを削除しますか?",
"removeSelectedApps": "選択したアプリを削除する",
@ -81,6 +72,8 @@
"unpinFromTop": "トップから固定解除",
"resetInstallStatusForSelectedAppsQuestion": "選択したアプリのインストール状態をリセットしますか?",
"installStatusOfXWillBeResetExplanation": "選択したアプリのインストール状態がリセットされます。\n\nアップデートに失敗した場合など、Obtainiumに表示されるアプリのバージョンが正しくない場合に有効です。",
"customLinkMessage": "これらのリンクはObtainiumがインストールされたデバイスで動作します",
"shareAppConfigLinks": "アプリの設定をHTMLリンクとして共有する",
"shareSelectedAppURLs": "選択したアプリのURLを共有する",
"resetInstallStatus": "インストール状態をリセットする",
"more": "もっと見る",
@ -88,7 +81,7 @@
"showOutdatedOnly": "アップデートが存在するアプリのみ表示する",
"filter": "フィルター",
"filterActive": "フィルター *",
"filterApps": "アプリを絞り込む",
"filterApps": "アプリをフィルタリングする",
"appName": "アプリ名",
"author": "作者",
"upToDateApps": "最新のアプリ",
@ -111,7 +104,6 @@
"importErrors": "インポートエラー",
"importedXOfYApps": "{} / {} アプリをインポートしました",
"followingURLsHadErrors": "以下のURLでエラーが発生しました:",
"okay": "OK",
"selectURL": "URLを選択",
"selectURLs": "URLを選択",
"pick": "選択",
@ -119,8 +111,6 @@
"dark": "ダーク",
"light": "ライト",
"followSystem": "システムに従う",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "ピュアブラックダークテーマを使用する",
"appSortBy": "アプリの並び方",
"authorName": "作者名/アプリ名",
@ -152,7 +142,7 @@
"updatesAvailable": "アップデートが利用可能",
"updatesAvailableNotifDescription": "Obtainiumが追跡している1つまたは複数のアプリのアップデートが利用可能であることをユーザーに通知する",
"noNewUpdates": "新しいアップデートはありません",
"xHasAnUpdate": "{} のアップデートが利用可能です",
"xHasAnUpdate": "{} のアップデートが利用可能です",
"appsUpdated": "アプリをアップデートしました",
"appsUpdatedNotifDescription": "1つまたは複数のAppのアップデートがバックグラウンドで適用されたことをユーザーに通知する",
"xWasUpdatedToY": "{} が {} にアップデートされました",
@ -162,8 +152,8 @@
"appsRemovedNotifDescription": "アプリの読み込み中にエラーが発生したため、1つまたは複数のアプリが削除されたことをユーザーに通知する",
"xWasRemovedDueToErrorY": "このエラーのため、{} は削除されました: {}",
"completeAppInstallation": "アプリのインストールを完了する",
"obtainiumMustBeOpenToInstallApps": "アプリをインストールするにはObtainium開いている必要があります",
"completeAppInstallationNotifDescription": "アプリのインストールを完了するために、Obtainiumに戻る必要があります",
"obtainiumMustBeOpenToInstallApps": "アプリをインストールするにはObtainium開いている必要があります",
"completeAppInstallationNotifDescription": "アプリのインストールを完了するために、Obtainiumに戻る必要があります",
"checkingForUpdates": "アップデートを確認中",
"checkingForUpdatesNotifDescription": "アップデートを確認する際に表示される一時的な通知",
"pleaseAllowInstallPerm": "Obtainiumによるアプリのインストールを許可してください。",
@ -178,15 +168,14 @@
"lastUpdateCheckX": "最終アップデート確認: {}",
"remove": "削除",
"yesMarkUpdated": "はい、アップデート済みとしてマークします",
"fdroid": "F-Droid Official",
"fdroid": "F-Droid公式",
"appIdOrName": "アプリのIDまたは名前",
"appId": "App ID",
"appId": "アプリID",
"appWithIdOrNameNotFound": "そのIDや名前を持つアプリは見つかりませんでした",
"reposHaveMultipleApps": "リポジトリには複数のアプリが含まれることがあります",
"fdroidThirdPartyRepo": "F-Droid サードパーティリポジトリ",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "Steamモバイル",
"steamChat": "Steamチャット",
"install": "インストール",
"markInstalled": "インストール済みとしてマークする",
"update": "アップデート",
@ -211,7 +200,7 @@
"copiedToClipboard": "クリップボードにコピーしました",
"storagePermissionDenied": "ストレージ権限が拒否されました",
"selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。",
"filterAPKsByRegEx": "正規表現でAPKを絞り込む",
"filterAPKsByRegEx": "正規表現でAPKをフィルタリングする",
"removeFromObtainium": "Obtainiumから削除する",
"uninstallFromDevice": "デバイスからアンインストールする",
"onlyWorksWithNonVersionDetectApps": "バージョン検出を無効にしているアプリにのみ動作します。",
@ -220,10 +209,11 @@
"changes": "変更点",
"releaseDate": "リリース日",
"importFromURLsInFile": "ファイルOPMLなど内のURLからインポート",
"versionDetectionExplanation": "バージョン文字列とOSから検出されたバージョンを照合する",
"versionDetection": "バージョン検出",
"standardVersionDetection": "標準のバージョン検出",
"groupByCategory": "カテゴリ別にグループ化する",
"autoApkFilterByArch": "可能であればCPUアーキテクチャによるAPKのフィルタリングを試みる",
"autoApkFilterByArch": "可能であればCPUアーキテクチャによるAPKのフィルタリングを試みる",
"overrideSource": "ソースの上書き",
"dontShowAgain": "二度と表示しない",
"dontShowTrackOnlyWarnings": "「追跡のみ」の警告を表示しない",
@ -231,7 +221,7 @@
"moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる",
"gitlabPATLabel": "GitLab パーソナルアクセストークン\n(検索とより良いAPK検出の有効化)",
"about": "概要",
"requiresCredentialsInSettings": "これには追加の認証が必要です (設定にて)",
"requiresCredentialsInSettings": "{}: これには追加の認証が必要です (設定にて)",
"checkOnStart": "起動時にアップデートを確認する",
"tryInferAppIdFromCode": "ソースコードからApp IDを推測する",
"removeOnExternalUninstall": "外部でアンインストールされたアプリを自動的に削除する",
@ -244,15 +234,79 @@
"addInfoInSettings": "設定でこの情報を追加してください。",
"githubSourceNote": "GitHubのレート制限はAPIキーを使うことで回避できます。",
"gitlabSourceNote": "GitLabのAPK抽出はAPIキーがないと動作しない場合があります。",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"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": "設定を含む",
"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": "通常",
"root": "Root",
"shizukuBinderNotFound": "Shizukuが起動していません",
"useSystemFont": "システムフォントを使用する",
"systemFontError": "システムフォントの読み込みエラー: {}",
"useVersionCodeAsOSVersion": "アプリのバージョンコードをOSで検出されたバージョンとして使用する",
"requestHeader": "リクエストヘッダー",
"useLatestAssetDateAsReleaseDate": "最新のアセットアップロードをリリース日として使用する",
"defaultPseudoVersioningMethod": "デフォルトの疑似バージョン管理方法",
"partialAPKHash": "部分的なAPKのハッシュ",
"APKLinkHash": "APKリンクのハッシュ",
"directAPKLink": "APKのダイレクトリンク",
"pseudoVersionInUse": "疑似バージョンが使用されています",
"installed": "インストール済み",
"latest": "最新",
"invertRegEx": "正規表現を反転",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"removeAppQuestion": {
"one": "アプリを削除しますか?",
"other": "アプリを削除しますか?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください",
"other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください"
"one": "リクエストが多すぎます(レート制限)- {} 分後に再試行してください",
"other": "リクエストが多すぎます(レート制限)- {} 分後に再試行してください"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "バックグラウンドでのアップデート確認で {} の問題が発生, {} 分後に再試行します",
@ -263,35 +317,39 @@
"other": "バックグラウンドでのアップデート確認で {} 個のアップデートを発見 - 必要に応じてユーザーに通知します"
},
"apps": {
"one": "{}個のアプリ",
"other": "{}個のアプリ"
"one": "{} 個のアプリ",
"other": "{} 個のアプリ"
},
"url": {
"one": "{}個のURL",
"other": "{}個のURL"
"one": "{} 個のURL",
"other": "{} 個のURL"
},
"minute": {
"one": "{}分",
"other": "{}分"
"one": "{} 分",
"other": "{} 分"
},
"hour": {
"one": "{}時間",
"other": "{}時間"
"one": "{} 時間",
"other": "{} 時間"
},
"day": {
"one": "{}日",
"other": "{}日"
"one": "{} 日",
"other": "{} 日"
},
"clearedNLogsBeforeXAfterY": {
"one": "{n}個のログをクリアしました (前 = {before}, 後 = {after})",
"other": "{n}個のログをクリアしました (前 = {before}, 後 = {after})"
"one": "{n} 個のログをクリアしました (前 = {before}, 後 = {after})",
"other": "{n} 個のログをクリアしました (前 = {before}, 後 = {after})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} とさらに {} 個のアプリのアップデートが利用可能です",
"other": "{} とさらに {} 個のアプリのアップデートが利用可能です"
"one": "{} とさらに {} 個のアプリのアップデートが利用可能です",
"other": "{} とさらに {} 個のアプリのアップデートが利用可能です"
},
"xAndNMoreUpdatesInstalled": {
"one": "{} とさらに {} 個のアプリがアップデートされました",
"other": "{} とさらに {} 個のアプリがアップデートされました"
"one": "{} とさらに {} 個のアプリがアップデートされました",
"other": "{} とさらに {} 個のアプリがアップデートされました"
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} とさらに 1 個のアプリがアップデートされた可能性があります。",
"other": "{} とさらに {} 個のアプリがアップデートされた可能性があります。"
}
}

355
assets/translations/nl.json Normal file
View File

@ -0,0 +1,355 @@
{
"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-repo's met ster",
"uname": "Gebruikersnaam",
"wrongArgNum": "Onjuist aantal argumenten verstrekt.",
"xIsTrackOnly": "{} is alleen tracken",
"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": "(Alleen track)",
"searchableInBrackets": "(Doorzoekbaar)",
"appsString": "Apps",
"noApps": "Geen Apps",
"noAppsForFilter": "Geen Apps voor filter",
"byX": "Door {}",
"percentProgress": "Vooruitgang: {}%",
"pleaseWait": "Even geduld",
"updateAvailable": "Update beschikbaar",
"notInstalled": "Niet geinstalleerd",
"pseudoVersion": "pseudo-versie",
"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.",
"customLinkMessage": "Deze links werken op apparaten waarop Obtainium is geïnstalleerd",
"shareAppConfigLinks": "App-configuratie delen als HTML-link",
"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": "Filteren *",
"filterApps": "Filter apps",
"appName": "App naam",
"author": "Auteur",
"upToDateApps": "Up-to-date apps",
"nonInstalledApps": "Niet-geïnstalleerde apps",
"importExport": "Importeren/Exporteren",
"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": "Importeer {}",
"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",
"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": "Alleen track",
"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-ambtenaar",
"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 Repository van derden",
"steamMobile": "Stoommobiel",
"steamChat": "Steamchat",
"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": "Etiket",
"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)",
"versionDetectionExplanation": "Versiereeks afstemmen met versie gedetecteerd door besturingssysteem",
"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": "Laat dit niet meer zien",
"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": "Sorteren op alleen het laatste segment van de 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 voor een 'Intermediaire' link om te bezoeken",
"filterByLinkText": "Links filteren op linktekst",
"intermediateLinkNotFound": "Tussenliggende link niet gevonden",
"intermediateLink": "Intermediaire 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": "Instellingen opnemen",
"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": "Neem de eerste link",
"skipSort": "Sorteren overslaan",
"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": "Ondersteuning vaste APK URL's",
"selectX": "Selecteer {}",
"parallelDownloads": "Parallelle downloads toestaan",
"installMethod": "Installatiemethode",
"normal": "Normaal",
"root": "Wortel",
"shizukuBinderNotFound": "Shizuku draait niet",
"useSystemFont": "Gebruik het systeemlettertype",
"systemFontError": "Fout bij het laden van het systeemlettertype: {}",
"useVersionCodeAsOSVersion": "Gebruik app versieCode als door OS gedetecteerde versie",
"requestHeader": "Verzoekkoptekst",
"useLatestAssetDateAsReleaseDate": "Gebruik laatste upload als releasedatum",
"defaultPseudoVersioningMethod": "Standaard pseudo-versiebeheermethode",
"partialAPKHash": "Gedeeltelijke APK-hash",
"APKLinkHash": "APK-link-hash",
"directAPKLink": "Directe APK-link",
"pseudoVersionInUse": "Er is een pseudo-versie in gebruik",
"installed": "Geïnstalleerd",
"latest": "Laatste",
"invertRegEx": "Reguliere expressie omkeren",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"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": "{} URL's"
},
"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."
}
}

View File

@ -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 {}",
"noReleaseFound": "Nie można znaleźć odpowiedniego wydania",
"noVersionFound": "Nie można określić wersji wydania",
@ -14,24 +6,17 @@
"cantInstallOlderVersion": "Nie można zainstalować starszej wersji aplikacji",
"appIdMismatch": "Pobrane ID pakietu nie pasuje do istniejącego ID aplikacji",
"functionNotImplemented": "Ta klasa nie zaimplementowała tej funkcji",
"placeholder": "Placeholder",
"placeholder": "Zbiornik",
"someErrors": "Wystąpiły pewne błędy",
"unexpectedError": "Nieoczekiwany błąd",
"ok": "Okej",
"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ń)",
"githubPATHint": "Wymagany format: użytkownik:token",
"githubPATFormat": "użytkownik:token",
"includePrereleases": "Uwzględnij wersje wstępne",
"fallbackToOlderReleases": "Powracaj do starszych wersji",
"filterReleaseTitlesByRegEx": "Filtruj tytuły wydań wg. wyrażeń regularnych",
"invalidRegEx": "Nieprawidłowe wyrażenie regularne",
"noDescription": "Brak opisu",
"cancel": "Anuluj",
"continue": "Kontynuuj",
"requiredInBrackets": "(Wymagane)",
@ -40,11 +25,11 @@
"githubStarredRepos": "Repozytoria GitHub oznaczone gwiazdką",
"uname": "Nazwa użytkownika",
"wrongArgNum": "Nieprawidłowa liczba podanych argumentów",
"xIsTrackOnly": "{} jest tylko obserwowana",
"xIsTrackOnly": "{} jest tylko obserwowane",
"source": "Źródło",
"app": "Aplikacja",
"appsFromSourceAreTrackOnly": "Aplikacje z tego źródła są „Obserwowane.",
"youPickedTrackOnly": "Wybrano opcję Tylko obserwuj.",
"appsFromSourceAreTrackOnly": "Aplikacje z tego źródła są tylko obserwowane.",
"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ć.",
"cancelled": "Anulowano",
"appAlreadyAdded": "Aplikacja już została dodana",
@ -56,9 +41,9 @@
"searchSomeSourcesLabel": "Szukaj (tylko niektóre źródła)",
"search": "Szukaj",
"additionalOptsFor": "Dodatkowe opcje dla {}",
"supportedSourcesBelow": "Obsługiwane źródła:",
"supportedSources": "Obsługiwane źródła",
"trackOnlyInBrackets": "(tylko obserwowane)",
"searchableInBrackets": "(Wyszukiwalne)",
"searchableInBrackets": "(wyszukiwalne)",
"appsString": "Aplikacje",
"noApps": "Brak aplikacji",
"noAppsForFilter": "Brak aplikacji dla filtra",
@ -66,27 +51,29 @@
"percentProgress": "Postęp: {}%",
"pleaseWait": "Proszę czekać",
"updateAvailable": "Dostępna aktualizacja",
"estimateInBracketsShort": "(Szac.)",
"notInstalled": "Nie zainstalowano",
"estimateInBrackets": "(Szacunkowo)",
"pseudoVersion": "pseudowersja",
"selectAll": "Zaznacz wszystkie",
"deselectN": "Odznacz {}",
"deselectX": "Odznacz {}",
"xWillBeRemovedButRemainInstalled": "{} zostanie usunięty z Obtainium, ale pozostanie zainstalowany na urządzeniu.",
"removeSelectedAppsQuestion": "Usunąć wybrane aplikacje?",
"removeSelectedApps": "Usuń wybrane aplikacje",
"updateX": "Zaktualizuj {}",
"installX": "Zainstaluj {}",
"markXTrackOnlyAsUpdated": "Oznacz {}\n(Tylko obserwowana)\njako zaktualizowaną",
"markXTrackOnlyAsUpdated": "Oznacz {}\n(tylko obserwowana)\njako zaktualizowaną",
"changeX": "Zmień {}",
"installUpdateApps": "Instaluj/aktualizuj aplikacje",
"installUpdateSelectedApps": "Zainstaluj/zaktualizuj wybrane aplikacje",
"markXSelectedAppsAsUpdated": "Oznaczyć {} wybranych aplikacji jako zaktualizowane?",
"no": "Nie",
"yes": "Tak",
"markSelectedAppsUpdated": "Oznacz wybrane aplikacje jako zaktualizowane",
"pinToTop": "Przypnij",
"unpinFromTop": "Odepnij",
"resetInstallStatusForSelectedAppsQuestion": "Zresetować status instalacji dla wybranych aplikacji?",
"installStatusOfXWillBeResetExplanation": "Stan instalacji wybranych aplikacji zostanie zresetowany.\n\nMoże być to pomocne, gdy wersja aplikacji wyświetlana w Obtainium jest nieprawidłowa z powodu nieudanych aktualizacji lub innych problemów.",
"customLinkMessage": "Te linki działają na urządzeniach z zainstalowanym Obtainium",
"shareAppConfigLinks": "Udostępnij konfigurację aplikacji jako link HTML",
"shareSelectedAppURLs": "Udostępnij wybrane adresy URL aplikacji",
"resetInstallStatus": "Zresetuj stan instalacji",
"more": "Więcej",
@ -113,7 +100,7 @@
"searchX": "Przeszukaj {}",
"noResults": "Nie znaleziono wyników",
"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",
"importedXOfYApps": "Zaimportowano {} z {} aplikacji.",
"followingURLsHadErrors": "Następujące adresy URL zawierały błędy:",
@ -124,8 +111,6 @@
"dark": "Ciemny",
"light": "Jasny",
"followSystem": "Zgodny z systemem",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Użyj czarnego motywu",
"appSortBy": "Sortuj aplikacje według",
"authorName": "Autor/Nazwa",
@ -155,20 +140,20 @@
"warning": "Uwaga",
"sourceIsXButPackageFromYPrompt": "Źródłem aplikacji jest '{}', ale pakiet wydania pochodzi z '{}'. Kontynuować?",
"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.",
"xHasAnUpdate": "{} ma aktualizację.",
"appsUpdated": "Zaktualizowane aplikacje",
"appsUpdatedNotifDescription": "Powiadamia użytkownika, gdy jedna lub więcej aplikacji zostało zaktualizowanych w tle",
"appsUpdated": "Zaktualizowano aplikacje",
"appsUpdatedNotifDescription": "Informuje, gdy co najmniej jedna aplikacja została zaktualizowana w tle",
"xWasUpdatedToY": "{} zaktualizowano do {}.",
"errorCheckingUpdates": "Sprawdzanie błędów aktualizacji",
"errorCheckingUpdatesNotifDescription": "Powiadomienie wyświetlane, gdy sprawdzanie aktualizacji w tle nie powiedzie się",
"errorCheckingUpdates": "Błąd sprawdzania aktualizacji",
"errorCheckingUpdatesNotifDescription": "Jest wyświetlane, gdy sprawdzanie aktualizacji w tle nie powiedzie się",
"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: {}",
"completeAppInstallation": "Ukończenie instalacji aplikacji",
"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",
"checkingForUpdatesNotifDescription": "Tymczasowe powiadomienie pojawiające się podczas sprawdzania aktualizacji",
"pleaseAllowInstallPerm": "Pozwól Obtainium instalować aplikacje",
@ -185,12 +170,12 @@
"yesMarkUpdated": "Tak, oznacz jako zaktualizowane",
"fdroid": "Oficjalny F-Droid",
"appIdOrName": "ID aplikacji lub nazwa",
"appId": "ID aplikacji",
"appWithIdOrNameNotFound": "Nie znaleziono aplikacji o tym identyfikatorze lub nazwie",
"reposHaveMultipleApps": "Repozytoria mogą zawierać wiele aplikacji",
"fdroidThirdPartyRepo": "Zewnętrzne repo F-Droid",
"steam": "Steam",
"steamMobile": "Mobilny Steam",
"steamChat": "Steam Chat",
"steamChat": "Czat Steam",
"install": "Instaluj",
"markInstalled": "Oznacz jako zainstalowane",
"update": "Zaktualizuj",
@ -199,7 +184,7 @@
"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.",
"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",
"noVersionDetection": "Bez wykrywania wersji",
"categorize": "Kategoryzuj",
@ -224,18 +209,19 @@
"changes": "Zmiany",
"releaseDate": "Data wydania",
"importFromURLsInFile": "Importuj z adresów URL w pliku (typu OPML)",
"versionDetectionExplanation": "Uzgodnij ciąg wersji z wersją wykrytą w systemie operacyjnym",
"versionDetection": "Wykrywanie wersji",
"standardVersionDetection": "Standardowe wykrywanie wersji",
"groupByCategory": "Grupuj według kategorii",
"autoApkFilterByArch": "Spróbuj filtrować pliki APK według architektury procesora, jeśli to możliwe",
"overrideSource": "Nadpisz źródło",
"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",
"moveNonInstalledAppsToBottom": "Przenieś niezainstalowane aplikacje na dół widoku aplikacji",
"gitlabPATLabel": "Osobisty token dostępu GitLab\n(Umożliwia wyszukiwanie i lepsze wykrywanie APK)",
"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",
"tryInferAppIdFromCode": "Spróbuj wywnioskować identyfikator aplikacji z kodu źródłowego",
"removeOnExternalUninstall": "Automatyczne usuń odinstalowane zewnętrznie aplikacje",
@ -248,50 +234,148 @@
"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.",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"sortByLastLinkSegment": "Sortuj tylko według ostatniego segmentu łącza",
"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": "Filtruj link \"pośredni\" do odwiedzenia",
"filterByLinkText": "Filtruj linki według tekstu linku",
"intermediateLinkNotFound": "Nie znaleziono linku pośredniego",
"intermediateLink": "Łącze pośrednie",
"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": "Dołącz ustawienia",
"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": "Weź pierwszy link",
"skipSort": "Pomiń sortowanie",
"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": "Zezwól na pobieranie równoległe",
"installMethod": "Metoda instalacji",
"normal": "Normalna",
"root": "Źródło",
"shizukuBinderNotFound": "Shizuku is not running",
"useSystemFont": "Użyj czcionki systemowej",
"systemFontError": "Błąd podczas ładowania czcionki systemowej: {}",
"useVersionCodeAsOSVersion": "Użyj kodu wersji aplikacji jako wersji wykrytej przez system operacyjny",
"requestHeader": "Nagłówek żądania",
"useLatestAssetDateAsReleaseDate": "Użyj najnowszego przesłanego zasobu jako daty wydania",
"defaultPseudoVersioningMethod": "Domyślna metoda pseudowersji",
"partialAPKHash": "Częściowy skrót APK",
"APKLinkHash": "Skrót łącza APK",
"directAPKLink": "Bezpośredni link APK",
"pseudoVersionInUse": "Pseudowersja jest w użyciu",
"installed": "Zainstalowano",
"latest": "Najnowszy",
"invertRegEx": "Odwróć wyrażenie regularne",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"removeAppQuestion": {
"one": "Usunąć aplikację?",
"few": "Usunąć aplikacje?",
"many": "Usunąć aplikacje?",
"other": "Usunąć aplikacje?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} min.",
"other": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} min."
"one": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} minutę",
"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": {
"one": "Podczas sprawdzania aktualizacji 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"
"one": "W tle znaleziono {} aktualizację - 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": {
"one": "{} aplik.",
"other": "{} aplik."
"one": "{} ap",
"few": "{} apki",
"many": "{} apek",
"other": "{} apki"
},
"url": {
"one": "{} adres URL",
"other": "{} adr. URL"
"few": "{} adresy URL",
"many": "{} adresów URL",
"other": "{} adresy URL"
},
"minute": {
"one": "{} min.",
"other": "{} min."
"one": "{} minuta",
"few": "{} minuty",
"many": "{} minut",
"other": "{} minuty"
},
"hour": {
"one": "{} godz.",
"other": "{} godz."
"one": "{} godzina",
"few": "{} godziny",
"many": "{} godzin",
"other": "{} godziny"
},
"day": {
"one": "{} dzień",
"few": "{} dni",
"many": "{} dni",
"other": "{} dni"
},
"clearedNLogsBeforeXAfterY": {
"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": {
"one": "{} i jeszcze 1 aplikacja mają aktualizacje.",
"other": "{} i {} aplik. otrzymało aktualizacje."
"one": "{} i 1 inna apka mają aktualizacje.",
"few": "{} i {} inne apki mają aktualizacje.",
"many": "{} i {} innych apek ma aktualizacje.",
"other": "{} i {} inne apki mają aktualizacje."
},
"xAndNMoreUpdatesInstalled": {
"one": "Zaktualizowano {} i jeszcze 1 aplikację.",
"other": "Zaktualizowano {} i {} aplik."
"one": "Zaktualizowano {} i 1 inną apkę.",
"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."
}
}

355
assets/translations/pt.json Normal file
View File

@ -0,0 +1,355 @@
{
"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": "A 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 usando 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": "Com estrela no GitHub",
"uname": "Nome de usuário",
"wrongArgNum": "Número de argumentos errado",
"xIsTrackOnly": "{} é 'Apenas para monitorar'",
"source": "Fonte",
"app": "Aplicativo",
"appsFromSourceAreTrackOnly": "Os aplicativos desta fonte são 'Apenas para monitorar'.",
"youPickedTrackOnly": "Você selecionou a opção 'Apenas para 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 para 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",
"notInstalled": "Não instalado",
"pseudoVersion": "pseudo-versão",
"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 para 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 nos aplicativos selecionados?",
"installStatusOfXWillBeResetExplanation": "O status de instalação de todos os aplicativos selecionados 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.",
"customLinkMessage": "Esses links funcionam em dispositivos com o Obtainium instalado",
"shareAppConfigLinks": "Compartilhar configuração do aplicativo como link HTML",
"shareSelectedAppURLs": "Compartilhar URLs de aplicativos selecionados",
"resetInstallStatus": "Reiniciar status de instalaçã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 atualizados",
"nonInstalledApps": "Aplicativos não instalados",
"importExport": "Importar/Exportar",
"settings": "Configurações",
"exportedTo": "Exportado para {}",
"obtainiumExport": "Exportar Obtainium",
"invalidInput": "Entrada inválida",
"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",
"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 web do aplicativo 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": "exportação de obtainium",
"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 concluída",
"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 para 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": "Oficial F-Droid",
"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 múltiplos aplicativos",
"fdroidThirdPartyRepo": "Repositórios de terceiros F-Droid",
"steamMobile": "Steam para celular",
"steamChat": "Chat do Steam",
"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 categorias",
"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 usando 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)",
"versionDetectionExplanation": "Reconciliar string de versão com versão detectada no sistema operacional",
"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 para monitorar'",
"dontShowAPKOriginWarnings": "Não mostrar avisos de origem da APK",
"moveNonInstalledAppsToBottom": "Mover aplicativos não instalados para o fundo da lista de aplicativos",
"gitlabPATLabel": "Token de acesso pessoal do Gitlab\n(Ativa pesquisa e melhora a descoberta de APKs)",
"about": "Sobre",
"requiresCredentialsInSettings": "{}: Isso requer credenciais adicionais (em Configurações)",
"checkOnStart": "Verificar se há 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 número 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 página",
"reversePageTransitions": "Reverter animações de transição de página",
"minStarCount": "Contagem mínima 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 endereço de download do APK no GitLab provavelmente não funcione sem que seja fornecido uma chave de API.",
"sortByLastLinkSegment": "Ordenar apenas usando o último segmento do link",
"filterReleaseNotesByRegEx": "Filtrar notas de versão usando Regex",
"customLinkFilterRegex": "Filtro de link personalizado usando 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 'última' etiqueta",
"intermediateLinkRegex": "Filtrar um link 'intermediário' para visitar",
"filterByLinkText": "Filtrar links pelo texto do link",
"intermediateLinkNotFound": "Link intermediário não encontrado",
"intermediateLink": "Link intermediário",
"exemptFromBackgroundUpdates": "Isento de atualizações em segundo-plano (se ativadas)",
"bgUpdatesOnWiFiOnly": "Desative as atualizações em segundo-plano quando não estiver conectado no Wi-Fi",
"autoSelectHighestVersionCode": "Auto-selecionar a versão mais recente",
"versionExtractionRegEx": "Regex de extração de versão",
"matchGroupToUse": "Grupo correspondente a ser usado no Regex de extração de versão",
"highlightTouchTargets": "Realçar áreas sensíveis ao toque que são menos óbvias",
"pickExportDir": "Escolher diretório para a exportação",
"autoExportOnChanges": "Auto-exportar em mudanças",
"includeSettings": "Incluir configurações",
"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": "Obter primeiro link",
"skipSort": "Ignorar ordenação",
"debugMenu": "Menu debug",
"bgTaskStarted": "Tarefa em segundo-plano iniciada - verifique os logs.",
"runBgCheckNow": "Execute agora em segundo-plano a verificação de atualizações",
"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 verificar atualizações de aplicativos instalados e 'Apenas monitorar'",
"supportFixedAPKURL": "Suporte a APK com URLs fixas",
"selectX": "Selecionar {}",
"parallelDownloads": "Permitir downloads paralelos",
"installMethod": "Método de instalação",
"normal": "Normal",
"root": "Root",
"shizukuBinderNotFound": "O Shizuku não está rodando",
"useSystemFont": "Usar fonte padrão do sistema",
"systemFontError": "Erro ao carregar a fonte do sistema: {}",
"useVersionCodeAsOSVersion": "Usar versionCode do aplicativo como versão detectada pelo sistema operacional",
"requestHeader": "Requisitar cabeçalho",
"useLatestAssetDateAsReleaseDate": "Use o último upload de recursos como data de lançamento",
"defaultPseudoVersioningMethod": "Método de pseudo-versionamento padrão",
"partialAPKHash": "Hash APK parcial",
"APKLinkHash": "Hash de links APK",
"directAPKLink": "Link direto do APK",
"pseudoVersionInUse": "Uma pseudo-versão está em uso",
"installed": "Instalado",
"latest": "Mais recente",
"invertRegEx": "Inverter expressão regular",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"removeAppQuestion": {
"one": "Remover aplicativo?",
"other": "Remover aplicativos?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Muitas solicitações (taxa de solicitações 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": "Foi limpo {n} log (antes = {antes}, depois = {depois})",
"other": "Foram limpos {n} logs (antes = {antes}, depois = {depois})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} e um outro aplicativo possui atualizações.",
"other": "{} e {} outros aplicativo possuem atualizações."
},
"xAndNMoreUpdatesInstalled": {
"one": "{} e um outro aplicativo foram atualizado.",
"other": "{} e {} outros aplicativos foram atualizados."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} e um outro aplicativo podem ter sido atualizados.",
"other": "{} e {} outros aplicativos podem ter sido atualizados."
}
}

View File

@ -1,5 +1,5 @@
{
"invalidURLForSource": "Неверный URL-адрес {} приложения",
"invalidURLForSource": "Неверный URL-адрес приложения: {}",
"noReleaseFound": "Не удалось найти подходящий релиз",
"noVersionFound": "Не удалось определить версию релиза",
"urlMatchesNoSource": "URL-адрес не соответствует известному источнику",
@ -9,49 +9,41 @@
"placeholder": "Заполнитель",
"someErrors": "Возникли некоторые ошибки",
"unexpectedError": "Неожиданная ошибка",
"ok": "Окей",
"ok": "Ok",
"and": "и",
"startedBgUpdateTask": "Запущена задача фоновой проверки обновлений",
"bgUpdateIgnoreAfterIs": "Параметр игнорирования фоновых обновлений: {}",
"startedActualBGUpdateCheck": "Запущена фактическая проверка фоновых обновлений",
"bgUpdateTaskFinished": "Завершена задача фоновой проверки обновлений",
"firstRun": "Это первый запуск Obtainium",
"settingUpdateCheckIntervalTo": "Установка интервала проверки обновлений: {}",
"githubPATLabel": "Персональный токен доступа GitHub (увеличивает лимит запросов)",
"githubPATHint": "Токен доступа должен быть в формате: имя_пользователя:токен",
"githubPATFormat": "имя_пользователя:токен",
"githubPATLabel": "Персональный токен доступа GitHub\n(увеличивает лимит запросов)",
"includePrereleases": "Включить предварительные релизы",
"fallbackToOlderReleases": "Откатиться к более старым версиям",
"filterReleaseTitlesByRegEx": "Фильтровать заголовки релизов\nс помощью регулярного выражения",
"fallbackToOlderReleases": "Откатываться к предыдущей версии",
"filterReleaseTitlesByRegEx": "Фильтровать заголовки релизов\n(регулярное выражение)",
"invalidRegEx": "Неверное регулярное выражение",
"noDescription": "Нет описания",
"cancel": "Отмена",
"continue": "Продолжить",
"requiredInBrackets": "(Обязательно)",
"dropdownNoOptsError": "Ошибка: Выпадающий список должен содержать хотя бы одну опцию",
"requiredInBrackets": "(обязательно)",
"dropdownNoOptsError": "Ошибка: в выпадающем списке должна быть выбрана хотя бы одна настройка",
"colour": "Цвет",
"githubStarredRepos": "Помеченные звездочкой репозитории на GitHub",
"githubStarredRepos": "Избранные репозитории GitHub",
"uname": "Имя пользователя",
"wrongArgNum": "Неправильное количество предоставленных аргументов",
"xIsTrackOnly": "{} только для отслеживания",
"source": "Источник",
"app": "Приложение",
"appsFromSourceAreTrackOnly": "Приложения из этого источника являются 'только для отслеживания'.",
"youPickedTrackOnly": "Вы выбрали опцию 'Только для отслеживания'.",
"trackOnlyAppDescription": "Приложение будет отслеживаться на предмет обновлений, но Obtainium не сможет загрузить или установить его.",
"appsFromSourceAreTrackOnly": "Приложения из этого источника настроены только для отслеживания",
"youPickedTrackOnly": "Вы выбрали опцию 'Только для отслеживания'",
"trackOnlyAppDescription": "Приложение будет отслеживаться на предмет обновлений, но Obtainium не сможет загрузить или установить его",
"cancelled": "Отменено",
"appAlreadyAdded": "Приложение уже добавлено",
"alreadyUpToDateQuestion": "Приложение уже обновлено?",
"addApp": "Добавить приложение",
"addApp": "Добавить",
"appSourceURL": "URL-источник приложения",
"error": "Ошибка",
"add": "Добавить",
"searchSomeSourcesLabel": "Поиск (только в некоторых источниках)",
"searchSomeSourcesLabel": "Поиск (в некоторых источниках)",
"search": "Поиск",
"additionalOptsFor": "Дополнительные опции для {}",
"supportedSourcesBelow": "Поддерживаемые источники:",
"trackOnlyInBrackets": "(Только для отслеживания)",
"searchableInBrackets": "(Поиск)",
"additionalOptsFor": "Дополнительные настройки для {}",
"supportedSources": "Поддерживаемые источники",
"trackOnlyInBrackets": "(только отслеживание)",
"searchableInBrackets": "(поиск)",
"appsString": "Приложения",
"noApps": "Нет приложений",
"noAppsForFilter": "Нет приложений для фильтра",
@ -59,12 +51,11 @@
"percentProgress": "Прогресс: {}%",
"pleaseWait": "Пожалуйста, подождите",
"updateAvailable": "Доступно обновление",
"estimateInBracketsShort": "(Оценка)",
"notInstalled": "Не установлено",
"estimateInBrackets": "(Оценка)",
"selectAll": "Выбрать все",
"deselectN": "Отменить выбор {}",
"xWillBeRemovedButRemainInstalled": "{} будет удалено из Obtainium, но останется установленным на устройстве.",
"pseudoVersion": "псевдоверсия",
"selectAll": "Выбрать всё",
"deselectX": "Отменить выбор {}",
"xWillBeRemovedButRemainInstalled": "{} будет удалено из Obtainium, но останется на устройстве",
"removeSelectedAppsQuestion": "Удалить выбранные приложения?",
"removeSelectedApps": "Удалить выбранные приложения",
"updateX": "Обновить {}",
@ -73,17 +64,19 @@
"changeX": "Изменить {}",
"installUpdateApps": "Установить/Обновить приложения",
"installUpdateSelectedApps": "Установить/Обновить выбранные приложения",
"markXSelectedAppsAsUpdated": "Отметить {} выбранные приложения как обновленные?",
"markXSelectedAppsAsUpdated": "Выбрано приложений: {}. Отметить как обновлённые?",
"no": "Нет",
"yes": "Да",
"markSelectedAppsUpdated": "Отметить выбранные приложения как обновленные",
"markSelectedAppsUpdated": "Отметить выбранные приложения как обновлённые",
"pinToTop": "Закрепить сверху",
"unpinFromTop": "Открепить",
"resetInstallStatusForSelectedAppsQuestion": "Сбросить статус установки для выбранных приложений?",
"installStatusOfXWillBeResetExplanation": "Статус установки для выбранных приложений будет сброшен.\n\nЭто может помочь, если версия приложения, отображаемая в Obtainium, неправильная из-за неудачных обновлений или других проблем.",
"installStatusOfXWillBeResetExplanation": "Статус установки для выбранных приложений будет сброшен.\n\nЭто может помочь, если версия приложения, отображаемая в Obtainium, некорректная из-за неудачных обновлений или других проблем",
"customLinkMessage": "Эти ссылки работают на устройствах с установленным Obtainium",
"shareAppConfigLinks": "Поделиться конфигурацией приложения в виде HTML-ссылки",
"shareSelectedAppURLs": "Поделиться выбранными URL-адресами приложений",
"resetInstallStatus": "Сбросить статус установки",
"more": "Еще",
"more": "Ещё",
"removeOutdatedFilter": "Удалить фильтр для устаревших приложений",
"showOutdatedOnly": "Показывать только устаревшие приложения",
"filter": "Фильтр",
@ -93,7 +86,7 @@
"author": "Автор",
"upToDateApps": "Приложения со свежими обновлениями",
"nonInstalledApps": "Неустановленные приложения",
"importExport": "Импорт/экспорт",
"importExport": "Данные",
"settings": "Настройки",
"exportedTo": "Экспортировано в {}",
"obtainiumExport": "Экспорт из Obtainium",
@ -107,63 +100,60 @@
"searchX": "Поиск {}",
"noResults": "Результатов не найдено",
"importX": "Импорт {}",
"importedAppsIdDisclaimer": "Импортированные приложения могут неверно отображаться как 'Не установлены'.\nДля исправления этой проблемы повторно установите их через Obtainium.\nЭто не должно повлиять на данные приложения.\n\nПроблемы возникают только при импорте из URL-адреса и сторонних источников.",
"importedAppsIdDisclaimer": "Импортированные приложения могут неверно отображаться как неустановленные.\nДля исправления этой проблемы повторно установите их через Obtainium.\nЭто не должно повлиять на данные приложения.\n\nПроблемы возникают только при импорте из URL-адреса и сторонних источников",
"importErrors": "Ошибка импорта",
"importedXOfYApps": "Импортировано {} из {} приложений.",
"importedXOfYApps": "Импортировано приложений: {} из {}",
"followingURLsHadErrors": "При импорте следующие URL-адреса содержали ошибки:",
"okay": "Окей",
"selectURL": "Выбрать URL-адрес",
"selectURLs": "Выбрать URL-адреса",
"pick": "Выбрать",
"theme": "Тема",
"dark": "Темная",
"dark": "Тёмная",
"light": "Светлая",
"followSystem": "Как в системе",
"obtainium": "Obtainium",
"materialYou": "Material You",
"followSystem": "Системная",
"useBlackTheme": "Использовать чёрную тему",
"appSortBy": "Сортировка приложений по",
"appSortBy": "Сортировка приложений",
"authorName": "Автор/Название",
"nameAuthor": "Название/Автор",
"asAdded": "В порядке добавления",
"appSortOrder": "Порядок сортировки приложений",
"appSortOrder": "Порядок",
"ascending": "По возрастанию",
"descending": "По убыванию",
"bgUpdateCheckInterval": "Интервал проверки обновлений в фоновом режиме",
"neverManualOnly": "Никогда - Только вручную",
"neverManualOnly": "Никогда — только вручную",
"appearance": "Внешний вид",
"showWebInAppView": "Показывать исходную веб-страницу в представлении приложения",
"pinUpdates": "Закрепить обновления сверху списка приложений",
"showWebInAppView": "Показывать исходную веб-страницу на странице приложения",
"pinUpdates": "Отображать обновления приложений сверху списка",
"updates": "Обновления",
"sourceSpecific": "Специфика источника",
"appSource": "Источник приложения",
"sourceSpecific": "Настройки источников",
"appSource": "Исходный код",
"noLogs": "Нет журналов",
"appLogs": "Журналы приложений",
"appLogs": "Логи",
"close": "Закрыть",
"share": "Поделиться",
"appNotFound": "Приложение не найдено",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"obtainiumExportHyphenatedLowercase": "получение-экспорт",
"pickAnAPK": "Выберите APK-файл",
"appHasMoreThanOnePackage": "{} имеет более одного пакета:",
"deviceSupportsXArch": "Ваше устройство поддерживает архитектуру процессора {}.",
"deviceSupportsXArch": "Ваше устройство поддерживает архитектуру процессора {}",
"deviceSupportsFollowingArchs": "Ваше устройство поддерживает следующие архитектуры процессора:",
"warning": "Предупреждение",
"sourceIsXButPackageFromYPrompt": "Источник приложения - '{}', но пакет для установки получен из '{}'. Продолжить?",
"sourceIsXButPackageFromYPrompt": "Источник приложения '{}', но пакет для установки получен из '{}'. Продолжить?",
"updatesAvailable": "Доступны обновления",
"updatesAvailableNotifDescription": "Уведомляет пользователя о наличии обновлений для одного или нескольких приложений, отслеживаемых Obtainium",
"noNewUpdates": "Нет новых обновлений.",
"xHasAnUpdate": "{} есть обновление.",
"updatesAvailableNotifDescription": "Уведомляет о наличии обновлений для одного или нескольких приложений в Obtainium",
"noNewUpdates": "Нет новых обновлений",
"xHasAnUpdate": "{} есть обновление",
"appsUpdated": "Приложения обновлены",
"appsUpdatedNotifDescription": "Уведомляет пользователя о том, что обновления для одного или нескольких приложений были применены в фоновом режиме",
"xWasUpdatedToY": "{} была обновлена до версии {}.",
"appsUpdatedNotifDescription": "Уведомляет об обновлении одного или нескольких приложений в фоновом режиме",
"xWasUpdatedToY": "{} была обновлена до версии {}",
"errorCheckingUpdates": "Ошибка при проверке обновлений",
"errorCheckingUpdatesNotifDescription": "Уведомление, которое появляется, когда проверка обновлений в фоновом режиме завершилась с ошибкой",
"errorCheckingUpdatesNotifDescription": "Уведомление о завершении проверки обновлений в фоновом режиме с ошибкой",
"appsRemoved": "Приложение удалено",
"appsRemovedNotifDescription": "Уведомляет пользователя о том, что одно или несколько приложений было удалено из-за ошибок при их загрузке",
"appsRemovedNotifDescription": "Уведомление об удалении одного или несколько приложений из-за ошибок при их загрузке",
"xWasRemovedDueToErrorY": "{} был удален из-за ошибки: {}",
"completeAppInstallation": "Завершение установки приложения",
"obtainiumMustBeOpenToInstallApps": "Для установки приложений Obtainium должен быть открыт",
"completeAppInstallationNotifDescription": "Просит пользователя вернуться в Obtainium, чтобы завершить установку приложения",
"obtainiumMustBeOpenToInstallApps": "Obtainium должен быть открыт для установки приложений",
"completeAppInstallationNotifDescription": "Уведомление о необходимости открыть Obtainium для завершения установки приложения",
"checkingForUpdates": "Проверка обновлений",
"checkingForUpdatesNotifDescription": "Временное уведомление, которое появляется при проверке обновлений",
"pleaseAllowInstallPerm": "Пожалуйста, разрешите Obtainium устанавливать приложения",
@ -175,123 +165,191 @@
"never": "Никогда",
"latestVersionX": "Последняя версия: {}",
"installedVersionX": "Установленная версия: {}",
"lastUpdateCheckX": "Последняя проверка обновлений: {}",
"lastUpdateCheckX": "Последняя проверка: {}",
"remove": "Удалить",
"yesMarkUpdated": "Да, отметить как обновленное",
"fdroid": "Официальный F-Droid",
"fdroid": "Официальные репозитории F-Droid",
"appIdOrName": "ID или название приложения",
"appId": "ID приложения",
"appWithIdOrNameNotFound": "Приложение с таким ID или названием не было найдено",
"reposHaveMultipleApps": "В хранилище может быть несколько приложений",
"reposHaveMultipleApps": "В хранилище несколько приложений",
"fdroidThirdPartyRepo": "Сторонние репозитории F-Droid",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "Стим Мобайл",
"steamChat": "Стим-чат",
"install": "Установить",
"markInstalled": "Пометить как установленное",
"update": "Обновить",
"markUpdated": "Отметить обновленным",
"additionalOptions": "Дополнительные опции",
"markUpdated": "Отметить обновлённым",
"additionalOptions": "Дополнительные настройки",
"disableVersionDetection": "Отключить обнаружение версии",
"noVersionDetectionExplanation": "Эта опция должна использоваться только для приложений, где обнаружение версии не работает корректно.",
"noVersionDetectionExplanation": "Эта настройка должна использоваться только для приложений, где обнаружение версии не работает корректно",
"downloadingX": "Загрузка {}",
"downloadNotifDescription": "Уведомляет пользователя о прогрессе загрузки приложения",
"noAPKFound": "APK не найден",
"noVersionDetection": "Версий не обнаружено",
"noVersionDetection": "Обнаружение версий отключено",
"categorize": "Категоризировать",
"categories": "Категории",
"category": "Категория",
"noCategory": "Без категории",
"noCategories": "Без категорий",
"deleteCategoriesQuestion": "Удалить категории?",
"categoryDeleteWarning": "Все приложения в удаленных категориях будут помечены как без категории.",
"categoryDeleteWarning": "Все приложения в удаленных категориях будут помечены как без категории",
"addCategory": "Добавить категорию",
"label": "Метка",
"language": "Язык",
"copiedToClipboard": "Скопировано в буфер обмена",
"storagePermissionDenied": "Отказано в доступе к хранилищу",
"selectedCategorizeWarning": "Это заменит все текущие настройки категорий для выбранных приложений.",
"filterAPKsByRegEx": "Фильтровать APK-файлы с помощью\nрегулярного выражения",
"selectedCategorizeWarning": "Это заменит все текущие настройки категорий для выбранных приложений",
"filterAPKsByRegEx": "Отфильтровать APK-файлы\n(регулярное выражение)",
"removeFromObtainium": "Удалить из Obtainium",
"uninstallFromDevice": "Удалить с устройства",
"onlyWorksWithNonVersionDetectApps": "Работает только для приложений с отключенным определением версии.",
"releaseDateAsVersion": "Использовать дату выпуска в качестве версии",
"releaseDateAsVersionExplanation": "Этот параметр следует использовать только для приложений, в которых определение версии не работает правильно, но имеется дата выпуска.",
"onlyWorksWithNonVersionDetectApps": "Работает только для приложений с отключенным определением версии",
"releaseDateAsVersion": "Дата выпуска вместо версии",
"releaseDateAsVersionExplanation": "Этот параметр следует использовать только для приложений, в которых определение версии не работает правильно, но имеется дата выпуска",
"changes": "Изменения",
"releaseDate": "Дата выпуска",
"importFromURLsInFile": "Импорт URL-адресов из файла (например, OPML)",
"importFromURLsInFile": "Импорт из файла URL-адресов (например: OPML)",
"versionDetectionExplanation": "Согласовать строку версии с версией, обнаруженной в ОС",
"versionDetection": "Определение версии",
"standardVersionDetection": "Стандартное определение версии",
"standardVersionDetection": "Стандартное",
"groupByCategory": "Группировать по категориям",
"autoApkFilterByArch": "Попытка фильтрации APK-файлов по архитектуре процессора, если это возможно",
"autoApkFilterByArch": "Попытаться отфильтровать APK-файлы по архитектуре процессора",
"overrideSource": "Переопределить источник",
"dontShowAgain": "Не показывать снова",
"dontShowTrackOnlyWarnings": "Не показывать предупреждения о только отслеживаемых приложениях",
"dontShowAPKOriginWarnings": "Не показывать предупреждения об источнике APK-файлов",
"moveNonInstalledAppsToBottom": "Переместить неустановленные приложения вниз списка",
"gitlabPATLabel": "Персональный токен доступа GitLab\n(Включает поиск и улучшает обнаружение APK)",
"about": "О приложении",
"requiresCredentialsInSettings": "Для этого требуются дополнительные учетные данные (в настройках)",
"dontShowAPKOriginWarnings": "Не показывать предупреждения об отличающемся источнике APK-файлов",
"moveNonInstalledAppsToBottom": "Отображать неустановленные приложения внизу списка",
"gitlabPATLabel": "Персональный токен доступа GitLab\n(включает поиск и улучшает обнаружение APK)",
"about": "Описание",
"requiresCredentialsInSettings": "{}: Для этого требуются дополнительные учетные данные (в настройках)",
"checkOnStart": "Проверять наличие обновлений при запуске",
"tryInferAppIdFromCode": "Попытаться определить ID приложения из исходного кода",
"removeOnExternalUninstall": "Автоматически убирать из списка удаленные извне приложения",
"pickHighestVersionCode": "Автовыбор кода наивысшей версии APK",
"checkUpdateOnDetailPage": "Проверять наличие обновлений при открытии страницы представления приложения",
"pickHighestVersionCode": "Автовыбор актуальной версии кода APK",
"checkUpdateOnDetailPage": "Проверять наличие обновлений при открытии страницы приложения",
"disablePageTransitions": "Отключить анимацию перехода между страницами",
"reversePageTransitions": "Реверс анимации перехода между страницами",
"minStarCount": "Минимальное количество звёзд",
"addInfoBelow": "Добавьте эту информацию ниже.",
"addInfoInSettings": "Добавьте эту информацию в Настройки.",
"githubSourceNote": "Лимит запросов GitHub можно обойти, используя ключ API.",
"gitlabSourceNote": "Извлечение APK из GitLab может не работать без ключа API.",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"addInfoBelow": "Добавьте эту информацию ниже",
"addInfoInSettings": "Добавьте эту информацию в Настройки",
"githubSourceNote": "Используя ключ API можно обойти лимит запросов GitHub",
"gitlabSourceNote": "Без ключа API может не работать извлечение APK с GitLab",
"sortByLastLinkSegment": "Сортировать только по последнему сегменту ссылки",
"filterReleaseNotesByRegEx": "Фильтровать примечания к выпуску\n(регулярное выражение)",
"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": "Поддержка фиксированных URL-адресов APK",
"selectX": "Выбрать {}",
"parallelDownloads": "Разрешить параллельные загрузки",
"installMethod": "Метод установки",
"normal": "Нормальный",
"root": "Суперпользователь",
"shizukuBinderNotFound": "Совместимый сервис Shizuku не найден",
"useSystemFont": "Использовать системный шрифт",
"systemFontError": "Ошибка загрузки системного шрифта: {}",
"useVersionCodeAsOSVersion": "Использовать код версии приложения как версию, обнаруженную ОС",
"requestHeader": "Заголовок запроса",
"useLatestAssetDateAsReleaseDate": "Использовать последнюю загрузку ресурса в качестве даты выпуска",
"defaultPseudoVersioningMethod": "Метод псевдоверсионирования по умолчанию",
"partialAPKHash": "Частичный хэш APK",
"APKLinkHash": "Хеш ссылки APK",
"directAPKLink": "Прямая ссылка на APK",
"pseudoVersionInUse": "Используется псевдоверсия",
"installed": "Установлен",
"latest": "Последний",
"invertRegEx": "Инвертировать регулярное выражение",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"removeAppQuestion": {
"one": "Удалить приложение?",
"other": "Удалить приложения?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Слишком много запросов (ограничение скорости) - попробуйте снова через {} минуту",
"other": "Слишком много запросов (ограничение скорости) - попробуйте снова через {} минуты"
"one": "Слишком много запросов (ограничение скорости) попробуйте снова через {} минуту",
"other": "Слишком много запросов (ограничение скорости) попробуйте снова через {} минуты"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "При проверке обновлений в фоновом режиме возникла ошибка {}, повторная проверка будет запланирована через {} минуту",
"other": "При проверке обновлений в фоновом режиме возникла ошибка {}, повторная проверка будет запланирована через {} минуты"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "В ходе проверки обновления в фоновом режиме было обнаружено {} обновление - Пользователю будет отправлено уведомление, если это необходимо",
"other": "В ходе проверки обновления в фоновом режиме было обнаружено {} обновлений - Пользователю будет отправлено уведомление, если это необходимо"
"one": "В ходе проверки обновления в фоновом режиме было обнаружено {} обновление Пользователю будет отправлено уведомление, если это необходимо",
"other": "В ходе проверки обновления в фоновом режиме было обнаружено {} обновлений Пользователю будет отправлено уведомление, если это необходимо"
},
"apps": {
"one": "{} Приложение",
"other": "{} Приложений"
"one": "{} приложение",
"other": "{} приложений"
},
"url": {
"one": "{} URL-адрес",
"other": "{} URL-адреса"
},
"minute": {
"one": "{} Минута",
"other": "{} Минуты"
"one": "{} минута",
"other": "{} минуты"
},
"hour": {
"one": "{} Час",
"other": "{} Часов"
"one": "{} час",
"other": "{} часов"
},
"day": {
"one": "{} День",
"other": "{} Дней"
"one": "{} день",
"other": "{} дней"
},
"clearedNLogsBeforeXAfterY": {
"one": "Очищен {n} журнал (до = {before}, после = {after})",
"other": "Очищено {n} журналов (до = {before}, после = {after})"
},
"xAndNMoreUpdatesAvailable": {
"one": "У {} и еще 1 приложения есть обновление.",
"other": "У {} и ещё {} приложений есть обновления."
"one": "У {} и еще 1 приложения есть обновление",
"other": "У {} и ещё {} приложений есть обновления"
},
"xAndNMoreUpdatesInstalled": {
"one": "{} и еще 1 приложение были обновлены.",
"other": "{} и еще {} приложений были обновлены."
"one": "{} и ещё 1 приложение были обновлены",
"other": "{} и ещё {} приложений были обновлены"
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} и ещё 1 приложение могли быть обновлены",
"other": "{} и ещё {} приложений могли быть обновлены"
}
}

View File

@ -0,0 +1,30 @@
// Take one (hardcoded) translation file and ensure that all other translation files have the same keys in the same order
// Then report which other translation files have identical items
const fs = require('fs')
const translationsDir = __dirname
const templateFile = `${translationsDir}/en.json`
const otherFiles = fs.readdirSync(translationsDir).map(f => {
return `${translationsDir}/${f}`
}).filter(f => f.endsWith('.json') && f != templateFile)
const templateTranslation = require(templateFile)
otherFiles.forEach(file => {
const thisTranslationOriginal = require(file)
const thisTranslationNew = {}
Object.keys(templateTranslation).forEach(k => {
thisTranslationNew[k] = thisTranslationOriginal[k] || templateTranslation[k]
})
fs.writeFileSync(file, `${JSON.stringify(thisTranslationNew, null, ' ')}\n`)
});
otherFiles.forEach(file => {
const thisTranslation = require(file)
Object.keys(templateTranslation).forEach(k => {
if (JSON.stringify(thisTranslation[k]) == JSON.stringify(templateTranslation[k])) {
console.log(`${file} :::: ${k} :::: ${JSON.stringify(thisTranslation[k])}`)
}
})
});

355
assets/translations/sv.json Normal file
View File

@ -0,0 +1,355 @@
{
"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": "Den här klassen har inte implementerat den här funktionen",
"placeholder": "Platshållare",
"someErrors": "Några fel uppstod",
"unexpectedError": "Oväntat fel",
"ok": "Okej",
"and": "och",
"githubPATLabel": "GitHub Personal Access Token (ökar hastighetsgräns)",
"includePrereleases": "Inkludera förreleaser",
"fallbackToOlderReleases": "Fall tillbaka till äldre releaser",
"filterReleaseTitlesByRegEx": "Filtrera utgivningstitlar efter reguljärt uttryck",
"invalidRegEx": "Ogiltigt reguljärt uttryck",
"noDescription": "Ingen beskrivning",
"cancel": "Avbryt",
"continue": "Fortsätt",
"requiredInBrackets": "(Kräver)",
"dropdownNoOptsError": "FEL: DROPDOWN MÅSTE HA MINST ETT OPT",
"colour": "Färg",
"githubStarredRepos": "GitHub Stjärnmärkta Förråd",
"uname": "Användarnamn",
"wrongArgNum": "Fel antal argument har angetts",
"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": "Framsteg: {}%",
"pleaseWait": "Vänta",
"updateAvailable": "Uppdatering Tillgänglig",
"notInstalled": "Inte Installerad",
"pseudoVersion": "pseudoversion",
"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.",
"customLinkMessage": "Dessa länkar fungerar på enheter med Obtainium installerat",
"shareAppConfigLinks": "Dela appkonfiguration som HTML-länk",
"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": "Filtrera",
"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-lista",
"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",
"useBlackTheme": "Använd svart tema",
"appSortBy": "Sortera Appar via",
"authorName": "Utvecklare/Namn",
"nameAuthor": "Namn/Utvecklare",
"asAdded": "Som tillagd",
"appSortOrder": "Appsorteringsordning",
"ascending": "Stigande",
"descending": "Fallande",
"bgUpdateCheckInterval": "Bakgrundsuppdateringskollfrekvens",
"neverManualOnly": "Aldrig - Endast manuell",
"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": "Meddelar användaren att uppdateringar av en eller flera appar har tillämpats i bakgrunden",
"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": "Övergående meddelande som visas när du söker efter uppdateringar",
"pleaseAllowInstallPerm": "Tillåt Obtanium att installera Appar",
"trackOnly": "Följ-Endast",
"errorWithHttpStatusCode": "Fel {}",
"versionCorrectionDisabled": "Versionskorrigering inaktiverat (plugin verkar inte fungera)",
"unknown": "Okänd",
"none": "Ingen",
"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",
"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": "Det här alternativet bör endast användas för appar där versionsidentifiering inte fungerar korrekt.",
"downloadingX": "Laddar ner {}",
"downloadNotifDescription": "Meddelar användaren om framstegen med att ladda ner en 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": "Märka",
"language": "Språk",
"copiedToClipboard": "Kopierat till Urklipp",
"storagePermissionDenied": "Lagringsbehörighet nekad",
"selectedCategorizeWarning": "Detta kommer att ersätta alla befintliga kategoriinställningar för de valda apparna.",
"filterAPKsByRegEx": "Filtrera APK-filer efter reguljärt uttryck",
"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": "Det här alternativet bör endast användas för appar där versionsidentifiering inte fungerar korrekt, men ett releasedatum är tillgängligt.",
"changes": "Ändringar",
"releaseDate": "Releasedatum",
"importFromURLsInFile": "Importera från URL:er i fil (som OPML)",
"versionDetectionExplanation": "Sammanställ versionssträng med version upptäckt från OS",
"versionDetection": "Versionsdetektering",
"standardVersionDetection": "Standardversionsdetektering",
"groupByCategory": "Gruppera via Kategori",
"autoApkFilterByArch": "Försök att filtrera APK-filer efter CPU-arkitektur om möjligt",
"overrideSource": "Överskrid Källa",
"dontShowAgain": "Visa inte detta igen",
"dontShowTrackOnlyWarnings": "Visa inte 'Följ-Endast' varningar",
"dontShowAPKOriginWarnings": "Visa inte APK-ursprung varningar",
"moveNonInstalledAppsToBottom": "Flytta icke-installerade appar till botten av appvyn",
"gitlabPATLabel": "GitLab Personal Access Token\\n(Möjliggör sökning och bättre APK-upptäckt)",
"about": "Om",
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
"checkOnStart": "Kolla efter uppdateringar vid start",
"tryInferAppIdFromCode": "Försök härleda app-id från källkoden",
"removeOnExternalUninstall": "Ta bort externt avinstallerade appar automatiskt",
"pickHighestVersionCode": "Välj automatiskt högsta versionskod APK",
"checkUpdateOnDetailPage": "Sök efter uppdateringar om att öppna en app-detaljsida",
"disablePageTransitions": "Inaktivera sidövergångsanimationer",
"reversePageTransitions": "Animeringar för omvänd sidövergång",
"minStarCount": "Minsta antal stjärnmarkeringar",
"addInfoBelow": "Lägg till denna information nedanför.",
"addInfoInSettings": "Lägg till denna information i Inställningar.",
"githubSourceNote": "GitHub-hastighetsbegränsning kan undvikas med en API-nyckel.",
"gitlabSourceNote": "GitLab APK-extraktion kanske inte fungerar utan en API-nyckel.",
"sortByLastLinkSegment": "Sortera endast efter det sista segmentet av länken",
"filterReleaseNotesByRegEx": "Filtrera versionskommentarer efter reguljärt uttryck",
"customLinkFilterRegex": "Anpassad APK-länkfiltrera efter reguljärt uttryck (standard '.apk$')",
"appsPossiblyUpdated": "Appuppdateringar försökte",
"appsPossiblyUpdatedNotifDescription": "Meddelar användaren om att uppdateringar av en eller flera appar potentiellt har tillämpats i bakgrunden",
"xWasPossiblyUpdatedToY": "{} kan ha uppdaterats till {}.",
"enableBackgroundUpdates": "Aktivera Bakgrundsuppdateringar",
"backgroundUpdateReqsExplanation": "Bakgrundsuppdateringar är inte möjligt för alla appar.",
"backgroundUpdateLimitsExplanation": "Framgången för en bakgrundsinstallation kan bara avgöras när Obtainium öppnas.",
"verifyLatestTag": "Verifiera 'senaste'-taggen",
"intermediateLinkRegex": "Filtrera för en \"mellanliggande\" länk att besöka",
"filterByLinkText": "Filtrera länkar efter länktext",
"intermediateLinkNotFound": "Mellanlänk hittades inte",
"intermediateLink": "Mellanlänk",
"exemptFromBackgroundUpdates": "Undta från bakgrundsuppdateringar (om aktiverad)",
"bgUpdatesOnWiFiOnly": "Inaktivera Bakgrundsuppdateringar utan WiFi",
"autoSelectHighestVersionCode": "Välj automatiskt högsta versionskod APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Markera mindre uppenbara beröringsobjekt",
"pickExportDir": "Välj Exportsökväg",
"autoExportOnChanges": "Automatisk export vid ändringar",
"includeSettings": "Inkludera inställningar",
"filterVersionsByRegEx": "Filtrera versioner efter reguljärt uttryck",
"trySelectingSuggestedVersionCode": "Försök att välja föreslagen versionCode APK",
"dontSortReleasesList": "Behåll släpporder från API",
"reverseSort": "Omvänd sortering",
"takeFirstLink": "Ta första länken",
"skipSort": "Hoppa över sortering",
"debugMenu": "Felsökningsmeny",
"bgTaskStarted": "Bakgrundsuppgift startat - kontrollera loggar.",
"runBgCheckNow": "Kör Bakgrundsuppdateringskoll Nu",
"versionExtractWholePage": "Tillämpa Regex för versionssträngextraktion på hela sidan",
"installing": "Installerar",
"skipUpdateNotifications": "Hoppa över uppdateringsmeddelanden",
"updatesAvailableNotifChannel": "Uppdateringar tillgängliga",
"appsUpdatedNotifChannel": "Appar uppdaterade",
"appsPossiblyUpdatedNotifChannel": "Appuppdateringar försökte",
"errorCheckingUpdatesNotifChannel": "Fel vid sökning efter uppdateringar",
"appsRemovedNotifChannel": "Appar borttagna",
"downloadingXNotifChannel": "Laddar ner {}",
"completeAppInstallationNotifChannel": "Fullständig appinstallation",
"checkingForUpdatesNotifChannel": "Kollar efter uppdateringar",
"onlyCheckInstalledOrTrackOnlyApps": "Kontrollera bara installerade appar och endast spårbara appar för uppdateringar",
"supportFixedAPKURL": "Stöd fasta APK-webbadresser",
"selectX": "Välj {}",
"parallelDownloads": "Tillåt parallella nedladdningar",
"installMethod": "Installationsmetod",
"normal": "Vanligt",
"root": "Rot",
"shizukuBinderNotFound": "Shizuku is not running",
"useSystemFont": "Använd systemteckensnittet",
"systemFontError": "Fel vid laddning av systemteckensnittet: {}",
"useVersionCodeAsOSVersion": "Använd appversionskoden som OS-upptäckt version",
"requestHeader": "Request header",
"useLatestAssetDateAsReleaseDate": "Använd senaste tillgångsuppladdning som releasedatum",
"defaultPseudoVersioningMethod": "Standard pseudoversionsmetod",
"partialAPKHash": "Delvis APK-hash",
"APKLinkHash": "APK Link Hash",
"directAPKLink": "Direkt APK-länk",
"pseudoVersionInUse": "En pseudoversion används",
"installed": "Installerad",
"latest": "Senast",
"invertRegEx": "Invertera reguljärt uttryck",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"removeAppQuestion": {
"one": "Ta Bort App?",
"other": "Ta Bort Appar?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "För många förfrågningar (begränsat pris) - försök igen om {} minut",
"other": "För många förfrågningar (begränsat pris) - försök igen om {} minuter"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "BG-uppdateringskontroll påträffade en {}, kommer att schemalägga ett nytt försök om {} minut",
"other": "BG-uppdateringskontroll påträffade en {}, kommer att schemalägga ett nytt försök om {} minuter"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "BG-uppdateringskontroll hittades {} uppdatering - kommer att meddela användaren om det behövs",
"other": "BG-uppdateringskontroll hittades {} uppdateringar - kommer att meddela användaren om det behövs"
},
"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."
}
}

355
assets/translations/tr.json Normal file
View File

@ -0,0 +1,355 @@
{
"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",
"notInstalled": "Yüklenmedi",
"pseudoVersion": "sözde versiyon",
"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.",
"customLinkMessage": "Bu bağlantılar Obtainium'un kurulu olduğu cihazlarda çalışır",
"shareAppConfigLinks": "Uygulama yapılandırmasını HTML bağlantısı olarak paylaş",
"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",
"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",
"steamMobile": "Buhar Mobil",
"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",
"versionDetectionExplanation": "Sürüm dizesini işletim sistemi tarafından algılanan sürümle uzlaştır",
"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": "Bağlantının yalnızca son bölümüne göre sırala",
"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": "Ziyaret Edilecek 'Orta Düzey' Bağlantıyı Filtrele",
"filterByLinkText": "Bağlantıları bağlantı metnine göre filtrele",
"intermediateLinkNotFound": "Ara bağlantı bulunamadı",
"intermediateLink": "Ara bağlantı",
"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": "Ayarları dahil et",
"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": "İlk bağlantıyı al",
"skipSort": "Sıralamayı atla",
"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": "Sabit APK URL'lerini destekleyin",
"selectX": "Seçme {}",
"parallelDownloads": "Paralel indirmelere izin ver",
"installMethod": "Kurulum yöntemi",
"normal": "Normal",
"root": "Kök",
"shizukuBinderNotFound": "Shizuku is not running",
"useSystemFont": "Sistem yazı tipini kullan",
"systemFontError": "Sistem yazı tipi yüklenirken hata oluştu: {}",
"useVersionCodeAsOSVersion": "Uygulama versionCode'unu işletim sistemi tarafından algılanan sürüm olarak kullan",
"requestHeader": "Başlık talep et",
"useLatestAssetDateAsReleaseDate": "Yayın tarihi olarak en son öğe yüklemesini kullan",
"defaultPseudoVersioningMethod": "Varsayılan Sözde Sürüm Oluşturma Yöntemi",
"partialAPKHash": "Kısmi APK Karması",
"APKLinkHash": "APK Bağlantı Karması",
"directAPKLink": "Doğrudan APK Bağlantısı",
"pseudoVersionInUse": "Sözde Versiyon Kullanımda",
"installed": "Kurulmuş",
"latest": "En sonuncu",
"invertRegEx": "Normal ifadeyi ters çevir",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"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."
}
}

355
assets/translations/vi.json Normal file
View File

@ -0,0 +1,355 @@
{
"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": "GitHub Token (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",
"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": "Đang tải {}%",
"pleaseWait": "Vui lòng chờ",
"updateAvailable": "Có sẵn bản cập nhật",
"notInstalled": "Chưa cài đặt",
"pseudoVersion": "phiên bản giả",
"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.",
"customLinkMessage": "Các liên kết này hoạt động trên các thiết bị có cài đặt Gainium",
"shareAppConfigLinks": "Chia sẻ cấu hình ứng dụng dưới dạng liên kết HTML",
"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",
"invalidInput": "Đầu vào không hợp lệ",
"importedX": "Đã nhập {}",
"obtainiumImport": "Nhập",
"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",
"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": "Thời gian tự động kiểm tra cập nhật",
"neverManualOnly": "Không bao giờ",
"appearance": "Hiển thị",
"showWebInAppView": "Hiển thị trang web Nguồn trong chế độ xem chi tiết Ứng dụng",
"pinUpdates": "Chuyển ứng dụng có phiên bản mới lên đầu danh sách",
"updates": "Cập nhật",
"sourceSpecific": "Cài đặt Nguồn",
"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",
"steamMobile": "Điện thoại di động hơi nước",
"steamChat": "Trò chuyện hơi nước",
"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)",
"versionDetectionExplanation": "Đối chiếu chuỗi phiên bản với phiên bản được phát hiện từ hệ điều hành",
"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": "Chuyển Ứng dụng chưa được cài đặt xuống cuối danh sách",
"gitlabPATLabel": "GitLab Token\n(Cho phép tìm kiếm và lọc 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": "Chỉ sắp xếp theo đoạn cuối của liên kết",
"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": "Tự động cập nhật trong nền",
"backgroundUpdateReqsExplanation": "Có thể không thực hiện được cập nhật trong nền cho tất cả ứng dụng.",
"backgroundUpdateLimitsExplanation": "Sự thành công của cài đặt trong nền chỉ có thể được xác định khi mở Obtainium.",
"verifyLatestTag": "Xác minh thẻ 'mới nhất'",
"intermediateLinkRegex": "Lọc tìm liên kết 'Trung cấp' để truy cập",
"filterByLinkText": "Lọc liên kết theo văn bản liên kết",
"intermediateLinkNotFound": "Không tìm thấy liên kết trung gian",
"intermediateLink": "Liên kết trung gian",
"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",
"includeSettings": "Bao gồm cài đặt ứng dụng",
"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": "Lấy liên kết đầu tiên",
"skipSort": "Bỏ qua việc sắp xếp",
"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ập nhật các ứng dụng đã cài đặt và Chỉ theo dõi",
"supportFixedAPKURL": "Hỗ trợ URL APK cố định",
"selectX": "Lựa chọn {}",
"parallelDownloads": "Cho phép tải đa luồng",
"installMethod": "Phương thức cài đặt",
"normal": "Mặc định",
"root": "Root",
"shizukuBinderNotFound": "Shizuku chưa khởi động",
"useSystemFont": "Sử dụng phông chữ hệ thống",
"systemFontError": "Lỗi tải phông chữ hệ thống: {}",
"useVersionCodeAsOSVersion": "Sử dụng Mã phiên bản ứng dụng làm phiên bản do hệ điều hành phát hiện",
"requestHeader": "Tiêu đề yêu cầu",
"useLatestAssetDateAsReleaseDate": "Sử dụng nội dung tải lên mới nhất làm ngày phát hành",
"defaultPseudoVersioningMethod": "Phương pháp tạo phiên bản giả mặc định",
"partialAPKHash": "Băm APK một phần",
"APKLinkHash": "Băm liên kết APK",
"directAPKLink": "Liên kết APK trực tiếp",
"pseudoVersionInUse": "Phiên bản giả đang được sử dụng",
"installed": "Đã cài đặt",
"latest": "Mới nhất",
"invertRegEx": "Đảo ngược biểu thức chính quy",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"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."
}
}

View File

@ -11,18 +11,10 @@
"unexpectedError": "意外错误",
"ok": "好的",
"and": "和",
"startedBgUpdateTask": "后台更新检查任务已启动",
"bgUpdateIgnoreAfterIs": "后台更新检查间隔为 {}",
"startedActualBGUpdateCheck": "开始后台更新检查",
"bgUpdateTaskFinished": "后台更新检查任务已完成",
"firstRun": "这是 Obtainium 首次启动",
"settingUpdateCheckIntervalTo": "更新检查间隔设置为 {}",
"githubPATLabel": "GitHub 个人访问令牌(提升 API 请求限额)",
"githubPATHint": "个人访问令牌必须为“username:token”的格式",
"githubPATFormat": "username:token",
"includePrereleases": "包含预发行版",
"fallbackToOlderReleases": "将旧发行版作为备选",
"filterReleaseTitlesByRegEx": "使用正则表达式筛选发行标题",
"filterReleaseTitlesByRegEx": "筛选发行标题(正则表达式)",
"invalidRegEx": "无效的正则表达式",
"noDescription": "无描述",
"cancel": "取消",
@ -30,11 +22,11 @@
"requiredInBrackets": "(必填)",
"dropdownNoOptsError": "错误:下拉菜单必须包含至少一个选项",
"colour": "配色",
"githubStarredRepos": "GitHub 已星标仓库",
"githubStarredRepos": "已星标的 GitHub 仓库",
"uname": "用户名",
"wrongArgNum": "参数数量错误",
"xIsTrackOnly": "{}为“仅追踪”模式",
"source": "源代码",
"xIsTrackOnly": "{}为“仅追踪”模式",
"source": "源",
"app": "应用",
"appsFromSourceAreTrackOnly": "此来源的应用为“仅追踪”模式。",
"youPickedTrackOnly": "您选择了“仅追踪”。",
@ -46,10 +38,10 @@
"appSourceURL": "来源 URL",
"error": "错误",
"add": "添加",
"searchSomeSourcesLabel": "搜索(仅部分来源)",
"searchSomeSourcesLabel": "搜索(仅支持部分来源)",
"search": "搜索",
"additionalOptsFor": "{} 的更多选项",
"supportedSourcesBelow": "支持的来源",
"supportedSources": "支持的来源",
"trackOnlyInBrackets": "(仅追踪)",
"searchableInBrackets": "(可搜索)",
"appsString": "应用列表",
@ -59,17 +51,16 @@
"percentProgress": "进度:{}%",
"pleaseWait": "请稍候",
"updateAvailable": "更新可用",
"estimateInBracketsShort": "(推测)",
"notInstalled": "未安装",
"estimateInBrackets": "(推测)",
"pseudoVersion": "伪版本",
"selectAll": "全选",
"deselectN": "取消选择 {}",
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。",
"deselectX": "取消选择 {}",
"xWillBeRemovedButRemainInstalled": "{}将从 Obtainium 中删除,但仍安装在您的设备中。",
"removeSelectedAppsQuestion": "是否删除选中的应用?",
"removeSelectedApps": "删除选中的应用",
"updateX": "更新 {}",
"installX": "安装 {}",
"markXTrackOnlyAsUpdated": "将 {}\n仅追踪\n标记为已更新",
"markXTrackOnlyAsUpdated": "将{}\n仅追踪\n标记为已更新",
"changeX": "更改 {}",
"installUpdateApps": "安装/更新应用",
"installUpdateSelectedApps": "安装/更新选中的应用",
@ -81,6 +72,8 @@
"unpinFromTop": "取消置顶",
"resetInstallStatusForSelectedAppsQuestion": "是否重置选中应用的安装状态?",
"installStatusOfXWillBeResetExplanation": "选中应用的安装状态将会被重置。\n\n当更新安装失败或其他问题导致 Obtainium 中的应用版本显示错误时,可以尝试通过此方法解决。",
"customLinkMessage": "这些链接适用于安装了 Gettingium 的设备",
"shareAppConfigLinks": "将应用程序配置共享为 HTML 链接",
"shareSelectedAppURLs": "分享选中应用的 URL",
"resetInstallStatus": "重置安装状态",
"more": "更多",
@ -96,22 +89,21 @@
"importExport": "导入/导出",
"settings": "设置",
"exportedTo": "已导出至 {}",
"obtainiumExport": "Obtainium 导出",
"obtainiumExport": "导出 Obtainium",
"invalidInput": "无效的输入",
"importedX": "已导入 {}",
"obtainiumImport": "Obtainium 导入",
"obtainiumImport": "导入 Obtainium",
"importFromURLList": "从 URL 列表导入",
"searchQuery": "搜索查询",
"appURLList": "应用 URL 列表",
"line": "行",
"searchX": "搜索 {}",
"searchX": "搜索{}",
"noResults": "无结果",
"importX": "导入 {}",
"importX": "导入{}",
"importedAppsIdDisclaimer": "导入的应用可能会错误地显示为“未安装”状态。\n请通过 Obtainium 重新安装这些应用来解决此问题。",
"importErrors": "导入错误",
"importedXOfYApps": "已导入 {} 中的 {} 个应用。",
"followingURLsHadErrors": "下列 URL 存在错误:",
"okay": "好的",
"selectURL": "选择 URL",
"selectURLs": "选择 URL",
"pick": "选择",
@ -119,8 +111,6 @@
"dark": "深色",
"light": "浅色",
"followSystem": "跟随系统",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "使用纯黑深色主题",
"appSortBy": "排序依据",
"authorName": "作者 / 应用名称",
@ -132,7 +122,7 @@
"bgUpdateCheckInterval": "后台更新检查间隔",
"neverManualOnly": "手动",
"appearance": "外观",
"showWebInAppView": "应用详情页显示来源网页",
"showWebInAppView": "应用详情页显示来源网页",
"pinUpdates": "将待更新应用置顶",
"updates": "更新",
"sourceSpecific": "来源",
@ -142,25 +132,25 @@
"close": "关闭",
"share": "分享",
"appNotFound": "未找到应用",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"obtainiumExportHyphenatedLowercase": "获取出口",
"pickAnAPK": "选择一个 APK 文件",
"appHasMoreThanOnePackage": "{} 有多个架构可用:",
"appHasMoreThanOnePackage": "{}有多个架构可用:",
"deviceSupportsXArch": "您的设备支持 {} 架构。",
"deviceSupportsFollowingArchs": "您的设备支持下列架构:",
"warning": "警告",
"sourceIsXButPackageFromYPrompt": "此应用的来源是“{}”,但 APK 文件来自“{}”。是否继续?",
"updatesAvailable": "更新可用",
"updatesAvailableNotifDescription": "Obtainium 追踪的应用有更新时发通知",
"updatesAvailableNotifDescription": "Obtainium 追踪的应用有更新时发通知",
"noNewUpdates": "全部应用已是最新。",
"xHasAnUpdate": "{} 可以更新了。",
"xHasAnUpdate": "{}可以更新了。",
"appsUpdated": "应用已更新",
"appsUpdatedNotifDescription": "当应用在后台安装更新时发通知",
"xWasUpdatedToY": "{} 已更新至 {}。",
"appsUpdatedNotifDescription": "当应用在后台安装更新时发通知",
"xWasUpdatedToY": "{}已更新至 {}。",
"errorCheckingUpdates": "检查更新出错",
"errorCheckingUpdatesNotifDescription": "当后台检查更新失败时显示的通知",
"appsRemoved": "应用已删除",
"appsRemovedNotifDescription": "当应用因加载出错而被删除时发通知",
"xWasRemovedDueToErrorY": "{} 由于以下错误被删除:{}",
"appsRemovedNotifDescription": "当应用因加载出错而被删除时发通知",
"xWasRemovedDueToErrorY": "{}由于以下错误被删除:{}",
"completeAppInstallation": "完成应用安装",
"obtainiumMustBeOpenToInstallApps": "必须启动 Obtainium 才能安装应用",
"completeAppInstallationNotifDescription": "提示返回 Obtainium 以完成应用的安装",
@ -168,7 +158,7 @@
"checkingForUpdatesNotifDescription": "检查更新时短暂显示的通知",
"pleaseAllowInstallPerm": "请授予 Obtainium 安装应用的权限",
"trackOnly": "仅追踪",
"errorWithHttpStatusCode": "错误 {}",
"errorWithHttpStatusCode": "{} 错误",
"versionCorrectionDisabled": "禁用版本号更正(插件似乎未起作用)",
"unknown": "未知",
"none": "无",
@ -180,13 +170,12 @@
"yesMarkUpdated": "是,标记为已更新",
"fdroid": "F-Droid 官方存储库",
"appIdOrName": "应用 ID 或名称",
"appId": "App ID",
"appId": "应用 ID",
"appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用",
"reposHaveMultipleApps": "存储库中可能包含多个应用",
"fdroidThirdPartyRepo": "F-Droid 第三方存储库",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "蒸汽手机",
"steamChat": "蒸汽聊天",
"install": "安装",
"markInstalled": "标记为已安装",
"update": "更新",
@ -194,11 +183,11 @@
"additionalOptions": "附加选项",
"disableVersionDetection": "禁用版本检测",
"noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。",
"downloadingX": "正在下载{}",
"downloadingX": "正在下载{}",
"downloadNotifDescription": "提示应用的下载进度",
"noAPKFound": "未找到 APK 文件",
"noVersionDetection": "禁用版本检测",
"categorize": "类",
"categorize": "类",
"categories": "类别",
"category": "类别",
"noCategory": "无类别",
@ -211,7 +200,7 @@
"copiedToClipboard": "已复制至剪贴板",
"storagePermissionDenied": "已拒绝授予存储权限",
"selectedCategorizeWarning": "这将覆盖选中应用当前的类别设置。",
"filterAPKsByRegEx": "使用正则表达式筛选 APK 文件",
"filterAPKsByRegEx": "筛选 APK 文件(正则表达式)",
"removeFromObtainium": "从 Obtainium 中删除",
"uninstallFromDevice": "从设备中卸载",
"onlyWorksWithNonVersionDetectApps": "仅适用于禁用版本检测的应用。",
@ -220,23 +209,24 @@
"changes": "更新日志",
"releaseDate": "发行日期",
"importFromURLsInFile": "从文件中的 URL 导入(如 OPML",
"versionDetectionExplanation": "将版本字符串与操作系统检测到的版本进行协调",
"versionDetection": "版本检测",
"standardVersionDetection": "常规版本检测",
"groupByCategory": "按类别分组显示",
"autoApkFilterByArch": "如果可能,尝试按设备支持的 CPU 架构筛选 APK 文件",
"overrideSource": "覆盖来源",
"dontShowAgain": "不再显示",
"dontShowTrackOnlyWarnings": "不显示“仅追踪”模式警告",
"dontShowAPKOriginWarnings": "不显示 APK 文件来源警告",
"dontShowTrackOnlyWarnings": "忽略“仅追踪”模式警告",
"dontShowAPKOriginWarnings": "忽略 APK 文件来源警告",
"moveNonInstalledAppsToBottom": "将未安装应用置底",
"gitlabPATLabel": "GitLab 个人访问令牌\n(启用搜索功能并增强 APK 发现)",
"gitlabPATLabel": "GitLab 个人访问令牌(启用搜索功能并增强 APK 发现)",
"about": "相关文档",
"requiresCredentialsInSettings": "此功能需要额外的凭据(在“设置”中添加)",
"requiresCredentialsInSettings": "{}此功能需要额外的凭据(在“设置”中添加)",
"checkOnStart": "启动时进行一次检查",
"tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
"removeOnExternalUninstall": "自动删除已卸载的外部应用",
"removeOnExternalUninstall": "自动删除列表中已卸载的应用",
"pickHighestVersionCode": "自动选择版本号最高的 APK 文件",
"checkUpdateOnDetailPage": "打开应用详情页时检查更新",
"checkUpdateOnDetailPage": "打开应用详情页时进行检查",
"disablePageTransitions": "禁用页面过渡动画效果",
"reversePageTransitions": "反转页面过渡动画效果",
"minStarCount": "最小星标数",
@ -244,8 +234,72 @@
"addInfoInSettings": "在“设置”中添加此凭据。",
"githubSourceNote": "使用访问令牌可避免触发 GitHub 的 API 请求限制。",
"gitlabSourceNote": "未使用访问令牌时可能无法从 GitLab 获取 APK 文件。",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"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": "常规",
"root": "root",
"shizukuBinderNotFound": "未发现兼容的 Shizuku 服务",
"useSystemFont": "使用系统字体",
"systemFontError": "加载系统字体出错:{}",
"useVersionCodeAsOSVersion": "使用应用程序版本代码作为操作系统检测到的版本",
"requestHeader": "请求标头",
"useLatestAssetDateAsReleaseDate": "使用最新资产上传作为发布日期",
"defaultPseudoVersioningMethod": "默认伪版本控制方法",
"partialAPKHash": "部分 APK 哈希值",
"APKLinkHash": "APK 链接哈希",
"directAPKLink": "直接 APK 链接",
"pseudoVersionInUse": "伪版本正在使用",
"installed": "已安装",
"latest": "最新的",
"invertRegEx": "反转正则表达式",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"removeAppQuestion": {
"one": "是否删除应用?",
"other": "是否删除应用?"
@ -259,8 +313,8 @@
"other": "后台更新检查遇到了“{}”问题,预定于 {} 分钟后重试"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "后台检查发现 {} 个应用更新 - 如有需要将发通知",
"other": "后台检查发现 {} 个应用更新 - 如有需要将发通知"
"one": "后台检查发现 {} 个应用更新 - 如有需要将发通知",
"other": "后台检查发现 {} 个应用更新 - 如有需要将发通知"
},
"apps": {
"one": "{} 个应用",
@ -287,11 +341,15 @@
"other": "清除了 {n} 个日志({before} 之前,{after} 之后)"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} 和另外 1 个应用可以更新了。",
"other": "{} 和另外 {} 个应用可以更新了。"
"one": "{}和另外 1 个应用可以更新了。",
"other": "{}和另外 {} 个应用可以更新了。"
},
"xAndNMoreUpdatesInstalled": {
"one": "{} 和另外 1 个应用已更新。",
"other": "{} 和另外 {} 个应用已更新。"
"other": "{}和另外 {} 个应用已更新。"
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} 和另外 1 个应用已尝试更新。",
"other": "“{}”和另外 {} 个应用已尝试更新。"
}
}

27
build.sh Executable file
View File

@ -0,0 +1,27 @@
#!/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
cd .flutter
git fetch
git checkout "$(flutter --version | head -2 | tail -1 | awk '{print $4}')" # Ensure included Flutter submodule version equals my environment
cd ..
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/

View File

@ -0,0 +1,54 @@
<p>Obtainium allows you to install and update Apps directly from their releases pages, and receive notifications when new releases are made available.</p>
<p>Read the <a href="https://github.com/ImranR98/Obtainium/wiki">Wiki</a></p>
<p>
<b>Currently supported App sources:</b>
</p>
<ul>
<li>
<p>Open Source - General:</p>
<ul>
<li>GitHub</li>
<li>GitLab</li>
<li>Codeberg</li>
<li>F-Droid</li>
<li>Third Party F-Droid Repos</li>
<li>IzzyOnDroid</li>
<li>SourceForge</li>
<li>SourceHut</li>
</ul>
</li>
<li>
<p>Other - General:</p>
<ul>
<li>APKPure</li>
<li>Aptoide</li>
<li>Uptodowng</li>
<li>APKMirror (Track-Only)</li>
<li>Huawei AppGallery</li>
<li>Jenkins Jobs</li>
</ul>
</li>
<li>
<p>Open Source - App-Specific:</p>
<ul>
<li>Mullvad</li>
<li>Signal</li>
<li>VLC</li>
</ul>
</li>
<li>
<p>Other - App-Specific:</p>
<ul>
<li>WhatsApp</li>
<li>Telegram App</li>
<li>Neutron Code</li>
</ul>
</li>
<li><p>"HTML" (Fallback): Any other URL that returns an HTML page with links to APK files</p></li>
</ul>
<p>
<b>Limitations:</b>
</p>
<p>
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.
</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

View File

@ -0,0 +1 @@
Get android app updates directly from the source

View File

@ -0,0 +1 @@
Obtainium

View File

@ -5,17 +5,20 @@ import 'package:obtainium/providers/source_provider.dart';
class APKCombo extends AppSource {
APKCombo() {
host = 'apkcombo.com';
hosts = ['apkcombo.com'];
showReleaseDateAsVersionToggle = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/+[^/]+/+[^/]+');
var match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+',
caseSensitive: false);
var match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -25,15 +28,20 @@ class APKCombo extends AppSource {
}
@override
Map<String, String> get requestHeaders => {
"User-Agent": "curl/8.0.1",
"Accept": "*/*",
"Connection": "keep-alive",
"Host": "$host"
};
Future<Map<String, String>?> getRequestHeaders(
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
return {
"User-Agent": "curl/8.0.1",
"Accept": "*/*",
"Connection": "keep-alive",
"Host": hosts[0]
};
}
Future<List<MapEntry<String, String>>> getApkUrls(String standardUrl) async {
var res = await sourceRequest('$standardUrl/download/apk');
Future<List<MapEntry<String, String>>> getApkUrls(
String standardUrl, Map<String, dynamic> additionalSettings) async {
var res = await sourceRequest('$standardUrl/download/apk', {});
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -66,9 +74,9 @@ class APKCombo extends AppSource {
}
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
var freshURLs = await getApkUrls(standardUrl);
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
Map<String, dynamic> additionalSettings) async {
var freshURLs = await getApkUrls(standardUrl, additionalSettings);
var path2Match = Uri.parse(apkUrl).path;
for (var url in freshURLs) {
if (Uri.parse(url.value).path == path2Match) {
@ -84,7 +92,7 @@ class APKCombo extends AppSource {
Map<String, dynamic> additionalSettings,
) async {
String appId = (await tryInferringAppId(standardUrl))!;
var preres = await sourceRequest(standardUrl);
var preres = await sourceRequest(standardUrl, additionalSettings);
if (preres.statusCode != 200) {
throw getObtainiumHttpError(preres);
}
@ -108,7 +116,9 @@ class APKCombo extends AppSource {
}
}
return APKDetails(
version, await getApkUrls(standardUrl), AppNames(author, appName),
version,
await getApkUrls(standardUrl, additionalSettings),
AppNames(author, appName),
releaseDate: releaseDate);
}
}

View File

@ -9,8 +9,9 @@ import 'package:obtainium/providers/source_provider.dart';
class APKMirror extends AppSource {
APKMirror() {
host = 'apkmirror.com';
hosts = ['apkmirror.com'];
enforceTrackOnly = true;
showReleaseDateAsVersionToggle = true;
additionalSourceAppSpecificSettingFormItems = [
[
@ -32,12 +33,14 @@ class APKMirror extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/apk/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/apk/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -57,7 +60,7 @@ class APKMirror extends AppSource {
true
? additionalSettings['filterReleaseTitlesByRegEx']
: null;
Response res = await sourceRequest('$standardUrl/feed');
Response res = await sourceRequest('$standardUrl/feed', additionalSettings);
if (res.statusCode == 200) {
var items = parse(res.body).querySelectorAll('item');
dynamic targetRelease;
@ -83,7 +86,7 @@ class APKMirror extends AppSource {
dateString != null ? HttpDate.parse('$dateString GMT') : null;
String? version = titleString
?.substring(RegExp('[0-9]').firstMatch(titleString)?.start ?? 0,
RegExp(' by ').firstMatch(titleString)?.start ?? 0)
RegExp(' by ').allMatches(titleString).last.start)
.trim();
if (version == null || version.isEmpty) {
version = titleString;

View File

@ -3,24 +3,46 @@ import 'package:html/parser.dart';
import 'package:obtainium/custom_errors.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 {
APKPure() {
host = 'apkpure.com';
hosts = ['apkpure.net', 'apkpure.com'];
allowSubDomains = true;
naiveStandardVersionDetection = true;
showReleaseDateAsVersionToggle = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExB = RegExp('^https?://m.$host/+[^/]+/+[^/]+');
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
RegExp standardUrlRegExB = RegExp(
'^https?://m.${getSourceRegex(hosts)}/+[^/]+/+[^/]+(/+[^/]+)?',
caseSensitive: false);
RegExpMatch? match = standardUrlRegExB.firstMatch(url);
if (match != null) {
url = 'https://$host/${Uri.parse(url).path}';
url = 'https://${getSourceRegex(hosts)}${Uri.parse(url).path}';
}
RegExp standardUrlRegExA = RegExp('^https?://$host/+[^/]+/+[^/]+');
match = standardUrlRegExA.firstMatch(url.toLowerCase());
RegExp standardUrlRegExA = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+(/+[^/]+)?',
caseSensitive: false);
match = standardUrlRegExA.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -36,8 +58,8 @@ class APKPure extends AppSource {
) async {
String appId = (await tryInferringAppId(standardUrl))!;
String host = Uri.parse(standardUrl).host;
var res = await sourceRequest('$standardUrl/download');
var resChangelog = await sourceRequest(standardUrl);
var res = await sourceRequest('$standardUrl/download', additionalSettings);
var resChangelog = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode == 200 && resChangelog.statusCode == 200) {
var html = parse(res.body);
var htmlChangelog = parse(resChangelog.body);
@ -47,20 +69,11 @@ class APKPure extends AppSource {
}
String? dateString =
html.querySelector('span.info-other span.date')?.text.trim();
DateTime? releaseDate;
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
}
DateTime? releaseDate = parseDateTimeMMMddCommayyyy(dateString);
String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK';
List<MapEntry<String, String>> apkUrls = [
MapEntry('$appId.apk', 'https://d.$host/b/$type/$appId?version=latest')
MapEntry('$appId.apk',
'https://d.${hosts.contains(host) ? 'cdnpure.com' : host}/b/$type/$appId?version=latest')
];
String author = html
.querySelector('span.info-sdk')
@ -70,11 +83,13 @@ class APKPure extends AppSource {
Uri.parse(standardUrl).pathSegments.reversed.last;
String appName =
html.querySelector('h1.info-title')?.text.trim() ?? appId;
String? changeLog = htmlChangelog.querySelector("div.whats-new-info p:not(.date)")?.innerHtml
.trim().replaceAll("<br>", " \n");
String? changeLog = htmlChangelog
.querySelector("div.whats-new-info p:not(.date)")
?.innerHtml
.trim()
.replaceAll("<br>", " \n");
return APKDetails(version, apkUrls, AppNames(author, appName),
releaseDate: releaseDate,
changeLog: changeLog);
releaseDate: releaseDate, changeLog: changeLog);
} else {
throw getObtainiumHttpError(res);
}

View File

@ -0,0 +1,82 @@
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() {
hosts = ['aptoide.com'];
name = 'Aptoide';
allowSubDomains = true;
naiveStandardVersionDetection = true;
showReleaseDateAsVersionToggle = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp(
'^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return match.group(0)!;
}
@override
Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async {
return (await getAppDetailsJSON(
standardUrl, additionalSettings))['package'];
}
Future<Map<String, dynamic>> getAppDetailsJSON(
String standardUrl, Map<String, dynamic> additionalSettings) async {
var res = await sourceRequest(standardUrl, additionalSettings);
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', additionalSettings);
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, additionalSettings);
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);
}
}

View File

@ -5,7 +5,7 @@ import 'package:obtainium/providers/source_provider.dart';
class Codeberg extends AppSource {
GitHub gh = GitHub();
Codeberg() {
host = 'codeberg.org';
hosts = ['codeberg.org'];
additionalSourceAppSpecificSettingFormItems =
gh.additionalSourceAppSpecificSettingFormItems;
@ -16,12 +16,14 @@ class Codeberg extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -35,7 +37,7 @@ class Codeberg extends AppSource {
) async {
return await gh.getLatestAPKDetailsCommon2(standardUrl, additionalSettings,
(bool useTagUrl) async {
return 'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
return 'https://${hosts[0]}/api/v1/repos${standardUrl.substring('https://${hosts[0]}'.length)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
}, null);
}
@ -50,7 +52,7 @@ class Codeberg extends AppSource {
{Map<String, dynamic> querySettings = const {}}) async {
return gh.searchCommon(
query,
'https://$host/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100',
'https://${hosts[0]}/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100',
'data',
querySettings: querySettings);
}

View File

@ -0,0 +1,44 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/providers/source_provider.dart';
class DirectAPKLink extends AppSource {
HTML html = HTML();
DirectAPKLink() {
neverAutoSelect = true;
name = tr('directAPKLink');
additionalSourceAppSpecificSettingFormItems = html
.additionalSourceAppSpecificSettingFormItems
.where((element) => element
.where((element) => element.key == 'requestHeader')
.isNotEmpty)
.toList();
excludeCommonSettingKeys = [
'versionExtractionRegEx',
'matchGroupToUse',
'versionDetection',
'useVersionCodeAsOSVersion',
'apkFilterRegEx',
'autoApkFilterByArch'
];
}
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
var additionalSettingsNew =
getDefaultValuesFromFormItems(html.combinedAppSpecificSettingFormItems);
for (var s in additionalSettings.keys) {
if (additionalSettingsNew.containsKey(s)) {
additionalSettingsNew[s] = additionalSettings[s];
}
}
additionalSettingsNew['defaultPseudoVersioningMethod'] = 'partialAPKHash';
additionalSettingsNew['directAPKLink'] = true;
additionalSettings['versionDetection'] = false;
return html.getLatestAPKDetails(standardUrl, additionalSettingsNew);
}
}

View File

@ -3,31 +3,58 @@ import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/app_sources/gitlab.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
class FDroid extends AppSource {
FDroid() {
host = 'f-droid.org';
hosts = ['f-droid.org'];
name = tr('fdroid');
naiveStandardVersionDetection = 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
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExB =
RegExp('^https?://$host/+[^/]+/+packages/+[^/]+');
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
RegExp standardUrlRegExB = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+packages/+[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegExB.firstMatch(url);
if (match != null) {
url =
'https://${Uri.parse(url.substring(0, match.end)).host}/packages/${Uri.parse(url).pathSegments.last}';
'https://${Uri.parse(match.group(0)!).host}/packages/${Uri.parse(url).pathSegments.last}';
}
RegExp standardUrlRegExA = RegExp('^https?://$host/+packages/+[^/]+');
match = standardUrlRegExA.firstMatch(url.toLowerCase());
RegExp standardUrlRegExA = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/+packages/+[^/]+',
caseSensitive: false);
match = standardUrlRegExA.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -36,28 +63,6 @@ class FDroid extends AppSource {
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
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
@ -65,17 +70,75 @@ class FDroid extends AppSource {
) async {
String? appId = await tryInferringAppId(standardUrl);
String host = Uri.parse(standardUrl).host;
return getAPKUrlsFromFDroidPackagesAPIResponse(
await sourceRequest('https://$host/api/v1/packages/$appId'),
var details = getAPKUrlsFromFDroidPackagesAPIResponse(
await sourceRequest(
'https://$host/api/v1/packages/$appId', additionalSettings),
'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',
additionalSettings);
var lines = res.body.split('\n');
var authorLines = lines.where((l) => l.startsWith('AuthorName: '));
if (authorLines.isNotEmpty) {
details.names.author =
authorLines.first.split(': ').sublist(1).join(': ');
}
var changelogUrls = lines
.where((l) => l.startsWith('Changelog: '))
.map((e) => e.split(' ').sublist(1).join(' '));
if (changelogUrls.isNotEmpty) {
details.changeLog = changelogUrls.first;
bool isGitHub = false;
bool isGitLab = false;
try {
GitHub().sourceSpecificStandardizeURL(details.changeLog!);
isGitHub = true;
} catch (e) {
//
}
try {
GitLab().sourceSpecificStandardizeURL(details.changeLog!);
isGitLab = true;
} catch (e) {
//
}
if ((isGitHub || isGitLab) &&
(details.changeLog?.indexOf('/blob/') ?? -1) >= 0) {
details.changeLog = (await sourceRequest(
details.changeLog!.replaceFirst('/blob/', '/raw/'),
additionalSettings))
.body;
}
}
} catch (e) {
// Fail silently
}
if ((details.changeLog?.length ?? 0) > 2048) {
details.changeLog = '${details.changeLog!.substring(0, 2048)}...';
}
}
return details;
}
@override
Future<Map<String, List<String>>> search(String query,
{Map<String, dynamic> querySettings = const {}}) async {
Response res = await sourceRequest(
'https://search.$host/?q=${Uri.encodeQueryComponent(query)}');
'https://search.${hosts[0]}/?q=${Uri.encodeQueryComponent(query)}', {});
if (res.statusCode == 200) {
Map<String, List<String>> urlsWithDescriptions = {};
parse(res.body).querySelectorAll('.package-header').forEach((e) {
@ -100,4 +163,79 @@ class FDroid extends AppSource {
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);
}
}
}

View File

@ -7,6 +7,10 @@ import 'package:obtainium/providers/source_provider.dart';
class FDroidRepo extends AppSource {
FDroidRepo() {
name = tr('fdroidThirdPartyRepo');
canSearch = true;
excludeFromMassSearch = true;
neverAutoSelect = true;
showReleaseDateAsVersionToggle = true;
additionalSourceAppSpecificSettingFormItems = [
[
@ -22,17 +26,100 @@ 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
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
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'];
if (appIdOrName == null) {
throw NoReleasesError();
}
var res = await sourceRequest('$standardUrl/index.xml');
var res = await sourceRequest(
'$standardUrl${standardUrl.endsWith('/index.xml') ? '' : '/index.xml'}',
additionalSettings);
if (res.statusCode == 200) {
var body = parse(res.body);
var foundApps = body.querySelectorAll('application').where((element) {
@ -41,7 +128,7 @@ class FDroidRepo extends AppSource {
if (foundApps.isEmpty) {
foundApps = body.querySelectorAll('application').where((element) {
return element.querySelector('name')?.innerHtml.toLowerCase() ==
appIdOrName.toLowerCase();
appIdOrName!.toLowerCase();
}).toList();
}
if (foundApps.isEmpty) {
@ -50,7 +137,7 @@ class FDroidRepo extends AppSource {
.querySelector('name')
?.innerHtml
.toLowerCase()
.contains(appIdOrName.toLowerCase()) ??
.contains(appIdOrName!.toLowerCase()) ??
false;
}).toList();
}
@ -58,8 +145,9 @@ class FDroidRepo extends AppSource {
throw ObtainiumError(tr('appWithIdOrNameNotFound'));
}
var authorName = body.querySelector('repo')?.attributes['name'] ?? name;
var appName =
foundApps[0].querySelector('name')?.innerHtml ?? appIdOrName;
String appId = foundApps[0].attributes['id']!;
foundApps[0].querySelector('name')?.innerHtml ?? appId;
var appName = foundApps[0].querySelector('name')?.innerHtml ?? appId;
var releases = foundApps[0].querySelectorAll('package');
String? latestVersion = releases[0].querySelector('version')?.innerHtml;
String? added = releases[0].querySelector('added')?.innerHtml;

View File

@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
@ -13,29 +14,15 @@ import 'package:url_launcher/url_launcher_string.dart';
class GitHub extends AppSource {
GitHub() {
host = 'github.com';
hosts = ['github.com'];
appIdInferIsOptional = true;
showReleaseDateAsVersionToggle = true;
sourceConfigSettingFormItems = [
GeneratedFormTextField('github-creds',
label: tr('githubPATLabel'),
password: true,
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: [
const SizedBox(
height: 4,
@ -85,6 +72,15 @@ class GitHub extends AppSource {
return regExValidator(value);
}
])
],
[GeneratedFormSwitch('verifyLatestTag', label: tr('verifyLatestTag'))],
[
GeneratedFormSwitch('dontSortReleasesList',
label: tr('dontSortReleasesList'))
],
[
GeneratedFormSwitch('useLatestAssetDateAsReleaseDate',
label: tr('useLatestAssetDateAsReleaseDate'), defaultValue: false)
]
];
@ -117,7 +113,8 @@ class GitHub extends AppSource {
for (var path in possibleBuildGradleLocations) {
try {
var res = await sourceRequest(
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path');
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path',
additionalSettings);
if (res.statusCode == 200) {
try {
var body = jsonDecode(res.body);
@ -126,22 +123,23 @@ class GitHub extends AppSource {
.decode(body['content'].toString().split('\n').join('')))
.split('\n')
.map((e) => e.trim());
var appId = trimmedLines
.where((l) =>
l.startsWith('applicationId "') ||
l.startsWith('applicationId \''))
.first;
appId = appId
.split(appId.startsWith('applicationId "') ? '"' : '\'')[1];
if (appId.startsWith('\${') && appId.endsWith('}')) {
appId = trimmedLines
.where((l) => l.startsWith(
'def ${appId.substring(2, appId.length - 1)}'))
.first;
appId = appId.split(appId.contains('"') ? '"' : '\'')[1];
}
if (appId.isNotEmpty) {
var appIds = trimmedLines.where((l) =>
l.startsWith('applicationId "') ||
l.startsWith('applicationId \''));
appIds = appIds.map((appId) => appId
.split(appId.startsWith('applicationId "') ? '"' : '\'')[1]);
appIds = appIds.map((appId) {
if (appId.startsWith('\${') && appId.endsWith('}')) {
appId = trimmedLines
.where((l) => l.startsWith(
'def ${appId.substring(2, appId.length - 1)}'))
.first;
appId = appId.split(appId.contains('"') ? '"' : '\'')[1];
}
return appId;
}).where((appId) => appId.isNotEmpty);
if (appIds.length == 1) {
return appIds.first;
}
} catch (err) {
LogsProvider().add(
@ -157,38 +155,67 @@ class GitHub extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
Future<String> getCredentialPrefixIfAny(
Map<String, dynamic> additionalSettings) async {
@override
Future<Map<String, String>?> getRequestHeaders(
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
var token = await getTokenIfAny(additionalSettings);
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?> getTokenIfAny(Map<String, dynamic> additionalSettings) async {
SettingsProvider settingsProvider = SettingsProvider();
await settingsProvider.initializeSettings();
var sourceConfig =
await getSourceConfigValues(additionalSettings, settingsProvider);
String? creds = sourceConfig['github-creds'];
return creds != null && creds.isNotEmpty ? '$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;
}
}
@override
Future<String?> getSourceNote() async {
if (!hostChanged && (await getCredentialPrefixIfAny({})).isEmpty) {
if (!hostChanged && (await getTokenIfAny({})) == null) {
return '${tr('githubSourceNote')} ${hostChanged ? tr('addInfoBelow') : tr('addInfoInSettings')}';
}
return null;
}
Future<String> getAPIHost(Map<String, dynamic> additionalSettings) async =>
'https://${await getCredentialPrefixIfAny(additionalSettings)}api.$host';
'https://api.${hosts[0]}';
Future<String> convertStandardUrlToAPIUrl(
String standardUrl, Map<String, dynamic> additionalSettings) async =>
'${await getAPIHost(additionalSettings)}/repos${standardUrl.substring('https://$host'.length)}';
'${await getAPIHost(additionalSettings)}/repos${standardUrl.substring('https://${hosts[0]}'.length)}';
@override
String? changeLogPageFromStandardUrl(String standardUrl) =>
@ -212,52 +239,121 @@ class GitHub extends AppSource {
true
? additionalSettings['filterReleaseNotesByRegEx']
: null;
Response res = await sourceRequest(requestUrl);
bool verifyLatestTag = additionalSettings['verifyLatestTag'] == true;
bool dontSortReleasesList =
additionalSettings['dontSortReleasesList'] == true;
bool useLatestAssetDateAsReleaseDate =
additionalSettings['useLatestAssetDateAsReleaseDate'] == true;
dynamic latestRelease;
if (verifyLatestTag) {
var temp = requestUrl.split('?');
Response res = await sourceRequest(
'${temp[0]}/latest${temp.length > 1 ? '?${temp.sublist(1).join('?')}' : ''}',
additionalSettings);
if (res.statusCode != 200) {
if (onHttpErrorCode != null) {
onHttpErrorCode(res);
}
throw getObtainiumHttpError(res);
}
latestRelease = jsonDecode(res.body);
}
Response res = await sourceRequest(requestUrl, additionalSettings);
if (res.statusCode == 200) {
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) =>
(release['assets'] as List<dynamic>?)
?.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,
e['browser_download_url'] as String)
(e['url'] ?? e['browser_download_url']) as String)
: const MapEntry('', '');
})
.where((element) => element.key.toLowerCase().endsWith('.apk'))
.toList() ??
[];
DateTime? getReleaseDateFromRelease(dynamic rel) =>
DateTime? getPublishDateFromRelease(dynamic rel) =>
rel?['published_at'] != null
? DateTime.parse(rel['published_at'])
: null;
releases.sort((a, b) {
// See #478 and #534
if (a == b) {
return 0;
} else if (a == null) {
return -1;
} else if (b == null) {
return 1;
} else {
var nameA = a['tag_name'] ?? a['name'];
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));
}
DateTime? getNewestAssetDateFromRelease(dynamic rel) {
var t = (rel['assets'] as List<dynamic>?)
?.map((e) {
return e?['updated_at'] != null
? DateTime.parse(e['updated_at'])
: null;
})
.where((e) => e != null)
.toList();
t?.sort((a, b) => b!.compareTo(a!));
if (t?.isNotEmpty == true) {
return t!.first;
}
});
return null;
}
DateTime? getReleaseDateFromRelease(dynamic rel, bool useAssetDate) =>
!useAssetDate
? getPublishDateFromRelease(rel)
: getNewestAssetDateFromRelease(rel);
if (dontSortReleasesList) {
releases = releases.reversed.toList();
} else {
releases.sort((a, b) {
// See #478 and #534
if (a == b) {
return 0;
} else if (a == null) {
return -1;
} else if (b == null) {
return 1;
} else {
var nameA = a['tag_name'] ?? a['name'];
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, useLatestAssetDateAsReleaseDate) ??
DateTime(1))
.compareTo(getReleaseDateFromRelease(
b, useLatestAssetDateAsReleaseDate) ??
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();
dynamic targetRelease;
var prerrelsSkipped = 0;
@ -286,6 +382,8 @@ class GitHub extends AppSource {
continue;
}
var apkUrls = getReleaseAPKUrls(releases[i]);
apkUrls = filterApks(apkUrls, additionalSettings['apkFilterRegEx'],
additionalSettings['invertAPKFilter']);
if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) {
continue;
}
@ -297,7 +395,8 @@ class GitHub extends AppSource {
throw NoReleasesError();
}
String? version = targetRelease['tag_name'] ?? targetRelease['name'];
DateTime? releaseDate = getReleaseDateFromRelease(targetRelease);
DateTime? releaseDate = getReleaseDateFromRelease(
targetRelease, useLatestAssetDateAsReleaseDate);
if (version == null) {
throw NoVersionError();
}
@ -359,7 +458,7 @@ class GitHub extends AppSource {
String query, String requestUrl, String rootProp,
{Function(Response)? onHttpErrorCode,
Map<String, dynamic> querySettings = const {}}) async {
Response res = await sourceRequest(requestUrl);
Response res = await sourceRequest(requestUrl, {});
if (res.statusCode == 200) {
int minStarCount = querySettings['minStarCount'] != null
? int.parse(querySettings['minStarCount'])

View File

@ -13,8 +13,9 @@ import 'package:url_launcher/url_launcher_string.dart';
class GitLab extends AppSource {
GitLab() {
host = 'gitlab.com';
hosts = ['gitlab.com'];
canSearch = true;
showReleaseDateAsVersionToggle = true;
sourceConfigSettingFormItems = [
GeneratedFormTextField('gitlab-creds',
@ -52,12 +53,14 @@ class GitLab extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
Future<String?> getPATIfAny(Map<String, dynamic> additionalSettings) async {
@ -80,20 +83,16 @@ class GitLab extends AppSource {
@override
Future<Map<String, List<String>>> search(String query,
{Map<String, dynamic> querySettings = const {}}) async {
String? PAT = await getPATIfAny({});
if (PAT == null) {
throw CredsNeededError(name);
}
var url =
'https://$host/api/v4/search?private_token=$PAT&scope=projects&search=${Uri.encodeQueryComponent(query)}';
var res = await sourceRequest(url);
'https://${hosts[0]}/api/v4/projects?search=${Uri.encodeQueryComponent(query)}';
var res = await sourceRequest(url, {});
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var json = jsonDecode(res.body) as List<dynamic>;
Map<String, List<String>> results = {};
for (var element in json) {
results['https://$host/${element['path_with_namespace']}'] = [
results['https://${hosts[0]}/${element['path_with_namespace']}'] = [
element['name_with_namespace'],
element['description'] ?? tr('noDescription')
];
@ -117,7 +116,8 @@ class GitLab extends AppSource {
if (PAT != null) {
var names = GitHub().getAppNames(standardUrl);
Response res = await sourceRequest(
'https://$host/api/v4/projects/${names.author}%2F${names.name}/releases?private_token=$PAT');
'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/releases?private_token=$PAT',
additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -152,7 +152,8 @@ class GitLab extends AppSource {
releaseDate: releaseDate);
});
} else {
Response res = await sourceRequest('$standardUrl/-/tags?format=atom');
Response res = await sourceRequest(
'$standardUrl/-/tags?format=atom', additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -174,7 +175,6 @@ class GitLab extends AppSource {
...getLinksFromParsedHTML(entryContent,
RegExp('/[^/]+\\.apk\$', caseSensitive: false), '')
.where((element) => Uri.parse(element).host != '')
.toList()
];
var entryId = entry.querySelector('id')?.innerHtml;
var version =
@ -192,7 +192,7 @@ class GitLab extends AppSource {
});
}
if (apkDetailsList.isEmpty) {
throw NoReleasesError();
throw NoReleasesError(note: tr('gitlabSourceNote'));
}
if (fallbackToOlderReleases) {
if (additionalSettings['trackOnly'] != true) {
@ -200,7 +200,7 @@ class GitLab extends AppSource {
apkDetailsList.where((e) => e.apkUrls.isNotEmpty).toList();
}
if (apkDetailsList.isEmpty) {
throw NoReleasesError();
throw NoReleasesError(note: tr('gitlabSourceNote'));
}
}
return apkDetailsList.first;

View File

@ -3,6 +3,7 @@ import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/source_provider.dart';
String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
@ -16,13 +17,19 @@ String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
.split('/')
.where((element) => element.trim().isNotEmpty)
.toList();
String absoluteUrl;
if (ambiguousUrl.startsWith('/') || currPathSegments.isEmpty) {
return '${referenceAbsoluteUrl.origin}/$ambiguousUrl';
} else if (ambiguousUrl.split('/').length == 1) {
return '${referenceAbsoluteUrl.origin}/${currPathSegments.join('/')}/$ambiguousUrl';
absoluteUrl = '${referenceAbsoluteUrl.origin}$ambiguousUrl';
} else if (currPathSegments.isEmpty) {
absoluteUrl = '${referenceAbsoluteUrl.origin}/$ambiguousUrl';
} else if (ambiguousUrl.split('/').where((e) => e.isNotEmpty).length == 1) {
absoluteUrl =
'${referenceAbsoluteUrl.origin}/${currPathSegments.join('/')}/$ambiguousUrl';
} 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) {
@ -86,59 +93,234 @@ bool _isNumeric(String s) {
}
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'))
]
];
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 = [
[
GeneratedFormSwitch('sortByFileNamesNotLinks',
label: tr('sortByFileNamesNotLinks'))
GeneratedFormSubForm(
'intermediateLink', [...intermediateFormItems, ...commonFormItems],
label: tr('intermediateLink'))
],
finalStepFormitems[0],
...commonFormItems,
...finalStepFormitems.sublist(1),
[
GeneratedFormSubForm(
'requestHeader',
[
[
GeneratedFormTextField('requestHeader',
label: tr('requestHeader'),
required: false,
additionalValidators: [
(value) {
if ((value ?? 'empty:valid')
.split(':')
.map((e) => e.trim())
.where((e) => e.isNotEmpty)
.length <
2) {
return tr('invalidInput');
}
return null;
}
])
]
],
label: tr('requestHeader'),
defaultValue: [
{
'requestHeader':
'User-Agent: Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36'
}
])
],
[
GeneratedFormDropdown(
'defaultPseudoVersioningMethod',
[
MapEntry('partialAPKHash', tr('partialAPKHash')),
MapEntry('APKLinkHash', tr('APKLinkHash'))
],
label: tr('defaultPseudoVersioningMethod'),
defaultValue: 'partialAPKHash')
]
];
}
@override
// TODO: implement requestHeaders choice, hardcoded for now
Map<String, String>? get requestHeaders => {
"User-Agent":
"Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"
};
Future<Map<String, String>?> getRequestHeaders(
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
if (additionalSettings.isNotEmpty) {
if (additionalSettings['requestHeader']?.isNotEmpty != true) {
additionalSettings['requestHeader'] = [];
}
additionalSettings['requestHeader'] = additionalSettings['requestHeader']
.where((l) => l['requestHeader'].isNotEmpty == true)
.toList();
Map<String, String> requestHeaders = {};
for (int i = 0; i < (additionalSettings['requestHeader'].length); i++) {
var temp =
(additionalSettings['requestHeader'][i]['requestHeader'] as String)
.split(':');
requestHeaders[temp[0].trim()] = temp.sublist(1).join(':').trim();
}
return requestHeaders;
}
return null;
}
@override
String sourceSpecificStandardizeURL(String 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
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
var uri = Uri.parse(standardUrl);
Response res = await sourceRequest(standardUrl);
if (res.statusCode == 200) {
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) => additionalSettings['sortByFileNamesNotLinks'] == true
? compareAlphaNumeric(a.split('/').last, b.split('/').last)
: compareAlphaNumeric(a, b));
if (additionalSettings['apkFilterRegEx'] != null) {
var reg = RegExp(additionalSettings['apkFilterRegEx']);
links = links.where((element) => reg.hasMatch(element)).toList();
var currentUrl = standardUrl;
if (additionalSettings['intermediateLink']?.isNotEmpty != true) {
additionalSettings['intermediateLink'] = [];
}
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),
additionalSettings['intermediateLink'][i]);
if (intLinks.isEmpty) {
throw NoReleasesError();
} else {
currentUrl = intLinks.last.key;
}
}
var uri = Uri.parse(currentUrl);
List<MapEntry<String, String>> links = [];
String versionExtractionWholePageString = currentUrl;
if (additionalSettings['directAPKLink'] != true) {
Response res = await sourceRequest(currentUrl, additionalSettings);
versionExtractionWholePageString =
res.body.split('\r\n').join('\n').split('\n').join('\\n');
links = await grabLinksCommon(res, additionalSettings);
links = filterApks(links, additionalSettings['apkFilterRegEx'],
additionalSettings['invertAPKFilter']);
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);
links = [MapEntry(currentUrl, currentUrl)];
}
var rel = links.last.key;
String? version;
version = extractVersion(
additionalSettings['versionExtractionRegEx'] as String?,
additionalSettings['matchGroupToUse'] as String?,
additionalSettings['versionExtractWholePage'] == true
? versionExtractionWholePageString
: rel);
version ??=
additionalSettings['defaultPseudoVersioningMethod'] == 'APKLinkHash'
? rel.hashCode.toString()
: (await checkPartialDownloadHashDynamc(rel)).toString();
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
AppNames(uri.host, tr('app')));
}
}

View File

@ -0,0 +1,95 @@
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';
hosts = ['appgallery.huawei.com'];
versionDetectionDisallowed = true;
showReleaseDateAsVersionToggle = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/app/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return match.group(0)!;
}
getDlUrl(String standardUrl) =>
'https://${hosts[0].replaceAll('appgallery.', 'appgallery.cloud.')}/appdl/${standardUrl.split('/').last}';
requestAppdlRedirect(
String dlUrl, Map<String, dynamic> additionalSettings) async {
Response res =
await sourceRequest(dlUrl, additionalSettings, 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, additionalSettings);
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, additionalSettings);
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);
}
}

View File

@ -3,24 +3,38 @@ import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
class IzzyOnDroid extends AppSource {
late FDroid fd;
IzzyOnDroid() {
host = 'android.izzysoft.de';
hosts = ['izzysoft.de'];
fd = FDroid();
additionalSourceAppSpecificSettingFormItems =
fd.additionalSourceAppSpecificSettingFormItems;
allowSubDomains = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/repo/apk/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegExA = RegExp(
'^https?://android.${getSourceRegex(hosts)}/repo/apk/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegExA.firstMatch(url);
if (match == null) {
RegExp standardUrlRegExB = RegExp(
'^https?://apt.${getSourceRegex(hosts)}/fdroid/index/apk/[^/]+',
caseSensitive: false);
match = standardUrlRegExB.firstMatch(url);
}
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async {
return FDroid().tryInferringAppId(standardUrl);
return fd.tryInferringAppId(standardUrl);
}
@override
@ -29,10 +43,17 @@ class IzzyOnDroid extends AppSource {
Map<String, dynamic> additionalSettings,
) async {
String? appId = await tryInferringAppId(standardUrl);
return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse(
return fd.getAPKUrlsFromFDroidPackagesAPIResponse(
await sourceRequest(
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'),
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId',
additionalSettings),
'https://android.izzysoft.de/frepo/$appId',
standardUrl);
standardUrl,
name,
autoSelectHighestVersionCode:
additionalSettings['autoSelectHighestVersionCode'] == true,
trySelectingSuggestedVersionCode:
additionalSettings['trySelectingSuggestedVersionCode'] == true,
filterVersionsByRegEx: additionalSettings['filterVersionsByRegEx']);
}
}

View File

@ -6,7 +6,9 @@ import 'package:obtainium/providers/source_provider.dart';
class Jenkins extends AppSource {
Jenkins() {
overrideVersionDetectionFormDefault('releaseDateAsVersion', true);
versionDetectionDisallowed = true;
neverAutoSelect = true;
showReleaseDateAsVersionToggle = true;
}
String trimJobUrl(String url) {
@ -15,7 +17,7 @@ class Jenkins extends AppSource {
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -28,8 +30,8 @@ class Jenkins extends AppSource {
Map<String, dynamic> additionalSettings,
) async {
standardUrl = trimJobUrl(standardUrl);
Response res =
await sourceRequest('$standardUrl/lastSuccessfulBuild/api/json');
Response res = await sourceRequest(
'$standardUrl/lastSuccessfulBuild/api/json', additionalSettings);
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
var releaseDate = json['timestamp'] == null

View File

@ -6,17 +6,19 @@ import 'package:obtainium/providers/source_provider.dart';
class Mullvad extends AppSource {
Mullvad() {
host = 'mullvad.net';
hosts = ['mullvad.net'];
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -28,7 +30,8 @@ class Mullvad extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest('$standardUrl/en/download/android');
Response res = await sourceRequest(
'$standardUrl/en/download/android', additionalSettings);
if (res.statusCode == 200) {
var versions = parse(res.body)
.querySelectorAll('p')

View File

@ -5,17 +5,20 @@ import 'package:obtainium/providers/source_provider.dart';
class NeutronCode extends AppSource {
NeutronCode() {
host = 'neutroncode.com';
hosts = ['neutroncode.com'];
showReleaseDateAsVersionToggle = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/downloads/file/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/downloads/file/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -78,7 +81,7 @@ class NeutronCode extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest(standardUrl);
Response res = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode == 200) {
var http = parse(res.body);
var name = http.querySelector('.pd-title')?.innerHtml;
@ -91,7 +94,7 @@ class NeutronCode extends AppSource {
if (version == null) {
throw NoVersionError();
}
String? apkUrl = 'https://$host/download/$filename';
String? apkUrl = 'https://${hosts[0]}/download/$filename';
var dateStringOriginal =
http.querySelector('.pd-date-txt')?.nextElementSibling?.innerHtml;
var dateString = dateStringOriginal != null

View File

@ -5,12 +5,12 @@ import 'package:obtainium/providers/source_provider.dart';
class Signal extends AppSource {
Signal() {
host = 'signal.org';
hosts = ['signal.org'];
}
@override
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
@override
@ -18,8 +18,8 @@ class Signal extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res =
await sourceRequest('https://updates.$host/android/latest.json');
Response res = await sourceRequest(
'https://updates.${hosts[0]}/android/latest.json', additionalSettings);
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
String? apkUrl = json['url'];

View File

@ -5,23 +5,27 @@ import 'package:obtainium/providers/source_provider.dart';
class SourceForge extends AppSource {
SourceForge() {
host = 'sourceforge.net';
hosts = ['sourceforge.net'];
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExB = RegExp('^https?://$host/p/[^/]+');
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
RegExp standardUrlRegExB = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/p/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegExB.firstMatch(url);
if (match != null) {
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(match.group(0)!).host}/projects/${url.substring(Uri.parse(match.group(0)!).host.length + '/projects/'.length + 1)}';
}
RegExp standardUrlRegExA = RegExp('^https?://$host/projects/[^/]+');
match = standardUrlRegExA.firstMatch(url.toLowerCase());
RegExp standardUrlRegExA = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/projects/[^/]+',
caseSensitive: false);
match = standardUrlRegExA.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -29,7 +33,8 @@ class SourceForge extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest('$standardUrl/rss?path=/');
Response res =
await sourceRequest('$standardUrl/rss?path=/', additionalSettings);
if (res.statusCode == 200) {
var parsedHtml = parse(res.body);
var allDownloadLinks =

View File

@ -8,7 +8,8 @@ import 'package:easy_localization/easy_localization.dart';
class SourceHut extends AppSource {
SourceHut() {
host = 'git.sr.ht';
hosts = ['git.sr.ht'];
showReleaseDateAsVersionToggle = true;
additionalSourceAppSpecificSettingFormItems = [
[
@ -20,12 +21,14 @@ class SourceHut extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -36,11 +39,21 @@ class SourceHut extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
if (standardUrl.endsWith('/refs')) {
standardUrl = standardUrl
.split('/')
.reversed
.toList()
.sublist(1)
.reversed
.join('/');
}
Uri standardUri = Uri.parse(standardUrl);
String appName = standardUri.pathSegments.last;
bool fallbackToOlderReleases =
additionalSettings['fallbackToOlderReleases'] == true;
Response res = await sourceRequest('$standardUrl/refs/rss.xml');
Response res =
await sourceRequest('$standardUrl/refs/rss.xml', additionalSettings);
if (res.statusCode == 200) {
var parsedHtml = parse(res.body);
List<APKDetails> apkDetailsList = [];
@ -69,7 +82,7 @@ class SourceHut extends AppSource {
} catch (e) {
// ignore
}
var res2 = await sourceRequest(releasePage);
var res2 = await sourceRequest(releasePage, additionalSettings);
List<MapEntry<String, String>> apkUrls = [];
if (res2.statusCode == 200) {
apkUrls = getApkUrlsFromUrls(parse(res2.body)

View File

@ -7,8 +7,8 @@ import 'package:obtainium/providers/source_provider.dart';
class SteamMobile extends AppSource {
SteamMobile() {
host = 'store.steampowered.com';
name = tr('steam');
hosts = ['store.steampowered.com'];
name = 'Steam';
additionalSourceAppSpecificSettingFormItems = [
[
GeneratedFormDropdown('app', apks.entries.toList(),
@ -21,7 +21,7 @@ class SteamMobile extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
@override
@ -29,7 +29,8 @@ class SteamMobile extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest('https://$host/mobile');
Response res =
await sourceRequest('https://${hosts[0]}/mobile', additionalSettings);
if (res.statusCode == 200) {
var apkNamePrefix = additionalSettings['app'] as String?;
if (apkNamePrefix == null) {

View File

@ -6,13 +6,13 @@ import 'package:obtainium/providers/source_provider.dart';
class TelegramApp extends AppSource {
TelegramApp() {
host = 'telegram.org';
hosts = ['telegram.org'];
name = 'Telegram ${tr('app')}';
}
@override
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
@override
@ -20,7 +20,8 @@ class TelegramApp extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest('https://t.me/s/TAndroidAPK');
Response res =
await sourceRequest('https://t.me/s/TAndroidAPK', additionalSettings);
if (res.statusCode == 200) {
var http = parse(res.body);
var messages =

View File

@ -0,0 +1,105 @@
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() {
hosts = ['uptodown.com'];
allowSubDomains = true;
naiveStandardVersionDetection = true;
showReleaseDateAsVersionToggle = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp(
'^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return '${match.group(0)!}/android/download';
}
@override
Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async {
return (await getAppDetailsFromPage(
standardUrl, additionalSettings))['appId'];
}
Future<Map<String, String?>> getAppDetailsFromPage(
String standardUrl, Map<String, dynamic> additionalSettings) async {
var res = await sourceRequest(standardUrl, additionalSettings);
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, additionalSettings);
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,
Map<String, dynamic> additionalSettings) async {
var res = await sourceRequest(apkUrl, additionalSettings);
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.${hosts[0]}/dwn/$finalUrlKey';
}
}

View File

@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
@ -5,12 +6,42 @@ import 'package:obtainium/providers/source_provider.dart';
class VLC extends AppSource {
VLC() {
host = 'videolan.org';
hosts = ['videolan.org'];
}
get dwUrlBase => 'https://get.${hosts[0]}/vlc-android/';
@override
Future<Map<String, String>?> getRequestHeaders(
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
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
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
Future<String?> getLatestVersion(
String standardUrl, Map<String, dynamic> additionalSettings) async {
Response res = await sourceRequest(dwUrlBase, additionalSettings);
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
@ -18,8 +49,8 @@ class VLC extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest(
'https://www.videolan.org/vlc/download-android.html');
Response res = await get(
Uri.parse('https://www.videolan.org/vlc/download-android.html'));
if (res.statusCode == 200) {
var dwUrlBase = 'get.videolan.org/vlc-android';
var dwLinks = parse(res.body)
@ -37,20 +68,9 @@ class VLC extends AppSource {
throw NoVersionError();
}
String? targetUrl = 'https://$dwUrlBase/$version/';
Response res2 = await sourceRequest(targetUrl);
List<String> apkUrls = [];
if (res2.statusCode == 200) {
apkUrls = parse(res2.body)
.querySelectorAll('a')
.map((e) => e.attributes['href']?.split('/').last)
.where((h) =>
h != null && h.isNotEmpty && h.toLowerCase().endsWith('.apk'))
.map((e) => targetUrl + e!)
.toList();
} else {
throw getObtainiumHttpError(res2);
}
var apkUrls = ['arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64']
.map((e) => '${targetUrl}VLC-Android-$version-$e.apk')
.toList();
return APKDetails(
version, getApkUrlsFromUrls(apkUrls), AppNames('VideoLAN', 'VLC'));
} else {
@ -59,9 +79,9 @@ class VLC extends AppSource {
}
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
Response res = await sourceRequest(apkUrl);
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
Map<String, dynamic> additionalSettings) async {
Response res = await sourceRequest(apkUrl, additionalSettings);
if (res.statusCode == 200) {
String? apkUrl =
parse(res.body).querySelector('#alt_link')?.attributes['href'];
@ -69,6 +89,20 @@ class VLC extends AppSource {
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);
}

View File

@ -5,25 +5,26 @@ import 'package:obtainium/providers/source_provider.dart';
class WhatsApp extends AppSource {
WhatsApp() {
host = 'whatsapp.com';
hosts = ['whatsapp.com'];
versionDetectionDisallowed = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
Response res = await sourceRequest('https://www.whatsapp.com/android');
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
Map<String, dynamic> additionalSettings) async {
Response res =
await sourceRequest('$standardUrl/android', additionalSettings);
if (res.statusCode == 200) {
var targetLinks = parse(res.body)
.querySelectorAll('a')
.map((e) => e.attributes['href'] ?? '')
.where((e) => e.isNotEmpty)
.where((e) =>
e.contains('content.whatsapp.net') && e.contains('WhatsApp.apk'))
.where((e) => e.contains('WhatsApp.apk'))
.toList();
if (targetLinks.isEmpty) {
throw NoAPKError();
@ -39,37 +40,16 @@ class WhatsApp extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest('https://www.whatsapp.com/android');
if (res.statusCode == 200) {
var targetElements = parse(res.body)
.querySelectorAll('p')
.where((element) => element.innerHtml.contains('Version '))
.toList();
if (targetElements.isEmpty) {
throw NoVersionError();
}
var vLines = targetElements[0]
.innerHtml
.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);
}
// This is a CDN link that is consistent per version
// But it has query params that change constantly
Uri apkUri = Uri.parse(await apkUrlPrefetchModifier(
standardUrl, standardUrl, additionalSettings));
var unusableApkUrl = '${apkUri.origin}/${apkUri.path}';
// So we use the param-less URL is a pseudo-version to add the app and check for updates
// See #357 for why we can't scrape the version number directly
// But we re-fetch the URL again with its latest query params at the actual download time
String version = unusableApkUrl.hashCode.toString();
return APKDetails(version, getApkUrlsFromUrls([unusableApkUrl]),
AppNames('Meta', 'WhatsApp'));
}
}

View File

@ -4,6 +4,7 @@ import 'package:hsluv/hsluv.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/providers/source_provider.dart';
abstract class GeneratedFormItem {
late String key;
@ -12,6 +13,7 @@ abstract class GeneratedFormItem {
late dynamic defaultValue;
List<dynamic> additionalValidators;
dynamic ensureType(dynamic val);
GeneratedFormItem clone();
GeneratedFormItem(this.key,
{this.label = 'Input',
@ -25,26 +27,38 @@ class GeneratedFormTextField extends GeneratedFormItem {
late int max;
late String? hint;
late bool password;
late TextInputType? textInputType;
GeneratedFormTextField(String key,
{String label = 'Input',
List<Widget> belowWidgets = const [],
String defaultValue = '',
List<String? Function(String? value)> additionalValidators = const [],
GeneratedFormTextField(super.key,
{super.label,
super.belowWidgets,
String super.defaultValue = '',
List<String? Function(String? value)> super.additionalValidators =
const [],
this.required = true,
this.max = 1,
this.hint,
this.password = false})
: super(key,
label: label,
belowWidgets: belowWidgets,
defaultValue: defaultValue,
additionalValidators: additionalValidators);
this.password = false,
this.textInputType});
@override
String ensureType(val) {
return val.toString();
}
@override
GeneratedFormTextField clone() {
return GeneratedFormTextField(key,
label: label,
belowWidgets: belowWidgets,
defaultValue: defaultValue,
additionalValidators: List.from(additionalValidators),
required: required,
max: max,
hint: hint,
password: password,
textInputType: textInputType);
}
}
class GeneratedFormDropdown extends GeneratedFormItem {
@ -52,42 +66,61 @@ class GeneratedFormDropdown extends GeneratedFormItem {
List<String>? disabledOptKeys;
GeneratedFormDropdown(
String key,
super.key,
this.opts, {
String label = 'Input',
List<Widget> belowWidgets = const [],
String defaultValue = '',
super.label,
super.belowWidgets,
String super.defaultValue = '',
this.disabledOptKeys,
List<String? Function(String? value)> additionalValidators = const [],
}) : super(key,
label: label,
belowWidgets: belowWidgets,
defaultValue: defaultValue,
additionalValidators: additionalValidators);
List<String? Function(String? value)> super.additionalValidators = const [],
});
@override
String ensureType(val) {
return val.toString();
}
@override
GeneratedFormDropdown clone() {
return GeneratedFormDropdown(
key,
opts?.map((e) => MapEntry(e.key, e.value)).toList(),
label: label,
belowWidgets: belowWidgets,
defaultValue: defaultValue,
disabledOptKeys:
disabledOptKeys != null ? List.from(disabledOptKeys!) : null,
additionalValidators: List.from(additionalValidators),
);
}
}
class GeneratedFormSwitch extends GeneratedFormItem {
bool disabled = false;
GeneratedFormSwitch(
String key, {
String label = 'Input',
List<Widget> belowWidgets = const [],
bool defaultValue = false,
List<String? Function(bool value)> additionalValidators = const [],
}) : super(key,
label: label,
belowWidgets: belowWidgets,
defaultValue: defaultValue,
additionalValidators: additionalValidators);
super.key, {
super.label,
super.belowWidgets,
bool super.defaultValue = false,
bool disabled = false,
List<String? Function(bool value)> super.additionalValidators = const [],
});
@override
bool ensureType(val) {
return val == true || val == 'true';
}
@override
GeneratedFormSwitch clone() {
return GeneratedFormSwitch(key,
label: label,
belowWidgets: belowWidgets,
defaultValue: defaultValue,
disabled: false,
additionalValidators: List.from(additionalValidators));
}
}
class GeneratedFormTagInput extends GeneratedFormItem {
@ -96,27 +129,36 @@ class GeneratedFormTagInput extends GeneratedFormItem {
late WrapAlignment alignment;
late String emptyMessage;
late bool showLabelWhenNotEmpty;
GeneratedFormTagInput(String key,
{String label = 'Input',
List<Widget> belowWidgets = const [],
Map<String, MapEntry<int, bool>> defaultValue = const {},
GeneratedFormTagInput(super.key,
{super.label,
super.belowWidgets,
Map<String, MapEntry<int, bool>> super.defaultValue = const {},
List<String? Function(Map<String, MapEntry<int, bool>> value)>
additionalValidators = const [],
super.additionalValidators = const [],
this.deleteConfirmationMessage,
this.singleSelect = false,
this.alignment = WrapAlignment.start,
this.emptyMessage = 'Input',
this.showLabelWhenNotEmpty = true})
: super(key,
label: label,
belowWidgets: belowWidgets,
defaultValue: defaultValue,
additionalValidators: additionalValidators);
this.showLabelWhenNotEmpty = true});
@override
Map<String, MapEntry<int, bool>> ensureType(val) {
return val is Map<String, MapEntry<int, bool>> ? val : {};
}
@override
GeneratedFormTagInput clone() {
return GeneratedFormTagInput(key,
label: label,
belowWidgets: belowWidgets,
defaultValue: defaultValue,
additionalValidators: List.from(additionalValidators),
deleteConfirmationMessage: deleteConfirmationMessage,
singleSelect: singleSelect,
alignment: alignment,
emptyMessage: emptyMessage,
showLabelWhenNotEmpty: showLabelWhenNotEmpty);
}
}
typedef OnValueChanges = void Function(
@ -133,6 +175,37 @@ class GeneratedForm extends StatefulWidget {
State<GeneratedForm> createState() => _GeneratedFormState();
}
List<List<GeneratedFormItem>> cloneFormItems(
List<List<GeneratedFormItem>> items) {
List<List<GeneratedFormItem>> clonedItems = [];
for (var row in items) {
List<GeneratedFormItem> clonedRow = [];
for (var it in row) {
clonedRow.add(it.clone());
}
clonedItems.add(clonedRow);
}
return clonedItems;
}
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>>
}
@override
GeneratedFormSubForm clone() {
return GeneratedFormSubForm(key, cloneFormItems(items),
label: label, belowWidgets: belowWidgets, defaultValue: defaultValue);
}
}
// Generates a color in the HSLuv (Pastel) color space
// https://pub.dev/documentation/hsluv/latest/hsluv/Hsluv/hpluvToRgb.html
Color generateRandomLightColor() {
@ -144,32 +217,44 @@ Color generateRandomLightColor() {
// Map from HPLuv color space to RGB, use constant saturation=100, lightness=70
final List<double> rgbValuesDbl = Hsluv.hpluvToRgb([hue, 100, 70]);
// 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]);
}
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> {
final _formKey = GlobalKey<FormState>();
Map<String, dynamic> values = {};
late List<List<Widget>> formInputs;
List<List<Widget>> rows = [];
String? initKey;
int forceUpdateKeyCount = 0;
// 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;
var valid = true;
for (int r = 0; r < widget.items.length; r++) {
for (int i = 0; i < widget.items[r].length; i++) {
if (formInputs[r][i] is TextFormField) {
var fieldState =
(formInputs[r][i].key as GlobalKey<FormFieldState>).currentState;
if (fieldState != null) {
valid = valid && fieldState.isValid;
}
valid = valid && validateTextField(formInputs[r][i] as TextFormField);
}
}
}
if (forceInvalid) {
valid = false;
}
widget.onValueChanges(returnValues, valid, isBuilding);
}
@ -190,6 +275,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
if (formItem is GeneratedFormTextField) {
final formFieldKey = GlobalKey<FormFieldState>();
return TextFormField(
keyboardType: formItem.textInputType,
obscureText: formItem.password,
autocorrect: !formItem.password,
enableSuggestions: !formItem.password,
@ -243,6 +329,17 @@ class _GeneratedFormState extends State<GeneratedForm> {
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 {
return Container(); // Some input types added in build
}
@ -264,6 +361,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
}
for (var r = 0; r < formInputs.length; r++) {
for (var e = 0; e < formInputs[r].length; e++) {
String fieldKey = widget.items[r][e].key;
if (widget.items[r][e] is GeneratedFormSwitch) {
formInputs[r][e] = Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -273,20 +371,55 @@ class _GeneratedFormState extends State<GeneratedForm> {
width: 8,
),
Switch(
value: values[widget.items[r][e].key],
onChanged: (value) {
setState(() {
values[widget.items[r][e].key] = value;
someValueChanged();
});
})
value: values[fieldKey],
onChanged:
(widget.items[r][e] as GeneratedFormSwitch).disabled
? null
: (value) {
setState(() {
values[fieldKey] = value;
someValueChanged();
});
})
],
);
} else if (widget.items[r][e] is GeneratedFormTagInput) {
onAddPressed() {
showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: widget.items[r][e].label,
items: [
[GeneratedFormTextField('label', label: tr('label'))]
]);
}).then((value) {
String? label = value?['label'];
if (label != null) {
setState(() {
var temp =
values[fieldKey] as Map<String, MapEntry<int, bool>>?;
temp ??= {};
if (temp[label] == null) {
var singleSelect =
(widget.items[r][e] as GeneratedFormTagInput)
.singleSelect;
var someSelected = temp.entries
.where((element) => element.value.value)
.isNotEmpty;
temp[label] = MapEntry(generateRandomLightColor().value,
!(someSelected && singleSelect));
values[fieldKey] = temp;
someValueChanged();
}
});
}
});
}
formInputs[r][e] =
Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
if ((values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?)
if ((values[fieldKey] as Map<String, MapEntry<int, bool>>?)
?.isNotEmpty ==
true &&
(widget.items[r][e] as GeneratedFormTagInput)
@ -309,17 +442,15 @@ class _GeneratedFormState extends State<GeneratedForm> {
(widget.items[r][e] as GeneratedFormTagInput).alignment,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
(values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?)
?.isEmpty ==
true
? Text(
(widget.items[r][e] as GeneratedFormTagInput)
.emptyMessage,
)
: const SizedBox.shrink(),
...(values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?)
// (values[fieldKey] as Map<String, MapEntry<int, bool>>?)
// ?.isEmpty ==
// true
// ? Text(
// (widget.items[r][e] as GeneratedFormTagInput)
// .emptyMessage,
// )
// : const SizedBox.shrink(),
...(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
?.entries
.map((e2) {
return Padding(
@ -332,11 +463,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
selected: e2.value.value,
onSelected: (value) {
setState(() {
(values[widget.items[r][e].key] as Map<String,
(values[fieldKey] as Map<String,
MapEntry<int, bool>>)[e2.key] =
MapEntry(
(values[widget.items[r][e].key] as Map<
String,
(values[fieldKey] as Map<String,
MapEntry<int, bool>>)[e2.key]!
.key,
value);
@ -344,22 +474,18 @@ class _GeneratedFormState extends State<GeneratedForm> {
as GeneratedFormTagInput)
.singleSelect &&
value == true) {
for (var key in (values[
widget.items[r][e].key]
for (var key in (values[fieldKey]
as Map<String, MapEntry<int, bool>>)
.keys) {
if (key != e2.key) {
(values[widget.items[r][e].key] as Map<
String,
MapEntry<int, bool>>)[key] =
MapEntry(
(values[widget.items[r][e].key]
as Map<
String,
MapEntry<int,
bool>>)[key]!
.key,
false);
(values[fieldKey] as Map<
String,
MapEntry<int,
bool>>)[key] = MapEntry(
(values[fieldKey] as Map<String,
MapEntry<int, bool>>)[key]!
.key,
false);
}
}
}
@ -369,38 +495,39 @@ class _GeneratedFormState extends State<GeneratedForm> {
));
}) ??
[const SizedBox.shrink()],
(values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?)
?.values
.where((e) => e.value)
.length == 1
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
?.values
.where((e) => e.value)
.length ==
1
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: IconButton(
onPressed: () {
setState(() {
var temp = values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>;
// get selected category str where bool is true
final oldEntry = temp.entries.firstWhere((entry) => entry.value.value);
// generate new color, ensure it is not the same
int newColor = oldEntry.value.key;
while(oldEntry.value.key == newColor) {
newColor = generateRandomLightColor().value;
}
// Update entry with new color, remain selected
temp.update(oldEntry.key, (old) => MapEntry(newColor, old.value));
values[widget.items[r][e].key] = temp;
someValueChanged();
});
},
icon: const Icon(Icons.format_color_fill_rounded),
visualDensity: VisualDensity.compact,
tooltip: tr('colour'),
))
padding: const EdgeInsets.symmetric(horizontal: 4),
child: IconButton(
onPressed: () {
setState(() {
var temp = values[fieldKey]
as Map<String, MapEntry<int, bool>>;
// get selected category str where bool is true
final oldEntry = temp.entries
.firstWhere((entry) => entry.value.value);
// generate new color, ensure it is not the same
int newColor = oldEntry.value.key;
while (oldEntry.value.key == newColor) {
newColor = generateRandomLightColor().value;
}
// Update entry with new color, remain selected
temp.update(oldEntry.key,
(old) => MapEntry(newColor, old.value));
values[fieldKey] = temp;
someValueChanged();
});
},
icon: const Icon(Icons.format_color_fill_rounded),
visualDensity: VisualDensity.compact,
tooltip: tr('colour'),
))
: const SizedBox.shrink(),
(values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?)
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
?.values
.where((e) => e.value)
.isNotEmpty ==
@ -411,10 +538,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
onPressed: () {
fn() {
setState(() {
var temp = values[widget.items[r][e].key]
var temp = values[fieldKey]
as Map<String, MapEntry<int, bool>>;
temp.removeWhere((key, value) => value.value);
values[widget.items[r][e].key] = temp;
values[fieldKey] = temp;
someValueChanged();
});
}
@ -446,52 +573,120 @@ class _GeneratedFormState extends State<GeneratedForm> {
tooltip: tr('remove'),
))
: const SizedBox.shrink(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: IconButton(
onPressed: () {
showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: widget.items[r][e].label,
items: [
[
GeneratedFormTextField('label',
label: tr('label'))
]
]);
}).then((value) {
String? label = value?['label'];
if (label != null) {
setState(() {
var temp = values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?;
temp ??= {};
if (temp[label] == null) {
var singleSelect = (widget.items[r][e]
as GeneratedFormTagInput)
.singleSelect;
var someSelected = temp.entries
.where((element) => element.value.value)
.isNotEmpty;
temp[label] = MapEntry(
generateRandomLightColor().value,
!(someSelected && singleSelect));
values[widget.items[r][e].key] = temp;
someValueChanged();
}
});
}
});
},
icon: const Icon(Icons.add),
visualDensity: VisualDensity.compact,
tooltip: tr('add'),
)),
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
?.isEmpty ==
true
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: TextButton.icon(
onPressed: onAddPressed,
icon: const Icon(Icons.add),
label: Text(
(widget.items[r][e] as GeneratedFormTagInput)
.label),
))
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: IconButton(
onPressed: onAddPressed,
icon: const Icon(Icons.add),
visualDensity: VisualDensity.compact,
tooltip: tr('add'),
)),
],
)
]);
} else if (widget.items[r][e] is GeneratedFormSubForm) {
List<Widget> subformColumn = [];
var compact = (widget.items[r][e] as GeneratedFormSubForm)
.items
.length ==
1 &&
(widget.items[r][e] as GeneratedFormSubForm).items[0].length == 1;
for (int i = 0; i < values[fieldKey].length; i++) {
var internalFormKey = ValueKey(generateRandomNumber(
values[fieldKey].length,
seed2: i,
seed3: forceUpdateKeyCount));
subformColumn.add(Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!compact)
const SizedBox(
height: 16,
),
if (!compact)
Text(
'${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})',
style: const TextStyle(fontWeight: FontWeight.bold),
),
GeneratedForm(
key: internalFormKey,
items: cloneFormItems(
(widget.items[r][e] as GeneratedFormSubForm).items)
.map((x) => x.map((y) {
y.defaultValue = values[fieldKey]?[i]?[y.key];
y.key = '${y.key.toString()},$internalFormKey';
return y;
}).toList())
.toList(),
onValueChanges: (values, valid, isBuilding) {
values = values.map(
(key, value) => MapEntry(key.split(',')[0], value));
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: const EdgeInsets.only(bottom: 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))),
],
),
));
formInputs[r][e] = Column(children: subformColumn);
}
}
}

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:android_package_installer/android_package_installer.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -32,7 +34,9 @@ class CredsNeededError 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 {
@ -65,30 +69,43 @@ class NotImplementedError 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);
add(String appId, String string) {
var tempIds = content.remove(string);
add(String appId, dynamic error, {String? appName}) {
if (error is SocketException) {
error = error.message;
}
rawErrors[appId] = error;
var string = error.toString();
var tempIds = idsByErrorString.remove(string);
tempIds ??= [];
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
String toString() {
String finalString = '';
for (var e in content.keys) {
finalString += '$e: ${content[e].toString()}\n\n';
}
return finalString;
}
String toString() => idsByErrorString.entries
.map((e) => errorsAppsString(e.key, e.value))
.join('\n\n');
}
showError(dynamic e, BuildContext context) {
showMessage(dynamic e, BuildContext context, {bool isError = 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)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toString())),
@ -100,8 +117,8 @@ showError(dynamic e, BuildContext context) {
return AlertDialog(
scrollable: true,
title: Text(e is MultiAppMultiError
? tr('someErrors')
: tr('unexpectedError')),
? tr(isError ? 'someErrors' : 'updates')
: tr(isError ? 'unexpectedError' : 'unknown')),
content: GestureDetector(
onLongPress: () {
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) {
return list.length == 2
? '${list[0]} ${tr('and')} ${list[1]}'

View File

@ -1,10 +1,7 @@
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/pages/home.dart';
import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/logs_provider.dart';
@ -15,22 +12,16 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import 'package:dynamic_color/dynamic_color.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';
// ignore: implementation_imports
import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.13.23';
const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
const int bgUpdateCheckAlarmId = 666;
List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('en'), 'English'),
MapEntry(Locale('zh'), '汉语'),
MapEntry(Locale('zh'), '简体中文'),
MapEntry(Locale('it'), 'Italiano'),
MapEntry(Locale('ja'), '日本語'),
MapEntry(Locale('hu'), 'Magyar'),
@ -39,11 +30,18 @@ List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('fr'), 'Français'),
MapEntry(Locale('es'), 'Español'),
MapEntry(Locale('pl'), 'Polski'),
MapEntry(Locale('ru'), 'Русский язык'),
MapEntry(Locale('ru'), 'Русский'),
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 localeDir = 'assets/translations';
var fdroid = false;
final globalNavigatorKey = GlobalKey<NavigatorState>();
@ -73,86 +71,16 @@ Future<void> loadTranslations() async {
}
@pragma('vm:entry-point')
Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
await loadTranslations();
LogsProvider logs = LogsProvider();
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 ClientException) {
var remainingMinutes = e is RateLimitError ? e.remainingMinutes : 15;
logs.add(
plural('bgUpdateGotErrorRetryInMinutes', remainingMinutes, args: [
e is ClientException
? '${(e).message}, ${e.uri?.path}'
: 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);
void backgroundFetchHeadlessTask(HeadlessTask task) async {
String taskId = task.taskId;
bool isTimeout = task.timeout;
if (isTimeout) {
print('BG update task timed out.');
BackgroundFetch.finish(taskId);
return;
}
await bgUpdateCheck(taskId, null);
BackgroundFetch.finish(taskId);
}
void main() async {
@ -172,7 +100,6 @@ void main() async {
);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
}
await AndroidAlarmManager.initialize();
runApp(MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AppsProvider()),
@ -187,6 +114,7 @@ void main() async {
useOnlyLangCode: true,
child: const Obtainium()),
));
BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
}
var defaultThemeColour = Colors.deepPurple;
@ -201,6 +129,33 @@ class Obtainium extends StatefulWidget {
class _ObtainiumState extends State<Obtainium> {
var existingUpdateInterval = -1;
@override
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
await BackgroundFetch.configure(
BackgroundFetchConfig(
minimumFetchInterval: 15,
stopOnTerminate: false,
startOnBoot: true,
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
Widget build(BuildContext context) {
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
@ -212,23 +167,35 @@ class _ObtainiumState extends State<Obtainium> {
} else {
bool isFirstRun = settingsProvider.checkAndFlipFirstRun();
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
Permission.notification.request();
appsProvider.saveApps([
App(
obtainiumId,
'https://github.com/ImranR98/Obtainium',
'ImranR98',
'Obtainium',
currentReleaseTag,
currentReleaseTag,
[],
0,
{'includePrereleases': true},
null,
false)
], onlyIfExists: false);
if (!fdroid) {
getInstalledInfo(obtainiumId).then((value) {
if (value?.versionName != null) {
appsProvider.saveApps([
App(
obtainiumId,
obtainiumUrl,
'ImranR98',
'Obtainium',
value!.versionName,
value.versionName!,
[],
0,
{
'versionDetection': true,
'apkFilterRegEx': 'fdroid',
'invertAPKFilter': true
},
null,
false)
], onlyIfExists: false);
}
}).catchError((err) {
print(err);
});
}
}
if (!supportedLocales
.map((e) => e.key.languageCode)
@ -238,24 +205,6 @@ class _ObtainiumState extends State<Obtainium> {
context.locale.languageCode)) {
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(
@ -292,14 +241,18 @@ class _ObtainiumState extends State<Obtainium> {
colorScheme: settingsProvider.theme == ThemeSettings.dark
? darkColorScheme
: lightColorScheme,
fontFamily: 'Metropolis'),
fontFamily:
settingsProvider.useSystemFont ? 'SystemFont' : 'Metropolis'),
darkTheme: ThemeData(
useMaterial3: true,
colorScheme: settingsProvider.theme == ThemeSettings.light
? lightColorScheme
: darkColorScheme,
fontFamily: 'Metropolis'),
home: const HomePage());
fontFamily:
settingsProvider.useSystemFont ? 'SystemFont' : 'Metropolis'),
home: Shortcuts(shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
}, child: const HomePage()));
});
}
}

6
lib/main_fdroid.dart Normal file
View File

@ -0,0 +1,6 @@
import 'main.dart' as m;
void main() async {
m.fdroid = true;
m.main();
}

View File

@ -15,8 +15,10 @@ class GitHubStars implements MassAppUrlSource {
Future<Map<String, List<String>>> getOnePageOfUserStarredUrlsWithDescriptions(
String username, int page) async {
Response res = await get(Uri.parse(
'https://${await GitHub().getCredentialPrefixIfAny({})}api.github.com/users/$username/starred?per_page=100&page=$page'));
Response res = await get(
Uri.parse(
'https://api.github.com/users/$username/starred?per_page=100&page=$page'),
headers: await GitHub().getRequestHeaders({}));
if (res.statusCode == 200) {
Map<String, List<String>> urlsWithDescriptions = {};
for (var e in (jsonDecode(res.body) as List<dynamic>)) {

View File

@ -11,6 +11,7 @@ import 'package:obtainium/pages/app.dart';
import 'package:obtainium/pages/import_export.dart';
import 'package:obtainium/pages/settings.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/source_provider.dart';
import 'package:provider/provider.dart';
@ -20,10 +21,10 @@ class AddAppPage extends StatefulWidget {
const AddAppPage({super.key});
@override
State<AddAppPage> createState() => _AddAppPageState();
State<AddAppPage> createState() => AddAppPageState();
}
class _AddAppPageState extends State<AddAppPage> {
class AddAppPageState extends State<AddAppPage> {
bool gettingAppInfo = false;
bool searching = false;
@ -35,57 +36,73 @@ class _AddAppPageState extends State<AddAppPage> {
bool additionalSettingsValid = true;
bool inferAppIdIfOptional = true;
List<String> pickedCategories = [];
int searchnum = 0;
int urlInputKey = 0;
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?.hosts.isNotEmpty == true
? pickedSource?.hosts[0]
: null;
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?.hosts[0])) {
pickedSource = source;
additionalSettings = source != null
? getDefaultValuesFromFormItems(
source.combinedAppSpecificSettingFormItems)
: {};
additionalSettingsValid = source != null
? !sourceProvider.ifRequiredAppSpecificSettingsExist(source)
: true;
inferAppIdIfOptional = true;
}
});
}
}
@override
Widget build(BuildContext context) {
AppsProvider appsProvider = context.read<AppsProvider>();
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
NotificationsProvider notificationsProvider =
context.read<NotificationsProvider>();
bool doingSomething = gettingAppInfo || searching;
changeUserInput(String input, bool valid, bool isBuilding,
{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,
{bool ignoreHideSetting = false}) async {
var useTrackOnly = userPickedTrackOnly || pickedSource!.enforceTrackOnly;
@ -118,8 +135,7 @@ class _AddAppPageState extends State<AddAppPage> {
getReleaseDateAsVersionConfirmationIfNeeded(
bool userPickedTrackOnly) async {
return (!(additionalSettings['versionDetection'] ==
'releaseDateAsVersion' &&
return (!(additionalSettings['releaseDateAsVersion'] == true &&
// ignore: use_build_context_synchronously
await showDialog(
context: context,
@ -145,13 +161,12 @@ class _AddAppPageState extends State<AddAppPage> {
userPickedTrackOnly))) {
var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly;
app = await sourceProvider.getApp(
pickedSource!, userInput, additionalSettings,
pickedSource!, userInput.trim(), additionalSettings,
trackOnlyOverride: trackOnly,
overrideSource: pickedSourceOverride,
inferAppIdIfOptional: inferAppIdIfOptional);
// Only download the APK here if you need to for the package ID
if (sourceProvider.isTempId(app) &&
app.additionalSettings['trackOnly'] != true) {
if (isTempId(app) && app.additionalSettings['trackOnly'] != true) {
// ignore: use_build_context_synchronously
var apkUrl = await appsProvider.confirmApkUrl(app, context);
if (apkUrl == null) {
@ -161,7 +176,8 @@ class _AddAppPageState extends State<AddAppPage> {
app.apkUrls.map((e) => e.value).toList().indexOf(apkUrl.value);
// ignore: use_build_context_synchronously
var downloadedArtifact = await appsProvider.downloadApp(
app, globalNavigatorKey.currentContext);
app, globalNavigatorKey.currentContext,
notificationsProvider: notificationsProvider);
DownloadedApk? downloadedFile;
DownloadedXApkDir? downloadedDir;
if (downloadedArtifact is DownloadedApk) {
@ -175,8 +191,7 @@ class _AddAppPageState extends State<AddAppPage> {
throw ObtainiumError(tr('appAlreadyAdded'));
}
if (app.additionalSettings['trackOnly'] == true ||
app.additionalSettings['versionDetection'] !=
'standardVersionDetection') {
app.additionalSettings['versionDetection'] != true) {
app.installedVersion = app.latestVersion;
}
app.categories = pickedCategories;
@ -202,7 +217,7 @@ class _AddAppPageState extends State<AddAppPage> {
children: [
Expanded(
child: GeneratedForm(
key: Key(searchnum.toString()),
key: Key(urlInputKey.toString()),
items: [
[
GeneratedFormTextField('appSourceURL',
@ -251,56 +266,82 @@ class _AddAppPageState extends State<AddAppPage> {
],
);
runSearch() async {
runSearch({bool filtered = true}) async {
setState(() {
searching = true;
});
var sourceStrings = <String, List<String>>{};
sourceProvider.sources
.where((e) => e.canSearch && !e.excludeFromMassSearch)
.forEach((s) {
sourceStrings[s.name] = [s.name];
});
try {
var results = await Future.wait(
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>?>(
var searchSources = await showDialog<List<String>?>(
context: context,
builder: (BuildContext ctx) {
return UrlSelectionModal(
urlsWithDescriptions: res,
selectedByDefault: false,
onlyOneSelectionAllowed: true,
return SelectionModal(
title: tr('selectX', args: [plural('source', 2)]),
entries: sourceStrings,
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) {
showError(e, context);
@ -455,40 +496,74 @@ class _AddAppPageState extends State<AddAppPage> {
],
);
Widget getSourcesListWidget() => Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
Widget getSourcesListWidget() => Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
const SizedBox(
height: 48,
),
Text(
tr('supportedSourcesBelow'),
),
const SizedBox(
height: 8,
),
...sourceProvider.sources
.map((e) => GestureDetector(
onTap: e.host != null
? () {
launchUrlString('https://${e.host}',
mode: LaunchMode.externalApplication);
}
: null,
child: Text(
'${e.name}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
style: TextStyle(
decoration: e.host != null
? TextDecoration.underline
: TextDecoration.none,
fontStyle: FontStyle.italic),
)))
.toList()
]);
GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) {
return GeneratedFormModal(
singleNullReturnButton: tr('ok'),
title: tr('supportedSources'),
items: const [],
additionalWidgets: [
...sourceProvider.sources.map(
(e) => Padding(
padding:
const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector(
onTap: e.hosts.isNotEmpty
? () {
launchUrlString(
'https://${e.hosts[0]}',
mode: LaunchMode
.externalApplication);
}
: null,
child: Text(
'${e.name}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
style: TextStyle(
decoration: e.hosts.isNotEmpty
? TextDecoration.underline
: TextDecoration.none),
))),
),
const SizedBox(
height: 16,
),
Text(
'${tr('note')}:',
style:
const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(
height: 4,
),
Text(tr('selfHostedNote',
args: [tr('overrideSource')])),
],
);
},
);
},
child: Text(
tr('supportedSources'),
style: const TextStyle(
fontWeight: FontWeight.bold,
decoration: TextDecoration.underline,
fontStyle: FontStyle.italic),
))
],
),
);
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
bottomNavigationBar:
pickedSource == null ? getSourcesListWidget() : null,
body: CustomScrollView(shrinkWrap: true, slivers: <Widget>[
CustomAppBar(title: tr('addApp')),
SliverToBoxAdapter(
@ -520,16 +595,7 @@ class _AddAppPageState extends State<AddAppPage> {
: const SizedBox();
},
future: pickedSource?.getSourceNote()),
const SizedBox(
height: 16,
),
if (pickedSource != null)
getAdditionalOptsCol()
else
getSourcesListWidget(),
const SizedBox(
height: 8,
),
if (pickedSource != null) getAdditionalOptsCol(),
])),
)
]));

View File

@ -1,7 +1,6 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
@ -24,16 +23,35 @@ class AppPage extends StatefulWidget {
class _AppPageState extends State<AppPage> {
AppInMemory? prevApp;
bool updating = false;
@override
Widget build(BuildContext context) {
var appsProvider = context.watch<AppsProvider>();
var settingsProvider = context.watch<SettingsProvider>();
getUpdate(String id) {
appsProvider.checkUpdate(id).catchError((e) {
showError(e, context);
return null;
});
getUpdate(String id, {bool resetVersion = false}) async {
try {
setState(() {
updating = true;
});
await appsProvider.checkUpdate(id);
if (resetVersion) {
appsProvider.apps[id]?.app.additionalSettings['versionDetection'] =
true;
if (appsProvider.apps[id]?.app.installedVersion != null) {
appsProvider.apps[id]?.app.installedVersion =
appsProvider.apps[id]?.app.latestVersion;
}
appsProvider.saveApps([appsProvider.apps[id]!.app]);
}
} catch (err) {
// ignore: use_build_context_synchronously
showError(err, context);
} finally {
setState(() {
updating = false;
});
}
}
bool areDownloadsRunning = appsProvider.areDownloadsRunning();
@ -54,18 +72,145 @@ class _AppPageState extends State<AppPage> {
var trackOnly = app?.app.additionalSettings['trackOnly'] == true;
bool isVersionDetectionStandard =
app?.app.additionalSettings['versionDetection'] ==
'standardVersionDetection';
app?.app.additionalSettings['versionDetection'] == true;
bool installedVersionIsEstimate = trackOnly ||
(app?.app.installedVersion != null &&
app?.app.additionalSettings['versionDetection'] ==
'noVersionDetection');
app?.app.additionalSettings['versionDetection'] != true);
getInfoColumn() => Column(
getInfoColumn() {
String versionLines = '';
bool installed = app?.app.installedVersion != null;
bool upToDate = app?.app.installedVersion == app?.app.latestVersion;
if (installed) {
versionLines = '${app?.app.installedVersion} ${tr('installed')}';
if (upToDate) {
versionLines += '/${tr('latest')}';
}
} else {
versionLines = tr('notInstalled');
}
if (!upToDate) {
versionLines += '\n${app?.app.latestVersion} ${tr('latest')}';
}
String infoLines = tr('lastUpdateCheckX', args: [
app?.app.lastUpdateCheck == null
? tr('never')
: '${app?.app.lastUpdateCheck?.toLocal()}'
]);
if (trackOnly) {
infoLines = '${tr('xIsTrackOnly', args: [tr('app')])}\n$infoLines';
}
if (installedVersionIsEstimate) {
infoLines = '${tr('pseudoVersionInUse')}\n$infoLines';
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 24),
child: Column(
children: [
const SizedBox(
height: 8,
),
Text(versionLines,
textAlign: TextAlign.start,
style: Theme.of(context)
.textTheme
.bodyLarge!
.copyWith(fontWeight: FontWeight.bold)),
app?.app.releaseDate == null
? const SizedBox.shrink()
: Text(
app!.app.releaseDate.toString(),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.labelSmall,
),
const SizedBox(
height: 8,
),
],
),
),
Text(
infoLines,
textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12),
),
const SizedBox(
height: 48,
),
CategoryEditorSelector(
alignment: WrapAlignment.center,
preselected: app?.app.categories != null
? app!.app.categories.toSet()
: {},
onSelected: (categories) {
if (app != null) {
app.app.categories = categories;
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),
),
)
],
),
],
);
}
getFullInfoColumn() => Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 20),
app?.icon != null
? Row(mainAxisAlignment: MainAxisAlignment.center, children: [
GestureDetector(
child: Image.memory(
app!.icon!,
height: 150,
gaplessPlayback: true,
),
onTap: () => pm.openApp(app.app.id),
)
])
: Container(),
const SizedBox(
height: 25,
),
Text(
app?.name ?? tr('app'),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.displayLarge,
),
Text(tr('byX', args: [app?.app.author ?? tr('unknown')]),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(
height: 24,
),
GestureDetector(
onTap: () {
if (app?.app.url != null) {
@ -82,117 +227,15 @@ class _AppPageState extends State<AppPage> {
child: Text(
app?.app.url ?? '',
textAlign: TextAlign.center,
style: const TextStyle(
style: Theme.of(context).textTheme.labelSmall!.copyWith(
decoration: TextDecoration.underline,
fontStyle: FontStyle.italic,
fontSize: 12),
fontStyle: FontStyle.italic),
)),
const SizedBox(
height: 32,
),
Column(
children: [
Text(
'${tr('latestVersionX', args: [
app?.app.latestVersion ?? tr('unknown')
])}\n${tr('installedVersionX', args: [
app?.app.installedVersion ?? tr('none')
])}${installedVersionIsEstimate ? '\n${tr('estimateInBrackets')}' : ''}',
textAlign: TextAlign.end,
style: Theme.of(context).textTheme.bodyLarge!,
),
],
),
if (app?.app.installedVersion != null &&
!isVersionDetectionStandard)
Column(
children: [
const SizedBox(
height: 16,
),
Text(
'${trackOnly ? '${tr('xIsTrackOnly', args: [
tr('app')
])}\n' : ''}${tr('noVersionDetection')}',
style: Theme.of(context).textTheme.labelSmall,
textAlign: TextAlign.center,
)
],
),
const SizedBox(
height: 32,
),
Text(
tr('lastUpdateCheckX', args: [
app?.app.lastUpdateCheck == null
? tr('never')
: '\n${app?.app.lastUpdateCheck?.toLocal()}'
]),
textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12),
),
const SizedBox(
height: 48,
),
CategoryEditorSelector(
alignment: WrapAlignment.center,
preselected: app?.app.categories != null
? app!.app.categories.toSet()
: {},
onSelected: (categories) {
if (app != null) {
app.app.categories = categories;
appsProvider.saveApps([app.app]);
}
}),
],
);
getFullInfoColumn() => Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 125),
app?.installedInfo != null
? Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Image.memory(
app!.installedInfo!.icon!,
height: 150,
gaplessPlayback: true,
)
])
: Container(),
const SizedBox(
height: 25,
),
Text(
app?.name ?? tr('app'),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.displayLarge,
),
Text(
tr('byX', args: [app?.app.author ?? tr('unknown')]),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(
height: 8,
),
Text(
app?.app.id ?? '',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.labelSmall,
),
app?.app.releaseDate == null
? const SizedBox.shrink()
: Text(
app!.app.releaseDate.toString(),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.labelSmall,
),
const SizedBox(
height: 32,
),
getInfoColumn(),
const SizedBox(height: 150)
],
@ -261,25 +304,6 @@ class _AppPageState extends State<AppPage> {
return row;
}).toList();
items = items.map((row) {
row = row.map((e) {
if (e.key == 'versionDetection' && e is GeneratedFormDropdown) {
e.disabledOptKeys ??= [];
if (app?.app.installedVersion != null &&
app?.app.additionalSettings['versionDetection'] !=
'releaseDateAsVersion' &&
!appsProvider.isVersionDetectionPossible(app)) {
e.disabledOptKeys!.add('standardVersionDetection');
}
if (app?.app.releaseDate == null) {
e.disabledOptKeys!.add('releaseDateAsVersion');
}
}
return e;
}).toList();
return row;
}).toList();
return GeneratedFormModal(
title: tr('additionalOptions'), items: items);
});
@ -292,58 +316,61 @@ class _AppPageState extends State<AppPage> {
if (source?.enforceTrackOnly == true) {
app.app.additionalSettings['trackOnly'] = true;
// ignore: use_build_context_synchronously
showError(tr('appsFromSourceAreTrackOnly'), context);
showMessage(tr('appsFromSourceAreTrackOnly'), context);
}
if (app.app.additionalSettings['versionDetection'] ==
'releaseDateAsVersion') {
if (originalSettings['versionDetection'] != 'releaseDateAsVersion') {
if (app.app.releaseDate != null) {
bool isUpdated =
app.app.installedVersion == app.app.latestVersion;
app.app.latestVersion =
app.app.releaseDate!.microsecondsSinceEpoch.toString();
if (isUpdated) {
app.app.installedVersion = app.app.latestVersion;
}
var versionDetectionEnabled =
app.app.additionalSettings['versionDetection'] == true &&
originalSettings['versionDetection'] != true;
var releaseDateVersionEnabled =
app.app.additionalSettings['releaseDateAsVersion'] == true &&
originalSettings['releaseDateAsVersion'] != true;
var releaseDateVersionDisabled =
app.app.additionalSettings['releaseDateAsVersion'] != true &&
originalSettings['releaseDateAsVersion'] == true;
if (releaseDateVersionEnabled) {
if (app.app.releaseDate != null) {
bool isUpdated = app.app.installedVersion == app.app.latestVersion;
app.app.latestVersion =
app.app.releaseDate!.microsecondsSinceEpoch.toString();
if (isUpdated) {
app.app.installedVersion = app.app.latestVersion;
}
}
} else if (originalSettings['versionDetection'] ==
'releaseDateAsVersion') {
} else if (releaseDateVersionDisabled) {
app.app.installedVersion =
app.installedInfo?.versionName ?? app.app.installedVersion;
}
if (versionDetectionEnabled) {
app.app.additionalSettings['versionDetection'] = true;
app.app.additionalSettings['releaseDateAsVersion'] = false;
}
appsProvider.saveApps([app.app]).then((value) {
getUpdate(app.app.id);
getUpdate(app.app.id, resetVersion: versionDetectionEnabled);
});
}
}
getResetInstallStatusButton() => TextButton(
onPressed: app?.app == null
? null
: () {
app!.app.installedVersion = null;
appsProvider.saveApps([app.app]);
},
child: Text(
tr('resetInstallStatus'),
textAlign: TextAlign.center,
));
getInstallOrUpdateButton() => TextButton(
onPressed: (app?.app.installedVersion == null ||
onPressed: !updating &&
(app?.app.installedVersion == null ||
app?.app.installedVersion != app?.app.latestVersion) &&
!areDownloadsRunning
? () async {
try {
HapticFeedback.heavyImpact();
var res = await appsProvider.downloadAndInstallLatestApps(
app?.app.id != null ? [app!.app.id] : [],
globalNavigatorKey.currentContext);
app?.app.id != null ? [app!.app.id] : [],
globalNavigatorKey.currentContext,
);
if (res.isNotEmpty && !trackOnly) {
// ignore: use_build_context_synchronously
showMessage(tr('appsUpdated'), context);
}
if (res.isNotEmpty && mounted) {
Navigator.of(context).pop();
}
} catch (e) {
// ignore: use_build_context_synchronously
showError(e, context);
}
}
@ -367,20 +394,10 @@ class _AppPageState extends State<AppPage> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (app?.app.installedVersion != null &&
app?.app.installedVersion != app?.app.latestVersion &&
!isVersionDetectionStandard &&
!trackOnly)
IconButton(
onPressed: app?.downloadProgress != null
? null
: showMarkUpdatedDialog,
tooltip: tr('markUpdated'),
icon: const Icon(Icons.done)),
if (source != null &&
source.combinedAppSpecificSettingFormItems.isNotEmpty)
IconButton(
onPressed: app?.downloadProgress != null
onPressed: app?.downloadProgress != null || updating
? null
: () async {
var values =
@ -422,17 +439,33 @@ class _AppPageState extends State<AppPage> {
},
icon: const Icon(Icons.more_horiz),
tooltip: tr('more')),
if (app?.app.installedVersion != null &&
app?.app.installedVersion != app?.app.latestVersion &&
!isVersionDetectionStandard &&
!trackOnly)
IconButton(
onPressed: app?.downloadProgress != null || updating
? null
: showMarkUpdatedDialog,
tooltip: tr('markUpdated'),
icon: const Icon(Icons.done)),
if ((!isVersionDetectionStandard || trackOnly) &&
app?.app.installedVersion != null &&
app?.app.installedVersion == app?.app.latestVersion)
IconButton(
onPressed: app?.app == null || updating
? null
: () {
app!.app.installedVersion = null;
appsProvider.saveApps([app.app]);
},
icon: const Icon(Icons.restore_rounded),
tooltip: tr('resetInstallStatus')),
const SizedBox(width: 16.0),
Expanded(
child: (!isVersionDetectionStandard || trackOnly) &&
app?.app.installedVersion != null &&
app?.app.installedVersion ==
app?.app.latestVersion
? getResetInstallStatusButton()
: getInstallOrUpdateButton()),
Expanded(child: getInstallOrUpdateButton()),
const SizedBox(width: 16.0),
IconButton(
onPressed: app?.downloadProgress != null
onPressed: app?.downloadProgress != null || updating
? null
: () {
appsProvider
@ -458,8 +491,17 @@ class _AppPageState extends State<AppPage> {
],
));
appScreenAppBar() => AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
);
return Scaffold(
appBar: settingsProvider.showAppWebpage ? AppBar() : null,
appBar: settingsProvider.showAppWebpage ? AppBar() : appScreenAppBar(),
backgroundColor: Theme.of(context).colorScheme.surface,
body: RefreshIndicator(
child: settingsProvider.showAppWebpage

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -68,7 +70,7 @@ class AppsPageState extends State<AppsPage> {
refreshingSince = DateTime.now();
});
return appsProvider.checkUpdates().catchError((e) {
showError(e, context);
showError(e is Map ? e['errors'] : e, context);
return <App>[];
}).whenComplete(() {
setState(() {
@ -356,6 +358,16 @@ class AppsPageState extends State<AppsPage> {
String? changesUrl =
appSource.changeLogPageFromStandardUrl(listedApps[appIndex].app.url);
String? changeLog = listedApps[appIndex].app.changeLog;
if (changeLog?.split('\n').length == 1) {
if (RegExp(
'(http|ftp|https)://([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?')
.hasMatch(changeLog!)) {
if (changesUrl == null) {
changesUrl = changeLog;
changeLog = null;
}
}
}
return (changeLog == null && changesUrl == null)
? null
: () {
@ -393,9 +405,9 @@ class AppsPageState extends State<AppsPage> {
}
getAppIcon(int appIndex) {
return listedApps[appIndex].installedInfo != null
return listedApps[appIndex].icon != null
? Image.memory(
listedApps[appIndex].installedInfo!.icon!,
listedApps[appIndex].icon!,
gaplessPlayback: true,
)
: Row(
@ -419,7 +431,7 @@ class AppsPageState extends State<AppsPage> {
}
getVersionText(int appIndex) {
return '${listedApps[appIndex].app.installedVersion ?? tr('notInstalled')}${listedApps[appIndex].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBrackets')}' : ''}';
return '${listedApps[appIndex].app.installedVersion ?? tr('notInstalled')}${listedApps[appIndex].app.additionalSettings['trackOnly'] == true ? ' ${tr('pseudoVersion')}' : ''}';
}
getChangesButtonString(int appIndex, bool hasChangeLogFn) {
@ -448,47 +460,56 @@ class AppsPageState extends State<AppsPage> {
: const SizedBox.shrink(),
GestureDetector(
onTap: showChangesFn,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Row(mainAxisSize: MainAxisSize.min, children: [
Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width / 4),
child: Text(getVersionText(index),
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end)),
]),
Row(
mainAxisSize: MainAxisSize.min,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: settingsProvider.highlightTouchTargets &&
showChangesFn != null
? (Theme.of(context).brightness == Brightness.light
? Theme.of(context).primaryColor
: Theme.of(context).primaryColorLight)
.withAlpha(20)
: null),
padding: settingsProvider.highlightTouchTargets
? const EdgeInsetsDirectional.fromSTEB(12, 0, 12, 0)
: const EdgeInsetsDirectional.fromSTEB(24, 0, 0, 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
getChangesButtonString(index, showChangesFn != null),
style: TextStyle(
fontStyle: FontStyle.italic,
decoration: showChangesFn != null
? TextDecoration.underline
: TextDecoration.none),
)
Row(mainAxisSize: MainAxisSize.min, children: [
Container(
constraints: BoxConstraints(
maxWidth:
MediaQuery.of(context).size.width / 4),
child: Text(getVersionText(index),
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 =
Theme.of(context).colorScheme.background.withAlpha(0).value;
List<double> stops = [
...listedApps[index]
.app
.categories
.asMap()
.entries
.map((e) =>
((e.key / (listedApps[index].app.categories.length - 1))))
.toList(),
...listedApps[index].app.categories.asMap().entries.map(
(e) => ((e.key / (listedApps[index].app.categories.length - 1)))),
1
];
if (stops.length == 2) {
@ -501,13 +522,9 @@ class AppsPageState extends State<AppsPage> {
begin: const Alignment(-1, 0),
end: const Alignment(-0.97, 0),
colors: [
...listedApps[index]
.app
.categories
.map((e) =>
Color(settingsProvider.categories[e] ?? transparent)
.withAlpha(255))
.toList(),
...listedApps[index].app.categories.map((e) =>
Color(settingsProvider.categories[e] ?? transparent)
.withAlpha(255)),
Color(transparent)
])),
child: ListTile(
@ -551,7 +568,7 @@ class AppsPageState extends State<AppsPage> {
.toInt()
.toString()
])
: tr('pleaseWait'),
: tr('installing'),
textAlign: (listedApps[index].downloadProgress! >= 0)
? TextAlign.start
: TextAlign.end,
@ -643,15 +660,15 @@ class AppsPageState extends State<AppsPage> {
label: tr('installX', args: [
plural('apps', newInstallIdsAllOrSelected.length)
]),
defaultValue: existingUpdateIdsAllOrSelected.isNotEmpty));
defaultValue: existingUpdateIdsAllOrSelected.isEmpty));
}
if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) {
formItems.add(GeneratedFormSwitch('trackonlies',
label: tr('markXTrackOnlyAsUpdated', args: [
plural('apps', trackOnlyUpdateIdsAllOrSelected.length)
]),
defaultValue: existingUpdateIdsAllOrSelected.isNotEmpty ||
newInstallIdsAllOrSelected.isNotEmpty));
defaultValue: existingUpdateIdsAllOrSelected.isEmpty &&
newInstallIdsAllOrSelected.isEmpty));
}
showDialog<Map<String, dynamic>?>(
context: context,
@ -684,11 +701,14 @@ class AppsPageState extends State<AppsPage> {
}
appsProvider
.downloadAndInstallLatestApps(
toInstall, globalNavigatorKey.currentContext,
settingsProvider: settingsProvider)
toInstall, globalNavigatorKey.currentContext)
.catchError((e) {
showError(e, context);
return <String>[];
}).then((value) {
if (value.isNotEmpty && shouldInstallUpdates) {
showMessage(tr('appsUpdated'), context);
}
});
}
});
@ -815,7 +835,7 @@ class AppsPageState extends State<AppsPage> {
items: const [],
initValid: true,
message: tr('installStatusOfXWillBeResetExplanation',
args: [plural('app', selectedAppIds.length)]),
args: [plural('apps', selectedAppIds.length)]),
);
});
if (values != null) {
@ -867,16 +887,38 @@ class AppsPageState extends State<AppsPage> {
}
urls = urls.substring(0, urls.length - 1);
Share.share(urls,
subject: tr('selectedAppURLsFromObtainium'));
subject: 'Obtainium - ${tr('appsString')}');
Navigator.of(context).pop();
},
tooltip: tr('shareSelectedAppURLs'),
icon: const Icon(Icons.share),
icon: const Icon(Icons.share_rounded),
),
IconButton(
onPressed: resetSelectedAppsInstallStatuses,
tooltip: tr('resetInstallStatus'),
icon: const Icon(Icons.restore_page_outlined),
onPressed: selectedAppIds.isEmpty
? null
: () {
String urls =
'<p>${tr('customLinkMessage')}:</p>\n\n<ul>\n';
for (var a in selectedApps) {
urls +=
' <li><a href="obtainium://app/${Uri.encodeComponent(jsonEncode({
'id': a.id,
'url': a.url,
'author': a.author,
'name': a.name,
'preferredApkIndex':
a.preferredApkIndex,
'additionalSettings':
jsonEncode(a.additionalSettings)
}))}">${a.name}</a></li>\n';
}
urls +=
'</ul>\n\n<p><a href="$obtainiumUrl">${tr('about')}</a></p>';
Share.share(urls,
subject: 'Obtainium - ${tr('appsString')}');
},
tooltip: tr('shareAppConfigLinks'),
icon: const Icon(Icons.ios_share),
),
]),
),
@ -963,10 +1005,8 @@ class AppsPageState extends State<AppsPage> {
defaultValue: filter.sourceFilter,
[
MapEntry('', tr('none')),
...sourceProvider.sources
.map((e) =>
MapEntry(e.runtimeType.toString(), e.name))
.toList()
...sourceProvider.sources.map(
(e) => MapEntry(e.runtimeType.toString(), e.name))
])
]
],

View File

@ -1,7 +1,12 @@
import 'dart:async';
import 'package:animations/animations.dart';
import 'package:app_links/app_links.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/apps.dart';
import 'package:obtainium/pages/import_export.dart';
@ -30,58 +35,144 @@ class _HomePageState extends State<HomePage> {
bool isReversing = false;
int prevAppCount = -1;
bool prevIsLoading = true;
late AppLinks _appLinks;
StreamSubscription<Uri>? _linkSubscription;
bool isLinkActivity = false;
List<NavigationPageItem> pages = [
NavigationPageItem(tr('appsString'), Icons.apps,
AppsPage(key: GlobalKey<AppsPageState>())),
NavigationPageItem(tr('addApp'), Icons.add, const AddAppPage()),
NavigationPageItem(
tr('addApp'), Icons.add, AddAppPage(key: GlobalKey<AddAppPageState>())),
NavigationPageItem(
tr('importExport'), Icons.import_export, const ImportExportPage()),
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
Widget build(BuildContext context) {
AppsProvider appsProvider = context.watch<AppsProvider>();
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 &&
prevAppCount >= 0 &&
appsProvider.apps.length > prevAppCount &&
selectedIndexHistory.isNotEmpty &&
selectedIndexHistory.last == 1) {
selectedIndexHistory.last == 1 &&
!isLinkActivity) {
switchToPage(0);
}
prevAppCount = appsProvider.apps.length;
@ -129,6 +220,11 @@ class _HomePageState extends State<HomePage> {
),
),
onWillPop: () async {
if (isLinkActivity &&
selectedIndexHistory.length == 1 &&
selectedIndexHistory.last == 1) {
return true;
}
setIsReversing(selectedIndexHistory.length >= 2
? selectedIndexHistory.reversed.toList()[1]
: 0);
@ -143,4 +239,10 @@ class _HomePageState extends State<HomePage> {
?.clearSelected();
});
}
@override
void dispose() {
super.dispose();
_linkSubscription?.cancel();
}
}

View File

@ -4,6 +4,7 @@ import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/generated_form.dart';
import 'package:obtainium/components/generated_form_modal.dart';
@ -28,8 +29,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
@override
Widget build(BuildContext context) {
SourceProvider sourceProvider = SourceProvider();
var appsProvider = context.read<AppsProvider>();
var settingsProvider = context.read<SettingsProvider>();
var appsProvider = context.watch<AppsProvider>();
var settingsProvider = context.watch<SettingsProvider>();
var outlineButtonStyle = ButtonStyle(
shape: MaterialStateProperty.all(
@ -81,7 +82,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
});
appsProvider.addAppsByURL(urls).then((errors) {
if (errors.isEmpty) {
showError(tr('importedX', args: [plural('apps', urls.length)]),
showMessage(tr('importedX', args: [plural('apps', urls.length)]),
context);
} else {
showDialog(
@ -102,10 +103,17 @@ class _ImportExportPageState extends State<ImportExportPage> {
});
}
runObtainiumExport() {
runObtainiumExport({bool pickOnly = false}) async {
HapticFeedback.selectionClick();
appsProvider.exportApps().then((String path) {
showError(tr('exportedTo', args: [path]), context);
appsProvider
.export(
pickOnly:
pickOnly || (await settingsProvider.getExportDir()) == null,
sp: settingsProvider)
.then((String? result) {
if (result != null) {
showMessage(tr('exportedTo', args: [result]), context);
}
}).catchError((e) {
showError(e, context);
});
@ -124,7 +132,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
} catch (e) {
throw ObtainiumError(tr('invalidInput'));
}
appsProvider.importApps(data).then((value) {
appsProvider.import(data).then((value) {
var cats = settingsProvider.categories;
appsProvider.apps.forEach((key, value) {
for (var c in value.app.categories) {
@ -134,7 +142,11 @@ class _ImportExportPageState extends State<ImportExportPage> {
}
});
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 {
// User canceled the picker
@ -181,17 +193,30 @@ class _ImportExportPageState extends State<ImportExportPage> {
items: [
[
GeneratedFormTextField('searchQuery',
label: tr('searchQuery'))
label: tr('searchQuery'),
required: source.name != FDroidRepo().name)
],
...source.searchQuerySettingFormItems.map((e) => [e]),
[
GeneratedFormTextField('url',
label: source.hosts.isNotEmpty
? tr('overrideSource')
: plural('url', 1).substring(2),
defaultValue:
source.hosts.isNotEmpty ? source.hosts[0] : '',
required: true)
],
...source.searchQuerySettingFormItems.map((e) => [e])
],
);
});
if (values != null &&
(values['searchQuery'] as String?)?.isNotEmpty == true) {
if (values != null) {
setState(() {
importInProgress = true;
});
if (values['url'] != source.hosts[0]) {
source = sourceProvider.getSource(values['url'],
overrideSource: source.runtimeType.toString());
}
var urlsWithDescriptions = await source
.search(values['searchQuery'] as String, querySettings: values);
if (urlsWithDescriptions.isNotEmpty) {
@ -200,17 +225,19 @@ class _ImportExportPageState extends State<ImportExportPage> {
await showDialog<List<String>?>(
context: context,
builder: (BuildContext ctx) {
return UrlSelectionModal(
urlsWithDescriptions: urlsWithDescriptions,
return SelectionModal(
entries: urlsWithDescriptions,
selectedByDefault: false,
);
});
if (selectedUrls != null && selectedUrls.isNotEmpty) {
var errors = await appsProvider.addAppsByURL(selectedUrls);
var errors = await appsProvider.addAppsByURL(selectedUrls,
sourceOverride: source);
if (errors.isEmpty) {
// ignore: use_build_context_synchronously
showError(
tr('importedX', args: [plural('app', selectedUrls.length)]),
showMessage(
tr('importedX',
args: [plural('apps', selectedUrls.length)]),
context);
} else {
// ignore: use_build_context_synchronously
@ -259,15 +286,14 @@ class _ImportExportPageState extends State<ImportExportPage> {
await showDialog<List<String>?>(
context: context,
builder: (BuildContext ctx) {
return UrlSelectionModal(
urlsWithDescriptions: urlsWithDescriptions);
return SelectionModal(entries: urlsWithDescriptions);
});
if (selectedUrls != null) {
var errors = await appsProvider.addAppsByURL(selectedUrls);
if (errors.isEmpty) {
// ignore: use_build_context_synchronously
showError(
tr('importedX', args: [plural('app', selectedUrls.length)]),
showMessage(
tr('importedX', args: [plural('apps', selectedUrls.length)]),
context);
} else {
// ignore: use_build_context_synchronously
@ -290,6 +316,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(
backgroundColor: Theme.of(context).colorScheme.surface,
body: CustomScrollView(slivers: <Widget>[
@ -301,27 +332,102 @@ class _ImportExportPageState extends State<ImportExportPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Expanded(
child: TextButton(
style: outlineButtonStyle,
onPressed: appsProvider.apps.isEmpty ||
importInProgress
? null
: runObtainiumExport,
child: Text(tr('obtainiumExport')))),
const SizedBox(
width: 16,
),
Expanded(
child: TextButton(
style: outlineButtonStyle,
onPressed: importInProgress
? null
: runObtainiumImport,
child: Text(tr('obtainiumImport'))))
],
FutureBuilder(
future: settingsProvider.getExportDir(),
builder: (context, snapshot) {
return Column(
children: [
Row(
children: [
Expanded(
child: TextButton(
style: outlineButtonStyle,
onPressed: appsProvider.apps.isEmpty ||
importInProgress
? null
: () {
runObtainiumExport(pickOnly: true);
},
child: Text(tr('pickExportDir'),
textAlign: TextAlign.center),
)),
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)
const Column(
@ -341,6 +447,54 @@ class _ImportExportPageState extends State<ImportExportPage> {
const Divider(
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(
onPressed:
importInProgress ? null : urlListImport,
@ -356,39 +510,19 @@ class _ImportExportPageState extends State<ImportExportPage> {
)),
],
),
...sourceProvider.sources
.where((element) => element.canSearch)
.map((source) => Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 8),
TextButton(
onPressed: importInProgress
? null
: () {
runSourceSearch(source);
},
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(),
...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])))
])),
const Spacer(),
const Divider(
height: 32,
@ -399,7 +533,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
fontStyle: FontStyle.italic, fontSize: 12)),
const SizedBox(
height: 8,
)
),
],
)))
]));
@ -450,126 +584,191 @@ class _ImportErrorDialogState extends State<ImportErrorDialog> {
style: const TextStyle(fontStyle: FontStyle.italic),
)
]);
}).toList()
})
]),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(null);
},
child: Text(tr('okay')))
child: Text(tr('ok')))
],
);
}
}
// ignore: must_be_immutable
class UrlSelectionModal extends StatefulWidget {
UrlSelectionModal(
class SelectionModal extends StatefulWidget {
SelectionModal(
{super.key,
required this.urlsWithDescriptions,
required this.entries,
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;
List<String> deselectThese;
bool onlyOneSelectionAllowed;
bool titlesAreLinks;
@override
State<UrlSelectionModal> createState() => _UrlSelectionModalState();
State<SelectionModal> createState() => _SelectionModalState();
}
class _UrlSelectionModalState extends State<UrlSelectionModal> {
Map<MapEntry<String, List<String>>, bool> urlWithDescriptionSelections = {};
class _SelectionModalState extends State<SelectionModal> {
Map<MapEntry<String, List<String>>, bool> entrySelections = {};
String filterRegex = '';
@override
void initState() {
super.initState();
for (var url in widget.urlsWithDescriptions.entries) {
urlWithDescriptionSelections.putIfAbsent(url,
() => widget.selectedByDefault && !widget.onlyOneSelectionAllowed);
for (var entry in widget.entries.entries) {
entrySelections.putIfAbsent(
entry,
() =>
widget.selectedByDefault &&
!widget.onlyOneSelectionAllowed &&
!widget.deselectThese.contains(entry.key));
}
if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) {
selectOnlyOne(widget.urlsWithDescriptions.entries.first.key);
selectOnlyOne(widget.entries.entries.first.key);
}
}
selectOnlyOne(String url) {
for (var uwd in urlWithDescriptionSelections.keys) {
urlWithDescriptionSelections[uwd] = uwd.key == url;
for (var e in entrySelections.keys) {
entrySelections[e] = e.key == url;
}
}
@override
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(
scrollable: true,
title: Text(
widget.onlyOneSelectionAllowed ? tr('selectURL') : tr('selectURLs')),
title: Text(widget.title ?? tr('pick')),
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) {
setState(() {
value ??= false;
if (value! && widget.onlyOneSelectionAllowed) {
selectOnlyOne(urlWithD.key);
selectOnlyOne(entry.key);
} else {
urlWithDescriptionSelections[urlWithD] = value!;
entrySelections[entry] = value!;
}
});
}
var urlLink = GestureDetector(
onTap: () {
launchUrlString(urlWithD.key,
mode: LaunchMode.externalApplication);
},
onTap: !widget.titlesAreLinks
? null
: () {
launchUrlString(entry.key,
mode: LaunchMode.externalApplication);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
urlWithD.value[0],
style: const TextStyle(
decoration: TextDecoration.underline,
entry.value.isEmpty ? entry.key : entry.value[0],
style: TextStyle(
decoration: widget.titlesAreLinks
? TextDecoration.underline
: null,
fontWeight: FontWeight.bold),
textAlign: TextAlign.start,
),
Text(
Uri.parse(urlWithD.key).host,
style: const TextStyle(
decoration: TextDecoration.underline, fontSize: 12),
)
if (widget.titlesAreLinks)
Text(
Uri.parse(entry.key).host,
style: const TextStyle(
decoration: TextDecoration.underline, fontSize: 12),
)
],
));
var descriptionText = Text(
urlWithD.value[1].length > 128
? '${urlWithD.value[1].substring(0, 128)}...'
: urlWithD.value[1],
style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12),
);
var descriptionText = entry.value.length <= 1
? const SizedBox.shrink()
: Text(
entry.value[1].length > 128
? '${entry.value[1].substring(0, 128)}...'
: entry.value[1],
style: const TextStyle(
fontStyle: FontStyle.italic, fontSize: 12),
);
var selectedUrlsWithDs = urlWithDescriptionSelections.entries
.where((e) => e.value)
.toList();
var selectedEntries =
entrySelections.entries.where((e) => e.value).toList();
var singleSelectTile = ListTile(
title: urlLink,
subtitle: GestureDetector(
onTap: () {
setState(() {
selectOnlyOne(urlWithD.key);
});
},
child: descriptionText,
),
leading: Radio<String>(
value: urlWithD.key,
groupValue: selectedUrlsWithDs.isEmpty
title: GestureDetector(
onTap: widget.titlesAreLinks
? 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) {
setState(() {
selectOnlyOne(urlWithD.key);
selectOnlyOne(entry.key);
});
},
),
@ -577,7 +776,7 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
var multiSelectTile = Row(children: [
Checkbox(
value: urlWithDescriptionSelections[urlWithD],
value: entrySelections[entry],
onChanged: (value) {
selectThis(value);
}),
@ -592,14 +791,22 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
const SizedBox(
height: 8,
),
urlLink,
GestureDetector(
onTap: () {
selectThis(
!(urlWithDescriptionSelections[urlWithD] ?? false));
},
child: descriptionText,
onTap: widget.titlesAreLinks
? null
: () {
selectThis(!(entrySelections[entry] ?? false));
},
child: urlLink,
),
entry.value.length <= 1
? const SizedBox.shrink()
: GestureDetector(
onTap: () {
selectThis(!(entrySelections[entry] ?? false));
},
child: descriptionText,
),
const SizedBox(
height: 8,
)
@ -619,24 +826,18 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
},
child: Text(tr('cancel'))),
TextButton(
onPressed:
urlWithDescriptionSelections.values.where((b) => b).isEmpty
? null
: () {
Navigator.of(context).pop(urlWithDescriptionSelections
.entries
.where((entry) => entry.value)
.map((e) => e.key.key)
.toList());
},
onPressed: entrySelections.values.where((b) => b).isEmpty
? null
: () {
Navigator.of(context).pop(entrySelections.entries
.where((entry) => entry.value)
.map((e) => e.key.key)
.toList());
},
child: Text(widget.onlyOneSelectionAllowed
? tr('pick')
: tr('importX', args: [
plural(
'url',
urlWithDescriptionSelections.values
.where((b) => b)
.length)
: tr('selectX', args: [
entrySelections.values.where((b) => b).length.toString()
])))
],
);

View File

@ -1,3 +1,4 @@
import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/providers/apps_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/source_provider.dart';
import 'package:provider/provider.dart';
@ -28,6 +30,29 @@ class _SettingsPageState extends State<SettingsPage> {
settingsProvider.initializeSettings();
}
var installMethodDropdown = DropdownButtonFormField(
decoration: InputDecoration(labelText: tr('installMethod')),
value: settingsProvider.installMethod,
items: [
DropdownMenuItem(
value: InstallMethodSettings.normal,
child: Text(tr('normal')),
),
const DropdownMenuItem(
value: InstallMethodSettings.shizuku,
child: Text('Shizuku'),
),
DropdownMenuItem(
value: InstallMethodSettings.root,
child: Text(tr('root')),
)
],
onChanged: (value) {
if (value != null) {
settingsProvider.installMethod = value;
}
});
var themeDropdown = DropdownButtonFormField(
decoration: InputDecoration(labelText: tr('theme')),
value: settingsProvider.theme,
@ -54,14 +79,14 @@ class _SettingsPageState extends State<SettingsPage> {
var colourDropdown = DropdownButtonFormField(
decoration: InputDecoration(labelText: tr('colour')),
value: settingsProvider.colour,
items: [
items: const [
DropdownMenuItem(
value: ColourSettings.basic,
child: Text(tr('obtainium')),
child: Text('Obtainium'),
),
DropdownMenuItem(
value: ColourSettings.materialYou,
child: Text(tr('materialYou')),
child: Text('Material You'),
)
],
onChanged: (value) {
@ -184,6 +209,10 @@ class _SettingsPageState extends State<SettingsPage> {
}
});
const height8 = SizedBox(
height: 8,
);
const height16 = SizedBox(
height: 16,
);
@ -211,6 +240,72 @@ class _SettingsPageState extends State<SettingsPage> {
color: Theme.of(context).colorScheme.primary),
),
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,
Row(
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,
Text(
tr('sourceSpecific'),
@ -282,6 +408,34 @@ class _SettingsPageState extends State<SettingsPage> {
height16,
localeDropdown,
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(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -412,6 +566,21 @@ class _SettingsPageState extends State<SettingsPage> {
})
],
),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(tr('highlightTouchTargets'))),
Switch(
value:
settingsProvider.highlightTouchTargets,
onChanged: (value) {
settingsProvider.highlightTouchTargets =
value;
})
],
),
height32,
Text(
tr('categories'),
@ -448,7 +617,8 @@ class _SettingsPageState extends State<SettingsPage> {
onPressed: () {
context.read<LogsProvider>().get().then((logs) {
if (logs.isEmpty) {
showError(ObtainiumError(tr('noLogs')), context);
showMessage(
ObtainiumError(tr('noLogs')), context);
} else {
showDialog(
context: context,
@ -462,7 +632,9 @@ class _SettingsPageState extends State<SettingsPage> {
label: Text(tr('appLogs'))),
],
),
height16,
const SizedBox(
height: 16,
)
],
),
)

File diff suppressed because it is too large Load Diff

View 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});
}
}

View File

@ -22,13 +22,13 @@ class ObtainiumNotification {
}
class UpdateNotification extends ObtainiumNotification {
UpdateNotification(List<App> updates)
UpdateNotification(List<App> updates, {int? id})
: super(
2,
id ?? 2,
tr('updatesAvailable'),
'',
'UPDATES_AVAILABLE',
tr('updatesAvailable'),
tr('updatesAvailableNotifChannel'),
tr('updatesAvailableNotifDescription'),
Importance.max) {
message = updates.isEmpty
@ -41,9 +41,15 @@ class UpdateNotification extends ObtainiumNotification {
}
class SilentUpdateNotification extends ObtainiumNotification {
SilentUpdateNotification(List<App> updates)
: super(3, tr('appsUpdated'), '', 'APPS_UPDATED', tr('appsUpdated'),
tr('appsUpdatedNotifDescription'), Importance.defaultImportance) {
SilentUpdateNotification(List<App> updates, {int? id})
: super(
id ?? 3,
tr('appsUpdated'),
'',
'APPS_UPDATED',
tr('appsUpdatedNotifChannel'),
tr('appsUpdatedNotifDescription'),
Importance.defaultImportance) {
message = updates.length == 1
? tr('xWasUpdatedToY',
args: [updates[0].finalName, updates[0].latestVersion])
@ -52,22 +58,46 @@ class SilentUpdateNotification extends ObtainiumNotification {
}
}
class ErrorCheckingUpdatesNotification extends ObtainiumNotification {
ErrorCheckingUpdatesNotification(String error)
class SilentUpdateAttemptNotification extends ObtainiumNotification {
SilentUpdateAttemptNotification(List<App> updates, {int? id})
: 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'),
error,
'BG_UPDATE_CHECK_ERROR',
tr('errorCheckingUpdates'),
tr('errorCheckingUpdatesNotifChannel'),
tr('errorCheckingUpdatesNotifDescription'),
Importance.high);
}
class AppsRemovedNotification extends ObtainiumNotification {
AppsRemovedNotification(List<List<String>> namedReasons)
: super(6, tr('appsRemoved'), '', 'APPS_REMOVED', tr('appsRemoved'),
tr('appsRemovedNotifDescription'), Importance.max) {
: super(
6,
tr('appsRemoved'),
'',
'APPS_REMOVED',
tr('appsRemovedNotifChannel'),
tr('appsRemovedNotifDescription'),
Importance.max) {
message = '';
for (var r in namedReasons) {
message += '${tr('xWasRemovedDueToErrorY', args: [r[0], r[1]])} \n';
@ -83,7 +113,7 @@ class DownloadNotification extends ObtainiumNotification {
tr('downloadingX', args: [appName]),
'',
'APP_DOWNLOADING',
tr('downloadingX', args: [tr('app')]),
tr('downloadingXNotifChannel', args: [tr('app')]),
tr('downloadNotifDescription'),
Importance.low,
onlyAlertOnce: true,
@ -95,18 +125,21 @@ final completeInstallationNotification = ObtainiumNotification(
tr('completeAppInstallation'),
tr('obtainiumMustBeOpenToInstallApps'),
'COMPLETE_INSTALL',
tr('completeAppInstallation'),
tr('completeAppInstallationNotifChannel'),
tr('completeAppInstallationNotifDescription'),
Importance.max);
final checkingUpdatesNotification = ObtainiumNotification(
4,
tr('checkingForUpdates'),
'',
'BG_UPDATE_CHECK',
tr('checkingForUpdates'),
tr('checkingForUpdatesNotifDescription'),
Importance.min);
class CheckingUpdatesNotification extends ObtainiumNotification {
CheckingUpdatesNotification(String appName)
: super(
4,
tr('checkingForUpdates'),
appName,
'BG_UPDATE_CHECK',
tr('checkingForUpdatesNotifChannel'),
tr('checkingForUpdatesNotifDescription'),
Importance.min);
}
class NotificationsProvider {
FlutterLocalNotificationsPlugin notifications =

View File

@ -9,11 +9,16 @@ import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/main.dart';
import 'package:obtainium/providers/apps_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: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().hosts[0]}';
String obtainiumId = 'dev.imranr.obtainium';
String obtainiumUrl = 'https://github.com/ImranR98/Obtainium';
enum InstallMethodSettings { normal, shizuku, root }
enum ThemeSettings { system, light, dark }
@ -35,6 +40,7 @@ List<int> updateIntervals = [15, 30, 60, 120, 180, 360, 720, 1440, 4320, 0]
class SettingsProvider with ChangeNotifier {
SharedPreferences? prefs;
String? defaultAppDir;
bool justStarted = true;
String sourceUrl = 'https://github.com/ImranR98/Obtainium';
@ -42,6 +48,26 @@ class SettingsProvider with ChangeNotifier {
// Not done in constructor as we want to be able to await it
Future<void> initializeSettings() async {
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();
}
@ -209,7 +235,8 @@ class SettingsProvider with ChangeNotifier {
}
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) {
@ -309,4 +336,133 @@ class SettingsProvider with ChangeNotifier {
prefs?.setBool('reversePageTransitions', show);
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();
}
}

View File

@ -9,22 +9,26 @@ import 'package:html/dom.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/apkmirror.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/directAPKLink.dart';
import 'package:obtainium/app_sources/fdroid.dart';
import 'package:obtainium/app_sources/fdroidrepo.dart';
import 'package:obtainium/app_sources/github.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/html.dart';
import 'package:obtainium/app_sources/jenkins.dart';
import 'package:obtainium/app_sources/mullvad.dart';
import 'package:obtainium/app_sources/neutroncode.dart';
import 'package:obtainium/app_sources/signal.dart';
import 'package:obtainium/app_sources/sourceforge.dart';
import 'package:obtainium/app_sources/sourcehut.dart';
import 'package:obtainium/app_sources/steammobile.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/whatsapp.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/mass_app_sources/githubstars.dart';
@ -63,10 +67,11 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) {
.reduce((value, element) => [...value, ...element]);
Map<String, dynamic> additionalSettings =
getDefaultValuesFromFormItems([formItems]);
Map<String, dynamic> originalAdditionalSettings = {};
if (json['additionalSettings'] != null) {
additionalSettings.addEntries(
Map<String, dynamic>.from(jsonDecode(json['additionalSettings']))
.entries);
originalAdditionalSettings =
Map<String, dynamic>.from(jsonDecode(json['additionalSettings']));
additionalSettings.addEntries(originalAdditionalSettings.entries);
}
// If needed, migrate old-style additionalData to newer-style additionalSettings (V1)
if (json['additionalData'] != null) {
@ -99,6 +104,21 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) {
additionalSettings.remove('releaseDateAsVersion');
}
}
// Convert dropdown style version detection options back into bool style
if (additionalSettings['versionDetection'] == 'standardVersionDetection') {
additionalSettings['versionDetection'] = true;
} else if (additionalSettings['versionDetection'] == 'noVersionDetection') {
additionalSettings['versionDetection'] = false;
} else if (additionalSettings['versionDetection'] == 'releaseDateAsVersion') {
additionalSettings['versionDetection'] = false;
additionalSettings['releaseDateAsVersion'] = true;
}
// Convert bool style pseudo version method to dropdown style
if (originalAdditionalSettings['supportFixedAPKURL'] == true) {
additionalSettings['defaultPseudoVersioningMethod'] = 'partialAPKHash';
} else if (originalAdditionalSettings['supportFixedAPKURL'] == false) {
additionalSettings['defaultPseudoVersioningMethod'] = 'APKLinkHash';
}
// Ensure additionalSettings are correctly typed
for (var item in formItems) {
if (additionalSettings[item.key] != null) {
@ -130,6 +150,53 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) {
if (additionalSettings['autoApkFilterByArch'] == null) {
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);
// 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)
@ -225,9 +292,10 @@ class App {
json['installedVersion'] == null
? null
: json['installedVersion'] as String,
json['latestVersion'] as String,
assumed2DlistToStringMapList(jsonDecode(json['apkUrls'])),
json['preferredApkIndex'] as int,
(json['latestVersion'] ?? tr('unknown')) as String,
assumed2DlistToStringMapList(jsonDecode(
(json['apkUrls'] ?? '[["placeholder", "placeholder"]]'))),
(json['preferredApkIndex'] ?? -1) as int,
jsonDecode(json['additionalSettings']) as Map<String, dynamic>,
json['lastUpdateCheck'] == null
? null
@ -279,9 +347,6 @@ preStandardizeUrl(String url) {
url.toLowerCase().indexOf('https://') != 0) {
url = 'https://$url';
}
if (url.toLowerCase().indexOf('https://www.') == 0) {
url = 'https://${url.substring(12)}';
}
url = url
.split('/')
.where((e) => e.isNotEmpty)
@ -317,28 +382,38 @@ List<MapEntry<String, String>> getApkUrlsFromUrls(List<String> urls) =>
return MapEntry(apkSegs.isNotEmpty ? apkSegs.last : segments.last, e);
}).toList();
getSourceRegex(List<String> hosts) {
return '(${hosts.join('|').replaceAll('.', '\\.')})';
}
abstract class AppSource {
String? host;
List<String> hosts = [];
bool hostChanged = false;
late String name;
bool enforceTrackOnly = false;
bool changeLogIfAnyIsMarkDown = true;
bool appIdInferIsOptional = false;
bool allowSubDomains = false;
bool naiveStandardVersionDetection = false;
bool neverAutoSelect = false;
bool showReleaseDateAsVersionToggle = false;
bool versionDetectionDisallowed = false;
List<String> excludeCommonSettingKeys = [];
AppSource() {
name = runtimeType.toString();
}
overrideVersionDetectionFormDefault(String vd, bool disableStandard) {
additionalAppSpecificSourceAgnosticSettingFormItems =
additionalAppSpecificSourceAgnosticSettingFormItems.map((e) {
overrideAdditionalAppSpecificSourceAgnosticSettingSwitch(String key,
{bool disabled = true, bool defaultValue = true}) {
additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly =
additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly
.map((e) {
return e.map((e2) {
if (e2.key == 'versionDetection') {
var item = e2 as GeneratedFormDropdown;
item.defaultValue = vd;
if (disableStandard) {
item.disabledOptKeys = ['standardVersionDetection'];
}
if (e2.key == key) {
var item = e2 as GeneratedFormSwitch;
item.disabled = disabled;
item.defaultValue = defaultValue;
}
return e2;
}).toList();
@ -353,12 +428,26 @@ abstract class AppSource {
return url;
}
Map<String, String>? get requestHeaders => null;
Future<Map<String, String>?> getRequestHeaders(
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
return null;
}
Future<Response> sourceRequest(String url) async {
if (requestHeaders != null) {
App endOfGetAppChanges(App app) {
return app;
}
Future<Response> sourceRequest(
String url, Map<String, dynamic> additionalSettings,
{bool followRedirects = true}) async {
var requestHeaders = await getRequestHeaders(additionalSettings);
if (requestHeaders != null || followRedirects == false) {
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));
} else {
return get(Uri.parse(url));
@ -380,7 +469,7 @@ abstract class AppSource {
// Some additional data may be needed for Apps regardless of Source
List<List<GeneratedFormItem>>
additionalAppSpecificSourceAgnosticSettingFormItems = [
additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly = [
[
GeneratedFormSwitch(
'trackOnly',
@ -388,16 +477,22 @@ abstract class AppSource {
)
],
[
GeneratedFormDropdown(
'versionDetection',
[
MapEntry(
'standardVersionDetection', tr('standardVersionDetection')),
MapEntry('releaseDateAsVersion', tr('releaseDateAsVersion')),
MapEntry('noVersionDetection', tr('noVersionDetection'))
],
label: tr('versionDetection'),
defaultValue: 'standardVersionDetection')
GeneratedFormTextField('versionExtractionRegEx',
label: tr('versionExtractionRegEx'),
required: false,
additionalValidators: [(value) => regExValidator(value)]),
],
[
GeneratedFormTextField('matchGroupToUse',
label: tr('matchGroupToUse'), required: false, hint: '\$0')
],
[
GeneratedFormSwitch('versionDetection',
label: tr('versionDetectionExplanation'), defaultValue: true)
],
[
GeneratedFormSwitch('useVersionCodeAsOSVersion',
label: tr('useVersionCodeAsOSVersion'), defaultValue: false)
],
[
GeneratedFormTextField('apkFilterRegEx',
@ -409,18 +504,71 @@ abstract class AppSource {
}
])
],
[
GeneratedFormSwitch('invertAPKFilter',
label: '${tr('invertRegEx')} (${tr('filterAPKsByRegEx')})',
defaultValue: false)
],
[
GeneratedFormSwitch('autoApkFilterByArch',
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
List<List<GeneratedFormItem>> get combinedAppSpecificSettingFormItems {
if (showReleaseDateAsVersionToggle == true) {
if (additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly
.indexWhere((List<GeneratedFormItem> e) =>
e.indexWhere((GeneratedFormItem i) =>
i.key == 'releaseDateAsVersion') >=
0) <
0) {
additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly.insert(
additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly
.indexWhere((List<GeneratedFormItem> e) =>
e.indexWhere((GeneratedFormItem i) =>
i.key == 'versionDetection') >=
0) +
1,
[
GeneratedFormSwitch('releaseDateAsVersion',
label:
'${tr('releaseDateAsVersion')} (${tr('pseudoVersion')})',
defaultValue: false)
]);
}
}
additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly =
additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly
.map((e) => e
.where((ee) => !excludeCommonSettingKeys.contains(ee.key))
.toList())
.where((e) => e.isNotEmpty)
.toList();
if (versionDetectionDisallowed) {
overrideAdditionalAppSpecificSourceAgnosticSettingSwitch(
'versionDetection',
disabled: true,
defaultValue: false);
overrideAdditionalAppSpecificSourceAgnosticSettingSwitch(
'useVersionCodeAsOSVersion',
disabled: true,
defaultValue: false);
}
return [
...additionalSourceAppSpecificSettingFormItems,
...additionalAppSpecificSourceAgnosticSettingFormItems
...additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly
];
}
@ -450,12 +598,13 @@ abstract class AppSource {
return null;
}
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
Map<String, dynamic> additionalSettings) async {
return apkUrl;
}
bool canSearch = false;
bool excludeFromMassSearch = false;
List<GeneratedFormItem> searchQuerySettingFormItems = [];
Future<Map<String, List<String>>> search(String query,
{Map<String, dynamic> querySettings = const {}}) {
@ -494,6 +643,90 @@ regExValidator(String? value) {
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;
}
}
List<MapEntry<String, String>> filterApks(
List<MapEntry<String, String>> apkUrls,
String? apkFilterRegEx,
bool? invert) {
if (apkFilterRegEx?.isNotEmpty == true) {
var reg = RegExp(apkFilterRegEx!);
apkUrls = apkUrls.where((element) {
var hasMatch = reg.hasMatch(element.key);
return invert == true ? !hasMatch : hasMatch;
}).toList();
}
return apkUrls;
}
class SourceProvider {
// Add more source classes here so they are available via the service
List<AppSource> get sources => [
@ -501,21 +734,22 @@ class SourceProvider {
GitLab(),
Codeberg(),
FDroid(),
IzzyOnDroid(),
FDroidRepo(),
Jenkins(),
IzzyOnDroid(),
SourceForge(),
SourceHut(),
APKMirror(),
APKPure(),
// APKCombo(), // Can't get past their scraping blocking yet (get 403 Forbidden)
Mullvad(),
Aptoide(),
Uptodown(),
APKMirror(),
HuaweiAppGallery(),
Jenkins(),
Signal(),
VLC(),
// WhatsApp(), // As of 2023-03-20 this is unusable as the version on the webpage is months out of date
WhatsApp(),
TelegramApp(),
SteamMobile(),
NeutronCode(),
DirectAPKLink(),
HTML() // This should ALWAYS be the last option as they are tried in order
];
@ -531,19 +765,26 @@ class SourceProvider {
throw UnsupportedURLError();
}
var res = srcs.first;
res.host = Uri.parse(url).host;
res.hosts = [Uri.parse(url).host];
res.hostChanged = true;
return srcs.first;
}
AppSource? source;
for (var s in sources.where((element) => element.host != null)) {
if (RegExp('://${s.host}(/|\\z)?').hasMatch(url)) {
source = s;
break;
for (var s in sources.where((element) => element.hosts.isNotEmpty)) {
try {
if (RegExp(
'^${s.allowSubDomains ? '([^\\.]+\\.)*' : '(www\\.)?'}(${getSourceRegex(s.hosts)})\$')
.hasMatch(Uri.parse(url).host)) {
source = s;
break;
}
} catch (e) {
// Ignore
}
}
if (source == null) {
for (var s in sources.where((element) => element.host == null)) {
for (var s in sources.where(
(element) => element.hosts.isEmpty && !element.neverAutoSelect)) {
try {
s.sourceSpecificStandardizeURL(url);
source = s;
@ -574,11 +815,6 @@ class SourceProvider {
String standardUrl, Map<String, dynamic> additionalSettings) =>
(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(
AppSource source, String url, Map<String, dynamic> additionalSettings,
{App? currentApp,
@ -592,15 +828,24 @@ class SourceProvider {
String standardUrl = source.standardizeUrl(url);
APKDetails apk =
await source.getLatestAPKDetails(standardUrl, additionalSettings);
if (additionalSettings['versionDetection'] == 'releaseDateAsVersion' &&
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['releaseDateAsVersion'] == true &&
apk.releaseDate != null) {
apk.version = apk.releaseDate!.microsecondsSinceEpoch.toString();
}
if (additionalSettings['apkFilterRegEx'] != null) {
var reg = RegExp(additionalSettings['apkFilterRegEx']);
apk.apkUrls =
apk.apkUrls.where((element) => reg.hasMatch(element.key)).toList();
}
apk.apkUrls = filterApks(apk.apkUrls, additionalSettings['apkFilterRegEx'],
additionalSettings['invertAPKFilter']);
if (apk.apkUrls.isEmpty && !trackOnly) {
throw NoAPKError();
}
@ -617,13 +862,13 @@ class SourceProvider {
}
}
}
String apkVersion = apk.version.replaceAll('/', '-');
var name = currentApp != null ? currentApp.name.trim() : '';
name = name.isNotEmpty ? name : apk.names.name;
return App(
App finalApp = App(
currentApp?.id ??
((!source.appIdInferIsOptional ||
(source.appIdInferIsOptional && inferAppIdIfOptional))
(!trackOnly &&
(!source.appIdInferIsOptional ||
(source.appIdInferIsOptional && inferAppIdIfOptional))
? await source.tryInferringAppId(standardUrl,
additionalSettings: additionalSettings)
: null) ??
@ -632,7 +877,7 @@ class SourceProvider {
apk.names.author,
name,
currentApp?.installedVersion,
apkVersion,
apk.version,
apk.apkUrls,
apk.apkUrls.length - 1 >= 0 ? apk.apkUrls.length - 1 : 0,
additionalSettings,
@ -643,14 +888,17 @@ class SourceProvider {
changeLog: apk.changeLog,
overrideSource: overrideSource ?? currentApp?.overrideSource,
allowIdChange: currentApp?.allowIdChange ??
source.appIdInferIsOptional &&
inferAppIdIfOptional // Optional ID inferring may be incorrect - allow correction on first install
trackOnly ||
(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
Future<List<dynamic>> getAppsByURLNaive(List<String> urls,
{List<String> alreadyAddedUrls = const []}) async {
{List<String> alreadyAddedUrls = const [],
AppSource? sourceOverride}) async {
List<App> apps = [];
Map<String, dynamic> errors = {};
for (var url in urls) {
@ -658,7 +906,7 @@ class SourceProvider {
if (alreadyAddedUrls.contains(url)) {
throw ObtainiumError(tr('appAlreadyAdded'));
}
var source = getSource(url);
var source = sourceOverride ?? getSource(url);
apps.add(await getApp(
source,
url,

View File

@ -1,47 +1,55 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
android_alarm_manager_plus:
dependency: "direct main"
description:
name: android_alarm_manager_plus
sha256: c20d91a9096596f66274bf8172321c278f9cba8091638f80205fe66d31587fa5
url: "https://pub.dev"
source: hosted
version: "3.0.2"
android_intent_plus:
dependency: "direct main"
description:
name: android_intent_plus
sha256: f72ae20bb37108694f442e7ae6acbd28b453ca62ce86842f6787b784355abfe6
sha256: e1c62bb41c90e15083b7fb84dc327fe90396cc9c1445b55ff1082144fabfb4d9
url: "https://pub.dev"
source: hosted
version: "4.0.2"
version: "4.0.3"
android_package_installer:
dependency: "direct main"
description:
path: "."
ref: main
resolved-ref: f09c79eee5be3c60b04760143eb954a13fdd07f1
resolved-ref: ba2aa7a11edc2649d1d80c25ed9291521262f714
url: "https://github.com/ImranR98/android_package_installer"
source: git
version: "0.0.1"
android_package_manager:
dependency: "direct main"
description:
name: android_package_manager
sha256: e52ca607b9f19f95d5dae4211ed8fa93e67093f22ac570db47489c5bca512940
url: "https://pub.dev"
source: hosted
version: "0.7.0"
animations:
dependency: "direct main"
description:
name: animations
sha256: fe8a6bdca435f718bb1dc8a11661b2c22504c6da40ef934cee8327ed77934164
sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb
url: "https://pub.dev"
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:
dependency: transitive
description:
name: archive
sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
url: "https://pub.dev"
source: hosted
version: "3.3.7"
version: "3.4.10"
args:
dependency: transitive
description:
@ -58,6 +66,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.11.0"
background_fetch:
dependency: "direct main"
description:
name: background_fetch
sha256: "34550cf9b383e5a1844e7d22119aa500508c7df9421fa967c9fb4430d6cb2878"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
boolean_selector:
dependency: transitive
description:
@ -86,10 +102,10 @@ packages:
dependency: transitive
description:
name: cli_util
sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
url: "https://pub.dev"
source: hosted
version: "0.4.0"
version: "0.4.1"
clock:
dependency: transitive
description:
@ -102,10 +118,26 @@ packages:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
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:
dependency: transitive
description:
@ -118,12 +150,12 @@ packages:
dependency: transitive
description:
name: cross_file
sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9"
sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
url: "https://pub.dev"
source: hosted
version: "0.3.3+4"
version: "0.3.3+8"
crypto:
dependency: transitive
dependency: "direct main"
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
@ -142,26 +174,26 @@ packages:
dependency: "direct main"
description:
name: cupertino_icons
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev"
source: hosted
version: "1.0.5"
version: "1.0.6"
dbus:
dependency: transitive
description:
name: dbus
sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263"
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
url: "https://pub.dev"
source: hosted
version: "0.7.8"
version: "0.7.10"
device_info_plus:
dependency: "direct main"
description:
name: device_info_plus
sha256: "86add5ef97215562d2e090535b0a16f197902b10c369c558a100e74ea06e8659"
sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6"
url: "https://pub.dev"
source: hosted
version: "9.0.3"
version: "9.1.1"
device_info_plus_platform_interface:
dependency: transitive
description:
@ -174,18 +206,18 @@ packages:
dependency: "direct main"
description:
name: dynamic_color
sha256: de4798a7069121aee12d5895315680258415de9b00e717723a1bd73d58f0126d
sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b
url: "https://pub.dev"
source: hosted
version: "1.6.6"
version: "1.6.9"
easy_localization:
dependency: "direct main"
description:
name: easy_localization
sha256: "30ebf25448ffe169e0bd9bc4b5da94faa8398967a2ad2ca09f438be8b6953645"
sha256: de63e3b422adfc97f256cbb3f8cf12739b6a4993d390f3cadb3f51837afaefe5
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.0.3"
easy_logger:
dependency: transitive
description:
@ -206,26 +238,34 @@ packages:
dependency: transitive
description:
name: ffi
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
version: "2.1.0"
file:
dependency: transitive
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "6.1.4"
version: "7.0.0"
file_picker:
dependency: "direct main"
description:
name: file_picker
sha256: "21145c9c268d54b1f771d8380c195d2d6f655e0567dc1ca2f9c134c02c819e0a"
sha256: "4e42aacde3b993c5947467ab640882c56947d9d27342a5b6f2895b23956954a6"
url: "https://pub.dev"
source: hosted
version: "5.3.3"
version: "6.1.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
flutter:
dependency: "direct main"
description: flutter
@ -235,10 +275,10 @@ packages:
dependency: "direct main"
description:
name: flutter_archive
sha256: aec85d1da65e5b33a529db00a86df0b8e92bda78088a7cfaeeba5187701d0d85
sha256: "004132780d382df5171589ab793e2efc9c3eef570fe72d78b4ccfbfbe52762ae"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
version: "6.0.0"
flutter_fgbg:
dependency: "direct main"
description:
@ -259,18 +299,18 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
url: "https://pub.dev"
source: hosted
version: "2.0.2"
version: "3.0.1"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: "3cc40fe8c50ab8383f3e053a499f00f975636622ecdc8e20a77418ece3b1e975"
sha256: c18f1de98fe0bb9dd5ba91e1330d4febc8b6a7de6aae3ffe475ef423723e72f3
url: "https://pub.dev"
source: hosted
version: "15.1.0+1"
version: "16.3.2"
flutter_local_notifications_linux:
dependency: transitive
description:
@ -296,18 +336,18 @@ packages:
dependency: "direct main"
description:
name: flutter_markdown
sha256: "4b1bfbb802d76320a1a46d9ce984106135093efd9d969765d07c2125af107bdf"
sha256: "30088ce826b5b9cfbf9e8bece34c716c8a59fa54461dcae1e4ac01a94639e762"
url: "https://pub.dev"
source: hosted
version: "0.6.17"
version: "0.6.18+3"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360"
sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da
url: "https://pub.dev"
source: hosted
version: "2.0.15"
version: "2.0.17"
flutter_test:
dependency: "direct dev"
description: flutter
@ -322,10 +362,18 @@ packages:
dependency: "direct main"
description:
name: fluttertoast
sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c"
sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1
url: "https://pub.dev"
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:
dependency: "direct main"
description:
@ -346,10 +394,10 @@ packages:
dependency: "direct main"
description:
name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.2.0"
http_parser:
dependency: transitive
description:
@ -362,26 +410,18 @@ packages:
dependency: transitive
description:
name: image
sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf
sha256: "004a2e90ce080f8627b5a04aecb4cdfac87d2c3f3b520aa291260be5a32c033d"
url: "https://pub.dev"
source: hosted
version: "4.0.17"
installed_apps:
dependency: "direct main"
description:
name: installed_apps
sha256: "145af8eb6e4e7c830e9888d6de0573ae5c139e8e0742a3e67316e1db21ab6fe0"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
version: "4.1.4"
intl:
dependency: transitive
description:
name: intl
sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev"
source: hosted
version: "0.18.0"
version: "0.18.1"
js:
dependency: transitive
description:
@ -402,50 +442,50 @@ packages:
dependency: transitive
description:
name: lints
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "3.0.0"
markdown:
dependency: transitive
description:
name: markdown
sha256: acf35edccc0463a9d7384e437c015a3535772e09714cf60e07eeef3a15870dcd
sha256: "1b134d9f8ff2da15cb298efe6cd8b7d2a78958c1b00384ebcbdf13fe340a6c90"
url: "https://pub.dev"
source: hosted
version: "7.1.1"
version: "7.2.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.5.0"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
mime:
dependency: transitive
description:
name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.0.5"
nested:
dependency: transitive
description:
@ -454,22 +494,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
package_archive_info:
dependency: "direct main"
description:
name: package_archive_info
sha256: "8f671a29b79d15f192e5e2b0dab9d0bad66b9ee93fb58d4e0afdb62f91a259be"
url: "https://pub.dev"
source: hosted
version: "0.1.0"
package_info:
nm:
dependency: transitive
description:
name: package_info
sha256: "6c07d9d82c69e16afeeeeb6866fe43985a20b3b50df243091bfc4a4ad2b03b75"
name: nm
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
version: "0.5.0"
path:
dependency: transitive
description:
@ -482,202 +514,218 @@ packages:
dependency: "direct main"
description:
name: path_provider
sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
url: "https://pub.dev"
source: hosted
version: "2.0.15"
version: "2.1.2"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "5d44fc3314d969b84816b569070d7ace0f1dea04bd94a83f74c4829615d22ad8"
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.2.2"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "1b744d3d774e5a879bb76d6cd1ecee2ba2c6960c03b1020cd35212f6aa267ac5"
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.3.2"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: ba2b77f0c52a33db09fc8caf85b12df691bf28d983e84cf87ff6d693cfa007b3
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: bced5679c7df11190e1ddc35f3222c858f328fff85c3942e46e7f5589bf9eb84
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: ee0e0d164516b90ae1f970bdf29f726f1aa730d7cfc449ecc74c495378b705da
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.2.1"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81"
sha256: "45ff3fbcb99040fde55c528d5e3e6ca29171298a85436274d49c6201002087d6"
url: "https://pub.dev"
source: hosted
version: "10.4.3"
version: "11.2.0"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "2ffaf52a21f64ac9b35fe7369bb9533edbd4f698e5604db8645b1064ff4cf221"
sha256: "758284a0976772f9c744d6384fc5dc4834aa61e3f7aa40492927f244767374eb"
url: "https://pub.dev"
source: hosted
version: "10.3.3"
version: "12.0.3"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
sha256: c6bf440f80acd2a873d3d91a699e4cc770f86e7e6b576dda98759e8b92b39830
url: "https://pub.dev"
source: hosted
version: "9.1.4"
version: "9.3.0"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d"
url: "https://pub.dev"
source: hosted
version: "0.1.1"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9"
sha256: "5c43148f2bfb6d14c5a8162c0a712afe891f2d847f35fcff29c406b37da43c3c"
url: "https://pub.dev"
source: hosted
version: "3.11.3"
version: "4.1.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.1.3"
version: "0.2.1"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "5.4.0"
version: "6.0.2"
platform:
dependency: transitive
description:
name: platform
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.1.4"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd"
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
version: "2.1.8"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
url: "https://pub.dev"
source: hosted
version: "3.7.3"
version: "3.7.4"
provider:
dependency: "direct main"
description:
name: provider
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
url: "https://pub.dev"
source: hosted
version: "6.0.5"
version: "6.1.1"
share_plus:
dependency: "direct main"
description:
name: share_plus
sha256: "6cec740fa0943a826951223e76218df002804adb588235a8910dc3d6b0654e11"
sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd
url: "https://pub.dev"
source: hosted
version: "7.1.0"
version: "7.2.1"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7"
sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956
url: "https://pub.dev"
source: hosted
version: "3.3.0"
version: "3.3.1"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1"
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.2.2"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.2.1"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: f39696b83e844923b642ce9dd4bd31736c17e697f6731a5adf445b1274cf3cd4
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.3.5"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1"
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.3.2"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1"
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.3.2"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a"
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.2.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
url: "https://pub.dev"
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:
dependency: transitive
description: flutter
@ -687,42 +735,50 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
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:
dependency: "direct main"
description:
name: sqflite
sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a"
sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.3.2"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a"
sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5"
url: "https://pub.dev"
source: hosted
version: "2.5.0"
version: "2.5.3"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
string_scanner:
dependency: transitive
description:
@ -735,10 +791,10 @@ packages:
dependency: transitive
description:
name: synchronized
sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.1.0+1"
term_glyph:
dependency: transitive
description:
@ -751,10 +807,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.6.1"
timezone:
dependency: transitive
description:
@ -775,74 +831,74 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e"
sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c
url: "https://pub.dev"
source: hosted
version: "6.1.12"
version: "6.2.4"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "78cb6dea3e93148615109e58e42c35d1ffbf5ef66c44add673d0ab75f12ff3af"
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
url: "https://pub.dev"
source: hosted
version: "6.0.37"
version: "6.2.2"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
url: "https://pub.dev"
source: hosted
version: "6.1.4"
version: "6.2.4"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
url: "https://pub.dev"
source: hosted
version: "3.0.5"
version: "3.1.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1"
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
url: "https://pub.dev"
source: hosted
version: "3.0.6"
version: "3.1.0"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea
sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.3.1"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
url: "https://pub.dev"
source: hosted
version: "2.0.18"
version: "2.2.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422"
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
url: "https://pub.dev"
source: hosted
version: "3.0.7"
version: "3.1.1"
uuid:
dependency: transitive
description:
name: uuid
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
url: "https://pub.dev"
source: hosted
version: "3.0.7"
version: "4.3.3"
vector_math:
dependency: transitive
description:
@ -851,70 +907,78 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
web:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev"
source: hosted
version: "0.3.0"
webview_flutter:
dependency: "direct main"
description:
name: webview_flutter
sha256: "789d52bd789373cc1e100fb634af2127e86c99cf9abde09499743270c5de8d00"
sha256: "71e1bfaef41016c8d5954291df5e9f8c6172f1f6ff3af01b5656456ddb11f94c"
url: "https://pub.dev"
source: hosted
version: "4.2.2"
version: "4.4.4"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: d936a09fbfd08cb78f7329e0bbacf6158fbdfe24ffc908b22444c07d295eb193
sha256: "4ea3c4e1b8ed590162b15b8a61b41b1ef3ff179a314627c16ce40c086d94b8af"
url: "https://pub.dev"
source: hosted
version: "3.9.2"
version: "3.14.0"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
sha256: "564ef378cafc1a0e29f1d76ce175ef517a0a6115875dff7b43fccbef2b0aeb30"
sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.10.0"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: "5fa098f28b606f699e8ca52d9e4e11edbbfef65189f5f77ae92703ba5408fd25"
sha256: b99ca8d8bae9c6b43d568218691aa537fb0aeae1d7d34eadf112a6aa36d26506
url: "https://pub.dev"
source: hosted
version: "3.7.2"
version: "3.11.0"
win32:
dependency: transitive
description:
name: win32
sha256: f2add6fa510d3ae152903412227bda57d0d5a8da61d2c39c1fb022c9429a41c0
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
url: "https://pub.dev"
source: hosted
version: "5.0.6"
version: "5.2.0"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: e4506d60b7244251bc59df15656a3093501c37fb5af02105a944d73eb95be4c9
sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "1.1.2"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted
version: "1.0.1"
version: "1.0.4"
xml:
dependency: transitive
description:
name: xml
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.3.0"
version: "6.5.0"
yaml:
dependency: transitive
description:
@ -924,5 +988,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.0.0 <4.0.0"
flutter: ">=3.10.0"
dart: ">=3.2.3 <4.0.0"
flutter: ">=3.16.6"

View File

@ -1,5 +1,5 @@
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
# 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
# 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.
version: 0.13.23+187 # When changing this, update the tag in main() accordingly
version: 1.0.1+251 # When changing this, update the tag in main() accordingly
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.
# To automatically upgrade your package dependencies to the latest versions
@ -38,7 +38,7 @@ dependencies:
cupertino_icons: ^1.0.5
path_provider: ^2.0.11
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
http: ^1.0.0
webview_flutter: ^4.0.0
@ -46,25 +46,28 @@ dependencies:
html: ^0.15.0
shared_preferences: ^2.0.15
url_launcher: ^6.1.5
permission_handler: ^10.0.0
permission_handler: ^11.0.0
fluttertoast: ^8.0.9
device_info_plus: ^9.0.0
file_picker: ^5.2.10
file_picker: ^6.0.0
animations: ^2.0.4
android_package_installer:
git:
url: https://github.com/ImranR98/android_package_installer
ref: main
android_package_manager: ^0.7.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
easy_localization: ^3.0.1
android_intent_plus: ^4.0.0
flutter_markdown: ^0.6.14
flutter_archive: ^5.0.0
flutter_archive: ^6.0.0
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:
flutter_test:
@ -76,7 +79,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^2.0.1
flutter_lints: ^3.0.0
flutter_launcher_icons:
android: "ic_launcher"