Path:
lib/application/view_models/window_state_notifier.dart
Lines:
262
Non-empty lines:
225
Non-empty lines covered with requirements:
225 / 225 (100.0%)
Functions:
0
Functions covered by requirements:
0 / 0 (0.0%)
1
import 'dart:ui' as ui;
2
3
import 'package:bioflow_pro/domain/entities/ui/window_state.dart';
4
import 'package:flutter_riverpod/flutter_riverpod.dart';
5
import 'package:shared_preferences/shared_preferences.dart';
6
import 'package:window_manager/window_manager.dart';
7
8
// @relation(ARCH-007, scope=file)9
/// Notifier for managing window state with persistence and window_manager integration10
class WindowStateNotifier extends Notifier<WindowState> {
11
static const String _windowStateKey = 'window_state';
12
13
@override
14
WindowState build() {
15
_loadPersistedState();
16
return WindowState.defaultState();
17
}
18
19
/// Load persisted window state from SharedPreferences
20
Future<void> _loadPersistedState() async {
21
try {
22
final prefs = await SharedPreferences.getInstance();
23
final jsonString = prefs.getString(_windowStateKey);
24
25
if (jsonString != null) {
26
final json = Map<String, dynamic>.from(
27
Uri.splitQueryString(jsonString).map(
28
(key, value) => MapEntry(key, _parseValue(value)),
29
),
30
);
31
32
// Parse the stored window state
33
final persistedState = WindowState.fromJson(json);
34
35
// Validate and use persisted state if valid
36
if (persistedState.isValid) {
37
state = persistedState;
38
return;
39
}
40
}
41
} catch (e) {
42
// If loading fails, fall back to default state
43
// Error is silently handled to prevent app crashes
44
}
45
46
// Use default state if no valid persisted state found
47
state = WindowState.defaultState();
48
}
49
50
/// Parse stored value to appropriate type
51
dynamic _parseValue(String value) {
52
// Try to parse as double
53
if (value.contains('.')) {
54
final doubleValue = double.tryParse(value);
55
if (doubleValue != null) return doubleValue;
56
}
57
58
// Try to parse as int
59
final intValue = int.tryParse(value);
60
if (intValue != null) return intValue;
61
62
// Try to parse as bool
63
if (value.toLowerCase() == 'true') return true;
64
if (value.toLowerCase() == 'false') return false;
65
66
// Return as string if no other type matches
67
return value;
68
}
69
70
/// Persist current window state to SharedPreferences
71
Future<void> _persistState() async {
72
try {
73
final prefs = await SharedPreferences.getInstance();
74
final json = state.toJson();
75
76
// Convert to query string format for simple storage
77
final queryString = json.entries
78
.map((entry) => '${entry.key}=${entry.value}')
79
.join('&');
80
81
await prefs.setString(_windowStateKey, queryString);
82
} catch (e) {
83
// Silently handle persistence errors to prevent app crashes
84
}
85
}
86
87
/// Maximize the window
88
Future<void> maximize() async {
89
try {
90
await windowManager.maximize();
91
state = state.copyWith(
92
isMaximized: true,
93
lastUpdated: DateTime.now().toUtc(),
94
);
95
await _persistState();
96
} catch (e) {
97
// Handle window manager errors gracefully
98
}
99
}
100
101
/// Restore the window from maximized state
102
Future<void> restore() async {
103
try {
104
await windowManager.unmaximize();
105
state = state.copyWith(
106
isMaximized: false,
107
lastUpdated: DateTime.now().toUtc(),
108
);
109
await _persistState();
110
} catch (e) {
111
// Handle window manager errors gracefully
112
}
113
}
114
115
/// Remember current window position and size
116
Future<void> rememberPosition() async {
117
try {
118
final size = await windowManager.getSize();
119
final position = await windowManager.getPosition();
120
final isMaximized = await windowManager.isMaximized();
121
122
state = state.copyWith(
123
size: Size(size.width, size.height),
124
position: Offset(position.dx, position.dy),
125
isMaximized: isMaximized,
126
lastUpdated: DateTime.now().toUtc(),
127
);
128
129
await _persistState();
130
} catch (e) {
131
// Handle window manager errors gracefully
132
}
133
}
134
135
/// Update window size and apply it via window_manager
136
Future<void> updateSize(Size newSize) async {
137
try {
138
final validatedState = state.updateSize(newSize);
139
140
// Apply the size change via window_manager
141
await windowManager.setSize(
142
ui.Size(
143
validatedState.size.width,
144
validatedState.size.height,
145
),
146
);
147
148
state = validatedState;
149
await _persistState();
150
} catch (e) {
151
// Handle window manager errors gracefully
152
}
153
}
154
155
/// Update window position and apply it via window_manager
156
Future<void> updatePosition(Offset newPosition) async {
157
try {
158
final validatedState = state.updatePosition(newPosition);
159
160
// Apply the position change via window_manager
161
await windowManager.setPosition(
162
ui.Offset(
163
validatedState.position.dx,
164
validatedState.position.dy,
165
),
166
);
167
168
state = validatedState;
169
await _persistState();
170
} catch (e) {
171
// Handle window manager errors gracefully
172
}
173
}
174
175
/// Update display monitor
176
Future<void> updateDisplayMonitor(int monitor) async {
177
state = state.updateDisplayMonitor(monitor);
178
await _persistState();
179
}
180
181
/// Toggle maximized state
182
Future<void> toggleMaximized() async {
183
if (state.isMaximized) {
184
await restore();
185
} else {
186
await maximize();
187
}
188
}
189
190
/// Restore to safe default state
191
Future<void> restoreToSafe() async {
192
try {
193
final defaultState = WindowState.defaultState();
194
195
// Apply default size and position via window_manager
196
await windowManager.setSize(
197
ui.Size(
198
defaultState.size.width,
199
defaultState.size.height,
200
),
201
);
202
await windowManager.setPosition(
203
ui.Offset(
204
defaultState.position.dx,
205
defaultState.position.dy,
206
),
207
);
208
209
if (state.isMaximized) {
210
await windowManager.unmaximize();
211
}
212
213
state = defaultState;
214
await _persistState();
215
} catch (e) {
216
// Handle window manager errors gracefully
217
state = WindowState.defaultState();
218
await _persistState();
219
}
220
}
221
222
/// Initialize window state from window_manager on app startup
223
Future<void> initializeFromWindow() async {
224
try {
225
final size = await windowManager.getSize();
226
final position = await windowManager.getPosition();
227
final isMaximized = await windowManager.isMaximized();
228
229
state = WindowState.create(
230
size: Size(size.width, size.height),
231
position: Offset(position.dx, position.dy),
232
isMaximized: isMaximized,
233
);
234
235
await _persistState();
236
} catch (e) {
237
// Fall back to default state if initialization fails
238
state = WindowState.defaultState();
239
await _persistState();
240
}
241
}
242
243
/// Apply current state to window_manager (useful for app startup)
244
Future<void> applyStateToWindow() async {
245
try {
246
await windowManager.setSize(
247
ui.Size(state.size.width, state.size.height),
248
);
249
await windowManager.setPosition(
250
ui.Offset(state.position.dx, state.position.dy),
251
);
252
253
if (state.isMaximized) {
254
await windowManager.maximize();
255
} else {
256
await windowManager.unmaximize();
257
}
258
} catch (e) {
259
// Handle window manager errors gracefully
260
}
261
}
262
}