diff --git a/android/app/build.gradle b/android/app/build.gradle index 9f8312e7dc..7f8b88f905 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -85,7 +85,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // ReVanced - implementation "app.revanced:revanced-patcher:14.2.2" + implementation "app.revanced:revanced-patcher:16.0.0" // Signing & aligning implementation("org.bouncycastle:bcpkix-jdk15on:1.70") diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 08e9e77c22..3a2c4f8c8c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -42,6 +42,10 @@ + + diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt new file mode 100644 index 0000000000..f5c7ad6937 --- /dev/null +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt @@ -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 = 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 + } +} diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt index 22463f8c19..b88abb82cd 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt @@ -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.structures.ZipEntry import app.revanced.patcher.PatchBundleLoader +import app.revanced.patcher.PatchSet import app.revanced.patcher.Patcher 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 io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel import kotlinx.coroutines.cancel import kotlinx.coroutines.runBlocking +import org.json.JSONArray +import org.json.JSONObject import java.io.File import java.io.PrintWriter import java.io.StringWriter +import java.lang.Error import java.util.logging.LogRecord import java.util.logging.Logger @@ -33,6 +32,8 @@ class MainActivity : FlutterActivity() { private var cancel: Boolean = false private var stopResult: MethodChannel.Result? = null + private lateinit var patches: PatchSet + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) @@ -48,7 +49,6 @@ class MainActivity : FlutterActivity() { mainChannel.setMethodCallHandler { call, result -> when (call.method) { "runPatcher" -> { - val patchBundleFilePath = call.argument("patchBundleFilePath") val originalFilePath = call.argument("originalFilePath") val inputFilePath = call.argument("inputFilePath") val patchedFilePath = call.argument("patchedFilePath") @@ -59,7 +59,7 @@ class MainActivity : FlutterActivity() { val keyStoreFilePath = call.argument("keyStoreFilePath") val keystorePassword = call.argument("keystorePassword") - if (patchBundleFilePath != null && + if ( originalFilePath != null && inputFilePath != null && patchedFilePath != null && @@ -73,7 +73,6 @@ class MainActivity : FlutterActivity() { cancel = false runPatcher( result, - patchBundleFilePath, originalFilePath, inputFilePath, patchedFilePath, @@ -93,29 +92,44 @@ class MainActivity : FlutterActivity() { } "getPatches" -> { - val patchBundleFilePath = call.argument("patchBundleFilePath") - val cacheDirPath = call.argument("cacheDirPath") + val patchBundleFilePath = call.argument("patchBundleFilePath")!! + val cacheDirPath = call.argument("cacheDirPath")!! - if (patchBundleFilePath != null) { - val patches = PatchBundleLoader.Dex( + try { + patches = PatchBundleLoader.Dex( File(patchBundleFilePath), optimizedDexDirectory = File(cacheDirPath) - ).map { patch -> - val map = HashMap() - map["\"name\""] = "\"${patch.patchName.replace("\"","\\\"")}\"" - map["\"description\""] = "\"${patch.description?.replace("\"","\\\"")}\"" - map["\"excluded\""] = !patch.include - map["\"dependencies\""] = patch.dependencies?.map { "\"${it.java.patchName}\"" } ?: emptyList() - map["\"compatiblePackages\""] = patch.compatiblePackages?.map { - val map2 = HashMap() - map2["\"name\""] = "\"${it.name}\"" - map2["\"versions\""] = it.versions.map { version -> "\"${version}\"" } - map2 - } ?: emptyList() - map + ) + } catch (ex: Exception) { + return@setMethodCallHandler result.notImplemented() + } catch (err: Error) { + return@setMethodCallHandler 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) } - result.success(patches) - } else result.notImplemented() + }.toString().let(result::success) } else -> result.notImplemented() @@ -125,7 +139,6 @@ class MainActivity : FlutterActivity() { private fun runPatcher( result: MethodChannel.Result, - patchBundleFilePath: String, originalFilePath: String, inputFilePath: String, patchedFilePath: String, @@ -168,8 +181,11 @@ class MainActivity : FlutterActivity() { } 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) + } override fun flush() = Unit override fun close() = flush() @@ -209,10 +225,7 @@ class MainActivity : FlutterActivity() { updateProgress(0.1, "Loading patches...", "Loading patches") - val patches = PatchBundleLoader.Dex( - File(patchBundleFilePath), - optimizedDexDirectory = cacheDir - ).filter { patch -> + val patches = patches.filter { patch -> val isCompatible = patch.compatiblePackages?.any { it.name == patcher.context.packageMetadata.packageName } ?: false @@ -220,7 +233,7 @@ class MainActivity : FlutterActivity() { val compatibleOrUniversal = isCompatible || patch.compatiblePackages.isNullOrEmpty() - compatibleOrUniversal && selectedPatches.any { it == patch.patchName } + compatibleOrUniversal && selectedPatches.any { it == patch.name } } if (cancel) { @@ -251,9 +264,9 @@ class MainActivity : FlutterActivity() { val msg = patchResult.exception?.let { val writer = StringWriter() it.printStackTrace(PrintWriter(writer)) - "${patchResult.patchName} failed: $writer" + "${patchResult.patch.name} failed: $writer" } ?: run { - "${patchResult.patchName} succeeded" + "${patchResult.patch.name} succeeded" } updateProgress(progress, "", msg) @@ -317,7 +330,7 @@ class MainActivity : FlutterActivity() { val stack = ex.stackTraceToString() updateProgress( -100.0, - "Aborted", + "Failed", "An error occurred:\n$stack" ) } diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 05694ced7a..d3fe112855 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -23,13 +23,13 @@ "widgetTitle": "Dashboard", "updatesSubtitle": "Updates", - "patchedSubtitle": "Patched applications", + "patchedSubtitle": "Patched apps", "noUpdates": "No updates available", "WIP": "Work in progress...", - "noInstallations": "No patched applications installed", + "noInstallations": "No patched apps installed", "installUpdate": "Continue to install the update?", "updateDialogTitle": "Update Manager", @@ -56,9 +56,7 @@ "updatesDisabled": "Updating a patched app is currently disabled. Repatch the app again." }, "applicationItem": { - "patchButton": "Patch", - "infoButton": "Info", - "changelogLabel": "Changelog" + "infoButton": "Info" }, "latestCommitCard": { "loadingLabel": "Loading...", @@ -71,9 +69,8 @@ "widgetTitle": "Patcher", "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?", - "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?" }, "appSelectorCard": { @@ -148,9 +145,8 @@ "installTypeDescription": "Select the installation type to proceed with.", "installButton": "Install", - "installRootType": "Root", - "installNonRootType": "Non-root", - "installRecommendedType": "Recommended", + "installRootType": "Mount", + "installNonRootType": "Normal", "pressBackAgain": "Press back again to cancel", "openButton": "Open", @@ -162,10 +158,6 @@ "exportApkButtonTooltip": "Export patched APK", "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..." }, "settingsView": { @@ -285,7 +277,6 @@ "rootDialogText": "App was installed with superuser permissions, but currently ReVanced Manager has no permissions.\nPlease grant superuser permissions first.", "packageNameLabel": "Package name", - "originalPackageNameLabel": "Original package name", "installTypeLabel": "Installation type", "rootTypeLabel": "Root", "nonRootTypeLabel": "Non-root", diff --git a/docs/4_building.md b/docs/4_building.md index d4c0887cc8..adcf47dd62 100644 --- a/docs/4_building.md +++ b/docs/4_building.md @@ -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) -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 gpr.user = YourUsername diff --git a/lib/models/patch.dart b/lib/models/patch.dart index 7acf05ba7e..fe2b3c33c4 100644 --- a/lib/models/patch.dart +++ b/lib/models/patch.dart @@ -1,5 +1,4 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:revanced_manager/utils/string.dart'; part 'patch.g.dart'; @@ -9,26 +8,19 @@ class Patch { required this.name, required this.description, required this.excluded, - required this.dependencies, required this.compatiblePackages, }); factory Patch.fromJson(Map json) => _$PatchFromJson(json); final String name; - final String description; + final String? description; final bool excluded; - final List dependencies; final List compatiblePackages; Map toJson() => _$PatchToJson(this); String getSimpleName() { - return name - .replaceAll('-', ' ') - .split('-') - .join(' ') - .toTitleCase() - .replaceFirst('Microg', 'MicroG'); + return name; } } diff --git a/lib/models/patched_application.dart b/lib/models/patched_application.dart index 90bfb9a31e..23517595f8 100644 --- a/lib/models/patched_application.dart +++ b/lib/models/patched_application.dart @@ -9,23 +9,19 @@ class PatchedApplication { PatchedApplication({ required this.name, required this.packageName, - required this.originalPackageName, required this.version, required this.apkFilePath, required this.icon, required this.patchDate, this.isRooted = false, this.isFromStorage = false, - this.hasUpdates = false, this.appliedPatches = const [], - this.changelog = const [], }); factory PatchedApplication.fromJson(Map json) => _$PatchedApplicationFromJson(json); String name; String packageName; - String originalPackageName; String version; final String apkFilePath; @JsonKey( @@ -36,9 +32,7 @@ class PatchedApplication { DateTime patchDate; bool isRooted; bool isFromStorage; - bool hasUpdates; List appliedPatches; - List changelog; Map toJson() => _$PatchedApplicationToJson(this); diff --git a/lib/services/github_api.dart b/lib/services/github_api.dart index 0949f1b9a3..8d3244b3b6 100644 --- a/lib/services/github_api.dart +++ b/lib/services/github_api.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:io'; import 'package:collection/collection.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:injectable/injectable.dart'; import 'package:revanced_manager/app/app.locator.dart'; -import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/services/manager_api.dart'; @lazySingleton @@ -21,17 +19,6 @@ class GithubAPI { priority: CachePriority.high, ); - final Map 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 initialize(String repoUrl) async { try { _dio = Dio( @@ -142,38 +129,6 @@ class GithubAPI { } } - Future> 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 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 getLatestReleaseFile( String extension, String repoName, @@ -237,30 +192,4 @@ class GithubAPI { } return null; } - - Future> getPatches( - String repoName, - String version, - String url, - ) async { - List patches = []; - try { - final File? f = await getPatchesReleaseFile( - '.json', - repoName, - version, - url, - ); - if (f != null) { - final List list = jsonDecode(f.readAsStringSync()); - patches = list.map((patch) => Patch.fromJson(patch)).toList(); - } - } on Exception catch (e) { - if (kDebugMode) { - print(e); - } - } - - return patches; - } } diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart index 3401432bdd..d3eed8459f 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -42,12 +42,14 @@ class ManagerAPI { String defaultManagerRepo = 'revanced/revanced-manager'; String? patchesVersion = ''; String? integrationsVersion = ''; + bool isDefaultPatchesRepo() { return getPatchesRepo().toLowerCase() == 'revanced/revanced-patches'; } bool isDefaultIntegrationsRepo() { - return getIntegrationsRepo().toLowerCase() == 'revanced/revanced-integrations'; + return getIntegrationsRepo().toLowerCase() == + 'revanced/revanced-integrations'; } Future initialize() async { @@ -309,24 +311,24 @@ class ManagerAPI { final Directory appCache = await getTemporaryDirectory(); Directory('${appCache.path}/cache').createSync(); final Directory workDir = - Directory('${appCache.path}/cache').createTempSync('tmp-'); + Directory('${appCache.path}/cache').createTempSync('tmp-'); final Directory cacheDir = Directory('${workDir.path}/cache'); cacheDir.createSync(); if (patchBundleFile != null) { try { - final patchesObject = await PatcherAPI.patcherChannel.invokeMethod( + final String patchesJson = await PatcherAPI.patcherChannel.invokeMethod( 'getPatches', { 'patchBundleFilePath': patchBundleFile.path, 'cacheDirPath': cacheDir.path, }, ); - final List> patchesMap = []; - patchesObject.forEach((patch) { - patchesMap.add(jsonDecode('$patch')); - }); - patches = patchesMap.map((patch) => Patch.fromJson(patch)).toList(); + + final List patchesJsonList = jsonDecode(patchesJson); + patches = patchesJsonList + .map((patchJson) => Patch.fromJson(patchJson)) + .toList(); return patches; } on Exception catch (e) { if (kDebugMode) { @@ -503,62 +505,33 @@ class ManagerAPI { return toRemove; } - Future> getUnsavedApps( - List patchedApps, - ) async { - final List unsavedApps = []; + Future> getMountedApps() async { + final List mountedApps = []; final bool hasRootPermissions = await _rootAPI.hasRootPermissions(); if (hasRootPermissions) { final List installedApps = await _rootAPI.getInstalledApps(); for (final String packageName in installedApps) { - if (!patchedApps.any((app) => app.packageName == packageName)) { - final ApplicationWithIcon? application = await DeviceApps.getApp( - 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(), - isRooted: true, - ), - ); - } - } - } - } - final List userApps = - 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, + packageName, true, ) as ApplicationWithIcon?; if (application != null) { - unsavedApps.add( + mountedApps.add( PatchedApplication( name: application.appName, packageName: application.packageName, - originalPackageName: application.packageName, version: application.versionName!, apkFilePath: application.apkFilePath, icon: application.icon, patchDate: DateTime.now(), + isRooted: true, ), ); } } } - return unsavedApps; + + return mountedApps; } Future showPatchesChangeWarningDialog(BuildContext context) { @@ -620,34 +593,20 @@ class ManagerAPI { Future reAssessSavedApps() async { final List patchedApps = getPatchedApps(); - final List unsavedApps = - await getUnsavedApps(patchedApps); - patchedApps.addAll(unsavedApps); + + // Remove apps that are not installed anymore. final List toRemove = - await getAppsToRemove(patchedApps); + await getAppsToRemove(patchedApps); patchedApps.removeWhere((a) => toRemove.contains(a)); - for (final PatchedApplication app in patchedApps) { - app.hasUpdates = - await hasAppUpdates(app.originalPackageName, app.patchDate); - app.changelog = - await getAppChangelog(app.originalPackageName, app.patchDate); - if (!app.hasUpdates) { - 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( - currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''), - ); - if (currentInstalledVersionInt > currentSavedVersionInt) { - app.hasUpdates = true; - } - } - } - } + + // Determine all apps that are installed by mounting. + final List mountedApps = await getMountedApps(); + mountedApps.removeWhere( + (app) => patchedApps + .any((patchedApp) => patchedApp.packageName == app.packageName), + ); + patchedApps.addAll(mountedApps); + await setPatchedApps(patchedApps); } @@ -664,37 +623,6 @@ class ManagerAPI { return !existsNonRoot; } - Future hasAppUpdates( - String packageName, - DateTime patchDate, - ) async { - final List commits = await _githubAPI.getCommits( - packageName, - getPatchesRepo(), - patchDate, - ); - return commits.isNotEmpty; - } - - Future> getAppChangelog( - String packageName, - DateTime patchDate, - ) async { - List newCommits = await _githubAPI.getCommits( - packageName, - getPatchesRepo(), - patchDate, - ); - if (newCommits.isEmpty) { - newCommits = await _githubAPI.getCommits( - packageName, - getPatchesRepo(), - patchDate, - ); - } - return newCommits; - } - Future isSplitApk(PatchedApplication patchedApp) async { Application? app; if (patchedApp.isFromStorage) { @@ -762,6 +690,8 @@ class ManagerAPI { Future resetLastSelectedPatches() async { final File selectedPatchesFile = File(storedPatchesFile); - selectedPatchesFile.deleteSync(); + if (selectedPatchesFile.existsSync()) { + selectedPatchesFile.deleteSync(); + } } } diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index 5838ab3b7f..dc4ffabd20 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -28,10 +28,10 @@ class PatcherAPI { List _universalPatches = []; List _compatiblePackages = []; Map filteredPatches = >{}; - File? _outFile; + File? outFile; Future initialize() async { - await _loadPatches(); + await loadPatches(); await _managerAPI.downloadIntegrations(); final Directory appCache = await getTemporaryDirectory(); _dataDir = await getExternalStorageDirectory() ?? appCache; @@ -59,12 +59,10 @@ class PatcherAPI { } List getUniversalPatches() { - return _patches - .where((patch) => patch.compatiblePackages.isEmpty) - .toList(); + return _patches.where((patch) => patch.compatiblePackages.isEmpty).toList(); } - Future _loadPatches() async { + Future loadPatches() async { try { if (_patches.isEmpty) { _patches = await _managerAPI.getPatches(); @@ -85,15 +83,14 @@ class PatcherAPI { ) async { final List filteredApps = []; final bool allAppsIncluded = - _universalPatches.isNotEmpty && - showUniversalPatches; + _universalPatches.isNotEmpty && showUniversalPatches; if (allAppsIncluded) { final appList = await DeviceApps.getInstalledApplications( includeAppIcons: true, onlyAppsWithLaunchIntent: true, ); - for(final app in appList) { + for (final app in appList) { filteredApps.add(app as ApplicationWithIcon); } } @@ -149,55 +146,20 @@ class PatcherAPI { .toList(); } - Future needsResourcePatching( - List selectedPatches, - ) async { - return selectedPatches.any( - (patch) => patch.dependencies.any( - (dep) => dep.contains('resource-'), - ), - ); - } - - Future needsSettingsPatch(List selectedPatches) async { - return selectedPatches.any( - (patch) => patch.dependencies.any( - (dep) => dep.contains('settings'), - ), - ); - } - Future runPatcher( String packageName, String apkFilePath, List selectedPatches, ) 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(); - if (patchBundleFile != null) { + + if (integrationsFile != null) { _dataDir.createSync(); _tmpDir.createSync(); final Directory workDir = _tmpDir.createTempSync('tmp-'); final File inputFile = File('${workDir.path}/base.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'); cacheDir.createSync(); final String originalFilePath = apkFilePath; @@ -205,12 +167,11 @@ class PatcherAPI { await patcherChannel.invokeMethod( 'runPatcher', { - 'patchBundleFilePath': patchBundleFile.path, 'originalFilePath': originalFilePath, 'inputFilePath': inputFile.path, 'patchedFilePath': patchedFile.path, - 'outFilePath': _outFile!.path, - 'integrationsPath': integrationsFile!.path, + 'outFilePath': outFile!.path, + 'integrationsPath': integrationsFile.path, 'selectedPatches': selectedPatches.map((p) => p.name).toList(), 'cacheDirPath': cacheDir.path, 'keyStoreFilePath': _keyStoreFile.path, @@ -236,7 +197,7 @@ class PatcherAPI { } Future installPatchedFile(PatchedApplication patchedApp) async { - if (_outFile != null) { + if (outFile != null) { try { if (patchedApp.isRooted) { final bool hasRootPermissions = await _rootAPI.hasRootPermissions(); @@ -244,11 +205,11 @@ class PatcherAPI { return _rootAPI.installApp( patchedApp.packageName, patchedApp.apkFilePath, - _outFile!.path, + outFile!.path, ); } } else { - final install = await InstallPlugin.installApk(_outFile!.path); + final install = await InstallPlugin.installApk(outFile!.path); return install['isSuccess']; } } on Exception catch (e) { @@ -263,11 +224,11 @@ class PatcherAPI { void exportPatchedFile(String appName, String version) { try { - if (_outFile != null) { + if (outFile != null) { final String newName = _getFileName(appName, version); CRFileSaver.saveFileWithDialog( SaveFileDialogParams( - sourceFilePath: _outFile!.path, + sourceFilePath: outFile!.path, destinationFileName: newName, ), ); @@ -281,12 +242,12 @@ class PatcherAPI { void sharePatchedFile(String appName, String version) { try { - if (_outFile != null) { + if (outFile != null) { final String newName = _getFileName(appName, version); - final int lastSeparator = _outFile!.path.lastIndexOf('/'); + final int lastSeparator = outFile!.path.lastIndexOf('/'); final String newPath = - _outFile!.path.substring(0, lastSeparator + 1) + newName; - final File shareFile = _outFile!.copySync(newPath); + outFile!.path.substring(0, lastSeparator + 1) + newName; + final File shareFile = outFile!.copySync(newPath); ShareExtend.share(shareFile.path, 'file'); } } on Exception catch (e) { diff --git a/lib/services/revanced_api.dart b/lib/services/revanced_api.dart index dde23cee5e..fca17c0e85 100644 --- a/lib/services/revanced_api.dart +++ b/lib/services/revanced_api.dart @@ -7,12 +7,15 @@ import 'package:dio_cache_interceptor/dio_cache_interceptor.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:injectable/injectable.dart'; +import 'package:synchronized/synchronized.dart'; import 'package:timeago/timeago.dart'; @lazySingleton class RevancedAPI { late Dio _dio = Dio(); + final Lock getToolsLock = Lock(); + final _cacheOptions = CacheOptions( store: MemCacheStore(), maxStale: const Duration(days: 1), @@ -66,21 +69,23 @@ class RevancedAPI { Future?> _getLatestRelease( String extension, String repoName, - ) async { - try { - final response = await _dio.get('/tools'); - final List tools = response.data['tools']; - return tools.firstWhereOrNull( - (t) => - t['repository'] == repoName && - (t['name'] as String).endsWith(extension), - ); - } on Exception catch (e) { - if (kDebugMode) { - print(e); + ) { + return getToolsLock.synchronized(() async { + try { + final response = await _dio.get('/tools'); + final List tools = response.data['tools']; + return tools.firstWhereOrNull( + (t) => + t['repository'] == repoName && + (t['name'] as String).endsWith(extension), + ); + } on Exception catch (e) { + if (kDebugMode) { + print(e); + } + return null; } - return null; - } + }); } Future getLatestReleaseVersion( diff --git a/lib/ui/views/app_selector/app_selector_viewmodel.dart b/lib/ui/views/app_selector/app_selector_viewmodel.dart index e3691bba31..bd02913314 100644 --- a/lib/ui/views/app_selector/app_selector_viewmodel.dart +++ b/lib/ui/views/app_selector/app_selector_viewmodel.dart @@ -73,7 +73,6 @@ class AppSelectorViewModel extends BaseViewModel { locator().selectedApp = PatchedApplication( name: application.appName, packageName: application.packageName, - originalPackageName: application.packageName, version: application.versionName!, apkFilePath: application.apkFilePath, icon: application.icon, @@ -202,7 +201,6 @@ class AppSelectorViewModel extends BaseViewModel { locator().selectedApp = PatchedApplication( name: application.appName, packageName: application.packageName, - originalPackageName: application.packageName, version: application.versionName!, apkFilePath: result.files.single.path!, icon: application.icon, diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index cfc99e66de..1ab8437dab 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -37,7 +37,6 @@ class HomeViewModel extends BaseViewModel { DateTime? _lastUpdate; bool showUpdatableApps = false; List patchedInstalledApps = []; - List patchedUpdatableApps = []; String? _latestManagerVersion = ''; File? downloadedApk; @@ -82,7 +81,7 @@ class HomeViewModel extends BaseViewModel { _toast.showBottom('homeView.errorDownloadMessage'); } } - _getPatchedApps(); + _managerAPI.reAssessSavedApps().then((_) => _getPatchedApps()); } @@ -108,10 +107,6 @@ class HomeViewModel extends BaseViewModel { void _getPatchedApps() { patchedInstalledApps = _managerAPI.getPatchedApps().toList(); - patchedUpdatableApps = _managerAPI - .getPatchedApps() - .where((app) => app.hasUpdates == true) - .toList(); notifyListeners(); } @@ -469,11 +464,7 @@ class HomeViewModel extends BaseViewModel { } Future 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'); initialize(context); } diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index f523f2a09c..8d54987d7c 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -130,28 +130,28 @@ class InstallerViewModel extends BaseViewModel { Future runPatcher() async { try { - update(0.0, 'Initializing...', 'Initializing installer'); - if (_patches.isNotEmpty) { - try { - update(0.1, '', 'Creating working directory'); - await _patcherAPI.runPatcher( - _app.packageName, - _app.apkFilePath, - _patches, - ); - } on Exception catch (e) { - update( - -100.0, - 'Aborted...', - 'An error occurred! Aborted\nError:\n$e', - ); - if (kDebugMode) { - print(e); - } - } - } else { - update(-100.0, 'Aborted...', 'No app or patches selected! Aborted'); + await _patcherAPI.runPatcher( + _app.packageName, + _app.apkFilePath, + _patches, + ); + } on Exception catch (e) { + update( + -100.0, + 'Failed...', + 'Something went wrong:\n$e', + ); + if (kDebugMode) { + print(e); } + } + + // 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) { try { FlutterBackground.disableBackgroundExecution(); @@ -209,8 +209,8 @@ class InstallerViewModel extends BaseViewModel { ), RadioListTile( title: I18nText('installerView.installNonRootType'), - subtitle: I18nText('installerView.installRecommendedType'), - contentPadding: const EdgeInsets.symmetric(horizontal: 16), + contentPadding: + const EdgeInsets.symmetric(horizontal: 16), value: 0, groupValue: value, onChanged: (selected) { @@ -219,7 +219,8 @@ class InstallerViewModel extends BaseViewModel { ), RadioListTile( title: I18nText('installerView.installRootType'), - contentPadding: const EdgeInsets.symmetric(horizontal: 16), + contentPadding: + const EdgeInsets.symmetric(horizontal: 16), value: 1, groupValue: value, onChanged: (selected) { @@ -257,9 +258,9 @@ class InstallerViewModel extends BaseViewModel { Future stopPatcher() async { try { isCanceled = true; - update(0.5, 'Aborting...', 'Canceling patching process'); + update(0.5, 'Canceling...', 'Canceling patching process'); await _patcherAPI.stopPatcher(); - update(-100.0, 'Aborted...', 'Press back to exit'); + update(-100.0, 'Canceled...', 'Press back to exit'); } on Exception catch (e) { if (kDebugMode) { print(e); @@ -270,56 +271,33 @@ class InstallerViewModel extends BaseViewModel { Future installResult(BuildContext context, bool installAsRoot) async { try { _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: [ - CustomMaterialButton( - label: I18nText('okButton'), - onPressed: () => Navigator.of(context).pop(), - ), - ], - ), - ); - } else { - update( - 1.0, - 'Installing...', - _app.isRooted - ? 'Installing patched file using root method' - : 'Installing patched file using nonroot method', - ); - isInstalled = await _patcherAPI.installPatchedFile(_app); - if (isInstalled) { - update(1.0, 'Installed!', 'Installed!'); - _app.isFromStorage = false; - _app.patchDate = DateTime.now(); - _app.appliedPatches = _patches.map((p) => p.name).toList(); - if (hasMicroG) { - _app.name += ' ReVanced'; - _app.packageName = _app.packageName.replaceFirst( - 'com.google.', - 'app.revanced.', - ); - } - await _managerAPI.savePatchedApp(_app); + update( + 1.0, + 'Installing...', + _app.isRooted + ? 'Installing patched file using root method' + : 'Installing patched file using nonroot method', + ); + isInstalled = await _patcherAPI.installPatchedFile(_app); + if (isInstalled) { + _app.isFromStorage = false; + _app.patchDate = DateTime.now(); + _app.appliedPatches = _patches.map((p) => p.name).toList(); + + // In case a patch changed the app name or package name, + // update the app info. + final app = + await DeviceApps.getAppFromStorage(_patcherAPI.outFile!.path); + if (app != null) { + _app.name = app.appName; + _app.packageName = app.packageName; } + + await _managerAPI.savePatchedApp(_app); + + update(1.0, 'Installed!', 'Installed!'); + } else { + // TODO(aabed): Show error message. } } on Exception catch (e) { if (kDebugMode) { diff --git a/lib/ui/views/navigation/navigation_viewmodel.dart b/lib/ui/views/navigation/navigation_viewmodel.dart index a2a0e4bbd5..a911f070e0 100644 --- a/lib/ui/views/navigation/navigation_viewmodel.dart +++ b/lib/ui/views/navigation/navigation_viewmodel.dart @@ -39,9 +39,9 @@ class NavigationViewModel extends IndexTrackingViewModel { // Force disable Material You on Android 11 and below if (dynamicTheme.themeId.isOdd) { - const int ANDROID_12_SDK_VERSION = 31; + const int android12SdkVersion = 31; 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.setBool('useDynamicTheme', false); await dynamicTheme.setTheme(0); diff --git a/lib/ui/views/patcher/patcher_viewmodel.dart b/lib/ui/views/patcher/patcher_viewmodel.dart index 33ceb7195d..e0cadb4afa 100644 --- a/lib/ui/views/patcher/patcher_viewmodel.dart +++ b/lib/ui/views/patcher/patcher_viewmodel.dart @@ -44,49 +44,6 @@ class PatcherViewModel extends BaseViewModel { return selectedApp == null; } - Future 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 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: [ - CustomMaterialButton( - label: I18nText('noButton'), - onPressed: () => Navigator.of(context).pop(), - ), - CustomMaterialButton( - label: I18nText('yesButton'), - isFilled: false, - onPressed: () { - Navigator.of(context).pop(); - showArmv7WarningDialog(context); - }, - ), - ], - ), - ); - } - } - } - Future showRemovedPatchesDialog(BuildContext context) async { if (removedPatches.isNotEmpty) { return showDialog( @@ -115,7 +72,7 @@ class PatcherViewModel extends BaseViewModel { ), ); } else { - showArmv7WarningDialog(context); + showArmv7WarningDialog(context); // TODO(aabed): Find out why this is here } } @@ -185,9 +142,9 @@ class PatcherViewModel extends BaseViewModel { this.selectedPatches.clear(); removedPatches.clear(); final List selectedPatches = - await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName); + await _managerAPI.getSelectedPatches(selectedApp!.packageName); final List patches = - _patcherAPI.getFilteredPatches(selectedApp!.originalPackageName); + _patcherAPI.getFilteredPatches(selectedApp!.packageName); this .selectedPatches .addAll(patches.where((patch) => selectedPatches.contains(patch.name))); @@ -203,7 +160,7 @@ class PatcherViewModel extends BaseViewModel { .selectedPatches .removeWhere((patch) => patch.compatiblePackages.isEmpty); } - final usedPatches = _managerAPI.getUsedPatches(selectedApp!.originalPackageName); + final usedPatches = _managerAPI.getUsedPatches(selectedApp!.packageName); for (final patch in usedPatches){ if (!patches.any((p) => p.name == patch.name)){ removedPatches.add('\u2022 ${patch.name}'); diff --git a/lib/ui/views/patches_selector/patches_selector_view.dart b/lib/ui/views/patches_selector/patches_selector_view.dart index 26766f4e4c..b0494b520a 100644 --- a/lib/ui/views/patches_selector/patches_selector_view.dart +++ b/lib/ui/views/patches_selector/patches_selector_view.dart @@ -194,7 +194,7 @@ class _PatchesSelectorViewState extends State { return PatchItem( name: patch.name, simpleName: patch.getSimpleName(), - description: patch.description, + description: patch.description ?? '', packageVersion: model.getAppInfo().version, supportedPackageVersions: model.getSupportedVersions(patch), @@ -246,7 +246,7 @@ class _PatchesSelectorViewState extends State { return PatchItem( name: patch.name, simpleName: patch.getSimpleName(), - description: patch.description, + description: patch.description ?? '', packageVersion: model.getAppInfo().version, supportedPackageVersions: diff --git a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart index 71e4a16eb1..ea48c0dadf 100644 --- a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart +++ b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart @@ -28,7 +28,7 @@ class PatchesSelectorViewModel extends BaseViewModel { getPatchesVersion().whenComplete(() => notifyListeners()); patches.addAll( _patcherAPI.getFilteredPatches( - selectedApp!.originalPackageName, + selectedApp!.packageName, ), ); patches.sort((a, b) { @@ -98,11 +98,11 @@ class PatchesSelectorViewModel extends BaseViewModel { void selectDefaultPatches() { selectedPatches.clear(); - if (locator().selectedApp?.originalPackageName != null) { + if (locator().selectedApp?.packageName != null) { selectedPatches.addAll( _patcherAPI .getFilteredPatches( - locator().selectedApp!.originalPackageName, + locator().selectedApp!.packageName, ) .where( (element) => @@ -187,7 +187,7 @@ class PatchesSelectorViewModel extends BaseViewModel { final List selectedPatches = this.selectedPatches.map((patch) => patch.name).toList(); await _managerAPI.setSelectedPatches( - locator().selectedApp!.originalPackageName, + locator().selectedApp!.packageName, selectedPatches, ); } @@ -195,7 +195,7 @@ class PatchesSelectorViewModel extends BaseViewModel { Future loadSelectedPatches(BuildContext context) async { if (_managerAPI.isPatchesChangeEnabled()) { final List selectedPatches = await _managerAPI.getSelectedPatches( - locator().selectedApp!.originalPackageName, + locator().selectedApp!.packageName, ); if (selectedPatches.isNotEmpty) { this.selectedPatches.clear(); diff --git a/lib/ui/views/settings/settings_viewmodel.dart b/lib/ui/views/settings/settings_viewmodel.dart index 2441b0a6bd..7688d8b144 100644 --- a/lib/ui/views/settings/settings_viewmodel.dart +++ b/lib/ui/views/settings/settings_viewmodel.dart @@ -71,16 +71,16 @@ class SettingsViewModel extends BaseViewModel { actions: [ CustomMaterialButton( isFilled: false, - label: I18nText('noButton'), + label: I18nText('yesButton'), onPressed: () { + _managerAPI.setChangingToggleModified(true); + _managerAPI.setPatchesChangeEnabled(true); Navigator.of(context).pop(); }, ), CustomMaterialButton( - label: I18nText('yesButton'), + label: I18nText('noButton'), onPressed: () { - _managerAPI.setChangingToggleModified(true); - _managerAPI.setPatchesChangeEnabled(true); Navigator.of(context).pop(); }, ), diff --git a/lib/ui/widgets/appInfoView/app_info_view.dart b/lib/ui/widgets/appInfoView/app_info_view.dart index 2d0f17e13e..283beb1293 100644 --- a/lib/ui/widgets/appInfoView/app_info_view.dart +++ b/lib/ui/widgets/appInfoView/app_info_view.dart @@ -222,22 +222,6 @@ class AppInfoView extends StatelessWidget { subtitle: Text(app.packageName), ), 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( contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), diff --git a/lib/ui/widgets/appInfoView/app_info_viewmodel.dart b/lib/ui/widgets/appInfoView/app_info_viewmodel.dart index dd364c7d6b..bc24a55860 100644 --- a/lib/ui/widgets/appInfoView/app_info_viewmodel.dart +++ b/lib/ui/widgets/appInfoView/app_info_viewmodel.dart @@ -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/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; -import 'package:revanced_manager/utils/string.dart'; import 'package:stacked/stacked.dart'; class AppInfoViewModel extends BaseViewModel { @@ -147,17 +146,7 @@ class AppInfoViewModel extends BaseViewModel { } String getAppliedPatchesString(List appliedPatches) { - final List names = appliedPatches - .map( - (p) => p - .replaceAll('-', ' ') - .split('-') - .join(' ') - .toTitleCase() - .replaceFirst('Microg', 'MicroG'), - ) - .toList(); - return '\u2022 ${names.join('\n\u2022 ')}'; + return '\u2022 ${appliedPatches.join('\n\u2022 ')}'; } void openApp(PatchedApplication app) { diff --git a/lib/ui/widgets/homeView/installed_apps_card.dart b/lib/ui/widgets/homeView/installed_apps_card.dart index 7a98985641..ec825340bf 100644 --- a/lib/ui/widgets/homeView/installed_apps_card.dart +++ b/lib/ui/widgets/homeView/installed_apps_card.dart @@ -79,8 +79,6 @@ class InstalledAppsCard extends StatelessWidget { icon: app.icon, name: app.name, patchDate: app.patchDate, - changelog: app.changelog, - isUpdatableApp: false, onPressed: () => locator().navigateToAppInfo(app), ), diff --git a/lib/ui/widgets/settingsView/settings_advanced_section.dart b/lib/ui/widgets/settingsView/settings_advanced_section.dart index 2d9be3fcb7..f3e838a638 100644 --- a/lib/ui/widgets/settingsView/settings_advanced_section.dart +++ b/lib/ui/widgets/settingsView/settings_advanced_section.dart @@ -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_sources.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_enable_patches_selection.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; diff --git a/lib/ui/widgets/shared/application_item.dart b/lib/ui/widgets/shared/application_item.dart index 42eee351b5..b733643563 100644 --- a/lib/ui/widgets/shared/application_item.dart +++ b/lib/ui/widgets/shared/application_item.dart @@ -1,6 +1,5 @@ import 'dart:typed_data'; -import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_card.dart'; @@ -13,151 +12,84 @@ class ApplicationItem extends StatefulWidget { required this.icon, required this.name, required this.patchDate, - required this.changelog, - required this.isUpdatableApp, required this.onPressed, }) : super(key: key); final Uint8List icon; final String name; final DateTime patchDate; - final List changelog; - final bool isUpdatableApp; final Function() onPressed; @override State createState() => _ApplicationItemState(); } -class _ApplicationItemState extends State - with TickerProviderStateMixin { - late AnimationController _animationController; +class _ApplicationItemState extends State { @override void initState() { super.initState(); - _animationController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 300), - ); - } - - @override - void dispose() { - _animationController.dispose(); - super.dispose(); } @override Widget build(BuildContext context) { - final ExpandableController expController = ExpandableController(); return Container( margin: const EdgeInsets.only(bottom: 16.0), child: CustomCard( - onTap: () { - 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, - children: [ - Flexible( - child: Row( - children: [ - SizedBox( - width: 40, - child: Image.memory(widget.icon, height: 40, width: 40), - ), - const SizedBox(width: 19), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.name, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Row( + children: [ + SizedBox( + width: 40, + child: Image.memory(widget.icon, height: 40, width: 40), + ), + const SizedBox(width: 19), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, ), - Text( - format(widget.patchDate), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), + ), + Text( + format(widget.patchDate), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, ), - ], - ), - ), - ], - ), - ), - Row( - 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), - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - CustomMaterialButton( - label: widget.isUpdatableApp - ? I18nText('applicationItem.patchButton') - : I18nText('applicationItem.infoButton'), - onPressed: widget.onPressed, - ), - ], - ), ], ), - ], - ), - 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: [ - I18nText( - 'applicationItem.changelogLabel', - child: const Text( - '', - style: TextStyle(fontWeight: FontWeight.w700), - ), + Row( + children: [ + const SizedBox(width: 8), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + CustomMaterialButton( + label: I18nText('applicationItem.infoButton'), + onPressed: widget.onPressed, + ), + ], ), - const SizedBox(height: 4), - Text('\u2022 ${widget.changelog.join('\n\u2022 ')}'), ], ), - ), + ], ), ), ); diff --git a/pubspec.yaml b/pubspec.yaml index 98d16d32e5..34f43cc6d7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ homepage: https://github.com/revanced/revanced-manager publish_to: 'none' -version: 1.10.3+101000300 +version: 1.11.0+101100000 environment: sdk: '>=3.0.0 <4.0.0' @@ -75,6 +75,7 @@ dependencies: flutter_markdown: ^0.6.14 dio_cache_interceptor: ^3.4.0 install_plugin: ^2.1.0 + synchronized: ^3.1.0 dev_dependencies: json_serializable: ^6.6.1