mirror of
https://github.com/hanskokx/arcane_framework.git
synced 2026-05-14 10:29:06 +02:00
Fixes the reactive theme service to properly follow the system brightness
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
+60
-3
@@ -18,7 +18,7 @@ import "package:flutter/material.dart";
|
||||
/// child: MyApp(),
|
||||
/// );
|
||||
/// ```
|
||||
class ArcaneApp extends StatelessWidget {
|
||||
class ArcaneApp extends StatefulWidget {
|
||||
/// A list of Arcane services that will be made available to the application.
|
||||
///
|
||||
/// These services will be provided to the widget tree using
|
||||
@@ -42,13 +42,70 @@ class ArcaneApp extends StatelessWidget {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ArcaneApp> createState() => _ArcaneAppState();
|
||||
}
|
||||
|
||||
class _ArcaneAppState extends State<ArcaneApp> with WidgetsBindingObserver {
|
||||
final GlobalKey _appKey = GlobalKey();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ArcaneEnvironmentProvider(
|
||||
child: ArcaneServiceProvider(
|
||||
serviceInstances: services,
|
||||
child: child,
|
||||
serviceInstances: widget.services,
|
||||
child: Builder(
|
||||
key: _appKey,
|
||||
builder: (context) {
|
||||
_updateContextReference(context);
|
||||
|
||||
return StreamBuilder<ThemeMode>(
|
||||
stream: ArcaneReactiveTheme.I.currentThemeStream,
|
||||
builder: (context, AsyncSnapshot<ThemeMode> snapshot) {
|
||||
if (!snapshot.hasData) return widget.child;
|
||||
|
||||
return KeyedSubtree(
|
||||
key: Key(snapshot.data!.name),
|
||||
child: widget.child,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Update our context reference whenever the widget is built
|
||||
void _updateContextReference(BuildContext context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// Only store this context if the widget is still mounted
|
||||
if (mounted) {
|
||||
// Store this context in a way that ArcaneReactiveTheme can access it
|
||||
ArcaneReactiveTheme.I.checkSystemTheme(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Register as an observer to detect system theme changes
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Clean up the observer when the widget is disposed
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangePlatformBrightness() {
|
||||
// This is called when the system brightness changes
|
||||
// Check and update the theme if we're following system theme
|
||||
ArcaneReactiveTheme.I.checkSystemTheme(context);
|
||||
super.didChangePlatformBrightness();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ extension DarkMode on BuildContext {
|
||||
/// }
|
||||
/// ```
|
||||
bool get isDarkMode {
|
||||
final brightness = MediaQuery.of(this).platformBrightness;
|
||||
final brightness = MediaQuery.platformBrightnessOf(this);
|
||||
return brightness == Brightness.dark;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "dart:async";
|
||||
|
||||
import "package:arcane_framework/arcane_framework.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
@@ -8,7 +10,7 @@ part "reactive_theme_extensions.dart";
|
||||
/// `ArcaneReactiveTheme` allows switching between light and dark themes and provides
|
||||
/// methods to customize the themes. The current theme mode can be accessed, and the
|
||||
/// theme can be switched at runtime.
|
||||
class ArcaneReactiveTheme extends ArcaneService {
|
||||
class ArcaneReactiveTheme extends ArcaneService with WidgetsBindingObserver {
|
||||
/// The singleton instance of `ArcaneReactiveTheme`.
|
||||
static final ArcaneReactiveTheme _instance = ArcaneReactiveTheme._internal();
|
||||
|
||||
@@ -17,13 +19,24 @@ class ArcaneReactiveTheme extends ArcaneService {
|
||||
|
||||
ArcaneReactiveTheme._internal();
|
||||
|
||||
// Whether to follow system theme
|
||||
bool _followingSystemTheme = false;
|
||||
|
||||
final ValueNotifier<ThemeMode> _systemThemeNotifier =
|
||||
ValueNotifier(ThemeMode.light);
|
||||
ValueNotifier(ThemeMode.system);
|
||||
|
||||
final ValueNotifier<ThemeMode> _currentThemeNotifier =
|
||||
ValueNotifier(ThemeMode.light);
|
||||
final StreamController<ThemeMode> _themeStreamController =
|
||||
StreamController<ThemeMode>.broadcast(
|
||||
onCancel: () {
|
||||
I._themeStreamController.close();
|
||||
},
|
||||
);
|
||||
|
||||
ThemeMode get currentTheme => I._currentThemeNotifier.value;
|
||||
Stream<ThemeMode> get currentThemeStream => I._themeStreamController.stream;
|
||||
|
||||
ThemeMode _currentTheme = ThemeMode.light;
|
||||
|
||||
ThemeMode get currentTheme => _currentTheme;
|
||||
|
||||
/// A listenable that notifies listeners when the system theme mode changes.
|
||||
ThemeMode get systemTheme => I._systemThemeNotifier.value;
|
||||
@@ -52,21 +65,17 @@ class ArcaneReactiveTheme extends ArcaneService {
|
||||
/// ArcaneReactiveTheme.I.switchTheme();
|
||||
/// ```
|
||||
ArcaneReactiveTheme switchTheme({ThemeMode? themeMode}) {
|
||||
if (I._systemThemeNotifier.hasListeners) {
|
||||
_systemThemeNotifier.removeListener(_systemThemeListener);
|
||||
}
|
||||
_followingSystemTheme = false;
|
||||
|
||||
if (themeMode != null) {
|
||||
_currentThemeNotifier.value = themeMode;
|
||||
_updateTheme(themeMode);
|
||||
} else {
|
||||
_currentThemeNotifier.value =
|
||||
_currentThemeNotifier.value == ThemeMode.light
|
||||
? ThemeMode.dark
|
||||
: ThemeMode.light;
|
||||
_updateTheme(
|
||||
currentTheme == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark,
|
||||
);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
|
||||
return I;
|
||||
}
|
||||
|
||||
@@ -79,21 +88,33 @@ class ArcaneReactiveTheme extends ArcaneService {
|
||||
/// final ThemeMode mode = Arcane.theme.systemTheme.value;
|
||||
/// ```
|
||||
ArcaneReactiveTheme followSystemTheme(BuildContext context) {
|
||||
final ThemeMode systemMode =
|
||||
context.isDarkMode ? ThemeMode.dark : ThemeMode.light;
|
||||
if (!_followingSystemTheme) {
|
||||
_followingSystemTheme = true;
|
||||
|
||||
if (!I._systemThemeNotifier.hasListeners) {
|
||||
I._systemThemeNotifier.addListener(_systemThemeListener);
|
||||
}
|
||||
|
||||
if (systemMode != currentTheme) {
|
||||
_systemThemeNotifier.value = systemMode;
|
||||
notifyListeners();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
checkSystemTheme(context);
|
||||
});
|
||||
}
|
||||
|
||||
return I;
|
||||
}
|
||||
|
||||
/// Check and apply the system theme if we're following it
|
||||
void checkSystemTheme(BuildContext context) {
|
||||
if (!_followingSystemTheme) return;
|
||||
|
||||
final Brightness systemBrightness =
|
||||
MediaQuery.platformBrightnessOf(context);
|
||||
|
||||
final ThemeMode systemMode =
|
||||
systemBrightness == Brightness.dark ? ThemeMode.dark : ThemeMode.light;
|
||||
|
||||
if (systemMode != _currentTheme) {
|
||||
_updateTheme(systemMode);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a custom `ThemeData` for the dark theme.
|
||||
///
|
||||
/// This allows you to customize the dark theme and notify listeners to apply the
|
||||
@@ -128,15 +149,13 @@ class ArcaneReactiveTheme extends ArcaneService {
|
||||
void reset() {
|
||||
_darkTheme.value = ThemeData.dark();
|
||||
_lightTheme.value = ThemeData.light();
|
||||
_systemThemeNotifier.value = ThemeMode.light;
|
||||
_currentThemeNotifier.value = ThemeMode.light;
|
||||
_followingSystemTheme = false;
|
||||
_updateTheme(ThemeMode.light);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _systemThemeListener() {
|
||||
if (currentTheme != _systemThemeNotifier.value) {
|
||||
_currentThemeNotifier.value = _systemThemeNotifier.value;
|
||||
notifyListeners();
|
||||
}
|
||||
void _updateTheme(ThemeMode themeMode) {
|
||||
_currentTheme = themeMode;
|
||||
_themeStreamController.add(themeMode);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user