mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2025-05-19 23:46:55 +08:00
Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
e6a8f4e6dc | |||
494e268bc5 | |||
e1c6f65b7e | |||
89075c5588 | |||
c7fa9b8ce7 | |||
cb70082d31 | |||
48b9ac8f5b | |||
0be568bbbd | |||
ba44fa620f | |||
dde402afbf | |||
bbe5142ca9 | |||
e74ffac5b0 | |||
cfb8980e3a | |||
e06e1bdcbe | |||
d60ced2f61 | |||
e68689828e | |||
ba932758c8 | |||
ee43fa6311 | |||
ad6b164d51 | |||
4a5510acb2 | |||
970dbc4428 | |||
f8f37325eb | |||
bb999019ef | |||
533b6a155a | |||
4cdc92388c | |||
ccc6be1e71 | |||
b355778a92 | |||
6a12e8f37a | |||
59adb91f5f | |||
53677e2f39 | |||
1c74f43b22 | |||
b4801970e8 | |||
7a3a6b512f | |||
72ea33b6de | |||
d97192e0ee | |||
196d9fe4d2 | |||
e960fcc303 | |||
f334da95ff | |||
5d5f311e36 | |||
d577e97758 | |||
2dc92e26d3 | |||
f4994a36a3 | |||
7a785a8163 | |||
6ad0d860c7 | |||
38a2fa55df |
2
.github/workflows/pr-build.yml
vendored
2
.github/workflows/pr-build.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
distribution: 'zulu'
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
|
4
.github/workflows/release-build.yml
vendored
4
.github/workflows/release-build.yml
vendored
@ -12,10 +12,10 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set env
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
- name: Set up JDK 11
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: "11"
|
||||
java-version: "17"
|
||||
distribution: "zulu"
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
|
75
.releaserc
75
.releaserc
@ -1,75 +0,0 @@
|
||||
{
|
||||
"branches": [
|
||||
"main",
|
||||
{
|
||||
"name": "dev",
|
||||
"prerelease": true
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
"semantic-release-export-data",
|
||||
"@semantic-release/commit-analyzer",
|
||||
[
|
||||
"@semantic-release/release-notes-generator",
|
||||
{
|
||||
"presetConfig": {
|
||||
"types": [
|
||||
{
|
||||
"type": "build",
|
||||
"section": "Dependency Updates"
|
||||
},
|
||||
{
|
||||
"type": "chore",
|
||||
"section": "Other Changes",
|
||||
"hidden": false
|
||||
},
|
||||
{
|
||||
"type": "perf",
|
||||
"section": "Performance Improvements",
|
||||
"hidden": false
|
||||
},
|
||||
{
|
||||
"type": "refactor",
|
||||
"section": "Code Improvements",
|
||||
"hidden": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"@semantic-release/changelog",
|
||||
"semantic-release-flutter-plugin",
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": [
|
||||
"CHANGELOG.md",
|
||||
"pubspec.yaml"
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/github",
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"path": "build/app/outputs/apk/release/revanced-manager-*.apk"
|
||||
}
|
||||
],
|
||||
"successComment": false
|
||||
}
|
||||
],
|
||||
[
|
||||
"@saithodev/semantic-release-backmerge",
|
||||
{
|
||||
"backmergeBranches": [
|
||||
{
|
||||
"from": "main",
|
||||
"to": "dev"
|
||||
}
|
||||
],
|
||||
"clearWorkspace": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
@ -1 +0,0 @@
|
||||
|
@ -26,30 +26,26 @@ apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
compileSdk 34
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '11'
|
||||
jvmTarget = '17'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "app.revanced.manager.flutter"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 33
|
||||
minSdk 26
|
||||
targetSdk 34
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
shrinkResources false
|
||||
@ -71,10 +67,21 @@ android {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude '/prebuilt/**'
|
||||
jniLibs {
|
||||
useLegacyPackaging true
|
||||
excludes += ['/prebuilt/**']
|
||||
}
|
||||
resources {
|
||||
excludes += ['/prebuilt/**']
|
||||
}
|
||||
}
|
||||
|
||||
namespace 'app.revanced.manager.flutter'
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
flutter {
|
||||
@ -85,7 +92,7 @@ dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
// ReVanced
|
||||
implementation "app.revanced:revanced-patcher:17.0.0"
|
||||
implementation "app.revanced:revanced-patcher:19.0.0"
|
||||
|
||||
// Signing & aligning
|
||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||
|
@ -1,4 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="app.revanced.manager.flutter">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
|
@ -1,6 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="app.revanced.manager.flutter">
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
@ -24,8 +22,7 @@
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:largeHeap="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:extractNativeLibs="true">
|
||||
android:requestLegacyExternalStorage="true">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
@ -15,7 +15,9 @@ import app.revanced.patcher.patch.PatchResult
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import kotlinx.coroutines.InternalCoroutinesApi
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
@ -25,6 +27,7 @@ import java.io.StringWriter
|
||||
import java.util.logging.LogRecord
|
||||
import java.util.logging.Logger
|
||||
|
||||
|
||||
class MainActivity : FlutterActivity() {
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private lateinit var installerChannel: MethodChannel
|
||||
@ -98,8 +101,10 @@ class MainActivity : FlutterActivity() {
|
||||
val cacheDirPath = call.argument<String>("cacheDirPath")!!
|
||||
|
||||
try {
|
||||
val patchBundleFile = File(patchBundleFilePath)
|
||||
patchBundleFile.setWritable(false)
|
||||
patches = PatchBundleLoader.Dex(
|
||||
File(patchBundleFilePath),
|
||||
patchBundleFile,
|
||||
optimizedDexDirectory = File(cacheDirPath)
|
||||
)
|
||||
} catch (ex: Exception) {
|
||||
@ -131,24 +136,34 @@ class MainActivity : FlutterActivity() {
|
||||
})
|
||||
put("options", JSONArray().apply {
|
||||
it.options.values.forEach { option ->
|
||||
val optionJson = JSONObject().apply option@{
|
||||
JSONObject().apply {
|
||||
put("key", option.key)
|
||||
put("title", option.title)
|
||||
put("description", option.description)
|
||||
put("required", option.required)
|
||||
|
||||
when (val value = option.value) {
|
||||
null -> put("value", null)
|
||||
is Array<*> -> put("value", JSONArray().apply {
|
||||
|
||||
fun JSONObject.putValue(
|
||||
value: Any?,
|
||||
key: String = "value"
|
||||
) = if (value is Array<*>) put(
|
||||
key,
|
||||
JSONArray().apply {
|
||||
value.forEach { put(it) }
|
||||
})
|
||||
else -> put("value", option.value)
|
||||
}
|
||||
else put(key, value)
|
||||
|
||||
put("optionClassType", option::class.simpleName)
|
||||
}
|
||||
put(optionJson)
|
||||
putValue(option.default)
|
||||
|
||||
option.values?.let { values ->
|
||||
put("values",
|
||||
JSONObject().apply {
|
||||
values.forEach { (key, value) ->
|
||||
putValue(value, key)
|
||||
}
|
||||
})
|
||||
} ?: put("values", null)
|
||||
put("valueType", option.valueType)
|
||||
}.let(::put)
|
||||
}
|
||||
})
|
||||
}.let(::put)
|
||||
@ -161,6 +176,7 @@ class MainActivity : FlutterActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(InternalCoroutinesApi::class)
|
||||
private fun runPatcher(
|
||||
result: MethodChannel.Result,
|
||||
originalFilePath: String,
|
||||
@ -283,12 +299,12 @@ class MainActivity : FlutterActivity() {
|
||||
acceptPatches(patches)
|
||||
|
||||
runBlocking {
|
||||
apply(false).collect { patchResult: PatchResult ->
|
||||
apply(false).collect(FlowCollector { patchResult: PatchResult ->
|
||||
if (cancel) {
|
||||
handler.post { stopResult!!.success(null) }
|
||||
this.cancel()
|
||||
this@apply.close()
|
||||
return@collect
|
||||
return@FlowCollector
|
||||
}
|
||||
|
||||
val msg = patchResult.exception?.let {
|
||||
@ -301,7 +317,7 @@ class MainActivity : FlutterActivity() {
|
||||
|
||||
updateProgress(progress, "", msg)
|
||||
progress += progressStep
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="app.revanced.manager.flutter">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
|
@ -1,12 +1,12 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.9.0'
|
||||
ext.kotlin_version = '1.9.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.3'
|
||||
classpath 'com.android.tools.build:gradle:8.1.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
@ -22,12 +22,13 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
layout.buildDirectory.set(file("../build"))
|
||||
var root = layout.buildDirectory.get().asFile.absolutePath
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
project.layout.buildDirectory.set(file("$root/${project.name}"))
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
delete layout.buildDirectory
|
||||
}
|
||||
|
@ -4,3 +4,6 @@ org.gradle.daemon=true
|
||||
org.gradle.caching=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
||||
|
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "OK",
|
||||
"cancelButton": "Cancel",
|
||||
"dismissButton": "Dismiss",
|
||||
"quitButton": "Quit",
|
||||
"updateButton": "Update",
|
||||
"enabledLabel": "Enabled",
|
||||
@ -128,14 +129,14 @@
|
||||
"none": "None",
|
||||
"noneTooltip": "Deselect all patches",
|
||||
|
||||
"loadPatchesSelection": "Load patches selection",
|
||||
"noSavedPatches": "No saved patches for the selected app.\nPress Done to save current selection.",
|
||||
"loadPatchesSelection": "Load patch selection",
|
||||
"noSavedPatches": "No saved patch selection for the selected app.\nPress Done to save the current selection.",
|
||||
"noPatchesFound": "No patches found for the selected app",
|
||||
"setRequiredOption": "Some patches require options to be set:\n\n{patches}\n\nPlease set them before continuing.",
|
||||
|
||||
"selectAllPatchesWarningContent": "You are about to select all patches, that includes non-suggested patches and can cause unwanted behavior."
|
||||
"setRequiredOption": "Some patches require options to be set:\n\n{patches}\n\nPlease set them before continuing."
|
||||
},
|
||||
"patchOptionsView": {
|
||||
"customValue": "Custom value",
|
||||
"resetOptionsTooltip": "Reset patch options",
|
||||
"viewTitle": "Patch options",
|
||||
"saveOptions": "Save",
|
||||
|
||||
@ -152,10 +153,10 @@
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nSupported versions:\n{supportedVersions}",
|
||||
"unsupportedPatchVersion": "Patch is not supported for this app version. Enable the experimental toggle in settings to proceed.",
|
||||
"unsupportedPatchVersion": "Patch is not supported for this app version.",
|
||||
"unsupportedRequiredOption": "This patch contains a required option that is not supported by this app",
|
||||
|
||||
"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.",
|
||||
"patchesChangeWarningDialogText": "It is recommended to use the default patch selection and options. Changing them may result in unexpected issues.\n\nYou'll need to turn on \"Allow changing patch selection\" in settings before changing any patch selection.",
|
||||
"patchesChangeWarningDialogButton": "Use default selection"
|
||||
},
|
||||
"installerView": {
|
||||
@ -187,10 +188,9 @@
|
||||
|
||||
"appearanceSectionTitle": "Appearance",
|
||||
"teamSectionTitle": "Team",
|
||||
"infoSectionTitle": "Info",
|
||||
"debugSectionTitle": "Debugging",
|
||||
"advancedSectionTitle": "Advanced",
|
||||
"exportSectionTitle": "Import & export",
|
||||
"logsSectionTitle": "Logs",
|
||||
|
||||
"themeModeLabel": "App theme",
|
||||
"systemThemeLabel": "System",
|
||||
@ -204,15 +204,15 @@
|
||||
"englishOption": "English",
|
||||
|
||||
"sourcesLabel": "Sources",
|
||||
"sourcesLabelHint": "Configure your custom sources",
|
||||
"sourcesLabelHint": "Configure your sources",
|
||||
"sourcesIntegrationsLabel": "Integrations source",
|
||||
"sourcesResetDialogTitle": "Reset",
|
||||
"sourcesResetDialogText": "Are you sure you want to reset custom sources to their default values?",
|
||||
"apiURLResetDialogText": "Are you sure you want to reset API URL to its default value?",
|
||||
"sourcesUpdateNote": "Note: ReVanced Patches will be updated to the latest version automatically.\n\nThis will reveal your IP address to the server.",
|
||||
"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?",
|
||||
"sourcesUpdateNote": "Note: Patches will be updated to the latest version automatically.\n\nThis will reveal your IP address to the server.",
|
||||
|
||||
"apiURLLabel": "API URL",
|
||||
"apiURLHint": "Configure your custom API URL",
|
||||
"apiURLHint": "Configure your API URL",
|
||||
"selectApiURL": "API URL",
|
||||
"hostRepositoryLabel": "Repository API",
|
||||
"orgPatchesLabel": "Patches organization",
|
||||
@ -222,21 +222,20 @@
|
||||
"contributorsLabel": "Contributors",
|
||||
"contributorsHint": "A list of contributors of ReVanced",
|
||||
|
||||
"logsLabel": "Logs",
|
||||
"logsHint": "Share Manager's logs",
|
||||
"logsLabel": "Share logs",
|
||||
"logsHint": "Share ReVanced Manager 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?",
|
||||
"enablePatchesSelectionLabel": "Allow changing patch selection",
|
||||
"enablePatchesSelectionHint": "Allow changing the selection of patches",
|
||||
"enablePatchesSelectionWarningText": "Changing the selection of patches may cause unexpected issues.\n\nEnable anyways?",
|
||||
"disablePatchesSelectionWarningText": "You are about to disable changing the selection of patches.\nThe default selection of patches will be restored.\n\nDisable anyways?",
|
||||
|
||||
"autoUpdatePatchesLabel": "Auto update patches",
|
||||
"autoUpdatePatchesHint": "Automatically update ReVanced Patches to the latest version",
|
||||
"experimentalUniversalPatchesLabel": "Experimental universal patches support",
|
||||
"experimentalUniversalPatchesHint": "Display all applications to use with universal patches, loading list of apps may be slower",
|
||||
"experimentalPatchesLabel": "Experimental patches support",
|
||||
"experimentalPatchesHint": "Enable usage of unsupported patches in any app version",
|
||||
"enabledExperimentalPatches": "Experimental patches support enabled",
|
||||
"autoUpdatePatchesHint": "Automatically update patches to the latest version",
|
||||
"universalPatchesLabel": "Show universal patches",
|
||||
"universalPatchesHint": "Display all apps and universal patches (may slow down the app list)",
|
||||
"versionCompatibilityCheckLabel": "Version compatibility check",
|
||||
"versionCompatibilityCheckHint": "Restricts patches to supported app versions",
|
||||
|
||||
"aboutLabel": "About",
|
||||
"snackbarMessage": "Copied to clipboard",
|
||||
@ -246,54 +245,52 @@
|
||||
"deleteTempDirHint": "Delete unused temporary files",
|
||||
"deletedTempDir": "Temporary files deleted",
|
||||
|
||||
"exportPatchesLabel": "Export patches selection",
|
||||
"exportPatchesHint": "Export patches selection to a JSON file",
|
||||
"exportedPatches": "Patches selection exported",
|
||||
"noExportFileFound": "No patches selection to export",
|
||||
"exportPatchesLabel": "Export patch selection",
|
||||
"exportPatchesHint": "Export patch selection to a JSON file",
|
||||
"exportedPatches": "Patch selection exported",
|
||||
"noExportFileFound": "No patch selection to export",
|
||||
|
||||
"importPatchesLabel": "Import patches selection",
|
||||
"importPatchesHint": "Import patches selection from a JSON file",
|
||||
"importedPatches": "Patches selection imported",
|
||||
"importPatchesLabel": "Import patch selection",
|
||||
"importPatchesHint": "Import patch selection from a JSON file",
|
||||
"importedPatches": "Patch selection imported",
|
||||
|
||||
"resetStoredPatchesLabel": "Reset patches",
|
||||
"resetStoredPatchesHint": "Reset the stored patches selection",
|
||||
"resetStoredPatchesLabel": "Reset patch selection",
|
||||
"resetStoredPatchesHint": "Reset the stored patch selection",
|
||||
"resetStoredPatchesDialogTitle": "Reset patch selection?",
|
||||
"resetStoredPatchesDialogText": "The default selection of patches will be restored.",
|
||||
"resetStoredPatches": "Patch selection has been reset",
|
||||
|
||||
"resetStoredOptionsLabel": "Reset options",
|
||||
"resetStoredOptionsLabel": "Reset patch options",
|
||||
"resetStoredOptionsHint": "Reset all patch options",
|
||||
|
||||
"resetStoredPatchesDialogTitle": "Reset patches selection?",
|
||||
"resetStoredPatchesDialogText": "Resetting patches selection will remove all selected patches.",
|
||||
"resetStoredPatches": "Patches selection has been reset",
|
||||
|
||||
"resetStoredOptionsDialogTitle": "Reset options?",
|
||||
"resetStoredOptionsDialogText": "Resetting options will remove all saved options.",
|
||||
"resetStoredOptionsDialogTitle": "Reset patch options?",
|
||||
"resetStoredOptionsDialogText": "Resetting patch options will remove all saved options.",
|
||||
"resetStoredOptions": "Options have been reset",
|
||||
|
||||
"deleteLogsLabel": "Delete logs",
|
||||
"deleteLogsHint": "Delete collected manager logs",
|
||||
"deleteLogsLabel": "Clear logs",
|
||||
"deleteLogsHint": "Delete collected ReVanced Manager logs",
|
||||
"deletedLogs": "Logs deleted",
|
||||
|
||||
"regenerateKeystoreLabel": "Regenerate keystore",
|
||||
"regenerateKeystoreHint": "Regenerate the keystore used to sign the app",
|
||||
"regenerateKeystoreHint": "Regenerate the keystore used to sign apps",
|
||||
|
||||
"regenerateKeystoreDialogTitle": "Regenerate keystore?",
|
||||
"regenerateKeystoreDialogText": "Patched apps signed with the old keystore will no longer be able to update.",
|
||||
"regenerateKeystoreDialogText": "Patched apps signed with the old keystore will no longer be able to be updated.",
|
||||
"regeneratedKeystore": "Keystore regenerated",
|
||||
|
||||
"exportKeystoreLabel": "Export keystore",
|
||||
"exportKeystoreHint": "Export keystore used to sign apps",
|
||||
"exportKeystoreHint": "Export the keystore used to sign apps",
|
||||
"exportedKeystore": "Keystore exported",
|
||||
"noKeystoreExportFileFound": "No keystore to export",
|
||||
|
||||
"importKeystoreLabel": "Import keystore",
|
||||
"importKeystoreHint": "Import keystore used to sign apps",
|
||||
"importKeystoreHint": "Import a keystore used to sign apps",
|
||||
"importedKeystore": "Keystore imported",
|
||||
|
||||
"selectKeystorePassword": "Keystore Password",
|
||||
"selectKeystorePasswordHint": "Select keystore password used to sign the apk",
|
||||
"selectKeystorePassword": "Keystore password",
|
||||
"selectKeystorePasswordHint": "Select keystore password used to sign apps",
|
||||
|
||||
"jsonSelectorErrorMessage": "Unable to use selected JSON file",
|
||||
"keystoreSelectorErrorMessage": "Unable to use selected KEYSTORE file"
|
||||
"keystoreSelectorErrorMessage": "Unable to use selected keystore file"
|
||||
},
|
||||
"appInfoView": {
|
||||
"widgetTitle": "App info",
|
||||
|
@ -2,37 +2,41 @@
|
||||
|
||||
ReVanced Manager has settings that can be configured to your liking.
|
||||
|
||||
## ⭐ Essential settings
|
||||
## 🎛️ Essential settings
|
||||
|
||||
- ### 🔗 API URL
|
||||
- ### 🪛 Allow changing patch selection
|
||||
|
||||
API to use to fetch updates and ReVanced Patches from.
|
||||
Allows the user to change the patch selection from the default selection.
|
||||
|
||||
- ### 🔍 Version compatibility check
|
||||
|
||||
Constrains patches to supported app versions. Disable this to patch any version of an app.
|
||||
|
||||
> [!WARNING]
|
||||
> Disabling this may cause issues if the patches are not compatible with the app version.
|
||||
|
||||
- ### 🧑🔬 Show universal patches
|
||||
|
||||
Reveals patches which can be applied to any app.
|
||||
|
||||
> [!WARNING]
|
||||
> These patches may not work on all apps.
|
||||
|
||||
- ### 🧬 Sources
|
||||
|
||||
Override the API and download ReVanced Patches from a different source.
|
||||
Override the API and download patches from a different source.
|
||||
|
||||
- ### 🧪 Experimental ReVanced Patches support
|
||||
- ### 🔗 API URL
|
||||
|
||||
Disable checking for the version of the app when applying ReVanced Patches.
|
||||
|
||||
> [!WARNING]
|
||||
> This may cause issues if the ReVanced Patches are not compatible with the app version.
|
||||
|
||||
- ### 🧑🔬 Experimental universal support
|
||||
|
||||
This will show or hide ReVanced Patches, which are not meant for any app in particular but apply to all apps
|
||||
|
||||
> [!WARNING]
|
||||
> Because the patches generalize the app, they may not work on all apps.
|
||||
API to use to fetch updates and patches from.
|
||||
|
||||
- ### 💾 Imports & Exports
|
||||
|
||||
You can import, export or reset the following settings:
|
||||
|
||||
- 🔑 Keystore
|
||||
- 📄 ReVanced Patches selection
|
||||
- ⚙️ Options
|
||||
- 📄 Patch selection
|
||||
- ⚙️ Patch options
|
||||
|
||||
> [!NOTE]
|
||||
> This is particularly useful if you want to backup or reset your settings.
|
||||
@ -45,4 +49,4 @@ ReVanced Manager has settings that can be configured to your liking.
|
||||
|
||||
The next page will bring you back to the usage page.
|
||||
|
||||
Continue: [🛠️ Usage](2_usage.md)
|
||||
Continue: [🛠️ Usage](2_usage.md)
|
@ -9,31 +9,22 @@ This page will guide you through building ReVanced Manager from source.
|
||||
```sh
|
||||
git clone https://github.com/revanced/revanced-manager.git && cd revanced-manager
|
||||
```
|
||||
|
||||
3. Create a GitHub personal access token with the `read:packages` scope [here](https://github.com/settings/tokens/new?scopes=read:packages&description=ReVanced)
|
||||
|
||||
4. Add your GitHub username and the token to `~/android/gradle.properties`
|
||||
|
||||
```properties
|
||||
gpr.user = YourUsername
|
||||
gpr.key = ghp_longrandomkey
|
||||
```
|
||||
|
||||
5. Get dependencies
|
||||
3. Get dependencies
|
||||
|
||||
```sh
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
6. Delete conflicting outputs
|
||||
4. Delete conflicting outputs
|
||||
|
||||
```sh
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
> **Note**: Must be run every time you sync your local repository with the remote repository.
|
||||
> [!Note]
|
||||
> Must be run every time you sync your local repository with the remote repository.
|
||||
|
||||
7. Build the APK
|
||||
5. Build the APK
|
||||
|
||||
```sh
|
||||
flutter build apk
|
||||
|
BIN
fonts/custom-icons.ttf
Normal file
BIN
fonts/custom-icons.ttf
Normal file
Binary file not shown.
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
35
gradlew
vendored
35
gradlew
vendored
@ -55,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@ -80,13 +80,11 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@ -133,22 +131,29 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
1
gradlew.bat
vendored
1
gradlew.bat
vendored
@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:revanced_manager/services/download_manager.dart';
|
||||
import 'package:revanced_manager/services/github_api.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/patcher_api.dart';
|
||||
@ -41,6 +42,7 @@ import 'package:stacked_services/stacked_services.dart';
|
||||
LazySingleton(classType: PatcherAPI),
|
||||
LazySingleton(classType: RevancedAPI),
|
||||
LazySingleton(classType: GithubAPI),
|
||||
LazySingleton(classType: DownloadManager),
|
||||
LazySingleton(classType: Toast),
|
||||
],
|
||||
)
|
||||
|
@ -4,21 +4,21 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/download_manager.dart';
|
||||
import 'package:revanced_manager/services/github_api.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/revanced_api.dart';
|
||||
import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart';
|
||||
import 'package:revanced_manager/ui/views/navigation/navigation_view.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:stacked_themes/stacked_themes.dart';
|
||||
import 'package:timezone/data/latest.dart' as tz;
|
||||
|
||||
late SharedPreferences prefs;
|
||||
Future main() async {
|
||||
await ThemeManager.initialise();
|
||||
await setupLocator();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await locator<ManagerAPI>().initialize();
|
||||
await locator<DownloadManager>().initialize();
|
||||
final String apiUrl = locator<ManagerAPI>().getApiUrl();
|
||||
await locator<RevancedAPI>().initialize(apiUrl);
|
||||
final String repoUrl = locator<ManagerAPI>().getRepoUrl();
|
||||
|
@ -12,7 +12,18 @@ class Patch {
|
||||
required this.options,
|
||||
});
|
||||
|
||||
factory Patch.fromJson(Map<String, dynamic> json) => _$PatchFromJson(json);
|
||||
factory Patch.fromJson(Map<String, dynamic> json) {
|
||||
_migrateV16ToV17(json);
|
||||
|
||||
return _$PatchFromJson(json);
|
||||
}
|
||||
|
||||
static void _migrateV16ToV17(Map<String, dynamic> json) {
|
||||
if (json['options'] == null) {
|
||||
json['options'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
final String name;
|
||||
final String? description;
|
||||
final bool excluded;
|
||||
@ -49,18 +60,37 @@ class Option {
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.value,
|
||||
required this.values,
|
||||
required this.required,
|
||||
required this.optionClassType,
|
||||
required this.valueType,
|
||||
});
|
||||
|
||||
factory Option.fromJson(Map<String, dynamic> json) => _$OptionFromJson(json);
|
||||
factory Option.fromJson(Map<String, dynamic> json) {
|
||||
_migrateV17ToV19(json);
|
||||
|
||||
return _$OptionFromJson(json);
|
||||
}
|
||||
|
||||
static void _migrateV17ToV19(Map<String, dynamic> json) {
|
||||
if (json['valueType'] == null) {
|
||||
final type = json['optionClassType'];
|
||||
if (type is String) {
|
||||
json['valueType'] = type
|
||||
.replaceAll('PatchOption', '')
|
||||
.replaceAll('List', 'Array');
|
||||
|
||||
json['optionClassType'] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final String key;
|
||||
final String title;
|
||||
final String description;
|
||||
dynamic value;
|
||||
final dynamic value;
|
||||
final Map<String, dynamic>? values;
|
||||
final bool required;
|
||||
final String optionClassType;
|
||||
final String valueType;
|
||||
|
||||
Map toJson() => _$OptionToJson(this);
|
||||
}
|
||||
|
75
lib/services/download_manager.dart
Normal file
75
lib/services/download_manager.dart
Normal file
@ -0,0 +1,75 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_cache_manager/file.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
|
||||
@lazySingleton
|
||||
class DownloadManager {
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
late final String _userAgent;
|
||||
|
||||
final _cacheOptions = CacheOptions(
|
||||
store: MemCacheStore(),
|
||||
maxStale: const Duration(days: 1),
|
||||
priority: CachePriority.high,
|
||||
);
|
||||
|
||||
Future<void> initialize() async {
|
||||
_userAgent = 'ReVanced-Manager/${await _managerAPI.getCurrentManagerVersion()}';
|
||||
}
|
||||
|
||||
Dio initDio(String url) {
|
||||
var dio = Dio();
|
||||
try {
|
||||
dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: url,
|
||||
headers: {
|
||||
'User-Agent': _userAgent,
|
||||
},
|
||||
),
|
||||
);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
||||
dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
||||
return dio;
|
||||
}
|
||||
|
||||
Future<void> clearAllCache() async {
|
||||
try {
|
||||
await _cacheOptions.store!.clean();
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<File> getSingleFile(String url) async {
|
||||
return DefaultCacheManager().getSingleFile(
|
||||
url,
|
||||
headers: {
|
||||
'User-Agent': _userAgent,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Stream<FileResponse> getFileStream(String url) {
|
||||
return DefaultCacheManager().getFileStream(
|
||||
url,
|
||||
withProgress: true,
|
||||
headers: {
|
||||
'User-Agent': _userAgent,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,48 +1,24 @@
|
||||
import 'dart:io';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/download_manager.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
|
||||
@lazySingleton
|
||||
class GithubAPI {
|
||||
late Dio _dio = Dio();
|
||||
late final Dio _dio;
|
||||
late final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
|
||||
final _cacheOptions = CacheOptions(
|
||||
store: MemCacheStore(),
|
||||
maxStale: const Duration(days: 1),
|
||||
priority: CachePriority.high,
|
||||
);
|
||||
late final DownloadManager _downloadManager = locator<DownloadManager>();
|
||||
|
||||
Future<void> initialize(String repoUrl) async {
|
||||
try {
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: repoUrl,
|
||||
),
|
||||
);
|
||||
|
||||
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
_dio = _downloadManager.initDio(repoUrl);
|
||||
}
|
||||
|
||||
Future<void> clearAllCache() async {
|
||||
try {
|
||||
await _cacheOptions.store!.clean();
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
await _downloadManager.clearAllCache();
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> getLatestRelease(
|
||||
@ -104,7 +80,7 @@ class GithubAPI {
|
||||
final Map<String, dynamic> releases = response.data[0];
|
||||
int updates = 0;
|
||||
final String currentVersion =
|
||||
await ManagerAPI().getCurrentManagerVersion();
|
||||
await _managerAPI.getCurrentManagerVersion();
|
||||
while (response.data[updates]['tag_name'] != 'v$currentVersion') {
|
||||
updates++;
|
||||
}
|
||||
@ -141,7 +117,7 @@ class GithubAPI {
|
||||
(asset) => (asset['name'] as String).endsWith(extension),
|
||||
);
|
||||
if (asset != null) {
|
||||
return await DefaultCacheManager().getSingleFile(
|
||||
return await _downloadManager.getSingleFile(
|
||||
asset['browser_download_url'],
|
||||
);
|
||||
}
|
||||
@ -162,7 +138,7 @@ class GithubAPI {
|
||||
) async {
|
||||
try {
|
||||
if (url.isNotEmpty) {
|
||||
return await DefaultCacheManager().getSingleFile(
|
||||
return await _downloadManager.getSingleFile(
|
||||
url,
|
||||
);
|
||||
}
|
||||
@ -180,7 +156,7 @@ class GithubAPI {
|
||||
} else {
|
||||
_managerAPI.setPatchesDownloadURL(downloadUrl);
|
||||
}
|
||||
return await DefaultCacheManager().getSingleFile(
|
||||
return await _downloadManager.getSingleFile(
|
||||
downloadUrl,
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:device_apps/device_apps.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
@ -33,6 +34,7 @@ class ManagerAPI {
|
||||
Patch? selectedPatch;
|
||||
BuildContext? ctx;
|
||||
bool isRooted = false;
|
||||
bool isDynamicThemeAvailable = false;
|
||||
String storedPatchesFile = '/selected-patches.json';
|
||||
String keystoreFile =
|
||||
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore';
|
||||
@ -59,10 +61,16 @@ class ManagerAPI {
|
||||
Future<void> initialize() async {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
isRooted = await _rootAPI.isRooted();
|
||||
isDynamicThemeAvailable = (await getSdkVersion()) >= 31; // ANDROID_12_SDK_VERSION = 31
|
||||
storedPatchesFile =
|
||||
(await getApplicationDocumentsDirectory()).path + storedPatchesFile;
|
||||
}
|
||||
|
||||
Future<int> getSdkVersion() async {
|
||||
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
|
||||
return info.version.sdkInt;
|
||||
}
|
||||
|
||||
String getApiUrl() {
|
||||
return _prefs.getString('apiUrl') ?? defaultApiUrl;
|
||||
}
|
||||
@ -71,7 +79,6 @@ class ManagerAPI {
|
||||
if (url.isEmpty || url == ' ') {
|
||||
url = defaultApiUrl;
|
||||
}
|
||||
await _revancedAPI.initialize(url);
|
||||
await _revancedAPI.clearAllCache();
|
||||
await _prefs.setString('apiUrl', url);
|
||||
}
|
||||
@ -244,12 +251,12 @@ class ManagerAPI {
|
||||
await _prefs.setBool('universalPatchesEnabled', value);
|
||||
}
|
||||
|
||||
bool areExperimentalPatchesEnabled() {
|
||||
return _prefs.getBool('experimentalPatchesEnabled') ?? false;
|
||||
bool isVersionCompatibilityCheckEnabled() {
|
||||
return _prefs.getBool('versionCompatibilityCheckEnabled') ?? true;
|
||||
}
|
||||
|
||||
Future<void> enableExperimentalPatchesStatus(bool value) async {
|
||||
await _prefs.setBool('experimentalPatchesEnabled', value);
|
||||
Future<void> enableVersionCompatibilityCheckStatus(bool value) async {
|
||||
await _prefs.setBool('versionCompatibilityCheckEnabled', value);
|
||||
}
|
||||
|
||||
Future<void> setKeystorePassword(String password) async {
|
||||
@ -677,7 +684,7 @@ class ManagerAPI {
|
||||
Future<List<String>> getDefaultPatches() async {
|
||||
final List<Patch> patches = await getPatches();
|
||||
final List<String> defaultPatches = [];
|
||||
if (areExperimentalPatchesEnabled() == false) {
|
||||
if (isVersionCompatibilityCheckEnabled() == true) {
|
||||
defaultPatches.addAll(
|
||||
patches
|
||||
.where(
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cr_file_saver/file_saver.dart';
|
||||
import 'package:device_apps/device_apps.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:install_plugin/install_plugin.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@ -13,7 +13,7 @@ import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:revanced_manager/models/patched_application.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/root_api.dart';
|
||||
import 'package:share_extend/share_extend.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
@lazySingleton
|
||||
class PatcherAPI {
|
||||
@ -236,10 +236,10 @@ void exportPatchedFile(String appName, String version) {
|
||||
try {
|
||||
if (outFile != null) {
|
||||
final String newName = _getFileName(appName, version);
|
||||
CRFileSaver.saveFileWithDialog(
|
||||
SaveFileDialogParams(
|
||||
FlutterFileDialog.saveFile(
|
||||
params: SaveFileDialogParams(
|
||||
sourceFilePath: outFile!.path,
|
||||
destinationFileName: newName,
|
||||
fileName: newName,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -258,7 +258,7 @@ void sharePatchedFile(String appName, String version) {
|
||||
final String newPath =
|
||||
outFile!.path.substring(0, lastSeparator + 1) + newName;
|
||||
final File shareFile = outFile!.copySync(newPath);
|
||||
ShareExtend.share(shareFile.path, 'file');
|
||||
Share.shareXFiles([XFile(shareFile.path)]);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
@ -286,10 +286,10 @@ Future<void> exportPatcherLog(String logs) async {
|
||||
final String fileName = 'revanced-manager_patcher_$dateTime.txt';
|
||||
final File log = File('${logDir.path}/$fileName');
|
||||
log.writeAsStringSync(logs);
|
||||
CRFileSaver.saveFileWithDialog(
|
||||
SaveFileDialogParams(
|
||||
FlutterFileDialog.saveFile(
|
||||
params: SaveFileDialogParams(
|
||||
sourceFilePath: log.path,
|
||||
destinationFileName: fileName,
|
||||
fileName: fileName,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -3,49 +3,27 @@ import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/download_manager.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
import 'package:timeago/timeago.dart';
|
||||
|
||||
@lazySingleton
|
||||
class RevancedAPI {
|
||||
late Dio _dio = Dio();
|
||||
late final Dio _dio;
|
||||
late final DownloadManager _downloadManager = locator<DownloadManager>();
|
||||
|
||||
final Lock getToolsLock = Lock();
|
||||
|
||||
final _cacheOptions = CacheOptions(
|
||||
store: MemCacheStore(),
|
||||
maxStale: const Duration(days: 1),
|
||||
priority: CachePriority.high,
|
||||
);
|
||||
|
||||
Future<void> initialize(String apiUrl) async {
|
||||
try {
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: apiUrl,
|
||||
),
|
||||
);
|
||||
|
||||
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
Future<void> initialize(String repoUrl) async {
|
||||
_dio = _downloadManager.initDio(repoUrl);
|
||||
}
|
||||
|
||||
Future<void> clearAllCache() async {
|
||||
try {
|
||||
await _cacheOptions.store!.clean();
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
await _downloadManager.clearAllCache();
|
||||
}
|
||||
|
||||
Future<Map<String, List<dynamic>>> getContributors() async {
|
||||
@ -120,7 +98,7 @@ class RevancedAPI {
|
||||
);
|
||||
if (release != null) {
|
||||
final String url = release['browser_download_url'];
|
||||
return await DefaultCacheManager().getSingleFile(url);
|
||||
return await _downloadManager.getSingleFile(url);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
@ -152,9 +130,8 @@ class RevancedAPI {
|
||||
'revanced/revanced-manager',
|
||||
);
|
||||
File? outputFile;
|
||||
await for (final result in DefaultCacheManager().getFileStream(
|
||||
await for (final result in _downloadManager.getFileStream(
|
||||
release!['browser_download_url'] as String,
|
||||
withProgress: true,
|
||||
)) {
|
||||
if (result is DownloadProgress) {
|
||||
final totalSize = result.totalSize ?? 10000000;
|
||||
|
@ -38,13 +38,11 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||
floating: true,
|
||||
title: I18nText(
|
||||
'appSelectorView.viewTitle',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
),
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 22.0,
|
||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: Icon(
|
||||
Icons.arrow_back,
|
||||
|
@ -1,9 +1,9 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_apps/device_apps.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
@ -181,13 +181,14 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||
|
||||
Future<void> selectAppFromStorage(BuildContext context) async {
|
||||
try {
|
||||
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ['apk'],
|
||||
final String? result = await FlutterFileDialog.pickFile(
|
||||
params: const OpenFileDialogParams(
|
||||
fileExtensionsFilter: ['apk'],
|
||||
),
|
||||
);
|
||||
if (result != null && result.files.single.path != null) {
|
||||
final File apkFile = File(result.files.single.path!);
|
||||
final List<String> pathSplit = result.files.single.path!.split('/');
|
||||
if (result != null) {
|
||||
final File apkFile = File(result);
|
||||
final List<String> pathSplit = result.split('/');
|
||||
pathSplit.removeLast();
|
||||
final Directory filePickerCacheDir = Directory(pathSplit.join('/'));
|
||||
final Iterable<File> deletableFiles =
|
||||
@ -207,7 +208,7 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||
name: application.appName,
|
||||
packageName: application.packageName,
|
||||
version: application.versionName!,
|
||||
apkFilePath: result.files.single.path!,
|
||||
apkFilePath: result,
|
||||
icon: application.icon,
|
||||
patchDate: DateTime.now(),
|
||||
isFromStorage: true,
|
||||
|
@ -1,7 +1,7 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:cross_connectivity/cross_connectivity.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@ -64,8 +64,9 @@ class HomeViewModel extends BaseViewModel {
|
||||
flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermission();
|
||||
final bool isConnected = await Connectivity().checkConnection();
|
||||
?.requestNotificationsPermission();
|
||||
final bool isConnected = await Connectivity().checkConnectivity() !=
|
||||
ConnectivityResult.none;
|
||||
if (!isConnected) {
|
||||
_toast.showBottom('homeView.noConnection');
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
import 'package:revanced_manager/utils/about_info.dart';
|
||||
import 'package:screenshot_callback/screenshot_callback.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:wakelock/wakelock.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
class InstallerViewModel extends BaseViewModel {
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
@ -74,7 +74,7 @@ class InstallerViewModel extends BaseViewModel {
|
||||
screenshotDetected(context);
|
||||
}
|
||||
});
|
||||
await Wakelock.enable();
|
||||
await WakelockPlus.enable();
|
||||
await handlePlatformChannelMethods();
|
||||
await runPatcher();
|
||||
}
|
||||
@ -139,7 +139,6 @@ class InstallerViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
Future<void> runPatcher() async {
|
||||
|
||||
try {
|
||||
await _patcherAPI.runPatcher(
|
||||
_app.packageName,
|
||||
@ -159,7 +158,7 @@ class InstallerViewModel extends BaseViewModel {
|
||||
|
||||
// Necessary to reset the state of patches so that they
|
||||
// can be reloaded again.
|
||||
_managerAPI.patches.clear();
|
||||
_managerAPI.patches.clear();
|
||||
await _patcherAPI.loadPatches();
|
||||
|
||||
try {
|
||||
@ -172,7 +171,7 @@ class InstallerViewModel extends BaseViewModel {
|
||||
} // ignore
|
||||
}
|
||||
}
|
||||
await Wakelock.disable();
|
||||
await WakelockPlus.disable();
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
@ -184,29 +183,28 @@ class InstallerViewModel extends BaseViewModel {
|
||||
final info = await AboutInfo.getInfo();
|
||||
|
||||
final formattedLogs = [
|
||||
'```',
|
||||
'~ Device Info',
|
||||
'- Device Info',
|
||||
'ReVanced Manager: ${info['version']}',
|
||||
'Build: ${info['flavor']}',
|
||||
'Model: ${info['model']}',
|
||||
'Android version: ${info['androidVersion']}',
|
||||
'Supported architectures: ${info['supportedArch'].join(", ")}',
|
||||
|
||||
'\n~ Patch Info',
|
||||
'Root permissions: ${isRooted ? 'Yes' : 'No'}',
|
||||
|
||||
'\n- Patch Info',
|
||||
'App: ${_app.packageName} v${_app.version}',
|
||||
'Patches version: ${_managerAPI.patchesVersion}',
|
||||
'Patches: ${_patches.map((p) => p.name).toList().join(", ")}',
|
||||
|
||||
'\n~ Settings',
|
||||
'Enabled changing patches: ${_managerAPI.isPatchesChangeEnabled()}',
|
||||
'Enabled universal patches: ${_managerAPI.areUniversalPatchesEnabled()}',
|
||||
'Enabled experimental patches: ${_managerAPI.areExperimentalPatchesEnabled()}',
|
||||
'Patches: ${_patches.map((p) => p.name + (p.options.isEmpty ? '' : ' [${p.options.map((o) => '${o.title}: ${o.value}').join(", ")}]')).toList().join(", ")}',
|
||||
|
||||
'\n- Settings',
|
||||
'Allow changing patch selection: ${_managerAPI.isPatchesChangeEnabled()}',
|
||||
'Version compatibility check: ${_managerAPI.isVersionCompatibilityCheckEnabled()}',
|
||||
'Show universal patches: ${_managerAPI.areUniversalPatchesEnabled()}',
|
||||
'Patches source: ${_managerAPI.getPatchesRepo()}',
|
||||
'Integration source: ${_managerAPI.getIntegrationsRepo()}',
|
||||
|
||||
'\n~ Logs',
|
||||
|
||||
'\n- Logs',
|
||||
logs,
|
||||
'```',
|
||||
];
|
||||
|
||||
Clipboard.setData(ClipboardData(text: formattedLogs.join('\n')));
|
||||
|
@ -19,7 +19,6 @@ class NavigationViewModel extends IndexTrackingViewModel {
|
||||
Future<void> initialize(BuildContext context) async {
|
||||
locator<Toast>().initialize(context);
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await requestManageExternalStorage();
|
||||
|
||||
if (prefs.getBool('permissionsRequested') == null) {
|
||||
await Permission.storage.request();
|
||||
@ -60,17 +59,6 @@ class NavigationViewModel extends IndexTrackingViewModel {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> requestManageExternalStorage() async {
|
||||
final manageExternalStorageStatus =
|
||||
await Permission.manageExternalStorage.status;
|
||||
if (manageExternalStorageStatus.isDenied) {
|
||||
await Permission.manageExternalStorage.request();
|
||||
}
|
||||
if (manageExternalStorageStatus.isPermanentlyDenied) {
|
||||
await openAppSettings();
|
||||
}
|
||||
}
|
||||
|
||||
Widget getViewForIndex(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
|
@ -48,6 +48,10 @@ class PatchOptionsView extends StatelessWidget {
|
||||
icon: const Icon(
|
||||
Icons.history,
|
||||
),
|
||||
tooltip: FlutterI18n.translate(
|
||||
context,
|
||||
'patchOptionsView.resetOptionsTooltip',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -57,8 +61,8 @@ class PatchOptionsView extends StatelessWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
for (final Option option in model.visibleOptions)
|
||||
if (option.optionClassType == 'StringPatchOption' ||
|
||||
option.optionClassType == 'IntPatchOption')
|
||||
if (option.valueType == 'String' ||
|
||||
option.valueType == 'Int')
|
||||
IntAndStringPatchOption(
|
||||
patchOption: option,
|
||||
removeOption: (option) {
|
||||
@ -68,7 +72,7 @@ class PatchOptionsView extends StatelessWidget {
|
||||
model.modifyOptions(value, option);
|
||||
},
|
||||
)
|
||||
else if (option.optionClassType == 'BooleanPatchOption')
|
||||
else if (option.valueType == 'Boolean')
|
||||
BooleanPatchOption(
|
||||
patchOption: option,
|
||||
removeOption: (option) {
|
||||
@ -78,10 +82,10 @@ class PatchOptionsView extends StatelessWidget {
|
||||
model.modifyOptions(value, option);
|
||||
},
|
||||
)
|
||||
else if (option.optionClassType ==
|
||||
'StringListPatchOption' ||
|
||||
option.optionClassType == 'IntListPatchOption' ||
|
||||
option.optionClassType == 'LongListPatchOption')
|
||||
else if (option.valueType ==
|
||||
'StringArray' ||
|
||||
option.valueType == 'IntArray' ||
|
||||
option.valueType == 'LongArray')
|
||||
IntStringLongListPatchOption(
|
||||
patchOption: option,
|
||||
removeOption: (option) {
|
||||
|
@ -62,7 +62,10 @@ class PatchOptionsViewModel extends BaseViewModel {
|
||||
for (final Option option in options) {
|
||||
if (!visibleOptions.any((vOption) => vOption.key == option.key)) {
|
||||
_managerAPI.clearPatchOption(
|
||||
selectedApp, _managerAPI.selectedPatch!.name, option.key);
|
||||
selectedApp,
|
||||
_managerAPI.selectedPatch!.name,
|
||||
option.key,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (final Option option in visibleOptions) {
|
||||
@ -70,7 +73,10 @@ class PatchOptionsViewModel extends BaseViewModel {
|
||||
requiredNullOptions.add(option);
|
||||
} else {
|
||||
_managerAPI.setPatchOption(
|
||||
option, _managerAPI.selectedPatch!.name, selectedApp);
|
||||
option,
|
||||
_managerAPI.selectedPatch!.name,
|
||||
selectedApp,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (requiredNullOptions.isNotEmpty) {
|
||||
@ -89,7 +95,8 @@ class PatchOptionsViewModel extends BaseViewModel {
|
||||
final Option modifiedOption = Option(
|
||||
title: option.title,
|
||||
description: option.description,
|
||||
optionClassType: option.optionClassType,
|
||||
values: option.values,
|
||||
valueType: option.valueType,
|
||||
value: value,
|
||||
required: option.required,
|
||||
key: option.key,
|
||||
@ -107,7 +114,8 @@ class PatchOptionsViewModel extends BaseViewModel {
|
||||
final Option defaultOption = Option(
|
||||
title: option.title,
|
||||
description: option.description,
|
||||
optionClassType: option.optionClassType,
|
||||
values: option.values,
|
||||
valueType: option.valueType,
|
||||
value: option.value is List ? option.value.toList() : option.value,
|
||||
required: option.required,
|
||||
key: option.key,
|
||||
@ -147,7 +155,7 @@ class PatchOptionsViewModel extends BaseViewModel {
|
||||
),
|
||||
actions: [
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
label: I18nText('cancelButton'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
@ -165,30 +173,36 @@ class PatchOptionsViewModel extends BaseViewModel {
|
||||
.map((e) {
|
||||
return CustomCard(
|
||||
padding: const EdgeInsets.all(4),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
onTap: () {
|
||||
addOption(e);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
e.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
e.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
e.description,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
e.description,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -229,7 +243,10 @@ Future<void> showRequiredOptionNullDialog(
|
||||
locator<PatcherViewModel>().notifyListeners();
|
||||
for (final option in options) {
|
||||
managerAPI.clearPatchOption(
|
||||
selectedApp, managerAPI.selectedPatch!.name, option.key);
|
||||
selectedApp,
|
||||
managerAPI.selectedPatch!.name,
|
||||
option.key,
|
||||
);
|
||||
}
|
||||
Navigator.of(context)
|
||||
..pop()
|
||||
|
@ -80,7 +80,8 @@ class PatcherViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
bool checkRequiredPatchOption(BuildContext context) {
|
||||
if (getNullRequiredOptions(selectedPatches, selectedApp!.packageName).isNotEmpty) {
|
||||
if (getNullRequiredOptions(selectedPatches, selectedApp!.packageName)
|
||||
.isNotEmpty) {
|
||||
showRequiredOptionDialog(context);
|
||||
return false;
|
||||
}
|
||||
@ -190,7 +191,7 @@ class PatcherViewModel extends BaseViewModel {
|
||||
this.selectedPatches.clear();
|
||||
this.selectedPatches.addAll(patches.where((patch) => !patch.excluded));
|
||||
}
|
||||
if (!_managerAPI.areExperimentalPatchesEnabled()) {
|
||||
if (_managerAPI.isVersionCompatibilityCheckEnabled()) {
|
||||
this.selectedPatches.removeWhere((patch) => !isPatchSupported(patch));
|
||||
}
|
||||
if (!_managerAPI.areUniversalPatchesEnabled()) {
|
||||
@ -199,11 +200,12 @@ class PatcherViewModel extends BaseViewModel {
|
||||
.removeWhere((patch) => patch.compatiblePackages.isEmpty);
|
||||
}
|
||||
final usedPatches = _managerAPI.getUsedPatches(selectedApp!.packageName);
|
||||
for (final patch in usedPatches){
|
||||
if (!patches.any((p) => p.name == patch.name)){
|
||||
for (final patch in usedPatches) {
|
||||
if (!patches.any((p) => p.name == patch.name)) {
|
||||
removedPatches.add('• ${patch.name}');
|
||||
for (final option in patch.options) {
|
||||
_managerAPI.clearPatchOption(selectedApp!.packageName, patch.name, option.key);
|
||||
_managerAPI.clearPatchOption(
|
||||
selectedApp!.packageName, patch.name, option.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,7 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
class PatchesSelectorView extends StatefulWidget {
|
||||
@ -62,12 +59,10 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
floating: true,
|
||||
title: I18nText(
|
||||
'patchesSelectorView.viewTitle',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 22.0,
|
||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: Icon(
|
||||
@ -80,41 +75,34 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 12, bottom: 12),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.tertiary
|
||||
.withOpacity(0.5),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
model.patchesVersion!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).colorScheme.tertiary.withOpacity(0.5),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
model.patchesVersion!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
CustomPopupMenu(
|
||||
onSelected: (value) =>
|
||||
{model.onMenuSelection(value, context)},
|
||||
children: {
|
||||
0: I18nText(
|
||||
'patchesSelectorView.loadPatchesSelection',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
PopupMenuButton(
|
||||
onSelected: (value) {
|
||||
model.onMenuSelection(value, context);
|
||||
},
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
PopupMenuItem(
|
||||
value: 0,
|
||||
child: I18nText(
|
||||
'patchesSelectorView.loadPatchesSelection',
|
||||
),
|
||||
),
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
@ -192,185 +180,39 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (model.newPatchExists())
|
||||
if (model.getQueriedPatches(_query).any((patch) => model.isPatchNew(patch)))
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10.0,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10.0,
|
||||
bottom: 10.0,
|
||||
left: 5.0,
|
||||
),
|
||||
child: I18nText(
|
||||
'patchesSelectorView.newPatches',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
model.getPatchCategory(context, 'patchesSelectorView.newPatches'),
|
||||
...model.getQueriedPatches(_query).map((patch) {
|
||||
if (model.isPatchNew(patch)) {
|
||||
return PatchItem(
|
||||
name: patch.name,
|
||||
simpleName: patch.getSimpleName(),
|
||||
description: patch.description ?? '',
|
||||
packageVersion:
|
||||
model.getAppInfo().version,
|
||||
supportedPackageVersions:
|
||||
model.getSupportedVersions(patch),
|
||||
isUnsupported: !isPatchSupported(patch),
|
||||
isChangeEnabled:
|
||||
_managerAPI.isPatchesChangeEnabled(),
|
||||
hasUnsupportedPatchOption:
|
||||
hasUnsupportedRequiredOption(
|
||||
patch.options,
|
||||
patch,
|
||||
),
|
||||
options: patch.options,
|
||||
isSelected: model.isSelected(patch),
|
||||
navigateToOptions: (options) =>
|
||||
model.navigateToPatchOptions(
|
||||
options,
|
||||
patch,
|
||||
),
|
||||
onChanged: (value) => model.selectPatch(
|
||||
patch,
|
||||
value,
|
||||
context,
|
||||
),
|
||||
);
|
||||
return model.getPatchItem(context, patch);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
}),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10.0,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10.0,
|
||||
bottom: 10.0,
|
||||
left: 5.0,
|
||||
),
|
||||
child: I18nText(
|
||||
'patchesSelectorView.patches',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (model.getQueriedPatches(_query).any((patch) => !model.isPatchNew(patch) && patch.compatiblePackages.isNotEmpty))
|
||||
model.getPatchCategory(context, 'patchesSelectorView.patches'),
|
||||
],
|
||||
),
|
||||
...model.getQueriedPatches(_query).map(
|
||||
(patch) {
|
||||
if (patch.compatiblePackages.isNotEmpty) {
|
||||
return PatchItem(
|
||||
name: patch.name,
|
||||
simpleName: patch.getSimpleName(),
|
||||
description: patch.description ?? '',
|
||||
packageVersion: model.getAppInfo().version,
|
||||
supportedPackageVersions:
|
||||
model.getSupportedVersions(patch),
|
||||
isUnsupported: !isPatchSupported(patch),
|
||||
isChangeEnabled:
|
||||
_managerAPI.isPatchesChangeEnabled(),
|
||||
hasUnsupportedPatchOption:
|
||||
hasUnsupportedRequiredOption(
|
||||
patch.options, patch),
|
||||
options: patch.options,
|
||||
isSelected: model.isSelected(patch),
|
||||
navigateToOptions: (options) =>
|
||||
model.navigateToPatchOptions(
|
||||
options,
|
||||
patch,
|
||||
),
|
||||
onChanged: (value) => model.selectPatch(
|
||||
patch,
|
||||
value,
|
||||
context,
|
||||
),
|
||||
);
|
||||
if (patch.compatiblePackages.isNotEmpty && !model.isPatchNew(patch)) {
|
||||
return model.getPatchItem(context, patch);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
if (_managerAPI.areUniversalPatchesEnabled())
|
||||
if (model.getQueriedPatches(_query).any((patch) => patch.compatiblePackages.isEmpty))
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10.0,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10.0,
|
||||
bottom: 10.0,
|
||||
left: 5.0,
|
||||
),
|
||||
child: I18nText(
|
||||
'patchesSelectorView.universalPatches',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
model.getPatchCategory(context, 'patchesSelectorView.universalPatches'),
|
||||
...model.getQueriedPatches(_query).map((patch) {
|
||||
if (patch.compatiblePackages.isEmpty) {
|
||||
return PatchItem(
|
||||
name: patch.name,
|
||||
simpleName: patch.getSimpleName(),
|
||||
description: patch.description ?? '',
|
||||
packageVersion:
|
||||
model.getAppInfo().version,
|
||||
supportedPackageVersions:
|
||||
model.getSupportedVersions(patch),
|
||||
isUnsupported: !isPatchSupported(patch),
|
||||
isChangeEnabled:
|
||||
_managerAPI.isPatchesChangeEnabled(),
|
||||
hasUnsupportedPatchOption:
|
||||
hasUnsupportedRequiredOption(
|
||||
patch.options,
|
||||
patch,
|
||||
),
|
||||
options: patch.options,
|
||||
isSelected: model.isSelected(patch),
|
||||
navigateToOptions: (options) =>
|
||||
model.navigateToPatchOptions(
|
||||
options,
|
||||
patch,
|
||||
),
|
||||
onChanged: (value) => model.selectPatch(
|
||||
patch,
|
||||
value,
|
||||
context,
|
||||
),
|
||||
);
|
||||
if (patch.compatiblePackages.isEmpty && !model.isPatchNew(patch)) {
|
||||
return model.getPatchItem(context, patch);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/patcher_api.dart';
|
||||
import 'package:revanced_manager/services/toast.dart';
|
||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
@ -169,7 +170,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
.where(
|
||||
(element) =>
|
||||
!element.excluded &&
|
||||
(_managerAPI.areExperimentalPatchesEnabled() ||
|
||||
(!_managerAPI.isVersionCompatibilityCheckEnabled() ||
|
||||
isPatchSupported(element)),
|
||||
),
|
||||
);
|
||||
@ -209,7 +210,10 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
query.isEmpty ||
|
||||
query.length < 2 ||
|
||||
patch.name.toLowerCase().contains(query.toLowerCase()) ||
|
||||
patch.getSimpleName().toLowerCase().contains(query.toLowerCase()),
|
||||
patch.name
|
||||
.replaceAll(RegExp(r'[^\w\s]+'), '')
|
||||
.toLowerCase()
|
||||
.contains(query.toLowerCase()),
|
||||
)
|
||||
.toList();
|
||||
if (_managerAPI.areUniversalPatchesEnabled()) {
|
||||
@ -221,6 +225,57 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
Widget getPatchItem(BuildContext context, Patch patch) {
|
||||
return PatchItem(
|
||||
name: patch.name,
|
||||
simpleName: patch.getSimpleName(),
|
||||
description: patch.description ?? '',
|
||||
packageVersion: getAppInfo().version,
|
||||
supportedPackageVersions: getSupportedVersions(patch),
|
||||
isUnsupported: !isPatchSupported(patch),
|
||||
isChangeEnabled: _managerAPI.isPatchesChangeEnabled(),
|
||||
hasUnsupportedPatchOption: hasUnsupportedRequiredOption(
|
||||
patch.options,
|
||||
patch,
|
||||
),
|
||||
options: patch.options,
|
||||
isSelected: isSelected(patch),
|
||||
navigateToOptions: (options) => navigateToPatchOptions(
|
||||
options,
|
||||
patch,
|
||||
),
|
||||
onChanged: (value) => selectPatch(
|
||||
patch,
|
||||
value,
|
||||
context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget getPatchCategory(BuildContext context, String category) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10.0,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10.0,
|
||||
bottom: 10.0,
|
||||
left: 5.0,
|
||||
),
|
||||
child: I18nText(
|
||||
category,
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
PatchedApplication getAppInfo() {
|
||||
return locator<PatcherViewModel>().selectedApp!;
|
||||
}
|
||||
@ -236,12 +291,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
bool newPatchExists() {
|
||||
return patches.any(
|
||||
(patch) => isPatchNew(patch),
|
||||
);
|
||||
}
|
||||
|
||||
List<String> getSupportedVersions(Patch patch) {
|
||||
final PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
|
||||
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||
@ -281,7 +330,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
this.selectedPatches.addAll(
|
||||
patches.where((patch) => selectedPatches.contains(patch.name)),
|
||||
);
|
||||
if (!_managerAPI.areExperimentalPatchesEnabled()) {
|
||||
if (_managerAPI.isVersionCompatibilityCheckEnabled()) {
|
||||
this.selectedPatches.removeWhere((patch) => !isPatchSupported(patch));
|
||||
}
|
||||
} else {
|
||||
|
@ -67,6 +67,7 @@ class SManageApiUrl extends BaseViewModel {
|
||||
apiUrl = 'https://$apiUrl';
|
||||
}
|
||||
_managerAPI.setApiUrl(apiUrl);
|
||||
_toast.showBottom('settingsView.restartAppForChanges');
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
|
@ -6,46 +6,98 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
final _settingViewModel = SettingsViewModel();
|
||||
class SUpdateThemeUI extends StatefulWidget {
|
||||
const SUpdateThemeUI({super.key});
|
||||
|
||||
// ignore: constant_identifier_names
|
||||
const int ANDROID_12_SDK_VERSION = 31;
|
||||
@override
|
||||
State<SUpdateThemeUI> createState() => _SUpdateThemeUIState();
|
||||
}
|
||||
|
||||
class SUpdateTheme extends BaseViewModel {
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
|
||||
final ManagerAPI managerAPI = locator<ManagerAPI>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SettingsSection(
|
||||
title: 'settingsView.appearanceSectionTitle',
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.themeModeLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
trailing: CustomMaterialButton(
|
||||
label: getThemeModeName(),
|
||||
onPressed: () => {showThemeDialog(context)},
|
||||
),
|
||||
onTap: () => {showThemeDialog(context)},
|
||||
),
|
||||
if (managerAPI.isDynamicThemeAvailable)
|
||||
SwitchListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.dynamicThemeLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.dynamicThemeHint'),
|
||||
value: getDynamicThemeStatus(),
|
||||
onChanged: (value) => {
|
||||
setUseDynamicTheme(
|
||||
context,
|
||||
value,
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
bool getDynamicThemeStatus() {
|
||||
return _managerAPI.getUseDynamicTheme();
|
||||
return managerAPI.getUseDynamicTheme();
|
||||
}
|
||||
|
||||
Future<void> setUseDynamicTheme(BuildContext context, bool value) async {
|
||||
await _managerAPI.setUseDynamicTheme(value);
|
||||
await managerAPI.setUseDynamicTheme(value);
|
||||
final int currentTheme = (DynamicTheme.of(context)!.themeId ~/ 2) * 2;
|
||||
await DynamicTheme.of(context)!.setTheme(currentTheme + (value ? 1 : 0));
|
||||
notifyListeners();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
int getThemeMode() {
|
||||
return _managerAPI.getThemeMode();
|
||||
return managerAPI.getThemeMode();
|
||||
}
|
||||
|
||||
Future<void> setThemeMode(BuildContext context, int value) async {
|
||||
await _managerAPI.setThemeMode(value);
|
||||
await managerAPI.setThemeMode(value);
|
||||
final bool isDynamicTheme = DynamicTheme.of(context)!.themeId.isEven;
|
||||
await DynamicTheme.of(context)!.setTheme(value * 2 + (isDynamicTheme ? 0 : 1));
|
||||
final bool isLight = value != 2 && (value == 1 || DynamicTheme.of(context)!.theme.brightness == Brightness.light);
|
||||
await DynamicTheme.of(context)!
|
||||
.setTheme(value * 2 + (isDynamicTheme ? 0 : 1));
|
||||
final bool isLight = value != 2 &&
|
||||
(value == 1 ||
|
||||
DynamicTheme.of(context)!.theme.brightness == Brightness.light);
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
SystemUiOverlayStyle(
|
||||
systemNavigationBarIconBrightness:
|
||||
isLight ? Brightness.dark : Brightness.light,
|
||||
isLight ? Brightness.dark : Brightness.light,
|
||||
),
|
||||
);
|
||||
notifyListeners();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
I18nText getThemeModeName() {
|
||||
@ -131,63 +183,3 @@ class SUpdateTheme extends BaseViewModel {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final sUpdateTheme = SUpdateTheme();
|
||||
class SUpdateThemeUI extends StatelessWidget {
|
||||
const SUpdateThemeUI({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SettingsSection(
|
||||
title: 'settingsView.appearanceSectionTitle',
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.themeModeLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
trailing: CustomMaterialButton(
|
||||
label: sUpdateTheme.getThemeModeName(),
|
||||
onPressed: () => { sUpdateTheme.showThemeDialog(context) },
|
||||
),
|
||||
onTap: () => { sUpdateTheme.showThemeDialog(context) },
|
||||
),
|
||||
FutureBuilder<int>(
|
||||
future: _settingViewModel.getSdkVersion(),
|
||||
builder: (context, snapshot) => Visibility(
|
||||
visible:
|
||||
snapshot.hasData && snapshot.data! >= ANDROID_12_SDK_VERSION,
|
||||
child: SwitchListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.dynamicThemeLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.dynamicThemeHint'),
|
||||
value: _settingViewModel.sUpdateTheme.getDynamicThemeStatus(),
|
||||
onChanged: (value) => {
|
||||
_settingViewModel.sUpdateTheme.setUseDynamicTheme(
|
||||
context,
|
||||
value,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,8 @@ import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_advanced_section.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_debug_section.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_export_section.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_info_section.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_team_section.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
@ -40,16 +40,24 @@ class SettingsView extends StatelessWidget {
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate.fixed(
|
||||
<Widget>[
|
||||
SUpdateThemeUI(),
|
||||
// SUpdateLanguageUI(),
|
||||
// _settingsDivider,
|
||||
STeamSection(),
|
||||
_settingsDivider,
|
||||
SAdvancedSection(),
|
||||
_settingsDivider,
|
||||
SExportSection(),
|
||||
_settingsDivider,
|
||||
SInfoSection(),
|
||||
ListView(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: const [
|
||||
SUpdateThemeUI(),
|
||||
// _settingsDivider,
|
||||
// SUpdateLanguageUI(),
|
||||
_settingsDivider,
|
||||
SAdvancedSection(),
|
||||
_settingsDivider,
|
||||
SExportSection(),
|
||||
_settingsDivider,
|
||||
STeamSection(),
|
||||
_settingsDivider,
|
||||
SDebugSection(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1,9 +1,7 @@
|
||||
import 'dart:io';
|
||||
import 'package:cr_file_saver/file_saver.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:logcat/logcat.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@ -14,9 +12,8 @@ import 'package:revanced_manager/services/toast.dart';
|
||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_language.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
import 'package:share_extend/share_extend.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
|
||||
@ -29,7 +26,6 @@ class SettingsViewModel extends BaseViewModel {
|
||||
final Toast _toast = locator<Toast>();
|
||||
|
||||
final SUpdateLanguage sUpdateLanguage = SUpdateLanguage();
|
||||
final SUpdateTheme sUpdateTheme = SUpdateTheme();
|
||||
|
||||
void navigateToContributors() {
|
||||
_navigationService.navigateTo(Routes.contributorsView);
|
||||
@ -135,12 +131,12 @@ class SettingsViewModel extends BaseViewModel {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool areExperimentalPatchesEnabled() {
|
||||
return _managerAPI.areExperimentalPatchesEnabled();
|
||||
bool isVersionCompatibilityCheckEnabled() {
|
||||
return _managerAPI.isVersionCompatibilityCheckEnabled();
|
||||
}
|
||||
|
||||
void useExperimentalPatches(bool value) {
|
||||
_managerAPI.enableExperimentalPatchesStatus(value);
|
||||
void useVersionCompatibilityCheck(bool value) {
|
||||
_managerAPI.enableVersionCompatibilityCheckStatus(value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@ -162,10 +158,10 @@ class SettingsViewModel extends BaseViewModel {
|
||||
if (outFile.existsSync()) {
|
||||
final String dateTime =
|
||||
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
||||
await CRFileSaver.saveFileWithDialog(
|
||||
SaveFileDialogParams(
|
||||
await FlutterFileDialog.saveFile(
|
||||
params: SaveFileDialogParams(
|
||||
sourceFilePath: outFile.path,
|
||||
destinationFileName: 'selected_patches_$dateTime.json',
|
||||
fileName: 'selected_patches_$dateTime.json',
|
||||
),
|
||||
);
|
||||
_toast.showBottom('settingsView.exportedPatches');
|
||||
@ -182,12 +178,13 @@ class SettingsViewModel extends BaseViewModel {
|
||||
Future<void> importPatches(BuildContext context) async {
|
||||
if (isPatchesChangeEnabled()) {
|
||||
try {
|
||||
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ['json'],
|
||||
final String? result = await FlutterFileDialog.pickFile(
|
||||
params: const OpenFileDialogParams(
|
||||
fileExtensionsFilter: ['json'],
|
||||
),
|
||||
);
|
||||
if (result != null && result.files.single.path != null) {
|
||||
final File inFile = File(result.files.single.path!);
|
||||
if (result != null) {
|
||||
final File inFile = File(result);
|
||||
inFile.copySync(_managerAPI.storedPatchesFile);
|
||||
inFile.delete();
|
||||
if (_patcherViewModel.selectedApp != null) {
|
||||
@ -212,10 +209,10 @@ class SettingsViewModel extends BaseViewModel {
|
||||
if (outFile.existsSync()) {
|
||||
final String dateTime =
|
||||
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
||||
await CRFileSaver.saveFileWithDialog(
|
||||
SaveFileDialogParams(
|
||||
await FlutterFileDialog.saveFile(
|
||||
params: SaveFileDialogParams(
|
||||
sourceFilePath: outFile.path,
|
||||
destinationFileName: 'keystore_$dateTime.keystore',
|
||||
fileName: 'keystore_$dateTime.keystore',
|
||||
),
|
||||
);
|
||||
_toast.showBottom('settingsView.exportedKeystore');
|
||||
@ -231,9 +228,9 @@ class SettingsViewModel extends BaseViewModel {
|
||||
|
||||
Future<void> importKeystore() async {
|
||||
try {
|
||||
final FilePickerResult? result = await FilePicker.platform.pickFiles();
|
||||
if (result != null && result.files.single.path != null) {
|
||||
final File inFile = File(result.files.single.path!);
|
||||
final String? result = await FlutterFileDialog.pickFile();
|
||||
if (result != null) {
|
||||
final File inFile = File(result);
|
||||
inFile.copySync(_managerAPI.keystoreFile);
|
||||
|
||||
_toast.showBottom('settingsView.importedKeystore');
|
||||
@ -256,11 +253,6 @@ class SettingsViewModel extends BaseViewModel {
|
||||
_toast.showBottom('settingsView.resetStoredPatches');
|
||||
}
|
||||
|
||||
Future<int> getSdkVersion() async {
|
||||
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
|
||||
return info.version.sdkInt;
|
||||
}
|
||||
|
||||
Future<void> deleteLogs() async {
|
||||
final Directory appCacheDir = await getTemporaryDirectory();
|
||||
final Directory logsDir = Directory('${appCacheDir.path}/logs');
|
||||
@ -284,6 +276,6 @@ class SettingsViewModel extends BaseViewModel {
|
||||
File('${logDir.path}/revanced-manager_logcat_$dateTime.log');
|
||||
final String logs = await Logcat.execute();
|
||||
logcat.writeAsStringSync(logs);
|
||||
ShareExtend.share(logcat.path, 'file');
|
||||
await Share.shareXFiles([XFile(logcat.path)]);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/file.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/services/download_manager.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
@ -58,7 +58,7 @@ class _ContributorsCardState extends State<ContributorsCard> {
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
child: FutureBuilder<File?>(
|
||||
future: DefaultCacheManager().getSingleFile(
|
||||
future: DownloadManager().getSingleFile(
|
||||
widget.contributors[index]['avatar_url'],
|
||||
),
|
||||
builder: (context, snapshot) => snapshot.hasData
|
||||
|
@ -75,7 +75,7 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ReVanced Patches
|
||||
// Patches
|
||||
CustomCard(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@ -84,7 +84,7 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const Text('ReVanced Patches'),
|
||||
const Text('Patches'),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
|
@ -48,13 +48,13 @@ class _PatchItemState extends State<PatchItem> {
|
||||
Widget build(BuildContext context) {
|
||||
widget.isSelected = widget.isSelected &&
|
||||
(!widget.isUnsupported ||
|
||||
widget._managerAPI.areExperimentalPatchesEnabled()) &&
|
||||
!widget._managerAPI.isVersionCompatibilityCheckEnabled()) &&
|
||||
!widget.hasUnsupportedPatchOption;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Opacity(
|
||||
opacity: widget.isUnsupported &&
|
||||
widget._managerAPI.areExperimentalPatchesEnabled() == false
|
||||
widget._managerAPI.isVersionCompatibilityCheckEnabled() == true
|
||||
? 0.5
|
||||
: 1,
|
||||
child: CustomCard(
|
||||
@ -66,7 +66,7 @@ class _PatchItemState extends State<PatchItem> {
|
||||
),
|
||||
onTap: () {
|
||||
if (widget.isUnsupported &&
|
||||
!widget._managerAPI.areExperimentalPatchesEnabled()) {
|
||||
widget._managerAPI.isVersionCompatibilityCheckEnabled()) {
|
||||
widget.isSelected = false;
|
||||
widget.toast.showBottom('patchItem.unsupportedPatchVersion');
|
||||
} else if (widget.isChangeEnabled) {
|
||||
@ -80,7 +80,7 @@ class _PatchItemState extends State<PatchItem> {
|
||||
setState(() {});
|
||||
}
|
||||
if (!widget.isUnsupported ||
|
||||
widget._managerAPI.areExperimentalPatchesEnabled()) {
|
||||
!widget._managerAPI.isVersionCompatibilityCheckEnabled()) {
|
||||
widget.onChanged(widget.isSelected);
|
||||
}
|
||||
},
|
||||
@ -99,7 +99,8 @@ class _PatchItemState extends State<PatchItem> {
|
||||
),
|
||||
onChanged: (newValue) {
|
||||
if (widget.isUnsupported &&
|
||||
!widget._managerAPI.areExperimentalPatchesEnabled()) {
|
||||
widget._managerAPI
|
||||
.isVersionCompatibilityCheckEnabled()) {
|
||||
widget.isSelected = false;
|
||||
widget.toast.showBottom(
|
||||
'patchItem.unsupportedPatchVersion',
|
||||
@ -115,7 +116,8 @@ class _PatchItemState extends State<PatchItem> {
|
||||
setState(() {});
|
||||
}
|
||||
if (!widget.isUnsupported ||
|
||||
widget._managerAPI.areExperimentalPatchesEnabled()) {
|
||||
!widget._managerAPI
|
||||
.isVersionCompatibilityCheckEnabled()) {
|
||||
widget.onChanged(widget.isSelected);
|
||||
}
|
||||
},
|
||||
@ -155,8 +157,8 @@ class _PatchItemState extends State<PatchItem> {
|
||||
runSpacing: 4,
|
||||
children: [
|
||||
if (widget.isUnsupported &&
|
||||
widget._managerAPI
|
||||
.areExperimentalPatchesEnabled())
|
||||
!widget._managerAPI
|
||||
.isVersionCompatibilityCheckEnabled())
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: TextButton.icon(
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||
@ -59,13 +59,27 @@ class IntAndStringPatchOption extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ValueNotifier patchOptionValue = ValueNotifier(patchOption.value);
|
||||
String getKey() {
|
||||
if (patchOption.value != null && patchOption.values != null) {
|
||||
final List values = patchOption.values!.entries
|
||||
.where((e) => e.value == patchOption.value)
|
||||
.toList();
|
||||
if (values.isNotEmpty) {
|
||||
return values.first.key;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return PatchOption(
|
||||
widget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextFieldForPatchOption(
|
||||
value: patchOption.value,
|
||||
optionType: patchOption.optionClassType,
|
||||
values: patchOption.values,
|
||||
optionType: patchOption.valueType,
|
||||
selectedKey: getKey(),
|
||||
onChanged: (value) {
|
||||
patchOptionValue.value = value;
|
||||
onChanged(value, patchOption);
|
||||
@ -77,7 +91,7 @@ class IntAndStringPatchOption extends StatelessWidget {
|
||||
if (patchOption.required && value == null) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children:[
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
I18nText(
|
||||
'patchOptionsView.requiredOption',
|
||||
@ -119,17 +133,41 @@ class IntStringLongListPatchOption extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final String type = patchOption.optionClassType;
|
||||
final List<dynamic> values = patchOption.value ?? [];
|
||||
final List<dynamic> values = List.from(patchOption.value ?? []);
|
||||
final ValueNotifier patchOptionValue = ValueNotifier(values);
|
||||
final String type = patchOption.valueType;
|
||||
|
||||
String getKey(dynamic value) {
|
||||
if (value != null && patchOption.values != null) {
|
||||
final List values = patchOption.values!.entries
|
||||
.where((e) => e.value.toString() == value)
|
||||
.toList();
|
||||
if (values.isNotEmpty) {
|
||||
return values.first.key;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
bool isCustomValue() {
|
||||
if (values.length == 1 && patchOption.values != null) {
|
||||
if (getKey(values[0]) != '') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isTextFieldVisible = isCustomValue();
|
||||
|
||||
return PatchOption(
|
||||
widget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ValueListenableBuilder(
|
||||
valueListenable: patchOptionValue,
|
||||
builder: (context, value, child) {
|
||||
return ListView.builder(
|
||||
widget: ValueListenableBuilder(
|
||||
valueListenable: patchOptionValue,
|
||||
builder: (context, value, child) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: value.length,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
@ -137,54 +175,89 @@ class IntStringLongListPatchOption extends StatelessWidget {
|
||||
final e = values[index];
|
||||
return TextFieldForPatchOption(
|
||||
value: e.toString(),
|
||||
values: patchOption.values,
|
||||
optionType: type,
|
||||
selectedKey: value.length > 1 ? '' : getKey(e),
|
||||
showDropdown: index == 0,
|
||||
onChanged: (newValue) {
|
||||
values[index] = type == 'StringListPatchOption' ? newValue : type == 'IntListPatchOption' ? int.parse(newValue) : num.parse(newValue);
|
||||
if (newValue is List) {
|
||||
values.clear();
|
||||
isTextFieldVisible = false;
|
||||
values.add(newValue.toString());
|
||||
} else {
|
||||
isTextFieldVisible = true;
|
||||
if (values.length == 1 &&
|
||||
values[0].toString().startsWith('[') &&
|
||||
type.contains('Array')) {
|
||||
values.clear();
|
||||
values.addAll(patchOption.value);
|
||||
} else {
|
||||
values[index] = type == 'StringArray'
|
||||
? newValue
|
||||
: type == 'IntArray'
|
||||
? int.parse(
|
||||
newValue.toString().isEmpty
|
||||
? '0'
|
||||
: newValue.toString(),
|
||||
)
|
||||
: num.parse(
|
||||
newValue.toString().isEmpty
|
||||
? '0'
|
||||
: newValue.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
patchOptionValue.value = List.from(values);
|
||||
onChanged(values, patchOption);
|
||||
},
|
||||
removeValue: (value) {
|
||||
patchOptionValue.value = List.from(patchOptionValue.value)..removeAt(index);
|
||||
removeValue: () {
|
||||
patchOptionValue.value = List.from(patchOptionValue.value)
|
||||
..removeAt(index);
|
||||
values.removeAt(index);
|
||||
onChanged(values, patchOption);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
if (type == 'StringListPatchOption') {
|
||||
patchOptionValue.value = List.from(patchOptionValue.value)..add('');
|
||||
values.add('');
|
||||
} else {
|
||||
patchOptionValue.value = List.from(patchOptionValue.value)..add(0);
|
||||
values.add(0);
|
||||
}
|
||||
onChanged(values, patchOption);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.add, size: 20),
|
||||
I18nText(
|
||||
'add',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (isTextFieldVisible) ...[
|
||||
const SizedBox(height: 4),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
if (type == 'StringArray') {
|
||||
patchOptionValue.value =
|
||||
List.from(patchOptionValue.value)..add('');
|
||||
values.add('');
|
||||
} else {
|
||||
patchOptionValue.value =
|
||||
List.from(patchOptionValue.value)..add(0);
|
||||
values.add(0);
|
||||
}
|
||||
onChanged(values, patchOption);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.add, size: 20),
|
||||
I18nText(
|
||||
'add',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
patchOption: patchOption,
|
||||
removeOption: (Option option) {
|
||||
@ -196,6 +269,7 @@ class IntStringLongListPatchOption extends StatelessWidget {
|
||||
|
||||
class UnsupportedPatchOption extends StatelessWidget {
|
||||
const UnsupportedPatchOption({super.key, required this.patchOption});
|
||||
|
||||
final Option patchOption;
|
||||
|
||||
@override
|
||||
@ -239,7 +313,6 @@ class PatchOption extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: CustomCard(
|
||||
onTap: () {},
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@ -296,14 +369,20 @@ class TextFieldForPatchOption extends StatefulWidget {
|
||||
const TextFieldForPatchOption({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.values,
|
||||
this.removeValue,
|
||||
required this.onChanged,
|
||||
required this.optionType,
|
||||
required this.selectedKey,
|
||||
this.showDropdown = true,
|
||||
});
|
||||
|
||||
final String? value;
|
||||
final Map<String, dynamic>? values;
|
||||
final String optionType;
|
||||
final void Function(dynamic value)? removeValue;
|
||||
final String selectedKey;
|
||||
final bool showDropdown;
|
||||
final void Function()? removeValue;
|
||||
final void Function(dynamic value) onChanged;
|
||||
|
||||
@override
|
||||
@ -313,75 +392,155 @@ class TextFieldForPatchOption extends StatefulWidget {
|
||||
|
||||
class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
|
||||
final TextEditingController controller = TextEditingController();
|
||||
String? selectedKey;
|
||||
String? defaultValue;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool isStringOption = widget.optionType.contains('String');
|
||||
final bool isListOption = widget.optionType.contains('List');
|
||||
controller.text = widget.value ?? '';
|
||||
return TextFormField(
|
||||
inputFormatters: [
|
||||
if (widget.optionType.contains('Int'))
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
|
||||
if (widget.optionType.contains('Long'))
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^[0-9]*\.?[0-9]*')),
|
||||
],
|
||||
controller: controller,
|
||||
keyboardType: isStringOption ? TextInputType.text : TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: PopupMenuButton(
|
||||
tooltip: FlutterI18n.translate(
|
||||
context,
|
||||
'patchOptionsView.tooltip',
|
||||
final bool isArrayOption = widget.optionType.contains('Array');
|
||||
selectedKey ??= widget.selectedKey;
|
||||
controller.text = !isStringOption && isArrayOption && selectedKey == '' &&
|
||||
(widget.value != null && widget.value.toString().startsWith('['))
|
||||
? ''
|
||||
: widget.value ?? '';
|
||||
defaultValue ??= controller.text;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (widget.showDropdown && (widget.values?.isNotEmpty ?? false))
|
||||
DropdownButton<String>(
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
dropdownColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
isExpanded: true,
|
||||
value: selectedKey,
|
||||
items: widget.values!.entries
|
||||
.map(
|
||||
(e) => DropdownMenuItem(
|
||||
value: e.key,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: e.key,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: ' ${e.value}',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer
|
||||
.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
..add(
|
||||
DropdownMenuItem(
|
||||
value: '',
|
||||
child: I18nText(
|
||||
'patchOptionsView.customValue',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
if (value == '') {
|
||||
controller.text = defaultValue!;
|
||||
widget.onChanged(controller.text);
|
||||
} else {
|
||||
controller.text = widget.values![value].toString();
|
||||
widget.onChanged(
|
||||
isArrayOption ? widget.values![value] : controller.text,
|
||||
);
|
||||
}
|
||||
setState(() {
|
||||
selectedKey = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return [
|
||||
if (isListOption)
|
||||
PopupMenuItem(
|
||||
value: 'remove',
|
||||
child: I18nText('remove'),
|
||||
if (selectedKey == '')
|
||||
TextFormField(
|
||||
inputFormatters: [
|
||||
if (widget.optionType.contains('Int'))
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
|
||||
if (widget.optionType.contains('Long'))
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^[0-9]*\.?[0-9]*')),
|
||||
],
|
||||
controller: controller,
|
||||
keyboardType:
|
||||
isStringOption ? TextInputType.text : TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: PopupMenuButton(
|
||||
tooltip: FlutterI18n.translate(
|
||||
context,
|
||||
'patchOptionsView.tooltip',
|
||||
),
|
||||
if (isStringOption && !isListOption) ...[
|
||||
PopupMenuItem(
|
||||
value: 'patchOptionsView.selectFilePath',
|
||||
child: I18nText('patchOptionsView.selectFilePath'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'patchOptionsView.selectFolder',
|
||||
child: I18nText('patchOptionsView.selectFolder'),
|
||||
),
|
||||
],
|
||||
];
|
||||
},
|
||||
onSelected: (String selection) async {
|
||||
switch (selection) {
|
||||
case 'patchOptionsView.selectFilePath':
|
||||
final result = await FilePicker.platform.pickFiles();
|
||||
if (result != null && result.files.single.path != null) {
|
||||
controller.text = result.files.single.path.toString();
|
||||
widget.onChanged(controller.text);
|
||||
}
|
||||
break;
|
||||
case 'patchOptionsView.selectFolder':
|
||||
final result = await FilePicker.platform.getDirectoryPath();
|
||||
if (result != null) {
|
||||
controller.text = result;
|
||||
widget.onChanged(controller.text);
|
||||
}
|
||||
break;
|
||||
case 'remove':
|
||||
widget.removeValue!(widget.value);
|
||||
break;
|
||||
}
|
||||
},
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
onChanged: (String value) {
|
||||
widget.onChanged(value);
|
||||
},
|
||||
itemBuilder: (BuildContext context) {
|
||||
return [
|
||||
if (isArrayOption)
|
||||
PopupMenuItem(
|
||||
value: 'remove',
|
||||
child: I18nText('remove'),
|
||||
),
|
||||
if (isStringOption) ...[
|
||||
PopupMenuItem(
|
||||
value: 'patchOptionsView.selectFilePath',
|
||||
child: I18nText('patchOptionsView.selectFilePath'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'patchOptionsView.selectFolder',
|
||||
child: I18nText('patchOptionsView.selectFolder'),
|
||||
),
|
||||
],
|
||||
];
|
||||
},
|
||||
onSelected: (String selection) async {
|
||||
switch (selection) {
|
||||
case 'patchOptionsView.selectFilePath':
|
||||
final String? result = await FlutterFileDialog.pickFile();
|
||||
if (result != null) {
|
||||
controller.text = result;
|
||||
widget.onChanged(controller.text);
|
||||
}
|
||||
break;
|
||||
case 'patchOptionsView.selectFolder':
|
||||
final DirectoryLocation? result = await FlutterFileDialog.pickDirectory();
|
||||
if (result != null) {
|
||||
controller.text = result.toString();
|
||||
widget.onChanged(controller.text);
|
||||
}
|
||||
break;
|
||||
case 'remove':
|
||||
widget.removeValue!();
|
||||
break;
|
||||
}
|
||||
},
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
onChanged: (String value) {
|
||||
widget.onChanged(value);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,14 @@
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_api_url.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_sources.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_auto_update_patches.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_enable_patches_selection.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_section.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_universal_patches.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_version_compatibility_check.dart';
|
||||
|
||||
final _settingsViewModel = SettingsViewModel();
|
||||
|
||||
class SAdvancedSection extends StatelessWidget {
|
||||
const SAdvancedSection({super.key});
|
||||
@ -21,85 +17,14 @@ class SAdvancedSection extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return SettingsSection(
|
||||
title: 'settingsView.advancedSectionTitle',
|
||||
children: <Widget>[
|
||||
SManageApiUrlUI(),
|
||||
SManageSourcesUI(),
|
||||
// SManageKeystorePasswordUI(),
|
||||
children: const <Widget>[
|
||||
SAutoUpdatePatches(),
|
||||
SEnablePatchesSelection(),
|
||||
SExperimentalUniversalPatches(),
|
||||
SExperimentalPatches(),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.regenerateKeystoreLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.regenerateKeystoreHint'),
|
||||
onTap: () => _showDeleteKeystoreDialog(context),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.deleteTempDirLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.deleteTempDirHint'),
|
||||
onTap: () => _settingsViewModel.deleteTempDir(),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.deleteLogsLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.deleteLogsHint'),
|
||||
onTap: () => _settingsViewModel.deleteLogs(),
|
||||
),
|
||||
SVersionCompatibilityCheck(),
|
||||
SUniversalPatches(),
|
||||
SManageSourcesUI(),
|
||||
SManageApiUrlUI(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showDeleteKeystoreDialog(context) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('settingsView.regenerateKeystoreDialogTitle'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText('settingsView.regenerateKeystoreDialogText'),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('noButton'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
onPressed: () => {
|
||||
Navigator.of(context).pop(),
|
||||
_settingsViewModel.deleteKeystore(),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
68
lib/ui/widgets/settingsView/settings_debug_section.dart
Normal file
68
lib/ui/widgets/settingsView/settings_debug_section.dart
Normal file
@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/about_widget.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||
|
||||
final _settingsViewModel = SettingsViewModel();
|
||||
|
||||
class SDebugSection extends StatelessWidget {
|
||||
const SDebugSection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SettingsSection(
|
||||
title: 'settingsView.debugSectionTitle',
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.logsLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.logsHint'),
|
||||
onTap: () => _settingsViewModel.exportLogcatLogs(),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.deleteLogsLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.deleteLogsHint'),
|
||||
onTap: () => _settingsViewModel.deleteLogs(),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.deleteTempDirLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.deleteTempDirHint'),
|
||||
onTap: () => _settingsViewModel.deleteTempDir(),
|
||||
),
|
||||
const AboutWidget(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -45,42 +45,6 @@ class SExportSection extends StatelessWidget {
|
||||
subtitle: I18nText('settingsView.importPatchesHint'),
|
||||
onTap: () => _settingsViewModel.importPatches(context),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.exportKeystoreLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.exportKeystoreHint'),
|
||||
onTap: () => _settingsViewModel.exportKeystore(),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.importKeystoreLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.importKeystoreHint'),
|
||||
onTap: () async {
|
||||
await _settingsViewModel.importKeystore();
|
||||
final sManageKeystorePassword = SManageKeystorePassword();
|
||||
if (context.mounted) {
|
||||
sManageKeystorePassword.showKeystoreDialog(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
@ -121,6 +85,58 @@ class SExportSection extends StatelessWidget {
|
||||
_settingsViewModel.resetAllOptions,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.exportKeystoreLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.exportKeystoreHint'),
|
||||
onTap: () => _settingsViewModel.exportKeystore(),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.importKeystoreLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.importKeystoreHint'),
|
||||
onTap: () async {
|
||||
await _settingsViewModel.importKeystore();
|
||||
final sManageKeystorePassword = SManageKeystorePassword();
|
||||
if (context.mounted) {
|
||||
sManageKeystorePassword.showKeystoreDialog(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.regenerateKeystoreLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.regenerateKeystoreHint'),
|
||||
onTap: () => _showDeleteKeystoreDialog(context),
|
||||
),
|
||||
// SManageKeystorePasswordUI(),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -154,4 +170,29 @@ class SExportSection extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showDeleteKeystoreDialog(context) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('settingsView.regenerateKeystoreDialogTitle'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText('settingsView.regenerateKeystoreDialogText'),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('noButton'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
onPressed: () => {
|
||||
Navigator.of(context).pop(),
|
||||
_settingsViewModel.deleteKeystore(),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/about_widget.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||
|
||||
final _settingsViewModel = SettingsViewModel();
|
||||
|
||||
class SInfoSection extends StatelessWidget {
|
||||
const SInfoSection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SettingsSection(
|
||||
title: 'settingsView.infoSectionTitle',
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.logsLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.logsHint'),
|
||||
onTap: () => _settingsViewModel.exportLogcatLogs(),
|
||||
),
|
||||
const AboutWidget(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -4,26 +4,26 @@ import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
|
||||
class SExperimentalUniversalPatches extends StatefulWidget {
|
||||
const SExperimentalUniversalPatches({super.key});
|
||||
class SUniversalPatches extends StatefulWidget {
|
||||
const SUniversalPatches({super.key});
|
||||
|
||||
@override
|
||||
State<SExperimentalUniversalPatches> createState() =>
|
||||
_SExperimentalUniversalPatchesState();
|
||||
State<SUniversalPatches> createState() =>
|
||||
_SUniversalPatchesState();
|
||||
}
|
||||
|
||||
final _settingsViewModel = SettingsViewModel();
|
||||
final _patchesSelectorViewModel = PatchesSelectorViewModel();
|
||||
final _patcherViewModel = PatcherViewModel();
|
||||
|
||||
class _SExperimentalUniversalPatchesState
|
||||
extends State<SExperimentalUniversalPatches> {
|
||||
class _SUniversalPatchesState
|
||||
extends State<SUniversalPatches> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.experimentalUniversalPatchesLabel',
|
||||
'settingsView.universalPatchesLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
@ -32,7 +32,7 @@ class _SExperimentalUniversalPatchesState
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.experimentalUniversalPatchesHint'),
|
||||
subtitle: I18nText('settingsView.universalPatchesHint'),
|
||||
value: _settingsViewModel.areUniversalPatchesEnabled(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
@ -5,24 +5,24 @@ import 'package:revanced_manager/ui/views/patches_selector/patches_selector_view
|
||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||
|
||||
class SExperimentalPatches extends StatefulWidget {
|
||||
const SExperimentalPatches({super.key});
|
||||
class SVersionCompatibilityCheck extends StatefulWidget {
|
||||
const SVersionCompatibilityCheck({super.key});
|
||||
|
||||
@override
|
||||
State<SExperimentalPatches> createState() => _SExperimentalPatchesState();
|
||||
State<SVersionCompatibilityCheck> createState() => _SVersionCompatibilityCheckState();
|
||||
}
|
||||
|
||||
final _settingsViewModel = SettingsViewModel();
|
||||
final _patchesSelectorViewModel = PatchesSelectorViewModel();
|
||||
final _patcherViewModel = PatcherViewModel();
|
||||
|
||||
class _SExperimentalPatchesState extends State<SExperimentalPatches> {
|
||||
class _SVersionCompatibilityCheckState extends State<SVersionCompatibilityCheck> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.experimentalPatchesLabel',
|
||||
'settingsView.versionCompatibilityCheckLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
@ -31,11 +31,11 @@ class _SExperimentalPatchesState extends State<SExperimentalPatches> {
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.experimentalPatchesHint'),
|
||||
value: _settingsViewModel.areExperimentalPatchesEnabled(),
|
||||
subtitle: I18nText('settingsView.versionCompatibilityCheckHint'),
|
||||
value: _settingsViewModel.isVersionCompatibilityCheckEnabled(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_settingsViewModel.useExperimentalPatches(value);
|
||||
_settingsViewModel.useVersionCompatibilityCheck(value);
|
||||
});
|
||||
if (!value) {
|
||||
_patcherViewModel.selectedPatches
|
@ -4,6 +4,7 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/social_media_item.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_icon.dart';
|
||||
|
||||
class SocialMediaWidget extends StatelessWidget {
|
||||
const SocialMediaWidget({
|
||||
@ -42,11 +43,17 @@ class SocialMediaWidget extends StatelessWidget {
|
||||
child: const CustomCard(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
SocialMediaItem(
|
||||
icon: Icon(CustomIcon.revancedIcon),
|
||||
title: Text('Website'),
|
||||
subtitle: Text('revanced.app'),
|
||||
url: 'https://revanced.app',
|
||||
),
|
||||
SocialMediaItem(
|
||||
icon: FaIcon(FontAwesomeIcons.github),
|
||||
title: Text('GitHub'),
|
||||
subtitle: Text('github.com/revanced'),
|
||||
url: 'https://github.com/revanced',
|
||||
subtitle: Text('github.com/ReVanced'),
|
||||
url: 'https://github.com/ReVanced',
|
||||
),
|
||||
SocialMediaItem(
|
||||
icon: FaIcon(FontAwesomeIcons.discord),
|
||||
@ -67,10 +74,10 @@ class SocialMediaWidget extends StatelessWidget {
|
||||
url: 'https://reddit.com/r/revancedapp',
|
||||
),
|
||||
SocialMediaItem(
|
||||
icon: FaIcon(FontAwesomeIcons.twitter),
|
||||
title: Text('Twitter'),
|
||||
icon: FaIcon(FontAwesomeIcons.xTwitter),
|
||||
title: Text('X'),
|
||||
subtitle: Text('@revancedapp'),
|
||||
url: 'https://twitter.com/revancedapp',
|
||||
url: 'https://x.com/revancedapp',
|
||||
),
|
||||
SocialMediaItem(
|
||||
icon: FaIcon(FontAwesomeIcons.youtube),
|
||||
|
9
lib/ui/widgets/shared/custom_icon.dart
Normal file
9
lib/ui/widgets/shared/custom_icon.dart
Normal file
@ -0,0 +1,9 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class CustomIcon {
|
||||
CustomIcon._();
|
||||
|
||||
static const _kFontFam = 'CustomIcon';
|
||||
|
||||
static const IconData revancedIcon = IconData(0xe800, fontFamily: _kFontFam);
|
||||
}
|
@ -17,12 +17,12 @@ bool isPatchSupported(Patch patch) {
|
||||
bool hasUnsupportedRequiredOption(List<Option> options, Patch patch) {
|
||||
final List<String> requiredOptionsType = [];
|
||||
final List<String> supportedOptionsType = [
|
||||
'StringPatchOption',
|
||||
'BooleanPatchOption',
|
||||
'IntPatchOption',
|
||||
'StringListPatchOption',
|
||||
'IntListPatchOption',
|
||||
'LongListPatchOption',
|
||||
'String',
|
||||
'Boolean',
|
||||
'Int',
|
||||
'StringArray',
|
||||
'IntArray',
|
||||
'LongArray',
|
||||
];
|
||||
for (final Option option in options) {
|
||||
if (option.required &&
|
||||
@ -33,7 +33,7 @@ bool hasUnsupportedRequiredOption(List<Option> options, Patch patch) {
|
||||
patch.name,
|
||||
option.key,
|
||||
) == null) {
|
||||
requiredOptionsType.add(option.optionClassType);
|
||||
requiredOptionsType.add(option.valueType);
|
||||
}
|
||||
}
|
||||
for (final String optionType in requiredOptionsType) {
|
||||
|
7179
package-lock.json
generated
7179
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -1,10 +0,0 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@saithodev/semantic-release-backmerge": "^3.1.0",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"semantic-release": "^21.0.1",
|
||||
"semantic-release-export-data": "^1.0.1",
|
||||
"semantic-release-flutter-plugin": "^1.1.2"
|
||||
}
|
||||
}
|
72
pubspec.yaml
72
pubspec.yaml
@ -4,7 +4,7 @@ homepage: https://github.com/revanced/revanced-manager
|
||||
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.12.0+101200000
|
||||
version: 1.15.1+101500100
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
@ -12,71 +12,75 @@ environment:
|
||||
dependencies:
|
||||
animations: ^2.0.7
|
||||
collection: ^1.17.0
|
||||
cross_connectivity: ^3.0.5
|
||||
cr_file_saver:
|
||||
git:
|
||||
url: https://github.com/dhruvanbhalara/cr_file_saver
|
||||
ref: "fix/incorrect_file_name"
|
||||
device_apps:
|
||||
git:
|
||||
url: https://github.com/ponces/flutter_plugin_device_apps
|
||||
git: # switch back to ponces fork once https://github.com/ponces/flutter_plugin_device_apps/pull/1 is merged
|
||||
url: https://github.com/BenjaminHalko/flutter_plugin_device_apps
|
||||
ref: revanced-manager
|
||||
device_info_plus: ^8.1.0
|
||||
device_info_plus: ^9.1.0
|
||||
dynamic_color: ^1.6.3
|
||||
dio: ^5.0.0
|
||||
dynamic_themes: ^1.1.0
|
||||
expandable: ^5.0.1
|
||||
file_picker:
|
||||
git:
|
||||
url: https://github.com/alexmercerind/flutter_file_picker
|
||||
ref: master
|
||||
flex_color_scheme: ^7.0.1
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_background: ^1.2.0
|
||||
flutter_background:
|
||||
git: # remove once https://github.com/JulianAssmann/flutter_background/pull/79 is merged
|
||||
url: https://github.com/BenjaminHalko/flutter_background
|
||||
ref: specify-namespace
|
||||
flutter_cache_manager: ^3.3.0
|
||||
flutter_i18n: ^0.33.0
|
||||
flutter_local_notifications: ^13.0.0
|
||||
flutter_i18n: ^0.34.0
|
||||
flutter_local_notifications: ^16.1.0
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
flutter_svg: ^2.0.4
|
||||
fluttertoast: ^8.2.1
|
||||
fluttertoast:
|
||||
git: # remove once the next fluttertoast version is release (> 8.2.2)
|
||||
url: https://github.com/ponnamkarthik/FlutterToast
|
||||
ref: f4e7b4e1afc8c760eb5bac80f6a2e299906d83ca
|
||||
font_awesome_flutter: ^10.4.0
|
||||
get_it: 7.2.0
|
||||
google_fonts: ^4.0.3
|
||||
http: ^0.13.5
|
||||
get_it: ^7.6.4
|
||||
google_fonts: ^6.1.0
|
||||
http: ^1.1.0
|
||||
injectable: ^2.1.1
|
||||
intl: ^0.18.0
|
||||
json_annotation: ^4.8.0
|
||||
logcat:
|
||||
git:
|
||||
url: https://github.com/SuaMusica/logcat
|
||||
ref: feature/nullSafe
|
||||
package_info_plus: ^3.0.3
|
||||
url: https://github.com/BenjaminHalko/logcat
|
||||
ref: master
|
||||
package_info_plus: ^4.2.0
|
||||
path_provider: ^2.0.14
|
||||
permission_handler: ^10.2.0
|
||||
permission_handler: ^11.0.1
|
||||
pull_to_refresh: ^2.0.0
|
||||
root:
|
||||
git:
|
||||
url: https://github.com/EvadeMaster/root
|
||||
ref: 82803aa40f63cddff81c3e4d27ce8ce3e7c83f60
|
||||
share_extend: ^2.0.0
|
||||
url: https://github.com/validcube/root
|
||||
ref: 68e5678a535a2a3344828a14a39017fa74b9098c
|
||||
shared_preferences: ^2.1.0
|
||||
skeletons: ^0.0.3
|
||||
stacked: ^3.2.0
|
||||
stacked_generator: ^1.1.0
|
||||
stacked_services: ^1.0.0
|
||||
stacked_themes: ^0.3.10
|
||||
timeago: ^3.3.0
|
||||
timezone: ^0.9.0
|
||||
url_launcher: ^6.1.10
|
||||
wakelock: ^0.6.2
|
||||
flutter_dotenv: ^5.0.2
|
||||
flutter_markdown: ^0.6.14
|
||||
dio_cache_interceptor: ^3.4.0
|
||||
install_plugin: ^2.1.0
|
||||
screenshot_callback: ^3.0.1
|
||||
install_plugin:
|
||||
git: # remove once https://github.com/hui-z/flutter_install_plugin/pull/67 is merged
|
||||
url: https://github.com/BenjaminHalko/flutter_install_plugin
|
||||
ref: master
|
||||
screenshot_callback:
|
||||
git: # remove once https://github.com/flutter-moum/flutter_screenshot_callback/pull/81 is merged
|
||||
url: https://github.com/BenjaminHalko/flutter_screenshot_callback
|
||||
ref: master
|
||||
synchronized: ^3.1.0
|
||||
connectivity_plus: ^5.0.1
|
||||
flutter_file_dialog: ^3.0.2
|
||||
wakelock_plus: ^1.1.3
|
||||
share_plus: ^7.2.1
|
||||
|
||||
dev_dependencies:
|
||||
json_serializable: ^6.6.1
|
||||
@ -85,11 +89,15 @@ dev_dependencies:
|
||||
flutter_lints: ^2.0.1
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
injectable_generator: ^2.1.5
|
||||
injectable_generator: ^2.1.5
|
||||
|
||||
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
fonts:
|
||||
- family: CustomIcon
|
||||
fonts:
|
||||
- asset: fonts/custom-icons.ttf
|
||||
assets:
|
||||
- assets/i18n/
|
||||
|
Reference in New Issue
Block a user