From 5c200909fb6605f5f64a1cd2c8ab10fd665d8dab Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Fri, 2 Aug 2024 16:05:28 +0200 Subject: [PATCH 01/12] [gui] refactor notifications to allow complex content --- .../lib/notifications/notification_entries.dart | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/client/gui/lib/notifications/notification_entries.dart b/src/client/gui/lib/notifications/notification_entries.dart index 468b057b40..7a9c511403 100644 --- a/src/client/gui/lib/notifications/notification_entries.dart +++ b/src/client/gui/lib/notifications/notification_entries.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'notifications_list.dart'; class SimpleNotification extends StatelessWidget { - final String text; + final Widget child; final Widget icon; final Color barColor; final bool closeable; @@ -11,7 +11,7 @@ class SimpleNotification extends StatelessWidget { const SimpleNotification({ super.key, - required this.text, + required this.child, required this.icon, required this.barColor, this.closeable = true, @@ -40,7 +40,7 @@ class SimpleNotification extends StatelessWidget { child: FittedBox(fit: BoxFit.fill, child: icon), ), const SizedBox(width: 8), - Expanded(child: Text(text)), + Expanded(child: child), ]), ), ), @@ -113,10 +113,10 @@ class _TimeoutNotificationState extends State child: AnimatedBuilder( animation: timeoutController, builder: (_, __) => SimpleNotification( - text: widget.text, icon: widget.icon, barColor: widget.barColor, barFullness: 1.0 - timeoutController.value, + child: Text(widget.text), ), ), ); @@ -124,10 +124,11 @@ class _TimeoutNotificationState extends State } class ErrorNotification extends SimpleNotification { - const ErrorNotification({ + ErrorNotification({ super.key, - required super.text, + required String text, }) : super( + child: Text(text), barColor: Colors.red, icon: const Icon(Icons.cancel_outlined, color: Colors.red), ); @@ -167,7 +168,6 @@ class OperationNotification extends StatelessWidget { } return SimpleNotification( - text: text, barColor: Colors.blue, closeable: false, icon: const CircularProgressIndicator( @@ -175,6 +175,7 @@ class OperationNotification extends StatelessWidget { strokeAlign: -2, strokeWidth: 3.5, ), + child: Text(text), ); }, ); From 145e1e720a21ef0b7f3aa1661dd7abcfeb0219b3 Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Fri, 2 Aug 2024 16:06:13 +0200 Subject: [PATCH 02/12] [gui] remove update entry from settings --- .../gui/lib/settings/general_settings.dart | 118 +++++------------- 1 file changed, 28 insertions(+), 90 deletions(-) diff --git a/src/client/gui/lib/settings/general_settings.dart b/src/client/gui/lib/settings/general_settings.dart index e5e0b81310..f850f0b4e4 100644 --- a/src/client/gui/lib/settings/general_settings.dart +++ b/src/client/gui/lib/settings/general_settings.dart @@ -1,24 +1,12 @@ -import 'package:basics/basics.dart'; import 'package:flutter/material.dart' hide Switch; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:url_launcher/url_launcher.dart'; import '../dropdown.dart'; -import '../notifications/notifications_provider.dart'; +import '../notifications.dart'; import '../providers.dart'; import '../switch.dart'; import 'autostart_notifiers.dart'; -final updateProvider = Provider.autoDispose((ref) { - ref - .watch(grpcClientProvider) - .updateInfo() - .then((value) => ref.state = value) - .ignore(); - return UpdateInfo(); -}); - final onAppCloseProvider = guiSettingProvider(onAppCloseKey); class GeneralSettings extends ConsumerWidget { @@ -26,89 +14,39 @@ class GeneralSettings extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final update = ref.watch(updateProvider); final autostart = ref.watch(autostartProvider).valueOrNull ?? false; final onAppClose = ref.watch(onAppCloseProvider); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'General', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 20), - if (update.version.isNotBlank) UpdateAvailable(update), - Switch( - label: 'Open the Multipass GUI on startup', - value: autostart, - trailingSwitch: true, - size: 30, - onChanged: (value) { - ref - .read(autostartProvider.notifier) - .set(value) - .onError(ref.notifyError((e) => 'Failed to set autostart: $e')); - }, - ), - const SizedBox(height: 20), - Dropdown( - label: 'On close of application', - width: 260, - value: onAppClose ?? 'ask', - onChanged: (value) => - ref.read(onAppCloseProvider.notifier).set(value!), - items: const { - 'ask': 'Ask about running instances', - 'stop': 'Stop running instances', - 'nothing': 'Do not stop running instances', - }, - ), - ], - ); - } -} - -class UpdateAvailable extends StatelessWidget { - final UpdateInfo updateInfo; - - const UpdateAvailable(this.updateInfo, {super.key}); - - static final installUrl = Uri.parse('https://multipass.run/install'); - - static void launchInstallUrl() => launchUrl(installUrl); - - @override - Widget build(BuildContext context) { return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Update available', style: TextStyle(fontSize: 16)), - const SizedBox(height: 8), - Container( - color: const Color(0xffF7F7F7), - padding: const EdgeInsets.all(12), - width: 480, - child: Row(children: [ - Container( - alignment: Alignment.center, - color: const Color(0xffE95420), - height: 48, - width: 48, - child: SvgPicture.asset('assets/multipass.svg', width: 30), - ), - const SizedBox(width: 12), - Text( - 'Multipass ${updateInfo.version}\nis available', - style: const TextStyle(fontSize: 16), - ), - const SizedBox(width: 12), - const Spacer(), - const TextButton( - onPressed: launchInstallUrl, - child: Text('Upgrade now'), - ), - ]), + const Text( + 'General', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 20), + Switch( + label: 'Open the Multipass GUI on startup', + value: autostart, + trailingSwitch: true, + size: 30, + onChanged: (value) { + ref + .read(autostartProvider.notifier) + .set(value) + .onError(ref.notifyError((e) => 'Failed to set autostart: $e')); + }, + ), + const SizedBox(height: 20), + Dropdown( + label: 'On close of application', + width: 260, + value: onAppClose ?? 'ask', + onChanged: (value) => ref.read(onAppCloseProvider.notifier).set(value!), + items: const { + 'ask': 'Ask about running instances', + 'stop': 'Stop running instances', + 'nothing': 'Do not stop running instances', + }, + ), ]); } } From 8ffbecfd3c2238c9684442c95a6c1cf8d5bc2366 Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Fri, 2 Aug 2024 16:07:17 +0200 Subject: [PATCH 03/12] [gui] add update notification on startup --- src/client/gui/lib/main.dart | 6 ++++ src/client/gui/lib/update_available.dart | 43 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/client/gui/lib/update_available.dart diff --git a/src/client/gui/lib/main.dart b/src/client/gui/lib/main.dart index da5807d68b..ebbc8e1b99 100644 --- a/src/client/gui/lib/main.dart +++ b/src/client/gui/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:basics/basics.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hotkey_manager/hotkey_manager.dart'; @@ -15,6 +16,7 @@ import 'settings/hotkey.dart'; import 'settings/settings.dart'; import 'sidebar.dart'; import 'tray_menu.dart'; +import 'update_available.dart'; import 'vm_details/vm_details.dart'; import 'vm_table/vm_table_screen.dart'; @@ -138,6 +140,10 @@ class _AppState extends ConsumerState with WindowListener { super.initState(); windowManager.addListener(this); windowManager.setPreventClose(true); + ref.read(grpcClientProvider).updateInfo().then((updateInfo) { + if (updateInfo.version.isBlank) return; + ref.read(notificationsProvider.notifier).add(UpdateAvailable(updateInfo)); + }); } @override diff --git a/src/client/gui/lib/update_available.dart b/src/client/gui/lib/update_available.dart new file mode 100644 index 0000000000..f7e1314927 --- /dev/null +++ b/src/client/gui/lib/update_available.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import 'grpc_client.dart'; +import 'notifications/notification_entries.dart'; +import 'notifications/notifications_list.dart'; + +class UpdateAvailable extends StatelessWidget { + final UpdateInfo updateInfo; + + const UpdateAvailable(this.updateInfo, {super.key}); + + static final installUrl = Uri.parse('https://multipass.run/install'); + static const color = Color(0xffE95420); + + @override + Widget build(BuildContext context) { + return SimpleNotification( + barColor: color, + icon: SvgPicture.asset( + 'assets/multipass.svg', + width: 30, + colorFilter: const ColorFilter.mode(color, BlendMode.srcIn), + ), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text( + 'Multipass ${updateInfo.version} is available', + style: const TextStyle(fontSize: 16), + ), + const SizedBox(height: 12), + TextButton( + onPressed: () async { + await launchUrl(installUrl); + if (!context.mounted) return; + Actions.maybeInvoke(context, const CloseNotificationIntent()); + }, + child: const Text('Upgrade now', style: TextStyle(fontSize: 14)), + ), + ]), + ); + } +} From 03c2a5f3abbc1ada8b39a977bf03b50b9a6309cb Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Mon, 5 Aug 2024 15:08:21 +0200 Subject: [PATCH 04/12] [gui] add back update available provider --- src/client/gui/lib/update_available.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/client/gui/lib/update_available.dart b/src/client/gui/lib/update_available.dart index f7e1314927..44594bfe9f 100644 --- a/src/client/gui/lib/update_available.dart +++ b/src/client/gui/lib/update_available.dart @@ -1,10 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:url_launcher/url_launcher.dart'; import 'grpc_client.dart'; import 'notifications/notification_entries.dart'; import 'notifications/notifications_list.dart'; +import 'providers.dart'; + +final updateProvider = FutureProvider.autoDispose((ref) { + return ref.watch(grpcClientProvider).updateInfo(); +}); class UpdateAvailable extends StatelessWidget { final UpdateInfo updateInfo; From 912f893f71af1632fd050eb6190c3ed3444b968f Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Mon, 5 Aug 2024 15:10:12 +0200 Subject: [PATCH 05/12] [gui] refactor update notification --- src/client/gui/lib/main.dart | 5 +--- src/client/gui/lib/update_available.dart | 30 +++++++++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/client/gui/lib/main.dart b/src/client/gui/lib/main.dart index ebbc8e1b99..6aa536d41f 100644 --- a/src/client/gui/lib/main.dart +++ b/src/client/gui/lib/main.dart @@ -140,10 +140,7 @@ class _AppState extends ConsumerState with WindowListener { super.initState(); windowManager.addListener(this); windowManager.setPreventClose(true); - ref.read(grpcClientProvider).updateInfo().then((updateInfo) { - if (updateInfo.version.isBlank) return; - ref.read(notificationsProvider.notifier).add(UpdateAvailable(updateInfo)); - }); + ref.read(updateProvider.future).then(ref.showUpdateNotification); } @override diff --git a/src/client/gui/lib/update_available.dart b/src/client/gui/lib/update_available.dart index 44594bfe9f..57e000033c 100644 --- a/src/client/gui/lib/update_available.dart +++ b/src/client/gui/lib/update_available.dart @@ -1,33 +1,36 @@ +import 'package:basics/basics.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'grpc_client.dart'; import 'notifications/notification_entries.dart'; import 'notifications/notifications_list.dart'; +import 'notifications/notifications_provider.dart'; import 'providers.dart'; final updateProvider = FutureProvider.autoDispose((ref) { return ref.watch(grpcClientProvider).updateInfo(); }); -class UpdateAvailable extends StatelessWidget { - final UpdateInfo updateInfo; +const _color = Color(0xffE95420); +final installUrl = Uri.parse('https://multipass.run/install'); + +Future launchInstallUrl() => launchUrl(installUrl); - const UpdateAvailable(this.updateInfo, {super.key}); +class UpdateAvailableNotification extends StatelessWidget { + final UpdateInfo updateInfo; - static final installUrl = Uri.parse('https://multipass.run/install'); - static const color = Color(0xffE95420); + const UpdateAvailableNotification(this.updateInfo, {super.key}); @override Widget build(BuildContext context) { return SimpleNotification( - barColor: color, + barColor: _color, icon: SvgPicture.asset( 'assets/multipass.svg', width: 30, - colorFilter: const ColorFilter.mode(color, BlendMode.srcIn), + colorFilter: const ColorFilter.mode(_color, BlendMode.srcIn), ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( @@ -37,7 +40,7 @@ class UpdateAvailable extends StatelessWidget { const SizedBox(height: 12), TextButton( onPressed: () async { - await launchUrl(installUrl); + await launchInstallUrl(); if (!context.mounted) return; Actions.maybeInvoke(context, const CloseNotificationIntent()); }, @@ -47,3 +50,12 @@ class UpdateAvailable extends StatelessWidget { ); } } + +extension ShowUpdateExtension on WidgetRef { + void showUpdateNotification(UpdateInfo updateInfo) { + if (updateInfo.version.isNotBlank) { + read(notificationsProvider.notifier) + .add(UpdateAvailableNotification(updateInfo)); + } + } +} From 2fb089aa057c732cccdba601c29797539db3074f Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Mon, 5 Aug 2024 15:10:25 +0200 Subject: [PATCH 06/12] [gui] refactor update entry in settings --- .../gui/lib/settings/general_settings.dart | 7 ++++ src/client/gui/lib/update_available.dart | 39 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/client/gui/lib/settings/general_settings.dart b/src/client/gui/lib/settings/general_settings.dart index f850f0b4e4..83bbf297f8 100644 --- a/src/client/gui/lib/settings/general_settings.dart +++ b/src/client/gui/lib/settings/general_settings.dart @@ -1,3 +1,4 @@ +import 'package:basics/basics.dart'; import 'package:flutter/material.dart' hide Switch; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -5,6 +6,7 @@ import '../dropdown.dart'; import '../notifications.dart'; import '../providers.dart'; import '../switch.dart'; +import '../update_available.dart'; import 'autostart_notifiers.dart'; final onAppCloseProvider = guiSettingProvider(onAppCloseKey); @@ -14,6 +16,7 @@ class GeneralSettings extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final update = ref.watch(updateProvider).valueOrNull ?? UpdateInfo(); final autostart = ref.watch(autostartProvider).valueOrNull ?? false; final onAppClose = ref.watch(onAppCloseProvider); @@ -23,6 +26,10 @@ class GeneralSettings extends ConsumerWidget { style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 20), + if (update.version.isNotBlank) ...[ + UpdateAvailable(update), + const SizedBox(height: 20), + ], Switch( label: 'Open the Multipass GUI on startup', value: autostart, diff --git a/src/client/gui/lib/update_available.dart b/src/client/gui/lib/update_available.dart index 57e000033c..a259471057 100644 --- a/src/client/gui/lib/update_available.dart +++ b/src/client/gui/lib/update_available.dart @@ -18,6 +18,45 @@ final installUrl = Uri.parse('https://multipass.run/install'); Future launchInstallUrl() => launchUrl(installUrl); +class UpdateAvailable extends StatelessWidget { + final UpdateInfo updateInfo; + + const UpdateAvailable(this.updateInfo, {super.key}); + + @override + Widget build(BuildContext context) { + final icon = Container( + alignment: Alignment.center, + color: _color, + height: 40, + width: 40, + child: SvgPicture.asset('assets/multipass.svg', width: 25), + ); + + final text = Text( + 'Multipass ${updateInfo.version} is available', + style: const TextStyle(fontSize: 16), + ); + + const button = TextButton( + onPressed: launchInstallUrl, + child: Text('Upgrade now'), + ); + + return Container( + color: const Color(0xffF7F7F7), + padding: const EdgeInsets.all(12), + child: Row(children: [ + icon, + const SizedBox(width: 12), + text, + const Spacer(), + button, + ]), + ); + } +} + class UpdateAvailableNotification extends StatelessWidget { final UpdateInfo updateInfo; From 7e8b9e1f8c8f9093c3b4fc08e805a1944bff8375 Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Thu, 12 Sep 2024 15:25:35 +0200 Subject: [PATCH 07/12] [daemon] check for updates on info call --- src/daemon/daemon.cpp | 1 + src/rpc/multipass.proto | 1 + 2 files changed, 2 insertions(+) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a38552498e..dac8ec72b3 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1665,6 +1665,7 @@ try // clang-format on mpl::ClientLogger logger{mpl::level_from(request->verbosity_level()), *config->logger, server}; InfoReply response; + config->update_prompt->populate_if_time_to_show(response.mutable_update_info()); InstanceSnapshotsMap instance_snapshots_map; bool have_mounts = false; bool deleted = false; diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 330b5671c2..e6666ad820 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -254,6 +254,7 @@ message InfoReply { repeated DetailedInfoItem details = 1; bool snapshots = 2; // useful to determine what entity (instance/snapshot) was absent when details are empty string log_line = 3; + UpdateInfo update_info = 4; } message ListRequest { From eef2d68f05a261be919ce1976c198eb63463feb4 Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Thu, 12 Sep 2024 15:26:00 +0200 Subject: [PATCH 08/12] [cli/info] display updates from info cli command --- src/client/cli/cmd/info.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index 5594d30fee..18ad735902 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -35,6 +35,9 @@ mp::ReturnCode cmd::Info::run(mp::ArgParser* parser) auto on_success = [this](mp::InfoReply& reply) { cout << chosen_formatter->format(reply); + if (term->is_live() && update_available(reply.update_info())) + cout << update_notice(reply.update_info()); + return ReturnCode::Ok; }; From 23e45929be4845c93b52831f197a7bce45f24abc Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Thu, 12 Sep 2024 15:27:24 +0200 Subject: [PATCH 09/12] [gui] rewrite update provider into notifier with more complex logic the notifier makes sure that, if needed, only one update notification is on screen for a given version --- src/client/gui/lib/main.dart | 3 -- .../gui/lib/settings/general_settings.dart | 2 +- src/client/gui/lib/update_available.dart | 37 +++++++++++++------ 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/client/gui/lib/main.dart b/src/client/gui/lib/main.dart index 6aa536d41f..da5807d68b 100644 --- a/src/client/gui/lib/main.dart +++ b/src/client/gui/lib/main.dart @@ -1,4 +1,3 @@ -import 'package:basics/basics.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hotkey_manager/hotkey_manager.dart'; @@ -16,7 +15,6 @@ import 'settings/hotkey.dart'; import 'settings/settings.dart'; import 'sidebar.dart'; import 'tray_menu.dart'; -import 'update_available.dart'; import 'vm_details/vm_details.dart'; import 'vm_table/vm_table_screen.dart'; @@ -140,7 +138,6 @@ class _AppState extends ConsumerState with WindowListener { super.initState(); windowManager.addListener(this); windowManager.setPreventClose(true); - ref.read(updateProvider.future).then(ref.showUpdateNotification); } @override diff --git a/src/client/gui/lib/settings/general_settings.dart b/src/client/gui/lib/settings/general_settings.dart index 83bbf297f8..e0a100a297 100644 --- a/src/client/gui/lib/settings/general_settings.dart +++ b/src/client/gui/lib/settings/general_settings.dart @@ -16,7 +16,7 @@ class GeneralSettings extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final update = ref.watch(updateProvider).valueOrNull ?? UpdateInfo(); + final update = ref.watch(updateProvider); final autostart = ref.watch(autostartProvider).valueOrNull ?? false; final onAppClose = ref.watch(onAppCloseProvider); diff --git a/src/client/gui/lib/update_available.dart b/src/client/gui/lib/update_available.dart index a259471057..3710dc6515 100644 --- a/src/client/gui/lib/update_available.dart +++ b/src/client/gui/lib/update_available.dart @@ -9,9 +9,31 @@ import 'notifications/notifications_list.dart'; import 'notifications/notifications_provider.dart'; import 'providers.dart'; -final updateProvider = FutureProvider.autoDispose((ref) { - return ref.watch(grpcClientProvider).updateInfo(); -}); +class UpdateNotifier extends Notifier { + @override + UpdateInfo build() => UpdateInfo(); + + void set(UpdateInfo updateInfo) { + if (updateInfo.version.isBlank) return; + final updateNotificationExists = ref.read(notificationsProvider).any((n) { + return n is UpdateAvailableNotification && n.updateInfo == updateInfo; + }); + if (updateNotificationExists) return; + ref + .read(notificationsProvider.notifier) + .add(UpdateAvailableNotification(updateInfo)); + state = updateInfo; + } + + @override + bool updateShouldNotify(UpdateInfo previous, UpdateInfo next) { + return previous != next; + } +} + +final updateProvider = NotifierProvider( + UpdateNotifier.new, +); const _color = Color(0xffE95420); final installUrl = Uri.parse('https://multipass.run/install'); @@ -89,12 +111,3 @@ class UpdateAvailableNotification extends StatelessWidget { ); } } - -extension ShowUpdateExtension on WidgetRef { - void showUpdateNotification(UpdateInfo updateInfo) { - if (updateInfo.version.isNotBlank) { - read(notificationsProvider.notifier) - .add(UpdateAvailableNotification(updateInfo)); - } - } -} From 342584e631438c63716f3c0bf1b22bfff61fb82f Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Thu, 12 Sep 2024 15:28:55 +0200 Subject: [PATCH 10/12] [gui] make provider container accessible from anywhere --- src/client/gui/lib/main.dart | 2 +- src/client/gui/lib/providers.dart | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/client/gui/lib/main.dart b/src/client/gui/lib/main.dart index da5807d68b..c07ca9f99d 100644 --- a/src/client/gui/lib/main.dart +++ b/src/client/gui/lib/main.dart @@ -38,7 +38,7 @@ void main() async { await hotKeyManager.unregisterAll(); final sharedPreferences = await SharedPreferences.getInstance(); - final providerContainer = ProviderContainer(overrides: [ + providerContainer = ProviderContainer(overrides: [ guiSettingProvider.overrideWith(() { return GuiSettingNotifier(sharedPreferences); }), diff --git a/src/client/gui/lib/providers.dart b/src/client/gui/lib/providers.dart index 23f8fe0817..3d2cf6d420 100644 --- a/src/client/gui/lib/providers.dart +++ b/src/client/gui/lib/providers.dart @@ -14,6 +14,8 @@ import 'logger.dart'; export 'grpc_client.dart'; +late final ProviderContainer providerContainer; + final grpcClientProvider = Provider((_) { final address = getServerAddress(); final certPair = getCertPair(); From 6a3f021d6e272818718b985676b12457b5014659 Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Thu, 12 Sep 2024 15:29:53 +0200 Subject: [PATCH 11/12] [gui] make grpc client check for updates on specific rpcs --- src/client/gui/lib/grpc_client.dart | 36 ++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/client/gui/lib/grpc_client.dart b/src/client/gui/lib/grpc_client.dart index 81ab2f1c6d..d901ba43c0 100644 --- a/src/client/gui/lib/grpc_client.dart +++ b/src/client/gui/lib/grpc_client.dart @@ -6,8 +6,9 @@ import 'package:grpc/grpc.dart'; import 'package:protobuf/protobuf.dart' hide RpcClient; import 'package:rxdart/rxdart.dart'; -import 'generated/multipass.pbgrpc.dart'; import 'logger.dart'; +import 'providers.dart'; +import 'update_available.dart'; export 'generated/multipass.pbgrpc.dart'; @@ -21,6 +22,23 @@ extension on RpcMessage { String get repr => '$runtimeType${toProto3Json()}'; } +T checkForUpdate(T t) { + final updateInfo = switch (t) { + LaunchReply launchReply => launchReply.updateInfo, + InfoReply infoReply => infoReply.updateInfo, + ListReply listReply => listReply.updateInfo, + NetworksReply networksReply => networksReply.updateInfo, + StartReply startReply => startReply.updateInfo, + RestartReply restartReply => restartReply.updateInfo, + VersionReply versionReply => versionReply.updateInfo, + _ => UpdateInfo(), + }; + + providerContainer.read(updateProvider.notifier).set(updateInfo); + + return t; +} + void Function(StreamNotification) logGrpc(RpcMessage request) { return (notification) { switch (notification.kind) { @@ -62,6 +80,7 @@ class GrpcClient { logger.i('Sent ${request.repr}'); yield* _client .launch(Stream.value(request)) + .map(checkForUpdate) .doOnEach(logGrpc(request)) .map(Either.left); for (final mountRequest in mountRequests) { @@ -84,6 +103,7 @@ class GrpcClient { logger.i('Sent ${request.repr}'); return _client .start(Stream.value(request)) + .map(checkForUpdate) .doOnEach(logGrpc(request)) .firstOrNull; } @@ -117,6 +137,7 @@ class GrpcClient { logger.i('Sent ${request.repr}'); return _client .restart(Stream.value(request)) + .map(checkForUpdate) .doOnEach(logGrpc(request)) .firstOrNull; } @@ -167,6 +188,7 @@ class GrpcClient { ); return _client .info(Stream.value(request)) + .map(checkForUpdate) .last .then((r) => r.details.toList()); } @@ -204,6 +226,7 @@ class GrpcClient { logger.i('Sent ${request.repr}'); return _client .networks(Stream.value(request)) + .map(checkForUpdate) .doOnEach(logGrpc(request)) .last .then((r) => r.interfaces); @@ -214,21 +237,12 @@ class GrpcClient { logger.i('Sent ${request.repr}'); return _client .version(Stream.value(request)) + .map(checkForUpdate) .doOnEach(logGrpc(request)) .last .then((reply) => reply.version); } - Future updateInfo() { - final request = VersionRequest(); - logger.i('Sent ${request.repr}'); - return _client - .version(Stream.value(request)) - .doOnEach(logGrpc(request)) - .last - .then((reply) => reply.updateInfo); - } - Future get(String key) { final request = GetRequest(key: key); logger.i('Sent ${request.repr}'); From e655413a82918d9eb1a181c91e8b79fa0a700b85 Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Thu, 12 Sep 2024 20:16:12 +0200 Subject: [PATCH 12/12] [gui] simplify function for extracting update from rpc reply --- src/client/gui/lib/grpc_client.dart | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/client/gui/lib/grpc_client.dart b/src/client/gui/lib/grpc_client.dart index d901ba43c0..226f5ff1ab 100644 --- a/src/client/gui/lib/grpc_client.dart +++ b/src/client/gui/lib/grpc_client.dart @@ -22,8 +22,8 @@ extension on RpcMessage { String get repr => '$runtimeType${toProto3Json()}'; } -T checkForUpdate(T t) { - final updateInfo = switch (t) { +void checkForUpdate(RpcMessage message) { + final updateInfo = switch (message) { LaunchReply launchReply => launchReply.updateInfo, InfoReply infoReply => infoReply.updateInfo, ListReply listReply => listReply.updateInfo, @@ -35,8 +35,6 @@ T checkForUpdate(T t) { }; providerContainer.read(updateProvider.notifier).set(updateInfo); - - return t; } void Function(StreamNotification) logGrpc(RpcMessage request) { @@ -80,7 +78,7 @@ class GrpcClient { logger.i('Sent ${request.repr}'); yield* _client .launch(Stream.value(request)) - .map(checkForUpdate) + .doOnData(checkForUpdate) .doOnEach(logGrpc(request)) .map(Either.left); for (final mountRequest in mountRequests) { @@ -103,7 +101,7 @@ class GrpcClient { logger.i('Sent ${request.repr}'); return _client .start(Stream.value(request)) - .map(checkForUpdate) + .doOnData(checkForUpdate) .doOnEach(logGrpc(request)) .firstOrNull; } @@ -137,7 +135,7 @@ class GrpcClient { logger.i('Sent ${request.repr}'); return _client .restart(Stream.value(request)) - .map(checkForUpdate) + .doOnData(checkForUpdate) .doOnEach(logGrpc(request)) .firstOrNull; } @@ -188,7 +186,7 @@ class GrpcClient { ); return _client .info(Stream.value(request)) - .map(checkForUpdate) + .doOnData(checkForUpdate) .last .then((r) => r.details.toList()); } @@ -226,7 +224,7 @@ class GrpcClient { logger.i('Sent ${request.repr}'); return _client .networks(Stream.value(request)) - .map(checkForUpdate) + .doOnData(checkForUpdate) .doOnEach(logGrpc(request)) .last .then((r) => r.interfaces); @@ -237,7 +235,7 @@ class GrpcClient { logger.i('Sent ${request.repr}'); return _client .version(Stream.value(request)) - .map(checkForUpdate) + .doOnData(checkForUpdate) .doOnEach(logGrpc(request)) .last .then((reply) => reply.version);