| 
									
										
										
										
											2025-02-28 15:18:21 +08:00
										 |  |  | import 'dart:async'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 = | 
					
						
							| 
									
										
										
										
											2025-02-18 16:14:13 +08:00
										 |  |  |       'https://github.com/AppFlowy-IO/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; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-28 15:18:21 +08:00
										 |  |  |     // don't use await here, because the auto updater is not a blocking operation
 | 
					
						
							|  |  |  |     unawaited(_setupAutoUpdater()); | 
					
						
							| 
									
										
										
										
											2025-02-10 15:07:53 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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; | 
					
						
							|  |  |  | } |