Compare commits

...

31 Commits

Author SHA1 Message Date
Imran Remtulla
bbaa42fb01 Merge pull request #256 from ImranR98/dev
Bugfixes (#254, #252), App Uninstall and Settings Feature (#257)
2023-02-04 19:46:24 -05:00
Imran Remtulla
4fe311bc03 Update packages, increment version 2023-02-04 19:44:18 -05:00
Imran Remtulla
ea68b97ff7 Mark updated feature more clear 2023-02-04 19:30:41 -05:00
Imran Remtulla
6e0f6b528e Added App settings button 2023-02-04 19:11:28 -05:00
Imran Remtulla
a2c227931e Added uninstall option 2023-02-04 18:58:14 -05:00
Imran Remtulla
15ad3bb439 Removed unnecessary repetitive log 2023-02-04 17:21:39 -05:00
Imran Remtulla
b03d7fba1a Fix permission error on Android 10 #252 2023-02-04 17:07:59 -05:00
Imran Remtulla
31c491d7c5 Fix prev. commit. 2023-02-04 16:50:33 -05:00
Imran Remtulla
71c80f11f5 'Fix' for GlobalKey error #254 2023-02-04 12:30:34 -05:00
Imran Remtulla
eef4d33431 Merge pull request #246 from ImranR98/dev
Bugfixes for #242 and #245 + Various UI Improvements
2023-01-29 17:35:18 -05:00
Imran Remtulla
d56342e907 Merge pull request #243 from bluefly000/japanese-translation
Update Japanese translation
2023-01-29 17:32:09 -05:00
Imran Remtulla
c72c0fdb57 Increment version 2023-01-29 17:31:19 -05:00
Imran Remtulla
ffe29009ed URL select modal now works when tapping text 2023-01-29 17:29:41 -05:00
Imran Remtulla
60e3b68ebd Search allows option changes (no direct add) 2023-01-29 17:23:35 -05:00
Imran Remtulla
ee4d0f259f Generated form bugfix (initState not running) - #245 2023-01-29 17:07:11 -05:00
bluefly000
0ecfbef0a0 Update Japanese translation 2023-01-29 17:28:54 +09:00
Imran Remtulla
1b60e75ca7 Added delay after Obtainium install prompt 2023-01-28 20:59:17 -05:00
Imran Remtulla
abcfa389e8 Merge pull request #241 from ImranR98/dev
Updated screenshots
2023-01-28 00:47:26 -05:00
Imran Remtulla
a64bd67ef1 Updated screenshots 2023-01-28 00:46:54 -05:00
Imran Remtulla
4252c2711b Merge pull request #240 from ImranR98/dev
APK RegEx Filter, Increased GitHub/Codeberg Release Range, UI Tweaks
Addresses #237, #238.
2023-01-28 00:17:10 -05:00
Imran Remtulla
52913b0450 Slight UI tweak 2023-01-28 00:15:52 -05:00
Imran Remtulla
427b0ed8d2 Changed a string 2023-01-28 00:13:03 -05:00
Imran Remtulla
a85d6d4f08 Increment version, remove comment 2023-01-28 00:11:40 -05:00
Imran Remtulla
05f712603c GitHub & Codeberg - get first 100 releases (not 30) 2023-01-28 00:08:17 -05:00
Imran Remtulla
fa2a80e34c APK RegEx Filter + Updated Packages 2023-01-28 00:04:57 -05:00
Imran Remtulla
f43e5a2ff1 Merge pull request #235 from ImranR98/dev
Increment version, update packages
2023-01-22 19:55:35 -05:00
Imran Remtulla
b72aa8273e Increment version, update packages 2023-01-22 19:55:14 -05:00
Imran Remtulla
520f186e4a Merge pull request #234 from p1gp1g/themed-icon
Add themed icon for Android 13
2023-01-22 19:53:43 -05:00
sim
e1e97672cf Add themed icon for Android 13 2023-01-23 01:09:14 +01:00
Imran Remtulla
1494bcd013 Merge pull request #232 from ImranR98/dev
GitHub (and Codeberg) bugfix (#231)
2023-01-20 12:49:35 -05:00
Imran Remtulla
3457a0a12f GitHub (and Codeberg) bugfix (#231) 2023-01-20 12:48:55 -05:00
32 changed files with 1111 additions and 827 deletions

View File

@@ -31,4 +31,4 @@ Currently supported App sources:
| <img src="./assets/screenshots/1.apps.png" alt="Apps Page" /> | <img src="./assets/screenshots/2.dark_theme.png" alt="Dark Theme" /> | <img src="./assets/screenshots/3.material_you.png" alt="Material You" /> | | <img src="./assets/screenshots/1.apps.png" alt="Apps Page" /> | <img src="./assets/screenshots/2.dark_theme.png" alt="Dark Theme" /> | <img src="./assets/screenshots/3.material_you.png" alt="Material You" /> |
| ------------------------------------------------------ | ----------------------------------------------------------------------- | -------------------------------------------------------------------- | | ------------------------------------------------------ | ----------------------------------------------------------------------- | -------------------------------------------------------------------- |
| <img src="./assets/screenshots/4.app.png" alt="App Page" /> | <img src="./assets/screenshots/5.apk_picker.png" alt="Multiple APK Support" /> | <img src="./assets/screenshots/6.apk_install.png" alt="App Installation" /> | | <img src="./assets/screenshots/4.app.png" alt="App Page" /> | <img src="./assets/screenshots/5.app_opts.png" alt="App Options" /> | <img src="./assets/screenshots/6.app_webview.png" alt="App Web View" /> |

View File

@@ -3,7 +3,8 @@
<application <application
android:label="Obtainium" android:label="Obtainium"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
@@ -51,7 +52,8 @@
<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"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"/> android:maxSdkVersion="29"/>
</manifest> </manifest>

View File

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

View File

@@ -74,7 +74,6 @@
"changeX": "Ändern {}", "changeX": "Ändern {}",
"installUpdateApps": "Apps installieren/aktualisieren", "installUpdateApps": "Apps installieren/aktualisieren",
"installUpdateSelectedApps": "Ausgewählte Apps installieren/aktualisieren", "installUpdateSelectedApps": "Ausgewählte Apps installieren/aktualisieren",
"onlyWorksWithNonEVDApps": "Funktioniert nur bei Apps, deren Installationsstatus nicht automatisch erkannt werden kann (ungewöhnlich).",
"markXSelectedAppsAsUpdated": "Markiere {} ausgewählte Apps als aktuell?", "markXSelectedAppsAsUpdated": "Markiere {} ausgewählte Apps als aktuell?",
"no": "Nein", "no": "Nein",
"yes": "Ja", "yes": "Ja",
@@ -178,7 +177,6 @@
"installedVersionX": "Installierte Version: {}", "installedVersionX": "Installierte Version: {}",
"lastUpdateCheckX": "Letzte Aktualisierungsprüfung: {}", "lastUpdateCheckX": "Letzte Aktualisierungsprüfung: {}",
"remove": "Entfernen", "remove": "Entfernen",
"removeAppQuestion": "App entfernen?",
"yesMarkUpdated": "Ja, als aktualisiert markieren", "yesMarkUpdated": "Ja, als aktualisiert markieren",
"fdroid": "F-Droid", "fdroid": "F-Droid",
"appIdOrName": "App ID oder Name", "appIdOrName": "App ID oder Name",
@@ -211,6 +209,14 @@
"language": "Sprache", "language": "Sprache",
"storagePermissionDenied": "Storage permission denied", "storagePermissionDenied": "Storage permission denied",
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.", "selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.",
"removeAppQuestion": {
"one": "App entfernen?",
"other": "App entfernen?"
},
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut", "one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut",
"other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut" "other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut"

View File

@@ -74,7 +74,6 @@
"changeX": "Change {}", "changeX": "Change {}",
"installUpdateApps": "Install/Update Apps", "installUpdateApps": "Install/Update Apps",
"installUpdateSelectedApps": "Install/Update Selected Apps", "installUpdateSelectedApps": "Install/Update Selected Apps",
"onlyWorksWithNonEVDApps": "Only works for Apps whose install status cannot be automatically detected (uncommon).",
"markXSelectedAppsAsUpdated": "Mark {} Selected Apps as Updated?", "markXSelectedAppsAsUpdated": "Mark {} Selected Apps as Updated?",
"no": "No", "no": "No",
"yes": "Yes", "yes": "Yes",
@@ -178,7 +177,6 @@
"installedVersionX": "Installed Version: {}", "installedVersionX": "Installed Version: {}",
"lastUpdateCheckX": "Last Update Check: {}", "lastUpdateCheckX": "Last Update Check: {}",
"remove": "Remove", "remove": "Remove",
"removeAppQuestion": "Remove App?",
"yesMarkUpdated": "Yes, Mark as Updated", "yesMarkUpdated": "Yes, Mark as Updated",
"fdroid": "F-Droid", "fdroid": "F-Droid",
"appIdOrName": "App ID or Name", "appIdOrName": "App ID or Name",
@@ -211,6 +209,14 @@
"language": "Language", "language": "Language",
"storagePermissionDenied": "Storage permission denied", "storagePermissionDenied": "Storage permission denied",
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.", "selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.",
"removeAppQuestion": {
"one": "Remove App?",
"other": "Remove Apps?"
},
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Too many requests (rate limited) - try again in {} minute", "one": "Too many requests (rate limited) - try again in {} minute",
"other": "Too many requests (rate limited) - try again in {} minutes" "other": "Too many requests (rate limited) - try again in {} minutes"

View File

@@ -74,7 +74,6 @@
"changeX": "Változás {}", "changeX": "Változás {}",
"installUpdateApps": "Appok telepítése/frissítése", "installUpdateApps": "Appok telepítése/frissítése",
"installUpdateSelectedApps": "Telepítse/frissítse a kiválasztott appokat", "installUpdateSelectedApps": "Telepítse/frissítse a kiválasztott appokat",
"onlyWorksWithNonEVDApps": "Csak azoknál az alkalmazásoknál működik, amelyek telepítési állapota nem észlelhető autom. (nem gyakori).",
"markXSelectedAppsAsUpdated": "Megjelöl {} kiválasztott alkalmazást frissítettként?", "markXSelectedAppsAsUpdated": "Megjelöl {} kiválasztott alkalmazást frissítettként?",
"no": "Nem", "no": "Nem",
"yes": "Igen", "yes": "Igen",
@@ -178,7 +177,6 @@
"installedVersionX": "Telepített verzió: {}", "installedVersionX": "Telepített verzió: {}",
"lastUpdateCheckX": "Frissítés ellenőrizve: {}", "lastUpdateCheckX": "Frissítés ellenőrizve: {}",
"remove": "Eltávolítás", "remove": "Eltávolítás",
"removeAppQuestion": "Eltávolítja az alkalmazást?",
"yesMarkUpdated": "Igen, megjelölés frissítettként", "yesMarkUpdated": "Igen, megjelölés frissítettként",
"fdroid": "F-Droid", "fdroid": "F-Droid",
"appIdOrName": "App ID vagy név", "appIdOrName": "App ID vagy név",
@@ -210,6 +208,14 @@
"language": "Nyelv", "language": "Nyelv",
"storagePermissionDenied": "Tárhely engedély megtagadva", "storagePermissionDenied": "Tárhely engedély megtagadva",
"selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.", "selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.",
"removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazást?"
},
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva", "one": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva",
"other": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva" "other": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva"

View File

@@ -74,7 +74,6 @@
"changeX": "Modifica {}", "changeX": "Modifica {}",
"installUpdateApps": "Installa/Aggiorna App", "installUpdateApps": "Installa/Aggiorna App",
"installUpdateSelectedApps": "Installa/Aggiorna le App selezionate", "installUpdateSelectedApps": "Installa/Aggiorna le App selezionate",
"onlyWorksWithNonEVDApps": "Funziona solo per le App il cui stato d'installazione non può essere rilevato automaticamente (inconsueto).",
"markXSelectedAppsAsUpdated": "Contrassegnare le {} App selezionate come aggiornate?", "markXSelectedAppsAsUpdated": "Contrassegnare le {} App selezionate come aggiornate?",
"no": "No", "no": "No",
"yes": "Sì", "yes": "Sì",
@@ -178,7 +177,6 @@
"installedVersionX": "Versione installata: {}", "installedVersionX": "Versione installata: {}",
"lastUpdateCheckX": "Ultimo controllo degli aggiornamenti: {}", "lastUpdateCheckX": "Ultimo controllo degli aggiornamenti: {}",
"remove": "Rimuovi", "remove": "Rimuovi",
"removeAppQuestion": "Rimuovere l'App?",
"yesMarkUpdated": "Sì, contrassegna come aggiornato", "yesMarkUpdated": "Sì, contrassegna come aggiornato",
"fdroid": "F-Droid", "fdroid": "F-Droid",
"appIdOrName": "ID o nome dell'App", "appIdOrName": "ID o nome dell'App",
@@ -211,6 +209,14 @@
"language": "Lingua", "language": "Lingua",
"storagePermissionDenied": "Storage permission denied", "storagePermissionDenied": "Storage permission denied",
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.", "selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.",
"removeAppQuestion": {
"one": "Rimuovere l'App?",
"other": "Rimuovere l'App?"
},
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto",
"other": "Troppe richieste (traffico limitato) - riprova tra {} minuti" "other": "Troppe richieste (traffico limitato) - riprova tra {} minuti"

View File

@@ -7,7 +7,7 @@
"appIdMismatch": "ダウンロードしたパッケージのIDが既存のApp IDと一致しません", "appIdMismatch": "ダウンロードしたパッケージのIDが既存のApp IDと一致しません",
"functionNotImplemented": "このクラスはこの機能を実装していません", "functionNotImplemented": "このクラスはこの機能を実装していません",
"placeholder": "プレースホルダー", "placeholder": "プレースホルダー",
"someErrors": "いくつかのエラーが発生しました", "someErrors": "何らかのエラーが発生しました",
"unexpectedError": "予期せぬエラーが発生しました", "unexpectedError": "予期せぬエラーが発生しました",
"ok": "OK", "ok": "OK",
"and": "と", "and": "と",
@@ -74,7 +74,6 @@
"changeX": "{} を変更する", "changeX": "{} を変更する",
"installUpdateApps": "アプリのインストール/アップデート", "installUpdateApps": "アプリのインストール/アップデート",
"installUpdateSelectedApps": "選択したアプリのインストール/アップデート", "installUpdateSelectedApps": "選択したアプリのインストール/アップデート",
"onlyWorksWithNonEVDApps": "インストール状況を自動検出できないアプリ(一般的でないもの)のみ動作します。",
"markXSelectedAppsAsUpdated": "{}個の選択したアプリをアップデート済みとしてマークしますか?", "markXSelectedAppsAsUpdated": "{}個の選択したアプリをアップデート済みとしてマークしますか?",
"no": "いいえ", "no": "いいえ",
"yes": "はい", "yes": "はい",
@@ -82,7 +81,7 @@
"pinToTop": "トップに固定", "pinToTop": "トップに固定",
"unpinFromTop": "トップから固定解除", "unpinFromTop": "トップから固定解除",
"resetInstallStatusForSelectedAppsQuestion": "選択したアプリのインストール状態をリセットしますか?", "resetInstallStatusForSelectedAppsQuestion": "選択したアプリのインストール状態をリセットしますか?",
"installStatusOfXWillBeResetExplanation": "選択したアプリのインストール状態がリセットされます。\n\nアップデートに失敗するなどして、Obtainiumに表示されるアプリのバージョンが正しくない場合に役立ちます。", "installStatusOfXWillBeResetExplanation": "選択したアプリのインストール状態がリセットされます。\n\nアップデートに失敗した場合など、Obtainiumに表示されるアプリのバージョンが正しくない場合に有効です。",
"shareSelectedAppURLs": "選択したアプリのURLを共有する", "shareSelectedAppURLs": "選択したアプリのURLを共有する",
"resetInstallStatus": "インストール状態をリセットする", "resetInstallStatus": "インストール状態をリセットする",
"more": "もっと見る", "more": "もっと見る",
@@ -109,7 +108,7 @@
"searchX": "{}で検索", "searchX": "{}で検索",
"noResults": "結果は見つかりませんでした", "noResults": "結果は見つかりませんでした",
"importX": "{}をインポートする", "importX": "{}をインポートする",
"importedAppsIdDisclaimer": "インポートしたアプリが「未インストール」と表示されることがあります。\nこれを解決するには、Obtainiumから再インストールしてください。\nアプリのデータには影響しません。\n\nURLとサードパーティのインポートメソッドにのみ影響します。", "importedAppsIdDisclaimer": "インポートしたアプリが「未インストール」と表示されることがあります。\nこれを解決するには、Obtainiumから再インストールしてください。\nアプリのデータには影響しません。\n\nURLとサードパーティのインポートメソッドにのみ影響します。",
"importErrors": "インポートエラー", "importErrors": "インポートエラー",
"importedXOfYApps": "{} / {} アプリをインポートしました", "importedXOfYApps": "{} / {} アプリをインポートしました",
"followingURLsHadErrors": "以下のURLでエラーが発生しました:", "followingURLsHadErrors": "以下のURLでエラーが発生しました:",
@@ -133,7 +132,7 @@
"bgUpdateCheckInterval": "バックグラウンドでのアップデート確認の間隔", "bgUpdateCheckInterval": "バックグラウンドでのアップデート確認の間隔",
"neverManualOnly": "手動", "neverManualOnly": "手動",
"appearance": "外観", "appearance": "外観",
"showWebInAppView": "アプリビューにソースウェブページを表示する", "showWebInAppView": "アプリページにソースのWebページを表示する",
"pinUpdates": "アップデートがあるアプリをトップに固定する", "pinUpdates": "アップデートがあるアプリをトップに固定する",
"updates": "アップデート", "updates": "アップデート",
"sourceSpecific": "Github アクセストークン", "sourceSpecific": "Github アクセストークン",
@@ -178,13 +177,12 @@
"installedVersionX": "インストールされたバージョン: {}", "installedVersionX": "インストールされたバージョン: {}",
"lastUpdateCheckX": "最終アップデート確認: {}", "lastUpdateCheckX": "最終アップデート確認: {}",
"remove": "削除", "remove": "削除",
"removeAppQuestion": "アプリを削除しますか?",
"yesMarkUpdated": "はい、アップデート済みとしてマークします", "yesMarkUpdated": "はい、アップデート済みとしてマークします",
"fdroid": "F-Droid", "fdroid": "F-Droid",
"appIdOrName": "アプリのIDまたは名前", "appIdOrName": "アプリのIDまたは名前",
"appWithIdOrNameNotFound": "そのIDや名前を持つアプリは見つかりませんでした", "appWithIdOrNameNotFound": "そのIDや名前を持つアプリは見つかりませんでした",
"reposHaveMultipleApps": "リポジトリには複数のアプリが含まれることがあります", "reposHaveMultipleApps": "リポジトリには複数のアプリが含まれることがあります",
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo", "fdroidThirdPartyRepo": "F-Droid サードパーティリポジトリ",
"steam": "Steam", "steam": "Steam",
"steamMobile": "Steam Mobile", "steamMobile": "Steam Mobile",
"steamChat": "Steam Chat", "steamChat": "Steam Chat",
@@ -211,6 +209,14 @@
"language": "言語", "language": "言語",
"storagePermissionDenied": "ストレージ権限が拒否されました", "storagePermissionDenied": "ストレージ権限が拒否されました",
"selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。", "selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。",
"filterAPKsByRegEx": "正規表現でAPKを絞り込む",
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.",
"removeAppQuestion": {
"one": "アプリを削除しますか?",
"other": "アプリを削除しますか?"
},
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください", "one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください",
"other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください" "other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください"

View File

@@ -178,7 +178,6 @@
"installedVersionX": "已安装: {}", "installedVersionX": "已安装: {}",
"lastUpdateCheckX": "最后检查: {}", "lastUpdateCheckX": "最后检查: {}",
"remove": "删除", "remove": "删除",
"removeAppQuestion": "删除应用?",
"yesMarkUpdated": "'是的,标为已更新", "yesMarkUpdated": "'是的,标为已更新",
"fdroid": "F-Droid", "fdroid": "F-Droid",
"appIdOrName": "应用 ID 或名称", "appIdOrName": "应用 ID 或名称",
@@ -211,6 +210,13 @@
"language": "语言", "language": "语言",
"storagePermissionDenied": "存储权限已被拒绝", "storagePermissionDenied": "存储权限已被拒绝",
"selectedCategorizeWarning": "这将取代所选应用程序的任何现有类别", "selectedCategorizeWarning": "这将取代所选应用程序的任何现有类别",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"removeAppQuestion": {
"one": "删除应用?",
"other": "删除应用?"
},
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "请求过多 (API 限制) - 在 {} 分钟后重试", "one": "请求过多 (API 限制) - 在 {} 分钟后重试",
"other": "请求过多 (API 限制) - 在 {} 分钟后重试" "other": "请求过多 (API 限制) - 在 {} 分钟后重试"

View File

@@ -26,15 +26,7 @@ class Codeberg extends AppSource {
required: false, required: false,
additionalValidators: [ additionalValidators: [
(value) { (value) {
if (value == null || value.isEmpty) { return regExValidator(value);
return null;
}
try {
RegExp(value);
} catch (e) {
return tr('invalidRegEx');
}
return null;
} }
]) ])
] ]
@@ -72,7 +64,7 @@ class Codeberg extends AppSource {
? additionalSettings['filterReleaseTitlesByRegEx'] ? additionalSettings['filterReleaseTitlesByRegEx']
: null; : null;
Response res = await get(Uri.parse( Response res = await get(Uri.parse(
'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/releases')); 'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>; var releases = jsonDecode(res.body) as List<dynamic>;
@@ -99,8 +91,8 @@ class Codeberg extends AppSource {
if (releases[i]['draft'] == true) { if (releases[i]['draft'] == true) {
// Draft releases not supported // Draft releases not supported
} }
var nameToFilter = releases[i]['name'] as String; var nameToFilter = releases[i]['name'] as String?;
if (nameToFilter.trim().isEmpty) { if (nameToFilter == null || nameToFilter.trim().isEmpty) {
// Some leave titles empty so tag is used // Some leave titles empty so tag is used
nameToFilter = releases[i]['tag_name'] as String; nameToFilter = releases[i]['tag_name'] as String;
} }

View File

@@ -65,15 +65,7 @@ class GitHub extends AppSource {
required: false, required: false,
additionalValidators: [ additionalValidators: [
(value) { (value) {
if (value == null || value.isEmpty) { return regExValidator(value);
return null;
}
try {
RegExp(value);
} catch (e) {
return tr('invalidRegEx');
}
return null;
} }
]) ])
] ]
@@ -119,7 +111,7 @@ class GitHub extends AppSource {
? additionalSettings['filterReleaseTitlesByRegEx'] ? additionalSettings['filterReleaseTitlesByRegEx']
: null; : null;
Response res = await get(Uri.parse( Response res = await get(Uri.parse(
'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases')); 'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>; var releases = jsonDecode(res.body) as List<dynamic>;
@@ -141,8 +133,8 @@ class GitHub extends AppSource {
if (!includePrereleases && releases[i]['prerelease'] == true) { if (!includePrereleases && releases[i]['prerelease'] == true) {
continue; continue;
} }
var nameToFilter = releases[i]['name'] as String; var nameToFilter = releases[i]['name'] as String?;
if (nameToFilter.trim().isEmpty) { if (nameToFilter == null || nameToFilter.trim().isEmpty) {
// Some leave titles empty so tag is used // Some leave titles empty so tag is used
nameToFilter = releases[i]['tag_name'] as String; nameToFilter = releases[i]['tag_name'] as String;
} }

View File

@@ -150,6 +150,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
Map<String, dynamic> values = {}; Map<String, dynamic> values = {};
late List<List<Widget>> formInputs; late List<List<Widget>> formInputs;
List<List<Widget>> rows = []; List<List<Widget>> rows = [];
String? initKey;
// If any value changes, call this to update the parent with value and validity // If any value changes, call this to update the parent with value and validity
void someValueChanged({bool isBuilding = false}) { void someValueChanged({bool isBuilding = false}) {
@@ -169,13 +170,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
widget.onValueChanges(returnValues, valid, isBuilding); widget.onValueChanges(returnValues, valid, isBuilding);
} }
@override initForm() {
void initState() { initKey = widget.key.toString();
super.initState();
// Initialize form values as all empty // Initialize form values as all empty
values.clear(); values.clear();
int j = 0;
for (var row in widget.items) { for (var row in widget.items) {
for (var e in row) { for (var e in row) {
values[e.key] = e.defaultValue; values[e.key] = e.defaultValue;
@@ -245,8 +243,17 @@ class _GeneratedFormState extends State<GeneratedForm> {
someValueChanged(isBuilding: true); someValueChanged(isBuilding: true);
} }
@override
void initState() {
super.initState();
initForm();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (widget.key.toString() != initKey) {
initForm();
}
for (var r = 0; r < formInputs.length; r++) { for (var r = 0; r < formInputs.length; r++) {
for (var e = 0; e < formInputs[r].length; e++) { for (var e = 0; e < formInputs[r].length; e++) {
if (widget.items[r][e] is GeneratedFormSwitch) { if (widget.items[r][e] is GeneratedFormSwitch) {

View File

@@ -29,7 +29,7 @@ class NoReleasesError extends ObtainiumError {
} }
class NoAPKError extends ObtainiumError { class NoAPKError extends ObtainiumError {
NoAPKError() : super(tr('noReleaseFound')); NoAPKError() : super(tr('noAPKFound'));
} }
class NoVersionError extends ObtainiumError { class NoVersionError extends ObtainiumError {

View File

@@ -21,7 +21,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.10.2'; const String currentVersion = '0.10.7';
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

View File

@@ -32,6 +32,7 @@ class _AddAppPageState extends State<AddAppPage> {
Map<String, dynamic> additionalSettings = {}; Map<String, dynamic> additionalSettings = {};
bool additionalSettingsValid = true; bool additionalSettingsValid = true;
List<String> pickedCategories = []; List<String> pickedCategories = [];
int searchnum = 0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -40,10 +41,14 @@ class _AddAppPageState extends State<AddAppPage> {
bool doingSomething = gettingAppInfo || searching; bool doingSomething = gettingAppInfo || searching;
changeUserInput(String input, bool valid, bool isBuilding) { changeUserInput(String input, bool valid, bool isBuilding,
{bool isSearch = false}) {
userInput = input; userInput = input;
if (!isBuilding) { if (!isBuilding) {
setState(() { setState(() {
if (isSearch) {
searchnum++;
}
var source = valid ? sourceProvider.getSource(userInput) : null; var source = valid ? sourceProvider.getSource(userInput) : null;
if (pickedSource.runtimeType != source.runtimeType) { if (pickedSource.runtimeType != source.runtimeType) {
pickedSource = source; pickedSource = source;
@@ -70,6 +75,7 @@ class _AddAppPageState extends State<AddAppPage> {
additionalSettings['noVersionDetection'] == true; additionalSettings['noVersionDetection'] == true;
var cont = true; var cont = true;
if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) && if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) &&
// ignore: use_build_context_synchronously
await showDialog( await showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
@@ -88,6 +94,7 @@ class _AddAppPageState extends State<AddAppPage> {
cont = false; cont = false;
} }
if (userPickedNoVersionDetection && if (userPickedNoVersionDetection &&
// ignore: use_build_context_synchronously
await showDialog( await showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
@@ -167,30 +174,32 @@ class _AddAppPageState extends State<AddAppPage> {
children: [ children: [
Expanded( Expanded(
child: GeneratedForm( child: GeneratedForm(
key: Key(searchnum.toString()),
items: [ items: [
[ [
GeneratedFormTextField('appSourceURL', GeneratedFormTextField('appSourceURL',
label: tr('appSourceURL'), label: tr('appSourceURL'),
additionalValidators: [ defaultValue: userInput,
(value) { additionalValidators: [
try { (value) {
sourceProvider try {
.getSource(value ?? '') sourceProvider
.standardizeURL( .getSource(value ?? '')
preStandardizeUrl( .standardizeURL(
value ?? '')); preStandardizeUrl(
} catch (e) { value ?? ''));
return e is String } catch (e) {
? e return e is String
: e is ObtainiumError ? e
? e.toString() : e is ObtainiumError
: tr('error'); ? e.toString()
} : tr('error');
return null; }
} return null;
]) }
] ])
], ]
],
onValueChanges: (values, valid, isBuilding) { onValueChanges: (values, valid, isBuilding) {
changeUserInput(values['appSourceURL']!, changeUserInput(values['appSourceURL']!,
valid, isBuilding); valid, isBuilding);
@@ -294,8 +303,8 @@ class _AddAppPageState extends State<AddAppPage> {
if (selectedUrls != null && if (selectedUrls != null &&
selectedUrls.isNotEmpty) { selectedUrls.isNotEmpty) {
changeUserInput( changeUserInput(
selectedUrls[0], true, false); selectedUrls[0], true, false,
addApp(resetUserInputAfter: true); isSearch: true);
} }
}).catchError((e) { }).catchError((e) {
showError(e, context); showError(e, context);
@@ -325,6 +334,7 @@ class _AddAppPageState extends State<AddAppPage> {
height: 16, height: 16,
), ),
GeneratedForm( GeneratedForm(
key: Key(pickedSource.runtimeType.toString()),
items: pickedSource! items: pickedSource!
.combinedAppSpecificSettingFormItems, .combinedAppSpecificSettingFormItems,
onValueChanges: (values, valid, isBuilding) { onValueChanges: (values, valid, isBuilding) {

View File

@@ -42,6 +42,8 @@ class _AppPageState extends State<AppPage> {
getUpdate(app.app.id); getUpdate(app.app.id);
} }
var trackOnly = app?.app.additionalSettings['trackOnly'] == true; var trackOnly = app?.app.additionalSettings['trackOnly'] == true;
var noVersionDetection =
app?.app.additionalSettings['noVersionDetection'] == true;
var infoColumn = Column( var infoColumn = Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -190,8 +192,9 @@ class _AppPageState extends State<AppPage> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
if (app?.app.installedVersion != null && if (noVersionDetection &&
!trackOnly && !trackOnly &&
app?.app.installedVersion != null &&
app?.app.installedVersion != app?.app.latestVersion) app?.app.installedVersion != app?.app.latestVersion)
IconButton( IconButton(
onPressed: app?.downloadProgress != null onPressed: app?.downloadProgress != null
@@ -203,13 +206,6 @@ class _AppPageState extends State<AppPage> {
return AlertDialog( return AlertDialog(
title: Text(tr( title: Text(tr(
'alreadyUpToDateQuestion')), 'alreadyUpToDateQuestion')),
content: Text(
tr('onlyWorksWithNonEVDApps'),
style: const TextStyle(
fontWeight:
FontWeight.bold,
fontStyle:
FontStyle.italic)),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
@@ -267,8 +263,9 @@ class _AppPageState extends State<AppPage> {
return row; return row;
}).toList(); }).toList();
return GeneratedFormModal( return GeneratedFormModal(
title: tr('additionalOptions'), title: tr('additionalOptions'),
items: items); items: items,
);
}).then((values) { }).then((values) {
if (app != null && values != null) { if (app != null && values != null) {
var changedApp = app.app; var changedApp = app.app;
@@ -289,7 +286,15 @@ class _AppPageState extends State<AppPage> {
}); });
}, },
tooltip: tr('additionalOptions'), tooltip: tr('additionalOptions'),
icon: const Icon(Icons.settings)), icon: const Icon(Icons.edit)),
if (app != null && app.installedInfo != null)
IconButton(
onPressed: () {
appsProvider.openAppSettings(app.app.id);
},
icon: const Icon(Icons.settings),
tooltip: tr('settings'),
),
if (app != null && settingsProvider.showAppWebpage) if (app != null && settingsProvider.showAppWebpage)
IconButton( IconButton(
onPressed: () { onPressed: () {
@@ -317,7 +322,7 @@ class _AppPageState extends State<AppPage> {
tooltip: tr('more')), tooltip: tr('more')),
const SizedBox(width: 16.0), const SizedBox(width: 16.0),
Expanded( Expanded(
child: ElevatedButton( child: TextButton(
onPressed: (app?.app.installedVersion == null || onPressed: (app?.app.installedVersion == null ||
app?.app.installedVersion != app?.app.installedVersion !=
app?.app.latestVersion) && app?.app.latestVersion) &&
@@ -356,44 +361,17 @@ class _AppPageState extends State<AppPage> {
? tr('update') ? tr('update')
: tr('markUpdated')))), : tr('markUpdated')))),
const SizedBox(width: 16.0), const SizedBox(width: 16.0),
ElevatedButton( Expanded(
child: TextButton(
onPressed: app?.downloadProgress != null onPressed: app?.downloadProgress != null
? null ? null
: () { : () {
showDialog( appsProvider.removeAppsWithModal(
context: context, context, [app!.app]).then((value) {
builder: (BuildContext ctx) { if (value == true) {
return AlertDialog( Navigator.of(context).pop();
title: Text(tr('removeAppQuestion')), }
content: Text(tr( });
'xWillBeRemovedButRemainInstalled',
args: [
app?.installedInfo?.name ??
app?.app.name ??
tr('app')
])),
actions: [
TextButton(
onPressed: () {
HapticFeedback
.selectionClick();
appsProvider.removeApps(
[app!.app.id]).then((_) {
int count = 0;
Navigator.of(context)
.popUntil((_) =>
count++ >= 2);
});
},
child: Text(tr('remove'))),
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(tr('cancel')))
],
);
});
}, },
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: foregroundColor:
@@ -401,7 +379,7 @@ class _AppPageState extends State<AppPage> {
surfaceTintColor: surfaceTintColor:
Theme.of(context).colorScheme.error), Theme.of(context).colorScheme.error),
child: Text(tr('remove')), child: Text(tr('remove')),
), )),
])), ])),
if (app?.downloadProgress != null) if (app?.downloadProgress != null)
Padding( Padding(
@@ -413,3 +391,18 @@ class _AppPageState extends State<AppPage> {
); );
} }
} }
class RemoveAppsModal extends StatefulWidget {
const RemoveAppsModal({super.key, this.apps = const []});
final List<App> apps;
@override
State<RemoveAppsModal> createState() => _RemoveAppsModalState();
}
class _RemoveAppsModalState extends State<RemoveAppsModal> {
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -63,21 +63,29 @@ class _HomePageState extends State<HomePage> {
.map((e) => .map((e) =>
NavigationDestination(icon: Icon(e.icon), label: e.title)) NavigationDestination(icon: Icon(e.icon), label: e.title))
.toList(), .toList(),
onDestinationSelected: (int index) { onDestinationSelected: (int index) async {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
setState(() { if (index == 0) {
if (index == 0) { while ((pages[0].widget.key as GlobalKey<AppsPageState>)
.currentState !=
null) {
// Avoid duplicate GlobalKey error
await Future.delayed(const Duration(microseconds: 1));
}
setState(() {
selectedIndexHistory.clear(); selectedIndexHistory.clear();
} else if (selectedIndexHistory.isEmpty || });
(selectedIndexHistory.isNotEmpty && } else if (selectedIndexHistory.isEmpty ||
selectedIndexHistory.last != index)) { (selectedIndexHistory.isNotEmpty &&
selectedIndexHistory.last != index)) {
setState(() {
int existingInd = selectedIndexHistory.indexOf(index); int existingInd = selectedIndexHistory.indexOf(index);
if (existingInd >= 0) { if (existingInd >= 0) {
selectedIndexHistory.removeAt(existingInd); selectedIndexHistory.removeAt(existingInd);
} }
selectedIndexHistory.add(index); selectedIndexHistory.add(index);
} });
}); }
}, },
selectedIndex: selectedIndex:
selectedIndexHistory.isEmpty ? 0 : selectedIndexHistory.last, selectedIndexHistory.isEmpty ? 0 : selectedIndexHistory.last,

View File

@@ -564,18 +564,22 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
widget.onlyOneSelectionAllowed ? tr('selectURL') : tr('selectURLs')), widget.onlyOneSelectionAllowed ? tr('selectURL') : tr('selectURLs')),
content: Column(children: [ content: Column(children: [
...urlWithDescriptionSelections.keys.map((urlWithD) { ...urlWithDescriptionSelections.keys.map((urlWithD) {
select(bool? value) {
setState(() {
value ??= false;
if (value! && widget.onlyOneSelectionAllowed) {
selectOnlyOne(urlWithD.key);
} else {
urlWithDescriptionSelections[urlWithD] = value!;
}
});
}
return Row(children: [ return Row(children: [
Checkbox( Checkbox(
value: urlWithDescriptionSelections[urlWithD], value: urlWithDescriptionSelections[urlWithD],
onChanged: (value) { onChanged: (value) {
setState(() { select(value);
value ??= false;
if (value! && widget.onlyOneSelectionAllowed) {
selectOnlyOne(urlWithD.key);
} else {
urlWithDescriptionSelections[urlWithD] = value!;
}
});
}), }),
const SizedBox( const SizedBox(
width: 8, width: 8,
@@ -599,12 +603,17 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
const TextStyle(decoration: TextDecoration.underline), const TextStyle(decoration: TextDecoration.underline),
textAlign: TextAlign.start, textAlign: TextAlign.start,
)), )),
Text( GestureDetector(
urlWithD.value.length > 128 onTap: () {
? '${urlWithD.value.substring(0, 128)}...' select(!(urlWithDescriptionSelections[urlWithD] ?? false));
: urlWithD.value, },
style: const TextStyle( child: Text(
fontStyle: FontStyle.italic, fontSize: 12), urlWithD.value.length > 128
? '${urlWithD.value.substring(0, 128)}...'
: urlWithD.value,
style: const TextStyle(
fontStyle: FontStyle.italic, fontSize: 12),
),
), ),
const SizedBox( const SizedBox(
height: 8, height: 8,

View File

@@ -4,10 +4,8 @@ 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';
import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart'; import 'package:obtainium/main.dart';
import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/logs_provider.dart'; import 'package:obtainium/providers/logs_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';

View File

@@ -5,6 +5,7 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:android_intent_plus/flag.dart';
import 'package:device_info_plus/device_info_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';
@@ -12,6 +13,8 @@ import 'package:flutter/services.dart';
import 'package:install_plugin_v2/install_plugin_v2.dart'; import 'package:install_plugin_v2/install_plugin_v2.dart';
import 'package:installed_apps/app_info.dart'; import 'package:installed_apps/app_info.dart';
import 'package:installed_apps/installed_apps.dart'; import 'package:installed_apps/installed_apps.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/logs_provider.dart'; import 'package:obtainium/providers/logs_provider.dart';
import 'package:obtainium/providers/notifications_provider.dart'; import 'package:obtainium/providers/notifications_provider.dart';
@@ -23,6 +26,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:flutter_fgbg/flutter_fgbg.dart'; import 'package:flutter_fgbg/flutter_fgbg.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:android_intent_plus/android_intent.dart';
class AppInMemory { class AppInMemory {
late App app; late App app;
@@ -247,7 +251,11 @@ class AppsProvider with ChangeNotifier {
!(await canDowngradeApps())) { !(await canDowngradeApps())) {
throw DowngradeError(); throw DowngradeError();
} }
await InstallPlugin.installApk(file.file.path, 'dev.imranr.obtainium'); await InstallPlugin.installApk(file.file.path, obtainiumId);
if (file.appId == obtainiumId) {
// Obtainium prompt should be lowest
await Future.delayed(const Duration(milliseconds: 500));
}
apps[file.appId]!.app.installedVersion = apps[file.appId]!.app.installedVersion =
apps[file.appId]!.app.latestVersion; apps[file.appId]!.app.latestVersion;
// Don't correct install status as installation may not be done yet // Don't correct install status as installation may not be done yet
@@ -255,6 +263,15 @@ class AppsProvider with ChangeNotifier {
attemptToCorrectInstallStatus: false); attemptToCorrectInstallStatus: false);
} }
void uninstallApp(String appId) async {
var intent = AndroidIntent(
action: 'android.intent.action.DELETE',
data: 'package:$appId',
flags: <int>[Flag.FLAG_ACTIVITY_NEW_TASK],
package: 'vnd.android.package-archive');
await intent.launch();
}
Future<String?> confirmApkUrl(App app, BuildContext? context) async { Future<String?> confirmApkUrl(App app, BuildContext? context) async {
// If the App has more than one APK, the user should pick one (if context provided) // If the App has more than one APK, the user should pick one (if context provided)
String? apkUrl = app.apkUrls[app.preferredApkIndex]; String? apkUrl = app.apkUrls[app.preferredApkIndex];
@@ -262,6 +279,7 @@ class AppsProvider with ChangeNotifier {
List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis; List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis;
if (app.apkUrls.length > 1 && context != null) { if (app.apkUrls.length > 1 && context != null) {
// ignore: use_build_context_synchronously
apkUrl = await showDialog( apkUrl = await showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
@@ -281,6 +299,7 @@ class AppsProvider with ChangeNotifier {
if (apkUrl != null && if (apkUrl != null &&
getHost(apkUrl) != getHost(app.url) && getHost(apkUrl) != getHost(app.url) &&
context != null) { context != null) {
// ignore: use_build_context_synchronously
if (await showDialog( if (await showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
@@ -438,9 +457,6 @@ class AppsProvider with ChangeNotifier {
} catch (e) { } catch (e) {
// //
} }
if (!res) {
logs.add(tr('versionCorrectionDisabled'));
}
return res; return res;
} }
@@ -619,6 +635,57 @@ class AppsProvider with ChangeNotifier {
} }
} }
Future<bool> removeAppsWithModal(BuildContext context, List<App> apps) async {
var showUninstallOption =
apps.where((a) => a.installedVersion != null).isNotEmpty;
var values = await showDialog(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: plural('removeAppQuestion', apps.length),
items: !showUninstallOption
? []
: [
[
GeneratedFormSwitch('rmAppEntry',
label: tr('removeFromObtainium'), defaultValue: true)
],
[
GeneratedFormSwitch('uninstallApp',
label: tr('uninstallFromDevice'))
]
],
initValid: true,
);
});
if (values != null) {
bool uninstall = values['uninstallApp'] == true && showUninstallOption;
bool remove = values['rmAppEntry'] == true || !showUninstallOption;
if (uninstall) {
for (var i = 0; i < apps.length; i++) {
if (apps[i].installedVersion != null) {
uninstallApp(apps[i].id);
apps[i].installedVersion = null;
}
}
await saveApps(apps, attemptToCorrectInstallStatus: false);
}
if (remove) {
await removeApps(apps.map((e) => e.id).toList());
}
return uninstall || remove;
}
return false;
}
Future<void> openAppSettings(String appId) async {
final AndroidIntent intent = AndroidIntent(
action: 'action_application_details_settings',
data: 'package:$appId',
);
await intent.launch();
}
Future<App?> checkUpdate(String appId) async { Future<App?> checkUpdate(String appId) async {
App? currentApp = apps[appId]!.app; App? currentApp = apps[appId]!.app;
SourceProvider sourceProvider = SourceProvider(); SourceProvider sourceProvider = SourceProvider();
@@ -704,7 +771,7 @@ class AppsProvider with ChangeNotifier {
exportDir = await getExternalStorageDirectory(); exportDir = await getExternalStorageDirectory();
path = exportDir!.path; path = exportDir!.path;
} }
if ((await DeviceInfoPlugin().androidInfo).version.sdkInt <= 28) { if ((await DeviceInfoPlugin().androidInfo).version.sdkInt <= 29) {
if (await Permission.storage.isDenied) { if (await Permission.storage.isDenied) {
await Permission.storage.request(); await Permission.storage.request();
} }

View File

@@ -225,7 +225,19 @@ class AppSource {
label: tr('trackOnly'), label: tr('trackOnly'),
) )
], ],
[GeneratedFormSwitch('noVersionDetection', label: tr('noVersionDetection'))] [
GeneratedFormSwitch('noVersionDetection', label: tr('noVersionDetection'))
],
[
GeneratedFormTextField('apkFilterRegEx',
label: tr('filterAPKsByRegEx'),
required: false,
additionalValidators: [
(value) {
return regExValidator(value);
}
])
]
]; ];
// Previous 2 variables combined into one at runtime for convenient usage // Previous 2 variables combined into one at runtime for convenient usage
@@ -269,6 +281,18 @@ abstract class MassAppUrlSource {
Future<Map<String, String>> getUrlsWithDescriptions(List<String> args); Future<Map<String, String>> getUrlsWithDescriptions(List<String> args);
} }
regExValidator(String? value) {
if (value == null || value.isEmpty) {
return null;
}
try {
RegExp(value);
} catch (e) {
return tr('invalidRegEx');
}
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> sources = [ List<AppSource> sources = [
@@ -344,10 +368,13 @@ class SourceProvider {
} }
Future<App> getApp( Future<App> getApp(
AppSource source, String url, Map<String, dynamic> additionalSettings, AppSource source,
{App? currentApp, String url,
bool trackOnlyOverride = false, Map<String, dynamic> additionalSettings, {
noVersionDetectionOverride = false}) async { App? currentApp,
bool trackOnlyOverride = false,
noVersionDetectionOverride = false,
}) async {
if (trackOnlyOverride || source.enforceTrackOnly) { if (trackOnlyOverride || source.enforceTrackOnly) {
additionalSettings['trackOnly'] = true; additionalSettings['trackOnly'] = true;
} }
@@ -358,6 +385,11 @@ class SourceProvider {
String standardUrl = source.standardizeURL(preStandardizeUrl(url)); String standardUrl = source.standardizeURL(preStandardizeUrl(url));
APKDetails apk = APKDetails apk =
await source.getLatestAPKDetails(standardUrl, additionalSettings); await source.getLatestAPKDetails(standardUrl, additionalSettings);
if (additionalSettings['apkFilterRegEx'] != null) {
var reg = RegExp(additionalSettings['apkFilterRegEx']);
apk.apkUrls =
apk.apkUrls.where((element) => reg.hasMatch(element)).toList();
}
if (apk.apkUrls.isEmpty && !trackOnly) { if (apk.apkUrls.isEmpty && !trackOnly) {
throw NoAPKError(); throw NoAPKError();
} }

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@ 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.10.2+108 # When changing this, update the tag in main() accordingly version: 0.10.7+113 # When changing this, update the tag in main() accordingly
environment: environment:
sdk: '>=2.18.2 <3.0.0' sdk: '>=2.18.2 <3.0.0'
@@ -58,6 +58,7 @@ dependencies:
android_alarm_manager_plus: ^2.1.0 android_alarm_manager_plus: ^2.1.0
sqflite: ^2.2.0+3 sqflite: ^2.2.0+3
easy_localization: ^3.0.1 easy_localization: ^3.0.1
android_intent_plus: ^3.1.5
dev_dependencies: dev_dependencies: