chore: Merge branch dev to main (#1329)

This commit is contained in:
oSumAtrIX 2023-10-05 01:35:08 +02:00 committed by GitHub
commit d9acd0d74b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 346 additions and 613 deletions

View file

@ -85,7 +85,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:14.2.2" implementation "app.revanced:revanced-patcher:16.0.0"
// Signing & aligning // Signing & aligning
implementation("org.bouncycastle:bcpkix-jdk15on:1.70") implementation("org.bouncycastle:bcpkix-jdk15on:1.70")

View file

@ -42,6 +42,10 @@
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".ExportSettingsActivity"
android:exported="true">
</activity>
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />

View file

@ -0,0 +1,86 @@
package app.revanced.manager.flutter
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Base64
import org.json.JSONObject
import java.io.ByteArrayInputStream
import java.io.File
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.security.MessageDigest
class ExportSettingsActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callingPackageName = getCallingPackage()!!
if (getFingerprint(callingPackageName) == getFingerprint(getPackageName())) {
// Create JSON Object
val json = JSONObject()
// Default Data
json.put("keystorePassword", "s3cur3p@ssw0rd")
// Load Shared Preferences
val sharedPreferences = getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
val allEntries: Map<String, *> = sharedPreferences.getAll()
for ((key, value) in allEntries.entries) {
json.put(
key.replace("flutter.", ""),
if (value is Boolean) if (value) 1 else 0 else value
)
}
// Load keystore
val keystoreFile = File(getExternalFilesDir(null), "/revanced-manager.keystore")
if (keystoreFile.exists()) {
val keystoreBytes = keystoreFile.readBytes()
val keystoreBase64 = Base64.encodeToString(keystoreBytes, Base64.DEFAULT)
json.put("keystore", keystoreBase64)
}
// Load saved patches
val storedPatchesFile = File(filesDir.parentFile.absolutePath, "/app_flutter/selected-patches.json")
if (storedPatchesFile.exists()) {
val patchesBytes = storedPatchesFile.readBytes()
val patches = String(patchesBytes, Charsets.UTF_8)
json.put("patches", JSONObject(patches))
}
// Send data back
val resultIntent = Intent()
resultIntent.putExtra("data", json.toString())
setResult(Activity.RESULT_OK, resultIntent)
finish()
} else {
val resultIntent = Intent()
setResult(Activity.RESULT_CANCELED)
finish()
}
}
fun getFingerprint(packageName: String): String {
// Get the signature of the app that matches the package name
val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
val signature = packageInfo.signatures[0]
// Get the raw certificate data
val rawCert = signature.toByteArray()
// Generate an X509Certificate from the data
val certFactory = CertificateFactory.getInstance("X509")
val x509Cert = certFactory.generateCertificate(ByteArrayInputStream(rawCert)) as X509Certificate
// Get the SHA256 fingerprint
val fingerprint = MessageDigest.getInstance("SHA256").digest(x509Cert.encoded).joinToString("") {
"%02x".format(it)
}
return fingerprint
}
}

View file

@ -8,22 +8,21 @@ import app.revanced.manager.flutter.utils.signing.Signer
import app.revanced.manager.flutter.utils.zip.ZipFile import app.revanced.manager.flutter.utils.zip.ZipFile
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
import app.revanced.patcher.PatchBundleLoader import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.PatchSet
import app.revanced.patcher.Patcher import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions import app.revanced.patcher.PatcherOptions
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
import app.revanced.patcher.extensions.PatchExtensions.dependencies
import app.revanced.patcher.extensions.PatchExtensions.description
import app.revanced.patcher.extensions.PatchExtensions.include
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.patch.PatchResult 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.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.json.JSONArray
import org.json.JSONObject
import java.io.File import java.io.File
import java.io.PrintWriter import java.io.PrintWriter
import java.io.StringWriter import java.io.StringWriter
import java.lang.Error
import java.util.logging.LogRecord import java.util.logging.LogRecord
import java.util.logging.Logger import java.util.logging.Logger
@ -33,6 +32,8 @@ class MainActivity : FlutterActivity() {
private var cancel: Boolean = false private var cancel: Boolean = false
private var stopResult: MethodChannel.Result? = null private var stopResult: MethodChannel.Result? = null
private lateinit var patches: PatchSet
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
@ -48,7 +49,6 @@ class MainActivity : FlutterActivity() {
mainChannel.setMethodCallHandler { call, result -> mainChannel.setMethodCallHandler { call, result ->
when (call.method) { when (call.method) {
"runPatcher" -> { "runPatcher" -> {
val patchBundleFilePath = call.argument<String>("patchBundleFilePath")
val originalFilePath = call.argument<String>("originalFilePath") val originalFilePath = call.argument<String>("originalFilePath")
val inputFilePath = call.argument<String>("inputFilePath") val inputFilePath = call.argument<String>("inputFilePath")
val patchedFilePath = call.argument<String>("patchedFilePath") val patchedFilePath = call.argument<String>("patchedFilePath")
@ -59,7 +59,7 @@ class MainActivity : FlutterActivity() {
val keyStoreFilePath = call.argument<String>("keyStoreFilePath") val keyStoreFilePath = call.argument<String>("keyStoreFilePath")
val keystorePassword = call.argument<String>("keystorePassword") val keystorePassword = call.argument<String>("keystorePassword")
if (patchBundleFilePath != null && if (
originalFilePath != null && originalFilePath != null &&
inputFilePath != null && inputFilePath != null &&
patchedFilePath != null && patchedFilePath != null &&
@ -73,7 +73,6 @@ class MainActivity : FlutterActivity() {
cancel = false cancel = false
runPatcher( runPatcher(
result, result,
patchBundleFilePath,
originalFilePath, originalFilePath,
inputFilePath, inputFilePath,
patchedFilePath, patchedFilePath,
@ -93,29 +92,44 @@ class MainActivity : FlutterActivity() {
} }
"getPatches" -> { "getPatches" -> {
val patchBundleFilePath = call.argument<String>("patchBundleFilePath") val patchBundleFilePath = call.argument<String>("patchBundleFilePath")!!
val cacheDirPath = call.argument<String>("cacheDirPath") val cacheDirPath = call.argument<String>("cacheDirPath")!!
if (patchBundleFilePath != null) { try {
val patches = PatchBundleLoader.Dex( patches = PatchBundleLoader.Dex(
File(patchBundleFilePath), File(patchBundleFilePath),
optimizedDexDirectory = File(cacheDirPath) optimizedDexDirectory = File(cacheDirPath)
).map { patch -> )
val map = HashMap<String, Any>() } catch (ex: Exception) {
map["\"name\""] = "\"${patch.patchName.replace("\"","\\\"")}\"" return@setMethodCallHandler result.notImplemented()
map["\"description\""] = "\"${patch.description?.replace("\"","\\\"")}\"" } catch (err: Error) {
map["\"excluded\""] = !patch.include return@setMethodCallHandler result.notImplemented()
map["\"dependencies\""] = patch.dependencies?.map { "\"${it.java.patchName}\"" } ?: emptyList<Any>()
map["\"compatiblePackages\""] = patch.compatiblePackages?.map {
val map2 = HashMap<String, Any>()
map2["\"name\""] = "\"${it.name}\""
map2["\"versions\""] = it.versions.map { version -> "\"${version}\"" }
map2
} ?: emptyList<Any>()
map
} }
result.success(patches)
} else result.notImplemented() JSONArray().apply {
patches.forEach {
JSONObject().apply {
put("name", it.name)
put("description", it.description)
put("excluded", !it.use)
put("compatiblePackages", JSONArray().apply {
it.compatiblePackages?.forEach { compatiblePackage ->
val compatiblePackageJson = JSONObject().apply {
put("name", compatiblePackage.name)
put(
"versions",
JSONArray().apply {
compatiblePackage.versions?.forEach { version ->
put(version)
}
})
}
put(compatiblePackageJson)
}
})
}.let(::put)
}
}.toString().let(result::success)
} }
else -> result.notImplemented() else -> result.notImplemented()
@ -125,7 +139,6 @@ class MainActivity : FlutterActivity() {
private fun runPatcher( private fun runPatcher(
result: MethodChannel.Result, result: MethodChannel.Result,
patchBundleFilePath: String,
originalFilePath: String, originalFilePath: String,
inputFilePath: String, inputFilePath: String,
patchedFilePath: String, patchedFilePath: String,
@ -168,8 +181,11 @@ class MainActivity : FlutterActivity() {
} }
object : java.util.logging.Handler() { object : java.util.logging.Handler() {
override fun publish(record: LogRecord) = override fun publish(record: LogRecord) {
if (record.loggerName?.startsWith("app.revanced") != true) return
updateProgress(-1.0, "", record.message) updateProgress(-1.0, "", record.message)
}
override fun flush() = Unit override fun flush() = Unit
override fun close() = flush() override fun close() = flush()
@ -209,10 +225,7 @@ class MainActivity : FlutterActivity() {
updateProgress(0.1, "Loading patches...", "Loading patches") updateProgress(0.1, "Loading patches...", "Loading patches")
val patches = PatchBundleLoader.Dex( val patches = patches.filter { patch ->
File(patchBundleFilePath),
optimizedDexDirectory = cacheDir
).filter { patch ->
val isCompatible = patch.compatiblePackages?.any { val isCompatible = patch.compatiblePackages?.any {
it.name == patcher.context.packageMetadata.packageName it.name == patcher.context.packageMetadata.packageName
} ?: false } ?: false
@ -220,7 +233,7 @@ class MainActivity : FlutterActivity() {
val compatibleOrUniversal = val compatibleOrUniversal =
isCompatible || patch.compatiblePackages.isNullOrEmpty() isCompatible || patch.compatiblePackages.isNullOrEmpty()
compatibleOrUniversal && selectedPatches.any { it == patch.patchName } compatibleOrUniversal && selectedPatches.any { it == patch.name }
} }
if (cancel) { if (cancel) {
@ -251,9 +264,9 @@ class MainActivity : FlutterActivity() {
val msg = patchResult.exception?.let { val msg = patchResult.exception?.let {
val writer = StringWriter() val writer = StringWriter()
it.printStackTrace(PrintWriter(writer)) it.printStackTrace(PrintWriter(writer))
"${patchResult.patchName} failed: $writer" "${patchResult.patch.name} failed: $writer"
} ?: run { } ?: run {
"${patchResult.patchName} succeeded" "${patchResult.patch.name} succeeded"
} }
updateProgress(progress, "", msg) updateProgress(progress, "", msg)
@ -317,7 +330,7 @@ class MainActivity : FlutterActivity() {
val stack = ex.stackTraceToString() val stack = ex.stackTraceToString()
updateProgress( updateProgress(
-100.0, -100.0,
"Aborted", "Failed",
"An error occurred:\n$stack" "An error occurred:\n$stack"
) )
} }

View file

@ -23,13 +23,13 @@
"widgetTitle": "Dashboard", "widgetTitle": "Dashboard",
"updatesSubtitle": "Updates", "updatesSubtitle": "Updates",
"patchedSubtitle": "Patched applications", "patchedSubtitle": "Patched apps",
"noUpdates": "No updates available", "noUpdates": "No updates available",
"WIP": "Work in progress...", "WIP": "Work in progress...",
"noInstallations": "No patched applications installed", "noInstallations": "No patched apps installed",
"installUpdate": "Continue to install the update?", "installUpdate": "Continue to install the update?",
"updateDialogTitle": "Update Manager", "updateDialogTitle": "Update Manager",
@ -56,9 +56,7 @@
"updatesDisabled": "Updating a patched app is currently disabled. Repatch the app again." "updatesDisabled": "Updating a patched app is currently disabled. Repatch the app again."
}, },
"applicationItem": { "applicationItem": {
"patchButton": "Patch", "infoButton": "Info"
"infoButton": "Info",
"changelogLabel": "Changelog"
}, },
"latestCommitCard": { "latestCommitCard": {
"loadingLabel": "Loading...", "loadingLabel": "Loading...",
@ -71,9 +69,8 @@
"widgetTitle": "Patcher", "widgetTitle": "Patcher",
"patchButton": "Patch", "patchButton": "Patch",
"patchDialogText": "You have selected a resource patch and a split APK installation has been detected, so patching errors may occur.\nAre you sure you want to proceed?",
"armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?", "armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?",
"splitApkWarningDialogText": "Patching a split APK is not yet supported and might fail. Proceed anyways?",
"removedPatchesWarningDialogText": "The following patches have been removed since the last time you used them.\n\n{patches}\n\nProceed anyways?" "removedPatchesWarningDialogText": "The following patches have been removed since the last time you used them.\n\n{patches}\n\nProceed anyways?"
}, },
"appSelectorCard": { "appSelectorCard": {
@ -148,9 +145,8 @@
"installTypeDescription": "Select the installation type to proceed with.", "installTypeDescription": "Select the installation type to proceed with.",
"installButton": "Install", "installButton": "Install",
"installRootType": "Root", "installRootType": "Mount",
"installNonRootType": "Non-root", "installNonRootType": "Normal",
"installRecommendedType": "Recommended",
"pressBackAgain": "Press back again to cancel", "pressBackAgain": "Press back again to cancel",
"openButton": "Open", "openButton": "Open",
@ -162,10 +158,6 @@
"exportApkButtonTooltip": "Export patched APK", "exportApkButtonTooltip": "Export patched APK",
"exportLogButtonTooltip": "Export log", "exportLogButtonTooltip": "Export log",
"installErrorDialogTitle": "Error",
"installErrorDialogText1": "Root install is not possible with the current patches selection.\nRepatch your app or choose non-root install.",
"installErrorDialogText2": "Non-root install is not possible with the current patches selection.\nRepatch your app or choose root install if you have your device rooted.",
"installErrorDialogText3": "Root install is not possible as the original APK was selected from storage.\nSelect an installed app or choose non-root install.",
"noExit": "Installer is still running, cannot exit..." "noExit": "Installer is still running, cannot exit..."
}, },
"settingsView": { "settingsView": {
@ -285,7 +277,6 @@
"rootDialogText": "App was installed with superuser permissions, but currently ReVanced Manager has no permissions.\nPlease grant superuser permissions first.", "rootDialogText": "App was installed with superuser permissions, but currently ReVanced Manager has no permissions.\nPlease grant superuser permissions first.",
"packageNameLabel": "Package name", "packageNameLabel": "Package name",
"originalPackageNameLabel": "Original package name",
"installTypeLabel": "Installation type", "installTypeLabel": "Installation type",
"rootTypeLabel": "Root", "rootTypeLabel": "Root",
"nonRootTypeLabel": "Non-root", "nonRootTypeLabel": "Non-root",

View file

@ -12,7 +12,7 @@ This page will guide you through building ReVanced Manager from source.
3. Create a GitHub personal access token with the `read:packages` scope [here](https://github.com/settings/tokens/new?scopes=read:packages&description=ReVanced) 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 `~/.gradle/gradle.properties` 4. Add your GitHub username and the token to `~/android/gradle.properties`
```properties ```properties
gpr.user = YourUsername gpr.user = YourUsername

View file

@ -1,5 +1,4 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:revanced_manager/utils/string.dart';
part 'patch.g.dart'; part 'patch.g.dart';
@ -9,26 +8,19 @@ class Patch {
required this.name, required this.name,
required this.description, required this.description,
required this.excluded, required this.excluded,
required this.dependencies,
required this.compatiblePackages, required this.compatiblePackages,
}); });
factory Patch.fromJson(Map<String, dynamic> json) => _$PatchFromJson(json); factory Patch.fromJson(Map<String, dynamic> json) => _$PatchFromJson(json);
final String name; final String name;
final String description; final String? description;
final bool excluded; final bool excluded;
final List<String> dependencies;
final List<Package> compatiblePackages; final List<Package> compatiblePackages;
Map<String, dynamic> toJson() => _$PatchToJson(this); Map<String, dynamic> toJson() => _$PatchToJson(this);
String getSimpleName() { String getSimpleName() {
return name return name;
.replaceAll('-', ' ')
.split('-')
.join(' ')
.toTitleCase()
.replaceFirst('Microg', 'MicroG');
} }
} }

View file

@ -9,23 +9,19 @@ class PatchedApplication {
PatchedApplication({ PatchedApplication({
required this.name, required this.name,
required this.packageName, required this.packageName,
required this.originalPackageName,
required this.version, required this.version,
required this.apkFilePath, required this.apkFilePath,
required this.icon, required this.icon,
required this.patchDate, required this.patchDate,
this.isRooted = false, this.isRooted = false,
this.isFromStorage = false, this.isFromStorage = false,
this.hasUpdates = false,
this.appliedPatches = const [], this.appliedPatches = const [],
this.changelog = const [],
}); });
factory PatchedApplication.fromJson(Map<String, dynamic> json) => factory PatchedApplication.fromJson(Map<String, dynamic> json) =>
_$PatchedApplicationFromJson(json); _$PatchedApplicationFromJson(json);
String name; String name;
String packageName; String packageName;
String originalPackageName;
String version; String version;
final String apkFilePath; final String apkFilePath;
@JsonKey( @JsonKey(
@ -36,9 +32,7 @@ class PatchedApplication {
DateTime patchDate; DateTime patchDate;
bool isRooted; bool isRooted;
bool isFromStorage; bool isFromStorage;
bool hasUpdates;
List<String> appliedPatches; List<String> appliedPatches;
List<String> changelog;
Map<String, dynamic> toJson() => _$PatchedApplicationToJson(this); Map<String, dynamic> toJson() => _$PatchedApplicationToJson(this);

View file

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
@ -7,7 +6,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
@lazySingleton @lazySingleton
@ -21,17 +19,6 @@ class GithubAPI {
priority: CachePriority.high, priority: CachePriority.high,
); );
final Map<String, String> repoAppPath = {
'com.google.android.youtube': 'youtube',
'com.google.android.apps.youtube.music': 'music',
'com.twitter.android': 'twitter',
'com.reddit.frontpage': 'reddit',
'com.zhiliaoapp.musically': 'tiktok',
'de.dwd.warnapp': 'warnwetter',
'com.garzotto.pflotsh.ecmwf_a': 'ecmwf',
'com.spotify.music': 'spotify',
};
Future<void> initialize(String repoUrl) async { Future<void> initialize(String repoUrl) async {
try { try {
_dio = Dio( _dio = Dio(
@ -142,38 +129,6 @@ class GithubAPI {
} }
} }
Future<List<String>> getCommits(
String packageName,
String repoName,
DateTime since,
) async {
final String path =
'src/main/kotlin/app/revanced/patches/${repoAppPath[packageName]}';
try {
final response = await _dio.get(
'/repos/$repoName/commits',
queryParameters: {
'path': path,
'since': since.toIso8601String(),
},
);
final List<dynamic> commits = response.data;
return commits
.map(
(commit) => commit['commit']['message'].split('\n')[0] +
' - ' +
commit['commit']['author']['name'] +
'\n' as String,
)
.toList();
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
return [];
}
Future<File?> getLatestReleaseFile( Future<File?> getLatestReleaseFile(
String extension, String extension,
String repoName, String repoName,
@ -237,30 +192,4 @@ class GithubAPI {
} }
return null; return null;
} }
Future<List<Patch>> getPatches(
String repoName,
String version,
String url,
) async {
List<Patch> patches = [];
try {
final File? f = await getPatchesReleaseFile(
'.json',
repoName,
version,
url,
);
if (f != null) {
final List<dynamic> list = jsonDecode(f.readAsStringSync());
patches = list.map((patch) => Patch.fromJson(patch)).toList();
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
return patches;
}
} }

View file

@ -42,12 +42,14 @@ class ManagerAPI {
String defaultManagerRepo = 'revanced/revanced-manager'; String defaultManagerRepo = 'revanced/revanced-manager';
String? patchesVersion = ''; String? patchesVersion = '';
String? integrationsVersion = ''; String? integrationsVersion = '';
bool isDefaultPatchesRepo() { bool isDefaultPatchesRepo() {
return getPatchesRepo().toLowerCase() == 'revanced/revanced-patches'; return getPatchesRepo().toLowerCase() == 'revanced/revanced-patches';
} }
bool isDefaultIntegrationsRepo() { bool isDefaultIntegrationsRepo() {
return getIntegrationsRepo().toLowerCase() == 'revanced/revanced-integrations'; return getIntegrationsRepo().toLowerCase() ==
'revanced/revanced-integrations';
} }
Future<void> initialize() async { Future<void> initialize() async {
@ -315,18 +317,18 @@ class ManagerAPI {
if (patchBundleFile != null) { if (patchBundleFile != null) {
try { try {
final patchesObject = await PatcherAPI.patcherChannel.invokeMethod( final String patchesJson = await PatcherAPI.patcherChannel.invokeMethod(
'getPatches', 'getPatches',
{ {
'patchBundleFilePath': patchBundleFile.path, 'patchBundleFilePath': patchBundleFile.path,
'cacheDirPath': cacheDir.path, 'cacheDirPath': cacheDir.path,
}, },
); );
final List<Map<String, dynamic>> patchesMap = [];
patchesObject.forEach((patch) { final List<dynamic> patchesJsonList = jsonDecode(patchesJson);
patchesMap.add(jsonDecode('$patch')); patches = patchesJsonList
}); .map((patchJson) => Patch.fromJson(patchJson))
patches = patchesMap.map((patch) => Patch.fromJson(patch)).toList(); .toList();
return patches; return patches;
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
@ -503,25 +505,21 @@ class ManagerAPI {
return toRemove; return toRemove;
} }
Future<List<PatchedApplication>> getUnsavedApps( Future<List<PatchedApplication>> getMountedApps() async {
List<PatchedApplication> patchedApps, final List<PatchedApplication> mountedApps = [];
) async {
final List<PatchedApplication> unsavedApps = [];
final bool hasRootPermissions = await _rootAPI.hasRootPermissions(); final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
if (hasRootPermissions) { if (hasRootPermissions) {
final List<String> installedApps = await _rootAPI.getInstalledApps(); final List<String> installedApps = await _rootAPI.getInstalledApps();
for (final String packageName in installedApps) { for (final String packageName in installedApps) {
if (!patchedApps.any((app) => app.packageName == packageName)) {
final ApplicationWithIcon? application = await DeviceApps.getApp( final ApplicationWithIcon? application = await DeviceApps.getApp(
packageName, packageName,
true, true,
) as ApplicationWithIcon?; ) as ApplicationWithIcon?;
if (application != null) { if (application != null) {
unsavedApps.add( mountedApps.add(
PatchedApplication( PatchedApplication(
name: application.appName, name: application.appName,
packageName: application.packageName, packageName: application.packageName,
originalPackageName: application.packageName,
version: application.versionName!, version: application.versionName!,
apkFilePath: application.apkFilePath, apkFilePath: application.apkFilePath,
icon: application.icon, icon: application.icon,
@ -532,33 +530,8 @@ class ManagerAPI {
} }
} }
} }
}
final List<Application> userApps = return mountedApps;
await DeviceApps.getInstalledApplications();
for (final Application app in userApps) {
if (app.packageName.startsWith('app.revanced') &&
!app.packageName.startsWith('app.revanced.manager.') &&
!patchedApps.any((uapp) => uapp.packageName == app.packageName)) {
final ApplicationWithIcon? application = await DeviceApps.getApp(
app.packageName,
true,
) as ApplicationWithIcon?;
if (application != null) {
unsavedApps.add(
PatchedApplication(
name: application.appName,
packageName: application.packageName,
originalPackageName: application.packageName,
version: application.versionName!,
apkFilePath: application.apkFilePath,
icon: application.icon,
patchDate: DateTime.now(),
),
);
}
}
}
return unsavedApps;
} }
Future<void> showPatchesChangeWarningDialog(BuildContext context) { Future<void> showPatchesChangeWarningDialog(BuildContext context) {
@ -620,34 +593,20 @@ class ManagerAPI {
Future<void> reAssessSavedApps() async { Future<void> reAssessSavedApps() async {
final List<PatchedApplication> patchedApps = getPatchedApps(); final List<PatchedApplication> patchedApps = getPatchedApps();
final List<PatchedApplication> unsavedApps =
await getUnsavedApps(patchedApps); // Remove apps that are not installed anymore.
patchedApps.addAll(unsavedApps);
final List<PatchedApplication> toRemove = final List<PatchedApplication> toRemove =
await getAppsToRemove(patchedApps); await getAppsToRemove(patchedApps);
patchedApps.removeWhere((a) => toRemove.contains(a)); patchedApps.removeWhere((a) => toRemove.contains(a));
for (final PatchedApplication app in patchedApps) {
app.hasUpdates = // Determine all apps that are installed by mounting.
await hasAppUpdates(app.originalPackageName, app.patchDate); final List<PatchedApplication> mountedApps = await getMountedApps();
app.changelog = mountedApps.removeWhere(
await getAppChangelog(app.originalPackageName, app.patchDate); (app) => patchedApps
if (!app.hasUpdates) { .any((patchedApp) => patchedApp.packageName == app.packageName),
final String? currentInstalledVersion =
(await DeviceApps.getApp(app.packageName))?.versionName;
if (currentInstalledVersion != null) {
final String currentSavedVersion = app.version;
final int currentInstalledVersionInt = int.parse(
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''),
); );
final int currentSavedVersionInt = int.parse( patchedApps.addAll(mountedApps);
currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''),
);
if (currentInstalledVersionInt > currentSavedVersionInt) {
app.hasUpdates = true;
}
}
}
}
await setPatchedApps(patchedApps); await setPatchedApps(patchedApps);
} }
@ -664,37 +623,6 @@ class ManagerAPI {
return !existsNonRoot; return !existsNonRoot;
} }
Future<bool> hasAppUpdates(
String packageName,
DateTime patchDate,
) async {
final List<String> commits = await _githubAPI.getCommits(
packageName,
getPatchesRepo(),
patchDate,
);
return commits.isNotEmpty;
}
Future<List<String>> getAppChangelog(
String packageName,
DateTime patchDate,
) async {
List<String> newCommits = await _githubAPI.getCommits(
packageName,
getPatchesRepo(),
patchDate,
);
if (newCommits.isEmpty) {
newCommits = await _githubAPI.getCommits(
packageName,
getPatchesRepo(),
patchDate,
);
}
return newCommits;
}
Future<bool> isSplitApk(PatchedApplication patchedApp) async { Future<bool> isSplitApk(PatchedApplication patchedApp) async {
Application? app; Application? app;
if (patchedApp.isFromStorage) { if (patchedApp.isFromStorage) {
@ -762,6 +690,8 @@ class ManagerAPI {
Future<void> resetLastSelectedPatches() async { Future<void> resetLastSelectedPatches() async {
final File selectedPatchesFile = File(storedPatchesFile); final File selectedPatchesFile = File(storedPatchesFile);
if (selectedPatchesFile.existsSync()) {
selectedPatchesFile.deleteSync(); selectedPatchesFile.deleteSync();
} }
}
} }

View file

@ -28,10 +28,10 @@ class PatcherAPI {
List<Patch> _universalPatches = []; List<Patch> _universalPatches = [];
List<String> _compatiblePackages = []; List<String> _compatiblePackages = [];
Map filteredPatches = <String, List<Patch>>{}; Map filteredPatches = <String, List<Patch>>{};
File? _outFile; File? outFile;
Future<void> initialize() async { Future<void> initialize() async {
await _loadPatches(); await loadPatches();
await _managerAPI.downloadIntegrations(); await _managerAPI.downloadIntegrations();
final Directory appCache = await getTemporaryDirectory(); final Directory appCache = await getTemporaryDirectory();
_dataDir = await getExternalStorageDirectory() ?? appCache; _dataDir = await getExternalStorageDirectory() ?? appCache;
@ -59,12 +59,10 @@ class PatcherAPI {
} }
List<Patch> getUniversalPatches() { List<Patch> getUniversalPatches() {
return _patches return _patches.where((patch) => patch.compatiblePackages.isEmpty).toList();
.where((patch) => patch.compatiblePackages.isEmpty)
.toList();
} }
Future<void> _loadPatches() async { Future<void> loadPatches() async {
try { try {
if (_patches.isEmpty) { if (_patches.isEmpty) {
_patches = await _managerAPI.getPatches(); _patches = await _managerAPI.getPatches();
@ -85,15 +83,14 @@ class PatcherAPI {
) async { ) async {
final List<ApplicationWithIcon> filteredApps = []; final List<ApplicationWithIcon> filteredApps = [];
final bool allAppsIncluded = final bool allAppsIncluded =
_universalPatches.isNotEmpty && _universalPatches.isNotEmpty && showUniversalPatches;
showUniversalPatches;
if (allAppsIncluded) { if (allAppsIncluded) {
final appList = await DeviceApps.getInstalledApplications( final appList = await DeviceApps.getInstalledApplications(
includeAppIcons: true, includeAppIcons: true,
onlyAppsWithLaunchIntent: true, onlyAppsWithLaunchIntent: true,
); );
for(final app in appList) { for (final app in appList) {
filteredApps.add(app as ApplicationWithIcon); filteredApps.add(app as ApplicationWithIcon);
} }
} }
@ -149,55 +146,20 @@ class PatcherAPI {
.toList(); .toList();
} }
Future<bool> needsResourcePatching(
List<Patch> selectedPatches,
) async {
return selectedPatches.any(
(patch) => patch.dependencies.any(
(dep) => dep.contains('resource-'),
),
);
}
Future<bool> needsSettingsPatch(List<Patch> selectedPatches) async {
return selectedPatches.any(
(patch) => patch.dependencies.any(
(dep) => dep.contains('settings'),
),
);
}
Future<void> runPatcher( Future<void> runPatcher(
String packageName, String packageName,
String apkFilePath, String apkFilePath,
List<Patch> selectedPatches, List<Patch> selectedPatches,
) async { ) async {
final bool includeSettings = await needsSettingsPatch(selectedPatches);
if (includeSettings) {
try {
final Patch? settingsPatch = _patches.firstWhereOrNull(
(patch) =>
patch.name.contains('settings') &&
patch.compatiblePackages.any((pack) => pack.name == packageName),
);
if (settingsPatch != null) {
selectedPatches.add(settingsPatch);
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
}
final File? patchBundleFile = await _managerAPI.downloadPatches();
final File? integrationsFile = await _managerAPI.downloadIntegrations(); final File? integrationsFile = await _managerAPI.downloadIntegrations();
if (patchBundleFile != null) {
if (integrationsFile != null) {
_dataDir.createSync(); _dataDir.createSync();
_tmpDir.createSync(); _tmpDir.createSync();
final Directory workDir = _tmpDir.createTempSync('tmp-'); final Directory workDir = _tmpDir.createTempSync('tmp-');
final File inputFile = File('${workDir.path}/base.apk'); final File inputFile = File('${workDir.path}/base.apk');
final File patchedFile = File('${workDir.path}/patched.apk'); final File patchedFile = File('${workDir.path}/patched.apk');
_outFile = File('${workDir.path}/out.apk'); outFile = File('${workDir.path}/out.apk');
final Directory cacheDir = Directory('${workDir.path}/cache'); final Directory cacheDir = Directory('${workDir.path}/cache');
cacheDir.createSync(); cacheDir.createSync();
final String originalFilePath = apkFilePath; final String originalFilePath = apkFilePath;
@ -205,12 +167,11 @@ class PatcherAPI {
await patcherChannel.invokeMethod( await patcherChannel.invokeMethod(
'runPatcher', 'runPatcher',
{ {
'patchBundleFilePath': patchBundleFile.path,
'originalFilePath': originalFilePath, 'originalFilePath': originalFilePath,
'inputFilePath': inputFile.path, 'inputFilePath': inputFile.path,
'patchedFilePath': patchedFile.path, 'patchedFilePath': patchedFile.path,
'outFilePath': _outFile!.path, 'outFilePath': outFile!.path,
'integrationsPath': integrationsFile!.path, 'integrationsPath': integrationsFile.path,
'selectedPatches': selectedPatches.map((p) => p.name).toList(), 'selectedPatches': selectedPatches.map((p) => p.name).toList(),
'cacheDirPath': cacheDir.path, 'cacheDirPath': cacheDir.path,
'keyStoreFilePath': _keyStoreFile.path, 'keyStoreFilePath': _keyStoreFile.path,
@ -236,7 +197,7 @@ class PatcherAPI {
} }
Future<bool> installPatchedFile(PatchedApplication patchedApp) async { Future<bool> installPatchedFile(PatchedApplication patchedApp) async {
if (_outFile != null) { if (outFile != null) {
try { try {
if (patchedApp.isRooted) { if (patchedApp.isRooted) {
final bool hasRootPermissions = await _rootAPI.hasRootPermissions(); final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
@ -244,11 +205,11 @@ class PatcherAPI {
return _rootAPI.installApp( return _rootAPI.installApp(
patchedApp.packageName, patchedApp.packageName,
patchedApp.apkFilePath, patchedApp.apkFilePath,
_outFile!.path, outFile!.path,
); );
} }
} else { } else {
final install = await InstallPlugin.installApk(_outFile!.path); final install = await InstallPlugin.installApk(outFile!.path);
return install['isSuccess']; return install['isSuccess'];
} }
} on Exception catch (e) { } on Exception catch (e) {
@ -263,11 +224,11 @@ class PatcherAPI {
void exportPatchedFile(String appName, String version) { 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( CRFileSaver.saveFileWithDialog(
SaveFileDialogParams( SaveFileDialogParams(
sourceFilePath: _outFile!.path, sourceFilePath: outFile!.path,
destinationFileName: newName, destinationFileName: newName,
), ),
); );
@ -281,12 +242,12 @@ class PatcherAPI {
void sharePatchedFile(String appName, String version) { void sharePatchedFile(String appName, String version) {
try { try {
if (_outFile != null) { if (outFile != null) {
final String newName = _getFileName(appName, version); final String newName = _getFileName(appName, version);
final int lastSeparator = _outFile!.path.lastIndexOf('/'); final int lastSeparator = outFile!.path.lastIndexOf('/');
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'); ShareExtend.share(shareFile.path, 'file');
} }
} on Exception catch (e) { } on Exception catch (e) {

View file

@ -7,12 +7,15 @@ import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:synchronized/synchronized.dart';
import 'package:timeago/timeago.dart'; import 'package:timeago/timeago.dart';
@lazySingleton @lazySingleton
class RevancedAPI { class RevancedAPI {
late Dio _dio = Dio(); late Dio _dio = Dio();
final Lock getToolsLock = Lock();
final _cacheOptions = CacheOptions( final _cacheOptions = CacheOptions(
store: MemCacheStore(), store: MemCacheStore(),
maxStale: const Duration(days: 1), maxStale: const Duration(days: 1),
@ -66,7 +69,8 @@ class RevancedAPI {
Future<Map<String, dynamic>?> _getLatestRelease( Future<Map<String, dynamic>?> _getLatestRelease(
String extension, String extension,
String repoName, String repoName,
) async { ) {
return getToolsLock.synchronized(() async {
try { try {
final response = await _dio.get('/tools'); final response = await _dio.get('/tools');
final List<dynamic> tools = response.data['tools']; final List<dynamic> tools = response.data['tools'];
@ -81,6 +85,7 @@ class RevancedAPI {
} }
return null; return null;
} }
});
} }
Future<String?> getLatestReleaseVersion( Future<String?> getLatestReleaseVersion(

View file

@ -73,7 +73,6 @@ class AppSelectorViewModel extends BaseViewModel {
locator<PatcherViewModel>().selectedApp = PatchedApplication( locator<PatcherViewModel>().selectedApp = PatchedApplication(
name: application.appName, name: application.appName,
packageName: application.packageName, packageName: application.packageName,
originalPackageName: application.packageName,
version: application.versionName!, version: application.versionName!,
apkFilePath: application.apkFilePath, apkFilePath: application.apkFilePath,
icon: application.icon, icon: application.icon,
@ -202,7 +201,6 @@ class AppSelectorViewModel extends BaseViewModel {
locator<PatcherViewModel>().selectedApp = PatchedApplication( locator<PatcherViewModel>().selectedApp = PatchedApplication(
name: application.appName, name: application.appName,
packageName: application.packageName, packageName: application.packageName,
originalPackageName: application.packageName,
version: application.versionName!, version: application.versionName!,
apkFilePath: result.files.single.path!, apkFilePath: result.files.single.path!,
icon: application.icon, icon: application.icon,

View file

@ -37,7 +37,6 @@ class HomeViewModel extends BaseViewModel {
DateTime? _lastUpdate; DateTime? _lastUpdate;
bool showUpdatableApps = false; bool showUpdatableApps = false;
List<PatchedApplication> patchedInstalledApps = []; List<PatchedApplication> patchedInstalledApps = [];
List<PatchedApplication> patchedUpdatableApps = [];
String? _latestManagerVersion = ''; String? _latestManagerVersion = '';
File? downloadedApk; File? downloadedApk;
@ -82,7 +81,7 @@ class HomeViewModel extends BaseViewModel {
_toast.showBottom('homeView.errorDownloadMessage'); _toast.showBottom('homeView.errorDownloadMessage');
} }
} }
_getPatchedApps();
_managerAPI.reAssessSavedApps().then((_) => _getPatchedApps()); _managerAPI.reAssessSavedApps().then((_) => _getPatchedApps());
} }
@ -108,10 +107,6 @@ class HomeViewModel extends BaseViewModel {
void _getPatchedApps() { void _getPatchedApps() {
patchedInstalledApps = _managerAPI.getPatchedApps().toList(); patchedInstalledApps = _managerAPI.getPatchedApps().toList();
patchedUpdatableApps = _managerAPI
.getPatchedApps()
.where((app) => app.hasUpdates == true)
.toList();
notifyListeners(); notifyListeners();
} }
@ -469,11 +464,7 @@ class HomeViewModel extends BaseViewModel {
} }
Future<void> forceRefresh(BuildContext context) async { Future<void> forceRefresh(BuildContext context) async {
await Future.delayed(const Duration(seconds: 1));
if (_lastUpdate == null ||
_lastUpdate!.difference(DateTime.now()).inSeconds > 2) {
_managerAPI.clearAllData(); _managerAPI.clearAllData();
}
_toast.showBottom('homeView.refreshSuccess'); _toast.showBottom('homeView.refreshSuccess');
initialize(context); initialize(context);
} }

View file

@ -130,10 +130,6 @@ class InstallerViewModel extends BaseViewModel {
Future<void> runPatcher() async { Future<void> runPatcher() async {
try { try {
update(0.0, 'Initializing...', 'Initializing installer');
if (_patches.isNotEmpty) {
try {
update(0.1, '', 'Creating working directory');
await _patcherAPI.runPatcher( await _patcherAPI.runPatcher(
_app.packageName, _app.packageName,
_app.apkFilePath, _app.apkFilePath,
@ -142,16 +138,20 @@ class InstallerViewModel extends BaseViewModel {
} on Exception catch (e) { } on Exception catch (e) {
update( update(
-100.0, -100.0,
'Aborted...', 'Failed...',
'An error occurred! Aborted\nError:\n$e', 'Something went wrong:\n$e',
); );
if (kDebugMode) { if (kDebugMode) {
print(e); print(e);
} }
} }
} else {
update(-100.0, 'Aborted...', 'No app or patches selected! Aborted'); // Necessary to reset the state of patches by reloading them
} // in a later patching process.
_managerAPI.patches.clear();
await _patcherAPI.loadPatches();
try {
if (FlutterBackground.isBackgroundExecutionEnabled) { if (FlutterBackground.isBackgroundExecutionEnabled) {
try { try {
FlutterBackground.disableBackgroundExecution(); FlutterBackground.disableBackgroundExecution();
@ -209,8 +209,8 @@ class InstallerViewModel extends BaseViewModel {
), ),
RadioListTile( RadioListTile(
title: I18nText('installerView.installNonRootType'), title: I18nText('installerView.installNonRootType'),
subtitle: I18nText('installerView.installRecommendedType'), contentPadding:
contentPadding: const EdgeInsets.symmetric(horizontal: 16), const EdgeInsets.symmetric(horizontal: 16),
value: 0, value: 0,
groupValue: value, groupValue: value,
onChanged: (selected) { onChanged: (selected) {
@ -219,7 +219,8 @@ class InstallerViewModel extends BaseViewModel {
), ),
RadioListTile( RadioListTile(
title: I18nText('installerView.installRootType'), title: I18nText('installerView.installRootType'),
contentPadding: const EdgeInsets.symmetric(horizontal: 16), contentPadding:
const EdgeInsets.symmetric(horizontal: 16),
value: 1, value: 1,
groupValue: value, groupValue: value,
onChanged: (selected) { onChanged: (selected) {
@ -257,9 +258,9 @@ class InstallerViewModel extends BaseViewModel {
Future<void> stopPatcher() async { Future<void> stopPatcher() async {
try { try {
isCanceled = true; isCanceled = true;
update(0.5, 'Aborting...', 'Canceling patching process'); update(0.5, 'Canceling...', 'Canceling patching process');
await _patcherAPI.stopPatcher(); await _patcherAPI.stopPatcher();
update(-100.0, 'Aborted...', 'Press back to exit'); update(-100.0, 'Canceled...', 'Press back to exit');
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
print(e); print(e);
@ -270,34 +271,6 @@ class InstallerViewModel extends BaseViewModel {
Future<void> installResult(BuildContext context, bool installAsRoot) async { Future<void> installResult(BuildContext context, bool installAsRoot) async {
try { try {
_app.isRooted = installAsRoot; _app.isRooted = installAsRoot;
final bool hasMicroG =
_patches.any((p) => p.name.endsWith('MicroG support'));
final bool rootMicroG = installAsRoot && hasMicroG;
final bool rootFromStorage = installAsRoot && _app.isFromStorage;
final bool ytWithoutRootMicroG =
!installAsRoot && !hasMicroG && _app.packageName.contains('youtube');
if (rootMicroG || rootFromStorage || ytWithoutRootMicroG) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('installerView.installErrorDialogTitle'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText(
rootMicroG
? 'installerView.installErrorDialogText1'
: rootFromStorage
? 'installerView.installErrorDialogText3'
: 'installerView.installErrorDialogText2',
),
actions: <Widget>[
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
} else {
update( update(
1.0, 1.0,
'Installing...', 'Installing...',
@ -307,19 +280,24 @@ class InstallerViewModel extends BaseViewModel {
); );
isInstalled = await _patcherAPI.installPatchedFile(_app); isInstalled = await _patcherAPI.installPatchedFile(_app);
if (isInstalled) { if (isInstalled) {
update(1.0, 'Installed!', 'Installed!');
_app.isFromStorage = false; _app.isFromStorage = false;
_app.patchDate = DateTime.now(); _app.patchDate = DateTime.now();
_app.appliedPatches = _patches.map((p) => p.name).toList(); _app.appliedPatches = _patches.map((p) => p.name).toList();
if (hasMicroG) {
_app.name += ' ReVanced'; // In case a patch changed the app name or package name,
_app.packageName = _app.packageName.replaceFirst( // update the app info.
'com.google.', final app =
'app.revanced.', await DeviceApps.getAppFromStorage(_patcherAPI.outFile!.path);
); if (app != null) {
_app.name = app.appName;
_app.packageName = app.packageName;
} }
await _managerAPI.savePatchedApp(_app); await _managerAPI.savePatchedApp(_app);
}
update(1.0, 'Installed!', 'Installed!');
} else {
// TODO(aabed): Show error message.
} }
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {

View file

@ -39,9 +39,9 @@ class NavigationViewModel extends IndexTrackingViewModel {
// Force disable Material You on Android 11 and below // Force disable Material You on Android 11 and below
if (dynamicTheme.themeId.isOdd) { if (dynamicTheme.themeId.isOdd) {
const int ANDROID_12_SDK_VERSION = 31; const int android12SdkVersion = 31;
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo; final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
if (info.version.sdkInt < ANDROID_12_SDK_VERSION) { if (info.version.sdkInt < android12SdkVersion) {
await prefs.setInt('themeMode', 0); await prefs.setInt('themeMode', 0);
await prefs.setBool('useDynamicTheme', false); await prefs.setBool('useDynamicTheme', false);
await dynamicTheme.setTheme(0); await dynamicTheme.setTheme(0);

View file

@ -44,49 +44,6 @@ class PatcherViewModel extends BaseViewModel {
return selectedApp == null; return selectedApp == null;
} }
Future<bool> isValidPatchConfig() async {
final bool needsResourcePatching = await _patcherAPI.needsResourcePatching(
selectedPatches,
);
if (needsResourcePatching && selectedApp != null) {
final bool isSplit = await _managerAPI.isSplitApk(selectedApp!);
return !isSplit;
}
return true;
}
Future<void> showPatchConfirmationDialog(BuildContext context) async {
final bool isValid = await isValidPatchConfig();
if (context.mounted) {
if (isValid) {
showArmv7WarningDialog(context);
} else {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('warning'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText('patcherView.splitApkWarningDialogText'),
actions: <Widget>[
CustomMaterialButton(
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(),
),
CustomMaterialButton(
label: I18nText('yesButton'),
isFilled: false,
onPressed: () {
Navigator.of(context).pop();
showArmv7WarningDialog(context);
},
),
],
),
);
}
}
}
Future<void> showRemovedPatchesDialog(BuildContext context) async { Future<void> showRemovedPatchesDialog(BuildContext context) async {
if (removedPatches.isNotEmpty) { if (removedPatches.isNotEmpty) {
return showDialog( return showDialog(
@ -115,7 +72,7 @@ class PatcherViewModel extends BaseViewModel {
), ),
); );
} else { } else {
showArmv7WarningDialog(context); showArmv7WarningDialog(context); // TODO(aabed): Find out why this is here
} }
} }
@ -185,9 +142,9 @@ class PatcherViewModel extends BaseViewModel {
this.selectedPatches.clear(); this.selectedPatches.clear();
removedPatches.clear(); removedPatches.clear();
final List<String> selectedPatches = final List<String> selectedPatches =
await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName); await _managerAPI.getSelectedPatches(selectedApp!.packageName);
final List<Patch> patches = final List<Patch> patches =
_patcherAPI.getFilteredPatches(selectedApp!.originalPackageName); _patcherAPI.getFilteredPatches(selectedApp!.packageName);
this this
.selectedPatches .selectedPatches
.addAll(patches.where((patch) => selectedPatches.contains(patch.name))); .addAll(patches.where((patch) => selectedPatches.contains(patch.name)));
@ -203,7 +160,7 @@ class PatcherViewModel extends BaseViewModel {
.selectedPatches .selectedPatches
.removeWhere((patch) => patch.compatiblePackages.isEmpty); .removeWhere((patch) => patch.compatiblePackages.isEmpty);
} }
final usedPatches = _managerAPI.getUsedPatches(selectedApp!.originalPackageName); final usedPatches = _managerAPI.getUsedPatches(selectedApp!.packageName);
for (final patch in usedPatches){ for (final patch in usedPatches){
if (!patches.any((p) => p.name == patch.name)){ if (!patches.any((p) => p.name == patch.name)){
removedPatches.add('\u2022 ${patch.name}'); removedPatches.add('\u2022 ${patch.name}');

View file

@ -194,7 +194,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
return PatchItem( return PatchItem(
name: patch.name, name: patch.name,
simpleName: patch.getSimpleName(), simpleName: patch.getSimpleName(),
description: patch.description, description: patch.description ?? '',
packageVersion: model.getAppInfo().version, packageVersion: model.getAppInfo().version,
supportedPackageVersions: supportedPackageVersions:
model.getSupportedVersions(patch), model.getSupportedVersions(patch),
@ -246,7 +246,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
return PatchItem( return PatchItem(
name: patch.name, name: patch.name,
simpleName: patch.getSimpleName(), simpleName: patch.getSimpleName(),
description: patch.description, description: patch.description ?? '',
packageVersion: packageVersion:
model.getAppInfo().version, model.getAppInfo().version,
supportedPackageVersions: supportedPackageVersions:

View file

@ -28,7 +28,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
getPatchesVersion().whenComplete(() => notifyListeners()); getPatchesVersion().whenComplete(() => notifyListeners());
patches.addAll( patches.addAll(
_patcherAPI.getFilteredPatches( _patcherAPI.getFilteredPatches(
selectedApp!.originalPackageName, selectedApp!.packageName,
), ),
); );
patches.sort((a, b) { patches.sort((a, b) {
@ -98,11 +98,11 @@ class PatchesSelectorViewModel extends BaseViewModel {
void selectDefaultPatches() { void selectDefaultPatches() {
selectedPatches.clear(); selectedPatches.clear();
if (locator<PatcherViewModel>().selectedApp?.originalPackageName != null) { if (locator<PatcherViewModel>().selectedApp?.packageName != null) {
selectedPatches.addAll( selectedPatches.addAll(
_patcherAPI _patcherAPI
.getFilteredPatches( .getFilteredPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName, locator<PatcherViewModel>().selectedApp!.packageName,
) )
.where( .where(
(element) => (element) =>
@ -187,7 +187,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
final List<String> selectedPatches = final List<String> selectedPatches =
this.selectedPatches.map((patch) => patch.name).toList(); this.selectedPatches.map((patch) => patch.name).toList();
await _managerAPI.setSelectedPatches( await _managerAPI.setSelectedPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName, locator<PatcherViewModel>().selectedApp!.packageName,
selectedPatches, selectedPatches,
); );
} }
@ -195,7 +195,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
Future<void> loadSelectedPatches(BuildContext context) async { Future<void> loadSelectedPatches(BuildContext context) async {
if (_managerAPI.isPatchesChangeEnabled()) { if (_managerAPI.isPatchesChangeEnabled()) {
final List<String> selectedPatches = await _managerAPI.getSelectedPatches( final List<String> selectedPatches = await _managerAPI.getSelectedPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName, locator<PatcherViewModel>().selectedApp!.packageName,
); );
if (selectedPatches.isNotEmpty) { if (selectedPatches.isNotEmpty) {
this.selectedPatches.clear(); this.selectedPatches.clear();

View file

@ -71,12 +71,6 @@ class SettingsViewModel extends BaseViewModel {
actions: [ actions: [
CustomMaterialButton( CustomMaterialButton(
isFilled: false, isFilled: false,
label: I18nText('noButton'),
onPressed: () {
Navigator.of(context).pop();
},
),
CustomMaterialButton(
label: I18nText('yesButton'), label: I18nText('yesButton'),
onPressed: () { onPressed: () {
_managerAPI.setChangingToggleModified(true); _managerAPI.setChangingToggleModified(true);
@ -84,6 +78,12 @@ class SettingsViewModel extends BaseViewModel {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
CustomMaterialButton(
label: I18nText('noButton'),
onPressed: () {
Navigator.of(context).pop();
},
),
], ],
), ),
); );

View file

@ -222,22 +222,6 @@ class AppInfoView extends StatelessWidget {
subtitle: Text(app.packageName), subtitle: Text(app.packageName),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
ListTile(
contentPadding:
const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText(
'appInfoView.originalPackageNameLabel',
child: const Text(
'',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
),
subtitle: Text(app.originalPackageName),
),
const SizedBox(height: 4),
ListTile( ListTile(
contentPadding: contentPadding:
const EdgeInsets.symmetric(horizontal: 20.0), const EdgeInsets.symmetric(horizontal: 20.0),

View file

@ -13,7 +13,6 @@ import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart'; import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:revanced_manager/utils/string.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class AppInfoViewModel extends BaseViewModel { class AppInfoViewModel extends BaseViewModel {
@ -147,17 +146,7 @@ class AppInfoViewModel extends BaseViewModel {
} }
String getAppliedPatchesString(List<String> appliedPatches) { String getAppliedPatchesString(List<String> appliedPatches) {
final List<String> names = appliedPatches return '\u2022 ${appliedPatches.join('\n\u2022 ')}';
.map(
(p) => p
.replaceAll('-', ' ')
.split('-')
.join(' ')
.toTitleCase()
.replaceFirst('Microg', 'MicroG'),
)
.toList();
return '\u2022 ${names.join('\n\u2022 ')}';
} }
void openApp(PatchedApplication app) { void openApp(PatchedApplication app) {

View file

@ -79,8 +79,6 @@ class InstalledAppsCard extends StatelessWidget {
icon: app.icon, icon: app.icon,
name: app.name, name: app.name,
patchDate: app.patchDate, patchDate: app.patchDate,
changelog: app.changelog,
isUpdatableApp: false,
onPressed: () => onPressed: () =>
locator<HomeViewModel>().navigateToAppInfo(app), locator<HomeViewModel>().navigateToAppInfo(app),
), ),

View file

@ -5,8 +5,8 @@ import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_api_url.dart'; import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_api_url.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_sources.dart'; import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_sources.dart';
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart'; import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_enable_patches_selection.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_auto_update_patches.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_auto_update_patches.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_enable_patches_selection.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';

View file

@ -1,6 +1,5 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
@ -13,63 +12,30 @@ class ApplicationItem extends StatefulWidget {
required this.icon, required this.icon,
required this.name, required this.name,
required this.patchDate, required this.patchDate,
required this.changelog,
required this.isUpdatableApp,
required this.onPressed, required this.onPressed,
}) : super(key: key); }) : super(key: key);
final Uint8List icon; final Uint8List icon;
final String name; final String name;
final DateTime patchDate; final DateTime patchDate;
final List<String> changelog;
final bool isUpdatableApp;
final Function() onPressed; final Function() onPressed;
@override @override
State<ApplicationItem> createState() => _ApplicationItemState(); State<ApplicationItem> createState() => _ApplicationItemState();
} }
class _ApplicationItemState extends State<ApplicationItem> class _ApplicationItemState extends State<ApplicationItem> {
with TickerProviderStateMixin {
late AnimationController _animationController;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ExpandableController expController = ExpandableController();
return Container( return Container(
margin: const EdgeInsets.only(bottom: 16.0), margin: const EdgeInsets.only(bottom: 16.0),
child: CustomCard( child: CustomCard(
onTap: () { child: Row(
expController.toggle();
_animationController.isCompleted
? _animationController.reverse()
: _animationController.forward();
},
child: ExpandablePanel(
controller: expController,
theme: const ExpandableThemeData(
inkWellBorderRadius: BorderRadius.all(Radius.circular(16)),
tapBodyToCollapse: false,
tapBodyToExpand: false,
tapHeaderToExpand: false,
hasIcon: false,
animationDuration: Duration(milliseconds: 450),
),
header: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Flexible( Flexible(
@ -110,23 +76,13 @@ class _ApplicationItemState extends State<ApplicationItem>
), ),
Row( Row(
children: [ children: [
RotationTransition(
turns: Tween(begin: 0.0, end: 0.50)
.animate(_animationController),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(Icons.arrow_drop_down),
),
),
const SizedBox(width: 8), const SizedBox(width: 8),
Column( Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[ children: <Widget>[
CustomMaterialButton( CustomMaterialButton(
label: widget.isUpdatableApp label: I18nText('applicationItem.infoButton'),
? I18nText('applicationItem.patchButton')
: I18nText('applicationItem.infoButton'),
onPressed: widget.onPressed, onPressed: widget.onPressed,
), ),
], ],
@ -135,30 +91,6 @@ class _ApplicationItemState extends State<ApplicationItem>
), ),
], ],
), ),
collapsed: const SizedBox(),
expanded: Padding(
padding: const EdgeInsets.only(
top: 16.0,
left: 4.0,
right: 4.0,
bottom: 4.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
I18nText(
'applicationItem.changelogLabel',
child: const Text(
'',
style: TextStyle(fontWeight: FontWeight.w700),
),
),
const SizedBox(height: 4),
Text('\u2022 ${widget.changelog.join('\n\u2022 ')}'),
],
),
),
),
), ),
); );
} }

View file

@ -4,7 +4,7 @@ homepage: https://github.com/revanced/revanced-manager
publish_to: 'none' publish_to: 'none'
version: 1.10.3+101000300 version: 1.11.0+101100000
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'
@ -75,6 +75,7 @@ dependencies:
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: ^2.1.0
synchronized: ^3.1.0
dev_dependencies: dev_dependencies:
json_serializable: ^6.6.1 json_serializable: ^6.6.1