From 2a89ef797f41eeee8472c426ceac688f3cda6034 Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Sun, 24 Sep 2023 16:56:25 -0700 Subject: [PATCH 01/38] feat: share settings --- android/app/src/main/AndroidManifest.xml | 5 ++ .../revanced/manager/flutter/MainActivity.kt | 22 +++++ .../flutter/utils/share/ShareProvider.kt | 80 +++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/ShareProvider.kt diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 08e9e77c..8ae06734 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -54,5 +54,10 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> + + 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 22463f8c..444fa970 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 @@ -27,6 +27,13 @@ import java.io.StringWriter import java.util.logging.LogRecord import java.util.logging.Logger +import android.content.ContentResolver +import android.content.Context +import android.database.Cursor +import android.net.Uri + +import android.util.Log + class MainActivity : FlutterActivity() { private val handler = Handler(Looper.getMainLooper()) private lateinit var installerChannel: MethodChannel @@ -39,6 +46,21 @@ class MainActivity : FlutterActivity() { val patcherChannel = "app.revanced.manager.flutter/patcher" val installerChannel = "app.revanced.manager.flutter/installer" + val contentProviderUri = Uri.parse("content://app.revanced.manager.flutter.provider/settings") + val contentResolver: ContentResolver = context.contentResolver + val cursor: Cursor? = contentResolver.query(contentProviderUri, null, null, null, null) + + Log.d("app.revanced.manager.flutter.debug", "byhithere") + if (cursor != null) { + Log.d("app.revanced.manager.flutter.debug", "test2") + if (cursor.moveToFirst()) { + val helloValue = cursor.getString(cursor.getColumnIndex("settings")) + // Process the retrieved "hello" value + Log.d("testing2", helloValue) + } + cursor.close() + } + val mainChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, patcherChannel) diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/ShareProvider.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/ShareProvider.kt new file mode 100644 index 00000000..9460b335 --- /dev/null +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/ShareProvider.kt @@ -0,0 +1,80 @@ +package app.revanced.manager.flutter.utils.share + +import android.content.ContentProvider +import android.content.ContentValues +import android.content.Context +import android.content.UriMatcher +import android.database.Cursor +import android.database.MatrixCursor +import android.net.Uri +import org.json.JSONObject + +import android.util.Log + +class ShareProvider : ContentProvider() { + private val authority = "app.revanced.manager.flutter.provider" + private val URI_CODE_SETTINGS = 1 + + private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply { + addURI(authority, "settings", URI_CODE_SETTINGS) + } + + override fun onCreate(): Boolean { + return true + } + + fun getSettings(): String { + val json = JSONObject() + + // Default Data + + // TODO: load default data + + // Load Shared Preferences + val sharedPreferences = context!!.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE) + val allEntries: Map = sharedPreferences.getAll() + for ((key, value) in allEntries.entries) { + Log.d("map values", key + ": " + value.toString()) + json.put(key.replace("flutter.", ""), value) + } + + // TODO: Load keystore + + return json.toString() + } + + override fun query( + uri: Uri, + projection: Array?, + selection: String?, + selectionArgs: Array?, + sortOrder: String? + ):Cursor? { + when (uriMatcher.match(uri)) { + URI_CODE_SETTINGS -> { + val cursor = MatrixCursor(arrayOf("settings")) + val row = arrayOf(getSettings()) + cursor.addRow(row) + return cursor + } + else -> throw IllegalArgumentException("Unknown URI: $uri") + } + return null + } + + override fun insert(uri: Uri, values: ContentValues?): Uri? { + throw UnsupportedOperationException("Insert operation is not supported") + } + + override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?): Int { + throw UnsupportedOperationException("Update operation is not supported") + } + + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { + throw UnsupportedOperationException("Delete operation is not supported") + } + + override fun getType(uri: Uri): String? { + throw UnsupportedOperationException("Get type operation is not supported") + } +} From 99c92069b9f8302af3fd153e391cca7d41129a10 Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Mon, 25 Sep 2023 08:43:10 -0700 Subject: [PATCH 02/38] export saved patches and keystore --- android/app/src/main/AndroidManifest.xml | 2 +- .../revanced/manager/flutter/MainActivity.kt | 22 ---------------- ...eProvider.kt => LegacySettingsProvider.kt} | 26 ++++++++++++++----- 3 files changed, 20 insertions(+), 30 deletions(-) rename android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/{ShareProvider.kt => LegacySettingsProvider.kt} (72%) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8ae06734..6db88397 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -55,7 +55,7 @@ android:resource="@xml/file_paths" /> 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 444fa970..22463f8c 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 @@ -27,13 +27,6 @@ import java.io.StringWriter import java.util.logging.LogRecord import java.util.logging.Logger -import android.content.ContentResolver -import android.content.Context -import android.database.Cursor -import android.net.Uri - -import android.util.Log - class MainActivity : FlutterActivity() { private val handler = Handler(Looper.getMainLooper()) private lateinit var installerChannel: MethodChannel @@ -46,21 +39,6 @@ class MainActivity : FlutterActivity() { val patcherChannel = "app.revanced.manager.flutter/patcher" val installerChannel = "app.revanced.manager.flutter/installer" - val contentProviderUri = Uri.parse("content://app.revanced.manager.flutter.provider/settings") - val contentResolver: ContentResolver = context.contentResolver - val cursor: Cursor? = contentResolver.query(contentProviderUri, null, null, null, null) - - Log.d("app.revanced.manager.flutter.debug", "byhithere") - if (cursor != null) { - Log.d("app.revanced.manager.flutter.debug", "test2") - if (cursor.moveToFirst()) { - val helloValue = cursor.getString(cursor.getColumnIndex("settings")) - // Process the retrieved "hello" value - Log.d("testing2", helloValue) - } - cursor.close() - } - val mainChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, patcherChannel) diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/ShareProvider.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt similarity index 72% rename from android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/ShareProvider.kt rename to android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt index 9460b335..a5475b22 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/ShareProvider.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt @@ -7,11 +7,11 @@ import android.content.UriMatcher import android.database.Cursor import android.database.MatrixCursor import android.net.Uri +import android.util.Base64 import org.json.JSONObject +import java.io.File -import android.util.Log - -class ShareProvider : ContentProvider() { +class LegacySettingsProvider : ContentProvider() { private val authority = "app.revanced.manager.flutter.provider" private val URI_CODE_SETTINGS = 1 @@ -27,18 +27,30 @@ class ShareProvider : ContentProvider() { val json = JSONObject() // Default Data - - // TODO: load default data + json.put("keystorePassword", "s3cur3p@ssw0rd") // Load Shared Preferences val sharedPreferences = context!!.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE) val allEntries: Map = sharedPreferences.getAll() for ((key, value) in allEntries.entries) { - Log.d("map values", key + ": " + value.toString()) json.put(key.replace("flutter.", ""), value) } - // TODO: Load keystore + // Load keystore + val keystoreFile = File(context!!.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(context!!.filesDir.parentFile.absolutePath, "/app_flutter/selected-patches.json") + if (storedPatchesFile.exists()) { + val patchesBytes = storedPatchesFile.readBytes() + val patches = String(patchesBytes, Charsets.UTF_8) + json.put("savedPatches", patches) + } return json.toString() } From 378d62395a5f2de90da90fdff153a28ece14b1bd Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Mon, 25 Sep 2023 10:15:56 -0700 Subject: [PATCH 03/38] remove newlines from base64 output --- .../manager/flutter/utils/share/LegacySettingsProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt index a5475b22..b1b509ea 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt @@ -40,7 +40,7 @@ class LegacySettingsProvider : ContentProvider() { val keystoreFile = File(context!!.getExternalFilesDir(null), "/revanced-manager.keystore") if (keystoreFile.exists()) { val keystoreBytes = keystoreFile.readBytes() - val keystoreBase64 = Base64.encodeToString(keystoreBytes, Base64.DEFAULT) + val keystoreBase64 = Base64.encodeToString(keystoreBytes, Base64.DEFAULT).replace("\n", "") json.put("keystore", keystoreBase64) } From f1ea30629123c5fae7b3d216830135f5810e21d6 Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Mon, 25 Sep 2023 14:42:40 -0700 Subject: [PATCH 04/38] change booleans to numbers --- .../manager/flutter/utils/share/LegacySettingsProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt index b1b509ea..64a85ed8 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt @@ -33,7 +33,7 @@ class LegacySettingsProvider : ContentProvider() { val sharedPreferences = context!!.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE) val allEntries: Map = sharedPreferences.getAll() for ((key, value) in allEntries.entries) { - json.put(key.replace("flutter.", ""), value) + json.put(key.replace("flutter.", ""), if (value is Boolean) if (value) 1 else 0 else value) } // Load keystore From d9d5b746c3a677ba9f56f94f462ef6c25ab8c828 Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Tue, 26 Sep 2023 16:21:46 -0700 Subject: [PATCH 05/38] Added ExportSettingsActivity --- android/app/src/main/AndroidManifest.xml | 21 ++++- .../manager/flutter/ExportSettingsActivity.kt | 40 ++++++++ .../utils/share/LegacySettingsProvider.kt | 92 ------------------- assets/i18n/en_US.json | 6 ++ lib/main.dart | 16 +++- .../export_settings/export_settings_view.dart | 35 +++++++ .../export_settings_viewmodel.dart | 71 ++++++++++++++ 7 files changed, 181 insertions(+), 100 deletions(-) create mode 100644 android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt delete mode 100644 android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt create mode 100644 lib/ui/views/export_settings/export_settings_view.dart create mode 100644 lib/ui/views/export_settings/export_settings_viewmodel.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6db88397..69587cd7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -42,6 +42,22 @@ + + + + + + + @@ -54,10 +70,5 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> - - 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 00000000..b9e8e5aa --- /dev/null +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt @@ -0,0 +1,40 @@ +package app.revanced.manager.flutter + +import android.app.Activity +import android.content.Intent +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel +import java.io.Serializable + +class ExportSettingsActivity : FlutterActivity() { + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + + val settingsChannel = "app.revanced.manager.flutter/settings" + + val mainChannel = + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, settingsChannel) + + mainChannel.setMethodCallHandler { call, result -> + when (call.method) { + "accept" -> { + val data = call.argument("data") + val resultIntent = Intent() + resultIntent.putExtra("data", data as Serializable) + setResult(Activity.RESULT_OK, resultIntent) + finish() + } + "deny" -> { + setResult(Activity.RESULT_CANCELED) + finish() + } + else -> result.notImplemented() + } + } + } + + override fun getDartEntrypointFunctionName(): String { + return "mainExportSettings" + } +} diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt deleted file mode 100644 index 64a85ed8..00000000 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/share/LegacySettingsProvider.kt +++ /dev/null @@ -1,92 +0,0 @@ -package app.revanced.manager.flutter.utils.share - -import android.content.ContentProvider -import android.content.ContentValues -import android.content.Context -import android.content.UriMatcher -import android.database.Cursor -import android.database.MatrixCursor -import android.net.Uri -import android.util.Base64 -import org.json.JSONObject -import java.io.File - -class LegacySettingsProvider : ContentProvider() { - private val authority = "app.revanced.manager.flutter.provider" - private val URI_CODE_SETTINGS = 1 - - private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply { - addURI(authority, "settings", URI_CODE_SETTINGS) - } - - override fun onCreate(): Boolean { - return true - } - - fun getSettings(): String { - val json = JSONObject() - - // Default Data - json.put("keystorePassword", "s3cur3p@ssw0rd") - - // Load Shared Preferences - val sharedPreferences = context!!.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(context!!.getExternalFilesDir(null), "/revanced-manager.keystore") - if (keystoreFile.exists()) { - val keystoreBytes = keystoreFile.readBytes() - val keystoreBase64 = Base64.encodeToString(keystoreBytes, Base64.DEFAULT).replace("\n", "") - json.put("keystore", keystoreBase64) - } - - // Load saved patches - val storedPatchesFile = File(context!!.filesDir.parentFile.absolutePath, "/app_flutter/selected-patches.json") - if (storedPatchesFile.exists()) { - val patchesBytes = storedPatchesFile.readBytes() - val patches = String(patchesBytes, Charsets.UTF_8) - json.put("savedPatches", patches) - } - - return json.toString() - } - - override fun query( - uri: Uri, - projection: Array?, - selection: String?, - selectionArgs: Array?, - sortOrder: String? - ):Cursor? { - when (uriMatcher.match(uri)) { - URI_CODE_SETTINGS -> { - val cursor = MatrixCursor(arrayOf("settings")) - val row = arrayOf(getSettings()) - cursor.addRow(row) - return cursor - } - else -> throw IllegalArgumentException("Unknown URI: $uri") - } - return null - } - - override fun insert(uri: Uri, values: ContentValues?): Uri? { - throw UnsupportedOperationException("Insert operation is not supported") - } - - override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?): Int { - throw UnsupportedOperationException("Update operation is not supported") - } - - override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { - throw UnsupportedOperationException("Delete operation is not supported") - } - - override fun getType(uri: Uri): String? { - throw UnsupportedOperationException("Get type operation is not supported") - } -} diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 05694ced..cc398cb9 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -304,5 +304,11 @@ "integrationsContributors": "Integrations contributors", "cliContributors": "CLI contributors", "managerContributors": "Manager contributors" + }, + "exportSettingsView": { + "widgetTitle": "Export settings", + "description": "Would you like to export your settings to the latest version of ReVanced Manager?", + "exportButton": "Export", + "dismissButton": "No thanks" } } diff --git a/lib/main.dart b/lib/main.dart index 29b715b9..df883453 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:revanced_manager/services/github_api.dart'; import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/revanced_api.dart'; import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart'; +import 'package:revanced_manager/ui/views/export_settings/export_settings_view.dart'; import 'package:revanced_manager/ui/views/navigation/navigation_view.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:stacked_themes/stacked_themes.dart'; @@ -15,6 +16,14 @@ import 'package:timezone/data/latest.dart' as tz; late SharedPreferences prefs; Future main() async { + initialize(const NavigationView()); +} + +Future mainExportSettings() async { + initialize(const ExportSettingsView()); +} + +Future initialize(Widget homeView) async { await ThemeManager.initialise(); await setupLocator(); WidgetsFlutterBinding.ensureInitialized(); @@ -26,11 +35,12 @@ Future main() async { tz.initializeTimeZones(); prefs = await SharedPreferences.getInstance(); - runApp(const MyApp()); + runApp(MyApp(homeView: homeView)); } class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); + const MyApp({Key? key, required this.homeView}) : super(key: key); + final Widget homeView; @override Widget build(BuildContext context) { @@ -42,7 +52,7 @@ class MyApp extends StatelessWidget { return DynamicThemeBuilder( title: 'ReVanced Manager', - home: const NavigationView(), + home: homeView, localizationsDelegates: [ FlutterI18nDelegate( translationLoader: FileTranslationLoader( diff --git a/lib/ui/views/export_settings/export_settings_view.dart b/lib/ui/views/export_settings/export_settings_view.dart new file mode 100644 index 00000000..631ee655 --- /dev/null +++ b/lib/ui/views/export_settings/export_settings_view.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_i18n/flutter_i18n.dart'; +import 'package:revanced_manager/ui/views/export_settings/export_settings_viewmodel.dart'; +import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; + +final _exportSettingsViewModel = ExportSettingsViewModel(); + +class ExportSettingsView extends StatelessWidget { + const ExportSettingsView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + _exportSettingsViewModel.init(context); + return Material( + child: AlertDialog( + title: I18nText('exportSettingsView.widgetTitle'), + content: I18nText('exportSettingsView.description'), + icon: const Icon(Icons.update), + actions: [ + CustomMaterialButton( + isFilled: false, + label: I18nText('exportSettingsView.dismissButton'), + onPressed: _exportSettingsViewModel.deny, + ), + CustomMaterialButton( + label: I18nText('exportSettingsView.exportButton'), + onPressed: () async { + await _exportSettingsViewModel.accept(); + }, + ), + ], + ), + ); + } +} diff --git a/lib/ui/views/export_settings/export_settings_viewmodel.dart b/lib/ui/views/export_settings/export_settings_viewmodel.dart new file mode 100644 index 00000000..ce450f36 --- /dev/null +++ b/lib/ui/views/export_settings/export_settings_viewmodel.dart @@ -0,0 +1,71 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:dynamic_themes/dynamic_themes.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:injectable/injectable.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:revanced_manager/app/app.locator.dart'; +import 'package:revanced_manager/services/manager_api.dart'; +import 'package:stacked/stacked.dart'; + +@lazySingleton +class ExportSettingsViewModel extends BaseViewModel { + final _channel = const MethodChannel('app.revanced.manager.flutter/settings'); + final ManagerAPI _managerAPI = locator(); + + void init(BuildContext context) { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + systemNavigationBarColor: Colors.transparent, + systemNavigationBarIconBrightness: + DynamicTheme.of(context)!.theme.brightness == Brightness.light + ? Brightness.dark + : Brightness.light, + ), + ); + } + + Future accept() async { + final externalDir = await getExternalStorageDirectory(); + + final Map data = {}; + + data['themeMode'] = _managerAPI.getThemeMode(); + data['useDynamicTheme'] = _managerAPI.getUseDynamicTheme(); + + data['apiUrl'] = _managerAPI.getApiUrl(); + data['patchesRepo'] = _managerAPI.getPatchesRepo(); + data['integrationsRepo'] = _managerAPI.getIntegrationsRepo(); + + data['patchesAutoUpdate'] = _managerAPI.isPatchesAutoUpdate(); + data['patchesChangeEnabled'] = _managerAPI.isPatchesChangeEnabled(); + data['universalPatchesEnabled'] = _managerAPI.areUniversalPatchesEnabled(); + data['experimentalPatchesEnabled'] = _managerAPI.areExperimentalPatchesEnabled(); + + data['keystorePassword'] = _managerAPI.getKeystorePassword(); + + // Load keystore + if (externalDir != null) { + final keystoreFile = File('${externalDir.path}/revanced-manager.keystore'); + if (keystoreFile.existsSync()) { + final keystoreBytes = keystoreFile.readAsBytesSync(); + data['keystore'] = base64Encode(keystoreBytes); + } + } + + // Load patches + final patchFile = File(_managerAPI.storedPatchesFile); + if (patchFile.existsSync()) { + data['patches'] = patchFile.readAsStringSync(); + } + + _channel.invokeMethod('accept', {'data': jsonEncode(data)}); + } + + void deny() { + _channel.invokeMethod('deny'); + } +} From 2250e1bcab9145bfc6ae250a44af9a6d9f7fdb64 Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Tue, 26 Sep 2023 16:46:49 -0700 Subject: [PATCH 06/38] convert Booleans to Ints --- .../export_settings/export_settings_viewmodel.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ui/views/export_settings/export_settings_viewmodel.dart b/lib/ui/views/export_settings/export_settings_viewmodel.dart index ce450f36..84999b66 100644 --- a/lib/ui/views/export_settings/export_settings_viewmodel.dart +++ b/lib/ui/views/export_settings/export_settings_viewmodel.dart @@ -34,16 +34,16 @@ class ExportSettingsViewModel extends BaseViewModel { final Map data = {}; data['themeMode'] = _managerAPI.getThemeMode(); - data['useDynamicTheme'] = _managerAPI.getUseDynamicTheme(); + data['useDynamicTheme'] = _managerAPI.getUseDynamicTheme() ? 1 : 0; data['apiUrl'] = _managerAPI.getApiUrl(); data['patchesRepo'] = _managerAPI.getPatchesRepo(); data['integrationsRepo'] = _managerAPI.getIntegrationsRepo(); - data['patchesAutoUpdate'] = _managerAPI.isPatchesAutoUpdate(); - data['patchesChangeEnabled'] = _managerAPI.isPatchesChangeEnabled(); - data['universalPatchesEnabled'] = _managerAPI.areUniversalPatchesEnabled(); - data['experimentalPatchesEnabled'] = _managerAPI.areExperimentalPatchesEnabled(); + data['patchesAutoUpdate'] = _managerAPI.isPatchesAutoUpdate() ? 1 : 0; + data['patchesChangeEnabled'] = _managerAPI.isPatchesChangeEnabled() ? 1 : 0; + data['universalPatchesEnabled'] = _managerAPI.areUniversalPatchesEnabled() ? 1 : 0; + data['experimentalPatchesEnabled'] = _managerAPI.areExperimentalPatchesEnabled() ? 1 : 0; data['keystorePassword'] = _managerAPI.getKeystorePassword(); From 72ae132fcd74bf2d2d7cd015562777328976df92 Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Wed, 27 Sep 2023 12:21:27 -0700 Subject: [PATCH 07/38] make export settings activity transparent --- android/app/src/main/AndroidManifest.xml | 4 +-- .../manager/flutter/ExportSettingsActivity.kt | 5 +++ android/app/src/main/res/values/styles.xml | 6 ++++ assets/i18n/en_US.json | 6 ++-- .../export_settings/export_settings_view.dart | 35 +++++++++---------- .../export_settings_viewmodel.dart | 13 ------- 6 files changed, 32 insertions(+), 37 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 69587cd7..6102c540 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -46,13 +46,13 @@ android:name=".ExportSettingsActivity" android:exported="true" android:launchMode="singleTop" - android:theme="@style/LaunchTheme" + android:theme="@style/ExportSettingsTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> + android:resource="@style/ExportSettingsTheme"/> 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 index b9e8e5aa..6b9df401 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt @@ -3,6 +3,7 @@ package app.revanced.manager.flutter import android.app.Activity import android.content.Intent import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.android.TransparencyMode import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel import java.io.Serializable @@ -37,4 +38,8 @@ class ExportSettingsActivity : FlutterActivity() { override fun getDartEntrypointFunctionName(): String { return "mainExportSettings" } + + override fun getTransparencyMode(): TransparencyMode { + return TransparencyMode.transparent + } } diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index cb1ef880..ae451e0a 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -15,4 +15,10 @@ + + diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index cc398cb9..e1c1f848 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -306,9 +306,9 @@ "managerContributors": "Manager contributors" }, "exportSettingsView": { - "widgetTitle": "Export settings", - "description": "Would you like to export your settings to the latest version of ReVanced Manager?", - "exportButton": "Export", + "widgetTitle": "Import settings", + "description": "Would you like to import your settings to the previous version of ReVanced Manager?", + "exportButton": "Import", "dismissButton": "No thanks" } } diff --git a/lib/ui/views/export_settings/export_settings_view.dart b/lib/ui/views/export_settings/export_settings_view.dart index 631ee655..44e195be 100644 --- a/lib/ui/views/export_settings/export_settings_view.dart +++ b/lib/ui/views/export_settings/export_settings_view.dart @@ -10,26 +10,23 @@ class ExportSettingsView extends StatelessWidget { @override Widget build(BuildContext context) { - _exportSettingsViewModel.init(context); - return Material( - child: AlertDialog( - title: I18nText('exportSettingsView.widgetTitle'), - content: I18nText('exportSettingsView.description'), - icon: const Icon(Icons.update), - actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('exportSettingsView.dismissButton'), - onPressed: _exportSettingsViewModel.deny, - ), - CustomMaterialButton( - label: I18nText('exportSettingsView.exportButton'), - onPressed: () async { - await _exportSettingsViewModel.accept(); - }, - ), - ], + return AlertDialog( + title: I18nText('exportSettingsView.widgetTitle'), + content: I18nText('exportSettingsView.description'), + icon: const Icon(Icons.update), + actions: [ + CustomMaterialButton( + isFilled: false, + label: I18nText('exportSettingsView.dismissButton'), + onPressed: _exportSettingsViewModel.deny, ), + CustomMaterialButton( + label: I18nText('exportSettingsView.exportButton'), + onPressed: () async { + await _exportSettingsViewModel.accept(); + }, + ), + ], ); } } diff --git a/lib/ui/views/export_settings/export_settings_viewmodel.dart b/lib/ui/views/export_settings/export_settings_viewmodel.dart index 84999b66..0aee70fd 100644 --- a/lib/ui/views/export_settings/export_settings_viewmodel.dart +++ b/lib/ui/views/export_settings/export_settings_viewmodel.dart @@ -15,19 +15,6 @@ class ExportSettingsViewModel extends BaseViewModel { final _channel = const MethodChannel('app.revanced.manager.flutter/settings'); final ManagerAPI _managerAPI = locator(); - void init(BuildContext context) { - SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); - SystemChrome.setSystemUIOverlayStyle( - SystemUiOverlayStyle( - systemNavigationBarColor: Colors.transparent, - systemNavigationBarIconBrightness: - DynamicTheme.of(context)!.theme.brightness == Brightness.light - ? Brightness.dark - : Brightness.light, - ), - ); - } - Future accept() async { final externalDir = await getExternalStorageDirectory(); From 96736afb94b327c8dc3a5a592f055729f6f7c4e8 Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Wed, 27 Sep 2023 12:32:19 -0700 Subject: [PATCH 08/38] make bars transparent --- lib/ui/views/export_settings/export_settings_view.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/ui/views/export_settings/export_settings_view.dart b/lib/ui/views/export_settings/export_settings_view.dart index 44e195be..c7e797b5 100644 --- a/lib/ui/views/export_settings/export_settings_view.dart +++ b/lib/ui/views/export_settings/export_settings_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:revanced_manager/ui/views/export_settings/export_settings_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; @@ -10,6 +11,14 @@ class ExportSettingsView extends StatelessWidget { @override Widget build(BuildContext context) { + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + systemNavigationBarColor: Colors.black.withOpacity(0.002), + statusBarColor: Colors.black.withOpacity(0.002), + ), + ); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + return AlertDialog( title: I18nText('exportSettingsView.widgetTitle'), content: I18nText('exportSettingsView.description'), From 02822f4b38946b92f4fc6585ff449e7233989b5c Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Wed, 27 Sep 2023 13:47:59 -0700 Subject: [PATCH 09/38] remove user interaction --- android/app/src/main/AndroidManifest.xml | 14 +--- .../manager/flutter/ExportSettingsActivity.kt | 75 +++++++++++-------- android/app/src/main/res/values/styles.xml | 6 -- assets/i18n/en_US.json | 6 -- lib/main.dart | 16 +--- .../export_settings/export_settings_view.dart | 41 ---------- .../export_settings_viewmodel.dart | 58 -------------- 7 files changed, 47 insertions(+), 169 deletions(-) delete mode 100644 lib/ui/views/export_settings/export_settings_view.dart delete mode 100644 lib/ui/views/export_settings/export_settings_viewmodel.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6102c540..3a2c4f8c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -44,19 +44,7 @@ - - - - - + android:exported="true"> - when (call.method) { - "accept" -> { - val data = call.argument("data") - val resultIntent = Intent() - resultIntent.putExtra("data", data as Serializable) - setResult(Activity.RESULT_OK, resultIntent) - finish() - } - "deny" -> { - setResult(Activity.RESULT_CANCELED) - finish() - } - else -> result.notImplemented() - } + // 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 + ) } - } - override fun getDartEntrypointFunctionName(): String { - return "mainExportSettings" - } + // Load keystore + val keystoreFile = File(getExternalFilesDir(null), "/revanced-manager.keystore") + if (keystoreFile.exists()) { + val keystoreBytes = keystoreFile.readBytes() + val keystoreBase64 = + Base64.encodeToString(keystoreBytes, Base64.DEFAULT).replace("\n", "") + json.put("keystore", keystoreBase64) + } - override fun getTransparencyMode(): TransparencyMode { - return TransparencyMode.transparent + // 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", patches) + } + + // Send data back + Log.e("ExportSettingsActivity", json.toString()) + val resultIntent = Intent() + resultIntent.putExtra("data", json.toString()) + setResult(Activity.RESULT_OK, resultIntent) + finish() } } diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index ae451e0a..cb1ef880 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -15,10 +15,4 @@ - - diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index e1c1f848..05694ced 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -304,11 +304,5 @@ "integrationsContributors": "Integrations contributors", "cliContributors": "CLI contributors", "managerContributors": "Manager contributors" - }, - "exportSettingsView": { - "widgetTitle": "Import settings", - "description": "Would you like to import your settings to the previous version of ReVanced Manager?", - "exportButton": "Import", - "dismissButton": "No thanks" } } diff --git a/lib/main.dart b/lib/main.dart index df883453..29b715b9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,7 +8,6 @@ import 'package:revanced_manager/services/github_api.dart'; import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/revanced_api.dart'; import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart'; -import 'package:revanced_manager/ui/views/export_settings/export_settings_view.dart'; import 'package:revanced_manager/ui/views/navigation/navigation_view.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:stacked_themes/stacked_themes.dart'; @@ -16,14 +15,6 @@ import 'package:timezone/data/latest.dart' as tz; late SharedPreferences prefs; Future main() async { - initialize(const NavigationView()); -} - -Future mainExportSettings() async { - initialize(const ExportSettingsView()); -} - -Future initialize(Widget homeView) async { await ThemeManager.initialise(); await setupLocator(); WidgetsFlutterBinding.ensureInitialized(); @@ -35,12 +26,11 @@ Future initialize(Widget homeView) async { tz.initializeTimeZones(); prefs = await SharedPreferences.getInstance(); - runApp(MyApp(homeView: homeView)); + runApp(const MyApp()); } class MyApp extends StatelessWidget { - const MyApp({Key? key, required this.homeView}) : super(key: key); - final Widget homeView; + const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -52,7 +42,7 @@ class MyApp extends StatelessWidget { return DynamicThemeBuilder( title: 'ReVanced Manager', - home: homeView, + home: const NavigationView(), localizationsDelegates: [ FlutterI18nDelegate( translationLoader: FileTranslationLoader( diff --git a/lib/ui/views/export_settings/export_settings_view.dart b/lib/ui/views/export_settings/export_settings_view.dart deleted file mode 100644 index c7e797b5..00000000 --- a/lib/ui/views/export_settings/export_settings_view.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_i18n/flutter_i18n.dart'; -import 'package:revanced_manager/ui/views/export_settings/export_settings_viewmodel.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; - -final _exportSettingsViewModel = ExportSettingsViewModel(); - -class ExportSettingsView extends StatelessWidget { - const ExportSettingsView({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - SystemChrome.setSystemUIOverlayStyle( - SystemUiOverlayStyle( - systemNavigationBarColor: Colors.black.withOpacity(0.002), - statusBarColor: Colors.black.withOpacity(0.002), - ), - ); - SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); - - return AlertDialog( - title: I18nText('exportSettingsView.widgetTitle'), - content: I18nText('exportSettingsView.description'), - icon: const Icon(Icons.update), - actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('exportSettingsView.dismissButton'), - onPressed: _exportSettingsViewModel.deny, - ), - CustomMaterialButton( - label: I18nText('exportSettingsView.exportButton'), - onPressed: () async { - await _exportSettingsViewModel.accept(); - }, - ), - ], - ); - } -} diff --git a/lib/ui/views/export_settings/export_settings_viewmodel.dart b/lib/ui/views/export_settings/export_settings_viewmodel.dart deleted file mode 100644 index 0aee70fd..00000000 --- a/lib/ui/views/export_settings/export_settings_viewmodel.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:dynamic_themes/dynamic_themes.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:injectable/injectable.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:revanced_manager/app/app.locator.dart'; -import 'package:revanced_manager/services/manager_api.dart'; -import 'package:stacked/stacked.dart'; - -@lazySingleton -class ExportSettingsViewModel extends BaseViewModel { - final _channel = const MethodChannel('app.revanced.manager.flutter/settings'); - final ManagerAPI _managerAPI = locator(); - - Future accept() async { - final externalDir = await getExternalStorageDirectory(); - - final Map data = {}; - - data['themeMode'] = _managerAPI.getThemeMode(); - data['useDynamicTheme'] = _managerAPI.getUseDynamicTheme() ? 1 : 0; - - data['apiUrl'] = _managerAPI.getApiUrl(); - data['patchesRepo'] = _managerAPI.getPatchesRepo(); - data['integrationsRepo'] = _managerAPI.getIntegrationsRepo(); - - data['patchesAutoUpdate'] = _managerAPI.isPatchesAutoUpdate() ? 1 : 0; - data['patchesChangeEnabled'] = _managerAPI.isPatchesChangeEnabled() ? 1 : 0; - data['universalPatchesEnabled'] = _managerAPI.areUniversalPatchesEnabled() ? 1 : 0; - data['experimentalPatchesEnabled'] = _managerAPI.areExperimentalPatchesEnabled() ? 1 : 0; - - data['keystorePassword'] = _managerAPI.getKeystorePassword(); - - // Load keystore - if (externalDir != null) { - final keystoreFile = File('${externalDir.path}/revanced-manager.keystore'); - if (keystoreFile.existsSync()) { - final keystoreBytes = keystoreFile.readAsBytesSync(); - data['keystore'] = base64Encode(keystoreBytes); - } - } - - // Load patches - final patchFile = File(_managerAPI.storedPatchesFile); - if (patchFile.existsSync()) { - data['patches'] = patchFile.readAsStringSync(); - } - - _channel.invokeMethod('accept', {'data': jsonEncode(data)}); - } - - void deny() { - _channel.invokeMethod('deny'); - } -} From 7559c7b67e0025e6ad423acb9568868e11ef3dff Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Wed, 27 Sep 2023 14:17:29 -0700 Subject: [PATCH 10/38] verify fingerprint of calling app --- .../manager/flutter/ExportSettingsActivity.kt | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) 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 index 87111a4b..f31819e3 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt @@ -3,17 +3,56 @@ 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 android.util.Log +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 composeFingerprint = "" + // Get the package name of the app that started the activity + val packageName = getCallingPackage()!! + + // Get the signature of the app that matches the package name + val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) + val signatures = packageInfo.signatures + + // Loop through each signature and print its properties + for (signature in signatures) { + // 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) + } + + if (fingerprint == composeFingerprint) { + sendData() + } + } + + // Send data back + val resultIntent = Intent() + setResult(Activity.RESULT_CANCELED) + finish() + } + + fun sendData() { + // Create JSON Object val json = JSONObject() // Default Data @@ -47,7 +86,6 @@ class ExportSettingsActivity : Activity() { } // Send data back - Log.e("ExportSettingsActivity", json.toString()) val resultIntent = Intent() resultIntent.putExtra("data", json.toString()) setResult(Activity.RESULT_OK, resultIntent) From 83cbb34a5b3647c1e68d5c1921fd93ecf65dbd3a Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Wed, 27 Sep 2023 14:27:38 -0700 Subject: [PATCH 11/38] use revanced fingerprint --- .../app/revanced/manager/flutter/ExportSettingsActivity.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 index f31819e3..7a9f9f98 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt @@ -14,10 +14,12 @@ import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.security.MessageDigest +import android.util.Log + class ExportSettingsActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val composeFingerprint = "" + val revancedFingerprint = "b6362c6ea7888efd15c0800f480786ad0f5b133b4f84e12d46afba5f9eac1223" // Get the package name of the app that started the activity val packageName = getCallingPackage()!! @@ -40,7 +42,7 @@ class ExportSettingsActivity : Activity() { "%02x".format(it) } - if (fingerprint == composeFingerprint) { + if (fingerprint == revancedFingerprint) { sendData() } } From e7c8d0e78c31b8555e7e77fb048a929752188845 Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Wed, 27 Sep 2023 14:36:39 -0700 Subject: [PATCH 12/38] use same fingerprint --- .../manager/flutter/ExportSettingsActivity.kt | 121 +++++++++--------- 1 file changed, 57 insertions(+), 64 deletions(-) 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 index 7a9f9f98..72f72caf 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt @@ -19,78 +19,71 @@ import android.util.Log class ExportSettingsActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val revancedFingerprint = "b6362c6ea7888efd15c0800f480786ad0f5b133b4f84e12d46afba5f9eac1223" + val callingPackageName = getCallingPackage()!! - // Get the package name of the app that started the activity - val packageName = 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).replace("\n", "") + 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", 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 signatures = packageInfo.signatures + val signature = packageInfo.signatures[0] - // Loop through each signature and print its properties - for (signature in signatures) { - // Get the raw certificate data - val rawCert = signature.toByteArray() + // 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 + // 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) - } - - if (fingerprint == revancedFingerprint) { - sendData() - } + // Get the SHA256 fingerprint + val fingerprint = MessageDigest.getInstance("SHA256").digest(x509Cert.encoded).joinToString("") { + "%02x".format(it) } - // Send data back - val resultIntent = Intent() - setResult(Activity.RESULT_CANCELED) - finish() - } - - fun sendData() { - // 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).replace("\n", "") - 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", patches) - } - - // Send data back - val resultIntent = Intent() - resultIntent.putExtra("data", json.toString()) - setResult(Activity.RESULT_OK, resultIntent) - finish() + return fingerprint } } From 2968d96fe9b301926db1e5f91a0b2033415645a5 Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Wed, 27 Sep 2023 14:42:11 -0700 Subject: [PATCH 13/38] remove log import --- .../app/revanced/manager/flutter/ExportSettingsActivity.kt | 2 -- 1 file changed, 2 deletions(-) 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 index 72f72caf..8a397f8c 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt @@ -14,8 +14,6 @@ import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.security.MessageDigest -import android.util.Log - class ExportSettingsActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) From 37986c58ec4c8711689531db1c49df2c9d303115 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Thu, 28 Sep 2023 21:45:41 +0700 Subject: [PATCH 14/38] docs(building): correct path to gradle.properties --- docs/4_building.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/4_building.md b/docs/4_building.md index d4c0887c..adcf47dd 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 From 62505f2543632f74ce7496e9e0862adc8b5c9d7f Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 26 Sep 2023 05:14:27 +0200 Subject: [PATCH 15/38] build: Bump dependencies --- android/app/build.gradle | 2 +- .../revanced/manager/flutter/MainActivity.kt | 70 +++++++++++-------- assets/i18n/en_US.json | 1 - lib/models/patch.dart | 11 +-- lib/services/manager_api.dart | 10 ++- lib/services/patcher_api.dart | 35 ---------- lib/ui/views/patcher/patcher_viewmodel.dart | 45 +----------- .../patches_selector_view.dart | 4 +- 8 files changed, 51 insertions(+), 127 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 9f8312e7..16c72ee3 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:15.0.2" // Signing & aligning implementation("org.bouncycastle:bcpkix-jdk15on:1.70") 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 22463f8c..3cf0fafb 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 @@ -10,20 +10,18 @@ import app.revanced.manager.flutter.utils.zip.structures.ZipEntry import app.revanced.patcher.PatchBundleLoader 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 @@ -93,29 +91,43 @@ 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( - 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 + + JSONArray().apply { + try { + PatchBundleLoader.Dex( + File(patchBundleFilePath), + optimizedDexDirectory = File(cacheDirPath) + ) + } catch (ex: Exception) { + return@setMethodCallHandler result.notImplemented() + } catch (err: Error) { + return@setMethodCallHandler result.notImplemented() + }.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() @@ -220,7 +232,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 +263,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) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 05694ced..9092bbdc 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -73,7 +73,6 @@ "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": { diff --git a/lib/models/patch.dart b/lib/models/patch.dart index 7acf05ba..b3d99e2a 100644 --- a/lib/models/patch.dart +++ b/lib/models/patch.dart @@ -9,26 +9,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/services/manager_api.dart b/lib/services/manager_api.dart index 3401432b..7d42f6d7 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -315,18 +315,16 @@ class ManagerAPI { 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) { diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index 5838ab3b..ecc3b218 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -149,46 +149,11 @@ 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) { diff --git a/lib/ui/views/patcher/patcher_viewmodel.dart b/lib/ui/views/patcher/patcher_viewmodel.dart index 33ceb719..bd0bc41b 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 } } diff --git a/lib/ui/views/patches_selector/patches_selector_view.dart b/lib/ui/views/patches_selector/patches_selector_view.dart index 26766f4e..b0494b52 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: From 8661d72e453b12bd8696fad5121a66f93486427e Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 29 Sep 2023 17:00:34 +0200 Subject: [PATCH 16/38] feat: Simplify label --- assets/i18n/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 9092bbdc..8ff7d9bc 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -23,7 +23,7 @@ "widgetTitle": "Dashboard", "updatesSubtitle": "Updates", - "patchedSubtitle": "Patched applications", + "patchedSubtitle": "Patched apps", "noUpdates": "No updates available", From 8ce266bc94fb3add1fe9f2787734f121664e8df4 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 29 Sep 2023 18:39:07 +0200 Subject: [PATCH 17/38] perf: Reduce amount of network requests --- assets/i18n/en_US.json | 4 +- lib/models/patch.dart | 1 - lib/models/patched_application.dart | 4 - lib/services/github_api.dart | 59 +------ lib/services/manager_api.dart | 68 +------- lib/services/revanced_api.dart | 34 ++-- lib/ui/views/home/home_viewmodel.dart | 9 +- .../navigation/navigation_viewmodel.dart | 4 +- .../widgets/homeView/installed_apps_card.dart | 2 - .../settings_advanced_section.dart | 2 +- lib/ui/widgets/shared/application_item.dart | 162 +++++------------- pubspec.yaml | 1 + 12 files changed, 78 insertions(+), 272 deletions(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 8ff7d9bc..571888cf 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -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...", diff --git a/lib/models/patch.dart b/lib/models/patch.dart index b3d99e2a..fe2b3c33 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'; diff --git a/lib/models/patched_application.dart b/lib/models/patched_application.dart index 90bfb9a3..8f54dd72 100644 --- a/lib/models/patched_application.dart +++ b/lib/models/patched_application.dart @@ -16,9 +16,7 @@ class PatchedApplication { required this.patchDate, this.isRooted = false, this.isFromStorage = false, - this.hasUpdates = false, this.appliedPatches = const [], - this.changelog = const [], }); factory PatchedApplication.fromJson(Map json) => @@ -36,9 +34,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 0949f1b9..5f393daf 100644 --- a/lib/services/github_api.dart +++ b/lib/services/github_api.dart @@ -51,6 +51,7 @@ class GithubAPI { Future clearAllCache() async { try { await _cacheOptions.store!.clean(); + await DefaultCacheManager().emptyCache(); } on Exception catch (e) { if (kDebugMode) { print(e); @@ -142,38 +143,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 +206,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 7d42f6d7..5882f810 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -616,39 +616,6 @@ class ManagerAPI { ); } - Future reAssessSavedApps() async { - final List patchedApps = getPatchedApps(); - final List unsavedApps = - await getUnsavedApps(patchedApps); - patchedApps.addAll(unsavedApps); - final List toRemove = - 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; - } - } - } - } - await setPatchedApps(patchedApps); - } - Future isAppUninstalled(PatchedApplication app) async { bool existsRoot = false; final bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName); @@ -662,37 +629,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) { @@ -760,6 +696,8 @@ class ManagerAPI { Future resetLastSelectedPatches() async { final File selectedPatchesFile = File(storedPatchesFile); - selectedPatchesFile.deleteSync(); + if (selectedPatchesFile.existsSync()) { + selectedPatchesFile.deleteSync(); + } } } diff --git a/lib/services/revanced_api.dart b/lib/services/revanced_api.dart index dde23cee..f21e6b71 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), @@ -38,6 +41,7 @@ class RevancedAPI { Future clearAllCache() async { try { await _cacheOptions.store!.clean(); + await DefaultCacheManager().emptyCache(); } on Exception catch (e) { if (kDebugMode) { print(e); @@ -66,21 +70,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/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index cfc99e66..7ff6d1bb 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -37,15 +37,15 @@ class HomeViewModel extends BaseViewModel { DateTime? _lastUpdate; bool showUpdatableApps = false; List patchedInstalledApps = []; - List patchedUpdatableApps = []; String? _latestManagerVersion = ''; File? downloadedApk; Future initialize(BuildContext context) async { - _latestManagerVersion = await _managerAPI.getLatestManagerVersion(); if (!_managerAPI.getPatchesConsent()) { await showPatchesConsent(context); } + + _latestManagerVersion = await _managerAPI.getLatestManagerVersion(); await _patcherAPI.initialize(); await flutterLocalNotificationsPlugin.initialize( const InitializationSettings( @@ -83,7 +83,6 @@ class HomeViewModel extends BaseViewModel { } } _getPatchedApps(); - _managerAPI.reAssessSavedApps().then((_) => _getPatchedApps()); } void navigateToAppInfo(PatchedApplication app) { @@ -108,10 +107,6 @@ class HomeViewModel extends BaseViewModel { void _getPatchedApps() { patchedInstalledApps = _managerAPI.getPatchedApps().toList(); - patchedUpdatableApps = _managerAPI - .getPatchedApps() - .where((app) => app.hasUpdates == true) - .toList(); notifyListeners(); } diff --git a/lib/ui/views/navigation/navigation_viewmodel.dart b/lib/ui/views/navigation/navigation_viewmodel.dart index a2a0e4bb..a911f070 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/widgets/homeView/installed_apps_card.dart b/lib/ui/widgets/homeView/installed_apps_card.dart index 7a989856..ec825340 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 2d9be3fc..f3e838a6 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 42eee351..b7336435 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, - ), - ), - Text( - format(widget.patchDate), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ), - ], - ), - ), - Row( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: 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), - ), + SizedBox( + width: 40, + child: Image.memory(widget.icon, height: 40, width: 40), ), - 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, - ), - ], + 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, + ), + ), + ], + ), ), ], ), - ], - ), - 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 98d16d32..297c8ada 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 From 15b8613d3cb2e0fe9b19d3bb9304f53db8cba372 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 29 Sep 2023 19:40:02 +0200 Subject: [PATCH 18/38] feat: Only log relevant records --- .../main/kotlin/app/revanced/manager/flutter/MainActivity.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 3cf0fafb..bd33a8b2 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 @@ -180,8 +180,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() From 2e8e3b0d1ec4396796e264501ca6dbf1c615a878 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 29 Sep 2023 20:12:39 +0200 Subject: [PATCH 19/38] fix: Do not hardcode any predefined packages --- assets/i18n/en_US.json | 4 -- lib/services/github_api.dart | 11 ----- .../views/installer/installer_viewmodel.dart | 43 ++++--------------- .../appInfoView/app_info_viewmodel.dart | 12 +----- 4 files changed, 9 insertions(+), 61 deletions(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 571888cf..25945796 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -159,10 +159,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": { diff --git a/lib/services/github_api.dart b/lib/services/github_api.dart index 5f393daf..442b3ee6 100644 --- a/lib/services/github_api.dart +++ b/lib/services/github_api.dart @@ -21,17 +21,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( diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index f523f2a0..2245cab7 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -270,34 +270,6 @@ 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...', @@ -311,16 +283,17 @@ class InstallerViewModel extends BaseViewModel { _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.', - ); + + // In case a patch changed the app name or package name, + // update the app info. + final app = await DeviceApps.getAppFromStorage(_app.apkFilePath); + if (app != null) { + _app.name = app.appName; + _app.packageName = app.packageName; } + await _managerAPI.savePatchedApp(_app); } - } } on Exception catch (e) { if (kDebugMode) { print(e); diff --git a/lib/ui/widgets/appInfoView/app_info_viewmodel.dart b/lib/ui/widgets/appInfoView/app_info_viewmodel.dart index dd364c7d..69b3f68b 100644 --- a/lib/ui/widgets/appInfoView/app_info_viewmodel.dart +++ b/lib/ui/widgets/appInfoView/app_info_viewmodel.dart @@ -147,17 +147,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) { From 1e8d8f749abc61e6b28b9db9f44c7abab17e6544 Mon Sep 17 00:00:00 2001 From: aAbed Date: Sat, 30 Sep 2023 14:57:48 +0545 Subject: [PATCH 20/38] fix: do not ask for patches consent before initializing model --- lib/ui/views/home/home_viewmodel.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index 7ff6d1bb..69ce644d 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -41,11 +41,10 @@ class HomeViewModel extends BaseViewModel { File? downloadedApk; Future initialize(BuildContext context) async { + _latestManagerVersion = await _managerAPI.getLatestManagerVersion(); if (!_managerAPI.getPatchesConsent()) { await showPatchesConsent(context); } - - _latestManagerVersion = await _managerAPI.getLatestManagerVersion(); await _patcherAPI.initialize(); await flutterLocalNotificationsPlugin.initialize( const InitializationSettings( From 6bdc0c7bb24e98835caf0e9ab6bee03b0eeee476 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 30 Sep 2023 19:58:45 +0200 Subject: [PATCH 21/38] feat: Simplify label --- assets/i18n/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 25945796..e0038f3e 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -29,7 +29,7 @@ "WIP": "Work in progress...", - "noInstallations": "No patched applications installed", + "noInstallations": "No patched apps installed", "installUpdate": "Continue to install the update?", "updateDialogTitle": "Update Manager", From 36c86e22b1305043d6d19753cbbe1a3dc4df2eca Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 30 Sep 2023 19:59:31 +0200 Subject: [PATCH 22/38] fix: Load installed apps --- .../revanced/manager/flutter/MainActivity.kt | 1 - lib/services/manager_api.dart | 71 +++++++++---------- lib/ui/views/home/home_viewmodel.dart | 3 +- 3 files changed, 35 insertions(+), 40 deletions(-) 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 bd33a8b2..691267b3 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 @@ -94,7 +94,6 @@ class MainActivity : FlutterActivity() { val patchBundleFilePath = call.argument("patchBundleFilePath")!! val cacheDirPath = call.argument("cacheDirPath")!! - JSONArray().apply { try { PatchBundleLoader.Dex( diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart index 5882f810..275efc93 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,7 +311,7 @@ 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(); @@ -324,7 +326,9 @@ class ManagerAPI { ); final List patchesJsonList = jsonDecode(patchesJson); - patches = patchesJsonList.map((patchJson) => Patch.fromJson(patchJson)).toList(); + patches = patchesJsonList + .map((patchJson) => Patch.fromJson(patchJson)) + .toList(); return patches; } on Exception catch (e) { if (kDebugMode) { @@ -501,48 +505,18 @@ 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, @@ -551,12 +525,14 @@ class ManagerAPI { apkFilePath: application.apkFilePath, icon: application.icon, patchDate: DateTime.now(), + isRooted: true, ), ); } } } - return unsavedApps; + + return mountedApps; } Future showPatchesChangeWarningDialog(BuildContext context) { @@ -616,6 +592,25 @@ class ManagerAPI { ); } + Future reAssessSavedApps() async { + final List patchedApps = getPatchedApps(); + + // Remove apps that are not installed anymore. + final List toRemove = + await getAppsToRemove(patchedApps); + patchedApps.removeWhere((a) => toRemove.contains(a)); + + // 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); + } + Future isAppUninstalled(PatchedApplication app) async { bool existsRoot = false; final bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName); diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index 69ce644d..40cf85b4 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -81,7 +81,8 @@ class HomeViewModel extends BaseViewModel { _toast.showBottom('homeView.errorDownloadMessage'); } } - _getPatchedApps(); + + _managerAPI.reAssessSavedApps().then((_) => _getPatchedApps()); } void navigateToAppInfo(PatchedApplication app) { From 0492e910ea565bd3cb35f781c298f867ec8bfaf9 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 30 Sep 2023 20:10:45 +0200 Subject: [PATCH 23/38] fix: Fill the preferred action --- lib/ui/views/settings/settings_viewmodel.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/ui/views/settings/settings_viewmodel.dart b/lib/ui/views/settings/settings_viewmodel.dart index 2441b0a6..7688d8b1 100644 --- a/lib/ui/views/settings/settings_viewmodel.dart +++ b/lib/ui/views/settings/settings_viewmodel.dart @@ -71,12 +71,6 @@ class SettingsViewModel extends BaseViewModel { actions: [ CustomMaterialButton( isFilled: false, - label: I18nText('noButton'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - CustomMaterialButton( label: I18nText('yesButton'), onPressed: () { _managerAPI.setChangingToggleModified(true); @@ -84,6 +78,12 @@ class SettingsViewModel extends BaseViewModel { Navigator.of(context).pop(); }, ), + CustomMaterialButton( + label: I18nText('noButton'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), ], ), ); From 91837ebade5e0a2f47731ba33b7268e3c7bb09e4 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 30 Sep 2023 20:11:45 +0200 Subject: [PATCH 24/38] feat: Remove original package name in app info view --- assets/i18n/en_US.json | 1 - lib/models/patched_application.dart | 2 -- lib/services/github_api.dart | 2 -- lib/services/manager_api.dart | 1 - .../app_selector/app_selector_viewmodel.dart | 2 -- lib/ui/views/patcher/patcher_viewmodel.dart | 6 +++--- .../patches_selector_viewmodel.dart | 10 +++++----- lib/ui/widgets/appInfoView/app_info_view.dart | 16 ---------------- .../widgets/appInfoView/app_info_viewmodel.dart | 1 - 9 files changed, 8 insertions(+), 33 deletions(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index e0038f3e..3ad34a50 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -278,7 +278,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/lib/models/patched_application.dart b/lib/models/patched_application.dart index 8f54dd72..23517595 100644 --- a/lib/models/patched_application.dart +++ b/lib/models/patched_application.dart @@ -9,7 +9,6 @@ class PatchedApplication { PatchedApplication({ required this.name, required this.packageName, - required this.originalPackageName, required this.version, required this.apkFilePath, required this.icon, @@ -23,7 +22,6 @@ class PatchedApplication { _$PatchedApplicationFromJson(json); String name; String packageName; - String originalPackageName; String version; final String apkFilePath; @JsonKey( diff --git a/lib/services/github_api.dart b/lib/services/github_api.dart index 442b3ee6..03eacf8b 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 diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart index 275efc93..d3eed845 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -520,7 +520,6 @@ class ManagerAPI { PatchedApplication( name: application.appName, packageName: application.packageName, - originalPackageName: application.packageName, version: application.versionName!, apkFilePath: application.apkFilePath, icon: application.icon, diff --git a/lib/ui/views/app_selector/app_selector_viewmodel.dart b/lib/ui/views/app_selector/app_selector_viewmodel.dart index e3691bba..bd029133 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/patcher/patcher_viewmodel.dart b/lib/ui/views/patcher/patcher_viewmodel.dart index bd0bc41b..e0cadb4a 100644 --- a/lib/ui/views/patcher/patcher_viewmodel.dart +++ b/lib/ui/views/patcher/patcher_viewmodel.dart @@ -142,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))); @@ -160,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_viewmodel.dart b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart index 71e4a16e..ea48c0da 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/widgets/appInfoView/app_info_view.dart b/lib/ui/widgets/appInfoView/app_info_view.dart index 2d0f17e1..283beb12 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 69b3f68b..bc24a558 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 { From c4a795418f80ce873d7bdf90fdb2f4f19142b079 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 30 Sep 2023 21:13:32 +0200 Subject: [PATCH 25/38] fix: Move installation log to correct place --- lib/ui/views/installer/installer_viewmodel.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 2245cab7..52318967 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -279,7 +279,6 @@ class InstallerViewModel extends BaseViewModel { ); 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(); @@ -293,6 +292,8 @@ class InstallerViewModel extends BaseViewModel { } await _managerAPI.savePatchedApp(_app); + + update(1.0, 'Installed!', 'Installed!'); } } on Exception catch (e) { if (kDebugMode) { From 2b4b3ca0a51af92fab4b04edbc39d6c3a4ea2c33 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 30 Sep 2023 21:40:03 +0200 Subject: [PATCH 26/38] fix: Retrieve app information from patched app --- lib/services/patcher_api.dart | 24 +++++++++---------- .../views/installer/installer_viewmodel.dart | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index ecc3b218..9a094bec 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -28,7 +28,7 @@ class PatcherAPI { List _universalPatches = []; List _compatiblePackages = []; Map filteredPatches = >{}; - File? _outFile; + File? outFile; Future initialize() async { await _loadPatches(); @@ -162,7 +162,7 @@ class PatcherAPI { 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; @@ -174,7 +174,7 @@ class PatcherAPI { 'originalFilePath': originalFilePath, 'inputFilePath': inputFile.path, 'patchedFilePath': patchedFile.path, - 'outFilePath': _outFile!.path, + 'outFilePath': outFile!.path, 'integrationsPath': integrationsFile!.path, 'selectedPatches': selectedPatches.map((p) => p.name).toList(), 'cacheDirPath': cacheDir.path, @@ -201,7 +201,7 @@ class PatcherAPI { } Future installPatchedFile(PatchedApplication patchedApp) async { - if (_outFile != null) { + if (outFile != null) { try { if (patchedApp.isRooted) { final bool hasRootPermissions = await _rootAPI.hasRootPermissions(); @@ -209,11 +209,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) { @@ -228,11 +228,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, ), ); @@ -246,12 +246,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/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 52318967..426b0669 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -285,7 +285,7 @@ class InstallerViewModel extends BaseViewModel { // In case a patch changed the app name or package name, // update the app info. - final app = await DeviceApps.getAppFromStorage(_app.apkFilePath); + final app = await DeviceApps.getAppFromStorage(_patcherAPI.outFile!.path); if (app != null) { _app.name = app.appName; _app.packageName = app.packageName; From 123a375a27a787f621e05bf906e2cbdc6a36e8bb Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 30 Sep 2023 22:14:51 +0200 Subject: [PATCH 27/38] refactor: Remove unused strings --- assets/i18n/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 3ad34a50..242ebf64 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -69,8 +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?", + "removedPatchesWarningDialogText": "The following patches have been removed since the last time you used them.\n\n{patches}\n\nProceed anyways?" }, "appSelectorCard": { From 6e26130744efb71c5979f12fb0cfeb4fbe0d1577 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 30 Sep 2023 22:15:46 +0200 Subject: [PATCH 28/38] chore: Add todo --- lib/ui/views/installer/installer_viewmodel.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 426b0669..ab60c377 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -294,6 +294,8 @@ class InstallerViewModel extends BaseViewModel { await _managerAPI.savePatchedApp(_app); update(1.0, 'Installed!', 'Installed!'); + } else { + // TODO(aabed): Show error message. } } on Exception catch (e) { if (kDebugMode) { From 6961bb7fd096cd618e24e343a92f00fb8eeba1ee Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 1 Oct 2023 04:48:24 +0200 Subject: [PATCH 29/38] fix: Do not delete cached downloads --- lib/services/github_api.dart | 1 - lib/services/revanced_api.dart | 1 - lib/ui/views/home/home_viewmodel.dart | 6 +----- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/services/github_api.dart b/lib/services/github_api.dart index 03eacf8b..8d3244b3 100644 --- a/lib/services/github_api.dart +++ b/lib/services/github_api.dart @@ -38,7 +38,6 @@ class GithubAPI { Future clearAllCache() async { try { await _cacheOptions.store!.clean(); - await DefaultCacheManager().emptyCache(); } on Exception catch (e) { if (kDebugMode) { print(e); diff --git a/lib/services/revanced_api.dart b/lib/services/revanced_api.dart index f21e6b71..fca17c0e 100644 --- a/lib/services/revanced_api.dart +++ b/lib/services/revanced_api.dart @@ -41,7 +41,6 @@ class RevancedAPI { Future clearAllCache() async { try { await _cacheOptions.store!.clean(); - await DefaultCacheManager().emptyCache(); } on Exception catch (e) { if (kDebugMode) { print(e); diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index 40cf85b4..1ab8437d 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -464,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); } From c87f92b34601097dfb722162043469de0983b62d Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 1 Oct 2023 06:21:03 +0200 Subject: [PATCH 30/38] feat: Adjust install dialog labels --- assets/i18n/en_US.json | 5 ++--- lib/ui/views/installer/installer_viewmodel.dart | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 242ebf64..d3fe1128 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -145,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", diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index ab60c377..137d2269 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -209,7 +209,6 @@ class InstallerViewModel extends BaseViewModel { ), RadioListTile( title: I18nText('installerView.installNonRootType'), - subtitle: I18nText('installerView.installRecommendedType'), contentPadding: const EdgeInsets.symmetric(horizontal: 16), value: 0, groupValue: value, From 697ae92031a48fd071525694f08e442679c1a131 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 1 Oct 2023 19:02:49 +0200 Subject: [PATCH 31/38] Apply suggestions from code review [skip ci] --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 16c72ee3..7854f0c1 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:15.0.2" + implementation "app.revanced:revanced-patcher:15.0.3" // Signing & aligning implementation("org.bouncycastle:bcpkix-jdk15on:1.70") From 012110f00868c3f87e934397ceddbb6cc8b068f8 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 3 Oct 2023 18:54:51 +0200 Subject: [PATCH 32/38] perf: Do not load patches twice --- .../revanced/manager/flutter/MainActivity.kt | 35 +++++++++---------- lib/services/patcher_api.dart | 16 ++++----- .../views/installer/installer_viewmodel.dart | 2 ++ 3 files changed, 25 insertions(+), 28 deletions(-) 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 691267b3..76ca3c66 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,6 +8,7 @@ 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.patch.PatchResult @@ -31,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) @@ -46,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") @@ -57,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 && @@ -71,7 +73,6 @@ class MainActivity : FlutterActivity() { cancel = false runPatcher( result, - patchBundleFilePath, originalFilePath, inputFilePath, patchedFilePath, @@ -94,17 +95,19 @@ class MainActivity : FlutterActivity() { val patchBundleFilePath = call.argument("patchBundleFilePath")!! val cacheDirPath = call.argument("cacheDirPath")!! + try { + patches = PatchBundleLoader.Dex( + File(patchBundleFilePath), + optimizedDexDirectory = File(cacheDirPath) + ) + } catch (ex: Exception) { + return@setMethodCallHandler result.notImplemented() + } catch (err: Error) { + return@setMethodCallHandler result.notImplemented() + } + JSONArray().apply { - try { - PatchBundleLoader.Dex( - File(patchBundleFilePath), - optimizedDexDirectory = File(cacheDirPath) - ) - } catch (ex: Exception) { - return@setMethodCallHandler result.notImplemented() - } catch (err: Error) { - return@setMethodCallHandler result.notImplemented() - }.forEach { + patches.forEach { JSONObject().apply { put("name", it.name) put("description", it.description) @@ -136,7 +139,6 @@ class MainActivity : FlutterActivity() { private fun runPatcher( result: MethodChannel.Result, - patchBundleFilePath: String, originalFilePath: String, inputFilePath: String, patchedFilePath: String, @@ -223,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 diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index 9a094bec..f6c8aa3e 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -59,9 +59,7 @@ class PatcherAPI { } List getUniversalPatches() { - return _patches - .where((patch) => patch.compatiblePackages.isEmpty) - .toList(); + return _patches.where((patch) => patch.compatiblePackages.isEmpty).toList(); } Future _loadPatches() async { @@ -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); } } @@ -154,9 +151,9 @@ class PatcherAPI { String apkFilePath, List selectedPatches, ) async { - 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-'); @@ -170,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, + 'integrationsPath': integrationsFile.path, 'selectedPatches': selectedPatches.map((p) => p.name).toList(), 'cacheDirPath': cacheDir.path, 'keyStoreFilePath': _keyStoreFile.path, diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 137d2269..76a18f21 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -95,6 +95,8 @@ class InstallerViewModel extends BaseViewModel { isInstalled = false; hasErrors = false; } else if (value == 1.0) { + _managerAPI.patches.clear(); + isPatching = false; hasErrors = false; await _managerAPI.savePatches( From 3a88d4d3e69075adb6eedc4448476432cbb6a44b Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 4 Oct 2023 17:30:36 +0200 Subject: [PATCH 33/38] build: Bump dependencies --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 7854f0c1..7f8b88f9 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:15.0.3" + implementation "app.revanced:revanced-patcher:16.0.0" // Signing & aligning implementation("org.bouncycastle:bcpkix-jdk15on:1.70") From d8eadc2a2dcaaf958f79db4d236dacde787958fc Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 4 Oct 2023 18:45:13 +0200 Subject: [PATCH 34/38] feat: Use simpler wording --- .../revanced/manager/flutter/MainActivity.kt | 2 +- .../views/installer/installer_viewmodel.dart | 99 +++++++++---------- 2 files changed, 50 insertions(+), 51 deletions(-) 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 76ca3c66..b88abb82 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 @@ -330,7 +330,7 @@ class MainActivity : FlutterActivity() { val stack = ex.stackTraceToString() updateProgress( -100.0, - "Aborted", + "Failed", "An error occurred:\n$stack" ) } diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 76a18f21..32bc245c 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -132,28 +132,24 @@ 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'); + update(0.1, '', 'Creating working directory'); + 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); } + } + + try { if (FlutterBackground.isBackgroundExecutionEnabled) { try { FlutterBackground.disableBackgroundExecution(); @@ -211,7 +207,8 @@ class InstallerViewModel extends BaseViewModel { ), RadioListTile( title: I18nText('installerView.installNonRootType'), - contentPadding: const EdgeInsets.symmetric(horizontal: 16), + contentPadding: + const EdgeInsets.symmetric(horizontal: 16), value: 0, groupValue: value, onChanged: (selected) { @@ -220,7 +217,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) { @@ -258,9 +256,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); @@ -271,33 +269,34 @@ class InstallerViewModel extends BaseViewModel { Future installResult(BuildContext context, bool installAsRoot) async { try { _app.isRooted = installAsRoot; - 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(); + 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. + // 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) { print(e); From b07439d402edd392e39c2bc29b4dfbaec58fa7f6 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 4 Oct 2023 19:36:58 +0200 Subject: [PATCH 35/38] fix: Reload patches --- lib/ui/views/installer/installer_viewmodel.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 32bc245c..3bfa5d24 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -95,8 +95,6 @@ class InstallerViewModel extends BaseViewModel { isInstalled = false; hasErrors = false; } else if (value == 1.0) { - _managerAPI.patches.clear(); - isPatching = false; hasErrors = false; await _managerAPI.savePatches( @@ -132,7 +130,6 @@ class InstallerViewModel extends BaseViewModel { Future runPatcher() async { try { - update(0.1, '', 'Creating working directory'); await _patcherAPI.runPatcher( _app.packageName, _app.apkFilePath, @@ -149,6 +146,12 @@ class InstallerViewModel extends BaseViewModel { } } + // Necessary to reset the state of patches by reloading them + // in a later patching process. + // TODO(Benjamin): Fix this not working + _managerAPI.patches.clear(); + await _managerAPI.getPatches(); + try { if (FlutterBackground.isBackgroundExecutionEnabled) { try { From f7c11d07a89ed92c3d67e5213b1318f6dd2ac52d Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Wed, 4 Oct 2023 11:33:13 -0700 Subject: [PATCH 36/38] fix(export-settings): export patches as json object --- .../app/revanced/manager/flutter/ExportSettingsActivity.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 index 8a397f8c..f5c7ad69 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt @@ -40,8 +40,7 @@ class ExportSettingsActivity : Activity() { val keystoreFile = File(getExternalFilesDir(null), "/revanced-manager.keystore") if (keystoreFile.exists()) { val keystoreBytes = keystoreFile.readBytes() - val keystoreBase64 = - Base64.encodeToString(keystoreBytes, Base64.DEFAULT).replace("\n", "") + val keystoreBase64 = Base64.encodeToString(keystoreBytes, Base64.DEFAULT) json.put("keystore", keystoreBase64) } @@ -50,7 +49,7 @@ class ExportSettingsActivity : Activity() { if (storedPatchesFile.exists()) { val patchesBytes = storedPatchesFile.readBytes() val patches = String(patchesBytes, Charsets.UTF_8) - json.put("patches", patches) + json.put("patches", JSONObject(patches)) } // Send data back From cd07f39b695b1eff73409992bf72c59b02227076 Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Wed, 4 Oct 2023 12:16:56 -0700 Subject: [PATCH 37/38] fix: reset patches after patching --- lib/services/patcher_api.dart | 4 ++-- lib/ui/views/installer/installer_viewmodel.dart | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index f6c8aa3e..dc4ffabd 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -31,7 +31,7 @@ class PatcherAPI { File? outFile; Future initialize() async { - await _loadPatches(); + await loadPatches(); await _managerAPI.downloadIntegrations(); final Directory appCache = await getTemporaryDirectory(); _dataDir = await getExternalStorageDirectory() ?? appCache; @@ -62,7 +62,7 @@ class PatcherAPI { return _patches.where((patch) => patch.compatiblePackages.isEmpty).toList(); } - Future _loadPatches() async { + Future loadPatches() async { try { if (_patches.isEmpty) { _patches = await _managerAPI.getPatches(); diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 3bfa5d24..8d54987d 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -148,9 +148,8 @@ class InstallerViewModel extends BaseViewModel { // Necessary to reset the state of patches by reloading them // in a later patching process. - // TODO(Benjamin): Fix this not working _managerAPI.patches.clear(); - await _managerAPI.getPatches(); + await _patcherAPI.loadPatches(); try { if (FlutterBackground.isBackgroundExecutionEnabled) { From 7ae09159ba8f47ccfd81c43bf5c1b6da49936356 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 5 Oct 2023 01:23:48 +0200 Subject: [PATCH 38/38] build: Bump version to v1.11.0 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 297c8ada..34f43cc6 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'