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 352174a8..7ff82900 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 @@ -44,6 +44,8 @@ class MainActivity : FlutterActivity() { val selectedPatches = call.argument>("selectedPatches") val cacheDirPath = call.argument("cacheDirPath") val keyStoreFilePath = call.argument("keyStoreFilePath") + val keystorePassword = call.argument("keystorePassword") + if (patchBundleFilePath != null && originalFilePath != null && inputFilePath != null && @@ -52,7 +54,8 @@ class MainActivity : FlutterActivity() { integrationsPath != null && selectedPatches != null && cacheDirPath != null && - keyStoreFilePath != null + keyStoreFilePath != null && + keystorePassword != null ) { runPatcher( result, @@ -64,7 +67,8 @@ class MainActivity : FlutterActivity() { integrationsPath, selectedPatches, cacheDirPath, - keyStoreFilePath + keyStoreFilePath, + keystorePassword ) } else { result.notImplemented() @@ -85,7 +89,8 @@ class MainActivity : FlutterActivity() { integrationsPath: String, selectedPatches: List, cacheDirPath: String, - keyStoreFilePath: String + keyStoreFilePath: String, + keystorePassword: String ) { val originalFile = File(originalFilePath) val inputFile = File(inputFilePath) @@ -242,7 +247,7 @@ class MainActivity : FlutterActivity() { // Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile) try { - Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile) + Signer("ReVanced", keystorePassword).signApk(patchedFile, outFile, keyStoreFile) } catch (e: Exception) { //log to console print("Error signing apk: ${e.message}") diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 0ab716b6..b7e9cd53 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -169,7 +169,17 @@ "jsonSelectorErrorMessage": "Unable to use selected JSON file", "deleteLogsLabel": "Delete logs", "deleteLogsHint": "Delete collected manager logs", - "deletedLogs": "Logs deleted" + "deletedLogs": "Logs deleted", + "exportKeystoreLabel": "Export keystore", + "exportKeystoreHint": "Export keystore used to sign apps", + "exportedKeystore": "Keystore exported", + "noKeystoreExportFileFound": "No keystore to export", + "importKeystoreLabel": "Import keystore", + "importKeystoreHint": "Import keystore used to sign apps", + "importedKeystore": "Keystore imported", + "keystoreSelectorErrorMessage": "Unable to use selected KEYSTORE file", + "selectKeystorePassword": "Keystore Password", + "selectKeystorePasswordHint": "Select keystore password used to sign the apk" }, "appInfoView": { "widgetTitle": "App info", diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart index 7991c641..d100b837 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -22,6 +22,8 @@ class ManagerAPI { final String cliRepo = 'revanced-cli'; late SharedPreferences _prefs; String storedPatchesFile = '/selected-patches.json'; + String keystoreFile = '/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore'; + String defaultKeystorePassword = 's3cur3p@ssw0rd'; String defaultApiUrl = 'https://releases.revanced.app/'; String defaultRepoUrl = 'https://api.github.com'; String defaultPatcherRepo = 'revanced/revanced-patcher'; @@ -118,6 +120,14 @@ class ManagerAPI { await _prefs.setBool('experimentalPatchesEnabled', value); } + Future setKeystorePassword(String password) async { + await _prefs.setString('keystorePassword', password); + } + + String getKeystorePassword() { + return _prefs.getString('keystorePassword') ?? defaultKeystorePassword; + } + Future deleteTempFolder() async { final Directory dir = Directory('/data/local/tmp/revanced-manager'); if (await dir.exists()) { @@ -127,7 +137,7 @@ class ManagerAPI { Future deleteKeystore() async { final File keystore = File( - '/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore', + keystoreFile, ); if (await keystore.exists()) { await keystore.delete(); diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index 75e253a4..b600abac 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -210,6 +210,7 @@ class PatcherAPI { 'selectedPatches': selectedPatches.map((p) => p.name).toList(), 'cacheDirPath': cacheDir.path, 'keyStoreFilePath': _keyStoreFile.path, + 'keystorePassword': _managerAPI.getKeystorePassword(), }, ); } on Exception catch (e) { diff --git a/lib/ui/views/settings/settingsFragment/settings_manage_keystore_password.dart b/lib/ui/views/settings/settingsFragment/settings_manage_keystore_password.dart new file mode 100644 index 00000000..8e318256 --- /dev/null +++ b/lib/ui/views/settings/settingsFragment/settings_manage_keystore_password.dart @@ -0,0 +1,84 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/material.dart'; +import 'package:flutter_i18n/flutter_i18n.dart'; +import 'package:revanced_manager/app/app.locator.dart'; +import 'package:revanced_manager/services/manager_api.dart'; +import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart'; +import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart'; +import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; +import 'package:stacked/stacked.dart'; + +class SManageKeystorePassword extends BaseViewModel { + final ManagerAPI _managerAPI = locator(); + + final TextEditingController _keystorePasswordController = TextEditingController(); + + Future showKeystoreDialog(BuildContext context) async { + final String keystorePasswordText = _managerAPI.getKeystorePassword(); + _keystorePasswordController.text = keystorePasswordText; + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: Row( + children: [ + I18nText('settingsView.selectKeystorePassword'), + const Spacer(), + IconButton( + icon: const Icon(Icons.manage_history_outlined), + onPressed: () =>_keystorePasswordController.text = _managerAPI.defaultKeystorePassword, + color: Theme.of(context).colorScheme.secondary, + ) + ], + ), + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + content: SingleChildScrollView( + child: Column( + children: [ + CustomTextField( + inputController: _keystorePasswordController, + label: I18nText('settingsView.selectKeystorePassword'), + hint: "", + onChanged: (value) => notifyListeners(), + ), + ], + ), + ), + actions: [ + CustomMaterialButton( + isFilled: false, + label: I18nText('cancelButton'), + onPressed: () { + _keystorePasswordController.clear(); + Navigator.of(context).pop(); + }, + ), + CustomMaterialButton( + label: I18nText('okButton'), + onPressed: () { + String passwd = _keystorePasswordController.text; + _managerAPI.setKeystorePassword(passwd); + Navigator.of(context).pop(); + }, + ) + ], + ), + ); + } +} + +final sManageKeystorePassword = SManageKeystorePassword(); + +class SManageKeystorePasswordUI extends StatelessWidget { + const SManageKeystorePasswordUI({super.key}); + + @override + Widget build(BuildContext context) { + return SettingsTileDialog( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + title: 'settingsView.selectKeystorePassword', + subtitle: 'settingsView.selectKeystorePasswordHint', + onTap: () => sManageKeystorePassword.showKeystoreDialog(context), + ); + } +} diff --git a/lib/ui/views/settings/settings_viewmodel.dart b/lib/ui/views/settings/settings_viewmodel.dart index 65df575e..5edd2b5e 100644 --- a/lib/ui/views/settings/settings_viewmodel.dart +++ b/lib/ui/views/settings/settings_viewmodel.dart @@ -104,6 +104,46 @@ class SettingsViewModel extends BaseViewModel { } } + Future exportKeystore() async { + try { + final File outFile = File(_managerAPI.keystoreFile); + if (outFile.existsSync()) { + final String dateTime = + DateTime.now().toString().replaceAll(' ', '_').split('.').first; + await CRFileSaver.saveFileWithDialog( + SaveFileDialogParams( + sourceFilePath: outFile.path, + destinationFileName: 'keystore_$dateTime.keystore', + ), + ); + _toast.showBottom('settingsView.exportedKeystore'); + } else { + _toast.showBottom('settingsView.noKeystoreExportFileFound'); + } + } on Exception catch (e) { + if (kDebugMode) { + print(e); + } + } + } + + Future importKeystore() async { + try { + final FilePickerResult? result = await FilePicker.platform.pickFiles(); + if (result != null && result.files.single.path != null) { + final File inFile = File(result.files.single.path!); + inFile.copySync(_managerAPI.keystoreFile); + + _toast.showBottom('settingsView.importedKeystore'); + } + } on Exception catch (e) { + if (kDebugMode) { + print(e); + } + _toast.showBottom('settingsView.keystoreSelectorErrorMessage'); + } + } + void resetSelectedPatches() { _managerAPI.resetLastSelectedPatches(); _toast.showBottom('settingsView.resetStoredPatches'); diff --git a/lib/ui/widgets/settingsView/settings_advanced_section.dart b/lib/ui/widgets/settingsView/settings_advanced_section.dart index 23cfbcda..38582c97 100644 --- a/lib/ui/widgets/settingsView/settings_advanced_section.dart +++ b/lib/ui/widgets/settingsView/settings_advanced_section.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; 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/settingsFragment/settings_manage_keystore_password.dart'; import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart'; @@ -22,6 +23,7 @@ class SAdvancedSection extends StatelessWidget { children: [ SManageApiUrlUI(), SManageSourcesUI(), + // SManageKeystorePasswordUI(), SExperimentalUniversalPatches(), SExperimentalPatches(), ListTile( diff --git a/lib/ui/widgets/settingsView/settings_export_section.dart b/lib/ui/widgets/settingsView/settings_export_section.dart index c13bd91a..520f0a78 100644 --- a/lib/ui/widgets/settingsView/settings_export_section.dart +++ b/lib/ui/widgets/settingsView/settings_export_section.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_i18n/widgets/I18nText.dart'; import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart'; +import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_keystore_password.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; final _settingsViewModel = SettingsViewModel(); @@ -43,6 +44,40 @@ class SExportSection extends StatelessWidget { subtitle: I18nText('settingsView.importPatchesHint'), onTap: () => _settingsViewModel.importPatches(), ), + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.exportKeystoreLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.exportKeystoreHint'), + onTap: () => _settingsViewModel.exportKeystore(), + ), + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.importKeystoreLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.importKeystoreHint'), + onTap: () { + _settingsViewModel.importKeystore(); + final sManageKeystorePassword = SManageKeystorePassword(); + sManageKeystorePassword.showKeystoreDialog(context); + }, + ), ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), title: I18nText(