Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: settings migration (flutter) #1308

Merged
merged 13 commits into from
Oct 1, 2023
16 changes: 16 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".ExportSettingsActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"/>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String>("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"
}
}
6 changes: 6 additions & 0 deletions assets/i18n/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
16 changes: 13 additions & 3 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@ 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';
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();
Expand All @@ -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) {
Expand All @@ -42,7 +52,7 @@ class MyApp extends StatelessWidget {

return DynamicThemeBuilder(
title: 'ReVanced Manager',
home: const NavigationView(),
home: homeView,
localizationsDelegates: [
FlutterI18nDelegate(
translationLoader: FileTranslationLoader(
Expand Down
35 changes: 35 additions & 0 deletions lib/ui/views/export_settings/export_settings_view.dart
Original file line number Diff line number Diff line change
@@ -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: <Widget> [
CustomMaterialButton(
isFilled: false,
label: I18nText('exportSettingsView.dismissButton'),
onPressed: _exportSettingsViewModel.deny,
),
CustomMaterialButton(
label: I18nText('exportSettingsView.exportButton'),
onPressed: () async {
await _exportSettingsViewModel.accept();
},
),
],
),
);
}
}
71 changes: 71 additions & 0 deletions lib/ui/views/export_settings/export_settings_viewmodel.dart
Original file line number Diff line number Diff line change
@@ -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<ManagerAPI>();

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<void> accept() async {
final externalDir = await getExternalStorageDirectory();

final Map<String, dynamic> 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');
}
}