forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgoals_provider.dart
More file actions
259 lines (222 loc) · 6.73 KB
/
Copy pathgoals_provider.dart
File metadata and controls
259 lines (222 loc) · 6.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:omi/backend/http/api/goals.dart';
import 'package:omi/backend/preferences.dart';
class GoalsProvider extends ChangeNotifier {
static const String _legacyGoalsStorageKey = 'goals_tracker_local_goals';
List<Goal> _goals = [];
bool _isLoading = true;
// Track last goal deletion to prevent API sync from resurrecting deleted goals
DateTime? _lastGoalDeletion;
int _sessionGeneration = 0;
List<Goal> get goals => _goals;
bool get isLoading => _isLoading;
/// Initialize the provider by loading goals
Future<void> init() async {
await loadGoals();
}
/// Load goals from local storage first, then sync with API
Future<void> loadGoals() async {
final generation = _sessionGeneration;
_isLoading = true;
notifyListeners();
// Load from local storage first (most up-to-date with recent deletions/changes)
await _loadFromLocalStorage(generation);
if (generation != _sessionGeneration) return;
// Then sync with API in the background
await _syncWithApi(generation);
if (generation != _sessionGeneration) return;
_isLoading = false;
_notifyAfterFrame();
}
void _notifyAfterFrame() {
SchedulerBinding.instance.addPostFrameCallback((_) => notifyListeners());
}
Future<void> _loadFromLocalStorage(int generation) async {
try {
final prefs = await SharedPreferences.getInstance();
if (generation != _sessionGeneration) return;
SharedPreferencesUtil().scopeLegacyUserDataForCurrentUser();
final storageKey = _goalsStorageKey;
if (storageKey == null) return;
final goalsJson = prefs.getString(storageKey);
if (goalsJson != null) {
final List<dynamic> decoded = jsonDecode(goalsJson);
_goals = decoded.map((e) => Goal.fromJson(e)).toList();
_notifyAfterFrame();
}
} catch (_) {}
}
Future<void> _syncWithApi(int generation) async {
// Skip if we just deleted a goal to prevent resurrecting it
final now = DateTime.now();
final skipApiSync = _lastGoalDeletion != null && now.difference(_lastGoalDeletion!) < const Duration(seconds: 3);
if (skipApiSync) return;
try {
final goals = await getAllGoals();
if (generation != _sessionGeneration) return;
if (goals.isNotEmpty) {
_goals = goals;
await _saveToLocalStorage();
_notifyAfterFrame();
}
} catch (_) {}
}
Future<void> _saveToLocalStorage() async {
try {
final storageKey = _goalsStorageKey;
if (storageKey == null) return;
final prefs = await SharedPreferences.getInstance();
final goalsJson = jsonEncode(_goals.map((g) => g.toJson()).toList());
await prefs.setString(storageKey, goalsJson);
} catch (_) {}
}
String? get _goalsStorageKey {
final ownerUid = SharedPreferencesUtil().uid;
return ownerUid.isEmpty ? null : '$_legacyGoalsStorageKey:$ownerUid';
}
void clearUserData() {
_sessionGeneration++;
_goals = [];
_isLoading = false;
_lastGoalDeletion = null;
notifyListeners();
}
/// Create a new goal
Future<Goal?> createGoal({
required String title,
required String goalType,
required double targetValue,
double currentValue = 0,
double minValue = 0,
double maxValue = 10,
String? unit,
}) async {
final generation = _sessionGeneration;
final goal = await createGoalApi(
title: title,
goalType: goalType,
targetValue: targetValue,
currentValue: currentValue,
minValue: minValue,
maxValue: maxValue,
unit: unit,
);
if (generation != _sessionGeneration) return null;
if (goal != null) {
_goals.add(goal);
await _saveToLocalStorage();
notifyListeners();
}
return goal;
}
/// Update an existing goal
Future<Goal?> updateGoal(
String goalId, {
String? title,
double? targetValue,
double? currentValue,
double? minValue,
double? maxValue,
String? unit,
}) async {
final generation = _sessionGeneration;
final updatedGoal = await updateGoalApi(
goalId,
title: title,
targetValue: targetValue,
currentValue: currentValue,
minValue: minValue,
maxValue: maxValue,
unit: unit,
);
if (generation != _sessionGeneration) return null;
if (updatedGoal != null) {
final index = _goals.indexWhere((g) => g.id == goalId);
if (index != -1) {
_goals[index] = updatedGoal;
await _saveToLocalStorage();
notifyListeners();
}
}
return updatedGoal;
}
/// Update goal progress only
Future<Goal?> updateGoalProgress(String goalId, double currentValue) async {
final generation = _sessionGeneration;
final updatedGoal = await updateGoalProgressApi(goalId, currentValue);
if (generation != _sessionGeneration) return null;
if (updatedGoal != null) {
final index = _goals.indexWhere((g) => g.id == goalId);
if (index != -1) {
_goals[index] = updatedGoal;
await _saveToLocalStorage();
notifyListeners();
}
}
return updatedGoal;
}
/// Delete a goal
Future<bool> deleteGoal(String goalId) async {
_lastGoalDeletion = DateTime.now();
// Remove from local state immediately (optimistic update)
_goals.removeWhere((g) => g.id == goalId);
await _saveToLocalStorage();
notifyListeners();
// Then delete from server
final success = await deleteGoalApi(goalId);
return success;
}
/// Refresh goals from API
Future<void> refresh() async {
_lastGoalDeletion = null; // Reset deletion flag to allow API sync
await loadGoals();
}
}
// Rename API functions to avoid conflicts with provider methods
Future<Goal?> createGoalApi({
required String title,
required String goalType,
required double targetValue,
double currentValue = 0,
double minValue = 0,
double maxValue = 10,
String? unit,
}) {
return createGoal(
title: title,
goalType: goalType,
targetValue: targetValue,
currentValue: currentValue,
minValue: minValue,
maxValue: maxValue,
unit: unit,
);
}
Future<Goal?> updateGoalApi(
String goalId, {
String? title,
double? targetValue,
double? currentValue,
double? minValue,
double? maxValue,
String? unit,
}) {
return updateGoal(
goalId,
title: title,
targetValue: targetValue,
currentValue: currentValue,
minValue: minValue,
maxValue: maxValue,
unit: unit,
);
}
Future<Goal?> updateGoalProgressApi(String goalId, double currentValue) {
return updateGoalProgress(goalId, currentValue);
}
Future<bool> deleteGoalApi(String goalId) {
return deleteGoal(goalId);
}