mirror of
https://github.com/hanskokx/arcane_framework.git
synced 2026-05-14 10:29:06 +02:00
Refactor theme management to use ValueListenableBuilder for effective theme updates
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
+65
-53
@@ -270,9 +270,6 @@ class ArcaneThemeExample extends StatelessWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
static final Listenable themeListenable =
|
|
||||||
Listenable.merge([Arcane.theme.darkTheme, Arcane.theme.lightTheme]);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
@@ -313,37 +310,40 @@ class ArcaneThemeExample extends StatelessWidget {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Checkbox(
|
ValueListenableBuilder<bool>(
|
||||||
value: Arcane.theme.isFollowingSystemTheme,
|
valueListenable: Arcane.theme.followingSystemThemeChanges,
|
||||||
onChanged: (value) {
|
builder: (context, isFollowingSystem, _) => Checkbox(
|
||||||
final ThemeMode oldTheme =
|
value: isFollowingSystem,
|
||||||
Arcane.theme.currentThemeMode;
|
onChanged: (value) {
|
||||||
if (value == true) {
|
final ThemeMode oldTheme =
|
||||||
Arcane.theme.followSystemTheme(context);
|
Arcane.theme.currentThemeMode;
|
||||||
Arcane.log(
|
if (value == true) {
|
||||||
"Switching theme",
|
Arcane.theme.followSystemTheme(context);
|
||||||
metadata: {
|
Arcane.log(
|
||||||
"followingSystemTheme":
|
"Switching theme",
|
||||||
"${Arcane.theme.isFollowingSystemTheme}",
|
metadata: {
|
||||||
"newMode": Arcane.theme.currentThemeMode.name,
|
"followingSystemTheme":
|
||||||
"oldMode": oldTheme.name,
|
"${Arcane.theme.isFollowingSystemTheme}",
|
||||||
},
|
"newMode": Arcane.theme.currentThemeMode.name,
|
||||||
);
|
"oldMode": oldTheme.name,
|
||||||
} else {
|
},
|
||||||
Arcane.theme.switchTheme(
|
);
|
||||||
themeMode: Arcane.theme.systemThemeMode,
|
} else {
|
||||||
);
|
Arcane.theme.switchTheme(
|
||||||
Arcane.log(
|
themeMode: Arcane.theme.systemThemeMode,
|
||||||
"Switching theme",
|
);
|
||||||
metadata: {
|
Arcane.log(
|
||||||
"followingSystemTheme":
|
"Switching theme",
|
||||||
"${Arcane.theme.isFollowingSystemTheme}",
|
metadata: {
|
||||||
"newMode": Arcane.theme.currentThemeMode.name,
|
"followingSystemTheme":
|
||||||
"oldMode": oldTheme.name,
|
"${Arcane.theme.isFollowingSystemTheme}",
|
||||||
},
|
"newMode": Arcane.theme.currentThemeMode.name,
|
||||||
);
|
"oldMode": oldTheme.name,
|
||||||
}
|
},
|
||||||
},
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Text("Follow system"),
|
const Text("Follow system"),
|
||||||
],
|
],
|
||||||
@@ -359,8 +359,9 @@ class ArcaneThemeExample extends StatelessWidget {
|
|||||||
const Text("Color"),
|
const Text("Color"),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ValueListenableBuilder<ThemeData>(
|
child: ValueListenableBuilder<ThemeData>(
|
||||||
valueListenable: Arcane.theme.themeDataChanges,
|
valueListenable: Arcane.theme.effectiveThemeChanges,
|
||||||
builder: (context, themeData, _) => ListView.separated(
|
builder: (context, effectiveTheme, _) =>
|
||||||
|
ListView.separated(
|
||||||
itemCount: colors.length,
|
itemCount: colors.length,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
separatorBuilder: (_, __) => const SizedBox(width: 4),
|
separatorBuilder: (_, __) => const SizedBox(width: 4),
|
||||||
@@ -387,23 +388,34 @@ class ArcaneThemeExample extends StatelessWidget {
|
|||||||
"Setting ${Arcane.theme.currentThemeMode.name} theme color to ${colors[index].name}",
|
"Setting ${Arcane.theme.currentThemeMode.name} theme color to ${colors[index].name}",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: ValueListenableBuilder<ThemeMode>(
|
child: Container(
|
||||||
valueListenable: Arcane.theme.themeModeChanges,
|
key: Key(
|
||||||
builder: (context, themeMode, _) {
|
"${colors[index]}-${Arcane.theme.currentThemeMode}"),
|
||||||
return Container(
|
decoration: BoxDecoration(
|
||||||
key: Key("${colors[index]}-${themeMode}"),
|
color: colors[index],
|
||||||
decoration: BoxDecoration(
|
border:
|
||||||
color: colors[index],
|
effectiveTheme.colorScheme.primary.name ==
|
||||||
border:
|
colors[index].name
|
||||||
themeData.colorScheme.primary.name ==
|
? Border.all(
|
||||||
colors[index].name
|
width: 2,
|
||||||
? Border.all(width: 2)
|
color: Colors.white,
|
||||||
: null,
|
)
|
||||||
),
|
: null,
|
||||||
width: 20,
|
boxShadow:
|
||||||
height: 20,
|
effectiveTheme.colorScheme.primary.name ==
|
||||||
);
|
colors[index].name
|
||||||
},
|
? [
|
||||||
|
const BoxShadow(
|
||||||
|
color: Colors.black,
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 1,
|
||||||
|
offset: Offset(0, 0),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -66,6 +66,22 @@ class ArcaneReactiveTheme extends ArcaneService {
|
|||||||
final ValueNotifier<ThemeData> _themeNotifier =
|
final ValueNotifier<ThemeData> _themeNotifier =
|
||||||
ValueNotifier<ThemeData>(ThemeData.light());
|
ValueNotifier<ThemeData>(ThemeData.light());
|
||||||
|
|
||||||
|
/// ValueListenable that rebuilds when the effective theme changes.
|
||||||
|
/// This includes theme mode changes and active theme data changes.
|
||||||
|
/// Use this for most UI components that need to react to theme changes.
|
||||||
|
ValueListenable<ThemeData> get effectiveThemeChanges =>
|
||||||
|
I._effectiveThemeNotifier;
|
||||||
|
|
||||||
|
final ValueNotifier<ThemeData> _effectiveThemeNotifier =
|
||||||
|
ValueNotifier<ThemeData>(ThemeData.light());
|
||||||
|
|
||||||
|
/// ValueListenable that notifies when the system theme following state changes.
|
||||||
|
ValueListenable<bool> get followingSystemThemeChanges =>
|
||||||
|
I._followingSystemThemeNotifier;
|
||||||
|
|
||||||
|
final ValueNotifier<bool> _followingSystemThemeNotifier =
|
||||||
|
ValueNotifier<bool>(false);
|
||||||
|
|
||||||
// ************************************************************************ //
|
// ************************************************************************ //
|
||||||
// * MARK: Light/Dark theme
|
// * MARK: Light/Dark theme
|
||||||
// ************************************************************************ //
|
// ************************************************************************ //
|
||||||
@@ -101,6 +117,7 @@ class ArcaneReactiveTheme extends ArcaneService {
|
|||||||
/// ```
|
/// ```
|
||||||
ArcaneReactiveTheme switchTheme({ThemeMode? themeMode}) {
|
ArcaneReactiveTheme switchTheme({ThemeMode? themeMode}) {
|
||||||
_followingSystemTheme = false;
|
_followingSystemTheme = false;
|
||||||
|
_followingSystemThemeNotifier.value = false;
|
||||||
|
|
||||||
if (themeMode != null) {
|
if (themeMode != null) {
|
||||||
_updateTheme(themeMode);
|
_updateTheme(themeMode);
|
||||||
@@ -127,6 +144,7 @@ class ArcaneReactiveTheme extends ArcaneService {
|
|||||||
/// ```
|
/// ```
|
||||||
ArcaneReactiveTheme followSystemTheme(BuildContext context) {
|
ArcaneReactiveTheme followSystemTheme(BuildContext context) {
|
||||||
_followingSystemTheme = true;
|
_followingSystemTheme = true;
|
||||||
|
_followingSystemThemeNotifier.value = true;
|
||||||
|
|
||||||
_currentSystemThemeMode =
|
_currentSystemThemeMode =
|
||||||
context.isDarkMode ? ThemeMode.dark : ThemeMode.light;
|
context.isDarkMode ? ThemeMode.dark : ThemeMode.light;
|
||||||
@@ -152,6 +170,7 @@ class ArcaneReactiveTheme extends ArcaneService {
|
|||||||
if (_currentThemeMode == ThemeMode.dark) {
|
if (_currentThemeMode == ThemeMode.dark) {
|
||||||
_themeNotifier.value = theme;
|
_themeNotifier.value = theme;
|
||||||
_currentTheme = theme;
|
_currentTheme = theme;
|
||||||
|
_effectiveThemeNotifier.value = theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
return I;
|
return I;
|
||||||
@@ -173,6 +192,7 @@ class ArcaneReactiveTheme extends ArcaneService {
|
|||||||
if (_currentThemeMode == ThemeMode.light) {
|
if (_currentThemeMode == ThemeMode.light) {
|
||||||
_themeNotifier.value = theme;
|
_themeNotifier.value = theme;
|
||||||
_currentTheme = theme;
|
_currentTheme = theme;
|
||||||
|
_effectiveThemeNotifier.value = theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
return I;
|
return I;
|
||||||
@@ -187,9 +207,11 @@ class ArcaneReactiveTheme extends ArcaneService {
|
|||||||
_darkTheme.value = ThemeData.dark();
|
_darkTheme.value = ThemeData.dark();
|
||||||
_lightTheme.value = ThemeData.light();
|
_lightTheme.value = ThemeData.light();
|
||||||
_followingSystemTheme = false;
|
_followingSystemTheme = false;
|
||||||
|
_followingSystemThemeNotifier.value = false;
|
||||||
_updateTheme(ThemeMode.light);
|
_updateTheme(ThemeMode.light);
|
||||||
_themeNotifier.value = _lightTheme.value;
|
_themeNotifier.value = _lightTheme.value;
|
||||||
_currentTheme = _lightTheme.value;
|
_currentTheme = _lightTheme.value;
|
||||||
|
_effectiveThemeNotifier.value = _lightTheme.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the current theme mode and broadcasts the change.
|
/// Updates the current theme mode and broadcasts the change.
|
||||||
@@ -201,6 +223,7 @@ class ArcaneReactiveTheme extends ArcaneService {
|
|||||||
final ThemeData newTheme = themeMode == ThemeMode.dark ? dark : light;
|
final ThemeData newTheme = themeMode == ThemeMode.dark ? dark : light;
|
||||||
_currentTheme = newTheme;
|
_currentTheme = newTheme;
|
||||||
_themeNotifier.value = newTheme;
|
_themeNotifier.value = newTheme;
|
||||||
|
_effectiveThemeNotifier.value = newTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disposes of the theme service resources.
|
/// Disposes of the theme service resources.
|
||||||
@@ -212,6 +235,8 @@ class ArcaneReactiveTheme extends ArcaneService {
|
|||||||
_systemThemeNotifier.dispose();
|
_systemThemeNotifier.dispose();
|
||||||
_themeModeNotifier.dispose();
|
_themeModeNotifier.dispose();
|
||||||
_themeNotifier.dispose();
|
_themeNotifier.dispose();
|
||||||
|
_effectiveThemeNotifier.dispose();
|
||||||
|
_followingSystemThemeNotifier.dispose();
|
||||||
_darkTheme.dispose();
|
_darkTheme.dispose();
|
||||||
_lightTheme.dispose();
|
_lightTheme.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|||||||
@@ -98,11 +98,16 @@ class MockArcaneAuthInterface extends _i1.Mock
|
|||||||
as _i5.Future<void>);
|
as _i5.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i5.Future<_i2.Result<void, String>> logout() =>
|
_i5.Future<_i2.Result<void, String>> logout({
|
||||||
|
_i5.Future<void> Function()? onLoggedOut,
|
||||||
|
}) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(#logout, []),
|
Invocation.method(#logout, [], {#onLoggedOut: onLoggedOut}),
|
||||||
returnValue: _i5.Future<_i2.Result<void, String>>.value(
|
returnValue: _i5.Future<_i2.Result<void, String>>.value(
|
||||||
_FakeResult_0<void, String>(this, Invocation.method(#logout, [])),
|
_FakeResult_0<void, String>(
|
||||||
|
this,
|
||||||
|
Invocation.method(#logout, [], {#onLoggedOut: onLoggedOut}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
as _i5.Future<_i2.Result<void, String>>);
|
as _i5.Future<_i2.Result<void, String>>);
|
||||||
|
|||||||
Reference in New Issue
Block a user