feat(settings): add manual update check
This commit is contained in:
@@ -22,6 +22,22 @@ class AppUpdateInfo {
|
||||
});
|
||||
}
|
||||
|
||||
enum UpdateCheckStatus { updateAvailable, upToDate, failed }
|
||||
|
||||
class UpdateCheckResult {
|
||||
final UpdateCheckStatus status;
|
||||
final AppUpdateInfo? update;
|
||||
|
||||
const UpdateCheckResult._(this.status, [this.update]);
|
||||
|
||||
const UpdateCheckResult.updateAvailable(AppUpdateInfo update)
|
||||
: this._(UpdateCheckStatus.updateAvailable, update);
|
||||
|
||||
const UpdateCheckResult.upToDate() : this._(UpdateCheckStatus.upToDate);
|
||||
|
||||
const UpdateCheckResult.failed() : this._(UpdateCheckStatus.failed);
|
||||
}
|
||||
|
||||
abstract class UpdateChecker {
|
||||
static const String _owner = 'KometTeam';
|
||||
static const String _repo = 'Komet';
|
||||
@@ -96,6 +112,21 @@ abstract class UpdateChecker {
|
||||
return update;
|
||||
}
|
||||
|
||||
/// Runs a user-initiated check without applying the automatic-check interval
|
||||
/// or the "skip this version" preference.
|
||||
static Future<UpdateCheckResult> checkNow() async {
|
||||
try {
|
||||
final update = await fetchLatest();
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt(_lastCheckKey, DateTime.now().millisecondsSinceEpoch);
|
||||
return update == null
|
||||
? const UpdateCheckResult.upToDate()
|
||||
: UpdateCheckResult.updateAvailable(update);
|
||||
} catch (_) {
|
||||
return const UpdateCheckResult.failed();
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> skip(String tag) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_skippedTagKey, tag);
|
||||
@@ -117,22 +148,25 @@ abstract class UpdateChecker {
|
||||
final resp = await req.close().timeout(_timeout);
|
||||
if (resp.statusCode != HttpStatus.ok) {
|
||||
await resp.drain<void>();
|
||||
return null;
|
||||
throw HttpException(
|
||||
'GitHub returned HTTP ${resp.statusCode}',
|
||||
uri: uri,
|
||||
);
|
||||
}
|
||||
final body = await resp
|
||||
.transform(const Utf8Decoder())
|
||||
.join()
|
||||
.timeout(_timeout);
|
||||
final decoded = jsonDecode(body);
|
||||
if (decoded is! List) return null;
|
||||
if (decoded is! List) {
|
||||
throw const FormatException('Invalid GitHub releases response');
|
||||
}
|
||||
for (final entry in decoded) {
|
||||
if (entry is! Map) continue;
|
||||
if (entry['draft'] == true) continue;
|
||||
return entry.cast<String, dynamic>();
|
||||
}
|
||||
return null;
|
||||
} catch (_) {
|
||||
return null;
|
||||
} finally {
|
||||
client.close(force: true);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import '../../../core/config/komet_settings.dart';
|
||||
import '../../../core/config/app_show_extra_info.dart';
|
||||
import '../../../core/storage/app_database.dart';
|
||||
import '../../../core/utils/format.dart';
|
||||
import '../../../core/utils/update_checker.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../widgets/avatar_history_screen.dart';
|
||||
@@ -19,6 +20,7 @@ import '../../widgets/settings_card.dart';
|
||||
import '../../widgets/sheet_helpers.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/update_dialog.dart';
|
||||
import '../auth/login_screen.dart';
|
||||
import '../auth/proxy_settings_sheet.dart';
|
||||
import '../../../core/config/app_digital_id_mode.dart';
|
||||
@@ -49,6 +51,7 @@ class _SettingsTabState extends State<SettingsTab> {
|
||||
bool _isPhoneVisible = false;
|
||||
String? _appVersionLabel;
|
||||
bool _debugMenuVisible = false;
|
||||
bool _isCheckingForUpdates = false;
|
||||
int _versionSecretTapCount = 0;
|
||||
Timer? _versionSecretTapResetTimer;
|
||||
StreamSubscription? _profileUpdateSub;
|
||||
@@ -105,6 +108,33 @@ class _SettingsTabState extends State<SettingsTab> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _checkForUpdates() async {
|
||||
if (_isCheckingForUpdates) return;
|
||||
setState(() => _isCheckingForUpdates = true);
|
||||
|
||||
final result = await UpdateChecker.checkNow();
|
||||
if (!mounted) return;
|
||||
setState(() => _isCheckingForUpdates = false);
|
||||
|
||||
switch (result.status) {
|
||||
case UpdateCheckStatus.updateAvailable:
|
||||
await showUpdateDialog(context, result.update!);
|
||||
return;
|
||||
case UpdateCheckStatus.upToDate:
|
||||
showCustomNotification(
|
||||
context,
|
||||
AppLocalizations.of(context)!.updateUpToDate,
|
||||
);
|
||||
return;
|
||||
case UpdateCheckStatus.failed:
|
||||
showCustomNotification(
|
||||
context,
|
||||
AppLocalizations.of(context)!.updateCheckFailed,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _openCloudStorage(BuildContext context) async {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final ok = await showInfoActionSheet(
|
||||
@@ -221,6 +251,7 @@ class _SettingsTabState extends State<SettingsTab> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
if (_profile == null) {
|
||||
return const Center(child: SmallSpinner(size: 36));
|
||||
@@ -444,6 +475,13 @@ class _SettingsTabState extends State<SettingsTab> {
|
||||
child: _buildSection(
|
||||
context,
|
||||
items: [
|
||||
_SettingsItem(
|
||||
icon: Symbols.system_update,
|
||||
label: _isCheckingForUpdates
|
||||
? l10n.updateChecking
|
||||
: l10n.updateCheck,
|
||||
onTap: _isCheckingForUpdates ? null : _checkForUpdates,
|
||||
),
|
||||
_SettingsItem(
|
||||
leading: Image.asset(
|
||||
'assets/komet.png',
|
||||
|
||||
+5
-1
@@ -903,5 +903,9 @@
|
||||
"updateLater": "Later",
|
||||
"updateSkip": "Skip",
|
||||
"updateDownloading": "Downloading update…",
|
||||
"updateDownloadFailed": "Failed to download the update"
|
||||
"updateDownloadFailed": "Failed to download the update",
|
||||
"updateCheck": "Check for updates",
|
||||
"updateChecking": "Checking for updates…",
|
||||
"updateUpToDate": "You have the latest version",
|
||||
"updateCheckFailed": "Couldn't check for updates. Try again later"
|
||||
}
|
||||
|
||||
@@ -4021,6 +4021,30 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to download the update'**
|
||||
String get updateDownloadFailed;
|
||||
|
||||
/// No description provided for @updateCheck.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Check for updates'**
|
||||
String get updateCheck;
|
||||
|
||||
/// No description provided for @updateChecking.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Checking for updates…'**
|
||||
String get updateChecking;
|
||||
|
||||
/// No description provided for @updateUpToDate.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You have the latest version'**
|
||||
String get updateUpToDate;
|
||||
|
||||
/// No description provided for @updateCheckFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Couldn\'t check for updates. Try again later'**
|
||||
String get updateCheckFailed;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -2084,4 +2084,17 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get updateDownloadFailed => 'Failed to download the update';
|
||||
|
||||
@override
|
||||
String get updateCheck => 'Check for updates';
|
||||
|
||||
@override
|
||||
String get updateChecking => 'Checking for updates…';
|
||||
|
||||
@override
|
||||
String get updateUpToDate => 'You have the latest version';
|
||||
|
||||
@override
|
||||
String get updateCheckFailed =>
|
||||
'Couldn\'t check for updates. Try again later';
|
||||
}
|
||||
|
||||
@@ -2095,4 +2095,17 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get updateDownloadFailed => 'Не удалось скачать обновление';
|
||||
|
||||
@override
|
||||
String get updateCheck => 'Проверить обновление';
|
||||
|
||||
@override
|
||||
String get updateChecking => 'Проверяем обновления…';
|
||||
|
||||
@override
|
||||
String get updateUpToDate => 'Установлена актуальная версия';
|
||||
|
||||
@override
|
||||
String get updateCheckFailed =>
|
||||
'Не удалось проверить обновления. Повторите позже';
|
||||
}
|
||||
|
||||
+5
-1
@@ -685,5 +685,9 @@
|
||||
"updateLater": "Позже",
|
||||
"updateSkip": "Пропустить",
|
||||
"updateDownloading": "Загрузка обновления…",
|
||||
"updateDownloadFailed": "Не удалось скачать обновление"
|
||||
"updateDownloadFailed": "Не удалось скачать обновление",
|
||||
"updateCheck": "Проверить обновление",
|
||||
"updateChecking": "Проверяем обновления…",
|
||||
"updateUpToDate": "Установлена актуальная версия",
|
||||
"updateCheckFailed": "Не удалось проверить обновления. Повторите позже"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user