2025-02-10 09:20:24 +08:00
|
|
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
2025-02-10 15:07:53 +08:00
|
|
|
import 'package:appflowy/shared/version_checker/version_checker.dart';
|
2025-02-10 09:20:24 +08:00
|
|
|
import 'package:appflowy/startup/tasks/app_widget.dart';
|
|
|
|
import 'package:appflowy/startup/tasks/device_info_task.dart';
|
|
|
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
|
|
|
import 'package:appflowy_backend/log.dart';
|
|
|
|
import 'package:auto_updater/auto_updater.dart';
|
|
|
|
import 'package:collection/collection.dart';
|
|
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:universal_platform/universal_platform.dart';
|
|
|
|
|
|
|
|
import '../startup.dart';
|
|
|
|
|
|
|
|
class AutoUpdateTask extends LaunchTask {
|
|
|
|
AutoUpdateTask();
|
|
|
|
|
2025-02-10 15:07:53 +08:00
|
|
|
static const _feedUrl =
|
|
|
|
'https://github.com/LucasXu0/AppFlowy/releases/latest/download/appcast-{os}-{arch}.xml';
|
2025-02-10 09:20:24 +08:00
|
|
|
final _listener = _AppFlowyAutoUpdaterListener();
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> initialize(LaunchContext context) async {
|
2025-02-10 15:07:53 +08:00
|
|
|
// the auto updater is not supported on mobile
|
|
|
|
if (UniversalPlatform.isMobile) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await _setupAutoUpdater();
|
|
|
|
|
|
|
|
ApplicationInfo.isCriticalUpdateNotifier.addListener(
|
|
|
|
_showCriticalUpdateDialog,
|
|
|
|
);
|
2025-02-10 09:20:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> dispose() async {
|
|
|
|
autoUpdater.removeListener(_listener);
|
|
|
|
|
|
|
|
ApplicationInfo.isCriticalUpdateNotifier.removeListener(
|
|
|
|
_showCriticalUpdateDialog,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-02-10 15:07:53 +08:00
|
|
|
// On macOS and windows, we use auto_updater to check for updates.
|
|
|
|
// On linux, we use the version checker to check for updates because the auto_updater is not supported.
|
|
|
|
Future<void> _setupAutoUpdater() async {
|
|
|
|
Log.info(
|
|
|
|
'[AutoUpdate] current version: ${ApplicationInfo.applicationVersion}, current cpu architecture: ${ApplicationInfo.architecture}',
|
|
|
|
);
|
|
|
|
|
|
|
|
// Since the appcast.xml is not supported the arch, we separate the feed url by os and arch.
|
|
|
|
final feedUrl = _feedUrl
|
|
|
|
.replaceAll('{os}', ApplicationInfo.os)
|
|
|
|
.replaceAll('{arch}', ApplicationInfo.architecture);
|
|
|
|
|
|
|
|
// the auto updater is only supported on macOS and windows, so we don't need to check the platform
|
|
|
|
if (UniversalPlatform.isMacOS || UniversalPlatform.isWindows) {
|
|
|
|
autoUpdater.addListener(_listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
Log.info('[AutoUpdate] feed url: $feedUrl');
|
|
|
|
|
|
|
|
versionChecker.setFeedUrl(feedUrl);
|
|
|
|
final item = await versionChecker.checkForUpdateInformation();
|
|
|
|
if (item != null) {
|
|
|
|
ApplicationInfo.latestAppcastItem = item;
|
|
|
|
ApplicationInfo.latestVersionNotifier.value =
|
|
|
|
item.displayVersionString ?? '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-10 09:20:24 +08:00
|
|
|
void _showCriticalUpdateDialog() {
|
|
|
|
showCustomConfirmDialog(
|
|
|
|
context: AppGlobals.rootNavKey.currentContext!,
|
|
|
|
title: LocaleKeys.autoUpdate_criticalUpdateTitle.tr(),
|
|
|
|
description: LocaleKeys.autoUpdate_criticalUpdateDescription.tr(
|
|
|
|
namedArgs: {
|
|
|
|
'currentVersion': ApplicationInfo.applicationVersion,
|
|
|
|
'newVersion': ApplicationInfo.latestVersion,
|
|
|
|
},
|
|
|
|
),
|
|
|
|
builder: (context) => const SizedBox.shrink(),
|
|
|
|
// if the update is critical, dont allow the user to dismiss the dialog
|
|
|
|
barrierDismissible: false,
|
|
|
|
showCloseButton: false,
|
|
|
|
enableKeyboardListener: false,
|
|
|
|
closeOnConfirm: false,
|
|
|
|
confirmLabel: LocaleKeys.autoUpdate_criticalUpdateButton.tr(),
|
|
|
|
onConfirm: () async {
|
2025-02-10 15:07:53 +08:00
|
|
|
await versionChecker.checkForUpdate();
|
2025-02-10 09:20:24 +08:00
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _AppFlowyAutoUpdaterListener extends UpdaterListener {
|
|
|
|
@override
|
|
|
|
void onUpdaterBeforeQuitForUpdate(AppcastItem? item) {}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void onUpdaterCheckingForUpdate(Appcast? appcast) {
|
|
|
|
// Due to the reason documented in the following link, the update will not be found if the user has skipped the update.
|
|
|
|
// We have to check the skipped version manually.
|
|
|
|
// https://sparkle-project.org/documentation/api-reference/Classes/SPUUpdater.html#/c:objc(cs)SPUUpdater(im)checkForUpdateInformation
|
|
|
|
final items = appcast?.items;
|
|
|
|
if (items != null) {
|
|
|
|
final String? currentPlatform;
|
|
|
|
if (UniversalPlatform.isMacOS) {
|
|
|
|
currentPlatform = 'macos';
|
|
|
|
} else if (UniversalPlatform.isWindows) {
|
|
|
|
currentPlatform = 'windows';
|
|
|
|
} else {
|
|
|
|
currentPlatform = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
final matchingItem = items.firstWhereOrNull(
|
|
|
|
(item) => item.os == currentPlatform,
|
|
|
|
);
|
|
|
|
|
|
|
|
if (matchingItem != null) {
|
|
|
|
_updateVersionNotifier(matchingItem);
|
|
|
|
|
|
|
|
Log.info(
|
|
|
|
'[AutoUpdate] latest version: ${matchingItem.displayVersionString}',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void onUpdaterError(UpdaterError? error) {
|
|
|
|
Log.error('[AutoUpdate] On update error: $error');
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void onUpdaterUpdateNotAvailable(UpdaterError? error) {
|
|
|
|
Log.info('[AutoUpdate] Update not available $error');
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void onUpdaterUpdateAvailable(AppcastItem? item) {
|
|
|
|
_updateVersionNotifier(item);
|
|
|
|
|
|
|
|
Log.info('[AutoUpdate] Update available: ${item?.displayVersionString}');
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void onUpdaterUpdateDownloaded(AppcastItem? item) {
|
|
|
|
Log.info('[AutoUpdate] Update downloaded: ${item?.displayVersionString}');
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void onUpdaterUpdateCancelled(AppcastItem? item) {
|
|
|
|
_updateVersionNotifier(item);
|
|
|
|
|
|
|
|
Log.info('[AutoUpdate] Update cancelled: ${item?.displayVersionString}');
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void onUpdaterUpdateInstalled(AppcastItem? item) {
|
|
|
|
_updateVersionNotifier(item);
|
|
|
|
|
|
|
|
Log.info('[AutoUpdate] Update installed: ${item?.displayVersionString}');
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void onUpdaterUpdateSkipped(AppcastItem? item) {
|
|
|
|
_updateVersionNotifier(item);
|
|
|
|
|
|
|
|
Log.info('[AutoUpdate] Update skipped: ${item?.displayVersionString}');
|
|
|
|
}
|
|
|
|
|
|
|
|
void _updateVersionNotifier(AppcastItem? item) {
|
|
|
|
if (item != null) {
|
|
|
|
ApplicationInfo.latestAppcastItem = item;
|
|
|
|
ApplicationInfo.latestVersionNotifier.value =
|
|
|
|
item.displayVersionString ?? '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class AppFlowyAutoUpdateVersion {
|
|
|
|
AppFlowyAutoUpdateVersion({
|
|
|
|
required this.latestVersion,
|
|
|
|
required this.currentVersion,
|
|
|
|
required this.isForceUpdate,
|
|
|
|
});
|
|
|
|
|
|
|
|
factory AppFlowyAutoUpdateVersion.initial() => AppFlowyAutoUpdateVersion(
|
|
|
|
latestVersion: '0.0.0',
|
|
|
|
currentVersion: '0.0.0',
|
|
|
|
isForceUpdate: false,
|
|
|
|
);
|
|
|
|
|
|
|
|
final String latestVersion;
|
|
|
|
final String currentVersion;
|
|
|
|
|
|
|
|
final bool isForceUpdate;
|
|
|
|
|
|
|
|
bool get isUpdateAvailable => latestVersion != currentVersion;
|
|
|
|
}
|