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:
+24
-22
@@ -40,24 +40,22 @@ Future<void> main() async {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
runApp(const MainApp());
|
runApp(
|
||||||
}
|
ArcaneApp(
|
||||||
|
|
||||||
class MainApp extends StatefulWidget {
|
|
||||||
const MainApp({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<MainApp> createState() => _MainAppState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MainAppState extends State<MainApp> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ArcaneApp(
|
|
||||||
services: [
|
services: [
|
||||||
IdService.I,
|
IdService.I,
|
||||||
],
|
],
|
||||||
child: MaterialApp(
|
child: const MainApp(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MainApp extends StatelessWidget {
|
||||||
|
const MainApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: Arcane.theme.light,
|
theme: Arcane.theme.light,
|
||||||
darkTheme: Arcane.theme.dark,
|
darkTheme: Arcane.theme.dark,
|
||||||
@@ -66,18 +64,22 @@ class _MainAppState extends State<MainApp> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text("Arcane Framework Example"),
|
title: const Text("Arcane Framework Example"),
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.settings_system_daydream),
|
||||||
|
onPressed: () {
|
||||||
|
Arcane.theme.followSystemTheme(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.contrast),
|
icon: const Icon(Icons.contrast),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Arcane.theme.switchTheme();
|
Arcane.theme.switchTheme();
|
||||||
setState(() {});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: const HomeScreen(),
|
body: const HomeScreen(),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,11 +118,11 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
child: const Text("Sign in"),
|
child: const Text("Sign in"),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await Arcane.auth.login<Map<String, String>>(
|
await Arcane.auth.login<Credentials>(
|
||||||
input: {
|
input: (
|
||||||
"email": "email",
|
email: "email",
|
||||||
"password": "password",
|
password: "password",
|
||||||
},
|
),
|
||||||
onLoggedIn: () async {
|
onLoggedIn: () async {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
|
|||||||
+60
-3
@@ -18,7 +18,7 @@ import "package:flutter/material.dart";
|
|||||||
/// child: MyApp(),
|
/// child: MyApp(),
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
class ArcaneApp extends StatelessWidget {
|
class ArcaneApp extends StatefulWidget {
|
||||||
/// A list of Arcane services that will be made available to the application.
|
/// A list of Arcane services that will be made available to the application.
|
||||||
///
|
///
|
||||||
/// These services will be provided to the widget tree using
|
/// These services will be provided to the widget tree using
|
||||||
@@ -42,13 +42,70 @@ class ArcaneApp extends StatelessWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ArcaneApp> createState() => _ArcaneAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ArcaneAppState extends State<ArcaneApp> with WidgetsBindingObserver {
|
||||||
|
final GlobalKey _appKey = GlobalKey();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ArcaneEnvironmentProvider(
|
return ArcaneEnvironmentProvider(
|
||||||
child: ArcaneServiceProvider(
|
child: ArcaneServiceProvider(
|
||||||
serviceInstances: services,
|
serviceInstances: widget.services,
|
||||||
child: child,
|
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 {
|
bool get isDarkMode {
|
||||||
final brightness = MediaQuery.of(this).platformBrightness;
|
final brightness = MediaQuery.platformBrightnessOf(this);
|
||||||
return brightness == Brightness.dark;
|
return brightness == Brightness.dark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import "dart:async";
|
||||||
|
|
||||||
import "package:arcane_framework/arcane_framework.dart";
|
import "package:arcane_framework/arcane_framework.dart";
|
||||||
import "package:flutter/material.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
|
/// `ArcaneReactiveTheme` allows switching between light and dark themes and provides
|
||||||
/// methods to customize the themes. The current theme mode can be accessed, and the
|
/// methods to customize the themes. The current theme mode can be accessed, and the
|
||||||
/// theme can be switched at runtime.
|
/// theme can be switched at runtime.
|
||||||
class ArcaneReactiveTheme extends ArcaneService {
|
class ArcaneReactiveTheme extends ArcaneService with WidgetsBindingObserver {
|
||||||
/// The singleton instance of `ArcaneReactiveTheme`.
|
/// The singleton instance of `ArcaneReactiveTheme`.
|
||||||
static final ArcaneReactiveTheme _instance = ArcaneReactiveTheme._internal();
|
static final ArcaneReactiveTheme _instance = ArcaneReactiveTheme._internal();
|
||||||
|
|
||||||
@@ -17,13 +19,24 @@ class ArcaneReactiveTheme extends ArcaneService {
|
|||||||
|
|
||||||
ArcaneReactiveTheme._internal();
|
ArcaneReactiveTheme._internal();
|
||||||
|
|
||||||
|
// Whether to follow system theme
|
||||||
|
bool _followingSystemTheme = false;
|
||||||
|
|
||||||
final ValueNotifier<ThemeMode> _systemThemeNotifier =
|
final ValueNotifier<ThemeMode> _systemThemeNotifier =
|
||||||
ValueNotifier(ThemeMode.light);
|
ValueNotifier(ThemeMode.system);
|
||||||
|
|
||||||
final ValueNotifier<ThemeMode> _currentThemeNotifier =
|
final StreamController<ThemeMode> _themeStreamController =
|
||||||
ValueNotifier(ThemeMode.light);
|
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.
|
/// A listenable that notifies listeners when the system theme mode changes.
|
||||||
ThemeMode get systemTheme => I._systemThemeNotifier.value;
|
ThemeMode get systemTheme => I._systemThemeNotifier.value;
|
||||||
@@ -52,21 +65,17 @@ class ArcaneReactiveTheme extends ArcaneService {
|
|||||||
/// ArcaneReactiveTheme.I.switchTheme();
|
/// ArcaneReactiveTheme.I.switchTheme();
|
||||||
/// ```
|
/// ```
|
||||||
ArcaneReactiveTheme switchTheme({ThemeMode? themeMode}) {
|
ArcaneReactiveTheme switchTheme({ThemeMode? themeMode}) {
|
||||||
if (I._systemThemeNotifier.hasListeners) {
|
_followingSystemTheme = false;
|
||||||
_systemThemeNotifier.removeListener(_systemThemeListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (themeMode != null) {
|
if (themeMode != null) {
|
||||||
_currentThemeNotifier.value = themeMode;
|
_updateTheme(themeMode);
|
||||||
} else {
|
} else {
|
||||||
_currentThemeNotifier.value =
|
_updateTheme(
|
||||||
_currentThemeNotifier.value == ThemeMode.light
|
currentTheme == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark,
|
||||||
? ThemeMode.dark
|
);
|
||||||
: ThemeMode.light;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
return I;
|
return I;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,21 +88,33 @@ class ArcaneReactiveTheme extends ArcaneService {
|
|||||||
/// final ThemeMode mode = Arcane.theme.systemTheme.value;
|
/// final ThemeMode mode = Arcane.theme.systemTheme.value;
|
||||||
/// ```
|
/// ```
|
||||||
ArcaneReactiveTheme followSystemTheme(BuildContext context) {
|
ArcaneReactiveTheme followSystemTheme(BuildContext context) {
|
||||||
final ThemeMode systemMode =
|
if (!_followingSystemTheme) {
|
||||||
context.isDarkMode ? ThemeMode.dark : ThemeMode.light;
|
_followingSystemTheme = true;
|
||||||
|
|
||||||
if (!I._systemThemeNotifier.hasListeners) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
I._systemThemeNotifier.addListener(_systemThemeListener);
|
checkSystemTheme(context);
|
||||||
}
|
});
|
||||||
|
|
||||||
if (systemMode != currentTheme) {
|
|
||||||
_systemThemeNotifier.value = systemMode;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return I;
|
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.
|
/// Sets a custom `ThemeData` for the dark theme.
|
||||||
///
|
///
|
||||||
/// This allows you to customize the dark theme and notify listeners to apply the
|
/// This allows you to customize the dark theme and notify listeners to apply the
|
||||||
@@ -128,15 +149,13 @@ class ArcaneReactiveTheme extends ArcaneService {
|
|||||||
void reset() {
|
void reset() {
|
||||||
_darkTheme.value = ThemeData.dark();
|
_darkTheme.value = ThemeData.dark();
|
||||||
_lightTheme.value = ThemeData.light();
|
_lightTheme.value = ThemeData.light();
|
||||||
_systemThemeNotifier.value = ThemeMode.light;
|
_followingSystemTheme = false;
|
||||||
_currentThemeNotifier.value = ThemeMode.light;
|
_updateTheme(ThemeMode.light);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _systemThemeListener() {
|
void _updateTheme(ThemeMode themeMode) {
|
||||||
if (currentTheme != _systemThemeNotifier.value) {
|
_currentTheme = themeMode;
|
||||||
_currentThemeNotifier.value = _systemThemeNotifier.value;
|
_themeStreamController.add(themeMode);
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user