diff --git a/frontend/appflowy_flutter/dsa_pub.pem b/frontend/appflowy_flutter/dsa_pub.pem new file mode 100644 index 0000000000000..b4522087e39ce --- /dev/null +++ b/frontend/appflowy_flutter/dsa_pub.pem @@ -0,0 +1,36 @@ +-----BEGIN PUBLIC KEY----- +MIIGQjCCBDUGByqGSM44BAEwggQoAoICAQCJsCIhS7IK+c4R3GVBuJBjI0/gW19n +flfKzC0kdt6HUtyirJ/v8SafqwRWSkbPVcrdVvHHBXOs2v7JogyZuy9v8SRvpW1s +trR6hFExtllYTo8uLi1YE5LGt4SwjduRYpwPvGvSxU5An8yESu/96JShQekHVPTj +ILfDNOOP2iRVIwRRVNZfFT/XOX5mN4RysUd+0KZ5IBR1LzRgw2O02sHheMkK/Uqr +19Of5eC1JTqgki9hHdjtjTsohJCrecFPDg4ej5Ac7HrbsGEklDDwtd7ftit2mnuz +ZNhG9qpfNztz9TQ6HCQCCrQCm1H7BkQDg5y7qFC9bARjMiQh0ygic97USeQLDu3l +EyxPAbIvo4m4IdHpaQSYRkse24y3b+k8BrQ9qNm4ElTIjEt4rs3Ic9aJ2j62qugq +7q+3EA0aDHbsB+TbjMm4sW3G9NVsr88nk1UUCayLDyCHwaXVB5KCfrVgM/5r/Avl +2ukkDdIB3A5gnO5+MY5fVS4WgxCPuAfJ6vp9/r2U1dGRDUAaISqg44uOwppXTnoZ +JQ3ZxZmyF6BVwvpjrZ7B8TfPco2JwBFI33W9byOWckUtc2cLAbuPutR1ymMp1F8J +B4G0VbpzT2liGaHqBV82J5+4ljAhcUnEzFOJ4ysDaJ0/n3NQuNGb2EX6SF9E5A2A +eaeEu5C0MWSFLQIdAJJP3sJsHh/raF/aqWjwKQQ2NWom4fKH1v7OSIcCggIAGn3W +SUWgyINiLoahtDZ2zkL7oMDQwZCC7lGUTyhi9gDnllNtJkpBpyEvgU479MnpGpgp +IlheOHYrEtL3EM31KwKvRism4TMkNd5+ZFw6//AcuoRd0YcGisk/UdE9PlhoKvLu +pzlQLf9iCeFtevW0TgHp/su0ZP6tSb1p/97vJOO5qY8gE4bCUNknlovFuhCwI9Al +eaQjhoDDDhimnkC8ZMsybvri9ZtNe24erZDaD1eQ85U0zR8CR73wFo1osDBjGgeK +cngcnQXoPfpj1tgmd1lEAcO8qD6BzZkEVyN+fgeqj22nfwqDDe9sXIEvf1KkhKEy +zWsdoZ6LCgWfI6Iqs4mqYlJPRhGFZ6hq0tncBzoxZcTeX1EYRhgYUeBe91CRlHMs +SIfsW5fpjWZ2QeDAsr9qmJ+RK06bN6U65o2nSOh0jaJpjT0VF6VdLLDF/bGrIjey +oEHg7b/SO8z7zO5H6CnHF4m4TG/EfkD5CSlT2SGer7yEF5KYeG5QX0W15ELTghak +IRsvBT6D8MWKjnfuxJjKLQoXPiw9Ua+nhG4YyQpDey1e7Y2oUFONdFRR38ocZuqQ +7YyxUzsQPLs1oEqfLm09gaF9gV/yPY09ocV2t+dO6PRq6BHWkdCdipUsFAwcPxmV +r4c8HCXKvlpNHDDu7Lw2CkfQdCbqpuT7gqsz+WgDggIFAAKCAgBOv5yBTT5SuSax +SmWwsoorMV+CxLRj0SM2T2FW9YK354BpDm95rpQZ4Za9m2wj59i73j/HboREsKgd +FzajWEd2S9VSHJ4dYx4m3wkQ6BFN/ryVocmG2PzAistXAOBB32dnQkK5cwDxQn2R ++rKHSSWtuJGv2YrwbxdpZM0zvOHvjYEXndV2XdH/DW4j9DivuCNoJlzaOvyXQsuE +jpcB0tg0QfZ564/6MIbzUivo7RKiLnmFiIksKymDYwp49382EWoSvF+rTjpqs1zH ++7jyva1m14jTgdSDjdoZakpkNpJpyN3Z6whQczBL0g3T3t3VEN6F7NGA0aZVrSkO +rPYXvZdEtsH4qGpb9SQx2fbE3RBD2LcxWxFBk0SlzF7RiCzolre6v/kqfJA5oejA +qTyTJpadheUn/+yYltnzCinILrDYwHzPAZWUXc1guRrdRRcv+d1gW3VG5n37NdYh +lSgowVOUfTcZv6nCr+Ohbirmogc/crcBVUhSxLufajKKMtzJBwbZ8yvUtgmCXCZ7 +6kVYIoRuQCkzJvN50dM9Jd20+e2b0MXdj8fEMF9UnBIqAklStMpkaK5dFXLUDHho +uG9cYnd4HIeLHZvfLgQ+DrBBdvLKPBez3vkIofocQDP4KIS4Dra4lLtDA07UmSwY +p6Dfx6Mqf0+pSpyZcf+qW1vlk3q92g== +-----END PUBLIC KEY----- diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/data_migration/anon_user_data_migration_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/data_migration/anon_user_data_migration_test.dart index 06c27093f9ecd..230ee5949523b 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/data_migration/anon_user_data_migration_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/data_migration/anon_user_data_migration_test.dart @@ -31,12 +31,6 @@ void main() { await tester.enterUserName('local_user'); // Scroll to sign-in - await tester.scrollUntilVisible( - find.byType(AccountSignInOutButton), - 100, - scrollable: find.findSettingsScrollable(), - ); - await tester.tapButton(find.byType(AccountSignInOutButton)); // sign up with Google diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/uncategorized/appflowy_cloud_auth_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/uncategorized/appflowy_cloud_auth_test.dart index 0b649fd6d50d3..fd65c299276f8 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/uncategorized/appflowy_cloud_auth_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/uncategorized/appflowy_cloud_auth_test.dart @@ -57,12 +57,6 @@ void main() { await tester.openSettings(); await tester.openSettingsPage(SettingsPage.account); - // Scroll to sign-in - await tester.scrollUntilVisible( - find.byType(AccountSignInOutButton), - 100, - scrollable: find.findSettingsScrollable(), - ); await tester.tapButton(find.byType(AccountSignInOutButton)); tester.expectToSeeGoogleLoginButton(); diff --git a/frontend/appflowy_flutter/ios/Podfile.lock b/frontend/appflowy_flutter/ios/Podfile.lock index 46b13d240de22..e4e87805cd0f0 100644 --- a/frontend/appflowy_flutter/ios/Podfile.lock +++ b/frontend/appflowy_flutter/ios/Podfile.lock @@ -175,36 +175,36 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" SPEC CHECKSUMS: - app_links: e70ca16b4b0f88253b3b3660200d4a10b4ea9795 - appflowy_backend: 144c20d8bfb298c4e10fa3fa6701a9f41bf98b88 - connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d - device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d + app_links: c5161ac5ab5383ad046884568b4b91cb52df5d91 + appflowy_backend: 78f6a053f756e6bc29bcc5a2106cbe77b756e97a + connectivity_plus: 481668c94744c30c53b8895afb39159d1e619bdf + device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896 DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 - flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc + file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517 + flowy_infra_ui: 931b73a18b54a392ab6152eebe29a63a30751f53 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c - image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 - integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 - irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9 - keyboard_height_plugin: 43fa8bba20fd5c4fdeed5076466b8b9d43cc6b86 - open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4 - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + fluttertoast: 76fea30fcf04176325f6864c87306927bd7d2038 + image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a + integration_test: d5929033778cc4991a187e4e1a85396fa4f59b3a + irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486 + keyboard_height_plugin: ef70a8181b29f27670e9e2450814ca6b6dc05b05 + open_filex: 432f3cd11432da3e39f47fcc0df2b1603854eff1 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84 Sentry: 1fe34e9c2cbba1e347623610d26db121dcb569f1 - sentry_flutter: a39c2a2d67d5e5b9cb0b94a4985c76dd5b3fc737 - share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - super_native_extensions: 4916b3c627a9c7fffdc48a23a9eca0b1ac228fa7 + sentry_flutter: e24b397f9a61fa5bbefd8279c3b2242ca86faa90 + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3 + super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4 SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - webview_flutter_wkwebview: 2a23822e9039b7b1bc52e5add778e5d89ad488d1 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + webview_flutter_wkwebview: 45a041c7831641076618876de3ba75c712860c6b PODFILE CHECKSUM: d0d9b4ff572d8695c38eb3f9b490f55cdfc57eca diff --git a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart index a0321e7e661f0..64747b48291c9 100644 --- a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart +++ b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart @@ -1,15 +1,15 @@ +import 'package:appflowy/ai/service/ai_client.dart'; +import 'package:appflowy/ai/service/appflowy_ai_service.dart'; import 'package:appflowy/core/config/kv.dart'; import 'package:appflowy/core/network_monitor.dart'; import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/plugins/document/application/prelude.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; -import 'package:appflowy/ai/service/ai_client.dart'; import 'package:appflowy/plugins/trash/application/prelude.dart'; import 'package:appflowy/shared/appflowy_cache_manager.dart'; import 'package:appflowy/shared/custom_image_cache_manager.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/tasks/appflowy_cloud_task.dart'; -import 'package:appflowy/ai/service/appflowy_ai_service.dart'; import 'package:appflowy/user/application/auth/af_cloud_auth_service.dart'; import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/application/prelude.dart'; diff --git a/frontend/appflowy_flutter/lib/startup/startup.dart b/frontend/appflowy_flutter/lib/startup/startup.dart index ac0be447c035e..7fa18fc54da36 100644 --- a/frontend/appflowy_flutter/lib/startup/startup.dart +++ b/frontend/appflowy_flutter/lib/startup/startup.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:io'; import 'package:appflowy/env/cloud_env.dart'; -import 'package:appflowy/startup/tasks/feature_flag_task.dart'; import 'package:appflowy/util/expand_views.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart'; import 'package:appflowy_backend/appflowy_backend.dart'; @@ -139,6 +138,8 @@ class FlowyRunner { // The DeviceOrApplicationInfoTask should be placed before the AppWidgetTask to fetch the app information. // It is unable to get the device information from the test environment. const ApplicationInfoTask(), + // The auto update task should be placed after the ApplicationInfoTask to fetch the latest version. + if (!mode.isIntegrationTest) AutoUpdateTask(), const HotKeyTask(), if (isAppFlowyCloudEnabled) InitAppFlowyCloudTask(), const InitAppWidgetTask(), diff --git a/frontend/appflowy_flutter/lib/startup/tasks/auto_update_task.dart b/frontend/appflowy_flutter/lib/startup/tasks/auto_update_task.dart new file mode 100644 index 0000000000000..3371abf22412c --- /dev/null +++ b/frontend/appflowy_flutter/lib/startup/tasks/auto_update_task.dart @@ -0,0 +1,188 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +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(); + + // static const _feedUrl = + // 'https://github.com/LucasXu0/AppFlowy/releases/latest/download/appcast-{os}-{arch}.xml'; + final _listener = _AppFlowyAutoUpdaterListener(); + + @override + Future initialize(LaunchContext context) async { + // Enable auto update when the integration of Windows and Linux is completed. + return; + // // the auto updater is not supported on mobile and linux + // if (UniversalPlatform.isMobile || UniversalPlatform.isLinux) { + // return; + // } + + // Log.info( + // '[AutoUpdate] current version: ${ApplicationInfo.applicationVersion}, current cpu architecture: ${ApplicationInfo.architecture}', + // ); + + // autoUpdater.addListener(_listener); + + // // 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); + // Log.info('[AutoUpdate] feed url: $feedUrl'); + + // await autoUpdater.setFeedURL(feedUrl); + // await autoUpdater.checkForUpdateInformation(); + + // ApplicationInfo.isCriticalUpdateNotifier.addListener( + // _showCriticalUpdateDialog, + // ); + } + + @override + Future dispose() async { + autoUpdater.removeListener(_listener); + + ApplicationInfo.isCriticalUpdateNotifier.removeListener( + _showCriticalUpdateDialog, + ); + } + + 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 { + await autoUpdater.checkForUpdates(); + }, + ); + } +} + +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; +} diff --git a/frontend/appflowy_flutter/lib/startup/tasks/device_info_task.dart b/frontend/appflowy_flutter/lib/startup/tasks/device_info_task.dart index 1558eefa5362c..81464f59d7730 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/device_info_task.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/device_info_task.dart @@ -1,8 +1,11 @@ import 'dart:io'; import 'package:appflowy_backend/log.dart'; +import 'package:auto_updater/auto_updater.dart'; import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/foundation.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:version/version.dart'; import '../startup.dart'; @@ -11,10 +14,39 @@ class ApplicationInfo { static String applicationVersion = ''; static String buildNumber = ''; static String deviceId = ''; + static String architecture = ''; + static String os = ''; // macOS major version static int? macOSMajorVersion; static int? macOSMinorVersion; + + // latest version + static ValueNotifier latestVersionNotifier = ValueNotifier(''); + // the version number is like 0.9.0 + static String get latestVersion => latestVersionNotifier.value; + + // If the latest version is greater than the current version, it means there is an update available + static bool get isUpdateAvailable { + try { + return Version.parse(latestVersion) > Version.parse(applicationVersion); + } catch (e) { + return false; + } + } + + // the latest appcast item + static AppcastItem? _latestAppcastItem; + static AppcastItem? get latestAppcastItem => _latestAppcastItem; + static set latestAppcastItem(AppcastItem? value) { + _latestAppcastItem = value; + + isCriticalUpdateNotifier.value = value?.criticalUpdate == true; + } + + // is critical update + static ValueNotifier isCriticalUpdateNotifier = ValueNotifier(false); + static bool get isCriticalUpdate => isCriticalUpdateNotifier.value; } class ApplicationInfoTask extends LaunchTask { @@ -36,38 +68,54 @@ class ApplicationInfoTask extends LaunchTask { ApplicationInfo.androidSDKVersion = androidInfo.version.sdkInt; } - if (Platform.isAndroid || Platform.isIOS) { - ApplicationInfo.applicationVersion = packageInfo.version; - ApplicationInfo.buildNumber = packageInfo.buildNumber; - } + ApplicationInfo.applicationVersion = packageInfo.version; + ApplicationInfo.buildNumber = packageInfo.buildNumber; String? deviceId; + String? architecture; + String? os; try { if (Platform.isAndroid) { final AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo; deviceId = androidInfo.device; + architecture = androidInfo.supportedAbis.firstOrNull; + os = 'android'; } else if (Platform.isIOS) { final IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo; deviceId = iosInfo.identifierForVendor; + architecture = iosInfo.utsname.machine; + os = 'ios'; } else if (Platform.isMacOS) { final MacOsDeviceInfo macInfo = await deviceInfoPlugin.macOsInfo; deviceId = macInfo.systemGUID; + architecture = macInfo.arch; + os = 'macos'; } else if (Platform.isWindows) { final WindowsDeviceInfo windowsInfo = await deviceInfoPlugin.windowsInfo; deviceId = windowsInfo.deviceId; + // we only support x86_64 on Windows + architecture = 'x86_64'; + os = 'windows'; } else if (Platform.isLinux) { final LinuxDeviceInfo linuxInfo = await deviceInfoPlugin.linuxInfo; deviceId = linuxInfo.machineId; + // we only support x86_64 on Linux + architecture = 'x86_64'; + os = 'linux'; } else { deviceId = null; + architecture = null; + os = null; } } catch (e) { Log.error('Failed to get platform version, $e'); } ApplicationInfo.deviceId = deviceId ?? ''; + ApplicationInfo.architecture = architecture ?? ''; + ApplicationInfo.os = os ?? ''; } @override diff --git a/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart b/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart index 4be5f0f6f7acd..9e8f9df49a522 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart @@ -1,7 +1,9 @@ export 'app_widget.dart'; export 'appflowy_cloud_task.dart'; +export 'auto_update_task.dart'; export 'debug_task.dart'; export 'device_info_task.dart'; +export 'feature_flag_task.dart'; export 'generate_router.dart'; export 'hot_key.dart'; export 'load_plugin.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_upgarde_application_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_upgarde_application_button.dart new file mode 100644 index 0000000000000..abe3ffd3548b5 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_upgarde_application_button.dart @@ -0,0 +1,110 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/util/theme_extension.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +class SidebarUpgradeApplicationButton extends StatelessWidget { + const SidebarUpgradeApplicationButton({ + super.key, + required this.onUpdateButtonTap, + required this.onCloseButtonTap, + }); + + final VoidCallback onUpdateButtonTap; + final VoidCallback onCloseButtonTap; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: context.sidebarUpgradeButtonBackground, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // title + _buildTitle(), + const VSpace(2), + // description + _buildDescription(), + const VSpace(10), + // update button + _buildUpdateButton(), + ], + ), + ); + } + + Widget _buildTitle() { + return Row( + children: [ + const FlowySvg( + FlowySvgs.sidebar_upgrade_version_s, + blendMode: null, + ), + const HSpace(6), + FlowyText.medium( + LocaleKeys.autoUpdate_bannerUpdateTitle.tr(), + fontSize: 14, + figmaLineHeight: 18, + ), + const Spacer(), + FlowyButton( + useIntrinsicWidth: true, + text: const FlowySvg(FlowySvgs.upgrade_close_s), + onTap: onCloseButtonTap, + ), + ], + ); + } + + Widget _buildDescription() { + return Opacity( + opacity: 0.7, + child: FlowyText( + LocaleKeys.autoUpdate_bannerUpdateDescription.tr(), + fontSize: 13, + figmaLineHeight: 16, + maxLines: null, + ), + ); + } + + Widget _buildUpdateButton() { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: onUpdateButtonTap, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + decoration: ShapeDecoration( + color: const Color(0xFFA44AFD), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(9), + ), + ), + child: FlowyText.medium( + LocaleKeys.autoUpdate_settingsUpdateButton.tr(), + color: Colors.white, + fontSize: 12.0, + figmaLineHeight: 15.0, + ), + ), + ), + ); + } +} + +extension on BuildContext { + Color get sidebarUpgradeButtonBackground => Theme.of(this).isLightMode + ? const Color(0xB2EBE4FF) + : const Color(0xB239275B); +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart index 6906dc8b73beb..d98c24287e996 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart @@ -8,6 +8,7 @@ import 'package:appflowy/plugins/document/presentation/editor_notification.dart' import 'package:appflowy/shared/feature_flags.dart'; import 'package:appflowy/shared/loading.dart'; import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/startup/tasks/device_info_task.dart'; import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart'; import 'package:appflowy/workspace/application/command_palette/command_palette_bloc.dart'; @@ -23,6 +24,7 @@ import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart'; import 'package:appflowy/workspace/presentation/home/home_sizes.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/footer/sidebar_upgarde_application_button.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/header/sidebar_top_menu.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/header/sidebar_user.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_folder.dart'; @@ -34,6 +36,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart' show UserProfilePB; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:auto_updater/auto_updater.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -261,6 +264,9 @@ class _SidebarState extends State<_Sidebar> { final _isHovered = ValueNotifier(false); final _scrollOffset = ValueNotifier(0); + // mute the update button during the current application lifecycle. + final _muteUpdateButton = ValueNotifier(false); + @override void initState() { super.initState(); @@ -347,6 +353,7 @@ class _SidebarState extends State<_Sidebar> { const VSpace(8), _renderUpgradeSpaceButton(menuHorizontalInset), + _buildUpgradeApplicationButton(menuHorizontalInset), const VSpace(8), Padding( @@ -432,6 +439,42 @@ class _SidebarState extends State<_Sidebar> { ); } + Widget _buildUpgradeApplicationButton(EdgeInsets menuHorizontalInset) { + return ValueListenableBuilder( + valueListenable: _muteUpdateButton, + builder: (_, mute, child) { + if (mute) { + return const SizedBox.shrink(); + } + + return ValueListenableBuilder( + valueListenable: ApplicationInfo.latestVersionNotifier, + builder: (_, latestVersion, child) { + if (!ApplicationInfo.isUpdateAvailable) { + return const SizedBox.shrink(); + } + + return Padding( + padding: menuHorizontalInset + + const EdgeInsets.only( + left: 4.0, + right: 4.0, + ), + child: SidebarUpgradeApplicationButton( + onUpdateButtonTap: () { + autoUpdater.checkForUpdates(); + }, + onCloseButtonTap: () { + _muteUpdateButton.value = true; + }, + ), + ); + }, + ); + }, + ); + } + void _onScrollChanged() { setState(() => _isScrolling = true); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart index 9b279cc11a889..d06016dfb8b20 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart @@ -275,6 +275,8 @@ class ConfirmPopup extends StatefulWidget { this.confirmButtonColor, this.child, this.closeOnAction = true, + this.showCloseButton = true, + this.enableKeyboardListener = true, }); final String title; @@ -303,6 +305,16 @@ class ConfirmPopup extends StatefulWidget { /// final bool closeOnAction; + /// Show close button. + /// Defaults to true. + /// + final bool showCloseButton; + + /// Enable keyboard listener. + /// Defaults to true. + /// + final bool enableKeyboardListener; + @override State createState() => _ConfirmPopupState(); } @@ -316,14 +328,16 @@ class _ConfirmPopupState extends State { focusNode: focusNode, autofocus: true, onKeyEvent: (event) { - if (event is KeyDownEvent && - event.logicalKey == LogicalKeyboardKey.escape) { - Navigator.of(context).pop(); - } else if (event is KeyUpEvent && - event.logicalKey == LogicalKeyboardKey.enter) { - widget.onConfirm(); - if (widget.closeOnAction) { + if (widget.enableKeyboardListener) { + if (event is KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.escape) { Navigator.of(context).pop(); + } else if (event is KeyUpEvent && + event.logicalKey == LogicalKeyboardKey.enter) { + widget.onConfirm(); + if (widget.closeOnAction) { + Navigator.of(context).pop(); + } } } }, @@ -367,15 +381,17 @@ class _ConfirmPopupState extends State { ), ), const HSpace(6.0), - FlowyButton( - margin: const EdgeInsets.all(3), - useIntrinsicWidth: true, - text: const FlowySvg( - FlowySvgs.upgrade_close_s, - size: Size.square(18.0), + if (widget.showCloseButton) ...[ + FlowyButton( + margin: const EdgeInsets.all(3), + useIntrinsicWidth: true, + text: const FlowySvg( + FlowySvgs.upgrade_close_s, + size: Size.square(18.0), + ), + onTap: () => Navigator.of(context).pop(), ), - onTap: () => Navigator.of(context).pop(), - ), + ], ], ); } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/about/app_version.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/about/app_version.dart new file mode 100644 index 0000000000000..85e3c897f6c46 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/about/app_version.dart @@ -0,0 +1,147 @@ +import 'package:appflowy/core/helpers/url_launcher.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/startup/tasks/device_info_task.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:auto_updater/auto_updater.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +class SettingsAppVersion extends StatelessWidget { + const SettingsAppVersion({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return ApplicationInfo.isUpdateAvailable + ? const _UpdateAppSection() + : _buildIsUpToDate(); + } + + Widget _buildIsUpToDate() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const FlowyText.regular( + 'AppFlowy is up to date!', + figmaLineHeight: 17, + ), + const VSpace(4), + Opacity( + opacity: 0.7, + child: FlowyText.regular( + 'Version ${ApplicationInfo.applicationVersion} (Official build)', + fontSize: 12, + figmaLineHeight: 13, + ), + ), + ], + ); + } +} + +class _UpdateAppSection extends StatelessWidget { + const _UpdateAppSection(); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded(child: _buildDescription(context)), + _buildUpdateButton(), + ], + ); + } + + Widget _buildUpdateButton() { + return PrimaryRoundedButton( + text: LocaleKeys.autoUpdate_settingsUpdateButton.tr(), + margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + fontWeight: FontWeight.w500, + radius: 8.0, + onTap: () { + Log.info('[AutoUpdater] Checking for updates'); + autoUpdater.checkForUpdates(); + }, + ); + } + + Widget _buildDescription(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + _buildRedDot(), + const HSpace(6), + Flexible( + child: FlowyText.medium( + LocaleKeys.autoUpdate_settingsUpdateTitle.tr( + namedArgs: { + 'newVersion': ApplicationInfo.latestVersion, + }, + ), + figmaLineHeight: 17, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const VSpace(4), + _buildCurrentVersionAndLatestVersion(context), + ], + ); + } + + Widget _buildCurrentVersionAndLatestVersion(BuildContext context) { + return Row( + children: [ + Flexible( + child: Opacity( + opacity: 0.7, + child: FlowyText.regular( + LocaleKeys.autoUpdate_settingsUpdateDescription.tr( + namedArgs: { + 'currentVersion': ApplicationInfo.applicationVersion, + 'newVersion': ApplicationInfo.latestVersion, + }, + ), + fontSize: 12, + figmaLineHeight: 13, + overflow: TextOverflow.ellipsis, + ), + ), + ), + const HSpace(6), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + afLaunchUrlString('https://www.appflowy.io/what-is-new'); + }, + child: FlowyText.regular( + LocaleKeys.autoUpdate_settingsUpdateWhatsNew.tr(), + decoration: TextDecoration.underline, + color: Theme.of(context).colorScheme.primary, + fontSize: 12, + figmaLineHeight: 13, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ], + ); + } + + Widget _buildRedDot() { + return Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + color: Color(0xFFFB006D), + shape: BoxShape.circle, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_sign_in_out.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_sign_in_out.dart index 8d15c80181340..13f5d832d082d 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_sign_in_out.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_sign_in_out.dart @@ -14,6 +14,34 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +class AccountSignInOutSection extends StatelessWidget { + const AccountSignInOutSection({ + super.key, + required this.userProfile, + required this.onAction, + this.signIn = true, + }); + + final UserProfilePB userProfile; + final VoidCallback onAction; + final bool signIn; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + FlowyText.regular(LocaleKeys.settings_accountPage_login_title.tr()), + const Spacer(), + AccountSignInOutButton( + userProfile: userProfile, + onAction: onAction, + signIn: signIn, + ), + ], + ); + } +} + class AccountSignInOutButton extends StatelessWidget { const AccountSignInOutButton({ super.key, @@ -32,9 +60,9 @@ class AccountSignInOutButton extends StatelessWidget { text: signIn ? LocaleKeys.settings_accountPage_login_loginLabel.tr() : LocaleKeys.settings_accountPage_login_logoutLabel.tr(), - margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), - fontWeight: FontWeight.w600, - radius: 12.0, + margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + fontWeight: FontWeight.w500, + radius: 8.0, onTap: () => signIn ? _showSignInDialog(context) : _showLogoutDialog(context), ); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_account_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_account_view.dart index bb0e4aac9f317..701d1cb565498 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_account_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_account_view.dart @@ -2,6 +2,7 @@ import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/user/settings_user_bloc.dart'; +import 'package:appflowy/workspace/presentation/settings/pages/about/app_version.dart'; import 'package:appflowy/workspace/presentation/settings/pages/account/account.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart'; @@ -74,23 +75,42 @@ class _SettingsAccountViewState extends State { title: LocaleKeys.settings_accountPage_email_title.tr(), children: [ FlowyText.regular(state.userProfile.email), + AccountSignInOutSection( + userProfile: state.userProfile, + onAction: state.userProfile.authenticator == + AuthenticatorPB.Local + ? widget.didLogin + : widget.didLogout, + signIn: state.userProfile.authenticator == + AuthenticatorPB.Local, + ), ], ), ], - // user sign in/out + if (isAuthEnabled && + state.userProfile.authenticator == AuthenticatorPB.Local) ...[ + SettingsCategory( + title: LocaleKeys.settings_accountPage_login_title.tr(), + children: [ + AccountSignInOutSection( + userProfile: state.userProfile, + onAction: state.userProfile.authenticator == + AuthenticatorPB.Local + ? widget.didLogin + : widget.didLogout, + signIn: state.userProfile.authenticator == + AuthenticatorPB.Local, + ), + ], + ), + ], + + // App version SettingsCategory( - title: LocaleKeys.settings_accountPage_login_title.tr(), - children: [ - AccountSignInOutButton( - userProfile: state.userProfile, - onAction: - state.userProfile.authenticator == AuthenticatorPB.Local - ? widget.didLogin - : widget.didLogout, - signIn: state.userProfile.authenticator == - AuthenticatorPB.Local, - ), + title: LocaleKeys.newSettings_myAccount_aboutAppFlowy.tr(), + children: const [ + SettingsAppVersion(), ], ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart index 1a36ea2a66749..aa541b902c247 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart @@ -673,9 +673,13 @@ Future showCustomConfirmDialog({ String? confirmLabel, ConfirmPopupStyle style = ConfirmPopupStyle.onlyOk, bool closeOnConfirm = true, + bool showCloseButton = true, + bool enableKeyboardListener = true, + bool barrierDismissible = true, }) { return showDialog( context: context, + barrierDismissible: barrierDismissible, builder: (context) { return Dialog( shape: RoundedRectangleBorder( @@ -692,6 +696,8 @@ Future showCustomConfirmDialog({ confirmButtonColor: Theme.of(context).colorScheme.primary, style: style, closeOnAction: closeOnConfirm, + showCloseButton: showCloseButton, + enableKeyboardListener: enableKeyboardListener, child: builder(context), ), ), diff --git a/frontend/appflowy_flutter/macos/Podfile.lock b/frontend/appflowy_flutter/macos/Podfile.lock index 9c0ef584299c7..30ee626f09b91 100644 --- a/frontend/appflowy_flutter/macos/Podfile.lock +++ b/frontend/appflowy_flutter/macos/Podfile.lock @@ -3,6 +3,9 @@ PODS: - FlutterMacOS - appflowy_backend (0.0.1): - FlutterMacOS + - auto_updater_macos (0.0.1): + - FlutterMacOS + - Sparkle - bitsdojo_window_macos (0.0.1): - FlutterMacOS - connectivity_plus (0.0.1): @@ -17,7 +20,7 @@ PODS: - flowy_infra_ui (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) - - HotKey (0.2.0) + - HotKey (0.2.1) - hotkey_manager (0.0.1): - FlutterMacOS - HotKey @@ -30,7 +33,7 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - ReachabilitySwift (5.2.3) + - ReachabilitySwift (5.2.4) - screen_retriever_macos (0.0.1): - FlutterMacOS - Sentry/HybridSDK (8.35.1) @@ -43,6 +46,7 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS + - Sparkle (2.6.4) - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS @@ -59,6 +63,7 @@ PODS: DEPENDENCIES: - app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`) - appflowy_backend (from `Flutter/ephemeral/.symlinks/plugins/appflowy_backend/macos`) + - auto_updater_macos (from `Flutter/ephemeral/.symlinks/plugins/auto_updater_macos/macos`) - bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`) - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`) - desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`) @@ -86,12 +91,15 @@ SPEC REPOS: - HotKey - ReachabilitySwift - Sentry + - Sparkle EXTERNAL SOURCES: app_links: :path: Flutter/ephemeral/.symlinks/plugins/app_links/macos appflowy_backend: :path: Flutter/ephemeral/.symlinks/plugins/appflowy_backend/macos + auto_updater_macos: + :path: Flutter/ephemeral/.symlinks/plugins/auto_updater_macos/macos bitsdojo_window_macos: :path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos connectivity_plus: @@ -136,32 +144,34 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: - app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a - appflowy_backend: 865496343de667fc8c600e04b9fd05234e130cf9 - bitsdojo_window_macos: 44e3b8fe3dd463820e0321f6256c5b1c16bb6a00 - connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747 - desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 - device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720 - file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d - flowy_infra_ui: 03301a39ad118771adbf051a664265c61c507f38 + app_links: 9028728e32c83a0831d9db8cf91c526d16cc5468 + appflowy_backend: 464aeb3e5c6966a41641a2111e5ead72ce2695f7 + auto_updater_macos: 3a42f1a06be6981f1a18be37e6e7bf86aa732118 + bitsdojo_window_macos: 7959fb0ca65a3ccda30095c181ecb856fae48ea9 + connectivity_plus: e74b9f74717d2d99d45751750e266e55912baeb5 + desktop_drop: e0b672a7d84c0a6cbc378595e82cdb15f2970a43 + device_info_plus: a56e6e74dbbd2bb92f2da12c64ddd4f67a749041 + file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 + flowy_infra_ui: 8760ff42a789de40bf5007a5f176b454722a341e FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - HotKey: e96d8a2ddbf4591131e2bb3f54e69554d90cdca6 - hotkey_manager: c32bf0bfe8f934b7bc17ab4ad5c4c142960b023c - irondash_engine_context: da62996ee25616d2f01bbeb85dc115d813359478 - local_notifier: e9506bc66fc70311e8bc7291fb70f743c081e4ff - package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979 - screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161 + HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277 + hotkey_manager: b443f35f4d772162937aa73fd8995e579f8ac4e2 + irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba + local_notifier: ebf072651e35ae5e47280ad52e2707375cb2ae4e + package_info_plus: f0052d280d17aa382b932f399edf32507174e870 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda + screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f Sentry: 1fe34e9c2cbba1e347623610d26db121dcb569f1 - sentry_flutter: a39c2a2d67d5e5b9cb0b94a4985c76dd5b3fc737 - share_plus: 1fa619de8392a4398bfaf176d441853922614e89 - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d - super_native_extensions: 85efee3a7495b46b04befcfc86ed12069264ebf3 - url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 - webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4 - window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 + sentry_flutter: e24b397f9a61fa5bbefd8279c3b2242ca86faa90 + share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + Sparkle: 5f8960a7a119aa7d45dacc0d5837017170bc5675 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189 + url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 + webview_flutter_wkwebview: 44d4dee7d7056d5ad185d25b38404436d56c547c + window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c PODFILE CHECKSUM: 0532f3f001ca3110b8be345d6491fff690e95823 diff --git a/frontend/appflowy_flutter/macos/Runner/AppDelegate.swift b/frontend/appflowy_flutter/macos/Runner/AppDelegate.swift index 14f99e9e07c39..c7872aaec902f 100644 --- a/frontend/appflowy_flutter/macos/Runner/AppDelegate.swift +++ b/frontend/appflowy_flutter/macos/Runner/AppDelegate.swift @@ -16,4 +16,8 @@ class AppDelegate: FlutterAppDelegate { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/frontend/appflowy_flutter/macos/Runner/DebugProfile.entitlements b/frontend/appflowy_flutter/macos/Runner/DebugProfile.entitlements index 71949adefe794..4c829c7ab0c19 100644 --- a/frontend/appflowy_flutter/macos/Runner/DebugProfile.entitlements +++ b/frontend/appflowy_flutter/macos/Runner/DebugProfile.entitlements @@ -1,20 +1,20 @@ - - com.apple.security.app-sandbox - - com.apple.security.files.downloads.read-write - - com.apple.security.files.user-selected.read-write - - com.apple.security.network.client - - com.apple.security.network.server - - com.apple.security.temporary-exception.files.absolute-path.read-write - - / - - - + + com.apple.security.app-sandbox + + com.apple.security.files.downloads.read-write + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + com.apple.security.network.server + + com.apple.security.temporary-exception.files.absolute-path.read-write + + / + + + \ No newline at end of file diff --git a/frontend/appflowy_flutter/macos/Runner/Info.plist b/frontend/appflowy_flutter/macos/Runner/Info.plist index cd078871349ee..cb3d1127a0bb6 100644 --- a/frontend/appflowy_flutter/macos/Runner/Info.plist +++ b/frontend/appflowy_flutter/macos/Runner/Info.plist @@ -1,57 +1,61 @@ - - LSApplicationCategoryType - public.app-category.productivity - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleLocalizations - - en - fr - it - zh - - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleURLTypes - + + LSApplicationCategoryType + public.app-category.productivity + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLocalizations + + en + fr + it + zh + + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleURLTypes + + + CFBundleURLName + + CFBundleURLSchemes + + appflowy-flutter + + + + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSAppTransportSecurity - CFBundleURLName - - CFBundleURLSchemes - - appflowy-flutter - + NSAllowsArbitraryLoads + - - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSAppTransportSecurity - - NSAllowsArbitraryLoads - + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + SUPublicEDKey + Bs++IOmOwYmNTjMMC2jMqLNldP+mndDp/LwujCg2/kw= + SUAllowsAutomaticUpdates + - NSHumanReadableCopyright - $(PRODUCT_COPYRIGHT) - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - - + \ No newline at end of file diff --git a/frontend/appflowy_flutter/macos/Runner/Release.entitlements b/frontend/appflowy_flutter/macos/Runner/Release.entitlements index 71949adefe794..4c829c7ab0c19 100644 --- a/frontend/appflowy_flutter/macos/Runner/Release.entitlements +++ b/frontend/appflowy_flutter/macos/Runner/Release.entitlements @@ -1,20 +1,20 @@ - - com.apple.security.app-sandbox - - com.apple.security.files.downloads.read-write - - com.apple.security.files.user-selected.read-write - - com.apple.security.network.client - - com.apple.security.network.server - - com.apple.security.temporary-exception.files.absolute-path.read-write - - / - - - + + com.apple.security.app-sandbox + + com.apple.security.files.downloads.read-write + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + com.apple.security.network.server + + com.apple.security.temporary-exception.files.absolute-path.read-write + + / + + + \ No newline at end of file diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Podfile b/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Podfile index 8c77835677a9e..012a925033b5d 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Podfile +++ b/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.13' +platform :osx, '10.14' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/project.pbxproj b/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/project.pbxproj index ac3debdf8ed63..b6c555963430a 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/project.pbxproj +++ b/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -26,11 +26,7 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; }; - 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B7C2E82907836001B5A6F548 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49D7864808B727FDFB82A4C2 /* Pods_Runner.framework */; }; - D73912F022F37F9E000D13A0 /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; }; - D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -50,8 +46,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */, - 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */, ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; @@ -71,7 +65,6 @@ 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; - 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FlutterMacOS.framework; path = Flutter/ephemeral/FlutterMacOS.framework; sourceTree = SOURCE_ROOT; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; @@ -80,7 +73,6 @@ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; A82DF8E6F43DF0AD4D0653DC /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - D73912EF22F37F9E000D13A0 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/ephemeral/App.framework; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -88,8 +80,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D73912F022F37F9E000D13A0 /* App.framework in Frameworks */, - 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */, B7C2E82907836001B5A6F548 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -145,8 +135,6 @@ 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, - D73912EF22F37F9E000D13A0 /* App.framework */, - 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */, ); path = Flutter; sourceTree = ""; @@ -215,7 +203,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { @@ -268,6 +256,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -281,7 +270,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename\n"; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -414,7 +403,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -497,7 +486,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -544,7 +533,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3d8205cf56dc3..758981e665d36 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index ab5e606eb2a70..9b4d235d471cc 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -2511,6 +2511,7 @@ "accountLogin": "Account Login", "updateNameError": "Failed to update name", "updateIconError": "Failed to update icon", + "aboutAppFlowy": "About @:appName", "deleteAccount": { "title": "Delete Account", "subtitle": "Permanently delete your account and all of your data.", @@ -3081,5 +3082,17 @@ }, "ai": { "contentPolicyViolation": "Image generation failed due to sensitive content. Please rephrase your input and try again" + }, + "autoUpdate": { + "criticalUpdateTitle": "Update required to continue", + "criticalUpdateDescription": "We've made improvements to enhance your experience! Please update from {currentVersion} to {newVersion} to keep using the app.", + "criticalUpdateButton": "Update now", + "bannerUpdateTitle": "New Version Available!", + "bannerUpdateDescription": "Get the latest features and bug fixes. Click \"Update\" to install now", + "bannerUpdateButton": "Update", + "settingsUpdateTitle": "New Version ({newVersion}) Available!", + "settingsUpdateDescription": "Current version: {currentVersion} (Official build) → {newVersion}", + "settingsUpdateButton": "Update now", + "settingsUpdateWhatsNew": "What's new" } }