diff --git a/assets/i18n/en.i18n.json b/assets/i18n/en.i18n.json index cf4fd816..0bc55d3a 100755 --- a/assets/i18n/en.i18n.json +++ b/assets/i18n/en.i18n.json @@ -180,6 +180,9 @@ "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 patches to the latest version", + "usePrereleasesLabel": "Use pre-releases", + "usePrereleasesHint": "Use pre-release versions of ReVanced Manager and ReVanced Patches", + "usePrereleasesWarningText": "Using pre-release versions may cause unexpected issues.\n\nEnable anyways?", "showUpdateDialogLabel": "Show update dialog", "showUpdateDialogHint": "Show a dialog when a new update is available", "universalPatchesLabel": "Show universal patches", diff --git a/lib/services/github_api.dart b/lib/services/github_api.dart index fb9cd4f9..b85706ab 100644 --- a/lib/services/github_api.dart +++ b/lib/services/github_api.dart @@ -33,13 +33,16 @@ class GithubAPI { }); } - Future?> getLatestRelease( - String repoName, - ) async { + Future?> getLatestRelease(String repoName) async { + final String target = + _managerAPI.usePrereleases() ? '?per_page=1' : '/latest'; try { final response = await _dioGetSynchronously( - '/repos/$repoName/releases/latest', + '/repos/$repoName/releases$target', ); + if (_managerAPI.usePrereleases()) { + return response.data.first; + } return response.data; } on Exception catch (e) { if (kDebugMode) { @@ -50,17 +53,19 @@ class GithubAPI { } Future getChangelogs(bool isPatches) async { - final String repoName = isPatches - ? _managerAPI.getPatchesRepo() - : _managerAPI.defaultManagerRepo; + final String repoName = + isPatches + ? _managerAPI.getPatchesRepo() + : _managerAPI.defaultManagerRepo; try { final response = await _dioGetSynchronously( '/repos/$repoName/releases?per_page=50', ); final buffer = StringBuffer(); - final String version = isPatches - ? _managerAPI.getLastUsedPatchesVersion() - : await _managerAPI.getCurrentManagerVersion(); + final String version = + isPatches + ? _managerAPI.getLastUsedPatchesVersion() + : await _managerAPI.getCurrentManagerVersion(); int releases = 0; for (final release in response.data) { if (release['tag_name'] == version) { @@ -70,7 +75,7 @@ class GithubAPI { } break; } - if (release['prerelease']) { + if (!_managerAPI.usePrereleases() && release['prerelease']) { continue; } buffer.writeln(release['body']); @@ -96,25 +101,21 @@ class GithubAPI { ) async { try { if (url.isNotEmpty) { - return await _downloadManager.getSingleFile( - url, - ); + return await _downloadManager.getSingleFile(url); } final response = await _dioGetSynchronously( '/repos/$repoName/releases/tags/$version', ); final Map? release = response.data; if (release != null) { - final Map? asset = - (release['assets'] as List).firstWhereOrNull( - (asset) => (asset['name'] as String).endsWith(extension), - ); + final Map? asset = (release['assets'] as List) + .firstWhereOrNull( + (asset) => (asset['name'] as String).endsWith(extension), + ); if (asset != null) { final String downloadUrl = asset['browser_download_url']; _managerAPI.setPatchesDownloadURL(downloadUrl); - return await _downloadManager.getSingleFile( - downloadUrl, - ); + return await _downloadManager.getSingleFile(downloadUrl); } } } on Exception catch (e) { diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart index 26d51e7c..e8119917 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -36,7 +36,6 @@ class ManagerAPI { Patch? selectedPatch; BuildContext? ctx; bool isRooted = false; - bool releaseBuild = false; bool suggestedAppVersionSelected = true; bool isDynamicThemeAvailable = false; bool isScopedStorageAvailable = false; @@ -63,11 +62,9 @@ class ManagerAPI { isScopedStorageAvailable = sdkVersion >= 30; // ANDROID_11_SDK_VERSION = 30 storedPatchesFile = (await getApplicationDocumentsDirectory()).path + storedPatchesFile; - if (kReleaseMode) { - releaseBuild = !(await getCurrentManagerVersion()).contains('-dev'); - } - final hasMigratedToNewMigrationSystem = _prefs.getBool('migratedToNewApiPrefSystem') ?? false; + final hasMigratedToNewMigrationSystem = + _prefs.getBool('migratedToNewApiPrefSystem') ?? false; if (!hasMigratedToNewMigrationSystem) { final apiUrl = getApiUrl().toLowerCase(); @@ -168,6 +165,18 @@ class ManagerAPI { return _prefs.getBool('patchesAutoUpdate') ?? false; } + bool usePrereleases() { + return _prefs.getBool('usePrereleases') ?? false; + } + + void setPrereleases(bool value) { + _prefs.setBool('usePrereleases', value); + if (isPatchesAutoUpdate()) { + setCurrentPatchesVersion('0.0.0'); + _toast.showBottom(t.settingsView.restartAppForChanges); + } + } + bool isPatchesChangeEnabled() { return _prefs.getBool('patchesChangeEnabled') ?? false; } @@ -207,32 +216,36 @@ class ManagerAPI { List getSavedPatches(String packageName) { final List patchesJson = _prefs.getStringList('savedPatches-$packageName') ?? []; - final List patches = patchesJson.map((String patchJson) { - return Patch.fromJson(jsonDecode(patchJson)); - }).toList(); + final List patches = + patchesJson.map((String patchJson) { + return Patch.fromJson(jsonDecode(patchJson)); + }).toList(); return patches; } Future savePatches(List patches, String packageName) async { - final List patchesJson = patches.map((Patch patch) { - return jsonEncode(patch.toJson()); - }).toList(); + final List patchesJson = + patches.map((Patch patch) { + return jsonEncode(patch.toJson()); + }).toList(); await _prefs.setStringList('savedPatches-$packageName', patchesJson); } List getUsedPatches(String packageName) { final List patchesJson = _prefs.getStringList('usedPatches-$packageName') ?? []; - final List patches = patchesJson.map((String patchJson) { - return Patch.fromJson(jsonDecode(patchJson)); - }).toList(); + final List patches = + patchesJson.map((String patchJson) { + return Patch.fromJson(jsonDecode(patchJson)); + }).toList(); return patches; } Future setUsedPatches(List patches, String packageName) async { - final List patchesJson = patches.map((Patch patch) { - return jsonEncode(patch.toJson()); - }).toList(); + final List patchesJson = + patches.map((Patch patch) { + return jsonEncode(patch.toJson()); + }).toList(); await _prefs.setStringList('usedPatches-$packageName', patchesJson); } @@ -246,8 +259,9 @@ class ManagerAPI { } Option? getPatchOption(String packageName, String patchName, String key) { - final String? optionJson = - _prefs.getString('patchOption-$packageName-$patchName-$key'); + final String? optionJson = _prefs.getString( + 'patchOption-$packageName-$patchName-$key', + ); if (optionJson != null) { final Option option = Option.fromJson(jsonDecode(optionJson)); return option; @@ -340,9 +354,7 @@ class ManagerAPI { } Future deleteKeystore() async { - final File keystore = File( - keystoreFile, - ); + final File keystore = File(keystoreFile); if (await keystore.exists()) { await keystore.delete(); } @@ -364,16 +376,14 @@ class ManagerAPI { Future setLastPatchedApp( PatchedApplication app, - File outFile, + File outFile ) async { + deleteLastPatchedApp(); final Directory appCache = await getApplicationSupportDirectory(); app.patchedFilePath = outFile.copySync('${appCache.path}/lastPatchedApp.apk').path; app.fileSize = outFile.lengthSync(); - await _prefs.setString( - 'lastPatchedApp', - json.encode(app.toJson()), - ); + await _prefs.setString('lastPatchedApp', json.encode(app.toJson())); } List getPatchedApps() { @@ -381,9 +391,7 @@ class ManagerAPI { return apps.map((a) => PatchedApplication.fromJson(jsonDecode(a))).toList(); } - Future setPatchedApps( - List patchedApps, - ) async { + Future setPatchedApps(List patchedApps) async { if (patchedApps.length > 1) { patchedApps.sort((a, b) => a.name.compareTo(b.name)); } @@ -396,10 +404,8 @@ class ManagerAPI { Future savePatchedApp(PatchedApplication app) async { final List patchedApps = getPatchedApps(); patchedApps.removeWhere((a) => a.packageName == app.packageName); - final ApplicationWithIcon? installed = await DeviceApps.getApp( - app.packageName, - true, - ) as ApplicationWithIcon?; + final ApplicationWithIcon? installed = + await DeviceApps.getApp(app.packageName, true) as ApplicationWithIcon?; if (installed != null) { app.name = installed.appName; app.version = installed.versionName!; @@ -439,14 +445,13 @@ class ManagerAPI { try { final String patchesJson = await PatcherAPI.patcherChannel.invokeMethod( 'getPatches', - { - 'patchBundleFilePath': patchBundleFile.path, - }, + {'patchBundleFilePath': patchBundleFile.path}, ); final List patchesJsonList = jsonDecode(patchesJson); - patches = patchesJsonList - .map((patchJson) => Patch.fromJson(patchJson)) - .toList(); + patches = + patchesJsonList + .map((patchJson) => Patch.fromJson(patchJson)) + .toList(); return patches; } on Exception catch (e) { if (kDebugMode) { @@ -491,8 +496,9 @@ class ManagerAPI { } else { final release = await _githubAPI.getLatestRelease(getPatchesRepo()); if (release != null) { - final DateTime timestamp = - DateTime.parse(release['created_at'] as String); + final DateTime timestamp = DateTime.parse( + release['created_at'] as String, + ); return format(timestamp, locale: 'en_short'); } else { return null; @@ -501,22 +507,16 @@ class ManagerAPI { } Future getLatestManagerReleaseTime() async { - return await _revancedAPI.getLatestReleaseTime( - 'manager', - ); + return await _revancedAPI.getLatestReleaseTime('manager'); } Future getLatestManagerVersion() async { - return await _revancedAPI.getLatestReleaseVersion( - 'manager', - ); + return await _revancedAPI.getLatestReleaseVersion('manager'); } Future getLatestPatchesVersion() async { if (!isUsingAlternativeSources()) { - return await _revancedAPI.getLatestReleaseVersion( - 'patches', - ); + return await _revancedAPI.getLatestReleaseVersion('patches'); } else { final release = await _githubAPI.getLatestRelease(getPatchesRepo()); if (release != null) { @@ -530,8 +530,9 @@ class ManagerAPI { String getLastUsedPatchesVersion() { final String lastPatchesVersions = _prefs.getString('lastUsedPatchesVersion') ?? '{}'; - final Map lastPatchesVersionMap = - jsonDecode(lastPatchesVersions); + final Map lastPatchesVersionMap = jsonDecode( + lastPatchesVersions, + ); final String repo = getPatchesRepo(); return lastPatchesVersionMap[repo] ?? '0.0.0'; } @@ -539,8 +540,9 @@ class ManagerAPI { void setLastUsedPatchesVersion({String? version}) { final String lastPatchesVersions = _prefs.getString('lastUsedPatchesVersion') ?? '{}'; - final Map lastPatchesVersionMap = - jsonDecode(lastPatchesVersions); + final Map lastPatchesVersionMap = jsonDecode( + lastPatchesVersions, + ); final repo = getPatchesRepo(); final String lastPatchesVersion = version ?? lastPatchesVersionMap[repo] ?? '0.0.0'; @@ -597,10 +599,8 @@ class ManagerAPI { if (hasRootPermissions) { final List installedApps = await _rootAPI.getInstalledApps(); for (final String packageName in installedApps) { - final ApplicationWithIcon? application = await DeviceApps.getApp( - packageName, - true, - ) as ApplicationWithIcon?; + final ApplicationWithIcon? application = + await DeviceApps.getApp(packageName, true) as ApplicationWithIcon?; if (application != null) { mountedApps.add( PatchedApplication( @@ -621,55 +621,55 @@ class ManagerAPI { } Future showPatchesChangeWarningDialog(BuildContext context) { - final ValueNotifier noShow = - ValueNotifier(!showPatchesChangeWarning()); + final ValueNotifier noShow = ValueNotifier( + !showPatchesChangeWarning(), + ); return showDialog( barrierDismissible: false, context: context, - builder: (context) => PopScope( - canPop: false, - child: AlertDialog( - title: Text(t.warning), - content: ValueListenableBuilder( - valueListenable: noShow, - builder: (context, value, child) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - t.patchItem.patchesChangeWarningDialogText, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 8), - HapticCheckboxListTile( - value: value, - contentPadding: EdgeInsets.zero, - title: Text( - t.noShowAgain, - ), - onChanged: (selected) { - noShow.value = selected!; - }, - ), - ], - ); - }, - ), - actions: [ - FilledButton( - onPressed: () { - setPatchesChangeWarning(noShow.value); - Navigator.of(context).pop(); - }, - child: Text(t.okButton), + builder: + (context) => PopScope( + canPop: false, + child: AlertDialog( + title: Text(t.warning), + content: ValueListenableBuilder( + valueListenable: noShow, + builder: (context, value, child) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + t.patchItem.patchesChangeWarningDialogText, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 8), + HapticCheckboxListTile( + value: value, + contentPadding: EdgeInsets.zero, + title: Text(t.noShowAgain), + onChanged: (selected) { + noShow.value = selected!; + }, + ), + ], + ); + }, + ), + actions: [ + FilledButton( + onPressed: () { + setPatchesChangeWarning(noShow.value); + Navigator.of(context).pop(); + }, + child: Text(t.okButton), + ), + ], ), - ], - ), - ), + ), ); } @@ -677,15 +677,17 @@ class ManagerAPI { final List patchedApps = getPatchedApps(); // Remove apps that are not installed anymore. - final List toRemove = - await getAppsToRemove(patchedApps); + final List toRemove = await getAppsToRemove( + patchedApps, + ); patchedApps.removeWhere((a) => toRemove.contains(a)); // Determine all apps that are installed by mounting. final List mountedApps = await getMountedApps(); mountedApps.removeWhere( - (app) => patchedApps - .any((patchedApp) => patchedApp.packageName == app.packageName), + (app) => patchedApps.any( + (patchedApp) => patchedApp.packageName == app.packageName, + ), ); patchedApps.addAll(mountedApps); @@ -715,10 +717,7 @@ class ManagerAPI { return app != null && app.isSplit; } - Future setSelectedPatches( - String app, - List patches, - ) async { + Future setSelectedPatches(String app, List patches) async { final File selectedPatchesFile = File(storedPatchesFile); final Map patchesMap = await readSelectedPatchesFile(); if (patches.isEmpty) { @@ -774,9 +773,9 @@ class ManagerAPI { final Map settings = _prefs .getKeys() .fold>({}, (Map map, String key) { - map[key] = _prefs.get(key); - return map; - }); + map[key] = _prefs.get(key); + return map; + }); return jsonEncode(settings); } @@ -801,11 +800,11 @@ class ManagerAPI { } void resetAllOptions() { - _prefs.getKeys().where((key) => key.startsWith('patchOption-')).forEach( - (key) { - _prefs.remove(key); - }, - ); + _prefs.getKeys().where((key) => key.startsWith('patchOption-')).forEach(( + key, + ) { + _prefs.remove(key); + }); } Future resetLastSelectedPatches() async { diff --git a/lib/services/revanced_api.dart b/lib/services/revanced_api.dart index 5c722988..80063648 100644 --- a/lib/services/revanced_api.dart +++ b/lib/services/revanced_api.dart @@ -14,6 +14,7 @@ import 'package:timeago/timeago.dart'; class RevancedAPI { late final Dio _dio; late final DownloadManager _downloadManager = locator(); + late final ManagerAPI _managerAPI = locator(); final Lock getToolsLock = Lock(); @@ -43,15 +44,15 @@ class RevancedAPI { return contributors; } - Future?> _getLatestRelease( - String toolName, - ) { + Future?> _getLatestRelease(String toolName) { if (!locator().getDownloadConsent()) { return Future(() => null); } return getToolsLock.synchronized(() async { try { - final response = await _dio.get('/$toolName'); + final response = await _dio.get( + '/$toolName?prerelease=${_managerAPI.usePrereleases()}', + ); return response.data; } on Exception catch (e) { if (kDebugMode) { @@ -62,13 +63,9 @@ class RevancedAPI { }); } - Future getLatestReleaseVersion( - String toolName, - ) async { + Future getLatestReleaseVersion(String toolName) async { try { - final Map? release = await _getLatestRelease( - toolName, - ); + final Map? release = await _getLatestRelease(toolName); if (release != null) { return release['version']; } @@ -81,13 +78,9 @@ class RevancedAPI { return null; } - Future getLatestReleaseFile( - String toolName, - ) async { + Future getLatestReleaseFile(String toolName) async { try { - final Map? release = await _getLatestRelease( - toolName, - ); + final Map? release = await _getLatestRelease(toolName); if (release != null) { final String url = release['download_url']; return await _downloadManager.getSingleFile(url); @@ -136,16 +129,13 @@ class RevancedAPI { return outputFile; } - Future getLatestReleaseTime( - String toolName, - ) async { + Future getLatestReleaseTime(String toolName) async { try { - final Map? release = await _getLatestRelease( - toolName, - ); + final Map? release = await _getLatestRelease(toolName); if (release != null) { - final DateTime timestamp = - DateTime.parse(release['created_at'] as String); + final DateTime timestamp = DateTime.parse( + release['created_at'] as String, + ); return format(timestamp, locale: 'en_short'); } } on Exception catch (e) { diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index 444b9fea..67d3050e 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -82,10 +82,13 @@ class HomeViewModel extends BaseViewModel { ); flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() + AndroidFlutterLocalNotificationsPlugin + >() ?.requestNotificationsPermission(); - final bool isConnected = !(await Connectivity().checkConnectivity()) - .contains(ConnectivityResult.none); + final bool isConnected = + !(await Connectivity().checkConnectivity()).contains( + ConnectivityResult.none, + ); if (!isConnected) { _toast.showBottom(t.homeView.noConnection); } @@ -106,8 +109,10 @@ class HomeViewModel extends BaseViewModel { void navigateToAppInfo(PatchedApplication app, bool isLastPatchedApp) { _navigationService.navigateTo( Routes.appInfoView, - arguments: - AppInfoViewArguments(app: app, isLastPatchedApp: isLastPatchedApp), + arguments: AppInfoViewArguments( + app: app, + isLastPatchedApp: isLastPatchedApp, + ), ); } @@ -118,8 +123,8 @@ class HomeViewModel extends BaseViewModel { Future navigateToPatcher(PatchedApplication app) async { locator().selectedApp = app; - locator().selectedPatches = - await _patcherAPI.getAppliedPatches(app.appliedPatches); + locator().selectedPatches = await _patcherAPI + .getAppliedPatches(app.appliedPatches); locator().notifyListeners(); locator().setIndex(1); } @@ -135,9 +140,6 @@ class HomeViewModel extends BaseViewModel { } Future hasManagerUpdates() async { - if (!_managerAPI.releaseBuild) { - return false; - } latestManagerVersion = await _managerAPI.getLatestManagerVersion() ?? _currentManagerVersion; @@ -151,10 +153,12 @@ class HomeViewModel extends BaseViewModel { latestPatchesVersion = await _managerAPI.getLatestPatchesVersion(); if (latestPatchesVersion != null) { try { - final int latestVersionInt = - int.parse(latestPatchesVersion!.replaceAll(RegExp('[^0-9]'), '')); - final int currentVersionInt = - int.parse(_currentPatchesVersion.replaceAll(RegExp('[^0-9]'), '')); + final int latestVersionInt = int.parse( + latestPatchesVersion!.replaceAll(RegExp('[^0-9]'), ''), + ); + final int currentVersionInt = int.parse( + _currentPatchesVersion.replaceAll(RegExp('[^0-9]'), ''), + ); return latestVersionInt > currentVersionInt; } on Exception catch (e) { if (kDebugMode) { @@ -187,123 +191,128 @@ class HomeViewModel extends BaseViewModel { await showDialog( context: context, barrierDismissible: false, - builder: (context) => PopScope( - canPop: false, - child: AlertDialog( - title: Text(t.homeView.downloadConsentDialogTitle), - content: ValueListenableBuilder( - valueListenable: autoUpdate, - builder: (context, value, child) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - t.homeView.downloadConsentDialogText, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.secondary, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Text( - t.homeView.downloadConsentDialogText2( - url: _managerAPI.defaultApiUrl.split('/')[2], + builder: + (context) => PopScope( + canPop: false, + child: AlertDialog( + title: Text(t.homeView.downloadConsentDialogTitle), + content: ValueListenableBuilder( + valueListenable: autoUpdate, + builder: (context, value, child) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + t.homeView.downloadConsentDialogText, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.secondary, + ), ), - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.error, + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + t.homeView.downloadConsentDialogText2( + url: _managerAPI.defaultApiUrl.split('/')[2], + ), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.error, + ), + ), ), - ), - ), - ], - ); - }, + ], + ); + }, + ), + actions: [ + TextButton( + onPressed: () async { + _managerAPI.setDownloadConsent(false); + SystemNavigator.pop(); + }, + child: Text(t.quitButton), + ), + FilledButton( + onPressed: () async { + _managerAPI.setDownloadConsent(true); + _managerAPI.setPatchesAutoUpdate(autoUpdate.value); + Navigator.of(context).pop(); + }, + child: Text(t.okButton), + ), + ], + ), ), - actions: [ - TextButton( - onPressed: () async { - _managerAPI.setDownloadConsent(false); - SystemNavigator.pop(); - }, - child: Text(t.quitButton), - ), - FilledButton( - onPressed: () async { - _managerAPI.setDownloadConsent(true); - _managerAPI.setPatchesAutoUpdate(autoUpdate.value); - Navigator.of(context).pop(); - }, - child: Text(t.okButton), - ), - ], - ), - ), ); } void showUpdateDialog(BuildContext context, bool isPatches) { - final ValueNotifier noShow = - ValueNotifier(!_managerAPI.showUpdateDialog()); + final ValueNotifier noShow = ValueNotifier( + !_managerAPI.showUpdateDialog(), + ); showDialog( context: context, - builder: (innerContext) => AlertDialog( - title: Text(t.homeView.updateDialogTitle), - content: ValueListenableBuilder( - valueListenable: noShow, - builder: (context, value, child) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - t.homeView.updateDialogText( - file: isPatches ? 'ReVanced Patches' : 'ReVanced Manager', - version: isPatches - ? _currentPatchesVersion - : _currentManagerVersion, - ), - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.secondary, - ), - ), - const SizedBox(height: 10), - HapticCheckboxListTile( - value: value, - contentPadding: EdgeInsets.zero, - title: Text(t.noShowAgain), - subtitle: Text(t.homeView.changeLaterSubtitle), - onChanged: (selected) { - noShow.value = selected!; - }, - ), - ], - ); - }, - ), - actions: [ - TextButton( - onPressed: () async { - _managerAPI.setShowUpdateDialog(!noShow.value); - Navigator.pop(innerContext); - }, - child: Text(t.dismissButton), // Decide later + builder: + (innerContext) => AlertDialog( + title: Text(t.homeView.updateDialogTitle), + content: ValueListenableBuilder( + valueListenable: noShow, + builder: (context, value, child) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + t.homeView.updateDialogText( + file: + isPatches ? 'ReVanced Patches' : 'ReVanced Manager', + version: + isPatches + ? _currentPatchesVersion + : _currentManagerVersion, + ), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.secondary, + ), + ), + const SizedBox(height: 10), + HapticCheckboxListTile( + value: value, + contentPadding: EdgeInsets.zero, + title: Text(t.noShowAgain), + subtitle: Text(t.homeView.changeLaterSubtitle), + onChanged: (selected) { + noShow.value = selected!; + }, + ), + ], + ); + }, + ), + actions: [ + TextButton( + onPressed: () async { + _managerAPI.setShowUpdateDialog(!noShow.value); + Navigator.pop(innerContext); + }, + child: Text(t.dismissButton), // Decide later + ), + FilledButton( + onPressed: () async { + _managerAPI.setShowUpdateDialog(!noShow.value); + Navigator.pop(innerContext); + await showUpdateConfirmationDialog(context, isPatches); + }, + child: Text(t.showUpdateButton), + ), + ], ), - FilledButton( - onPressed: () async { - _managerAPI.setShowUpdateDialog(!noShow.value); - Navigator.pop(innerContext); - await showUpdateConfirmationDialog(context, isPatches); - }, - child: Text(t.showUpdateButton), - ), - ], - ), ); } @@ -326,94 +335,95 @@ class HomeViewModel extends BaseViewModel { _toast.showBottom(t.homeView.downloadingMessage); showDialog( context: context, - builder: (context) => ValueListenableBuilder( - valueListenable: downloaded, - builder: (context, value, child) { - return AlertDialog( - title: Text( - !value - ? t.homeView.downloadingMessage - : t.homeView.downloadedMessage, - ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (!value) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - StreamBuilder( - initialData: 0.0, - stream: _revancedAPI.managerUpdateProgress.stream, - builder: (context, snapshot) { - return LinearProgressIndicator( - value: snapshot.data! * 0.01, - valueColor: AlwaysStoppedAnimation( - Theme.of(context).colorScheme.secondary, - ), - ); - }, - ), - const SizedBox(height: 16.0), - Align( - alignment: Alignment.centerRight, - child: FilledButton( - onPressed: () { - _revancedAPI.disposeManagerUpdateProgress(); - Navigator.of(context).pop(); - }, - child: Text(t.cancelButton), - ), - ), - ], - ), - if (value) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - t.homeView.installUpdate, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.secondary, - ), - ), - const SizedBox(height: 16.0), - Row( - mainAxisAlignment: MainAxisAlignment.end, + builder: + (context) => ValueListenableBuilder( + valueListenable: downloaded, + builder: (context, value, child) { + return AlertDialog( + title: Text( + !value + ? t.homeView.downloadingMessage + : t.homeView.downloadedMessage, + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (!value) + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + StreamBuilder( + initialData: 0.0, + stream: _revancedAPI.managerUpdateProgress.stream, + builder: (context, snapshot) { + return LinearProgressIndicator( + value: snapshot.data! * 0.01, + valueColor: AlwaysStoppedAnimation( + Theme.of(context).colorScheme.secondary, + ), + ); + }, + ), + const SizedBox(height: 16.0), Align( alignment: Alignment.centerRight, - child: TextButton( + child: FilledButton( onPressed: () { + _revancedAPI.disposeManagerUpdateProgress(); Navigator.of(context).pop(); }, child: Text(t.cancelButton), ), ), - const SizedBox(width: 8.0), - Align( - alignment: Alignment.centerRight, - child: FilledButton( - onPressed: () async { - await _patcherAPI.installApk( - context, - downloadedApk!.path, - ); - }, - child: Text(t.updateButton), + ], + ), + if (value) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + t.homeView.installUpdate, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.secondary, ), ), + const SizedBox(height: 16.0), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text(t.cancelButton), + ), + ), + const SizedBox(width: 8.0), + Align( + alignment: Alignment.centerRight, + child: FilledButton( + onPressed: () async { + await _patcherAPI.installApk( + context, + downloadedApk!.path, + ); + }, + child: Text(t.updateButton), + ), + ), + ], + ), ], ), - ], - ), - ], - ), - ); - }, - ), + ], + ), + ); + }, + ), ); final File? managerApk = await downloadManager(); if (managerApk != null) { @@ -468,10 +478,11 @@ class HomeViewModel extends BaseViewModel { shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24.0)), ), - builder: (context) => UpdateConfirmationSheet( - isPatches: isPatches, - changelog: changelog, - ), + builder: + (context) => UpdateConfirmationSheet( + isPatches: isPatches, + changelog: changelog, + ), ); } diff --git a/lib/ui/views/settings/settings_viewmodel.dart b/lib/ui/views/settings/settings_viewmodel.dart index 542eef42..041176e7 100644 --- a/lib/ui/views/settings/settings_viewmodel.dart +++ b/lib/ui/views/settings/settings_viewmodel.dart @@ -40,6 +40,10 @@ class SettingsViewModel extends BaseViewModel { notifyListeners(); } + bool usePrereleases() { + return _managerAPI.usePrereleases(); + } + bool showUpdateDialog() { return _managerAPI.showUpdateDialog(); } @@ -64,6 +68,45 @@ class SettingsViewModel extends BaseViewModel { return _managerAPI.isUsingAlternativeSources(); } + Future showUsePrereleasesDialog( + BuildContext context, + bool value, + ) async { + if (value) { + return showDialog( + context: context, + builder: + (context) => AlertDialog( + title: Text(t.warning), + content: Text( + t.settingsView.usePrereleasesWarningText, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + actions: [ + TextButton( + onPressed: () { + _managerAPI.setPrereleases(true); + Navigator.of(context).pop(); + }, + child: Text(t.yesButton), + ), + FilledButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text(t.noButton), + ), + ], + ), + ); + } else { + _managerAPI.setPrereleases(false); + } + } + Future showPatchesChangeEnableDialog( bool value, BuildContext context, @@ -71,63 +114,65 @@ class SettingsViewModel extends BaseViewModel { if (value) { return showDialog( context: context, - builder: (context) => AlertDialog( - title: Text(t.warning), - content: Text( - t.settingsView.enablePatchesSelectionWarningText, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, + builder: + (context) => AlertDialog( + title: Text(t.warning), + content: Text( + t.settingsView.enablePatchesSelectionWarningText, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + actions: [ + TextButton( + onPressed: () { + _managerAPI.setChangingToggleModified(true); + _managerAPI.setPatchesChangeEnabled(true); + Navigator.of(context).pop(); + }, + child: Text(t.yesButton), + ), + FilledButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text(t.noButton), + ), + ], ), - ), - actions: [ - TextButton( - onPressed: () { - _managerAPI.setChangingToggleModified(true); - _managerAPI.setPatchesChangeEnabled(true); - Navigator.of(context).pop(); - }, - child: Text(t.yesButton), - ), - FilledButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text(t.noButton), - ), - ], - ), ); } else { return showDialog( context: context, - builder: (context) => AlertDialog( - title: Text(t.warning), - content: Text( - t.settingsView.disablePatchesSelectionWarningText, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, + builder: + (context) => AlertDialog( + title: Text(t.warning), + content: Text( + t.settingsView.disablePatchesSelectionWarningText, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text(t.noButton), + ), + FilledButton( + onPressed: () { + _managerAPI.setChangingToggleModified(true); + _patchesSelectorViewModel.selectDefaultPatches(); + _managerAPI.setPatchesChangeEnabled(false); + Navigator.of(context).pop(); + }, + child: Text(t.yesButton), + ), + ], ), - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text(t.noButton), - ), - FilledButton( - onPressed: () { - _managerAPI.setChangingToggleModified(true); - _patchesSelectorViewModel.selectDefaultPatches(); - _managerAPI.setPatchesChangeEnabled(false); - Navigator.of(context).pop(); - }, - child: Text(t.yesButton), - ), - ], - ), ); } } @@ -173,31 +218,32 @@ class SettingsViewModel extends BaseViewModel { if (!value) { return showDialog( context: context, - builder: (context) => AlertDialog( - title: Text(t.warning), - content: Text( - t.settingsView.requireSuggestedAppVersionDialogText, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, + builder: + (context) => AlertDialog( + title: Text(t.warning), + content: Text( + t.settingsView.requireSuggestedAppVersionDialogText, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + actions: [ + TextButton( + onPressed: () { + _managerAPI.enableRequireSuggestedAppVersionStatus(false); + Navigator.of(context).pop(); + }, + child: Text(t.yesButton), + ), + FilledButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text(t.noButton), + ), + ], ), - ), - actions: [ - TextButton( - onPressed: () { - _managerAPI.enableRequireSuggestedAppVersionStatus(false); - Navigator.of(context).pop(); - }, - child: Text(t.yesButton), - ), - FilledButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text(t.noButton), - ), - ], - ), ); } else { _managerAPI.enableRequireSuggestedAppVersionStatus(true); @@ -249,9 +295,7 @@ class SettingsViewModel extends BaseViewModel { Future importSettings() async { try { final String? result = await FlutterFileDialog.pickFile( - params: const OpenFileDialogParams( - fileExtensionsFilter: ['json'], - ), + params: const OpenFileDialogParams(fileExtensionsFilter: ['json']), ); if (result != null) { final File inFile = File(result); @@ -298,9 +342,7 @@ class SettingsViewModel extends BaseViewModel { if (isPatchesChangeEnabled()) { try { final String? result = await FlutterFileDialog.pickFile( - params: const OpenFileDialogParams( - fileExtensionsFilter: ['json'], - ), + params: const OpenFileDialogParams(fileExtensionsFilter: ['json']), ); if (result != null) { final File inFile = File(result); @@ -393,8 +435,9 @@ class SettingsViewModel extends BaseViewModel { .replaceAll(':', '') .replaceAll('T', '') .replaceAll('.', ''); - final File logcat = - File('${logDir.path}/revanced-manager_logcat_$dateTime.log'); + final File logcat = File( + '${logDir.path}/revanced-manager_logcat_$dateTime.log', + ); final String logs = await Logcat.execute(); logcat.writeAsStringSync(logs); await Share.shareXFiles([XFile(logcat.path)]); diff --git a/lib/ui/widgets/settingsView/settings_data_section.dart b/lib/ui/widgets/settingsView/settings_data_section.dart index b464038e..f4ab5ee5 100644 --- a/lib/ui/widgets/settingsView/settings_data_section.dart +++ b/lib/ui/widgets/settingsView/settings_data_section.dart @@ -5,6 +5,7 @@ import 'package:revanced_manager/gen/strings.g.dart'; import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_api_url.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_use_alternative_sources.dart'; +import 'package:revanced_manager/ui/widgets/settingsView/settings_use_prereleases.dart'; class SDataSection extends StatelessWidget { const SDataSection({super.key}); @@ -15,6 +16,7 @@ class SDataSection extends StatelessWidget { title: t.settingsView.dataSectionTitle, children: const [ SManageApiUrlUI(), + SUsePrereleases(), SUseAlternativeSources(), ], ); diff --git a/lib/ui/widgets/settingsView/settings_use_prereleases.dart b/lib/ui/widgets/settingsView/settings_use_prereleases.dart new file mode 100644 index 00000000..fc18737c --- /dev/null +++ b/lib/ui/widgets/settingsView/settings_use_prereleases.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:revanced_manager/gen/strings.g.dart'; +import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart'; +import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_switch_list_tile.dart'; + +class SUsePrereleases extends StatefulWidget { + const SUsePrereleases({super.key}); + + @override + State createState() => _SUsePrereleasesState(); +} + +final _settingsViewModel = SettingsViewModel(); + +class _SUsePrereleasesState extends State { + @override + Widget build(BuildContext context) { + return HapticSwitchListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), + title: Text( + t.settingsView.usePrereleasesLabel, + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500), + ), + subtitle: Text(t.settingsView.usePrereleasesHint), + value: _settingsViewModel.usePrereleases(), + onChanged: (value) async { + await _settingsViewModel.showUsePrereleasesDialog(context, value); + setState(() {}); + }, + ); + } +}