diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 31c70fea..f7e4cc4b 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -5,6 +5,7 @@ "disabledLabel": "Disabled", "yesButton": "Yes", "noButton": "No", + "warning": "Warning", "navigationView": { "dashboardTab": "Dashboard", "patcherTab": "Patcher", @@ -45,7 +46,6 @@ "patcherView": { "widgetTitle": "Patcher", "patchButton": "Patch", - "patchDialogTitle": "Warning", "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?" }, "appSelectorCard": { @@ -80,12 +80,9 @@ "loadPatchesSelection": "Load patches selection", "noSavedPatches": "No saved patches for the selected app\nPress Done to save current selection", "noPatchesFound": "No patches found for the selected app", - "selectAllPatchesWarningTitle": "Warning", "selectAllPatchesWarningContent": "You are about to select all patches, that includes unrecommended patches and can cause unwanted behavior." }, "patchItem": { - "unsupportedWarningButton": "Warning", - "unsupportedDialogTitle": "Warning", "unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nSupported versions:\n{supportedVersions}", "unsupportedPatchVersion": "Patch is not supported for this app version. Enable experimental toggle in settings to proceed." }, @@ -119,7 +116,6 @@ "dynamicThemeHint": "Enjoy an experience closer to your device", "languageLabel": "Language", "englishOption": "English", - "frenchOption": "French", "sourcesLabel": "Sources", "sourcesLabelHint": "Configure your custom sources", "orgPatchesLabel": "Patches organization", diff --git a/assets/i18n/hi_IN.json b/assets/i18n/hi_IN.json new file mode 100644 index 00000000..1a3cc118 --- /dev/null +++ b/assets/i18n/hi_IN.json @@ -0,0 +1,197 @@ +{ + "okButton": "ठीक है", + "cancelButton": "रद्द करें", + "enabledLabel": "सक्रिय", + "disabledLabel": "निष्क्रिय", + "yesButton": "हाँ", + "noButton": "नहीं", + "navigationView": { + "dashboardTab": "नियंत्रण-पट्ट", + "patcherTab": "पैचर", + "settingsTab": "सेटिंग्स" + }, + "homeView": { + "widgetTitle": "नियंत्रण पट्ट", + "updatesSubtitle": "अपडेट", + "patchedSubtitle": "Patched applications", + "updatesAvailable": "अपडेट उपलब्ध है", + "noUpdates": "कोई अपडेट उपलब्ध नहीं", + "WIP": "Work in progress...", + "noInstallations": "कोई पैबंद किये हुआ अनुप्रयोग नहीं है।", + "installed": "इंस्टॉल किया हुआ", + "updateDialogTitle": "अपडेट Manager", + "updateDialogText": "क्या आप ReVanced Manager को डाउनलोड और अपडेट करना चाहते है?", + "notificationTitle": "Update downloaded", + "notificationText": "Tap to install the update", + "downloadingMessage": "अपडेट डाउनलोड हो रहा है", + "installingMessage": "अपडेट इंस्टॉल हो रहा है", + "errorDownloadMessage": "अपडेट डाउनलोड करने मे असफल", + "errorInstallMessage": "अपडेट इंस्टॉल करने में असफल", + "noConnection": "कोई इंटरनेट कनेक्शन नहीं", + "updatesDisabled": "पैच किए गए ऐप को अपडेट करना वर्तमान में अक्षम है। ऐप को फिर से रीपैच करें।" + }, + "applicationItem": { + "patchButton": "पैबंद", + "infoButton": "जानकारी", + "changelogLabel": "परिवर्तन पत्र" + }, + "latestCommitCard": { + "loadingLabel": "लोड हो रहा है...", + "timeagoLabel": "{time} पहले", + "patcherLabel": "पैबंद: ", + "managerLabel": "Manager: ", + "updateButton": "Manager अपडेट करे" + }, + "patcherView": { + "widgetTitle": "पैचर", + "patchButton": "पैबंद", + "patchDialogTitle": "चेतावनी", + "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?" + }, + "appSelectorCard": { + "widgetTitle": "Select an application", + "widgetTitleSelected": "चुना हुआ ऐप्लकैशन", + "widgetSubtitle": "कोई ऐप्लकैशन चुना हुआ नहीं", + "noAppsLabel": "No applications found", + "currentVersion": "वर्तमान", + "recommendedVersion": "अनुशंसित", + "anyVersion": "कोई" + }, + "patchSelectorCard": { + "widgetTitle": "पैच चुने", + "widgetTitleSelected": "चुने हुए पैच", + "widgetSubtitle": "पहले किसी एप्लिकेशन को चुने", + "widgetEmptySubtitle": "कोई पैच चुना हुआ नहीं" + }, + "socialMediaCard": { + "widgetTitle": "सामाजिक", + "widgetSubtitle": "हम ऑनलाइन है" + }, + "appSelectorView": { + "viewTitle": "Select an application", + "searchBarHint": "ऐप्लकैशन खोजे", + "storageButton": "स्टोरेज", + "errorMessage": "Unable to use selected application" + }, + "patchesSelectorView": { + "viewTitle": "पैच चुने", + "searchBarHint": "पैच खोजे", + "doneButton": "पूर्ण", + "loadPatchesSelection": "Load patches selection", + "noSavedPatches": "No saved patches for the selected app\nPress Done to save current selection", + "noPatchesFound": "चुने हुए ऐप्लकैशन के लिए कोई पैच नहीं मिले", + "selectAllPatchesWarningTitle": "चेतावनी", + "selectAllPatchesWarningContent": "आप सभी पैच का चयन करने वाले हैं, जिसमें अनुशंसित पैच शामिल हैं और अवांछित व्यवहार का कारण बन सकते हैं।" + }, + "patchItem": { + "unsupportedWarningButton": "चेतावनी", + "unsupportedDialogTitle": "चेतावनी", + "unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nSupported versions:\n{supportedVersions}", + "unsupportedPatchVersion": "इस ऐप संस्करण के लिए पैच समर्थित नहीं है। आगे बढ़ने के लिए सेटिंग में प्रयोगात्मक टॉगल सक्षम करें." + }, + "installerView": { + "widgetTitle": "इंस्टॉल कर्ता", + "installButton": "इंस्टॉल करे", + "installRootButton": "रूट के रूप मे इंस्टॉल करे", + "openButton": "खोलें", + "shareButton": "फाइल शेयर करे", + "notificationTitle": "ReVanced Manager पैच कर रहा है", + "notificationText": "इंस्टॉल कर्ता पर जाने के लिए टैप करे", + "shareApkMenuOption": "APK शेयर करे", + "exportApkMenuOption": "निर्यात APK", + "shareLogMenuOption": "लॉग शेयर करें", + "installErrorDialogTitle": "त्रुटि", + "installErrorDialogText1": "वर्तमान पैच चयन के साथ रूट इंस्टॉल संभव नहीं है।\nअपने ऐप को रीपैच करें या नॉन-रूट इंस्टॉल चुनें।", + "installErrorDialogText2": "वर्तमान पैच चयन के साथ नॉन-रूट इंस्टॉल संभव नहीं है।\nयदि आपने अपना डिवाइस रूट किया है तो अपने ऐप को रीपैच करें या रूट इंस्टॉल चुनें।", + "installErrorDialogText3": "रूट इंस्टॉल संभव नहीं है क्योंकि मूल APK को स्टोरेज से चुना गया था।\nएक इंस्टॉल किया गया ऐप चुनें या नॉन-रूट इंस्टॉल चुनें।", + "noExit": "Installer is still running, cannot exit..." + }, + "settingsView": { + "widgetTitle": "सेटिंग्स", + "appearanceSectionTitle": "स्वरूप", + "teamSectionTitle": "टीम", + "infoSectionTitle": "जानकारी", + "advancedSectionTitle": "एडवांसड", + "logsSectionTitle": "लॉग्स", + "darkThemeLabel": "Dark mode", + "darkThemeHint": "Welcome to the dark side", + "dynamicThemeLabel": "मेटीरियल यू", + "dynamicThemeHint": "अपने डिवाइस के करीब एक अनुभव का आनंद लें", + "languageLabel": "भाषा", + "englishOption": "अंग्रेज़ी", + "frenchOption": "फ्रेंच", + "sourcesLabel": "स्रोत", + "sourcesLabelHint": "अपने कस्टम साधन कॉन्फ़िगर करे", + "orgPatchesLabel": "Patches organization", + "sourcesPatchesLabel": "Patches source", + "orgIntegrationsLabel": "Integrations organization", + "sourcesIntegrationsLabel": "Integrations source", + "sourcesResetDialogTitle": "रीसेट करें", + "sourcesResetDialogText": "क्या आप वाकई कस्टम साधन को डिफ़ॉल्ट वैल्यू पर रीसेट करना चाहते हैं?", + "apiURLResetDialogText": "क्या आप वाकई कस्टम API URL को डिफ़ॉल्ट वैल्यू पर रीसेट करना चाहते हैं?", + "contributorsLabel": "योगदानकर्ता", + "contributorsHint": "ReVanced के योगदानकर्ताओ की सूची", + "logsLabel": "लॉग्स", + "logsHint": "Share Manager's logs", + "apiURLLabel": "API URL", + "apiURLHint": "अपनी कस्टम API URL कॉन्फ़िगर करे", + "selectApiURL": "API URL", + "experimentalPatchesLabel": "Experimental patches support", + "experimentalPatchesHint": "Enable usage of unsupported patches in any app version", + "enabledExperimentalPatches": "Experimental patches support enabled", + "exportSectionTitle": "Import & export", + "aboutLabel": "विवरण", + "snackbarMessage": "क्लिपबोर्ड में कॉपी हो गया है", + "sentryLabel": "Sentry logging", + "sentryHint": "उन्नत प्रबंधक को बेहतर बनाने में हमारी सहायता करने के लिए अनाम लॉग भेजें", + "restartAppForChanges": "Restart the app to apply changes", + "deleteKeystoreLabel": "कीस्टोर मिटाएं", + "deleteKeystoreHint": "ऐप पर हस्ताक्षर करने के लिए उपयोग की जाने वाली कीस्टोर हटाएं", + "deletedKeystore": "कीस्टोर हटा दिया गया", + "deleteTempDirLabel": "Delete temporary files", + "deleteTempDirHint": "Delete the unused temporary files", + "deletedTempDir": "Temporary files deleted", + "exportPatchesLabel": "Export patches selection", + "exportPatchesHint": "Export patches selection to a JSON file", + "exportedPatches": "Patches selection exported", + "noExportFileFound": "No patches selection to export", + "importPatchesLabel": "Import patches selection", + "importPatchesHint": "Import patches selection from a JSON file", + "importedPatches": "Patches selection imported", + "resetStoredPatchesLabel": "Reset patches", + "resetStoredPatchesHint": "Reset the stored patches selection", + "resetStoredPatches": "Patches selection has been reset", + "jsonSelectorErrorMessage": "Unable to use selected JSON file", + "deleteLogsLabel": "लॉग हटाएं", + "deleteLogsHint": "एकत्रित प्रबंधक लॉग हटाएं", + "deletedLogs": "लॉग हटा दिए गए" + }, + "appInfoView": { + "widgetTitle": "App info", + "openButton": "खोलें", + "uninstallButton": "अनइंस्टॉल करें", + "patchButton": "पैच", + "unpatchButton": "अनपैच करे", + "unpatchDialogText": "क्या वाकई आप इस एप को अनपैच करना चाहते हैं?", + "rootDialogTitle": "त्रुटि", + "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": "रूट", + "nonRootTypeLabel": "नॉन-रूट", + "patchedDateLabel": "Patched date", + "patchedDateHint": "{date} {time} पर", + "appliedPatchesLabel": "Applied patches", + "appliedPatchesHint": "{quantity} लागू किए हुआ पैच", + "updateNotImplemented": "This feature has not been implemented yet" + }, + "contributorsView": { + "widgetTitle": "योगदानकर्ता", + "patcherContributors": "Patcher contributors", + "patchesContributors": "Patches contributors", + "integrationsContributors": "Integrations contributors", + "cliContributors": "CLI contributors", + "managerContributors": "Manager contributors" + } +} diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart index ab0e2154..c7631823 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -86,13 +86,13 @@ class ManagerAPI { await _prefs.setBool('useDarkTheme', value); } - // bool isSentryEnabled() { - // return _prefs.getBool('sentryEnabled') ?? true; - // } + bool isSentryEnabled() { + return _prefs.getBool('sentryEnabled') ?? true; + } - // Future setSentryStatus(bool value) async { - // await _prefs.setBool('sentryEnabled', value); - // } + Future setSentryStatus(bool value) async { + await _prefs.setBool('sentryEnabled', value); + } bool areExperimentalPatchesEnabled() { return _prefs.getBool('experimentalPatchesEnabled') ?? false; diff --git a/lib/ui/views/app_selector/app_selector_viewmodel.dart b/lib/ui/views/app_selector/app_selector_viewmodel.dart index 2b4c0286..6802a0ef 100644 --- a/lib/ui/views/app_selector/app_selector_viewmodel.dart +++ b/lib/ui/views/app_selector/app_selector_viewmodel.dart @@ -49,8 +49,12 @@ class AppSelectorViewModel extends BaseViewModel { List pathSplit = result.files.single.path!.split("/"); pathSplit.removeLast(); Directory filePickerCacheDir = Directory(pathSplit.join("/")); - Iterable deletableFiles = (await filePickerCacheDir.list().toList()).whereType(); - for (var file in deletableFiles) { if (file.path != apkFile.path && file.path.endsWith(".apk")) file.delete(); } + Iterable deletableFiles = + (await filePickerCacheDir.list().toList()).whereType(); + for (var file in deletableFiles) { + if (file.path != apkFile.path && file.path.endsWith(".apk")) + file.delete(); + } ApplicationWithIcon? application = await DeviceApps.getAppFromStorage( apkFile.path, true, @@ -72,7 +76,7 @@ class AppSelectorViewModel extends BaseViewModel { } } on Exception catch (e, s) { await Sentry.captureException(e, stackTrace: s); - _toast.show('appSelectorView.errorMessage'); + _toast.showBottom('appSelectorView.errorMessage'); } } diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index a23aef03..764a03ec 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -47,7 +47,7 @@ class HomeViewModel extends BaseViewModel { ?.requestPermission(); bool isConnected = await Connectivity().checkConnection(); if (!isConnected) { - _toast.show('homeView.noConnection'); + _toast.showBottom('homeView.noConnection'); } _getPatchedApps(); _managerAPI.reAssessSavedApps().then((_) => _getPatchedApps()); @@ -105,7 +105,7 @@ class HomeViewModel extends BaseViewModel { Future updateManager(BuildContext context) async { try { - _toast.show('homeView.downloadingMessage'); + _toast.showBottom('homeView.downloadingMessage'); File? managerApk = await _managerAPI.downloadManager(); if (managerApk != null) { await flutterLocalNotificationsPlugin.zonedSchedule( @@ -132,19 +132,19 @@ class HomeViewModel extends BaseViewModel { uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, ); - _toast.show('homeView.installingMessage'); + _toast.showBottom('homeView.installingMessage'); await AppInstaller.installApk(managerApk.path); } else { - _toast.show('homeView.errorDownloadMessage'); + _toast.showBottom('homeView.errorDownloadMessage'); } } on Exception catch (e, s) { await Sentry.captureException(e, stackTrace: s); - _toast.show('homeView.errorInstallMessage'); + _toast.showBottom('homeView.errorInstallMessage'); } } void updatesAreDisabled() { - _toast.show('homeView.updatesDisabled'); + _toast.showBottom('homeView.updatesDisabled'); } Future showUpdateConfirmationDialog(BuildContext parentContext) async { diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 50fa006a..dca72755 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -268,7 +268,7 @@ class InstallerViewModel extends BaseViewModel { Future onWillPop(BuildContext context) async { if (isPatching) { - _toast.show('installerView.noExit'); + _toast.showBottom('installerView.noExit'); return false; } cleanPatcher(); diff --git a/lib/ui/views/patcher/patcher_viewmodel.dart b/lib/ui/views/patcher/patcher_viewmodel.dart index f2d7e60e..ee6e3a2d 100644 --- a/lib/ui/views/patcher/patcher_viewmodel.dart +++ b/lib/ui/views/patcher/patcher_viewmodel.dart @@ -58,7 +58,7 @@ class PatcherViewModel extends BaseViewModel { return showDialog( context: context, builder: (context) => AlertDialog( - title: I18nText('patcherView.patchDialogTitle'), + title: I18nText('warning'), backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText('patcherView.patchDialogText'), actions: [ @@ -114,7 +114,8 @@ class PatcherViewModel extends BaseViewModel { await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName); List patches = await _patcherAPI.getFilteredPatches(selectedApp!.originalPackageName); - this.selectedPatches + this + .selectedPatches .addAll(patches.where((patch) => selectedPatches.contains(patch.name))); notifyListeners(); } diff --git a/lib/ui/views/patches_selector/patches_selector_view.dart b/lib/ui/views/patches_selector/patches_selector_view.dart index 3338da9f..0e824405 100644 --- a/lib/ui/views/patches_selector/patches_selector_view.dart +++ b/lib/ui/views/patches_selector/patches_selector_view.dart @@ -80,9 +80,7 @@ class _PatchesSelectorViewState extends State { ), ), CustomPopupMenu( - onSelected: (value) => { - model.onMenuSelection(value) - }, + onSelected: (value) => {model.onMenuSelection(value)}, children: { 0: I18nText( 'patchesSelectorView.loadPatchesSelection', @@ -158,85 +156,6 @@ class _PatchesSelectorViewState extends State { onChanged: (value) => model.selectPatch(patch, value), ), - /* TODO: Enable this and make use of new Patch Options implementation - patch.hasOptions ? ExpandablePanel( - controller: expController, - theme: const ExpandableThemeData( - hasIcon: false, - tapBodyToExpand: true, - tapBodyToCollapse: true, - tapHeaderToExpand: true, - ), - header: Column( - children: [ - GestureDetector( - onLongPress: () => - expController.toggle(), - child: PatchItem( - name: patch.name, - simpleName: patch.getSimpleName(), - description: patch.description, - version: patch.version, - packageVersion: - model.getAppVersion(), - supportedPackageVersions: model - .getSupportedVersions(patch), - isUnsupported: !model - .isPatchSupported(patch), - isSelected: - model.isSelected(patch), - onChanged: (value) => model - .selectPatch(patch, value), - child: const Padding( - padding: EdgeInsets.symmetric( - vertical: 8.0, - ), - child: Text( - 'Long press for additional options.', - ), - ), - ), - ), - ], - ), - expanded: Padding( - padding: const EdgeInsets.symmetric( - vertical: 10.0, - horizontal: 10, - ), - child: Container( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 8, - ), - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .tertiary - .withOpacity(0.1), - borderRadius: - BorderRadius.circular(12), - ), - child: Column( - children: [ - Text( - 'Patch options', - style: GoogleFonts.inter( - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - const OptionsTextField( - hint: 'App name'), - const OptionsFilePicker( - optionName: 'Choose a logo', - ), - ], - ), - ), - ), - collapsed: Container(), - ) */ ) .toList(), ), diff --git a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart index 90836f2f..e872aea6 100644 --- a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart +++ b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart @@ -25,6 +25,7 @@ class PatchesSelectorViewModel extends BaseViewModel { locator().selectedApp!.originalPackageName, )); patches.sort((a, b) => a.name.compareTo(b.name)); + selectRecommendedPatches(); notifyListeners(); } @@ -47,7 +48,7 @@ class PatchesSelectorViewModel extends BaseViewModel { return showDialog( context: context, builder: (context) => AlertDialog( - title: I18nText('patchesSelectorView.selectAllPatchesWarningTitle'), + title: I18nText('warning'), backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText('patchesSelectorView.selectAllPatchesWarningContent'), actions: [ @@ -75,6 +76,22 @@ class PatchesSelectorViewModel extends BaseViewModel { notifyListeners(); } + void selectRecommendedPatches() { + selectedPatches.clear(); + + if (_managerAPI.areExperimentalPatchesEnabled() == false) { + selectedPatches.addAll(patches.where( + (element) => element.excluded == false && isPatchSupported(element))); + } + + if (_managerAPI.areExperimentalPatchesEnabled()) { + selectedPatches + .addAll(patches.where((element) => element.excluded == false)); + } + + notifyListeners(); + } + void selectPatches() { locator().selectedPatches = selectedPatches; saveSelectedPatches(); diff --git a/lib/ui/views/settings/settingsFragement/settings_manage_api_url.dart b/lib/ui/views/settings/settingsFragement/settings_manage_api_url.dart new file mode 100644 index 00000000..756432e3 --- /dev/null +++ b/lib/ui/views/settings/settingsFragement/settings_manage_api_url.dart @@ -0,0 +1,117 @@ +// 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 SManageApiUrl extends BaseViewModel { + final ManagerAPI _managerAPI = locator(); + + final TextEditingController _apiUrlController = TextEditingController(); + + Future showApiUrlDialog(BuildContext context) async { + String apiUrl = _managerAPI.getApiUrl(); + _apiUrlController.text = apiUrl.replaceAll('https://', ''); + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: Row( + children: [ + I18nText('settingsView.apiURLLabel'), + const Spacer(), + IconButton( + icon: const Icon(Icons.manage_history_outlined), + onPressed: () => showApiUrlResetDialog(context), + color: Theme.of(context).colorScheme.secondary, + ) + ], + ), + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + content: SingleChildScrollView( + child: Column( + children: [ + CustomTextField( + leadingIcon: Icon( + Icons.api_outlined, + color: Theme.of(context).colorScheme.secondary, + ), + inputController: _apiUrlController, + label: I18nText('settingsView.selectApiURL'), + hint: apiUrl.split('/')[0], + onChanged: (value) => notifyListeners(), + ), + ], + ), + ), + actions: [ + CustomMaterialButton( + isFilled: false, + label: I18nText('cancelButton'), + onPressed: () { + _apiUrlController.clear(); + Navigator.of(context).pop(); + }, + ), + CustomMaterialButton( + label: I18nText('okButton'), + onPressed: () { + String apiUrl = _apiUrlController.text; + if (!apiUrl.startsWith('https')) { + apiUrl = 'https://$apiUrl'; + } + _managerAPI.setApiUrl(apiUrl); + Navigator.of(context).pop(); + }, + ) + ], + ), + ); + } + + Future showApiUrlResetDialog(BuildContext context) async { + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: I18nText('settingsView.sourcesResetDialogTitle'), + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + content: I18nText('settingsView.apiURLResetDialogText'), + actions: [ + CustomMaterialButton( + isFilled: false, + label: I18nText('noButton'), + onPressed: () => Navigator.of(context).pop(), + ), + CustomMaterialButton( + label: I18nText('yesButton'), + onPressed: () { + _managerAPI.setApiUrl(''); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }, + ) + ], + ), + ); + } +} + +final sManageApiUrl = SManageApiUrl(); + +class SManageApiUrlUI extends StatelessWidget { + const SManageApiUrlUI({super.key}); + + @override + Widget build(BuildContext context) { + return SettingsTileDialog( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + title: 'settingsView.apiURLLabel', + subtitle: 'settingsView.apiURLHint', + onTap: () => sManageApiUrl.showApiUrlDialog(context), + ); + } +} diff --git a/lib/ui/views/settings/settingsFragement/settings_manage_sources.dart b/lib/ui/views/settings/settingsFragement/settings_manage_sources.dart new file mode 100644 index 00000000..81d197c2 --- /dev/null +++ b/lib/ui/views/settings/settingsFragement/settings_manage_sources.dart @@ -0,0 +1,162 @@ +// 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 SManageSources extends BaseViewModel { + final ManagerAPI _managerAPI = locator(); + + final TextEditingController _orgPatSourceController = TextEditingController(); + final TextEditingController _patSourceController = TextEditingController(); + final TextEditingController _orgIntSourceController = TextEditingController(); + final TextEditingController _intSourceController = TextEditingController(); + + Future showSourcesDialog(BuildContext context) async { + String patchesRepo = _managerAPI.getPatchesRepo(); + String integrationsRepo = _managerAPI.getIntegrationsRepo(); + _orgPatSourceController.text = patchesRepo.split('/')[0]; + _patSourceController.text = patchesRepo.split('/')[1]; + _orgIntSourceController.text = integrationsRepo.split('/')[0]; + _intSourceController.text = integrationsRepo.split('/')[1]; + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: Row( + children: [ + I18nText('settingsView.sourcesLabel'), + const Spacer(), + IconButton( + icon: const Icon(Icons.manage_history_outlined), + onPressed: () => showResetConfirmationDialog(context), + color: Theme.of(context).colorScheme.secondary, + ) + ], + ), + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + content: SingleChildScrollView( + child: Column( + children: [ + CustomTextField( + leadingIcon: Icon( + Icons.extension_outlined, + color: Theme.of(context).colorScheme.secondary, + ), + inputController: _orgPatSourceController, + label: I18nText('settingsView.orgPatchesLabel'), + hint: patchesRepo.split('/')[0], + onChanged: (value) => notifyListeners(), + ), + const SizedBox(height: 8), + CustomTextField( + leadingIcon: const Icon( + Icons.extension_outlined, + color: Colors.transparent, + ), + inputController: _patSourceController, + label: I18nText('settingsView.sourcesPatchesLabel'), + hint: patchesRepo.split('/')[1], + onChanged: (value) => notifyListeners(), + ), + const SizedBox(height: 20), + CustomTextField( + leadingIcon: Icon( + Icons.merge_outlined, + color: Theme.of(context).colorScheme.secondary, + ), + inputController: _orgIntSourceController, + label: I18nText('settingsView.orgIntegrationsLabel'), + hint: integrationsRepo.split('/')[0], + onChanged: (value) => notifyListeners(), + ), + const SizedBox(height: 8), + CustomTextField( + leadingIcon: const Icon( + Icons.merge_outlined, + color: Colors.transparent, + ), + inputController: _intSourceController, + label: I18nText('settingsView.sourcesIntegrationsLabel'), + hint: integrationsRepo.split('/')[1], + onChanged: (value) => notifyListeners(), + ), + ], + ), + ), + actions: [ + CustomMaterialButton( + isFilled: false, + label: I18nText('cancelButton'), + onPressed: () { + _orgPatSourceController.clear(); + _patSourceController.clear(); + _orgIntSourceController.clear(); + _intSourceController.clear(); + Navigator.of(context).pop(); + }, + ), + CustomMaterialButton( + label: I18nText('okButton'), + onPressed: () { + _managerAPI.setPatchesRepo( + '${_orgPatSourceController.text}/${_patSourceController.text}', + ); + _managerAPI.setIntegrationsRepo( + '${_orgIntSourceController.text}/${_intSourceController.text}', + ); + Navigator.of(context).pop(); + }, + ) + ], + ), + ); + } + + Future showResetConfirmationDialog(BuildContext context) async { + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: I18nText('settingsView.sourcesResetDialogTitle'), + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + content: I18nText('settingsView.sourcesResetDialogText'), + actions: [ + CustomMaterialButton( + isFilled: false, + label: I18nText('noButton'), + onPressed: () => Navigator.of(context).pop(), + ), + CustomMaterialButton( + label: I18nText('yesButton'), + onPressed: () { + _managerAPI.setPatchesRepo(''); + _managerAPI.setIntegrationsRepo(''); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }, + ) + ], + ), + ); + } +} + +final sManageSources = SManageSources(); + +class SManageSourcesUI extends StatelessWidget { + const SManageSourcesUI({super.key}); + + @override + Widget build(BuildContext context) { + return SettingsTileDialog( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + title: 'settingsView.sourcesLabel', + subtitle: 'settingsView.sourcesLabelHint', + onTap: () => sManageSources.showSourcesDialog(context), + ); + } +} diff --git a/lib/ui/views/settings/settingsFragement/settings_update_language.dart b/lib/ui/views/settings/settingsFragement/settings_update_language.dart new file mode 100644 index 00000000..833d4a87 --- /dev/null +++ b/lib/ui/views/settings/settingsFragement/settings_update_language.dart @@ -0,0 +1,98 @@ +// 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/main.dart'; +import 'package:revanced_manager/services/crowdin_api.dart'; +import 'package:revanced_manager/services/toast.dart'; +import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart'; +import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart'; +import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:stacked/stacked.dart'; +import 'package:timeago/timeago.dart' as timeago; + +final _settingViewModel = SettingsViewModel(); + +class SUpdateLanguage extends BaseViewModel { + final CrowdinAPI _crowdinAPI = locator(); + final Toast _toast = locator(); + late SharedPreferences _prefs; + String selectedLanguage = 'English'; + String selectedLanguageLocale = prefs.getString('language') ?? 'en_US'; + List languages = []; + + Future initialize() async { + _prefs = await SharedPreferences.getInstance(); + selectedLanguageLocale = + _prefs.getString('language') ?? selectedLanguageLocale; + notifyListeners(); + } + + Future updateLanguage(BuildContext context, String? value) async { + if (value != null) { + selectedLanguageLocale = value; + _prefs = await SharedPreferences.getInstance(); + await _prefs.setString('language', value); + await FlutterI18n.refresh(context, Locale(value)); + timeago.setLocaleMessages(value, timeago.EnMessages()); + locator().notifyListeners(); + notifyListeners(); + } + } + + Future initLang() async { + languages = await _crowdinAPI.getLanguages(); + languages.sort((a, b) => a['name'].compareTo(b['name'])); + notifyListeners(); + } + + Future showLanguagesDialog(BuildContext parentContext) { + initLang(); + return showDialog( + context: parentContext, + builder: (context) => SimpleDialog( + title: I18nText('settingsView.languageLabel'), + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + children: [ + SizedBox( + height: 500, + child: ListView.builder( + itemCount: languages.length, + itemBuilder: (context, index) { + return RadioListTile( + title: Text(languages[index]['name']), + subtitle: Text(languages[index]['locale']), + value: languages[index]['locale'], + groupValue: selectedLanguageLocale, + onChanged: (value) { + selectedLanguage = languages[index]['name']; + _toast.showBottom('settingsView.restartAppForChanges'); + updateLanguage(context, value); + Navigator.pop(context); + }, + ); + }, + ), + ), + ], + ), + ); + } +} + +class SUpdateLanguageUI extends StatelessWidget { + const SUpdateLanguageUI({super.key}); + + @override + Widget build(BuildContext context) { + return SettingsTileDialog( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + title: 'settingsView.languageLabel', + subtitle: _settingViewModel.sUpdateLanguage.selectedLanguage, + onTap: () => + _settingViewModel.sUpdateLanguage.showLanguagesDialog(context), + ); + } +} diff --git a/lib/ui/views/settings/settingsFragement/settings_update_theme.dart b/lib/ui/views/settings/settingsFragement/settings_update_theme.dart new file mode 100644 index 00000000..822af1c1 --- /dev/null +++ b/lib/ui/views/settings/settingsFragement/settings_update_theme.dart @@ -0,0 +1,116 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:dynamic_themes/dynamic_themes.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_i18n/widgets/I18nText.dart'; +import 'package:revanced_manager/app/app.locator.dart'; +import 'package:revanced_manager/services/manager_api.dart'; +import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart'; +import 'package:revanced_manager/ui/widgets/settingsView/custom_switch_tile.dart'; +import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; +import 'package:stacked/stacked.dart'; + +final _settingViewModel = SettingsViewModel(); + +// ignore: constant_identifier_names +const int ANDROID_12_SDK_VERSION = 31; + +class SUpdateTheme extends BaseViewModel { + final ManagerAPI _managerAPI = locator(); + + bool getDynamicThemeStatus() { + return _managerAPI.getUseDynamicTheme(); + } + + void setUseDynamicTheme(BuildContext context, bool value) async { + await _managerAPI.setUseDynamicTheme(value); + int currentTheme = DynamicTheme.of(context)!.themeId; + if (currentTheme.isEven) { + await DynamicTheme.of(context)!.setTheme(value ? 2 : 0); + } else { + await DynamicTheme.of(context)!.setTheme(value ? 3 : 1); + } + notifyListeners(); + } + + bool getDarkThemeStatus() { + return _managerAPI.getUseDarkTheme(); + } + + void setUseDarkTheme(BuildContext context, bool value) async { + await _managerAPI.setUseDarkTheme(value); + int currentTheme = DynamicTheme.of(context)!.themeId; + if (currentTheme < 2) { + await DynamicTheme.of(context)!.setTheme(value ? 1 : 0); + } else { + await DynamicTheme.of(context)!.setTheme(value ? 3 : 2); + } + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + systemNavigationBarIconBrightness: + value ? Brightness.light : Brightness.dark, + ), + ); + notifyListeners(); + } +} + +class SUpdateThemeUI extends StatelessWidget { + const SUpdateThemeUI({super.key}); + + @override + Widget build(BuildContext context) { + return SettingsSection( + title: 'settingsView.appearanceSectionTitle', + children: [ + CustomSwitchTile( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.darkThemeLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.darkThemeHint'), + value: SUpdateTheme().getDarkThemeStatus(), + onTap: (value) => SUpdateTheme().setUseDarkTheme( + context, + value, + ), + ), + FutureBuilder( + future: _settingViewModel.getSdkVersion(), + builder: (context, snapshot) => Visibility( + visible: + snapshot.hasData && snapshot.data! >= ANDROID_12_SDK_VERSION, + child: CustomSwitchTile( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.dynamicThemeLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.dynamicThemeHint'), + value: _settingViewModel.sUpdateTheme.getDynamicThemeStatus(), + onTap: (value) => + _settingViewModel.sUpdateTheme.setUseDynamicTheme( + context, + value, + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/ui/views/settings/settings_view.dart b/lib/ui/views/settings/settings_view.dart index 702de165..91cfa996 100644 --- a/lib/ui/views/settings/settings_view.dart +++ b/lib/ui/views/settings/settings_view.dart @@ -1,12 +1,15 @@ +// ignore_for_file: prefer_const_constructors + import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_language.dart'; +import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_theme.dart'; import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart'; -import 'package:revanced_manager/ui/widgets/settingsView/about_widget.dart'; -import 'package:revanced_manager/ui/widgets/settingsView/custom_switch_tile.dart'; -import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart'; -import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; -import 'package:revanced_manager/ui/widgets/settingsView/social_media_widget.dart'; +import 'package:revanced_manager/ui/widgets/settingsView/settings_advanced_section.dart'; +import 'package:revanced_manager/ui/widgets/settingsView/settings_export_section.dart'; +import 'package:revanced_manager/ui/widgets/settingsView/settings_info_section.dart'; +import 'package:revanced_manager/ui/widgets/settingsView/settings_team_section.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart'; import 'package:stacked/stacked.dart'; @@ -38,273 +41,18 @@ class SettingsView extends StatelessWidget { SliverList( delegate: SliverChildListDelegate.fixed( [ - SettingsSection( - title: 'settingsView.appearanceSectionTitle', - children: [ - CustomSwitchTile( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - title: I18nText( - 'settingsView.darkThemeLabel', - child: const Text( - '', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - ), - subtitle: I18nText('settingsView.darkThemeHint'), - value: model.getDarkThemeStatus(), - onTap: (value) => model.setUseDarkTheme( - context, - value, - ), - ), - FutureBuilder( - future: model.getSdkVersion(), - builder: (context, snapshot) => Visibility( - visible: snapshot.hasData && - snapshot.data! >= ANDROID_12_SDK_VERSION, - child: CustomSwitchTile( - padding: - const EdgeInsets.symmetric(horizontal: 20.0), - title: I18nText( - 'settingsView.dynamicThemeLabel', - child: const Text( - '', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - ), - subtitle: I18nText('settingsView.dynamicThemeHint'), - value: model.getDynamicThemeStatus(), - onTap: (value) => model.setUseDynamicTheme( - context, - value, - ), - ), - ), - ), - ], - ), - SettingsTileDialog( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - title: 'settingsView.languageLabel', - subtitle: model.selectedLanguage, - onTap: () => model.showLanguagesDialog(context), - ), + SUpdateThemeUI(), + SUpdateLanguageUI(), _settingsDivider, - SettingsSection( - title: 'settingsView.teamSectionTitle', - children: [ - ListTile( - contentPadding: - const EdgeInsets.symmetric(horizontal: 20.0), - title: I18nText( - 'settingsView.contributorsLabel', - child: const Text( - '', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - ), - subtitle: I18nText('settingsView.contributorsHint'), - onTap: () => model.navigateToContributors(), - ), - const SocialMediaWidget( - padding: EdgeInsets.symmetric(horizontal: 20.0), - ), - ], - ), + STeamSection(), _settingsDivider, - SettingsSection( - title: 'settingsView.advancedSectionTitle', - children: [ - SettingsTileDialog( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - title: 'settingsView.apiURLLabel', - subtitle: 'settingsView.apiURLHint', - onTap: () => model.showApiUrlDialog(context), - ), - SettingsTileDialog( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - title: 'settingsView.sourcesLabel', - subtitle: 'settingsView.sourcesLabelHint', - onTap: () => model.showSourcesDialog(context), - ), - CustomSwitchTile( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - title: I18nText( - 'settingsView.experimentalPatchesLabel', - child: const Text( - '', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - ), - subtitle: - I18nText('settingsView.experimentalPatchesHint'), - value: model.areExperimentalPatchesEnabled(), - onTap: (value) => model.useExperimentalPatches(value), - ), - ListTile( - contentPadding: - const EdgeInsets.symmetric(horizontal: 20.0), - title: I18nText( - 'settingsView.deleteKeystoreLabel', - child: const Text( - '', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - ), - subtitle: I18nText('settingsView.deleteKeystoreHint'), - onTap: () => model.deleteKeystore, - ), - ListTile( - contentPadding: - const EdgeInsets.symmetric(horizontal: 20.0), - title: I18nText( - 'settingsView.deleteTempDirLabel', - child: const Text( - '', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - ), - subtitle: I18nText('settingsView.deleteTempDirHint'), - onTap: () => model.deleteTempDir(), - ), - ListTile( - contentPadding: - const EdgeInsets.symmetric(horizontal: 20.0), - title: I18nText( - 'settingsView.deleteLogsLabel', - child: const Text( - '', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - ), - subtitle: I18nText('settingsView.deleteLogsHint'), - onTap: () => model.deleteLogs(), - ), - ], - ), + SAdvancedSection(), _settingsDivider, - SettingsSection( - title: 'settingsView.exportSectionTitle', - children: [ - ListTile( - contentPadding: - const EdgeInsets.symmetric(horizontal: 20.0), - title: I18nText( - 'settingsView.exportPatchesLabel', - child: const Text( - '', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - ), - subtitle: I18nText('settingsView.exportPatchesHint'), - onTap: () => model.exportPatches(), - ), - ListTile( - contentPadding: - const EdgeInsets.symmetric(horizontal: 20.0), - title: I18nText( - 'settingsView.importPatchesLabel', - child: const Text( - '', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - ), - subtitle: I18nText('settingsView.importPatchesHint'), - onTap: () => model.importPatches(), - ), - ListTile( - contentPadding: - const EdgeInsets.symmetric(horizontal: 20.0), - title: I18nText( - 'settingsView.resetStoredPatchesLabel', - child: const Text( - '', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - ), - subtitle: - I18nText('settingsView.resetStoredPatchesHint'), - onTap: () => model.resetSelectedPatches(), - ), - ], - ), + SExportSection(), _settingsDivider, - // SettingsSection( - // title: 'settingsView.logsSectionTitle', - // children: [ - // CustomSwitchTile( - // padding: const EdgeInsets.symmetric(horizontal: 20.0), - // title: I18nText( - // 'settingsView.sentryLabel', - // child: const Text( - // '', - // style: TextStyle( - // fontSize: 20, - // fontWeight: FontWeight.w500, - // ), - // ), - // ), - // subtitle: I18nText('settingsView.sentryHint'), - // value: model.isSentryEnabled(), - // onTap: (value) => model.useSentry(value), - // ), - // ], - // ), + // SLoggingSection(), // _settingsDivider, - SettingsSection( - title: 'settingsView.infoSectionTitle', - children: [ - ListTile( - contentPadding: - const EdgeInsets.symmetric(horizontal: 20.0), - title: I18nText( - 'settingsView.logsLabel', - child: const Text( - '', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - ), - subtitle: I18nText('settingsView.logsHint'), - onTap: () => model.exportLogcatLogs(), - ), - const AboutWidget( - padding: EdgeInsets.symmetric(horizontal: 20.0), - ), - ], - ), + SInfoSection(), ], ), ), diff --git a/lib/ui/views/settings/settings_viewmodel.dart b/lib/ui/views/settings/settings_viewmodel.dart index 33f35442..453d2804 100644 --- a/lib/ui/views/settings/settings_viewmodel.dart +++ b/lib/ui/views/settings/settings_viewmodel.dart @@ -1,376 +1,49 @@ -// ignore_for_file: use_build_context_synchronously import 'dart:io'; import 'package:cr_file_saver/file_saver.dart'; import 'package:device_info_plus/device_info_plus.dart'; -import 'package:dynamic_themes/dynamic_themes.dart'; import 'package:file_picker/file_picker.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:logcat/logcat.dart'; import 'package:path_provider/path_provider.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.router.dart'; -import 'package:revanced_manager/main.dart'; -import 'package:revanced_manager/services/crowdin_api.dart'; import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/toast.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/ui/widgets/settingsView/custom_text_field.dart'; +import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_language.dart'; +import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_theme.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:share_extend/share_extend.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; -import 'package:timeago/timeago.dart' as timeago; -import 'package:shared_preferences/shared_preferences.dart'; - -// ignore: constant_identifier_names -const int ANDROID_12_SDK_VERSION = 31; class SettingsViewModel extends BaseViewModel { final NavigationService _navigationService = locator(); final ManagerAPI _managerAPI = locator(); - final CrowdinAPI _crowdinAPI = locator(); final Toast _toast = locator(); - final TextEditingController _orgPatSourceController = TextEditingController(); - final TextEditingController _patSourceController = TextEditingController(); - final TextEditingController _orgIntSourceController = TextEditingController(); - final TextEditingController _intSourceController = TextEditingController(); - final TextEditingController _apiUrlController = TextEditingController(); - late SharedPreferences _prefs; - String selectedLanguage = 'English'; - String selectedLanguageLocale = prefs.getString('language') ?? 'en_US'; - List languages = []; - Future initLang() async { - languages = await _crowdinAPI.getLanguages(); - languages.sort((a, b) => a['name'].compareTo(b['name'])); - notifyListeners(); - } - - Future initialize() async { - _prefs = await SharedPreferences.getInstance(); - selectedLanguageLocale = - _prefs.getString('language') ?? selectedLanguageLocale; - notifyListeners(); - } + final SUpdateLanguage sUpdateLanguage = SUpdateLanguage(); + final SUpdateTheme sUpdateTheme = SUpdateTheme(); void navigateToContributors() { _navigationService.navigateTo(Routes.contributorsView); } - Future updateLanguage(BuildContext context, String? value) async { - if (value != null) { - selectedLanguageLocale = value; - _prefs = await SharedPreferences.getInstance(); - await _prefs.setString('language', value); - await FlutterI18n.refresh(context, Locale(value)); - timeago.setLocaleMessages(value, timeago.EnMessages()); - locator().notifyListeners(); - notifyListeners(); - } + bool isSentryEnabled() { + return _managerAPI.isSentryEnabled(); } - bool getDynamicThemeStatus() { - return _managerAPI.getUseDynamicTheme(); - } - - void setUseDynamicTheme(BuildContext context, bool value) async { - await _managerAPI.setUseDynamicTheme(value); - int currentTheme = DynamicTheme.of(context)!.themeId; - if (currentTheme.isEven) { - await DynamicTheme.of(context)!.setTheme(value ? 2 : 0); - } else { - await DynamicTheme.of(context)!.setTheme(value ? 3 : 1); - } + void useSentry(bool value) { + _managerAPI.setSentryStatus(value); + _toast.showBottom('settingsView.restartAppForChanges'); notifyListeners(); } - bool getDarkThemeStatus() { - return _managerAPI.getUseDarkTheme(); - } - - void setUseDarkTheme(BuildContext context, bool value) async { - await _managerAPI.setUseDarkTheme(value); - int currentTheme = DynamicTheme.of(context)!.themeId; - if (currentTheme < 2) { - await DynamicTheme.of(context)!.setTheme(value ? 1 : 0); - } else { - await DynamicTheme.of(context)!.setTheme(value ? 3 : 2); - } - SystemChrome.setSystemUIOverlayStyle( - SystemUiOverlayStyle( - systemNavigationBarIconBrightness: - value ? Brightness.light : Brightness.dark, - ), - ); - notifyListeners(); - } - - Future showLanguagesDialog(BuildContext parentContext) { - initLang(); - return showDialog( - context: parentContext, - builder: (context) => SimpleDialog( - title: I18nText('settingsView.languageLabel'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, - children: [ - SizedBox( - height: 500, - child: ListView.builder( - itemCount: languages.length, - itemBuilder: (context, index) { - return RadioListTile( - title: Text(languages[index]['name']), - subtitle: Text(languages[index]['locale']), - value: languages[index]['locale'], - groupValue: selectedLanguageLocale, - onChanged: (value) { - selectedLanguage = languages[index]['name']; - _toast.show('settingsView.restartAppForChanges'); - updateLanguage(context, value); - Navigator.pop(context); - }, - ); - }, - ), - ), - ], - ), - ); - } - - Future showSourcesDialog(BuildContext context) async { - String patchesRepo = _managerAPI.getPatchesRepo(); - String integrationsRepo = _managerAPI.getIntegrationsRepo(); - _orgPatSourceController.text = patchesRepo.split('/')[0]; - _patSourceController.text = patchesRepo.split('/')[1]; - _orgIntSourceController.text = integrationsRepo.split('/')[0]; - _intSourceController.text = integrationsRepo.split('/')[1]; - return showDialog( - context: context, - builder: (context) => AlertDialog( - title: Row( - children: [ - I18nText('settingsView.sourcesLabel'), - const Spacer(), - IconButton( - icon: const Icon(Icons.manage_history_outlined), - onPressed: () => showResetConfirmationDialog(context), - color: Theme.of(context).colorScheme.secondary, - ) - ], - ), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, - content: SingleChildScrollView( - child: Column( - children: [ - CustomTextField( - leadingIcon: Icon( - Icons.extension_outlined, - color: Theme.of(context).colorScheme.secondary, - ), - inputController: _orgPatSourceController, - label: I18nText('settingsView.orgPatchesLabel'), - hint: patchesRepo.split('/')[0], - onChanged: (value) => notifyListeners(), - ), - const SizedBox(height: 8), - CustomTextField( - leadingIcon: const Icon( - Icons.extension_outlined, - color: Colors.transparent, - ), - inputController: _patSourceController, - label: I18nText('settingsView.sourcesPatchesLabel'), - hint: patchesRepo.split('/')[1], - onChanged: (value) => notifyListeners(), - ), - const SizedBox(height: 20), - CustomTextField( - leadingIcon: Icon( - Icons.merge_outlined, - color: Theme.of(context).colorScheme.secondary, - ), - inputController: _orgIntSourceController, - label: I18nText('settingsView.orgIntegrationsLabel'), - hint: integrationsRepo.split('/')[0], - onChanged: (value) => notifyListeners(), - ), - const SizedBox(height: 8), - CustomTextField( - leadingIcon: const Icon( - Icons.merge_outlined, - color: Colors.transparent, - ), - inputController: _intSourceController, - label: I18nText('settingsView.sourcesIntegrationsLabel'), - hint: integrationsRepo.split('/')[1], - onChanged: (value) => notifyListeners(), - ), - ], - ), - ), - actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('cancelButton'), - onPressed: () { - _orgPatSourceController.clear(); - _patSourceController.clear(); - _orgIntSourceController.clear(); - _intSourceController.clear(); - Navigator.of(context).pop(); - }, - ), - CustomMaterialButton( - label: I18nText('okButton'), - onPressed: () { - _managerAPI.setPatchesRepo( - '${_orgPatSourceController.text}/${_patSourceController.text}', - ); - _managerAPI.setIntegrationsRepo( - '${_orgIntSourceController.text}/${_intSourceController.text}', - ); - Navigator.of(context).pop(); - }, - ) - ], - ), - ); - } - - Future showApiUrlDialog(BuildContext context) async { - String apiUrl = _managerAPI.getApiUrl(); - _apiUrlController.text = apiUrl.replaceAll('https://', ''); - return showDialog( - context: context, - builder: (context) => AlertDialog( - title: Row( - children: [ - I18nText('settingsView.apiURLLabel'), - const Spacer(), - IconButton( - icon: const Icon(Icons.manage_history_outlined), - onPressed: () => showApiUrlResetDialog(context), - color: Theme.of(context).colorScheme.secondary, - ) - ], - ), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, - content: SingleChildScrollView( - child: Column( - children: [ - CustomTextField( - leadingIcon: Icon( - Icons.api_outlined, - color: Theme.of(context).colorScheme.secondary, - ), - inputController: _apiUrlController, - label: I18nText('settingsView.selectApiURL'), - hint: apiUrl.split('/')[0], - onChanged: (value) => notifyListeners(), - ), - ], - ), - ), - actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('cancelButton'), - onPressed: () { - _apiUrlController.clear(); - Navigator.of(context).pop(); - }, - ), - CustomMaterialButton( - label: I18nText('okButton'), - onPressed: () { - String apiUrl = _apiUrlController.text; - if (!apiUrl.startsWith('https')) { - apiUrl = 'https://$apiUrl'; - } - _managerAPI.setApiUrl(apiUrl); - Navigator.of(context).pop(); - }, - ) - ], - ), - ); - } - - Future showResetConfirmationDialog(BuildContext context) async { - return showDialog( - context: context, - builder: (context) => AlertDialog( - title: I18nText('settingsView.sourcesResetDialogTitle'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, - content: I18nText('settingsView.sourcesResetDialogText'), - actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('noButton'), - onPressed: () => Navigator.of(context).pop(), - ), - CustomMaterialButton( - label: I18nText('yesButton'), - onPressed: () { - _managerAPI.setPatchesRepo(''); - _managerAPI.setIntegrationsRepo(''); - Navigator.of(context).pop(); - Navigator.of(context).pop(); - }, - ) - ], - ), - ); - } - - Future showApiUrlResetDialog(BuildContext context) async { - return showDialog( - context: context, - builder: (context) => AlertDialog( - title: I18nText('settingsView.sourcesResetDialogTitle'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, - content: I18nText('settingsView.apiURLResetDialogText'), - actions: [ - CustomMaterialButton( - isFilled: false, - label: I18nText('noButton'), - onPressed: () => Navigator.of(context).pop(), - ), - CustomMaterialButton( - label: I18nText('yesButton'), - onPressed: () { - _managerAPI.setApiUrl(''); - Navigator.of(context).pop(); - Navigator.of(context).pop(); - }, - ) - ], - ), - ); - } - - // bool isSentryEnabled() { - // return _managerAPI.isSentryEnabled(); - // } - - // void useSentry(bool value) { - // _managerAPI.setSentryStatus(value); - // _toast.showBottom('settingsView.restartAppForChanges'); - // notifyListeners(); - // } - bool areExperimentalPatchesEnabled() { return _managerAPI.areExperimentalPatchesEnabled(); } void useExperimentalPatches(bool value) { _managerAPI.enableExperimentalPatchesStatus(value); - _toast.showBottom('settingsView.enabledExperimentalPatches'); notifyListeners(); } diff --git a/lib/ui/widgets/appInfoView/app_info_viewmodel.dart b/lib/ui/widgets/appInfoView/app_info_viewmodel.dart index a151eece..bb919465 100644 --- a/lib/ui/widgets/appInfoView/app_info_viewmodel.dart +++ b/lib/ui/widgets/appInfoView/app_info_viewmodel.dart @@ -54,7 +54,7 @@ class AppInfoViewModel extends BaseViewModel { } void updateNotImplemented(BuildContext context) { - _toast.show('appInfoView.updateNotImplemented'); + _toast.showBottom('appInfoView.updateNotImplemented'); } Future showUninstallDialog( diff --git a/lib/ui/widgets/patchesSelectorView/patch_item.dart b/lib/ui/widgets/patchesSelectorView/patch_item.dart index eb80d603..0cb75c15 100644 --- a/lib/ui/widgets/patchesSelectorView/patch_item.dart +++ b/lib/ui/widgets/patchesSelectorView/patch_item.dart @@ -54,8 +54,7 @@ class _PatchItemState extends State { onTap: () { setState(() { if (widget.isUnsupported && - !widget._managerAPI.areExperimentalPatchesEnabled() - ) { + !widget._managerAPI.areExperimentalPatchesEnabled()) { widget.isSelected = false; widget.toast.showBottom('patchItem.unsupportedPatchVersion'); } else { @@ -125,8 +124,8 @@ class _PatchItemState extends State { onChanged: (newValue) { setState(() { if (widget.isUnsupported && - !widget._managerAPI.areExperimentalPatchesEnabled() - ) { + !widget._managerAPI + .areExperimentalPatchesEnabled()) { widget.isSelected = false; widget.toast .showBottom('patchItem.unsupportedPatchVersion'); @@ -146,7 +145,7 @@ class _PatchItemState extends State { Padding( padding: const EdgeInsets.only(top: 8), child: TextButton.icon( - label: I18nText('patchItem.unsupportedWarningButton'), + label: I18nText('warning'), icon: const Icon(Icons.warning, size: 20.0), onPressed: () => _showUnsupportedWarningDialog(), style: ButtonStyle( @@ -183,7 +182,7 @@ class _PatchItemState extends State { return showDialog( context: context, builder: (context) => AlertDialog( - title: I18nText('patchItem.unsupportedDialogTitle'), + title: I18nText('warning'), backgroundColor: Theme.of(context).colorScheme.secondaryContainer, content: I18nText( 'patchItem.unsupportedDialogText', diff --git a/lib/ui/widgets/settingsView/settings_advanced_section.dart b/lib/ui/widgets/settingsView/settings_advanced_section.dart new file mode 100644 index 00000000..e8907a12 --- /dev/null +++ b/lib/ui/widgets/settingsView/settings_advanced_section.dart @@ -0,0 +1,72 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_i18n/widgets/I18nText.dart'; +import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_manage_api_url.dart'; +import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_manage_sources.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_section.dart'; + +final _settingsViewModel = SettingsViewModel(); + +class SAdvancedSection extends StatelessWidget { + const SAdvancedSection({super.key}); + + @override + Widget build(BuildContext context) { + return SettingsSection( + title: 'settingsView.advancedSectionTitle', + children: [ + SManageApiUrlUI(), + SManageSourcesUI(), + SExperimentalPatches(), + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.deleteKeystoreLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.deleteKeystoreHint'), + onTap: () => _settingsViewModel.deleteKeystore, + ), + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.deleteTempDirLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.deleteTempDirHint'), + onTap: () => _settingsViewModel.deleteTempDir(), + ), + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.deleteLogsLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.deleteLogsHint'), + onTap: () => _settingsViewModel.deleteLogs(), + ), + ], + ); + } +} diff --git a/lib/ui/widgets/settingsView/settings_experimental_patches.dart b/lib/ui/widgets/settingsView/settings_experimental_patches.dart new file mode 100644 index 00000000..964787a1 --- /dev/null +++ b/lib/ui/widgets/settingsView/settings_experimental_patches.dart @@ -0,0 +1,39 @@ +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/widgets/settingsView/custom_switch_tile.dart'; + +class SExperimentalPatches extends StatefulWidget { + const SExperimentalPatches({super.key}); + + @override + State createState() => _SExperimentalPatchesState(); +} + +final _settingsViewModel = SettingsViewModel(); + +class _SExperimentalPatchesState extends State { + @override + Widget build(BuildContext context) { + return CustomSwitchTile( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.experimentalPatchesLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.experimentalPatchesHint'), + value: _settingsViewModel.areExperimentalPatchesEnabled(), + onTap: (value) { + setState(() { + _settingsViewModel.useExperimentalPatches(value); + }); + }, + ); + } +} diff --git a/lib/ui/widgets/settingsView/settings_export_section.dart b/lib/ui/widgets/settingsView/settings_export_section.dart new file mode 100644 index 00000000..c13bd91a --- /dev/null +++ b/lib/ui/widgets/settingsView/settings_export_section.dart @@ -0,0 +1,64 @@ +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/widgets/settingsView/settings_section.dart'; + +final _settingsViewModel = SettingsViewModel(); + +class SExportSection extends StatelessWidget { + const SExportSection({super.key}); + + @override + Widget build(BuildContext context) { + return SettingsSection( + title: 'settingsView.exportSectionTitle', + children: [ + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.exportPatchesLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.exportPatchesHint'), + onTap: () => _settingsViewModel.exportPatches(), + ), + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.importPatchesLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.importPatchesHint'), + onTap: () => _settingsViewModel.importPatches(), + ), + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.resetStoredPatchesLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.resetStoredPatchesHint'), + onTap: () => _settingsViewModel.resetSelectedPatches(), + ), + ], + ); + } +} diff --git a/lib/ui/widgets/settingsView/settings_info_section.dart b/lib/ui/widgets/settingsView/settings_info_section.dart new file mode 100644 index 00000000..0a714581 --- /dev/null +++ b/lib/ui/widgets/settingsView/settings_info_section.dart @@ -0,0 +1,38 @@ +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/widgets/settingsView/about_widget.dart'; +import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; + +final _settingsViewModel = SettingsViewModel(); + +class SInfoSection extends StatelessWidget { + const SInfoSection({super.key}); + + @override + Widget build(BuildContext context) { + return SettingsSection( + title: 'settingsView.infoSectionTitle', + children: [ + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.logsLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.logsHint'), + onTap: () => _settingsViewModel.exportLogcatLogs(), + ), + const AboutWidget( + padding: EdgeInsets.symmetric(horizontal: 20.0), + ), + ], + ); + } +} diff --git a/lib/ui/widgets/settingsView/settings_logging_section.dart b/lib/ui/widgets/settingsView/settings_logging_section.dart new file mode 100644 index 00000000..b06b589c --- /dev/null +++ b/lib/ui/widgets/settingsView/settings_logging_section.dart @@ -0,0 +1,36 @@ +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/widgets/settingsView/custom_switch_tile.dart'; +import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; + +final _settingsViewModel = SettingsViewModel(); + +class SLoggingSection extends StatelessWidget { + const SLoggingSection({super.key}); + + @override + Widget build(BuildContext context) { + return SettingsSection( + title: 'settingsView.logsSectionTitle', + children: [ + CustomSwitchTile( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.sentryLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.sentryHint'), + value: _settingsViewModel.isSentryEnabled(), + onTap: (value) => _settingsViewModel.useSentry(value), + ), + ], + ); + } +} diff --git a/lib/ui/widgets/settingsView/settings_team_section.dart b/lib/ui/widgets/settingsView/settings_team_section.dart new file mode 100644 index 00000000..aa2d81a6 --- /dev/null +++ b/lib/ui/widgets/settingsView/settings_team_section.dart @@ -0,0 +1,38 @@ +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/widgets/settingsView/settings_section.dart'; +import 'package:revanced_manager/ui/widgets/settingsView/social_media_widget.dart'; + +final _settingsViewModel = SettingsViewModel(); + +class STeamSection extends StatelessWidget { + const STeamSection({super.key}); + + @override + Widget build(BuildContext context) { + return SettingsSection( + title: 'settingsView.teamSectionTitle', + children: [ + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.contributorsLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.contributorsHint'), + onTap: () => _settingsViewModel.navigateToContributors(), + ), + const SocialMediaWidget( + padding: EdgeInsets.symmetric(horizontal: 20.0), + ), + ], + ); + } +}