mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-31 00:07:25 +02:00
feat(update): force update when 3+ stable releases behind
UpdateChecker now derives the latest release and a releasesBehind count from one releases-list call. When the installed version is forceUpdateThreshold (3) or more stable releases behind, the update dialog becomes mandatory: barrier and back-press are blocked, the later/don't-remind actions are hidden, backing out of the installer returns to the dialog, and the check bypasses the user's check-for-updates opt-out. Prereleases never count toward the threshold.
This commit is contained in:
@@ -29,6 +29,10 @@ class UpdateInfo {
|
||||
final DateTime publishedAt;
|
||||
final bool isPrerelease;
|
||||
|
||||
/// How many stable (non-prerelease) releases are newer than the installed
|
||||
/// version. Drives the forced-update flow.
|
||||
final int releasesBehind;
|
||||
|
||||
const UpdateInfo({
|
||||
required this.version,
|
||||
required this.changelog,
|
||||
@@ -36,63 +40,76 @@ class UpdateInfo {
|
||||
this.apkDownloadUrl,
|
||||
required this.publishedAt,
|
||||
this.isPrerelease = false,
|
||||
this.releasesBehind = 0,
|
||||
});
|
||||
}
|
||||
|
||||
class UpdateChecker {
|
||||
static const String _latestApiUrl =
|
||||
'https://api.github.com/repos/${AppInfo.githubRepo}/releases/latest';
|
||||
static const String _allReleasesApiUrl =
|
||||
'https://api.github.com/repos/${AppInfo.githubRepo}/releases';
|
||||
|
||||
/// Installed versions this many stable releases (or more) behind must
|
||||
/// update before continuing to use the app.
|
||||
static const int forceUpdateThreshold = 3;
|
||||
|
||||
static Future<UpdateInfo?> checkForUpdate({String channel = 'stable'}) async {
|
||||
if (!Platform.isAndroid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final response = await http
|
||||
.get(
|
||||
Uri.parse('$_allReleasesApiUrl?per_page=30'),
|
||||
headers: {'Accept': 'application/vnd.github.v3+json'},
|
||||
)
|
||||
.timeout(const Duration(seconds: 10));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
_log.w('GitHub API returned ${response.statusCode}');
|
||||
return null;
|
||||
}
|
||||
|
||||
final releases = (jsonDecode(response.body) as List<dynamic>)
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.toList();
|
||||
if (releases.isEmpty) {
|
||||
_log.i('No releases found');
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, dynamic>? releaseData;
|
||||
|
||||
if (channel == 'preview') {
|
||||
final response = await http
|
||||
.get(
|
||||
Uri.parse('$_allReleasesApiUrl?per_page=10'),
|
||||
headers: {'Accept': 'application/vnd.github.v3+json'},
|
||||
)
|
||||
.timeout(const Duration(seconds: 10));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
_log.w('GitHub API returned ${response.statusCode}');
|
||||
return null;
|
||||
}
|
||||
|
||||
final releases = jsonDecode(response.body) as List<dynamic>;
|
||||
if (releases.isEmpty) {
|
||||
_log.i('No releases found');
|
||||
return null;
|
||||
}
|
||||
|
||||
releaseData = releases.first as Map<String, dynamic>;
|
||||
releaseData = releases.first;
|
||||
} else {
|
||||
final response = await http
|
||||
.get(
|
||||
Uri.parse(_latestApiUrl),
|
||||
headers: {'Accept': 'application/vnd.github.v3+json'},
|
||||
)
|
||||
.timeout(const Duration(seconds: 10));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
_log.w('GitHub API returned ${response.statusCode}');
|
||||
return null;
|
||||
for (final release in releases) {
|
||||
if (release['prerelease'] != true) {
|
||||
releaseData = release;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
releaseData = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
}
|
||||
if (releaseData == null) {
|
||||
_log.i('No stable release found');
|
||||
return null;
|
||||
}
|
||||
|
||||
final tagName = releaseData['tag_name'] as String? ?? '';
|
||||
final latestVersion = tagName.replaceFirst('v', '');
|
||||
final isPrerelease = releaseData['prerelease'] as bool? ?? false;
|
||||
|
||||
var releasesBehind = 0;
|
||||
for (final release in releases) {
|
||||
if (release['prerelease'] == true) continue;
|
||||
final version = (release['tag_name'] as String? ?? '').replaceFirst(
|
||||
'v',
|
||||
'',
|
||||
);
|
||||
if (version.isNotEmpty && _isNewerVersion(version, AppInfo.version)) {
|
||||
releasesBehind++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_isNewerVersion(latestVersion, AppInfo.version)) {
|
||||
_log.i(
|
||||
'No update available (current: ${AppInfo.version}, latest: $latestVersion, channel: $channel)',
|
||||
@@ -114,7 +131,8 @@ class UpdateChecker {
|
||||
final apkUrl = selectedAsset?.url;
|
||||
|
||||
_log.i(
|
||||
'Update available: $latestVersion (prerelease: $isPrerelease), '
|
||||
'Update available: $latestVersion (prerelease: $isPrerelease, '
|
||||
'releases behind: $releasesBehind), '
|
||||
'APK asset: ${selectedAsset?.name ?? 'none'}, APK URL: $apkUrl',
|
||||
);
|
||||
|
||||
@@ -125,6 +143,7 @@ class UpdateChecker {
|
||||
apkDownloadUrl: apkUrl,
|
||||
publishedAt: publishedAt,
|
||||
isPrerelease: isPrerelease,
|
||||
releasesBehind: releasesBehind,
|
||||
);
|
||||
} catch (e) {
|
||||
_log.e('Error checking for updates: $e');
|
||||
|
||||
Reference in New Issue
Block a user