fix(ui): theme toggle is now actually reactive
The toggle changed _mode and called setState on StudioAppState, but the sidebar that hosts the toggle button never rebuilt: StudioApp returns `MaterialApp(home: const StudioShell())`, and the const child's element doesn't get a didUpdateWidget on parent rebuild. So the toggle's icon would only flip on the next health-poll tick (up to 5s later) when the sidebar's own setState fired for an unrelated reason. Switch to a ValueNotifier-based pattern: - StudioAppState exposes `modeNotifier` (ValueNotifier<...>) instead of a getter. - StudioApp wraps MaterialApp in a ValueListenableBuilder bound to the notifier so themeMode flips immediately. - _ThemeToggle is itself a ValueListenableBuilder against the same notifier — its icon and onPressed always reflect the current value without depending on parent rebuild timing. Bumps fai_studio 0.7.2 -> 0.7.3. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
20230684a9
commit
2f32023322
2 changed files with 54 additions and 37 deletions
|
|
@ -19,7 +19,7 @@ import 'widgets/widgets.dart';
|
||||||
/// Studio's own build version. Bump on every UI commit so the
|
/// Studio's own build version. Bump on every UI commit so the
|
||||||
/// running app self-identifies — visible in the sidebar header
|
/// running app self-identifies — visible in the sidebar header
|
||||||
/// and quick-glance proof that you're seeing the current build.
|
/// and quick-glance proof that you're seeing the current build.
|
||||||
const String kStudioVersion = '0.7.2';
|
const String kStudioVersion = '0.7.3';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
@ -45,23 +45,32 @@ class StudioApp extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class StudioAppState extends State<StudioApp> {
|
class StudioAppState extends State<StudioApp> {
|
||||||
late ThemeModeValue _mode;
|
/// Exposed so any descendant can listen via
|
||||||
|
/// `ValueListenableBuilder` and rebuild instantly when the
|
||||||
ThemeModeValue get mode => _mode;
|
/// theme flips. setState alone wouldn't propagate through
|
||||||
|
/// `MaterialApp(home: const StudioShell())` because the
|
||||||
|
/// const child's element doesn't get a `didUpdateWidget`.
|
||||||
|
late final ValueNotifier<ThemeModeValue> modeNotifier;
|
||||||
|
|
||||||
Future<void> setMode(ThemeModeValue m) async {
|
Future<void> setMode(ThemeModeValue m) async {
|
||||||
setState(() => _mode = m);
|
modeNotifier.value = m;
|
||||||
await HubService.instance.saveThemeMode(m);
|
await HubService.instance.saveThemeMode(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_mode = widget.initialThemeMode;
|
modeNotifier = ValueNotifier(widget.initialThemeMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeMode _flutterMode() {
|
@override
|
||||||
switch (_mode) {
|
void dispose() {
|
||||||
|
modeNotifier.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeMode _flutterMode(ThemeModeValue v) {
|
||||||
|
switch (v) {
|
||||||
case ThemeModeValue.system:
|
case ThemeModeValue.system:
|
||||||
return ThemeMode.system;
|
return ThemeMode.system;
|
||||||
case ThemeModeValue.light:
|
case ThemeModeValue.light:
|
||||||
|
|
@ -73,13 +82,16 @@ class StudioAppState extends State<StudioApp> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return ValueListenableBuilder<ThemeModeValue>(
|
||||||
|
valueListenable: modeNotifier,
|
||||||
|
builder: (_, mode, _) => MaterialApp(
|
||||||
title: 'F∆I Studio',
|
title: 'F∆I Studio',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: FaiTheme.light(),
|
theme: FaiTheme.light(),
|
||||||
darkTheme: FaiTheme.dark(),
|
darkTheme: FaiTheme.dark(),
|
||||||
themeMode: _flutterMode(),
|
themeMode: _flutterMode(mode),
|
||||||
home: const StudioShell(),
|
home: const StudioShell(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -436,7 +448,10 @@ class _ThemeToggle extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final app = StudioApp.of(context);
|
final app = StudioApp.of(context);
|
||||||
final mode = app?.mode ?? ThemeModeValue.system;
|
if (app == null) return const SizedBox.shrink();
|
||||||
|
return ValueListenableBuilder<ThemeModeValue>(
|
||||||
|
valueListenable: app.modeNotifier,
|
||||||
|
builder: (_, mode, _) {
|
||||||
final icon = switch (mode) {
|
final icon = switch (mode) {
|
||||||
ThemeModeValue.system => Icons.brightness_auto_outlined,
|
ThemeModeValue.system => Icons.brightness_auto_outlined,
|
||||||
ThemeModeValue.light => Icons.light_mode_outlined,
|
ThemeModeValue.light => Icons.light_mode_outlined,
|
||||||
|
|
@ -457,7 +472,9 @@ class _ThemeToggle extends StatelessWidget {
|
||||||
ThemeModeValue.light => ThemeModeValue.dark,
|
ThemeModeValue.light => ThemeModeValue.dark,
|
||||||
ThemeModeValue.dark => ThemeModeValue.system,
|
ThemeModeValue.dark => ThemeModeValue.system,
|
||||||
};
|
};
|
||||||
app?.setMode(next);
|
app.setMode(next);
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
name: fai_studio
|
name: fai_studio
|
||||||
description: "F∆I Studio — desktop GUI for the F∆I hub"
|
description: "F∆I Studio — desktop GUI for the F∆I hub"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.7.2
|
version: 0.7.3
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.0-200.1.beta
|
sdk: ^3.11.0-200.1.beta
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue