chore: Merge branch dev to main (#1446)

This commit is contained in:
oSumAtrIX 2023-11-04 22:10:14 +01:00 committed by GitHub
commit e1c6f65b7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 587 additions and 473 deletions

View File

@ -23,7 +23,7 @@ jobs:
- name: Setup JDK - name: Setup JDK
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
java-version: '11' java-version: '17'
distribution: 'zulu' distribution: 'zulu'
- name: Setup Flutter - name: Setup Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2

View File

@ -12,10 +12,10 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set env - name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up JDK 11 - name: Set up JDK
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
java-version: "11" java-version: "17"
distribution: "zulu" distribution: "zulu"
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:

View File

@ -26,30 +26,26 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion flutter.compileSdkVersion compileSdk 34
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_11 sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_17
} }
kotlinOptions { kotlinOptions {
jvmTarget = '11' jvmTarget = '17'
} }
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
} }
defaultConfig { defaultConfig {
applicationId "app.revanced.manager.flutter" applicationId "app.revanced.manager.flutter"
minSdkVersion 26 minSdk 26
targetSdkVersion 33 targetSdk 34
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }
buildTypes { buildTypes {
release { release {
shrinkResources false shrinkResources false
@ -71,10 +67,21 @@ android {
} }
} }
} }
packagingOptions { packagingOptions {
exclude '/prebuilt/**' jniLibs {
useLegacyPackaging true
excludes += ['/prebuilt/**']
}
resources {
excludes += ['/prebuilt/**']
}
} }
namespace 'app.revanced.manager.flutter'
}
kotlin {
jvmToolchain(17)
} }
flutter { flutter {
@ -85,7 +92,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// ReVanced // ReVanced
implementation "app.revanced:revanced-patcher:17.0.0" implementation "app.revanced:revanced-patcher:19.0.0"
// Signing & aligning // Signing & aligning
implementation("org.bouncycastle:bcpkix-jdk15on:1.70") implementation("org.bouncycastle:bcpkix-jdk15on:1.70")

View File

@ -1,4 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="app.revanced.manager.flutter">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
</manifest> </manifest>

View File

@ -1,6 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="app.revanced.manager.flutter">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
@ -24,8 +22,7 @@
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:largeHeap="true" android:largeHeap="true"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true">
android:extractNativeLibs="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

View File

@ -15,7 +15,9 @@ import app.revanced.patcher.patch.PatchResult
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
@ -25,6 +27,7 @@ import java.io.StringWriter
import java.util.logging.LogRecord import java.util.logging.LogRecord
import java.util.logging.Logger import java.util.logging.Logger
class MainActivity : FlutterActivity() { class MainActivity : FlutterActivity() {
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
private lateinit var installerChannel: MethodChannel private lateinit var installerChannel: MethodChannel
@ -98,8 +101,10 @@ class MainActivity : FlutterActivity() {
val cacheDirPath = call.argument<String>("cacheDirPath")!! val cacheDirPath = call.argument<String>("cacheDirPath")!!
try { try {
val patchBundleFile = File(patchBundleFilePath)
patchBundleFile.setWritable(false)
patches = PatchBundleLoader.Dex( patches = PatchBundleLoader.Dex(
File(patchBundleFilePath), patchBundleFile,
optimizedDexDirectory = File(cacheDirPath) optimizedDexDirectory = File(cacheDirPath)
) )
} catch (ex: Exception) { } catch (ex: Exception) {
@ -131,24 +136,34 @@ class MainActivity : FlutterActivity() {
}) })
put("options", JSONArray().apply { put("options", JSONArray().apply {
it.options.values.forEach { option -> it.options.values.forEach { option ->
val optionJson = JSONObject().apply option@{ JSONObject().apply {
put("key", option.key) put("key", option.key)
put("title", option.title) put("title", option.title)
put("description", option.description) put("description", option.description)
put("required", option.required) put("required", option.required)
when (val value = option.value) { fun JSONObject.putValue(
null -> put("value", null) value: Any?,
is Array<*> -> put("value", JSONArray().apply { key: String = "value"
) = if (value is Array<*>) put(
key,
JSONArray().apply {
value.forEach { put(it) } value.forEach { put(it) }
}) })
else -> put("value", option.value) else put(key, value)
}
put("optionClassType", option::class.simpleName) putValue(option.default)
}
put(optionJson) 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) }.let(::put)
@ -161,6 +176,7 @@ class MainActivity : FlutterActivity() {
} }
} }
@OptIn(InternalCoroutinesApi::class)
private fun runPatcher( private fun runPatcher(
result: MethodChannel.Result, result: MethodChannel.Result,
originalFilePath: String, originalFilePath: String,
@ -283,12 +299,12 @@ class MainActivity : FlutterActivity() {
acceptPatches(patches) acceptPatches(patches)
runBlocking { runBlocking {
apply(false).collect { patchResult: PatchResult -> apply(false).collect(FlowCollector { patchResult: PatchResult ->
if (cancel) { if (cancel) {
handler.post { stopResult!!.success(null) } handler.post { stopResult!!.success(null) }
this.cancel() this.cancel()
this@apply.close() this@apply.close()
return@collect return@FlowCollector
} }
val msg = patchResult.exception?.let { val msg = patchResult.exception?.let {
@ -301,7 +317,7 @@ class MainActivity : FlutterActivity() {
updateProgress(progress, "", msg) updateProgress(progress, "", msg)
progress += progressStep progress += progressStep
} })
} }
} }

View File

@ -1,4 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="app.revanced.manager.flutter">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
</manifest> </manifest>

View File

@ -1,12 +1,12 @@
buildscript { buildscript {
ext.kotlin_version = '1.9.0' ext.kotlin_version = '1.9.10'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
dependencies { 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" 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 { subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}" project.layout.buildDirectory.set(file("$root/${project.name}"))
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }
tasks.register("clean", Delete) { tasks.register("clean", Delete) {
delete rootProject.buildDir delete layout.buildDirectory
} }

View File

@ -4,3 +4,6 @@ org.gradle.daemon=true
org.gradle.caching=true org.gradle.caching=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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

View File

@ -135,6 +135,7 @@
"setRequiredOption": "Some patches require options to be set:\n\n{patches}\n\nPlease set them before continuing." "setRequiredOption": "Some patches require options to be set:\n\n{patches}\n\nPlease set them before continuing."
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Custom value",
"resetOptionsTooltip": "Reset patch options", "resetOptionsTooltip": "Reset patch options",
"viewTitle": "Patch options", "viewTitle": "Patch options",
"saveOptions": "Save", "saveOptions": "Save",

View File

@ -9,23 +9,13 @@ This page will guide you through building ReVanced Manager from source.
```sh ```sh
git clone https://github.com/revanced/revanced-manager.git && cd revanced-manager git clone https://github.com/revanced/revanced-manager.git && cd revanced-manager
``` ```
3. Get dependencies
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
```sh ```sh
flutter pub get flutter pub get
``` ```
6. Delete conflicting outputs 4. Delete conflicting outputs
```sh ```sh
flutter packages pub run build_runner build --delete-conflicting-outputs flutter packages pub run build_runner build --delete-conflicting-outputs
@ -34,7 +24,7 @@ This page will guide you through building ReVanced Manager from source.
> [!Note] > [!Note]
> Must be run every time you sync your local repository with the remote repository. > Must be run every time you sync your local repository with the remote repository.
7. Build the APK 5. Build the APK
```sh ```sh
flutter build apk flutter build apk

Binary file not shown.

View File

@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

35
gradlew vendored
View File

@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (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. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@ -80,13 +80,11 @@ do
esac esac
done done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # This is normally unused
# shellcheck disable=SC2034
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@ -133,22 +131,29 @@ location of your Java installation."
fi fi
else else
JAVACMD=java 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 Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) 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 ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
case $MAX_FD in #( case $MAX_FD in #(
'' | soft) :;; #( '' | 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" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then
done done
fi fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
# shell script including quotes and variable substitutions, so put them in DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded. # 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 -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \

1
gradlew.bat vendored
View File

@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%

View File

@ -11,12 +11,10 @@ import 'package:revanced_manager/services/revanced_api.dart';
import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart'; import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart';
import 'package:revanced_manager/ui/views/navigation/navigation_view.dart'; import 'package:revanced_manager/ui/views/navigation/navigation_view.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:stacked_themes/stacked_themes.dart';
import 'package:timezone/data/latest.dart' as tz; import 'package:timezone/data/latest.dart' as tz;
late SharedPreferences prefs; late SharedPreferences prefs;
Future main() async { Future main() async {
await ThemeManager.initialise();
await setupLocator(); await setupLocator();
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await locator<ManagerAPI>().initialize(); await locator<ManagerAPI>().initialize();

View File

@ -13,12 +13,15 @@ class Patch {
}); });
factory Patch.fromJson(Map<String, dynamic> json) { factory Patch.fromJson(Map<String, dynamic> json) {
// See: https://github.com/ReVanced/revanced-manager/issues/1364#issuecomment-1760414618 _migrateV16ToV17(json);
return _$PatchFromJson(json);
}
static void _migrateV16ToV17(Map<String, dynamic> json) {
if (json['options'] == null) { if (json['options'] == null) {
json['options'] = []; json['options'] = [];
} }
return _$PatchFromJson(json);
} }
final String name; final String name;
@ -57,18 +60,34 @@ class Option {
required this.title, required this.title,
required this.description, required this.description,
required this.value, required this.value,
required this.values,
required this.required, 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) {
json['valueType'] = json['optionClassType']
.replace('PatchOption', '')
.replace('List', 'Array');
json['optionClassType'] = null;
}
}
final String key; final String key;
final String title; final String title;
final String description; final String description;
dynamic value; final dynamic value;
final Map<String, dynamic>? values;
final bool required; final bool required;
final String optionClassType; final String valueType;
Map toJson() => _$OptionToJson(this); Map toJson() => _$OptionToJson(this);
} }

View File

@ -1,10 +1,10 @@
import 'dart:io'; import 'dart:io';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:cr_file_saver/file_saver.dart';
import 'package:device_apps/device_apps.dart'; import 'package:device_apps/device_apps.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:install_plugin/install_plugin.dart'; import 'package:install_plugin/install_plugin.dart';
import 'package:path_provider/path_provider.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/models/patched_application.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/root_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 @lazySingleton
class PatcherAPI { class PatcherAPI {
@ -236,10 +236,10 @@ void exportPatchedFile(String appName, String version) {
try { try {
if (outFile != null) { if (outFile != null) {
final String newName = _getFileName(appName, version); final String newName = _getFileName(appName, version);
CRFileSaver.saveFileWithDialog( FlutterFileDialog.saveFile(
SaveFileDialogParams( params: SaveFileDialogParams(
sourceFilePath: outFile!.path, sourceFilePath: outFile!.path,
destinationFileName: newName, fileName: newName,
), ),
); );
} }
@ -258,7 +258,7 @@ void sharePatchedFile(String appName, String version) {
final String newPath = final String newPath =
outFile!.path.substring(0, lastSeparator + 1) + newName; outFile!.path.substring(0, lastSeparator + 1) + newName;
final File shareFile = outFile!.copySync(newPath); final File shareFile = outFile!.copySync(newPath);
ShareExtend.share(shareFile.path, 'file'); Share.shareXFiles([XFile(shareFile.path)]);
} }
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
@ -286,10 +286,10 @@ Future<void> exportPatcherLog(String logs) async {
final String fileName = 'revanced-manager_patcher_$dateTime.txt'; final String fileName = 'revanced-manager_patcher_$dateTime.txt';
final File log = File('${logDir.path}/$fileName'); final File log = File('${logDir.path}/$fileName');
log.writeAsStringSync(logs); log.writeAsStringSync(logs);
CRFileSaver.saveFileWithDialog( FlutterFileDialog.saveFile(
SaveFileDialogParams( params: SaveFileDialogParams(
sourceFilePath: log.path, sourceFilePath: log.path,
destinationFileName: fileName, fileName: fileName,
), ),
); );
} }

View File

@ -1,9 +1,9 @@
import 'dart:io'; import 'dart:io';
import 'package:device_apps/device_apps.dart'; import 'package:device_apps/device_apps.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patch.dart';
@ -181,13 +181,14 @@ class AppSelectorViewModel extends BaseViewModel {
Future<void> selectAppFromStorage(BuildContext context) async { Future<void> selectAppFromStorage(BuildContext context) async {
try { try {
final FilePickerResult? result = await FilePicker.platform.pickFiles( final String? result = await FlutterFileDialog.pickFile(
type: FileType.custom, params: const OpenFileDialogParams(
allowedExtensions: ['apk'], fileExtensionsFilter: ['apk'],
),
); );
if (result != null && result.files.single.path != null) { if (result != null) {
final File apkFile = File(result.files.single.path!); final File apkFile = File(result);
final List<String> pathSplit = result.files.single.path!.split('/'); final List<String> pathSplit = result.split('/');
pathSplit.removeLast(); pathSplit.removeLast();
final Directory filePickerCacheDir = Directory(pathSplit.join('/')); final Directory filePickerCacheDir = Directory(pathSplit.join('/'));
final Iterable<File> deletableFiles = final Iterable<File> deletableFiles =
@ -207,7 +208,7 @@ class AppSelectorViewModel extends BaseViewModel {
name: application.appName, name: application.appName,
packageName: application.packageName, packageName: application.packageName,
version: application.versionName!, version: application.versionName!,
apkFilePath: result.files.single.path!, apkFilePath: result,
icon: application.icon, icon: application.icon,
patchDate: DateTime.now(), patchDate: DateTime.now(),
isFromStorage: true, isFromStorage: true,

View File

@ -1,7 +1,7 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'dart:async'; import 'dart:async';
import 'dart:io'; 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/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -64,8 +64,9 @@ class HomeViewModel extends BaseViewModel {
flutterLocalNotificationsPlugin flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation< .resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>() AndroidFlutterLocalNotificationsPlugin>()
?.requestPermission(); ?.requestNotificationsPermission();
final bool isConnected = await Connectivity().checkConnection(); final bool isConnected = await Connectivity().checkConnectivity() !=
ConnectivityResult.none;
if (!isConnected) { if (!isConnected) {
_toast.showBottom('homeView.noConnection'); _toast.showBottom('homeView.noConnection');
} }

View File

@ -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:revanced_manager/utils/about_info.dart';
import 'package:screenshot_callback/screenshot_callback.dart'; import 'package:screenshot_callback/screenshot_callback.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock_plus/wakelock_plus.dart';
class InstallerViewModel extends BaseViewModel { class InstallerViewModel extends BaseViewModel {
final ManagerAPI _managerAPI = locator<ManagerAPI>(); final ManagerAPI _managerAPI = locator<ManagerAPI>();
@ -74,7 +74,7 @@ class InstallerViewModel extends BaseViewModel {
screenshotDetected(context); screenshotDetected(context);
} }
}); });
await Wakelock.enable(); await WakelockPlus.enable();
await handlePlatformChannelMethods(); await handlePlatformChannelMethods();
await runPatcher(); await runPatcher();
} }
@ -171,7 +171,7 @@ class InstallerViewModel extends BaseViewModel {
} // ignore } // ignore
} }
} }
await Wakelock.disable(); await WakelockPlus.disable();
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
print(e); print(e);

View File

@ -61,8 +61,8 @@ class PatchOptionsView extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
for (final Option option in model.visibleOptions) for (final Option option in model.visibleOptions)
if (option.optionClassType == 'StringPatchOption' || if (option.valueType == 'String' ||
option.optionClassType == 'IntPatchOption') option.valueType == 'Int')
IntAndStringPatchOption( IntAndStringPatchOption(
patchOption: option, patchOption: option,
removeOption: (option) { removeOption: (option) {
@ -72,7 +72,7 @@ class PatchOptionsView extends StatelessWidget {
model.modifyOptions(value, option); model.modifyOptions(value, option);
}, },
) )
else if (option.optionClassType == 'BooleanPatchOption') else if (option.valueType == 'Boolean')
BooleanPatchOption( BooleanPatchOption(
patchOption: option, patchOption: option,
removeOption: (option) { removeOption: (option) {
@ -82,10 +82,10 @@ class PatchOptionsView extends StatelessWidget {
model.modifyOptions(value, option); model.modifyOptions(value, option);
}, },
) )
else if (option.optionClassType == else if (option.valueType ==
'StringListPatchOption' || 'StringArray' ||
option.optionClassType == 'IntListPatchOption' || option.valueType == 'IntArray' ||
option.optionClassType == 'LongListPatchOption') option.valueType == 'LongArray')
IntStringLongListPatchOption( IntStringLongListPatchOption(
patchOption: option, patchOption: option,
removeOption: (option) { removeOption: (option) {

View File

@ -62,7 +62,10 @@ class PatchOptionsViewModel extends BaseViewModel {
for (final Option option in options) { for (final Option option in options) {
if (!visibleOptions.any((vOption) => vOption.key == option.key)) { if (!visibleOptions.any((vOption) => vOption.key == option.key)) {
_managerAPI.clearPatchOption( _managerAPI.clearPatchOption(
selectedApp, _managerAPI.selectedPatch!.name, option.key); selectedApp,
_managerAPI.selectedPatch!.name,
option.key,
);
} }
} }
for (final Option option in visibleOptions) { for (final Option option in visibleOptions) {
@ -70,7 +73,10 @@ class PatchOptionsViewModel extends BaseViewModel {
requiredNullOptions.add(option); requiredNullOptions.add(option);
} else { } else {
_managerAPI.setPatchOption( _managerAPI.setPatchOption(
option, _managerAPI.selectedPatch!.name, selectedApp); option,
_managerAPI.selectedPatch!.name,
selectedApp,
);
} }
} }
if (requiredNullOptions.isNotEmpty) { if (requiredNullOptions.isNotEmpty) {
@ -89,7 +95,8 @@ class PatchOptionsViewModel extends BaseViewModel {
final Option modifiedOption = Option( final Option modifiedOption = Option(
title: option.title, title: option.title,
description: option.description, description: option.description,
optionClassType: option.optionClassType, values: option.values,
valueType: option.valueType,
value: value, value: value,
required: option.required, required: option.required,
key: option.key, key: option.key,
@ -107,7 +114,8 @@ class PatchOptionsViewModel extends BaseViewModel {
final Option defaultOption = Option( final Option defaultOption = Option(
title: option.title, title: option.title,
description: option.description, description: option.description,
optionClassType: option.optionClassType, values: option.values,
valueType: option.valueType,
value: option.value is List ? option.value.toList() : option.value, value: option.value is List ? option.value.toList() : option.value,
required: option.required, required: option.required,
key: option.key, key: option.key,
@ -172,21 +180,27 @@ class PatchOptionsViewModel extends BaseViewModel {
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Column( child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Expanded(
e.title, child: Column(
style: const TextStyle( crossAxisAlignment: CrossAxisAlignment.start,
fontSize: 16, children: [
), Text(
), e.title,
const SizedBox(height: 4), style: const TextStyle(
Text( fontSize: 16,
e.description, ),
style: TextStyle( ),
fontSize: 14, const SizedBox(height: 4),
color: Theme.of(context).colorScheme.onSurface, Text(
e.description,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).colorScheme.onSurface,
),
),
],
), ),
), ),
], ],
@ -229,7 +243,10 @@ Future<void> showRequiredOptionNullDialog(
locator<PatcherViewModel>().notifyListeners(); locator<PatcherViewModel>().notifyListeners();
for (final option in options) { for (final option in options) {
managerAPI.clearPatchOption( managerAPI.clearPatchOption(
selectedApp, managerAPI.selectedPatch!.name, option.key); selectedApp,
managerAPI.selectedPatch!.name,
option.key,
);
} }
Navigator.of(context) Navigator.of(context)
..pop() ..pop()

View File

@ -3,9 +3,7 @@ import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/services/manager_api.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/views/patches_selector/patches_selector_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
import 'package:revanced_manager/ui/widgets/shared/search_bar.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'; import 'package:stacked/stacked.dart';
class PatchesSelectorView extends StatefulWidget { class PatchesSelectorView extends StatefulWidget {
@ -182,187 +180,39 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
), ),
], ],
), ),
if (model.newPatchExists()) if (model.getQueriedPatches(_query).any((patch) => model.isPatchNew(patch)))
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( model.getPatchCategory(context, 'patchesSelectorView.newPatches'),
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.getQueriedPatches(_query).map((patch) { ...model.getQueriedPatches(_query).map((patch) {
if (model.isPatchNew(patch)) { if (model.isPatchNew(patch)) {
return PatchItem( return model.getPatchItem(context, patch);
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,
),
);
} else { } else {
return Container(); return Container();
} }
}), }),
Padding( if (model.getQueriedPatches(_query).any((patch) => !model.isPatchNew(patch) && patch.compatiblePackages.isNotEmpty))
padding: const EdgeInsets.symmetric( model.getPatchCategory(context, 'patchesSelectorView.patches'),
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,
),
),
),
),
),
], ],
), ),
...model.getQueriedPatches(_query).map( ...model.getQueriedPatches(_query).map(
(patch) { (patch) {
if (patch.compatiblePackages.isNotEmpty) { if (patch.compatiblePackages.isNotEmpty && !model.isPatchNew(patch)) {
return PatchItem( return model.getPatchItem(context, patch);
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,
),
);
} else { } else {
return Container(); return Container();
} }
}, },
), ),
if (_managerAPI.areUniversalPatchesEnabled()) if (model.getQueriedPatches(_query).any((patch) => patch.compatiblePackages.isEmpty))
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( model.getPatchCategory(context, 'patchesSelectorView.universalPatches'),
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.getQueriedPatches(_query).map((patch) { ...model.getQueriedPatches(_query).map((patch) {
if (patch.compatiblePackages.isEmpty) { if (patch.compatiblePackages.isEmpty && !model.isPatchNew(patch)) {
return PatchItem( return model.getPatchItem(context, patch);
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,
),
);
} else { } else {
return Container(); return Container();
} }

View File

@ -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/patcher_api.dart';
import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:revanced_manager/utils/check_for_supported_patch.dart'; import 'package:revanced_manager/utils/check_for_supported_patch.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
@ -224,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() { PatchedApplication getAppInfo() {
return locator<PatcherViewModel>().selectedApp!; return locator<PatcherViewModel>().selectedApp!;
} }
@ -239,12 +291,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
} }
} }
bool newPatchExists() {
return patches.any(
(patch) => isPatchNew(patch),
);
}
List<String> getSupportedVersions(Patch patch) { List<String> getSupportedVersions(Patch patch) {
final PatchedApplication app = locator<PatcherViewModel>().selectedApp!; final PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
final Package? package = patch.compatiblePackages.firstWhereOrNull( final Package? package = patch.compatiblePackages.firstWhereOrNull(

View File

@ -1,8 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:cr_file_saver/file_saver.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:logcat/logcat.dart'; import 'package:logcat/logcat.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -14,7 +13,7 @@ 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/patches_selector/patches_selector_viewmodel.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_language.dart'; import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_language.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.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/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
@ -159,10 +158,10 @@ class SettingsViewModel extends BaseViewModel {
if (outFile.existsSync()) { if (outFile.existsSync()) {
final String dateTime = final String dateTime =
DateTime.now().toString().replaceAll(' ', '_').split('.').first; DateTime.now().toString().replaceAll(' ', '_').split('.').first;
await CRFileSaver.saveFileWithDialog( await FlutterFileDialog.saveFile(
SaveFileDialogParams( params: SaveFileDialogParams(
sourceFilePath: outFile.path, sourceFilePath: outFile.path,
destinationFileName: 'selected_patches_$dateTime.json', fileName: 'selected_patches_$dateTime.json',
), ),
); );
_toast.showBottom('settingsView.exportedPatches'); _toast.showBottom('settingsView.exportedPatches');
@ -179,12 +178,13 @@ class SettingsViewModel extends BaseViewModel {
Future<void> importPatches(BuildContext context) async { Future<void> importPatches(BuildContext context) async {
if (isPatchesChangeEnabled()) { if (isPatchesChangeEnabled()) {
try { try {
final FilePickerResult? result = await FilePicker.platform.pickFiles( final String? result = await FlutterFileDialog.pickFile(
type: FileType.custom, params: const OpenFileDialogParams(
allowedExtensions: ['json'], fileExtensionsFilter: ['json'],
),
); );
if (result != null && result.files.single.path != null) { if (result != null) {
final File inFile = File(result.files.single.path!); final File inFile = File(result);
inFile.copySync(_managerAPI.storedPatchesFile); inFile.copySync(_managerAPI.storedPatchesFile);
inFile.delete(); inFile.delete();
if (_patcherViewModel.selectedApp != null) { if (_patcherViewModel.selectedApp != null) {
@ -209,10 +209,10 @@ class SettingsViewModel extends BaseViewModel {
if (outFile.existsSync()) { if (outFile.existsSync()) {
final String dateTime = final String dateTime =
DateTime.now().toString().replaceAll(' ', '_').split('.').first; DateTime.now().toString().replaceAll(' ', '_').split('.').first;
await CRFileSaver.saveFileWithDialog( await FlutterFileDialog.saveFile(
SaveFileDialogParams( params: SaveFileDialogParams(
sourceFilePath: outFile.path, sourceFilePath: outFile.path,
destinationFileName: 'keystore_$dateTime.keystore', fileName: 'keystore_$dateTime.keystore',
), ),
); );
_toast.showBottom('settingsView.exportedKeystore'); _toast.showBottom('settingsView.exportedKeystore');
@ -228,9 +228,9 @@ class SettingsViewModel extends BaseViewModel {
Future<void> importKeystore() async { Future<void> importKeystore() async {
try { try {
final FilePickerResult? result = await FilePicker.platform.pickFiles(); final String? result = await FlutterFileDialog.pickFile();
if (result != null && result.files.single.path != null) { if (result != null) {
final File inFile = File(result.files.single.path!); final File inFile = File(result);
inFile.copySync(_managerAPI.keystoreFile); inFile.copySync(_managerAPI.keystoreFile);
_toast.showBottom('settingsView.importedKeystore'); _toast.showBottom('settingsView.importedKeystore');
@ -276,6 +276,6 @@ class SettingsViewModel extends BaseViewModel {
File('${logDir.path}/revanced-manager_logcat_$dateTime.log'); File('${logDir.path}/revanced-manager_logcat_$dateTime.log');
final String logs = await Logcat.execute(); final String logs = await Logcat.execute();
logcat.writeAsStringSync(logs); logcat.writeAsStringSync(logs);
ShareExtend.share(logcat.path, 'file'); await Share.shareXFiles([XFile(logcat.path)]);
} }
} }

View File

@ -1,6 +1,6 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
@ -59,13 +59,27 @@ class IntAndStringPatchOption extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ValueNotifier patchOptionValue = ValueNotifier(patchOption.value); 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( return PatchOption(
widget: Column( widget: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
TextFieldForPatchOption( TextFieldForPatchOption(
value: patchOption.value, value: patchOption.value,
optionType: patchOption.optionClassType, values: patchOption.values,
optionType: patchOption.valueType,
selectedKey: getKey(),
onChanged: (value) { onChanged: (value) {
patchOptionValue.value = value; patchOptionValue.value = value;
onChanged(value, patchOption); onChanged(value, patchOption);
@ -119,17 +133,41 @@ class IntStringLongListPatchOption extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final String type = patchOption.optionClassType; final List<dynamic> values = List.from(patchOption.value ?? []);
final List<dynamic> values = patchOption.value ?? [];
final ValueNotifier patchOptionValue = ValueNotifier(values); 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( return PatchOption(
widget: Column( widget: ValueListenableBuilder(
crossAxisAlignment: CrossAxisAlignment.start, valueListenable: patchOptionValue,
children: [ builder: (context, value, child) {
ValueListenableBuilder( return Column(
valueListenable: patchOptionValue, crossAxisAlignment: CrossAxisAlignment.start,
builder: (context, value, child) { children: [
return ListView.builder( ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: value.length, itemCount: value.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
@ -137,16 +175,42 @@ class IntStringLongListPatchOption extends StatelessWidget {
final e = values[index]; final e = values[index];
return TextFieldForPatchOption( return TextFieldForPatchOption(
value: e.toString(), value: e.toString(),
values: patchOption.values,
optionType: type, optionType: type,
selectedKey: value.length > 1 ? '' : getKey(e),
showDropdown: index == 0,
onChanged: (newValue) { onChanged: (newValue) {
values[index] = type == 'StringListPatchOption' if (newValue is List) {
? newValue values.clear();
: type == 'IntListPatchOption' isTextFieldVisible = false;
? int.parse(newValue) values.add(newValue.toString());
: num.parse(newValue); } 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); onChanged(values, patchOption);
}, },
removeValue: (value) { removeValue: () {
patchOptionValue.value = List.from(patchOptionValue.value) patchOptionValue.value = List.from(patchOptionValue.value)
..removeAt(index); ..removeAt(index);
values.removeAt(index); values.removeAt(index);
@ -154,44 +218,46 @@ class IntStringLongListPatchOption extends StatelessWidget {
}, },
); );
}, },
); ),
}, if (isTextFieldVisible) ...[
), const SizedBox(height: 4),
const SizedBox(height: 4), Align(
Align( alignment: Alignment.centerLeft,
alignment: Alignment.centerLeft, child: TextButton(
child: TextButton( onPressed: () {
onPressed: () { if (type == 'StringArray') {
if (type == 'StringListPatchOption') { patchOptionValue.value =
patchOptionValue.value = List.from(patchOptionValue.value) List.from(patchOptionValue.value)..add('');
..add(''); values.add('');
values.add(''); } else {
} else { patchOptionValue.value =
patchOptionValue.value = List.from(patchOptionValue.value) List.from(patchOptionValue.value)..add(0);
..add(0); values.add(0);
values.add(0); }
} onChanged(values, patchOption);
onChanged(values, patchOption); },
}, child: Row(
child: Row( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ const Icon(Icons.add, size: 20),
const Icon(Icons.add, size: 20), I18nText(
I18nText( 'add',
'add', child: const Text(
child: const Text( '',
'', style: TextStyle(
style: TextStyle( fontSize: 14,
fontSize: 14, fontWeight: FontWeight.w600,
fontWeight: FontWeight.w600, ),
), ),
),
],
), ),
), ),
], ),
), ],
), ],
), );
], },
), ),
patchOption: patchOption, patchOption: patchOption,
removeOption: (Option option) { removeOption: (Option option) {
@ -203,6 +269,7 @@ class IntStringLongListPatchOption extends StatelessWidget {
class UnsupportedPatchOption extends StatelessWidget { class UnsupportedPatchOption extends StatelessWidget {
const UnsupportedPatchOption({super.key, required this.patchOption}); const UnsupportedPatchOption({super.key, required this.patchOption});
final Option patchOption; final Option patchOption;
@override @override
@ -302,14 +369,20 @@ class TextFieldForPatchOption extends StatefulWidget {
const TextFieldForPatchOption({ const TextFieldForPatchOption({
super.key, super.key,
required this.value, required this.value,
required this.values,
this.removeValue, this.removeValue,
required this.onChanged, required this.onChanged,
required this.optionType, required this.optionType,
required this.selectedKey,
this.showDropdown = true,
}); });
final String? value; final String? value;
final Map<String, dynamic>? values;
final String optionType; 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; final void Function(dynamic value) onChanged;
@override @override
@ -319,75 +392,155 @@ class TextFieldForPatchOption extends StatefulWidget {
class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> { class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
final TextEditingController controller = TextEditingController(); final TextEditingController controller = TextEditingController();
String? selectedKey;
String? defaultValue;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool isStringOption = widget.optionType.contains('String'); final bool isStringOption = widget.optionType.contains('String');
final bool isListOption = widget.optionType.contains('List'); final bool isArrayOption = widget.optionType.contains('Array');
controller.text = widget.value ?? ''; selectedKey ??= widget.selectedKey;
return TextFormField( controller.text = !isStringOption && isArrayOption && selectedKey == '' &&
inputFormatters: [ (widget.value != null && widget.value.toString().startsWith('['))
if (widget.optionType.contains('Int')) ? ''
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), : widget.value ?? '';
if (widget.optionType.contains('Long')) defaultValue ??= controller.text;
FilteringTextInputFormatter.allow(RegExp(r'^[0-9]*\.?[0-9]*')), return Column(
], crossAxisAlignment: CrossAxisAlignment.start,
controller: controller, children: [
keyboardType: isStringOption ? TextInputType.text : TextInputType.number, if (widget.showDropdown && (widget.values?.isNotEmpty ?? false))
decoration: InputDecoration( DropdownButton<String>(
suffixIcon: PopupMenuButton( style: const TextStyle(
tooltip: FlutterI18n.translate( fontSize: 16,
context, ),
'patchOptionsView.tooltip', 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) { if (selectedKey == '')
return [ TextFormField(
if (isListOption) inputFormatters: [
PopupMenuItem( if (widget.optionType.contains('Int'))
value: 'remove', FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
child: I18nText('remove'), 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) ...[ itemBuilder: (BuildContext context) {
PopupMenuItem( return [
value: 'patchOptionsView.selectFilePath', if (isArrayOption)
child: I18nText('patchOptionsView.selectFilePath'), PopupMenuItem(
), value: 'remove',
PopupMenuItem( child: I18nText('remove'),
value: 'patchOptionsView.selectFolder', ),
child: I18nText('patchOptionsView.selectFolder'), if (isStringOption) ...[
), PopupMenuItem(
], value: 'patchOptionsView.selectFilePath',
]; child: I18nText('patchOptionsView.selectFilePath'),
}, ),
onSelected: (String selection) async { PopupMenuItem(
switch (selection) { value: 'patchOptionsView.selectFolder',
case 'patchOptionsView.selectFilePath': child: I18nText('patchOptionsView.selectFolder'),
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); },
} onSelected: (String selection) async {
break; switch (selection) {
case 'patchOptionsView.selectFolder': case 'patchOptionsView.selectFilePath':
final result = await FilePicker.platform.getDirectoryPath(); final String? result = await FlutterFileDialog.pickFile();
if (result != null) { if (result != null) {
controller.text = result; controller.text = result;
widget.onChanged(controller.text); widget.onChanged(controller.text);
} }
break; break;
case 'remove': case 'patchOptionsView.selectFolder':
widget.removeValue!(widget.value); final DirectoryLocation? result = await FlutterFileDialog.pickDirectory();
break; if (result != null) {
} controller.text = result.toString();
}, widget.onChanged(controller.text);
), }
hintStyle: TextStyle( break;
fontSize: 14, case 'remove':
color: Theme.of(context).colorScheme.onSecondaryContainer, widget.removeValue!();
), break;
), }
onChanged: (String value) { },
widget.onChanged(value); ),
}, hintStyle: TextStyle(
fontSize: 14,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
onChanged: (String value) {
widget.onChanged(value);
},
),
],
); );
} }
} }

View File

@ -17,12 +17,12 @@ bool isPatchSupported(Patch patch) {
bool hasUnsupportedRequiredOption(List<Option> options, Patch patch) { bool hasUnsupportedRequiredOption(List<Option> options, Patch patch) {
final List<String> requiredOptionsType = []; final List<String> requiredOptionsType = [];
final List<String> supportedOptionsType = [ final List<String> supportedOptionsType = [
'StringPatchOption', 'String',
'BooleanPatchOption', 'Boolean',
'IntPatchOption', 'Int',
'StringListPatchOption', 'StringArray',
'IntListPatchOption', 'IntArray',
'LongListPatchOption', 'LongArray',
]; ];
for (final Option option in options) { for (final Option option in options) {
if (option.required && if (option.required &&
@ -33,7 +33,7 @@ bool hasUnsupportedRequiredOption(List<Option> options, Patch patch) {
patch.name, patch.name,
option.key, option.key,
) == null) { ) == null) {
requiredOptionsType.add(option.optionClassType); requiredOptionsType.add(option.valueType);
} }
} }
for (final String optionType in requiredOptionsType) { for (final String optionType in requiredOptionsType) {

View File

@ -4,7 +4,7 @@ homepage: https://github.com/revanced/revanced-manager
publish_to: 'none' publish_to: 'none'
version: 1.14.2+101400200 version: 1.15.0+101500000
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'
@ -12,71 +12,75 @@ environment:
dependencies: dependencies:
animations: ^2.0.7 animations: ^2.0.7
collection: ^1.17.0 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: device_apps:
git: git: # switch back to ponces fork once https://github.com/ponces/flutter_plugin_device_apps/pull/1 is merged
url: https://github.com/ponces/flutter_plugin_device_apps url: https://github.com/BenjaminHalko/flutter_plugin_device_apps
ref: revanced-manager ref: revanced-manager
device_info_plus: ^8.1.0 device_info_plus: ^9.1.0
dynamic_color: ^1.6.3 dynamic_color: ^1.6.3
dio: ^5.0.0 dio: ^5.0.0
dynamic_themes: ^1.1.0 dynamic_themes: ^1.1.0
expandable: ^5.0.1 expandable: ^5.0.1
file_picker:
git:
url: https://github.com/alexmercerind/flutter_file_picker
ref: master
flex_color_scheme: ^7.0.1 flex_color_scheme: ^7.0.1
flutter: flutter:
sdk: 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_cache_manager: ^3.3.0
flutter_i18n: ^0.33.0 flutter_i18n: ^0.34.0
flutter_local_notifications: ^13.0.0 flutter_local_notifications: ^16.1.0
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
flutter_svg: ^2.0.4 flutter_svg: ^2.0.4
fluttertoast: ^8.2.1 fluttertoast:
font_awesome_flutter: ^10.6.0 git: # remove once the next fluttertoast version is release (> 8.2.2)
get_it: 7.2.0 url: https://github.com/ponnamkarthik/FlutterToast
google_fonts: ^4.0.3 ref: f4e7b4e1afc8c760eb5bac80f6a2e299906d83ca
http: ^0.13.5 font_awesome_flutter: ^10.4.0
get_it: ^7.6.4
google_fonts: ^6.1.0
http: ^1.1.0
injectable: ^2.1.1 injectable: ^2.1.1
intl: ^0.18.0 intl: ^0.18.0
json_annotation: ^4.8.0 json_annotation: ^4.8.0
logcat: logcat:
git: git:
url: https://github.com/SuaMusica/logcat url: https://github.com/BenjaminHalko/logcat
ref: feature/nullSafe ref: master
package_info_plus: ^3.0.3 package_info_plus: ^4.2.0
path_provider: ^2.0.14 path_provider: ^2.0.14
permission_handler: ^10.2.0 permission_handler: ^11.0.1
pull_to_refresh: ^2.0.0 pull_to_refresh: ^2.0.0
root: root:
git: git:
url: https://github.com/EvadeMaster/root url: https://github.com/validcube/root
ref: 82803aa40f63cddff81c3e4d27ce8ce3e7c83f60 ref: 68e5678a535a2a3344828a14a39017fa74b9098c
share_extend: ^2.0.0
shared_preferences: ^2.1.0 shared_preferences: ^2.1.0
skeletons: ^0.0.3 skeletons: ^0.0.3
stacked: ^3.2.0 stacked: ^3.2.0
stacked_generator: ^1.1.0 stacked_generator: ^1.1.0
stacked_services: ^1.0.0 stacked_services: ^1.0.0
stacked_themes: ^0.3.10
timeago: ^3.3.0 timeago: ^3.3.0
timezone: ^0.9.0 timezone: ^0.9.0
url_launcher: ^6.1.10 url_launcher: ^6.1.10
wakelock: ^0.6.2
flutter_dotenv: ^5.0.2 flutter_dotenv: ^5.0.2
flutter_markdown: ^0.6.14 flutter_markdown: ^0.6.14
dio_cache_interceptor: ^3.4.0 dio_cache_interceptor: ^3.4.0
install_plugin: ^2.1.0 install_plugin:
screenshot_callback: ^3.0.1 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 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: dev_dependencies:
json_serializable: ^6.6.1 json_serializable: ^6.6.1
@ -85,7 +89,7 @@ dev_dependencies:
flutter_lints: ^2.0.1 flutter_lints: ^2.0.1
flutter_test: flutter_test:
sdk: flutter sdk: flutter
injectable_generator: ^2.1.5 injectable_generator: ^2.1.5