From d9d5b746c3a677ba9f56f94f462ef6c25ab8c828 Mon Sep 17 00:00:00 2001 From: Benjamin Halko Date: Tue, 26 Sep 2023 16:21:46 -0700 Subject: [PATCH] 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'); + } +}