Skip to content

Commit fa7ae0f

Browse files
xsahil03xd3xvn
andauthored
feat(persistence): add support for polls and poll votes (#2060)
Co-authored-by: Deven Joshi <[email protected]> Co-authored-by: xsahil03x <[email protected]>
1 parent 337074e commit fa7ae0f

39 files changed

+6268
-2969
lines changed

.github/actions/pana/action.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@ runs:
2121
using: "composite"
2222
steps:
2323
- name: Temporary Override Local Dependencies
24-
shell: bash
25-
run: |
26-
git apply .github/actions/pana/chore__temporarily_override_dep_to_local.patch
24+
uses: mikefarah/yq@master
25+
with:
26+
cmd: |
27+
yq eval '.dependencies.stream_chat_flutter = {"path": "../stream_chat_flutter"}' -i packages/stream_chat_localizations/pubspec.yaml
28+
yq eval '.dependencies.stream_chat = {"path": "../stream_chat"}' -i packages/stream_chat_flutter_core/pubspec.yaml
29+
yq eval '.dependencies.stream_chat_flutter_core = {"path": "../stream_chat_flutter_core"}' -i packages/stream_chat_flutter/pubspec.yaml
30+
yq eval '.dependencies.stream_chat = {"path": "../stream_chat"}' -i packages/stream_chat_persistence/pubspec.yaml
2731
2832
- name: Install Flutter
2933
uses: subosito/flutter-action@v2

.github/actions/pana/chore__temporarily_override_dep_to_local.patch

Lines changed: 0 additions & 79 deletions
This file was deleted.

melos.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ command:
6161
rxdart: ^0.28.0
6262
share_plus: ^10.0.2
6363
shimmer: ^3.0.0
64-
sqlite3_flutter_libs: ^0.5.24
64+
sqlite3_flutter_libs: ^0.5.26
6565
stream_chat: ^8.3.0
6666
stream_chat_flutter: ^8.3.0
6767
stream_chat_flutter_core: ^8.3.0

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ const _nullConst = _NullConst();
2020
/// {@endtemplate}
2121
enum VotingVisibility {
2222
/// The voting process is anonymous.
23+
@JsonValue('anonymous')
2324
anonymous,
2425

2526
/// The voting process is public.
27+
@JsonValue('public')
2628
public,
2729
}
2830

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,28 @@ class PollVote extends Equatable {
7070
/// Serialize to json
7171
Map<String, dynamic> toJson() => _$PollVoteToJson(this);
7272

73+
/// Creates a copy of [PollVote] with specified attributes overridden.
74+
PollVote copyWith({
75+
String? id,
76+
String? pollId,
77+
String? optionId,
78+
String? answerText,
79+
DateTime? createdAt,
80+
DateTime? updatedAt,
81+
String? userId,
82+
User? user,
83+
}) =>
84+
PollVote(
85+
id: id ?? this.id,
86+
pollId: pollId ?? this.pollId,
87+
optionId: optionId ?? this.optionId,
88+
answerText: answerText ?? this.answerText,
89+
createdAt: createdAt ?? this.createdAt,
90+
updatedAt: updatedAt ?? this.updatedAt,
91+
userId: userId ?? this.userId,
92+
user: user ?? this.user,
93+
);
94+
7395
@override
7496
List<Object?> get props => [
7597
id,

packages/stream_chat/lib/src/db/chat_persistence_client.dart

Lines changed: 83 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import 'package:stream_chat/src/core/models/event.dart';
66
import 'package:stream_chat/src/core/models/filter.dart';
77
import 'package:stream_chat/src/core/models/member.dart';
88
import 'package:stream_chat/src/core/models/message.dart';
9+
import 'package:stream_chat/src/core/models/poll.dart';
10+
import 'package:stream_chat/src/core/models/poll_vote.dart';
911
import 'package:stream_chat/src/core/models/reaction.dart';
1012
import 'package:stream_chat/src/core/models/read.dart';
1113
import 'package:stream_chat/src/core/models/user.dart';
@@ -169,6 +171,12 @@ abstract class ChatPersistenceClient {
169171
/// Updates all the channels using the new [channels] data.
170172
Future<void> updateChannels(List<ChannelModel> channels);
171173

174+
/// Updates all the polls using the new [polls] data.
175+
Future<void> updatePolls(List<Poll> polls);
176+
177+
/// Deletes all the polls by [pollIds].
178+
Future<void> deletePollsByIds(List<String> pollIds);
179+
172180
/// Updates all the members of a particular channle [cid]
173181
/// with the new [members] data
174182
Future<void> updateMembers(String cid, List<Member> members) =>
@@ -194,12 +202,18 @@ abstract class ChatPersistenceClient {
194202
/// Updates the pinned message reactions data with the new [reactions] data
195203
Future<void> updatePinnedMessageReactions(List<Reaction> reactions);
196204

205+
/// Updates the poll votes data with the new [pollVotes] data
206+
Future<void> updatePollVotes(List<PollVote> pollVotes);
207+
197208
/// Deletes all the reactions by [messageIds]
198209
Future<void> deleteReactionsByMessageId(List<String> messageIds);
199210

200211
/// Deletes all the pinned messages reactions by [messageIds]
201212
Future<void> deletePinnedMessageReactionsByMessageId(List<String> messageIds);
202213

214+
/// Deletes all the poll votes by [pollIds]
215+
Future<void> deletePollVotesByPollIds(List<String> pollIds);
216+
203217
/// Deletes all the members by channel [cids]
204218
Future<void> deleteMembersByCids(List<String> cids);
205219

@@ -245,50 +259,64 @@ abstract class ChatPersistenceClient {
245259
final reactions = <Reaction>[];
246260
final pinnedReactions = <Reaction>[];
247261

262+
final polls = <Poll>[];
263+
final pollVotes = <PollVote>[];
264+
final pollVotesToDelete = <String>[];
265+
248266
for (final state in channelStates) {
249267
final channel = state.channel;
250-
if (channel != null) {
251-
channels.add(channel);
252-
253-
final cid = channel.cid;
254-
final reads = state.read;
255-
final members = state.members;
256-
final Iterable<Message>? messages;
257-
if (CurrentPlatform.isWeb) {
258-
messages = state.messages?.where(
268+
// Continue if channel is not available.
269+
if (channel == null) continue;
270+
channels.add(channel);
271+
272+
final cid = channel.cid;
273+
final reads = state.read;
274+
final members = state.members;
275+
final messages = switch (CurrentPlatform.isWeb) {
276+
true => state.messages?.where(
259277
(it) => !it.attachments.any(
260278
(it) => it.uploadState != const UploadState.success(),
261279
),
262-
);
263-
} else {
264-
messages = state.messages;
265-
}
266-
final pinnedMessages = state.pinnedMessages;
267-
268-
// Preparing deletion data
269-
membersToDelete.add(cid);
270-
reactionsToDelete.addAll(state.messages?.map((it) => it.id) ?? []);
271-
pinnedReactionsToDelete
272-
.addAll(state.pinnedMessages?.map((it) => it.id) ?? []);
273-
274-
// preparing addition data
275-
channelWithReads[cid] = reads;
276-
channelWithMembers[cid] = members;
277-
channelWithMessages[cid] = messages?.toList();
278-
channelWithPinnedMessages[cid] = pinnedMessages;
279-
280-
reactions.addAll(messages?.expand(_expandReactions) ?? []);
281-
pinnedReactions.addAll(pinnedMessages?.expand(_expandReactions) ?? []);
282-
283-
users.addAll([
284-
channel.createdBy,
285-
...messages?.map((it) => it.user) ?? <User>[],
286-
...reads?.map((it) => it.user) ?? <User>[],
287-
...members?.map((it) => it.user) ?? <User>[],
288-
...reactions.map((it) => it.user),
289-
...pinnedReactions.map((it) => it.user),
290-
].withNullifyer);
291-
}
280+
),
281+
_ => state.messages,
282+
};
283+
284+
final pinnedMessages = state.pinnedMessages;
285+
286+
// Preparing deletion data
287+
membersToDelete.add(cid);
288+
reactionsToDelete.addAll(messages?.map((it) => it.id) ?? []);
289+
pinnedReactionsToDelete.addAll(pinnedMessages?.map((it) => it.id) ?? []);
290+
291+
// preparing addition data
292+
channelWithReads[cid] = reads;
293+
channelWithMembers[cid] = members;
294+
channelWithMessages[cid] = messages?.toList();
295+
channelWithPinnedMessages[cid] = pinnedMessages;
296+
297+
reactions.addAll(messages?.expand(_expandReactions) ?? []);
298+
pinnedReactions.addAll(pinnedMessages?.expand(_expandReactions) ?? []);
299+
300+
polls.addAll([
301+
...?messages?.map((it) => it.poll),
302+
...?pinnedMessages?.map((it) => it.poll),
303+
].withNullifyer);
304+
305+
pollVotesToDelete.addAll(polls.map((it) => it.id));
306+
307+
pollVotes.addAll(polls.expand(_expandPollVotes));
308+
309+
users.addAll([
310+
channel.createdBy,
311+
...?messages?.map((it) => it.user),
312+
...?pinnedMessages?.map((it) => it.user),
313+
...?reads?.map((it) => it.user),
314+
...?members?.map((it) => it.user),
315+
...reactions.map((it) => it.user),
316+
...pinnedReactions.map((it) => it.user),
317+
...polls.map((it) => it.createdBy),
318+
...pollVotes.map((it) => it.user),
319+
].withNullifyer);
292320
}
293321

294322
// Removing old members and reactions data as they may have
@@ -297,12 +325,14 @@ abstract class ChatPersistenceClient {
297325
deleteMembersByCids(membersToDelete),
298326
deleteReactionsByMessageId(reactionsToDelete),
299327
deletePinnedMessageReactionsByMessageId(pinnedReactionsToDelete),
328+
deletePollVotesByPollIds(pollVotesToDelete),
300329
]);
301330

302331
// Updating first as does not depend on any other table.
303332
await Future.wait([
304333
updateUsers(users.toList(growable: false)),
305334
updateChannels(channels.toList(growable: false)),
335+
updatePolls(polls.toList(growable: false)),
306336
]);
307337

308338
// All has a foreign key relation with channels table.
@@ -315,10 +345,9 @@ abstract class ChatPersistenceClient {
315345

316346
// Both has a foreign key relation with messages, pinnedMessages table.
317347
await Future.wait([
318-
updateReactions(reactions.toList(growable: false)),
319-
updatePinnedMessageReactions(
320-
pinnedReactions.toList(growable: false),
321-
),
348+
updateReactions(reactions),
349+
updatePinnedMessageReactions(pinnedReactions),
350+
updatePollVotes(pollVotes),
322351
]);
323352
}
324353

@@ -330,4 +359,15 @@ abstract class ChatPersistenceClient {
330359
if (latest != null) ...latest.where((r) => r.userId != null),
331360
];
332361
}
362+
363+
List<PollVote> _expandPollVotes(Poll poll) {
364+
final latestAnswers = poll.latestAnswers;
365+
final latestVotes = poll.latestVotesByOption.values;
366+
final ownVotesAndAnswers = poll.ownVotesAndAnswers;
367+
return [
368+
...latestAnswers,
369+
...latestVotes.expand((it) => it),
370+
...ownVotesAndAnswers,
371+
];
372+
}
333373
}

packages/stream_chat/test/src/db/chat_persistence_client_test.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import 'package:stream_chat/src/core/models/event.dart';
55
import 'package:stream_chat/src/core/models/filter.dart';
66
import 'package:stream_chat/src/core/models/member.dart';
77
import 'package:stream_chat/src/core/models/message.dart';
8+
import 'package:stream_chat/src/core/models/poll.dart';
9+
import 'package:stream_chat/src/core/models/poll_vote.dart';
810
import 'package:stream_chat/src/core/models/reaction.dart';
911
import 'package:stream_chat/src/core/models/read.dart';
1012
import 'package:stream_chat/src/core/models/user.dart';
@@ -49,6 +51,9 @@ class TestPersistenceClient extends ChatPersistenceClient {
4951
List<String> messageIds) =>
5052
Future.value();
5153

54+
@override
55+
Future<void> deletePollVotesByPollIds(List<String> pollIds) => Future.value();
56+
5257
@override
5358
Future<void> disconnect({bool flush = false}) => throw UnimplementedError();
5459

@@ -119,6 +124,9 @@ class TestPersistenceClient extends ChatPersistenceClient {
119124
Future<void> updatePinnedMessageReactions(List<Reaction> reactions) =>
120125
Future.value();
121126

127+
@override
128+
Future<void> updatePollVotes(List<PollVote> pollVotes) => Future.value();
129+
122130
@override
123131
Future<void> updateUsers(List<User> users) => Future.value();
124132

@@ -137,6 +145,12 @@ class TestPersistenceClient extends ChatPersistenceClient {
137145
@override
138146
Future<void> bulkUpdateReads(Map<String, List<Read>?> reads) =>
139147
Future.value();
148+
149+
@override
150+
Future<void> deletePollsByIds(List<String> pollIds) => Future.value();
151+
152+
@override
153+
Future<void> updatePolls(List<Poll> polls) => Future.value();
140154
}
141155

142156
void main() {
@@ -169,6 +183,16 @@ void main() {
169183
expect(channelState, isNotNull);
170184
});
171185

186+
test('deletePollsByIds', () {
187+
const pollIds = ['poll-id'];
188+
persistenceClient.deletePollsByIds(pollIds);
189+
});
190+
191+
test('updatePolls', () async {
192+
final poll = Poll(id: 'poll-id', name: 'poll-name', options: const []);
193+
persistenceClient.updatePolls([poll]);
194+
});
195+
172196
test('updateChannelThreads', () async {
173197
const cid = 'test:cid';
174198
final user = User(id: 'test-user-id');

0 commit comments

Comments
 (0)