mirror of
https://github.com/hanskokx/arcane_framework.git
synced 2026-05-14 10:29:06 +02:00
Fixes tests and updates reactive theme
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -59,7 +59,8 @@ class MainApp extends StatelessWidget {
|
|||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: Arcane.theme.light,
|
theme: Arcane.theme.light,
|
||||||
darkTheme: Arcane.theme.dark,
|
darkTheme: Arcane.theme.dark,
|
||||||
themeMode: Arcane.theme.currentTheme,
|
themeMode:
|
||||||
|
ArcaneTheme.of(context)?.themeMode ?? Arcane.theme.currentTheme,
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text("Arcane Framework Example"),
|
title: const Text("Arcane Framework Example"),
|
||||||
|
|||||||
@@ -45,4 +45,5 @@ export "package:arcane_framework/src/services/authentication/authentication_serv
|
|||||||
export "package:arcane_framework/src/services/feature_flags/feature_flags_service.dart";
|
export "package:arcane_framework/src/services/feature_flags/feature_flags_service.dart";
|
||||||
export "package:arcane_framework/src/services/logging/logging_service.dart";
|
export "package:arcane_framework/src/services/logging/logging_service.dart";
|
||||||
export "package:arcane_framework/src/services/reactive_theme/reactive_theme_service.dart";
|
export "package:arcane_framework/src/services/reactive_theme/reactive_theme_service.dart";
|
||||||
|
export "package:arcane_framework/src/services/reactive_theme/reactive_theme_wrapper.dart";
|
||||||
export "package:result_monad/result_monad.dart";
|
export "package:result_monad/result_monad.dart";
|
||||||
|
|||||||
+16
-20
@@ -56,16 +56,15 @@ class _ArcaneAppState extends State<ArcaneApp> with WidgetsBindingObserver {
|
|||||||
serviceInstances: widget.services,
|
serviceInstances: widget.services,
|
||||||
child: Builder(
|
child: Builder(
|
||||||
key: _appKey,
|
key: _appKey,
|
||||||
builder: (context) {
|
builder: (BuildContext currentContext) {
|
||||||
_updateContextReference(context);
|
|
||||||
|
|
||||||
return StreamBuilder<ThemeMode>(
|
return StreamBuilder<ThemeMode>(
|
||||||
stream: ArcaneReactiveTheme.I.currentThemeStream,
|
stream: ArcaneReactiveTheme.I.currentThemeStream,
|
||||||
|
initialData: ArcaneReactiveTheme.I.currentTheme,
|
||||||
builder: (context, AsyncSnapshot<ThemeMode> snapshot) {
|
builder: (context, AsyncSnapshot<ThemeMode> snapshot) {
|
||||||
if (!snapshot.hasData) return widget.child;
|
final ThemeMode themeMode = snapshot.data ?? ThemeMode.light;
|
||||||
|
|
||||||
return KeyedSubtree(
|
return ArcaneTheme(
|
||||||
key: Key(snapshot.data!.name),
|
themeMode: themeMode,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -76,17 +75,6 @@ class _ArcaneAppState extends State<ArcaneApp> with WidgetsBindingObserver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -103,9 +91,17 @@ class _ArcaneAppState extends State<ArcaneApp> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangePlatformBrightness() {
|
void didChangePlatformBrightness() {
|
||||||
// This is called when the system brightness changes
|
// When system brightness changes, find the current builder context
|
||||||
// Check and update the theme if we're following system theme
|
// and use it to check the system theme
|
||||||
ArcaneReactiveTheme.I.checkSystemTheme(context);
|
if (mounted && _appKey.currentContext != null) {
|
||||||
|
// Use the current context from the key to check system theme
|
||||||
|
final BuildContext currentContext = _appKey.currentContext!;
|
||||||
|
if (ArcaneReactiveTheme.I.isFollowingSystemTheme) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
ArcaneReactiveTheme.I.checkSystemTheme(currentContext);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
super.didChangePlatformBrightness();
|
super.didChangePlatformBrightness();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ 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 with WidgetsBindingObserver {
|
///
|
||||||
|
/// System theme changes are detected by the `ArcaneApp` widget, which ensures
|
||||||
|
/// theme updates happen automatically when the device theme changes.
|
||||||
|
class ArcaneReactiveTheme extends ArcaneService {
|
||||||
/// The singleton instance of `ArcaneReactiveTheme`.
|
/// The singleton instance of `ArcaneReactiveTheme`.
|
||||||
static final ArcaneReactiveTheme _instance = ArcaneReactiveTheme._internal();
|
static final ArcaneReactiveTheme _instance = ArcaneReactiveTheme._internal();
|
||||||
|
|
||||||
@@ -22,6 +25,12 @@ class ArcaneReactiveTheme extends ArcaneService with WidgetsBindingObserver {
|
|||||||
// Whether to follow system theme
|
// Whether to follow system theme
|
||||||
bool _followingSystemTheme = false;
|
bool _followingSystemTheme = false;
|
||||||
|
|
||||||
|
/// Whether the theme service is currently following the system theme.
|
||||||
|
///
|
||||||
|
/// When true, the theme will automatically switch between light and dark
|
||||||
|
/// based on the system's brightness setting.
|
||||||
|
bool get isFollowingSystemTheme => _followingSystemTheme;
|
||||||
|
|
||||||
final ValueNotifier<ThemeMode> _systemThemeNotifier =
|
final ValueNotifier<ThemeMode> _systemThemeNotifier =
|
||||||
ValueNotifier(ThemeMode.system);
|
ValueNotifier(ThemeMode.system);
|
||||||
|
|
||||||
@@ -32,10 +41,12 @@ class ArcaneReactiveTheme extends ArcaneService with WidgetsBindingObserver {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Stream of theme mode changes that can be listened to for reactive UI updates.
|
||||||
Stream<ThemeMode> get currentThemeStream => I._themeStreamController.stream;
|
Stream<ThemeMode> get currentThemeStream => I._themeStreamController.stream;
|
||||||
|
|
||||||
ThemeMode _currentTheme = ThemeMode.light;
|
ThemeMode _currentTheme = ThemeMode.light;
|
||||||
|
|
||||||
|
/// The currently active theme mode (light or dark).
|
||||||
ThemeMode get currentTheme => _currentTheme;
|
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.
|
||||||
@@ -49,10 +60,14 @@ class ArcaneReactiveTheme extends ArcaneService with WidgetsBindingObserver {
|
|||||||
|
|
||||||
/// Returns the current dark theme `ThemeData`.
|
/// Returns the current dark theme `ThemeData`.
|
||||||
ThemeData get dark => _darkTheme.value;
|
ThemeData get dark => _darkTheme.value;
|
||||||
|
|
||||||
|
/// ValueNotifier for the dark theme that can be observed for changes.
|
||||||
ValueNotifier<ThemeData> get darkTheme => I._darkTheme;
|
ValueNotifier<ThemeData> get darkTheme => I._darkTheme;
|
||||||
|
|
||||||
/// Returns the current light theme `ThemeData`.
|
/// Returns the current light theme `ThemeData`.
|
||||||
ThemeData get light => _lightTheme.value;
|
ThemeData get light => _lightTheme.value;
|
||||||
|
|
||||||
|
/// ValueNotifier for the light theme that can be observed for changes.
|
||||||
ValueNotifier<ThemeData> get lightTheme => I._lightTheme;
|
ValueNotifier<ThemeData> get lightTheme => I._lightTheme;
|
||||||
|
|
||||||
/// Switches the current theme between light and dark modes.
|
/// Switches the current theme between light and dark modes.
|
||||||
@@ -82,24 +97,26 @@ class ArcaneReactiveTheme extends ArcaneService with WidgetsBindingObserver {
|
|||||||
/// Switches the current theme between light and dark modes automatically
|
/// Switches the current theme between light and dark modes automatically
|
||||||
/// based upon the system's current mode.
|
/// based upon the system's current mode.
|
||||||
///
|
///
|
||||||
|
/// This will also register for system theme changes, so the theme will
|
||||||
|
/// automatically update when the system theme changes.
|
||||||
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// ArcaneReactiveTheme.I.followSystemTheme(context);
|
/// ArcaneReactiveTheme.I.followSystemTheme(context);
|
||||||
/// final ThemeMode mode = Arcane.theme.systemTheme.value;
|
|
||||||
/// ```
|
/// ```
|
||||||
ArcaneReactiveTheme followSystemTheme(BuildContext context) {
|
ArcaneReactiveTheme followSystemTheme(BuildContext context) {
|
||||||
if (!_followingSystemTheme) {
|
_followingSystemTheme = true;
|
||||||
_followingSystemTheme = true;
|
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
// Always check the system theme when this method is called
|
||||||
checkSystemTheme(context);
|
checkSystemTheme(context);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return I;
|
return I;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check and apply the system theme if we're following it
|
/// Check and apply the system theme if we're following it
|
||||||
|
///
|
||||||
|
/// This is called automatically when the system brightness changes if
|
||||||
|
/// [followSystemTheme] has been enabled.
|
||||||
void checkSystemTheme(BuildContext context) {
|
void checkSystemTheme(BuildContext context) {
|
||||||
if (!_followingSystemTheme) return;
|
if (!_followingSystemTheme) return;
|
||||||
|
|
||||||
@@ -109,6 +126,7 @@ class ArcaneReactiveTheme extends ArcaneService with WidgetsBindingObserver {
|
|||||||
final ThemeMode systemMode =
|
final ThemeMode systemMode =
|
||||||
systemBrightness == Brightness.dark ? ThemeMode.dark : ThemeMode.light;
|
systemBrightness == Brightness.dark ? ThemeMode.dark : ThemeMode.light;
|
||||||
|
|
||||||
|
// Only update and notify if the theme actually changed
|
||||||
if (systemMode != _currentTheme) {
|
if (systemMode != _currentTheme) {
|
||||||
_updateTheme(systemMode);
|
_updateTheme(systemMode);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -145,6 +163,10 @@ class ArcaneReactiveTheme extends ArcaneService with WidgetsBindingObserver {
|
|||||||
return I;
|
return I;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resets the theme service to its default state.
|
||||||
|
///
|
||||||
|
/// This resets both light and dark themes to their default values and
|
||||||
|
/// disables system theme following.
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
void reset() {
|
void reset() {
|
||||||
_darkTheme.value = ThemeData.dark();
|
_darkTheme.value = ThemeData.dark();
|
||||||
@@ -154,6 +176,7 @@ class ArcaneReactiveTheme extends ArcaneService with WidgetsBindingObserver {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates the current theme mode and broadcasts the change.
|
||||||
void _updateTheme(ThemeMode themeMode) {
|
void _updateTheme(ThemeMode themeMode) {
|
||||||
_currentTheme = themeMode;
|
_currentTheme = themeMode;
|
||||||
_themeStreamController.add(themeMode);
|
_themeStreamController.add(themeMode);
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import "package:arcane_framework/src/services/reactive_theme/reactive_theme_service.dart";
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
|
||||||
|
class ArcaneTheme extends InheritedWidget {
|
||||||
|
final ThemeMode themeMode;
|
||||||
|
|
||||||
|
const ArcaneTheme({
|
||||||
|
required this.themeMode,
|
||||||
|
required super.child,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
static ArcaneTheme? of(BuildContext context) {
|
||||||
|
return context.dependOnInheritedWidgetOfExactType<ArcaneTheme>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool updateShouldNotify(ArcaneTheme oldWidget) {
|
||||||
|
return oldWidget.themeMode != themeMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ArcaneReactiveTheme get service => ArcaneReactiveTheme.I;
|
||||||
|
static bool get isFollowingSystemTheme => service.isFollowingSystemTheme;
|
||||||
|
static Stream<ThemeMode> get currentThemeStream => service.currentThemeStream;
|
||||||
|
static ThemeMode get currentTheme => service.currentTheme;
|
||||||
|
static ThemeMode get systemTheme => service.systemTheme;
|
||||||
|
static ThemeData get dark => service.dark;
|
||||||
|
static ValueNotifier<ThemeData> get darkTheme => service.darkTheme;
|
||||||
|
static ThemeData get light => service.light;
|
||||||
|
static ValueNotifier<ThemeData> get lightTheme => service.lightTheme;
|
||||||
|
|
||||||
|
static ArcaneReactiveTheme Function({ThemeMode? themeMode}) get switchTheme =>
|
||||||
|
service.switchTheme;
|
||||||
|
static ArcaneReactiveTheme Function(BuildContext context)
|
||||||
|
get followSystemTheme => service.followSystemTheme;
|
||||||
|
static void get checkSystemTheme => service.checkSystemTheme;
|
||||||
|
static ArcaneReactiveTheme Function(ThemeData theme) get setDarkTheme =>
|
||||||
|
service.setDarkTheme;
|
||||||
|
static ArcaneReactiveTheme Function(ThemeData theme) get setLightTheme =>
|
||||||
|
service.setLightTheme;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user