dev
dart 288 lines 8.31 KB
Raw
1 import 'package:cake_wallet/entities/preferences_key.dart';
2 import 'package:cake_wallet/utils/device_info.dart';
3 import 'package:cake_wallet/utils/responsive_layout_util.dart';
4 import 'package:flutter/material.dart';
5 import 'package:mobx/mobx.dart';
6 import 'package:cake_wallet/themes/utils/theme_list.dart';
7 import 'package:shared_preferences/shared_preferences.dart';
8
9 import 'material_base_theme.dart';
10 import 'package:cake_wallet/themes/theme_classes/black_theme.dart';
11
12 part 'theme_store.g.dart';
13
14 /// MobX store for managing theme state
15 class ThemeStore = ThemeStoreBase with _$ThemeStore;
16
17 abstract class ThemeStoreBase with Store {
18 @observable
19 MaterialThemeBase _currentTheme = ThemeList.lightTheme;
20
21 @observable
22 ThemeMode _themeMode = ThemeMode.system;
23
24 @observable
25 bool _isOled = false;
26
27 @computed
28 MaterialThemeBase get currentTheme => _currentTheme;
29
30 @computed
31 ThemeMode get themeMode => _themeMode;
32
33 @computed
34 bool get isDarkMode => _currentTheme.isDark;
35
36 @computed
37 bool get isOled => _isOled;
38
39 @computed
40 bool get hasCustomTheme => sharedPreferences.getInt(PreferencesKey.currentTheme) != null;
41
42 @computed
43 MaterialThemeBase? get savedCustomTheme {
44 final raw = sharedPreferences.getInt(PreferencesKey.currentTheme);
45 return raw != null ? ThemeList.deserialize(raw: raw) : null;
46 }
47
48 @computed
49 MaterialThemeBase? get savedDarkTheme {
50 try {
51 final raw = sharedPreferences.getInt(PreferencesKey.savedDarkTheme);
52 return raw != null ? ThemeList.deserialize(raw: raw) : null;
53 } catch (e) {
54 return null;
55 }
56 }
57
58 @computed
59 MaterialThemeBase? get savedLightTheme {
60 try {
61 final raw = sharedPreferences.getInt(PreferencesKey.savedLightTheme);
62 return raw != null ? ThemeList.deserialize(raw: raw) : null;
63 } catch (e) {
64 return null;
65 }
66 }
67
68 late SharedPreferences sharedPreferences;
69
70 @action
71 Future<void> setTheme(MaterialThemeBase theme) async {
72 if (_currentTheme == theme) return;
73
74 _currentTheme = theme;
75 await sharedPreferences.setInt(PreferencesKey.currentTheme, theme.raw);
76 _isOled = theme is BlackTheme ? theme.isOled : false;
77 await sharedPreferences.setBool(PreferencesKey.blackThemeOled, _isOled);
78
79 // Save preferred themes for system mode
80 if (theme.isDark) {
81 await sharedPreferences.setInt(PreferencesKey.savedDarkTheme, theme.raw);
82 } else {
83 await sharedPreferences.setInt(PreferencesKey.savedLightTheme, theme.raw);
84 }
85 }
86
87 @action
88 Future<void> setThemeMode(ThemeMode mode) async {
89 if (_themeMode == mode) return;
90
91 _themeMode = mode;
92 await _saveThemeModeToPrefs(mode);
93
94 if (mode == ThemeMode.system) {
95 // We'll always use getThemeFromSystem() for system mode to respect saved themes
96 await setTheme(getThemeFromSystem());
97 return;
98 }
99
100 if (!hasCustomTheme) return;
101
102 final savedTheme = savedCustomTheme;
103 if (savedTheme == null) return;
104
105 if (_isThemeCompatibleWithMode(savedTheme, mode)) {
106 await setTheme(savedTheme);
107 }
108 }
109
110 @action
111 Future<void> setOledEnabled(bool value) async {
112 if (_isOled == value) return;
113 _isOled = value;
114 await sharedPreferences.setBool(PreferencesKey.blackThemeOled, value);
115
116 if (_currentTheme is BlackTheme) {
117 final current = _currentTheme as BlackTheme;
118 await setTheme(BlackTheme(current.accentColor, isOled: value));
119 }
120 }
121
122 Future<void> _saveThemeModeToPrefs(ThemeMode mode) async {
123 await sharedPreferences.setString(PreferencesKey.themeMode, mode.toString());
124 }
125
126 /// Loads the saved theme preferences
127 Future<void> loadThemePreferences() async {
128 sharedPreferences = await SharedPreferences.getInstance();
129
130 await _loadThemeMode();
131 await _loadAndSetTheme();
132
133 _setupThemeReaction();
134 }
135
136 /// Loads the saved theme mode from SharedPreferences
137 Future<void> _loadThemeMode() async {
138 final savedThemeMode = sharedPreferences.getString(PreferencesKey.themeMode);
139 if (savedThemeMode != null) {
140 _themeMode = ThemeMode.values.firstWhere(
141 (mode) => mode.toString() == savedThemeMode,
142 orElse: () => ThemeMode.system,
143 );
144 }
145 }
146
147 /// Loads and sets the appropriate theme based on platform and installation status
148 Future<void> _loadAndSetTheme() async {
149 final bool isNewInstall = sharedPreferences.getBool(PreferencesKey.isNewInstall) ?? true;
150
151 bool shouldUseMobileTheme =
152 responsiveLayoutUtil.shouldRenderMobileUI && DeviceInfo.instance.isMobile;
153
154 if (shouldUseMobileTheme) {
155 await _handleMobileTheme(isNewInstall);
156 } else {
157 await _handleNonMobileTheme();
158 }
159 }
160
161 /// Handles theme loading for non-mobile platforms
162 Future<void> _handleNonMobileTheme() async {
163 await setTheme(ThemeList.darkTheme);
164 await setThemeMode(ThemeMode.dark);
165 }
166
167 /// Handles theme loading for mobile platforms
168 Future<void> _handleMobileTheme(bool isNewInstall) async {
169 if (isNewInstall) {
170 await _setSystemTheme(isNewInstall: isNewInstall);
171 } else {
172 await loadSavedTheme();
173 }
174 }
175
176 /// Loads the saved theme from SharedPreferences
177 Future<void> loadSavedTheme({bool isFromBackup = false}) async {
178 try {
179 _isOled = sharedPreferences.getBool(PreferencesKey.blackThemeOled) ?? false;
180 final theme = savedCustomTheme;
181
182 if (!hasCustomTheme || theme == null) {
183 await _setSystemTheme();
184 return;
185 }
186
187 if (theme is BlackTheme) {
188 final adjusted = BlackTheme(theme.accentColor, isOled: _isOled);
189 if (_currentTheme != adjusted) {
190 await setTheme(adjusted);
191 }
192 } else if (_currentTheme != theme) {
193 await setTheme(theme);
194 }
195
196 final newThemeMode = _getThemeModeOnStartUp(theme, isFromBackup);
197
198 if (newThemeMode == ThemeMode.system) {
199 await _setSystemTheme();
200 return;
201 }
202
203 if (_themeMode != newThemeMode) {
204 await setThemeMode(newThemeMode);
205 }
206 } catch (e) {
207 await _setSystemTheme();
208 }
209 }
210
211 Future<void> _setSystemTheme({bool isNewInstall = false}) async {
212 if (!isNewInstall && hasCustomTheme) return;
213
214 final systemTheme = getThemeFromSystem();
215
216 if (_currentTheme != systemTheme) {
217 await setTheme(systemTheme);
218 }
219
220 if (isNewInstall) {
221 await _saveThemeModeToPrefs(ThemeMode.system);
222 if (_themeMode != ThemeMode.system) {
223 await setThemeMode(ThemeMode.system);
224 }
225 }
226 }
227
228 ThemeMode _getThemeModeOnStartUp(MaterialThemeBase theme, bool isFromBackup) {
229 if (isFromBackup || _themeMode != ThemeMode.system) {
230 return theme.isDark ? ThemeMode.dark : ThemeMode.light;
231 }
232
233 return ThemeMode.system;
234 }
235
236 void _setupThemeReaction() {
237 reaction(
238 (_) => currentTheme,
239 (MaterialThemeBase theme) => sharedPreferences.setInt(PreferencesKey.currentTheme, theme.raw),
240 );
241 }
242
243 MaterialThemeBase getThemeFromSystem() {
244 final systemBrightness = WidgetsBinding.instance.platformDispatcher.platformBrightness;
245
246 if (systemBrightness == Brightness.dark) {
247 // Prefer saved dark theme when available
248 try {
249 final savedDark = savedDarkTheme;
250 if (savedDark != null) {
251 if (savedDark is BlackTheme) {
252 // We'll use the OLED flag from the saved theme itself, not from separate preference
253 return BlackTheme(savedDark.accentColor, isOled: savedDark.isOled);
254 }
255 return savedDark;
256 }
257 } catch (_) {}
258
259 // If no saved dark theme, check if current theme is BlackTheme with accent colors
260 // and preserve it when switching to dark mode
261 if (_currentTheme is BlackTheme) {
262 final currentBlackTheme = _currentTheme as BlackTheme;
263 return BlackTheme(currentBlackTheme.accentColor, isOled: currentBlackTheme.isOled);
264 }
265
266 return ThemeList.darkTheme;
267 }
268
269 // Light system preference: prefer saved light theme when available
270 try {
271 final savedLight = savedLightTheme;
272 if (savedLight != null) return savedLight;
273 } catch (_) {}
274 return ThemeList.lightTheme;
275 }
276
277 /// Checks if a theme is compatible with a theme mode
278 bool _isThemeCompatibleWithMode(MaterialThemeBase theme, ThemeMode mode) {
279 switch (mode) {
280 case ThemeMode.light:
281 return !theme.isDark;
282 case ThemeMode.dark:
283 return theme.isDark;
284 case ThemeMode.system:
285 return true; // All themes are compatible with system mode
286 }
287 }
288 }