mirror of
https://github.com/revanced/revanced-manager
synced 2024-05-14 13:56:57 +02:00
chore: merge dev
to main
(#1125)
This commit is contained in:
commit
d537d48f8e
12
.github/workflows/pr-build.yml
vendored
12
.github/workflows/pr-build.yml
vendored
@ -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
|
||||
|
@ -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")
|
||||
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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 */
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
ReVanced Manager has settings that can be configured to your liking.
|
||||
|
||||
## 🪛 Essential settings
|
||||
## ⭐ Essential settings
|
||||
|
||||
- ### 🔗 API URL
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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<void> 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<void> 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<void> setIntegrationsDownloadURL(String value) async {
|
||||
await _prefs.setString('integrationsDownloadURL', value);
|
||||
}
|
||||
|
||||
List<Patch> getUsedPatches(String packageName) {
|
||||
final List<String> 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<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()) {
|
||||
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<void> setCurrentIntegrationsVersion(String version) async {
|
||||
await _prefs.setString('integrationsVersion', version);
|
||||
await setIntegrationsDownloadURL('');
|
||||
await downloadIntegrations();
|
||||
}
|
||||
|
||||
Future<List<PatchedApplication>> getAppsToRemove(
|
||||
@ -478,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 =
|
||||
|
@ -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');
|
||||
@ -283,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();
|
||||
@ -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) {
|
||||
|
@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -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');
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -169,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;
|
||||
@ -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<void> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -20,6 +20,17 @@ 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) {
|
||||
return ViewModelBuilder<PatchesSelectorViewModel>.reactive(
|
||||
@ -87,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',
|
||||
@ -139,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: [
|
||||
@ -152,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),
|
||||
@ -163,7 +179,11 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
'patchesSelectorView.noneTooltip',
|
||||
),
|
||||
onPressed: () {
|
||||
model.clearPatches();
|
||||
if (_managerAPI.isPatchesChangeEnabled()) {
|
||||
model.clearPatches();
|
||||
} else {
|
||||
model.showPatchesChangeDialog(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -179,13 +199,14 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
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<PatchesSelectorView> {
|
||||
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();
|
||||
|
@ -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<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();
|
||||
}
|
||||
|
||||
@ -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<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();
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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<Toast>();
|
||||
final _managerAPI = locator<ManagerAPI>();
|
||||
@ -58,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>[
|
||||
@ -124,11 +128,13 @@ class _PatchItemState extends State<PatchItem> {
|
||||
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<PatchItem> {
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
widget.child ?? const SizedBox(),
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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(() {});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user