What's new and the updater come from AppGraph (no new singleton; singletons_test back at its baseline)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-18 02:22:03 +03:00
1 parent 5bc252bf02
commit d48915dc47
6 files changed
+29 -16

No files matched your search

+15
View File
@@ -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,
);
}
@@ -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),
+4 -4
View File
@@ -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<AppShell> 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<void> _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();
}
@@ -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<void> Function(UpdateManifest manifest)? beforeRestart;
/// notes for its first start ([WhatsNew.installing], wired by `AppGraph`).
Future<void> Function(UpdateManifest manifest)? beforeRestart;
UpdateController(
{this.currentVersion = appVersion,
@@ -70,8 +69,7 @@ class UpdateController {
this.installRoot,
this.restart = _closeAndRestart,
this.skipDevBuilds = true,
Future<void> 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
+3 -3
View File
@@ -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<void> 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<ReleaseNotes?>(null);