chore: merge dev to main (#1125)

This commit is contained in:
Ushie 2023-08-27 02:21:48 +03:00 committed by GitHub
commit d537d48f8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 806 additions and 378 deletions

View File

@ -2,6 +2,11 @@ name: PR Build
on: on:
pull_request: pull_request:
paths:
- ".github/workflows/pr-build.yml"
- "android/**"
- "assets/**"
- "lib/**"
jobs: jobs:
build: build:
@ -20,7 +25,6 @@ jobs:
with: with:
java-version: '11' java-version: '11'
distribution: 'zulu' distribution: 'zulu'
cache: 'gradle'
- name: Setup Flutter - name: Setup Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
@ -33,9 +37,9 @@ jobs:
- name: Build with Flutter - name: Build with Flutter
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: flutter build apk run: flutter build apk --debug
- name: Upload build - name: Upload build
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: revanced-manager name: revanced-manager
path: build/app/outputs/flutter-apk/app-release.apk path: build/app/outputs/flutter-apk/app-debug.apk

View File

@ -54,7 +54,21 @@ android {
release { release {
shrinkResources false shrinkResources false
minifyEnabled false minifyEnabled false
resValue "string", "app_name", "ReVanced Manager"
signingConfig signingConfigs.debug 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" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// ReVanced // ReVanced
implementation "app.revanced:revanced-patcher:11.0.4" implementation "app.revanced:revanced-patcher:14.1.0"
// Signing & aligning // Signing & aligning
implementation("org.bouncycastle:bcpkix-jdk15on:1.70") implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
implementation("com.android.tools.build:apksig:7.2.2") implementation("com.android.tools.build:apksig:7.2.2")
} }

View File

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

View File

@ -1,25 +1,30 @@
package app.revanced.manager.flutter package app.revanced.manager.flutter
import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.annotation.NonNull
import app.revanced.manager.flutter.utils.Aapt import app.revanced.manager.flutter.utils.Aapt
import app.revanced.manager.flutter.utils.aligning.ZipAligner import app.revanced.manager.flutter.utils.aligning.ZipAligner
import app.revanced.manager.flutter.utils.signing.Signer import app.revanced.manager.flutter.utils.signing.Signer
import app.revanced.manager.flutter.utils.zip.ZipFile import app.revanced.manager.flutter.utils.zip.ZipFile
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.Patcher import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions import app.revanced.patcher.PatcherOptions
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
import app.revanced.patcher.extensions.PatchExtensions.patchName import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.logging.Logger import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.util.patch.PatchBundle
import dalvik.system.DexClassLoader
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.cancel
import kotlinx.coroutines.runBlocking
import java.io.File 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 PATCHER_CHANNEL = "app.revanced.manager.flutter/patcher"
private const val INSTALLER_CHANNEL = "app.revanced.manager.flutter/installer" private const val INSTALLER_CHANNEL = "app.revanced.manager.flutter/installer"
@ -30,10 +35,11 @@ class MainActivity : FlutterActivity() {
private var cancel: Boolean = false private var cancel: Boolean = false
private var stopResult: MethodChannel.Result? = null private var stopResult: MethodChannel.Result? = null
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
val mainChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, PATCHER_CHANNEL) 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 -> mainChannel.setMethodCallHandler { call, result ->
when (call.method) { when (call.method) {
"runPatcher" -> { "runPatcher" -> {
@ -77,10 +83,12 @@ class MainActivity : FlutterActivity() {
result.notImplemented() result.notImplemented()
} }
} }
"stopPatcher" -> { "stopPatcher" -> {
cancel = true cancel = true
stopResult = result stopResult = result
} }
else -> result.notImplemented() else -> result.notImplemented()
} }
} }
@ -105,55 +113,79 @@ class MainActivity : FlutterActivity() {
val outFile = File(outFilePath) val outFile = File(outFilePath)
val integrations = File(integrationsPath) val integrations = File(integrationsPath)
val keyStoreFile = File(keyStoreFilePath) val keyStoreFile = File(keyStoreFilePath)
val cacheDir = File(cacheDirPath)
Thread { Thread {
try { 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 { handler.post {
installerChannel.invokeMethod( installerChannel.invokeMethod(
"update", "update",
mapOf( mapOf(
"progress" to 0.1, "progress" to 0.1,
"header" to "", "header" to "",
"log" to "Copying original apk" "log" to "Copying original APK"
) )
) )
} }
if(cancel) { if (cancel) {
handler.post { stopResult!!.success(null) } handler.post { stopResult!!.success(null) }
return@Thread return@Thread
} }
originalFile.copyTo(inputFile, true) originalFile.copyTo(inputFile, true)
if (cancel) {
handler.post { stopResult!!.success(null) }
return@Thread
}
handler.post { handler.post {
installerChannel.invokeMethod( installerChannel.invokeMethod(
"update", "update",
mapOf( mapOf(
"progress" to 0.2, "progress" to 0.2,
"header" to "Unpacking apk...", "header" to "Reading APK...",
"log" to "Unpacking input apk" "log" to "Reading input APK"
) )
) )
} }
if(cancel) {
handler.post { stopResult!!.success(null) }
return@Thread
}
val patcher = val patcher =
Patcher( Patcher(
PatcherOptions( PatcherOptions(
inputFile, inputFile,
cacheDirPath, cacheDir,
Aapt.binary(applicationContext).absolutePath, Aapt.binary(applicationContext).absolutePath,
cacheDirPath, cacheDir.path,
logger = ManagerLogger()
) )
) )
if(cancel) { if (cancel) {
handler.post { stopResult!!.success(null) } handler.post { stopResult!!.success(null) }
return@Thread return@Thread
} }
@ -161,28 +193,19 @@ class MainActivity : FlutterActivity() {
handler.post { handler.post {
installerChannel.invokeMethod( installerChannel.invokeMethod(
"update", "update",
mapOf("progress" to 0.3, "header" to "", "log" to "") mapOf("progress" to 0.3, "header" to "Loading patches...", "log" to "Loading patches")
)
}
handler.post {
installerChannel.invokeMethod(
"update",
mapOf(
"progress" to 0.4,
"header" to "Merging integrations...",
"log" to "Merging integrations"
)
) )
} }
if(cancel) { val patches =
handler.post { stopResult!!.success(null) } PatchBundleLoader.Dex(
return@Thread 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) } handler.post { stopResult!!.success(null) }
return@Thread return@Thread
} }
@ -192,91 +215,75 @@ class MainActivity : FlutterActivity() {
"update", "update",
mapOf( mapOf(
"progress" to 0.5, "progress" to 0.5,
"header" to "Applying patches...", "header" to "Executing patches...",
"log" to "" "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) } handler.post { stopResult!!.success(null) }
return@Thread 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 { handler.post {
installerChannel.invokeMethod( installerChannel.invokeMethod(
"update", "update",
mapOf( mapOf(
"progress" to 0.7, "progress" to 0.7,
"header" to "Repacking apk...", "header" to "Repacking APK...",
"log" to "Repacking patched apk" "log" to ""
) )
) )
} }
if(cancel) { val res = patcher.get()
handler.post { stopResult!!.success(null) } patcher.close()
return@Thread
}
val res = patcher.save()
ZipFile(patchedFile).use { file -> ZipFile(patchedFile).use { file ->
res.dexFiles.forEach { res.dexFiles.forEach {
if(cancel) { if (cancel) {
handler.post { stopResult!!.success(null) } handler.post { stopResult!!.success(null) }
return@Thread return@Thread
} }
@ -296,7 +303,7 @@ class MainActivity : FlutterActivity() {
ZipAligner::getEntryAlignment ZipAligner::getEntryAlignment
) )
} }
if(cancel) { if (cancel) {
handler.post { stopResult!!.success(null) } handler.post { stopResult!!.success(null) }
return@Thread return@Thread
} }
@ -305,7 +312,7 @@ class MainActivity : FlutterActivity() {
"update", "update",
mapOf( mapOf(
"progress" to 0.9, "progress" to 0.9,
"header" to "Signing apk...", "header" to "Signing APK...",
"log" to "" "log" to ""
) )
) )
@ -319,7 +326,7 @@ class MainActivity : FlutterActivity() {
) )
} catch (e: Exception) { } catch (e: Exception) {
//log to console //log to console
print("Error signing apk: ${e.message}") print("Error signing APK: ${e.message}")
e.printStackTrace() e.printStackTrace()
} }
@ -334,52 +341,54 @@ class MainActivity : FlutterActivity() {
) )
} }
} catch (ex: Throwable) { } catch (ex: Throwable) {
val stack = ex.stackTraceToString() if (!cancel) {
handler.post { val stack = ex.stackTraceToString()
installerChannel.invokeMethod( handler.post {
"update", installerChannel.invokeMethod(
mapOf( "update",
"progress" to -100.0, mapOf(
"header" to "Aborted...", "progress" to -100.0,
"log" to "An error occurred! Aborted\nError:\n$stack" "header" to "Aborted...",
"log" to "An error occurred! Aborted\nError:\n$stack"
)
) )
) }
} }
} }
handler.post { result.success(null) } handler.post { result.success(null) }
}.start() }.start()
} }
inner class ManagerLogger : Logger { // inner class ManagerLogger : Logger {
override fun error(msg: String) { // override fun error(msg: String) {
handler.post { // handler.post {
installerChannel // installerChannel
.invokeMethod( // .invokeMethod(
"update", // "update",
mapOf("progress" to -1.0, "header" to "", "log" to msg) // mapOf("progress" to -1.0, "header" to "", "log" to msg)
) // )
} // }
} // }
//
override fun warn(msg: String) { // override fun warn(msg: String) {
handler.post { // handler.post {
installerChannel.invokeMethod( // installerChannel.invokeMethod(
"update", // "update",
mapOf("progress" to -1.0, "header" to "", "log" to msg) // mapOf("progress" to -1.0, "header" to "", "log" to msg)
) // )
} // }
} // }
//
override fun info(msg: String) { // override fun info(msg: String) {
handler.post { // handler.post {
installerChannel.invokeMethod( // installerChannel.invokeMethod(
"update", // "update",
mapOf("progress" to -1.0, "header" to "", "log" to msg) // mapOf("progress" to -1.0, "header" to "", "log" to msg)
) // )
} // }
} // }
//
override fun trace(_msg: String) { /* unused */ // override fun trace(_msg: String) { /* unused */
} // }
} // }
} }

View File

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.7.10' ext.kotlin_version = '1.9.0'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
@ -22,6 +22,7 @@ allprojects {
password = (project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN")) as String password = (project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN")) as String
} }
} }
mavenLocal()
} }
} }

View File

@ -11,6 +11,7 @@
"noButton": "No", "noButton": "No",
"warning": "Warning", "warning": "Warning",
"notice": "Notice", "notice": "Notice",
"noShowAgain": "Don't show this again",
"new": "New", "new": "New",
"navigationView": { "navigationView": {
"dashboardTab": "Dashboard", "dashboardTab": "Dashboard",
@ -136,12 +137,21 @@
"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.", "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": { "installerView": {
"widgetTitle": "Installer", "widgetTitle": "Installer",
"installType": "Select install type",
"installTypeDescription": "Select the installation type to proceed with.",
"installButton": "Install", "installButton": "Install",
"installRootButton": "Install as Root", "installRootType": "Root",
"installNonRootType": "Non-root",
"installRecommendedType": "Recommended",
"pressBackAgain": "Press back again to cancel", "pressBackAgain": "Press back again to cancel",
"openButton": "Open", "openButton": "Open",
"shareButton": "Share file", "shareButton": "Share file",
@ -149,9 +159,8 @@
"notificationTitle": "ReVanced Manager is patching", "notificationTitle": "ReVanced Manager is patching",
"notificationText": "Tap to return to the installer", "notificationText": "Tap to return to the installer",
"shareApkMenuOption": "Share APK", "exportApkButtonTooltip": "Export patched APK",
"exportApkMenuOption": "Export APK", "exportLogButtonTooltip": "Export log",
"shareLogMenuOption": "Share log",
"installErrorDialogTitle": "Error", "installErrorDialogTitle": "Error",
"installErrorDialogText1": "Root install is not possible with the current patches selection.\nRepatch your app or choose non-root install.", "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", "logsLabel": "Logs",
"logsHint": "Share Manager's 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", "autoUpdatePatchesLabel": "Auto update patches",
"autoUpdatePatchesHint": "Automatically update ReVanced Patches to the latest version", "autoUpdatePatchesHint": "Automatically update ReVanced Patches to the latest version",
"experimentalUniversalPatchesLabel": "Experimental universal patches support", "experimentalUniversalPatchesLabel": "Experimental universal patches support",

View File

@ -2,7 +2,7 @@
In order to use ReVanced on your Android device, ReVanced Manager must be installed. 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) 1. Download the latest version of ReVanced Manager from [here](https://github.com/revanced/revanced-manager/releases/latest)
2. Install ReVanced Manager 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. 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. 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 1. Navigate to the **Patcher** tab from the bottom navigation bar
2. Tap on the **Select an app** card 2. Tap on the **Select an app** card

View File

@ -2,7 +2,7 @@
After patching an app, you may want to manage it. This page will guide you through managing patched apps. 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 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. Tap on the **Info** button for the app you want to manage

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. 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 1. Navigate to the **Dashboard** tab from the bottom navigation bar
2. Tap on the **Update** button in the **Updates** section 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. ReVanced Manager has settings that can be configured to your liking.
## 🪛 Essential settings ## Essential settings
- ### 🔗 API URL - ### 🔗 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. 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. 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. 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) 2. [🧰 Managing patched apps](2_2_managing.md)
3. [🔄 Updating ReVanced Manager](2_3_updating.md) 3. [🔄 Updating ReVanced Manager](2_3_updating.md)
4. [⚙️ Configuring ReVanced Manager](2_4_settings.md) 4. [⚙️ Configuring ReVanced Manager](2_4_settings.md)
3. [🛟 Troubleshooting](3_troubleshooting.md) 3. [ Troubleshooting](3_troubleshooting.md)
4. [🛠 Building from source](4_building.md) 4. [🔨 Building from source](4_building.md)
## ⏭️ Start here ## ⏭️ Start here

View File

@ -6,12 +6,14 @@ import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.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/models/patch.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
@lazySingleton @lazySingleton
class GithubAPI { class GithubAPI {
late Dio _dio = Dio(); late Dio _dio = Dio();
late final ManagerAPI _managerAPI = locator<ManagerAPI>();
final _cacheOptions = CacheOptions( final _cacheOptions = CacheOptions(
store: MemCacheStore(), store: MemCacheStore(),
@ -201,8 +203,14 @@ class GithubAPI {
String extension, String extension,
String repoName, String repoName,
String version, String version,
String url,
) async { ) async {
try { try {
if (url.isNotEmpty) {
return await DefaultCacheManager().getSingleFile(
url,
);
}
final Map<String, dynamic>? release = final Map<String, dynamic>? release =
await getPatchesRelease(repoName, version); await getPatchesRelease(repoName, version);
if (release != null) { if (release != null) {
@ -211,8 +219,16 @@ class GithubAPI {
(asset) => (asset['name'] as String).endsWith(extension), (asset) => (asset['name'] as String).endsWith(extension),
); );
if (asset != null) { 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( return await DefaultCacheManager().getSingleFile(
asset['browser_download_url'], downloadUrl,
); );
} }
} }
@ -224,10 +240,19 @@ class GithubAPI {
return null; 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 = []; List<Patch> patches = [];
try { try {
final File? f = await getPatchesReleaseFile('.json', repoName, version); final File? f = await getPatchesReleaseFile(
'.json',
repoName,
version,
url,
);
if (f != null) { if (f != null) {
final List<dynamic> list = jsonDecode(f.readAsStringSync()); final List<dynamic> list = jsonDecode(f.readAsStringSync());
patches = list.map((patch) => Patch.fromJson(patch)).toList(); patches = list.map((patch) => Patch.fromJson(patch)).toList();

View File

@ -2,6 +2,8 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:device_apps/device_apps.dart'; import 'package:device_apps/device_apps.dart';
import 'package:flutter/foundation.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:injectable/injectable.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.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/github_api.dart';
import 'package:revanced_manager/services/revanced_api.dart'; import 'package:revanced_manager/services/revanced_api.dart';
import 'package:revanced_manager/services/root_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:revanced_manager/utils/check_for_supported_patch.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:timeago/timeago.dart'; import 'package:timeago/timeago.dart';
@ -76,6 +79,14 @@ class ManagerAPI {
await _prefs.setString('repoUrl', url); 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() { String getPatchesRepo() {
return _prefs.getString('patchesRepo') ?? defaultPatchesRepo; return _prefs.getString('patchesRepo') ?? defaultPatchesRepo;
} }
@ -99,6 +110,41 @@ class ManagerAPI {
return _prefs.getBool('patchesAutoUpdate') ?? false; 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 { Future<void> setPatchesAutoUpdate(bool value) async {
await _prefs.setBool('patchesAutoUpdate', value); await _prefs.setBool('patchesAutoUpdate', value);
} }
@ -119,6 +165,14 @@ class ManagerAPI {
await _prefs.setStringList('savedPatches-$packageName', patchesJson); 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) { List<Patch> getUsedPatches(String packageName) {
final List<String> patchesJson = final List<String> patchesJson =
_prefs.getStringList('usedPatches-$packageName') ?? []; _prefs.getStringList('usedPatches-$packageName') ?? [];
@ -260,7 +314,12 @@ class ManagerAPI {
try { try {
final String repoName = getPatchesRepo(); final String repoName = getPatchesRepo();
final String currentVersion = await getCurrentPatchesVersion(); 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) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
print(e); print(e);
@ -273,10 +332,12 @@ class ManagerAPI {
try { try {
final String repoName = getPatchesRepo(); final String repoName = getPatchesRepo();
final String currentVersion = await getCurrentPatchesVersion(); final String currentVersion = await getCurrentPatchesVersion();
final String url = getPatchesDownloadURL(true);
return await _githubAPI.getPatchesReleaseFile( return await _githubAPI.getPatchesReleaseFile(
'.jar', '.jar',
repoName, repoName,
currentVersion, currentVersion,
url,
); );
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
@ -290,10 +351,12 @@ class ManagerAPI {
try { try {
final String repoName = getIntegrationsRepo(); final String repoName = getIntegrationsRepo();
final String currentVersion = await getCurrentIntegrationsVersion(); final String currentVersion = await getCurrentIntegrationsVersion();
final String url = getIntegrationsDownloadURL();
return await _githubAPI.getPatchesReleaseFile( return await _githubAPI.getPatchesReleaseFile(
'.apk', '.apk',
repoName, repoName,
currentVersion, currentVersion,
url,
); );
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
@ -384,27 +447,39 @@ class ManagerAPI {
Future<String> getCurrentPatchesVersion() async { Future<String> getCurrentPatchesVersion() async {
patchesVersion = _prefs.getString('patchesVersion') ?? '0.0.0'; patchesVersion = _prefs.getString('patchesVersion') ?? '0.0.0';
if (patchesVersion == '0.0.0' || isPatchesAutoUpdate()) { if (patchesVersion == '0.0.0' || isPatchesAutoUpdate()) {
patchesVersion = await getLatestPatchesVersion() ?? '0.0.0'; final String newPatchesVersion =
await setCurrentPatchesVersion(patchesVersion!); await getLatestPatchesVersion() ?? '0.0.0';
if (patchesVersion != newPatchesVersion && newPatchesVersion != '0.0.0') {
await setCurrentPatchesVersion(newPatchesVersion);
}
} }
return patchesVersion!; return patchesVersion!;
} }
Future<void> setCurrentPatchesVersion(String version) async { Future<void> setCurrentPatchesVersion(String version) async {
await _prefs.setString('patchesVersion', version); await _prefs.setString('patchesVersion', version);
await setPatchesDownloadURL('', false);
await setPatchesDownloadURL('', true);
await downloadPatches();
} }
Future<String> getCurrentIntegrationsVersion() async { Future<String> getCurrentIntegrationsVersion() async {
integrationsVersion = _prefs.getString('integrationsVersion') ?? '0.0.0'; integrationsVersion = _prefs.getString('integrationsVersion') ?? '0.0.0';
if (integrationsVersion == '0.0.0' || isPatchesAutoUpdate()) { if (integrationsVersion == '0.0.0' || isPatchesAutoUpdate()) {
integrationsVersion = await getLatestIntegrationsVersion() ?? '0.0.0'; final String newIntegrationsVersion =
await setCurrentIntegrationsVersion(integrationsVersion!); await getLatestIntegrationsVersion() ?? '0.0.0';
if (integrationsVersion != newIntegrationsVersion &&
newIntegrationsVersion != '0.0.0') {
await setCurrentIntegrationsVersion(newIntegrationsVersion);
}
} }
return integrationsVersion!; return integrationsVersion!;
} }
Future<void> setCurrentIntegrationsVersion(String version) async { Future<void> setCurrentIntegrationsVersion(String version) async {
await _prefs.setString('integrationsVersion', version); await _prefs.setString('integrationsVersion', version);
await setIntegrationsDownloadURL('');
await downloadIntegrations();
} }
Future<List<PatchedApplication>> getAppsToRemove( Future<List<PatchedApplication>> getAppsToRemove(
@ -478,6 +553,63 @@ class ManagerAPI {
return unsavedApps; 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 { Future<void> reAssessSavedApps() async {
final List<PatchedApplication> patchedApps = getPatchedApps(); final List<PatchedApplication> patchedApps = getPatchedApps();
final List<PatchedApplication> unsavedApps = final List<PatchedApplication> unsavedApps =

View File

@ -30,6 +30,8 @@ class PatcherAPI {
Future<void> initialize() async { Future<void> initialize() async {
await _loadPatches(); await _loadPatches();
await _managerAPI.downloadPatches();
await _managerAPI.downloadIntegrations();
final Directory appCache = await getTemporaryDirectory(); final Directory appCache = await getTemporaryDirectory();
_dataDir = await getExternalStorageDirectory() ?? appCache; _dataDir = await getExternalStorageDirectory() ?? appCache;
_tmpDir = Directory('${appCache.path}/patcher'); _tmpDir = Directory('${appCache.path}/patcher');
@ -283,7 +285,7 @@ class PatcherAPI {
return newName; return newName;
} }
Future<void> sharePatcherLog(String logs) async { Future<void> exportPatcherLog(String logs) async {
final Directory appCache = await getTemporaryDirectory(); final Directory appCache = await getTemporaryDirectory();
final Directory logDir = Directory('${appCache.path}/logs'); final Directory logDir = Directory('${appCache.path}/logs');
logDir.createSync(); logDir.createSync();
@ -293,10 +295,15 @@ class PatcherAPI {
.replaceAll(':', '') .replaceAll(':', '')
.replaceAll('T', '') .replaceAll('T', '')
.replaceAll('.', ''); .replaceAll('.', '');
final File log = final String fileName = 'revanced-manager_patcher_$dateTime.log';
File('${logDir.path}/revanced-manager_patcher_$dateTime.log'); final File log = File('${logDir.path}/$fileName');
log.writeAsStringSync(logs); log.writeAsStringSync(logs);
ShareExtend.share(log.path, 'file'); CRFileSaver.saveFileWithDialog(
SaveFileDialogParams(
sourceFilePath: log.path,
destinationFileName: fileName,
),
);
} }
String getSuggestedVersion(String packageName) { String getSuggestedVersion(String packageName) {

View File

@ -94,7 +94,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
padding: const EdgeInsets.symmetric(horizontal: 12.0) padding: const EdgeInsets.symmetric(horizontal: 12.0)
.copyWith( .copyWith(
bottom: bottom:
MediaQuery.of(context).viewPadding.bottom + 8.0, MediaQuery.viewPaddingOf(context).bottom + 8.0,
), ),
child: Column( child: Column(
children: [ children: [
@ -133,6 +133,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
), ),
) )
.toList(), .toList(),
const SizedBox(height: 70.0),
], ],
), ),
), ),

View File

@ -57,7 +57,7 @@ class ContributorsView extends StatelessWidget {
title: 'contributorsView.managerContributors', title: 'contributorsView.managerContributors',
contributors: model.managerContributors, contributors: model.managerContributors,
), ),
SizedBox(height: MediaQuery.of(context).viewPadding.bottom), SizedBox(height: MediaQuery.viewPaddingOf(context).bottom),
], ],
), ),
), ),

View File

@ -256,9 +256,9 @@ class HomeViewModel extends BaseViewModel {
final String integrationsVersion = final String integrationsVersion =
await _managerAPI.getLatestIntegrationsVersion() ?? '0.0.0'; await _managerAPI.getLatestIntegrationsVersion() ?? '0.0.0';
if (patchesVersion != '0.0.0' && integrationsVersion != '0.0.0') { if (patchesVersion != '0.0.0' && integrationsVersion != '0.0.0') {
_toast.showBottom('homeView.downloadedMessage');
await _managerAPI.setCurrentPatchesVersion(patchesVersion); await _managerAPI.setCurrentPatchesVersion(patchesVersion);
await _managerAPI.setCurrentIntegrationsVersion(integrationsVersion); await _managerAPI.setCurrentIntegrationsVersion(integrationsVersion);
_toast.showBottom('homeView.downloadedMessage');
forceRefresh(context); forceRefresh(context);
} else { } else {
_toast.showBottom('homeView.errorDownloadMessage'); _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/views/installer/installer_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/installerView/gradient_progress_indicator.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_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:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
@ -22,6 +20,45 @@ class InstallerView extends StatelessWidget {
top: false, top: false,
bottom: false, bottom: false,
child: Scaffold( 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( body: CustomScrollView(
controller: model.scrollController, controller: model.scrollController,
slivers: <Widget>[ slivers: <Widget>[
@ -35,44 +72,6 @@ class InstallerView extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
onBackButtonPressed: () => model.onWillPop(context), 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( bottom: PreferredSize(
preferredSize: const Size(double.infinity, 1.0), preferredSize: const Size(double.infinity, 1.0),
child: GradientProgressIndicator(progress: model.progress), 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

@ -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 { Future<void> stopPatcher() async {
try { try {
isCanceled = true; isCanceled = true;
@ -253,18 +336,8 @@ class InstallerViewModel extends BaseViewModel {
} }
} }
void shareResult() { void exportLog() {
try { _patcherAPI.exportPatcherLog(logs);
_patcherAPI.sharePatchedFile(_app.name, _app.version);
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
}
void shareLog() {
_patcherAPI.sharePatcherLog(logs);
} }
Future<void> cleanPatcher() async { Future<void> cleanPatcher() async {
@ -284,16 +357,13 @@ class InstallerViewModel extends BaseViewModel {
DeviceApps.openApp(_app.packageName); DeviceApps.openApp(_app.packageName);
} }
void onMenuSelection(int value) { void onButtonPressed(int value) {
switch (value) { switch (value) {
case 0: case 0:
shareResult();
break;
case 1:
exportResult(); exportResult();
break; break;
case 2: case 1:
shareLog(); exportLog();
break; break;
} }
} }

View File

@ -33,7 +33,7 @@ class NavigationViewModel extends IndexTrackingViewModel {
if (prefs.getBool('useDarkTheme') == null) { if (prefs.getBool('useDarkTheme') == null) {
final bool isDark = final bool isDark =
MediaQuery.of(context).platformBrightness != Brightness.light; MediaQuery.platformBrightnessOf(context) != Brightness.light;
await prefs.setBool('useDarkTheme', isDark); await prefs.setBool('useDarkTheme', isDark);
await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0); await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0);
} }

View File

@ -191,6 +191,10 @@ class PatcherViewModel extends BaseViewModel {
this this
.selectedPatches .selectedPatches
.addAll(patches.where((patch) => selectedPatches.contains(patch.name))); .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()) { if (!_managerAPI.areExperimentalPatchesEnabled()) {
this.selectedPatches.removeWhere((patch) => !isPatchSupported(patch)); this.selectedPatches.removeWhere((patch) => !isPatchSupported(patch));
} }

View File

@ -20,6 +20,17 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
String _query = ''; String _query = '';
final _managerAPI = locator<ManagerAPI>(); final _managerAPI = locator<ManagerAPI>();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!_managerAPI.isPatchesChangeEnabled() &&
_managerAPI.showPatchesChangeWarning()) {
_managerAPI.showPatchesChangeWarningDialog(context);
}
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ViewModelBuilder<PatchesSelectorViewModel>.reactive( return ViewModelBuilder<PatchesSelectorViewModel>.reactive(
@ -87,7 +98,8 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
), ),
), ),
CustomPopupMenu( CustomPopupMenu(
onSelected: (value) => {model.onMenuSelection(value)}, onSelected: (value) =>
{model.onMenuSelection(value, context)},
children: { children: {
0: I18nText( 0: I18nText(
'patchesSelectorView.loadPatchesSelection', 'patchesSelectorView.loadPatchesSelection',
@ -139,7 +151,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
: Padding( : Padding(
padding: padding:
const EdgeInsets.symmetric(horizontal: 12.0).copyWith( const EdgeInsets.symmetric(horizontal: 12.0).copyWith(
bottom: MediaQuery.of(context).viewPadding.bottom + 8.0, bottom: MediaQuery.viewPaddingOf(context).bottom + 8.0,
), ),
child: Column( child: Column(
children: [ children: [
@ -152,7 +164,11 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
'patchesSelectorView.defaultTooltip', 'patchesSelectorView.defaultTooltip',
), ),
onPressed: () { onPressed: () {
model.selectDefaultPatches(); if (_managerAPI.isPatchesChangeEnabled()) {
model.selectDefaultPatches();
} else {
model.showPatchesChangeDialog(context);
}
}, },
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@ -163,7 +179,11 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
'patchesSelectorView.noneTooltip', 'patchesSelectorView.noneTooltip',
), ),
onPressed: () { onPressed: () {
model.clearPatches(); if (_managerAPI.isPatchesChangeEnabled()) {
model.clearPatches();
} else {
model.showPatchesChangeDialog(context);
}
}, },
), ),
], ],
@ -179,13 +199,14 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
supportedPackageVersions: supportedPackageVersions:
model.getSupportedVersions(patch), model.getSupportedVersions(patch),
isUnsupported: !isPatchSupported(patch), isUnsupported: !isPatchSupported(patch),
isChangeEnabled: _managerAPI.isPatchesChangeEnabled(),
isNew: model.isPatchNew( isNew: model.isPatchNew(
patch, patch,
model.getAppInfo().packageName, model.getAppInfo().packageName,
), ),
isSelected: model.isSelected(patch), isSelected: model.isSelected(patch),
onChanged: (value) => onChanged: (value) =>
model.selectPatch(patch, value), model.selectPatch(patch, value, context),
); );
} else { } else {
return Container(); return Container();
@ -215,10 +236,14 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
supportedPackageVersions: supportedPackageVersions:
model.getSupportedVersions(patch), model.getSupportedVersions(patch),
isUnsupported: !isPatchSupported(patch), isUnsupported: !isPatchSupported(patch),
isChangeEnabled: _managerAPI.isPatchesChangeEnabled(),
isNew: false, isNew: false,
isSelected: model.isSelected(patch), isSelected: model.isSelected(patch),
onChanged: (value) => onChanged: (value) => model.selectPatch(
model.selectPatch(patch, value), patch,
value,
context,
),
); );
} else { } else {
return Container(); return Container();

View File

@ -1,4 +1,6 @@
import 'package:collection/collection.dart'; 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/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/models/patched_application.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/patcher_api.dart';
import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:revanced_manager/utils/check_for_supported_patch.dart'; import 'package:revanced_manager/utils/check_for_supported_patch.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
@ -45,31 +48,70 @@ class PatchesSelectorViewModel extends BaseViewModel {
); );
} }
void selectPatch(Patch patch, bool isSelected) { void selectPatch(Patch patch, bool isSelected, BuildContext context) {
if (isSelected && !selectedPatches.contains(patch)) { if (_managerAPI.isPatchesChangeEnabled()) {
selectedPatches.add(patch); if (isSelected && !selectedPatches.contains(patch)) {
selectedPatches.add(patch);
} else {
selectedPatches.remove(patch);
}
notifyListeners();
} else { } 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() { void selectDefaultPatches() {
selectedPatches.clear(); selectedPatches.clear();
if (locator<PatcherViewModel>().selectedApp?.originalPackageName != null) {
if (_managerAPI.areExperimentalPatchesEnabled() == false) {
selectedPatches.addAll( selectedPatches.addAll(
patches.where( _patcherAPI
(element) => element.excluded == false && isPatchSupported(element), .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(); notifyListeners();
} }
@ -133,10 +175,10 @@ class PatchesSelectorViewModel extends BaseViewModel {
} }
} }
void onMenuSelection(value) { void onMenuSelection(value, BuildContext context) {
switch (value) { switch (value) {
case 0: case 0:
loadSelectedPatches(); loadSelectedPatches(context);
break; break;
} }
} }
@ -150,18 +192,25 @@ class PatchesSelectorViewModel extends BaseViewModel {
); );
} }
Future<void> loadSelectedPatches() async { Future<void> loadSelectedPatches(BuildContext context) async {
final List<String> selectedPatches = await _managerAPI.getSelectedPatches( if (_managerAPI.isPatchesChangeEnabled()) {
locator<PatcherViewModel>().selectedApp!.originalPackageName, final List<String> selectedPatches = await _managerAPI.getSelectedPatches(
); locator<PatcherViewModel>().selectedApp!.originalPackageName,
if (selectedPatches.isNotEmpty) { );
this.selectedPatches.clear(); if (selectedPatches.isNotEmpty) {
this.selectedPatches.addAll( this.selectedPatches.clear();
patches.where((patch) => selectedPatches.contains(patch.name)), 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 { } else {
locator<Toast>().showBottom('patchesSelectorView.noSavedPatches'); showPatchesChangeDialog(context);
} }
notifyListeners();
} }
} }

View File

@ -129,6 +129,7 @@ class SManageSources extends BaseViewModel {
'${_orgIntSourceController.text.trim()}/${_intSourceController.text.trim()}', '${_orgIntSourceController.text.trim()}/${_intSourceController.text.trim()}',
); );
_managerAPI.setCurrentPatchesVersion('0.0.0'); _managerAPI.setCurrentPatchesVersion('0.0.0');
_managerAPI.setCurrentIntegrationsVersion('0.0.0');
_toast.showBottom('settingsView.restartAppForChanges'); _toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@ -158,6 +159,7 @@ class SManageSources extends BaseViewModel {
_managerAPI.setPatchesRepo(''); _managerAPI.setPatchesRepo('');
_managerAPI.setIntegrationsRepo(''); _managerAPI.setIntegrationsRepo('');
_managerAPI.setCurrentPatchesVersion('0.0.0'); _managerAPI.setCurrentPatchesVersion('0.0.0');
_managerAPI.setCurrentIntegrationsVersion('0.0.0');
_toast.showBottom('settingsView.restartAppForChanges'); _toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context) 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:device_info_plus/device_info_plus.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.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:logcat/logcat.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:revanced_manager/app/app.locator.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/manager_api.dart';
import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.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_language.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.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:share_extend/share_extend.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
@ -19,6 +23,9 @@ import 'package:stacked_services/stacked_services.dart';
class SettingsViewModel extends BaseViewModel { class SettingsViewModel extends BaseViewModel {
final NavigationService _navigationService = locator<NavigationService>(); final NavigationService _navigationService = locator<NavigationService>();
final ManagerAPI _managerAPI = locator<ManagerAPI>(); final ManagerAPI _managerAPI = locator<ManagerAPI>();
final PatchesSelectorViewModel _patchesSelectorViewModel =
PatchesSelectorViewModel();
final PatcherViewModel _patcherViewModel = locator<PatcherViewModel>();
final Toast _toast = locator<Toast>(); final Toast _toast = locator<Toast>();
final SUpdateLanguage sUpdateLanguage = SUpdateLanguage(); final SUpdateLanguage sUpdateLanguage = SUpdateLanguage();
@ -37,6 +44,88 @@ class SettingsViewModel extends BaseViewModel {
notifyListeners(); 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() { bool areUniversalPatchesEnabled() {
return _managerAPI.areUniversalPatchesEnabled(); return _managerAPI.areUniversalPatchesEnabled();
} }
@ -90,26 +179,30 @@ class SettingsViewModel extends BaseViewModel {
} }
} }
Future<void> importPatches() async { Future<void> importPatches(BuildContext context) async {
try { if (isPatchesChangeEnabled()) {
final FilePickerResult? result = await FilePicker.platform.pickFiles( try {
type: FileType.custom, final FilePickerResult? result = await FilePicker.platform.pickFiles(
allowedExtensions: ['json'], type: FileType.custom,
); allowedExtensions: ['json'],
if (result != null && result.files.single.path != null) { );
final File inFile = File(result.files.single.path!); if (result != null && result.files.single.path != null) {
inFile.copySync(_managerAPI.storedPatchesFile); final File inFile = File(result.files.single.path!);
inFile.delete(); inFile.copySync(_managerAPI.storedPatchesFile);
if (locator<PatcherViewModel>().selectedApp != null) { inFile.delete();
locator<PatcherViewModel>().loadLastSelectedPatches(); 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) { } else {
if (kDebugMode) { _managerAPI.showPatchesChangeWarningDialog(context);
print(e);
}
_toast.showBottom('settingsView.jsonSelectorErrorMessage');
} }
} }

View File

@ -7,7 +7,7 @@ class AppSkeletonLoader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.sizeOf(context).width;
return ListView.builder( return ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: 7, itemCount: 7,

View File

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

View File

@ -19,6 +19,7 @@ class PatchItem extends StatefulWidget {
required this.isNew, required this.isNew,
required this.isSelected, required this.isSelected,
required this.onChanged, required this.onChanged,
required this.isChangeEnabled,
this.child, this.child,
}) : super(key: key); }) : super(key: key);
final String name; final String name;
@ -30,6 +31,7 @@ class PatchItem extends StatefulWidget {
final bool isNew; final bool isNew;
bool isSelected; bool isSelected;
final Function(bool) onChanged; final Function(bool) onChanged;
final bool isChangeEnabled;
final Widget? child; final Widget? child;
final toast = locator<Toast>(); final toast = locator<Toast>();
final _managerAPI = locator<ManagerAPI>(); final _managerAPI = locator<ManagerAPI>();
@ -58,11 +60,13 @@ class _PatchItemState extends State<PatchItem> {
!widget._managerAPI.areExperimentalPatchesEnabled()) { !widget._managerAPI.areExperimentalPatchesEnabled()) {
widget.isSelected = false; widget.isSelected = false;
widget.toast.showBottom('patchItem.unsupportedPatchVersion'); widget.toast.showBottom('patchItem.unsupportedPatchVersion');
} else { } else if (widget.isChangeEnabled) {
widget.isSelected = !widget.isSelected; widget.isSelected = !widget.isSelected;
} }
}); });
widget.onChanged(widget.isSelected); if (!widget.isUnsupported || widget._managerAPI.areExperimentalPatchesEnabled()) {
widget.onChanged(widget.isSelected);
}
}, },
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
@ -124,11 +128,13 @@ class _PatchItemState extends State<PatchItem> {
widget.toast.showBottom( widget.toast.showBottom(
'patchItem.unsupportedPatchVersion', 'patchItem.unsupportedPatchVersion',
); );
} else { } else if (widget.isChangeEnabled) {
widget.isSelected = newValue!; 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(), widget.child ?? const SizedBox(),

View File

@ -8,8 +8,9 @@ class OptionsTextField extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final sHeight = MediaQuery.of(context).size.height; final size = MediaQuery.sizeOf(context);
final sWidth = MediaQuery.of(context).size.width; final sHeight = size.height;
final sWidth = size.width;
return Container( return Container(
margin: const EdgeInsets.only(top: 12, bottom: 6), margin: const EdgeInsets.only(top: 12, bottom: 6),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,

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_api_url.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_sources.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/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_auto_update_patches.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_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'; import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart';
@ -25,6 +26,7 @@ class SAdvancedSection extends StatelessWidget {
SManageSourcesUI(), SManageSourcesUI(),
// SManageKeystorePasswordUI(), // SManageKeystorePasswordUI(),
SAutoUpdatePatches(), SAutoUpdatePatches(),
SEnablePatchesSelection(),
SExperimentalUniversalPatches(), SExperimentalUniversalPatches(),
SExperimentalPatches(), SExperimentalPatches(),
ListTile( ListTile(

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

@ -43,7 +43,7 @@ class SExportSection extends StatelessWidget {
), ),
), ),
subtitle: I18nText('settingsView.importPatchesHint'), subtitle: I18nText('settingsView.importPatchesHint'),
onTap: () => _settingsViewModel.importPatches(), onTap: () => _settingsViewModel.importPatches(context),
), ),
ListTile( ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
@ -73,10 +73,10 @@ class SExportSection extends StatelessWidget {
), ),
), ),
subtitle: I18nText('settingsView.importKeystoreHint'), subtitle: I18nText('settingsView.importKeystoreHint'),
onTap: () async{ onTap: () async {
await _settingsViewModel.importKeystore(); await _settingsViewModel.importKeystore();
final sManageKeystorePassword = SManageKeystorePassword(); final sManageKeystorePassword = SManageKeystorePassword();
if(context.mounted){ if (context.mounted) {
sManageKeystorePassword.showKeystoreDialog(context); sManageKeystorePassword.showKeystoreDialog(context);
} }
}, },