From 5556a0db2cc71e280120df9f56ef84f11370f580 Mon Sep 17 00:00:00 2001 From: aAbed Date: Mon, 27 Nov 2023 09:29:53 +0545 Subject: [PATCH 01/17] feat: Improve installation robustness --- android/app/src/main/AndroidManifest.xml | 33 +- .../revanced/manager/flutter/MainActivity.kt | 59 ++- .../packageInstaller/InstallerReceiver.kt | 32 ++ .../packageInstaller/UninstallerReceiver.kt | 24 ++ assets/i18n/en_US.json | 48 ++- lib/main.dart | 8 + lib/services/download_manager.dart | 1 - lib/services/patcher_api.dart | 356 ++++++++++++------ lib/services/root_api.dart | 131 +++---- lib/ui/views/home/home_viewmodel.dart | 13 +- .../views/installer/installer_viewmodel.dart | 98 ++++- lib/ui/views/patcher/patcher_viewmodel.dart | 8 +- .../patches_selector_viewmodel.dart | 4 - .../appInfoView/app_info_viewmodel.dart | 4 +- pubspec.yaml | 4 - 15 files changed, 571 insertions(+), 252 deletions(-) create mode 100644 android/app/src/main/kotlin/app/revanced/manager/flutter/utils/packageInstaller/InstallerReceiver.kt create mode 100644 android/app/src/main/kotlin/app/revanced/manager/flutter/utils/packageInstaller/UninstallerReceiver.kt diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7fade03bd1..782ef780c7 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 10ff238e24..6fe5438e4f 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) } @@ -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 0000000000..d14a9daa5e --- /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 0000000000..84dec3cca9 --- /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/assets/i18n/en_US.json b/assets/i18n/en_US.json index 511cd9c45a..6764adcebc 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -163,13 +163,15 @@ }, "installerView": { "widgetTitle": "Installer", - "installType": "Select install type", - "installTypeDescription": "Select the installation type to proceed with.", + "installType": "Choose installation method", + "installTypeDescription": "To install, you can either mount or install normally. Mounting will install the patched app on top of an existing installation.", "installButton": "Install", "installRootType": "Mount", "installNonRootType": "Normal", + "warning": "Disable auto updates after installing the app to avoid unexpected issues.", + "pressBackAgain": "Press back again to cancel", "openButton": "Open", "shareButton": "Share file", @@ -327,5 +329,47 @@ "integrationsContributors": "Integrations contributors", "cliContributors": "CLI contributors", "managerContributors": "Manager contributors" + }, + "installErrorDialog": { + "title0": "Version mismatch", + "title1": "No installation found", + "title2": "Installation blocked", + "title3_0": "Installation blocked", + + "title4": "Installation invalid", + "title4_0": "Can't downgrade", + + "title5": "Installation conflict", + "title6": "Installation storage issue", + "title7": "Installation incompatible", + "title8": "Installation timeout", + "title10": "No root access", + + "callback0": "To install by mounting the patched app, the version of the installed app must match the version of the patched app.", + "callback1": "To install by mounting the patched app, the unpatched app must be installed on this device.", + "callback2": "The installation was blocked by {packageName}.", + "callback3_0": "The installation was blocked.", + + "callback4": "The app is invalid.", + "callback4_0": "The version of the installed app is newer than the version of the patched app.", + + "callback5": "An existing installation of the app prevents the installation.", + "callback6": "The app could not be installed due to insufficient storage.", + "callback7": "The app is incompatible with this device.", + "callback8": "The installation took too long.", + "callback10": "To install by mounting the patched app, root access is required.", + + "solution0": "Install the version of the app you are mounting and try again.", + "solution2": "Adjust your security settings and try again.", + "solution3_0": "Adjust your security settings and try again.", + + "solution4": "Reinstall by uninstalling the app and try again?", + "solution4_0": "Uninstall the current version and try again?", + + "solution5": "Uninstall the app and try again?", + "solution6": "Free up some space and try again.", + "solution7": "Contact the developer of the app and ask for support.", + "solution8": "Try again.", + "solution10": "Grant root access to ReVanced Manager and try again." } } diff --git a/lib/main.dart b/lib/main.dart index b38cb4c78c..5b8df919b8 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/services/download_manager.dart b/lib/services/download_manager.dart index 4c0919b984..1aa9fc2bee 100644 --- a/lib/services/download_manager.dart +++ b/lib/services/download_manager.dart @@ -72,4 +72,3 @@ class DownloadManager { ); } } - diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index 4266a88032..e6821d1fe7 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -3,22 +3,24 @@ 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'; import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/root_api.dart'; +import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; 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 +81,8 @@ class PatcherAPI { } Future> getFilteredInstalledApps( - bool showUniversalPatches,) async { + bool showUniversalPatches, + ) async { final List filteredApps = []; final bool allAppsIncluded = _universalPatches.isNotEmpty && showUniversalPatches; @@ -121,11 +124,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 +140,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,131 +202,249 @@ 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(10); + } else if (packageVersion == null) { + installErrorDialog(1); + } else if (packageVersion == patchedApp.version) { + return await _rootAPI.installApp( + patchedApp.packageName, + patchedApp.apkFilePath, + outFile!.path, + ) + ? 0 + : 1; + } else { + installErrorDialog(0); + } + } else { + if (await _rootAPI.hasRootPermissions()) { + await _rootAPI.unmount(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, + Future installErrorDialog( + int statusCode, [ + status, + bool hasExtra = false, + ]) async { + final String extraStatus = hasExtra ? '_0' : ''; + bool cleanInstall = false; + await showDialog( + context: _managerAPI.ctx!, + builder: (context) => AlertDialog( + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + title: I18nText('installErrorDialog.title$statusCode$extraStatus'), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + I18nText( + 'installErrorDialog.callback$statusCode$extraStatus', + translationParams: statusCode == 2 + ? { + 'packageName': status['otherPackageName'], + } + : null, + ), + const SizedBox(height: 16), + I18nText( + 'installErrorDialog.solution$statusCode$extraStatus', + ), + ], ), - ); - } - } on Exception catch (e) { - if (kDebugMode) { - print(e); - } + actions: (status == null) + ? [ + CustomMaterialButton( + label: I18nText('okButton'), + onPressed: () async { + Navigator.pop(context); + }, + ), + ] + : [ + CustomMaterialButton( + isFilled: !(statusCode == 4 || statusCode == 5), + label: I18nText('cancelButton'), + onPressed: () { + Navigator.pop(context); + }, + ), + if (statusCode == 4 || statusCode == 5) + CustomMaterialButton( + label: I18nText('okButton'), + onPressed: () async { + final int response = await patcherChannel.invokeMethod( + 'uninstallApp', + {'packageName': status['packageName']}, + ); + if (response == 0 && context.mounted) { + cleanInstall = true; + Navigator.pop(context); + } + }, + ), + ], + ), + ); + return cleanInstall ? 10 : 1; } -} -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); + 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, + ), + ); + } + } on Exception catch (e) { + if (kDebugMode) { + print(e); + } } } -} -String _getFileName(String appName, String version) { - final String prefix = appName.toLowerCase().replaceAll(' ', '-'); - final String newName = '$prefix-revanced_v$version.apk'; - return newName; -} + 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); + } + } + } -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 _getFileName(String appName, String version) { + final String prefix = appName.toLowerCase().replaceAll(' ', '-'); + final String newName = '$prefix-revanced_v$version.apk'; + return newName; + } -String getSuggestedVersion(String packageName) { - final Map versions = {}; - for (final Patch patch in _patches) { - final Package? package = patch.compatiblePackages.firstWhereOrNull( - (pack) => pack.name == packageName, + 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, + ), ); - if (package != null) { - for (final String version in package.versions) { - versions.update( - version, - (value) => versions[version]! + 1, - ifAbsent: () => 1, - ); + } + + 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 ''; } - 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 ''; -}} +} diff --git a/lib/services/root_api.dart b/lib/services/root_api.dart index f0c7d9173f..a77c91c0e6 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 { @@ -75,7 +75,7 @@ class RootAPI { Future> getInstalledApps() async { final List apps = List.empty(growable: true); try { - String? res = await Root.exec( + final String? res = await Root.exec( cmd: 'ls "$_revancedDirPath"', ); if (res != null) { @@ -83,15 +83,6 @@ class RootAPI { 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"', - ); - if (res != null) { - final List list = res.split('\n'); - list.removeWhere((pack) => pack.isEmpty); - apps.addAll(list.map((pack) => pack.trim()).toList()); - } } on Exception catch (e) { if (kDebugMode) { print(e); @@ -100,16 +91,9 @@ class RootAPI { return apps; } - Future deleteApp(String packageName, String originalFilePath) async { + Future unmount(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"', + 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', ); await Root.exec( cmd: 'rm -rf "$_revancedDirPath/$packageName"', @@ -117,8 +101,21 @@ class RootAPI { await Root.exec( cmd: 'rm -rf "$_serviceDDirPath/$packageName.sh"', ); - await Root.exec( - cmd: 'rm -rf "$_postFsDataDirPath/$packageName.sh"', + } + + // 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 + ''' + .trim(), ); } @@ -128,7 +125,6 @@ class RootAPI { String patchedFilePath, ) async { try { - await deleteApp(packageName, originalFilePath); await Root.exec( cmd: 'mkdir -p "$_revancedDirPath/$packageName"', ); @@ -138,11 +134,9 @@ class RootAPI { '', '$_revancedDirPath/$packageName', ); - await saveOriginalFilePath(packageName, originalFilePath); await installServiceDScript(packageName); - await installPostFsDataScript(packageName); await installApk(packageName, patchedFilePath); - await mountApk(packageName, originalFilePath); + await mountApk(packageName); return true; } on Exception catch (e) { if (kDebugMode) { @@ -156,26 +150,22 @@ class RootAPI { await Root.exec( 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 scriptFilePath = '$_serviceDDirPath/$packageName.sh'; - await Root.exec( - cmd: 'echo \'$content\' > "$scriptFilePath"', - ); - await setPermissions('0744', '', '', scriptFilePath); - } + final String content = ''' + #!/system/bin/sh + MAGISKTMP="\$(magisk --path)" || MAGISKTMP=/sbin + MIRROR="\$MAGISKTMP/.magisk/mirror" - 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'; + until [ "\$(getprop sys.boot_completed)" = 1 ]; do sleep 3; done + until [ -d "/sdcard/Android" ]; do sleep 1; 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 + ''' + .trim(); + final String scriptFilePath = '$_serviceDDirPath/$packageName.sh'; await Root.exec( cmd: 'echo \'$content\' > "$scriptFilePath"', ); @@ -195,49 +185,12 @@ 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"', - ); + Future mountApk(String packageName,) async { 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( - 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, + 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 + .$_serviceDDirPath/$packageName.sh + '''.trim(), ); } diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index b85e49da1c..b2bdad09e4 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'; @@ -54,7 +53,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'); } @@ -76,7 +75,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'); } @@ -85,6 +84,7 @@ class HomeViewModel extends BaseViewModel { _managerAPI.reAssessSavedApps().then((_) => _getPatchedApps()); } + void navigateToAppInfo(PatchedApplication app) { _navigationService.navigateTo( Routes.appInfoView, @@ -270,6 +270,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 @@ -369,9 +370,7 @@ class HomeViewModel extends BaseViewModel { child: CustomMaterialButton( label: I18nText('updateButton'), onPressed: () async { - await InstallPlugin.installApk( - downloadedApk!.path, - ); + await _patcherAPI.installApk(context, downloadedApk!.path); }, ), ), @@ -415,7 +414,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 01226fac65..2cb5327130 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -199,7 +199,7 @@ class InstallerViewModel extends BaseViewModel { 'Android version: ${info['androidVersion']}', 'Supported architectures: ${info['supportedArch'].join(", ")}', 'Root permissions: ${isRooted ? 'Yes' : 'No'}', - + '\n- Patch Info', 'App: ${_app.packageName} v${_app.version}', 'Patches version: ${_managerAPI.patchesVersion}', @@ -210,7 +210,7 @@ class InstallerViewModel extends BaseViewModel { 'Show universal patches: ${_managerAPI.areUniversalPatchesEnabled()}', 'Patches source: ${_managerAPI.getPatchesRepo()}', 'Integration source: ${_managerAPI.getIntegrationsRepo()}', - + '\n- Logs', logs, ]; @@ -258,11 +258,12 @@ 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, + backgroundColor: + Theme.of(innerContext).colorScheme.secondaryContainer, icon: const Icon(Icons.file_download_outlined), contentPadding: const EdgeInsets.symmetric(vertical: 16), content: SingleChildScrollView( @@ -310,6 +311,19 @@ 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, + ), + ), + ), + ), ], ); }, @@ -320,13 +334,13 @@ class InstallerViewModel extends BaseViewModel { label: I18nText('cancelButton'), isFilled: false, onPressed: () { - Navigator.of(context).pop(); + Navigator.of(innerContext).pop(); }, ), CustomMaterialButton( label: I18nText('installerView.installButton'), onPressed: () { - Navigator.of(context).pop(); + Navigator.of(innerContext).pop(); installResult(context, installType.value == 1); }, ), @@ -334,7 +348,35 @@ class InstallerViewModel extends BaseViewModel { ), ); } else { - installResult(context, false); + await showDialog( + context: context, + barrierDismissible: false, + builder: (innerContext) => AlertDialog( + title: I18nText( + 'warning', + ), + backgroundColor: + Theme.of(innerContext).colorScheme.secondaryContainer, + contentPadding: const EdgeInsets.all(16), + content: I18nText('installerView.warning'), + actions: [ + CustomMaterialButton( + label: I18nText('cancelButton'), + isFilled: false, + onPressed: () { + Navigator.of(innerContext).pop(); + }, + ), + CustomMaterialButton( + label: I18nText('installerView.installButton'), + onPressed: () { + Navigator.of(innerContext).pop(); + installResult(context, false); + }, + ), + ], + ), + ); } } @@ -355,15 +397,18 @@ 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( + 1.0, + '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(); @@ -379,9 +424,26 @@ class InstallerViewModel extends BaseViewModel { await _managerAPI.savePatchedApp(_app); - update(1.0, 'Installed!', 'Installed!'); + update(1.0, 'Installed', 'Installed'); + } else if (response == 3) { + update( + 1.0, + 'Installation canceled', + 'Installation canceled', + ); + } else if (response == 10) { + installResult(context, installAsRoot); + update( + 1.0, + '', + 'Starting installer', + ); } else { - // TODO(aabed): Show error message. + update( + 1.0, + 'Installation failed', + 'Installation failed', + ); } } on Exception catch (e) { if (kDebugMode) { diff --git a/lib/ui/views/patcher/patcher_viewmodel.dart b/lib/ui/views/patcher/patcher_viewmodel.dart index 19d7072160..616c5fc2bd 100644 --- a/lib/ui/views/patcher/patcher_viewmodel.dart +++ b/lib/ui/views/patcher/patcher_viewmodel.dart @@ -174,7 +174,8 @@ class PatcherViewModel extends BaseViewModel { if (suggestedVersion.isNotEmpty) { await openDefaultBrowser( - '${selectedApp!.packageName} apk version v$suggestedVersion'); + '${selectedApp!.packageName} apk version v$suggestedVersion', + ); } else { await openDefaultBrowser('${selectedApp!.packageName} apk'); } @@ -244,7 +245,10 @@ class PatcherViewModel extends BaseViewModel { removedPatches.add('• ${patch.name}'); for (final option in patch.options) { _managerAPI.clearPatchOption( - selectedApp!.packageName, patch.name, option.key); + selectedApp!.packageName, + patch.name, + option.key, + ); } } } diff --git a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart index 173c1f5376..0470e4e7a5 100644 --- a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart +++ b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart @@ -186,10 +186,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/widgets/appInfoView/app_info_viewmodel.dart b/lib/ui/widgets/appInfoView/app_info_viewmodel.dart index 324415672c..d186016843 100644 --- a/lib/ui/widgets/appInfoView/app_info_viewmodel.dart +++ b/lib/ui/widgets/appInfoView/app_info_viewmodel.dart @@ -30,7 +30,9 @@ class AppInfoViewModel extends BaseViewModel { if (app.isRooted) { final bool hasRootPermissions = await _rootAPI.hasRootPermissions(); if (hasRootPermissions) { - await _rootAPI.deleteApp(app.packageName, app.apkFilePath); + await _rootAPI.unmount( + app.packageName, + ); if (!onlyUnpatch) { await DeviceApps.uninstallApp(app.packageName); } diff --git a/pubspec.yaml b/pubspec.yaml index 0f9ea9333d..e2380cced2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,10 +68,6 @@ dependencies: flutter_dotenv: ^5.0.2 flutter_markdown: ^0.6.14 dio_cache_interceptor: ^3.4.0 - install_plugin: - git: # remove once https://github.com/hui-z/flutter_install_plugin/pull/67 is merged - url: https://github.com/BenjaminHalko/flutter_install_plugin - ref: master screenshot_callback: git: # remove once https://github.com/flutter-moum/flutter_screenshot_callback/pull/81 is merged url: https://github.com/BenjaminHalko/flutter_screenshot_callback From aae748e1010638d8cabec9022018ab310486b14e Mon Sep 17 00:00:00 2001 From: aAbed Date: Sat, 2 Dec 2023 12:32:34 +0545 Subject: [PATCH 02/17] refactor: apply changes from suggestions --- assets/i18n/en_US.json | 67 ++++++++++++++-------------------- lib/services/patcher_api.dart | 68 ++++++++++++++++++++++++++++++----- 2 files changed, 87 insertions(+), 48 deletions(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 6764adcebc..5bfac1af91 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -331,45 +331,32 @@ "managerContributors": "Manager contributors" }, "installErrorDialog": { - "title0": "Version mismatch", - "title1": "No installation found", - "title2": "Installation blocked", - "title3_0": "Installation blocked", - - "title4": "Installation invalid", - "title4_0": "Can't downgrade", - - "title5": "Installation conflict", - "title6": "Installation storage issue", - "title7": "Installation incompatible", - "title8": "Installation timeout", - "title10": "No root access", - - "callback0": "To install by mounting the patched app, the version of the installed app must match the version of the patched app.", - "callback1": "To install by mounting the patched app, the unpatched app must be installed on this device.", - "callback2": "The installation was blocked by {packageName}.", - "callback3_0": "The installation was blocked.", - - "callback4": "The app is invalid.", - "callback4_0": "The version of the installed app is newer than the version of the patched app.", - - "callback5": "An existing installation of the app prevents the installation.", - "callback6": "The app could not be installed due to insufficient storage.", - "callback7": "The app is incompatible with this device.", - "callback8": "The installation took too long.", - "callback10": "To install by mounting the patched app, root access is required.", - - "solution0": "Install the version of the app you are mounting and try again.", - "solution2": "Adjust your security settings and try again.", - "solution3_0": "Adjust your security settings and try again.", - - "solution4": "Reinstall by uninstalling the app and try again?", - "solution4_0": "Uninstall the current version and try again?", - - "solution5": "Uninstall the app and try again?", - "solution6": "Free up some space and try again.", - "solution7": "Contact the developer of the app and ask for support.", - "solution8": "Try again.", - "solution10": "Grant root access to ReVanced Manager and try again." + "mount_version_mismatch": "Version mismatch", + "mount_no_root": "No root access", + "mount_missing_installation": "No installation found", + + "status_failure_blocked": "Installation blocked", + "install_failed_verification_failure": "Installation blocked", + "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", + "unknown_status": "Unknown status", + + "mount_version_mismatch_description": "To install by mounting the patched app, the version of the installed app must match the version of the patched app.\n\nInstall the version of the app you are mounting and try again.", + "mount_no_root_description": "To install by mounting the patched app, root access is required.\n\nGrant root access to ReVanced Manager and try again.", + "mount_missing_installation_description": "To install by mounting the patched app, the unpatched app must be installed on this device.\n\nInstall the app and try again.", + + "status_failure_timeout_description": "Try again", + "status_failure_storage_description": "The app could not be installed due to insufficient storage.\n\nFree up some space and try again.", + "status_failure_invalid_description": "The app is invalid.\n\nReinstall by uninstalling 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": "An existing installation of the app prevents the installation.\n\nUninstall the 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 was blocked.\n\nAdjust your security settings and try again.", + "install_failed_version_downgrade_description": "The version of the installed app is newer than the version of the patched app.\n\nUninstall the current version and try again?", + "unknown_status_description": "An unknown error occurred during installation. Please try again." } } diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index 8616e6bfd1..7bb11292e7 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -296,29 +296,25 @@ class PatcherAPI { status, bool hasExtra = false, ]) async { - final String extraStatus = hasExtra ? '_0' : ''; + final String statusValue = InstallStatus.byCode(hasExtra ? double.parse('$statusCode.1') : statusCode); bool cleanInstall = false; await showDialog( context: _managerAPI.ctx!, builder: (context) => AlertDialog( backgroundColor: Theme.of(context).colorScheme.secondaryContainer, - title: I18nText('installErrorDialog.title$statusCode$extraStatus'), + title: I18nText('installErrorDialog.$statusValue'), content: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ I18nText( - 'installErrorDialog.callback$statusCode$extraStatus', + 'installErrorDialog.${statusValue}_description', translationParams: statusCode == 2 ? { 'packageName': status['otherPackageName'], } : null, ), - const SizedBox(height: 16), - I18nText( - 'installErrorDialog.solution$statusCode$extraStatus', - ), ], ), actions: (status == null) @@ -366,7 +362,8 @@ class PatcherAPI { params: SaveFileDialogParams( sourceFilePath: outFile!.path, fileName: newName, - mimeTypesFilter: ['application/vnd.android.package-archive'],), + mimeTypesFilter: ['application/vnd.android.package-archive'], + ), ); } } on Exception catch (e) { @@ -448,3 +445,58 @@ class PatcherAPI { return ''; } } + +enum InstallStatus { + statusFailureTimeout(8), + statusFailureStorage(6), + statusFailureInvalid(4), + statusFailureIncompatible(7), + statusFailureConflict(5), + statusFailureBlocked(2), + installFailedVerificationFailure(3.1), + installFailedVersionDowngrade(4.1), + + mountVersionMismatch(0), + mountMissingInstallation(1), + mountNoRoot(10); + + const InstallStatus(this.statusCode); + final double statusCode; + + static String byCode(num code) { + return InstallStatus.values + .firstWhere((flag) => flag.statusCode == code) + .status; + } +} + +extension InstallStatusExtension on InstallStatus { + String get status { + switch (this) { + case InstallStatus.statusFailureTimeout: + return 'status_failure_timeout'; + case InstallStatus.statusFailureStorage: + return 'status_failure_storage'; + case InstallStatus.statusFailureInvalid: + return 'status_failure_invalid'; + case InstallStatus.statusFailureIncompatible: + return 'status_failure_incompatible'; + case InstallStatus.statusFailureConflict: + return 'status_failure_conflict'; + case InstallStatus.statusFailureBlocked: + return 'status_failure_blocked'; + case InstallStatus.installFailedVerificationFailure: + return 'install_failed_verification_failure'; + case InstallStatus.installFailedVersionDowngrade: + return 'install_failed_version_downgrade'; + case InstallStatus.mountVersionMismatch: + return 'mount_version_mismatch'; + case InstallStatus.mountMissingInstallation: + return 'mount_missing_installation'; + case InstallStatus.mountNoRoot: + return 'mount_no_root'; + default: + return 'unknownStatus'; + } + } +} From 0b853c27c681438847f6efc45c8e3f987853cde1 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 2 Dec 2023 23:44:59 +0100 Subject: [PATCH 03/17] Update assets/i18n/en_US.json [skip ci] Co-authored-by: Ushie --- assets/i18n/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 5bfac1af91..1ab7b3467f 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -351,7 +351,7 @@ "status_failure_timeout_description": "Try again", "status_failure_storage_description": "The app could not be installed due to insufficient storage.\n\nFree up some space and try again.", - "status_failure_invalid_description": "The app is invalid.\n\nReinstall by uninstalling the app and try again?", + "status_failure_invalid_description": "The app is 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": "An existing installation of the app prevents the installation.\n\nUninstall the app and try again?", "status_failure_blocked_description": "The installation was blocked by {packageName}.\n\nAdjust your security settings and try again.", From 36d2e485e386db5f1a1183c70d9179e239e30e93 Mon Sep 17 00:00:00 2001 From: Dhruvan Bhalara <53393418+dhruvanbhalara@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:19:16 +0530 Subject: [PATCH 04/17] feat: append patch version to the APK name while sharing/exporting (#1547) --- lib/services/patcher_api.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index 65f5ecc095..f2e9ce88f4 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -237,7 +237,7 @@ void exportPatchedFile(String appName, String version) { if (outFile != null) { final String newName = _getFileName(appName, version); FlutterFileDialog.saveFile( - params: SaveFileDialogParams( + params: SaveFileDialogParams( sourceFilePath: outFile!.path, fileName: newName, mimeTypesFilter: ['application/vnd.android.package-archive'], @@ -269,8 +269,9 @@ void sharePatchedFile(String appName, String version) { } String _getFileName(String appName, String version) { + final String patchVersion = _managerAPI.patchesVersion!; final String prefix = appName.toLowerCase().replaceAll(' ', '-'); - final String newName = '$prefix-revanced_v$version.apk'; + final String newName = '$prefix-revanced_v$version-patches_v$patchVersion.apk'; return newName; } From 9e003edef4f6083823dec3ca1f4d7bde9412dbc4 Mon Sep 17 00:00:00 2001 From: aAbed Date: Tue, 5 Dec 2023 10:23:52 +0545 Subject: [PATCH 05/17] refactor: code formatting --- lib/services/patcher_api.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index 7f39fb729a..f5a55ade1e 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -392,7 +392,7 @@ class PatcherAPI { String _getFileName(String appName, String version) { final String patchVersion = _managerAPI.patchesVersion!; - final String prefix = appName.toLowerCase().replaceAll(' ', '-'); + final String prefix = appName.toLowerCase().replaceAll(' ', '-'); final String newName = '$prefix-revanced_v$version-patches_v$patchVersion.apk'; return newName; } From e03a67bc8354abb0a0785b630426bfc79505804e Mon Sep 17 00:00:00 2001 From: aAbed Date: Tue, 5 Dec 2023 13:09:04 +0545 Subject: [PATCH 06/17] refactor: code formatting --- lib/services/patcher_api.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index 5c9c99d468..4c78131b41 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -296,7 +296,9 @@ class PatcherAPI { status, bool hasExtra = false, ]) async { - final String statusValue = InstallStatus.byCode(hasExtra ? double.parse('$statusCode.1') : statusCode); + final String statusValue = InstallStatus.byCode( + hasExtra ? double.parse('$statusCode.1') : statusCode, + ); bool cleanInstall = false; await showDialog( context: _managerAPI.ctx!, @@ -359,7 +361,7 @@ class PatcherAPI { if (outFile != null) { final String newName = _getFileName(appName, version); FlutterFileDialog.saveFile( - params: SaveFileDialogParams( + params: SaveFileDialogParams( sourceFilePath: outFile!.path, fileName: newName, mimeTypesFilter: ['application/vnd.android.package-archive'], @@ -393,7 +395,8 @@ class PatcherAPI { 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'; + final String newName = + '$prefix-revanced_v$version-patches_$patchVersion.apk'; return newName; } From fdb3a1d6720e0649f0fc64bf6e872a7dd7a5a51b Mon Sep 17 00:00:00 2001 From: aAbed Date: Tue, 5 Dec 2023 13:50:32 +0545 Subject: [PATCH 07/17] refactor: default string --- lib/services/patcher_api.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index 4c78131b41..eb81267522 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -500,7 +500,7 @@ extension InstallStatusExtension on InstallStatus { case InstallStatus.mountNoRoot: return 'mount_no_root'; default: - return 'unknownStatus'; + return 'unknown_status'; } } } From c388aab29d3e750e4c19a3c611306db100b1ee10 Mon Sep 17 00:00:00 2001 From: aAbed Date: Tue, 5 Dec 2023 16:51:53 +0545 Subject: [PATCH 08/17] refactor: modify strings --- assets/i18n/en_US.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 1ab7b3467f..3b237e6c60 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -336,7 +336,7 @@ "mount_missing_installation": "No installation found", "status_failure_blocked": "Installation blocked", - "install_failed_verification_failure": "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", @@ -355,7 +355,7 @@ "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": "An existing installation of the app prevents the installation.\n\nUninstall the 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 was blocked.\n\nAdjust your security settings and try again.", + "install_failed_verification_failure_description": "The app could not be installed due to a verification issue.\n\nAdjust your security settings and try again.", "install_failed_version_downgrade_description": "The version of the installed app is newer than the version of the patched app.\n\nUninstall the current version and try again?", "unknown_status_description": "An unknown error occurred during installation. Please try again." } From 2e69aa594d78e278b237e44cb2fe3bfda99c81a9 Mon Sep 17 00:00:00 2001 From: aAbed Date: Tue, 5 Dec 2023 17:02:39 +0545 Subject: [PATCH 09/17] refactor: apply changes from suggestion --- assets/i18n/en_US.json | 4 ++-- lib/services/patcher_api.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 3b237e6c60..7cc26320d7 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -343,7 +343,7 @@ "status_failure_storage": "Installation storage issue", "status_failure_incompatible": "Installation incompatible", "status_failure_timeout": "Installation timeout", - "unknown_status": "Unknown status", + "status_unknown": "Installation failed", "mount_version_mismatch_description": "To install by mounting the patched app, the version of the installed app must match the version of the patched app.\n\nInstall the version of the app you are mounting and try again.", "mount_no_root_description": "To install by mounting the patched app, root access is required.\n\nGrant root access to ReVanced Manager and try again.", @@ -357,6 +357,6 @@ "status_failure_blocked_description": "The installation was blocked by {packageName}.\n\nAdjust your security settings and try again.", "install_failed_verification_failure_description": "The app could not be installed due to a verification issue.\n\nAdjust your security settings and try again.", "install_failed_version_downgrade_description": "The version of the installed app is newer than the version of the patched app.\n\nUninstall the current version and try again?", - "unknown_status_description": "An unknown error occurred during installation. Please try again." + "status_unknown_description": "The installation failed due to an unknown reason. Please try again." } } diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index eb81267522..c817301f63 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -500,7 +500,7 @@ extension InstallStatusExtension on InstallStatus { case InstallStatus.mountNoRoot: return 'mount_no_root'; default: - return 'unknown_status'; + return 'status_unknown'; } } } From be1a5da6a096077d99433f1e1735482590d4748e Mon Sep 17 00:00:00 2001 From: aAbed <39409020+TheAabedKhan@users.noreply.github.com> Date: Wed, 6 Dec 2023 20:37:44 +0545 Subject: [PATCH 10/17] Update assets/i18n/en_US.json Co-authored-by: Ushie --- assets/i18n/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 7cc26320d7..630c27052b 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -333,7 +333,7 @@ "installErrorDialog": { "mount_version_mismatch": "Version mismatch", "mount_no_root": "No root access", - "mount_missing_installation": "No installation found", + "mount_missing_installation": "Installation not found", "status_failure_blocked": "Installation blocked", "install_failed_verification_failure": "Verification failed", From a812a586028b3ae09eee29512ec90b738f589c86 Mon Sep 17 00:00:00 2001 From: aAbed Date: Wed, 6 Dec 2023 21:48:00 +0545 Subject: [PATCH 11/17] refactor: apply changes from suggestion --- assets/i18n/en_US.json | 4 +- lib/services/patcher_api.dart | 75 ++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 630c27052b..6b83be2329 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -163,8 +163,8 @@ }, "installerView": { "widgetTitle": "Installer", - "installType": "Choose installation method", - "installTypeDescription": "To install, you can either mount or install normally. Mounting will install the patched app on top of an existing installation.", + "installType": "Select install type", + "installTypeDescription": "Select the installation type to proceed with.", "installButton": "Install", "installRootType": "Mount", diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index c817301f63..a22c610bf3 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -226,9 +226,9 @@ class PatcherAPI { final packageVersion = await DeviceApps.getApp(patchedApp.packageName) .then((app) => app?.versionName); if (!hasRootPermissions) { - installErrorDialog(10); - } else if (packageVersion == null) { installErrorDialog(1); + } else if (packageVersion == null) { + installErrorDialog(1.2); } else if (packageVersion == patchedApp.version) { return await _rootAPI.installApp( patchedApp.packageName, @@ -238,7 +238,7 @@ class PatcherAPI { ? 0 : 1; } else { - installErrorDialog(0); + installErrorDialog(1.1); } } else { if (await _rootAPI.hasRootPermissions()) { @@ -292,7 +292,7 @@ class PatcherAPI { } Future installErrorDialog( - int statusCode, [ + num statusCode, [ status, bool hasExtra = false, ]) async { @@ -300,6 +300,7 @@ class PatcherAPI { hasExtra ? double.parse('$statusCode.1') : statusCode, ); bool cleanInstall = false; + final bool isFixable = statusCode == 4 || statusCode == 5; await showDialog( context: _managerAPI.ctx!, builder: (context) => AlertDialog( @@ -330,13 +331,13 @@ class PatcherAPI { ] : [ CustomMaterialButton( - isFilled: !(statusCode == 4 || statusCode == 5), + isFilled: !isFixable, label: I18nText('cancelButton'), onPressed: () { Navigator.pop(context); }, ), - if (statusCode == 4 || statusCode == 5) + if (isFixable) CustomMaterialButton( label: I18nText('okButton'), onPressed: () async { @@ -451,56 +452,58 @@ class PatcherAPI { } enum InstallStatus { - statusFailureTimeout(8), - statusFailureStorage(6), - statusFailureInvalid(4), - statusFailureIncompatible(7), - statusFailureConflict(5), + mountNoRoot(1), + mountVersionMismatch(1.1), + mountMissingInstallation(1.2), + statusFailureBlocked(2), installFailedVerificationFailure(3.1), + statusFailureInvalid(4), installFailedVersionDowngrade(4.1), - - mountVersionMismatch(0), - mountMissingInstallation(1), - mountNoRoot(10); + statusFailureConflict(5), + statusFailureStorage(6), + statusFailureIncompatible(7), + statusFailureTimeout(8); const InstallStatus(this.statusCode); final double statusCode; static String byCode(num code) { - return InstallStatus.values - .firstWhere((flag) => flag.statusCode == code) - .status; + 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.statusFailureTimeout: - return 'status_failure_timeout'; - case InstallStatus.statusFailureStorage: - return 'status_failure_storage'; - case InstallStatus.statusFailureInvalid: - return 'status_failure_invalid'; - case InstallStatus.statusFailureIncompatible: - return 'status_failure_incompatible'; - case InstallStatus.statusFailureConflict: - return 'status_failure_conflict'; + 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.mountVersionMismatch: - return 'mount_version_mismatch'; - case InstallStatus.mountMissingInstallation: - return 'mount_missing_installation'; - case InstallStatus.mountNoRoot: - return 'mount_no_root'; - default: - return 'status_unknown'; + 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'; } } } From 63ddc1ca0a1bd9c86493ced60f450c53acb63a91 Mon Sep 17 00:00:00 2001 From: aAbed <39409020+TheAabedKhan@users.noreply.github.com> Date: Thu, 7 Dec 2023 21:18:47 +0545 Subject: [PATCH 12/17] Update assets/i18n/en_US.json Co-authored-by: Ushie --- assets/i18n/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 6b83be2329..d2e416c5c5 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -346,7 +346,7 @@ "status_unknown": "Installation failed", "mount_version_mismatch_description": "To install by mounting the patched app, the version of the installed app must match the version of the patched app.\n\nInstall the version of the app you are mounting and try again.", - "mount_no_root_description": "To install by mounting the patched app, root access is required.\n\nGrant root access to ReVanced Manager and try again.", + "mount_no_root_description": "To install by mounting the patched app, root access must be granted.\n\nGrant root access to ReVanced Manager and try again.", "mount_missing_installation_description": "To install by mounting the patched app, the unpatched app must be installed on this device.\n\nInstall the app and try again.", "status_failure_timeout_description": "Try again", From 11e3e89f0fae2fbcc12663ca6f8980e0bf6f11d9 Mon Sep 17 00:00:00 2001 From: aAbed <39409020+TheAabedKhan@users.noreply.github.com> Date: Sat, 9 Dec 2023 20:20:56 +0545 Subject: [PATCH 13/17] Update lib/services/root_api.dart Co-authored-by: oSumAtrIX --- lib/services/root_api.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/services/root_api.dart b/lib/services/root_api.dart index a77c91c0e6..2b3f4cf2d9 100644 --- a/lib/services/root_api.dart +++ b/lib/services/root_api.dart @@ -163,6 +163,9 @@ class RootAPI { 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's already running + am force-stop $packageName ''' .trim(); final String scriptFilePath = '$_serviceDDirPath/$packageName.sh'; From 0819376a85da9cf7ee60a246ca7dffb42f70e5f8 Mon Sep 17 00:00:00 2001 From: Ushie Date: Sat, 9 Dec 2023 18:40:09 +0300 Subject: [PATCH 14/17] feat: more string consistency --- assets/i18n/en_US.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index d2e416c5c5..80992d9ec6 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -345,18 +345,18 @@ "status_failure_timeout": "Installation timeout", "status_unknown": "Installation failed", - "mount_version_mismatch_description": "To install by mounting the patched app, the version of the installed app must match the version of the patched app.\n\nInstall the version of the app you are mounting and try again.", - "mount_no_root_description": "To install by mounting the patched app, root access must be granted.\n\nGrant root access to ReVanced Manager and try again.", - "mount_missing_installation_description": "To install by mounting the patched app, the unpatched app must be installed on this device.\n\nInstall the app and try again.", + "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.\n\nInstall the app and try again.", "status_failure_timeout_description": "Try again", - "status_failure_storage_description": "The app could not be installed due to insufficient storage.\n\nFree up some space and try again.", - "status_failure_invalid_description": "The app is invalid.\n\nUninstall the app and 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": "An existing installation of the app prevents the installation.\n\nUninstall the app and try again?", + "status_failure_conflict_description": "The installation was prevented by an existing installation of the app.\n\nUninstall the 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 app could not be installed due to a verification issue.\n\nAdjust your security settings and try again.", - "install_failed_version_downgrade_description": "The version of the installed app is newer than the version of the patched app.\n\nUninstall the current version 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." } } From a9dfb8465eacfdba1f8c5146eb5a1389949150a7 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 23 Dec 2023 00:45:40 +0100 Subject: [PATCH 15/17] Update assets/i18n/en_US.json Co-authored-by: Pun Butrach --- assets/i18n/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 80992d9ec6..dde2237a00 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -349,7 +349,7 @@ "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.\n\nInstall the app and try again.", - "status_failure_timeout_description": "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.", From abd43da253e85b0f5a90e4eb03ff481891e6950a Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Sat, 23 Dec 2023 10:14:00 +0700 Subject: [PATCH 16/17] Update button --- lib/ui/views/installer/installer_viewmodel.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 0826a9c74a..c89356ae55 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -415,19 +415,18 @@ class InstallerViewModel extends BaseViewModel { contentPadding: const EdgeInsets.all(16), content: I18nText('installerView.warning'), actions: [ - CustomMaterialButton( - label: I18nText('cancelButton'), - isFilled: false, + TextButton( onPressed: () { Navigator.of(innerContext).pop(); }, + child: I18nText('cancelButton'), ), - CustomMaterialButton( - label: I18nText('installerView.installButton'), + FilledButton( onPressed: () { Navigator.of(innerContext).pop(); installResult(context, false); }, + child: I18nText('installerView.installButton'), ), ], ), From 5c39cbac3a3cb1527baf3cca205b60d9473a0d48 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Sat, 23 Dec 2023 10:15:55 +0700 Subject: [PATCH 17/17] Update style --- lib/ui/views/installer/installer_viewmodel.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index c89356ae55..039af2b4ce 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -410,8 +410,6 @@ class InstallerViewModel extends BaseViewModel { title: I18nText( 'warning', ), - backgroundColor: - Theme.of(innerContext).colorScheme.secondaryContainer, contentPadding: const EdgeInsets.all(16), content: I18nText('installerView.warning'), actions: [