part of 'data_service.dart'; extension DataServiceFriendships on DataService { Future fetchFriendships() async { _isFriendshipsLoading = true; try { final json = await api.get('/friendships'); List? list; if (json is List) { list = json; } else if (json is Map) { for (final key in ['friendships', 'data', 'items', 'results']) { final value = json[key]; if (value is List) { list = value; break; } } } final parsed = list ?.whereType() .map((e) => Friendship.fromJson( e.map((k, v) => MapEntry(k.toString(), v)), )) .where((f) => f.isAccepted) .toList(); _friendships = parsed ?? []; } catch (e) { debugPrint('Failed to fetch friendships: $e'); _friendships = []; } finally { _isFriendshipsLoading = false; _notifyAsync(); } } Future fetchPendingFriendships() async { _isPendingFriendshipsLoading = true; try { final incoming = await _fetchPending('incoming'); final outgoing = await _fetchPending('outgoing'); _pendingIncoming = incoming; _pendingOutgoing = outgoing; } catch (e) { debugPrint('Failed to fetch pending friendships: $e'); _pendingIncoming = []; _pendingOutgoing = []; } finally { _isPendingFriendshipsLoading = false; _notifyAsync(); } } Future> _fetchPending(String direction) async { final json = await api.get('/friendships/pending?direction=$direction'); List? list; if (json is List) { list = json; } else if (json is Map) { for (final key in ['friendships', 'data', 'items', 'results']) { final value = json[key]; if (value is List) { list = value; break; } } } return list ?.whereType() .map((e) => Friendship.fromJson( e.map((k, v) => MapEntry(k.toString(), v)), )) .where((f) => f.isPending) .toList() ?? const []; } Future fetchFriendshipById(String friendshipId) async { try { final json = await api.get('/friendships/$friendshipId'); if (json is Map) { return Friendship.fromJson( json.map((k, v) => MapEntry(k.toString(), v)), ); } } catch (e) { debugPrint('Failed to fetch friendship $friendshipId: $e'); } return null; } Future> searchUsers( String query, { bool friendsOnly = false, }) async { final encoded = Uri.encodeComponent(query); final json = await api.get( '/users/search?search=$encoded${friendsOnly ? '&friends_only=true' : ''}', ); List? list; if (json is List) { list = json; } else if (json is Map) { for (final key in ['users', 'data', 'results', 'items']) { final value = json[key]; if (value is List) { list = value; break; } } } if (list == null) return const []; return list .whereType() .map((e) => UserSummary.fromJson( e.map((k, v) => MapEntry(k.toString(), v)), )) .toList(); } Future fetchFriendshipStatus(String targetUserId) async { try { final json = await api.get('/friendships/status/$targetUserId'); if (json is Map) { return Friendship.fromStatusJson( json.map((k, v) => MapEntry(k.toString(), v)), targetUserId: targetUserId, ); } } catch (e) { debugPrint('Failed to fetch friendship status for $targetUserId: $e'); } return Friendship( id: null, status: 'none', requesterId: '', addresseeId: targetUserId, ); } Future requestFriendship( String targetUserId, { UserSummary? targetUser, }) async { final json = await api.post('/friendships/request', { "target_user_id": targetUserId, }); final friendship = _parseAndUpsertFriendship( json, fallbackStatus: 'pending', overrideAddressee: targetUser, ); _pendingOutgoing = [friendship, ..._pendingOutgoing]; _notifyAsync(); return friendship; } Future acceptFriendship(String friendshipId) async { final json = await api.post('/friendships/$friendshipId/accept', {}); final friendship = _parseAndUpsertFriendship(json, fallbackStatus: 'accepted'); _pendingIncoming = _pendingIncoming.where((f) => f.id != friendshipId).toList(); _notifyAsync(); return friendship; } Future rejectFriendship(String friendshipId) async { final json = await api.post('/friendships/$friendshipId/reject', {}); final friendship = _parseAndUpsertFriendship(json, fallbackStatus: 'declined'); _pendingIncoming = _pendingIncoming.where((f) => f.id != friendshipId).toList(); _notifyAsync(); return friendship; } Future cancelFriendship(String friendshipId) async { final json = await api.post('/friendships/$friendshipId/cancel', {}); final friendship = _parseAndRemoveFriendship(json, friendshipId, status: 'none'); _pendingOutgoing = _pendingOutgoing.where((f) => f.id != friendshipId).toList(); _notifyAsync(); return friendship; } Future deleteFriendship(String friendshipId) async { try { await api.delete('/friendships/$friendshipId'); } catch (e) { debugPrint('Failed to delete friendship $friendshipId: $e'); rethrow; } _friendships = _friendships.where((f) => f.id != friendshipId).toList(); _pendingIncoming = _pendingIncoming.where((f) => f.id != friendshipId).toList(); _pendingOutgoing = _pendingOutgoing.where((f) => f.id != friendshipId).toList(); _notifyAsync(); } Future blockUser(String targetUserId) async { final json = await api.post('/friendships/block', { "target_user_id": targetUserId, }); final friendship = _parseAndUpsertFriendship(json, fallbackStatus: 'blocked'); _pendingIncoming = _pendingIncoming .where((f) => f.addresseeId != targetUserId && f.requesterId != targetUserId) .toList(); _pendingOutgoing = _pendingOutgoing .where((f) => f.addresseeId != targetUserId && f.requesterId != targetUserId) .toList(); _notifyAsync(); return friendship; } Friendship _parseAndUpsertFriendship( dynamic json, { String fallbackStatus = 'none', UserSummary? overrideAddressee, }) { Friendship friendship; if (json is Map) { friendship = Friendship.fromJson( json.map((k, v) => MapEntry(k.toString(), v)), ); } else { friendship = Friendship( id: null, status: fallbackStatus, requesterId: '', addresseeId: '', ); } if (overrideAddressee != null && (friendship.addressee == null || friendship.addressee!.userId.isEmpty || friendship.addressee!.displayName.isEmpty)) { friendship = friendship.copyWith( addressee: overrideAddressee, addresseeId: overrideAddressee.userId, ); } _upsertFriendship(friendship); return friendship; } Friendship _parseAndRemoveFriendship( dynamic json, String friendshipId, { required String status, }) { Friendship friendship; if (json is Map) { friendship = Friendship.fromJson( json.map((k, v) => MapEntry(k.toString(), v)), ); } else { friendship = Friendship( id: friendshipId, status: status, requesterId: '', addresseeId: '', ); } _friendships = _friendships.where((f) => f.id != friendshipId).toList(); _notifyAsync(); return friendship; } void _upsertFriendship(Friendship friendship) { if (friendship.id == null || friendship.id!.isEmpty) { _notifyAsync(); return; } final idx = _friendships.indexWhere((f) => f.id == friendship.id); if (idx >= 0) { _friendships[idx] = friendship; } else { _friendships = [friendship, ..._friendships]; } _friendships = _friendships.where((f) => f.isAccepted).toList(); _notifyAsync(); } }