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');
+ }
+}