diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 009cef0d..08cb344c 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -45,10 +45,10 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - gh repo clone ${{ github.repository }} + gh repo clone "${{ github.repository }}" cd revanced-manager - gh repo set-default ${{ github.repository }} - gh pr checkout ${{ inputs.pr-number }} + gh repo set-default "${{ github.repository }}" + gh pr checkout "${{ inputs.pr-number }}" echo "DATETIME=$( TZ='UTC+0' date --rfc-email )" >> $GITHUB_ENV echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV @@ -83,7 +83,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - flutter build apk --${{ inputs.app-flavour }}; + flutter build apk --"${{ inputs.app-flavour }}"; - name: Prepare to comment run: | diff --git a/.gitignore b/.gitignore index aa4fd514..50c7901c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ .buildlog/ .history .svn/ +local.properties # IntelliJ related *.iml diff --git a/README.md b/README.md index 6db2ff9b..c4484d98 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ ReVanced Manager is an Android application that uses ReVanced Patcher to add, re ## 💪 Features -We provide the some of the features are: +Some of the features we provide are: * 📱 **Portable**: ReVanced Patcher that fit in your pocket; * 🤗 **Intuitive UI**: Help you manage your patched applications with easy-to-use interface; diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7fade03b..782ef780 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -32,17 +32,17 @@ android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" /> - - + + - - + + @@ -55,5 +55,22 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt index 10ff238e..da997f9b 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt @@ -1,11 +1,16 @@ package app.revanced.manager.flutter +import android.app.PendingIntent import android.app.SearchManager import android.content.Intent +import android.content.pm.PackageInstaller +import android.os.Build import android.os.Handler import android.os.Looper import app.revanced.manager.flutter.utils.Aapt import app.revanced.manager.flutter.utils.aligning.ZipAligner +import app.revanced.manager.flutter.utils.packageInstaller.InstallerReceiver +import app.revanced.manager.flutter.utils.packageInstaller.UninstallerReceiver import app.revanced.manager.flutter.utils.signing.Signer import app.revanced.manager.flutter.utils.zip.ZipFile import app.revanced.manager.flutter.utils.zip.structures.ZipEntry @@ -184,12 +189,24 @@ class MainActivity : FlutterActivity() { }.toString().let(result::success) } + "installApk" -> { + val apkPath = call.argument("apkPath")!! + PackageInstallerManager.result = result + installApk(apkPath) + } + + "uninstallApp" -> { + val packageName = call.argument("packageName")!! + uninstallApp(packageName) + PackageInstallerManager.result = result + } + else -> result.notImplemented() } } } - fun openBrowser(query: String?) { + private fun openBrowser(query: String?) { val intent = Intent(Intent.ACTION_WEB_SEARCH).apply { putExtra(SearchManager.QUERY, query) } @@ -349,7 +366,7 @@ class MainActivity : FlutterActivity() { return@Thread } - updateProgress(0.8, "Building...", "") + updateProgress(0.75, "Building...", "") val res = patcher.get() patcher.close() @@ -382,7 +399,7 @@ class MainActivity : FlutterActivity() { return@Thread } - updateProgress(0.9, "Signing...", "Signing APK") + updateProgress(0.8, "Signing...", "Signing APK") try { Signer("ReVanced", keystorePassword) @@ -392,7 +409,7 @@ class MainActivity : FlutterActivity() { e.printStackTrace() } - updateProgress(1.0, "Patched", "Patched") + updateProgress(.85, "Patched", "Patched APK") } catch (ex: Throwable) { if (!cancel) { val stack = ex.stackTraceToString() @@ -407,4 +424,44 @@ class MainActivity : FlutterActivity() { handler.post { result.success(null) } }.start() } + + private fun installApk(apkPath: String) { + val packageInstaller: PackageInstaller = applicationContext.packageManager.packageInstaller + val sessionParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) + val sessionId: Int = packageInstaller.createSession(sessionParams) + val session: PackageInstaller.Session = packageInstaller.openSession(sessionId) + session.use { activeSession -> + val sessionOutputStream = activeSession.openWrite(applicationContext.packageName, 0, -1) + sessionOutputStream.use { outputStream -> + val apkFile = File(apkPath) + apkFile.inputStream().use { inputStream -> + inputStream.copyTo(outputStream) + } + } + } + val receiverIntent = Intent(applicationContext, InstallerReceiver::class.java).apply { + action = "APP_INSTALL_ACTION" + } + val receiverPendingIntent = PendingIntent.getBroadcast(context, sessionId, receiverIntent, PackageInstallerManager.flags) + session.commit(receiverPendingIntent.intentSender) + session.close() + } + + private fun uninstallApp(packageName: String) { + val packageInstaller: PackageInstaller = applicationContext.packageManager.packageInstaller + val receiverIntent = Intent(applicationContext, UninstallerReceiver::class.java).apply { + action = "APP_UNINSTALL_ACTION" + } + val receiverPendingIntent = PendingIntent.getBroadcast(context, 0, receiverIntent, PackageInstallerManager.flags) + packageInstaller.uninstall(packageName, receiverPendingIntent.intentSender) + } + + object PackageInstallerManager { + var result: MethodChannel.Result? = null + val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + } else { + PendingIntent.FLAG_UPDATE_CURRENT + } + } } diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/packageInstaller/InstallerReceiver.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/packageInstaller/InstallerReceiver.kt new file mode 100644 index 00000000..d14a9daa --- /dev/null +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/packageInstaller/InstallerReceiver.kt @@ -0,0 +1,32 @@ +package app.revanced.manager.flutter.utils.packageInstaller + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInstaller +import app.revanced.manager.flutter.MainActivity + +class InstallerReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) { + PackageInstaller.STATUS_PENDING_USER_ACTION -> { + val confirmationIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT) + if (confirmationIntent != null) { + context.startActivity(confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) + } + } + + else -> { + val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME) + val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + val otherPackageName = intent.getStringExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME) + MainActivity.PackageInstallerManager.result!!.success(mapOf( + "status" to status, + "packageName" to packageName, + "message" to message, + "otherPackageName" to otherPackageName + )) + } + } + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/packageInstaller/UninstallerReceiver.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/packageInstaller/UninstallerReceiver.kt new file mode 100644 index 00000000..84dec3cc --- /dev/null +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/packageInstaller/UninstallerReceiver.kt @@ -0,0 +1,24 @@ +package app.revanced.manager.flutter.utils.packageInstaller + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInstaller +import app.revanced.manager.flutter.MainActivity + +class UninstallerReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) { + PackageInstaller.STATUS_PENDING_USER_ACTION -> { + val confirmationIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT) + if (confirmationIntent != null) { + context.startActivity(confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) + } + } + + else -> { + MainActivity.PackageInstallerManager.result!!.success(status) + } + } + } +} \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 744c64d1..db8c3baa 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 511cd9c4..1f8002db 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -168,7 +168,9 @@ "installButton": "Install", "installRootType": "Mount", - "installNonRootType": "Normal", + "installNonRootType": "Regular", + + "warning": "Disable auto updates for the patched app to avoid unexpected issues.", "pressBackAgain": "Press back again to cancel", "openButton": "Open", @@ -302,16 +304,17 @@ "widgetTitle": "App info", "openButton": "Open", "uninstallButton": "Uninstall", - "unpatchButton": "Unpatch", + "unmountButton": "Unmount", "rootDialogTitle": "Error", - "unpatchDialogText": "Are you sure you want to unpatch this app?", + "unmountDialogText": "Are you sure you want to unmount this app?", + "uninstallDialogText": "Are you sure you want to uninstall this app?", "rootDialogText": "App was installed with superuser permissions, but currently ReVanced Manager has no permissions.\nPlease grant superuser permissions first.", "packageNameLabel": "Package name", "installTypeLabel": "Installation type", - "rootTypeLabel": "Root", - "nonRootTypeLabel": "Non-root", + "mountTypeLabel": "Mount", + "regularTypeLabel": "Regular", "patchedDateLabel": "Patched date", "appliedPatchesLabel": "Applied patches", @@ -327,5 +330,34 @@ "integrationsContributors": "Integrations contributors", "cliContributors": "CLI contributors", "managerContributors": "Manager contributors" + }, + "installErrorDialog": { + "mount_version_mismatch": "Version mismatch", + "mount_no_root": "No root access", + "mount_missing_installation": "Installation not found", + + "status_failure_blocked": "Installation blocked", + "install_failed_verification_failure": "Verification failed", + "status_failure_invalid": "Installation invalid", + "install_failed_version_downgrade": "Can't downgrade", + "status_failure_conflict": "Installation conflict", + "status_failure_storage": "Installation storage issue", + "status_failure_incompatible": "Installation incompatible", + "status_failure_timeout": "Installation timeout", + "status_unknown": "Installation failed", + + "mount_version_mismatch_description": "The installation failed due to the installed app being a different version than the patched app.\n\nInstall the version of the app you are mounting and try again.", + "mount_no_root_description": "The installation failed due to root access not being granted.\n\nGrant root access to ReVanced Manager and try again.", + "mount_missing_installation_description": "The installation failed due to the unpatched app not being installed on this device in order to mount over it.\n\nInstall the unpatched app before mounting and try again.", + + "status_failure_timeout_description": "The installation took too long to finish.\n\nWould you like to try again?", + "status_failure_storage_description": "The installation failed due to insufficient storage.\n\nFree up some space and try again.", + "status_failure_invalid_description": "The installation failed due to the patched app being invalid.\n\nUninstall the app and try again?", + "status_failure_incompatible_description": "The app is incompatible with this device.\n\nContact the developer of the app and ask for support.", + "status_failure_conflict_description": "The installation was prevented by an existing installation of the app.\n\nUninstall the installed app and try again?", + "status_failure_blocked_description": "The installation was blocked by {packageName}.\n\nAdjust your security settings and try again.", + "install_failed_verification_failure_description": "The installation failed due to a verification issue.\n\nAdjust your security settings and try again.", + "install_failed_version_downgrade_description": "The installation failed due to the patched app being a lower version than the installed app.\n\nUninstall the app and try again?", + "status_unknown_description": "The installation failed due to an unknown reason. Please try again." } } diff --git a/docs/3_troubleshooting.md b/docs/3_troubleshooting.md index 3c1fe03b..1573e508 100644 --- a/docs/3_troubleshooting.md +++ b/docs/3_troubleshooting.md @@ -5,14 +5,16 @@ In case you encounter any issues while using ReVanced Manager, please refer to t - 💉 Patching fails with an error Make sure ReVanced Manager is up to date by following [🔄 Updating ReVanced Manager](2_3_updating.md) and select the **Default** button when choosing patches. - + - 🚫 App not installed as package conflicts with an existing package - An existing installation of the app you're trying to patch is conflicting with the patched app. Uninstall the existing app before installing the patched app. + An existing installation of the app you're trying to patch conflicts with the patched app (i.e., signature mismatch or downgrade). Uninstall the existing app before installing the patched app. - ❗️ Error code `135`, `139` or `1` when patching the app - Your device is not supported. Refer to the [Prerequisites](0_prerequisites.md) page for supported devices. + You may be trying to patch a split APK[^1]. This may not work under certain circumstances. In such a case, patch a full APK. + + Your device may otherwise be unsupported. Please look at the [Prerequisites](0_prerequisites.md) page for supported devices. Alternatively, you can use [ReVanced CLI](https://github.com/revanced/revanced-cli) to patch the app. @@ -25,3 +27,5 @@ In case you encounter any issues while using ReVanced Manager, please refer to t The next page will teach you how to build ReVanced Manager from source. Continue: [🔨 Building from source](4_building.md) + +[^1]: https://developer.android.com/guide/app-bundle/app-bundle-format diff --git a/lib/main.dart b/lib/main.dart index b38cb4c7..5b8df919 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:revanced_manager/services/download_manager.dart'; import 'package:revanced_manager/services/github_api.dart'; import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/revanced_api.dart'; +import 'package:revanced_manager/services/root_api.dart'; import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart'; import 'package:revanced_manager/ui/views/navigation/navigation_view.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -24,6 +25,13 @@ Future main() async { final String repoUrl = locator().getRepoUrl(); locator().initialize(repoUrl); tz.initializeTimeZones(); + + // TODO(aAbed): remove in the future, keep it for now during migration. + final rootAPI = RootAPI(); + if (await rootAPI.hasRootPermissions()) { + await rootAPI.removeOrphanedFiles(); + } + prefs = await SharedPreferences.getInstance(); runApp(const MyApp()); diff --git a/lib/models/patch.dart b/lib/models/patch.dart index ebcebf59..1b86ba5b 100644 --- a/lib/models/patch.dart +++ b/lib/models/patch.dart @@ -75,9 +75,8 @@ class Option { if (json['valueType'] == null) { final type = json['optionClassType']; if (type is String) { - json['valueType'] = type - .replaceAll('PatchOption', '') - .replaceAll('List', 'Array'); + json['valueType'] = + type.replaceAll('PatchOption', '').replaceAll('List', 'Array'); json['optionClassType'] = null; } diff --git a/lib/services/download_manager.dart b/lib/services/download_manager.dart index 4c0919b9..caa705d7 100644 --- a/lib/services/download_manager.dart +++ b/lib/services/download_manager.dart @@ -19,7 +19,8 @@ class DownloadManager { ); Future initialize() async { - _userAgent = 'ReVanced-Manager/${await _managerAPI.getCurrentManagerVersion()}'; + _userAgent = + 'ReVanced-Manager/${await _managerAPI.getCurrentManagerVersion()}'; } Dio initDio(String url) { @@ -72,4 +73,3 @@ class DownloadManager { ); } } - diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart index 7a9e091d..06d87f43 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -15,7 +15,6 @@ import 'package:revanced_manager/services/github_api.dart'; import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/services/revanced_api.dart'; import 'package:revanced_manager/services/root_api.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/utils/check_for_supported_patch.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:timeago/timeago.dart'; @@ -62,7 +61,8 @@ class ManagerAPI { Future initialize() async { _prefs = await SharedPreferences.getInstance(); isRooted = await _rootAPI.isRooted(); - isDynamicThemeAvailable = (await getSdkVersion()) >= 31; // ANDROID_12_SDK_VERSION = 31 + isDynamicThemeAvailable = + (await getSdkVersion()) >= 31; // ANDROID_12_SDK_VERSION = 31 storedPatchesFile = (await getApplicationDocumentsDirectory()).path + storedPatchesFile; } @@ -585,7 +585,6 @@ class ManagerAPI { builder: (context) => WillPopScope( onWillPop: () async => false, child: AlertDialog( - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, title: I18nText('warning'), content: ValueListenableBuilder( valueListenable: noShow, @@ -620,12 +619,12 @@ class ManagerAPI { }, ), actions: [ - CustomMaterialButton( - label: I18nText('okButton'), + FilledButton( onPressed: () { setPatchesChangeWarning(noShow.value); Navigator.of(context).pop(); }, + child: I18nText('okButton'), ), ], ), diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index f79d02e5..48a431e1 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -3,10 +3,11 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:device_apps/device_apps.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_file_dialog/flutter_file_dialog.dart'; +import 'package:flutter_i18n/widgets/I18nText.dart'; import 'package:injectable/injectable.dart'; -import 'package:install_plugin/install_plugin.dart'; import 'package:path_provider/path_provider.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/models/patch.dart'; @@ -18,7 +19,7 @@ import 'package:share_plus/share_plus.dart'; @lazySingleton class PatcherAPI { static const patcherChannel = - MethodChannel('app.revanced.manager.flutter/patcher'); + MethodChannel('app.revanced.manager.flutter/patcher'); final ManagerAPI _managerAPI = locator(); final RootAPI _rootAPI = RootAPI(); late Directory _dataDir; @@ -79,7 +80,8 @@ class PatcherAPI { } Future> getFilteredInstalledApps( - bool showUniversalPatches,) async { + bool showUniversalPatches, + ) async { final List filteredApps = []; final bool allAppsIncluded = _universalPatches.isNotEmpty && showUniversalPatches; @@ -121,11 +123,11 @@ class PatcherAPI { final List patches = _patches .where( (patch) => - patch.compatiblePackages.isEmpty || - !patch.name.contains('settings') && - patch.compatiblePackages - .any((pack) => pack.name == packageName), - ) + patch.compatiblePackages.isEmpty || + !patch.name.contains('settings') && + patch.compatiblePackages + .any((pack) => pack.name == packageName), + ) .toList(); if (!_managerAPI.areUniversalPatchesEnabled()) { filteredPatches[packageName] = patches @@ -137,22 +139,27 @@ class PatcherAPI { return filteredPatches[packageName]; } - Future> getAppliedPatches(List appliedPatches,) async { + Future> getAppliedPatches( + List appliedPatches, + ) async { return _patches .where((patch) => appliedPatches.contains(patch.name)) .toList(); } - Future runPatcher(String packageName, - String apkFilePath, - List selectedPatches,) async { + Future runPatcher( + String packageName, + String apkFilePath, + List selectedPatches, + ) async { final File? integrationsFile = await _managerAPI.downloadIntegrations(); final Map> options = {}; for (final patch in selectedPatches) { if (patch.options.isNotEmpty) { final Map patchOptions = {}; for (final option in patch.options) { - final patchOption = _managerAPI.getPatchOption(packageName, patch.name, option.key); + final patchOption = + _managerAPI.getPatchOption(packageName, patch.name, option.key); if (patchOption != null) { patchOptions[patchOption.key] = patchOption.value; } @@ -194,133 +201,314 @@ class PatcherAPI { } } } -} + } -Future stopPatcher() async { - try { - await patcherChannel.invokeMethod('stopPatcher'); - } on Exception catch (e) { - if (kDebugMode) { - print(e); + Future stopPatcher() async { + try { + await patcherChannel.invokeMethod('stopPatcher'); + } on Exception catch (e) { + if (kDebugMode) { + print(e); + } } } -} -Future installPatchedFile(PatchedApplication patchedApp) async { - if (outFile != null) { - try { - if (patchedApp.isRooted) { - final bool hasRootPermissions = await _rootAPI.hasRootPermissions(); - if (hasRootPermissions) { - return _rootAPI.installApp( - patchedApp.packageName, - patchedApp.apkFilePath, - outFile!.path, - ); + Future installPatchedFile( + BuildContext context, + PatchedApplication patchedApp, + ) async { + if (outFile != null) { + _managerAPI.ctx = context; + try { + if (patchedApp.isRooted) { + final bool hasRootPermissions = await _rootAPI.hasRootPermissions(); + final packageVersion = await DeviceApps.getApp(patchedApp.packageName) + .then((app) => app?.versionName); + if (!hasRootPermissions) { + installErrorDialog(1); + } else if (packageVersion == null) { + installErrorDialog(1.2); + } else if (packageVersion == patchedApp.version) { + return await _rootAPI.install( + patchedApp.packageName, + patchedApp.apkFilePath, + outFile!.path, + ) + ? 0 + : 1; + } else { + installErrorDialog(1.1); + } + } else { + if (await _rootAPI.hasRootPermissions()) { + await _rootAPI.uninstall(patchedApp.packageName); + } + if (context.mounted) { + return await installApk( + context, + outFile!.path, + ); + } } + } on Exception catch (e) { + if (kDebugMode) { + print(e); + } + } + } + return 1; + } + + Future installApk( + BuildContext context, + String apkPath, + ) async { + try { + final status = await patcherChannel.invokeMethod('installApk', { + 'apkPath': apkPath, + }); + final int statusCode = status['status']; + final String message = status['message']; + final bool hasExtra = + message.contains('INSTALL_FAILED_VERIFICATION_FAILURE') || + message.contains('INSTALL_FAILED_VERSION_DOWNGRADE'); + if (statusCode == 0 || (statusCode == 3 && !hasExtra)) { + return statusCode; } else { - final install = await InstallPlugin.installApk(outFile!.path); - return install['isSuccess']; + _managerAPI.ctx = context; + return await installErrorDialog( + statusCode, + status, + hasExtra, + ); } } on Exception catch (e) { if (kDebugMode) { print(e); } - return false; + return 3; } } - return false; -} -void exportPatchedFile(String appName, String version) { - try { - if (outFile != null) { - final String newName = _getFileName(appName, version); - FlutterFileDialog.saveFile( - params: SaveFileDialogParams( - sourceFilePath: outFile!.path, - fileName: newName, - mimeTypesFilter: ['application/vnd.android.package-archive'], - ), - ); - } - } on Exception catch (e) { - if (kDebugMode) { - print(e); - } - } -} - -void sharePatchedFile(String appName, String version) { - try { - if (outFile != null) { - final String newName = _getFileName(appName, version); - final int lastSeparator = outFile!.path.lastIndexOf('/'); - final String newPath = - outFile!.path.substring(0, lastSeparator + 1) + newName; - final File shareFile = outFile!.copySync(newPath); - Share.shareXFiles([XFile(shareFile.path)]); - } - } on Exception catch (e) { - if (kDebugMode) { - print(e); - } - } -} - -String _getFileName(String appName, String version) { - final String patchVersion = _managerAPI.patchesVersion!; - final String prefix = appName.toLowerCase().replaceAll(' ', '-'); - final String newName = '$prefix-revanced_v$version-patches_$patchVersion.apk'; - return newName; -} - -Future exportPatcherLog(String logs) async { - final Directory appCache = await getTemporaryDirectory(); - final Directory logDir = Directory('${appCache.path}/logs'); - logDir.createSync(); - final String dateTime = DateTime.now() - .toIso8601String() - .replaceAll('-', '') - .replaceAll(':', '') - .replaceAll('T', '') - .replaceAll('.', ''); - final String fileName = 'revanced-manager_patcher_$dateTime.txt'; - final File log = File('${logDir.path}/$fileName'); - log.writeAsStringSync(logs); - FlutterFileDialog.saveFile( - params:SaveFileDialogParams( - sourceFilePath: log.path, - fileName: fileName, - ), - ); -} - -String getSuggestedVersion(String packageName) { - final Map versions = {}; - for (final Patch patch in _patches) { - final Package? package = patch.compatiblePackages.firstWhereOrNull( - (pack) => pack.name == packageName, + Future installErrorDialog( + num statusCode, [ + status, + bool hasExtra = false, + ]) async { + final String statusValue = InstallStatus.byCode( + hasExtra ? double.parse('$statusCode.1') : statusCode, ); - if (package != null) { - for (final String version in package.versions) { - versions.update( - version, - (value) => versions[version]! + 1, - ifAbsent: () => 1, + bool cleanInstall = false; + final bool isFixable = statusCode == 4 || statusCode == 5; + await showDialog( + context: _managerAPI.ctx!, + builder: (context) => AlertDialog( + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + title: I18nText('installErrorDialog.$statusValue'), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + I18nText( + 'installErrorDialog.${statusValue}_description', + translationParams: statusCode == 2 + ? { + 'packageName': status['otherPackageName'], + } + : null, + ), + ], + ), + actions: (status == null) + ? [ + FilledButton( + onPressed: () async { + Navigator.pop(context); + }, + child: I18nText('okButton'), + ), + ] + : [ + if (!isFixable) + FilledButton( + onPressed: () { + Navigator.pop(context); + }, + child: I18nText('cancelButton'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: I18nText('cancelButton'), + ), + if (isFixable) + FilledButton( + onPressed: () async { + final int response = await patcherChannel.invokeMethod( + 'uninstallApp', + {'packageName': status['packageName']}, + ); + if (response == 0 && context.mounted) { + cleanInstall = true; + Navigator.pop(context); + } + }, + child: I18nText('okButton'), + ), + ], + ), + ); + return cleanInstall ? 10 : 1; + } + + void exportPatchedFile(String appName, String version) { + try { + if (outFile != null) { + final String newName = _getFileName(appName, version); + FlutterFileDialog.saveFile( + params: SaveFileDialogParams( + sourceFilePath: outFile!.path, + fileName: newName, + mimeTypesFilter: ['application/vnd.android.package-archive'], + ), ); } + } on Exception catch (e) { + if (kDebugMode) { + print(e); + } } } - if (versions.isNotEmpty) { - final entries = versions.entries.toList() - ..sort((a, b) => a.value.compareTo(b.value)); - versions - ..clear() - ..addEntries(entries); - versions.removeWhere((key, value) => value != versions.values.last); - return (versions.keys.toList() - ..sort()).last; + + void sharePatchedFile(String appName, String version) { + try { + if (outFile != null) { + final String newName = _getFileName(appName, version); + final int lastSeparator = outFile!.path.lastIndexOf('/'); + final String newPath = + outFile!.path.substring(0, lastSeparator + 1) + newName; + final File shareFile = outFile!.copySync(newPath); + Share.shareXFiles([XFile(shareFile.path)]); + } + } on Exception catch (e) { + if (kDebugMode) { + print(e); + } + } } - return ''; -}} + + String _getFileName(String appName, String version) { + final String patchVersion = _managerAPI.patchesVersion!; + final String prefix = appName.toLowerCase().replaceAll(' ', '-'); + final String newName = + '$prefix-revanced_v$version-patches_$patchVersion.apk'; + return newName; + } + + Future exportPatcherLog(String logs) async { + final Directory appCache = await getTemporaryDirectory(); + final Directory logDir = Directory('${appCache.path}/logs'); + logDir.createSync(); + final String dateTime = DateTime.now() + .toIso8601String() + .replaceAll('-', '') + .replaceAll(':', '') + .replaceAll('T', '') + .replaceAll('.', ''); + final String fileName = 'revanced-manager_patcher_$dateTime.txt'; + final File log = File('${logDir.path}/$fileName'); + log.writeAsStringSync(logs); + FlutterFileDialog.saveFile( + params: SaveFileDialogParams( + sourceFilePath: log.path, + fileName: fileName, + ), + ); + } + + String getSuggestedVersion(String packageName) { + final Map versions = {}; + for (final Patch patch in _patches) { + final Package? package = patch.compatiblePackages.firstWhereOrNull( + (pack) => pack.name == packageName, + ); + if (package != null) { + for (final String version in package.versions) { + versions.update( + version, + (value) => versions[version]! + 1, + ifAbsent: () => 1, + ); + } + } + } + if (versions.isNotEmpty) { + final entries = versions.entries.toList() + ..sort((a, b) => a.value.compareTo(b.value)); + versions + ..clear() + ..addEntries(entries); + versions.removeWhere((key, value) => value != versions.values.last); + return (versions.keys.toList()..sort()).last; + } + return ''; + } +} + +enum InstallStatus { + mountNoRoot(1), + mountVersionMismatch(1.1), + mountMissingInstallation(1.2), + + statusFailureBlocked(2), + installFailedVerificationFailure(3.1), + statusFailureInvalid(4), + installFailedVersionDowngrade(4.1), + statusFailureConflict(5), + statusFailureStorage(6), + statusFailureIncompatible(7), + statusFailureTimeout(8); + + const InstallStatus(this.statusCode); + final double statusCode; + + static String byCode(num code) { + try { + return InstallStatus.values + .firstWhere((flag) => flag.statusCode == code) + .status; + } catch (e) { + return 'status_unknown'; + } + } +} + +extension InstallStatusExtension on InstallStatus { + String get status { + switch (this) { + case InstallStatus.mountNoRoot: + return 'mount_no_root'; + case InstallStatus.mountVersionMismatch: + return 'mount_version_mismatch'; + case InstallStatus.mountMissingInstallation: + return 'mount_missing_installation'; + case InstallStatus.statusFailureBlocked: + return 'status_failure_blocked'; + case InstallStatus.installFailedVerificationFailure: + return 'install_failed_verification_failure'; + case InstallStatus.statusFailureInvalid: + return 'status_failure_invalid'; + case InstallStatus.installFailedVersionDowngrade: + return 'install_failed_version_downgrade'; + case InstallStatus.statusFailureConflict: + return 'status_failure_conflict'; + case InstallStatus.statusFailureStorage: + return 'status_failure_storage'; + case InstallStatus.statusFailureIncompatible: + return 'status_failure_incompatible'; + case InstallStatus.statusFailureTimeout: + return 'status_failure_timeout'; + } + } +} diff --git a/lib/services/root_api.dart b/lib/services/root_api.dart index f0c7d917..dd2090ab 100644 --- a/lib/services/root_api.dart +++ b/lib/services/root_api.dart @@ -2,10 +2,10 @@ import 'package:flutter/foundation.dart'; import 'package:root/root.dart'; class RootAPI { - // TODO(ponces): remove in the future, keep it for now during migration. - final String _revancedOldDirPath = '/data/local/tmp/revanced-manager'; - final String _revancedDirPath = '/data/adb/revanced'; + // TODO(aAbed): remove in the future, keep it for now during migration. final String _postFsDataDirPath = '/data/adb/post-fs-data.d'; + + final String _revancedDirPath = '/data/adb/revanced'; final String _serviceDDirPath = '/data/adb/service.d'; Future isRooted() async { @@ -43,21 +43,22 @@ class RootAPI { String filePath, ) async { try { + final StringBuffer commands = StringBuffer(); if (permissions.isNotEmpty) { - await Root.exec( - cmd: 'chmod $permissions "$filePath"', - ); + commands.writeln('chmod $permissions $filePath'); } + if (ownerGroup.isNotEmpty) { - await Root.exec( - cmd: 'chown $ownerGroup "$filePath"', - ); + commands.writeln('chown $ownerGroup $filePath'); } + if (seLinux.isNotEmpty) { - await Root.exec( - cmd: 'chcon $seLinux "$filePath"', - ); + commands.writeln('chcon $seLinux $filePath'); } + + await Root.exec( + cmd: commands.toString(), + ); } on Exception catch (e) { if (kDebugMode) { print(e); @@ -75,18 +76,7 @@ class RootAPI { Future> getInstalledApps() async { final List apps = List.empty(growable: true); try { - String? res = await Root.exec( - cmd: 'ls "$_revancedDirPath"', - ); - if (res != null) { - final List list = res.split('\n'); - list.removeWhere((pack) => pack.isEmpty); - apps.addAll(list.map((pack) => pack.trim()).toList()); - } - // TODO(ponces): remove in the future, keep it for now during migration. - res = await Root.exec( - cmd: 'ls "$_revancedOldDirPath"', - ); + final String? res = await Root.exec(cmd: 'ls $_revancedDirPath'); if (res != null) { final List list = res.split('\n'); list.removeWhere((pack) => pack.isEmpty); @@ -100,49 +90,45 @@ class RootAPI { return apps; } - Future deleteApp(String packageName, String originalFilePath) async { + Future uninstall(String packageName) async { await Root.exec( - cmd: 'am force-stop "$packageName"', - ); - await Root.exec( - cmd: 'su -mm -c "umount -l $originalFilePath"', - ); - // TODO(ponces): remove in the future, keep it for now during migration. - await Root.exec( - cmd: 'rm -rf "$_revancedOldDirPath/$packageName"', - ); - await Root.exec( - cmd: 'rm -rf "$_revancedDirPath/$packageName"', - ); - await Root.exec( - cmd: 'rm -rf "$_serviceDDirPath/$packageName.sh"', - ); - await Root.exec( - cmd: 'rm -rf "$_postFsDataDirPath/$packageName.sh"', + cmd: ''' + grep $packageName /proc/mounts | while read -r line; do echo \$line | cut -d " " -f 2 | sed "s/apk.*/apk/" | xargs -r umount -l; done + rm -rf $_revancedDirPath/$packageName $_serviceDDirPath/$packageName.sh + ''', ); } - Future installApp( + // TODO(aAbed): remove in the future, keep it for now during migration. + Future removeOrphanedFiles() async { + await Root.exec( + cmd: ''' + find $_revancedDirPath -type f -name original.apk -delete + for file in "$_serviceDDirPath"/*; do + filename=\$(basename "\$file") + if [ -f "$_postFsDataDirPath/\$filename" ]; then + rm "$_postFsDataDirPath/\$filename" + fi + done + ''', + ); + } + + Future install( String packageName, String originalFilePath, String patchedFilePath, ) async { try { - await deleteApp(packageName, originalFilePath); - await Root.exec( - cmd: 'mkdir -p "$_revancedDirPath/$packageName"', - ); await setPermissions( '0755', 'shell:shell', '', '$_revancedDirPath/$packageName', ); - await saveOriginalFilePath(packageName, originalFilePath); + await installPatchedApk(packageName, patchedFilePath); await installServiceDScript(packageName); - await installPostFsDataScript(packageName); - await installApk(packageName, patchedFilePath); - await mountApk(packageName, originalFilePath); + await runMountScript(packageName); return true; } on Exception catch (e) { if (kDebugMode) { @@ -154,38 +140,44 @@ class RootAPI { Future installServiceDScript(String packageName) async { await Root.exec( - cmd: 'mkdir -p "$_serviceDDirPath"', + cmd: 'mkdir -p $_serviceDDirPath', ); - final String content = '#!/system/bin/sh\n' - 'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n' - 'base_path=$_revancedDirPath/$packageName/base.apk\n' - 'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n' - r'[ ! -z $stock_path ] && mount -o bind $base_path $stock_path'; + final String mountScript = ''' + #!/system/bin/sh + MAGISKTMP="\$(magisk --path)" || MAGISKTMP=/sbin + MIRROR="\$MAGISKTMP/.magisk/mirror" + + until [ "\$(getprop sys.boot_completed)" = 1 ]; do sleep 3; done + until [ -d "/sdcard/Android" ]; do sleep 1; done + + # Unmount any existing installation to prevent multiple unnecessary mounts. + grep $packageName /proc/mounts | while read -r line; do echo \$line | cut -d " " -f 2 | sed "s/apk.*/apk/" | xargs -r umount -l; done + + base_path=$_revancedDirPath/$packageName/base.apk + stock_path=\$(pm path $packageName | grep base | sed "s/package://g" ) + + chcon u:object_r:apk_data_file:s0 \$base_path + mount -o bind \$MIRROR\$base_path \$stock_path + + # Kill the app to force it to restart the mounted APK in case it is already running + am force-stop $packageName + ''' + .trimMultilineString(); final String scriptFilePath = '$_serviceDDirPath/$packageName.sh'; await Root.exec( - cmd: 'echo \'$content\' > "$scriptFilePath"', + cmd: 'echo \'$mountScript\' > "$scriptFilePath"', ); await setPermissions('0744', '', '', scriptFilePath); } - Future installPostFsDataScript(String packageName) async { - await Root.exec( - cmd: 'mkdir -p "$_postFsDataDirPath"', - ); - final String content = '#!/system/bin/sh\n' - 'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n' - r'[ ! -z $stock_path ] && umount -l $stock_path'; - final String scriptFilePath = '$_postFsDataDirPath/$packageName.sh'; - await Root.exec( - cmd: 'echo \'$content\' > "$scriptFilePath"', - ); - await setPermissions('0744', '', '', scriptFilePath); - } - - Future installApk(String packageName, String patchedFilePath) async { + Future installPatchedApk( + String packageName, String patchedFilePath) async { final String newPatchedFilePath = '$_revancedDirPath/$packageName/base.apk'; await Root.exec( - cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"', + cmd: ''' + mkdir -p $_revancedDirPath/$packageName + cp "$patchedFilePath" $newPatchedFilePath + ''', ); await setPermissions( '0644', @@ -195,50 +187,10 @@ class RootAPI { ); } - Future mountApk(String packageName, String originalFilePath) async { - final String newPatchedFilePath = '$_revancedDirPath/$packageName/base.apk'; - await Root.exec( - cmd: 'am force-stop "$packageName"', - ); - await Root.exec( - cmd: 'su -mm -c "umount -l $originalFilePath"', - ); - await Root.exec( - cmd: 'su -mm -c "mount -o bind $newPatchedFilePath $originalFilePath"', - ); - } - - Future isMounted(String packageName) async { - final String? res = await Root.exec( - cmd: 'cat /proc/mounts | grep $packageName', - ); - return res != null && res.isNotEmpty; - } - - Future saveOriginalFilePath( + Future runMountScript( String packageName, - String originalFilePath, ) async { - final String originalRootPath = - '$_revancedDirPath/$packageName/original.apk'; - await Root.exec( - cmd: 'mkdir -p "$_revancedDirPath/$packageName"', - ); - await setPermissions( - '0755', - 'shell:shell', - '', - '$_revancedDirPath/$packageName', - ); - await Root.exec( - cmd: 'cp "$originalFilePath" "$originalRootPath"', - ); - await setPermissions( - '0644', - 'shell:shell', - 'u:object_r:apk_data_file:s0', - originalFilePath, - ); + await Root.exec(cmd: '.$_serviceDDirPath/$packageName.sh'); } Future fileExists(String path) async { @@ -255,3 +207,10 @@ class RootAPI { } } } + +// Remove leading spaces manually until +// https://github.com/dart-lang/language/issues/559 is closed +extension StringExtension on String { + String trimMultilineString() => + split('\n').map((line) => line.trim()).join('\n').trim(); +} diff --git a/lib/ui/theme/dynamic_theme_builder.dart b/lib/ui/theme/dynamic_theme_builder.dart index 8f9fc98b..9ec2d1bd 100644 --- a/lib/ui/theme/dynamic_theme_builder.dart +++ b/lib/ui/theme/dynamic_theme_builder.dart @@ -25,7 +25,8 @@ class DynamicThemeBuilder extends StatefulWidget { State createState() => _DynamicThemeBuilderState(); } -class _DynamicThemeBuilderState extends State with WidgetsBindingObserver { +class _DynamicThemeBuilderState extends State + with WidgetsBindingObserver { Brightness brightness = PlatformDispatcher.instance.platformBrightness; final ManagerAPI _managerAPI = locator(); @@ -43,8 +44,9 @@ class _DynamicThemeBuilderState extends State with WidgetsB if (_managerAPI.getThemeMode() < 2) { SystemChrome.setSystemUIOverlayStyle( SystemUiOverlayStyle( - systemNavigationBarIconBrightness: - brightness == Brightness.light ? Brightness.dark : Brightness.light, + systemNavigationBarIconBrightness: brightness == Brightness.light + ? Brightness.dark + : Brightness.light, ), ); } @@ -83,24 +85,31 @@ class _DynamicThemeBuilderState extends State with WidgetsB return DynamicTheme( themeCollection: ThemeCollection( themes: { - 0: brightness == Brightness.light ? lightCustomTheme : darkCustomTheme, - 1: brightness == Brightness.light ? lightDynamicTheme : darkDynamicTheme, + 0: brightness == Brightness.light + ? lightCustomTheme + : darkCustomTheme, + 1: brightness == Brightness.light + ? lightDynamicTheme + : darkDynamicTheme, 2: lightCustomTheme, 3: lightDynamicTheme, 4: darkCustomTheme, 5: darkDynamicTheme, }, - fallbackTheme: PlatformDispatcher.instance.platformBrightness == Brightness.light ? lightCustomTheme : darkCustomTheme, + fallbackTheme: PlatformDispatcher.instance.platformBrightness == + Brightness.light + ? lightCustomTheme + : darkCustomTheme, ), builder: (context, theme) => MaterialApp( - debugShowCheckedModeBanner: false, - title: widget.title, - navigatorKey: StackedService.navigatorKey, - onGenerateRoute: StackedRouter().onGenerateRoute, - theme: theme, - home: widget.home, - localizationsDelegates: widget.localizationsDelegates, - ), + debugShowCheckedModeBanner: false, + title: widget.title, + navigatorKey: StackedService.navigatorKey, + onGenerateRoute: StackedRouter().onGenerateRoute, + theme: theme, + home: widget.home, + localizationsDelegates: widget.localizationsDelegates, + ), ); }, ); diff --git a/lib/ui/views/app_selector/app_selector_view.dart b/lib/ui/views/app_selector/app_selector_view.dart index 09f6dacc..a4c60149 100644 --- a/lib/ui/views/app_selector/app_selector_view.dart +++ b/lib/ui/views/app_selector/app_selector_view.dart @@ -40,8 +40,8 @@ class _AppSelectorViewState extends State { ), titleTextStyle: TextStyle( fontSize: 22.0, - color: Theme.of(context).textTheme.titleLarge!.color, - ), + color: Theme.of(context).textTheme.titleLarge!.color, + ), leading: IconButton( icon: Icon( Icons.arrow_back, @@ -94,9 +94,7 @@ class _AppSelectorViewState extends State { ), child: Column( children: [ - ...model - .getFilteredApps(_query) - .map( + ...model.getFilteredApps(_query).map( (app) => InstalledAppItem( name: app.appName, pkgName: app.packageName, @@ -117,11 +115,8 @@ class _AppSelectorViewState extends State { packageName: app.packageName, ), ), - ) - , - ...model - .getFilteredAppsNames(_query) - .map( + ), + ...model.getFilteredAppsNames(_query).map( (app) => NotInstalledAppItem( name: app, patchesCount: model.patchesCount(app), @@ -135,8 +130,7 @@ class _AppSelectorViewState extends State { packageName: app, ), ), - ) - , + ), const SizedBox(height: 70.0), ], ), diff --git a/lib/ui/views/app_selector/app_selector_viewmodel.dart b/lib/ui/views/app_selector/app_selector_viewmodel.dart index 7961ceb9..5b94eee7 100644 --- a/lib/ui/views/app_selector/app_selector_viewmodel.dart +++ b/lib/ui/views/app_selector/app_selector_viewmodel.dart @@ -13,7 +13,6 @@ import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/utils/check_for_supported_patch.dart'; import 'package:stacked/stacked.dart'; @@ -105,7 +104,8 @@ class AppSelectorViewModel extends BaseViewModel { ]) async { final String suggestedVersion = getSuggestedVersion(application.packageName); - if (application.versionName != suggestedVersion && suggestedVersion.isNotEmpty) { + if (application.versionName != suggestedVersion && + suggestedVersion.isNotEmpty) { _managerAPI.suggestedAppVersionSelected = false; if (_managerAPI.isRequireSuggestedAppVersionEnabled() && context.mounted) { @@ -168,7 +168,6 @@ class AppSelectorViewModel extends BaseViewModel { return showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, title: I18nText('warning'), content: I18nText( 'appSelectorView.requireSuggestedAppVersionDialogText', @@ -185,9 +184,9 @@ class AppSelectorViewModel extends BaseViewModel { ), ), actions: [ - CustomMaterialButton( - label: I18nText('okButton'), + FilledButton( onPressed: () => Navigator.of(context).pop(), + child: I18nText('okButton'), ), ], ), @@ -232,12 +231,12 @@ class AppSelectorViewModel extends BaseViewModel { ), ), const SizedBox(height: 30), - CustomMaterialButton( + FilledButton( onPressed: () async { Navigator.pop(innerContext); await selectAppFromStorage(context); }, - label: Row( + child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.sd_card), @@ -247,12 +246,11 @@ class AppSelectorViewModel extends BaseViewModel { ), ), const SizedBox(height: 10), - CustomMaterialButton( - isFilled: false, + TextButton( onPressed: () { Navigator.pop(innerContext); }, - label: Row( + child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(width: 10), diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index b85e49da..c9356a56 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -8,7 +8,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:injectable/injectable.dart'; -import 'package:install_plugin/install_plugin.dart'; import 'package:path_provider/path_provider.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.router.dart'; @@ -21,7 +20,6 @@ import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/homeView/update_confirmation_dialog.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; @@ -54,7 +52,7 @@ class HomeViewModel extends BaseViewModel { _toast.showBottom('homeView.installingMessage'); final File? managerApk = await _managerAPI.downloadManager(); if (managerApk != null) { - await InstallPlugin.installApk(managerApk.path); + await _patcherAPI.installApk(context, managerApk.path); } else { _toast.showBottom('homeView.errorDownloadMessage'); } @@ -65,8 +63,8 @@ class HomeViewModel extends BaseViewModel { .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.requestNotificationsPermission(); - final bool isConnected = await Connectivity().checkConnectivity() != - ConnectivityResult.none; + final bool isConnected = + await Connectivity().checkConnectivity() != ConnectivityResult.none; if (!isConnected) { _toast.showBottom('homeView.noConnection'); } @@ -76,7 +74,7 @@ class HomeViewModel extends BaseViewModel { _toast.showBottom('homeView.installingMessage'); final File? managerApk = await _managerAPI.downloadManager(); if (managerApk != null) { - await InstallPlugin.installApk(managerApk.path); + await _patcherAPI.installApk(context, managerApk.path); } else { _toast.showBottom('homeView.errorDownloadMessage'); } @@ -223,21 +221,20 @@ class HomeViewModel extends BaseViewModel { }, ), actions: [ - CustomMaterialButton( - isFilled: false, + TextButton( onPressed: () async { await _managerAPI.setPatchesConsent(false); SystemNavigator.pop(); }, - label: I18nText('quitButton'), + child: I18nText('quitButton'), ), - CustomMaterialButton( + FilledButton( onPressed: () async { await _managerAPI.setPatchesConsent(true); await _managerAPI.setPatchesAutoUpdate(autoUpdate.value); Navigator.of(context).pop(); }, - label: I18nText('okButton'), + child: I18nText('okButton'), ), ], ), @@ -270,6 +267,7 @@ class HomeViewModel extends BaseViewModel { valueListenable: downloaded, builder: (context, value, child) { return SimpleDialog( + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, contentPadding: const EdgeInsets.all(16.0), title: I18nText( !value @@ -324,12 +322,12 @@ class HomeViewModel extends BaseViewModel { const SizedBox(height: 16.0), Align( alignment: Alignment.centerRight, - child: CustomMaterialButton( - label: I18nText('cancelButton'), + child: FilledButton( onPressed: () { _revancedAPI.disposeManagerUpdateProgress(); Navigator.of(context).pop(); }, + child: I18nText('cancelButton'), ), ), ], @@ -355,24 +353,24 @@ class HomeViewModel extends BaseViewModel { children: [ Align( alignment: Alignment.centerRight, - child: CustomMaterialButton( - isFilled: false, - label: I18nText('cancelButton'), + child: TextButton( onPressed: () { Navigator.of(context).pop(); }, + child: I18nText('cancelButton'), ), ), const SizedBox(width: 8.0), Align( alignment: Alignment.centerRight, - child: CustomMaterialButton( - label: I18nText('updateButton'), + child: FilledButton( onPressed: () async { - await InstallPlugin.installApk( + await _patcherAPI.installApk( + context, downloadedApk!.path, ); }, + child: I18nText('updateButton'), ), ), ], @@ -415,7 +413,7 @@ class HomeViewModel extends BaseViewModel { // UILocalNotificationDateInterpretation.absoluteTime, // ); _toast.showBottom('homeView.installingMessage'); - await InstallPlugin.installApk(managerApk.path); + await _patcherAPI.installApk(context, managerApk.path); } else { _toast.showBottom('homeView.errorDownloadMessage'); } diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index d16f1309..8919d062 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -13,8 +13,8 @@ import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/services/root_api.dart'; import 'package:revanced_manager/services/toast.dart'; +import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/utils/about_info.dart'; import 'package:screenshot_callback/screenshot_callback.dart'; import 'package:stacked/stacked.dart'; @@ -104,7 +104,7 @@ class InstallerViewModel extends BaseViewModel { isPatching = true; isInstalled = false; hasErrors = false; - } else if (value == 1.0) { + } else if (value == .85) { isPatching = false; hasErrors = false; await _managerAPI.savePatches( @@ -115,6 +115,7 @@ class InstallerViewModel extends BaseViewModel { } else if (value == -100.0) { isPatching = false; hasErrors = true; + progress = 0.0; } if (header.isNotEmpty) { headerLogs = header; @@ -127,10 +128,10 @@ class InstallerViewModel extends BaseViewModel { if (logs[logs.length - 1] == '\n') { logs = logs.substring(0, logs.length - 1); } - Future.delayed(const Duration(milliseconds: 500)).then((value) { + Future.delayed(const Duration(milliseconds: 100)).then((value) { scrollController.animateTo( scrollController.position.maxScrollExtent, - duration: const Duration(milliseconds: 200), + duration: const Duration(milliseconds: 100), curve: Curves.fastOutSlowIn, ); }); @@ -184,7 +185,9 @@ class InstallerViewModel extends BaseViewModel { final index = logLines.indexWhere((line) => line.endsWith(keyword)); if (newString != null && lineCount > 0) { logLines.insert( - index, newString.replaceAll('{lineCount}', lineCount.toString())); + index, + newString.replaceAll('{lineCount}', lineCount.toString()), + ); } logLines.removeWhere((lines) => lines.endsWith(keyword)); } @@ -199,18 +202,18 @@ class InstallerViewModel extends BaseViewModel { } } - String _formatPatches(List patches) { - if (patches.isEmpty) { - return 'None'; - } - return patches - .map((p) => - p.name + - (p.options.isEmpty - ? '' - : ' [${p.options.map((o) => '${o.title}: ${_getPatchOptionValue(p.name, o)}').join(", ")}]')) - .toList() - .join(', '); + String _formatPatches(List patches, String noneString) { + return patches.isEmpty + ? noneString + : patches.map((p) { + final optionsChanged = p.options + .where((o) => _getPatchOptionValue(p.name, o) != o.value) + .toList(); + return p.name + + (optionsChanged.isEmpty + ? '' + : ' [${optionsChanged.map((o) => '${o.title}: ${_getPatchOptionValue(p.name, o)}').join(", ")}]'); + }).join(', '); } String _getSuggestedVersion(String packageName) { @@ -236,23 +239,25 @@ class InstallerViewModel extends BaseViewModel { .getFilteredPatches(_app.packageName) .where((p) => !p.excluded) .toList(); - final patchesAdded = - _patches.where((p) => !defaultPatches.contains(p)).toList(); - final patchesRemoved = - defaultPatches.where((p) => !_patches.contains(p)).toList(); + final appliedPatchesNames = _patches.map((p) => p.name).toList(); - // Options changed - final patchesChanged = defaultPatches - .where((p) => - _patches.contains(p) && - p.options.any((o) => _getPatchOptionValue(p.name, o) != o.value)) + final patchesAdded = _patches.where((p) => p.excluded).toList(); + final patchesRemoved = defaultPatches + .where((p) => !appliedPatchesNames.contains(p.name)) + .map((p) => p.name) + .toList(); + final patchesOptionsChanged = defaultPatches + .where( + (p) => + appliedPatchesNames.contains(p.name) && + p.options.any((o) => _getPatchOptionValue(p.name, o) != o.value), + ) .toList(); // Add Info final formattedLogs = [ '- Device Info', 'ReVanced Manager: ${info['version']}', - 'Build: ${info['flavor']}', 'Model: ${info['model']}', 'Android version: ${info['androidVersion']}', 'Supported architectures: ${info['supportedArch'].join(", ")}', @@ -261,9 +266,9 @@ class InstallerViewModel extends BaseViewModel { '\n- Patch Info', 'App: ${_app.packageName} v${_app.version} (Suggested: ${_getSuggestedVersion(_app.packageName)})', 'Patches version: ${_managerAPI.patchesVersion}', - 'Patches added: ${_formatPatches(patchesAdded)}', - 'Patches removed: ${_formatPatches(patchesRemoved)}', - 'Options changed: ${_formatPatches(patchesChanged)}', // + 'Patches added: ${_formatPatches(patchesAdded, 'Default')}', + 'Patches removed: ${patchesRemoved.isEmpty ? 'None' : patchesRemoved.join(', ')}', + 'Default patch options changed: ${_formatPatches(patchesOptionsChanged, 'None')}', // '\n- Settings', 'Allow changing patch selection: ${_managerAPI.isPatchesChangeEnabled()}', @@ -287,26 +292,24 @@ class InstallerViewModel extends BaseViewModel { title: I18nText( 'warning', ), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, icon: const Icon(Icons.warning), content: SingleChildScrollView( child: I18nText('installerView.screenshotDetected'), ), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('noButton'), + TextButton( onPressed: () { Navigator.of(context).pop(); }, + child: I18nText('noButton'), ), - CustomMaterialButton( - label: I18nText('yesButton'), + FilledButton( onPressed: () { copyLogs(); showPopupScreenshotWarning = true; Navigator.of(context).pop(); }, + child: I18nText('yesButton'), ), ], ), @@ -319,11 +322,10 @@ class InstallerViewModel extends BaseViewModel { await showDialog( context: context, barrierDismissible: false, - builder: (context) => AlertDialog( + builder: (innerContext) => AlertDialog( title: I18nText( 'installerView.installType', ), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, icon: const Icon(Icons.file_download_outlined), contentPadding: const EdgeInsets.symmetric(vertical: 16), content: SingleChildScrollView( @@ -371,31 +373,68 @@ class InstallerViewModel extends BaseViewModel { installType.value = selected!; }, ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: I18nText( + 'installerView.warning', + child: Text( + '', + style: TextStyle( + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.error, + ), + ), + ), + ), ], ); }, ), ), actions: [ - CustomMaterialButton( - label: I18nText('cancelButton'), - isFilled: false, + TextButton( onPressed: () { - Navigator.of(context).pop(); + Navigator.of(innerContext).pop(); }, + child: I18nText('cancelButton'), ), - CustomMaterialButton( - label: I18nText('installerView.installButton'), + FilledButton( onPressed: () { - Navigator.of(context).pop(); + Navigator.of(innerContext).pop(); installResult(context, installType.value == 1); }, + child: I18nText('installerView.installButton'), ), ], ), ); } else { - installResult(context, false); + await showDialog( + context: context, + barrierDismissible: false, + builder: (innerContext) => AlertDialog( + title: I18nText( + 'warning', + ), + contentPadding: const EdgeInsets.all(16), + content: I18nText('installerView.warning'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(innerContext).pop(); + }, + child: I18nText('cancelButton'), + ), + FilledButton( + onPressed: () { + Navigator.of(innerContext).pop(); + installResult(context, false); + }, + child: I18nText('installerView.installButton'), + ), + ], + ), + ); } } @@ -416,15 +455,16 @@ class InstallerViewModel extends BaseViewModel { Future installResult(BuildContext context, bool installAsRoot) async { try { _app.isRooted = installAsRoot; - update( - 1.0, - 'Installing...', - _app.isRooted - ? 'Installing patched file using root method' - : 'Installing patched file using nonroot method', - ); - isInstalled = await _patcherAPI.installPatchedFile(_app); - if (isInstalled) { + if (headerLogs != 'Installing...') { + update( + .85, + 'Installing...', + _app.isRooted ? 'Mounting patched app' : 'Installing patched app', + ); + } + final int response = await _patcherAPI.installPatchedFile(context, _app); + if (response == 0) { + isInstalled = true; _app.isFromStorage = false; _app.patchDate = DateTime.now(); _app.appliedPatches = _patches.map((p) => p.name).toList(); @@ -439,10 +479,23 @@ class InstallerViewModel extends BaseViewModel { } await _managerAPI.savePatchedApp(_app); + await locator().initialize(context); - update(1.0, 'Installed!', 'Installed!'); + update(1.0, 'Installed', 'Installed'); + } else if (response == 3) { + update( + .85, + 'Installation canceled', + 'Installation canceled', + ); + } else if (response == 10) { + installResult(context, installAsRoot); } else { - // TODO(aabed): Show error message. + update( + .85, + 'Installation failed', + 'Installation failed', + ); } } on Exception catch (e) { if (kDebugMode) { diff --git a/lib/ui/views/patch_options/patch_options_view.dart b/lib/ui/views/patch_options/patch_options_view.dart index e35b849d..e6ac1bb2 100644 --- a/lib/ui/views/patch_options/patch_options_view.dart +++ b/lib/ui/views/patch_options/patch_options_view.dart @@ -4,7 +4,6 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/ui/views/patch_options/patch_options_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_options_fields.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:stacked/stacked.dart'; class PatchOptionsView extends StatelessWidget { @@ -82,8 +81,7 @@ class PatchOptionsView extends StatelessWidget { model.modifyOptions(value, option); }, ) - else if (option.valueType == - 'StringArray' || + else if (option.valueType == 'StringArray' || option.valueType == 'IntArray' || option.valueType == 'LongArray') IntStringLongListPatchOption( @@ -104,11 +102,11 @@ class PatchOptionsView extends StatelessWidget { const SizedBox( height: 8, ), - CustomMaterialButton( + FilledButton( onPressed: () { model.showAddOptionDialog(context); }, - label: Row( + child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.add), diff --git a/lib/ui/views/patch_options/patch_options_viewmodel.dart b/lib/ui/views/patch_options/patch_options_viewmodel.dart index 520fd9c6..2dbaef7b 100644 --- a/lib/ui/views/patch_options/patch_options_viewmodel.dart +++ b/lib/ui/views/patch_options/patch_options_viewmodel.dart @@ -6,7 +6,6 @@ import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_card.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:stacked/stacked.dart'; class PatchOptionsViewModel extends BaseViewModel { @@ -32,13 +31,11 @@ class PatchOptionsViewModel extends BaseViewModel { if (savedOptions.isNotEmpty) { visibleOptions = [ ...savedOptions, - ...options - .where( - (option) => - option.required && - !savedOptions.any((sOption) => sOption.key == option.key), - ) - , + ...options.where( + (option) => + option.required && + !savedOptions.any((sOption) => sOption.key == option.key), + ), ]; } else { visibleOptions = [ @@ -136,7 +133,6 @@ class PatchOptionsViewModel extends BaseViewModel { await showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, title: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -154,11 +150,11 @@ class PatchOptionsViewModel extends BaseViewModel { ], ), actions: [ - CustomMaterialButton( - label: I18nText('cancelButton'), + FilledButton( onPressed: () { Navigator.of(context).pop(); }, + child: I18nText('cancelButton'), ), ], contentPadding: const EdgeInsets.all(8), @@ -227,14 +223,9 @@ Future showRequiredOptionNullDialog( await showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, title: I18nText('notice'), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText( - 'patchOptionsView.deselectPatch', - ), + TextButton( onPressed: () async { if (managerAPI.isPatchesChangeEnabled()) { locator() @@ -256,12 +247,13 @@ Future showRequiredOptionNullDialog( PatchesSelectorViewModel().showPatchesChangeDialog(context); } }, + child: I18nText('patchOptionsView.deselectPatch'), ), - CustomMaterialButton( - label: I18nText('okButton'), + FilledButton( onPressed: () { Navigator.of(context).pop(); }, + child: I18nText('okButton'), ), ], content: I18nText( diff --git a/lib/ui/views/patcher/patcher_view.dart b/lib/ui/views/patcher/patcher_view.dart index 0921bb1d..9def6c05 100644 --- a/lib/ui/views/patcher/patcher_view.dart +++ b/lib/ui/views/patcher/patcher_view.dart @@ -22,7 +22,7 @@ class PatcherView extends StatelessWidget { child: FloatingActionButton.extended( label: I18nText('patcherView.patchButton'), icon: const Icon(Icons.build), - onPressed: () async{ + onPressed: () async { if (model.checkRequiredPatchOption(context)) { final bool proceed = model.showRemovedPatchesDialog(context); if (proceed && context.mounted) { diff --git a/lib/ui/views/patcher/patcher_viewmodel.dart b/lib/ui/views/patcher/patcher_viewmodel.dart index 480babec..2c3940fd 100644 --- a/lib/ui/views/patcher/patcher_viewmodel.dart +++ b/lib/ui/views/patcher/patcher_viewmodel.dart @@ -13,7 +13,6 @@ import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/patcher_api.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/utils/about_info.dart'; import 'package:revanced_manager/utils/check_for_supported_patch.dart'; import 'package:stacked/stacked.dart'; @@ -56,25 +55,23 @@ class PatcherViewModel extends BaseViewModel { context: context, builder: (context) => AlertDialog( title: I18nText('notice'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText( 'patcherView.removedPatchesWarningDialogText', translationParams: {'patches': removedPatches.join('\n')}, ), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('noButton'), + TextButton( onPressed: () { Navigator.of(context).pop(); }, + child: I18nText('noButton'), ), - CustomMaterialButton( - label: I18nText('yesButton'), + FilledButton( onPressed: () { Navigator.of(context).pop(); showArmv7WarningDialog(context); }, + child: I18nText('yesButton'), ), ], ), @@ -98,22 +95,20 @@ class PatcherViewModel extends BaseViewModel { context: context ?? ctx, builder: (context) => AlertDialog( title: I18nText('notice'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText('patcherView.requiredOptionDialogText'), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('cancelButton'), + TextButton( onPressed: () => { Navigator.of(context).pop(), }, + child: I18nText('cancelButton'), ), - CustomMaterialButton( - label: I18nText('okButton'), + FilledButton( onPressed: () => { Navigator.pop(context), navigateToPatchesSelector(), }, + child: I18nText('okButton'), ), ], ), @@ -131,20 +126,18 @@ class PatcherViewModel extends BaseViewModel { context: context, builder: (context) => AlertDialog( title: I18nText('warning'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText('patcherView.armv7WarningDialogText'), actions: [ - CustomMaterialButton( - label: I18nText('noButton'), + FilledButton( onPressed: () => Navigator.of(context).pop(), + child: I18nText('noButton'), ), - CustomMaterialButton( - label: I18nText('yesButton'), - isFilled: false, + TextButton( onPressed: () { Navigator.of(context).pop(); navigateToInstaller(); }, + child: I18nText('yesButton'), ), ], ), diff --git a/lib/ui/views/patches_selector/patches_selector_view.dart b/lib/ui/views/patches_selector/patches_selector_view.dart index 29489078..71e90d79 100644 --- a/lib/ui/views/patches_selector/patches_selector_view.dart +++ b/lib/ui/views/patches_selector/patches_selector_view.dart @@ -180,11 +180,16 @@ class _PatchesSelectorViewState extends State { ), ], ), - if (model.getQueriedPatches(_query).any((patch) => model.isPatchNew(patch))) + if (model + .getQueriedPatches(_query) + .any((patch) => model.isPatchNew(patch))) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - model.getPatchCategory(context, 'patchesSelectorView.newPatches'), + model.getPatchCategory( + context, + 'patchesSelectorView.newPatches', + ), ...model.getQueriedPatches(_query).map((patch) { if (model.isPatchNew(patch)) { return model.getPatchItem(context, patch); @@ -192,26 +197,40 @@ class _PatchesSelectorViewState extends State { return Container(); } }), - if (model.getQueriedPatches(_query).any((patch) => !model.isPatchNew(patch) && patch.compatiblePackages.isNotEmpty)) - model.getPatchCategory(context, 'patchesSelectorView.patches'), + if (model.getQueriedPatches(_query).any( + (patch) => + !model.isPatchNew(patch) && + patch.compatiblePackages.isNotEmpty, + )) + model.getPatchCategory( + context, + 'patchesSelectorView.patches', + ), ], ), ...model.getQueriedPatches(_query).map( (patch) { - if (patch.compatiblePackages.isNotEmpty && !model.isPatchNew(patch)) { + if (patch.compatiblePackages.isNotEmpty && + !model.isPatchNew(patch)) { return model.getPatchItem(context, patch); } else { return Container(); } }, ), - if (model.getQueriedPatches(_query).any((patch) => patch.compatiblePackages.isEmpty)) + if (model + .getQueriedPatches(_query) + .any((patch) => patch.compatiblePackages.isEmpty)) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - model.getPatchCategory(context, 'patchesSelectorView.universalPatches'), + model.getPatchCategory( + context, + 'patchesSelectorView.universalPatches', + ), ...model.getQueriedPatches(_query).map((patch) { - if (patch.compatiblePackages.isEmpty && !model.isPatchNew(patch)) { + if (patch.compatiblePackages.isEmpty && + !model.isPatchNew(patch)) { return model.getPatchItem(context, patch); } else { return Container(); diff --git a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart index 71d073a9..82f330b5 100644 --- a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart +++ b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart @@ -10,7 +10,6 @@ import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/utils/check_for_supported_patch.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; @@ -94,7 +93,6 @@ class PatchesSelectorViewModel extends BaseViewModel { context: context, builder: (context) => AlertDialog( title: I18nText('notice'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText( 'patchesSelectorView.setRequiredOption', translationParams: { @@ -102,11 +100,11 @@ class PatchesSelectorViewModel extends BaseViewModel { }, ), actions: [ - CustomMaterialButton( - label: I18nText('okButton'), + FilledButton( onPressed: () => { Navigator.of(context).pop(), }, + child: I18nText('okButton'), ), ], ), @@ -130,7 +128,6 @@ class PatchesSelectorViewModel extends BaseViewModel { return showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, title: I18nText('warning'), content: I18nText( 'patchItem.patchesChangeWarningDialogText', @@ -143,18 +140,17 @@ class PatchesSelectorViewModel extends BaseViewModel { ), ), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('okButton'), + TextButton( onPressed: () => Navigator.of(context).pop(), + child: I18nText('okButton'), ), - CustomMaterialButton( - label: I18nText('patchItem.patchesChangeWarningDialogButton'), + FilledButton( onPressed: () { Navigator.of(context) ..pop() ..pop(); }, + child: I18nText('patchItem.patchesChangeWarningDialogButton'), ), ], ), @@ -188,10 +184,6 @@ class PatchesSelectorViewModel extends BaseViewModel { void selectPatches() { locator().selectedPatches = selectedPatches; saveSelectedPatches(); - if (_managerAPI.ctx != null) { - Navigator.pop(_managerAPI.ctx!); - _managerAPI.ctx = null; - } locator().notifyListeners(); } diff --git a/lib/ui/views/settings/settingsFragment/settings_manage_api_url.dart b/lib/ui/views/settings/settingsFragment/settings_manage_api_url.dart index 6fa1ccc1..5669a073 100644 --- a/lib/ui/views/settings/settingsFragment/settings_manage_api_url.dart +++ b/lib/ui/views/settings/settingsFragment/settings_manage_api_url.dart @@ -7,7 +7,6 @@ import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:stacked/stacked.dart'; class SManageApiUrl extends BaseViewModel { @@ -33,7 +32,6 @@ class SManageApiUrl extends BaseViewModel { ), ], ), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: SingleChildScrollView( child: Column( children: [ @@ -51,16 +49,14 @@ class SManageApiUrl extends BaseViewModel { ), ), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('cancelButton'), + TextButton( onPressed: () { _apiUrlController.clear(); Navigator.of(context).pop(); }, + child: I18nText('cancelButton'), ), - CustomMaterialButton( - label: I18nText('okButton'), + FilledButton( onPressed: () { String apiUrl = _apiUrlController.text; if (!apiUrl.startsWith('https')) { @@ -70,6 +66,7 @@ class SManageApiUrl extends BaseViewModel { _toast.showBottom('settingsView.restartAppForChanges'); Navigator.of(context).pop(); }, + child: I18nText('okButton'), ), ], ), @@ -81,16 +78,13 @@ class SManageApiUrl extends BaseViewModel { context: context, builder: (context) => AlertDialog( title: I18nText('settingsView.sourcesResetDialogTitle'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText('settingsView.apiURLResetDialogText'), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('noButton'), + TextButton( onPressed: () => Navigator.of(context).pop(), + child: I18nText('noButton'), ), - CustomMaterialButton( - label: I18nText('yesButton'), + FilledButton( onPressed: () { _managerAPI.setApiUrl(''); _toast.showBottom('settingsView.restartAppForChanges'); @@ -98,6 +92,7 @@ class SManageApiUrl extends BaseViewModel { ..pop() ..pop(); }, + child: I18nText('yesButton'), ), ], ), diff --git a/lib/ui/views/settings/settingsFragment/settings_manage_keystore_password.dart b/lib/ui/views/settings/settingsFragment/settings_manage_keystore_password.dart index 4ac4689b..fb717f64 100644 --- a/lib/ui/views/settings/settingsFragment/settings_manage_keystore_password.dart +++ b/lib/ui/views/settings/settingsFragment/settings_manage_keystore_password.dart @@ -6,7 +6,6 @@ import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:stacked/stacked.dart'; class SManageKeystorePassword extends BaseViewModel { @@ -33,7 +32,6 @@ class SManageKeystorePassword extends BaseViewModel { ), ], ), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: SingleChildScrollView( child: Column( children: [ @@ -47,21 +45,20 @@ class SManageKeystorePassword extends BaseViewModel { ), ), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('cancelButton'), + TextButton( onPressed: () { _keystorePasswordController.clear(); Navigator.of(context).pop(); }, + child: I18nText('cancelButton'), ), - CustomMaterialButton( - label: I18nText('okButton'), + FilledButton( onPressed: () { final String passwd = _keystorePasswordController.text; _managerAPI.setKeystorePassword(passwd); Navigator.of(context).pop(); }, + child: I18nText('okButton'), ), ], ), diff --git a/lib/ui/views/settings/settingsFragment/settings_manage_sources.dart b/lib/ui/views/settings/settingsFragment/settings_manage_sources.dart index 76e3171b..52c30ff0 100644 --- a/lib/ui/views/settings/settingsFragment/settings_manage_sources.dart +++ b/lib/ui/views/settings/settingsFragment/settings_manage_sources.dart @@ -7,7 +7,6 @@ import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:stacked/stacked.dart'; class SManageSources extends BaseViewModel { @@ -43,7 +42,6 @@ class SManageSources extends BaseViewModel { ), ], ), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: SingleChildScrollView( child: Column( children: [ @@ -107,9 +105,7 @@ class SManageSources extends BaseViewModel { ), ), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('cancelButton'), + TextButton( onPressed: () { _orgPatSourceController.clear(); _patSourceController.clear(); @@ -117,9 +113,9 @@ class SManageSources extends BaseViewModel { _intSourceController.clear(); Navigator.of(context).pop(); }, + child: I18nText('cancelButton'), ), - CustomMaterialButton( - label: I18nText('okButton'), + FilledButton( onPressed: () { _managerAPI.setRepoUrl(_hostSourceController.text.trim()); _managerAPI.setPatchesRepo( @@ -133,6 +129,7 @@ class SManageSources extends BaseViewModel { _toast.showBottom('settingsView.restartAppForChanges'); Navigator.of(context).pop(); }, + child: I18nText('okButton'), ), ], ), @@ -144,16 +141,13 @@ class SManageSources extends BaseViewModel { context: context, builder: (context) => AlertDialog( title: I18nText('settingsView.sourcesResetDialogTitle'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText('settingsView.sourcesResetDialogText'), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('noButton'), + TextButton( onPressed: () => Navigator.of(context).pop(), + child: I18nText('noButton'), ), - CustomMaterialButton( - label: I18nText('yesButton'), + FilledButton( onPressed: () { _managerAPI.setRepoUrl(''); _managerAPI.setPatchesRepo(''); @@ -165,6 +159,7 @@ class SManageSources extends BaseViewModel { ..pop() ..pop(); }, + child: I18nText('yesButton'), ), ], ), diff --git a/lib/ui/views/settings/settingsFragment/settings_update_language.dart b/lib/ui/views/settings/settingsFragment/settings_update_language.dart index 66bb2c3e..1e7b4a72 100644 --- a/lib/ui/views/settings/settingsFragment/settings_update_language.dart +++ b/lib/ui/views/settings/settingsFragment/settings_update_language.dart @@ -51,7 +51,6 @@ class SUpdateLanguage extends BaseViewModel { context: parentContext, builder: (context) => SimpleDialog( title: I18nText('settingsView.languageLabel'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, children: [ SizedBox( height: 500, diff --git a/lib/ui/views/settings/settingsFragment/settings_update_theme.dart b/lib/ui/views/settings/settingsFragment/settings_update_theme.dart index 66fa6830..09c1b28b 100644 --- a/lib/ui/views/settings/settingsFragment/settings_update_theme.dart +++ b/lib/ui/views/settings/settingsFragment/settings_update_theme.dart @@ -7,7 +7,6 @@ import 'package:flutter_i18n/widgets/I18nText.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; class SUpdateThemeUI extends StatefulWidget { const SUpdateThemeUI({super.key}); @@ -36,9 +35,9 @@ class _SUpdateThemeUIState extends State { ), ), ), - trailing: CustomMaterialButton( - label: getThemeModeName(), + trailing: FilledButton( onPressed: () => {showThemeDialog(context)}, + child: getThemeModeName(), ), onTap: () => {showThemeDialog(context)}, ), @@ -122,7 +121,6 @@ class _SUpdateThemeUIState extends State { title: I18nText('settingsView.themeModeLabel'), icon: const Icon(Icons.palette), contentPadding: const EdgeInsets.symmetric(vertical: 16), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: SingleChildScrollView( child: ValueListenableBuilder( valueListenable: newTheme, @@ -164,19 +162,18 @@ class _SUpdateThemeUIState extends State { ), ), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('cancelButton'), + TextButton( onPressed: () { Navigator.of(context).pop(); }, + child: I18nText('cancelButton'), ), - CustomMaterialButton( - label: I18nText('okButton'), + FilledButton( onPressed: () { setThemeMode(context, newTheme.value); Navigator.of(context).pop(); }, + child: I18nText('okButton'), ), ], ), diff --git a/lib/ui/views/settings/settings_viewmodel.dart b/lib/ui/views/settings/settings_viewmodel.dart index e51b1382..6fa85730 100644 --- a/lib/ui/views/settings/settings_viewmodel.dart +++ b/lib/ui/views/settings/settings_viewmodel.dart @@ -12,7 +12,6 @@ import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart'; import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_language.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:share_plus/share_plus.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; @@ -52,7 +51,6 @@ class SettingsViewModel extends BaseViewModel { return showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, title: I18nText('warning'), content: I18nText( 'settingsView.enablePatchesSelectionWarningText', @@ -65,20 +63,19 @@ class SettingsViewModel extends BaseViewModel { ), ), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('yesButton'), + TextButton( onPressed: () { _managerAPI.setChangingToggleModified(true); _managerAPI.setPatchesChangeEnabled(true); Navigator.of(context).pop(); }, + child: I18nText('yesButton'), ), - CustomMaterialButton( - label: I18nText('noButton'), + FilledButton( onPressed: () { Navigator.of(context).pop(); }, + child: I18nText('noButton'), ), ], ), @@ -87,7 +84,6 @@ class SettingsViewModel extends BaseViewModel { return showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, title: I18nText('warning'), content: I18nText( 'settingsView.disablePatchesSelectionWarningText', @@ -100,21 +96,20 @@ class SettingsViewModel extends BaseViewModel { ), ), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('noButton'), + TextButton( onPressed: () { Navigator.of(context).pop(); }, + child: I18nText('noButton'), ), - CustomMaterialButton( - label: I18nText('yesButton'), + FilledButton( onPressed: () { _managerAPI.setChangingToggleModified(true); _patchesSelectorViewModel.selectDefaultPatches(); _managerAPI.setPatchesChangeEnabled(false); Navigator.of(context).pop(); }, + child: I18nText('yesButton'), ), ], ), @@ -145,12 +140,13 @@ class SettingsViewModel extends BaseViewModel { } Future? showRequireSuggestedAppVersionDialog( - BuildContext context, bool value,) { + BuildContext context, + bool value, + ) { if (!value) { return showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, title: I18nText('warning'), content: I18nText( 'settingsView.requireSuggestedAppVersionDialogText', @@ -163,19 +159,18 @@ class SettingsViewModel extends BaseViewModel { ), ), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('yesButton'), + TextButton( onPressed: () { _managerAPI.enableRequireSuggestedAppVersionStatus(false); Navigator.of(context).pop(); }, + child: I18nText('yesButton'), ), - CustomMaterialButton( - label: I18nText('noButton'), + FilledButton( onPressed: () { Navigator.of(context).pop(); }, + child: I18nText('noButton'), ), ], ), @@ -210,7 +205,7 @@ class SettingsViewModel extends BaseViewModel { final String dateTime = DateTime.now().toString().replaceAll(' ', '_').split('.').first; await FlutterFileDialog.saveFile( - params: SaveFileDialogParams( + params: SaveFileDialogParams( sourceFilePath: outFile.path, fileName: 'selected_patches_$dateTime.json', ), @@ -261,7 +256,7 @@ class SettingsViewModel extends BaseViewModel { final String dateTime = DateTime.now().toString().replaceAll(' ', '_').split('.').first; await FlutterFileDialog.saveFile( - params: SaveFileDialogParams( + params: SaveFileDialogParams( sourceFilePath: outFile.path, fileName: 'keystore_$dateTime.keystore', ), diff --git a/lib/ui/widgets/appInfoView/app_info_view.dart b/lib/ui/widgets/appInfoView/app_info_view.dart index 0e7bed3d..da8cf7ba 100644 --- a/lib/ui/widgets/appInfoView/app_info_view.dart +++ b/lib/ui/widgets/appInfoView/app_info_view.dart @@ -184,7 +184,7 @@ class AppInfoView extends StatelessWidget { ), const SizedBox(height: 10), I18nText( - 'appInfoView.unpatchButton', + 'appInfoView.unmountButton', child: Text( '', style: TextStyle( @@ -236,8 +236,8 @@ class AppInfoView extends StatelessWidget { ), ), subtitle: app.isRooted - ? I18nText('appInfoView.rootTypeLabel') - : I18nText('appInfoView.nonRootTypeLabel'), + ? I18nText('appInfoView.mountTypeLabel') + : I18nText('appInfoView.regularTypeLabel'), ), const SizedBox(height: 4), ListTile( diff --git a/lib/ui/widgets/appInfoView/app_info_viewmodel.dart b/lib/ui/widgets/appInfoView/app_info_viewmodel.dart index 32441567..f3b70d31 100644 --- a/lib/ui/widgets/appInfoView/app_info_viewmodel.dart +++ b/lib/ui/widgets/appInfoView/app_info_viewmodel.dart @@ -12,7 +12,6 @@ import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:stacked/stacked.dart'; class AppInfoViewModel extends BaseViewModel { @@ -26,18 +25,17 @@ class AppInfoViewModel extends BaseViewModel { PatchedApplication app, bool onlyUnpatch, ) async { - bool isUninstalled = true; - if (app.isRooted) { - final bool hasRootPermissions = await _rootAPI.hasRootPermissions(); - if (hasRootPermissions) { - await _rootAPI.deleteApp(app.packageName, app.apkFilePath); - if (!onlyUnpatch) { - await DeviceApps.uninstallApp(app.packageName); - } - } - } else { + var isUninstalled = onlyUnpatch; + + if (!onlyUnpatch) { + // TODO(Someone): Wait for the app to uninstall successfully. isUninstalled = await DeviceApps.uninstallApp(app.packageName); } + + if (isUninstalled && app.isRooted && await _rootAPI.hasRootPermissions()) { + await _rootAPI.uninstall(app.packageName); + } + if (isUninstalled) { await _managerAPI.deletePatchedApp(app); locator().initialize(context); @@ -67,12 +65,11 @@ class AppInfoViewModel extends BaseViewModel { context: context, builder: (context) => AlertDialog( title: I18nText('appInfoView.rootDialogTitle'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText('appInfoView.rootDialogText'), actions: [ - CustomMaterialButton( - label: I18nText('okButton'), + FilledButton( onPressed: () => Navigator.of(context).pop(), + child: I18nText('okButton'), ), ], ), @@ -83,32 +80,53 @@ class AppInfoViewModel extends BaseViewModel { context: context, builder: (context) => AlertDialog( title: I18nText( - 'appInfoView.unpatchButton', + 'appInfoView.unmountButton', ), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText( - 'appInfoView.unpatchDialogText', + 'appInfoView.unmountDialogText', ), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('noButton'), + TextButton( onPressed: () => Navigator.of(context).pop(), + child: I18nText('noButton'), ), - CustomMaterialButton( - label: I18nText('yesButton'), + FilledButton( onPressed: () { - uninstallApp(context, app, onlyUnpatch); + uninstallApp(context, app, true); Navigator.of(context).pop(); Navigator.of(context).pop(); }, + child: I18nText('yesButton'), ), ], ), ); } else { - uninstallApp(context, app, onlyUnpatch); - Navigator.of(context).pop(); + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: I18nText( + 'appInfoView.uninstallButton', + ), + content: I18nText( + 'appInfoView.uninstallDialogText', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: I18nText('noButton'), + ), + FilledButton( + onPressed: () { + uninstallApp(context, app, false); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }, + child: I18nText('yesButton'), + ), + ], + ), + ); } } } @@ -131,14 +149,13 @@ class AppInfoViewModel extends BaseViewModel { context: context, builder: (context) => AlertDialog( title: I18nText('appInfoView.appliedPatchesLabel'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: SingleChildScrollView( child: Text(getAppliedPatchesString(app.appliedPatches)), ), actions: [ - CustomMaterialButton( - label: I18nText('okButton'), + FilledButton( onPressed: () => Navigator.of(context).pop(), + child: I18nText('okButton'), ), ], ), diff --git a/lib/ui/widgets/homeView/latest_commit_card.dart b/lib/ui/widgets/homeView/latest_commit_card.dart index 9d0625ce..55252698 100644 --- a/lib/ui/widgets/homeView/latest_commit_card.dart +++ b/lib/ui/widgets/homeView/latest_commit_card.dart @@ -3,7 +3,6 @@ import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_card.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; class LatestCommitCard extends StatefulWidget { const LatestCommitCard({ @@ -58,14 +57,14 @@ class _LatestCommitCardState extends State { initialData: false, builder: (context, snapshot) => Opacity( opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25, - child: CustomMaterialButton( - label: I18nText('updateButton'), + child: FilledButton( onPressed: snapshot.hasData && snapshot.data! ? () => widget.model.showUpdateConfirmationDialog( widget.parentContext, false, ) : () => {}, + child: I18nText('updateButton'), ), ), ), @@ -113,14 +112,14 @@ class _LatestCommitCardState extends State { initialData: false, builder: (context, snapshot) => Opacity( opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25, - child: CustomMaterialButton( - label: I18nText('updateButton'), + child: FilledButton( onPressed: snapshot.hasData && snapshot.data! ? () => widget.model.showUpdateConfirmationDialog( widget.parentContext, true, ) : () => {}, + child: I18nText('updateButton'), ), ), ), diff --git a/lib/ui/widgets/homeView/update_confirmation_dialog.dart b/lib/ui/widgets/homeView/update_confirmation_dialog.dart index 7839536a..de5b72a0 100644 --- a/lib/ui/widgets/homeView/update_confirmation_dialog.dart +++ b/lib/ui/widgets/homeView/update_confirmation_dialog.dart @@ -3,7 +3,6 @@ import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; class UpdateConfirmationDialog extends StatelessWidget { const UpdateConfirmationDialog({super.key, required this.isPatches}); @@ -86,15 +85,14 @@ class UpdateConfirmationDialog extends StatelessWidget { ], ), ), - CustomMaterialButton( - isExpanded: true, - label: I18nText('updateButton'), + FilledButton( onPressed: () { Navigator.of(context).pop(); isPatches ? model.updatePatches(context) : model.updateManager(context); }, + child: I18nText('updateButton'), ), ], ), diff --git a/lib/ui/widgets/patcherView/patch_selector_card.dart b/lib/ui/widgets/patcherView/patch_selector_card.dart index ceea41c9..9dd48686 100644 --- a/lib/ui/widgets/patcherView/patch_selector_card.dart +++ b/lib/ui/widgets/patcherView/patch_selector_card.dart @@ -58,7 +58,8 @@ class PatchSelectorCard extends StatelessWidget { String _getPatchesSelection() { String text = ''; - final List selectedPatches = locator().selectedPatches; + final List selectedPatches = + locator().selectedPatches; selectedPatches.sort((a, b) => a.name.compareTo(b.name)); for (final Patch p in selectedPatches) { text += '• ${p.getSimpleName()}\n'; diff --git a/lib/ui/widgets/patchesSelectorView/patch_item.dart b/lib/ui/widgets/patchesSelectorView/patch_item.dart index 11fd0839..70692ab2 100644 --- a/lib/ui/widgets/patchesSelectorView/patch_item.dart +++ b/lib/ui/widgets/patchesSelectorView/patch_item.dart @@ -5,7 +5,6 @@ import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_card.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; // ignore: must_be_immutable class PatchItem extends StatefulWidget { @@ -216,7 +215,6 @@ class _PatchItemState extends State { context: context, builder: (context) => AlertDialog( title: I18nText('warning'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText( 'patchItem.unsupportedDialogText', translationParams: { @@ -226,9 +224,9 @@ class _PatchItemState extends State { }, ), actions: [ - CustomMaterialButton( - label: I18nText('okButton'), + FilledButton( onPressed: () => Navigator.of(context).pop(), + child: I18nText('okButton'), ), ], ), @@ -240,14 +238,13 @@ class _PatchItemState extends State { context: context, builder: (context) => AlertDialog( title: I18nText('notice'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText( 'patchItem.unsupportedRequiredOption', ), actions: [ - CustomMaterialButton( - label: I18nText('okButton'), + FilledButton( onPressed: () => Navigator.of(context).pop(), + child: I18nText('okButton'), ), ], ), diff --git a/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart b/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart index ae026cb7..2d5d2dfd 100644 --- a/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart +++ b/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart @@ -400,7 +400,9 @@ class _TextFieldForPatchOptionState extends State { final bool isStringOption = widget.optionType.contains('String'); final bool isArrayOption = widget.optionType.contains('Array'); selectedKey ??= widget.selectedKey; - controller.text = !isStringOption && isArrayOption && selectedKey == '' && + controller.text = !isStringOption && + isArrayOption && + selectedKey == '' && (widget.value != null && widget.value.toString().startsWith('[')) ? '' : widget.value ?? ''; @@ -519,7 +521,8 @@ class _TextFieldForPatchOptionState extends State { } break; case 'patchOptionsView.selectFolder': - final DirectoryLocation? result = await FlutterFileDialog.pickDirectory(); + final DirectoryLocation? result = + await FlutterFileDialog.pickDirectory(); if (result != null) { controller.text = result.toString(); widget.onChanged(controller.text); diff --git a/lib/ui/widgets/settingsView/settings_advanced_section.dart b/lib/ui/widgets/settingsView/settings_advanced_section.dart index 53b3cadf..4182b7a0 100644 --- a/lib/ui/widgets/settingsView/settings_advanced_section.dart +++ b/lib/ui/widgets/settingsView/settings_advanced_section.dart @@ -10,7 +10,6 @@ import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_universal_patches.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_version_compatibility_check.dart'; - class SAdvancedSection extends StatelessWidget { const SAdvancedSection({super.key}); diff --git a/lib/ui/widgets/settingsView/settings_enable_patches_selection.dart b/lib/ui/widgets/settingsView/settings_enable_patches_selection.dart index a0c5b463..a2fcc86b 100644 --- a/lib/ui/widgets/settingsView/settings_enable_patches_selection.dart +++ b/lib/ui/widgets/settingsView/settings_enable_patches_selection.dart @@ -6,7 +6,8 @@ class SEnablePatchesSelection extends StatefulWidget { const SEnablePatchesSelection({super.key}); @override - State createState() => _SEnablePatchesSelectionState(); + State createState() => + _SEnablePatchesSelectionState(); } final _settingsViewModel = SettingsViewModel(); diff --git a/lib/ui/widgets/settingsView/settings_export_section.dart b/lib/ui/widgets/settingsView/settings_export_section.dart index 70aaf9b0..2f7e4aa7 100644 --- a/lib/ui/widgets/settingsView/settings_export_section.dart +++ b/lib/ui/widgets/settingsView/settings_export_section.dart @@ -3,7 +3,6 @@ import 'package:flutter_i18n/widgets/I18nText.dart'; import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_keystore_password.dart'; import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; final _settingsViewModel = SettingsViewModel(); @@ -151,20 +150,18 @@ class SExportSection extends StatelessWidget { context: context, builder: (context) => AlertDialog( title: I18nText(dialogTitle), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText(dialogText), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('noButton'), + TextButton( onPressed: () => Navigator.of(context).pop(), + child: I18nText('noButton'), ), - CustomMaterialButton( - label: I18nText('yesButton'), + FilledButton( onPressed: () => { Navigator.of(context).pop(), dialogAction(), }, + child: I18nText('yesButton'), ), ], ), @@ -176,20 +173,18 @@ class SExportSection extends StatelessWidget { context: context, builder: (context) => AlertDialog( title: I18nText('settingsView.regenerateKeystoreDialogTitle'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText('settingsView.regenerateKeystoreDialogText'), actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('noButton'), + TextButton( onPressed: () => Navigator.of(context).pop(), + child: I18nText('noButton'), ), - CustomMaterialButton( - label: I18nText('yesButton'), + FilledButton( onPressed: () => { Navigator.of(context).pop(), _settingsViewModel.deleteKeystore(), }, + child: I18nText('yesButton'), ), ], ), diff --git a/lib/ui/widgets/settingsView/settings_require_suggested_app_version.dart b/lib/ui/widgets/settingsView/settings_require_suggested_app_version.dart index 1d431e60..da583a97 100644 --- a/lib/ui/widgets/settingsView/settings_require_suggested_app_version.dart +++ b/lib/ui/widgets/settingsView/settings_require_suggested_app_version.dart @@ -6,12 +6,14 @@ class SRequireSuggestedAppVersion extends StatefulWidget { const SRequireSuggestedAppVersion({super.key}); @override - State createState() => _SRequireSuggestedAppVersionState(); + State createState() => + _SRequireSuggestedAppVersionState(); } final _settingsViewModel = SettingsViewModel(); -class _SRequireSuggestedAppVersionState extends State { +class _SRequireSuggestedAppVersionState + extends State { @override Widget build(BuildContext context) { return SwitchListTile( @@ -29,8 +31,11 @@ class _SRequireSuggestedAppVersionState extends State createState() => - _SUniversalPatchesState(); + State createState() => _SUniversalPatchesState(); } final _settingsViewModel = SettingsViewModel(); final _patchesSelectorViewModel = PatchesSelectorViewModel(); final _patcherViewModel = PatcherViewModel(); -class _SUniversalPatchesState - extends State { +class _SUniversalPatchesState extends State { @override Widget build(BuildContext context) { return SwitchListTile( diff --git a/lib/ui/widgets/settingsView/settings_version_compatibility_check.dart b/lib/ui/widgets/settingsView/settings_version_compatibility_check.dart index ead3b070..a17fcb3b 100644 --- a/lib/ui/widgets/settingsView/settings_version_compatibility_check.dart +++ b/lib/ui/widgets/settingsView/settings_version_compatibility_check.dart @@ -9,14 +9,16 @@ class SVersionCompatibilityCheck extends StatefulWidget { const SVersionCompatibilityCheck({super.key}); @override - State createState() => _SVersionCompatibilityCheckState(); + State createState() => + _SVersionCompatibilityCheckState(); } final _settingsViewModel = SettingsViewModel(); final _patchesSelectorViewModel = PatchesSelectorViewModel(); final _patcherViewModel = PatcherViewModel(); -class _SVersionCompatibilityCheckState extends State { +class _SVersionCompatibilityCheckState + extends State { @override Widget build(BuildContext context) { return SwitchListTile( diff --git a/lib/ui/widgets/shared/application_item.dart b/lib/ui/widgets/shared/application_item.dart index 5f527eb1..ce432138 100644 --- a/lib/ui/widgets/shared/application_item.dart +++ b/lib/ui/widgets/shared/application_item.dart @@ -3,7 +3,6 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_card.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:timeago/timeago.dart'; class ApplicationItem extends StatefulWidget { @@ -24,7 +23,6 @@ class ApplicationItem extends StatefulWidget { } class _ApplicationItemState extends State { - @override void initState() { super.initState(); @@ -81,9 +79,9 @@ class _ApplicationItemState extends State { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ - CustomMaterialButton( - label: I18nText('applicationItem.infoButton'), + FilledButton( onPressed: widget.onPressed, + child: I18nText('applicationItem.infoButton'), ), ], ), diff --git a/lib/ui/widgets/shared/custom_material_button.dart b/lib/ui/widgets/shared/custom_material_button.dart deleted file mode 100644 index c861a709..00000000 --- a/lib/ui/widgets/shared/custom_material_button.dart +++ /dev/null @@ -1,126 +0,0 @@ -import 'package:flutter/material.dart'; - -class CustomMaterialButton extends StatelessWidget { - const CustomMaterialButton({ - super.key, - required this.label, - this.isFilled = true, - this.isExpanded = false, - required this.onPressed, - }); - final Widget label; - final bool isFilled; - final bool isExpanded; - final Function()? onPressed; - - @override - Widget build(BuildContext context) { - return TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all( - isExpanded - ? const EdgeInsets.symmetric(horizontal: 24, vertical: 12) - : const EdgeInsets.symmetric(horizontal: 20, vertical: 12), - ), - shape: MaterialStateProperty.all( - StadiumBorder( - side: isFilled - ? BorderSide.none - : BorderSide( - color: Theme.of(context).colorScheme.primary, - ), - ), - ), - backgroundColor: MaterialStateProperty.all( - isFilled ? Theme.of(context).colorScheme.primary : Colors.transparent, - ), - foregroundColor: MaterialStateProperty.all( - isFilled - ? Theme.of(context).colorScheme.surface - : Theme.of(context).colorScheme.primary, - ), - ), - onPressed: onPressed, - child: label, - ); - } -} - -// ignore: must_be_immutable -class TimerButton extends StatefulWidget { - TimerButton({ - super.key, - required this.seconds, - required this.isRunning, - required this.onTimerEnd, - this.label = const Text(''), - this.isFilled = true, - }); - Widget label; - bool isFilled; - int seconds; - final bool isRunning; - final Function()? onTimerEnd; - - @override - State createState() => _TimerButtonState(); -} - -class _TimerButtonState extends State { - void timer(int seconds) { - Future.delayed(const Duration(seconds: 1), () { - if (seconds > 0) { - setState(() { - seconds--; - }); - timer(seconds); - } else { - widget.onTimerEnd!(); - } - }); - } - - @override - void initState() { - //decrement seconds - if (widget.isRunning) { - timer(widget.seconds); - } - super.initState(); - } - - @override - Widget build(BuildContext build) { - return TextButton( - style: ButtonStyle( - shape: MaterialStateProperty.all( - StadiumBorder( - side: widget.isFilled - ? BorderSide.none - : BorderSide( - color: Theme.of(context).colorScheme.primary, - ), - ), - ), - backgroundColor: MaterialStateProperty.all( - widget.isFilled - ? Theme.of(context).colorScheme.primary - : Colors.transparent, - ), - foregroundColor: MaterialStateProperty.all( - widget.isFilled - ? Theme.of(context).colorScheme.surface - : Theme.of(context).colorScheme.primary, - ), - ), - onPressed: widget.isRunning ? null : widget.onTimerEnd, - child: Text( - widget.isRunning ? '${widget.seconds}' : 'Install', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ); - } -} diff --git a/lib/utils/check_for_supported_patch.dart b/lib/utils/check_for_supported_patch.dart index 19bd30c1..5cb311c6 100644 --- a/lib/utils/check_for_supported_patch.dart +++ b/lib/utils/check_for_supported_patch.dart @@ -27,12 +27,12 @@ bool hasUnsupportedRequiredOption(List