Compare commits

...

295 Commits

Author SHA1 Message Date
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
2a5118a2cf Merge pull request #749 from ImranR98/dev
- Fix a French word (#735)
- Add filename-only sort for HTML (#734)
- Add dynamic mirror picking to VLC (#715)
- Disable reverse transition method when n/a (#739)
- Make delete confirmation button red (#741)
- Add release notes filter for GitHub/Codeberg (#719)
2023-08-05 14:31:35 -04:00
347c56da55 Ran dart fix 2023-08-05 14:29:42 -04:00
8073723e1f Increment version, update packages 2023-08-05 14:28:40 -04:00
c8e90a755d Add release notes filter for GitHub/Codeberg(#719) 2023-08-05 14:23:23 -04:00
aeb0a5d8ea Make delete confirmation button red (#741) 2023-08-05 14:12:03 -04:00
09fe7f3ecd Disable reverse transition method when n/a (#739) 2023-08-05 14:00:50 -04:00
a549411589 Add dynamic mirror picking to VLC (#715) 2023-08-05 13:55:25 -04:00
f426b5e118 Add filename-only sort for HTML (#734) 2023-08-05 12:52:08 -04:00
12a8ef5e31 Fix a French word (#735) 2023-08-05 12:39:58 -04:00
8210279a4c Merge pull request #728 from TangyWrecker/main
Update ru.json
2023-08-05 12:37:35 -04:00
999d13b80d Merge pull request #731 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-08-05 12:37:30 -04:00
0573c0b270 Merge pull request #733 from bluefly000/japanese-translation
Update ja.json
2023-08-05 12:37:25 -04:00
5a36a7980c Merge pull request #736 from gidano/main
Update hu.json
2023-08-05 12:37:18 -04:00
6bf9b5297f Merge pull request #746 from vaginessa/main
Update de.json - translate missing strings
2023-08-05 12:37:12 -04:00
fdc6b0ff00 Merge pull request #748 from LilligantMatsuri/main
Update Chinese translation
2023-08-05 12:37:05 -04:00
73c20a53d2 Update zh.json
- Translate new strings
- Slight improvements

Signed-off-by: Matsuri <matsuri@vmoe.info>
2023-08-05 23:27:13 +08:00
16ae8d8e4d Update de.json - translate missing strings 2023-08-05 15:53:09 +02:00
3f8cfae64e Update hu.json 2023-08-01 16:28:31 +02:00
6861a71efb Update ja.json 2023-07-31 19:52:12 +09:00
2a603f410f locale(pl): Update Polish translations
Co-authored-by: Daviteusz <daviteusz0@gmail.com>
2023-07-30 16:28:42 +02:00
6d22788f92 Update ru.json 2023-07-30 13:03:14 +03:00
e36e6bbaca Merge pull request #713 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-07-29 22:44:20 -04:00
ac3a13ed73 Merge branch 'main' into weblate-obtainium-translate 2023-07-29 22:44:13 -04:00
202f7df5cb Merge pull request #724 from markus-gitdev/patch-1
Update de.json
2023-07-29 22:43:53 -04:00
1b902b1a18 Merge branch 'main' into patch-1 2023-07-29 22:43:45 -04:00
1765d399c8 Merge pull request #726 from ImranR98/dev
Use App-Specific Source Config for Overridden Sources, Show Source Config Notes/Hints (#720), Bugfixes and UI tweaks (#714, #722, #723)
2023-07-29 22:42:58 -04:00
5dd79707f1 Update packages, increment version 2023-07-29 22:40:52 -04:00
14755134bf Use icon button for delete on app page (#722) 2023-07-29 22:39:50 -04:00
cccde7e135 UI bugfix (#723) 2023-07-29 22:29:08 -04:00
3dafd643c0 Only show host+path for ClientException log (#714) 2023-07-29 22:21:07 -04:00
76f8cd4102 Source configuration settings changes
- "Source config" refers to source-specific, app-agnostic settings
- Don't use saved config for overridden sources
- For overridden sources, use app-specific source config
- Allow sources to show notes on add-app page (#720)
2023-07-29 22:06:42 -04:00
a8bfb03f58 Wait 5s between download retries 2023-07-29 20:22:04 -04:00
10ead4f3e0 Update de.json 2023-07-29 14:44:21 +02:00
af5a6857ba locale(pl): Update Polish translations
Co-authored-by: Daviteusz <daviteusz0@gmail.com>
2023-07-24 23:42:11 +02:00
d9225fd639 Merge pull request #712 from ImranR98/dev
Auto retry failed download (3 times) (#634)
2023-07-23 15:03:34 -04:00
995d44551c Auto retry failed download (3 times) (#634) 2023-07-23 15:02:58 -04:00
68c0224b98 Merge pull request #711 from ImranR98/dev
Increment version, update modules
2023-07-23 14:26:48 -04:00
7767468d5d Increment version, update modules 2023-07-23 14:26:22 -04:00
f9f83d8243 Merge pull request #710 from ImranR98/dev
Fix empty error messages for HTTP errors
2023-07-23 14:24:50 -04:00
cc4cec829f Merge pull request #705 from gidano/main
Update hu.json
2023-07-23 14:24:23 -04:00
f665bf1eb2 Merge pull request #706 from TangyWrecker/main
Update ru.json
2023-07-23 14:24:18 -04:00
bccd52054e Merge pull request #708 from LilligantMatsuri/main
Update Chinese translation
2023-07-23 14:24:12 -04:00
3a4c782aab Fix empty error messages for HTTP errors 2023-07-23 14:23:34 -04:00
6e047e96fa Update Chinese translation
- Translate new strings

Signed-off-by: Matsuri <matsuri@vmoe.info>
2023-07-23 19:56:23 +08:00
42f8753166 Update ru.json 2023-07-23 14:34:15 +03:00
b6a64129b3 Update hu.json 2023-07-23 08:52:14 +02:00
bb5bd23263 Merge pull request #704 from ImranR98/dev
Copy error dialog on long press (#692), Minimum star count for GitHub/Codeberg search (#688)
2023-07-23 02:09:25 -04:00
58361a0493 Ran dart fix 2023-07-23 02:08:35 -04:00
7671ab95f9 Update packages, increment versions 2023-07-23 02:05:34 -04:00
41f102c0ce Minimum star count for GitHub/Codeberg search (#688) 2023-07-23 02:04:40 -04:00
5cee527d6f Copy error dialog on long press (#692) 2023-07-23 01:34:12 -04:00
c2eb13d5e5 Merge remote-tracking branch 'origin/main' into dev 2023-07-23 01:30:33 -04:00
ed89e20826 Fix error from previous conflict resolution commit 2023-07-23 01:30:21 -04:00
e50c7554e3 Merge remote-tracking branch 'origin/main' into dev 2023-07-23 01:28:53 -04:00
00354f10a3 Merge pull request #687 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-07-23 01:27:09 -04:00
031d0b7444 Merge pull request #691 from TangyWrecker/patch-1
Update ru.json and correct main.dart
2023-07-23 01:26:59 -04:00
2ced06367b Merge branch 'main' into patch-1 2023-07-23 01:26:51 -04:00
9ef3279bae Merge pull request #699 from Erudaro/main
Add Bosnian translation
2023-07-23 01:26:19 -04:00
b055486d74 Merge pull request #700 from bluefly000/japanese-translation
Update ja.json
2023-07-23 01:26:12 -04:00
c70f2c481e Merge pull request #703 from djm2k/feature/accessible-random-colors
Fix #397 Edit Category Colors & Accessible colors using HSLuv
2023-07-23 01:25:58 -04:00
e4b26be01f Add color button to Category editor
Prevent randomly picking the same color
2023-07-23 09:11:02 +10:00
b27bdc63c1 Make colors more accessible
Using a combination of the HSLuv (Pastel) color space and incrementing by the Golden Angle
2023-07-23 09:11:02 +10:00
543e7d8cdc locale(pl): Update Polish translation
Co-authored-by: Daviteusz <imefiu3@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
2023-07-22 16:31:20 +02:00
852decbe7d Remove redundant generateRandomLightColor() 2023-07-22 14:26:19 +10:00
d2150320fa Add dependency: hsluv 2023-07-22 14:25:38 +10:00
9c723c7522 Update ja.json 2023-07-19 11:38:09 +09:00
ac5660de88 Update main.dart supportedLocales with bs 2023-07-18 23:16:57 +02:00
e6467452a6 Add files via upload
Adding Bosnian language json file
2023-07-18 22:49:13 +02:00
04b49c2e61 Fix null check error for GitHub repos w/o releases 2023-07-17 15:57:10 -04:00
324773ba58 Correct ru main.dart
That's more correct😅
2023-07-16 23:01:31 +03:00
d885ca5db7 Update ru.json 2023-07-16 22:46:44 +03:00
e093fc28b0 Merge pull request #685 from ImranR98/dev
Make page transition animation optional (#683), Add reverse transition animation toggle (#683), Fix text alignment + visibility on app page (#680), Attempt at more resilient XAPK installs (#682)
2023-07-16 12:06:56 -04:00
579bc94847 Attempt at more resilient XAPK installs (#682) 2023-07-16 12:05:43 -04:00
53dba06cc3 Fix text alignment + visibility on app page (#680) 2023-07-16 11:52:18 -04:00
1c390a7f04 Add reverse transition animation toggle (#683) 2023-07-16 11:41:02 -04:00
6b16857186 Make page transition animation optional (#683) 2023-07-16 11:36:49 -04:00
785bba1f45 Merge pull request #679 from ImranR98/dev
Don't auto-capitalize names (#669)
2023-07-15 19:36:26 -04:00
3befcbc16d Don't auto-capitalize names (#669) 2023-07-15 19:35:40 -04:00
be300608a9 Merge pull request #678 from ImranR98/dev
Make transition animation directional (#675), Fix missing update button bug (perform full load on foreground)
2023-07-15 19:23:49 -04:00
f05902925d Increment version 2023-07-15 19:23:00 -04:00
d32e7acc8f Fix missing update button bug (perform full load on foreground) 2023-07-15 19:20:17 -04:00
fda9e6195a Make transition animation directional (#675) 2023-07-15 18:54:53 -04:00
b5d91adb21 Merge pull request #677 from ImranR98/dev
Improve App load performance (#579)
2023-07-15 18:21:03 -04:00
1b52cb6233 Merge pull request #676 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-07-15 18:20:44 -04:00
5669634b44 Merge pull request #673 from gidano/main
Update hu.json
2023-07-15 18:20:25 -04:00
d6bb365cf1 Merge pull request #671 from bluefly000/japanese-translation
Update ja.json
2023-07-15 18:20:05 -04:00
53b7cfb9bf Merge pull request #667 from TangyWrecker/main
Update ru.json
2023-07-15 18:19:52 -04:00
2558c851c0 Improve App load performance (#579) 2023-07-15 18:18:35 -04:00
a8aa63daa3 locale(pl): Update Polish translation
Co-authored-by: Daviteusz <imefiu3@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
2023-07-15 23:53:20 +02:00
d587e9f708 Update hu.json 2023-07-15 13:21:58 +02:00
e2a8f40e3e Update ru.json
Updated, fix and added \n for ru
2023-07-15 08:49:35 +03:00
1cc4241fac Update ja.json 2023-07-15 14:47:27 +09:00
bb98dfe0b3 Update ja.json 2023-07-15 14:09:13 +09:00
6395dd820b Update ru.json
Fix
2023-07-15 07:38:34 +03:00
e909c03356 Update ru.json 2023-07-15 07:10:46 +03:00
6a3278432d Merge pull request #666 from ImranR98/dev
Add better GitLab APK discover when API key set (#3 and duplicates #41, #282, partly #356)
2023-07-14 23:56:27 -04:00
81c51970aa Merge remote-tracking branch 'origin/main' into dev 2023-07-14 23:54:17 -04:00
7f9d431b9b Merge pull request #664 from TangyWrecker/main
Update ru.json
2023-07-14 23:54:03 -04:00
360591ebb9 Add better GitLab APK discover when API key set (#3) 2023-07-14 23:53:28 -04:00
814ff2c2e5 Update ru.json
fix and update
2023-07-15 06:32:08 +03:00
f74df57400 Merge pull request #663 from ImranR98/dev
- Add auto-remove on uninstall option (#656)
- Add option to auto-select the highest version code APK for F-Droid Third-Party Repos (#658)
- Toggle to disable update check on detail page (#659)
- Refresh-on-foreground bugfix
- String capitalization consistency
2023-07-14 22:00:04 -04:00
6b29a0f0f3 Update Packages, increment version 2023-07-14 21:56:57 -04:00
2a58ee8729 Toggle to disable update check on detail page (#659), string capitalization consistency 2023-07-14 21:53:50 -04:00
41d9edcf83 Auto-select version code for F-Droid Third-Party Repos (#658) 2023-07-14 21:36:42 -04:00
3ec33a1c77 Add auto-remove option (#656) + on-foreground bugfix 2023-07-14 21:09:20 -04:00
3f4c6a1b76 Merge pull request #653 from ImranR98/dev
Increment version, update packages
2023-07-09 01:04:34 -04:00
60ad3199ca Increment version, update packages 2023-07-09 01:02:43 -04:00
1984ffb1c0 Merge pull request #647 from 1xFF/fix-remove
fix race condition when checking updates
2023-07-09 01:01:07 -04:00
7877a14f07 Use onlyIfExists flag 2023-07-08 21:02:46 -07:00
568a94968b Merge pull request #649 from 1xFF/apkpure-changelog
Add changelog for apkpure
2023-07-08 22:20:08 -04:00
a6a68af24e Merge pull request #652 from LilligantMatsuri/main
Update zh.json
2023-07-08 22:18:09 -04:00
5cdd110544 Merge pull request #646 from 1xFF/archive-flutter
change archive library
2023-07-08 22:17:53 -04:00
5bbe306f8f Update zh.json
- Translate new strings
- Slight improvements

Signed-off-by: Matsuri <matsuri@vmoe.info>
2023-07-08 21:46:49 +08:00
48acbc563a Update README.md
Removed izzydroid as it was out of date.
2023-07-06 00:36:47 -04:00
ab1f7e7179 keep naming convention 2023-07-03 16:50:28 -07:00
667e909a70 Add changelog for apkpure 2023-07-03 16:44:21 -07:00
bcc0d280ab fix race condition when checking updates 2023-07-03 01:27:12 -07:00
da027b7734 change archive library 2023-07-02 02:16:47 -07:00
09056665c2 Merge pull request #644 from ImranR98/dev
Increment version, update modules
2023-07-01 18:02:52 -04:00
f4c3951f6d Increment version, update modules 2023-07-01 18:02:34 -04:00
00f42bb881 Merge pull request #640 from mehdeej/main
Update (Persian) fa.json
2023-07-01 17:59:22 -04:00
d8408a26c2 Merge pull request #638 from 1xFF/OBB-Support
add OBB support
2023-07-01 17:59:12 -04:00
ede54531c8 Update (Persian) fa.json 2023-06-29 14:35:09 +00:00
0fa0a4b19a fix race condition 2023-06-28 13:31:10 -07:00
af5ea3db0f add Permissions for android 10 and below 2023-06-28 03:48:13 -07:00
e75ca05aa4 Change recursion 2023-06-28 02:50:18 -07:00
3483190b78 Merge branch 'ImranR98:main' into OBB-Support 2023-06-24 21:50:37 -07:00
69656e65c3 Basic OBB support 2023-06-24 21:48:57 -07:00
e6c6841fac Merge pull request #630 from ImranR98/dev
HTML Source: treat whole link as version (also applies to APK filter regex)
2023-06-24 17:58:57 -04:00
16d63a4416 HTML Source: treat whole link as version (also applies to APK filter regex) 2023-06-24 17:58:00 -04:00
2eaf443359 Merge pull request #627 from ImranR98/dev
Fixed syntax errors in RU translation file
2023-06-23 18:01:48 -04:00
5979957d60 Fixed syntax errors in RU translation file 2023-06-23 18:01:34 -04:00
049eb5914c Merge pull request #626 from ImranR98/dev
App ID infer bugfix for GitHub
2023-06-23 16:31:25 -04:00
7577f3ac9b App ID infer bugfix for GitHub 2023-06-23 16:30:35 -04:00
50 changed files with 3518 additions and 1054 deletions

32
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,32 @@
---
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. -->
**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,29 @@
---
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. -->
**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. -->

View File

@ -2,42 +2,46 @@
Get Android App Updates Directly From the Source. Get Android App Updates Directly From the Source.
Obtainium allows you to install and update Open-Source Apps directly from their releases pages, and receive notifications when new releases are made available. Obtainium allows you to install and update Apps directly from their releases pages, and receive notifications when new releases are made available.
Motivation: [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0) Motivation: [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0)
Currently supported App sources: Currently supported App sources:
- [GitHub](https://github.com/) - Open Source - General:
- [GitLab](https://gitlab.com/) - [GitHub](https://github.com/)
- [Codeberg](https://codeberg.org/) - [GitLab](https://gitlab.com/)
- [F-Droid](https://f-droid.org/) - [Codeberg](https://codeberg.org/)
- [IzzyOnDroid](https://android.izzysoft.de/) - [F-Droid](https://f-droid.org/)
- [Mullvad](https://mullvad.net/en/) - Third Party F-Droid Repos
- [Signal](https://signal.org/) - [IzzyOnDroid](https://android.izzysoft.de/)
- [SourceForge](https://sourceforge.net/) - [SourceForge](https://sourceforge.net/)
- [SourceHut](https://git.sr.ht/) - [SourceHut](https://git.sr.ht/)
- [APKMirror](https://apkmirror.com/) (Track-Only) - Other - General:
- [APKPure](https://apkpure.com/) - [APKPure](https://apkpure.com/)
- Third Party F-Droid Repos - [Aptoide](https://aptoide.com/)
- Jenkins Jobs - [Uptodown](https://uptodown.com/)
- [Steam](https://store.steampowered.com/mobile) - [APKMirror](https://apkmirror.com/) (Track-Only)
- [Telegram App](https://telegram.org) - [Huawei AppGallery](https://appgallery.huawei.com/)
- [VLC](https://www.videolan.org/vlc/download-android.html) - Jenkins Jobs
- [Neutron Code](https://neutroncode.com) - Open Source - App-Specific:
- "HTML" (Fallback) - [Mullvad](https://mullvad.net/en/)
- Any other URL that returns an HTML page with links to APK files (if multiple, the last file alphabetically is picked) - [Signal](https://signal.org/)
- [VLC](https://videolan.org/)
- Other - App-Specific:
- [Telegram App](https://telegram.org)
- [Steam Mobile Apps](https://store.steampowered.com/mobile)
- [Neutron Code](https://neutroncode.com)
- "HTML" (Fallback): Any other URL that returns an HTML page with links to APK files
## Installation ## Installation
[<img src="https://github.com/machiav3lli/oandbackupx/blob/034b226cea5c1b30eb4f6a6f313e4dadcbb0ece4/badge_github.png" [<img src="https://github.com/machiav3lli/oandbackupx/blob/034b226cea5c1b30eb4f6a6f313e4dadcbb0ece4/badge_github.png"
alt="Get it on GitHub" alt="Get it on GitHub"
height="80">](https://github.com/ImranR98/Obtainium/releases) height="80">](https://github.com/ImranR98/Obtainium/releases)
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png"
alt="Get it on IzzyOnDroid" [PGP Public Key](https://keyserver.ubuntu.com/pks/lookup?search=contact%40imranr.dev&fingerprint=on&op=index)
height="80">](https://apt.izzysoft.de/fdroid/index/apk/dev.imranr.obtainium)
## Limitations ## Limitations
- Auto (unattended) updates are unsupported due to a lack of any capable Flutter plugin.
- For some sources, data is gathered using Web scraping and can easily break due to changes in website design. In such cases, more reliable methods may be unavailable. - For some sources, data is gathered using Web scraping and can easily break due to changes in website design. In such cases, more reliable methods may be unavailable.
## Screenshots ## Screenshots

View File

@ -49,7 +49,6 @@ android {
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "dev.imranr.obtainium" applicationId "dev.imranr.obtainium"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.

View File

@ -62,6 +62,7 @@
</application> </application>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
@ -70,4 +71,5 @@
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"/> android:maxSdkVersion="29"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
</manifest> </manifest>

311
assets/translations/br.json Normal file
View File

@ -0,0 +1,311 @@
{
"invalidURLForSource": "URL {} inválida",
"noReleaseFound": "Não foi possivel encontrar uma versão adequada",
"noVersionFound": "Não foi possivel encontrar uma versão lançada",
"urlMatchesNoSource": "URL não corresponde a uma fonte conhecida",
"cantInstallOlderVersion": "Não pode instalar uma versão anterior de um App",
"appIdMismatch": "ID do pacote baixado não é igual ao ID do App 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 Acceso Pessoal do GitHub (Reduz tempos de espera)",
"includePrereleases": "Incluir pré-lançamentos",
"fallbackToOlderReleases": "Retornar para versões anteriores",
"filterReleaseTitlesByRegEx": "Filtrar Titulos de Versões por Expressão Regular",
"invalidRegEx": "Expressão Regular Inválida",
"noDescription": "Sem descrição",
"cancel": "Cancelar",
"continue": "Continuar",
"requiredInBrackets": "(Necessário)",
"dropdownNoOptsError": "ERRO: O DROPDOWN DEVE TER PELO MENOS UMA OPÇÃO",
"colour": "Cor",
"githubStarredRepos": "Favoritados no GitHub",
"uname": "Nome de usuário",
"wrongArgNum": "Número de argumentos errado",
"xIsTrackOnly": "{} é 'Apenas Seguir'",
"source": "Fonte",
"app": "App",
"appsFromSourceAreTrackOnly": "Os apps desta fonte são 'Apenas Seguir'.",
"youPickedTrackOnly": "Você selecionou a opção 'Apenas Seguir'.",
"trackOnlyAppDescription": "Esse App vai ser seguido por atualizações, mais o Obtainium não poderá baixa-lo ou instala-lo.",
"cancelled": "Cancelado",
"appAlreadyAdded": "App já adicionado",
"alreadyUpToDateQuestion": "App já atualizado?",
"addApp": "Adicionar App",
"appSourceURL": "URL de origem do App",
"error": "Erro",
"add": "Adicionar",
"searchSomeSourcesLabel": "Procurar (Apenas Algumas Fontes)",
"search": "Procurar",
"additionalOptsFor": "Opções Adicionais para {}",
"supportedSources": "Fontes Compatíveis",
"trackOnlyInBrackets": "(Apenas Seguir)",
"searchableInBrackets": "(Pesquisável)",
"appsString": "Apps",
"noApps": "Sem Apps",
"noAppsForFilter": "Sem Apps para Filtrar",
"byX": "Por {}",
"percentProgress": "Progresso: {}%",
"pleaseWait": "Por Favor Espere",
"updateAvailable": "Atualização Disponível",
"estimateInBracketsShort": "(Aprox.)",
"notInstalled": "Não Instalado",
"estimateInBrackets": "(Aproximado)",
"selectAll": "Selecionar All",
"deselectN": "Deselecionar {}",
"xWillBeRemovedButRemainInstalled": "{} sera removido do Obtainium mais permanecerá instalado no dispositivo.",
"removeSelectedAppsQuestion": "Remover Apps Selecionados?",
"removeSelectedApps": "Remover Apps Selecionados",
"updateX": "Atualizar {}",
"installX": "Instalar {}",
"markXTrackOnlyAsUpdated": "Marcar {}\n(Apenas Seguir)\ncomo Atualizado",
"changeX": "Mudar {}",
"installUpdateApps": "Instalar/Atualizar Apps",
"installUpdateSelectedApps": "Instalar/Atualizar Apps Selecionados",
"markXSelectedAppsAsUpdated": "Marcar {} Apps Delecionados como Atualizados?",
"no": "Não",
"yes": "Sim",
"markSelectedAppsUpdated": "Marcar Apps Selecionados como Atualizados",
"pinToTop": "Fixar no topo",
"unpinFromTop": "Desafixar do topo",
"resetInstallStatusForSelectedAppsQuestion": "Reiniciar Status de Instalação para Apps Seleciondos?",
"installStatusOfXWillBeResetExplanation": "O status de instalação de qualquer app selecionado sera reiniciado.\n\nIsso pode ajudar quando uma versão de um App mostrada no Obtainium é incorreta devido a falhas ao atualizar ou outros problemas.",
"shareSelectedAppURLs": "Compartilhar URLs de Apps Selecionados",
"resetInstallStatus": "Reiniciar Status de Instalação",
"more": "Mais",
"removeOutdatedFilter": "Remover Filtro de Apps Desatualizados",
"showOutdatedOnly": "Mostrar Apenas Apps Desatualizados",
"filter": "Filtro",
"filterActive": "Filtro *",
"filterApps": "Filtrar Apps",
"appName": "Nome do App",
"author": "Autor",
"upToDateApps": "Apps Atualizados",
"nonInstalledApps": "Apps Não Instalados",
"importExport": "Importar/Exportar",
"settings": "Configurações",
"exportedTo": "Exportado para {}",
"obtainiumExport": "Exportar Obtainium",
"invalidInput": "Input Inválido",
"importedX": "Importado {}",
"obtainiumImport": "Importar Obtainium",
"importFromURLList": "Importar de Lista de URLs",
"searchQuery": "Pesquisa",
"appURLList": "Lista de URLs de Apps",
"line": "Linha",
"searchX": "Pesquisa {}",
"noResults": "Nenhum resultado encontrado",
"importX": "Importar {}",
"importedAppsIdDisclaimer": "Apps Importados podem ser mostrados incorretamente como \"Não Instalado\".\nPara consertar, reinstale-os usando o Obtainium.\nIsso não deve afetar dados do App.\n\nAfeta apenas métodos de importação de URL e de terceiros.",
"importErrors": "Erros de Importação",
"importedXOfYApps": "{} de {} Apps importados.",
"followingURLsHadErrors": "As seguintes URLs apresentaram erros:",
"okay": "Ok",
"selectURL": "Selecionar URL",
"selectURLs": "Selecionar URLs",
"pick": "Escolher",
"theme": "Tema",
"dark": "Escuro",
"light": "Claro",
"followSystem": "Seguir o Sistema",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Usar tema preto completamente escuro",
"appSortBy": "Classificar App por",
"authorName": "Autor/Nome",
"nameAuthor": "Nome/Autor",
"asAdded": "Como Adicionado",
"appSortOrder": "Ordem de classificação de Apps",
"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áginas da internet em App view",
"pinUpdates": "Fixar atualizações no topo da visão de Apps",
"updates": "Atualizações",
"sourceSpecific": "Específico a fonte",
"appSource": "Fonte de Apps",
"noLogs": "Sem Logs",
"appLogs": "Logs do App",
"close": "Fechar",
"share": "Compartilhar",
"appNotFound": "App não encontrado",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "Selecionar um APK",
"appHasMoreThanOnePackage": "{} tem mais de um pacote:",
"deviceSupportsXArch": "Seu dispositivo suporta a arquitetura de CPU {}.",
"deviceSupportsFollowingArchs": "Seu dispositivo suporta as seguintes arquiteturas de CPU:",
"warning": "Aviso",
"sourceIsXButPackageFromYPrompt": "A Fonte do App é '{}' mais o pacote lançado vem de '{}'. Continuar?",
"updatesAvailable": "Atualizações Disponíveis",
"updatesAvailableNotifDescription": "Notifica o usuário quando atualizações estão disponíveis um ou mais Apps seguidos pelo Obtainium",
"noNewUpdates": "Sem novas atualizações.",
"xHasAnUpdate": "{} tem uma atualização.",
"appsUpdated": "Apps Atualizados",
"appsUpdatedNotifDescription": "Notifica o usuário quando atualizações para um ou mais Apps foram aplicadas em segundo plano",
"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": "Apps Removidos",
"appsRemovedNotifDescription": "Notifica o usuário quando um ou mais Apps foram removidos devido a erros ao carregá-los",
"xWasRemovedDueToErrorY": "{} foi removido devido a este erro: {}",
"completeAppInstallation": "Instalação completa do App",
"obtainiumMustBeOpenToInstallApps": "Obtainium deve estar aberto para instalar Apps",
"completeAppInstallationNotifDescription": "Pede ao usuário que retorne ao Obtainium para finalizar a instalação de um App",
"checkingForUpdates": "Checando por Atualizações",
"checkingForUpdatesNotifDescription": "Notificação transiente que aparece quando checando por atualizações",
"pleaseAllowInstallPerm": "Por favor, permita o Obtainium instalar Apps",
"trackOnly": "Apenas Seguir",
"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 Checagem por Atualização: {}",
"remove": "Remover",
"yesMarkUpdated": "Sim, Marcar como Atualizado",
"fdroid": "F-Droid Official",
"appIdOrName": "ID do App ou Nome",
"appId": "ID do App",
"appWithIdOrNameNotFound": "Nenhum App foi encontrado com esse ID ou nome",
"reposHaveMultipleApps": "Repositórios podem conter multiplos Apps",
"fdroidThirdPartyRepo": "Repositórios de terceiros F-Droid",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"install": "Instalar",
"markInstalled": "Marcar Instalado",
"update": "Atualizar",
"markUpdated": "Marcar Atualizado",
"additionalOptions": "Opções Adicionais",
"disableVersionDetection": "Desativar Detecção de Versão",
"noVersionDetectionExplanation": "Essa opção deve apenas ser usada por Apps onde detecção de versão não funciona corretamente.",
"downloadingX": "Baixando {}",
"downloadNotifDescription": "Notifica o usuário do progresso ao baixar um App",
"noAPKFound": "APK não encontrado",
"noVersionDetection": "Sem Detecção de versão",
"categorize": "Categorizar",
"categories": "Categorias",
"category": "Categoria",
"noCategory": "Sem Categoria",
"noCategories": "Sem Categoria",
"deleteCategoriesQuestion": "Deletar Categorias?",
"categoryDeleteWarning": "Todos os Apps em categorias removidas serão descategorizados.",
"addCategory": "Adicionar Categoria",
"label": "Etiqueta",
"language": "Linguagem",
"copiedToClipboard": "Copiado para a área de transferência",
"storagePermissionDenied": "Permição ao armazenamento negada",
"selectedCategorizeWarning": "Isso vai substituir qualquer confirução de categoria para os Apps selecionados.",
"filterAPKsByRegEx": "Filtrar APKs por Expressão Regular",
"removeFromObtainium": "Remover do Obtainium",
"uninstallFromDevice": "Desinstalar do dispositivo",
"onlyWorksWithNonVersionDetectApps": "Apenas funciona para Apps com detecção de versão desativada.",
"releaseDateAsVersion": "Usar Data de Lançamento como Versão",
"releaseDateAsVersionExplanation": "Esta opção só deve ser usada para aplicativos onde a detecção de versão não funciona corretamente, mas há uma data de lançamento disponível.",
"changes": "Mudanças",
"releaseDate": "Data de Lançamento",
"importFromURLsInFile": "Importar de URLs em Arquivo (como OPML)",
"versionDetection": "Detecção de Versão",
"standardVersionDetection": "Detecção de versão padrão",
"groupByCategory": "Agroupar por Categoria",
"autoApkFilterByArch": "Tente filtrar APKs por arquitetura de CPU, se possível",
"overrideSource": "Substituir Fonte",
"dontShowAgain": "Não mostrar isso novamente",
"dontShowTrackOnlyWarnings": "Não mostrar avisos 'Apenas Seguir'",
"dontShowAPKOriginWarnings": "Não mostrar avisos de origem da APK",
"moveNonInstalledAppsToBottom": "Mover Apps não instalados para o fundo da visão de Apps",
"gitlabPATLabel": "Token de Acceso Pessoal do Gitlab\n(Ativa Pesquisa e Melhor Descoberta de APKs)",
"about": "Sobre",
"requiresCredentialsInSettings": "Isso requer credenciais adicionais (em Configurações)",
"checkOnStart": "Checar por atualizações ao iniciar ",
"tryInferAppIdFromCode": "Tente inferir o ID do App pelo código fonte",
"removeOnExternalUninstall": "Remover automaticamente Apps desinstalados externamente",
"pickHighestVersionCode": "Auto-selecionar o maior numero de versão do APK",
"checkUpdateOnDetailPage": "Checar por atualizações ao abrir a pagina de detalhes de um App",
"disablePageTransitions": "Desativar animações de transição de pagina",
"reversePageTransitions": "Reverter animações de transição de pagina",
"minStarCount": "Contagem Minima de Estrelas",
"addInfoBelow": "Adicionar essa informação abaixo.",
"addInfoInSettings": "Adicionar essa informação nas configurações.",
"githubSourceNote": "A limitação de taxa do GitHub pode ser evitada usando uma chave de API.",
"gitlabSourceNote": "A extração de APK do GitLab pode não funcionar sem uma chave de API.",
"sortByFileNamesNotLinks": "Classifique por nomes de arquivos em vez de links completos",
"filterReleaseNotesByRegEx": "Filtrar Notas de Lançamento por Expressão Regular",
"customLinkFilterRegex": "Filtro de Link Personalizado por Expressão Regular (Padrão '.apk$')",
"appsPossiblyUpdated": "Tentativas de atualização de Apps",
"appsPossiblyUpdatedNotifDescription": "Notifica o usuário de que atualizações de um ou mais Apps 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 Apps.",
"backgroundUpdateLimitsExplanation": "O sucesso de uma instalação em segundo plano só pode ser determinado quando o Obtainium é aberto.",
"verifyLatestTag": "Verifique a 'ultima' etiqueta",
"intermediateLinkRegex": "Filtre por um Link 'Intermediário' para Visitar Primeiro",
"intermediateLinkNotFound": "Link intermediário não encontrado",
"exemptFromBackgroundUpdates": "Isento de atualizações em segundo plano (se ativadas)",
"bgUpdatesOnWiFiOnly": "Desative atualizações em segundo plano quando não estiver em WiFi",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
"removeAppQuestion": {
"one": "Remover App?",
"other": "Remover Apps?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Muitas solicitações (taxa limitada) - tente novamente em {} minuto",
"other": "Muitas solicitações (taxa limitada) - tente novamente em {} minutos"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "A verificação de atualizações em segundo plano encontrou um {}, agendada uma nova verificação em {} minuto",
"other": "A verificação de atualizações em segundo plano encontrou um {}, agendada uma nova verificação em {} minutos"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "A verificação de atualizações em segundo plano encontrou {} atualização, o usuário sera notificado caso necessário",
"other": "A verificação de atualizações em segundo plano encontrou {} atualizações, o usuário sera notificado caso necessário"
},
"apps": {
"one": "{} App",
"other": "{} Apps"
},
"url": {
"one": "{} URL",
"other": "{} URLs"
},
"minute": {
"one": "{} Minuto",
"other": "{} Minutos"
},
"hour": {
"one": "{} Hora",
"other": "{} Horas"
},
"day": {
"one": "{} Dia",
"other": "{} Dias"
},
"clearedNLogsBeforeXAfterY": {
"one": "Limpo {n} log (antes = {antes}, depois = {depois})",
"other": "Limpados {n} logs (antes = {antes}, depois = {depois})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} e 1 outro app tem atualizações.",
"other": "{} e {} outros apps tem atualizações."
},
"xAndNMoreUpdatesInstalled": {
"one": "{} e 1 outro app foi atualizado.",
"other": "{} e {} outros apps foram atualizados."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} e 1 outro app pode ter sido atualizado.",
"other": "{} e {} outros apps podem ter sido atualizados."
}
}

308
assets/translations/bs.json Normal file
View File

@ -0,0 +1,308 @@
{
"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",
"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",
"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 {}.",
"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",
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
"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": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
}
}

View File

@ -11,15 +11,7 @@
"unexpectedError": "Unerwarteter Fehler", "unexpectedError": "Unerwarteter Fehler",
"ok": "Okay", "ok": "Okay",
"and": "und", "and": "und",
"startedBgUpdateTask": "Hintergrundaktualisierungsprüfung gestartet",
"bgUpdateIgnoreAfterIs": "Hintergrundaktualisierung 'ignoreAfter' ist {}",
"startedActualBGUpdateCheck": "Überprüfung der Hintergrundaktualisierung gestartet",
"bgUpdateTaskFinished": "Hintergrundaktualisierungsprüfung abgeschlossen",
"firstRun": "Dies ist der erste Start von Obtainium überhaupt",
"settingUpdateCheckIntervalTo": "Aktualisierungsintervall auf {} stellen",
"githubPATLabel": "GitHub Personal Access Token (Erhöht das Ratenlimit)", "githubPATLabel": "GitHub Personal Access Token (Erhöht das Ratenlimit)",
"githubPATHint": "PAT muss in diesem Format sein: Benutzername:Token",
"githubPATFormat": "Benutzername:Token",
"includePrereleases": "Vorabversionen einbeziehen", "includePrereleases": "Vorabversionen einbeziehen",
"fallbackToOlderReleases": "Fallback auf ältere Versionen", "fallbackToOlderReleases": "Fallback auf ältere Versionen",
"filterReleaseTitlesByRegEx": "Release-Titel nach regulärem Ausdruck\nfiltern", "filterReleaseTitlesByRegEx": "Release-Titel nach regulärem Ausdruck\nfiltern",
@ -49,7 +41,7 @@
"searchSomeSourcesLabel": "Suche (nur bestimmte Quellen)", "searchSomeSourcesLabel": "Suche (nur bestimmte Quellen)",
"search": "Suchen", "search": "Suchen",
"additionalOptsFor": "Zusatzoptionen für {}", "additionalOptsFor": "Zusatzoptionen für {}",
"supportedSourcesBelow": "Unterstützte Quellen:", "supportedSources": "Unterstützte Quellen",
"trackOnlyInBrackets": "(Nur Nachverfolgen)", "trackOnlyInBrackets": "(Nur Nachverfolgen)",
"searchableInBrackets": "(Durchsuchbar)", "searchableInBrackets": "(Durchsuchbar)",
"appsString": "Apps", "appsString": "Apps",
@ -229,11 +221,38 @@
"dontShowTrackOnlyWarnings": "Warnung für 'Nur Nachverfolgen' nicht anzeigen", "dontShowTrackOnlyWarnings": "Warnung für 'Nur Nachverfolgen' nicht anzeigen",
"dontShowAPKOriginWarnings": "Warnung für APK-Herkunft nicht anzeigen", "dontShowAPKOriginWarnings": "Warnung für APK-Herkunft nicht anzeigen",
"moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben", "moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben",
"gitlabPATLabel": "GitLab Personal Access Token (Aktiviert Suche)", "gitlabPATLabel": "GitLab Personal Access Token\n(Aktiviert Suche und bessere APK Entdeckung)",
"about": "Über", "about": "Über",
"requiresCredentialsInSettings": "Benötigt zusätzliche Anmeldedaten (in den Einstellungen)", "requiresCredentialsInSettings": "Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
"checkOnStart": "Überprüfe einmalig beim Start", "checkOnStart": "Überprüfe einmalig beim Start",
"tryInferAppIdFromCode": "Try inferring App ID from source code", "tryInferAppIdFromCode": "Versuche, die App-ID aus dem Quellcode zu ermitteln",
"removeOnExternalUninstall": "Automatisches Entfernen von extern deinstallierten Apps",
"pickHighestVersionCode": "Automatische Auswahl des APK mit höchstem Versionscode",
"checkUpdateOnDetailPage": "Nach Updates suchen, wenn eine App-Detailseite geöffnet wird",
"disablePageTransitions": "Animationen für Seitenübergänge deaktivieren",
"reversePageTransitions": "Umgekehrte Animationen für Seitenübergänge",
"minStarCount": "Minimale Anzahl von Sternen",
"addInfoBelow": "Fügen Sie diese Informationen unten hinzu.",
"addInfoInSettings": "Fügen Sie diese Info in den Einstellungen hinzu.",
"githubSourceNote": "Die GitHub-Ratenbegrenzung kann mit einem API-Schlüssel umgangen werden.",
"gitlabSourceNote": "GitLab APK-Extraktion funktioniert möglicherweise nicht ohne API-Schlüssel",
"sortByFileNamesNotLinks": "Sortiere nach Dateinamen, anstelle von ganzen 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 {}.",
"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",
"exemptFromBackgroundUpdates": "Ausschluss von Hintergrundaktualisierungen (falls aktiviert)",
"bgUpdatesOnWiFiOnly": "Hintergrundaktualisierungen deaktivieren, wenn kein WLAN vorhanden ist",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
"removeAppQuestion": { "removeAppQuestion": {
"one": "App entfernen?", "one": "App entfernen?",
"other": "Apps entfernen?" "other": "Apps entfernen?"
@ -281,5 +300,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} und 1 weitere Anwendung wurden aktualisiert.", "one": "{} und 1 weitere Anwendung wurden aktualisiert.",
"other": "{} und {} weitere Anwendungen wurden aktualisiert." "other": "{} und {} weitere Anwendungen wurden aktualisiert."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} und 1 weitere Anwendung wurden möglicherweise aktualisiert.",
"other": "{} und {} weitere Anwendungen wurden möglicherweise aktualisiert."
} }
} }

View File

@ -11,15 +11,7 @@
"unexpectedError": "Unexpected Error", "unexpectedError": "Unexpected Error",
"ok": "Okay", "ok": "Okay",
"and": "and", "and": "and",
"startedBgUpdateTask": "Started BG update check task",
"bgUpdateIgnoreAfterIs": "Bg update ignoreAfter is {}",
"startedActualBGUpdateCheck": "Started actual BG update checking",
"bgUpdateTaskFinished": "Finished BG update check task",
"firstRun": "This is the first ever run of Obtainium",
"settingUpdateCheckIntervalTo": "Setting update interval to {}",
"githubPATLabel": "GitHub Personal Access Token (Increases Rate Limit)", "githubPATLabel": "GitHub Personal Access Token (Increases Rate Limit)",
"githubPATHint": "PAT must be in this format: username:token",
"githubPATFormat": "username:token",
"includePrereleases": "Include prereleases", "includePrereleases": "Include prereleases",
"fallbackToOlderReleases": "Fallback to older releases", "fallbackToOlderReleases": "Fallback to older releases",
"filterReleaseTitlesByRegEx": "Filter Release Titles by Regular Expression", "filterReleaseTitlesByRegEx": "Filter Release Titles by Regular Expression",
@ -49,7 +41,7 @@
"searchSomeSourcesLabel": "Search (Some Sources Only)", "searchSomeSourcesLabel": "Search (Some Sources Only)",
"search": "Search", "search": "Search",
"additionalOptsFor": "Additional Options for {}", "additionalOptsFor": "Additional Options for {}",
"supportedSourcesBelow": "Supported Sources:", "supportedSources": "Supported Sources",
"trackOnlyInBrackets": "(Track-Only)", "trackOnlyInBrackets": "(Track-Only)",
"searchableInBrackets": "(Searchable)", "searchableInBrackets": "(Searchable)",
"appsString": "Apps", "appsString": "Apps",
@ -121,7 +113,7 @@
"followSystem": "Follow System", "followSystem": "Follow System",
"obtainium": "Obtainium", "obtainium": "Obtainium",
"materialYou": "Material You", "materialYou": "Material You",
"useBlackTheme": "Use Pure Black Dark Theme", "useBlackTheme": "Use pure black dark theme",
"appSortBy": "App Sort By", "appSortBy": "App Sort By",
"authorName": "Author/Name", "authorName": "Author/Name",
"nameAuthor": "Name/Author", "nameAuthor": "Name/Author",
@ -132,8 +124,8 @@
"bgUpdateCheckInterval": "Background Update Checking Interval", "bgUpdateCheckInterval": "Background Update Checking Interval",
"neverManualOnly": "Never - Manual Only", "neverManualOnly": "Never - Manual Only",
"appearance": "Appearance", "appearance": "Appearance",
"showWebInAppView": "Show Source Webpage in App View", "showWebInAppView": "Show Source webpage in App view",
"pinUpdates": "Pin Updates to Top of Apps View", "pinUpdates": "Pin updates to top of Apps view",
"updates": "Updates", "updates": "Updates",
"sourceSpecific": "Source-Specific", "sourceSpecific": "Source-Specific",
"appSource": "App Source", "appSource": "App Source",
@ -226,14 +218,44 @@
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible", "autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
"overrideSource": "Override Source", "overrideSource": "Override Source",
"dontShowAgain": "Don't show this again", "dontShowAgain": "Don't show this again",
"dontShowTrackOnlyWarnings": "Don't Show 'Track-Only' Warnings", "dontShowTrackOnlyWarnings": "Don't show 'Track-Only' warnings",
"dontShowAPKOriginWarnings": "Don't Show APK Origin Warnings", "dontShowAPKOriginWarnings": "Don't show APK origin warnings",
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View", "moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check Once on Start", "checkOnStart": "Check for updates on startup",
"tryInferAppIdFromCode": "Try inferring App ID from source code", "tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
"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",
"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 First",
"intermediateLinkNotFound": "Intermediate link not found",
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Remove App?", "one": "Remove App?",
"other": "Remove Apps?" "other": "Remove Apps?"
@ -279,7 +301,11 @@
"other": "{} and {} more apps have updates." "other": "{} and {} more apps have updates."
}, },
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} and 1 more app were updated.", "one": "{} and 1 more app was updated.",
"other": "{} and {} more apps were updated." "other": "{} and {} more apps were updated."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@ -11,15 +11,7 @@
"unexpectedError": "Error Inesperado", "unexpectedError": "Error Inesperado",
"ok": "Correcto", "ok": "Correcto",
"and": "y", "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)", "githubPATLabel": "Token de Acceso Personal de GitHub (Reduce tiempos de espera)",
"githubPATHint": "El TAP debe tener este formato: nombre_de_usuario:token",
"githubPATFormat": "nombre_de_usuario:token",
"includePrereleases": "Incluir versiones preliminares", "includePrereleases": "Incluir versiones preliminares",
"fallbackToOlderReleases": "Retorceder a versiones previas", "fallbackToOlderReleases": "Retorceder a versiones previas",
"filterReleaseTitlesByRegEx": "Filtra Títulos de Versiones mediantes Expresiones Regulares", "filterReleaseTitlesByRegEx": "Filtra Títulos de Versiones mediantes Expresiones Regulares",
@ -49,7 +41,7 @@
"searchSomeSourcesLabel": "Buscar (Solo Algunas Fuentes)", "searchSomeSourcesLabel": "Buscar (Solo Algunas Fuentes)",
"search": "Buscar", "search": "Buscar",
"additionalOptsFor": "Opciones Adicionales para {}", "additionalOptsFor": "Opciones Adicionales para {}",
"supportedSourcesBelow": "Fuentes Soportadas:", "supportedSources": "Fuentes Soportadas",
"trackOnlyInBrackets": "(Solo Seguimiento)", "trackOnlyInBrackets": "(Solo Seguimiento)",
"searchableInBrackets": "(Soporta Búsquedas)", "searchableInBrackets": "(Soporta Búsquedas)",
"appsString": "Aplicaciones", "appsString": "Aplicaciones",
@ -228,12 +220,39 @@
"dontShowAgain": "No mostrar de nuevo", "dontShowAgain": "No mostrar de nuevo",
"dontShowTrackOnlyWarnings": "No mostrar avisos de 'Solo Seguimiento'", "dontShowTrackOnlyWarnings": "No mostrar avisos de 'Solo Seguimiento'",
"dontShowAPKOriginWarnings": "No mostrar avisos de las fuentes de las APks", "dontShowAPKOriginWarnings": "No mostrar avisos de las fuentes de las APks",
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View", "moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check Once on Start", "checkOnStart": "Check for updates on startup",
"tryInferAppIdFromCode": "Try inferring App ID from source code", "tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
"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",
"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 {}.",
"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",
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
"removeAppQuestion": { "removeAppQuestion": {
"one": "¿Eliminar Aplicación?", "one": "¿Eliminar Aplicación?",
"other": "¿Eliminar Aplicaciones?" "other": "¿Eliminar Aplicaciones?"
@ -281,5 +300,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} y 1 aplicación más han sido actualizadas.", "one": "{} y 1 aplicación más han sido actualizadas.",
"other": "{} y {} aplicaciones más han sido actualizadas." "other": "{} y {} aplicaciones más han sido actualizadas."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@ -11,15 +11,7 @@
"unexpectedError": "خطای غیرمنتظره", "unexpectedError": "خطای غیرمنتظره",
"ok": "باشه", "ok": "باشه",
"and": "و", "and": "و",
"startedBgUpdateTask": "شروع بررسی بروزرسانی BG",
"bgUpdateIgnoreAfterIs": "نادیده گرفتن بروزرسانی BG بعد از {} است",
"startedActualBGUpdateCheck": "بررسی به‌روزرسانی واقعی BG آغاز شد",
"bgUpdateTaskFinished": "کار بررسی به‌روزرسانی BG تمام شد",
"firstRun": "این اولین اجرای Obtainium است",
"settingUpdateCheckIntervalTo": "تنظیم فاصله به‌روزرسانی روی {}",
"githubPATLabel": "توکن دسترسی شخصی گیت هاب(محدودیت نرخ را افزایش میدهد)", "githubPATLabel": "توکن دسترسی شخصی گیت هاب(محدودیت نرخ را افزایش میدهد)",
"githubPATHint": "PAT باید در این قالب باشد: username:token",
"githubPATFormat": "username:token",
"includePrereleases": "شامل نسخه های اولیه", "includePrereleases": "شامل نسخه های اولیه",
"fallbackToOlderReleases": "بازگشت به نسخه های قدیمی تر", "fallbackToOlderReleases": "بازگشت به نسخه های قدیمی تر",
"filterReleaseTitlesByRegEx": "عناوین انتشار را با بیان منظم فیلتر کنید", "filterReleaseTitlesByRegEx": "عناوین انتشار را با بیان منظم فیلتر کنید",
@ -49,7 +41,7 @@
"searchSomeSourcesLabel": "جستجو (فقط برخی منابع)", "searchSomeSourcesLabel": "جستجو (فقط برخی منابع)",
"search": "جستجو کردن", "search": "جستجو کردن",
"additionalOptsFor": "گزینه های اضافی برای {}", "additionalOptsFor": "گزینه های اضافی برای {}",
"supportedSourcesBelow": "منابع پشتیبانی شده:", "supportedSources": "منابع پشتیبانی شده",
"trackOnlyInBrackets": "«فقط ردیابی»", "trackOnlyInBrackets": "«فقط ردیابی»",
"searchableInBrackets": "(قابل جستجو)", "searchableInBrackets": "(قابل جستجو)",
"appsString": "برنامه ها", "appsString": "برنامه ها",
@ -228,12 +220,39 @@
"dontShowAgain": "دوباره این را نشان نده", "dontShowAgain": "دوباره این را نشان نده",
"dontShowTrackOnlyWarnings": "هشدار 'فقط ردیابی' را نشان ندهید", "dontShowTrackOnlyWarnings": "هشدار 'فقط ردیابی' را نشان ندهید",
"dontShowAPKOriginWarnings": "هشدارهای منبع APK را نشان ندهید", "dontShowAPKOriginWarnings": "هشدارهای منبع APK را نشان ندهید",
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View", "moveNonInstalledAppsToBottom": "برنامه های نصب نشده را به نمای پایین برنامه ها منتقل کنید",
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "رمز دسترسی شخصی GitLab\n(جستجو و کشف بهتر APK را فعال میکند)",
"about": "About", "about": "درباره",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "این به اعتبارنامه های اضافی نیاز دارد (در تنظیمات)",
"checkOnStart": "Check Once on Start", "checkOnStart": "بررسی در شروع",
"tryInferAppIdFromCode": "Try inferring App ID from source code", "tryInferAppIdFromCode": "شناسه برنامه را از کد منبع استنباط کنید",
"removeOnExternalUninstall": "حذف خودکار برنامه های حذف نصب شده خارجی",
"pickHighestVersionCode": "انتخاب خودکار بالاترین کد نسخه APK",
"checkUpdateOnDetailPage": "برای باز کردن صفحه جزئیات برنامه، به‌روزرسانی‌ها را بررسی کنید",
"disablePageTransitions": "غیرفعال کردن انیمیشن های انتقال صفحه",
"reversePageTransitions": "انیمیشن های انتقال معکوس صفحه",
"minStarCount": "حداقل تعداد ستاره",
"addInfoBelow": "این اطلاعات را در زیر اضافه کنید",
"addInfoInSettings": "این اطلاعات را در تنظیمات اضافه کنید.",
"githubSourceNote": "با استفاده از کلید API می توان از محدودیت نرخ GitHub جلوگیری کرد.",
"gitlabSourceNote": "استخراج APK GitLab ممکن است بدون کلید API کار نکند.",
"sortByFileNamesNotLinks": "مرتب سازی بر اساس نام فایل به جای پیوندهای کامل",
"filterReleaseNotesByRegEx": "یادداشت های انتشار را با بیان منظم فیلتر کنید",
"customLinkFilterRegex": "فیلتر پیوند سفارشی بر اساس عبارت منظم (پیش‌فرض '.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 {}.",
"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",
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
"removeAppQuestion": { "removeAppQuestion": {
"one": "برنامه حذف شود؟", "one": "برنامه حذف شود؟",
"other": "برنامه ها حذف شوند؟" "other": "برنامه ها حذف شوند؟"
@ -281,5 +300,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} و 1 برنامه دیگر به روز شدند.", "one": "{} و 1 برنامه دیگر به روز شدند.",
"other": "{} و {} برنامه دیگر به روز شدند." "other": "{} و {} برنامه دیگر به روز شدند."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@ -11,15 +11,7 @@
"unexpectedError": "Erreur inattendue", "unexpectedError": "Erreur inattendue",
"ok": "Okay", "ok": "Okay",
"and": "et", "and": "et",
"startedBgUpdateTask": "Démarrage de la tâche de vérification de mise à jour en arrière-plan",
"bgUpdateIgnoreAfterIs": "Mise à jour en arrière-plan est ignoré après {}",
"startedActualBGUpdateCheck": "Démarrage de la vérification de la mise à jour en arrière-plan",
"bgUpdateTaskFinished": "Tâche de vérification de la mise à jour en arrière-plan terminée",
"firstRun": "Il s'agit de la toute première exécution d'Obtainium",
"settingUpdateCheckIntervalTo": "Définition de l'intervalle de mise à jour sur {}",
"githubPATLabel": "Jeton d'Accès Personnel GitHub (Augmente la limite de débit)", "githubPATLabel": "Jeton d'Accès Personnel GitHub (Augmente la limite de débit)",
"githubPATHint": "Le JAP doit être dans ce format : username:token",
"githubPATFormat": "username:token",
"includePrereleases": "Inclure les avant-premières", "includePrereleases": "Inclure les avant-premières",
"fallbackToOlderReleases": "Retour aux anciennes versions", "fallbackToOlderReleases": "Retour aux anciennes versions",
"filterReleaseTitlesByRegEx": "Filtrer les titres de version par expression régulière", "filterReleaseTitlesByRegEx": "Filtrer les titres de version par expression régulière",
@ -45,11 +37,11 @@
"addApp": "Ajouter une application", "addApp": "Ajouter une application",
"appSourceURL": "URL de la source de l'application", "appSourceURL": "URL de la source de l'application",
"error": "Erreur", "error": "Erreur",
"add": "Ajoutée", "add": "Ajouter",
"searchSomeSourcesLabel": "Rechercher (certaines sources uniquement)", "searchSomeSourcesLabel": "Rechercher (certaines sources uniquement)",
"search": "Rechercher", "search": "Rechercher",
"additionalOptsFor": "Options supplémentaires pour {}", "additionalOptsFor": "Options supplémentaires pour {}",
"supportedSourcesBelow": "Sources prises en charge :", "supportedSources": "Sources prises en charge ",
"trackOnlyInBrackets": "(Suivi uniquement)", "trackOnlyInBrackets": "(Suivi uniquement)",
"searchableInBrackets": "(Recherchable)", "searchableInBrackets": "(Recherchable)",
"appsString": "Applications", "appsString": "Applications",
@ -121,7 +113,7 @@
"followSystem": "Suivre le système", "followSystem": "Suivre le système",
"obtainium": "Obtainium", "obtainium": "Obtainium",
"materialYou": "Material You", "materialYou": "Material You",
"useBlackTheme": "Use Pure Black Dark Theme", "useBlackTheme": "Use pure black dark theme",
"appSortBy": "Applications triées par", "appSortBy": "Applications triées par",
"authorName": "Auteur/Nom", "authorName": "Auteur/Nom",
"nameAuthor": "Nom/Auteur", "nameAuthor": "Nom/Auteur",
@ -227,13 +219,40 @@
"overrideSource": "Override Source", "overrideSource": "Override Source",
"dontShowAgain": "Don't show this again", "dontShowAgain": "Don't show this again",
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning", "dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning",
"dontShowAPKOriginWarnings": "Don't Show APK Origin Warnings", "dontShowAPKOriginWarnings": "Don't show APK origin warnings",
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View", "moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check Once on Start", "checkOnStart": "Check for updates on startup",
"tryInferAppIdFromCode": "Try inferring App ID from source code", "tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
"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",
"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 {}.",
"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",
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Supprimer l'application ?", "one": "Supprimer l'application ?",
"other": "Supprimer les applications ?" "other": "Supprimer les applications ?"
@ -281,5 +300,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} et 1 autre application ont été mises à jour.", "one": "{} et 1 autre application ont été mises à jour.",
"other": "{} et {} autres applications ont été mises à jour." "other": "{} et {} autres applications ont été mises à jour."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@ -11,15 +11,7 @@
"unexpectedError": "Váratlan hiba", "unexpectedError": "Váratlan hiba",
"ok": "Oké", "ok": "Oké",
"and": "és", "and": "és",
"startedBgUpdateTask": "Háttérfrissítés ellenőrzési feladat elindítva", "githubPATLabel": "GitHub Personal Access Token (megnöveli a díjkorlátot)",
"bgUpdateIgnoreAfterIs": "Háttérfrissítés ignoreAfter a következő: {}",
"startedActualBGUpdateCheck": "Elkezdődött a tényleges háttérfrissítés ellenőrzése",
"bgUpdateTaskFinished": "A háttérfrissítés ellenőrzési feladat befejeződött",
"firstRun": "Ez az Obtainium első futása",
"settingUpdateCheckIntervalTo": "A frissítési intervallum beállítása erre: {}",
"githubPATLabel": "GitHub személyes hozzáférési token (megnöveli a díjkorlátot)",
"githubPATHint": "A PAT-nak a következő formátumban kell lennie: felhasználónév:token",
"githubPATFormat": "felhasználónév:token",
"includePrereleases": "Tartalmazza az előzetes kiadásokat", "includePrereleases": "Tartalmazza az előzetes kiadásokat",
"fallbackToOlderReleases": "Visszatérés a régebbi kiadásokhoz", "fallbackToOlderReleases": "Visszatérés a régebbi kiadásokhoz",
"filterReleaseTitlesByRegEx": "A kiadás címeinek szűrése reguláris kifejezéssel", "filterReleaseTitlesByRegEx": "A kiadás címeinek szűrése reguláris kifejezéssel",
@ -49,7 +41,7 @@
"searchSomeSourcesLabel": "Keresés (csak egyes források)", "searchSomeSourcesLabel": "Keresés (csak egyes források)",
"search": "Keresés", "search": "Keresés",
"additionalOptsFor": "További lehetőségek a következőhöz: {}", "additionalOptsFor": "További lehetőségek a következőhöz: {}",
"supportedSourcesBelow": "Támogatott források:", "supportedSources": "Támogatott források",
"trackOnlyInBrackets": "(Csak nyomonkövetés)", "trackOnlyInBrackets": "(Csak nyomonkövetés)",
"searchableInBrackets": "(Kereshető)", "searchableInBrackets": "(Kereshető)",
"appsString": "Appok", "appsString": "Appok",
@ -96,10 +88,10 @@
"importExport": "Import/Export", "importExport": "Import/Export",
"settings": "Beállítások", "settings": "Beállítások",
"exportedTo": "Exportálva ide {}", "exportedTo": "Exportálva ide {}",
"obtainiumExport": "Obtainium Export", "obtainiumExport": "Obtainium Adat Exportálás",
"invalidInput": "Hibás bemenet", "invalidInput": "Hibás bemenet",
"importedX": "Importálva innen {}", "importedX": "Importálva innen {}",
"obtainiumImport": "Obtainium Import", "obtainiumImport": "Obtainium Adat Importálás",
"importFromURLList": "Importálás URL listából", "importFromURLList": "Importálás URL listából",
"searchQuery": "Keresési lekérdezés", "searchQuery": "Keresési lekérdezés",
"appURLList": "App URL lista", "appURLList": "App URL lista",
@ -139,8 +131,8 @@
"appSource": "App forrás", "appSource": "App forrás",
"noLogs": "Nincsenek naplók", "noLogs": "Nincsenek naplók",
"appLogs": "App naplók", "appLogs": "App naplók",
"close": "Bezár", "close": "Bezárás",
"share": "Megoszt", "share": "Megosztás",
"appNotFound": "App nem található", "appNotFound": "App nem található",
"obtainiumExportHyphenatedLowercase": "obtainium-export", "obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "Válasszon egy APK-t", "pickAnAPK": "Válasszon egy APK-t",
@ -222,17 +214,44 @@
"versionDetection": "Verzió érzékelés", "versionDetection": "Verzió érzékelés",
"standardVersionDetection": "Alapért. verzió érzékelés", "standardVersionDetection": "Alapért. verzió érzékelés",
"groupByCategory": "Csoportosítás Kategória alapján", "groupByCategory": "Csoportosítás Kategória alapján",
"autoApkFilterByArch": "Ha lehetséges, próbálja CPU architektúra szerint szűrni az APK-kat", "autoApkFilterByArch": "Ha lehetséges, próbálja CPU architektúra szerint szűrni az APK-okat",
"overrideSource": "Forrás felülbírálása", "overrideSource": "Forrás felülbírálása",
"dontShowAgain": "Ne mutassa ezt újra", "dontShowAgain": "Ne mutassa ezt újra",
"dontShowTrackOnlyWarnings": "Ne jelenítsen meg 'Csak nyomon követés' figyelmeztetést", "dontShowTrackOnlyWarnings": "Ne jelenítsen meg 'Csak nyomon követés' figyelmeztetést",
"dontShowAPKOriginWarnings": "Ne jelenítsen meg az APK eredetére vonatkozó figyelmeztetéseket", "dontShowAPKOriginWarnings": "Ne jelenítsen meg az APK eredetére vonatkozó figyelmeztetéseket",
"moveNonInstalledAppsToBottom": "Helyezze át a nem telepített appokat az App nézet aljára", "moveNonInstalledAppsToBottom": "Helyezze át a nem telepített appokat az App nézet aljára",
"gitlabPATLabel": "GitLab Personal Access Token (Engedélyezi a Keresést)", "gitlabPATLabel": "GitLab Personal Access Token\n(Engedélyezi a Keresést és jobb APK felfedezés)",
"about": "Rólunk", "about": "Rólunk",
"requiresCredentialsInSettings": "Ehhez további hitelesítő adatokra van szükség (a Beállításokban)", "requiresCredentialsInSettings": "Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
"checkOnStart": "Egyszer az indításkor", "checkOnStart": "Egyszer az alkalmazás indításakor is",
"tryInferAppIdFromCode": "Próbálja kikövetkeztetni az app azonosítót a forráskódból", "tryInferAppIdFromCode": "Próbálja kikövetkeztetni az app azonosítót a forráskódból",
"removeOnExternalUninstall": "A külsőleg eltávolított appok auto. eltávolítása",
"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.",
"sortByFileNamesNotLinks": "Fájlnevek szerinti elrendezés teljes linkek helyett",
"filterReleaseNotesByRegEx": "Kiadási megjegyzések szűrése reguláris kifejezéssel",
"customLinkFilterRegex": "Egyéni APK hivatkozásszűrő reguláris kifejezéssel (Alapérték '.apk$')",
"appsPossiblyUpdated": "App frissítési kísérlet",
"appsPossiblyUpdatedNotifDescription": "Értesíti a felhasználót, hogy egy vagy több alkalmazás frissítése lehetséges a háttérben",
"xWasPossiblyUpdatedToY": "{} frissítve lehet erre {}.",
"backgroundUpdateReqsExplanation": "Előfordulhat, hogy nem minden appnál lehetséges a háttérbeli frissítés.",
"backgroundUpdateLimitsExplanation": "A háttérben történő telepítés sikeressége csak az Obtainium megnyitásakor állapítható meg.",
"verifyLatestTag": "Ellenőrizze a „legújabb” címkét",
"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": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?", "one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazást?" "other": "Eltávolítja az alkalmazást?"
@ -275,10 +294,14 @@
}, },
"xAndNMoreUpdatesAvailable": { "xAndNMoreUpdatesAvailable": {
"one": "A(z) {} és 1 további alkalmazás frissítéseket kapott.", "one": "A(z) {} és 1 további alkalmazás frissítéseket kapott.",
"other": "{} és további {} alkalmazás frissítéseket kapott." "other": "{} és {} további alkalmazás frissítéseket kapott."
}, },
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "A(z) {} és 1 további alkalmazás frissítve.", "one": "A(z) {} és 1 további alkalmazás frissítve.",
"other": "{} és további {} alkalmazás frissítve." "other": "{} és {} további alkalmazás frissítve."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} és 1 további alkalmazás is frissült.",
"other": "{} és {} további alkalmazás is frissült."
} }
} }

View File

@ -11,15 +11,7 @@
"unexpectedError": "Errore imprevisto", "unexpectedError": "Errore imprevisto",
"ok": "Va bene", "ok": "Va bene",
"and": "e", "and": "e",
"startedBgUpdateTask": "Avviata l'attività di controllo degli aggiornamenti in secondo piano",
"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)", "githubPATLabel": "GitHub Personal Access Token (diminuisce limite di traffico)",
"githubPATHint": "PAT deve seguire questo formato: nomeutente:token",
"githubPATFormat": "nomeutente:token",
"includePrereleases": "Includi prerelease", "includePrereleases": "Includi prerelease",
"fallbackToOlderReleases": "Ripiega su release precedenti", "fallbackToOlderReleases": "Ripiega su release precedenti",
"filterReleaseTitlesByRegEx": "Filtra release con espressioni regolari", "filterReleaseTitlesByRegEx": "Filtra release con espressioni regolari",
@ -49,7 +41,7 @@
"searchSomeSourcesLabel": "Cerca (solo per alcune fonti)", "searchSomeSourcesLabel": "Cerca (solo per alcune fonti)",
"search": "Cerca", "search": "Cerca",
"additionalOptsFor": "Opzioni aggiuntive per {}", "additionalOptsFor": "Opzioni aggiuntive per {}",
"supportedSourcesBelow": "Fonti supportate:", "supportedSources": "Fonti supportate",
"trackOnlyInBrackets": "(Solo-Monitoraggio)", "trackOnlyInBrackets": "(Solo-Monitoraggio)",
"searchableInBrackets": "(ricercabile)", "searchableInBrackets": "(ricercabile)",
"appsString": "App", "appsString": "App",
@ -229,11 +221,38 @@
"dontShowTrackOnlyWarnings": "Non mostrare gli avvisi 'Solo-Monitoraggio'", "dontShowTrackOnlyWarnings": "Non mostrare gli avvisi 'Solo-Monitoraggio'",
"dontShowAPKOriginWarnings": "Non mostrare gli avvisi di origine dell'APK", "dontShowAPKOriginWarnings": "Non mostrare gli avvisi di origine dell'APK",
"moveNonInstalledAppsToBottom": "Sposta le app non installate in fondo alla lista", "moveNonInstalledAppsToBottom": "Sposta le app non installate in fondo alla lista",
"gitlabPATLabel": "GitLab Personal Access Token (attiva la ricerca)", "gitlabPATLabel": "GitLab Personal Access Token\n(attiva la ricerca and Better APK Discovery)",
"about": "Informazioni", "about": "Informazioni",
"requiresCredentialsInSettings": "Servono credenziali aggiuntive (in Impostazioni)", "requiresCredentialsInSettings": "Servono credenziali aggiuntive (in Impostazioni)",
"checkOnStart": "Controlla una volta all'avvio", "checkOnStart": "Controlla una volta all'avvio",
"tryInferAppIdFromCode": "Prova a dedurre l'ID dell'app dal codice sorgente", "tryInferAppIdFromCode": "Prova a dedurre l'ID dell'app dal codice sorgente",
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
"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",
"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 {}.",
"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",
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Rimuovere l'app?", "one": "Rimuovere l'app?",
"other": "Rimuovere le app?" "other": "Rimuovere le app?"
@ -281,5 +300,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} e un'altra app sono state aggiornate.", "one": "{} e un'altra app sono state aggiornate.",
"other": "{} e altre {} app sono state aggiornate." "other": "{} e altre {} app sono state aggiornate."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@ -11,18 +11,10 @@
"unexpectedError": "予期せぬエラーが発生しました", "unexpectedError": "予期せぬエラーが発生しました",
"ok": "OK", "ok": "OK",
"and": "と", "and": "と",
"startedBgUpdateTask": "バックグラウンドのアップデート確認タスクを開始",
"bgUpdateIgnoreAfterIs": "Bg update ignoreAfter is {}",
"startedActualBGUpdateCheck": "実際のバックグラウンドのアップデート確認を開始",
"bgUpdateTaskFinished": "バックグラウンドのアップデート確認タスクを終了",
"firstRun": "これがObtainiumの最初の実行です",
"settingUpdateCheckIntervalTo": "確認間隔を{}に設定する",
"githubPATLabel": "GitHub パーソナルアクセストークン (レート制限の引き上げ)", "githubPATLabel": "GitHub パーソナルアクセストークン (レート制限の引き上げ)",
"githubPATHint": "PATは次の形式でなければなりません: ユーザー名:トークン",
"githubPATFormat": "ユーザー名:トークン",
"includePrereleases": "プレリリースを含む", "includePrereleases": "プレリリースを含む",
"fallbackToOlderReleases": "旧リリースへのフォールバック", "fallbackToOlderReleases": "旧リリースへのフォールバック",
"filterReleaseTitlesByRegEx": "正規表現でリリースタイトルを絞り込む", "filterReleaseTitlesByRegEx": "正規表現でリリースタイトルをフィルタリングする",
"invalidRegEx": "無効な正規表現", "invalidRegEx": "無効な正規表現",
"noDescription": "説明はありません", "noDescription": "説明はありません",
"cancel": "キャンセル", "cancel": "キャンセル",
@ -49,7 +41,7 @@
"searchSomeSourcesLabel": "検索 (一部ソースのみ)", "searchSomeSourcesLabel": "検索 (一部ソースのみ)",
"search": "検索", "search": "検索",
"additionalOptsFor": "{}の追加オプション", "additionalOptsFor": "{}の追加オプション",
"supportedSourcesBelow": "対応するソース:", "supportedSources": "対応するソース",
"trackOnlyInBrackets": "(追跡のみ)", "trackOnlyInBrackets": "(追跡のみ)",
"searchableInBrackets": "(検索可能)", "searchableInBrackets": "(検索可能)",
"appsString": "アプリ", "appsString": "アプリ",
@ -88,7 +80,7 @@
"showOutdatedOnly": "アップデートが存在するアプリのみ表示する", "showOutdatedOnly": "アップデートが存在するアプリのみ表示する",
"filter": "フィルター", "filter": "フィルター",
"filterActive": "フィルター *", "filterActive": "フィルター *",
"filterApps": "アプリを絞り込む", "filterApps": "アプリをフィルタリングする",
"appName": "アプリ名", "appName": "アプリ名",
"author": "作者", "author": "作者",
"upToDateApps": "最新のアプリ", "upToDateApps": "最新のアプリ",
@ -135,7 +127,7 @@
"showWebInAppView": "アプリページにソースのWebページを表示する", "showWebInAppView": "アプリページにソースのWebページを表示する",
"pinUpdates": "アップデートがあるアプリをトップに固定する", "pinUpdates": "アップデートがあるアプリをトップに固定する",
"updates": "アップデート", "updates": "アップデート",
"sourceSpecific": "Github アクセストークン", "sourceSpecific": "ソース別の設定",
"appSource": "アプリのソース", "appSource": "アプリのソース",
"noLogs": "ログはありません", "noLogs": "ログはありません",
"appLogs": "アプリのログ", "appLogs": "アプリのログ",
@ -211,7 +203,7 @@
"copiedToClipboard": "クリップボードにコピーしました", "copiedToClipboard": "クリップボードにコピーしました",
"storagePermissionDenied": "ストレージ権限が拒否されました", "storagePermissionDenied": "ストレージ権限が拒否されました",
"selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。", "selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。",
"filterAPKsByRegEx": "正規表現でAPKを絞り込む", "filterAPKsByRegEx": "正規表現でAPKをフィルタリングする",
"removeFromObtainium": "Obtainiumから削除する", "removeFromObtainium": "Obtainiumから削除する",
"uninstallFromDevice": "デバイスからアンインストールする", "uninstallFromDevice": "デバイスからアンインストールする",
"onlyWorksWithNonVersionDetectApps": "バージョン検出を無効にしているアプリにのみ動作します。", "onlyWorksWithNonVersionDetectApps": "バージョン検出を無効にしているアプリにのみ動作します。",
@ -227,20 +219,48 @@
"overrideSource": "ソースの上書き", "overrideSource": "ソースの上書き",
"dontShowAgain": "二度と表示しない", "dontShowAgain": "二度と表示しない",
"dontShowTrackOnlyWarnings": "「追跡のみ」の警告を表示しない", "dontShowTrackOnlyWarnings": "「追跡のみ」の警告を表示しない",
"dontShowAPKOriginWarnings": "APK Originの警告を表示しない", "dontShowAPKOriginWarnings": "APKのダウンロード元の警告を表示しない",
"moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる", "moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる",
"gitlabPATLabel": "GitLab パーソナルアクセストークン (検索有効化する)", "gitlabPATLabel": "GitLab パーソナルアクセストークン\n(検索とより良いAPK検出の有効化)",
"about": "概要", "about": "概要",
"requiresCredentialsInSettings": "これには追加の認証が必要です (設定にて)", "requiresCredentialsInSettings": "これには追加の認証が必要です (設定にて)",
"checkOnStart": "Check Once on Start", "checkOnStart": "起動時にアップデートを確認する",
"tryInferAppIdFromCode": "Try inferring App ID from source code", "tryInferAppIdFromCode": "ソースコードからApp IDを推測する",
"removeOnExternalUninstall": "外部でアンインストールされたアプリを自動的に削除する",
"pickHighestVersionCode": "最も高いバージョンコードのAPKを自動的に選択する",
"checkUpdateOnDetailPage": "アプリの詳細ページを開く際にアップデートを確認する",
"disablePageTransitions": "ページ遷移アニメーションを無効化する",
"reversePageTransitions": "ページ遷移アニメーションを反転する",
"minStarCount": "最小スター数",
"addInfoBelow": "下部でこの情報を追加してください。",
"addInfoInSettings": "設定でこの情報を追加してください。",
"githubSourceNote": "GitHubのレート制限はAPIキーを使うことで回避できます。",
"gitlabSourceNote": "GitLabのAPK抽出はAPIキーがないと動作しない場合があります。",
"sortByFileNamesNotLinks": "フルのリンクではなくファイル名でソートする",
"filterReleaseNotesByRegEx": "正規表現でリリースノートをフィルタリングする",
"customLinkFilterRegex": "正規表現によるカスタムリンクフィルター (デフォルト '.apk$')",
"appsPossiblyUpdated": "アプリのアップデートを試行",
"appsPossiblyUpdatedNotifDescription": "1つまたは複数のアプリのアップデートがバックグラウンドで適用された可能性があることをユーザーに通知する",
"xWasPossiblyUpdatedToY": "{} が {} にアップデートされた可能性があります",
"enableBackgroundUpdates": "バックグラウンドアップデートを有効化する",
"backgroundUpdateReqsExplanation": "バックグラウンドアップデートは、すべてのアプリで可能とは限りません。",
"backgroundUpdateLimitsExplanation": "バックグラウンドアップデートが成功したかどうかは、Obtainiumを起動したときにしか判断できません。",
"verifyLatestTag": "'latest'タグを確認する",
"exemptFromBackgroundUpdates": "バックグラウンドアップデートを行わない (有効な場合)",
"bgUpdatesOnWiFiOnly": "WiFiを使用していない場合バックグラウンドアップデートを無効にする",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
"removeAppQuestion": { "removeAppQuestion": {
"one": "アプリを削除しますか?", "one": "アプリを削除しますか?",
"other": "アプリを削除しますか?" "other": "アプリを削除しますか?"
}, },
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください", "one": "リクエストが多すぎます(レート制限)- {} 分後に再試行してください",
"other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください" "other": "リクエストが多すぎます(レート制限)- {} 分後に再試行してください"
}, },
"bgUpdateGotErrorRetryInMinutes": { "bgUpdateGotErrorRetryInMinutes": {
"one": "バックグラウンドでのアップデート確認で {} の問題が発生, {} 分後に再試行します", "one": "バックグラウンドでのアップデート確認で {} の問題が発生, {} 分後に再試行します",
@ -251,28 +271,28 @@
"other": "バックグラウンドでのアップデート確認で {} 個のアップデートを発見 - 必要に応じてユーザーに通知します" "other": "バックグラウンドでのアップデート確認で {} 個のアップデートを発見 - 必要に応じてユーザーに通知します"
}, },
"apps": { "apps": {
"one": "{}個のアプリ", "one": "{} 個のアプリ",
"other": "{}個のアプリ" "other": "{} 個のアプリ"
}, },
"url": { "url": {
"one": "{}個のURL", "one": "{} 個のURL",
"other": "{}個のURL" "other": "{} 個のURL"
}, },
"minute": { "minute": {
"one": "{}分", "one": "{} 分",
"other": "{}分" "other": "{} 分"
}, },
"hour": { "hour": {
"one": "{}時間", "one": "{} 時間",
"other": "{}時間" "other": "{} 時間"
}, },
"day": { "day": {
"one": "{}日", "one": "{} 日",
"other": "{}日" "other": "{} 日"
}, },
"clearedNLogsBeforeXAfterY": { "clearedNLogsBeforeXAfterY": {
"one": "{n}個のログをクリアしました (前 = {before}, 後 = {after})", "one": "{n} 個のログをクリアしました (前 = {before}, 後 = {after})",
"other": "{n}個のログをクリアしました (前 = {before}, 後 = {after})" "other": "{n} 個のログをクリアしました (前 = {before}, 後 = {after})"
}, },
"xAndNMoreUpdatesAvailable": { "xAndNMoreUpdatesAvailable": {
"one": "{} とさらに {} 個のアプリのアップデートが利用可能です", "one": "{} とさらに {} 個のアプリのアップデートが利用可能です",
@ -281,5 +301,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} とさらに {} 個のアプリがアップデートされました", "one": "{} とさらに {} 個のアプリがアップデートされました",
"other": "{} とさらに {} 個のアプリがアップデートされました" "other": "{} とさらに {} 個のアプリがアップデートされました"
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} とさらに 1 個のアプリがアップデートされた可能性があります",
"other": "{} とさらに {} 個のアプリがアップデートされた可能性があります"
} }
} }

View File

@ -4,30 +4,24 @@
"okay": "Okej", "okay": "Okej",
"appId": "ID aplikacji", "appId": "ID aplikacji",
"bgUpdateGotErrorRetryInMinutes": { "bgUpdateGotErrorRetryInMinutes": {
"one": "Sprawdzanie aktualizacji w tle napotkało {}, zaplanuje ponowne sprawdzenie za {} min.", "one": "Sprawdzanie aktualizacji w tle napotkało {}, zaplanuje ponowne sprawdzenie za {} minutę",
"other": "Sprawdzanie aktualizacji w tle napotkało {}, zaplanuje ponowne sprawdzenie za {} min." "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"
}, },
"invalidURLForSource": "Nieprawidłowy adres URL aplikacji {}", "invalidURLForSource": "Nieprawidłowy adres URL aplikacji {}",
"noReleaseFound": "Nie można znaleźć odpowiedniego wydania", "noReleaseFound": "Nie można znaleźć odpowiedniego wydania",
"noVersionFound": "Nie można określić wersji wydania", "noVersionFound": "Nie można określić wersji wydania",
"urlMatchesNoSource": "Adres URL nie pasuje do znanego źródła", "urlMatchesNoSource": "Adres URL nie pasuje do znanego źródła",
"cantInstallOlderVersion": "Nie można zainstalować starszej wersji aplikacji", "cantInstallOlderVersion": "Nie można zainstalować starszej wersji aplikacji",
"appIdMismatch": "Pobrany identyfikator pakietu nie pasuje do istniejącego identyfikatora aplikacji", "appIdMismatch": "Pobrane ID pakietu nie pasuje do istniejącego ID aplikacji",
"functionNotImplemented": "Ta klasa nie zaimplementowała tej funkcji", "functionNotImplemented": "Ta klasa nie zaimplementowała tej funkcji",
"placeholder": "Placeholder", "placeholder": "Placeholder",
"someErrors": "Wystąpiły pewne błędy", "someErrors": "Wystąpiły pewne błędy",
"unexpectedError": "Nieoczekiwany błąd", "unexpectedError": "Nieoczekiwany błąd",
"ok": "Okej", "ok": "Okej",
"and": "i", "and": "i",
"startedBgUpdateTask": "Rozpoczęto zadanie sprawdzania aktualizacji w tle", "githubPATLabel": "Osobisty token dostępu GitHub (zwiększa limit zapytań)",
"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 OTD: użytkownik:token",
"githubPATFormat": "użytkownik:token",
"includePrereleases": "Uwzględnij wersje wstępne", "includePrereleases": "Uwzględnij wersje wstępne",
"fallbackToOlderReleases": "Powracaj do starszych wersji", "fallbackToOlderReleases": "Powracaj do starszych wersji",
"filterReleaseTitlesByRegEx": "Filtruj tytuły wydań wg. wyrażeń regularnych", "filterReleaseTitlesByRegEx": "Filtruj tytuły wydań wg. wyrażeń regularnych",
@ -56,7 +50,7 @@
"searchSomeSourcesLabel": "Szukaj (tylko niektóre źródła)", "searchSomeSourcesLabel": "Szukaj (tylko niektóre źródła)",
"search": "Szukaj", "search": "Szukaj",
"additionalOptsFor": "Dodatkowe opcje dla {}", "additionalOptsFor": "Dodatkowe opcje dla {}",
"supportedSourcesBelow": "Obsługiwane źródła:", "supportedSources": "Obsługiwane źródła",
"trackOnlyInBrackets": "(tylko obserwowane)", "trackOnlyInBrackets": "(tylko obserwowane)",
"searchableInBrackets": "(Wyszukiwalne)", "searchableInBrackets": "(Wyszukiwalne)",
"appsString": "Aplikacje", "appsString": "Aplikacje",
@ -230,56 +224,114 @@
"autoApkFilterByArch": "Spróbuj filtrować pliki APK według architektury procesora, jeśli to możliwe", "autoApkFilterByArch": "Spróbuj filtrować pliki APK według architektury procesora, jeśli to możliwe",
"overrideSource": "Nadpisz źródło", "overrideSource": "Nadpisz źródło",
"dontShowAgain": "Nie pokazuj tego ponownie", "dontShowAgain": "Nie pokazuj tego ponownie",
"dontShowTrackOnlyWarnings": "Nie wyświetlaj ostrzeżeń „Tylko obserwowana”", "dontShowTrackOnlyWarnings": "Nie pokazuj ostrzeżeń „Tylko obserwowana”",
"dontShowAPKOriginWarnings": "Nie pokazuj ostrzeżeń o pochodzeniu APK", "dontShowAPKOriginWarnings": "Nie pokazuj ostrzeżeń o pochodzeniu APK",
"moveNonInstalledAppsToBottom": "Przenieś niezainstalowane aplikacje na dół widoku aplikacji", "moveNonInstalledAppsToBottom": "Przenieś niezainstalowane aplikacje na dół widoku aplikacji",
"gitlabPATLabel": "Osobisty Token Dostępu GitLab (umożliwia wyszukiwanie)", "gitlabPATLabel": "Osobisty token dostępu GitLab\n(Umożliwia wyszukiwanie i lepsze wykrywanie APK)",
"about": "Więcej informacji", "about": "Więcej informacji",
"requiresCredentialsInSettings": "Wymaga to dodatkowych poświadczeń (w Ustawieniach)", "requiresCredentialsInSettings": "Wymaga to dodatkowych poświadczeń (w Ustawieniach)",
"checkOnStart": "Sprawdź raz przy starcie", "checkOnStart": "Sprawdź aktualizacje przy uruchomieniu",
"tryInferAppIdFromCode": "Spróbuj wywnioskować identyfikator aplikacji z kodu źródłowego", "tryInferAppIdFromCode": "Spróbuj wywnioskować identyfikator aplikacji z kodu źródłowego",
"removeOnExternalUninstall": "Automatyczne usuń odinstalowane zewnętrznie aplikacje",
"pickHighestVersionCode": "Automatycznie wybierz najwyższy kod wersji APK",
"checkUpdateOnDetailPage": "Sprawdzaj aktualizacje podczas otwierania strony szczegółów aplikacji",
"disablePageTransitions": "Wyłącz animacje przejścia między stronami",
"reversePageTransitions": "Odwróć animacje przejścia pomiędzy stronami",
"minStarCount": "Minimalna ilość gwiazdek",
"addInfoBelow": "Dodaj tę informację poniżej.",
"addInfoInSettings": "Dodaj tę informację w Ustawieniach.",
"githubSourceNote": "Limit żądań GitHub można ominąć za pomocą klucza API.",
"gitlabSourceNote": "Pozyskiwanie pliku APK z GitLab może nie działać bez klucza API.",
"sortByFileNamesNotLinks": "Sortuj wg nazw plików zamiast pełnych linków",
"filterReleaseNotesByRegEx": "Filtruj informacje o wersji według wyrażenia regularnego",
"customLinkFilterRegex": "Filtruj linki APK według wyrażenia regularnego (domyślnie \".apk$\")",
"appsPossiblyUpdated": "Informuj o próbach aktualizacji",
"appsPossiblyUpdatedNotifDescription": "Powiadamiaj o potencjalnym zastosowaniu w tle aktualizacji jednej lub większej ilości aplikacji",
"xWasPossiblyUpdatedToY": "{} być może zaktualizowano do {}.",
"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",
"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": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Usunąć aplikację?", "one": "Usunąć aplikację?",
"few": "Usunąć aplikacje?",
"many": "Usunąć aplikacje?",
"other": "Usunąć aplikacje?" "other": "Usunąć aplikacje?"
}, },
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} min.", "one": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} minutę",
"other": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} min." "few": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} minuty",
"many": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} minut",
"other": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} minuty"
}, },
"bgCheckFoundUpdatesWillNotifyIfNeeded": { "bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "Podczas sprawdzania aktualizacji w tle znaleziono {} aktualizację - w razie potrzeby użytkownik zostanie o tym powiadomiony", "one": "W tle znaleziono {} aktualizację - w razie potrzeby użytkownik zostanie o tym powiadomiony",
"other": "Podczas sprawdzania aktualizacji w tle znaleziono {} akt. - w razie potrzeby użytkownik zostanie o tym powiadomiony" "few": "W tle znaleziono {} aktualizacje - w razie potrzeby użytkownik zostanie o tym powiadomiony",
"many": "W tle znaleziono {} aktualizacji - w razie potrzeby użytkownik zostanie o tym powiadomiony",
"other": "W tle znaleziono {} aktualizacje - w razie potrzeby użytkownik zostanie o tym powiadomiony"
}, },
"apps": { "apps": {
"one": "{} aplik.", "one": "{} ap",
"other": "{} aplik." "few": "{} apki",
"many": "{} apek",
"other": "{} apki"
}, },
"url": { "url": {
"one": "{} adres URL", "one": "{} adres URL",
"other": "{} adr. URL" "few": "{} adresy URL",
"many": "{} adresów URL",
"other": "{} adresy URL"
}, },
"minute": { "minute": {
"one": "{} min.", "one": "{} minuta",
"other": "{} min." "few": "{} minuty",
"many": "{} minut",
"other": "{} minuty"
}, },
"hour": { "hour": {
"one": "{} godz.", "one": "{} godzina",
"other": "{} godz." "few": "{} godziny",
"many": "{} godzin",
"other": "{} godziny"
}, },
"day": { "day": {
"one": "{} dzień", "one": "{} dzień",
"few": "{} dni",
"many": "{} dni",
"other": "{} dni" "other": "{} dni"
}, },
"clearedNLogsBeforeXAfterY": { "clearedNLogsBeforeXAfterY": {
"one": "Wyczyszczono {n} log (przed = {before}, po = {after})", "one": "Wyczyszczono {n} log (przed = {before}, po = {after})",
"other": "Wyczyszczono logi: {n} (przed = {before}, po = {after})" "few": "Wyczyszczono {n} logi (przed = {before}, po = {after})",
"many": "Wyczyszczono {n} logów (przed = {before}, po = {after})",
"other": "Wyczyszczono {n} logi (przed = {before}, po = {after})"
}, },
"xAndNMoreUpdatesAvailable": { "xAndNMoreUpdatesAvailable": {
"one": "{} i jeszcze 1 aplikacja mają aktualizacje.", "one": "{} i 1 inna apka mają aktualizacje.",
"other": "{} i {} aplik. otrzymało aktualizacje." "few": "{} i {} inne apki mają aktualizacje.",
"many": "{} i {} innych apek ma aktualizacje.",
"other": "{} i {} inne apki mają aktualizacje."
}, },
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "Zaktualizowano {} i jeszcze 1 aplikację.", "one": "Zaktualizowano {} i 1 inną apkę.",
"other": "Zaktualizowano {} i {} aplik." "few": "{} i {} inne apki zostały zaktualizowane.",
} "many": "{} i {} innych apek zostało zaktualizowanych.",
} "other": "{} i {} inne apki zostały zaktualizowane."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} i 1 inna apka mogły zostać zaktualizowane.",
"few": "{} i {} inne apki mogły zostać zaktualizowane.",
"many": "{} i {} innych apek mogło zostać zaktualizowanych.",
"other": "{} i {} inne apki mogły zostać zaktualizowane."
},
"enableBackgroundUpdates": "Włącz aktualizacje w tle",
"intermediateLinkRegex": "Filtr linków \"pośrednich\" do odwiedzenia w pierwszej kolejności",
"intermediateLinkNotFound": "Nie znaleziono linku pośredniego"
}

View File

@ -7,24 +7,16 @@
"appIdMismatch": "ID загруженного пакета не совпадает с существующим ID приложения", "appIdMismatch": "ID загруженного пакета не совпадает с существующим ID приложения",
"functionNotImplemented": "Этот класс не реализовал эту функцию", "functionNotImplemented": "Этот класс не реализовал эту функцию",
"placeholder": "Заполнитель", "placeholder": "Заполнитель",
"someErrors": "Произошли некоторые ошибки", "someErrors": "Возникли некоторые ошибки",
"unexpectedError": "Неожиданная ошибка", "unexpectedError": "Неожиданная ошибка",
"ok": "Окей", "ok": "Окей",
"and": "и", "and": "и",
"startedBgUpdateTask": "Запущена задача фоновой проверки обновлений",
"bgUpdateIgnoreAfterIs": "Параметр игнорирования фоновых обновлений: {}",
"startedActualBGUpdateCheck": "Запущена фактическая проверка фоновых обновлений",
"bgUpdateTaskFinished": "Завершена задача фоновой проверки обновлений",
"firstRun": "Это первый запуск Obtainium",
"settingUpdateCheckIntervalTo": "Установка интервала проверки обновлений: {}",
"githubPATLabel": "Персональный токен доступа GitHub (увеличивает лимит запросов)", "githubPATLabel": "Персональный токен доступа GitHub (увеличивает лимит запросов)",
"githubPATHint": "Токен доступа должен быть в формате: имя_пользователя:токен",
"githubPATFormat": "имя_пользователя:токен",
"includePrereleases": "Включить предварительные релизы", "includePrereleases": "Включить предварительные релизы",
"fallbackToOlderReleases": "Откатиться к более старым версиям", "fallbackToOlderReleases": "Откатиться к более старым версиям",
"filterReleaseTitlesByRegEx": "Фильтровать заголовки релизов с помощью регулярного выражения", "filterReleaseTitlesByRegEx": "Фильтровать заголовки релизов\nс помощью регулярного выражения",
"invalidRegEx": "Неверное регулярное выражение", "invalidRegEx": "Неверное регулярное выражение",
"noDescription": "Нет описания" "noDescription": "Нет описания",
"cancel": "Отмена", "cancel": "Отмена",
"continue": "Продолжить", "continue": "Продолжить",
"requiredInBrackets": "(Обязательно)", "requiredInBrackets": "(Обязательно)",
@ -33,7 +25,7 @@
"githubStarredRepos": "Помеченные звездочкой репозитории на GitHub", "githubStarredRepos": "Помеченные звездочкой репозитории на GitHub",
"uname": "Имя пользователя", "uname": "Имя пользователя",
"wrongArgNum": "Неправильное количество предоставленных аргументов", "wrongArgNum": "Неправильное количество предоставленных аргументов",
"xIsTrackOnly": "{} является приложением только для отслеживания", "xIsTrackOnly": "{} только для отслеживания",
"source": "Источник", "source": "Источник",
"app": "Приложение", "app": "Приложение",
"appsFromSourceAreTrackOnly": "Приложения из этого источника являются 'только для отслеживания'.", "appsFromSourceAreTrackOnly": "Приложения из этого источника являются 'только для отслеживания'.",
@ -49,7 +41,7 @@
"searchSomeSourcesLabel": "Поиск (только в некоторых источниках)", "searchSomeSourcesLabel": "Поиск (только в некоторых источниках)",
"search": "Поиск", "search": "Поиск",
"additionalOptsFor": "Дополнительные опции для {}", "additionalOptsFor": "Дополнительные опции для {}",
"supportedSourcesBelow": "Поддерживаемые источники:", "supportedSources": "Поддерживаемые источники",
"trackOnlyInBrackets": "(Только для отслеживания)", "trackOnlyInBrackets": "(Только для отслеживания)",
"searchableInBrackets": "(Поиск)", "searchableInBrackets": "(Поиск)",
"appsString": "Приложения", "appsString": "Приложения",
@ -68,7 +60,7 @@
"removeSelectedAppsQuestion": "Удалить выбранные приложения?", "removeSelectedAppsQuestion": "Удалить выбранные приложения?",
"removeSelectedApps": "Удалить выбранные приложения", "removeSelectedApps": "Удалить выбранные приложения",
"updateX": "Обновить {}", "updateX": "Обновить {}",
"installX": "Установить {}" "installX": "Установить {}",
"markXTrackOnlyAsUpdated": "Отметить {}\n(Только для отслеживания)\nкак обновленное", "markXTrackOnlyAsUpdated": "Отметить {}\n(Только для отслеживания)\nкак обновленное",
"changeX": "Изменить {}", "changeX": "Изменить {}",
"installUpdateApps": "Установить/Обновить приложения", "installUpdateApps": "Установить/Обновить приложения",
@ -90,7 +82,7 @@
"filterActive": "Фильтр *", "filterActive": "Фильтр *",
"filterApps": "Фильтровать приложения", "filterApps": "Фильтровать приложения",
"appName": "Название приложения", "appName": "Название приложения",
"author": "Автор" "author": "Автор",
"upToDateApps": "Приложения со свежими обновлениями", "upToDateApps": "Приложения со свежими обновлениями",
"nonInstalledApps": "Неустановленные приложения", "nonInstalledApps": "Неустановленные приложения",
"importExport": "Импорт/экспорт", "importExport": "Импорт/экспорт",
@ -100,7 +92,7 @@
"invalidInput": "Неверный ввод", "invalidInput": "Неверный ввод",
"importedX": "Импортировано {}", "importedX": "Импортировано {}",
"obtainiumImport": "Импорт в Obtainium", "obtainiumImport": "Импорт в Obtainium",
"importFromURLList": "Импорт из списка URL-адреса", "importFromURLList": "Импорт из списка URL-адресов",
"searchQuery": "Поисковый запрос", "searchQuery": "Поисковый запрос",
"appURLList": "Список URL приложений", "appURLList": "Список URL приложений",
"line": "Строка", "line": "Строка",
@ -116,12 +108,12 @@
"selectURLs": "Выбрать URL-адреса", "selectURLs": "Выбрать URL-адреса",
"pick": "Выбрать", "pick": "Выбрать",
"theme": "Тема", "theme": "Тема",
"dark": "Темный", "dark": "Темная",
"light": "Светлый", "light": "Светлая",
"followSystem": "Следовать системе", "followSystem": "Как в системе",
"obtainium": "Obtainium", "obtainium": "Obtainium",
"materialYou": "Material You", "materialYou": "Material You",
"useBlackTheme": "Использовать темную тему", "useBlackTheme": "Использовать чёрную тему",
"appSortBy": "Сортировка приложений по", "appSortBy": "Сортировка приложений по",
"authorName": "Автор/Название", "authorName": "Автор/Название",
"nameAuthor": "Название/Автор", "nameAuthor": "Название/Автор",
@ -132,7 +124,7 @@
"bgUpdateCheckInterval": "Интервал проверки обновлений в фоновом режиме", "bgUpdateCheckInterval": "Интервал проверки обновлений в фоновом режиме",
"neverManualOnly": "Никогда - Только вручную", "neverManualOnly": "Никогда - Только вручную",
"appearance": "Внешний вид", "appearance": "Внешний вид",
"showWebInAppView": "Показывать веб-страницу источника в представлении приложения", "showWebInAppView": "Показывать исходную веб-страницу в представлении приложения",
"pinUpdates": "Закрепить обновления сверху списка приложений", "pinUpdates": "Закрепить обновления сверху списка приложений",
"updates": "Обновления", "updates": "Обновления",
"sourceSpecific": "Специфика источника", "sourceSpecific": "Специфика источника",
@ -183,7 +175,7 @@
"appId": "ID приложения", "appId": "ID приложения",
"appWithIdOrNameNotFound": "Приложение с таким ID или названием не было найдено", "appWithIdOrNameNotFound": "Приложение с таким ID или названием не было найдено",
"reposHaveMultipleApps": "В хранилище может быть несколько приложений", "reposHaveMultipleApps": "В хранилище может быть несколько приложений",
"fdroidThirdPartyRepo": "Хранилище F-Droid сторонних разработчиков", "fdroidThirdPartyRepo": "Сторонние репозитории F-Droid",
"steam": "Steam", "steam": "Steam",
"steamMobile": "Steam Mobile", "steamMobile": "Steam Mobile",
"steamChat": "Steam Chat", "steamChat": "Steam Chat",
@ -207,11 +199,11 @@
"categoryDeleteWarning": "Все приложения в удаленных категориях будут помечены как без категории.", "categoryDeleteWarning": "Все приложения в удаленных категориях будут помечены как без категории.",
"addCategory": "Добавить категорию", "addCategory": "Добавить категорию",
"label": "Метка", "label": "Метка",
"language": "Язык" "language": "Язык",
"copiedToClipboard": "Скопировано в буфер обмена", "copiedToClipboard": "Скопировано в буфер обмена",
"storagePermissionDenied": "Отказано в доступе к хранилищу", "storagePermissionDenied": "Отказано в доступе к хранилищу",
"selectedCategorizeWarning": "Это заменит все текущие настройки категорий для выбранных приложений.", "selectedCategorizeWarning": "Это заменит все текущие настройки категорий для выбранных приложений.",
"filterAPKsByRegEx": "Фильтровать APK-файлы с помощью регулярного выражения", "filterAPKsByRegEx": "Фильтровать APK-файлы с помощью\nрегулярного выражения",
"removeFromObtainium": "Удалить из Obtainium", "removeFromObtainium": "Удалить из Obtainium",
"uninstallFromDevice": "Удалить с устройства", "uninstallFromDevice": "Удалить с устройства",
"onlyWorksWithNonVersionDetectApps": "Работает только для приложений с отключенным определением версии.", "onlyWorksWithNonVersionDetectApps": "Работает только для приложений с отключенным определением версии.",
@ -219,7 +211,7 @@
"releaseDateAsVersionExplanation": "Этот параметр следует использовать только для приложений, в которых определение версии не работает правильно, но имеется дата выпуска.", "releaseDateAsVersionExplanation": "Этот параметр следует использовать только для приложений, в которых определение версии не работает правильно, но имеется дата выпуска.",
"changes": "Изменения", "changes": "Изменения",
"releaseDate": "Дата выпуска", "releaseDate": "Дата выпуска",
"importFromURLsInFile": "Импортировать из URL-адресов в файл (например, OPML)", "importFromURLsInFile": "Импорт URL-адресов из файла (например, OPML)",
"versionDetection": "Определение версии", "versionDetection": "Определение версии",
"standardVersionDetection": "Стандартное определение версии", "standardVersionDetection": "Стандартное определение версии",
"groupByCategory": "Группировать по категориям", "groupByCategory": "Группировать по категориям",
@ -229,11 +221,38 @@
"dontShowTrackOnlyWarnings": "Не показывать предупреждения о только отслеживаемых приложениях", "dontShowTrackOnlyWarnings": "Не показывать предупреждения о только отслеживаемых приложениях",
"dontShowAPKOriginWarnings": "Не показывать предупреждения об источнике APK-файлов", "dontShowAPKOriginWarnings": "Не показывать предупреждения об источнике APK-файлов",
"moveNonInstalledAppsToBottom": "Переместить неустановленные приложения вниз списка", "moveNonInstalledAppsToBottom": "Переместить неустановленные приложения вниз списка",
"gitlabPATLabel": "Персональный токен доступа GitLab (Включает поиск)", "gitlabPATLabel": "Персональный токен доступа GitLab\n(Включает поиск и улучшает обнаружение APK)",
"about": "О приложении", "about": "О приложении",
"requiresCredentialsInSettings": "Для этого требуются дополнительные учетные данные (в настройках)", "requiresCredentialsInSettings": "Для этого требуются дополнительные учетные данные (в настройках)",
"checkOnStart": "Проверить один раз при запуске", "checkOnStart": "Проверять наличие обновлений при запуске",
"tryInferAppIdFromCode": "Попытаться определить ID приложения из исходного кода", "tryInferAppIdFromCode": "Попытаться определить ID приложения из исходного кода",
"removeOnExternalUninstall": "Автоматически убирать из списка удаленные извне приложения",
"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",
"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 {}.",
"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",
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Удалить приложение?", "one": "Удалить приложение?",
"other": "Удалить приложения?" "other": "Удалить приложения?"
@ -255,8 +274,8 @@
"other": "{} Приложений" "other": "{} Приложений"
}, },
"url": { "url": {
"one": "{} Ссылка", "one": "{} URL-адрес",
"other": "{} Ссылки" "other": "{} URL-адреса"
}, },
"minute": { "minute": {
"one": "{} Минута", "one": "{} Минута",
@ -275,11 +294,15 @@
"other": "Очищено {n} журналов (до = {before}, после = {after})" "other": "Очищено {n} журналов (до = {before}, после = {after})"
}, },
"xAndNMoreUpdatesAvailable": { "xAndNMoreUpdatesAvailable": {
"one": "У {} и еще 1 приложения есть обновления.", "one": "У {} и еще 1 приложения есть обновление.",
"other": "{} and {} more apps have updates." "other": "У {} и ещё {} приложений есть обновления."
}, },
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} and 1 more app were updated.", "one": "{} и еще 1 приложение были обновлены.",
"other": "У {} и еще {} приложений есть обновления." "other": "{} и еще {} приложений были обновлены."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@ -11,15 +11,7 @@
"unexpectedError": "意外错误", "unexpectedError": "意外错误",
"ok": "好的", "ok": "好的",
"and": "和", "and": "和",
"startedBgUpdateTask": "后台更新检查任务已启动",
"bgUpdateIgnoreAfterIs": "后台更新检查间隔为 {}",
"startedActualBGUpdateCheck": "开始后台更新检查",
"bgUpdateTaskFinished": "后台更新检查任务已完成",
"firstRun": "这是 Obtainium 首次启动",
"settingUpdateCheckIntervalTo": "更新检查间隔设置为 {}",
"githubPATLabel": "GitHub 个人访问令牌(提升 API 请求限额)", "githubPATLabel": "GitHub 个人访问令牌(提升 API 请求限额)",
"githubPATHint": "个人访问令牌必须为“username:token”的格式",
"githubPATFormat": "username:token",
"includePrereleases": "包含预发行版", "includePrereleases": "包含预发行版",
"fallbackToOlderReleases": "将旧发行版作为备选", "fallbackToOlderReleases": "将旧发行版作为备选",
"filterReleaseTitlesByRegEx": "使用正则表达式筛选发行标题", "filterReleaseTitlesByRegEx": "使用正则表达式筛选发行标题",
@ -46,10 +38,10 @@
"appSourceURL": "来源 URL", "appSourceURL": "来源 URL",
"error": "错误", "error": "错误",
"add": "添加", "add": "添加",
"searchSomeSourcesLabel": "搜索(仅部分来源)", "searchSomeSourcesLabel": "搜索(仅支持部分来源)",
"search": "搜索", "search": "搜索",
"additionalOptsFor": "{} 的更多选项", "additionalOptsFor": "{} 的更多选项",
"supportedSourcesBelow": "支持的来源", "supportedSources": "支持的来源",
"trackOnlyInBrackets": "(仅追踪)", "trackOnlyInBrackets": "(仅追踪)",
"searchableInBrackets": "(可搜索)", "searchableInBrackets": "(可搜索)",
"appsString": "应用列表", "appsString": "应用列表",
@ -107,7 +99,7 @@
"searchX": "搜索 {}", "searchX": "搜索 {}",
"noResults": "无结果", "noResults": "无结果",
"importX": "导入 {}", "importX": "导入 {}",
"importedAppsIdDisclaimer": "导入的应用可能错误地显示为“未安装”。\n请通过 Obtainium 重新安装这些应用来解决此问题。", "importedAppsIdDisclaimer": "导入的应用可能错误地显示为“未安装”状态。\n请通过 Obtainium 重新安装这些应用来解决此问题。",
"importErrors": "导入错误", "importErrors": "导入错误",
"importedXOfYApps": "已导入 {} 中的 {} 个应用。", "importedXOfYApps": "已导入 {} 中的 {} 个应用。",
"followingURLsHadErrors": "下列 URL 存在错误:", "followingURLsHadErrors": "下列 URL 存在错误:",
@ -135,7 +127,7 @@
"showWebInAppView": "在应用详情页显示来源网页", "showWebInAppView": "在应用详情页显示来源网页",
"pinUpdates": "将待更新应用置顶", "pinUpdates": "将待更新应用置顶",
"updates": "更新", "updates": "更新",
"sourceSpecific": "来源相关", "sourceSpecific": "来源",
"appSource": "源代码", "appSource": "源代码",
"noLogs": "无日志", "noLogs": "无日志",
"appLogs": "日志", "appLogs": "日志",
@ -150,16 +142,16 @@
"warning": "警告", "warning": "警告",
"sourceIsXButPackageFromYPrompt": "此应用的来源是“{}”,但 APK 文件来自“{}”。是否继续?", "sourceIsXButPackageFromYPrompt": "此应用的来源是“{}”,但 APK 文件来自“{}”。是否继续?",
"updatesAvailable": "更新可用", "updatesAvailable": "更新可用",
"updatesAvailableNotifDescription": "Obtainium 追踪的应用有更新时发通知", "updatesAvailableNotifDescription": "Obtainium 追踪的应用有更新时发通知",
"noNewUpdates": "全部应用已是最新。", "noNewUpdates": "全部应用已是最新。",
"xHasAnUpdate": "{} 可以更新了。", "xHasAnUpdate": "{} 可以更新了。",
"appsUpdated": "应用已更新", "appsUpdated": "应用已更新",
"appsUpdatedNotifDescription": "当应用在后台安装更新时发通知", "appsUpdatedNotifDescription": "当应用在后台安装更新时发通知",
"xWasUpdatedToY": "{} 已更新至 {}。", "xWasUpdatedToY": "{} 已更新至 {}。",
"errorCheckingUpdates": "检查更新出错", "errorCheckingUpdates": "检查更新出错",
"errorCheckingUpdatesNotifDescription": "当后台检查更新失败时显示的通知", "errorCheckingUpdatesNotifDescription": "当后台检查更新失败时显示的通知",
"appsRemoved": "应用已删除", "appsRemoved": "应用已删除",
"appsRemovedNotifDescription": "当应用因加载出错而被删除时发通知", "appsRemovedNotifDescription": "当应用因加载出错而被删除时发通知",
"xWasRemovedDueToErrorY": "{} 由于以下错误被删除:{}", "xWasRemovedDueToErrorY": "{} 由于以下错误被删除:{}",
"completeAppInstallation": "完成应用安装", "completeAppInstallation": "完成应用安装",
"obtainiumMustBeOpenToInstallApps": "必须启动 Obtainium 才能安装应用", "obtainiumMustBeOpenToInstallApps": "必须启动 Obtainium 才能安装应用",
@ -180,7 +172,7 @@
"yesMarkUpdated": "是,标记为已更新", "yesMarkUpdated": "是,标记为已更新",
"fdroid": "F-Droid 官方存储库", "fdroid": "F-Droid 官方存储库",
"appIdOrName": "应用 ID 或名称", "appIdOrName": "应用 ID 或名称",
"appId": "App ID", "appId": "应用 ID",
"appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用", "appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用",
"reposHaveMultipleApps": "存储库中可能包含多个应用", "reposHaveMultipleApps": "存储库中可能包含多个应用",
"fdroidThirdPartyRepo": "F-Droid 第三方存储库", "fdroidThirdPartyRepo": "F-Droid 第三方存储库",
@ -229,11 +221,39 @@
"dontShowTrackOnlyWarnings": "不显示“仅追踪”模式警告", "dontShowTrackOnlyWarnings": "不显示“仅追踪”模式警告",
"dontShowAPKOriginWarnings": "不显示 APK 文件来源警告", "dontShowAPKOriginWarnings": "不显示 APK 文件来源警告",
"moveNonInstalledAppsToBottom": "将未安装应用置底", "moveNonInstalledAppsToBottom": "将未安装应用置底",
"gitlabPATLabel": "GitLab 个人访问令牌(用于搜索", "gitlabPATLabel": "GitLab 个人访问令牌\n启用搜索功能并增强 APK 发现",
"about": "相关文档", "about": "相关文档",
"requiresCredentialsInSettings": "此功能需要额外的凭据(在“设置”中添加)", "requiresCredentialsInSettings": "此功能需要额外的凭据(在“设置”中添加)",
"checkOnStart": "启动时进行一次检查", "checkOnStart": "启动时进行一次检查",
"tryInferAppIdFromCode": "Try inferring App ID from source code", "tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
"removeOnExternalUninstall": "自动删除已卸载的外部应用",
"pickHighestVersionCode": "自动选择版本号最高的 APK 文件",
"checkUpdateOnDetailPage": "打开应用详情页时检查更新",
"disablePageTransitions": "禁用页面过渡动画效果",
"reversePageTransitions": "反转页面过渡动画效果",
"minStarCount": "最小星标数",
"addInfoBelow": "在下方添加此凭据。",
"addInfoInSettings": "在“设置”中添加此凭据。",
"githubSourceNote": "使用访问令牌可避免触发 GitHub 的 API 请求限制。",
"gitlabSourceNote": "未使用访问令牌时可能无法从 GitLab 获取 APK 文件。",
"sortByFileNamesNotLinks": "使用文件名代替链接进行排序",
"filterReleaseNotesByRegEx": "使用正则表达式筛选发行说明",
"customLinkFilterRegex": "使用正则表达式自定义链接筛选(默认模式为“.apk$”)",
"appsPossiblyUpdated": "已尝试更新应用",
"appsPossiblyUpdatedNotifDescription": "当应用已尝试在后台更新时发送通知",
"xWasPossiblyUpdatedToY": "已尝试将 {} 更新至 {}。",
"enableBackgroundUpdates": "启用后台更新",
"backgroundUpdateReqsExplanation": "后台更新未必适用于所有的应用。",
"backgroundUpdateLimitsExplanation": "只有在启动 Obtainium 时才能确认安装是否成功。",
"verifyLatestTag": "验证“Latest”标签",
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
"removeAppQuestion": { "removeAppQuestion": {
"one": "是否删除应用?", "one": "是否删除应用?",
"other": "是否删除应用?" "other": "是否删除应用?"
@ -247,8 +267,8 @@
"other": "后台更新检查遇到了“{}”问题,预定于 {} 分钟后重试" "other": "后台更新检查遇到了“{}”问题,预定于 {} 分钟后重试"
}, },
"bgCheckFoundUpdatesWillNotifyIfNeeded": { "bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "后台检查发现 {} 个应用更新 - 如有需要将发通知", "one": "后台检查发现 {} 个应用更新 - 如有需要将发通知",
"other": "后台检查发现 {} 个应用更新 - 如有需要将发通知" "other": "后台检查发现 {} 个应用更新 - 如有需要将发通知"
}, },
"apps": { "apps": {
"one": "{} 个应用", "one": "{} 个应用",
@ -281,5 +301,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} 和另外 1 个应用已更新。", "one": "{} 和另外 1 个应用已更新。",
"other": "{} 和另外 {} 个应用已更新。" "other": "{} 和另外 {} 个应用已更新。"
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} 和另外 1 个应用已尝试更新。",
"other": "{} 和另外 {} 个应用已尝试更新。"
} }
} }

20
build.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
# Convenience script
CURR_DIR="$(pwd)"
trap "cd "$CURR_DIR"" EXIT
if [ -z "$1" ]; then
git fetch && git merge origin/main && git push # Typically run after a PR to main, so bring dev up to date
fi
rm ./build/app/outputs/flutter-apk/* 2>/dev/null # Get rid of older builds if any
flutter build apk && flutter build apk --split-per-abi # Build (both split and combined APKs)
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

@ -25,12 +25,16 @@ class APKCombo extends AppSource {
} }
@override @override
Map<String, String> get requestHeaders => { Future<Map<String, String>?> getRequestHeaders(
"User-Agent": "curl/8.0.1", {Map<String, dynamic> additionalSettings = const <String, dynamic>{},
"Accept": "*/*", bool forAPKDownload = false}) async {
"Connection": "keep-alive", return {
"Host": "$host" "User-Agent": "curl/8.0.1",
}; "Accept": "*/*",
"Connection": "keep-alive",
"Host": "$host"
};
}
Future<List<MapEntry<String, String>>> getApkUrls(String standardUrl) async { Future<List<MapEntry<String, String>>> getApkUrls(String standardUrl) async {
var res = await sourceRequest('$standardUrl/download/apk'); var res = await sourceRequest('$standardUrl/download/apk');

View File

@ -3,6 +3,21 @@ import 'package:html/parser.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
parseDateTimeMMMddCommayyyy(String? dateString) {
DateTime? releaseDate;
try {
releaseDate = dateString != null
? DateFormat('MMM dd, yyyy').parse(dateString)
: null;
releaseDate = dateString != null && releaseDate == null
? DateFormat('MMMM dd, yyyy').parse(dateString)
: releaseDate;
} catch (err) {
// ignore
}
return releaseDate;
}
class APKPure extends AppSource { class APKPure extends AppSource {
APKPure() { APKPure() {
host = 'apkpure.com'; host = 'apkpure.com';
@ -37,25 +52,17 @@ class APKPure extends AppSource {
String appId = (await tryInferringAppId(standardUrl))!; String appId = (await tryInferringAppId(standardUrl))!;
String host = Uri.parse(standardUrl).host; String host = Uri.parse(standardUrl).host;
var res = await sourceRequest('$standardUrl/download'); var res = await sourceRequest('$standardUrl/download');
if (res.statusCode == 200) { var resChangelog = await sourceRequest(standardUrl);
if (res.statusCode == 200 && resChangelog.statusCode == 200) {
var html = parse(res.body); var html = parse(res.body);
var htmlChangelog = parse(resChangelog.body);
String? version = html.querySelector('span.info-sdk span')?.text.trim(); String? version = html.querySelector('span.info-sdk span')?.text.trim();
if (version == null) { if (version == null) {
throw NoVersionError(); throw NoVersionError();
} }
String? dateString = String? dateString =
html.querySelector('span.info-other span.date')?.text.trim(); html.querySelector('span.info-other span.date')?.text.trim();
DateTime? releaseDate; DateTime? releaseDate = parseDateTimeMMMddCommayyyy(dateString);
try {
releaseDate = dateString != null
? DateFormat('MMM dd, yyyy').parse(dateString)
: null;
releaseDate = dateString != null && releaseDate == null
? DateFormat('MMMM dd, yyyy').parse(dateString)
: null;
} catch (err) {
// ignore
}
String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK'; String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK';
List<MapEntry<String, String>> apkUrls = [ List<MapEntry<String, String>> apkUrls = [
MapEntry('$appId.apk', 'https://d.$host/b/$type/$appId?version=latest') MapEntry('$appId.apk', 'https://d.$host/b/$type/$appId?version=latest')
@ -68,8 +75,13 @@ class APKPure extends AppSource {
Uri.parse(standardUrl).pathSegments.reversed.last; Uri.parse(standardUrl).pathSegments.reversed.last;
String appName = String appName =
html.querySelector('h1.info-title')?.text.trim() ?? appId; html.querySelector('h1.info-title')?.text.trim() ?? appId;
String? changeLog = htmlChangelog
.querySelector("div.whats-new-info p:not(.date)")
?.innerHtml
.trim()
.replaceAll("<br>", " \n");
return APKDetails(version, apkUrls, AppNames(author, appName), return APKDetails(version, apkUrls, AppNames(author, appName),
releaseDate: releaseDate); releaseDate: releaseDate, changeLog: changeLog);
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }

View File

@ -0,0 +1,76 @@
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
class Aptoide extends AppSource {
Aptoide() {
host = 'aptoide.com';
name = tr('Aptoide');
allowSubDomains = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://([^\\.]+\\.){2,}$host');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
}
@override
Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async {
return (await getAppDetailsJSON(standardUrl))['package'];
}
Future<Map<String, dynamic>> getAppDetailsJSON(String standardUrl) async {
var res = await sourceRequest(standardUrl);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var idMatch = RegExp('"app":{"id":[0-9]+').firstMatch(res.body);
String? id;
if (idMatch != null) {
id = res.body.substring(idMatch.start + 12, idMatch.end);
} else {
throw NoReleasesError();
}
var res2 =
await sourceRequest('https://ws2.aptoide.com/api/7/getApp/app_id/$id');
if (res2.statusCode != 200) {
throw getObtainiumHttpError(res);
}
return jsonDecode(res2.body)?['nodes']?['meta']?['data'];
}
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
var appDetails = await getAppDetailsJSON(standardUrl);
String appName = appDetails['name'] ?? tr('app');
String author = appDetails['developer']?['name'] ?? name;
String? dateStr = appDetails['updated'];
String? version = appDetails['file']?['vername'];
String? apkUrl = appDetails['file']?['path'];
if (version == null) {
throw NoVersionError();
}
if (apkUrl == null) {
throw NoAPKError();
}
DateTime? relDate;
if (dateStr != null) {
relDate = DateTime.parse(dateStr);
}
return APKDetails(
version, getApkUrlsFromUrls([apkUrl]), AppNames(author, appName),
releaseDate: relDate);
}
}

View File

@ -1,41 +1,19 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:obtainium/app_sources/github.dart'; import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
class Codeberg extends AppSource { class Codeberg extends AppSource {
GitHub gh = GitHub();
Codeberg() { Codeberg() {
host = 'codeberg.org'; host = 'codeberg.org';
additionalSourceSpecificSettingFormItems = []; additionalSourceAppSpecificSettingFormItems =
gh.additionalSourceAppSpecificSettingFormItems;
additionalSourceAppSpecificSettingFormItems = [
[
GeneratedFormSwitch('includePrereleases',
label: tr('includePrereleases'), defaultValue: false)
],
[
GeneratedFormSwitch('fallbackToOlderReleases',
label: tr('fallbackToOlderReleases'), defaultValue: true)
],
[
GeneratedFormTextField('filterReleaseTitlesByRegEx',
label: tr('filterReleaseTitlesByRegEx'),
required: false,
additionalValidators: [
(value) {
return regExValidator(value);
}
])
]
];
canSearch = true; canSearch = true;
searchQuerySettingFormItems = gh.searchQuerySettingFormItems;
} }
var gh = GitHub();
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+'); RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
@ -68,10 +46,12 @@ class Codeberg extends AppSource {
} }
@override @override
Future<Map<String, List<String>>> search(String query) async { Future<Map<String, List<String>>> search(String query,
{Map<String, dynamic> querySettings = const {}}) async {
return gh.searchCommon( return gh.searchCommon(
query, query,
'https://$host/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100', 'https://$host/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100',
'data'); 'data',
querySettings: querySettings);
} }
} }

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@ -11,6 +12,12 @@ class FDroid extends AppSource {
host = 'f-droid.org'; host = 'f-droid.org';
name = tr('fdroid'); name = tr('fdroid');
canSearch = true; canSearch = true;
additionalSourceAppSpecificSettingFormItems = [
[
GeneratedFormSwitch('autoSelectHighestVersionCode',
label: tr('autoSelectHighestVersionCode'))
]
];
} }
@override @override
@ -37,7 +44,8 @@ class FDroid extends AppSource {
} }
APKDetails getAPKUrlsFromFDroidPackagesAPIResponse( APKDetails getAPKUrlsFromFDroidPackagesAPIResponse(
Response res, String apkUrlPrefix, String standardUrl) { Response res, String apkUrlPrefix, String standardUrl,
{bool autoSelectHighestVersionCode = false}) {
if (res.statusCode == 200) { if (res.statusCode == 200) {
List<dynamic> releases = jsonDecode(res.body)['packages'] ?? []; List<dynamic> releases = jsonDecode(res.body)['packages'] ?? [];
if (releases.isEmpty) { if (releases.isEmpty) {
@ -47,8 +55,12 @@ class FDroid extends AppSource {
if (latestVersion == null) { if (latestVersion == null) {
throw NoVersionError(); throw NoVersionError();
} }
List<String> apkUrls = releases Iterable<dynamic> latestReleases =
.where((element) => element['versionName'] == latestVersion) releases.where((element) => element['versionName'] == latestVersion);
if (latestReleases.length > 1 && autoSelectHighestVersionCode) {
latestReleases = [latestReleases.first];
}
List<String> apkUrls = latestReleases
.map((e) => '${apkUrlPrefix}_${e['versionCode']}.apk') .map((e) => '${apkUrlPrefix}_${e['versionCode']}.apk')
.toList(); .toList();
return APKDetails(latestVersion, getApkUrlsFromUrls(apkUrls), return APKDetails(latestVersion, getApkUrlsFromUrls(apkUrls),
@ -68,11 +80,14 @@ class FDroid extends AppSource {
return getAPKUrlsFromFDroidPackagesAPIResponse( return getAPKUrlsFromFDroidPackagesAPIResponse(
await sourceRequest('https://$host/api/v1/packages/$appId'), await sourceRequest('https://$host/api/v1/packages/$appId'),
'https://$host/repo/$appId', 'https://$host/repo/$appId',
standardUrl); standardUrl,
autoSelectHighestVersionCode:
additionalSettings['autoSelectHighestVersionCode'] == true);
} }
@override @override
Future<Map<String, List<String>>> search(String query) async { Future<Map<String, List<String>>> search(String query,
{Map<String, dynamic> querySettings = const {}}) async {
Response res = await sourceRequest( Response res = await sourceRequest(
'https://search.$host/?q=${Uri.encodeQueryComponent(query)}'); 'https://search.$host/?q=${Uri.encodeQueryComponent(query)}');
if (res.statusCode == 200) { if (res.statusCode == 200) {

View File

@ -14,6 +14,10 @@ class FDroidRepo extends AppSource {
label: tr('appIdOrName'), label: tr('appIdOrName'),
hint: tr('reposHaveMultipleApps'), hint: tr('reposHaveMultipleApps'),
required: true) required: true)
],
[
GeneratedFormSwitch('pickHighestVersionCode',
label: tr('pickHighestVersionCode'), defaultValue: false)
] ]
]; ];
} }
@ -24,6 +28,7 @@ class FDroidRepo extends AppSource {
Map<String, dynamic> additionalSettings, Map<String, dynamic> additionalSettings,
) async { ) async {
String? appIdOrName = additionalSettings['appIdOrName']; String? appIdOrName = additionalSettings['appIdOrName'];
bool pickHighestVersionCode = additionalSettings['pickHighestVersionCode'];
if (appIdOrName == null) { if (appIdOrName == null) {
throw NoReleasesError(); throw NoReleasesError();
} }
@ -62,10 +67,19 @@ class FDroidRepo extends AppSource {
if (latestVersion == null) { if (latestVersion == null) {
throw NoVersionError(); throw NoVersionError();
} }
List<String> apkUrls = releases var latestVersionReleases = releases
.where((element) => .where((element) =>
element.querySelector('version')?.innerHtml == latestVersion && element.querySelector('version')?.innerHtml == latestVersion &&
element.querySelector('apkname') != null) element.querySelector('apkname') != null)
.toList();
if (latestVersionReleases.length > 1 && pickHighestVersionCode) {
latestVersionReleases.sort((e1, e2) {
return int.parse(e2.querySelector('versioncode')!.innerHtml)
.compareTo(int.parse(e1.querySelector('versioncode')!.innerHtml));
});
latestVersionReleases = [latestVersionReleases[0]];
}
List<String> apkUrls = latestVersionReleases
.map((e) => '$standardUrl/${e.querySelector('apkname')!.innerHtml}') .map((e) => '$standardUrl/${e.querySelector('apkname')!.innerHtml}')
.toList(); .toList();
return APKDetails(latestVersion, getApkUrlsFromUrls(apkUrls), return APKDetails(latestVersion, getApkUrlsFromUrls(apkUrls),

View File

@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
@ -16,25 +17,11 @@ class GitHub extends AppSource {
host = 'github.com'; host = 'github.com';
appIdInferIsOptional = true; appIdInferIsOptional = true;
additionalSourceSpecificSettingFormItems = [ sourceConfigSettingFormItems = [
GeneratedFormTextField('github-creds', GeneratedFormTextField('github-creds',
label: tr('githubPATLabel'), label: tr('githubPATLabel'),
password: true, password: true,
required: false, required: false,
additionalValidators: [
(value) {
if (value != null && value.trim().isNotEmpty) {
if (value
.split(':')
.where((element) => element.trim().isNotEmpty)
.length !=
2) {
return tr('githubPATHint');
}
}
return null;
}
],
hint: tr('githubPATFormat'), hint: tr('githubPATFormat'),
belowWidgets: [ belowWidgets: [
const SizedBox( const SizedBox(
@ -75,10 +62,39 @@ class GitHub extends AppSource {
return regExValidator(value); return regExValidator(value);
} }
]) ])
],
[
GeneratedFormTextField('filterReleaseNotesByRegEx',
label: tr('filterReleaseNotesByRegEx'),
required: false,
additionalValidators: [
(value) {
return regExValidator(value);
}
])
],
[
GeneratedFormSwitch('verifyLatestTag',
label: tr('verifyLatestTag'), defaultValue: false)
] ]
]; ];
canSearch = true; canSearch = true;
searchQuerySettingFormItems = [
GeneratedFormTextField('minStarCount',
label: tr('minStarCount'),
defaultValue: '0',
additionalValidators: [
(value) {
try {
int.parse(value ?? '0');
} catch (e) {
return tr('invalidInput');
}
return null;
}
])
];
} }
@override @override
@ -92,7 +108,7 @@ class GitHub extends AppSource {
for (var path in possibleBuildGradleLocations) { for (var path in possibleBuildGradleLocations) {
try { try {
var res = await sourceRequest( var res = await sourceRequest(
'${await convertStandardUrlToAPIUrl(standardUrl)}/contents/$path'); '${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path');
if (res.statusCode == 200) { if (res.statusCode == 200) {
try { try {
var body = jsonDecode(res.body); var body = jsonDecode(res.body);
@ -102,15 +118,18 @@ class GitHub extends AppSource {
.split('\n') .split('\n')
.map((e) => e.trim()); .map((e) => e.trim());
var appId = trimmedLines var appId = trimmedLines
.where((l) => l.startsWith('applicationId "')) .where((l) =>
.first l.startsWith('applicationId "') ||
.split('"')[1]; l.startsWith('applicationId \''))
.first;
appId = appId
.split(appId.startsWith('applicationId "') ? '"' : '\'')[1];
if (appId.startsWith('\${') && appId.endsWith('}')) { if (appId.startsWith('\${') && appId.endsWith('}')) {
appId = trimmedLines appId = trimmedLines
.where((l) => l.startsWith( .where((l) => l.startsWith(
'def ${appId.substring(2, appId.length - 1)}')) 'def ${appId.substring(2, appId.length - 1)}'))
.first .first;
.split('"')[1]; appId = appId.split(appId.contains('"') ? '"' : '\'')[1];
} }
if (appId.isNotEmpty) { if (appId.isNotEmpty) {
return appId; return appId;
@ -137,19 +156,57 @@ class GitHub extends AppSource {
return url.substring(0, match.end); return url.substring(0, match.end);
} }
Future<String> getCredentialPrefixIfAny() async { @override
SettingsProvider settingsProvider = SettingsProvider(); Future<Map<String, String>?> getRequestHeaders(
await settingsProvider.initializeSettings(); {Map<String, dynamic> additionalSettings = const <String, dynamic>{},
String? creds = settingsProvider bool forAPKDownload = false}) async {
.getSettingString(additionalSourceSpecificSettingFormItems[0].key); var token = await getTokenIfAny(additionalSettings);
return creds != null && creds.isNotEmpty ? '$creds@' : ''; var headers = <String, String>{};
if (token != null) {
headers[HttpHeaders.authorizationHeader] = 'Token $token';
}
if (forAPKDownload == true) {
headers[HttpHeaders.acceptHeader] = 'application/octet-stream';
}
if (headers.isNotEmpty) {
return headers;
} else {
return null;
}
} }
Future<String> getAPIHost() async => Future<String?> getTokenIfAny(Map<String, dynamic> additionalSettings) async {
'https://${await getCredentialPrefixIfAny()}api.$host'; SettingsProvider settingsProvider = SettingsProvider();
await settingsProvider.initializeSettings();
var sourceConfig =
await getSourceConfigValues(additionalSettings, settingsProvider);
String? creds = sourceConfig['github-creds'];
if (creds != null) {
var userNameEndIndex = creds.indexOf(':');
if (userNameEndIndex > 0) {
creds = creds.substring(
userNameEndIndex + 1); // For old username-included token inputs
}
return creds;
} else {
return null;
}
}
Future<String> convertStandardUrlToAPIUrl(String standardUrl) async => @override
'${await getAPIHost()}/repos${standardUrl.substring('https://$host'.length)}'; Future<String?> getSourceNote() async {
if (!hostChanged && (await getTokenIfAny({})) == null) {
return '${tr('githubSourceNote')} ${hostChanged ? tr('addInfoBelow') : tr('addInfoInSettings')}';
}
return null;
}
Future<String> getAPIHost(Map<String, dynamic> additionalSettings) async =>
'https://api.$host';
Future<String> convertStandardUrlToAPIUrl(
String standardUrl, Map<String, dynamic> additionalSettings) async =>
'${await getAPIHost(additionalSettings)}/repos${standardUrl.substring('https://$host'.length)}';
@override @override
String? changeLogPageFromStandardUrl(String standardUrl) => String? changeLogPageFromStandardUrl(String standardUrl) =>
@ -167,6 +224,27 @@ class GitHub extends AppSource {
true true
? additionalSettings['filterReleaseTitlesByRegEx'] ? additionalSettings['filterReleaseTitlesByRegEx']
: null; : null;
String? regexNotesFilter =
(additionalSettings['filterReleaseNotesByRegEx'] as String?)
?.isNotEmpty ==
true
? additionalSettings['filterReleaseNotesByRegEx']
: null;
bool verifyLatestTag = additionalSettings['verifyLatestTag'] == true;
String? latestTag;
if (verifyLatestTag) {
var temp = requestUrl.split('?');
Response res = await sourceRequest(
'${temp[0]}/latest${temp.length > 1 ? '?${temp.sublist(1).join('?')}' : ''}');
if (res.statusCode != 200) {
if (onHttpErrorCode != null) {
onHttpErrorCode(res);
}
throw getObtainiumHttpError(res);
}
var jsres = jsonDecode(res.body);
latestTag = jsres['tag_name'] ?? jsres['name'];
}
Response res = await sourceRequest(requestUrl); Response res = await sourceRequest(requestUrl);
if (res.statusCode == 200) { if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>; var releases = jsonDecode(res.body) as List<dynamic>;
@ -174,9 +252,10 @@ class GitHub extends AppSource {
List<MapEntry<String, String>> getReleaseAPKUrls(dynamic release) => List<MapEntry<String, String>> getReleaseAPKUrls(dynamic release) =>
(release['assets'] as List<dynamic>?) (release['assets'] as List<dynamic>?)
?.map((e) { ?.map((e) {
return e['name'] != null && e['browser_download_url'] != null return (e['name'] != null) &&
((e['url'] ?? e['browser_download_url']) != null)
? MapEntry(e['name'] as String, ? MapEntry(e['name'] as String,
e['browser_download_url'] as String) (e['url'] ?? e['browser_download_url']) as String)
: const MapEntry('', ''); : const MapEntry('', '');
}) })
.where((element) => element.key.toLowerCase().endsWith('.apk')) .where((element) => element.key.toLowerCase().endsWith('.apk'))
@ -208,11 +287,22 @@ class GitHub extends AppSource {
(nameA as String).substring(matchA!.start, matchA.end), (nameA as String).substring(matchA!.start, matchA.end),
(nameB as String).substring(matchB!.start, matchB.end)); (nameB as String).substring(matchB!.start, matchB.end));
} else { } else {
return getReleaseDateFromRelease(a)! return (getReleaseDateFromRelease(a) ?? DateTime(1))
.compareTo(getReleaseDateFromRelease(b)!); .compareTo(getReleaseDateFromRelease(b) ?? DateTime(0));
} }
} }
}); });
if (latestTag != null &&
releases.isNotEmpty &&
latestTag !=
(releases[releases.length - 1]['tag_name'] ??
releases[0]['name'])) {
var ind = releases.indexWhere(
(element) => latestTag == (element['tag_name'] ?? element['name']));
if (ind >= 0) {
releases.add(releases.removeAt(ind));
}
}
releases = releases.reversed.toList(); releases = releases.reversed.toList();
dynamic targetRelease; dynamic targetRelease;
var prerrelsSkipped = 0; var prerrelsSkipped = 0;
@ -235,6 +325,11 @@ class GitHub extends AppSource {
!RegExp(regexFilter).hasMatch(nameToFilter.trim())) { !RegExp(regexFilter).hasMatch(nameToFilter.trim())) {
continue; continue;
} }
if (regexNotesFilter != null &&
!RegExp(regexNotesFilter)
.hasMatch(((releases[i]['body'] as String?) ?? '').trim())) {
continue;
}
var apkUrls = getReleaseAPKUrls(releases[i]); var apkUrls = getReleaseAPKUrls(releases[i]);
if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) { if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) {
continue; continue;
@ -293,7 +388,7 @@ class GitHub extends AppSource {
) async { ) async {
return await getLatestAPKDetailsCommon2(standardUrl, additionalSettings, return await getLatestAPKDetailsCommon2(standardUrl, additionalSettings,
(bool useTagUrl) async { (bool useTagUrl) async {
return '${await convertStandardUrlToAPIUrl(standardUrl)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100'; return '${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
}, (Response res) { }, (Response res) {
rateLimitErrorCheck(res); rateLimitErrorCheck(res);
}); });
@ -307,20 +402,26 @@ class GitHub extends AppSource {
Future<Map<String, List<String>>> searchCommon( Future<Map<String, List<String>>> searchCommon(
String query, String requestUrl, String rootProp, String query, String requestUrl, String rootProp,
{Function(Response)? onHttpErrorCode}) async { {Function(Response)? onHttpErrorCode,
Map<String, dynamic> querySettings = const {}}) async {
Response res = await sourceRequest(requestUrl); Response res = await sourceRequest(requestUrl);
if (res.statusCode == 200) { if (res.statusCode == 200) {
int minStarCount = querySettings['minStarCount'] != null
? int.parse(querySettings['minStarCount'])
: 0;
Map<String, List<String>> urlsWithDescriptions = {}; Map<String, List<String>> urlsWithDescriptions = {};
for (var e in (jsonDecode(res.body)[rootProp] as List<dynamic>)) { for (var e in (jsonDecode(res.body)[rootProp] as List<dynamic>)) {
urlsWithDescriptions.addAll({ if ((e['stargazers_count'] ?? e['stars_count'] ?? 0) >= minStarCount) {
e['html_url'] as String: [ urlsWithDescriptions.addAll({
e['full_name'] as String, e['html_url'] as String: [
((e['archived'] == true ? '[ARCHIVED] ' : '') + e['full_name'] as String,
(e['description'] != null ((e['archived'] == true ? '[ARCHIVED] ' : '') +
? e['description'] as String (e['description'] != null
: tr('noDescription'))) ? e['description'] as String
] : tr('noDescription')))
}); ]
});
}
} }
return urlsWithDescriptions; return urlsWithDescriptions;
} else { } else {
@ -332,13 +433,14 @@ class GitHub extends AppSource {
} }
@override @override
Future<Map<String, List<String>>> search(String query) async { Future<Map<String, List<String>>> search(String query,
{Map<String, dynamic> querySettings = const {}}) async {
return searchCommon( return searchCommon(
query, query,
'${await getAPIHost()}/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100', '${await getAPIHost({})}/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100',
'items', onHttpErrorCode: (Response res) { 'items', onHttpErrorCode: (Response res) {
rateLimitErrorCheck(res); rateLimitErrorCheck(res);
}); }, querySettings: querySettings);
} }
rateLimitErrorCheck(Response res) { rateLimitErrorCheck(Response res) {

View File

@ -16,7 +16,7 @@ class GitLab extends AppSource {
host = 'gitlab.com'; host = 'gitlab.com';
canSearch = true; canSearch = true;
additionalSourceSpecificSettingFormItems = [ sourceConfigSettingFormItems = [
GeneratedFormTextField('gitlab-creds', GeneratedFormTextField('gitlab-creds',
label: tr('gitlabPATLabel'), label: tr('gitlabPATLabel'),
password: true, password: true,
@ -60,17 +60,27 @@ class GitLab extends AppSource {
return url.substring(0, match.end); return url.substring(0, match.end);
} }
Future<String?> getPATIfAny() async { Future<String?> getPATIfAny(Map<String, dynamic> additionalSettings) async {
SettingsProvider settingsProvider = SettingsProvider(); SettingsProvider settingsProvider = SettingsProvider();
await settingsProvider.initializeSettings(); await settingsProvider.initializeSettings();
String? creds = settingsProvider var sourceConfig =
.getSettingString(additionalSourceSpecificSettingFormItems[0].key); await getSourceConfigValues(additionalSettings, settingsProvider);
String? creds = sourceConfig['gitlab-creds'];
return creds != null && creds.isNotEmpty ? creds : null; return creds != null && creds.isNotEmpty ? creds : null;
} }
@override @override
Future<Map<String, List<String>>> search(String query) async { Future<String?> getSourceNote() async {
String? PAT = await getPATIfAny(); if ((await getPATIfAny({})) == null) {
return '${tr('gitlabSourceNote')} ${hostChanged ? tr('addInfoBelow') : tr('addInfoInSettings')}';
}
return null;
}
@override
Future<Map<String, List<String>>> search(String query,
{Map<String, dynamic> querySettings = const {}}) async {
String? PAT = await getPATIfAny({});
if (PAT == null) { if (PAT == null) {
throw CredsNeededError(name); throw CredsNeededError(name);
} }
@ -102,11 +112,53 @@ class GitLab extends AppSource {
) async { ) async {
bool fallbackToOlderReleases = bool fallbackToOlderReleases =
additionalSettings['fallbackToOlderReleases'] == true; additionalSettings['fallbackToOlderReleases'] == true;
Response res = await sourceRequest('$standardUrl/-/tags?format=atom'); String? PAT = await getPATIfAny(hostChanged ? additionalSettings : {});
if (res.statusCode == 200) { Iterable<APKDetails> apkDetailsList = [];
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');
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var json = jsonDecode(res.body) as List<dynamic>;
apkDetailsList = json.map((e) {
var apkUrlsFromAssets = (e['assets']?['links'] as List<dynamic>? ?? [])
.map((e) {
return (e['direct_asset_url'] ?? e['url'] ?? '') as String;
})
.where((s) => s.isNotEmpty)
.toList();
List<String> uploadedAPKsFromDescription =
((e['description'] ?? '') as String)
.split('](')
.join('\n')
.split('.apk)')
.join('.apk\n')
.split('\n')
.where((s) => s.startsWith('/uploads/') && s.endsWith('apk'))
.map((s) => '$standardUrl$s')
.toList();
var apkUrlsSet = apkUrlsFromAssets.toSet();
apkUrlsSet.addAll(uploadedAPKsFromDescription);
var releaseDateString = e['released_at'] ?? e['created_at'];
DateTime? releaseDate = releaseDateString != null
? DateTime.parse(releaseDateString)
: null;
return APKDetails(
e['tag_name'] ?? e['name'],
getApkUrlsFromUrls(apkUrlsSet.toList()),
GitHub().getAppNames(standardUrl),
releaseDate: releaseDate);
});
} else {
Response res = await sourceRequest('$standardUrl/-/tags?format=atom');
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var standardUri = Uri.parse(standardUrl); var standardUri = Uri.parse(standardUrl);
var parsedHtml = parse(res.body); var parsedHtml = parse(res.body);
var apkDetailsList = parsedHtml.querySelectorAll('entry').map((entry) { apkDetailsList = parsedHtml.querySelectorAll('entry').map((entry) {
var entryContent = parse( var entryContent = parse(
parseFragment(entry.querySelector('content')!.innerHtml).text); parseFragment(entry.querySelector('content')!.innerHtml).text);
var apkUrls = [ var apkUrls = [
@ -124,7 +176,6 @@ class GitLab extends AppSource {
.where((element) => Uri.parse(element).host != '') .where((element) => Uri.parse(element).host != '')
.toList() .toList()
]; ];
var entryId = entry.querySelector('id')?.innerHtml; var entryId = entry.querySelector('id')?.innerHtml;
var version = var version =
entryId == null ? null : Uri.parse(entryId).pathSegments.last; entryId == null ? null : Uri.parse(entryId).pathSegments.last;
@ -139,21 +190,19 @@ class GitLab extends AppSource {
GitHub().getAppNames(standardUrl), GitHub().getAppNames(standardUrl),
releaseDate: releaseDate); releaseDate: releaseDate);
}); });
}
if (apkDetailsList.isEmpty) {
throw NoReleasesError();
}
if (fallbackToOlderReleases) {
if (additionalSettings['trackOnly'] != true) {
apkDetailsList =
apkDetailsList.where((e) => e.apkUrls.isNotEmpty).toList();
}
if (apkDetailsList.isEmpty) { if (apkDetailsList.isEmpty) {
throw NoReleasesError(); throw NoReleasesError();
} }
if (fallbackToOlderReleases) {
if (additionalSettings['trackOnly'] != true) {
apkDetailsList =
apkDetailsList.where((e) => e.apkUrls.isNotEmpty).toList();
}
if (apkDetailsList.isEmpty) {
throw NoReleasesError();
}
}
return apkDetailsList.first;
} else {
throw getObtainiumHttpError(res);
} }
return apkDetailsList.first;
} }
} }

View File

@ -1,6 +1,8 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@ -17,7 +19,7 @@ String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
.toList(); .toList();
if (ambiguousUrl.startsWith('/') || currPathSegments.isEmpty) { if (ambiguousUrl.startsWith('/') || currPathSegments.isEmpty) {
return '${referenceAbsoluteUrl.origin}/$ambiguousUrl'; return '${referenceAbsoluteUrl.origin}/$ambiguousUrl';
} else if (ambiguousUrl.split('/').length == 1) { } else if (ambiguousUrl.split('/').where((e) => e.isNotEmpty).length == 1) {
return '${referenceAbsoluteUrl.origin}/${currPathSegments.join('/')}/$ambiguousUrl'; return '${referenceAbsoluteUrl.origin}/${currPathSegments.join('/')}/$ambiguousUrl';
} else { } else {
return '${referenceAbsoluteUrl.origin}/${currPathSegments.sublist(0, currPathSegments.length - 1).join('/')}/$ambiguousUrl'; return '${referenceAbsoluteUrl.origin}/${currPathSegments.sublist(0, currPathSegments.length - 1).join('/')}/$ambiguousUrl';
@ -85,12 +87,61 @@ bool _isNumeric(String s) {
} }
class HTML extends AppSource { class HTML extends AppSource {
HTML() {
additionalSourceAppSpecificSettingFormItems = [
[
GeneratedFormSwitch('sortByFileNamesNotLinks',
label: tr('sortByFileNamesNotLinks'))
],
[
GeneratedFormTextField('customLinkFilterRegex',
label: tr('customLinkFilterRegex'),
hint: 'download/(.*/)?(android|apk|mobile)',
required: false,
additionalValidators: [
(value) {
return regExValidator(value);
}
])
],
[
GeneratedFormTextField('intermediateLinkRegex',
label: tr('intermediateLinkRegex'),
hint: '([0-9]+\.)*[0-9]+/\$',
required: false,
additionalValidators: [(value) => regExValidator(value)])
],
[
GeneratedFormTextField('versionExtractionRegEx',
label: tr('versionExtractionRegEx'),
required: false,
additionalValidators: [(value) => regExValidator(value)]),
GeneratedFormTextField('matchGroupToUse',
label: tr('matchGroupToUse'),
required: false,
hint: '1',
textInputType: const TextInputType.numberWithOptions(),
additionalValidators: [
(value) {
value ??= '1';
return intValidator(value);
}
])
]
];
overrideVersionDetectionFormDefault('noVersionDetection',
disableStandard: true, disableRelDate: true);
}
@override @override
// TODO: implement requestHeaders choice, hardcoded for now Future<Map<String, String>?> getRequestHeaders(
Map<String, String>? get requestHeaders => { {Map<String, dynamic> additionalSettings = const <String, dynamic>{},
"User-Agent": bool forAPKDownload = false}) async {
"Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0" return {
}; "User-Agent":
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36"
};
}
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
@ -105,15 +156,44 @@ class HTML extends AppSource {
var uri = Uri.parse(standardUrl); var uri = Uri.parse(standardUrl);
Response res = await sourceRequest(standardUrl); Response res = await sourceRequest(standardUrl);
if (res.statusCode == 200) { if (res.statusCode == 200) {
List<String> links = parse(res.body) var html = parse(res.body);
List<String> allLinks = html
.querySelectorAll('a') .querySelectorAll('a')
.map((element) => element.attributes['href'] ?? '') .map((element) => element.attributes['href'] ?? '')
.where((element) =>
Uri.parse(element).path.toLowerCase().endsWith('.apk'))
.toList(); .toList();
links.sort( List<String> links = [];
(a, b) => compareAlphaNumeric(a.split('/').last, b.split('/').last)); if ((additionalSettings['intermediateLinkRegex'] as String?)
if (additionalSettings['apkFilterRegEx'] != null) { ?.isNotEmpty ==
true) {
var reg = RegExp(additionalSettings['intermediateLinkRegex']);
links = allLinks.where((element) => reg.hasMatch(element)).toList();
links.sort((a, b) => compareAlphaNumeric(a, b));
if (links.isEmpty) {
throw ObtainiumError(tr('intermediateLinkNotFound'));
}
Map<String, dynamic> additionalSettingsTemp =
Map.from(additionalSettings);
additionalSettingsTemp['intermediateLinkRegex'] = null;
return getLatestAPKDetails(
ensureAbsoluteUrl(links.last, uri), additionalSettingsTemp);
}
if ((additionalSettings['customLinkFilterRegex'] as String?)
?.isNotEmpty ==
true) {
var reg = RegExp(additionalSettings['customLinkFilterRegex']);
links = allLinks.where((element) => reg.hasMatch(element)).toList();
} else {
links = allLinks
.where((element) =>
Uri.parse(element).path.toLowerCase().endsWith('.apk'))
.toList();
}
links.sort((a, b) => additionalSettings['sortByFileNamesNotLinks'] == true
? compareAlphaNumeric(a.split('/').where((e) => e.isNotEmpty).last,
b.split('/').where((e) => e.isNotEmpty).last)
: compareAlphaNumeric(a, b));
if ((additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty ==
true) {
var reg = RegExp(additionalSettings['apkFilterRegEx']); var reg = RegExp(additionalSettings['apkFilterRegEx']);
links = links.where((element) => reg.hasMatch(element)).toList(); links = links.where((element) => reg.hasMatch(element)).toList();
} }
@ -121,12 +201,24 @@ class HTML extends AppSource {
throw NoReleasesError(); throw NoReleasesError();
} }
var rel = links.last; var rel = links.last;
var apkName = rel.split('/').last; String? version = rel.hashCode.toString();
var version = apkName.substring(0, apkName.length - 4); var versionExtractionRegEx =
additionalSettings['versionExtractionRegEx'] as String?;
if (versionExtractionRegEx?.isNotEmpty == true) {
var match = RegExp(versionExtractionRegEx!).allMatches(rel);
if (match.isEmpty) {
throw NoVersionError();
}
version = match.last
.group(int.parse(additionalSettings['matchGroupToUse'] as String));
if (version?.isEmpty == true) {
throw NoVersionError();
}
}
List<String> apkUrls = List<String> apkUrls =
[rel].map((e) => ensureAbsoluteUrl(e, uri)).toList(); [rel].map((e) => ensureAbsoluteUrl(e, uri)).toList();
return APKDetails( return APKDetails(version!, apkUrls.map((e) => MapEntry(e, e)).toList(),
version, getApkUrlsFromUrls(apkUrls), AppNames(uri.host, tr('app'))); AppNames(uri.host, tr('app')));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }

View File

@ -0,0 +1,91 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
class HuaweiAppGallery extends AppSource {
HuaweiAppGallery() {
name = 'Huawei AppGallery';
host = 'appgallery.huawei.com';
overrideVersionDetectionFormDefault('releaseDateAsVersion',
disableStandard: true);
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/app/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
}
getDlUrl(String standardUrl) =>
'https://${host!.replaceAll('appgallery.', 'appgallery.cloud.')}/appdl/${standardUrl.split('/').last}';
requestAppdlRedirect(String dlUrl) async {
Response res = await sourceRequest(dlUrl, followRedirects: false);
if (res.statusCode == 200 ||
res.statusCode == 302 ||
res.statusCode == 304) {
return res;
} else {
throw getObtainiumHttpError(res);
}
}
appIdFromRedirectDlUrl(String redirectDlUrl) {
var parts = redirectDlUrl
.split('?')[0]
.split('/')
.last
.split('.')
.reversed
.toList();
parts.removeAt(0);
parts.removeAt(0);
return parts.reversed.join('.');
}
@override
Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async {
String dlUrl = getDlUrl(standardUrl);
Response res = await requestAppdlRedirect(dlUrl);
return res.headers['location'] != null
? appIdFromRedirectDlUrl(res.headers['location']!)
: null;
}
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
String dlUrl = getDlUrl(standardUrl);
Response res = await requestAppdlRedirect(dlUrl);
if (res.headers['location'] == null) {
throw NoReleasesError();
}
String appId = appIdFromRedirectDlUrl(res.headers['location']!);
var relDateStr =
res.headers['location']?.split('?')[0].split('.').reversed.toList()[1];
var relDateStrAdj = relDateStr?.split('');
var tempLen = relDateStrAdj?.length ?? 0;
var i = 2;
while (i < tempLen) {
relDateStrAdj?.insert((i + i ~/ 2 - 1), '-');
i += 2;
}
var relDate = relDateStrAdj == null
? null
: DateFormat('yy-MM-dd-HH-mm').parse(relDateStrAdj.join(''));
if (relDateStr == null) {
throw NoVersionError();
}
return APKDetails(
relDateStr, [MapEntry('$appId.apk', dlUrl)], AppNames(name, appId),
releaseDate: relDate);
}
}

View File

@ -3,8 +3,13 @@ import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
class IzzyOnDroid extends AppSource { class IzzyOnDroid extends AppSource {
late FDroid fd;
IzzyOnDroid() { IzzyOnDroid() {
host = 'android.izzysoft.de'; host = 'android.izzysoft.de';
fd = FDroid();
additionalSourceAppSpecificSettingFormItems =
fd.additionalSourceAppSpecificSettingFormItems;
} }
@override @override
@ -20,7 +25,7 @@ class IzzyOnDroid extends AppSource {
@override @override
Future<String?> tryInferringAppId(String standardUrl, Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async { {Map<String, dynamic> additionalSettings = const {}}) async {
return FDroid().tryInferringAppId(standardUrl); return fd.tryInferringAppId(standardUrl);
} }
@override @override
@ -29,10 +34,12 @@ class IzzyOnDroid extends AppSource {
Map<String, dynamic> additionalSettings, Map<String, dynamic> additionalSettings,
) async { ) async {
String? appId = await tryInferringAppId(standardUrl); String? appId = await tryInferringAppId(standardUrl);
return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse( return fd.getAPKUrlsFromFDroidPackagesAPIResponse(
await sourceRequest( await sourceRequest(
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'), 'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'),
'https://android.izzysoft.de/frepo/$appId', 'https://android.izzysoft.de/frepo/$appId',
standardUrl); standardUrl,
autoSelectHighestVersionCode:
additionalSettings['autoSelectHighestVersionCode'] == true);
} }
} }

View File

@ -6,7 +6,8 @@ import 'package:obtainium/providers/source_provider.dart';
class Jenkins extends AppSource { class Jenkins extends AppSource {
Jenkins() { Jenkins() {
overrideVersionDetectionFormDefault('releaseDateAsVersion', true); overrideVersionDetectionFormDefault('releaseDateAsVersion',
disableStandard: true);
} }
String trimJobUrl(String url) { String trimJobUrl(String url) {

View File

@ -0,0 +1,82 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:obtainium/app_sources/apkpure.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
class Uptodown extends AppSource {
Uptodown() {
host = 'uptodown.com';
allowSubDomains = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://([^\\.]+\\.){2,}$host');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {
throw InvalidURLError(name);
}
return '${url.substring(0, match.end)}/android/download';
}
@override
Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async {
return (await getAppDetailsFromPage(standardUrl))['appId'];
}
Future<Map<String, String?>> getAppDetailsFromPage(String standardUrl) async {
var res = await sourceRequest(standardUrl);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var html = parse(res.body);
String? version = html.querySelector('div.version')?.innerHtml;
String? apkUrl =
html.querySelector('#detail-download-button')?.attributes['data-url'];
String? name = html.querySelector('#detail-app-name')?.innerHtml.trim();
String? author = html.querySelector('#author-link')?.innerHtml.trim();
var detailElements = html.querySelectorAll('#technical-information td');
String? appId = (detailElements.elementAtOrNull(2))?.innerHtml.trim();
String? dateStr = (detailElements.elementAtOrNull(29))?.innerHtml.trim();
return Map.fromEntries([
MapEntry('version', version),
MapEntry('apkUrl', apkUrl),
MapEntry('appId', appId),
MapEntry('name', name),
MapEntry('author', author),
MapEntry('dateStr', dateStr)
]);
}
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
var appDetails = await getAppDetailsFromPage(standardUrl);
var version = appDetails['version'];
var apkUrl = appDetails['apkUrl'];
var appId = appDetails['appId'];
if (version == null) {
throw NoVersionError();
}
if (apkUrl == null) {
throw NoAPKError();
}
if (appId == null) {
throw NoReleasesError();
}
String appName = appDetails['name'] ?? tr('app');
String author = appDetails['author'] ?? name;
String? dateStr = appDetails['dateStr'];
DateTime? relDate;
if (dateStr != null) {
relDate = parseDateTimeMMMddCommayyyy(dateStr);
}
return APKDetails(
version, getApkUrlsFromUrls([apkUrl]), AppNames(author, appName),
releaseDate: relDate);
}
}

View File

@ -1,5 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@ -7,19 +9,46 @@ class VLC extends AppSource {
VLC() { VLC() {
host = 'videolan.org'; host = 'videolan.org';
} }
get dwUrlBase => 'https://get.$host/vlc-android/';
@override
Future<Map<String, String>?> getRequestHeaders(
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
bool forAPKDownload = false}) =>
HTML().getRequestHeaders(
additionalSettings: additionalSettings,
forAPKDownload: forAPKDownload);
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
return 'https://$host'; return 'https://$host';
} }
Future<String?> getLatestVersion(String standardUrl) async {
Response res = await sourceRequest(dwUrlBase);
if (res.statusCode == 200) {
var dwLinks = parse(res.body)
.querySelectorAll('a')
.where((element) => element.attributes['href'] != 'last/')
.map((e) => e.attributes['href']?.split('/')[0])
.toList();
String? version = dwLinks.isNotEmpty ? dwLinks.last : null;
if (version == null) {
throw NoVersionError();
}
return version;
} else {
throw getObtainiumHttpError(res);
}
}
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, String standardUrl,
Map<String, dynamic> additionalSettings, Map<String, dynamic> additionalSettings,
) async { ) async {
Response res = await sourceRequest( Response res = await get(
'https://www.videolan.org/vlc/download-android.html'); Uri.parse('https://www.videolan.org/vlc/download-android.html'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
var dwUrlBase = 'get.videolan.org/vlc-android'; var dwUrlBase = 'get.videolan.org/vlc-android';
var dwLinks = parse(res.body) var dwLinks = parse(res.body)
@ -37,26 +66,43 @@ class VLC extends AppSource {
throw NoVersionError(); throw NoVersionError();
} }
String? targetUrl = 'https://$dwUrlBase/$version/'; String? targetUrl = 'https://$dwUrlBase/$version/';
Response res2 = await sourceRequest(targetUrl); var apkUrls = ['arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64']
String mirrorDwBase = .map((e) => '${targetUrl}VLC-Android-$version-$e.apk')
'https://plug-mirror.rcac.purdue.edu/vlc/vlc-android/$version/'; .toList();
List<String> apkUrls = [];
if (res2.statusCode == 200) {
apkUrls = parse(res2.body)
.querySelectorAll('a')
.map((e) => e.attributes['href'])
.where((h) =>
h != null && h.isNotEmpty && h.toLowerCase().endsWith('.apk'))
.map((e) => mirrorDwBase + e!)
.toList();
} else {
throw getObtainiumHttpError(res2);
}
return APKDetails( return APKDetails(
version, getApkUrlsFromUrls(apkUrls), AppNames('VideoLAN', 'VLC')); version, getApkUrlsFromUrls(apkUrls), AppNames('VideoLAN', 'VLC'));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }
} }
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
Response res = await sourceRequest(apkUrl);
if (res.statusCode == 200) {
String? apkUrl =
parse(res.body).querySelector('#alt_link')?.attributes['href'];
if (apkUrl == null) {
throw NoAPKError();
}
return apkUrl;
} else if (res.statusCode == 500 &&
res.body.toLowerCase().indexOf('mirror') > 0) {
var html = parse(res.body);
var err = '';
html.body?.nodes.forEach((element) {
if (element.text != null) {
err += '${element.text}\n';
}
});
err = err.trim();
if (err.isEmpty) {
err = tr('err');
}
throw ObtainiumError(err);
} else {
throw getObtainiumHttpError(res);
}
}
} }

View File

@ -1,5 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:hsluv/hsluv.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:obtainium/components/generated_form_modal.dart'; import 'package:obtainium/components/generated_form_modal.dart';
@ -24,6 +25,7 @@ class GeneratedFormTextField extends GeneratedFormItem {
late int max; late int max;
late String? hint; late String? hint;
late bool password; late bool password;
late TextInputType? textInputType;
GeneratedFormTextField(String key, GeneratedFormTextField(String key,
{String label = 'Input', {String label = 'Input',
@ -33,7 +35,8 @@ class GeneratedFormTextField extends GeneratedFormItem {
this.required = true, this.required = true,
this.max = 1, this.max = 1,
this.hint, this.hint,
this.password = false}) this.password = false,
this.textInputType})
: super(key, : super(key,
label: label, label: label,
belowWidgets: belowWidgets, belowWidgets: belowWidgets,
@ -132,19 +135,20 @@ class GeneratedForm extends StatefulWidget {
State<GeneratedForm> createState() => _GeneratedFormState(); State<GeneratedForm> createState() => _GeneratedFormState();
} }
// Generates a random light color // Generates a color in the HSLuv (Pastel) color space
// Courtesy of ChatGPT 😭 (with a bugfix 🥳) // https://pub.dev/documentation/hsluv/latest/hsluv/Hsluv/hpluvToRgb.html
Color generateRandomLightColor() { Color generateRandomLightColor() {
// Create a random number generator final randomSeed = Random().nextInt(120);
final Random random = Random(); // https://en.wikipedia.org/wiki/Golden_angle
final goldenAngle = 180 * (3 - sqrt(5));
// Generate random hue, saturation, and value values // Generate next golden angle hue
final double hue = random.nextDouble() * 360; final double hue = randomSeed * goldenAngle;
final double saturation = 0.5 + random.nextDouble() * 0.5; // Map from HPLuv color space to RGB, use constant saturation=100, lightness=70
final double value = 0.9 + random.nextDouble() * 0.1; final List<double> rgbValuesDbl = Hsluv.hpluvToRgb([hue, 100, 70]);
// Map RBG values from 0-1 to 0-255:
// Create a HSV color with the random values final List<int> rgbValues =
return HSVColor.fromAHSV(1.0, hue, saturation, value).toColor(); rgbValuesDbl.map((rgb) => (rgb * 255).toInt()).toList();
return Color.fromARGB(255, rgbValues[0], rgbValues[1], rgbValues[2]);
} }
class _GeneratedFormState extends State<GeneratedForm> { class _GeneratedFormState extends State<GeneratedForm> {
@ -189,6 +193,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
if (formItem is GeneratedFormTextField) { if (formItem is GeneratedFormTextField) {
final formFieldKey = GlobalKey<FormFieldState>(); final formFieldKey = GlobalKey<FormFieldState>();
return TextFormField( return TextFormField(
keyboardType: formItem.textInputType,
obscureText: formItem.password, obscureText: formItem.password,
autocorrect: !formItem.password, autocorrect: !formItem.password,
enableSuggestions: !formItem.password, enableSuggestions: !formItem.password,
@ -368,6 +373,39 @@ class _GeneratedFormState extends State<GeneratedForm> {
)); ));
}) ?? }) ??
[const SizedBox.shrink()], [const SizedBox.shrink()],
(values[widget.items[r][e].key]
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'),
))
: const SizedBox.shrink(),
(values[widget.items[r][e].key] (values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?) as Map<String, MapEntry<int, bool>>?)
?.values ?.values

View File

@ -11,7 +11,8 @@ class GeneratedFormModal extends StatefulWidget {
this.initValid = false, this.initValid = false,
this.message = '', this.message = '',
this.additionalWidgets = const [], this.additionalWidgets = const [],
this.singleNullReturnButton}); this.singleNullReturnButton,
this.primaryActionColour});
final String title; final String title;
final String message; final String message;
@ -19,6 +20,7 @@ class GeneratedFormModal extends StatefulWidget {
final bool initValid; final bool initValid;
final List<Widget> additionalWidgets; final List<Widget> additionalWidgets;
final String? singleNullReturnButton; final String? singleNullReturnButton;
final Color? primaryActionColour;
@override @override
State<GeneratedFormModal> createState() => _GeneratedFormModalState(); State<GeneratedFormModal> createState() => _GeneratedFormModalState();
@ -71,6 +73,10 @@ class _GeneratedFormModalState extends State<GeneratedFormModal> {
: widget.singleNullReturnButton!)), : widget.singleNullReturnButton!)),
widget.singleNullReturnButton == null widget.singleNullReturnButton == null
? TextButton( ? TextButton(
style: widget.primaryActionColour == null
? null
: TextButton.styleFrom(
foregroundColor: widget.primaryActionColour),
onPressed: !valid onPressed: !valid
? null ? null
: () { : () {

View File

@ -1,6 +1,7 @@
import 'package:android_package_installer/android_package_installer.dart'; import 'package:android_package_installer/android_package_installer.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/providers/logs_provider.dart'; import 'package:obtainium/providers/logs_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -101,7 +102,14 @@ showError(dynamic e, BuildContext context) {
title: Text(e is MultiAppMultiError title: Text(e is MultiAppMultiError
? tr('someErrors') ? tr('someErrors')
: tr('unexpectedError')), : tr('unexpectedError')),
content: Text(e.toString()), content: GestureDetector(
onLongPress: () {
Clipboard.setData(ClipboardData(text: e.toString()));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(tr('copiedToClipboard')),
));
},
child: Text(e.toString())),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {

View File

@ -1,9 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/pages/home.dart'; import 'package:obtainium/pages/home.dart';
import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/logs_provider.dart'; import 'package:obtainium/providers/logs_provider.dart';
@ -21,7 +19,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports // ignore: implementation_imports
import 'package:easy_localization/src/localization.dart'; import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.13.9'; const String currentVersion = '0.14.13';
const String currentReleaseTag = const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
@ -39,6 +37,8 @@ List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('es'), 'Español'), MapEntry(Locale('es'), 'Español'),
MapEntry(Locale('pl'), 'Polski'), MapEntry(Locale('pl'), 'Polski'),
MapEntry(Locale('ru'), 'Русский язык'), MapEntry(Locale('ru'), 'Русский язык'),
MapEntry(Locale('bs'), 'Bosanski'),
// MapEntry(Locale('br'), 'Brasileiro'),
]; ];
const fallbackLocale = Locale('en'); const fallbackLocale = Locale('en');
const localeDir = 'assets/translations'; const localeDir = 'assets/translations';
@ -70,84 +70,6 @@ Future<void> loadTranslations() async {
fallbackTranslations: controller.fallbackTranslations); fallbackTranslations: controller.fallbackTranslations);
} }
@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 SocketException) {
var remainingMinutes = e is RateLimitError ? e.remainingMinutes : 15;
logs.add(plural('bgUpdateGotErrorRetryInMinutes', remainingMinutes,
args: [e.toString(), remainingMinutes.toString()]));
AndroidAlarmManager.oneShot(Duration(minutes: remainingMinutes),
Random().nextInt(pow(2, 31) as int), bgUpdateCheck, params: {
'ignoreAfterMicroseconds': nextIgnoreAfter.microsecondsSinceEpoch
});
} else {
err = e.toString();
}
}
List<App> newUpdates = appsProvider
.findExistingUpdates(installedOnly: true)
.where((id) => !existingUpdateIds.contains(id))
.map((e) => appsProvider.apps[e]!.app)
.toList();
// TODO: This silent update code doesn't work yet
// List<String> silentlyUpdated = await appsProvider
// .downloadAndInstallLatestApp(
// [...newUpdates.map((e) => e.id), ...existingUpdateIds], null);
// if (silentlyUpdated.isNotEmpty) {
// newUpdates = newUpdates
// .where((element) => !silentlyUpdated.contains(element.id))
// .toList();
// notificationsProvider.notify(
// SilentUpdateNotification(
// silentlyUpdated.map((e) => appsProvider.apps[e]!.app).toList()),
// cancelExisting: true);
// }
logs.add(
plural('bgCheckFoundUpdatesWillNotifyIfNeeded', newUpdates.length));
if (newUpdates.isNotEmpty) {
notificationsProvider.notify(UpdateNotification(newUpdates));
}
if (err != null) {
throw err;
}
} catch (e) {
notificationsProvider
.notify(ErrorCheckingUpdatesNotification(e.toString()));
} finally {
logs.add(tr('bgUpdateTaskFinished'));
await notificationsProvider.cancel(checkingUpdatesNotification.id);
}
}
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
try { try {
@ -205,7 +127,7 @@ class _ObtainiumState extends State<Obtainium> {
} else { } else {
bool isFirstRun = settingsProvider.checkAndFlipFirstRun(); bool isFirstRun = settingsProvider.checkAndFlipFirstRun();
if (isFirstRun) { if (isFirstRun) {
logs.add(tr('firstRun')); logs.add('This is the first ever run of Obtainium.');
// If this is the first run, ask for notification permissions and add Obtainium to the Apps list // If this is the first run, ask for notification permissions and add Obtainium to the Apps list
Permission.notification.request(); Permission.notification.request();
appsProvider.saveApps([ appsProvider.saveApps([
@ -226,28 +148,34 @@ class _ObtainiumState extends State<Obtainium> {
if (!supportedLocales if (!supportedLocales
.map((e) => e.key.languageCode) .map((e) => e.key.languageCode)
.contains(context.locale.languageCode) || .contains(context.locale.languageCode) ||
settingsProvider.forcedLocale == null && (settingsProvider.forcedLocale == null &&
context.deviceLocale.languageCode != context.deviceLocale.languageCode !=
context.locale.languageCode) { context.locale.languageCode)) {
settingsProvider.resetLocaleSafe(context); settingsProvider.resetLocaleSafe(context);
} }
// Register the background update task according to the user's setting // Register the background update task according to the user's setting
if (existingUpdateInterval != settingsProvider.updateInterval) { var actualUpdateInterval = settingsProvider.updateInterval;
if (existingUpdateInterval != -1) { if (existingUpdateInterval != actualUpdateInterval) {
logs.add(tr('settingUpdateCheckIntervalTo', if (actualUpdateInterval == 0) {
args: [settingsProvider.updateInterval.toString()]));
}
existingUpdateInterval = settingsProvider.updateInterval;
if (existingUpdateInterval == 0) {
AndroidAlarmManager.cancel(bgUpdateCheckAlarmId); AndroidAlarmManager.cancel(bgUpdateCheckAlarmId);
} else { } else {
AndroidAlarmManager.periodic( var settingChanged = existingUpdateInterval != -1;
Duration(minutes: existingUpdateInterval), var lastCheckWasTooLongAgo = actualUpdateInterval != 0 &&
bgUpdateCheckAlarmId, settingsProvider.lastBGCheckTime
bgUpdateCheck, .add(Duration(minutes: actualUpdateInterval + 60))
rescheduleOnReboot: true, .isBefore(DateTime.now());
wakeup: true); if (settingChanged || lastCheckWasTooLongAgo) {
logs.add(
'Update interval was set to ${actualUpdateInterval.toString()} (reason: ${settingChanged ? 'setting changed' : 'last check was ${settingsProvider.lastBGCheckTime.toLocal().toString()}'}).');
AndroidAlarmManager.periodic(
Duration(minutes: actualUpdateInterval),
bgUpdateCheckAlarmId,
bgUpdateCheck,
rescheduleOnReboot: true,
wakeup: true);
}
} }
existingUpdateInterval = actualUpdateInterval;
} }
} }
@ -292,7 +220,9 @@ class _ObtainiumState extends State<Obtainium> {
? lightColorScheme ? lightColorScheme
: darkColorScheme, : darkColorScheme,
fontFamily: 'Metropolis'), fontFamily: 'Metropolis'),
home: const HomePage()); home: Shortcuts(shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
}, child: const HomePage()));
}); });
} }
} }

View File

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

View File

@ -11,6 +11,7 @@ import 'package:obtainium/pages/app.dart';
import 'package:obtainium/pages/import_export.dart'; import 'package:obtainium/pages/import_export.dart';
import 'package:obtainium/pages/settings.dart'; import 'package:obtainium/pages/settings.dart';
import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/notifications_provider.dart';
import 'package:obtainium/providers/settings_provider.dart'; import 'package:obtainium/providers/settings_provider.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -41,6 +42,9 @@ class _AddAppPageState extends State<AddAppPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
AppsProvider appsProvider = context.read<AppsProvider>(); AppsProvider appsProvider = context.read<AppsProvider>();
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
NotificationsProvider notificationsProvider =
context.read<NotificationsProvider>();
bool doingSomething = gettingAppInfo || searching; bool doingSomething = gettingAppInfo || searching;
@ -85,8 +89,7 @@ class _AddAppPageState extends State<AddAppPage> {
} }
} }
Future<bool> getTrackOnlyConfirmationIfNeeded( Future<bool> getTrackOnlyConfirmationIfNeeded(bool userPickedTrackOnly,
bool userPickedTrackOnly, SettingsProvider settingsProvider,
{bool ignoreHideSetting = false}) async { {bool ignoreHideSetting = false}) async {
var useTrackOnly = userPickedTrackOnly || pickedSource!.enforceTrackOnly; var useTrackOnly = userPickedTrackOnly || pickedSource!.enforceTrackOnly;
if (useTrackOnly && if (useTrackOnly &&
@ -138,11 +141,9 @@ class _AddAppPageState extends State<AddAppPage> {
gettingAppInfo = true; gettingAppInfo = true;
}); });
try { try {
var settingsProvider = context.read<SettingsProvider>();
var userPickedTrackOnly = additionalSettings['trackOnly'] == true; var userPickedTrackOnly = additionalSettings['trackOnly'] == true;
App? app; App? app;
if ((await getTrackOnlyConfirmationIfNeeded( if ((await getTrackOnlyConfirmationIfNeeded(userPickedTrackOnly)) &&
userPickedTrackOnly, settingsProvider)) &&
(await getReleaseDateAsVersionConfirmationIfNeeded( (await getReleaseDateAsVersionConfirmationIfNeeded(
userPickedTrackOnly))) { userPickedTrackOnly))) {
var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly; var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly;
@ -163,7 +164,8 @@ class _AddAppPageState extends State<AddAppPage> {
app.apkUrls.map((e) => e.value).toList().indexOf(apkUrl.value); app.apkUrls.map((e) => e.value).toList().indexOf(apkUrl.value);
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
var downloadedArtifact = await appsProvider.downloadApp( var downloadedArtifact = await appsProvider.downloadApp(
app, globalNavigatorKey.currentContext); app, globalNavigatorKey.currentContext,
notificationsProvider: notificationsProvider);
DownloadedApk? downloadedFile; DownloadedApk? downloadedFile;
DownloadedXApkDir? downloadedDir; DownloadedXApkDir? downloadedDir;
if (downloadedArtifact is DownloadedApk) { if (downloadedArtifact is DownloadedApk) {
@ -410,7 +412,13 @@ class _AddAppPageState extends State<AddAppPage> {
), ),
GeneratedForm( GeneratedForm(
key: Key(pickedSource.runtimeType.toString()), key: Key(pickedSource.runtimeType.toString()),
items: pickedSource!.combinedAppSpecificSettingFormItems, items: [
...pickedSource!.combinedAppSpecificSettingFormItems,
...(pickedSourceOverride != null
? pickedSource!.sourceConfigSettingFormItems
.map((e) => [e])
: [])
],
onValueChanges: (values, valid, isBuilding) { onValueChanges: (values, valid, isBuilding) {
if (!isBuilding) { if (!isBuilding) {
setState(() { setState(() {
@ -455,14 +463,12 @@ class _AddAppPageState extends State<AddAppPage> {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const SizedBox(
height: 48,
),
Text( Text(
tr('supportedSourcesBelow'), tr('supportedSources'),
style: const TextStyle(fontWeight: FontWeight.bold),
), ),
const SizedBox( const SizedBox(
height: 8, height: 16,
), ),
...sourceProvider.sources ...sourceProvider.sources
.map((e) => GestureDetector( .map((e) => GestureDetector(
@ -504,15 +510,29 @@ class _AddAppPageState extends State<AddAppPage> {
HTML().runtimeType.toString())) HTML().runtimeType.toString()))
getHTMLSourceOverrideDropdown(), getHTMLSourceOverrideDropdown(),
if (shouldShowSearchBar()) getSearchBarRow(), if (shouldShowSearchBar()) getSearchBarRow(),
const SizedBox(
height: 16,
),
if (pickedSource != null) if (pickedSource != null)
getAdditionalOptsCol() FutureBuilder(
else builder: (ctx, val) {
getSourcesListWidget(), return val.data != null && val.data!.isNotEmpty
const SizedBox( ? Text(
height: 8, val.data!,
style:
Theme.of(context).textTheme.bodySmall,
)
: const SizedBox();
},
future: pickedSource?.getSourceNote()),
SizedBox(
height: pickedSource != null ? 16 : 96,
),
if (pickedSource != null) getAdditionalOptsCol(),
if (pickedSource == null)
const Divider(
height: 48,
),
if (pickedSource == null) getSourcesListWidget(),
SizedBox(
height: pickedSource != null ? 8 : 2,
), ),
])), ])),
) )

View File

@ -1,5 +1,3 @@
import 'dart:ui';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -46,7 +44,10 @@ class _AppPageState extends State<AppPage> {
? sourceProvider.getSource(app.app.url, ? sourceProvider.getSource(app.app.url,
overrideSource: app.app.overrideSource) overrideSource: app.app.overrideSource)
: null; : null;
if (!areDownloadsRunning && prevApp == null && app != null) { if (!areDownloadsRunning &&
prevApp == null &&
app != null &&
settingsProvider.checkUpdateOnDetailPage) {
prevApp = app; prevApp = app;
getUpdate(app.app.id); getUpdate(app.app.id);
} }
@ -56,6 +57,11 @@ class _AppPageState extends State<AppPage> {
app?.app.additionalSettings['versionDetection'] == app?.app.additionalSettings['versionDetection'] ==
'standardVersionDetection'; 'standardVersionDetection';
bool installedVersionIsEstimate = trackOnly ||
(app?.app.installedVersion != null &&
app?.app.additionalSettings['versionDetection'] ==
'noVersionDetection');
getInfoColumn() => Column( getInfoColumn() => Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@ -91,9 +97,7 @@ class _AppPageState extends State<AppPage> {
app?.app.latestVersion ?? tr('unknown') app?.app.latestVersion ?? tr('unknown')
])}\n${tr('installedVersionX', args: [ ])}\n${tr('installedVersionX', args: [
app?.app.installedVersion ?? tr('none') app?.app.installedVersion ?? tr('none')
])}${trackOnly ? ' ${tr('estimateInBrackets')}\n\n${tr('xIsTrackOnly', args: [ ])}${installedVersionIsEstimate ? '\n${tr('estimateInBrackets')}' : ''}',
tr('app')
])}' : ''}',
textAlign: TextAlign.end, textAlign: TextAlign.end,
style: Theme.of(context).textTheme.bodyLarge!, style: Theme.of(context).textTheme.bodyLarge!,
), ),
@ -104,11 +108,14 @@ class _AppPageState extends State<AppPage> {
Column( Column(
children: [ children: [
const SizedBox( const SizedBox(
height: 4, height: 16,
), ),
Text( Text(
tr('noVersionDetection'), '${trackOnly ? '${tr('xIsTrackOnly', args: [
tr('app')
])}\n' : ''}${tr('noVersionDetection')}',
style: Theme.of(context).textTheme.labelSmall, style: Theme.of(context).textTheme.labelSmall,
textAlign: TextAlign.center,
) )
], ],
), ),
@ -146,10 +153,10 @@ class _AppPageState extends State<AppPage> {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
const SizedBox(height: 125), const SizedBox(height: 125),
app?.installedInfo != null app?.icon != null
? Row(mainAxisAlignment: MainAxisAlignment.center, children: [ ? Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Image.memory( Image.memory(
app!.installedInfo!.icon!, app!.icon!,
height: 150, height: 150,
gaplessPlayback: true, gaplessPlayback: true,
) )
@ -331,12 +338,18 @@ class _AppPageState extends State<AppPage> {
try { try {
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
var res = await appsProvider.downloadAndInstallLatestApps( var res = await appsProvider.downloadAndInstallLatestApps(
app?.app.id != null ? [app!.app.id] : [], app?.app.id != null ? [app!.app.id] : [],
globalNavigatorKey.currentContext); globalNavigatorKey.currentContext,
);
if (app?.app.installedVersion != null && !trackOnly) {
// ignore: use_build_context_synchronously
showError(tr('appsUpdated'), context);
}
if (res.isNotEmpty && mounted) { if (res.isNotEmpty && mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
} catch (e) { } catch (e) {
// ignore: use_build_context_synchronously
showError(e, context); showError(e, context);
} }
} }
@ -424,8 +437,7 @@ class _AppPageState extends State<AppPage> {
? getResetInstallStatusButton() ? getResetInstallStatusButton()
: getInstallOrUpdateButton()), : getInstallOrUpdateButton()),
const SizedBox(width: 16.0), const SizedBox(width: 16.0),
Expanded( IconButton(
child: TextButton(
onPressed: app?.downloadProgress != null onPressed: app?.downloadProgress != null
? null ? null
: () { : () {
@ -438,13 +450,9 @@ class _AppPageState extends State<AppPage> {
} }
}); });
}, },
style: TextButton.styleFrom( tooltip: tr('remove'),
foregroundColor: icon: const Icon(Icons.delete_outline),
Theme.of(context).colorScheme.error, ),
surfaceTintColor:
Theme.of(context).colorScheme.error),
child: Text(tr('remove')),
)),
])), ])),
if (app?.downloadProgress != null) if (app?.downloadProgress != null)
Padding( Padding(

View File

@ -393,9 +393,9 @@ class AppsPageState extends State<AppsPage> {
} }
getAppIcon(int appIndex) { getAppIcon(int appIndex) {
return listedApps[appIndex].installedInfo != null return listedApps[appIndex].icon != null
? Image.memory( ? Image.memory(
listedApps[appIndex].installedInfo!.icon!, listedApps[appIndex].icon!,
gaplessPlayback: true, gaplessPlayback: true,
) )
: Row( : Row(
@ -448,33 +448,48 @@ class AppsPageState extends State<AppsPage> {
: const SizedBox.shrink(), : const SizedBox.shrink(),
GestureDetector( GestureDetector(
onTap: showChangesFn, onTap: showChangesFn,
child: Column( child: Container(
mainAxisAlignment: MainAxisAlignment.center, decoration: BoxDecoration(
crossAxisAlignment: CrossAxisAlignment.end, borderRadius: BorderRadius.circular(12),
children: [ color: settingsProvider.highlightTouchTargets &&
Row(mainAxisSize: MainAxisSize.min, children: [ showChangesFn != null
Container( ? (Theme.of(context).brightness == Brightness.light
constraints: BoxConstraints( ? Theme.of(context).primaryColor
maxWidth: MediaQuery.of(context).size.width / 4), : Theme.of(context).primaryColorLight)
child: Text(getVersionText(index), .withAlpha(20)
overflow: TextOverflow.ellipsis, : null),
textAlign: TextAlign.end)), padding: settingsProvider.highlightTouchTargets
]), ? const EdgeInsetsDirectional.fromSTEB(12, 0, 12, 0)
Row( : const EdgeInsetsDirectional.fromSTEB(24, 0, 0, 0),
mainAxisSize: MainAxisSize.min, child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Text( Row(mainAxisSize: MainAxisSize.min, children: [
getChangesButtonString(index, showChangesFn != null), Container(
style: TextStyle( constraints: BoxConstraints(
fontStyle: FontStyle.italic, maxWidth:
decoration: showChangesFn != null MediaQuery.of(context).size.width / 4),
? TextDecoration.underline child: Text(getVersionText(index),
: TextDecoration.none), overflow: TextOverflow.ellipsis,
) textAlign: TextAlign.end)),
]),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
getChangesButtonString(
index, showChangesFn != null),
style: TextStyle(
fontStyle: FontStyle.italic,
decoration: showChangesFn != null
? TextDecoration.underline
: TextDecoration.none),
)
],
),
], ],
), )))
],
))
], ],
); );
@ -543,20 +558,19 @@ class AppsPageState extends State<AppsPage> {
: FontWeight.normal)), : FontWeight.normal)),
trailing: listedApps[index].downloadProgress != null trailing: listedApps[index].downloadProgress != null
? SizedBox( ? SizedBox(
width: 90,
child: Text( child: Text(
listedApps[index].downloadProgress! >= 0 listedApps[index].downloadProgress! >= 0
? tr('percentProgress', args: [ ? tr('percentProgress', args: [
listedApps[index] listedApps[index]
.downloadProgress! .downloadProgress!
.toInt() .toInt()
.toString() .toString()
]) ])
: tr('pleaseWait'), : tr('pleaseWait'),
textAlign: (listedApps[index].downloadProgress! >= 0) textAlign: (listedApps[index].downloadProgress! >= 0)
? TextAlign.start ? TextAlign.start
: TextAlign.end, : TextAlign.end,
)) ))
: trailingRow, : trailingRow,
onTap: () { onTap: () {
if (selectedAppIds.isNotEmpty) { if (selectedAppIds.isNotEmpty) {
@ -644,15 +658,15 @@ class AppsPageState extends State<AppsPage> {
label: tr('installX', args: [ label: tr('installX', args: [
plural('apps', newInstallIdsAllOrSelected.length) plural('apps', newInstallIdsAllOrSelected.length)
]), ]),
defaultValue: existingUpdateIdsAllOrSelected.isNotEmpty)); defaultValue: existingUpdateIdsAllOrSelected.isEmpty));
} }
if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) { if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) {
formItems.add(GeneratedFormSwitch('trackonlies', formItems.add(GeneratedFormSwitch('trackonlies',
label: tr('markXTrackOnlyAsUpdated', args: [ label: tr('markXTrackOnlyAsUpdated', args: [
plural('apps', trackOnlyUpdateIdsAllOrSelected.length) plural('apps', trackOnlyUpdateIdsAllOrSelected.length)
]), ]),
defaultValue: existingUpdateIdsAllOrSelected.isNotEmpty || defaultValue: existingUpdateIdsAllOrSelected.isEmpty &&
newInstallIdsAllOrSelected.isNotEmpty)); newInstallIdsAllOrSelected.isEmpty));
} }
showDialog<Map<String, dynamic>?>( showDialog<Map<String, dynamic>?>(
context: context, context: context,
@ -685,11 +699,14 @@ class AppsPageState extends State<AppsPage> {
} }
appsProvider appsProvider
.downloadAndInstallLatestApps( .downloadAndInstallLatestApps(
toInstall, globalNavigatorKey.currentContext, toInstall, globalNavigatorKey.currentContext)
settingsProvider: settingsProvider)
.catchError((e) { .catchError((e) {
showError(e, context); showError(e, context);
return <String>[]; return <String>[];
}).then((value) {
if (shouldInstallUpdates) {
showError(tr('appsUpdated'), context);
}
}); });
} }
}); });

View File

@ -7,6 +7,7 @@ import 'package:obtainium/pages/apps.dart';
import 'package:obtainium/pages/import_export.dart'; import 'package:obtainium/pages/import_export.dart';
import 'package:obtainium/pages/settings.dart'; import 'package:obtainium/pages/settings.dart';
import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/settings_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
@ -26,6 +27,7 @@ class NavigationPageItem {
class _HomePageState extends State<HomePage> { class _HomePageState extends State<HomePage> {
List<int> selectedIndexHistory = []; List<int> selectedIndexHistory = [];
bool isReversing = false;
int prevAppCount = -1; int prevAppCount = -1;
bool prevIsLoading = true; bool prevIsLoading = true;
@ -41,8 +43,18 @@ class _HomePageState extends State<HomePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
AppsProvider appsProvider = context.watch<AppsProvider>(); AppsProvider appsProvider = context.watch<AppsProvider>();
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
setIsReversing(int targetIndex) {
bool reversing = selectedIndexHistory.isNotEmpty &&
selectedIndexHistory.last > targetIndex;
setState(() {
isReversing = reversing;
});
}
switchToPage(int index) async { switchToPage(int index) async {
setIsReversing(index);
if (index == 0) { if (index == 0) {
while ((pages[0].widget.key as GlobalKey<AppsPageState>).currentState != while ((pages[0].widget.key as GlobalKey<AppsPageState>).currentState !=
null) { null) {
@ -79,6 +91,12 @@ class _HomePageState extends State<HomePage> {
child: Scaffold( child: Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
body: PageTransitionSwitcher( body: PageTransitionSwitcher(
duration: Duration(
milliseconds:
settingsProvider.disablePageTransitions ? 0 : 300),
reverse: settingsProvider.reversePageTransitions
? !isReversing
: isReversing,
transitionBuilder: ( transitionBuilder: (
Widget child, Widget child,
Animation<double> animation, Animation<double> animation,
@ -104,13 +122,16 @@ class _HomePageState extends State<HomePage> {
.toList(), .toList(),
onDestinationSelected: (int index) async { onDestinationSelected: (int index) async {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
await switchToPage(index); switchToPage(index);
}, },
selectedIndex: selectedIndex:
selectedIndexHistory.isEmpty ? 0 : selectedIndexHistory.last, selectedIndexHistory.isEmpty ? 0 : selectedIndexHistory.last,
), ),
), ),
onWillPop: () async { onWillPop: () async {
setIsReversing(selectedIndexHistory.length >= 2
? selectedIndexHistory.reversed.toList()[1]
: 0);
if (selectedIndexHistory.isNotEmpty) { if (selectedIndexHistory.isNotEmpty) {
setState(() { setState(() {
selectedIndexHistory.removeLast(); selectedIndexHistory.removeLast();

View File

@ -28,8 +28,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SourceProvider sourceProvider = SourceProvider(); SourceProvider sourceProvider = SourceProvider();
var appsProvider = context.read<AppsProvider>(); var appsProvider = context.watch<AppsProvider>();
var settingsProvider = context.read<SettingsProvider>(); var settingsProvider = context.watch<SettingsProvider>();
var outlineButtonStyle = ButtonStyle( var outlineButtonStyle = ButtonStyle(
shape: MaterialStateProperty.all( shape: MaterialStateProperty.all(
@ -102,10 +102,16 @@ class _ImportExportPageState extends State<ImportExportPage> {
}); });
} }
runObtainiumExport() { runObtainiumExport() async {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
appsProvider.exportApps().then((String path) { appsProvider
showError(tr('exportedTo', args: [path]), context); .exportApps(
pickOnly: (await settingsProvider.getExportDir()) == null,
sp: settingsProvider)
.then((String? result) {
if (result != null) {
showError(tr('exportedTo', args: [result]), context);
}
}).catchError((e) { }).catchError((e) {
showError(e, context); showError(e, context);
}); });
@ -182,7 +188,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
[ [
GeneratedFormTextField('searchQuery', GeneratedFormTextField('searchQuery',
label: tr('searchQuery')) label: tr('searchQuery'))
] ],
...source.searchQuerySettingFormItems.map((e) => [e])
], ],
); );
}); });
@ -191,8 +198,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
setState(() { setState(() {
importInProgress = true; importInProgress = true;
}); });
var urlsWithDescriptions = var urlsWithDescriptions = await source
await source.search(values['searchQuery'] as String); .search(values['searchQuery'] as String, querySettings: values);
if (urlsWithDescriptions.isNotEmpty) { if (urlsWithDescriptions.isNotEmpty) {
var selectedUrls = var selectedUrls =
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
@ -300,27 +307,68 @@ class _ImportExportPageState extends State<ImportExportPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Row( FutureBuilder(
children: [ future: settingsProvider.getExportDir(),
Expanded( builder: (context, snapshot) {
child: TextButton( return Column(
style: outlineButtonStyle, children: [
onPressed: appsProvider.apps.isEmpty || Row(
importInProgress children: [
? null Expanded(
: runObtainiumExport, child: TextButton(
child: Text(tr('obtainiumExport')))), style: outlineButtonStyle,
const SizedBox( onPressed: appsProvider.apps.isEmpty ||
width: 16, importInProgress
), ? null
Expanded( : runObtainiumExport,
child: TextButton( child: Text(tr(snapshot.data != null
style: outlineButtonStyle, ? 'obtainiumExport'
onPressed: importInProgress : 'pickExportDir')),
? null )),
: runObtainiumImport, const SizedBox(
child: Text(tr('obtainiumImport')))) width: 16,
], ),
Expanded(
child: TextButton(
style: outlineButtonStyle,
onPressed: importInProgress
? null
: runObtainiumImport,
child: Text(tr('obtainiumImport'))))
],
),
if (snapshot.data != null)
Column(
children: [
const SizedBox(height: 16),
GeneratedForm(
items: [
[
GeneratedFormSwitch(
'autoExportOnChanges',
label: tr('autoExportOnChanges'),
defaultValue: settingsProvider
.autoExportOnChanges,
)
]
],
onValueChanges:
(value, valid, isBuilding) {
if (valid && !isBuilding) {
if (value['autoExportOnChanges'] !=
null) {
settingsProvider
.autoExportOnChanges = value[
'autoExportOnChanges'] ==
true;
}
}
}),
],
),
],
);
},
), ),
if (importInProgress) if (importInProgress)
const Column( const Column(
@ -398,7 +446,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
fontStyle: FontStyle.italic, fontSize: 12)), fontStyle: FontStyle.italic, fontSize: 12)),
const SizedBox( const SizedBox(
height: 8, height: 8,
) ),
], ],
))) )))
])); ]));

View File

@ -1,5 +1,5 @@
import 'dart:math'; import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:obtainium/components/custom_app_bar.dart'; import 'package:obtainium/components/custom_app_bar.dart';
@ -21,21 +21,6 @@ class SettingsPage extends StatefulWidget {
State<SettingsPage> createState() => _SettingsPageState(); State<SettingsPage> createState() => _SettingsPageState();
} }
// Generates a random light color
// Courtesy of ChatGPT 😭 (with a bugfix 🥳)
Color generateRandomLightColor() {
// Create a random number generator
final Random random = Random();
// Generate random hue, saturation, and value values
final double hue = random.nextDouble() * 360;
final double saturation = 0.5 + random.nextDouble() * 0.5;
final double value = 0.9 + random.nextDouble() * 0.1;
// Create a HSV color with the random values
return HSVColor.fromAHSV(1.0, hue, saturation, value).toColor();
}
class _SettingsPageState extends State<SettingsPage> { class _SettingsPageState extends State<SettingsPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -183,9 +168,9 @@ class _SettingsPageState extends State<SettingsPage> {
}); });
var sourceSpecificFields = sourceProvider.sources.map((e) { var sourceSpecificFields = sourceProvider.sources.map((e) {
if (e.additionalSourceSpecificSettingFormItems.isNotEmpty) { if (e.sourceConfigSettingFormItems.isNotEmpty) {
return GeneratedForm( return GeneratedForm(
items: e.additionalSourceSpecificSettingFormItems.map((e) { items: e.sourceConfigSettingFormItems.map((e) {
e.defaultValue = settingsProvider.getSettingString(e.key); e.defaultValue = settingsProvider.getSettingString(e.key);
return [e]; return [e];
}).toList(), }).toList(),
@ -201,6 +186,10 @@ class _SettingsPageState extends State<SettingsPage> {
} }
}); });
const height8 = SizedBox(
height: 8,
);
const height16 = SizedBox( const height16 = SizedBox(
height: 16, height: 16,
); );
@ -228,6 +217,72 @@ class _SettingsPageState extends State<SettingsPage> {
color: Theme.of(context).colorScheme.primary), color: Theme.of(context).colorScheme.primary),
), ),
intervalDropdown, intervalDropdown,
FutureBuilder(
builder: (ctx, val) {
return (val.data?.version.sdkInt ?? 0) >= 30
? Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
height16,
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Flexible(
child: Text(tr(
'enableBackgroundUpdates'))),
Switch(
value: settingsProvider
.enableBackgroundUpdates,
onChanged: (value) {
settingsProvider
.enableBackgroundUpdates =
value;
})
],
),
height8,
Text(tr('backgroundUpdateReqsExplanation'),
style: Theme.of(context)
.textTheme
.labelSmall),
Text(tr('backgroundUpdateLimitsExplanation'),
style: Theme.of(context)
.textTheme
.labelSmall),
height8,
if (settingsProvider
.enableBackgroundUpdates)
Column(
children: [
height16,
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Flexible(
child: Text(tr(
'bgUpdatesOnWiFiOnly'))),
Switch(
value: settingsProvider
.bgUpdatesOnWiFiOnly,
onChanged: (value) {
settingsProvider
.bgUpdatesOnWiFiOnly =
value;
})
],
),
],
),
],
)
: const SizedBox.shrink();
},
future: DeviceInfoPlugin().androidInfo),
height16, height16,
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -240,6 +295,21 @@ class _SettingsPageState extends State<SettingsPage> {
}) })
], ],
), ),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(tr('checkUpdateOnDetailPage'))),
Switch(
value: settingsProvider
.checkUpdateOnDetailPage,
onChanged: (value) {
settingsProvider.checkUpdateOnDetailPage =
value;
})
],
),
height32, height32,
Text( Text(
tr('sourceSpecific'), tr('sourceSpecific'),
@ -322,6 +392,22 @@ class _SettingsPageState extends State<SettingsPage> {
], ],
), ),
height16, height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child:
Text(tr('removeOnExternalUninstall'))),
Switch(
value: settingsProvider
.removeOnExternalUninstall,
onChanged: (value) {
settingsProvider
.removeOnExternalUninstall = value;
})
],
),
height16,
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -365,6 +451,54 @@ class _SettingsPageState extends State<SettingsPage> {
}) })
], ],
), ),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(tr('disablePageTransitions'))),
Switch(
value:
settingsProvider.disablePageTransitions,
onChanged: (value) {
settingsProvider.disablePageTransitions =
value;
})
],
),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(tr('reversePageTransitions'))),
Switch(
value:
settingsProvider.reversePageTransitions,
onChanged: settingsProvider
.disablePageTransitions
? null
: (value) {
settingsProvider
.reversePageTransitions = value;
})
],
),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(tr('highlightTouchTargets'))),
Switch(
value:
settingsProvider.highlightTouchTargets,
onChanged: (value) {
settingsProvider.highlightTouchTargets =
value;
})
],
),
height32, height32,
Text( Text(
tr('categories'), tr('categories'),
@ -415,7 +549,44 @@ class _SettingsPageState extends State<SettingsPage> {
label: Text(tr('appLogs'))), label: Text(tr('appLogs'))),
], ],
), ),
height16, const Divider(
height: 32,
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Column(children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Flexible(child: Text('Debug Menu')),
Switch(
value: settingsProvider.showDebugOpts,
onChanged: (value) {
settingsProvider.showDebugOpts = value;
})
],
),
if (settingsProvider.showDebugOpts)
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
height16,
TextButton(
onPressed: () {
AndroidAlarmManager.oneShot(
const Duration(seconds: 0),
bgUpdateCheckAlarmId + 200,
bgUpdateCheck);
showError(
'Background task started - check logs.',
context);
},
child:
const Text('Run Background Update Check Now'))
],
),
]),
),
], ],
), ),
) )

File diff suppressed because it is too large Load Diff

View File

@ -22,9 +22,9 @@ class ObtainiumNotification {
} }
class UpdateNotification extends ObtainiumNotification { class UpdateNotification extends ObtainiumNotification {
UpdateNotification(List<App> updates) UpdateNotification(List<App> updates, {int? id})
: super( : super(
2, id ?? 2,
tr('updatesAvailable'), tr('updatesAvailable'),
'', '',
'UPDATES_AVAILABLE', 'UPDATES_AVAILABLE',
@ -41,8 +41,8 @@ class UpdateNotification extends ObtainiumNotification {
} }
class SilentUpdateNotification extends ObtainiumNotification { class SilentUpdateNotification extends ObtainiumNotification {
SilentUpdateNotification(List<App> updates) SilentUpdateNotification(List<App> updates, {int? id})
: super(3, tr('appsUpdated'), '', 'APPS_UPDATED', tr('appsUpdated'), : super(id ?? 3, tr('appsUpdated'), '', 'APPS_UPDATED', tr('appsUpdated'),
tr('appsUpdatedNotifDescription'), Importance.defaultImportance) { tr('appsUpdatedNotifDescription'), Importance.defaultImportance) {
message = updates.length == 1 message = updates.length == 1
? tr('xWasUpdatedToY', ? tr('xWasUpdatedToY',
@ -52,10 +52,28 @@ class SilentUpdateNotification extends ObtainiumNotification {
} }
} }
class ErrorCheckingUpdatesNotification extends ObtainiumNotification { class SilentUpdateAttemptNotification extends ObtainiumNotification {
ErrorCheckingUpdatesNotification(String error) SilentUpdateAttemptNotification(List<App> updates, {int? id})
: super( : super(
5, id ?? 3,
tr('appsPossiblyUpdated'),
'',
'APPS_POSSIBLY_UPDATED',
tr('appsPossiblyUpdated'),
tr('appsPossiblyUpdatedNotifDescription'),
Importance.defaultImportance) {
message = updates.length == 1
? tr('xWasPossiblyUpdatedToY',
args: [updates[0].finalName, updates[0].latestVersion])
: plural('xAndNMoreUpdatesPossiblyInstalled', updates.length - 1,
args: [updates[0].finalName, (updates.length - 1).toString()]);
}
}
class ErrorCheckingUpdatesNotification extends ObtainiumNotification {
ErrorCheckingUpdatesNotification(String error, {int? id})
: super(
id ?? 5,
tr('errorCheckingUpdates'), tr('errorCheckingUpdates'),
error, error,
'BG_UPDATE_CHECK_ERROR', 'BG_UPDATE_CHECK_ERROR',
@ -99,14 +117,17 @@ final completeInstallationNotification = ObtainiumNotification(
tr('completeAppInstallationNotifDescription'), tr('completeAppInstallationNotifDescription'),
Importance.max); Importance.max);
final checkingUpdatesNotification = ObtainiumNotification( class CheckingUpdatesNotification extends ObtainiumNotification {
4, CheckingUpdatesNotification(String appName)
tr('checkingForUpdates'), : super(
'', 4,
'BG_UPDATE_CHECK', tr('checkingForUpdates'),
tr('checkingForUpdates'), appName,
tr('checkingForUpdatesNotifDescription'), 'BG_UPDATE_CHECK',
Importance.min); tr('checkingForUpdates'),
tr('checkingForUpdatesNotifDescription'),
Importance.min);
}
class NotificationsProvider { class NotificationsProvider {
FlutterLocalNotificationsPlugin notifications = FlutterLocalNotificationsPlugin notifications =

View File

@ -9,8 +9,10 @@ import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/main.dart'; import 'package:obtainium/main.dart';
import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:shared_storage/shared_storage.dart' as saf;
String obtainiumTempId = 'imranr98_obtainium_${GitHub().host}'; String obtainiumTempId = 'imranr98_obtainium_${GitHub().host}';
String obtainiumId = 'dev.imranr.obtainium'; String obtainiumId = 'dev.imranr.obtainium';
@ -35,6 +37,7 @@ List<int> updateIntervals = [15, 30, 60, 120, 180, 360, 720, 1440, 4320, 0]
class SettingsProvider with ChangeNotifier { class SettingsProvider with ChangeNotifier {
SharedPreferences? prefs; SharedPreferences? prefs;
String? defaultAppDir;
bool justStarted = true; bool justStarted = true;
String sourceUrl = 'https://github.com/ImranR98/Obtainium'; String sourceUrl = 'https://github.com/ImranR98/Obtainium';
@ -42,6 +45,7 @@ class SettingsProvider with ChangeNotifier {
// Not done in constructor as we want to be able to await it // Not done in constructor as we want to be able to await it
Future<void> initializeSettings() async { Future<void> initializeSettings() async {
prefs = await SharedPreferences.getInstance(); prefs = await SharedPreferences.getInstance();
defaultAppDir = (await getExternalStorageDirectory())!.path;
notifyListeners(); notifyListeners();
} }
@ -273,4 +277,133 @@ class SettingsProvider with ChangeNotifier {
context.deleteSaveLocale(); context.deleteSaveLocale();
} }
} }
bool get removeOnExternalUninstall {
return prefs?.getBool('removeOnExternalUninstall') ?? false;
}
set removeOnExternalUninstall(bool show) {
prefs?.setBool('removeOnExternalUninstall', show);
notifyListeners();
}
bool get checkUpdateOnDetailPage {
return prefs?.getBool('checkUpdateOnDetailPage') ?? true;
}
set checkUpdateOnDetailPage(bool show) {
prefs?.setBool('checkUpdateOnDetailPage', show);
notifyListeners();
}
bool get disablePageTransitions {
return prefs?.getBool('disablePageTransitions') ?? false;
}
set disablePageTransitions(bool show) {
prefs?.setBool('disablePageTransitions', show);
notifyListeners();
}
bool get reversePageTransitions {
return prefs?.getBool('reversePageTransitions') ?? false;
}
set reversePageTransitions(bool show) {
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 lastBGCheckTime {
int? temp = prefs?.getInt('lastBGCheckTime');
return temp != null
? DateTime.fromMillisecondsSinceEpoch(temp)
: DateTime.fromMillisecondsSinceEpoch(0);
}
set lastBGCheckTime(DateTime val) {
prefs?.setInt('lastBGCheckTime', 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();
}
} }

View File

@ -9,11 +9,13 @@ import 'package:html/dom.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:obtainium/app_sources/apkmirror.dart'; import 'package:obtainium/app_sources/apkmirror.dart';
import 'package:obtainium/app_sources/apkpure.dart'; import 'package:obtainium/app_sources/apkpure.dart';
import 'package:obtainium/app_sources/aptoide.dart';
import 'package:obtainium/app_sources/codeberg.dart'; import 'package:obtainium/app_sources/codeberg.dart';
import 'package:obtainium/app_sources/fdroid.dart'; import 'package:obtainium/app_sources/fdroid.dart';
import 'package:obtainium/app_sources/fdroidrepo.dart'; import 'package:obtainium/app_sources/fdroidrepo.dart';
import 'package:obtainium/app_sources/github.dart'; import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/app_sources/gitlab.dart'; import 'package:obtainium/app_sources/gitlab.dart';
import 'package:obtainium/app_sources/huaweiappgallery.dart';
import 'package:obtainium/app_sources/izzyondroid.dart'; import 'package:obtainium/app_sources/izzyondroid.dart';
import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/app_sources/jenkins.dart'; import 'package:obtainium/app_sources/jenkins.dart';
@ -24,10 +26,12 @@ import 'package:obtainium/app_sources/sourceforge.dart';
import 'package:obtainium/app_sources/sourcehut.dart'; import 'package:obtainium/app_sources/sourcehut.dart';
import 'package:obtainium/app_sources/steammobile.dart'; import 'package:obtainium/app_sources/steammobile.dart';
import 'package:obtainium/app_sources/telegramapp.dart'; import 'package:obtainium/app_sources/telegramapp.dart';
import 'package:obtainium/app_sources/uptodown.dart';
import 'package:obtainium/app_sources/vlc.dart'; import 'package:obtainium/app_sources/vlc.dart';
import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/mass_app_sources/githubstars.dart'; import 'package:obtainium/mass_app_sources/githubstars.dart';
import 'package:obtainium/providers/settings_provider.dart';
class AppNames { class AppNames {
late String author; late String author;
@ -323,21 +327,29 @@ abstract class AppSource {
bool enforceTrackOnly = false; bool enforceTrackOnly = false;
bool changeLogIfAnyIsMarkDown = true; bool changeLogIfAnyIsMarkDown = true;
bool appIdInferIsOptional = false; bool appIdInferIsOptional = false;
bool allowSubDomains = false;
AppSource() { AppSource() {
name = runtimeType.toString(); name = runtimeType.toString();
} }
overrideVersionDetectionFormDefault(String vd, bool disableStandard) { overrideVersionDetectionFormDefault(String vd,
{bool disableStandard = false, bool disableRelDate = false}) {
additionalAppSpecificSourceAgnosticSettingFormItems = additionalAppSpecificSourceAgnosticSettingFormItems =
additionalAppSpecificSourceAgnosticSettingFormItems.map((e) { additionalAppSpecificSourceAgnosticSettingFormItems.map((e) {
return e.map((e2) { return e.map((e2) {
if (e2.key == 'versionDetection') { if (e2.key == 'versionDetection') {
var item = e2 as GeneratedFormDropdown; var item = e2 as GeneratedFormDropdown;
item.defaultValue = vd; item.defaultValue = vd;
item.disabledOptKeys = [];
if (disableStandard) { if (disableStandard) {
item.disabledOptKeys = ['standardVersionDetection']; item.disabledOptKeys?.add('standardVersionDetection');
} }
if (disableRelDate) {
item.disabledOptKeys?.add('releaseDateAsVersion');
}
item.disabledOptKeys =
item.disabledOptKeys?.where((element) => element != vd).toList();
} }
return e2; return e2;
}).toList(); }).toList();
@ -352,12 +364,24 @@ abstract class AppSource {
return url; return url;
} }
Map<String, String>? get requestHeaders => null; Future<Map<String, String>?> getRequestHeaders(
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
bool forAPKDownload = false}) async {
return null;
}
Future<Response> sourceRequest(String url) async { Future<Response> sourceRequest(String url,
if (requestHeaders != null) { {bool followRedirects = true,
Map<String, dynamic> additionalSettings =
const <String, dynamic>{}}) async {
var requestHeaders =
await getRequestHeaders(additionalSettings: additionalSettings);
if (requestHeaders != null || followRedirects == false) {
var req = Request('GET', Uri.parse(url)); var req = Request('GET', Uri.parse(url));
req.headers.addAll(requestHeaders!); req.followRedirects = followRedirects;
if (requestHeaders != null) {
req.headers.addAll(requestHeaders);
}
return Response.fromStream(await Client().send(req)); return Response.fromStream(await Client().send(req));
} else { } else {
return get(Uri.parse(url)); return get(Uri.parse(url));
@ -412,7 +436,11 @@ abstract class AppSource {
GeneratedFormSwitch('autoApkFilterByArch', GeneratedFormSwitch('autoApkFilterByArch',
label: tr('autoApkFilterByArch'), defaultValue: true) label: tr('autoApkFilterByArch'), defaultValue: true)
], ],
[GeneratedFormTextField('appName', label: tr('appName'), required: false)] [GeneratedFormTextField('appName', label: tr('appName'), required: false)],
[
GeneratedFormSwitch('exemptFromBackgroundUpdates',
label: tr('exemptFromBackgroundUpdates'))
]
]; ];
// Previous 2 variables combined into one at runtime for convenient usage // Previous 2 variables combined into one at runtime for convenient usage
@ -424,19 +452,40 @@ abstract class AppSource {
} }
// Some Sources may have additional settings at the Source level (not specific to Apps) - these use SettingsProvider // Some Sources may have additional settings at the Source level (not specific to Apps) - these use SettingsProvider
List<GeneratedFormItem> additionalSourceSpecificSettingFormItems = []; // If the source has been overridden, we expect the user to define one-time values as additional settings - don't use the stored values
List<GeneratedFormItem> sourceConfigSettingFormItems = [];
Future<Map<String, String>> getSourceConfigValues(
Map<String, dynamic> additionalSettings,
SettingsProvider settingsProvider) async {
Map<String, String> results = {};
for (var e in sourceConfigSettingFormItems) {
var val = hostChanged
? additionalSettings[e.key]
: settingsProvider.getSettingString(e.key);
if (val != null) {
results[e.key] = val;
}
}
return results;
}
String? changeLogPageFromStandardUrl(String standardUrl) { String? changeLogPageFromStandardUrl(String standardUrl) {
return null; return null;
} }
Future<String?> getSourceNote() async {
return null;
}
Future<String> apkUrlPrefetchModifier( Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async { String apkUrl, String standardUrl) async {
return apkUrl; return apkUrl;
} }
bool canSearch = false; bool canSearch = false;
Future<Map<String, List<String>>> search(String query) { List<GeneratedFormItem> searchQuerySettingFormItems = [];
Future<Map<String, List<String>>> search(String query,
{Map<String, dynamic> querySettings = const {}}) {
throw NotImplementedError(); throw NotImplementedError();
} }
@ -447,8 +496,11 @@ abstract class AppSource {
} }
ObtainiumError getObtainiumHttpError(Response res) { ObtainiumError getObtainiumHttpError(Response res) {
return ObtainiumError(res.reasonPhrase ?? return ObtainiumError((res.reasonPhrase != null &&
tr('errorWithHttpStatusCode', args: [res.statusCode.toString()])); res.reasonPhrase != null &&
res.reasonPhrase!.isNotEmpty)
? res.reasonPhrase!
: tr('errorWithHttpStatusCode', args: [res.statusCode.toString()]));
} }
abstract class MassAppUrlSource { abstract class MassAppUrlSource {
@ -469,6 +521,20 @@ regExValidator(String? value) {
return null; return null;
} }
intValidator(String? value, {bool positive = false}) {
if (value == null) {
return tr('invalidInput');
}
var num = int.tryParse(value);
if (num == null) {
return tr('invalidInput');
}
if (positive && num <= 0) {
return tr('invalidInput');
}
return null;
}
class SourceProvider { class SourceProvider {
// Add more source classes here so they are available via the service // Add more source classes here so they are available via the service
List<AppSource> get sources => [ List<AppSource> get sources => [
@ -476,13 +542,16 @@ class SourceProvider {
GitLab(), GitLab(),
Codeberg(), Codeberg(),
FDroid(), FDroid(),
IzzyOnDroid(),
FDroidRepo(), FDroidRepo(),
Jenkins(), IzzyOnDroid(),
SourceForge(), SourceForge(),
SourceHut(), SourceHut(),
APKMirror(),
APKPure(), APKPure(),
Aptoide(),
Uptodown(),
APKMirror(),
HuaweiAppGallery(),
Jenkins(),
// APKCombo(), // Can't get past their scraping blocking yet (get 403 Forbidden) // APKCombo(), // Can't get past their scraping blocking yet (get 403 Forbidden)
Mullvad(), Mullvad(),
Signal(), Signal(),
@ -512,7 +581,9 @@ class SourceProvider {
} }
AppSource? source; AppSource? source;
for (var s in sources.where((element) => element.host != null)) { for (var s in sources.where((element) => element.host != null)) {
if (RegExp('://${s.host}(/|\\z)?').hasMatch(url)) { if (RegExp(
'://${s.allowSubDomains ? '([^\\.]+\\.)*' : ''}${s.host}(/|\\z)?')
.hasMatch(url)) {
source = s; source = s;
break; break;
} }
@ -594,9 +665,7 @@ class SourceProvider {
} }
String apkVersion = apk.version.replaceAll('/', '-'); String apkVersion = apk.version.replaceAll('/', '-');
var name = currentApp != null ? currentApp.name.trim() : ''; var name = currentApp != null ? currentApp.name.trim() : '';
name = name.isNotEmpty name = name.isNotEmpty ? name : apk.names.name;
? name
: apk.names.name[0].toUpperCase() + apk.names.name.substring(1);
return App( return App(
currentApp?.id ?? currentApp?.id ??
((!source.appIdInferIsOptional || ((!source.appIdInferIsOptional ||
@ -606,7 +675,7 @@ class SourceProvider {
: null) ?? : null) ??
generateTempID(standardUrl, additionalSettings), generateTempID(standardUrl, additionalSettings),
standardUrl, standardUrl,
apk.names.author[0].toUpperCase() + apk.names.author.substring(1), apk.names.author,
name, name,
currentApp?.installedVersion, currentApp?.installedVersion,
apkVersion, apkVersion,

View File

@ -5,43 +5,51 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: android_alarm_manager_plus name: android_alarm_manager_plus
sha256: "80f963d47cb7ab0818144c7b0668aea4c038f9cb8626626e89a4ea77375defb7" sha256: c20d91a9096596f66274bf8172321c278f9cba8091638f80205fe66d31587fa5
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
android_intent_plus: android_intent_plus:
dependency: "direct main" dependency: "direct main"
description: description:
name: android_intent_plus name: android_intent_plus
sha256: "2c87d8330ba5deef5fe20e77f4d178190b3b24531dce08368030ab4be40a9d4e" sha256: f72ae20bb37108694f442e7ae6acbd28b453ca62ce86842f6787b784355abfe6
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.1" version: "4.0.2"
android_package_installer: android_package_installer:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: f09c79eee5be3c60b04760143eb954a13fdd07f1 resolved-ref: ba2aa7a11edc2649d1d80c25ed9291521262f714
url: "https://github.com/ImranR98/android_package_installer" url: "https://github.com/ImranR98/android_package_installer"
source: git source: git
version: "0.0.1" version: "0.0.1"
android_package_manager:
dependency: "direct main"
description:
name: android_package_manager
sha256: b873fe5856f7c442aca9751dac05d117285be9e4de08eb15d1ffb811fd1b688d
url: "https://pub.dev"
source: hosted
version: "0.6.0"
animations: animations:
dependency: "direct main" dependency: "direct main"
description: description:
name: animations name: animations
sha256: fe8a6bdca435f718bb1dc8a11661b2c22504c6da40ef934cee8327ed77934164 sha256: ef57563eed3620bd5d75ad96189846aca1e033c0c45fc9a7d26e80ab02b88a70
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.7" version: "2.0.8"
archive: archive:
dependency: "direct main" dependency: transitive
description: description:
name: archive name: archive
sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" sha256: "49b1fad315e57ab0bbc15bcbb874e83116a1d78f77ebd500a4af6c9407d6b28e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.7" version: "3.3.8"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -102,10 +110,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.1" version: "1.17.2"
connectivity_plus:
dependency: "direct main"
description:
name: connectivity_plus
sha256: "77a180d6938f78ca7d2382d2240eb626c0f6a735d0bfdce227d8ffb80f95c48b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
url: "https://pub.dev"
source: hosted
version: "1.2.4"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -118,10 +142,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: cross_file name: cross_file
sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" sha256: fd832b5384d0d6da4f6df60b854d33accaaeb63aa9e10e736a87381f08dee2cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.3+4" version: "0.3.3+5"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@ -142,10 +166,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: cupertino_icons name: cupertino_icons
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.5" version: "1.0.6"
dbus: dbus:
dependency: transitive dependency: transitive
description: description:
@ -158,10 +182,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: device_info_plus name: device_info_plus
sha256: "2c35b6d1682b028e42d07b3aee4b98fa62996c10bc12cb651ec856a80d6a761b" sha256: "86add5ef97215562d2e090535b0a16f197902b10c369c558a100e74ea06e8659"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.0.2" version: "9.0.3"
device_info_plus_platform_interface: device_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -174,18 +198,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dynamic_color name: dynamic_color
sha256: "74dff1435a695887ca64899b8990004f8d1232b0e84bfc4faa1fdda7c6f57cc1" sha256: de4798a7069121aee12d5895315680258415de9b00e717723a1bd73d58f0126d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.6.5" version: "1.6.6"
easy_localization: easy_localization:
dependency: "direct main" dependency: "direct main"
description: description:
name: easy_localization name: easy_localization
sha256: "30ebf25448ffe169e0bd9bc4b5da94faa8398967a2ad2ca09f438be8b6953645" sha256: de63e3b422adfc97f256cbb3f8cf12739b6a4993d390f3cadb3f51837afaefe5
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "3.0.3"
easy_logger: easy_logger:
dependency: transitive dependency: transitive
description: description:
@ -206,10 +230,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2" version: "2.1.0"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -222,23 +246,31 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: file_picker name: file_picker
sha256: b1729fc96627dd44012d0a901558177418818d6bd428df59dcfeb594e5f66432 sha256: be325344c1f3070354a1d84a231a1ba75ea85d413774ec4bdf444c023342e030
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.3.2" version: "5.5.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_archive:
dependency: "direct main"
description:
name: flutter_archive
sha256: aec85d1da65e5b33a529db00a86df0b8e92bda78088a7cfaeeba5187701d0d85
url: "https://pub.dev"
source: hosted
version: "5.0.0"
flutter_fgbg: flutter_fgbg:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_fgbg name: flutter_fgbg
sha256: d37511eef6afb7e2e3f2278ec6498bb12c650ed517c81bcd09452d910e8b9174 sha256: "08c4d2fd229e3df26083d5aecc3dea9ff4f2d188f8cd57aaf2b3f047bd08a047"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.2" version: "0.3.0"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -251,18 +283,18 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.0.3"
flutter_local_notifications: flutter_local_notifications:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_local_notifications name: flutter_local_notifications
sha256: "812791d43ccfc1b443a0d39fa02a206fc228c597e28ff9337e09e3ca8d370391" sha256: "3002092e5b8ce2f86c3361422e52e6db6776c23ee21e0b2f71b892bf4259ef04"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.1.1" version: "15.1.1"
flutter_local_notifications_linux: flutter_local_notifications_linux:
dependency: transitive dependency: transitive
description: description:
@ -288,18 +320,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_markdown name: flutter_markdown
sha256: dc6d5258653f6857135b32896ccda7f7af0c54dcec832495ad6835154c6c77c0 sha256: d4a1cb250c4e059586af0235f32e02882860a508e189b61f2b31b8810c1e1330
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.15" version: "0.6.17+2"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" sha256: f185ac890306b5779ecbd611f52502d8d4d63d27703ef73161ca0407e815f02c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.15" version: "2.0.16"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -318,6 +350,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.2.2" version: "8.2.2"
hsluv:
dependency: "direct main"
description:
name: hsluv
sha256: f33e63b0c24ceee0f6492874424aa8edc671ef9a20cc889e4b969284d8f02eb1
url: "https://pub.dev"
source: hosted
version: "1.1.3"
html: html:
dependency: "direct main" dependency: "direct main"
description: description:
@ -330,10 +370,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3" sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.1.0"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -350,22 +390,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.17" 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"
intl: intl:
dependency: transitive dependency: transitive
description: description:
name: intl name: intl
sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.18.0" version: "0.18.1"
js: js:
dependency: transitive dependency: transitive
description: description:
@ -394,26 +426,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: markdown name: markdown
sha256: "8e332924094383133cee218b676871f42db2514f1f6ac617b6cf6152a7faab8e" sha256: acf35edccc0463a9d7384e437c015a3535772e09714cf60e07eeef3a15870dcd
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.1.0" version: "7.1.1"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.15" version: "0.12.16"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0" version: "0.5.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -438,22 +470,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
package_archive_info: nm:
dependency: "direct main"
description:
name: package_archive_info
sha256: "8f671a29b79d15f192e5e2b0dab9d0bad66b9ee93fb58d4e0afdb62f91a259be"
url: "https://pub.dev"
source: hosted
version: "0.1.0"
package_info:
dependency: transitive dependency: transitive
description: description:
name: package_info name: nm
sha256: "6c07d9d82c69e16afeeeeb6866fe43985a20b3b50df243091bfc4a4ad2b03b75" sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2" version: "0.5.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -466,90 +490,90 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.15" version: "2.1.1"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" sha256: "6b8b19bd80da4f11ce91b2d1fb931f3006911477cec227cce23d3253d80df3f1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.27" version: "2.2.0"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
name: path_provider_foundation name: path_provider_foundation
sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.3" version: "2.3.1"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
name: path_provider_linux name: path_provider_linux
sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.11" version: "2.2.1"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: path_provider_platform_interface name: path_provider_platform_interface
sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.6" version: "2.1.1"
path_provider_windows: path_provider_windows:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.7" version: "2.2.1"
permission_handler: permission_handler:
dependency: "direct main" dependency: "direct main"
description: description:
name: permission_handler name: permission_handler
sha256: "1b6b3e73f0bcbc856548bbdfb1c33084a401c4f143e220629a9055233d76c331" sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.3.0" version: "10.4.5"
permission_handler_android: permission_handler_android:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_android name: permission_handler_android
sha256: "8f6a95ccbca13766882f95d32684d7c9bfe6c45650c32bedba948ef1c6a4ddf7" sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.2.3" version: "10.3.6"
permission_handler_apple: permission_handler_apple:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_apple name: permission_handler_apple
sha256: "08dcb6ce628ac0b257e429944b4c652c2a4e6af725bdf12b498daa2c6b2b1edb" sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.1.0" version: "9.1.4"
permission_handler_platform_interface: permission_handler_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_platform_interface name: permission_handler_platform_interface
sha256: de20a5c3269229c1ae2e5a6b822f6cb59578b23e8255c93fbeebfc82116e6b11 sha256: f2343e9fa9c22ae4fd92d4732755bfe452214e7189afcc097380950cf567b4b2
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.10.0" version: "3.11.5"
permission_handler_windows: permission_handler_windows:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_windows name: permission_handler_windows
sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.2" version: "0.1.3"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -562,18 +586,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.1.2"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.6"
pointycastle: pointycastle:
dependency: transitive dependency: transitive
description: description:
@ -582,14 +606,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.7.3" version: "3.7.3"
process:
dependency: transitive
description:
name: process
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
url: "https://pub.dev"
source: hosted
version: "4.2.4"
provider: provider:
dependency: "direct main" dependency: "direct main"
description: description:
@ -602,74 +618,82 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: share_plus name: share_plus
sha256: ed3fcea4f789ed95913328e629c0c53e69e80e08b6c24542f1b3576046c614e8 sha256: "6cec740fa0943a826951223e76218df002804adb588235a8910dc3d6b0654e11"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.2" version: "7.1.0"
share_plus_platform_interface: share_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: share_plus_platform_interface name: share_plus_platform_interface
sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981" sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.1" version: "3.3.0"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: "396f85b8afc6865182610c0a2fc470853d56499f75f7499e2a73a9f0539d23d0" sha256: b7f41bad7e521d205998772545de63ff4e6c97714775902c199353f8bf1511ac
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.2.1"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749" sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.2.1"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.3.4"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_linux name: shared_preferences_linux
sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa" sha256: c2eb5bf57a2fe9ad6988121609e47d3e07bb3bdca5b6f8444e4cf302428a128a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.3.1"
shared_preferences_platform_interface: shared_preferences_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_platform_interface name: shared_preferences_platform_interface
sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.3.1"
shared_preferences_web: shared_preferences_web:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5" sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.2.1"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_windows name: shared_preferences_windows
sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173" sha256: f763a101313bd3be87edffe0560037500967de9c394a714cd598d945517f694f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.3.1"
shared_storage:
dependency: "direct main"
description:
name: shared_storage
sha256: "7c65a9d64f0f5521256be974cfd74010af12196657cec9f9fb7b03b2f11bcaf6"
url: "https://pub.dev"
source: hosted
version: "0.8.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -679,26 +703,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.10.0"
sqflite: sqflite:
dependency: "direct main" dependency: "direct main"
description: description:
name: sqflite name: sqflite
sha256: b4d6710e1200e96845747e37338ea8a819a12b51689a3bcf31eff0003b37a0b9 sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.8+4" version: "2.3.0"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
name: sqflite_common name: sqflite_common
sha256: "8f7603f3f8f126740bc55c4ca2d1027aab4b74a1267a3e31ce51fe40e3b65b8f" sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.5+1" version: "2.5.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -743,10 +767,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.6.0"
timezone: timezone:
dependency: transitive dependency: transitive
description: description:
@ -767,66 +791,66 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.11" version: "6.1.14"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03" sha256: b04af59516ab45762b2ca6da40fa830d72d0f6045cd97744450b73493fa76330
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.36" version: "6.1.0"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" sha256: "7c65021d5dee51813d652357bc65b8dd4a6177082a9966bc8ba6ee477baa795f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.4" version: "6.1.5"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_linux name: url_launcher_linux
sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" sha256: b651aad005e0cb06a01dbd84b428a301916dc75f0e7ea6165f80057fee2d8e8e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.5" version: "3.0.6"
url_launcher_macos: url_launcher_macos:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_macos name: url_launcher_macos
sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" sha256: b55486791f666e62e0e8ff825e58a023fd6b1f71c49926483f1128d3bbd8fe88
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.5" version: "3.0.7"
url_launcher_platform_interface: url_launcher_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_platform_interface name: url_launcher_platform_interface
sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea sha256: "95465b39f83bfe95fcb9d174829d6476216f2d548b79c38ab2506e0458787618"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.5"
url_launcher_web: url_launcher_web:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab" sha256: "2942294a500b4fa0b918685aff406773ba0a4cd34b7f42198742a94083020ce5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.17" version: "2.0.20"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_windows name: url_launcher_windows
sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" sha256: "95fef3129dc7cfaba2bc3d5ba2e16063bb561fc6d78e63eee16162bc70029069"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.6" version: "3.0.8"
uuid: uuid:
dependency: transitive dependency: transitive
description: description:
@ -843,46 +867,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
web:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
webview_flutter: webview_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: webview_flutter name: webview_flutter
sha256: "789d52bd789373cc1e100fb634af2127e86c99cf9abde09499743270c5de8d00" sha256: "82f6787d5df55907aa01e49bd9644f4ed1cc82af7a8257dd9947815959d2e755"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.2" version: "4.2.4"
webview_flutter_android: webview_flutter_android:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_android name: webview_flutter_android
sha256: "532135f6f6b8030cd039f30eab23f340d650350e29f38e9b37d2eaad028f1018" sha256: "9427774649fd3c8b7ff53523051395d13aed2ca355822b822e6493d79f5fc05a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.8.0" version: "3.10.0"
webview_flutter_platform_interface: webview_flutter_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_platform_interface name: webview_flutter_platform_interface
sha256: "656e2aeaef318900fffd21468b6ddc7958c7092a642f0e7220bac328b70d4a81" sha256: "6d9213c65f1060116757a7c473247c60f3f7f332cac33dc417c9e362a9a13e4f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.6.0"
webview_flutter_wkwebview: webview_flutter_wkwebview:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_wkwebview name: webview_flutter_wkwebview
sha256: ecc9e9ea15216afc5ba3b1f14aa19414ceba526e57b19cebd970bfa91a0f4058 sha256: d2f7241849582da80b79acb03bb936422412ce5c0c79fb5f6a1de5421a5aecc4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.5.0" version: "3.7.4"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "1414f27dd781737e51afa9711f2ac2ace6ab4498ee98e20863fa5505aa00c58c" sha256: "9e82a402b7f3d518fb9c02d0e9ae45952df31b9bf34d77baf19da2de03fc2aaa"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.4" version: "5.0.7"
win32_registry: win32_registry:
dependency: transitive dependency: transitive
description: description:
@ -895,10 +927,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.0.3"
xml: xml:
dependency: transitive dependency: transitive
description: description:
@ -916,5 +948,5 @@ packages:
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.0.0 <4.0.0" dart: ">=3.1.0 <4.0.0"
flutter: ">=3.4.0-17.0.pre" flutter: ">=3.13.0"

View File

@ -1,5 +1,5 @@
name: obtainium name: obtainium
description: A new Flutter project. description: Get Android App Updates Directly From the Source.
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
@ -17,10 +17,10 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 0.13.9+173 # When changing this, update the tag in main() accordingly version: 0.14.13+205 # When changing this, update the tag in main() accordingly
environment: environment:
sdk: '>=2.18.2 <3.0.0' sdk: '>=3.0.0 <4.0.0'
# Dependencies specify other packages that your package needs in order to work. # Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions # To automatically upgrade your package dependencies to the latest versions
@ -37,8 +37,8 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.5 cupertino_icons: ^1.0.5
path_provider: ^2.0.11 path_provider: ^2.0.11
flutter_fgbg: ^0.2.0 # Try removing reliance on this flutter_fgbg: ^0.3.0 # Try removing reliance on this
flutter_local_notifications: ^14.0.0+1 flutter_local_notifications: ^15.1.0+1
provider: ^6.0.3 provider: ^6.0.3
http: ^1.0.0 http: ^1.0.0
webview_flutter: ^4.0.0 webview_flutter: ^4.0.0
@ -55,16 +55,17 @@ dependencies:
git: git:
url: https://github.com/ImranR98/android_package_installer url: https://github.com/ImranR98/android_package_installer
ref: main ref: main
android_package_manager: ^0.6.0
share_plus: ^7.0.0 share_plus: ^7.0.0
installed_apps: ^1.3.1
package_archive_info: ^0.1.0
android_alarm_manager_plus: ^3.0.0 android_alarm_manager_plus: ^3.0.0
sqflite: ^2.2.0+3 sqflite: ^2.2.0+3
easy_localization: ^3.0.1 easy_localization: ^3.0.1
android_intent_plus: ^4.0.0 android_intent_plus: ^4.0.0
flutter_markdown: ^0.6.14 flutter_markdown: ^0.6.14
archive: ^3.3.7 flutter_archive: ^5.0.0
hsluv: ^1.1.3
connectivity_plus: ^4.0.2
shared_storage: ^0.8.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: