From b77d46b2c26953ef0e352d947abed04c76104350 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Thu, 10 Aug 2023 19:02:30 +0700 Subject: [PATCH 01/20] feat(installer): redesign utility options (#1062) Co-authored-by: Ushie --- assets/i18n/en_US.json | 7 +- lib/services/patcher_api.dart | 13 +- lib/ui/views/installer/installer_view.dart | 140 +++++------------- .../views/installer/installer_viewmodel.dart | 103 ++++++++++--- 4 files changed, 132 insertions(+), 131 deletions(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 2f7e1b64..fb3004c7 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -140,6 +140,8 @@ }, "installerView": { "widgetTitle": "Installer", + "installType": "Select install type", + "installTypeDescription": "Select the installation type to proceed with.", "installButton": "Install", "installRootButton": "Install as Root", "pressBackAgain": "Press back again to cancel", @@ -149,9 +151,8 @@ "notificationTitle": "ReVanced Manager is patching", "notificationText": "Tap to return to the installer", - "shareApkMenuOption": "Share APK", - "exportApkMenuOption": "Export APK", - "shareLogMenuOption": "Share log", + "exportApkButtonTooltip": "Export patched APK", + "exportLogButtonTooltip": "Export log", "installErrorDialogTitle": "Error", "installErrorDialogText1": "Root install is not possible with the current patches selection.\nRepatch your app or choose non-root install.", diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index df99db8b..c449cbc5 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -283,7 +283,7 @@ class PatcherAPI { return newName; } - Future sharePatcherLog(String logs) async { + Future exportPatcherLog(String logs) async { final Directory appCache = await getTemporaryDirectory(); final Directory logDir = Directory('${appCache.path}/logs'); logDir.createSync(); @@ -293,10 +293,15 @@ class PatcherAPI { .replaceAll(':', '') .replaceAll('T', '') .replaceAll('.', ''); - final File log = - File('${logDir.path}/revanced-manager_patcher_$dateTime.log'); + final String fileName = 'revanced-manager_patcher_$dateTime.log'; + final File log = File('${logDir.path}/$fileName'); log.writeAsStringSync(logs); - ShareExtend.share(log.path, 'file'); + CRFileSaver.saveFileWithDialog( + SaveFileDialogParams( + sourceFilePath: log.path, + destinationFileName: fileName, + ), + ); } String getSuggestedVersion(String packageName) { diff --git a/lib/ui/views/installer/installer_view.dart b/lib/ui/views/installer/installer_view.dart index 981d0f55..6f020189 100644 --- a/lib/ui/views/installer/installer_view.dart +++ b/lib/ui/views/installer/installer_view.dart @@ -4,8 +4,6 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/installerView/gradient_progress_indicator.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_card.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; -import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart'; import 'package:stacked/stacked.dart'; @@ -22,6 +20,40 @@ class InstallerView extends StatelessWidget { top: false, bottom: false, child: Scaffold( + floatingActionButton: Visibility( + visible: !model.isPatching, + child: FloatingActionButton.extended( + label: I18nText('installerView.installButton'), + icon: const Icon(Icons.file_download_outlined), + onPressed: () => model.installTypeDialog(context), + ), + ), + floatingActionButtonLocation: + FloatingActionButtonLocation.endContained, + bottomNavigationBar: Visibility( + visible: !model.isPatching, + child: BottomAppBar( + child: Row( + children: [ + Visibility( + visible: !model.hasErrors, + child: IconButton.filledTonal( + tooltip: FlutterI18n.translate( + context, 'installerView.exportApkButtonTooltip'), + icon: const Icon(Icons.save), + onPressed: () => model.onButtonPressed(0), + ), + ), + IconButton.filledTonal( + tooltip: FlutterI18n.translate( + context, 'installerView.exportLogButtonTooltip'), + icon: const Icon(Icons.post_add), + onPressed: () => model.onButtonPressed(1), + ), + ], + ), + ), + ), body: CustomScrollView( controller: model.scrollController, slivers: [ @@ -35,44 +67,6 @@ class InstallerView extends StatelessWidget { overflow: TextOverflow.ellipsis, ), onBackButtonPressed: () => model.onWillPop(context), - actions: [ - Visibility( - visible: !model.isPatching, - child: CustomPopupMenu( - onSelected: (value) => model.onMenuSelection(value), - children: { - if (!model.hasErrors) - 0: I18nText( - 'installerView.shareApkMenuOption', - child: const Text( - '', - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - ), - 1: I18nText( - 'installerView.exportApkMenuOption', - child: const Text( - '', - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - ), - 2: I18nText( - 'installerView.shareLogMenuOption', - child: const Text( - '', - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - ), - }, - ), - ), - ], bottom: PreferredSize( preferredSize: const Size(double.infinity, 1.0), child: GradientProgressIndicator(progress: model.progress), @@ -96,72 +90,6 @@ class InstallerView extends StatelessWidget { ), ), ), - SliverFillRemaining( - hasScrollBody: false, - child: Align( - alignment: Alignment.bottomCenter, - child: Visibility( - visible: !model.isPatching && !model.hasErrors, - child: Padding( - padding: const EdgeInsets.all(20.0).copyWith(top: 0.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Visibility( - visible: model.isInstalled, - child: CustomMaterialButton( - label: I18nText('installerView.openButton'), - isExpanded: true, - onPressed: () { - model.openApp(); - model.cleanPatcher(); - Navigator.of(context).pop(); - }, - ), - ), - Visibility( - visible: !model.isInstalled && model.isRooted, - child: CustomMaterialButton( - isFilled: false, - label: - I18nText('installerView.installRootButton'), - isExpanded: true, - onPressed: () => model.installResult( - context, - true, - ), - ), - ), - Visibility( - visible: !model.isInstalled, - child: const SizedBox( - width: 16, - ), - ), - Visibility( - visible: !model.isInstalled, - child: CustomMaterialButton( - label: I18nText('installerView.installButton'), - isExpanded: true, - onPressed: () => model.installResult( - context, - false, - ), - ), - ), - ], - ), - ), - ), - ), - ), - SliverFillRemaining( - hasScrollBody: false, - child: SizedBox( - height: MediaQuery.of(context).viewPadding.bottom, - ), - ), ], ), ), diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 3258e9ba..047c9a88 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -169,6 +169,86 @@ class InstallerViewModel extends BaseViewModel { } } + Future installTypeDialog(BuildContext context) async { + final ValueNotifier installType = ValueNotifier(0); + if (isRooted) { + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => AlertDialog( + title: I18nText( + 'installerView.installType', + ), + icon: const Icon(Icons.file_download_outlined), + contentPadding: const EdgeInsets.symmetric(vertical: 16), + content: ValueListenableBuilder( + valueListenable: installType, + builder: (context, value, child) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + child: I18nText( + 'installerView.installTypeDescription', + child: Text( + '', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.secondary, + ), + ), + ), + ), + RadioListTile( + title: const Text('Non-root'), + subtitle: const Text('Recommended'), + contentPadding: const EdgeInsets.symmetric(horizontal: 10), + value: 0, + groupValue: value, + onChanged: (selected) { + installType.value = selected!; + }, + ), + RadioListTile( + title: const Text('Root'), + contentPadding: const EdgeInsets.symmetric(horizontal: 10), + value: 1, + groupValue: value, + onChanged: (selected) { + installType.value = selected!; + }, + ), + ], + ); + }, + ), + actions: [ + CustomMaterialButton( + label: I18nText('cancelButton'), + isFilled: false, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + CustomMaterialButton( + label: I18nText('installerView.installButton'), + onPressed: () { + Navigator.of(context).pop(); + installResult(context, installType.value == 1); + }, + ) + ], + ), + ); + } else { + installResult(context, false); + } + } + Future stopPatcher() async { try { isCanceled = true; @@ -253,18 +333,8 @@ class InstallerViewModel extends BaseViewModel { } } - void shareResult() { - try { - _patcherAPI.sharePatchedFile(_app.name, _app.version); - } on Exception catch (e) { - if (kDebugMode) { - print(e); - } - } - } - - void shareLog() { - _patcherAPI.sharePatcherLog(logs); + void exportLog() { + _patcherAPI.exportPatcherLog(logs); } Future cleanPatcher() async { @@ -284,16 +354,13 @@ class InstallerViewModel extends BaseViewModel { DeviceApps.openApp(_app.packageName); } - void onMenuSelection(int value) { + void onButtonPressed(int value) { switch (value) { case 0: - shareResult(); - break; - case 1: exportResult(); break; - case 2: - shareLog(); + case 1: + exportLog(); break; } } From 14f765f4b4fda3bf873863c0c943f843f3f6b1d3 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Thu, 10 Aug 2023 19:12:15 +0700 Subject: [PATCH 02/20] ci(build): don't cache Gradle Gradle take up so much space in cache that it wouldn't make sense for us to cache it since we are rapidly hitting the cache limit every time. --- .github/workflows/pr-build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 647ab294..0329d43e 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -20,7 +20,6 @@ jobs: with: java-version: '11' distribution: 'zulu' - cache: 'gradle' - name: Setup Flutter uses: subosito/flutter-action@v2 with: From f5e45ead260d2e1bbd84caba43b26798a0a06bdf Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Thu, 10 Aug 2023 19:14:13 +0700 Subject: [PATCH 03/20] ci(build): don't build on every pull request unrelated to application --- .github/workflows/pr-build.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 0329d43e..f889490f 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -2,7 +2,12 @@ name: PR Build on: pull_request: - + paths: + - "android/**" + - "assets/**" + - "fastlane/**" + - "lib/**" + jobs: build: name: Build From 97e37f304bb9e66f982b589f8c43a1a2f32f071e Mon Sep 17 00:00:00 2001 From: Dhruvan Bhalara <53393418+dhruvanbhalara@users.noreply.github.com> Date: Thu, 10 Aug 2023 17:55:07 +0530 Subject: [PATCH 04/20] refactor: migrate MediaQuery properties to InheritedModel (#1115) Co-authored-by: Ushie --- lib/ui/views/app_selector/app_selector_view.dart | 2 +- lib/ui/views/contributors/contributors_view.dart | 2 +- lib/ui/views/navigation/navigation_viewmodel.dart | 2 +- lib/ui/views/patches_selector/patches_selector_view.dart | 2 +- lib/ui/widgets/appSelectorView/app_skeleton_loader.dart | 2 +- .../widgets/installerView/gradient_progress_indicator.dart | 2 +- lib/ui/widgets/patchesSelectorView/patch_options_fields.dart | 5 +++-- 7 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/ui/views/app_selector/app_selector_view.dart b/lib/ui/views/app_selector/app_selector_view.dart index 6368df61..2a3ce795 100644 --- a/lib/ui/views/app_selector/app_selector_view.dart +++ b/lib/ui/views/app_selector/app_selector_view.dart @@ -94,7 +94,7 @@ class _AppSelectorViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 12.0) .copyWith( bottom: - MediaQuery.of(context).viewPadding.bottom + 8.0, + MediaQuery.viewPaddingOf(context).bottom + 8.0, ), child: Column( children: [ diff --git a/lib/ui/views/contributors/contributors_view.dart b/lib/ui/views/contributors/contributors_view.dart index 26cdb12b..a409c17e 100644 --- a/lib/ui/views/contributors/contributors_view.dart +++ b/lib/ui/views/contributors/contributors_view.dart @@ -57,7 +57,7 @@ class ContributorsView extends StatelessWidget { title: 'contributorsView.managerContributors', contributors: model.managerContributors, ), - SizedBox(height: MediaQuery.of(context).viewPadding.bottom), + SizedBox(height: MediaQuery.viewPaddingOf(context).bottom), ], ), ), diff --git a/lib/ui/views/navigation/navigation_viewmodel.dart b/lib/ui/views/navigation/navigation_viewmodel.dart index 51b3497c..99110cc1 100644 --- a/lib/ui/views/navigation/navigation_viewmodel.dart +++ b/lib/ui/views/navigation/navigation_viewmodel.dart @@ -33,7 +33,7 @@ class NavigationViewModel extends IndexTrackingViewModel { if (prefs.getBool('useDarkTheme') == null) { final bool isDark = - MediaQuery.of(context).platformBrightness != Brightness.light; + MediaQuery.platformBrightnessOf(context) != Brightness.light; await prefs.setBool('useDarkTheme', isDark); await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0); } diff --git a/lib/ui/views/patches_selector/patches_selector_view.dart b/lib/ui/views/patches_selector/patches_selector_view.dart index f5f4a7bd..3067263a 100644 --- a/lib/ui/views/patches_selector/patches_selector_view.dart +++ b/lib/ui/views/patches_selector/patches_selector_view.dart @@ -139,7 +139,7 @@ class _PatchesSelectorViewState extends State { : Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0).copyWith( - bottom: MediaQuery.of(context).viewPadding.bottom + 8.0, + bottom: MediaQuery.viewPaddingOf(context).bottom + 8.0, ), child: Column( children: [ diff --git a/lib/ui/widgets/appSelectorView/app_skeleton_loader.dart b/lib/ui/widgets/appSelectorView/app_skeleton_loader.dart index 51c51b72..0cb80428 100644 --- a/lib/ui/widgets/appSelectorView/app_skeleton_loader.dart +++ b/lib/ui/widgets/appSelectorView/app_skeleton_loader.dart @@ -7,7 +7,7 @@ class AppSkeletonLoader extends StatelessWidget { @override Widget build(BuildContext context) { - final screenWidth = MediaQuery.of(context).size.width; + final screenWidth = MediaQuery.sizeOf(context).width; return ListView.builder( shrinkWrap: true, itemCount: 7, diff --git a/lib/ui/widgets/installerView/gradient_progress_indicator.dart b/lib/ui/widgets/installerView/gradient_progress_indicator.dart index 3936810b..d4032184 100644 --- a/lib/ui/widgets/installerView/gradient_progress_indicator.dart +++ b/lib/ui/widgets/installerView/gradient_progress_indicator.dart @@ -25,7 +25,7 @@ class _GradientProgressIndicatorState extends State { ), ), height: 5, - width: MediaQuery.of(context).size.width * widget.progress!, + width: MediaQuery.sizeOf(context).width * widget.progress!, ), ); } diff --git a/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart b/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart index 3f40e4a5..45a13843 100644 --- a/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart +++ b/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart @@ -8,8 +8,9 @@ class OptionsTextField extends StatelessWidget { @override Widget build(BuildContext context) { - final sHeight = MediaQuery.of(context).size.height; - final sWidth = MediaQuery.of(context).size.width; + final size = MediaQuery.sizeOf(context); + final sHeight = size.height; + final sWidth = size.width; return Container( margin: const EdgeInsets.only(top: 12, bottom: 6), padding: EdgeInsets.zero, From f90f6e81ee7614aacbcbb96175cb8f2c10947153 Mon Sep 17 00:00:00 2001 From: aAbed <39409020+TheAabedKhan@users.noreply.github.com> Date: Fri, 11 Aug 2023 06:56:19 +0545 Subject: [PATCH 05/20] feat: patch apps without internet (#1114) --- lib/services/github_api.dart | 31 ++++++++++-- lib/services/manager_api.dart | 47 +++++++++++++++++-- lib/services/patcher_api.dart | 2 + lib/ui/views/home/home_viewmodel.dart | 2 +- .../settings_manage_sources.dart | 2 + 5 files changed, 75 insertions(+), 9 deletions(-) diff --git a/lib/services/github_api.dart b/lib/services/github_api.dart index c846d272..de8c160b 100644 --- a/lib/services/github_api.dart +++ b/lib/services/github_api.dart @@ -6,12 +6,14 @@ import 'package:dio_cache_interceptor/dio_cache_interceptor.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:injectable/injectable.dart'; +import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/services/manager_api.dart'; @lazySingleton class GithubAPI { late Dio _dio = Dio(); + late final ManagerAPI _managerAPI = locator(); final _cacheOptions = CacheOptions( store: MemCacheStore(), @@ -201,8 +203,14 @@ class GithubAPI { String extension, String repoName, String version, + String url, ) async { try { + if (url.isNotEmpty) { + return await DefaultCacheManager().getSingleFile( + url, + ); + } final Map? release = await getPatchesRelease(repoName, version); if (release != null) { @@ -211,8 +219,16 @@ class GithubAPI { (asset) => (asset['name'] as String).endsWith(extension), ); if (asset != null) { + final String downloadUrl = asset['browser_download_url']; + if (extension == '.apk') { + _managerAPI.setIntegrationsDownloadURL(downloadUrl); + } else if (extension == '.json') { + _managerAPI.setPatchesDownloadURL(downloadUrl, false); + } else { + _managerAPI.setPatchesDownloadURL(downloadUrl, true); + } return await DefaultCacheManager().getSingleFile( - asset['browser_download_url'], + downloadUrl, ); } } @@ -224,10 +240,19 @@ class GithubAPI { return null; } - Future> getPatches(String repoName, String version) async { + Future> getPatches( + String repoName, + String version, + String url, + ) async { List patches = []; try { - final File? f = await getPatchesReleaseFile('.json', repoName, version); + final File? f = await getPatchesReleaseFile( + '.json', + repoName, + version, + url, + ); if (f != null) { final List list = jsonDecode(f.readAsStringSync()); patches = list.map((patch) => Patch.fromJson(patch)).toList(); diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart index 28b2b8fa..d334eba2 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -76,6 +76,14 @@ class ManagerAPI { await _prefs.setString('repoUrl', url); } + String getPatchesDownloadURL(bool bundle) { + return _prefs.getString('patchesDownloadURL-$bundle') ?? ''; + } + + Future setPatchesDownloadURL(String value, bool bundle) async { + await _prefs.setString('patchesDownloadURL-$bundle', value); + } + String getPatchesRepo() { return _prefs.getString('patchesRepo') ?? defaultPatchesRepo; } @@ -119,6 +127,14 @@ class ManagerAPI { await _prefs.setStringList('savedPatches-$packageName', patchesJson); } + String getIntegrationsDownloadURL() { + return _prefs.getString('integrationsDownloadURL') ?? ''; + } + + Future setIntegrationsDownloadURL(String value) async { + await _prefs.setString('integrationsDownloadURL', value); + } + List getUsedPatches(String packageName) { final List patchesJson = _prefs.getStringList('usedPatches-$packageName') ?? []; @@ -260,7 +276,12 @@ class ManagerAPI { try { final String repoName = getPatchesRepo(); final String currentVersion = await getCurrentPatchesVersion(); - return await _githubAPI.getPatches(repoName, currentVersion); + final String url = getPatchesDownloadURL(false); + return await _githubAPI.getPatches( + repoName, + currentVersion, + url, + ); } on Exception catch (e) { if (kDebugMode) { print(e); @@ -273,10 +294,12 @@ class ManagerAPI { try { final String repoName = getPatchesRepo(); final String currentVersion = await getCurrentPatchesVersion(); + final String url = getPatchesDownloadURL(true); return await _githubAPI.getPatchesReleaseFile( '.jar', repoName, currentVersion, + url, ); } on Exception catch (e) { if (kDebugMode) { @@ -290,10 +313,12 @@ class ManagerAPI { try { final String repoName = getIntegrationsRepo(); final String currentVersion = await getCurrentIntegrationsVersion(); + final String url = getIntegrationsDownloadURL(); return await _githubAPI.getPatchesReleaseFile( '.apk', repoName, currentVersion, + url, ); } on Exception catch (e) { if (kDebugMode) { @@ -384,27 +409,39 @@ class ManagerAPI { Future getCurrentPatchesVersion() async { patchesVersion = _prefs.getString('patchesVersion') ?? '0.0.0'; if (patchesVersion == '0.0.0' || isPatchesAutoUpdate()) { - patchesVersion = await getLatestPatchesVersion() ?? '0.0.0'; - await setCurrentPatchesVersion(patchesVersion!); + final String newPatchesVersion = + await getLatestPatchesVersion() ?? '0.0.0'; + if (patchesVersion != newPatchesVersion && newPatchesVersion != '0.0.0') { + await setCurrentPatchesVersion(newPatchesVersion); + } } return patchesVersion!; } Future setCurrentPatchesVersion(String version) async { await _prefs.setString('patchesVersion', version); + await setPatchesDownloadURL('', false); + await setPatchesDownloadURL('', true); + await downloadPatches(); } Future getCurrentIntegrationsVersion() async { integrationsVersion = _prefs.getString('integrationsVersion') ?? '0.0.0'; if (integrationsVersion == '0.0.0' || isPatchesAutoUpdate()) { - integrationsVersion = await getLatestIntegrationsVersion() ?? '0.0.0'; - await setCurrentIntegrationsVersion(integrationsVersion!); + final String newIntegrationsVersion = + await getLatestIntegrationsVersion() ?? '0.0.0'; + if (integrationsVersion != newIntegrationsVersion && + newIntegrationsVersion != '0.0.0') { + await setCurrentIntegrationsVersion(newIntegrationsVersion); + } } return integrationsVersion!; } Future setCurrentIntegrationsVersion(String version) async { await _prefs.setString('integrationsVersion', version); + await setIntegrationsDownloadURL(''); + await downloadIntegrations(); } Future> getAppsToRemove( diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index c449cbc5..dfaaf3ec 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -30,6 +30,8 @@ class PatcherAPI { Future initialize() async { await _loadPatches(); + await _managerAPI.downloadPatches(); + await _managerAPI.downloadIntegrations(); final Directory appCache = await getTemporaryDirectory(); _dataDir = await getExternalStorageDirectory() ?? appCache; _tmpDir = Directory('${appCache.path}/patcher'); diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index e2ce5125..cfc99e66 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -256,9 +256,9 @@ class HomeViewModel extends BaseViewModel { final String integrationsVersion = await _managerAPI.getLatestIntegrationsVersion() ?? '0.0.0'; if (patchesVersion != '0.0.0' && integrationsVersion != '0.0.0') { - _toast.showBottom('homeView.downloadedMessage'); await _managerAPI.setCurrentPatchesVersion(patchesVersion); await _managerAPI.setCurrentIntegrationsVersion(integrationsVersion); + _toast.showBottom('homeView.downloadedMessage'); forceRefresh(context); } else { _toast.showBottom('homeView.errorDownloadMessage'); diff --git a/lib/ui/views/settings/settingsFragment/settings_manage_sources.dart b/lib/ui/views/settings/settingsFragment/settings_manage_sources.dart index 97dd35be..76e3171b 100644 --- a/lib/ui/views/settings/settingsFragment/settings_manage_sources.dart +++ b/lib/ui/views/settings/settingsFragment/settings_manage_sources.dart @@ -129,6 +129,7 @@ class SManageSources extends BaseViewModel { '${_orgIntSourceController.text.trim()}/${_intSourceController.text.trim()}', ); _managerAPI.setCurrentPatchesVersion('0.0.0'); + _managerAPI.setCurrentIntegrationsVersion('0.0.0'); _toast.showBottom('settingsView.restartAppForChanges'); Navigator.of(context).pop(); }, @@ -158,6 +159,7 @@ class SManageSources extends BaseViewModel { _managerAPI.setPatchesRepo(''); _managerAPI.setIntegrationsRepo(''); _managerAPI.setCurrentPatchesVersion('0.0.0'); + _managerAPI.setCurrentIntegrationsVersion('0.0.0'); _toast.showBottom('settingsView.restartAppForChanges'); Navigator.of(context) ..pop() From 94acebbebd7a6af6681cc4029f6e44692261fc28 Mon Sep 17 00:00:00 2001 From: Subhamoy Biswas <86715146+neosubhamoy@users.noreply.github.com> Date: Fri, 11 Aug 2023 08:47:46 +0530 Subject: [PATCH 06/20] docs(fixed): replaced some emoji icons, not rendering on chromium based browsers on windows (#1123) --- docs/1_installation.md | 4 ++-- docs/2_1_patching.md | 2 +- docs/2_2_managing.md | 2 +- docs/2_3_updating.md | 2 +- docs/2_4_settings.md | 2 +- docs/2_usage.md | 2 +- docs/3_troubleshooting.md | 4 ++-- docs/README.md | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/1_installation.md b/docs/1_installation.md index b25e0758..d4c08984 100644 --- a/docs/1_installation.md +++ b/docs/1_installation.md @@ -2,7 +2,7 @@ In order to use ReVanced on your Android device, ReVanced Manager must be installed. -## 🪜 Installation steps +## ✅ Installation steps 1. Download the latest version of ReVanced Manager from [here](https://github.com/revanced/revanced-manager/releases/latest) 2. Install ReVanced Manager @@ -11,4 +11,4 @@ In order to use ReVanced on your Android device, ReVanced Manager must be instal The next page will guide you through using ReVanced Manager. -Continue: [🪛 Usage](2_usage.md) +Continue: [🛠️ Usage](2_usage.md) diff --git a/docs/2_1_patching.md b/docs/2_1_patching.md index 5dab9d64..428660ce 100644 --- a/docs/2_1_patching.md +++ b/docs/2_1_patching.md @@ -2,7 +2,7 @@ The following pages will guide you through using ReVanced Manager to patch apps. -## 🪜 Steps to patch apps +## ✅ Steps to patch apps 1. Navigate to the **Patcher** tab from the bottom navigation bar 2. Tap on the **Select an app** card diff --git a/docs/2_2_managing.md b/docs/2_2_managing.md index 5e8e378b..1ad02229 100644 --- a/docs/2_2_managing.md +++ b/docs/2_2_managing.md @@ -2,7 +2,7 @@ After patching an app, you may want to manage it. This page will guide you through managing patched apps. -## 🪜 Steps to manage patched apps +## ✅ Steps to manage patched apps 1. Tap on the **Dashboard** tab in the bottom navigation bar 2. Tap on the **Info** button for the app you want to manage diff --git a/docs/2_3_updating.md b/docs/2_3_updating.md index a14717a3..9851ac90 100644 --- a/docs/2_3_updating.md +++ b/docs/2_3_updating.md @@ -2,7 +2,7 @@ In order to keep up with the latest features and bug fixes, it is recommended to keep ReVanced Manager up to date. -## 🪜 Updating steps +## ✅ Updating steps 1. Navigate to the **Dashboard** tab from the bottom navigation bar 2. Tap on the **Update** button in the **Updates** section diff --git a/docs/2_4_settings.md b/docs/2_4_settings.md index e1d49033..008cda46 100644 --- a/docs/2_4_settings.md +++ b/docs/2_4_settings.md @@ -2,7 +2,7 @@ ReVanced Manager has settings that can be configured to your liking. -## 🪛 Essential settings +## ⭐ Essential settings - ### 🔗 API URL diff --git a/docs/2_usage.md b/docs/2_usage.md index 2bef4330..f079782f 100644 --- a/docs/2_usage.md +++ b/docs/2_usage.md @@ -13,4 +13,4 @@ The following pages will guide you through using ReVanced Manager to patch apps, The next page will guide you through troubleshooting ReVanced Manager. -Continue: [🛟 Troubleshooting](3_troubleshooting.md) +Continue: [❔ Troubleshooting](3_troubleshooting.md) diff --git a/docs/3_troubleshooting.md b/docs/3_troubleshooting.md index 80b93048..5a860c6b 100644 --- a/docs/3_troubleshooting.md +++ b/docs/3_troubleshooting.md @@ -1,4 +1,4 @@ -# 🛟 Troubleshooting +# ❔ Troubleshooting In case you encounter any issues while using ReVanced Manager, please refer to this page for possible solutions. @@ -28,4 +28,4 @@ In case you encounter any issues while using ReVanced Manager, please refer to t The next page will teach you how to build ReVanced Manager from source. -Continue: [🛠️ Building from source](4_building.md) +Continue: [🔨 Building from source](4_building.md) diff --git a/docs/README.md b/docs/README.md index 0577ad03..af2926b6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,8 +11,8 @@ This documentation explains how to use [ReVanced Manager](https://github.com/rev 2. [🧰 Managing patched apps](2_2_managing.md) 3. [🔄 Updating ReVanced Manager](2_3_updating.md) 4. [⚙️ Configuring ReVanced Manager](2_4_settings.md) -3. [🛟 Troubleshooting](3_troubleshooting.md) -4. [🛠 Building from source](4_building.md) +3. [❔ Troubleshooting](3_troubleshooting.md) +4. [🔨 Building from source](4_building.md) ## ⏭️ Start here From 381daff980c5e2ff29a4f54d3ee58c0d0af3679e Mon Sep 17 00:00:00 2001 From: kitadai31 <90122968+kitadai31@users.noreply.github.com> Date: Sat, 12 Aug 2023 01:19:33 +0900 Subject: [PATCH 07/20] fix: exclude x86 aapt2 binary from release builds (#1126) --- android/app/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/android/app/build.gradle b/android/app/build.gradle index a752c0e1..65711fda 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -55,6 +55,9 @@ android { shrinkResources false minifyEnabled false signingConfig signingConfigs.debug + ndk { + abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' + } } } From 372ce174c9dc7cdcf55782a5e636aae69df37a17 Mon Sep 17 00:00:00 2001 From: Dhruvan Bhalara <53393418+dhruvanbhalara@users.noreply.github.com> Date: Fri, 11 Aug 2023 21:49:44 +0530 Subject: [PATCH 08/20] fix: overlapping issue in application selection page (#1128) --- lib/ui/views/app_selector/app_selector_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ui/views/app_selector/app_selector_view.dart b/lib/ui/views/app_selector/app_selector_view.dart index 2a3ce795..7877a226 100644 --- a/lib/ui/views/app_selector/app_selector_view.dart +++ b/lib/ui/views/app_selector/app_selector_view.dart @@ -133,6 +133,7 @@ class _AppSelectorViewState extends State { ), ) .toList(), + const SizedBox(height: 70.0), ], ), ), From 4b0c8cecc89c6f6d4b1feaf61d3b12a13f1be5d3 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Sat, 12 Aug 2023 13:56:18 +0700 Subject: [PATCH 09/20] ci(build): update event trigger This add ".github/workflows/pr-build.yml" to build on every change to the CI Build to check if there's no error when it got modified. And also remove "fastlane/**" because that's not related to the ReVanced Manager's code --- .github/workflows/pr-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index f889490f..d8ec9809 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -3,9 +3,9 @@ name: PR Build on: pull_request: paths: + - ".github/workflows/pr-build.yml" - "android/**" - "assets/**" - - "fastlane/**" - "lib/**" jobs: From 11d8f9fd30e73b5b49a0ea86f7800b6183d23bf4 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Sat, 12 Aug 2023 14:33:15 +0700 Subject: [PATCH 10/20] refactor: apply changes according to Dart trailing commas --- lib/ui/views/installer/installer_view.dart | 8 ++++++-- lib/ui/views/installer/installer_viewmodel.dart | 6 ++++-- lib/ui/widgets/patchesSelectorView/patch_item.dart | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/ui/views/installer/installer_view.dart b/lib/ui/views/installer/installer_view.dart index 6f020189..47c0e36a 100644 --- a/lib/ui/views/installer/installer_view.dart +++ b/lib/ui/views/installer/installer_view.dart @@ -39,14 +39,18 @@ class InstallerView extends StatelessWidget { visible: !model.hasErrors, child: IconButton.filledTonal( tooltip: FlutterI18n.translate( - context, 'installerView.exportApkButtonTooltip'), + context, + 'installerView.exportApkButtonTooltip', + ), icon: const Icon(Icons.save), onPressed: () => model.onButtonPressed(0), ), ), IconButton.filledTonal( tooltip: FlutterI18n.translate( - context, 'installerView.exportLogButtonTooltip'), + context, + 'installerView.exportLogButtonTooltip', + ), icon: const Icon(Icons.post_add), onPressed: () => model.onButtonPressed(1), ), diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 047c9a88..489f75dd 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -190,7 +190,9 @@ class InstallerViewModel extends BaseViewModel { children: [ Padding( padding: const EdgeInsets.symmetric( - horizontal: 20, vertical: 10), + horizontal: 20, + vertical: 10, + ), child: I18nText( 'installerView.installTypeDescription', child: Text( @@ -240,7 +242,7 @@ class InstallerViewModel extends BaseViewModel { Navigator.of(context).pop(); installResult(context, installType.value == 1); }, - ) + ), ], ), ); diff --git a/lib/ui/widgets/patchesSelectorView/patch_item.dart b/lib/ui/widgets/patchesSelectorView/patch_item.dart index 21bd4726..0dc6da77 100644 --- a/lib/ui/widgets/patchesSelectorView/patch_item.dart +++ b/lib/ui/widgets/patchesSelectorView/patch_item.dart @@ -186,7 +186,7 @@ class _PatchItemState extends State { ), ), ), - ) + ), ], ), widget.child ?? const SizedBox(), From 72fd24e624aa7d6475fd6cbb4d8b41180375dd8b Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Sat, 12 Aug 2023 15:06:17 +0700 Subject: [PATCH 11/20] fix: use i18n translation for installer page --- assets/i18n/en_US.json | 6 +++++- lib/ui/views/installer/installer_viewmodel.dart | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index fb3004c7..06af7447 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -142,8 +142,12 @@ "widgetTitle": "Installer", "installType": "Select install type", "installTypeDescription": "Select the installation type to proceed with.", + "installButton": "Install", - "installRootButton": "Install as Root", + "installRootType": "Root", + "installNonRootType": "Non-root", + "installRecommendedType": "Recommended", + "pressBackAgain": "Press back again to cancel", "openButton": "Open", "shareButton": "Share file", diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 489f75dd..61cc877a 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -206,8 +206,8 @@ class InstallerViewModel extends BaseViewModel { ), ), RadioListTile( - title: const Text('Non-root'), - subtitle: const Text('Recommended'), + title: I18nText('installerView.installNonRootType'), + subtitle: I18nText('installerView.installRecommendedType'), contentPadding: const EdgeInsets.symmetric(horizontal: 10), value: 0, groupValue: value, @@ -216,7 +216,7 @@ class InstallerViewModel extends BaseViewModel { }, ), RadioListTile( - title: const Text('Root'), + title: I18nText('installerView.installRootType'), contentPadding: const EdgeInsets.symmetric(horizontal: 10), value: 1, groupValue: value, From 7525e52fab38b3b8dcf5a72bc30d9afba71a43c8 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Sat, 12 Aug 2023 15:07:04 +0700 Subject: [PATCH 12/20] fix(installer): use correct bg colour for dialog --- lib/ui/views/installer/installer_viewmodel.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 61cc877a..b11d47da 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -179,6 +179,7 @@ class InstallerViewModel extends BaseViewModel { title: I18nText( 'installerView.installType', ), + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, icon: const Icon(Icons.file_download_outlined), contentPadding: const EdgeInsets.symmetric(vertical: 16), content: ValueListenableBuilder( From dcd5ba41cfb42245e5d30c2010cee25cac055a93 Mon Sep 17 00:00:00 2001 From: Ushie Date: Mon, 14 Aug 2023 03:40:05 +0300 Subject: [PATCH 13/20] build: add mavenlocal to repositories --- android/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/android/build.gradle b/android/build.gradle index 5086ec38..88ac491c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -22,6 +22,7 @@ allprojects { password = (project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN")) as String } } + mavenLocal() } } From c40061933807602d4efb060fe02915272d17fa67 Mon Sep 17 00:00:00 2001 From: aAbed <39409020+TheAabedKhan@users.noreply.github.com> Date: Tue, 15 Aug 2023 14:50:27 +0545 Subject: [PATCH 14/20] feat: disable changing patches selection by default (#1132) Co-authored-by: oSumAtrIX --- assets/i18n/en_US.json | 11 +- lib/services/manager_api.dart | 95 +++++++++++++ lib/ui/views/patcher/patcher_viewmodel.dart | 4 + .../patches_selector_view.dart | 37 ++++- .../patches_selector_viewmodel.dart | 107 +++++++++++---- lib/ui/views/settings/settings_viewmodel.dart | 129 +++++++++++++++--- .../patchesSelectorView/patch_item.dart | 14 +- .../settings_advanced_section.dart | 2 + .../settings_enable_patches_selection.dart | 37 +++++ .../settingsView/settings_export_section.dart | 6 +- 10 files changed, 381 insertions(+), 61 deletions(-) create mode 100644 lib/ui/widgets/settingsView/settings_enable_patches_selection.dart diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 06af7447..63e82677 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -11,6 +11,7 @@ "noButton": "No", "warning": "Warning", "notice": "Notice", + "noShowAgain": "Don't show this again", "new": "New", "navigationView": { "dashboardTab": "Dashboard", @@ -136,7 +137,10 @@ "unsupportedPatchVersion": "Patch is not supported for this app version. Enable the experimental toggle in settings to proceed.", "newPatchDialogText": "This is a new patch that has been added since the last time you have patched this app.", - "newPatch": "New patch" + "newPatch": "New patch", + + "patchesChangeWarningDialogText": "It is recommended to use the default selection of patches because changing it may cause unexpected issues.\n\nIf you know what you are doing, you can enable \"Enable changing selection\" in the settings.", + "patchesChangeWarningDialogButton": "Use default selection" }, "installerView": { "widgetTitle": "Installer", @@ -205,6 +209,11 @@ "logsLabel": "Logs", "logsHint": "Share Manager's logs", + "enablePatchesSelectionLabel": "Enable changing selection", + "enablePatchesSelectionHint": "Enable changing the selection of patches.", + "enablePatchesSelectionWarningText": "Changing the default selection of patches may cause unexpected issues.\n\nEnable anyways?", + "disablePatchesSelectionWarningText": "You are about to disable changing the selection of patches.\nThe default selection of patches will be restored.\n\nDisable anyways?", + "autoUpdatePatchesLabel": "Auto update patches", "autoUpdatePatchesHint": "Automatically update ReVanced Patches to the latest version", "experimentalUniversalPatchesLabel": "Experimental universal patches support", diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart index d334eba2..1391f707 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -2,6 +2,8 @@ import 'dart:convert'; import 'dart:io'; import 'package:device_apps/device_apps.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_i18n/widgets/I18nText.dart'; import 'package:injectable/injectable.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; @@ -11,6 +13,7 @@ import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/services/github_api.dart'; import 'package:revanced_manager/services/revanced_api.dart'; import 'package:revanced_manager/services/root_api.dart'; +import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/utils/check_for_supported_patch.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:timeago/timeago.dart'; @@ -107,6 +110,41 @@ class ManagerAPI { return _prefs.getBool('patchesAutoUpdate') ?? false; } + bool isPatchesChangeEnabled() { + if (getPatchedApps().isNotEmpty && !isChangingToggleModified()) { + for (final apps in getPatchedApps()) { + if (getSavedPatches(apps.originalPackageName) + .indexWhere((patch) => patch.excluded) != + -1) { + setPatchesChangeWarning(false); + setPatchesChangeEnabled(true); + break; + } + } + } + return _prefs.getBool('patchesChangeEnabled') ?? false; + } + + void setPatchesChangeEnabled(bool value) { + _prefs.setBool('patchesChangeEnabled', value); + } + + bool showPatchesChangeWarning() { + return _prefs.getBool('showPatchesChangeWarning') ?? true; + } + + void setPatchesChangeWarning(bool value) { + _prefs.setBool('showPatchesChangeWarning', !value); + } + + bool isChangingToggleModified() { + return _prefs.getBool('isChangingToggleModified') ?? false; + } + + void setChangingToggleModified(bool value) { + _prefs.setBool('isChangingToggleModified', value); + } + Future setPatchesAutoUpdate(bool value) async { await _prefs.setBool('patchesAutoUpdate', value); } @@ -515,6 +553,63 @@ class ManagerAPI { return unsavedApps; } + Future showPatchesChangeWarningDialog(BuildContext context) { + final ValueNotifier noShow = + ValueNotifier(!showPatchesChangeWarning()); + return showDialog( + barrierDismissible: false, + context: context, + builder: (context) => WillPopScope( + onWillPop: () async => false, + child: AlertDialog( + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + title: I18nText('warning'), + content: ValueListenableBuilder( + valueListenable: noShow, + builder: (context, value, child) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + I18nText( + 'patchItem.patchesChangeWarningDialogText', + child: const Text( + '', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + const SizedBox(height: 8), + CheckboxListTile( + value: value, + contentPadding: EdgeInsets.zero, + title: I18nText( + 'noShowAgain', + ), + onChanged: (selected) { + noShow.value = selected!; + }, + ), + ], + ); + }, + ), + actions: [ + CustomMaterialButton( + label: I18nText('okButton'), + onPressed: () { + setPatchesChangeWarning(noShow.value); + Navigator.of(context).pop(); + }, + ), + ], + ), + ), + ); + } + Future reAssessSavedApps() async { final List patchedApps = getPatchedApps(); final List unsavedApps = diff --git a/lib/ui/views/patcher/patcher_viewmodel.dart b/lib/ui/views/patcher/patcher_viewmodel.dart index 843ee6a7..33ceb719 100644 --- a/lib/ui/views/patcher/patcher_viewmodel.dart +++ b/lib/ui/views/patcher/patcher_viewmodel.dart @@ -191,6 +191,10 @@ class PatcherViewModel extends BaseViewModel { this .selectedPatches .addAll(patches.where((patch) => selectedPatches.contains(patch.name))); + if (!_managerAPI.isPatchesChangeEnabled()) { + this.selectedPatches.clear(); + this.selectedPatches.addAll(patches.where((patch) => !patch.excluded)); + } if (!_managerAPI.areExperimentalPatchesEnabled()) { this.selectedPatches.removeWhere((patch) => !isPatchSupported(patch)); } diff --git a/lib/ui/views/patches_selector/patches_selector_view.dart b/lib/ui/views/patches_selector/patches_selector_view.dart index 3067263a..a8654dd8 100644 --- a/lib/ui/views/patches_selector/patches_selector_view.dart +++ b/lib/ui/views/patches_selector/patches_selector_view.dart @@ -20,6 +20,17 @@ class _PatchesSelectorViewState extends State { String _query = ''; final _managerAPI = locator(); + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (!_managerAPI.isPatchesChangeEnabled() && + _managerAPI.showPatchesChangeWarning()) { + _managerAPI.showPatchesChangeWarningDialog(context); + } + }); + } + @override Widget build(BuildContext context) { return ViewModelBuilder.reactive( @@ -87,7 +98,8 @@ class _PatchesSelectorViewState extends State { ), ), CustomPopupMenu( - onSelected: (value) => {model.onMenuSelection(value)}, + onSelected: (value) => + {model.onMenuSelection(value, context)}, children: { 0: I18nText( 'patchesSelectorView.loadPatchesSelection', @@ -152,7 +164,11 @@ class _PatchesSelectorViewState extends State { 'patchesSelectorView.defaultTooltip', ), onPressed: () { - model.selectDefaultPatches(); + if (_managerAPI.isPatchesChangeEnabled()) { + model.selectDefaultPatches(); + } else { + model.showPatchesChangeDialog(context); + } }, ), const SizedBox(width: 8), @@ -163,7 +179,11 @@ class _PatchesSelectorViewState extends State { 'patchesSelectorView.noneTooltip', ), onPressed: () { - model.clearPatches(); + if (_managerAPI.isPatchesChangeEnabled()) { + model.clearPatches(); + } else { + model.showPatchesChangeDialog(context); + } }, ), ], @@ -179,13 +199,14 @@ class _PatchesSelectorViewState extends State { supportedPackageVersions: model.getSupportedVersions(patch), isUnsupported: !isPatchSupported(patch), + isChangeEnabled: _managerAPI.isPatchesChangeEnabled(), isNew: model.isPatchNew( patch, model.getAppInfo().packageName, ), isSelected: model.isSelected(patch), onChanged: (value) => - model.selectPatch(patch, value), + model.selectPatch(patch, value, context), ); } else { return Container(); @@ -215,10 +236,14 @@ class _PatchesSelectorViewState extends State { supportedPackageVersions: model.getSupportedVersions(patch), isUnsupported: !isPatchSupported(patch), + isChangeEnabled: _managerAPI.isPatchesChangeEnabled(), isNew: false, isSelected: model.isSelected(patch), - onChanged: (value) => - model.selectPatch(patch, value), + onChanged: (value) => model.selectPatch( + patch, + false, + context, + ), ); } else { return Container(); diff --git a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart index f333887d..71e4a16e 100644 --- a/lib/ui/views/patches_selector/patches_selector_viewmodel.dart +++ b/lib/ui/views/patches_selector/patches_selector_viewmodel.dart @@ -1,4 +1,6 @@ import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_i18n/widgets/I18nText.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patched_application.dart'; @@ -6,6 +8,7 @@ import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/services/toast.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/utils/check_for_supported_patch.dart'; import 'package:stacked/stacked.dart'; @@ -45,31 +48,70 @@ class PatchesSelectorViewModel extends BaseViewModel { ); } - void selectPatch(Patch patch, bool isSelected) { - if (isSelected && !selectedPatches.contains(patch)) { - selectedPatches.add(patch); + void selectPatch(Patch patch, bool isSelected, BuildContext context) { + if (_managerAPI.isPatchesChangeEnabled()) { + if (isSelected && !selectedPatches.contains(patch)) { + selectedPatches.add(patch); + } else { + selectedPatches.remove(patch); + } + notifyListeners(); } else { - selectedPatches.remove(patch); + showPatchesChangeDialog(context); } - notifyListeners(); + } + + Future showPatchesChangeDialog(BuildContext context) async { + return showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + title: I18nText('warning'), + content: I18nText( + 'patchItem.patchesChangeWarningDialogText', + child: const Text( + '', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + actions: [ + CustomMaterialButton( + isFilled: false, + label: I18nText('okButton'), + onPressed: () => Navigator.of(context).pop(), + ), + CustomMaterialButton( + label: I18nText('patchItem.patchesChangeWarningDialogButton'), + onPressed: () { + Navigator.of(context) + ..pop() + ..pop(); + }, + ), + ], + ), + ); } void selectDefaultPatches() { selectedPatches.clear(); - - if (_managerAPI.areExperimentalPatchesEnabled() == false) { + if (locator().selectedApp?.originalPackageName != null) { selectedPatches.addAll( - patches.where( - (element) => element.excluded == false && isPatchSupported(element), - ), + _patcherAPI + .getFilteredPatches( + locator().selectedApp!.originalPackageName, + ) + .where( + (element) => + !element.excluded && + (_managerAPI.areExperimentalPatchesEnabled() || + isPatchSupported(element)), + ), ); } - - if (_managerAPI.areExperimentalPatchesEnabled()) { - selectedPatches - .addAll(patches.where((element) => element.excluded == false)); - } - notifyListeners(); } @@ -133,10 +175,10 @@ class PatchesSelectorViewModel extends BaseViewModel { } } - void onMenuSelection(value) { + void onMenuSelection(value, BuildContext context) { switch (value) { case 0: - loadSelectedPatches(); + loadSelectedPatches(context); break; } } @@ -150,18 +192,25 @@ class PatchesSelectorViewModel extends BaseViewModel { ); } - Future loadSelectedPatches() async { - final List selectedPatches = await _managerAPI.getSelectedPatches( - locator().selectedApp!.originalPackageName, - ); - if (selectedPatches.isNotEmpty) { - this.selectedPatches.clear(); - this.selectedPatches.addAll( - patches.where((patch) => selectedPatches.contains(patch.name)), - ); + Future loadSelectedPatches(BuildContext context) async { + if (_managerAPI.isPatchesChangeEnabled()) { + final List selectedPatches = await _managerAPI.getSelectedPatches( + locator().selectedApp!.originalPackageName, + ); + if (selectedPatches.isNotEmpty) { + this.selectedPatches.clear(); + this.selectedPatches.addAll( + patches.where((patch) => selectedPatches.contains(patch.name)), + ); + if (!_managerAPI.areExperimentalPatchesEnabled()) { + this.selectedPatches.removeWhere((patch) => !isPatchSupported(patch)); + } + } else { + locator().showBottom('patchesSelectorView.noSavedPatches'); + } + notifyListeners(); } else { - locator().showBottom('patchesSelectorView.noSavedPatches'); + showPatchesChangeDialog(context); } - notifyListeners(); } } diff --git a/lib/ui/views/settings/settings_viewmodel.dart b/lib/ui/views/settings/settings_viewmodel.dart index 483834b8..2441b0a6 100644 --- a/lib/ui/views/settings/settings_viewmodel.dart +++ b/lib/ui/views/settings/settings_viewmodel.dart @@ -3,6 +3,8 @@ import 'package:cr_file_saver/file_saver.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.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'; @@ -10,8 +12,10 @@ import 'package:revanced_manager/app/app.router.dart'; import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; +import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart'; import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_language.dart'; import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart'; +import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:share_extend/share_extend.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; @@ -19,6 +23,9 @@ import 'package:stacked_services/stacked_services.dart'; class SettingsViewModel extends BaseViewModel { final NavigationService _navigationService = locator(); final ManagerAPI _managerAPI = locator(); + final PatchesSelectorViewModel _patchesSelectorViewModel = + PatchesSelectorViewModel(); + final PatcherViewModel _patcherViewModel = locator(); final Toast _toast = locator(); final SUpdateLanguage sUpdateLanguage = SUpdateLanguage(); @@ -37,6 +44,88 @@ class SettingsViewModel extends BaseViewModel { notifyListeners(); } + bool isPatchesChangeEnabled() { + return _managerAPI.isPatchesChangeEnabled(); + } + + Future showPatchesChangeEnableDialog( + bool value, + BuildContext context, + ) async { + if (value) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + title: I18nText('warning'), + content: I18nText( + 'settingsView.enablePatchesSelectionWarningText', + child: const Text( + '', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + actions: [ + CustomMaterialButton( + isFilled: false, + label: I18nText('noButton'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + CustomMaterialButton( + label: I18nText('yesButton'), + onPressed: () { + _managerAPI.setChangingToggleModified(true); + _managerAPI.setPatchesChangeEnabled(true); + Navigator.of(context).pop(); + }, + ), + ], + ), + ); + } else { + return showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + title: I18nText('warning'), + content: I18nText( + 'settingsView.disablePatchesSelectionWarningText', + child: const Text( + '', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + actions: [ + CustomMaterialButton( + isFilled: false, + label: I18nText('noButton'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + CustomMaterialButton( + label: I18nText('yesButton'), + onPressed: () { + _managerAPI.setChangingToggleModified(true); + _patchesSelectorViewModel.selectDefaultPatches(); + _managerAPI.setPatchesChangeEnabled(false); + Navigator.of(context).pop(); + }, + ), + ], + ), + ); + } + } + bool areUniversalPatchesEnabled() { return _managerAPI.areUniversalPatchesEnabled(); } @@ -90,26 +179,30 @@ class SettingsViewModel extends BaseViewModel { } } - Future importPatches() async { - try { - final FilePickerResult? result = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['json'], - ); - if (result != null && result.files.single.path != null) { - final File inFile = File(result.files.single.path!); - inFile.copySync(_managerAPI.storedPatchesFile); - inFile.delete(); - if (locator().selectedApp != null) { - locator().loadLastSelectedPatches(); + Future importPatches(BuildContext context) async { + if (isPatchesChangeEnabled()) { + try { + final FilePickerResult? result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['json'], + ); + if (result != null && result.files.single.path != null) { + final File inFile = File(result.files.single.path!); + inFile.copySync(_managerAPI.storedPatchesFile); + inFile.delete(); + if (_patcherViewModel.selectedApp != null) { + _patcherViewModel.loadLastSelectedPatches(); + } + _toast.showBottom('settingsView.importedPatches'); } - _toast.showBottom('settingsView.importedPatches'); + } on Exception catch (e) { + if (kDebugMode) { + print(e); + } + _toast.showBottom('settingsView.jsonSelectorErrorMessage'); } - } on Exception catch (e) { - if (kDebugMode) { - print(e); - } - _toast.showBottom('settingsView.jsonSelectorErrorMessage'); + } else { + _managerAPI.showPatchesChangeWarningDialog(context); } } diff --git a/lib/ui/widgets/patchesSelectorView/patch_item.dart b/lib/ui/widgets/patchesSelectorView/patch_item.dart index 0dc6da77..78a92e36 100644 --- a/lib/ui/widgets/patchesSelectorView/patch_item.dart +++ b/lib/ui/widgets/patchesSelectorView/patch_item.dart @@ -19,6 +19,7 @@ class PatchItem extends StatefulWidget { required this.isNew, required this.isSelected, required this.onChanged, + required this.isChangeEnabled, this.child, }) : super(key: key); final String name; @@ -30,6 +31,7 @@ class PatchItem extends StatefulWidget { final bool isNew; bool isSelected; final Function(bool) onChanged; + final bool isChangeEnabled; final Widget? child; final toast = locator(); final _managerAPI = locator(); @@ -58,11 +60,13 @@ class _PatchItemState extends State { !widget._managerAPI.areExperimentalPatchesEnabled()) { widget.isSelected = false; widget.toast.showBottom('patchItem.unsupportedPatchVersion'); - } else { + } else if (widget.isChangeEnabled) { widget.isSelected = !widget.isSelected; } }); - widget.onChanged(widget.isSelected); + if (!widget.isUnsupported || widget._managerAPI.areExperimentalPatchesEnabled()) { + widget.onChanged(widget.isSelected); + } }, child: Column( children: [ @@ -124,11 +128,13 @@ class _PatchItemState extends State { widget.toast.showBottom( 'patchItem.unsupportedPatchVersion', ); - } else { + } else if (widget.isChangeEnabled) { widget.isSelected = newValue!; } }); - widget.onChanged(widget.isSelected); + if (!widget.isUnsupported || widget._managerAPI.areExperimentalPatchesEnabled()) { + widget.onChanged(widget.isSelected); + } }, ), ), diff --git a/lib/ui/widgets/settingsView/settings_advanced_section.dart b/lib/ui/widgets/settingsView/settings_advanced_section.dart index 694aa1d5..2d9be3fc 100644 --- a/lib/ui/widgets/settingsView/settings_advanced_section.dart +++ b/lib/ui/widgets/settingsView/settings_advanced_section.dart @@ -5,6 +5,7 @@ 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/settings_viewmodel.dart'; +import 'package:revanced_manager/ui/widgets/settingsView/settings_enable_patches_selection.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_auto_update_patches.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart'; @@ -25,6 +26,7 @@ class SAdvancedSection extends StatelessWidget { SManageSourcesUI(), // SManageKeystorePasswordUI(), SAutoUpdatePatches(), + SEnablePatchesSelection(), SExperimentalUniversalPatches(), SExperimentalPatches(), ListTile( diff --git a/lib/ui/widgets/settingsView/settings_enable_patches_selection.dart b/lib/ui/widgets/settingsView/settings_enable_patches_selection.dart new file mode 100644 index 00000000..a0c5b463 --- /dev/null +++ b/lib/ui/widgets/settingsView/settings_enable_patches_selection.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_i18n/widgets/I18nText.dart'; +import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart'; + +class SEnablePatchesSelection extends StatefulWidget { + const SEnablePatchesSelection({super.key}); + + @override + State createState() => _SEnablePatchesSelectionState(); +} + +final _settingsViewModel = SettingsViewModel(); + +class _SEnablePatchesSelectionState extends State { + @override + Widget build(BuildContext context) { + return SwitchListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), + title: I18nText( + 'settingsView.enablePatchesSelectionLabel', + child: const Text( + '', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ), + subtitle: I18nText('settingsView.enablePatchesSelectionHint'), + value: _settingsViewModel.isPatchesChangeEnabled(), + onChanged: (value) async { + await _settingsViewModel.showPatchesChangeEnableDialog(value, context); + setState(() {}); + }, + ); + } +} diff --git a/lib/ui/widgets/settingsView/settings_export_section.dart b/lib/ui/widgets/settingsView/settings_export_section.dart index 817f9825..bb693739 100644 --- a/lib/ui/widgets/settingsView/settings_export_section.dart +++ b/lib/ui/widgets/settingsView/settings_export_section.dart @@ -43,7 +43,7 @@ class SExportSection extends StatelessWidget { ), ), subtitle: I18nText('settingsView.importPatchesHint'), - onTap: () => _settingsViewModel.importPatches(), + onTap: () => _settingsViewModel.importPatches(context), ), ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), @@ -73,10 +73,10 @@ class SExportSection extends StatelessWidget { ), ), subtitle: I18nText('settingsView.importKeystoreHint'), - onTap: () async{ + onTap: () async { await _settingsViewModel.importKeystore(); final sManageKeystorePassword = SManageKeystorePassword(); - if(context.mounted){ + if (context.mounted) { sManageKeystorePassword.showKeystoreDialog(context); } }, From 6436a1ec61159916edaa655fa3f6aa27c2781415 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Tue, 15 Aug 2023 20:37:43 +0700 Subject: [PATCH 15/20] build: debug apk come with application suffix Instead of using the same one as Release, app.revanced.manager.flutter - We add .debug to it, yay, co-installation of two variants! --- android/app/build.gradle | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/android/app/build.gradle b/android/app/build.gradle index 65711fda..0f849f6c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -59,6 +59,15 @@ android { abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' } } + debug { + shrinkResources false + minifyEnabled false + signingConfig signingConfigs.debug + applicationIdSuffix ".debug" + ndk { + abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' + } + } } packagingOptions { From 63175cdec648531c9260b9a3478a9c830803955d Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Tue, 15 Aug 2023 20:55:11 +0700 Subject: [PATCH 16/20] ci(build): create debug variant instead of release --- .github/workflows/pr-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index d8ec9809..0bb9bdb1 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -37,9 +37,9 @@ jobs: - name: Build with Flutter env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: flutter build apk + run: flutter build apk --debug - name: Upload build uses: actions/upload-artifact@v3 with: name: revanced-manager - path: build/app/outputs/flutter-apk/app-release.apk + path: build/app/outputs/flutter-apk/app-debug.apk From 43f98cec43fab96c486f5dfdeb7898788c99e17a Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Tue, 15 Aug 2023 21:31:34 +0700 Subject: [PATCH 17/20] build: make variant more identifiable Release variant will use the "ReVanced Manager" app name, Debug will use "ReVanced Manager Debug" --- android/app/build.gradle | 6 +++--- android/app/src/main/AndroidManifest.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 0f849f6c..8663736a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -52,8 +52,7 @@ android { buildTypes { release { - shrinkResources false - minifyEnabled false + resValue "string", "app_name", "ReVanced Manager" signingConfig signingConfigs.debug ndk { abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' @@ -62,8 +61,9 @@ android { debug { shrinkResources false minifyEnabled false - signingConfig signingConfigs.debug + resValue "string", "app_name", "ReVanced Manager Debug" applicationIdSuffix ".debug" + signingConfig signingConfigs.debug ndk { abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 69b3f6d6..85483241 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -20,7 +20,7 @@ Date: Thu, 17 Aug 2023 18:25:23 +0545 Subject: [PATCH 18/20] fix: universal patches not selectable --- lib/ui/views/patches_selector/patches_selector_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ui/views/patches_selector/patches_selector_view.dart b/lib/ui/views/patches_selector/patches_selector_view.dart index a8654dd8..9039c2ce 100644 --- a/lib/ui/views/patches_selector/patches_selector_view.dart +++ b/lib/ui/views/patches_selector/patches_selector_view.dart @@ -241,7 +241,7 @@ class _PatchesSelectorViewState extends State { isSelected: model.isSelected(patch), onChanged: (value) => model.selectPatch( patch, - false, + value, context, ), ); From d9953b14734f5148ff520766a49e9c42023c9138 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Thu, 24 Aug 2023 19:00:50 +0700 Subject: [PATCH 19/20] fix(installer): use correct elevation level --- lib/ui/views/installer/installer_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ui/views/installer/installer_view.dart b/lib/ui/views/installer/installer_view.dart index 47c0e36a..508c449d 100644 --- a/lib/ui/views/installer/installer_view.dart +++ b/lib/ui/views/installer/installer_view.dart @@ -26,6 +26,7 @@ class InstallerView extends StatelessWidget { label: I18nText('installerView.installButton'), icon: const Icon(Icons.file_download_outlined), onPressed: () => model.installTypeDialog(context), + elevation: 0, ), ), floatingActionButtonLocation: From b456512bbb395d1db9748a08a85b26dab09397a4 Mon Sep 17 00:00:00 2001 From: Ushie Date: Sun, 27 Aug 2023 02:21:16 +0300 Subject: [PATCH 20/20] build: bump patcher to v14.1.0 (#1153) Co-authored-by: aAbed --- android/app/build.gradle | 5 +- .../revanced/manager/flutter/MainActivity.kt | 305 +++++++++--------- android/build.gradle | 2 +- 3 files changed, 161 insertions(+), 151 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 8663736a..1e32f0b7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -52,6 +52,8 @@ android { buildTypes { release { + shrinkResources false + minifyEnabled false resValue "string", "app_name", "ReVanced Manager" signingConfig signingConfigs.debug ndk { @@ -83,10 +85,9 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // ReVanced - implementation "app.revanced:revanced-patcher:11.0.4" + implementation "app.revanced:revanced-patcher:14.1.0" // Signing & aligning implementation("org.bouncycastle:bcpkix-jdk15on:1.70") implementation("com.android.tools.build:apksig:7.2.2") - } 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 2c8d7716..b94a582c 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 @@ -1,25 +1,30 @@ package app.revanced.manager.flutter -import android.os.Build import android.os.Handler import android.os.Looper -import androidx.annotation.NonNull import app.revanced.manager.flutter.utils.Aapt import app.revanced.manager.flutter.utils.aligning.ZipAligner import app.revanced.manager.flutter.utils.signing.Signer import app.revanced.manager.flutter.utils.zip.ZipFile import app.revanced.manager.flutter.utils.zip.structures.ZipEntry +import app.revanced.patcher.PatchBundleLoader import app.revanced.patcher.Patcher import app.revanced.patcher.PatcherOptions import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages import app.revanced.patcher.extensions.PatchExtensions.patchName -import app.revanced.patcher.logging.Logger -import app.revanced.patcher.util.patch.PatchBundle -import dalvik.system.DexClassLoader +import app.revanced.patcher.patch.PatchResult import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel +import kotlinx.coroutines.cancel +import kotlinx.coroutines.runBlocking import java.io.File +import java.io.PrintWriter +import java.io.StringWriter +import java.util.logging.Level +import java.util.logging.LogRecord +import java.util.logging.Logger +import java.util.logging.SimpleFormatter private const val PATCHER_CHANNEL = "app.revanced.manager.flutter/patcher" private const val INSTALLER_CHANNEL = "app.revanced.manager.flutter/installer" @@ -30,10 +35,11 @@ class MainActivity : FlutterActivity() { private var cancel: Boolean = false private var stopResult: MethodChannel.Result? = null - override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) val mainChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, PATCHER_CHANNEL) - installerChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, INSTALLER_CHANNEL) + installerChannel = + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, INSTALLER_CHANNEL) mainChannel.setMethodCallHandler { call, result -> when (call.method) { "runPatcher" -> { @@ -77,10 +83,12 @@ class MainActivity : FlutterActivity() { result.notImplemented() } } + "stopPatcher" -> { cancel = true stopResult = result } + else -> result.notImplemented() } } @@ -105,55 +113,79 @@ class MainActivity : FlutterActivity() { val outFile = File(outFilePath) val integrations = File(integrationsPath) val keyStoreFile = File(keyStoreFilePath) + val cacheDir = File(cacheDirPath) Thread { try { + Logger.getLogger("").apply { + handlers.forEach { + it.close() + removeHandler(it) + } + object : java.util.logging.Handler() { + override fun publish(record: LogRecord) = formatter.format(record).toByteArray().let { + if (record.level.intValue() > Level.INFO.intValue()) + System.err.write(it) + else + System.out.write(it) + } + + override fun flush() { + System.out.flush() + System.err.flush() + } + + override fun close() = flush() + }.also { + it.level = Level.ALL + it.formatter = SimpleFormatter() + }.let(::addHandler) + } handler.post { installerChannel.invokeMethod( "update", mapOf( "progress" to 0.1, "header" to "", - "log" to "Copying original apk" + "log" to "Copying original APK" ) ) } - if(cancel) { + if (cancel) { handler.post { stopResult!!.success(null) } return@Thread } originalFile.copyTo(inputFile, true) + if (cancel) { + handler.post { stopResult!!.success(null) } + return@Thread + } + handler.post { installerChannel.invokeMethod( "update", mapOf( "progress" to 0.2, - "header" to "Unpacking apk...", - "log" to "Unpacking input apk" + "header" to "Reading APK...", + "log" to "Reading input APK" ) ) } - if(cancel) { - handler.post { stopResult!!.success(null) } - return@Thread - } - val patcher = Patcher( PatcherOptions( inputFile, - cacheDirPath, + cacheDir, Aapt.binary(applicationContext).absolutePath, - cacheDirPath, - logger = ManagerLogger() + cacheDir.path, ) ) - if(cancel) { + if (cancel) { handler.post { stopResult!!.success(null) } return@Thread } @@ -161,28 +193,19 @@ class MainActivity : FlutterActivity() { handler.post { installerChannel.invokeMethod( "update", - mapOf("progress" to 0.3, "header" to "", "log" to "") - ) - } - handler.post { - installerChannel.invokeMethod( - "update", - mapOf( - "progress" to 0.4, - "header" to "Merging integrations...", - "log" to "Merging integrations" - ) + mapOf("progress" to 0.3, "header" to "Loading patches...", "log" to "Loading patches") ) } - if(cancel) { - handler.post { stopResult!!.success(null) } - return@Thread - } + val patches = + PatchBundleLoader.Dex( + File(patchBundleFilePath) + ).filter { patch -> + (patch.compatiblePackages?.any { it.name == patcher.context.packageMetadata.packageName } == true || patch.compatiblePackages.isNullOrEmpty()) && + selectedPatches.any { it == patch.patchName } + } - patcher.addIntegrations(listOf(integrations)) {} - - if(cancel) { + if (cancel) { handler.post { stopResult!!.success(null) } return@Thread } @@ -192,91 +215,75 @@ class MainActivity : FlutterActivity() { "update", mapOf( "progress" to 0.5, - "header" to "Applying patches...", + "header" to "Executing patches...", "log" to "" ) ) } - if(cancel) { + patcher.apply { + acceptIntegrations(listOf(integrations)) + acceptPatches(patches) + + runBlocking { + apply(false).collect { patchResult: PatchResult -> + patchResult.exception?.let { + if (cancel) { + handler.post { stopResult!!.success(null) } + this.cancel() + return@collect + } + StringWriter().use { writer -> + it.printStackTrace(PrintWriter(writer)) + handler.post { + installerChannel.invokeMethod( + "update", + mapOf("progress" to 0.5, "header" to "", "log" to "${patchResult.patchName} failed: $writer") + ) + } + } + } ?: run { + if (cancel) { + handler.post { stopResult!!.success(null) } + this.cancel() + return@collect + } + val msg = "${patchResult.patchName} succeeded" + handler.post { + installerChannel.invokeMethod( + "update", + mapOf( + "progress" to 0.5, + "header" to "", + "log" to msg + ) + ) + } + } + } + } + } + + if (cancel) { handler.post { stopResult!!.success(null) } return@Thread } - val patches = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) { - PatchBundle.Dex( - patchBundleFilePath, - DexClassLoader( - patchBundleFilePath, - cacheDirPath, - null, - javaClass.classLoader - ) - ).loadPatches().filter { patch -> - (patch.compatiblePackages?.any { it.name == patcher.context.packageMetadata.packageName } == true || patch.compatiblePackages.isNullOrEmpty()) && - selectedPatches.any { it == patch.patchName } - } - } else { - TODO("VERSION.SDK_INT < CUPCAKE") - } - - if(cancel) { - handler.post { stopResult!!.success(null) } - return@Thread - } - - patcher.addPatches(patches) - patcher.executePatches().forEach { (patch, res) -> - if (res.isSuccess) { - val msg = "Applied $patch" - handler.post { - installerChannel.invokeMethod( - "update", - mapOf( - "progress" to 0.5, - "header" to "", - "log" to msg - ) - ) - } - if(cancel) { - handler.post { stopResult!!.success(null) } - return@Thread - } - return@forEach - } - val msg = - "Failed to apply $patch: " + "${res.exceptionOrNull()!!.message ?: res.exceptionOrNull()!!.cause!!::class.simpleName}" - handler.post { - installerChannel.invokeMethod( - "update", - mapOf("progress" to 0.5, "header" to "", "log" to msg) - ) - } - if(cancel) { - handler.post { stopResult!!.success(null) } - return@Thread - } - } - handler.post { installerChannel.invokeMethod( "update", mapOf( "progress" to 0.7, - "header" to "Repacking apk...", - "log" to "Repacking patched apk" + "header" to "Repacking APK...", + "log" to "" ) ) } - if(cancel) { - handler.post { stopResult!!.success(null) } - return@Thread - } - val res = patcher.save() + val res = patcher.get() + patcher.close() ZipFile(patchedFile).use { file -> res.dexFiles.forEach { - if(cancel) { + if (cancel) { handler.post { stopResult!!.success(null) } return@Thread } @@ -296,7 +303,7 @@ class MainActivity : FlutterActivity() { ZipAligner::getEntryAlignment ) } - if(cancel) { + if (cancel) { handler.post { stopResult!!.success(null) } return@Thread } @@ -305,7 +312,7 @@ class MainActivity : FlutterActivity() { "update", mapOf( "progress" to 0.9, - "header" to "Signing apk...", + "header" to "Signing APK...", "log" to "" ) ) @@ -319,7 +326,7 @@ class MainActivity : FlutterActivity() { ) } catch (e: Exception) { //log to console - print("Error signing apk: ${e.message}") + print("Error signing APK: ${e.message}") e.printStackTrace() } @@ -334,52 +341,54 @@ class MainActivity : FlutterActivity() { ) } } catch (ex: Throwable) { - val stack = ex.stackTraceToString() - handler.post { - installerChannel.invokeMethod( - "update", - mapOf( - "progress" to -100.0, - "header" to "Aborted...", - "log" to "An error occurred! Aborted\nError:\n$stack" + if (!cancel) { + val stack = ex.stackTraceToString() + handler.post { + installerChannel.invokeMethod( + "update", + mapOf( + "progress" to -100.0, + "header" to "Aborted...", + "log" to "An error occurred! Aborted\nError:\n$stack" + ) ) - ) + } } } handler.post { result.success(null) } }.start() } - inner class ManagerLogger : Logger { - override fun error(msg: String) { - handler.post { - installerChannel - .invokeMethod( - "update", - mapOf("progress" to -1.0, "header" to "", "log" to msg) - ) - } - } - - override fun warn(msg: String) { - handler.post { - installerChannel.invokeMethod( - "update", - mapOf("progress" to -1.0, "header" to "", "log" to msg) - ) - } - } - - override fun info(msg: String) { - handler.post { - installerChannel.invokeMethod( - "update", - mapOf("progress" to -1.0, "header" to "", "log" to msg) - ) - } - } - - override fun trace(_msg: String) { /* unused */ - } - } +// inner class ManagerLogger : Logger { +// override fun error(msg: String) { +// handler.post { +// installerChannel +// .invokeMethod( +// "update", +// mapOf("progress" to -1.0, "header" to "", "log" to msg) +// ) +// } +// } +// +// override fun warn(msg: String) { +// handler.post { +// installerChannel.invokeMethod( +// "update", +// mapOf("progress" to -1.0, "header" to "", "log" to msg) +// ) +// } +// } +// +// override fun info(msg: String) { +// handler.post { +// installerChannel.invokeMethod( +// "update", +// mapOf("progress" to -1.0, "header" to "", "log" to msg) +// ) +// } +// } +// +// override fun trace(_msg: String) { /* unused */ +// } +// } } diff --git a/android/build.gradle b/android/build.gradle index 88ac491c..bfe266a7 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.9.0' repositories { google() mavenCentral()