diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 2bc8000..8b78ce2 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -52,6 +52,7 @@ + diff --git a/assets/translations/de.json b/assets/translations/de.json index ac7da30..9d72135 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -178,7 +178,6 @@ "installedVersionX": "Installierte Version: {}", "lastUpdateCheckX": "Letzte Aktualisierungsprüfung: {}", "remove": "Entfernen", - "removeAppQuestion": "App entfernen?", "yesMarkUpdated": "Ja, als aktualisiert markieren", "fdroid": "F-Droid", "appIdOrName": "App ID oder Name", @@ -212,6 +211,12 @@ "storagePermissionDenied": "Storage permission denied", "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", + "removeAppQuestion": { + "one": "App entfernen?", + "other": "App entfernen?" + }, "tooManyRequestsTryAgainInMinutes": { "one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut", "other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut" diff --git a/assets/translations/en.json b/assets/translations/en.json index 274947e..6fcc87a 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -178,7 +178,6 @@ "installedVersionX": "Installed Version: {}", "lastUpdateCheckX": "Last Update Check: {}", "remove": "Remove", - "removeAppQuestion": "Remove App?", "yesMarkUpdated": "Yes, Mark as Updated", "fdroid": "F-Droid", "appIdOrName": "App ID or Name", @@ -212,6 +211,12 @@ "storagePermissionDenied": "Storage permission denied", "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", + "removeAppQuestion": { + "one": "Remove App?", + "other": "Remove Apps?" + }, "tooManyRequestsTryAgainInMinutes": { "one": "Too many requests (rate limited) - try again in {} minute", "other": "Too many requests (rate limited) - try again in {} minutes" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index c6466fd..0ba0763 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -178,7 +178,6 @@ "installedVersionX": "Telepített verzió: {}", "lastUpdateCheckX": "Frissítés ellenőrizve: {}", "remove": "Eltávolítás", - "removeAppQuestion": "Eltávolítja az alkalmazást?", "yesMarkUpdated": "Igen, megjelölés frissítettként", "fdroid": "F-Droid", "appIdOrName": "App ID vagy név", @@ -211,6 +210,12 @@ "storagePermissionDenied": "Tárhely engedély megtagadva", "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", + "removeAppQuestion": { + "one": "Eltávolítja az alkalmazást?", + "other": "Eltávolítja az alkalmazást?" + }, "tooManyRequestsTryAgainInMinutes": { "one": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva", "other": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva" diff --git a/assets/translations/it.json b/assets/translations/it.json index 4a56c8a..a6eaaab 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -178,7 +178,6 @@ "installedVersionX": "Versione installata: {}", "lastUpdateCheckX": "Ultimo controllo degli aggiornamenti: {}", "remove": "Rimuovi", - "removeAppQuestion": "Rimuovere l'App?", "yesMarkUpdated": "Sì, contrassegna come aggiornato", "fdroid": "F-Droid", "appIdOrName": "ID o nome dell'App", @@ -212,6 +211,12 @@ "storagePermissionDenied": "Storage permission denied", "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", + "removeAppQuestion": { + "one": "Rimuovere l'App?", + "other": "Rimuovere l'App?" + }, "tooManyRequestsTryAgainInMinutes": { "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", "other": "Troppe richieste (traffico limitato) - riprova tra {} minuti" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index eb078b8..20ef37d 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -178,7 +178,6 @@ "installedVersionX": "インストールされたバージョン: {}", "lastUpdateCheckX": "最終アップデート確認: {}", "remove": "削除", - "removeAppQuestion": "アプリを削除しますか?", "yesMarkUpdated": "はい、アップデート済みとしてマークします", "fdroid": "F-Droid", "appIdOrName": "アプリのIDまたは名前", @@ -212,6 +211,12 @@ "storagePermissionDenied": "ストレージ権限が拒否されました", "selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。", "filterAPKsByRegEx": "正規表現でAPKを絞り込む", + "removeFromObtainium": "Remove from Obtainium", + "uninstallFromDevice": "Uninstall from Device", + "removeAppQuestion": { + "one": "アプリを削除しますか?", + "other": "アプリを削除しますか?" + }, "tooManyRequestsTryAgainInMinutes": { "one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください", "other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 8f103a8..89b6ea6 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -178,7 +178,6 @@ "installedVersionX": "已安装: {}", "lastUpdateCheckX": "最后检查: {}", "remove": "删除", - "removeAppQuestion": "删除应用?", "yesMarkUpdated": "'是的,标为已更新", "fdroid": "F-Droid", "appIdOrName": "应用 ID 或名称", @@ -212,6 +211,12 @@ "storagePermissionDenied": "存储权限已被拒绝", "selectedCategorizeWarning": "这将取代所选应用程序的任何现有类别", "filterAPKsByRegEx": "Filter APKs by Regular Expression", + "removeFromObtainium": "Remove from Obtainium", + "uninstallFromDevice": "Uninstall from Device", + "removeAppQuestion": { + "one": "删除应用?", + "other": "删除应用?" + }, "tooManyRequestsTryAgainInMinutes": { "one": "请求过多 (API 限制) - 在 {} 分钟后重试", "other": "请求过多 (API 限制) - 在 {} 分钟后重试" diff --git a/lib/pages/app.dart b/lib/pages/app.dart index 94711ca..02bf1bb 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form_modal.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/main.dart'; @@ -361,40 +362,12 @@ class _AppPageState extends State { onPressed: app?.downloadProgress != null ? null : () { - showDialog( - context: context, - builder: (BuildContext ctx) { - return AlertDialog( - 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'))) - ], - ); - }); + appsProvider.removeAppsWithModal( + context, [app!.app]).then((value) { + if (value == true) { + Navigator.of(context).pop(); + } + }); }, style: TextButton.styleFrom( foregroundColor: @@ -414,3 +387,18 @@ class _AppPageState extends State { ); } } + +class RemoveAppsModal extends StatefulWidget { + const RemoveAppsModal({super.key, this.apps = const []}); + final List apps; + + @override + State createState() => _RemoveAppsModalState(); +} + +class _RemoveAppsModalState extends State { + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 56f569a..e81be28 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -389,28 +389,30 @@ class AppsPageState extends State { onPressed: selectedApps.isEmpty ? null : () { - showDialog?>( - context: context, - builder: (BuildContext ctx) { - return GeneratedFormModal( - title: tr( - 'removeSelectedAppsQuestion'), - items: const [], - initValid: true, - message: tr( - 'xWillBeRemovedButRemainInstalled', - args: [ - plural('apps', - selectedApps.length) - ]), - ); - }).then((values) { - if (values != null) { - appsProvider.removeApps(selectedApps - .map((e) => e.id) - .toList()); - } - }); + appsProvider.removeAppsWithModal( + context, selectedApps.toList()); + // showDialog?>( + // context: context, + // builder: (BuildContext ctx) { + // return GeneratedFormModal( + // title: tr( + // 'removeSelectedAppsQuestion'), + // items: const [], + // initValid: true, + // message: tr( + // 'xWillBeRemovedButRemainInstalled', + // args: [ + // plural('apps', + // selectedApps.length) + // ]), + // ); + // }).then((values) { + // if (values != null) { + // appsProvider.removeApps(selectedApps + // .map((e) => e.id) + // .toList()); + // } + // }); }, tooltip: tr('removeSelectedApps'), icon: const Icon(Icons.delete_outline_outlined), diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 18e5124..c4dbbaa 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:android_intent_plus/flag.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:easy_localization/easy_localization.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:installed_apps/app_info.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/providers/logs_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:obtainium/providers/source_provider.dart'; import 'package:http/http.dart'; +import 'package:android_intent_plus/android_intent.dart'; class AppInMemory { late App app; @@ -259,6 +263,15 @@ class AppsProvider with ChangeNotifier { attemptToCorrectInstallStatus: false); } + void uninstallApp(String appId) async { + var intent = AndroidIntent( + action: 'android.intent.action.DELETE', + data: 'package:$appId', + flags: [Flag.FLAG_ACTIVITY_NEW_TASK], + package: 'vnd.android.package-archive'); + await intent.launch(); + } + Future confirmApkUrl(App app, BuildContext? context) async { // If the App has more than one APK, the user should pick one (if context provided) String? apkUrl = app.apkUrls[app.preferredApkIndex]; @@ -622,6 +635,49 @@ class AppsProvider with ChangeNotifier { } } + Future removeAppsWithModal(BuildContext context, List 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 checkUpdate(String appId) async { App? currentApp = apps[appId]!.app; SourceProvider sourceProvider = SourceProvider(); diff --git a/pubspec.lock b/pubspec.lock index 4bd6b84..5c0b82a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + android_intent_plus: + dependency: "direct main" + description: + name: android_intent_plus + sha256: ebd110b60723334bdc6eeb373116d6c52e9bed8feb9dcbd9f034531f56636e31 + url: "https://pub.dev" + source: hosted + version: "3.1.5" animations: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 105ad20..6e82baf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -58,6 +58,7 @@ dependencies: android_alarm_manager_plus: ^2.1.0 sqflite: ^2.2.0+3 easy_localization: ^3.0.1 + android_intent_plus: ^3.1.5 dev_dependencies: