BioFlow Requirements
lib\application\view_models\window_state_notifier.dart
Source file coverage
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 integration
10
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
}