Compare commits

...

43 Commits

Author SHA1 Message Date
557b42bc56 build: bump version to 1.9.1 2023-08-27 03:58:30 +03:00
8423914748 chore: merge dev to main (#1155) 2023-08-27 03:57:57 +03:00
07dce23794 build: bump patcher to v14.2.0 2023-08-27 03:55:56 +03:00
18fd0552db build: bump version to v1.9.0 2023-08-27 02:23:36 +03:00
d537d48f8e chore: merge dev to main (#1125) 2023-08-27 02:21:48 +03:00
b456512bbb build: bump patcher to v14.1.0 (#1153)
Co-authored-by: aAbed <aabedhkhan@gmail.com>
2023-08-27 02:21:16 +03:00
d9953b1473 fix(installer): use correct elevation level 2023-08-24 19:00:50 +07:00
c6ac898390 fix: universal patches not selectable 2023-08-17 18:25:23 +05:45
43f98cec43 build: make variant more identifiable
Release variant will use the "ReVanced Manager" app name, Debug will use "ReVanced Manager Debug"
2023-08-15 21:31:34 +07:00
63175cdec6 ci(build): create debug variant instead of release 2023-08-15 20:55:11 +07:00
6436a1ec61 build: debug apk come with application suffix
Instead of using the same one as Release, app.revanced.manager.flutter - We add .debug to it, yay, co-installation of two variants!
2023-08-15 20:37:43 +07:00
c400619338 feat: disable changing patches selection by default (#1132)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-08-15 16:05:27 +07:00
dcd5ba41cf build: add mavenlocal to repositories 2023-08-14 03:40:05 +03:00
7525e52fab fix(installer): use correct bg colour for dialog 2023-08-12 15:07:04 +07:00
72fd24e624 fix: use i18n translation for installer page 2023-08-12 15:06:17 +07:00
11d8f9fd30 refactor: apply changes according to Dart
trailing commas
2023-08-12 14:53:54 +07:00
4b0c8cecc8 ci(build): update event trigger
This add ".github/workflows/pr-build.yml" to build on every change to the CI Build to check if there's no error when it got modified.

And also remove "fastlane/**" because that's not related to the ReVanced Manager's code
2023-08-12 13:56:18 +07:00
372ce174c9 fix: overlapping issue in application selection page (#1128) 2023-08-11 19:19:44 +03:00
381daff980 fix: exclude x86 aapt2 binary from release builds (#1126) 2023-08-11 19:19:33 +03:00
94acebbebd docs(fixed): replaced some emoji icons, not rendering on chromium based browsers on windows (#1123) 2023-08-11 10:17:46 +07:00
f90f6e81ee feat: patch apps without internet (#1114) 2023-08-11 08:11:19 +07:00
97e37f304b refactor: migrate MediaQuery properties to InheritedModel (#1115)
Co-authored-by: Ushie <ushiekane@gmail.com>
2023-08-10 15:25:07 +03:00
f5e45ead26 ci(build): don't build on every pull request unrelated to application 2023-08-10 19:14:13 +07:00
14f765f4b4 ci(build): don't cache Gradle
Gradle take up so much space in cache that it wouldn't make sense for us to cache it since we are rapidly hitting the cache limit every time.
2023-08-10 19:12:15 +07:00
b77d46b2c2 feat(installer): redesign utility options (#1062)
Co-authored-by: Ushie <ushiekane@gmail.com>
2023-08-10 19:02:30 +07:00
1442916b20 build: bump version to 1.8.0 2023-08-10 03:01:03 +03:00
c6a5f42d23 chore: merge dev to main (#1120) 2023-08-10 03:00:27 +03:00
580d50eb8d fix: use right label for auto patch 2023-08-10 00:05:47 +03:00
9a66b6e50d fix(root-install): create service and postfs directories if they dont exist (#1116)
Fixes #860
2023-08-09 23:57:09 +03:00
acec064cb7 feat(patcher-view): show notice for removed patches that were used previously (#1107) 2023-08-09 22:32:13 +03:00
ea05d13a1f fix(patches-selector): New tag showing for old patches (#1113) 2023-08-09 22:31:37 +03:00
fd741f2ccf build: bump version to v1.7.0 2023-08-08 03:52:03 +03:00
055c52178f chore: merge dev branch to main branch (#1096) 2023-08-08 03:51:05 +03:00
722a5859a5 fix: pair integrations and patches updates (#1102) 2023-08-08 03:30:48 +03:00
1714c3fa86 feat(patches-selector): show New tag for new patches (#1099) 2023-08-07 22:28:09 +07:00
131df28110 docs(managing): update to reflect current changes
The chip was removed because it was not implemented for too long.
2023-08-07 20:13:23 +07:00
1be284f8d8 feat: switch to the new ReVanced API (#1101) 2023-08-07 17:14:16 +07:00
218e53ae75 refactor(analysis): migrate deprecated rule
deprecated since Dart 2.0, yike
2023-08-06 14:41:15 +07:00
264d8d90c4 refactor: apply changes according to analysis
trailing commas, and sort import alphabetically
2023-08-06 14:39:46 +07:00
0b529c2629 fix(patches-selector): separate all universal patches to the bottom (#1092) 2023-08-06 12:12:01 +07:00
5abcc7191f fix: experimental/universal patches being used even when turned off (#1090)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2023-08-06 12:10:57 +07:00
5346f6e1bf fix(patch-item): hide universal patches if not enabled (#1087) 2023-08-05 20:19:36 +07:00
2f471b3de4 fix: use user friendly strings (#1088) 2023-08-05 20:19:03 +07:00
51 changed files with 1167 additions and 477 deletions

View File

@ -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

View File

@ -71,12 +71,11 @@ linter:
- flutter_style_todos
- hash_and_equals
- implementation_imports
- iterable_contains_unrelated_type
- collection_methods_unrelated_type
- leading_newlines_in_multiline_strings
- library_names
- library_prefixes
- library_private_types_in_public_api
- list_remove_unrelated_type
- missing_whitespace_between_adjacent_strings
- no_adjacent_strings_in_list
- no_duplicate_case_values

View File

@ -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.2.0"
// Signing & aligning
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
implementation("com.android.tools.build:apksig:7.2.2")
}

View File

@ -20,7 +20,7 @@
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application
android:label="ReVanced Manager"
android:label="@string/app_name"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:largeHeap="true"

View File

@ -1,25 +1,30 @@
package app.revanced.manager.flutter
import android.os.Build
import android.os.Handler
import android.os.Looper
import androidx.annotation.NonNull
import app.revanced.manager.flutter.utils.Aapt
import app.revanced.manager.flutter.utils.aligning.ZipAligner
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
import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.logging.Logger
import app.revanced.patcher.util.patch.PatchBundle
import dalvik.system.DexClassLoader
import app.revanced.patcher.patch.PatchResult
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.cancel
import kotlinx.coroutines.runBlocking
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import java.util.logging.Level
import java.util.logging.LogRecord
import java.util.logging.Logger
import java.util.logging.SimpleFormatter
private const val PATCHER_CHANNEL = "app.revanced.manager.flutter/patcher"
private const val INSTALLER_CHANNEL = "app.revanced.manager.flutter/installer"
@ -30,10 +35,11 @@ class MainActivity : FlutterActivity() {
private var cancel: Boolean = false
private var stopResult: MethodChannel.Result? = null
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
val mainChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, PATCHER_CHANNEL)
installerChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, INSTALLER_CHANNEL)
installerChannel =
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, INSTALLER_CHANNEL)
mainChannel.setMethodCallHandler { call, result ->
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 */
// }
// }
}

View File

@ -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()
}
}

View File

@ -10,6 +10,9 @@
"yesButton": "Yes",
"noButton": "No",
"warning": "Warning",
"notice": "Notice",
"noShowAgain": "Don't show this again",
"new": "New",
"navigationView": {
"dashboardTab": "Dashboard",
"patcherTab": "Patcher",
@ -33,10 +36,10 @@
"updatePatchesDialogTitle": "Update ReVanced Patches",
"updateChangelogTitle": "Changelog",
"patchesConsentDialogText": "ReVanced Patches need to be downloaded to patch apps.",
"patchesConsentDialogText2": "This will reveal your IP address to {url}.",
"patchesConsentDialogText3": "Auto update",
"patchesConsentDialogText3Sub": "You can still change this in the settings later",
"patchesConsentDialogText": "ReVanced Patches needs to be downloaded.",
"patchesConsentDialogText2": "This will connect you to {url}.",
"patchesConsentDialogText3": "Auto update?",
"patchesConsentDialogText3Sub": "You can change this in settings at a later time.",
"notificationTitle": "Update downloaded",
"notificationText": "Tap to install the update",
@ -70,7 +73,8 @@
"patchDialogText": "You have selected a resource patch and a split APK installation has been detected, so patching errors may occur.\nAre you sure you want to proceed?",
"armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?",
"splitApkWarningDialogText": "Patching a split APK is not yet supported and might fail. Proceed anyways?"
"splitApkWarningDialogText": "Patching a split APK is not yet supported and might fail. Proceed anyways?",
"removedPatchesWarningDialogText": "The following patches have been removed since the last time you used them.\n\n{patches}\n\nProceed anyways?"
},
"appSelectorCard": {
"widgetTitle": "Select an application",
@ -112,6 +116,7 @@
"patchesSelectorView": {
"viewTitle": "Select patches",
"searchBarHint": "Search patches",
"universalPatches": "Universal patches",
"doneButton": "Done",
@ -129,12 +134,24 @@
},
"patchItem": {
"unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nSupported versions:\n{supportedVersions}",
"unsupportedPatchVersion": "Patch is not supported for this app version. Enable the experimental toggle in settings to proceed."
"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",
"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",
@ -142,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.",
@ -193,6 +209,12 @@
"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",
"experimentalUniversalPatchesHint": "Display all applications to use with universal patches, loading list of apps may be slower",

View File

@ -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)

View File

@ -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

View File

@ -2,12 +2,11 @@
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. Select the **Installed** chip
3. Tap on the **Info** button for the app you want to manage
4. Choose one of the options from the menu
2. Tap on the **Info** button for the app you want to manage
3. Choose one of the options from the menu
## ⏭️ What's next

View File

@ -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

View File

@ -2,7 +2,7 @@
ReVanced Manager has settings that can be configured to your liking.
## 🪛 Essential settings
## Essential settings
- ### 🔗 API URL

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -58,7 +58,7 @@ class MyApp extends StatelessWidget {
},
),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
GlobalWidgetsLocalizations.delegate,
],
);
}

View File

@ -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<ManagerAPI>();
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<String, dynamic>? 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<List<Patch>> getPatches(String repoName, String version) async {
Future<List<Patch>> getPatches(
String repoName,
String version,
String url,
) async {
List<Patch> patches = [];
try {
final File? f = await getPatchesReleaseFile('.json', repoName, version);
final File? f = await getPatchesReleaseFile(
'.json',
repoName,
version,
url,
);
if (f != null) {
final List<dynamic> list = jsonDecode(f.readAsStringSync());
patches = list.map((patch) => Patch.fromJson(patch)).toList();

View File

@ -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';
@ -28,7 +31,7 @@ class ManagerAPI {
String keystoreFile =
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore';
String defaultKeystorePassword = 's3cur3p@ssw0rd';
String defaultApiUrl = 'https://releases.revanced.app/';
String defaultApiUrl = 'https://api.revanced.app/';
String defaultRepoUrl = 'https://api.github.com';
String defaultPatcherRepo = 'revanced/revanced-patcher';
String defaultPatchesRepo = 'revanced/revanced-patches';
@ -36,10 +39,15 @@ class ManagerAPI {
String defaultCliRepo = 'revanced/revanced-cli';
String defaultManagerRepo = 'revanced/revanced-manager';
String? patchesVersion = '';
String? integrationsVersion = '';
bool isDefaultPatchesRepo() {
return getPatchesRepo() == 'revanced/revanced-patches';
}
bool isDefaultIntegrationsRepo() {
return getIntegrationsRepo() == 'revanced/revanced-integrations';
}
Future<void> initialize() async {
_prefs = await SharedPreferences.getInstance();
isRooted = await _rootAPI.isRooted();
@ -71,6 +79,14 @@ class ManagerAPI {
await _prefs.setString('repoUrl', url);
}
String getPatchesDownloadURL(bool bundle) {
return _prefs.getString('patchesDownloadURL-$bundle') ?? '';
}
Future<void> setPatchesDownloadURL(String value, bool bundle) async {
await _prefs.setString('patchesDownloadURL-$bundle', value);
}
String getPatchesRepo() {
return _prefs.getString('patchesRepo') ?? defaultPatchesRepo;
}
@ -94,10 +110,85 @@ 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<void> setPatchesAutoUpdate(bool value) async {
await _prefs.setBool('patchesAutoUpdate', value);
}
List<Patch> getSavedPatches(String packageName) {
final List<String> patchesJson =
_prefs.getStringList('savedPatches-$packageName') ?? [];
final List<Patch> patches = patchesJson.map((String patchJson) {
return Patch.fromJson(jsonDecode(patchJson));
}).toList();
return patches;
}
Future<void> savePatches(List<Patch> patches, String packageName) async {
final List<String> patchesJson = patches.map((Patch patch) {
return jsonEncode(patch.toJson());
}).toList();
await _prefs.setStringList('savedPatches-$packageName', patchesJson);
}
String getIntegrationsDownloadURL() {
return _prefs.getString('integrationsDownloadURL') ?? '';
}
Future<void> setIntegrationsDownloadURL(String value) async {
await _prefs.setString('integrationsDownloadURL', value);
}
List<Patch> getUsedPatches(String packageName) {
final List<String> patchesJson =
_prefs.getStringList('usedPatches-$packageName') ?? [];
final List<Patch> patches = patchesJson.map((String patchJson) {
return Patch.fromJson(jsonDecode(patchJson));
}).toList();
return patches;
}
Future<void> setUsedPatches(List<Patch> patches, String packageName) async {
final List<String> patchesJson = patches.map((Patch patch) {
return jsonEncode(patch.toJson());
}).toList();
await _prefs.setStringList('usedPatches-$packageName', patchesJson);
}
String getIntegrationsRepo() {
return _prefs.getString('integrationsRepo') ?? defaultIntegrationsRepo;
}
@ -223,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);
@ -236,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) {
@ -252,14 +350,14 @@ class ManagerAPI {
Future<File?> downloadIntegrations() async {
try {
final String repoName = getIntegrationsRepo();
if (repoName == defaultIntegrationsRepo) {
return await _revancedAPI.getLatestReleaseFile(
'.apk',
defaultIntegrationsRepo,
);
} else {
return await _githubAPI.getLatestReleaseFile('.apk', repoName);
}
final String currentVersion = await getCurrentIntegrationsVersion();
final String url = getIntegrationsDownloadURL();
return await _githubAPI.getPatchesReleaseFile(
'.apk',
repoName,
currentVersion,
url,
);
} on Exception catch (e) {
if (kDebugMode) {
print(e);
@ -308,6 +406,22 @@ class ManagerAPI {
);
}
Future<String?> getLatestIntegrationsVersion() async {
if (isDefaultIntegrationsRepo()) {
return await _revancedAPI.getLatestReleaseVersion(
'.apk',
defaultIntegrationsRepo,
);
} else {
final release = await _githubAPI.getLatestRelease(getIntegrationsRepo());
if (release != null) {
return release['tag_name'];
} else {
return null;
}
}
}
Future<String?> getLatestPatchesVersion() async {
if (isDefaultPatchesRepo()) {
return await _revancedAPI.getLatestReleaseVersion(
@ -315,7 +429,8 @@ class ManagerAPI {
defaultPatchesRepo,
);
} else {
final release = await _githubAPI.getLatestPatchesRelease(getPatchesRepo());
final release =
await _githubAPI.getLatestPatchesRelease(getPatchesRepo());
if (release != null) {
return release['tag_name'];
} else {
@ -332,14 +447,39 @@ class ManagerAPI {
Future<String> 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<void> setCurrentPatchesVersion(String version) async {
await _prefs.setString('patchesVersion', version);
await setPatchesDownloadURL('', false);
await setPatchesDownloadURL('', true);
await downloadPatches();
}
Future<String> getCurrentIntegrationsVersion() async {
integrationsVersion = _prefs.getString('integrationsVersion') ?? '0.0.0';
if (integrationsVersion == '0.0.0' || isPatchesAutoUpdate()) {
final String newIntegrationsVersion =
await getLatestIntegrationsVersion() ?? '0.0.0';
if (integrationsVersion != newIntegrationsVersion &&
newIntegrationsVersion != '0.0.0') {
await setCurrentIntegrationsVersion(newIntegrationsVersion);
}
}
return integrationsVersion!;
}
Future<void> setCurrentIntegrationsVersion(String version) async {
await _prefs.setString('integrationsVersion', version);
await setIntegrationsDownloadURL('');
await downloadIntegrations();
}
Future<List<PatchedApplication>> getAppsToRemove(
@ -413,6 +553,63 @@ class ManagerAPI {
return unsavedApps;
}
Future<void> showPatchesChangeWarningDialog(BuildContext context) {
final ValueNotifier<bool> 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<void> reAssessSavedApps() async {
final List<PatchedApplication> patchedApps = getPatchedApps();
final List<PatchedApplication> unsavedApps =

View File

@ -30,6 +30,8 @@ class PatcherAPI {
Future<void> initialize() async {
await _loadPatches();
await _managerAPI.downloadPatches();
await _managerAPI.downloadIntegrations();
final Directory appCache = await getTemporaryDirectory();
_dataDir = await getExternalStorageDirectory() ?? appCache;
_tmpDir = Directory('${appCache.path}/patcher');
@ -103,16 +105,20 @@ class PatcherAPI {
}
List<Patch> getFilteredPatches(String packageName) {
if (!filteredPatches.keys.contains(packageName)) {
final List<Patch> patches = _patches
.where(
(patch) =>
patch.compatiblePackages.isEmpty ||
!patch.name.contains('settings') &&
patch.compatiblePackages
.any((pack) => pack.name == packageName),
)
final List<Patch> patches = _patches
.where(
(patch) =>
patch.compatiblePackages.isEmpty ||
!patch.name.contains('settings') &&
patch.compatiblePackages
.any((pack) => pack.name == packageName),
)
.toList();
if (!_managerAPI.areUniversalPatchesEnabled()) {
filteredPatches[packageName] = patches
.where((patch) => patch.compatiblePackages.isNotEmpty)
.toList();
} else {
filteredPatches[packageName] = patches;
}
return filteredPatches[packageName];
@ -279,7 +285,7 @@ class PatcherAPI {
return newName;
}
Future<void> sharePatcherLog(String logs) async {
Future<void> exportPatcherLog(String logs) async {
final Directory appCache = await getTemporaryDirectory();
final Directory logDir = Directory('${appCache.path}/logs');
logDir.createSync();
@ -289,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) {

View File

@ -153,6 +153,9 @@ class RootAPI {
}
Future<void> installServiceDScript(String packageName) async {
await Root.exec(
cmd: 'mkdir -p "$_serviceDDirPath"',
);
final String content = '#!/system/bin/sh\n'
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n'
'base_path=$_revancedDirPath/$packageName/base.apk\n'
@ -166,6 +169,9 @@ class RootAPI {
}
Future<void> 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';

View File

@ -94,7 +94,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
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<AppSelectorView> {
),
)
.toList(),
const SizedBox(height: 70.0),
],
),
),

View File

@ -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),
],
),
),

View File

@ -43,7 +43,7 @@ class HomeViewModel extends BaseViewModel {
Future<void> initialize(BuildContext context) async {
_latestManagerVersion = await _managerAPI.getLatestManagerVersion();
if(!_managerAPI.getPatchesConsent()){
if (!_managerAPI.getPatchesConsent()) {
await showPatchesConsent(context);
}
await _patcherAPI.initialize();
@ -168,13 +168,13 @@ class HomeViewModel extends BaseViewModel {
}
}
Future<void> showPatchesConsent(BuildContext context) async{
Future<void> showPatchesConsent(BuildContext context) async {
final ValueNotifier<bool> autoUpdate = ValueNotifier(true);
await showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: const Text('ReVanced Patches'),
title: const Text('Download ReVanced Patches?'),
content: ValueListenableBuilder(
valueListenable: autoUpdate,
builder: (context, value, child) {
@ -197,7 +197,9 @@ class HomeViewModel extends BaseViewModel {
padding: const EdgeInsets.symmetric(vertical: 10),
child: I18nText(
'homeView.patchesConsentDialogText2',
translationParams: {'url': _managerAPI.defaultApiUrl.split('/')[2]},
translationParams: {
'url': _managerAPI.defaultApiUrl.split('/')[2],
},
child: Text(
'',
style: TextStyle(
@ -211,8 +213,12 @@ class HomeViewModel extends BaseViewModel {
CheckboxListTile(
value: value,
contentPadding: EdgeInsets.zero,
title: I18nText('homeView.patchesConsentDialogText3',),
subtitle: I18nText('homeView.patchesConsentDialogText3Sub',),
title: I18nText(
'homeView.patchesConsentDialogText3',
),
subtitle: I18nText(
'homeView.patchesConsentDialogText3Sub',
),
onChanged: (selected) {
autoUpdate.value = selected!;
},
@ -237,7 +243,7 @@ class HomeViewModel extends BaseViewModel {
Navigator.of(context).pop();
},
label: I18nText('okButton'),
)
),
],
),
);
@ -247,9 +253,12 @@ class HomeViewModel extends BaseViewModel {
_toast.showBottom('homeView.downloadingMessage');
final String patchesVersion =
await _managerAPI.getLatestPatchesVersion() ?? '0.0.0';
if (patchesVersion != '0.0.0') {
_toast.showBottom('homeView.downloadedMessage');
final String integrationsVersion =
await _managerAPI.getLatestIntegrationsVersion() ?? '0.0.0';
if (patchesVersion != '0.0.0' && integrationsVersion != '0.0.0') {
await _managerAPI.setCurrentPatchesVersion(patchesVersion);
await _managerAPI.setCurrentIntegrationsVersion(integrationsVersion);
_toast.showBottom('homeView.downloadedMessage');
forceRefresh(context);
} else {
_toast.showBottom('homeView.errorDownloadMessage');

View File

@ -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: <Widget>[
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: <Widget>[
@ -35,44 +72,6 @@ class InstallerView extends StatelessWidget {
overflow: TextOverflow.ellipsis,
),
onBackButtonPressed: () => model.onWillPop(context),
actions: <Widget>[
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: <Widget>[
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,
),
),
],
),
),

View File

@ -85,7 +85,7 @@ class InstallerViewModel extends BaseViewModel {
});
}
void update(double value, String header, String log) {
Future<void> update(double value, String header, String log) async {
if (value >= 0.0) {
progress = value;
}
@ -97,6 +97,11 @@ class InstallerViewModel extends BaseViewModel {
} else if (value == 1.0) {
isPatching = false;
hasErrors = false;
await _managerAPI.savePatches(
_patcherAPI.getFilteredPatches(_app.packageName),
_app.packageName,
);
await _managerAPI.setUsedPatches(_patches, _app.packageName);
} else if (value == -100.0) {
isPatching = false;
hasErrors = true;
@ -164,6 +169,89 @@ class InstallerViewModel extends BaseViewModel {
}
}
Future<void> installTypeDialog(BuildContext context) async {
final ValueNotifier<int> 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<void> stopPatcher() async {
try {
isCanceled = true;
@ -203,7 +291,7 @@ class InstallerViewModel extends BaseViewModel {
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(),
)
),
],
),
);
@ -248,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<void> cleanPatcher() async {
@ -279,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;
}
}

View File

@ -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);
}

View File

@ -22,7 +22,7 @@ class PatcherView extends StatelessWidget {
child: FloatingActionButton.extended(
label: I18nText('patcherView.patchButton'),
icon: const Icon(Icons.build),
onPressed: () => model.showPatchConfirmationDialog(context),
onPressed: () => model.showRemovedPatchesDialog(context),
),
),
body: CustomScrollView(

View File

@ -11,6 +11,7 @@ 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';
import 'package:stacked_services/stacked_services.dart';
@ -21,6 +22,7 @@ class PatcherViewModel extends BaseViewModel {
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
PatchedApplication? selectedApp;
List<Patch> selectedPatches = [];
List<String> removedPatches = [];
void navigateToAppSelector() {
_navigationService.navigateTo(Routes.appSelectorView);
@ -77,7 +79,7 @@ class PatcherViewModel extends BaseViewModel {
Navigator.of(context).pop();
showArmv7WarningDialog(context);
},
)
),
],
),
);
@ -85,6 +87,38 @@ class PatcherViewModel extends BaseViewModel {
}
}
Future<void> showRemovedPatchesDialog(BuildContext context) async {
if (removedPatches.isNotEmpty) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('notice'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText(
'patcherView.removedPatchesWarningDialogText',
translationParams: {'patches': removedPatches.join('\n')},
),
actions: <Widget>[
CustomMaterialButton(
isFilled: false,
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(),
),
CustomMaterialButton(
label: I18nText('yesButton'),
onPressed: () {
Navigator.of(context).pop();
navigateToInstaller();
},
),
],
),
);
} else {
showArmv7WarningDialog(context);
}
}
Future<void> showArmv7WarningDialog(BuildContext context) async {
final bool armv7 = await AboutInfo.getInfo().then((info) {
final List<String> archs = info['supportedArch'];
@ -110,7 +144,7 @@ class PatcherViewModel extends BaseViewModel {
Navigator.of(context).pop();
navigateToInstaller();
},
)
),
],
),
);
@ -149,6 +183,7 @@ class PatcherViewModel extends BaseViewModel {
Future<void> loadLastSelectedPatches() async {
this.selectedPatches.clear();
removedPatches.clear();
final List<String> selectedPatches =
await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName);
final List<Patch> patches =
@ -156,6 +191,24 @@ 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));
}
if (!_managerAPI.areUniversalPatchesEnabled()) {
this
.selectedPatches
.removeWhere((patch) => patch.compatiblePackages.isEmpty);
}
final usedPatches = _managerAPI.getUsedPatches(selectedApp!.originalPackageName);
for (final patch in usedPatches){
if (!patches.any((p) => p.name == patch.name)){
removedPatches.add('\u2022 ${patch.name}');
}
}
notifyListeners();
}
}

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart' hide SearchBar;
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
@ -16,6 +18,18 @@ class PatchesSelectorView extends StatefulWidget {
class _PatchesSelectorViewState extends State<PatchesSelectorView> {
String _query = '';
final _managerAPI = locator<ManagerAPI>();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!_managerAPI.isPatchesChangeEnabled() &&
_managerAPI.showPatchesChangeWarning()) {
_managerAPI.showPatchesChangeWarningDialog(context);
}
});
}
@override
Widget build(BuildContext context) {
@ -30,7 +44,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
label: Row(
children: <Widget>[
I18nText('patchesSelectorView.doneButton'),
Text(' (${model.selectedPatches.length})')
Text(' (${model.selectedPatches.length})'),
],
),
icon: const Icon(Icons.check),
@ -84,7 +98,8 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
),
),
CustomPopupMenu(
onSelected: (value) => {model.onMenuSelection(value)},
onSelected: (value) =>
{model.onMenuSelection(value, context)},
children: {
0: I18nText(
'patchesSelectorView.loadPatchesSelection',
@ -136,7 +151,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
: 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: [
@ -149,7 +164,11 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
'patchesSelectorView.defaultTooltip',
),
onPressed: () {
model.selectDefaultPatches();
if (_managerAPI.isPatchesChangeEnabled()) {
model.selectDefaultPatches();
} else {
model.showPatchesChangeDialog(context);
}
},
),
const SizedBox(width: 8),
@ -160,28 +179,79 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
'patchesSelectorView.noneTooltip',
),
onPressed: () {
model.clearPatches();
if (_managerAPI.isPatchesChangeEnabled()) {
model.clearPatches();
} else {
model.showPatchesChangeDialog(context);
}
},
),
],
),
...model
.getQueriedPatches(_query)
.map(
(patch) => PatchItem(
...model.getQueriedPatches(_query).map(
(patch) {
if (patch.compatiblePackages.isNotEmpty) {
return PatchItem(
name: patch.name,
simpleName: patch.getSimpleName(),
description: patch.description,
packageVersion: model.getAppVersion(),
packageVersion: model.getAppInfo().version,
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();
}
},
),
if (_managerAPI.areUniversalPatchesEnabled())
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 10.0,
),
child: I18nText(
'patchesSelectorView.universalPatches',
),
),
)
.toList(),
...model.getQueriedPatches(_query).map((patch) {
if (patch.compatiblePackages.isEmpty) {
return PatchItem(
name: patch.name,
simpleName: patch.getSimpleName(),
description: patch.description,
packageVersion:
model.getAppInfo().version,
supportedPackageVersions:
model.getSupportedVersions(patch),
isUnsupported: !isPatchSupported(patch),
isChangeEnabled: _managerAPI.isPatchesChangeEnabled(),
isNew: false,
isSelected: model.isSelected(patch),
onChanged: (value) => model.selectPatch(
patch,
value,
context,
),
);
} else {
return Container();
}
}),
],
),
const SizedBox(height: 70.0),
],
),
),

View File

@ -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';
@ -15,6 +18,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
final List<Patch> patches = [];
final List<Patch> selectedPatches =
locator<PatcherViewModel>().selectedPatches;
PatchedApplication? selectedApp = locator<PatcherViewModel>().selectedApp;
String? patchesVersion = '';
bool isDefaultPatchesRepo() {
return _managerAPI.getPatchesRepo() == 'revanced/revanced-patches';
@ -24,10 +28,17 @@ class PatchesSelectorViewModel extends BaseViewModel {
getPatchesVersion().whenComplete(() => notifyListeners());
patches.addAll(
_patcherAPI.getFilteredPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName,
selectedApp!.originalPackageName,
),
);
patches.sort((a, b) => a.name.compareTo(b.name));
patches.sort((a, b) {
if (isPatchNew(a, selectedApp!.packageName) ==
isPatchNew(b, selectedApp!.packageName)) {
return a.name.compareTo(b.name);
} else {
return isPatchNew(b, selectedApp!.packageName) ? 1 : -1;
}
});
notifyListeners();
}
@ -37,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<void> 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<PatcherViewModel>().selectedApp?.originalPackageName != null) {
selectedPatches.addAll(
patches.where(
(element) => element.excluded == false && isPatchSupported(element),
),
_patcherAPI
.getFilteredPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName,
)
.where(
(element) =>
!element.excluded &&
(_managerAPI.areExperimentalPatchesEnabled() ||
isPatchSupported(element)),
),
);
}
if (_managerAPI.areExperimentalPatchesEnabled()) {
selectedPatches
.addAll(patches.where((element) => element.excluded == false));
}
notifyListeners();
}
@ -81,7 +131,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
}
List<Patch> getQueriedPatches(String query) {
return patches
final List<Patch> patch = patches
.where(
(patch) =>
query.isEmpty ||
@ -90,10 +140,27 @@ class PatchesSelectorViewModel extends BaseViewModel {
patch.getSimpleName().toLowerCase().contains(query.toLowerCase()),
)
.toList();
if (_managerAPI.areUniversalPatchesEnabled()) {
return patch;
} else {
return patch
.where((patch) => patch.compatiblePackages.isNotEmpty)
.toList();
}
}
String getAppVersion() {
return locator<PatcherViewModel>().selectedApp!.version;
PatchedApplication getAppInfo() {
return locator<PatcherViewModel>().selectedApp!;
}
bool isPatchNew(Patch patch, String packageName) {
final List<Patch> savedPatches = _managerAPI.getSavedPatches(packageName);
if (savedPatches.isEmpty) {
return false;
} else {
return !savedPatches
.any((p) => p.getSimpleName() == patch.getSimpleName());
}
}
List<String> getSupportedVersions(Patch patch) {
@ -108,10 +175,10 @@ class PatchesSelectorViewModel extends BaseViewModel {
}
}
void onMenuSelection(value) {
void onMenuSelection(value, BuildContext context) {
switch (value) {
case 0:
loadSelectedPatches();
loadSelectedPatches(context);
break;
}
}
@ -125,18 +192,25 @@ class PatchesSelectorViewModel extends BaseViewModel {
);
}
Future<void> loadSelectedPatches() async {
final List<String> selectedPatches = await _managerAPI.getSelectedPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName,
);
if (selectedPatches.isNotEmpty) {
this.selectedPatches.clear();
this.selectedPatches.addAll(
patches.where((patch) => selectedPatches.contains(patch.name)),
);
Future<void> loadSelectedPatches(BuildContext context) async {
if (_managerAPI.isPatchesChangeEnabled()) {
final List<String> selectedPatches = await _managerAPI.getSelectedPatches(
locator<PatcherViewModel>().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<Toast>().showBottom('patchesSelectorView.noSavedPatches');
}
notifyListeners();
} else {
locator<Toast>().showBottom('patchesSelectorView.noSavedPatches');
showPatchesChangeDialog(context);
}
notifyListeners();
}
}

View File

@ -30,7 +30,7 @@ class SManageApiUrl extends BaseViewModel {
icon: const Icon(Icons.manage_history_outlined),
onPressed: () => showApiUrlResetDialog(context),
color: Theme.of(context).colorScheme.secondary,
)
),
],
),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
@ -69,7 +69,7 @@ class SManageApiUrl extends BaseViewModel {
_managerAPI.setApiUrl(apiUrl);
Navigator.of(context).pop();
},
)
),
],
),
);
@ -97,7 +97,7 @@ class SManageApiUrl extends BaseViewModel {
..pop()
..pop();
},
)
),
],
),
);

View File

@ -30,7 +30,7 @@ class SManageKeystorePassword extends BaseViewModel {
onPressed: () => _keystorePasswordController.text =
_managerAPI.defaultKeystorePassword,
color: Theme.of(context).colorScheme.secondary,
)
),
],
),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
@ -62,7 +62,7 @@ class SManageKeystorePassword extends BaseViewModel {
_managerAPI.setKeystorePassword(passwd);
Navigator.of(context).pop();
},
)
),
],
),
);

View File

@ -40,7 +40,7 @@ class SManageSources extends BaseViewModel {
icon: const Icon(Icons.manage_history_outlined),
onPressed: () => showResetConfirmationDialog(context),
color: Theme.of(context).colorScheme.secondary,
)
),
],
),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
@ -102,7 +102,7 @@ class SManageSources extends BaseViewModel {
onChanged: (value) => notifyListeners(),
),
const SizedBox(height: 20),
I18nText('settingsView.sourcesUpdateNote')
I18nText('settingsView.sourcesUpdateNote'),
],
),
),
@ -129,10 +129,11 @@ 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,12 +159,13 @@ 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()
..pop();
},
)
),
],
),
);

View File

@ -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<NavigationService>();
final ManagerAPI _managerAPI = locator<ManagerAPI>();
final PatchesSelectorViewModel _patchesSelectorViewModel =
PatchesSelectorViewModel();
final PatcherViewModel _patcherViewModel = locator<PatcherViewModel>();
final Toast _toast = locator<Toast>();
final SUpdateLanguage sUpdateLanguage = SUpdateLanguage();
@ -37,6 +44,88 @@ class SettingsViewModel extends BaseViewModel {
notifyListeners();
}
bool isPatchesChangeEnabled() {
return _managerAPI.isPatchesChangeEnabled();
}
Future<void> 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<void> 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<PatcherViewModel>().selectedApp != null) {
locator<PatcherViewModel>().loadLastSelectedPatches();
Future<void> 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);
}
}

View File

@ -74,7 +74,7 @@ class AppInfoViewModel extends BaseViewModel {
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(),
)
),
],
),
);
@ -103,7 +103,7 @@ class AppInfoViewModel extends BaseViewModel {
Navigator.of(context).pop();
Navigator.of(context).pop();
},
)
),
],
),
);
@ -140,7 +140,7 @@ class AppInfoViewModel extends BaseViewModel {
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(),
)
),
],
),
);

View File

@ -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,

View File

@ -66,7 +66,7 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
context,
'installed',
translationParams: {
'version': 'v${widget.installedVersion}'
'version': 'v${widget.installedVersion}',
},
),
),

View File

@ -64,7 +64,7 @@ class InstalledAppsCard extends StatelessWidget {
Theme.of(context).colorScheme.secondary,
),
),
)
),
],
),
),

View File

@ -95,7 +95,7 @@ class UpdateConfirmationDialog extends StatelessWidget {
? model.updatePatches(context)
: model.updateManager(context);
},
)
),
],
),
),

View File

@ -25,7 +25,7 @@ class _GradientProgressIndicatorState extends State<GradientProgressIndicator> {
),
),
height: 5,
width: MediaQuery.of(context).size.width * widget.progress!,
width: MediaQuery.sizeOf(context).width * widget.progress!,
),
);
}

View File

@ -3,7 +3,6 @@ import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
@ -17,8 +16,10 @@ class PatchItem extends StatefulWidget {
required this.packageVersion,
required this.supportedPackageVersions,
required this.isUnsupported,
required this.isNew,
required this.isSelected,
required this.onChanged,
required this.isChangeEnabled,
this.child,
}) : super(key: key);
final String name;
@ -27,8 +28,10 @@ class PatchItem extends StatefulWidget {
final String packageVersion;
final List<String> supportedPackageVersions;
final bool isUnsupported;
final bool isNew;
bool isSelected;
final Function(bool) onChanged;
final bool isChangeEnabled;
final Widget? child;
final toast = locator<Toast>();
final _managerAPI = locator<ManagerAPI>();
@ -57,11 +60,13 @@ class _PatchItemState extends State<PatchItem> {
!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: <Widget>[
@ -123,28 +128,24 @@ class _PatchItemState extends State<PatchItem> {
widget.toast.showBottom(
'patchItem.unsupportedPatchVersion',
);
} else {
} else if (widget.isChangeEnabled) {
widget.isSelected = newValue!;
}
if (widget.isUnsupported &&
widget.isSelected &&
!selectedUnsupportedPatches
.contains(widget.name)) {
selectedUnsupportedPatches.add(widget.name);
}
});
widget.onChanged(widget.isSelected);
if (!widget.isUnsupported || widget._managerAPI.areExperimentalPatchesEnabled()) {
widget.onChanged(widget.isSelected);
}
},
),
)
),
],
),
if (widget.isUnsupported &&
widget._managerAPI.areExperimentalPatchesEnabled())
Row(
children: <Widget>[
Row(
children: [
if (widget.isUnsupported &&
widget._managerAPI.areExperimentalPatchesEnabled())
Padding(
padding: const EdgeInsets.only(top: 8),
padding: const EdgeInsets.only(top: 8, right: 8),
child: TextButton.icon(
label: I18nText('warning'),
icon: const Icon(Icons.warning, size: 20.0),
@ -167,10 +168,33 @@ class _PatchItemState extends State<PatchItem> {
),
),
),
],
)
else
Container(),
if (widget.isNew)
Padding(
padding: const EdgeInsets.only(top: 8),
child: TextButton.icon(
label: I18nText('new'),
icon: const Icon(Icons.star, size: 20.0),
onPressed: () => _showNewPatchDialog(),
style: ButtonStyle(
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(
color: Theme.of(context).colorScheme.secondary,
),
),
),
backgroundColor: MaterialStateProperty.all(
Colors.transparent,
),
foregroundColor: MaterialStateProperty.all(
Theme.of(context).colorScheme.secondary,
),
),
),
),
],
),
widget.child ?? const SizedBox(),
],
),
@ -197,7 +221,26 @@ class _PatchItemState extends State<PatchItem> {
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(),
)
),
],
),
);
}
Future<void> _showNewPatchDialog() {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('patchItem.newPatch'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText(
'patchItem.newPatchDialogText',
),
actions: <Widget>[
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(),
),
],
),
);

View File

@ -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,

View File

@ -50,7 +50,7 @@ class CustomSwitch extends StatelessWidget {
color: Colors.black12.withOpacity(0.1),
spreadRadius: 0.5,
blurRadius: 1,
)
),
],
),
),

View File

@ -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(
@ -93,9 +95,9 @@ class SAdvancedSection extends StatelessWidget {
label: I18nText('yesButton'),
onPressed: () => {
Navigator.of(context).pop(),
_settingsViewModel.deleteKeystore()
_settingsViewModel.deleteKeystore(),
},
)
),
],
),
);

View File

@ -17,7 +17,7 @@ class _SAutoUpdatePatchesState extends State<SAutoUpdatePatches> {
return SwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText(
'homeView.patchesConsentDialogText3',
'settingsView.autoUpdatePatchesLabel',
child: const Text(
'',
style: TextStyle(

View File

@ -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<SEnablePatchesSelection> createState() => _SEnablePatchesSelectionState();
}
final _settingsViewModel = SettingsViewModel();
class _SEnablePatchesSelectionState extends State<SEnablePatchesSelection> {
@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(() {});
},
);
}
}

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_i18n/widgets/I18nText.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/settings_viewmodel.dart';
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
class SExperimentalPatches extends StatefulWidget {
const SExperimentalPatches({super.key});
@ -11,7 +13,8 @@ class SExperimentalPatches extends StatefulWidget {
}
final _settingsViewModel = SettingsViewModel();
final List<String> selectedUnsupportedPatches = [];
final _patchesSelectorViewModel = PatchesSelectorViewModel();
final _patcherViewModel = PatcherViewModel();
class _SExperimentalPatchesState extends State<SExperimentalPatches> {
@override
@ -35,12 +38,10 @@ class _SExperimentalPatchesState extends State<SExperimentalPatches> {
_settingsViewModel.useExperimentalPatches(value);
});
if (!value) {
for (final patch in selectedUnsupportedPatches) {
PatchesSelectorViewModel()
.selectedPatches
.removeWhere((element) => patch == element.name);
}
selectedUnsupportedPatches.clear();
_patcherViewModel.selectedPatches
.removeWhere((patch) => !isPatchSupported(patch));
_patchesSelectorViewModel.selectedPatches
.removeWhere((patch) => !isPatchSupported(patch));
}
},
);

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_i18n/widgets/I18nText.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/settings_viewmodel.dart';
class SExperimentalUniversalPatches extends StatefulWidget {
@ -11,6 +13,8 @@ class SExperimentalUniversalPatches extends StatefulWidget {
}
final _settingsViewModel = SettingsViewModel();
final _patchesSelectorViewModel = PatchesSelectorViewModel();
final _patcherViewModel = PatcherViewModel();
class _SExperimentalUniversalPatchesState
extends State<SExperimentalUniversalPatches> {
@ -34,6 +38,12 @@ class _SExperimentalUniversalPatchesState
setState(() {
_settingsViewModel.showUniversalPatches(value);
});
if (!value) {
_patcherViewModel.selectedPatches
.removeWhere((patch) => patch.compatiblePackages.isEmpty);
_patchesSelectorViewModel.selectedPatches
.removeWhere((patch) => patch.compatiblePackages.isEmpty);
}
},
);
}

View File

@ -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);
}
},
@ -119,9 +119,9 @@ class SExportSection extends StatelessWidget {
label: I18nText('yesButton'),
onPressed: () => {
Navigator.of(context).pop(),
_settingsViewModel.resetSelectedPatches()
_settingsViewModel.resetSelectedPatches(),
},
)
),
],
),
);

View File

@ -11,7 +11,7 @@ class AboutInfo {
'flavor': kReleaseMode ? 'release' : 'debug',
'model': info.model,
'androidVersion': info.version.release,
'supportedArch': info.supportedAbis
'supportedArch': info.supportedAbis,
};
}
}

View File

@ -4,7 +4,7 @@ homepage: https://github.com/revanced/revanced-manager
publish_to: 'none'
version: 1.6.1+100600001
version: 1.9.1+100900100
environment:
sdk: '>=3.0.0 <4.0.0'