@@ -5,6 +5,7 @@ import 'package:fluttersdk_wind/fluttersdk_wind.dart';
55import '../facades/config.dart' ;
66import '../facades/lang.dart' ;
77import '../facades/log.dart' ;
8+ import '../facades/vault.dart' ;
89import '../routing/magic_router.dart' ;
910
1011import 'magic.dart' ;
@@ -56,6 +57,11 @@ class MagicAppWidgetState extends State<MagicAppWidget> {
5657/// Handles all internal initialization (environment, config, routing)
5758/// and wraps the app in WindTheme for Wind widget support.
5859///
60+ /// Theme preference is automatically persisted to Vault.
61+ /// When a user manually toggles dark/light mode, the preference is saved
62+ /// and restored on next app launch. If no preference is saved, the app
63+ /// follows the system brightness setting.
64+ ///
5965/// ## Usage
6066///
6167/// ```dart
@@ -92,10 +98,11 @@ class MagicApplication extends StatefulWidget {
9298
9399 /// Callback fired when the user manually toggles the theme.
94100 ///
95- /// Use this to persist the user's preference:
101+ /// This is called IN ADDITION to the built-in theme persistence.
102+ /// Use this for custom side-effects beyond storage:
96103 /// ```dart
97104 /// MagicApplication(
98- /// onThemeChanged: (brightness) => saveThemePreference(brightness ),
105+ /// onThemeChanged: (brightness) => analytics.track('theme_changed' ),
99106 /// )
100107 /// ```
101108 final ValueChanged <Brightness >? onThemeChanged;
@@ -128,25 +135,40 @@ class MagicApplication extends StatefulWidget {
128135}
129136
130137class _MagicApplicationState extends State <MagicApplication > {
138+ /// Vault storage key for theme preference.
139+ static const _themeKey = 'theme_mode' ;
140+
131141 bool _initialized = false ;
132142 bool _hasError = false ;
133143
144+ /// Saved brightness preference loaded from Vault.
145+ ///
146+ /// - `null` means no preference saved (follow system).
147+ /// - Non-null means user has a manual preference.
148+ Brightness ? _savedBrightness;
149+
134150 @override
135151 void initState () {
136152 super .initState ();
137153 _initialize ();
138154 }
139155
140- void _initialize () {
156+ /// Initialize the application and load saved theme preference.
157+ ///
158+ /// Loads theme preference from Vault before marking as initialized.
159+ /// If Vault is not available (no VaultServiceProvider registered),
160+ /// gracefully falls back to system default.
161+ Future <void > _initialize () async {
141162 try {
142- // Magic.init() has already been called in main.dart before runApp()
143- // So we just need to call onInit callback if provided
163+ // 1. Load saved theme preference from Vault.
164+ _savedBrightness = await _loadThemePreference ();
144165
145- // Configure initial route if different from default
166+ // 2. Configure initial route if different from default.
146167 if (widget.initialRoute != '/' ) {
147168 MagicRouter .instance.setInitialLocation (widget.initialRoute);
148169 }
149170
171+ // 3. Call the app's onInit callback.
150172 widget.onInit? .call ();
151173 setState (() => _initialized = true );
152174 } catch (e) {
@@ -174,11 +196,14 @@ class _MagicApplicationState extends State<MagicApplication> {
174196 );
175197 }
176198
177- final windThemeData = widget.windTheme ?? WindThemeData ();
199+ // 1. Apply saved brightness preference to WindThemeData.
200+ final windThemeData = _applyThemePreference (
201+ widget.windTheme ?? WindThemeData (),
202+ );
178203
179204 return WindTheme (
180205 data: windThemeData,
181- onThemeChanged: widget.onThemeChanged ,
206+ onThemeChanged: _onThemeChanged ,
182207 builder: (context, controller) => MagicAppWidget (
183208 key: MagicAppWidget ._appKey,
184209 themeMode: widget.themeMode,
@@ -236,4 +261,80 @@ class _MagicApplicationState extends State<MagicApplication> {
236261 return Locale (code.toString ());
237262 }).toList ();
238263 }
264+
265+ // ---------------------------------------------------------------------------
266+ // Theme Persistence
267+ // ---------------------------------------------------------------------------
268+
269+ /// Apply saved brightness preference to the theme data.
270+ ///
271+ /// When a saved preference exists, overrides the brightness and disables
272+ /// system sync. Otherwise, returns the original theme data unchanged.
273+ WindThemeData _applyThemePreference (WindThemeData data) {
274+ if (_savedBrightness == null ) {
275+ return data;
276+ }
277+
278+ return data.copyWith (
279+ brightness: _savedBrightness,
280+ syncWithSystem: false ,
281+ );
282+ }
283+
284+ /// Handle theme change — persist to Vault and forward to user callback.
285+ ///
286+ /// Called only on user-initiated theme changes (not system changes).
287+ void _onThemeChanged (Brightness brightness) {
288+ // 1. Persist the preference to Vault.
289+ _saveThemePreference (brightness);
290+
291+ // 2. Forward to user's callback if provided.
292+ widget.onThemeChanged? .call (brightness);
293+ }
294+
295+ /// Load theme preference from Vault.
296+ ///
297+ /// Returns the saved [Brightness] , or `null` if no preference is stored.
298+ /// Gracefully handles missing VaultServiceProvider.
299+ Future <Brightness ?> _loadThemePreference () async {
300+ try {
301+ if (! Magic .bound ('vault' )) {
302+ return null ;
303+ }
304+
305+ final saved = await Vault .get (_themeKey);
306+
307+ if (saved == 'dark' ) {
308+ Log .info ('[MagicApplication] Theme preference loaded: dark' );
309+ return Brightness .dark;
310+ }
311+
312+ if (saved == 'light' ) {
313+ Log .info ('[MagicApplication] Theme preference loaded: light' );
314+ return Brightness .light;
315+ }
316+
317+ return null ;
318+ } catch (e) {
319+ Log .error ('[MagicApplication] Failed to load theme preference: $e ' );
320+ return null ;
321+ }
322+ }
323+
324+ /// Save theme preference to Vault.
325+ ///
326+ /// Stores 'dark' or 'light' string. Gracefully handles missing Vault.
327+ Future <void > _saveThemePreference (Brightness brightness) async {
328+ try {
329+ if (! Magic .bound ('vault' )) {
330+ return ;
331+ }
332+
333+ final value = brightness == Brightness .dark ? 'dark' : 'light' ;
334+ await Vault .put (_themeKey, value);
335+ Log .info ('[MagicApplication] Theme preference saved: $value ' );
336+ } catch (e) {
337+ Log .error ('[MagicApplication] Failed to save theme preference: $e ' );
338+ }
339+ }
239340}
0 commit comments