Skip to content

Commit cfcbd5a

Browse files
xsahil03xrenefloor
andauthored
feat(llc): add support for OwnUser.blockedUserIds (#2168)
* feat(llc): add support for `OwnUser.blockedUserIds` * chore: update CHANGELOG.md * chore: update tests * Update packages/stream_chat/lib/src/client/client.dart Co-authored-by: Rene Floor <[email protected]> * chore: add meta import --------- Co-authored-by: Rene Floor <[email protected]>
1 parent 7e36828 commit cfcbd5a

File tree

5 files changed

+247
-12
lines changed

5 files changed

+247
-12
lines changed

packages/stream_chat/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212

1313
- Added support for message moderation feature.
1414

15+
- Improved user blocking functionality by updating client state when blocking/unblocking users:
16+
- `client.blockUser` now updates `currentUser.blockedUserIds` list with newly blocked user IDs.
17+
- `client.unblockUser` now removes the unblocked user ID from `currentUser.blockedUserIds` list.
18+
- `client.queryBlockedUsers` now updates `currentUser.blockedUserIds` with the latest blocked users data.
19+
1520
🐞 Fixed
1621

1722
- [[#1964]](https://github.com/GetStream/stream-chat-flutter/issues/1964) Fixes `Channel.membership`

packages/stream_chat/lib/src/client/client.dart

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'dart:async';
22

33
import 'package:dio/dio.dart';
44
import 'package:logging/logging.dart';
5+
import 'package:meta/meta.dart';
56
import 'package:rxdart/rxdart.dart';
67
import 'package:stream_chat/src/client/channel.dart';
78
import 'package:stream_chat/src/client/retry_policy.dart';
@@ -1451,6 +1452,68 @@ class StreamChatClient {
14511452
...options,
14521453
});
14531454

1455+
final _userBlockLock = Lock();
1456+
1457+
/// Blocks a user with the provided [userId].
1458+
Future<UserBlockResponse> blockUser(String userId) async {
1459+
try {
1460+
final response = await _userBlockLock.synchronized(
1461+
() => _chatApi.user.blockUser(userId),
1462+
);
1463+
1464+
final blockedUserId = response.blockedUserId;
1465+
final currentBlockedUserIds = [...?state.currentUser?.blockedUserIds];
1466+
if (!currentBlockedUserIds.contains(blockedUserId)) {
1467+
// Add the new blocked user to the blocked user list.
1468+
state.blockedUserIds = [...currentBlockedUserIds, blockedUserId];
1469+
}
1470+
1471+
return response;
1472+
} catch (e, stk) {
1473+
logger.severe('Error blocking user', e, stk);
1474+
rethrow;
1475+
}
1476+
}
1477+
1478+
/// Unblocks a previously blocked user with the provided [userId].
1479+
Future<EmptyResponse> unblockUser(String userId) async {
1480+
try {
1481+
final response = await _userBlockLock.synchronized(
1482+
() => _chatApi.user.unblockUser(userId),
1483+
);
1484+
1485+
final unblockedUserId = userId;
1486+
final currentBlockedUserIds = [...?state.currentUser?.blockedUserIds];
1487+
if (currentBlockedUserIds.contains(unblockedUserId)) {
1488+
// Remove the unblocked user from the blocked user list.
1489+
state.blockedUserIds = currentBlockedUserIds..remove(unblockedUserId);
1490+
}
1491+
1492+
return response;
1493+
} catch (e, stk) {
1494+
logger.severe('Error unblocking user', e, stk);
1495+
rethrow;
1496+
}
1497+
}
1498+
1499+
/// Retrieves a list of all users that the current user has blocked.
1500+
Future<BlockedUsersResponse> queryBlockedUsers() async {
1501+
try {
1502+
final response = await _userBlockLock.synchronized(
1503+
() => _chatApi.user.queryBlockedUsers(),
1504+
);
1505+
1506+
// Update the blocked user IDs with the latest data.
1507+
final blockedUserIds = response.blocks.map((it) => it.blockedUserId);
1508+
state.blockedUserIds = [...blockedUserIds.nonNulls];
1509+
1510+
return response;
1511+
} catch (e, stk) {
1512+
logger.severe('Error querying blocked users', e, stk);
1513+
rethrow;
1514+
}
1515+
}
1516+
14541517
/// Mutes a user
14551518
Future<EmptyResponse> muteUser(String userId) =>
14561519
_chatApi.moderation.muteUser(userId);
@@ -1459,18 +1522,6 @@ class StreamChatClient {
14591522
Future<EmptyResponse> unmuteUser(String userId) =>
14601523
_chatApi.moderation.unmuteUser(userId);
14611524

1462-
/// Blocks a user
1463-
Future<UserBlockResponse> blockUser(String userId) =>
1464-
_chatApi.user.blockUser(userId);
1465-
1466-
/// Unblocks a user
1467-
Future<EmptyResponse> unblockUser(String userId) =>
1468-
_chatApi.user.unblockUser(userId);
1469-
1470-
/// Requests users with a given query.
1471-
Future<BlockedUsersResponse> queryBlockedUsers() =>
1472-
_chatApi.user.queryBlockedUsers();
1473-
14741525
/// Flag a message
14751526
Future<EmptyResponse> flagMessage(String messageId) =>
14761527
_chatApi.moderation.flagMessage(messageId);
@@ -1997,6 +2048,11 @@ class ClientState {
19972048
channels = channels..remove(channelCid);
19982049
}
19992050

2051+
@visibleForTesting
2052+
set blockedUserIds(List<String> blockedUserIds) {
2053+
currentUser = currentUser?.copyWith(blockedUserIds: blockedUserIds);
2054+
}
2055+
20002056
/// Used internally for optimistic update of unread count
20012057
set totalUnreadCount(int unreadCount) {
20022058
_totalUnreadCountController.add(unreadCount);

packages/stream_chat/lib/src/core/models/own_user.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class OwnUser extends User {
1717
this.unreadChannels = 0,
1818
this.channelMutes = const [],
1919
this.unreadThreads = 0,
20+
this.blockedUserIds = const [],
2021
required super.id,
2122
super.role,
2223
super.name,
@@ -72,6 +73,7 @@ class OwnUser extends User {
7273
List<ChannelMute>? channelMutes,
7374
List<Device>? devices,
7475
List<Mute>? mutes,
76+
List<String>? blockedUserIds,
7577
int? totalUnreadCount,
7678
int? unreadChannels,
7779
int? unreadThreads,
@@ -99,6 +101,7 @@ class OwnUser extends User {
99101
totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount,
100102
unreadChannels: unreadChannels ?? this.unreadChannels,
101103
unreadThreads: unreadThreads ?? this.unreadThreads,
104+
blockedUserIds: blockedUserIds ?? this.blockedUserIds,
102105
language: language ?? this.language,
103106
);
104107

@@ -124,6 +127,7 @@ class OwnUser extends User {
124127
totalUnreadCount: other.totalUnreadCount,
125128
unreadChannels: other.unreadChannels,
126129
unreadThreads: other.unreadThreads,
130+
blockedUserIds: other.blockedUserIds,
127131
updatedAt: other.updatedAt,
128132
language: other.language,
129133
);
@@ -153,6 +157,10 @@ class OwnUser extends User {
153157
@JsonKey(includeIfNull: false)
154158
final int unreadThreads;
155159

160+
/// List of user ids that are blocked by the user.
161+
@JsonKey(includeIfNull: false)
162+
final List<String> blockedUserIds;
163+
156164
/// Known top level fields.
157165
///
158166
/// Useful for [Serializer] methods.
@@ -163,6 +171,7 @@ class OwnUser extends User {
163171
'unread_channels',
164172
'channel_mutes',
165173
'unread_threads',
174+
'blocked_user_ids',
166175
...User.topLevelFields,
167176
];
168177
}

packages/stream_chat/lib/src/core/models/own_user.g.dart

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/stream_chat/test/src/client/client_test.dart

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2294,6 +2294,167 @@ void main() {
22942294
verifyNoMoreInteractions(api.user);
22952295
});
22962296

2297+
group('Block user state management', () {
2298+
test('blockUser should update blockedUserIds on client state', () async {
2299+
final testUser = OwnUser(id: 'test-user');
2300+
const userId = 'blocked-user-id';
2301+
2302+
// Verify initial state
2303+
expect(client.state.currentUser?.blockedUserIds, isEmpty);
2304+
2305+
when(() => api.user.blockUser(userId)).thenAnswer(
2306+
(_) async => UserBlockResponse()
2307+
..blockedUserId = userId
2308+
..blockedByUserId = testUser.id
2309+
..createdAt = DateTime.now(),
2310+
);
2311+
2312+
await client.blockUser(userId);
2313+
2314+
// Verify - should now include the blocked user ID
2315+
expect(client.state.currentUser?.blockedUserIds, contains(userId));
2316+
verify(() => api.user.blockUser(userId)).called(1);
2317+
verifyNoMoreInteractions(api.user);
2318+
});
2319+
2320+
test(
2321+
'blockUser should not duplicate existing blocked user IDs',
2322+
() async {
2323+
const userId = 'blocked-user-id';
2324+
client.state.blockedUserIds = const [userId];
2325+
2326+
// Verify the user is already in the blocked list
2327+
expect(client.state.currentUser?.blockedUserIds, contains(userId));
2328+
2329+
when(() => api.user.blockUser(userId)).thenAnswer(
2330+
(_) async => UserBlockResponse()
2331+
..blockedUserId = userId
2332+
..blockedByUserId = client.state.currentUser!.id
2333+
..createdAt = DateTime.now(),
2334+
);
2335+
2336+
await client.blockUser(userId);
2337+
2338+
// Verify - should still have only one entry
2339+
expect(client.state.currentUser?.blockedUserIds, contains(userId));
2340+
expect(client.state.currentUser?.blockedUserIds.length, 1);
2341+
verify(() => api.user.blockUser(userId)).called(1);
2342+
verifyNoMoreInteractions(api.user);
2343+
},
2344+
);
2345+
2346+
test('unblockUser should remove user from blockedUserIds', () async {
2347+
const blockedUserId = 'blocked-user-id';
2348+
const otherBlockedId = 'other-blocked-id';
2349+
client.state.blockedUserIds = const [blockedUserId, otherBlockedId];
2350+
2351+
// Verify initial state includes both blocked IDs
2352+
expect(
2353+
client.state.currentUser?.blockedUserIds,
2354+
containsAll([blockedUserId, otherBlockedId]),
2355+
);
2356+
2357+
when(() => api.user.unblockUser(blockedUserId)).thenAnswer(
2358+
(_) async => EmptyResponse(),
2359+
);
2360+
2361+
await client.unblockUser(blockedUserId);
2362+
2363+
// Verify - blockedUserId should be removed
2364+
expect(
2365+
client.state.currentUser?.blockedUserIds,
2366+
contains(otherBlockedId),
2367+
);
2368+
2369+
expect(
2370+
client.state.currentUser?.blockedUserIds,
2371+
isNot(contains(blockedUserId)),
2372+
);
2373+
2374+
verify(() => api.user.unblockUser(blockedUserId)).called(1);
2375+
verifyNoMoreInteractions(api.user);
2376+
});
2377+
2378+
test(
2379+
'unblockUser should be resilient if user ID not in blocked list',
2380+
() async {
2381+
const nonBlockedUserId = 'not-in-list';
2382+
const otherBlockedId = 'other-blocked-id';
2383+
client.state.blockedUserIds = const [otherBlockedId];
2384+
2385+
// Verify initial state
2386+
expect(
2387+
client.state.currentUser?.blockedUserIds,
2388+
contains(otherBlockedId),
2389+
);
2390+
2391+
expect(
2392+
client.state.currentUser?.blockedUserIds,
2393+
isNot(contains(nonBlockedUserId)),
2394+
);
2395+
2396+
when(() => api.user.unblockUser(nonBlockedUserId)).thenAnswer(
2397+
(_) async => EmptyResponse(),
2398+
);
2399+
2400+
await client.unblockUser(nonBlockedUserId);
2401+
2402+
// Verify - should remain unchanged
2403+
expect(client.state.currentUser?.blockedUserIds,
2404+
contains(otherBlockedId));
2405+
expect(client.state.currentUser?.blockedUserIds,
2406+
isNot(contains(nonBlockedUserId)));
2407+
verify(() => api.user.unblockUser(nonBlockedUserId)).called(1);
2408+
verifyNoMoreInteractions(api.user);
2409+
},
2410+
);
2411+
2412+
test(
2413+
'queryBlockedUsers should update client state with blockedUserIds',
2414+
() async {
2415+
const blockedId1 = 'blocked-1';
2416+
const blockedId2 = 'blocked-2';
2417+
2418+
// Verify initial state
2419+
expect(client.state.currentUser?.blockedUserIds, isEmpty);
2420+
2421+
// Create mock users
2422+
final blockedUser1 = User(id: 'blocked-user-1');
2423+
final blockedUser2 = User(id: 'blocked-user-2');
2424+
2425+
// Mock the queryBlockedUsers API call
2426+
when(() => api.user.queryBlockedUsers()).thenAnswer(
2427+
(_) async => BlockedUsersResponse()
2428+
..blocks = [
2429+
UserBlock(
2430+
user: user,
2431+
userId: user.id,
2432+
blockedUser: blockedUser1,
2433+
blockedUserId: blockedId1,
2434+
),
2435+
UserBlock(
2436+
user: user,
2437+
userId: user.id,
2438+
blockedUser: blockedUser2,
2439+
blockedUserId: blockedId2,
2440+
),
2441+
],
2442+
);
2443+
2444+
await client.queryBlockedUsers();
2445+
2446+
// Verify - should now include both blocked IDs
2447+
expect(
2448+
client.state.currentUser?.blockedUserIds,
2449+
containsAll([blockedId1, blockedId2]),
2450+
);
2451+
2452+
verify(() => api.user.queryBlockedUsers()).called(1);
2453+
verifyNoMoreInteractions(api.user);
2454+
},
2455+
);
2456+
});
2457+
22972458
test('`.shadowBan`', () async {
22982459
const userId = 'test-user-id';
22992460

0 commit comments

Comments
 (0)