mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2026-01-19 09:13:57 +00:00
Compare commits
11 Commits
v1.23.0-de
...
v1.23.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9552b2ebc5 | ||
|
|
7e3afe0cb2 | ||
|
|
7b7d91d661 | ||
|
|
44b8d4ceee | ||
|
|
aaa97ebb71 | ||
|
|
d99e5af384 | ||
|
|
c47c7c0a88 | ||
|
|
3e32c0fd90 | ||
|
|
a45d9598cc | ||
|
|
8c8df698d4 | ||
|
|
8d0d782ab5 |
@@ -7,7 +7,7 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "app.revanced.manager.flutter"
|
namespace = "app.revanced.manager.flutter"
|
||||||
compileSdk = 34
|
compileSdk = 35
|
||||||
ndkVersion = "27.0.12077973"
|
ndkVersion = "27.0.12077973"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -24,9 +24,19 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "app.revanced.manager.flutter"
|
applicationId = "app.revanced.manager.flutter"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 35
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
|
|
||||||
|
resValue("string", "app_name", "ReVanced Manager")
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationVariants.all {
|
||||||
|
outputs.all {
|
||||||
|
this as com.android.build.gradle.internal.api.ApkVariantOutputImpl
|
||||||
|
|
||||||
|
outputFileName = "revanced-manager-$versionName.apk"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -37,7 +47,6 @@ android {
|
|||||||
signingConfig = signingConfigs["debug"]
|
signingConfig = signingConfigs["debug"]
|
||||||
|
|
||||||
ndk.abiFilters += setOf("armeabi-v7a", "arm64-v8a", "x86_64")
|
ndk.abiFilters += setOf("armeabi-v7a", "arm64-v8a", "x86_64")
|
||||||
setProperty("archivesBaseName", "revanced-manager-v${flutter.versionName}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
release {
|
release {
|
||||||
@@ -52,19 +61,21 @@ android {
|
|||||||
keyAlias = System.getenv("KEYSTORE_ENTRY_ALIAS")
|
keyAlias = System.getenv("KEYSTORE_ENTRY_ALIAS")
|
||||||
keyPassword = System.getenv("KEYSTORE_ENTRY_PASSWORD")
|
keyPassword = System.getenv("KEYSTORE_ENTRY_PASSWORD")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resValue("string", "app_name", "ReVanced Manager")
|
||||||
} else {
|
} else {
|
||||||
resValue("string", "app_name", "ReVanced Manager (Debug)")
|
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
|
|
||||||
signingConfig = signingConfigs["debug"]
|
signingConfig = signingConfigs["debug"]
|
||||||
}
|
|
||||||
|
|
||||||
resValue("string", "app_name", "ReVanced Manager")
|
resValue("string", "app_name", "ReVanced Manager (Debug signed)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug {
|
debug {
|
||||||
resValue("string", "app_name", "ReVanced Manager (Debug)")
|
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
|
|
||||||
|
resValue("string", "app_name", "ReVanced Manager (Debug)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +91,7 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
source = "../.."
|
source = "../.."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package app.revanced.manager.flutter
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageInfo
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
@@ -17,9 +16,8 @@ import java.security.MessageDigest
|
|||||||
class ExportSettingsActivity : Activity() {
|
class ExportSettingsActivity : Activity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val callingPackageName = getCallingPackage()!!
|
|
||||||
|
|
||||||
if (getFingerprint(callingPackageName) == getFingerprint(getPackageName())) {
|
if (getFingerprint(callingPackage!!) == getFingerprint(packageName)) {
|
||||||
// Create JSON Object
|
// Create JSON Object
|
||||||
val json = JSONObject()
|
val json = JSONObject()
|
||||||
|
|
||||||
@@ -64,7 +62,7 @@ class ExportSettingsActivity : Activity() {
|
|||||||
fun getFingerprint(packageName: String): String {
|
fun getFingerprint(packageName: String): String {
|
||||||
// Get the signature of the app that matches the package name
|
// Get the signature of the app that matches the package name
|
||||||
val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
|
val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
|
||||||
val signature = packageInfo.signatures[0]
|
val signature = packageInfo.signatures!![0]
|
||||||
|
|
||||||
// Get the raw certificate data
|
// Get the raw certificate data
|
||||||
val rawCert = signature.toByteArray()
|
val rawCert = signature.toByteArray()
|
||||||
|
|||||||
@@ -9,14 +9,15 @@ import android.os.Handler
|
|||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import app.revanced.library.ApkUtils
|
import app.revanced.library.ApkUtils
|
||||||
import app.revanced.library.ApkUtils.applyTo
|
import app.revanced.library.ApkUtils.applyTo
|
||||||
|
import app.revanced.library.installation.installer.LocalInstaller
|
||||||
import app.revanced.manager.flutter.utils.Aapt
|
import app.revanced.manager.flutter.utils.Aapt
|
||||||
import app.revanced.manager.flutter.utils.packageInstaller.InstallerReceiver
|
import app.revanced.manager.flutter.utils.packageInstaller.InstallerReceiver
|
||||||
import app.revanced.manager.flutter.utils.packageInstaller.UninstallerReceiver
|
import app.revanced.manager.flutter.utils.packageInstaller.UninstallerReceiver
|
||||||
import app.revanced.patcher.PatchBundleLoader
|
|
||||||
import app.revanced.patcher.PatchSet
|
|
||||||
import app.revanced.patcher.Patcher
|
import app.revanced.patcher.Patcher
|
||||||
import app.revanced.patcher.PatcherConfig
|
import app.revanced.patcher.PatcherConfig
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
import app.revanced.patcher.patch.PatchResult
|
import app.revanced.patcher.patch.PatchResult
|
||||||
|
import app.revanced.patcher.patch.loadPatchesFromDex
|
||||||
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
|
||||||
@@ -37,7 +38,7 @@ 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
|
||||||
|
|
||||||
private lateinit var patches: PatchSet
|
private lateinit var patches: Set<Patch<*>>
|
||||||
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
@@ -70,7 +71,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
"runPatcher" -> {
|
"runPatcher" -> {
|
||||||
val inFilePath = call.argument<String>("inFilePath")
|
val inFilePath = call.argument<String>("inFilePath")
|
||||||
val outFilePath = call.argument<String>("outFilePath")
|
val outFilePath = call.argument<String>("outFilePath")
|
||||||
val integrationsPath = call.argument<String>("integrationsPath")
|
|
||||||
val selectedPatches = call.argument<List<String>>("selectedPatches")
|
val selectedPatches = call.argument<List<String>>("selectedPatches")
|
||||||
val options = call.argument<Map<String, Map<String, Any>>>("options")
|
val options = call.argument<Map<String, Map<String, Any>>>("options")
|
||||||
val tmpDirPath = call.argument<String>("tmpDirPath")
|
val tmpDirPath = call.argument<String>("tmpDirPath")
|
||||||
@@ -80,7 +80,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
if (
|
if (
|
||||||
inFilePath != null &&
|
inFilePath != null &&
|
||||||
outFilePath != null &&
|
outFilePath != null &&
|
||||||
integrationsPath != null &&
|
|
||||||
selectedPatches != null &&
|
selectedPatches != null &&
|
||||||
options != null &&
|
options != null &&
|
||||||
tmpDirPath != null &&
|
tmpDirPath != null &&
|
||||||
@@ -92,14 +91,17 @@ class MainActivity : FlutterActivity() {
|
|||||||
result,
|
result,
|
||||||
inFilePath,
|
inFilePath,
|
||||||
outFilePath,
|
outFilePath,
|
||||||
integrationsPath,
|
|
||||||
selectedPatches,
|
selectedPatches,
|
||||||
options,
|
options,
|
||||||
tmpDirPath,
|
tmpDirPath,
|
||||||
keyStoreFilePath,
|
keyStoreFilePath,
|
||||||
keystorePassword
|
keystorePassword
|
||||||
)
|
)
|
||||||
} else result.notImplemented()
|
} else result.error(
|
||||||
|
"INVALID_ARGUMENTS",
|
||||||
|
"Invalid arguments",
|
||||||
|
"One or more arguments are missing"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"stopPatcher" -> {
|
"stopPatcher" -> {
|
||||||
@@ -113,14 +115,16 @@ class MainActivity : FlutterActivity() {
|
|||||||
try {
|
try {
|
||||||
val patchBundleFile = File(patchBundleFilePath)
|
val patchBundleFile = File(patchBundleFilePath)
|
||||||
patchBundleFile.setWritable(false)
|
patchBundleFile.setWritable(false)
|
||||||
patches = PatchBundleLoader.Dex(
|
patches = loadPatchesFromDex(
|
||||||
patchBundleFile,
|
setOf(patchBundleFile),
|
||||||
optimizedDexDirectory = codeCacheDir
|
optimizedDexDirectory = codeCacheDir
|
||||||
)
|
)
|
||||||
} catch (ex: Exception) {
|
} catch (t: Throwable) {
|
||||||
return@setMethodCallHandler result.notImplemented()
|
return@setMethodCallHandler result.error(
|
||||||
} catch (err: Error) {
|
"PATCH_BUNDLE_ERROR",
|
||||||
return@setMethodCallHandler result.notImplemented()
|
"Failed to load patch bundle",
|
||||||
|
t.stackTraceToString()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONArray().apply {
|
JSONArray().apply {
|
||||||
@@ -130,13 +134,13 @@ class MainActivity : FlutterActivity() {
|
|||||||
put("description", it.description)
|
put("description", it.description)
|
||||||
put("excluded", !it.use)
|
put("excluded", !it.use)
|
||||||
put("compatiblePackages", JSONArray().apply {
|
put("compatiblePackages", JSONArray().apply {
|
||||||
it.compatiblePackages?.forEach { compatiblePackage ->
|
it.compatiblePackages?.forEach { (name, versions) ->
|
||||||
val compatiblePackageJson = JSONObject().apply {
|
val compatiblePackageJson = JSONObject().apply {
|
||||||
put("name", compatiblePackage.name)
|
put("name", name)
|
||||||
put(
|
put(
|
||||||
"versions",
|
"versions",
|
||||||
JSONArray().apply {
|
JSONArray().apply {
|
||||||
compatiblePackage.versions?.forEach { version ->
|
versions?.forEach { version ->
|
||||||
put(version)
|
put(version)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -172,7 +176,7 @@ class MainActivity : FlutterActivity() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} ?: put("values", null)
|
} ?: put("values", null)
|
||||||
put("valueType", option.valueType)
|
put("type", option.type)
|
||||||
}.let(::put)
|
}.let(::put)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -211,7 +215,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
result: MethodChannel.Result,
|
result: MethodChannel.Result,
|
||||||
inFilePath: String,
|
inFilePath: String,
|
||||||
outFilePath: String,
|
outFilePath: String,
|
||||||
integrationsPath: String,
|
|
||||||
selectedPatches: List<String>,
|
selectedPatches: List<String>,
|
||||||
options: Map<String, Map<String, Any>>,
|
options: Map<String, Map<String, Any>>,
|
||||||
tmpDirPath: String,
|
tmpDirPath: String,
|
||||||
@@ -223,7 +226,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
inFile.setWritable(true)
|
inFile.setWritable(true)
|
||||||
inFile.setReadable(true)
|
inFile.setReadable(true)
|
||||||
val outFile = File(outFilePath)
|
val outFile = File(outFilePath)
|
||||||
val integrations = File(integrationsPath)
|
|
||||||
val keyStoreFile = File(keyStoreFilePath)
|
val keyStoreFile = File(keyStoreFilePath)
|
||||||
val tmpDir = File(tmpDirPath)
|
val tmpDir = File(tmpDirPath)
|
||||||
|
|
||||||
@@ -281,7 +283,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
tmpDir,
|
tmpDir,
|
||||||
Aapt.binary(applicationContext).absolutePath,
|
Aapt.binary(applicationContext).absolutePath,
|
||||||
tmpDir.path,
|
tmpDir.path,
|
||||||
true // TODO: Add option to disable this
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -289,8 +290,8 @@ class MainActivity : FlutterActivity() {
|
|||||||
updateProgress(0.02, "Loading patches...", "Loading patches")
|
updateProgress(0.02, "Loading patches...", "Loading patches")
|
||||||
|
|
||||||
val patches = patches.filter { patch ->
|
val patches = patches.filter { patch ->
|
||||||
val isCompatible = patch.compatiblePackages?.any {
|
val isCompatible = patch.compatiblePackages?.any { (name, _) ->
|
||||||
it.name == patcher.context.packageMetadata.packageName
|
name == patcher.context.packageMetadata.packageName
|
||||||
} ?: false
|
} ?: false
|
||||||
|
|
||||||
val compatibleOrUniversal =
|
val compatibleOrUniversal =
|
||||||
@@ -307,10 +308,7 @@ class MainActivity : FlutterActivity() {
|
|||||||
updateProgress(0.05, "Executing...", "")
|
updateProgress(0.05, "Executing...", "")
|
||||||
|
|
||||||
val patcherResult = patcher.use {
|
val patcherResult = patcher.use {
|
||||||
patcher.apply {
|
it += patches
|
||||||
acceptIntegrations(setOf(integrations))
|
|
||||||
acceptPatches(patches)
|
|
||||||
}
|
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
// Update the progress bar every time a patch is executed from 0.15 to 0.7
|
// Update the progress bar every time a patch is executed from 0.15 to 0.7
|
||||||
@@ -318,7 +316,7 @@ class MainActivity : FlutterActivity() {
|
|||||||
val progressStep = 0.55 / totalPatchesCount
|
val progressStep = 0.55 / totalPatchesCount
|
||||||
var progress = 0.05
|
var progress = 0.05
|
||||||
|
|
||||||
patcher.apply(false).collect(FlowCollector { patchResult: PatchResult ->
|
patcher().collect(FlowCollector { patchResult: PatchResult ->
|
||||||
if (cancel(patcher::close)) return@FlowCollector
|
if (cancel(patcher::close)) return@FlowCollector
|
||||||
|
|
||||||
val msg = patchResult.exception?.let {
|
val msg = patchResult.exception?.let {
|
||||||
@@ -346,10 +344,11 @@ class MainActivity : FlutterActivity() {
|
|||||||
|
|
||||||
if (cancel(patcher::close)) return@Thread
|
if (cancel(patcher::close)) return@Thread
|
||||||
|
|
||||||
ApkUtils.sign(
|
ApkUtils.signApk(
|
||||||
inFile,
|
inFile,
|
||||||
outFile,
|
outFile,
|
||||||
ApkUtils.SigningOptions(
|
"ReVanced",
|
||||||
|
ApkUtils.KeyStoreDetails(
|
||||||
keyStoreFile,
|
keyStoreFile,
|
||||||
keystorePassword,
|
keystorePassword,
|
||||||
"alias",
|
"alias",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import com.android.build.api.dsl.CommonExtension
|
import com.android.build.api.dsl.CommonExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
@@ -17,11 +18,20 @@ allprojects {
|
|||||||
|
|
||||||
layout.buildDirectory = File("../build")
|
layout.buildDirectory = File("../build")
|
||||||
|
|
||||||
|
project(":screenshot_callback") {
|
||||||
|
tasks.withType<KotlinCompile>().configureEach {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
extensions.findByName("android")?.let {
|
extensions.findByName("android")?.let {
|
||||||
it as CommonExtension<*, *, *, *, *, *>
|
it as CommonExtension<*, *, *, *, *, *>
|
||||||
it.compileSdk = 34
|
if (it.compileSdk != null && it.compileSdk!! < 31)
|
||||||
|
it.compileSdk = 34
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,5 @@ android.useAndroidX=true
|
|||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
org.gradle.daemon=true
|
org.gradle.daemon=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
android.defaults.buildfeatures.buildconfig=true
|
|
||||||
android.nonTransitiveRClass=false
|
android.nonTransitiveRClass=false
|
||||||
android.nonFinalResIds=false
|
android.nonFinalResIds=false
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[versions]
|
[versions]
|
||||||
revanced-patcher = "19.3.1" # TODO: Update to non-dev
|
revanced-patcher = "21.0.0"
|
||||||
revanced-library = "2.2.1"
|
revanced-library = "3.0.2"
|
||||||
desugar_jdk_libs = "2.1.2"
|
desugar_jdk_libs = "2.1.3"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
|
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
id("com.android.application") version "8.7.0" apply false
|
id("com.android.application") version "8.7.2" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "2.0.20" apply false
|
id("org.jetbrains.kotlin.android") version "2.0.20" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,20 +158,18 @@
|
|||||||
"languageLabel": "Language",
|
"languageLabel": "Language",
|
||||||
"languageUpdated": "Language updated",
|
"languageUpdated": "Language updated",
|
||||||
"sourcesLabel": "Alternative sources",
|
"sourcesLabel": "Alternative sources",
|
||||||
"sourcesLabelHint": "Configure the alternative sources for ReVanced Patches and ReVanced Integrations",
|
"sourcesLabelHint": "Configure the alternative sources for ReVanced Patches",
|
||||||
"sourcesIntegrationsLabel": "Integrations source",
|
|
||||||
"useAlternativeSources": "Use alternative sources",
|
"useAlternativeSources": "Use alternative sources",
|
||||||
"useAlternativeSourcesHint": "Use alternative sources for ReVanced Patches and ReVanced Integrations instead of the API",
|
"useAlternativeSourcesHint": "Use alternative sources for ReVanced Patches instead of the API",
|
||||||
"sourcesResetDialogTitle": "Reset",
|
"sourcesResetDialogTitle": "Reset",
|
||||||
"sourcesResetDialogText": "Are you sure you want to reset your sources to their default values?",
|
"sourcesResetDialogText": "Are you sure you want to reset your sources to their default values?",
|
||||||
"apiURLResetDialogText": "Are you sure you want to reset your API URL to its default value?",
|
"apiURLResetDialogText": "Are you sure you want to reset your API URL to its default value?",
|
||||||
"sourcesUpdateNote": "Note: This will automatically download ReVanced Patches and ReVanced Integrations from the alternative sources.\n\nThis will connect you to the alternative source.",
|
"sourcesUpdateNote": "Note: This will automatically download ReVanced Patches from the alternative sources.\n\nThis will connect you to the alternative source.",
|
||||||
"apiURLLabel": "API URL",
|
"apiURLLabel": "API URL",
|
||||||
"apiURLHint": "Configure the API URL of ReVanced Manager",
|
"apiURLHint": "Configure the API URL of ReVanced Manager",
|
||||||
"selectApiURL": "API URL",
|
"selectApiURL": "API URL",
|
||||||
"orgPatchesLabel": "Patches organization",
|
"orgPatchesLabel": "Patches organization",
|
||||||
"sourcesPatchesLabel": "Patches source",
|
"sourcesPatchesLabel": "Patches source",
|
||||||
"orgIntegrationsLabel": "Integrations organization",
|
|
||||||
"contributorsLabel": "Contributors",
|
"contributorsLabel": "Contributors",
|
||||||
"contributorsHint": "A list of contributors of ReVanced",
|
"contributorsHint": "A list of contributors of ReVanced",
|
||||||
"logsLabel": "Share logs",
|
"logsLabel": "Share logs",
|
||||||
@@ -199,6 +197,12 @@
|
|||||||
"deleteTempDirLabel": "Delete temporary files",
|
"deleteTempDirLabel": "Delete temporary files",
|
||||||
"deleteTempDirHint": "Delete unused temporary files",
|
"deleteTempDirHint": "Delete unused temporary files",
|
||||||
"deletedTempDir": "Temporary files deleted",
|
"deletedTempDir": "Temporary files deleted",
|
||||||
|
"exportSettingsLabel": "Export settings",
|
||||||
|
"exportSettingsHint": "Export settings to a JSON file",
|
||||||
|
"exportedSettings": "Settings exported",
|
||||||
|
"importSettingsLabel": "Import settings",
|
||||||
|
"importSettingsHint": "Import settings from a JSON file",
|
||||||
|
"importedSettings": "Settings imported",
|
||||||
"exportPatchesLabel": "Export patch selection",
|
"exportPatchesLabel": "Export patch selection",
|
||||||
"exportPatchesHint": "Export patch selection to a JSON file",
|
"exportPatchesHint": "Export patch selection to a JSON file",
|
||||||
"exportedPatches": "Patch selection exported",
|
"exportedPatches": "Patch selection exported",
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ Learn how to configure ReVanced Manager.
|
|||||||
- 🔑 Keystore used to sign patched apps
|
- 🔑 Keystore used to sign patched apps
|
||||||
- 📄 Remembered selection of patches for each app
|
- 📄 Remembered selection of patches for each app
|
||||||
- ⚙️ Remembered patch options
|
- ⚙️ Remembered patch options
|
||||||
|
- 🛠️ Remembered settings
|
||||||
|
|
||||||
> ℹ️ Note
|
> ℹ️ Note
|
||||||
> These can be used to backup and restore or reset settings to default in case of issues.
|
> These can be used to backup and restore or reset settings to default in case of issues.
|
||||||
|
|||||||
@@ -62,11 +62,12 @@ class Option {
|
|||||||
required this.value,
|
required this.value,
|
||||||
required this.values,
|
required this.values,
|
||||||
required this.required,
|
required this.required,
|
||||||
required this.valueType,
|
required this.type,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Option.fromJson(Map<String, dynamic> json) {
|
factory Option.fromJson(Map<String, dynamic> json) {
|
||||||
_migrateV17ToV19(json);
|
_migrateV17ToV19(json);
|
||||||
|
_migrateV19ToV20(json);
|
||||||
|
|
||||||
return _$OptionFromJson(json);
|
return _$OptionFromJson(json);
|
||||||
}
|
}
|
||||||
@@ -83,13 +84,25 @@ class Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _migrateV19ToV20(Map<String, dynamic> json) {
|
||||||
|
if (json['valueType'] != null) {
|
||||||
|
final String type = json['valueType'];
|
||||||
|
|
||||||
|
json['type'] = type.endsWith('Array')
|
||||||
|
? 'kotlin.collections.List<kotlin.${type.replaceAll('Array', '')}>'
|
||||||
|
: 'kotlin.$type';
|
||||||
|
|
||||||
|
json['valueType'] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final String key;
|
final String key;
|
||||||
final String title;
|
final String title;
|
||||||
final String description;
|
final String description;
|
||||||
final dynamic value;
|
final dynamic value;
|
||||||
final Map<String, dynamic>? values;
|
final Map<String, dynamic>? values;
|
||||||
final bool required;
|
final bool required;
|
||||||
final String valueType;
|
final String type;
|
||||||
|
|
||||||
Map toJson() => _$OptionToJson(this);
|
Map toJson() => _$OptionToJson(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,11 +111,7 @@ class GithubAPI {
|
|||||||
);
|
);
|
||||||
if (asset != null) {
|
if (asset != null) {
|
||||||
final String downloadUrl = asset['browser_download_url'];
|
final String downloadUrl = asset['browser_download_url'];
|
||||||
if (extension == '.apk') {
|
_managerAPI.setPatchesDownloadURL(downloadUrl);
|
||||||
_managerAPI.setIntegrationsDownloadURL(downloadUrl);
|
|
||||||
} else {
|
|
||||||
_managerAPI.setPatchesDownloadURL(downloadUrl);
|
|
||||||
}
|
|
||||||
return await _downloadManager.getSingleFile(
|
return await _downloadManager.getSingleFile(
|
||||||
downloadUrl,
|
downloadUrl,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class ManagerAPI {
|
|||||||
final String patcherRepo = 'revanced-patcher';
|
final String patcherRepo = 'revanced-patcher';
|
||||||
final String cliRepo = 'revanced-cli';
|
final String cliRepo = 'revanced-cli';
|
||||||
late SharedPreferences _prefs;
|
late SharedPreferences _prefs;
|
||||||
|
Map<String, List>? contributors;
|
||||||
List<Patch> patches = [];
|
List<Patch> patches = [];
|
||||||
List<Option> options = [];
|
List<Option> options = [];
|
||||||
Patch? selectedPatch;
|
Patch? selectedPatch;
|
||||||
@@ -44,15 +45,13 @@ class ManagerAPI {
|
|||||||
String keystoreFile =
|
String keystoreFile =
|
||||||
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore';
|
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore';
|
||||||
String defaultKeystorePassword = 's3cur3p@ssw0rd';
|
String defaultKeystorePassword = 's3cur3p@ssw0rd';
|
||||||
String defaultApiUrl = 'https://api.revanced.app/';
|
String defaultApiUrl = 'https://api.revanced.app/v4';
|
||||||
String defaultRepoUrl = 'https://api.github.com';
|
String defaultRepoUrl = 'https://api.github.com';
|
||||||
String defaultPatcherRepo = 'revanced/revanced-patcher';
|
String defaultPatcherRepo = 'revanced/revanced-patcher';
|
||||||
String defaultPatchesRepo = 'revanced/revanced-patches';
|
String defaultPatchesRepo = 'revanced/revanced-patches';
|
||||||
String defaultIntegrationsRepo = 'revanced/revanced-integrations';
|
|
||||||
String defaultCliRepo = 'revanced/revanced-cli';
|
String defaultCliRepo = 'revanced/revanced-cli';
|
||||||
String defaultManagerRepo = 'revanced/revanced-manager';
|
String defaultManagerRepo = 'revanced/revanced-manager';
|
||||||
String? patchesVersion = '';
|
String? patchesVersion = '';
|
||||||
String? integrationsVersion = '';
|
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
_prefs = await SharedPreferences.getInstance();
|
_prefs = await SharedPreferences.getInstance();
|
||||||
@@ -68,14 +67,23 @@ class ManagerAPI {
|
|||||||
releaseBuild = !(await getCurrentManagerVersion()).contains('-dev');
|
releaseBuild = !(await getCurrentManagerVersion()).contains('-dev');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate to new API URL if not done yet as the old one is sunset.
|
final hasMigratedToNewMigrationSystem = _prefs.getBool('migratedToNewApiPrefSystem') ?? false;
|
||||||
final bool hasMigratedToNewApi =
|
if (!hasMigratedToNewMigrationSystem) {
|
||||||
_prefs.getBool('migratedToNewApiUrl') ?? false;
|
final apiUrl = getApiUrl().toLowerCase();
|
||||||
if (!hasMigratedToNewApi) {
|
|
||||||
final String apiUrl = getApiUrl().toLowerCase();
|
final isReleases = apiUrl.contains('releases.revanced.app');
|
||||||
if (apiUrl.contains('releases.revanced.app')) {
|
final isV2 = apiUrl.contains('api.revanced.app/v2');
|
||||||
await setApiUrl(''); // Reset to default.
|
final isV3 = apiUrl.contains('api.revanced.app/v3');
|
||||||
_prefs.setBool('migratedToNewApiUrl', true);
|
|
||||||
|
if (isReleases || isV2 || isV3) {
|
||||||
|
await resetApiUrl();
|
||||||
|
// At this point, the preference is removed.
|
||||||
|
// Now, no more migration is needed because:
|
||||||
|
// If the user touches the API URL,
|
||||||
|
// it will be remembered forever as intended.
|
||||||
|
// On the other hand, if the user resets it or sets it to the default,
|
||||||
|
// the URL will be updated whenever the app is updated.
|
||||||
|
_prefs.setBool('migratedToNewApiPrefSystem', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,10 +91,8 @@ class ManagerAPI {
|
|||||||
_prefs.getBool('migratedToAlternativeSource') ?? false;
|
_prefs.getBool('migratedToAlternativeSource') ?? false;
|
||||||
if (!hasMigratedToAlternativeSource) {
|
if (!hasMigratedToAlternativeSource) {
|
||||||
final String patchesRepo = getPatchesRepo();
|
final String patchesRepo = getPatchesRepo();
|
||||||
final String integrationsRepo = getIntegrationsRepo();
|
|
||||||
final bool usingAlternativeSources =
|
final bool usingAlternativeSources =
|
||||||
patchesRepo.toLowerCase() != defaultPatchesRepo ||
|
patchesRepo.toLowerCase() != defaultPatchesRepo;
|
||||||
integrationsRepo.toLowerCase() != defaultIntegrationsRepo;
|
|
||||||
_prefs.setBool('useAlternativeSources', usingAlternativeSources);
|
_prefs.setBool('useAlternativeSources', usingAlternativeSources);
|
||||||
_prefs.setBool('migratedToAlternativeSource', true);
|
_prefs.setBool('migratedToAlternativeSource', true);
|
||||||
}
|
}
|
||||||
@@ -101,12 +107,25 @@ class ManagerAPI {
|
|||||||
return _prefs.getString('apiUrl') ?? defaultApiUrl;
|
return _prefs.getString('apiUrl') ?? defaultApiUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setApiUrl(String url) async {
|
Future<void> resetApiUrl() async {
|
||||||
if (url.isEmpty || url == ' ') {
|
await _prefs.remove('apiUrl');
|
||||||
url = defaultApiUrl;
|
|
||||||
}
|
|
||||||
await _revancedAPI.clearAllCache();
|
await _revancedAPI.clearAllCache();
|
||||||
|
_toast.showBottom(t.settingsView.restartAppForChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setApiUrl(String url) async {
|
||||||
|
url = url.toLowerCase();
|
||||||
|
|
||||||
|
if (url == defaultApiUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!url.startsWith('http')) {
|
||||||
|
url = 'https://$url';
|
||||||
|
}
|
||||||
|
|
||||||
await _prefs.setString('apiUrl', url);
|
await _prefs.setString('apiUrl', url);
|
||||||
|
await _revancedAPI.clearAllCache();
|
||||||
_toast.showBottom(t.settingsView.restartAppForChanges);
|
_toast.showBottom(t.settingsView.restartAppForChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,14 +219,6 @@ 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') ?? [];
|
||||||
@@ -256,17 +267,6 @@ class ManagerAPI {
|
|||||||
_prefs.remove('patchOption-$packageName-$patchName-$key');
|
_prefs.remove('patchOption-$packageName-$patchName-$key');
|
||||||
}
|
}
|
||||||
|
|
||||||
String getIntegrationsRepo() {
|
|
||||||
return _prefs.getString('integrationsRepo') ?? defaultIntegrationsRepo;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setIntegrationsRepo(String value) async {
|
|
||||||
if (value.isEmpty || value.startsWith('/') || value.endsWith('/')) {
|
|
||||||
value = defaultIntegrationsRepo;
|
|
||||||
}
|
|
||||||
await _prefs.setString('integrationsRepo', value);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool getUseDynamicTheme() {
|
bool getUseDynamicTheme() {
|
||||||
return _prefs.getBool('useDynamicTheme') ?? false;
|
return _prefs.getBool('useDynamicTheme') ?? false;
|
||||||
}
|
}
|
||||||
@@ -427,7 +427,7 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, List<dynamic>>> getContributors() async {
|
Future<Map<String, List<dynamic>>> getContributors() async {
|
||||||
return await _revancedAPI.getContributors();
|
return contributors ??= await _revancedAPI.getContributors();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Patch>> getPatches() async {
|
Future<List<Patch>> getPatches() async {
|
||||||
@@ -459,33 +459,16 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> downloadPatches() async {
|
Future<File?> downloadPatches() async {
|
||||||
|
if (!isUsingAlternativeSources()) {
|
||||||
|
return await _revancedAPI.getLatestReleaseFile('patches');
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final String repoName = getPatchesRepo();
|
final String repoName = getPatchesRepo();
|
||||||
final String currentVersion = await getCurrentPatchesVersion();
|
final String currentVersion = await getCurrentPatchesVersion();
|
||||||
final String url = getPatchesDownloadURL();
|
final String url = getPatchesDownloadURL();
|
||||||
return await _githubAPI.getReleaseFile(
|
return await _githubAPI.getReleaseFile(
|
||||||
'.jar',
|
'.rvp',
|
||||||
repoName,
|
|
||||||
currentVersion,
|
|
||||||
url,
|
|
||||||
);
|
|
||||||
} on Exception catch (e) {
|
|
||||||
if (kDebugMode) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<File?> downloadIntegrations() async {
|
|
||||||
try {
|
|
||||||
final String repoName = !isUsingAlternativeSources()
|
|
||||||
? defaultIntegrationsRepo
|
|
||||||
: getIntegrationsRepo();
|
|
||||||
final String currentVersion = await getCurrentIntegrationsVersion();
|
|
||||||
final String url = getIntegrationsDownloadURL();
|
|
||||||
return await _githubAPI.getReleaseFile(
|
|
||||||
'.apk',
|
|
||||||
repoName,
|
repoName,
|
||||||
currentVersion,
|
currentVersion,
|
||||||
url,
|
url,
|
||||||
@@ -499,18 +482,12 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> downloadManager() async {
|
Future<File?> downloadManager() async {
|
||||||
return await _revancedAPI.getLatestReleaseFile(
|
return await _revancedAPI.getLatestReleaseFile('manager');
|
||||||
'.apk',
|
|
||||||
defaultManagerRepo,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getLatestPatchesReleaseTime() async {
|
Future<String?> getLatestPatchesReleaseTime() async {
|
||||||
if (!isUsingAlternativeSources()) {
|
if (!isUsingAlternativeSources()) {
|
||||||
return await _revancedAPI.getLatestReleaseTime(
|
return await _revancedAPI.getLatestReleaseTime('patches');
|
||||||
'.json',
|
|
||||||
defaultPatchesRepo,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
final release = await _githubAPI.getLatestRelease(getPatchesRepo());
|
final release = await _githubAPI.getLatestRelease(getPatchesRepo());
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
@@ -525,39 +502,20 @@ class ManagerAPI {
|
|||||||
|
|
||||||
Future<String?> getLatestManagerReleaseTime() async {
|
Future<String?> getLatestManagerReleaseTime() async {
|
||||||
return await _revancedAPI.getLatestReleaseTime(
|
return await _revancedAPI.getLatestReleaseTime(
|
||||||
'.apk',
|
'manager',
|
||||||
defaultManagerRepo,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getLatestManagerVersion() async {
|
Future<String?> getLatestManagerVersion() async {
|
||||||
return await _revancedAPI.getLatestReleaseVersion(
|
return await _revancedAPI.getLatestReleaseVersion(
|
||||||
'.apk',
|
'manager',
|
||||||
defaultManagerRepo,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getLatestIntegrationsVersion() async {
|
|
||||||
if (!isUsingAlternativeSources()) {
|
|
||||||
return await _revancedAPI.getLatestReleaseVersion(
|
|
||||||
'.apk',
|
|
||||||
defaultIntegrationsRepo,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
final release = await _githubAPI.getLatestRelease(getIntegrationsRepo());
|
|
||||||
if (release != null) {
|
|
||||||
return release['tag_name'];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> getLatestPatchesVersion() async {
|
Future<String?> getLatestPatchesVersion() async {
|
||||||
if (!isUsingAlternativeSources()) {
|
if (!isUsingAlternativeSources()) {
|
||||||
return await _revancedAPI.getLatestReleaseVersion(
|
return await _revancedAPI.getLatestReleaseVersion(
|
||||||
'.json',
|
'patches',
|
||||||
defaultPatchesRepo,
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final release = await _githubAPI.getLatestRelease(getPatchesRepo());
|
final release = await _githubAPI.getLatestRelease(getPatchesRepo());
|
||||||
@@ -620,25 +578,6 @@ class ManagerAPI {
|
|||||||
await downloadPatches();
|
await downloadPatches();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getCurrentIntegrationsVersion() async {
|
|
||||||
integrationsVersion = _prefs.getString('integrationsVersion') ?? '0.0.0';
|
|
||||||
if (integrationsVersion == '0.0.0' || isPatchesAutoUpdate()) {
|
|
||||||
final String newIntegrationsVersion =
|
|
||||||
await getLatestIntegrationsVersion() ?? '0.0.0';
|
|
||||||
if (integrationsVersion != newIntegrationsVersion &&
|
|
||||||
newIntegrationsVersion != '0.0.0') {
|
|
||||||
await setCurrentIntegrationsVersion(newIntegrationsVersion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return integrationsVersion!;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setCurrentIntegrationsVersion(String version) async {
|
|
||||||
await _prefs.setString('integrationsVersion', version);
|
|
||||||
await setIntegrationsDownloadURL('');
|
|
||||||
await downloadIntegrations();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<PatchedApplication>> getAppsToRemove(
|
Future<List<PatchedApplication>> getAppsToRemove(
|
||||||
List<PatchedApplication> patchedApps,
|
List<PatchedApplication> patchedApps,
|
||||||
) async {
|
) async {
|
||||||
@@ -841,6 +780,36 @@ class ManagerAPI {
|
|||||||
return jsonDecode(string);
|
return jsonDecode(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String exportSettings() {
|
||||||
|
final Map<String, dynamic> settings = _prefs
|
||||||
|
.getKeys()
|
||||||
|
.fold<Map<String, dynamic>>({}, (Map<String, dynamic> map, String key) {
|
||||||
|
map[key] = _prefs.get(key);
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
return jsonEncode(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> importSettings(String settings) async {
|
||||||
|
final Map<String, dynamic> settingsMap = jsonDecode(settings);
|
||||||
|
settingsMap.forEach((key, value) {
|
||||||
|
if (value is bool) {
|
||||||
|
_prefs.setBool(key, value);
|
||||||
|
} else if (value is int) {
|
||||||
|
_prefs.setInt(key, value);
|
||||||
|
} else if (value is double) {
|
||||||
|
_prefs.setDouble(key, value);
|
||||||
|
} else if (value is String) {
|
||||||
|
_prefs.setString(key, value);
|
||||||
|
} else if (value is List<dynamic>) {
|
||||||
|
_prefs.setStringList(
|
||||||
|
key,
|
||||||
|
value.map((a) => json.encode(a.toJson())).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void resetAllOptions() {
|
void resetAllOptions() {
|
||||||
_prefs.getKeys().where((key) => key.startsWith('patchOption-')).forEach(
|
_prefs.getKeys().where((key) => key.startsWith('patchOption-')).forEach(
|
||||||
(key) {
|
(key) {
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ import 'package:share_plus/share_plus.dart';
|
|||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class PatcherAPI {
|
class PatcherAPI {
|
||||||
static const patcherChannel =
|
static const patcherChannel = MethodChannel('app.revanced.manager.flutter/patcher');
|
||||||
MethodChannel('app.revanced.manager.flutter/patcher');
|
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
final RootAPI _rootAPI = RootAPI();
|
final RootAPI _rootAPI = RootAPI();
|
||||||
late Directory _dataDir;
|
late Directory _dataDir;
|
||||||
@@ -27,13 +26,12 @@ class PatcherAPI {
|
|||||||
late File _keyStoreFile;
|
late File _keyStoreFile;
|
||||||
List<Patch> _patches = [];
|
List<Patch> _patches = [];
|
||||||
List<Patch> _universalPatches = [];
|
List<Patch> _universalPatches = [];
|
||||||
List<String> _compatiblePackages = [];
|
Set<String> _compatiblePackages = {};
|
||||||
Map filteredPatches = <String, List<Patch>>{};
|
Map filteredPatches = <String, List<Patch>>{};
|
||||||
File? outFile;
|
File? outFile;
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
await loadPatches();
|
await loadPatches();
|
||||||
await _managerAPI.downloadIntegrations();
|
|
||||||
final Directory appCache = await getApplicationSupportDirectory();
|
final Directory appCache = await getApplicationSupportDirectory();
|
||||||
_dataDir = await getExternalStorageDirectory() ?? appCache;
|
_dataDir = await getExternalStorageDirectory() ?? appCache;
|
||||||
_tmpDir = Directory('${appCache.path}/patcher');
|
_tmpDir = Directory('${appCache.path}/patcher');
|
||||||
@@ -47,8 +45,8 @@ class PatcherAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> getCompatiblePackages() {
|
Set<String> getCompatiblePackages() {
|
||||||
final List<String> compatiblePackages = [];
|
final Set<String> compatiblePackages = {};
|
||||||
for (final Patch patch in _patches) {
|
for (final Patch patch in _patches) {
|
||||||
for (final Package package in patch.compatiblePackages) {
|
for (final Package package in patch.compatiblePackages) {
|
||||||
if (!compatiblePackages.contains(package.name)) {
|
if (!compatiblePackages.contains(package.name)) {
|
||||||
@@ -67,16 +65,16 @@ class PatcherAPI {
|
|||||||
try {
|
try {
|
||||||
if (_patches.isEmpty) {
|
if (_patches.isEmpty) {
|
||||||
_patches = await _managerAPI.getPatches();
|
_patches = await _managerAPI.getPatches();
|
||||||
|
_universalPatches = getUniversalPatches();
|
||||||
|
_compatiblePackages = getCompatiblePackages();
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(e);
|
print(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
_patches = List.empty();
|
_patches = List.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
_compatiblePackages = getCompatiblePackages();
|
|
||||||
_universalPatches = getUniversalPatches();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
|
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
|
||||||
@@ -85,6 +83,7 @@ class PatcherAPI {
|
|||||||
final List<ApplicationWithIcon> filteredApps = [];
|
final List<ApplicationWithIcon> filteredApps = [];
|
||||||
final bool allAppsIncluded =
|
final bool allAppsIncluded =
|
||||||
_universalPatches.isNotEmpty && showUniversalPatches;
|
_universalPatches.isNotEmpty && showUniversalPatches;
|
||||||
|
|
||||||
if (allAppsIncluded) {
|
if (allAppsIncluded) {
|
||||||
final appList = await DeviceApps.getInstalledApplications(
|
final appList = await DeviceApps.getInstalledApplications(
|
||||||
includeAppIcons: true,
|
includeAppIcons: true,
|
||||||
@@ -95,6 +94,7 @@ class PatcherAPI {
|
|||||||
filteredApps.add(app as ApplicationWithIcon);
|
filteredApps.add(app as ApplicationWithIcon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final packageName in _compatiblePackages) {
|
for (final packageName in _compatiblePackages) {
|
||||||
try {
|
try {
|
||||||
if (!filteredApps.any((app) => app.packageName == packageName)) {
|
if (!filteredApps.any((app) => app.packageName == packageName)) {
|
||||||
@@ -153,7 +153,6 @@ class PatcherAPI {
|
|||||||
List<Patch> selectedPatches,
|
List<Patch> selectedPatches,
|
||||||
bool isFromStorage,
|
bool isFromStorage,
|
||||||
) async {
|
) async {
|
||||||
final File? integrationsFile = await _managerAPI.downloadIntegrations();
|
|
||||||
final Map<String, Map<String, dynamic>> options = {};
|
final Map<String, Map<String, dynamic>> options = {};
|
||||||
for (final patch in selectedPatches) {
|
for (final patch in selectedPatches) {
|
||||||
if (patch.options.isNotEmpty) {
|
if (patch.options.isNotEmpty) {
|
||||||
@@ -169,44 +168,41 @@ class PatcherAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (integrationsFile != null) {
|
_dataDir.createSync();
|
||||||
_dataDir.createSync();
|
_tmpDir.createSync();
|
||||||
_tmpDir.createSync();
|
final Directory workDir = await _tmpDir.createTemp('tmp-');
|
||||||
final Directory workDir = await _tmpDir.createTemp('tmp-');
|
|
||||||
|
|
||||||
final File inApkFile = File('${workDir.path}/in.apk');
|
final File inApkFile = File('${workDir.path}/in.apk');
|
||||||
await File(apkFilePath).copy(inApkFile.path);
|
await File(apkFilePath).copy(inApkFile.path);
|
||||||
|
|
||||||
if (isFromStorage) {
|
if (isFromStorage) {
|
||||||
// The selected apk was copied to cacheDir by the file picker, so it's not needed anymore.
|
// The selected apk was copied to cacheDir by the file picker, so it's not needed anymore.
|
||||||
// rename() can't be used here, as Android system also counts the size of files moved out from cacheDir
|
// rename() can't be used here, as Android system also counts the size of files moved out from cacheDir
|
||||||
// as part of the app's cache size.
|
// as part of the app's cache size.
|
||||||
File(apkFilePath).delete();
|
File(apkFilePath).delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
outFile = File('${workDir.path}/out.apk');
|
outFile = File('${workDir.path}/out.apk');
|
||||||
|
|
||||||
final Directory tmpDir =
|
final Directory tmpDir =
|
||||||
Directory('${workDir.path}/revanced-temporary-files');
|
Directory('${workDir.path}/revanced-temporary-files');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await patcherChannel.invokeMethod(
|
await patcherChannel.invokeMethod(
|
||||||
'runPatcher',
|
'runPatcher',
|
||||||
{
|
{
|
||||||
'inFilePath': inApkFile.path,
|
'inFilePath': inApkFile.path,
|
||||||
'outFilePath': outFile!.path,
|
'outFilePath': outFile!.path,
|
||||||
'integrationsPath': integrationsFile.path,
|
'selectedPatches': selectedPatches.map((p) => p.name).toList(),
|
||||||
'selectedPatches': selectedPatches.map((p) => p.name).toList(),
|
'options': options,
|
||||||
'options': options,
|
'tmpDirPath': tmpDir.path,
|
||||||
'tmpDirPath': tmpDir.path,
|
'keyStoreFilePath': _keyStoreFile.path,
|
||||||
'keyStoreFilePath': _keyStoreFile.path,
|
'keystorePassword': _managerAPI.getKeystorePassword(),
|
||||||
'keystorePassword': _managerAPI.getKeystorePassword(),
|
},
|
||||||
},
|
);
|
||||||
);
|
} on Exception catch (e) {
|
||||||
} on Exception catch (e) {
|
if (kDebugMode) {
|
||||||
if (kDebugMode) {
|
print(e);
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.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';
|
||||||
@@ -31,7 +29,7 @@ class RevancedAPI {
|
|||||||
final Map<String, List<dynamic>> contributors = {};
|
final Map<String, List<dynamic>> contributors = {};
|
||||||
try {
|
try {
|
||||||
final response = await _dio.get('/contributors');
|
final response = await _dio.get('/contributors');
|
||||||
final List<dynamic> repositories = response.data['repositories'];
|
final List<dynamic> repositories = response.data;
|
||||||
for (final Map<String, dynamic> repo in repositories) {
|
for (final Map<String, dynamic> repo in repositories) {
|
||||||
final String name = repo['name'];
|
final String name = repo['name'];
|
||||||
contributors[name] = repo['contributors'];
|
contributors[name] = repo['contributors'];
|
||||||
@@ -46,21 +44,15 @@ class RevancedAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>?> _getLatestRelease(
|
Future<Map<String, dynamic>?> _getLatestRelease(
|
||||||
String extension,
|
String toolName,
|
||||||
String repoName,
|
|
||||||
) {
|
) {
|
||||||
if (!locator<ManagerAPI>().getDownloadConsent()) {
|
if (!locator<ManagerAPI>().getDownloadConsent()) {
|
||||||
return Future(() => null);
|
return Future(() => null);
|
||||||
}
|
}
|
||||||
return getToolsLock.synchronized(() async {
|
return getToolsLock.synchronized(() async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.get('/tools');
|
final response = await _dio.get('/$toolName');
|
||||||
final List<dynamic> tools = response.data['tools'];
|
return response.data;
|
||||||
return tools.firstWhereOrNull(
|
|
||||||
(t) =>
|
|
||||||
(t['repository'] as String) == repoName &&
|
|
||||||
(t['name'] as String).endsWith(extension),
|
|
||||||
);
|
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(e);
|
print(e);
|
||||||
@@ -71,13 +63,11 @@ class RevancedAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getLatestReleaseVersion(
|
Future<String?> getLatestReleaseVersion(
|
||||||
String extension,
|
String toolName,
|
||||||
String repoName,
|
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final Map<String, dynamic>? release = await _getLatestRelease(
|
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||||
extension,
|
toolName,
|
||||||
repoName,
|
|
||||||
);
|
);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
return release['version'];
|
return release['version'];
|
||||||
@@ -92,16 +82,14 @@ class RevancedAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> getLatestReleaseFile(
|
Future<File?> getLatestReleaseFile(
|
||||||
String extension,
|
String toolName,
|
||||||
String repoName,
|
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final Map<String, dynamic>? release = await _getLatestRelease(
|
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||||
extension,
|
toolName,
|
||||||
repoName,
|
|
||||||
);
|
);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
final String url = release['browser_download_url'];
|
final String url = release['download_url'];
|
||||||
return await _downloadManager.getSingleFile(url);
|
return await _downloadManager.getSingleFile(url);
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
@@ -129,13 +117,10 @@ class RevancedAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> downloadManager() async {
|
Future<File?> downloadManager() async {
|
||||||
final Map<String, dynamic>? release = await _getLatestRelease(
|
final Map<String, dynamic>? release = await _getLatestRelease('manager');
|
||||||
'.apk',
|
|
||||||
'revanced/revanced-manager',
|
|
||||||
);
|
|
||||||
File? outputFile;
|
File? outputFile;
|
||||||
await for (final result in _downloadManager.getFileStream(
|
await for (final result in _downloadManager.getFileStream(
|
||||||
release!['browser_download_url'] as String,
|
release!['download_url'] as String,
|
||||||
)) {
|
)) {
|
||||||
if (result is DownloadProgress) {
|
if (result is DownloadProgress) {
|
||||||
final totalSize = result.totalSize ?? 10000000;
|
final totalSize = result.totalSize ?? 10000000;
|
||||||
@@ -152,17 +137,15 @@ class RevancedAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getLatestReleaseTime(
|
Future<String?> getLatestReleaseTime(
|
||||||
String extension,
|
String toolName,
|
||||||
String repoName,
|
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final Map<String, dynamic>? release = await _getLatestRelease(
|
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||||
extension,
|
toolName,
|
||||||
repoName,
|
|
||||||
);
|
);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
final DateTime timestamp =
|
final DateTime timestamp =
|
||||||
DateTime.parse(release['timestamp'] as String);
|
DateTime.parse(release['created_at'] as String);
|
||||||
return format(timestamp, locale: 'en_short');
|
return format(timestamp, locale: 'en_short');
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
|
|||||||
@@ -30,30 +30,13 @@ class ContributorsView extends StatelessWidget {
|
|||||||
sliver: SliverList(
|
sliver: SliverList(
|
||||||
delegate: SliverChildListDelegate.fixed(
|
delegate: SliverChildListDelegate.fixed(
|
||||||
<Widget>[
|
<Widget>[
|
||||||
ContributorsCard(
|
for (final String tool in model.contributors.keys) ...[
|
||||||
title: 'ReVanced Patcher',
|
ContributorsCard(
|
||||||
contributors: model.patcherContributors,
|
title: tool,
|
||||||
),
|
contributors: model.contributors[tool]!,
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
ContributorsCard(
|
const SizedBox(height: 20),
|
||||||
title: 'ReVanced Patches',
|
],
|
||||||
contributors: model.patchesContributors,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
ContributorsCard(
|
|
||||||
title: 'ReVanced Integrations',
|
|
||||||
contributors: model.integrationsContributors,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
ContributorsCard(
|
|
||||||
title: 'ReVanced CLI',
|
|
||||||
contributors: model.cliContributors,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
ContributorsCard(
|
|
||||||
title: 'ReVanced Manager',
|
|
||||||
contributors: model.managerContributors,
|
|
||||||
),
|
|
||||||
SizedBox(height: MediaQuery.viewPaddingOf(context).bottom),
|
SizedBox(height: MediaQuery.viewPaddingOf(context).bottom),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,21 +4,10 @@ import 'package:stacked/stacked.dart';
|
|||||||
|
|
||||||
class ContributorsViewModel extends BaseViewModel {
|
class ContributorsViewModel extends BaseViewModel {
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
List<dynamic> patcherContributors = [];
|
Map<String, List<dynamic>> contributors = {};
|
||||||
List<dynamic> patchesContributors = [];
|
|
||||||
List<dynamic> integrationsContributors = [];
|
|
||||||
List<dynamic> cliContributors = [];
|
|
||||||
List<dynamic> managerContributors = [];
|
|
||||||
|
|
||||||
Future<void> getContributors() async {
|
Future<void> getContributors() async {
|
||||||
final Map<String, List<dynamic>> contributors =
|
contributors = await _managerAPI.getContributors();
|
||||||
await _managerAPI.getContributors();
|
|
||||||
patcherContributors = contributors[_managerAPI.defaultPatcherRepo] ?? [];
|
|
||||||
patchesContributors = contributors[_managerAPI.defaultPatchesRepo] ?? [];
|
|
||||||
integrationsContributors =
|
|
||||||
contributors[_managerAPI.defaultIntegrationsRepo] ?? [];
|
|
||||||
cliContributors = contributors[_managerAPI.defaultCliRepo] ?? [];
|
|
||||||
managerContributors = contributors[_managerAPI.defaultManagerRepo] ?? [];
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,11 +311,8 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
_toast.showBottom(t.homeView.downloadingMessage);
|
_toast.showBottom(t.homeView.downloadingMessage);
|
||||||
final String patchesVersion =
|
final String patchesVersion =
|
||||||
await _managerAPI.getLatestPatchesVersion() ?? '0.0.0';
|
await _managerAPI.getLatestPatchesVersion() ?? '0.0.0';
|
||||||
final String integrationsVersion =
|
if (patchesVersion != '0.0.0') {
|
||||||
await _managerAPI.getLatestIntegrationsVersion() ?? '0.0.0';
|
|
||||||
if (patchesVersion != '0.0.0' && integrationsVersion != '0.0.0') {
|
|
||||||
await _managerAPI.setCurrentPatchesVersion(patchesVersion);
|
await _managerAPI.setCurrentPatchesVersion(patchesVersion);
|
||||||
await _managerAPI.setCurrentIntegrationsVersion(integrationsVersion);
|
|
||||||
_toast.showBottom(t.homeView.downloadedMessage);
|
_toast.showBottom(t.homeView.downloadedMessage);
|
||||||
forceRefresh(context);
|
forceRefresh(context);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -330,7 +330,6 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
'Version compatibility check: ${_managerAPI.isVersionCompatibilityCheckEnabled()}',
|
'Version compatibility check: ${_managerAPI.isVersionCompatibilityCheckEnabled()}',
|
||||||
'Show universal patches: ${_managerAPI.areUniversalPatchesEnabled()}',
|
'Show universal patches: ${_managerAPI.areUniversalPatchesEnabled()}',
|
||||||
'Patches source: ${_managerAPI.getPatchesRepo()}',
|
'Patches source: ${_managerAPI.getPatchesRepo()}',
|
||||||
'Integration source: ${_managerAPI.getIntegrationsRepo()}', //
|
|
||||||
|
|
||||||
'\n- Logs',
|
'\n- Logs',
|
||||||
logsTrimmed.join('\n'),
|
logsTrimmed.join('\n'),
|
||||||
|
|||||||
@@ -44,20 +44,20 @@ class PatchOptionsView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
for (final Option option in model.modifiedOptions)
|
for (final Option option in model.modifiedOptions)
|
||||||
if (option.valueType == 'String' ||
|
if (option.type == 'kotlin.String' ||
|
||||||
option.valueType == 'Int')
|
option.type == 'kotlin.Int')
|
||||||
IntAndStringPatchOption(
|
IntAndStringPatchOption(
|
||||||
patchOption: option,
|
patchOption: option,
|
||||||
model: model,
|
model: model,
|
||||||
)
|
)
|
||||||
else if (option.valueType == 'Boolean')
|
else if (option.type == 'kotlin.Boolean')
|
||||||
BooleanPatchOption(
|
BooleanPatchOption(
|
||||||
patchOption: option,
|
patchOption: option,
|
||||||
model: model,
|
model: model,
|
||||||
)
|
)
|
||||||
else if (option.valueType == 'StringArray' ||
|
else if (option.type == 'kotlin.collections.List<kotlin.String>' ||
|
||||||
option.valueType == 'IntArray' ||
|
option.type == 'kotlin.collections.List<kotlin.Int>' ||
|
||||||
option.valueType == 'LongArray')
|
option.type == 'kotlin.collections.List<kotlin.Long>')
|
||||||
IntStringLongListPatchOption(
|
IntStringLongListPatchOption(
|
||||||
patchOption: option,
|
patchOption: option,
|
||||||
model: model,
|
model: model,
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class PatchOptionsViewModel extends BaseViewModel {
|
|||||||
title: option.title,
|
title: option.title,
|
||||||
description: option.description,
|
description: option.description,
|
||||||
values: option.values,
|
values: option.values,
|
||||||
valueType: option.valueType,
|
type: option.type,
|
||||||
value: value,
|
value: value,
|
||||||
required: option.required,
|
required: option.required,
|
||||||
key: option.key,
|
key: option.key,
|
||||||
@@ -90,7 +90,7 @@ class PatchOptionsViewModel extends BaseViewModel {
|
|||||||
title: option.title,
|
title: option.title,
|
||||||
description: option.description,
|
description: option.description,
|
||||||
values: option.values,
|
values: option.values,
|
||||||
valueType: option.valueType,
|
type: option.type,
|
||||||
value: option.value is List ? option.value.toList() : option.value,
|
value: option.value is List ? option.value.toList() : option.value,
|
||||||
required: option.required,
|
required: option.required,
|
||||||
key: option.key,
|
key: option.key,
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ class SManageApiUrl extends BaseViewModel {
|
|||||||
final TextEditingController _apiUrlController = TextEditingController();
|
final TextEditingController _apiUrlController = TextEditingController();
|
||||||
|
|
||||||
Future<void> showApiUrlDialog(BuildContext context) async {
|
Future<void> showApiUrlDialog(BuildContext context) async {
|
||||||
final String apiUrl = _managerAPI.getApiUrl();
|
final apiUrl = _managerAPI.getApiUrl();
|
||||||
_apiUrlController.text = apiUrl.replaceAll('https://', '');
|
|
||||||
|
_apiUrlController.text = apiUrl;
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
@@ -60,11 +61,7 @@ class SManageApiUrl extends BaseViewModel {
|
|||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
String apiUrl = _apiUrlController.text;
|
_managerAPI.setApiUrl(_apiUrlController.text);
|
||||||
if (!apiUrl.startsWith('https')) {
|
|
||||||
apiUrl = 'https://$apiUrl';
|
|
||||||
}
|
|
||||||
_managerAPI.setApiUrl(apiUrl);
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: Text(t.okButton),
|
child: Text(t.okButton),
|
||||||
@@ -87,7 +84,7 @@ class SManageApiUrl extends BaseViewModel {
|
|||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_managerAPI.setApiUrl('');
|
_managerAPI.resetApiUrl();
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
..pop()
|
..pop()
|
||||||
..pop();
|
..pop();
|
||||||
|
|||||||
@@ -14,16 +14,11 @@ class SManageSources extends BaseViewModel {
|
|||||||
|
|
||||||
final TextEditingController _orgPatSourceController = TextEditingController();
|
final TextEditingController _orgPatSourceController = TextEditingController();
|
||||||
final TextEditingController _patSourceController = TextEditingController();
|
final TextEditingController _patSourceController = TextEditingController();
|
||||||
final TextEditingController _orgIntSourceController = TextEditingController();
|
|
||||||
final TextEditingController _intSourceController = TextEditingController();
|
|
||||||
|
|
||||||
Future<void> showSourcesDialog(BuildContext context) async {
|
Future<void> showSourcesDialog(BuildContext context) async {
|
||||||
final String patchesRepo = _managerAPI.getPatchesRepo();
|
final String patchesRepo = _managerAPI.getPatchesRepo();
|
||||||
final String integrationsRepo = _managerAPI.getIntegrationsRepo();
|
|
||||||
_orgPatSourceController.text = patchesRepo.split('/')[0];
|
_orgPatSourceController.text = patchesRepo.split('/')[0];
|
||||||
_patSourceController.text = patchesRepo.split('/')[1];
|
_patSourceController.text = patchesRepo.split('/')[1];
|
||||||
_orgIntSourceController.text = integrationsRepo.split('/')[0];
|
|
||||||
_intSourceController.text = integrationsRepo.split('/')[1];
|
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
@@ -72,38 +67,6 @@ class SManageSources extends BaseViewModel {
|
|||||||
hintText: patchesRepo.split('/')[1],
|
hintText: patchesRepo.split('/')[1],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
|
||||||
// Integrations owner's name
|
|
||||||
TextField(
|
|
||||||
controller: _orgIntSourceController,
|
|
||||||
autocorrect: false,
|
|
||||||
onChanged: (value) => notifyListeners(),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
icon: Icon(
|
|
||||||
Icons.merge_outlined,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
labelText: t.settingsView.orgIntegrationsLabel,
|
|
||||||
hintText: integrationsRepo.split('/')[0],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
// Integrations repository's name
|
|
||||||
TextField(
|
|
||||||
controller: _intSourceController,
|
|
||||||
autocorrect: false,
|
|
||||||
onChanged: (value) => notifyListeners(),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.merge_outlined,
|
|
||||||
color: Colors.transparent,
|
|
||||||
),
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
labelText: t.settingsView.sourcesIntegrationsLabel,
|
|
||||||
hintText: integrationsRepo.split('/')[1],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Text(t.settingsView.sourcesUpdateNote),
|
Text(t.settingsView.sourcesUpdateNote),
|
||||||
],
|
],
|
||||||
@@ -113,8 +76,6 @@ class SManageSources extends BaseViewModel {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
_orgPatSourceController.clear();
|
_orgPatSourceController.clear();
|
||||||
_patSourceController.clear();
|
_patSourceController.clear();
|
||||||
_orgIntSourceController.clear();
|
|
||||||
_intSourceController.clear();
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: Text(t.cancelButton),
|
child: Text(t.cancelButton),
|
||||||
@@ -124,11 +85,7 @@ class SManageSources extends BaseViewModel {
|
|||||||
_managerAPI.setPatchesRepo(
|
_managerAPI.setPatchesRepo(
|
||||||
'${_orgPatSourceController.text.trim()}/${_patSourceController.text.trim()}',
|
'${_orgPatSourceController.text.trim()}/${_patSourceController.text.trim()}',
|
||||||
);
|
);
|
||||||
_managerAPI.setIntegrationsRepo(
|
|
||||||
'${_orgIntSourceController.text.trim()}/${_intSourceController.text.trim()}',
|
|
||||||
);
|
|
||||||
_managerAPI.setCurrentPatchesVersion('0.0.0');
|
_managerAPI.setCurrentPatchesVersion('0.0.0');
|
||||||
_managerAPI.setCurrentIntegrationsVersion('0.0.0');
|
|
||||||
_managerAPI.setLastUsedPatchesVersion();
|
_managerAPI.setLastUsedPatchesVersion();
|
||||||
_toast.showBottom(t.settingsView.restartAppForChanges);
|
_toast.showBottom(t.settingsView.restartAppForChanges);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
@@ -154,9 +111,7 @@ class SManageSources extends BaseViewModel {
|
|||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_managerAPI.setPatchesRepo('');
|
_managerAPI.setPatchesRepo('');
|
||||||
_managerAPI.setIntegrationsRepo('');
|
|
||||||
_managerAPI.setCurrentPatchesVersion('0.0.0');
|
_managerAPI.setCurrentPatchesVersion('0.0.0');
|
||||||
_managerAPI.setCurrentIntegrationsVersion('0.0.0');
|
|
||||||
_toast.showBottom(t.settingsView.restartAppForChanges);
|
_toast.showBottom(t.settingsView.restartAppForChanges);
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
..pop()
|
..pop()
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
void useAlternativeSources(bool value) {
|
void useAlternativeSources(bool value) {
|
||||||
_managerAPI.useAlternativeSources(value);
|
_managerAPI.useAlternativeSources(value);
|
||||||
_managerAPI.setCurrentPatchesVersion('0.0.0');
|
_managerAPI.setCurrentPatchesVersion('0.0.0');
|
||||||
_managerAPI.setCurrentIntegrationsVersion('0.0.0');
|
|
||||||
_managerAPI.setLastUsedPatchesVersion();
|
_managerAPI.setLastUsedPatchesVersion();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -223,6 +222,53 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> exportSettings() async {
|
||||||
|
try {
|
||||||
|
final String settings = _managerAPI.exportSettings();
|
||||||
|
final Directory tempDir = await getTemporaryDirectory();
|
||||||
|
final String filePath = '${tempDir.path}/manager_settings.json';
|
||||||
|
final File file = File(filePath);
|
||||||
|
await file.writeAsString(settings);
|
||||||
|
final String? result = await FlutterFileDialog.saveFile(
|
||||||
|
params: SaveFileDialogParams(
|
||||||
|
sourceFilePath: file.path,
|
||||||
|
fileName: 'manager_settings.json',
|
||||||
|
mimeTypesFilter: ['application/json'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (result != null) {
|
||||||
|
_toast.showBottom(t.settingsView.exportedSettings);
|
||||||
|
}
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> importSettings() async {
|
||||||
|
try {
|
||||||
|
final String? result = await FlutterFileDialog.pickFile(
|
||||||
|
params: const OpenFileDialogParams(
|
||||||
|
fileExtensionsFilter: ['json'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (result != null) {
|
||||||
|
final File inFile = File(result);
|
||||||
|
final String settings = inFile.readAsStringSync();
|
||||||
|
inFile.delete();
|
||||||
|
_managerAPI.importSettings(settings);
|
||||||
|
_toast.showBottom(t.settingsView.importedSettings);
|
||||||
|
_toast.showBottom(t.settingsView.restartAppForChanges);
|
||||||
|
}
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
_toast.showBottom(t.settingsView.jsonSelectorErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> exportPatches() async {
|
Future<void> exportPatches() async {
|
||||||
try {
|
try {
|
||||||
final File outFile = File(_managerAPI.storedPatchesFile);
|
final File outFile = File(_managerAPI.storedPatchesFile);
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class _ContributorsCardState extends State<ContributorsCard> {
|
|||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => launchUrl(
|
onTap: () => launchUrl(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
widget.contributors[index]['html_url'],
|
widget.contributors[index]['url'],
|
||||||
),
|
),
|
||||||
mode: LaunchMode.externalApplication,
|
mode: LaunchMode.externalApplication,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class IntStringLongListPatchOption extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final List<dynamic> values = List.from(patchOption.value ?? []);
|
final List<dynamic> values = List.from(patchOption.value ?? []);
|
||||||
final ValueNotifier patchOptionValue = ValueNotifier(values);
|
final ValueNotifier patchOptionValue = ValueNotifier(values);
|
||||||
final String type = patchOption.valueType;
|
final String type = patchOption.type;
|
||||||
|
|
||||||
String getKey(dynamic value) {
|
String getKey(dynamic value) {
|
||||||
if (value != null && patchOption.values != null) {
|
if (value != null && patchOption.values != null) {
|
||||||
@@ -408,12 +408,12 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final bool isStringOption = widget.patchOption.valueType.contains('String');
|
final bool isStringOption = widget.patchOption.type.contains('String');
|
||||||
final bool isArrayOption = widget.patchOption.valueType.contains('Array');
|
final bool isListOption = widget.patchOption.type.contains('List');
|
||||||
selectedKey = selectedKey == '' ? selectedKey : widget.selectedKey;
|
selectedKey = selectedKey == '' ? selectedKey : widget.selectedKey;
|
||||||
final bool isValueArray = widget.value?.startsWith('[') ?? false;
|
final bool isValueArray = widget.value?.startsWith('[') ?? false;
|
||||||
final bool shouldResetValue =
|
final bool shouldResetValue =
|
||||||
!isStringOption && isArrayOption && selectedKey == '' && isValueArray;
|
!isStringOption && isListOption && selectedKey == '' && isValueArray;
|
||||||
controller.text = shouldResetValue ? '' : widget.value ?? '';
|
controller.text = shouldResetValue ? '' : widget.value ?? '';
|
||||||
defaultValue ??= controller.text;
|
defaultValue ??= controller.text;
|
||||||
return Column(
|
return Column(
|
||||||
@@ -479,7 +479,7 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
|
|||||||
} else {
|
} else {
|
||||||
controller.text = widget.patchOption.values![value].toString();
|
controller.text = widget.patchOption.values![value].toString();
|
||||||
widget.onChanged(
|
widget.onChanged(
|
||||||
isArrayOption
|
isListOption
|
||||||
? widget.patchOption.values![value]
|
? widget.patchOption.values![value]
|
||||||
: controller.text,
|
: controller.text,
|
||||||
);
|
);
|
||||||
@@ -492,9 +492,9 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
|
|||||||
if (selectedKey == '')
|
if (selectedKey == '')
|
||||||
TextFormField(
|
TextFormField(
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
if (widget.patchOption.valueType.contains('Int'))
|
if (widget.patchOption.type.contains('Int'))
|
||||||
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
|
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
|
||||||
if (widget.patchOption.valueType.contains('Long'))
|
if (widget.patchOption.type.contains('Long'))
|
||||||
FilteringTextInputFormatter.allow(RegExp(r'^[0-9]*\.?[0-9]*')),
|
FilteringTextInputFormatter.allow(RegExp(r'^[0-9]*\.?[0-9]*')),
|
||||||
],
|
],
|
||||||
controller: controller,
|
controller: controller,
|
||||||
@@ -505,7 +505,7 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
|
|||||||
tooltip: t.patchOptionsView.tooltip,
|
tooltip: t.patchOptionsView.tooltip,
|
||||||
itemBuilder: (BuildContext context) {
|
itemBuilder: (BuildContext context) {
|
||||||
return [
|
return [
|
||||||
if (isArrayOption)
|
if (isListOption)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 'remove',
|
value: 'remove',
|
||||||
child: Text(t.remove),
|
child: Text(t.remove),
|
||||||
|
|||||||
@@ -14,6 +14,30 @@ class SExportSection extends StatelessWidget {
|
|||||||
return SettingsSection(
|
return SettingsSection(
|
||||||
title: t.settingsView.exportSectionTitle,
|
title: t.settingsView.exportSectionTitle,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
title: Text(
|
||||||
|
t.settingsView.exportSettingsLabel,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Text(t.settingsView.exportSettingsHint),
|
||||||
|
onTap: () => _settingsViewModel.exportSettings(),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
title: Text(
|
||||||
|
t.settingsView.importSettingsLabel,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Text(t.settingsView.importSettingsHint),
|
||||||
|
onTap: () => _settingsViewModel.importSettings(),
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: Text(
|
title: Text(
|
||||||
@@ -114,7 +138,6 @@ class SExportSection extends StatelessWidget {
|
|||||||
subtitle: Text(t.settingsView.regenerateKeystoreHint),
|
subtitle: Text(t.settingsView.regenerateKeystoreHint),
|
||||||
onTap: () => _showDeleteKeystoreDialog(context),
|
onTap: () => _showDeleteKeystoreDialog(context),
|
||||||
),
|
),
|
||||||
// SManageKeystorePasswordUI(),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ bool hasUnsupportedRequiredOption(List<Option> options, Patch patch) {
|
|||||||
option.key,
|
option.key,
|
||||||
) ==
|
) ==
|
||||||
null) {
|
null) {
|
||||||
requiredOptionsType.add(option.valueType);
|
requiredOptionsType.add(option.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (final String optionType in requiredOptionsType) {
|
for (final String optionType in requiredOptionsType) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ homepage: https://revanced.app
|
|||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.23.0-dev.3+101800043
|
version: 1.23.0-dev.7+101800047
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.3
|
sdk: ^3.5.3
|
||||||
|
|||||||
Reference in New Issue
Block a user