diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml
index 009cef0dc7..08cb344c21 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 aa4fd514dc..50c7901c9a 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 6db2ff9bf1..c4484d9810 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 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..da997f9b90 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 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/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 744c64d127..db8c3baafe 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 511cd9c45a..1f8002dbdc 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 3c1fe03be2..1573e50868 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 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/models/patch.dart b/lib/models/patch.dart
index ebcebf5917..1b86ba5b50 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 4c0919b984..caa705d7e9 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 7a9e091d56..06d87f4344 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 f79d02e54a..48a431e1e3 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'],
+ Future installErrorDialog(
+ num statusCode, [
+ status,
+ bool hasExtra = false,
+ ]) async {
+ final String statusValue = InstallStatus.byCode(
+ hasExtra ? double.parse('$statusCode.1') : statusCode,
+ );
+ 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);
+ }
}
- } 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);
+ }
}
}
-}
-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)]);
+ 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,
+ );
+ }
+ }
}
- } 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;
}
+ 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;
-}
+enum InstallStatus {
+ mountNoRoot(1),
+ mountVersionMismatch(1.1),
+ mountMissingInstallation(1.2),
-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,
- ),
- );
-}
+ statusFailureBlocked(2),
+ installFailedVerificationFailure(3.1),
+ statusFailureInvalid(4),
+ installFailedVersionDowngrade(4.1),
+ statusFailureConflict(5),
+ statusFailureStorage(6),
+ statusFailureIncompatible(7),
+ statusFailureTimeout(8);
-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,
- );
- }
+ 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';
}
}
- 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;
+}
+
+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';
+ }
}
- return '';
-}}
+}
diff --git a/lib/services/root_api.dart b/lib/services/root_api.dart
index f0c7d9173f..dd2090abc9 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 {
- 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"',
- );
+ Future uninstall(String packageName) async {
await Root.exec(
- cmd: 'rm -rf "$_serviceDDirPath/$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
+ ''',
);
- 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
+ ''',
);
}
- Future installApp(
+ 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 scriptFilePath = '$_serviceDDirPath/$packageName.sh';
- await Root.exec(
- cmd: 'echo \'$content\' > "$scriptFilePath"',
- );
- await setPermissions('0744', '', '', scriptFilePath);
- }
+ final String mountScript = '''
+ #!/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
+
+ # 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 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 8f9fc98bcb..9ec2d1bda3 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 09f6daccbb..a4c6014970 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 7961ceb9ed..5b94eee7d8 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 b85e49da1c..c9356a561b 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 d16f130935..8919d06254 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();
-
- // Options changed
- final patchesChanged = defaultPatches
- .where((p) =>
- _patches.contains(p) &&
- p.options.any((o) => _getPatchOptionValue(p.name, o) != o.value))
+ final appliedPatchesNames = _patches.map((p) => p.name).toList();
+
+ 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);
-
- update(1.0, 'Installed!', 'Installed!');
+ await locator().initialize(context);
+
+ 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 e35b849da3..e6ac1bb280 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 520fd9c68e..2dbaef7b28 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 0921bb1d73..9def6c05a0 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 480babec96..2c3940fdc8 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 29489078ef..71e90d7962 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 71d073a9d5..82f330b5ba 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 6fa1ccc1dd..5669a07303 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 4ac4689bde..fb717f64bb 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 76e3171b1f..52c30ff043 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 66bb2c3e7f..1e7b4a721e 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 66fa683094..09c1b28b19 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 e51b13827c..6fa85730e3 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 0e7bed3d31..da8cf7ba86 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 324415672c..f3b70d3195 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 9d0625ce53..5525269881 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 7839536ad5..de5b72a047 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 ceea41c97d..9dd48686d1 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 11fd08392f..70692ab2e8 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 ae026cb73b..2d5d2dfd24 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 53b3cadf00..4182b7a02b 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 a0c5b463ef..a2fcc86b88 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 70aaf9b035..2f7e4aa7ab 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 1d431e6004..da583a9715 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 ead3b07094..a17fcb3b40 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 5f527eb14a..ce432138b6 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 c861a709ea..0000000000
--- 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 19bd30c169..5cb311c610 100644
--- a/lib/utils/check_for_supported_patch.dart
+++ b/lib/utils/check_for_supported_patch.dart
@@ -27,12 +27,12 @@ bool hasUnsupportedRequiredOption(List