11import 'package:flutter/foundation.dart' ;
2+ import 'package:flutter_it/flutter_it.dart' ;
23import 'package:school_data_hub_client/school_data_hub_client.dart' ;
34import 'package:school_data_hub_flutter/common/services/notification_service.dart' ;
45import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart' ;
56import 'package:school_data_hub_flutter/features/user/data/user_api_service.dart' ;
6- import 'package:flutter_it/flutter_it.dart' ;
77
88/// Data class for createUser command parameters.
99typedef CreateUserParams = ({
@@ -39,16 +39,27 @@ typedef UpdateUserParams = ({
3939 int reliefTimeUnits,
4040 int credit,
4141 bool isTester,
42+ String ? imageUrl,
4243});
4344
4445class UserManager {
4546 final _apiService = UserApiService ();
4647 HubSessionManager get _sessionManager => di <HubSessionManager >();
4748 NotificationService get _notificationService => di <NotificationService >();
4849
50+ final _usersWithDevices = ValueNotifier <List <UserWithDevices >>([]);
4951 final _users = ValueNotifier <List <User >>([]);
52+
53+ ValueListenable <List <UserWithDevices >> get usersWithDevices =>
54+ _usersWithDevices;
55+
56+ /// Derived list of users for search bar and backward compatibility.
5057 ValueListenable <List <User >> get users => _users;
5158
59+ void _syncUsersFromDevices () {
60+ _users.value = _usersWithDevices.value.map ((e) => e.user).toList ();
61+ }
62+
5263 //-- Commands --
5364
5465 late final fetchUsersCommand = Command .createAsyncNoParamNoResult (
@@ -65,17 +76,17 @@ class UserManager {
6576
6677 late final resetPasswordCommand =
6778 Command .createAsyncNoResult <ResetPasswordParams >(
68- _resetPassword,
69- debugName: 'resetPassword' ,
70- errorFilter: const GlobalIfNoLocalErrorFilter (),
71- );
79+ _resetPassword,
80+ debugName: 'resetPassword' ,
81+ errorFilter: const GlobalIfNoLocalErrorFilter (),
82+ );
7283
7384 late final changePasswordCommand =
7485 Command .createAsyncNoResult <ChangePasswordParams >(
75- _changePassword,
76- debugName: 'changePassword' ,
77- errorFilter: const GlobalIfNoLocalErrorFilter (),
78- );
86+ _changePassword,
87+ debugName: 'changePassword' ,
88+ errorFilter: const GlobalIfNoLocalErrorFilter (),
89+ );
7990
8091 late final blockUserCommand = Command .createAsyncNoResult <User >(
8192 _blockUser,
@@ -89,8 +100,13 @@ class UserManager {
89100 errorFilter: const GlobalIfNoLocalErrorFilter (),
90101 );
91102
92- late final increaseUsersCreditCommand =
93- Command .createAsyncNoParamNoResult (
103+ late final deleteDeviceCommand = Command .createAsyncNoResult <UserDevice >(
104+ _deleteDevice,
105+ debugName: 'deleteDevice' ,
106+ errorFilter: const LocalErrorFilter (),
107+ );
108+
109+ late final increaseUsersCreditCommand = Command .createAsyncNoParamNoResult (
94110 _increaseUsersCredit,
95111 debugName: 'increaseUsersCredit' ,
96112 errorFilter: const GlobalIfNoLocalErrorFilter (),
@@ -99,6 +115,7 @@ class UserManager {
99115 UserManager ();
100116
101117 void dispose () {
118+ _usersWithDevices.dispose ();
102119 _users.dispose ();
103120 }
104121
@@ -110,11 +127,15 @@ class UserManager {
110127 //-- Command implementations --
111128
112129 Future <void > _fetchUsers () async {
113- final List <User > responseUsers = await _apiService.getAllUsers ();
114- responseUsers.sort (
115- (a, b) => a.userInfo! .userName! .compareTo (b.userInfo! .userName! ),
130+ final List <UserWithDevices > list = await _apiService
131+ .getAllUsersWithDevices ();
132+ list.sort (
133+ (a, b) => (a.user.userInfo? .userName ?? '' ).compareTo (
134+ b.user.userInfo? .userName ?? '' ,
135+ ),
116136 );
117- _users.value = responseUsers;
137+ _usersWithDevices.value = list;
138+ _syncUsersFromDevices ();
118139 }
119140
120141 Future <void > _createUser (CreateUserParams params) async {
@@ -191,6 +212,7 @@ class UserManager {
191212 reliefTimeUnits: params.reliefTimeUnits,
192213 credit: params.credit,
193214 isTester: params.isTester,
215+ imageUrl: params.imageUrl,
194216 );
195217 await fetchUsersCommand.runAsync ();
196218 _notificationService.showSnackBar (
@@ -199,6 +221,15 @@ class UserManager {
199221 );
200222 }
201223
224+ Future <void > _deleteDevice (UserDevice device) async {
225+ await _apiService.deleteAuthKeyAssociatedWithDevice (device);
226+ await fetchUsersCommand.runAsync ();
227+ _notificationService.showSnackBar (
228+ NotificationType .success,
229+ 'Gerät gelöscht.' ,
230+ );
231+ }
232+
202233 Future <void > _increaseUsersCredit () async {
203234 final success = await _apiService.increaseStaffCredit ();
204235 if (! success) {
@@ -230,74 +261,129 @@ class UserManager {
230261 required Role role,
231262 required bool isTester,
232263 String ? tutoring,
233- }) =>
234- createUserCommand.runAsync ((
235- userName: userName,
236- fullName: fullName,
237- password: password,
238- email: email,
239- matrixUserId: matrixUserId,
240- timeUnits: timeUnits,
241- reliefTimeUnits: reliefTimeUnits,
242- credit: credit,
243- scopeNames: scopeNames,
244- role: role,
245- isTester: isTester,
246- tutoring: tutoring,
247- ));
264+ }) => createUserCommand.runAsync ((
265+ userName: userName,
266+ fullName: fullName,
267+ password: password,
268+ email: email,
269+ matrixUserId: matrixUserId,
270+ timeUnits: timeUnits,
271+ reliefTimeUnits: reliefTimeUnits,
272+ credit: credit,
273+ scopeNames: scopeNames,
274+ role: role,
275+ isTester: isTester,
276+ tutoring: tutoring,
277+ ));
248278
249279 Future <void > resetPassword (String userEmail, String newPassword) =>
250- resetPasswordCommand
251- .runAsync ((userEmail: userEmail, newPassword: newPassword));
280+ resetPasswordCommand.runAsync ((
281+ userEmail: userEmail,
282+ newPassword: newPassword,
283+ ));
252284
253285 Future <void > changePassword (String oldPassword, String newPassword) =>
254- changePasswordCommand
255- .runAsync ((oldPassword: oldPassword, newPassword: newPassword));
286+ changePasswordCommand.runAsync ((
287+ oldPassword: oldPassword,
288+ newPassword: newPassword,
289+ ));
256290
257291 Future <void > blockUser (User user) => blockUserCommand.runAsync (user);
258292
293+ Future <void > deleteDevice (UserDevice device) =>
294+ deleteDeviceCommand.runAsync (device);
295+
259296 Future <void > updateUser (UpdateUserParams params) =>
260297 updateUserCommand.runAsync (params);
261298
262- Future <void > increaseUsersCredit () =>
263- increaseUsersCreditCommand.runAsync ();
299+ Future <void > increaseUsersCredit () => increaseUsersCreditCommand.runAsync ();
264300
265301 //-- Local state helpers --
266302
267303 void setUsers (List <User > users) {
268- _users.value = users;
304+ final list = users
305+ .map ((u) => UserWithDevices (user: u, userDevices: []))
306+ .toList ();
307+ list.sort (
308+ (a, b) => (a.user.userInfo? .userName ?? '' ).compareTo (
309+ b.user.userInfo? .userName ?? '' ,
310+ ),
311+ );
312+ _usersWithDevices.value = list;
313+ _syncUsersFromDevices ();
269314 }
270315
271316 void _addUser (User user) {
272- final List <User > users = List .from (_users.value);
273- users.add (user);
274- users.sort (
275- (a, b) => a.userInfo! .userName! .compareTo (b.userInfo! .userName! ),
317+ final list = List <UserWithDevices >.from (_usersWithDevices.value);
318+ list.add (UserWithDevices (user: user, userDevices: []));
319+ list.sort (
320+ (a, b) => (a.user.userInfo? .userName ?? '' ).compareTo (
321+ b.user.userInfo? .userName ?? '' ,
322+ ),
276323 );
277- _users.value = users;
324+ _usersWithDevices.value = list;
325+ _syncUsersFromDevices ();
278326 }
279327
280328 void removeUser (User user) {
281- _users.value = _users.value
282- .where ((element) => element.id != user.id)
329+ _usersWithDevices.value = _usersWithDevices.value
330+ .where (
331+ (e) => e.user.id != user.id && e.user.userInfoId != user.userInfoId,
332+ )
283333 .toList ();
334+ _syncUsersFromDevices ();
284335 }
285336
286337 void clearUsers () {
287- _users.value = [];
338+ _usersWithDevices.value = [];
339+ _syncUsersFromDevices ();
288340 }
289341
290342 void removeUsers (List <User > users) {
291- _users.value = _users.value
292- .where ((element) => ! users.contains (element))
343+ final ids = users.map ((u) => u.id).toSet ();
344+ final infoIds = users.map ((u) => u.userInfoId).toSet ();
345+ _usersWithDevices.value = _usersWithDevices.value
346+ .where (
347+ (e) =>
348+ ! ids.contains (e.user.id) && ! infoIds.contains (e.user.userInfoId),
349+ )
293350 .toList ();
351+ _syncUsersFromDevices ();
294352 }
295353
296354 void addUsers (List <User > users) {
297- _users.value = [..._users.value, ...users];
355+ final list = List <UserWithDevices >.from (_usersWithDevices.value);
356+ for (final u in users) {
357+ list.add (UserWithDevices (user: u, userDevices: []));
358+ }
359+ list.sort (
360+ (a, b) => (a.user.userInfo? .userName ?? '' ).compareTo (
361+ b.user.userInfo? .userName ?? '' ,
362+ ),
363+ );
364+ _usersWithDevices.value = list;
365+ _syncUsersFromDevices ();
298366 }
299367
300368 void updateUsers (List <User > users) {
301- _users.value = users;
369+ final byInfoId = {
370+ for (final uwd in _usersWithDevices.value) uwd.user.userInfoId: uwd,
371+ };
372+ final newList = < UserWithDevices > [];
373+ for (final u in users) {
374+ final existing = byInfoId[u.userInfoId];
375+ newList.add (
376+ existing != null
377+ ? UserWithDevices (user: u, userDevices: existing.userDevices)
378+ : UserWithDevices (user: u, userDevices: []),
379+ );
380+ }
381+ newList.sort (
382+ (a, b) => (a.user.userInfo? .userName ?? '' ).compareTo (
383+ b.user.userInfo? .userName ?? '' ,
384+ ),
385+ );
386+ _usersWithDevices.value = newList;
387+ _syncUsersFromDevices ();
302388 }
303389}
0 commit comments