diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml
index 647ab294..0bb9bdb1 100644
--- a/.github/workflows/pr-build.yml
+++ b/.github/workflows/pr-build.yml
@@ -2,7 +2,12 @@ name: PR Build
on:
pull_request:
-
+ paths:
+ - ".github/workflows/pr-build.yml"
+ - "android/**"
+ - "assets/**"
+ - "lib/**"
+
jobs:
build:
name: Build
@@ -20,7 +25,6 @@ jobs:
with:
java-version: '11'
distribution: 'zulu'
- cache: 'gradle'
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
@@ -33,9 +37,9 @@ jobs:
- name: Build with Flutter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: flutter build apk
+ run: flutter build apk --debug
- name: Upload build
uses: actions/upload-artifact@v3
with:
name: revanced-manager
- path: build/app/outputs/flutter-apk/app-release.apk
+ path: build/app/outputs/flutter-apk/app-debug.apk
diff --git a/android/app/build.gradle b/android/app/build.gradle
index a752c0e1..1e32f0b7 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -54,7 +54,21 @@ android {
release {
shrinkResources false
minifyEnabled false
+ resValue "string", "app_name", "ReVanced Manager"
signingConfig signingConfigs.debug
+ ndk {
+ abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64'
+ }
+ }
+ debug {
+ shrinkResources false
+ minifyEnabled false
+ resValue "string", "app_name", "ReVanced Manager Debug"
+ applicationIdSuffix ".debug"
+ signingConfig signingConfigs.debug
+ ndk {
+ abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64'
+ }
}
}
@@ -71,10 +85,9 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// ReVanced
- implementation "app.revanced:revanced-patcher:11.0.4"
+ implementation "app.revanced:revanced-patcher:14.1.0"
// Signing & aligning
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
implementation("com.android.tools.build:apksig:7.2.2")
-
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 69b3f6d6..85483241 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -20,7 +20,7 @@
when (call.method) {
"runPatcher" -> {
@@ -77,10 +83,12 @@ class MainActivity : FlutterActivity() {
result.notImplemented()
}
}
+
"stopPatcher" -> {
cancel = true
stopResult = result
}
+
else -> result.notImplemented()
}
}
@@ -105,55 +113,79 @@ class MainActivity : FlutterActivity() {
val outFile = File(outFilePath)
val integrations = File(integrationsPath)
val keyStoreFile = File(keyStoreFilePath)
+ val cacheDir = File(cacheDirPath)
Thread {
try {
+ Logger.getLogger("").apply {
+ handlers.forEach {
+ it.close()
+ removeHandler(it)
+ }
+ object : java.util.logging.Handler() {
+ override fun publish(record: LogRecord) = formatter.format(record).toByteArray().let {
+ if (record.level.intValue() > Level.INFO.intValue())
+ System.err.write(it)
+ else
+ System.out.write(it)
+ }
+
+ override fun flush() {
+ System.out.flush()
+ System.err.flush()
+ }
+
+ override fun close() = flush()
+ }.also {
+ it.level = Level.ALL
+ it.formatter = SimpleFormatter()
+ }.let(::addHandler)
+ }
handler.post {
installerChannel.invokeMethod(
"update",
mapOf(
"progress" to 0.1,
"header" to "",
- "log" to "Copying original apk"
+ "log" to "Copying original APK"
)
)
}
- if(cancel) {
+ if (cancel) {
handler.post { stopResult!!.success(null) }
return@Thread
}
originalFile.copyTo(inputFile, true)
+ if (cancel) {
+ handler.post { stopResult!!.success(null) }
+ return@Thread
+ }
+
handler.post {
installerChannel.invokeMethod(
"update",
mapOf(
"progress" to 0.2,
- "header" to "Unpacking apk...",
- "log" to "Unpacking input apk"
+ "header" to "Reading APK...",
+ "log" to "Reading input APK"
)
)
}
- if(cancel) {
- handler.post { stopResult!!.success(null) }
- return@Thread
- }
-
val patcher =
Patcher(
PatcherOptions(
inputFile,
- cacheDirPath,
+ cacheDir,
Aapt.binary(applicationContext).absolutePath,
- cacheDirPath,
- logger = ManagerLogger()
+ cacheDir.path,
)
)
- if(cancel) {
+ if (cancel) {
handler.post { stopResult!!.success(null) }
return@Thread
}
@@ -161,28 +193,19 @@ class MainActivity : FlutterActivity() {
handler.post {
installerChannel.invokeMethod(
"update",
- mapOf("progress" to 0.3, "header" to "", "log" to "")
- )
- }
- handler.post {
- installerChannel.invokeMethod(
- "update",
- mapOf(
- "progress" to 0.4,
- "header" to "Merging integrations...",
- "log" to "Merging integrations"
- )
+ mapOf("progress" to 0.3, "header" to "Loading patches...", "log" to "Loading patches")
)
}
- if(cancel) {
- handler.post { stopResult!!.success(null) }
- return@Thread
- }
+ val patches =
+ PatchBundleLoader.Dex(
+ File(patchBundleFilePath)
+ ).filter { patch ->
+ (patch.compatiblePackages?.any { it.name == patcher.context.packageMetadata.packageName } == true || patch.compatiblePackages.isNullOrEmpty()) &&
+ selectedPatches.any { it == patch.patchName }
+ }
- patcher.addIntegrations(listOf(integrations)) {}
-
- if(cancel) {
+ if (cancel) {
handler.post { stopResult!!.success(null) }
return@Thread
}
@@ -192,91 +215,75 @@ class MainActivity : FlutterActivity() {
"update",
mapOf(
"progress" to 0.5,
- "header" to "Applying patches...",
+ "header" to "Executing patches...",
"log" to ""
)
)
}
- if(cancel) {
+ patcher.apply {
+ acceptIntegrations(listOf(integrations))
+ acceptPatches(patches)
+
+ runBlocking {
+ apply(false).collect { patchResult: PatchResult ->
+ patchResult.exception?.let {
+ if (cancel) {
+ handler.post { stopResult!!.success(null) }
+ this.cancel()
+ return@collect
+ }
+ StringWriter().use { writer ->
+ it.printStackTrace(PrintWriter(writer))
+ handler.post {
+ installerChannel.invokeMethod(
+ "update",
+ mapOf("progress" to 0.5, "header" to "", "log" to "${patchResult.patchName} failed: $writer")
+ )
+ }
+ }
+ } ?: run {
+ if (cancel) {
+ handler.post { stopResult!!.success(null) }
+ this.cancel()
+ return@collect
+ }
+ val msg = "${patchResult.patchName} succeeded"
+ handler.post {
+ installerChannel.invokeMethod(
+ "update",
+ mapOf(
+ "progress" to 0.5,
+ "header" to "",
+ "log" to msg
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+
+ if (cancel) {
handler.post { stopResult!!.success(null) }
return@Thread
}
- val patches = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) {
- PatchBundle.Dex(
- patchBundleFilePath,
- DexClassLoader(
- patchBundleFilePath,
- cacheDirPath,
- null,
- javaClass.classLoader
- )
- ).loadPatches().filter { patch ->
- (patch.compatiblePackages?.any { it.name == patcher.context.packageMetadata.packageName } == true || patch.compatiblePackages.isNullOrEmpty()) &&
- selectedPatches.any { it == patch.patchName }
- }
- } else {
- TODO("VERSION.SDK_INT < CUPCAKE")
- }
-
- if(cancel) {
- handler.post { stopResult!!.success(null) }
- return@Thread
- }
-
- patcher.addPatches(patches)
- patcher.executePatches().forEach { (patch, res) ->
- if (res.isSuccess) {
- val msg = "Applied $patch"
- handler.post {
- installerChannel.invokeMethod(
- "update",
- mapOf(
- "progress" to 0.5,
- "header" to "",
- "log" to msg
- )
- )
- }
- if(cancel) {
- handler.post { stopResult!!.success(null) }
- return@Thread
- }
- return@forEach
- }
- val msg =
- "Failed to apply $patch: " + "${res.exceptionOrNull()!!.message ?: res.exceptionOrNull()!!.cause!!::class.simpleName}"
- handler.post {
- installerChannel.invokeMethod(
- "update",
- mapOf("progress" to 0.5, "header" to "", "log" to msg)
- )
- }
- if(cancel) {
- handler.post { stopResult!!.success(null) }
- return@Thread
- }
- }
-
handler.post {
installerChannel.invokeMethod(
"update",
mapOf(
"progress" to 0.7,
- "header" to "Repacking apk...",
- "log" to "Repacking patched apk"
+ "header" to "Repacking APK...",
+ "log" to ""
)
)
}
- if(cancel) {
- handler.post { stopResult!!.success(null) }
- return@Thread
- }
- val res = patcher.save()
+ val res = patcher.get()
+ patcher.close()
ZipFile(patchedFile).use { file ->
res.dexFiles.forEach {
- if(cancel) {
+ if (cancel) {
handler.post { stopResult!!.success(null) }
return@Thread
}
@@ -296,7 +303,7 @@ class MainActivity : FlutterActivity() {
ZipAligner::getEntryAlignment
)
}
- if(cancel) {
+ if (cancel) {
handler.post { stopResult!!.success(null) }
return@Thread
}
@@ -305,7 +312,7 @@ class MainActivity : FlutterActivity() {
"update",
mapOf(
"progress" to 0.9,
- "header" to "Signing apk...",
+ "header" to "Signing APK...",
"log" to ""
)
)
@@ -319,7 +326,7 @@ class MainActivity : FlutterActivity() {
)
} catch (e: Exception) {
//log to console
- print("Error signing apk: ${e.message}")
+ print("Error signing APK: ${e.message}")
e.printStackTrace()
}
@@ -334,52 +341,54 @@ class MainActivity : FlutterActivity() {
)
}
} catch (ex: Throwable) {
- val stack = ex.stackTraceToString()
- handler.post {
- installerChannel.invokeMethod(
- "update",
- mapOf(
- "progress" to -100.0,
- "header" to "Aborted...",
- "log" to "An error occurred! Aborted\nError:\n$stack"
+ if (!cancel) {
+ val stack = ex.stackTraceToString()
+ handler.post {
+ installerChannel.invokeMethod(
+ "update",
+ mapOf(
+ "progress" to -100.0,
+ "header" to "Aborted...",
+ "log" to "An error occurred! Aborted\nError:\n$stack"
+ )
)
- )
+ }
}
}
handler.post { result.success(null) }
}.start()
}
- inner class ManagerLogger : Logger {
- override fun error(msg: String) {
- handler.post {
- installerChannel
- .invokeMethod(
- "update",
- mapOf("progress" to -1.0, "header" to "", "log" to msg)
- )
- }
- }
-
- override fun warn(msg: String) {
- handler.post {
- installerChannel.invokeMethod(
- "update",
- mapOf("progress" to -1.0, "header" to "", "log" to msg)
- )
- }
- }
-
- override fun info(msg: String) {
- handler.post {
- installerChannel.invokeMethod(
- "update",
- mapOf("progress" to -1.0, "header" to "", "log" to msg)
- )
- }
- }
-
- override fun trace(_msg: String) { /* unused */
- }
- }
+// inner class ManagerLogger : Logger {
+// override fun error(msg: String) {
+// handler.post {
+// installerChannel
+// .invokeMethod(
+// "update",
+// mapOf("progress" to -1.0, "header" to "", "log" to msg)
+// )
+// }
+// }
+//
+// override fun warn(msg: String) {
+// handler.post {
+// installerChannel.invokeMethod(
+// "update",
+// mapOf("progress" to -1.0, "header" to "", "log" to msg)
+// )
+// }
+// }
+//
+// override fun info(msg: String) {
+// handler.post {
+// installerChannel.invokeMethod(
+// "update",
+// mapOf("progress" to -1.0, "header" to "", "log" to msg)
+// )
+// }
+// }
+//
+// override fun trace(_msg: String) { /* unused */
+// }
+// }
}
diff --git a/android/build.gradle b/android/build.gradle
index 5086ec38..bfe266a7 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,5 +1,5 @@
buildscript {
- ext.kotlin_version = '1.7.10'
+ ext.kotlin_version = '1.9.0'
repositories {
google()
mavenCentral()
@@ -22,6 +22,7 @@ allprojects {
password = (project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN")) as String
}
}
+ mavenLocal()
}
}
diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json
index 2f7e1b64..63e82677 100644
--- a/assets/i18n/en_US.json
+++ b/assets/i18n/en_US.json
@@ -11,6 +11,7 @@
"noButton": "No",
"warning": "Warning",
"notice": "Notice",
+ "noShowAgain": "Don't show this again",
"new": "New",
"navigationView": {
"dashboardTab": "Dashboard",
@@ -136,12 +137,21 @@
"unsupportedPatchVersion": "Patch is not supported for this app version. Enable the experimental toggle in settings to proceed.",
"newPatchDialogText": "This is a new patch that has been added since the last time you have patched this app.",
- "newPatch": "New patch"
+ "newPatch": "New patch",
+
+ "patchesChangeWarningDialogText": "It is recommended to use the default selection of patches because changing it may cause unexpected issues.\n\nIf you know what you are doing, you can enable \"Enable changing selection\" in the settings.",
+ "patchesChangeWarningDialogButton": "Use default selection"
},
"installerView": {
"widgetTitle": "Installer",
+ "installType": "Select install type",
+ "installTypeDescription": "Select the installation type to proceed with.",
+
"installButton": "Install",
- "installRootButton": "Install as Root",
+ "installRootType": "Root",
+ "installNonRootType": "Non-root",
+ "installRecommendedType": "Recommended",
+
"pressBackAgain": "Press back again to cancel",
"openButton": "Open",
"shareButton": "Share file",
@@ -149,9 +159,8 @@
"notificationTitle": "ReVanced Manager is patching",
"notificationText": "Tap to return to the installer",
- "shareApkMenuOption": "Share APK",
- "exportApkMenuOption": "Export APK",
- "shareLogMenuOption": "Share log",
+ "exportApkButtonTooltip": "Export patched APK",
+ "exportLogButtonTooltip": "Export log",
"installErrorDialogTitle": "Error",
"installErrorDialogText1": "Root install is not possible with the current patches selection.\nRepatch your app or choose non-root install.",
@@ -200,6 +209,11 @@
"logsLabel": "Logs",
"logsHint": "Share Manager's logs",
+ "enablePatchesSelectionLabel": "Enable changing selection",
+ "enablePatchesSelectionHint": "Enable changing the selection of patches.",
+ "enablePatchesSelectionWarningText": "Changing the default selection of patches may cause unexpected issues.\n\nEnable anyways?",
+ "disablePatchesSelectionWarningText": "You are about to disable changing the selection of patches.\nThe default selection of patches will be restored.\n\nDisable anyways?",
+
"autoUpdatePatchesLabel": "Auto update patches",
"autoUpdatePatchesHint": "Automatically update ReVanced Patches to the latest version",
"experimentalUniversalPatchesLabel": "Experimental universal patches support",
diff --git a/docs/1_installation.md b/docs/1_installation.md
index b25e0758..d4c08984 100644
--- a/docs/1_installation.md
+++ b/docs/1_installation.md
@@ -2,7 +2,7 @@
In order to use ReVanced on your Android device, ReVanced Manager must be installed.
-## 🪜 Installation steps
+## ✅ Installation steps
1. Download the latest version of ReVanced Manager from [here](https://github.com/revanced/revanced-manager/releases/latest)
2. Install ReVanced Manager
@@ -11,4 +11,4 @@ In order to use ReVanced on your Android device, ReVanced Manager must be instal
The next page will guide you through using ReVanced Manager.
-Continue: [🪛 Usage](2_usage.md)
+Continue: [🛠️ Usage](2_usage.md)
diff --git a/docs/2_1_patching.md b/docs/2_1_patching.md
index 5dab9d64..428660ce 100644
--- a/docs/2_1_patching.md
+++ b/docs/2_1_patching.md
@@ -2,7 +2,7 @@
The following pages will guide you through using ReVanced Manager to patch apps.
-## 🪜 Steps to patch apps
+## ✅ Steps to patch apps
1. Navigate to the **Patcher** tab from the bottom navigation bar
2. Tap on the **Select an app** card
diff --git a/docs/2_2_managing.md b/docs/2_2_managing.md
index 5e8e378b..1ad02229 100644
--- a/docs/2_2_managing.md
+++ b/docs/2_2_managing.md
@@ -2,7 +2,7 @@
After patching an app, you may want to manage it. This page will guide you through managing patched apps.
-## 🪜 Steps to manage patched apps
+## ✅ Steps to manage patched apps
1. Tap on the **Dashboard** tab in the bottom navigation bar
2. Tap on the **Info** button for the app you want to manage
diff --git a/docs/2_3_updating.md b/docs/2_3_updating.md
index a14717a3..9851ac90 100644
--- a/docs/2_3_updating.md
+++ b/docs/2_3_updating.md
@@ -2,7 +2,7 @@
In order to keep up with the latest features and bug fixes, it is recommended to keep ReVanced Manager up to date.
-## 🪜 Updating steps
+## ✅ Updating steps
1. Navigate to the **Dashboard** tab from the bottom navigation bar
2. Tap on the **Update** button in the **Updates** section
diff --git a/docs/2_4_settings.md b/docs/2_4_settings.md
index e1d49033..008cda46 100644
--- a/docs/2_4_settings.md
+++ b/docs/2_4_settings.md
@@ -2,7 +2,7 @@
ReVanced Manager has settings that can be configured to your liking.
-## 🪛 Essential settings
+## ⭐ Essential settings
- ### 🔗 API URL
diff --git a/docs/2_usage.md b/docs/2_usage.md
index 2bef4330..f079782f 100644
--- a/docs/2_usage.md
+++ b/docs/2_usage.md
@@ -13,4 +13,4 @@ The following pages will guide you through using ReVanced Manager to patch apps,
The next page will guide you through troubleshooting ReVanced Manager.
-Continue: [🛟 Troubleshooting](3_troubleshooting.md)
+Continue: [❔ Troubleshooting](3_troubleshooting.md)
diff --git a/docs/3_troubleshooting.md b/docs/3_troubleshooting.md
index 80b93048..5a860c6b 100644
--- a/docs/3_troubleshooting.md
+++ b/docs/3_troubleshooting.md
@@ -1,4 +1,4 @@
-# 🛟 Troubleshooting
+# ❔ Troubleshooting
In case you encounter any issues while using ReVanced Manager, please refer to this page for possible solutions.
@@ -28,4 +28,4 @@ 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)
+Continue: [🔨 Building from source](4_building.md)
diff --git a/docs/README.md b/docs/README.md
index 0577ad03..af2926b6 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -11,8 +11,8 @@ This documentation explains how to use [ReVanced Manager](https://github.com/rev
2. [🧰 Managing patched apps](2_2_managing.md)
3. [🔄 Updating ReVanced Manager](2_3_updating.md)
4. [⚙️ Configuring ReVanced Manager](2_4_settings.md)
-3. [🛟 Troubleshooting](3_troubleshooting.md)
-4. [🛠 Building from source](4_building.md)
+3. [❔ Troubleshooting](3_troubleshooting.md)
+4. [🔨 Building from source](4_building.md)
## ⏭️ Start here
diff --git a/lib/services/github_api.dart b/lib/services/github_api.dart
index c846d272..de8c160b 100644
--- a/lib/services/github_api.dart
+++ b/lib/services/github_api.dart
@@ -6,12 +6,14 @@ import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.dart';
+import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/services/manager_api.dart';
@lazySingleton
class GithubAPI {
late Dio _dio = Dio();
+ late final ManagerAPI _managerAPI = locator();
final _cacheOptions = CacheOptions(
store: MemCacheStore(),
@@ -201,8 +203,14 @@ class GithubAPI {
String extension,
String repoName,
String version,
+ String url,
) async {
try {
+ if (url.isNotEmpty) {
+ return await DefaultCacheManager().getSingleFile(
+ url,
+ );
+ }
final Map? release =
await getPatchesRelease(repoName, version);
if (release != null) {
@@ -211,8 +219,16 @@ class GithubAPI {
(asset) => (asset['name'] as String).endsWith(extension),
);
if (asset != null) {
+ final String downloadUrl = asset['browser_download_url'];
+ if (extension == '.apk') {
+ _managerAPI.setIntegrationsDownloadURL(downloadUrl);
+ } else if (extension == '.json') {
+ _managerAPI.setPatchesDownloadURL(downloadUrl, false);
+ } else {
+ _managerAPI.setPatchesDownloadURL(downloadUrl, true);
+ }
return await DefaultCacheManager().getSingleFile(
- asset['browser_download_url'],
+ downloadUrl,
);
}
}
@@ -224,10 +240,19 @@ class GithubAPI {
return null;
}
- Future> getPatches(String repoName, String version) async {
+ Future> getPatches(
+ String repoName,
+ String version,
+ String url,
+ ) async {
List patches = [];
try {
- final File? f = await getPatchesReleaseFile('.json', repoName, version);
+ final File? f = await getPatchesReleaseFile(
+ '.json',
+ repoName,
+ version,
+ url,
+ );
if (f != null) {
final List list = jsonDecode(f.readAsStringSync());
patches = list.map((patch) => Patch.fromJson(patch)).toList();
diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart
index 28b2b8fa..1391f707 100644
--- a/lib/services/manager_api.dart
+++ b/lib/services/manager_api.dart
@@ -2,6 +2,8 @@ import 'dart:convert';
import 'dart:io';
import 'package:device_apps/device_apps.dart';
import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:injectable/injectable.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
@@ -11,6 +13,7 @@ import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/github_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';
@@ -76,6 +79,14 @@ class ManagerAPI {
await _prefs.setString('repoUrl', url);
}
+ String getPatchesDownloadURL(bool bundle) {
+ return _prefs.getString('patchesDownloadURL-$bundle') ?? '';
+ }
+
+ Future setPatchesDownloadURL(String value, bool bundle) async {
+ await _prefs.setString('patchesDownloadURL-$bundle', value);
+ }
+
String getPatchesRepo() {
return _prefs.getString('patchesRepo') ?? defaultPatchesRepo;
}
@@ -99,6 +110,41 @@ class ManagerAPI {
return _prefs.getBool('patchesAutoUpdate') ?? false;
}
+ bool isPatchesChangeEnabled() {
+ if (getPatchedApps().isNotEmpty && !isChangingToggleModified()) {
+ for (final apps in getPatchedApps()) {
+ if (getSavedPatches(apps.originalPackageName)
+ .indexWhere((patch) => patch.excluded) !=
+ -1) {
+ setPatchesChangeWarning(false);
+ setPatchesChangeEnabled(true);
+ break;
+ }
+ }
+ }
+ return _prefs.getBool('patchesChangeEnabled') ?? false;
+ }
+
+ void setPatchesChangeEnabled(bool value) {
+ _prefs.setBool('patchesChangeEnabled', value);
+ }
+
+ bool showPatchesChangeWarning() {
+ return _prefs.getBool('showPatchesChangeWarning') ?? true;
+ }
+
+ void setPatchesChangeWarning(bool value) {
+ _prefs.setBool('showPatchesChangeWarning', !value);
+ }
+
+ bool isChangingToggleModified() {
+ return _prefs.getBool('isChangingToggleModified') ?? false;
+ }
+
+ void setChangingToggleModified(bool value) {
+ _prefs.setBool('isChangingToggleModified', value);
+ }
+
Future setPatchesAutoUpdate(bool value) async {
await _prefs.setBool('patchesAutoUpdate', value);
}
@@ -119,6 +165,14 @@ class ManagerAPI {
await _prefs.setStringList('savedPatches-$packageName', patchesJson);
}
+ String getIntegrationsDownloadURL() {
+ return _prefs.getString('integrationsDownloadURL') ?? '';
+ }
+
+ Future setIntegrationsDownloadURL(String value) async {
+ await _prefs.setString('integrationsDownloadURL', value);
+ }
+
List getUsedPatches(String packageName) {
final List patchesJson =
_prefs.getStringList('usedPatches-$packageName') ?? [];
@@ -260,7 +314,12 @@ class ManagerAPI {
try {
final String repoName = getPatchesRepo();
final String currentVersion = await getCurrentPatchesVersion();
- return await _githubAPI.getPatches(repoName, currentVersion);
+ final String url = getPatchesDownloadURL(false);
+ return await _githubAPI.getPatches(
+ repoName,
+ currentVersion,
+ url,
+ );
} on Exception catch (e) {
if (kDebugMode) {
print(e);
@@ -273,10 +332,12 @@ class ManagerAPI {
try {
final String repoName = getPatchesRepo();
final String currentVersion = await getCurrentPatchesVersion();
+ final String url = getPatchesDownloadURL(true);
return await _githubAPI.getPatchesReleaseFile(
'.jar',
repoName,
currentVersion,
+ url,
);
} on Exception catch (e) {
if (kDebugMode) {
@@ -290,10 +351,12 @@ class ManagerAPI {
try {
final String repoName = getIntegrationsRepo();
final String currentVersion = await getCurrentIntegrationsVersion();
+ final String url = getIntegrationsDownloadURL();
return await _githubAPI.getPatchesReleaseFile(
'.apk',
repoName,
currentVersion,
+ url,
);
} on Exception catch (e) {
if (kDebugMode) {
@@ -384,27 +447,39 @@ class ManagerAPI {
Future getCurrentPatchesVersion() async {
patchesVersion = _prefs.getString('patchesVersion') ?? '0.0.0';
if (patchesVersion == '0.0.0' || isPatchesAutoUpdate()) {
- patchesVersion = await getLatestPatchesVersion() ?? '0.0.0';
- await setCurrentPatchesVersion(patchesVersion!);
+ final String newPatchesVersion =
+ await getLatestPatchesVersion() ?? '0.0.0';
+ if (patchesVersion != newPatchesVersion && newPatchesVersion != '0.0.0') {
+ await setCurrentPatchesVersion(newPatchesVersion);
+ }
}
return patchesVersion!;
}
Future setCurrentPatchesVersion(String version) async {
await _prefs.setString('patchesVersion', version);
+ await setPatchesDownloadURL('', false);
+ await setPatchesDownloadURL('', true);
+ await downloadPatches();
}
Future getCurrentIntegrationsVersion() async {
integrationsVersion = _prefs.getString('integrationsVersion') ?? '0.0.0';
if (integrationsVersion == '0.0.0' || isPatchesAutoUpdate()) {
- integrationsVersion = await getLatestIntegrationsVersion() ?? '0.0.0';
- await setCurrentIntegrationsVersion(integrationsVersion!);
+ final String newIntegrationsVersion =
+ await getLatestIntegrationsVersion() ?? '0.0.0';
+ if (integrationsVersion != newIntegrationsVersion &&
+ newIntegrationsVersion != '0.0.0') {
+ await setCurrentIntegrationsVersion(newIntegrationsVersion);
+ }
}
return integrationsVersion!;
}
Future setCurrentIntegrationsVersion(String version) async {
await _prefs.setString('integrationsVersion', version);
+ await setIntegrationsDownloadURL('');
+ await downloadIntegrations();
}
Future> getAppsToRemove(
@@ -478,6 +553,63 @@ class ManagerAPI {
return unsavedApps;
}
+ Future showPatchesChangeWarningDialog(BuildContext context) {
+ final ValueNotifier noShow =
+ ValueNotifier(!showPatchesChangeWarning());
+ return showDialog(
+ barrierDismissible: false,
+ context: context,
+ builder: (context) => WillPopScope(
+ onWillPop: () async => false,
+ child: AlertDialog(
+ backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
+ title: I18nText('warning'),
+ content: ValueListenableBuilder(
+ valueListenable: noShow,
+ builder: (context, value, child) {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ I18nText(
+ 'patchItem.patchesChangeWarningDialogText',
+ child: const Text(
+ '',
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ const SizedBox(height: 8),
+ CheckboxListTile(
+ value: value,
+ contentPadding: EdgeInsets.zero,
+ title: I18nText(
+ 'noShowAgain',
+ ),
+ onChanged: (selected) {
+ noShow.value = selected!;
+ },
+ ),
+ ],
+ );
+ },
+ ),
+ actions: [
+ CustomMaterialButton(
+ label: I18nText('okButton'),
+ onPressed: () {
+ setPatchesChangeWarning(noShow.value);
+ Navigator.of(context).pop();
+ },
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
Future reAssessSavedApps() async {
final List patchedApps = getPatchedApps();
final List unsavedApps =
diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart
index df99db8b..dfaaf3ec 100644
--- a/lib/services/patcher_api.dart
+++ b/lib/services/patcher_api.dart
@@ -30,6 +30,8 @@ class PatcherAPI {
Future initialize() async {
await _loadPatches();
+ await _managerAPI.downloadPatches();
+ await _managerAPI.downloadIntegrations();
final Directory appCache = await getTemporaryDirectory();
_dataDir = await getExternalStorageDirectory() ?? appCache;
_tmpDir = Directory('${appCache.path}/patcher');
@@ -283,7 +285,7 @@ class PatcherAPI {
return newName;
}
- Future sharePatcherLog(String logs) async {
+ Future exportPatcherLog(String logs) async {
final Directory appCache = await getTemporaryDirectory();
final Directory logDir = Directory('${appCache.path}/logs');
logDir.createSync();
@@ -293,10 +295,15 @@ class PatcherAPI {
.replaceAll(':', '')
.replaceAll('T', '')
.replaceAll('.', '');
- final File log =
- File('${logDir.path}/revanced-manager_patcher_$dateTime.log');
+ final String fileName = 'revanced-manager_patcher_$dateTime.log';
+ final File log = File('${logDir.path}/$fileName');
log.writeAsStringSync(logs);
- ShareExtend.share(log.path, 'file');
+ CRFileSaver.saveFileWithDialog(
+ SaveFileDialogParams(
+ sourceFilePath: log.path,
+ destinationFileName: fileName,
+ ),
+ );
}
String getSuggestedVersion(String packageName) {
diff --git a/lib/ui/views/app_selector/app_selector_view.dart b/lib/ui/views/app_selector/app_selector_view.dart
index 6368df61..7877a226 100644
--- a/lib/ui/views/app_selector/app_selector_view.dart
+++ b/lib/ui/views/app_selector/app_selector_view.dart
@@ -94,7 +94,7 @@ class _AppSelectorViewState extends State {
padding: const EdgeInsets.symmetric(horizontal: 12.0)
.copyWith(
bottom:
- MediaQuery.of(context).viewPadding.bottom + 8.0,
+ MediaQuery.viewPaddingOf(context).bottom + 8.0,
),
child: Column(
children: [
@@ -133,6 +133,7 @@ class _AppSelectorViewState extends State {
),
)
.toList(),
+ const SizedBox(height: 70.0),
],
),
),
diff --git a/lib/ui/views/contributors/contributors_view.dart b/lib/ui/views/contributors/contributors_view.dart
index 26cdb12b..a409c17e 100644
--- a/lib/ui/views/contributors/contributors_view.dart
+++ b/lib/ui/views/contributors/contributors_view.dart
@@ -57,7 +57,7 @@ class ContributorsView extends StatelessWidget {
title: 'contributorsView.managerContributors',
contributors: model.managerContributors,
),
- SizedBox(height: MediaQuery.of(context).viewPadding.bottom),
+ SizedBox(height: MediaQuery.viewPaddingOf(context).bottom),
],
),
),
diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart
index e2ce5125..cfc99e66 100644
--- a/lib/ui/views/home/home_viewmodel.dart
+++ b/lib/ui/views/home/home_viewmodel.dart
@@ -256,9 +256,9 @@ class HomeViewModel extends BaseViewModel {
final String integrationsVersion =
await _managerAPI.getLatestIntegrationsVersion() ?? '0.0.0';
if (patchesVersion != '0.0.0' && integrationsVersion != '0.0.0') {
- _toast.showBottom('homeView.downloadedMessage');
await _managerAPI.setCurrentPatchesVersion(patchesVersion);
await _managerAPI.setCurrentIntegrationsVersion(integrationsVersion);
+ _toast.showBottom('homeView.downloadedMessage');
forceRefresh(context);
} else {
_toast.showBottom('homeView.errorDownloadMessage');
diff --git a/lib/ui/views/installer/installer_view.dart b/lib/ui/views/installer/installer_view.dart
index 981d0f55..508c449d 100644
--- a/lib/ui/views/installer/installer_view.dart
+++ b/lib/ui/views/installer/installer_view.dart
@@ -4,8 +4,6 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/installerView/gradient_progress_indicator.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
-import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
-import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
import 'package:stacked/stacked.dart';
@@ -22,6 +20,45 @@ class InstallerView extends StatelessWidget {
top: false,
bottom: false,
child: Scaffold(
+ floatingActionButton: Visibility(
+ visible: !model.isPatching,
+ child: FloatingActionButton.extended(
+ label: I18nText('installerView.installButton'),
+ icon: const Icon(Icons.file_download_outlined),
+ onPressed: () => model.installTypeDialog(context),
+ elevation: 0,
+ ),
+ ),
+ floatingActionButtonLocation:
+ FloatingActionButtonLocation.endContained,
+ bottomNavigationBar: Visibility(
+ visible: !model.isPatching,
+ child: BottomAppBar(
+ child: Row(
+ children: [
+ Visibility(
+ visible: !model.hasErrors,
+ child: IconButton.filledTonal(
+ tooltip: FlutterI18n.translate(
+ context,
+ 'installerView.exportApkButtonTooltip',
+ ),
+ icon: const Icon(Icons.save),
+ onPressed: () => model.onButtonPressed(0),
+ ),
+ ),
+ IconButton.filledTonal(
+ tooltip: FlutterI18n.translate(
+ context,
+ 'installerView.exportLogButtonTooltip',
+ ),
+ icon: const Icon(Icons.post_add),
+ onPressed: () => model.onButtonPressed(1),
+ ),
+ ],
+ ),
+ ),
+ ),
body: CustomScrollView(
controller: model.scrollController,
slivers: [
@@ -35,44 +72,6 @@ class InstallerView extends StatelessWidget {
overflow: TextOverflow.ellipsis,
),
onBackButtonPressed: () => model.onWillPop(context),
- actions: [
- Visibility(
- visible: !model.isPatching,
- child: CustomPopupMenu(
- onSelected: (value) => model.onMenuSelection(value),
- children: {
- if (!model.hasErrors)
- 0: I18nText(
- 'installerView.shareApkMenuOption',
- child: const Text(
- '',
- style: TextStyle(
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- 1: I18nText(
- 'installerView.exportApkMenuOption',
- child: const Text(
- '',
- style: TextStyle(
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- 2: I18nText(
- 'installerView.shareLogMenuOption',
- child: const Text(
- '',
- style: TextStyle(
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- },
- ),
- ),
- ],
bottom: PreferredSize(
preferredSize: const Size(double.infinity, 1.0),
child: GradientProgressIndicator(progress: model.progress),
@@ -96,72 +95,6 @@ class InstallerView extends StatelessWidget {
),
),
),
- SliverFillRemaining(
- hasScrollBody: false,
- child: Align(
- alignment: Alignment.bottomCenter,
- child: Visibility(
- visible: !model.isPatching && !model.hasErrors,
- child: Padding(
- padding: const EdgeInsets.all(20.0).copyWith(top: 0.0),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.end,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Visibility(
- visible: model.isInstalled,
- child: CustomMaterialButton(
- label: I18nText('installerView.openButton'),
- isExpanded: true,
- onPressed: () {
- model.openApp();
- model.cleanPatcher();
- Navigator.of(context).pop();
- },
- ),
- ),
- Visibility(
- visible: !model.isInstalled && model.isRooted,
- child: CustomMaterialButton(
- isFilled: false,
- label:
- I18nText('installerView.installRootButton'),
- isExpanded: true,
- onPressed: () => model.installResult(
- context,
- true,
- ),
- ),
- ),
- Visibility(
- visible: !model.isInstalled,
- child: const SizedBox(
- width: 16,
- ),
- ),
- Visibility(
- visible: !model.isInstalled,
- child: CustomMaterialButton(
- label: I18nText('installerView.installButton'),
- isExpanded: true,
- onPressed: () => model.installResult(
- context,
- false,
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- ),
- ),
- SliverFillRemaining(
- hasScrollBody: false,
- child: SizedBox(
- height: MediaQuery.of(context).viewPadding.bottom,
- ),
- ),
],
),
),
diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart
index 3258e9ba..b11d47da 100644
--- a/lib/ui/views/installer/installer_viewmodel.dart
+++ b/lib/ui/views/installer/installer_viewmodel.dart
@@ -169,6 +169,89 @@ class InstallerViewModel extends BaseViewModel {
}
}
+ Future installTypeDialog(BuildContext context) async {
+ final ValueNotifier installType = ValueNotifier(0);
+ if (isRooted) {
+ await showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (context) => 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: ValueListenableBuilder(
+ valueListenable: installType,
+ builder: (context, value, child) {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 20,
+ vertical: 10,
+ ),
+ child: I18nText(
+ 'installerView.installTypeDescription',
+ child: Text(
+ '',
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ color: Theme.of(context).colorScheme.secondary,
+ ),
+ ),
+ ),
+ ),
+ RadioListTile(
+ title: I18nText('installerView.installNonRootType'),
+ subtitle: I18nText('installerView.installRecommendedType'),
+ contentPadding: const EdgeInsets.symmetric(horizontal: 10),
+ value: 0,
+ groupValue: value,
+ onChanged: (selected) {
+ installType.value = selected!;
+ },
+ ),
+ RadioListTile(
+ title: I18nText('installerView.installRootType'),
+ contentPadding: const EdgeInsets.symmetric(horizontal: 10),
+ value: 1,
+ groupValue: value,
+ onChanged: (selected) {
+ installType.value = selected!;
+ },
+ ),
+ ],
+ );
+ },
+ ),
+ actions: [
+ CustomMaterialButton(
+ label: I18nText('cancelButton'),
+ isFilled: false,
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ ),
+ CustomMaterialButton(
+ label: I18nText('installerView.installButton'),
+ onPressed: () {
+ Navigator.of(context).pop();
+ installResult(context, installType.value == 1);
+ },
+ ),
+ ],
+ ),
+ );
+ } else {
+ installResult(context, false);
+ }
+ }
+
Future stopPatcher() async {
try {
isCanceled = true;
@@ -253,18 +336,8 @@ class InstallerViewModel extends BaseViewModel {
}
}
- void shareResult() {
- try {
- _patcherAPI.sharePatchedFile(_app.name, _app.version);
- } on Exception catch (e) {
- if (kDebugMode) {
- print(e);
- }
- }
- }
-
- void shareLog() {
- _patcherAPI.sharePatcherLog(logs);
+ void exportLog() {
+ _patcherAPI.exportPatcherLog(logs);
}
Future cleanPatcher() async {
@@ -284,16 +357,13 @@ class InstallerViewModel extends BaseViewModel {
DeviceApps.openApp(_app.packageName);
}
- void onMenuSelection(int value) {
+ void onButtonPressed(int value) {
switch (value) {
case 0:
- shareResult();
- break;
- case 1:
exportResult();
break;
- case 2:
- shareLog();
+ case 1:
+ exportLog();
break;
}
}
diff --git a/lib/ui/views/navigation/navigation_viewmodel.dart b/lib/ui/views/navigation/navigation_viewmodel.dart
index 51b3497c..99110cc1 100644
--- a/lib/ui/views/navigation/navigation_viewmodel.dart
+++ b/lib/ui/views/navigation/navigation_viewmodel.dart
@@ -33,7 +33,7 @@ class NavigationViewModel extends IndexTrackingViewModel {
if (prefs.getBool('useDarkTheme') == null) {
final bool isDark =
- MediaQuery.of(context).platformBrightness != Brightness.light;
+ MediaQuery.platformBrightnessOf(context) != Brightness.light;
await prefs.setBool('useDarkTheme', isDark);
await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0);
}
diff --git a/lib/ui/views/patcher/patcher_viewmodel.dart b/lib/ui/views/patcher/patcher_viewmodel.dart
index 843ee6a7..33ceb719 100644
--- a/lib/ui/views/patcher/patcher_viewmodel.dart
+++ b/lib/ui/views/patcher/patcher_viewmodel.dart
@@ -191,6 +191,10 @@ class PatcherViewModel extends BaseViewModel {
this
.selectedPatches
.addAll(patches.where((patch) => selectedPatches.contains(patch.name)));
+ if (!_managerAPI.isPatchesChangeEnabled()) {
+ this.selectedPatches.clear();
+ this.selectedPatches.addAll(patches.where((patch) => !patch.excluded));
+ }
if (!_managerAPI.areExperimentalPatchesEnabled()) {
this.selectedPatches.removeWhere((patch) => !isPatchSupported(patch));
}
diff --git a/lib/ui/views/patches_selector/patches_selector_view.dart b/lib/ui/views/patches_selector/patches_selector_view.dart
index f5f4a7bd..9039c2ce 100644
--- a/lib/ui/views/patches_selector/patches_selector_view.dart
+++ b/lib/ui/views/patches_selector/patches_selector_view.dart
@@ -20,6 +20,17 @@ class _PatchesSelectorViewState extends State {
String _query = '';
final _managerAPI = locator();
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((_) async {
+ if (!_managerAPI.isPatchesChangeEnabled() &&
+ _managerAPI.showPatchesChangeWarning()) {
+ _managerAPI.showPatchesChangeWarningDialog(context);
+ }
+ });
+ }
+
@override
Widget build(BuildContext context) {
return ViewModelBuilder.reactive(
@@ -87,7 +98,8 @@ class _PatchesSelectorViewState extends State {
),
),
CustomPopupMenu(
- onSelected: (value) => {model.onMenuSelection(value)},
+ onSelected: (value) =>
+ {model.onMenuSelection(value, context)},
children: {
0: I18nText(
'patchesSelectorView.loadPatchesSelection',
@@ -139,7 +151,7 @@ class _PatchesSelectorViewState extends State {
: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12.0).copyWith(
- bottom: MediaQuery.of(context).viewPadding.bottom + 8.0,
+ bottom: MediaQuery.viewPaddingOf(context).bottom + 8.0,
),
child: Column(
children: [
@@ -152,7 +164,11 @@ class _PatchesSelectorViewState extends State {
'patchesSelectorView.defaultTooltip',
),
onPressed: () {
- model.selectDefaultPatches();
+ if (_managerAPI.isPatchesChangeEnabled()) {
+ model.selectDefaultPatches();
+ } else {
+ model.showPatchesChangeDialog(context);
+ }
},
),
const SizedBox(width: 8),
@@ -163,7 +179,11 @@ class _PatchesSelectorViewState extends State {
'patchesSelectorView.noneTooltip',
),
onPressed: () {
- model.clearPatches();
+ if (_managerAPI.isPatchesChangeEnabled()) {
+ model.clearPatches();
+ } else {
+ model.showPatchesChangeDialog(context);
+ }
},
),
],
@@ -179,13 +199,14 @@ class _PatchesSelectorViewState extends State {
supportedPackageVersions:
model.getSupportedVersions(patch),
isUnsupported: !isPatchSupported(patch),
+ isChangeEnabled: _managerAPI.isPatchesChangeEnabled(),
isNew: model.isPatchNew(
patch,
model.getAppInfo().packageName,
),
isSelected: model.isSelected(patch),
onChanged: (value) =>
- model.selectPatch(patch, value),
+ model.selectPatch(patch, value, context),
);
} else {
return Container();
@@ -215,10 +236,14 @@ class _PatchesSelectorViewState extends State {
supportedPackageVersions:
model.getSupportedVersions(patch),
isUnsupported: !isPatchSupported(patch),
+ isChangeEnabled: _managerAPI.isPatchesChangeEnabled(),
isNew: false,
isSelected: model.isSelected(patch),
- onChanged: (value) =>
- model.selectPatch(patch, value),
+ onChanged: (value) => model.selectPatch(
+ patch,
+ value,
+ context,
+ ),
);
} 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 f333887d..71e4a16e 100644
--- a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart
+++ b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart
@@ -1,4 +1,6 @@
import 'package:collection/collection.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/models/patched_application.dart';
@@ -6,6 +8,7 @@ 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';
@@ -45,31 +48,70 @@ class PatchesSelectorViewModel extends BaseViewModel {
);
}
- void selectPatch(Patch patch, bool isSelected) {
- if (isSelected && !selectedPatches.contains(patch)) {
- selectedPatches.add(patch);
+ void selectPatch(Patch patch, bool isSelected, BuildContext context) {
+ if (_managerAPI.isPatchesChangeEnabled()) {
+ if (isSelected && !selectedPatches.contains(patch)) {
+ selectedPatches.add(patch);
+ } else {
+ selectedPatches.remove(patch);
+ }
+ notifyListeners();
} else {
- selectedPatches.remove(patch);
+ showPatchesChangeDialog(context);
}
- notifyListeners();
+ }
+
+ Future showPatchesChangeDialog(BuildContext context) async {
+ return showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
+ title: I18nText('warning'),
+ content: I18nText(
+ 'patchItem.patchesChangeWarningDialogText',
+ child: const Text(
+ '',
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ actions: [
+ CustomMaterialButton(
+ isFilled: false,
+ label: I18nText('okButton'),
+ onPressed: () => Navigator.of(context).pop(),
+ ),
+ CustomMaterialButton(
+ label: I18nText('patchItem.patchesChangeWarningDialogButton'),
+ onPressed: () {
+ Navigator.of(context)
+ ..pop()
+ ..pop();
+ },
+ ),
+ ],
+ ),
+ );
}
void selectDefaultPatches() {
selectedPatches.clear();
-
- if (_managerAPI.areExperimentalPatchesEnabled() == false) {
+ if (locator().selectedApp?.originalPackageName != null) {
selectedPatches.addAll(
- patches.where(
- (element) => element.excluded == false && isPatchSupported(element),
- ),
+ _patcherAPI
+ .getFilteredPatches(
+ locator().selectedApp!.originalPackageName,
+ )
+ .where(
+ (element) =>
+ !element.excluded &&
+ (_managerAPI.areExperimentalPatchesEnabled() ||
+ isPatchSupported(element)),
+ ),
);
}
-
- if (_managerAPI.areExperimentalPatchesEnabled()) {
- selectedPatches
- .addAll(patches.where((element) => element.excluded == false));
- }
-
notifyListeners();
}
@@ -133,10 +175,10 @@ class PatchesSelectorViewModel extends BaseViewModel {
}
}
- void onMenuSelection(value) {
+ void onMenuSelection(value, BuildContext context) {
switch (value) {
case 0:
- loadSelectedPatches();
+ loadSelectedPatches(context);
break;
}
}
@@ -150,18 +192,25 @@ class PatchesSelectorViewModel extends BaseViewModel {
);
}
- Future loadSelectedPatches() async {
- final List selectedPatches = await _managerAPI.getSelectedPatches(
- locator().selectedApp!.originalPackageName,
- );
- if (selectedPatches.isNotEmpty) {
- this.selectedPatches.clear();
- this.selectedPatches.addAll(
- patches.where((patch) => selectedPatches.contains(patch.name)),
- );
+ Future loadSelectedPatches(BuildContext context) async {
+ if (_managerAPI.isPatchesChangeEnabled()) {
+ final List selectedPatches = await _managerAPI.getSelectedPatches(
+ locator().selectedApp!.originalPackageName,
+ );
+ if (selectedPatches.isNotEmpty) {
+ this.selectedPatches.clear();
+ this.selectedPatches.addAll(
+ patches.where((patch) => selectedPatches.contains(patch.name)),
+ );
+ if (!_managerAPI.areExperimentalPatchesEnabled()) {
+ this.selectedPatches.removeWhere((patch) => !isPatchSupported(patch));
+ }
+ } else {
+ locator().showBottom('patchesSelectorView.noSavedPatches');
+ }
+ notifyListeners();
} else {
- locator().showBottom('patchesSelectorView.noSavedPatches');
+ showPatchesChangeDialog(context);
}
- notifyListeners();
}
}
diff --git a/lib/ui/views/settings/settingsFragment/settings_manage_sources.dart b/lib/ui/views/settings/settingsFragment/settings_manage_sources.dart
index 97dd35be..76e3171b 100644
--- a/lib/ui/views/settings/settingsFragment/settings_manage_sources.dart
+++ b/lib/ui/views/settings/settingsFragment/settings_manage_sources.dart
@@ -129,6 +129,7 @@ class SManageSources extends BaseViewModel {
'${_orgIntSourceController.text.trim()}/${_intSourceController.text.trim()}',
);
_managerAPI.setCurrentPatchesVersion('0.0.0');
+ _managerAPI.setCurrentIntegrationsVersion('0.0.0');
_toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context).pop();
},
@@ -158,6 +159,7 @@ class SManageSources extends BaseViewModel {
_managerAPI.setPatchesRepo('');
_managerAPI.setIntegrationsRepo('');
_managerAPI.setCurrentPatchesVersion('0.0.0');
+ _managerAPI.setCurrentIntegrationsVersion('0.0.0');
_toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context)
..pop()
diff --git a/lib/ui/views/settings/settings_viewmodel.dart b/lib/ui/views/settings/settings_viewmodel.dart
index 483834b8..2441b0a6 100644
--- a/lib/ui/views/settings/settings_viewmodel.dart
+++ b/lib/ui/views/settings/settings_viewmodel.dart
@@ -3,6 +3,8 @@ import 'package:cr_file_saver/file_saver.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:logcat/logcat.dart';
import 'package:path_provider/path_provider.dart';
import 'package:revanced_manager/app/app.locator.dart';
@@ -10,8 +12,10 @@ import 'package:revanced_manager/app/app.router.dart';
import 'package:revanced_manager/services/manager_api.dart';
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/views/settings/settingsFragment/settings_update_theme.dart';
+import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:share_extend/share_extend.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
@@ -19,6 +23,9 @@ import 'package:stacked_services/stacked_services.dart';
class SettingsViewModel extends BaseViewModel {
final NavigationService _navigationService = locator();
final ManagerAPI _managerAPI = locator();
+ final PatchesSelectorViewModel _patchesSelectorViewModel =
+ PatchesSelectorViewModel();
+ final PatcherViewModel _patcherViewModel = locator();
final Toast _toast = locator();
final SUpdateLanguage sUpdateLanguage = SUpdateLanguage();
@@ -37,6 +44,88 @@ class SettingsViewModel extends BaseViewModel {
notifyListeners();
}
+ bool isPatchesChangeEnabled() {
+ return _managerAPI.isPatchesChangeEnabled();
+ }
+
+ Future showPatchesChangeEnableDialog(
+ bool value,
+ BuildContext context,
+ ) async {
+ if (value) {
+ return showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
+ title: I18nText('warning'),
+ content: I18nText(
+ 'settingsView.enablePatchesSelectionWarningText',
+ child: const Text(
+ '',
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ actions: [
+ CustomMaterialButton(
+ isFilled: false,
+ label: I18nText('noButton'),
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ ),
+ CustomMaterialButton(
+ label: I18nText('yesButton'),
+ onPressed: () {
+ _managerAPI.setChangingToggleModified(true);
+ _managerAPI.setPatchesChangeEnabled(true);
+ Navigator.of(context).pop();
+ },
+ ),
+ ],
+ ),
+ );
+ } else {
+ return showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
+ title: I18nText('warning'),
+ content: I18nText(
+ 'settingsView.disablePatchesSelectionWarningText',
+ child: const Text(
+ '',
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ actions: [
+ CustomMaterialButton(
+ isFilled: false,
+ label: I18nText('noButton'),
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ ),
+ CustomMaterialButton(
+ label: I18nText('yesButton'),
+ onPressed: () {
+ _managerAPI.setChangingToggleModified(true);
+ _patchesSelectorViewModel.selectDefaultPatches();
+ _managerAPI.setPatchesChangeEnabled(false);
+ Navigator.of(context).pop();
+ },
+ ),
+ ],
+ ),
+ );
+ }
+ }
+
bool areUniversalPatchesEnabled() {
return _managerAPI.areUniversalPatchesEnabled();
}
@@ -90,26 +179,30 @@ class SettingsViewModel extends BaseViewModel {
}
}
- Future importPatches() async {
- try {
- final FilePickerResult? result = await FilePicker.platform.pickFiles(
- type: FileType.custom,
- allowedExtensions: ['json'],
- );
- if (result != null && result.files.single.path != null) {
- final File inFile = File(result.files.single.path!);
- inFile.copySync(_managerAPI.storedPatchesFile);
- inFile.delete();
- if (locator().selectedApp != null) {
- locator().loadLastSelectedPatches();
+ Future importPatches(BuildContext context) async {
+ if (isPatchesChangeEnabled()) {
+ try {
+ final FilePickerResult? result = await FilePicker.platform.pickFiles(
+ type: FileType.custom,
+ allowedExtensions: ['json'],
+ );
+ if (result != null && result.files.single.path != null) {
+ final File inFile = File(result.files.single.path!);
+ inFile.copySync(_managerAPI.storedPatchesFile);
+ inFile.delete();
+ if (_patcherViewModel.selectedApp != null) {
+ _patcherViewModel.loadLastSelectedPatches();
+ }
+ _toast.showBottom('settingsView.importedPatches');
}
- _toast.showBottom('settingsView.importedPatches');
+ } on Exception catch (e) {
+ if (kDebugMode) {
+ print(e);
+ }
+ _toast.showBottom('settingsView.jsonSelectorErrorMessage');
}
- } on Exception catch (e) {
- if (kDebugMode) {
- print(e);
- }
- _toast.showBottom('settingsView.jsonSelectorErrorMessage');
+ } else {
+ _managerAPI.showPatchesChangeWarningDialog(context);
}
}
diff --git a/lib/ui/widgets/appSelectorView/app_skeleton_loader.dart b/lib/ui/widgets/appSelectorView/app_skeleton_loader.dart
index 51c51b72..0cb80428 100644
--- a/lib/ui/widgets/appSelectorView/app_skeleton_loader.dart
+++ b/lib/ui/widgets/appSelectorView/app_skeleton_loader.dart
@@ -7,7 +7,7 @@ class AppSkeletonLoader extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final screenWidth = MediaQuery.of(context).size.width;
+ final screenWidth = MediaQuery.sizeOf(context).width;
return ListView.builder(
shrinkWrap: true,
itemCount: 7,
diff --git a/lib/ui/widgets/installerView/gradient_progress_indicator.dart b/lib/ui/widgets/installerView/gradient_progress_indicator.dart
index 3936810b..d4032184 100644
--- a/lib/ui/widgets/installerView/gradient_progress_indicator.dart
+++ b/lib/ui/widgets/installerView/gradient_progress_indicator.dart
@@ -25,7 +25,7 @@ class _GradientProgressIndicatorState extends State {
),
),
height: 5,
- width: MediaQuery.of(context).size.width * widget.progress!,
+ width: MediaQuery.sizeOf(context).width * widget.progress!,
),
);
}
diff --git a/lib/ui/widgets/patchesSelectorView/patch_item.dart b/lib/ui/widgets/patchesSelectorView/patch_item.dart
index 21bd4726..78a92e36 100644
--- a/lib/ui/widgets/patchesSelectorView/patch_item.dart
+++ b/lib/ui/widgets/patchesSelectorView/patch_item.dart
@@ -19,6 +19,7 @@ class PatchItem extends StatefulWidget {
required this.isNew,
required this.isSelected,
required this.onChanged,
+ required this.isChangeEnabled,
this.child,
}) : super(key: key);
final String name;
@@ -30,6 +31,7 @@ class PatchItem extends StatefulWidget {
final bool isNew;
bool isSelected;
final Function(bool) onChanged;
+ final bool isChangeEnabled;
final Widget? child;
final toast = locator();
final _managerAPI = locator();
@@ -58,11 +60,13 @@ class _PatchItemState extends State {
!widget._managerAPI.areExperimentalPatchesEnabled()) {
widget.isSelected = false;
widget.toast.showBottom('patchItem.unsupportedPatchVersion');
- } else {
+ } else if (widget.isChangeEnabled) {
widget.isSelected = !widget.isSelected;
}
});
- widget.onChanged(widget.isSelected);
+ if (!widget.isUnsupported || widget._managerAPI.areExperimentalPatchesEnabled()) {
+ widget.onChanged(widget.isSelected);
+ }
},
child: Column(
children: [
@@ -124,11 +128,13 @@ class _PatchItemState extends State {
widget.toast.showBottom(
'patchItem.unsupportedPatchVersion',
);
- } else {
+ } else if (widget.isChangeEnabled) {
widget.isSelected = newValue!;
}
});
- widget.onChanged(widget.isSelected);
+ if (!widget.isUnsupported || widget._managerAPI.areExperimentalPatchesEnabled()) {
+ widget.onChanged(widget.isSelected);
+ }
},
),
),
@@ -186,7 +192,7 @@ class _PatchItemState extends State {
),
),
),
- )
+ ),
],
),
widget.child ?? const SizedBox(),
diff --git a/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart b/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart
index 3f40e4a5..45a13843 100644
--- a/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart
+++ b/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart
@@ -8,8 +8,9 @@ class OptionsTextField extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final sHeight = MediaQuery.of(context).size.height;
- final sWidth = MediaQuery.of(context).size.width;
+ final size = MediaQuery.sizeOf(context);
+ final sHeight = size.height;
+ final sWidth = size.width;
return Container(
margin: const EdgeInsets.only(top: 12, bottom: 6),
padding: EdgeInsets.zero,
diff --git a/lib/ui/widgets/settingsView/settings_advanced_section.dart b/lib/ui/widgets/settingsView/settings_advanced_section.dart
index 694aa1d5..2d9be3fc 100644
--- a/lib/ui/widgets/settingsView/settings_advanced_section.dart
+++ b/lib/ui/widgets/settingsView/settings_advanced_section.dart
@@ -5,6 +5,7 @@ import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_api_url.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_sources.dart';
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
+import 'package:revanced_manager/ui/widgets/settingsView/settings_enable_patches_selection.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_auto_update_patches.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart';
@@ -25,6 +26,7 @@ class SAdvancedSection extends StatelessWidget {
SManageSourcesUI(),
// SManageKeystorePasswordUI(),
SAutoUpdatePatches(),
+ SEnablePatchesSelection(),
SExperimentalUniversalPatches(),
SExperimentalPatches(),
ListTile(
diff --git a/lib/ui/widgets/settingsView/settings_enable_patches_selection.dart b/lib/ui/widgets/settingsView/settings_enable_patches_selection.dart
new file mode 100644
index 00000000..a0c5b463
--- /dev/null
+++ b/lib/ui/widgets/settingsView/settings_enable_patches_selection.dart
@@ -0,0 +1,37 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_i18n/widgets/I18nText.dart';
+import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
+
+class SEnablePatchesSelection extends StatefulWidget {
+ const SEnablePatchesSelection({super.key});
+
+ @override
+ State createState() => _SEnablePatchesSelectionState();
+}
+
+final _settingsViewModel = SettingsViewModel();
+
+class _SEnablePatchesSelectionState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return SwitchListTile(
+ contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
+ title: I18nText(
+ 'settingsView.enablePatchesSelectionLabel',
+ child: const Text(
+ '',
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ subtitle: I18nText('settingsView.enablePatchesSelectionHint'),
+ value: _settingsViewModel.isPatchesChangeEnabled(),
+ onChanged: (value) async {
+ await _settingsViewModel.showPatchesChangeEnableDialog(value, context);
+ setState(() {});
+ },
+ );
+ }
+}
diff --git a/lib/ui/widgets/settingsView/settings_export_section.dart b/lib/ui/widgets/settingsView/settings_export_section.dart
index 817f9825..bb693739 100644
--- a/lib/ui/widgets/settingsView/settings_export_section.dart
+++ b/lib/ui/widgets/settingsView/settings_export_section.dart
@@ -43,7 +43,7 @@ class SExportSection extends StatelessWidget {
),
),
subtitle: I18nText('settingsView.importPatchesHint'),
- onTap: () => _settingsViewModel.importPatches(),
+ onTap: () => _settingsViewModel.importPatches(context),
),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
@@ -73,10 +73,10 @@ class SExportSection extends StatelessWidget {
),
),
subtitle: I18nText('settingsView.importKeystoreHint'),
- onTap: () async{
+ onTap: () async {
await _settingsViewModel.importKeystore();
final sManageKeystorePassword = SManageKeystorePassword();
- if(context.mounted){
+ if (context.mounted) {
sManageKeystorePassword.showKeystoreDialog(context);
}
},