diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index e6fe85e4..9490cc91 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -137,6 +137,8 @@ "apiURLLabel": "API URL", "apiURLHint": "Configure your custom API URL", "selectApiURL": "API URL", + "experimentalUniversalPatchesLabel": "Experimental universal patches support", + "experimentalUniversalPatchesHint": "Display all applications to use with universal patches, loading list of apps may be slower", "experimentalPatchesLabel": "Experimental patches support", "experimentalPatchesHint": "Enable usage of unsupported patches in any app version", "enabledExperimentalPatches": "Experimental patches support enabled", diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart index 692c3edc..dbd75098 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -106,6 +106,14 @@ class ManagerAPI { await _prefs.setBool('sentryEnabled', value); } + bool areUniversalPatchesEnabled() { + return _prefs.getBool('universalPatchesEnabled') ?? false; + } + + Future enableUniversalPatchesStatus(bool value) async { + await _prefs.setBool('universalPatchesEnabled', value); + } + bool areExperimentalPatchesEnabled() { return _prefs.getBool('experimentalPatchesEnabled') ?? false; } diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index c0f7d70e..65b1cad2 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -24,6 +24,7 @@ class PatcherAPI { late Directory _tmpDir; late File _keyStoreFile; List _patches = []; + Map filteredPatches = >{}; File? _outFile; Future initialize() async { @@ -52,10 +53,10 @@ class PatcherAPI { } } - Future> getFilteredInstalledApps() async { + Future> getFilteredInstalledApps(bool showUniversalPatches) async { List filteredApps = []; bool? allAppsIncluded = - _patches.any((patch) => patch.compatiblePackages.isEmpty); + _patches.any((patch) => patch.compatiblePackages.isEmpty) && showUniversalPatches; if (allAppsIncluded) { var allPackages = await DeviceApps.getInstalledApplications( includeAppIcons: true, @@ -94,20 +95,18 @@ class PatcherAPI { return filteredApps; } - Future> getFilteredPatches(String packageName) async { - List filteredPatches = []; - _patches.forEach((patch) { - if (patch.compatiblePackages.isEmpty) { - filteredPatches.add(patch); - } else { - if (!patch.name.contains('settings') && - patch.compatiblePackages.any((pack) => pack.name == packageName) - ) { - filteredPatches.add(patch); - } - } - }); - return filteredPatches; + List getFilteredPatches(String packageName) { + if (!filteredPatches.keys.contains(packageName)) { + List patches = _patches + .where((patch) => + patch.compatiblePackages.isEmpty || + !patch.name.contains('settings') && + patch.compatiblePackages + .any((pack) => pack.name == packageName)) + .toList(); + filteredPatches[packageName] = patches; + } + return filteredPatches[packageName]; } Future> getAppliedPatches(List appliedPatches) async { diff --git a/lib/ui/views/app_selector/app_selector_view.dart b/lib/ui/views/app_selector/app_selector_view.dart index 9ba76d42..12440006 100644 --- a/lib/ui/views/app_selector/app_selector_view.dart +++ b/lib/ui/views/app_selector/app_selector_view.dart @@ -92,6 +92,8 @@ class _AppSelectorViewState extends State { name: app.appName, pkgName: app.packageName, icon: app.icon, + patchesCount: + model.patchesCount(app.packageName), onTap: () { model.selectApp(app); Navigator.of(context).pop(); diff --git a/lib/ui/views/app_selector/app_selector_viewmodel.dart b/lib/ui/views/app_selector/app_selector_viewmodel.dart index 6802a0ef..caaa4b31 100644 --- a/lib/ui/views/app_selector/app_selector_viewmodel.dart +++ b/lib/ui/views/app_selector/app_selector_viewmodel.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:device_apps/device_apps.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/services/patcher_api.dart'; @@ -10,16 +9,24 @@ import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:stacked/stacked.dart'; +import '../../../services/manager_api.dart'; class AppSelectorViewModel extends BaseViewModel { final PatcherAPI _patcherAPI = locator(); + final ManagerAPI _managerAPI = locator(); final Toast _toast = locator(); final List apps = []; bool noApps = false; + int patchesCount(String packageName) { + return _patcherAPI.getFilteredPatches(packageName).length; + } Future initialize() async { - apps.addAll(await _patcherAPI.getFilteredInstalledApps()); - apps.sort((a, b) => a.appName.compareTo(b.appName)); + apps.addAll(await _patcherAPI.getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled())); + apps.sort(((a, b) => _patcherAPI + .getFilteredPatches(b.packageName) + .length + .compareTo(_patcherAPI.getFilteredPatches(a.packageName).length))); noApps = apps.isEmpty; notifyListeners(); } diff --git a/lib/ui/views/patcher/patcher_viewmodel.dart b/lib/ui/views/patcher/patcher_viewmodel.dart index ee6e3a2d..1c16ed6c 100644 --- a/lib/ui/views/patcher/patcher_viewmodel.dart +++ b/lib/ui/views/patcher/patcher_viewmodel.dart @@ -113,7 +113,7 @@ class PatcherViewModel extends BaseViewModel { List selectedPatches = await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName); List patches = - await _patcherAPI.getFilteredPatches(selectedApp!.originalPackageName); + _patcherAPI.getFilteredPatches(selectedApp!.originalPackageName); this .selectedPatches .addAll(patches.where((patch) => selectedPatches.contains(patch.name))); diff --git a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart index 16c04012..81333e95 100644 --- a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart +++ b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart @@ -26,7 +26,7 @@ class PatchesSelectorViewModel extends BaseViewModel { Future initialize() async { getPatchesVersion(); - patches.addAll(await _patcherAPI.getFilteredPatches( + patches.addAll(_patcherAPI.getFilteredPatches( locator().selectedApp!.originalPackageName, )); patches.sort((a, b) => a.name.compareTo(b.name)); diff --git a/lib/ui/views/settings/settings_viewmodel.dart b/lib/ui/views/settings/settings_viewmodel.dart index 453d2804..53aac65e 100644 --- a/lib/ui/views/settings/settings_viewmodel.dart +++ b/lib/ui/views/settings/settings_viewmodel.dart @@ -38,6 +38,15 @@ class SettingsViewModel extends BaseViewModel { notifyListeners(); } + bool areUniversalPatchesEnabled() { + return _managerAPI.areUniversalPatchesEnabled(); + } + + void showUniversalPatches(bool value) { + _managerAPI.enableUniversalPatchesStatus(value); + notifyListeners(); + } + bool areExperimentalPatchesEnabled() { return _managerAPI.areExperimentalPatchesEnabled(); } diff --git a/lib/ui/widgets/appSelectorView/installed_app_item.dart b/lib/ui/widgets/appSelectorView/installed_app_item.dart index 3ce7641d..d50820b9 100644 --- a/lib/ui/widgets/appSelectorView/installed_app_item.dart +++ b/lib/ui/widgets/appSelectorView/installed_app_item.dart @@ -6,6 +6,7 @@ class InstalledAppItem extends StatefulWidget { final String name; final String pkgName; final Uint8List icon; + final int patchesCount; final Function()? onTap; const InstalledAppItem({ @@ -13,6 +14,7 @@ class InstalledAppItem extends StatefulWidget { required this.name, required this.pkgName, required this.icon, + required this.patchesCount, this.onTap, }) : super(key: key); @@ -45,14 +47,29 @@ class _InstalledAppItemState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - widget.name, - maxLines: 2, - overflow: TextOverflow.visible, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + widget.name, + maxLines: 2, + overflow: TextOverflow.visible, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 6), + Text( + widget.patchesCount == 1 + ? "${widget.patchesCount} patch" + : "${widget.patchesCount} patches", + style: TextStyle( + fontSize: 8, + color: Theme.of(context).colorScheme.secondary, + ), + ), + ], ), const SizedBox(height: 4), Text(widget.pkgName), diff --git a/lib/ui/widgets/settingsView/settings_advanced_section.dart b/lib/ui/widgets/settingsView/settings_advanced_section.dart index e8907a12..73a4de55 100644 --- a/lib/ui/widgets/settingsView/settings_advanced_section.dart +++ b/lib/ui/widgets/settingsView/settings_advanced_section.dart @@ -6,6 +6,7 @@ import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_ma 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_experimental_universal_patches.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; final _settingsViewModel = SettingsViewModel(); @@ -20,6 +21,7 @@ class SAdvancedSection extends StatelessWidget { children: [ SManageApiUrlUI(), SManageSourcesUI(), + SExperimentalUniversalPatches(), SExperimentalPatches(), ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), diff --git a/lib/ui/widgets/settingsView/settings_experimental_universal_patches.dart b/lib/ui/widgets/settingsView/settings_experimental_universal_patches.dart new file mode 100644 index 00000000..fe255c40 --- /dev/null +++ b/lib/ui/widgets/settingsView/settings_experimental_universal_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 SExperimentalUniversalPatches extends StatefulWidget { + const SExperimentalUniversalPatches({super.key}); + + @override + State createState() => _SExperimentalUniversalPatchesState(); +} + +final _settingsViewModel = SettingsViewModel(); + +class _SExperimentalUniversalPatchesState extends State { + @override + Widget build(BuildContext context) { + return CustomSwitchTile( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.experimentalUniversalPatchesLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.experimentalUniversalPatchesHint'), + value: _settingsViewModel.areUniversalPatchesEnabled(), + onTap: (value) { + setState(() { + _settingsViewModel.showUniversalPatches(value); + }); + }, + ); + } +}