Skip to content

Commit 34dab30

Browse files
added remaining manager to hub stream
1 parent 8b07c13 commit 34dab30

20 files changed

Lines changed: 424 additions & 35 deletions

File tree

docs/model_relations/school_data_hub_server.svg

Lines changed: 1 addition & 1 deletion
Loading

school_data_hub_client/lib/src/protocol/_features/hub/models/hub_object_type.dart

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@ enum HubObjectType implements _i1.SerializableModel {
2323
competenceReportCheck,
2424
competenceReportItem,
2525
competenceGoal,
26-
supportGoal;
26+
supportGoal,
27+
pupilWorkbook,
28+
pupilBookLending,
29+
schoolday,
30+
schoolSemester,
31+
schoolData,
32+
user;
2733

2834
static HubObjectType fromJson(int index) {
2935
switch (index) {
@@ -51,6 +57,18 @@ enum HubObjectType implements _i1.SerializableModel {
5157
return HubObjectType.competenceGoal;
5258
case 11:
5359
return HubObjectType.supportGoal;
60+
case 12:
61+
return HubObjectType.pupilWorkbook;
62+
case 13:
63+
return HubObjectType.pupilBookLending;
64+
case 14:
65+
return HubObjectType.schoolday;
66+
case 15:
67+
return HubObjectType.schoolSemester;
68+
case 16:
69+
return HubObjectType.schoolData;
70+
case 17:
71+
return HubObjectType.user;
5472
default:
5573
throw ArgumentError(
5674
'Value "$index" cannot be converted to "HubObjectType"');

school_data_hub_flutter/assets/school_data_hub_server.svg

Lines changed: 1 addition & 1 deletion
Loading

school_data_hub_flutter/lib/core/client/hub_stream_service.dart

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class HubStreamService with WidgetsBindingObserver {
106106
_connectivityListener = () {
107107
if (_disposed) return;
108108
if (!monitor.isConnected.value) {
109-
_log.info('[HUB] connectivity_lost — setting disconnected');
109+
_log.info('connectivity_lost — setting disconnected');
110110
_connecting = false;
111111
_cancelReconnectTimer();
112112
_cleanupSubscription();
@@ -117,7 +117,7 @@ class HubStreamService with WidgetsBindingObserver {
117117
final s = _state.value;
118118
if (s == HubConnectionState.waitingRetry ||
119119
s == HubConnectionState.disconnected) {
120-
_log.info('[HUB] connectivity_restored — attempting reconnect');
120+
_log.info('connectivity_restored — attempting reconnect');
121121
_attemptConnect(isReconnect: true);
122122
}
123123
};
@@ -135,15 +135,15 @@ class HubStreamService with WidgetsBindingObserver {
135135
_setState(HubConnectionState.background);
136136
_cancelReconnectTimer();
137137
_cleanupSubscription();
138-
_log.info('[HUB] App backgrounded — stream disconnected');
138+
_log.info('App backgrounded — stream disconnected');
139139
break;
140140
case AppLifecycleState.resumed:
141141
_appInForeground = true;
142142
if (isConnected) {
143-
_log.info('[HUB] App resumed — already connected, skipping');
143+
_log.info('App resumed — already connected, skipping');
144144
break;
145145
}
146-
_log.info('[HUB] App resumed — scheduling reconnect');
146+
_log.info('App resumed — scheduling reconnect');
147147
_scheduleReconnect(
148148
isReconnect: true,
149149
delayMs: _dnsGraceDelayMs,
@@ -185,7 +185,7 @@ class HubStreamService with WidgetsBindingObserver {
185185
final changeTimes = await di<Client>().hub.getLastChangeTimes();
186186
return computeChangedTypes(changeTimes, _disconnectedAt!);
187187
} catch (e) {
188-
_log.warning('[HUB] Could not fetch change times: $e');
188+
_log.warning('Could not fetch change times: $e');
189189
return null;
190190
}
191191
}
@@ -199,9 +199,7 @@ class HubStreamService with WidgetsBindingObserver {
199199
if (_connecting) return;
200200
if (_reconnectTimer != null) return;
201201
final delayWithJitter = _withJitter(delayMs);
202-
_log.info(
203-
'[HUB] reconnect_scheduled reason=$reason delayMs=$delayWithJitter',
204-
);
202+
_log.info('reconnect_scheduled reason=$reason delayMs=$delayWithJitter');
205203
_reconnectTimer = Timer(Duration(milliseconds: delayWithJitter), () {
206204
_reconnectTimer = null;
207205
_attemptConnect(isReconnect: isReconnect);
@@ -214,7 +212,7 @@ class HubStreamService with WidgetsBindingObserver {
214212
final hasConnectivity =
215213
di<ServerpodConnectivityMonitor>().isConnected.value;
216214
if (!hasConnectivity) {
217-
_log.info('[HUB] No connectivity — waiting for connectivity listener');
215+
_log.info('No connectivity — waiting for connectivity listener');
218216
return;
219217
}
220218

@@ -224,7 +222,7 @@ class HubStreamService with WidgetsBindingObserver {
224222
final serverUrl = di<EnvManager>().activeEnv?.serverUrl ?? 'unknown';
225223
final host = Uri.tryParse(serverUrl)?.host ?? serverUrl;
226224
_log.info(
227-
'[HUB] connect_attempt host=$host backoffMs=$_reconnectDelayMs '
225+
'connect_attempt host=$host backoffMs=$_reconnectDelayMs '
228226
'reconnect=$isReconnect',
229227
);
230228
_setState(HubConnectionState.connecting);
@@ -245,12 +243,12 @@ class HubStreamService with WidgetsBindingObserver {
245243
_events.add(HubReconnected());
246244
} else if (changedTypes.isNotEmpty) {
247245
_log.info(
248-
'[HUB] Selective reconnect: ${changedTypes.length} types changed',
246+
'Selective reconnect: ${changedTypes.length} types changed',
249247
);
250248
_pingStreamActivity();
251249
_events.add(HubSelectiveReconnect(changedTypes));
252250
} else {
253-
_log.info('[HUB] No events missed — skipping refetch');
251+
_log.info('No events missed — skipping refetch');
254252
}
255253

256254
_doSubscribe();
@@ -267,7 +265,7 @@ class HubStreamService with WidgetsBindingObserver {
267265
return;
268266
}
269267

270-
_log.info('[HUB] Subscribing to hub_events_stream');
268+
_log.info('Subscribing to hub_events_stream');
271269
final client = di<Client>();
272270
try {
273271
_subscription = client.hub.streamHubEvents().listen(

school_data_hub_flutter/lib/features/_pupil/domain/pupil_label_pdf_service.dart renamed to school_data_hub_flutter/lib/features/_pupil/services/pupil_label_pdf_service.dart

File renamed without changes.

school_data_hub_flutter/lib/features/app_main_navigation/school_lists_page.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import 'package:school_data_hub_flutter/app_utils/pdf_viewer_page.dart';
77
import 'package:school_data_hub_flutter/common/theme/app_colors.dart';
88
import 'package:school_data_hub_flutter/common/widgets/generic_components/generic_app_bar.dart';
99
import 'package:school_data_hub_flutter/features/_authorizations/presentation/authorizations_list_page/authorizations_list_page.dart';
10-
import 'package:school_data_hub_flutter/features/_pupil/domain/pupil_label_pdf_service.dart';
1110
import 'package:school_data_hub_flutter/features/_pupil/domain/pupil_proxy_manager.dart';
11+
import 'package:school_data_hub_flutter/features/_pupil/services/pupil_label_pdf_service.dart';
1212
import 'package:school_data_hub_flutter/features/_school_lists/presentation/school_lists_page/school_lists_page.dart';
1313
import 'package:school_data_hub_flutter/features/app_main_navigation/widgets/main_menu_button.dart';
1414
import 'package:school_data_hub_flutter/l10n/app_localizations.dart';
@@ -64,8 +64,8 @@ class SchoolListsMenuPage extends StatelessWidget {
6464
destinationPage: PdfViewerPage(
6565
pdfGenerator: () =>
6666
PupilLabelPdfService.generateLabelsPdf(
67-
di<PupilProxyManager>().allPupils,
68-
),
67+
di<PupilProxyManager>().allPupils,
68+
),
6969
title: 'Etiketten',
7070
iconData: Icons.label_outline,
7171
showZoomButton: true,

school_data_hub_flutter/lib/features/books/domain/pupil_book_lending_manager.dart

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1+
import 'dart:async';
12
import 'dart:io';
23

34
import 'package:flutter/foundation.dart';
45
import 'package:flutter_it/flutter_it.dart';
6+
import 'package:logging/logging.dart';
57
import 'package:school_data_hub_client/school_data_hub_client.dart';
68
import 'package:school_data_hub_flutter/app_utils/custom_encrypter.dart';
79
import 'package:school_data_hub_flutter/core/client/file_upload_service.dart';
10+
import 'package:school_data_hub_flutter/core/client/hub_stream_service.dart';
811
import 'package:school_data_hub_flutter/core/notification_manager.dart';
912
import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart';
1013
import 'package:school_data_hub_flutter/features/books/data/pupil_book_lending_api_service.dart';
1114

1215
class PupilBookLendingManager with ChangeNotifier {
1316
HubSessionManager get _hubSessionManager => di<HubSessionManager>();
1417
NotificationManager get _notificationService => di<NotificationManager>();
18+
final _log = Logger('PupilBookLendingManager');
19+
StreamSubscription<dynamic>? _hubSubscription;
1520

1621
final Map<int, List<PupilBookLending>> _pupilBookLendings = {};
1722
final Map<String, PupilBookLending> _lendingIdMap = {};
@@ -28,6 +33,8 @@ class PupilBookLendingManager with ChangeNotifier {
2833
_addPupilBookLendingToCollections(lending);
2934
}
3035

36+
_hubSubscription = di<HubStreamService>().events.listen(_onHubEvent);
37+
3138
return this;
3239
}
3340

@@ -343,8 +350,65 @@ class PupilBookLendingManager with ChangeNotifier {
343350
);
344351
}
345352

353+
//- Hub stream handlers
354+
355+
void _onHubEvent(dynamic event) {
356+
if (event is PupilBookLending) {
357+
_upsertFromStream(event);
358+
} else if (event is HubDeleteEvent &&
359+
event.objectType == HubObjectType.pupilBookLending) {
360+
_deleteFromStream(event.id);
361+
} else if (event is HubReconnected) {
362+
_refetchAll();
363+
} else if (event is HubSelectiveReconnect) {
364+
if (event.changedTypes.contains(HubObjectType.pupilBookLending)) {
365+
_refetchAll();
366+
}
367+
}
368+
}
369+
370+
void _upsertFromStream(PupilBookLending lending) {
371+
_log.fine('[STREAM] upsert pupilBookLending ${lending.id}');
372+
_updatePupilBookLendingInCollections(lending);
373+
notifyListeners();
374+
}
375+
376+
void _deleteFromStream(int id) {
377+
_log.fine('[STREAM] delete pupilBookLending $id');
378+
// Find the lending by id across all collections
379+
PupilBookLending? found;
380+
for (final lendings in _pupilBookLendings.values) {
381+
for (final lending in lendings) {
382+
if (lending.id == id) {
383+
found = lending;
384+
break;
385+
}
386+
}
387+
if (found != null) break;
388+
}
389+
if (found != null) {
390+
_removePupilBookLendingFromCollections(found);
391+
notifyListeners();
392+
}
393+
}
394+
395+
Future<void> _refetchAll() async {
396+
_pupilBookLendings.clear();
397+
_lendingIdMap.clear();
398+
_isbnPupilBookLendingsMap.clear();
399+
final allLendings =
400+
await _pupilBookLendingApiService.fetchAllPupilBookLendings();
401+
if (allLendings == null) return;
402+
for (var lending in allLendings) {
403+
_addPupilBookLendingToCollections(lending);
404+
}
405+
notifyListeners();
406+
}
407+
346408
@override
347409
void dispose() {
410+
_hubSubscription?.cancel();
411+
_hubSubscription = null;
348412
_pupilBookLendings.clear();
349413
_lendingIdMap.clear();
350414
super.dispose();

school_data_hub_flutter/lib/features/school/domain/school_data_manager.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import 'dart:async';
12
import 'dart:io';
23

34
import 'package:flutter/foundation.dart';
45
import 'package:flutter_it/flutter_it.dart';
56
import 'package:logging/logging.dart';
67
import 'package:school_data_hub_client/school_data_hub_client.dart';
8+
import 'package:school_data_hub_flutter/core/client/hub_stream_service.dart';
79
import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart';
810
import 'package:school_data_hub_flutter/features/school/data/school_data_api_service.dart';
911
import 'package:school_data_hub_flutter/features/school/domain/managers/school_data_manager.dart'
@@ -17,6 +19,8 @@ final _log = Logger('SchoolDataMainManager');
1719
/// Main school data manager that orchestrates all sub-managers
1820
/// This follows the established pattern from the timetable feature
1921
class SchoolDataMainManager {
22+
StreamSubscription<dynamic>? _hubSubscription;
23+
2024
// Sub-managers
2125
final data_manager.SchoolDataManager _dataManager;
2226
final _apiService = SchoolDataApiService();
@@ -46,16 +50,32 @@ class SchoolDataMainManager {
4650
await _dataManager.init();
4751
await _uiManager.init();
4852
await refreshData();
53+
_hubSubscription = di<HubStreamService>().events.listen(_onHubEvent);
4954

5055
return this;
5156
}
5257

5358
void dispose() {
59+
_hubSubscription?.cancel();
60+
_hubSubscription = null;
5461
_dataManager.dispose();
5562

5663
_uiManager.dispose();
5764
}
5865

66+
void _onHubEvent(dynamic event) {
67+
if (event is SchoolData) {
68+
_log.fine('[STREAM] upsert schoolData ${event.id}');
69+
_dataManager.setSchoolData(event);
70+
} else if (event is HubReconnected) {
71+
refreshData();
72+
} else if (event is HubSelectiveReconnect) {
73+
if (event.changedTypes.contains(HubObjectType.schoolData)) {
74+
refreshData();
75+
}
76+
}
77+
}
78+
5979
/// Refresh all data from API
6080
Future<void> refreshData() async {
6181
_dataManager.setLoading(true);

0 commit comments

Comments
 (0)