diff --git a/apps/desktop/lib/main.dart b/apps/desktop/lib/main.dart index 1e8fc554..8c0cf5f2 100644 --- a/apps/desktop/lib/main.dart +++ b/apps/desktop/lib/main.dart @@ -16,7 +16,7 @@ import 'src/platform/smoke.dart'; import 'src/screens/home/ui.dart'; import 'src/session/app_events.dart' show UpdateAvailable; import 'src/state/settings.dart'; -import 'src/update/update_controller.dart' show UpdateController, UpdatePhase, launchArgs; +import 'src/update/update_controller.dart' show UpdatePhase, launchArgs; Future main(List args) async { launchArgs = List.unmodifiable(args); @@ -77,7 +77,7 @@ Future main(List args) async { // Linux: the launcher entry and icon Wayland needs (release installs). unawaited(installDesktopEntry()); // A found update is an event too (the band shows it). - final updates = UpdateController.instance; + final updates = graph.updates; updates.status.addListener(() { final s = updates.status.value; final version = s.manifest?.version; diff --git a/apps/desktop/lib/src/app/app_graph.dart b/apps/desktop/lib/src/app/app_graph.dart index 00a65227..0fb6497b 100644 --- a/apps/desktop/lib/src/app/app_graph.dart +++ b/apps/desktop/lib/src/app/app_graph.dart @@ -21,6 +21,8 @@ import '../sfx/sound_bank.dart'; import '../sfx/synth_sfx.dart'; import '../state/connect_prefs.dart'; import '../state/settings.dart'; +import '../update/update_controller.dart'; +import '../update/whats_new.dart'; import '../watch/watch_history.dart'; import '../watch/watch_tracker.dart'; @@ -57,6 +59,12 @@ class AppGraph { /// Watch tracking and the device's watch history (D3). final WatchTracker watch; + /// The app's updater (checks, the update band, install and restart). + final UpdateController updates; + + /// What's new once after an update; keeps the notes the updater saves. + final WhatsNew whatsNew; + AppGraph._({ required this.ports, required this.sfx, @@ -68,6 +76,8 @@ class AppGraph { required this.navigation, required this.library, required this.watch, + required this.updates, + required this.whatsNew, }); /// The desktop app's graph. Tests pass their own [ports], [identity] @@ -146,6 +156,9 @@ class AppGraph { ..push = ({String? name, String? avatar}) { if (navigation.online.peek()) coordinator.updateProfile(name: name, avatar: avatar); }; + final updates = UpdateController.instance; + final whatsNew = WhatsNew(store: SettingsWhatsNewStore(settings)); + updates.beforeRestart ??= whatsNew.installing; return AppGraph._( ports: p, sfx: sfx, @@ -157,6 +170,8 @@ class AppGraph { navigation: navigation, library: matcher, watch: watch, + updates: updates, + whatsNew: whatsNew, ); } diff --git a/apps/desktop/lib/src/screens/home/about_page.dart b/apps/desktop/lib/src/screens/home/about_page.dart index cb94fa8b..727dd1ed 100644 --- a/apps/desktop/lib/src/screens/home/about_page.dart +++ b/apps/desktop/lib/src/screens/home/about_page.dart @@ -83,9 +83,9 @@ class AboutPanel extends StatelessWidget { UpdateStatusLine(controller: updates), // This version's notes, once kept (the install, a check). ValueListenableBuilder( - valueListenable: (updates ?? UpdateController.instance).latest, + valueListenable: (updates ?? AppScope.of(context).updates).latest, builder: (context, _, _) { - final notes = (whatsNew ?? WhatsNew.instance).current; + final notes = (whatsNew ?? AppScope.of(context).whatsNew).current; if (notes == null) return const SizedBox.shrink(); return Padding( padding: const EdgeInsets.only(top: 8), diff --git a/apps/desktop/lib/src/shell/app_shell.dart b/apps/desktop/lib/src/shell/app_shell.dart index 8634bc4c..e6de3dde 100644 --- a/apps/desktop/lib/src/shell/app_shell.dart +++ b/apps/desktop/lib/src/shell/app_shell.dart @@ -56,7 +56,7 @@ import 'welcome.dart'; class AppShell extends StatefulWidget { final UpdateController? updates; final bool autoUpdateCheck; - /// The card once after an update; defaults to [WhatsNew.instance] when + /// The card once after an update; defaults to the graph's [WhatsNew] when /// [autoUpdateCheck] is on (off in tests unless given). final WhatsNew? whatsNew; /// Off in tests: the drag area, window buttons and fullscreen need @@ -293,16 +293,16 @@ class AppShellState extends State with SingleTickerProviderStateMixin /// What's new once after an update: due now (notes kept by the install) /// or once a check reads this version's manifest. Future _startWhatsNew() async { - await AppSettings.instance.ensureLoaded(); + await _graph.coordinator.settings.ensureLoaded(); if (!mounted) return; - final w = _whatsNewSource = widget.whatsNew ?? WhatsNew.instance; + final w = _whatsNewSource = widget.whatsNew ?? _graph.whatsNew; w.due.addListener(_maybeWhatsNew); // Not over the full player while a video is on: once it ends or minimises. _stops.add(effect(() { _watching.value; untracked(() => _later(_maybeWhatsNew)); })); - w.start(widget.updates ?? UpdateController.instance); + w.start(widget.updates ?? _graph.updates); _maybeWhatsNew(); } diff --git a/apps/desktop/lib/src/update/update_controller.dart b/apps/desktop/lib/src/update/update_controller.dart index 1077abf5..8a5941bf 100644 --- a/apps/desktop/lib/src/update/update_controller.dart +++ b/apps/desktop/lib/src/update/update_controller.dart @@ -5,7 +5,6 @@ import 'package:hive_ce_flutter/hive_flutter.dart'; import 'package:sync_relay_core/core.dart'; import '../platform/app_shutdown.dart'; -import 'whats_new.dart'; // In-app update flow behind the update bar (UpdateBar). Same code path as // `--upgrade` (core's updater, downloadAndApply); this adds state for the @@ -60,8 +59,8 @@ class UpdateController { final RestartFn restart; final bool skipDevBuilds; /// Awaited after the apply, before [restart]: keeps the new version's - /// notes for its first start ([WhatsNew.installing]). - final Future Function(UpdateManifest manifest)? beforeRestart; + /// notes for its first start ([WhatsNew.installing], wired by `AppGraph`). + Future Function(UpdateManifest manifest)? beforeRestart; UpdateController( {this.currentVersion = appVersion, @@ -70,8 +69,7 @@ class UpdateController { this.installRoot, this.restart = _closeAndRestart, this.skipDevBuilds = true, - Future Function(UpdateManifest manifest)? beforeRestart}) - : beforeRestart = beforeRestart ?? WhatsNew.instance.installing; + this.beforeRestart}); final status = ValueNotifier(const UpdateStatus(UpdatePhase.idle)); /// The manifest of the last check that read one, newer or not (What's diff --git a/apps/desktop/lib/src/update/whats_new.dart b/apps/desktop/lib/src/update/whats_new.dart index 799dc708..c2faa1b3 100644 --- a/apps/desktop/lib/src/update/whats_new.dart +++ b/apps/desktop/lib/src/update/whats_new.dart @@ -61,7 +61,7 @@ abstract class WhatsNewStore { class SettingsWhatsNewStore implements WhatsNewStore { final AppSettings settings; - SettingsWhatsNewStore([AppSettings? settings]) : settings = settings ?? AppSettings.instance; + SettingsWhatsNewStore(this.settings); @override String? get seen => settings.whatsNewSeen; @override @@ -88,14 +88,14 @@ class MemoryWhatsNewStore implements WhatsNewStore { Future setNotes(ReleaseNotes notes) async => this.notes = notes; } +/// Made once by `AppGraph` (the shell and About read it from there). class WhatsNew { - static final instance = WhatsNew(); final String currentVersion; final WhatsNewStore store; - WhatsNew({this.currentVersion = appVersion, WhatsNewStore? store}) : store = store ?? SettingsWhatsNewStore(); + WhatsNew({this.currentVersion = appVersion, required this.store}); /// The card to show now (once); null when there is none. final due = ValueNotifier(null);