Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Pre-release patches toggle #1785

Draft
wants to merge 7 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions assets/i18n/strings.i18n.json
Expand Up @@ -208,6 +208,10 @@
"requireSuggestedAppVersionLabel": "Require suggested app version",
"requireSuggestedAppVersionHint": "Prevent selecting an app with a version that is not the suggested",
"requireSuggestedAppVersionDialogText": "Selecting an app that is not the suggested version may cause unexpected issues.\n\nDo you want to proceed anyways?",
"usePrereleasePatchesLabel": "Use pre-release patches",
"usePrereleasePatchesHint": "Use the pre-release (dev) versions of patches and integrations if available",
"usePrereleasePatchesDialogText": "Pre-release patches are for development purposes and may cause unexpected issues.",
"usePrereleasePatchesDialogText2": "It may not be compatible with the current version of ReVanced Manager and may cause patching errors.\n\nDo you want to proceed anyways?",
"aboutLabel": "About",
"snackbarMessage": "Copied to clipboard",
"restartAppForChanges": "Restart the app to apply changes",
Expand Down
28 changes: 28 additions & 0 deletions lib/services/github_api.dart
Expand Up @@ -37,6 +37,34 @@ class GithubAPI {
}
}

Future<Map<String, dynamic>?> getLatestReleaseWithPreReleases(
String repoName,
) async {
try {
final response = await _dio.get('/repos/$repoName/releases?per_page=10');
final List<dynamic> releases = response.data;

/*
* Loop through all releases (including pre-releases) and return the latest
*/
Map<String, dynamic>? latestRelease;
DateTime latestReleaseDate = DateTime.fromMillisecondsSinceEpoch(0);
for (final release in releases) {
final DateTime releaseDate = DateTime.parse(release['published_at']);
if (releaseDate.isAfter(latestReleaseDate)) {
latestReleaseDate = releaseDate;
latestRelease = release;
}
}
return latestRelease;
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
return null;
}
}

Future<Map<String, dynamic>?> getLatestManagerRelease(
String repoName,
) async {
Expand Down
57 changes: 40 additions & 17 deletions lib/services/manager_api.dart
Expand Up @@ -29,6 +29,8 @@ class ManagerAPI {
final RootAPI _rootAPI = RootAPI();
final String patcherRepo = 'revanced-patcher';
final String cliRepo = 'revanced-cli';
final String patchesRepo = 'revanced-patches';
final String integrationsRepo = 'revanced-integrations';
late SharedPreferences _prefs;
List<Patch> patches = [];
List<Option> modifiedOptions = [];
Expand Down Expand Up @@ -222,6 +224,14 @@ class ManagerAPI {
return _prefs.getBool('useAlternativeSources') ?? false;
}

Future<void> setPreReleasePatchesEnabled(bool value) async {
await _prefs.setBool('usePrereleasePatches', value);
}

bool isPreReleasePatchesEnabled() {
return _prefs.getBool('usePrereleasePatches') ?? false;
}

Option? getPatchOption(String packageName, String patchName, String key) {
final String? optionJson =
_prefs.getString('patchOption-$packageName-$patchName-$key');
Expand Down Expand Up @@ -464,13 +474,16 @@ class ManagerAPI {

Future<String?> getLatestPatchesReleaseTime() async {
if (!isUsingAlternativeSources()) {
return await _revancedAPI.getLatestReleaseTime(
'.json',
defaultPatchesRepo,
);
return !isPreReleasePatchesEnabled()
? await _revancedAPI.getLatestReleaseTime(
'.json',
defaultPatchesRepo,
)
: await _revancedAPI.getLatestReleaseTimeWithPreReleases(patchesRepo);
} else {
final release =
await _githubAPI.getLatestRelease(getPatchesRepo());
final release = !isPreReleasePatchesEnabled()
? await _githubAPI.getLatestRelease(getPatchesRepo())
: await _githubAPI.getLatestReleaseWithPreReleases(getPatchesRepo());
if (release != null) {
final DateTime timestamp =
DateTime.parse(release['created_at'] as String);
Expand All @@ -497,12 +510,18 @@ class ManagerAPI {

Future<String?> getLatestIntegrationsVersion() async {
if (!isUsingAlternativeSources()) {
return await _revancedAPI.getLatestReleaseVersion(
'.apk',
defaultIntegrationsRepo,
);
return !isPreReleasePatchesEnabled()
? await _revancedAPI.getLatestReleaseVersion(
'.apk',
defaultIntegrationsRepo,
)
: await _revancedAPI.getLatestReleaseVersionWithPreReleases(
integrationsRepo,
);
} else {
final release = await _githubAPI.getLatestRelease(getIntegrationsRepo());
final release = !isPreReleasePatchesEnabled()
? await _githubAPI.getLatestRelease(getIntegrationsRepo())
: await _githubAPI.getLatestReleaseWithPreReleases(getIntegrationsRepo());
if (release != null) {
return release['tag_name'];
} else {
Expand All @@ -513,13 +532,17 @@ class ManagerAPI {

Future<String?> getLatestPatchesVersion() async {
if (!isUsingAlternativeSources()) {
return await _revancedAPI.getLatestReleaseVersion(
'.json',
defaultPatchesRepo,
);
return !isPreReleasePatchesEnabled()
? await _revancedAPI.getLatestReleaseVersion(
'.json',
defaultPatchesRepo,
)
: await _revancedAPI
.getLatestReleaseVersionWithPreReleases(patchesRepo);
} else {
final release =
await _githubAPI.getLatestRelease(getPatchesRepo());
final release = !isPreReleasePatchesEnabled()
? await _githubAPI.getLatestRelease(getPatchesRepo())
: await _githubAPI.getLatestReleaseWithPreReleases(getPatchesRepo());
if (release != null) {
return release['tag_name'];
} else {
Expand Down
59 changes: 59 additions & 0 deletions lib/services/revanced_api.dart
Expand Up @@ -18,6 +18,7 @@ class RevancedAPI {
late final DownloadManager _downloadManager = locator<DownloadManager>();

final Lock getToolsLock = Lock();
final Lock getV2Lock = Lock();

Future<void> initialize(String repoUrl) async {
_dio = _downloadManager.initDio(repoUrl);
Expand Down Expand Up @@ -173,4 +174,62 @@ class RevancedAPI {
}
return null;
}

Future<Map<String, dynamic>?> _getLatestReleaseWithPreReleases(
String repoName, // must not contain organization name
) async {
if (!locator<ManagerAPI>().getDownloadConsent()) {
return Future(() => null);
}
return getV2Lock.synchronized(() async {
try {
final response =
await _dio.get('/v2/$repoName/releases/latest?dev=true');
return response.data['release'];
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
return null;
}
});
}

Future<String?> getLatestReleaseVersionWithPreReleases(
String repoName,
) async {
try {
final Map<String, dynamic>? release =
await _getLatestReleaseWithPreReleases(repoName);
if (release != null) {
return release['metadata']['tag_name'];
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
return null;
}
return null;
}

Future<String?> getLatestReleaseTimeWithPreReleases(
String repoName,
) async {
try {
final Map<String, dynamic>? release =
await _getLatestReleaseWithPreReleases(repoName);
if (release != null) {
final DateTime timestamp =
DateTime.parse(release['metadata']['published_at'] as String);
return format(timestamp, locale: 'en_short');
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
return null;
}
return null;
}
}
7 changes: 6 additions & 1 deletion lib/ui/views/home/home_viewmodel.dart
Expand Up @@ -480,7 +480,12 @@ class HomeViewModel extends BaseViewModel {
}

Future<Map<String, dynamic>?> getLatestPatchesRelease() {
return _githubAPI.getLatestRelease(_managerAPI.defaultPatchesRepo);
if (_managerAPI.isPreReleasePatchesEnabled()) {
return _githubAPI
.getLatestReleaseWithPreReleases(_managerAPI.getPatchesRepo());
} else {
return _githubAPI.getLatestRelease(_managerAPI.getPatchesRepo());
}
}

Future<String?> getLatestPatchesReleaseTime() {
Expand Down
60 changes: 60 additions & 0 deletions lib/ui/views/settings/settings_viewmodel.dart
Expand Up @@ -198,6 +198,66 @@ class SettingsViewModel extends BaseViewModel {
}
}

bool isPreReleasePatchesEnabled() {
return _managerAPI.isPreReleasePatchesEnabled();
}

Future<void>? showUsePreReleasePatchesDialog(
BuildContext context,
bool value,
) {
if (value) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(t.warning),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
t.settingsView.usePrereleasePatchesDialogText,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 16),
Text(
t.settingsView.usePrereleasePatchesDialogText2,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.error,
),
),
],
),
actions: [
TextButton(
onPressed: () {
_managerAPI.setPreReleasePatchesEnabled(true);
_toast.showBottom(t.settingsView.restartAppForChanges);
Navigator.of(context).pop();
},
child: Text(t.yesButton),
),
FilledButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(t.noButton),
),
],
),
);
} else {
_managerAPI.setPreReleasePatchesEnabled(false);
_toast.showBottom(t.settingsView.restartAppForChanges);
}
return null;
}

void deleteKeystore() {
_managerAPI.deleteKeystore();
_toast.showBottom(t.settingsView.regeneratedKeystore);
Expand Down
2 changes: 2 additions & 0 deletions lib/ui/widgets/settingsView/settings_data_section.dart
Expand Up @@ -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_prerelease_patches.dart';

class SDataSection extends StatelessWidget {
const SDataSection({super.key});
Expand All @@ -16,6 +17,7 @@ class SDataSection extends StatelessWidget {
children: const <Widget>[
SManageApiUrlUI(),
SUseAlternativeSources(),
SUsePrereleasePatches(),
],
);
}
Expand Down
Expand Up @@ -31,8 +31,9 @@ class _SUseAlternativeSourcesState extends State<SUseAlternativeSources> {
subtitle: Text(t.settingsView.useAlternativeSourcesHint),
value: _settingsViewModel.isUsingAlternativeSources(),
onChanged: (value) {
_settingsViewModel.useAlternativeSources(value);
setState(() {});
setState(() {
_settingsViewModel.useAlternativeSources(value);
});
},
),
if (_settingsViewModel.isUsingAlternativeSources())
Expand Down
35 changes: 35 additions & 0 deletions lib/ui/widgets/settingsView/settings_use_prerelease_patches.dart
@@ -0,0 +1,35 @@
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 SUsePrereleasePatches extends StatefulWidget {
const SUsePrereleasePatches({super.key});

@override
State<SUsePrereleasePatches> createState() => _SUsePrereleasePatchesState();
}

final _settingsViewModel = SettingsViewModel();

class _SUsePrereleasePatchesState extends State<SUsePrereleasePatches> {
@override
Widget build(BuildContext context) {
return HapticSwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: Text(
t.settingsView.usePrereleasePatchesLabel,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
subtitle: Text(t.settingsView.usePrereleasePatchesHint),
value: _settingsViewModel.isPreReleasePatchesEnabled(),
onChanged: (value) async {
await _settingsViewModel.showUsePreReleasePatchesDialog(context, value);
setState(() {});
},
);
}
}