Skip to content

Commit

Permalink
feat(ui, core, localization): Add Poll attachment interactor (#2052)
Browse files Browse the repository at this point in the history
Co-authored-by: Deven Joshi <deven9852@gmail.com>
Co-authored-by: xsahil03x <xsahil03x@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 24, 2024
1 parent 714a194 commit 337074e
Show file tree
Hide file tree
Showing 131 changed files with 6,999 additions and 113 deletions.
12 changes: 1 addition & 11 deletions .github/workflows/update_goldens.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,8 @@ jobs:
update_goldens:
runs-on: ubuntu-latest
steps:
- name: 🚫 Ensure branch is not master
if: ${{ github.event.inputs.branch == 'master' || github.event.inputs.branch == 'origin/master'}}
run: |
echo "Updating goldens on 'master' branch is prohibited."
exit 1
- name: 📚 Checkout branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch }}

- name: 🐦 Install Flutter
uses: subosito/flutter-action@v2
Expand All @@ -32,10 +24,8 @@ jobs:
run: melos bootstrap --verbose

- name: 🖼️ Update Goldens
working-directory: packages/stream_chat_flutter
continue-on-error: true
run: |
flutter test --tags golden --update-goldens
run: melos run update:goldens

- name: 📤 Commit Changes
id: commit_changes
Expand Down
4 changes: 4 additions & 0 deletions melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ scripts:
flutter: true
dirExists: test

update:goldens:
run: melos exec -c 1 --depends-on="alchemist" -- "flutter test --tags golden --update-goldens"
description: Update golden files for all packages in this project.

clean:flutter:
run: melos exec -c 4 --fail-fast -- "flutter clean"
description: Run Flutter clean for a specific package in this project.
Expand Down
138 changes: 119 additions & 19 deletions packages/stream_chat/lib/src/client/channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,8 @@ class Channel {
return _client.sendEvent(id!, type, event);
}

final _pollLock = Lock();

/// Send a message with a poll to this channel.
///
/// Optionally provide a [messageText] to send a message along with the poll.
Expand All @@ -1043,7 +1045,7 @@ class Channel {
String messageText = '',
}) async {
_checkInitialized();
final res = await _client.createPoll(poll);
final res = await _pollLock.synchronized(() => _client.createPoll(poll));
return sendMessage(
Message(
text: messageText,
Expand All @@ -1056,13 +1058,109 @@ class Channel {
/// Updates the [poll] in this channel.
Future<UpdatePollResponse> updatePoll(Poll poll) {
_checkInitialized();
return _client.updatePoll(poll);
return _pollLock.synchronized(() => _client.updatePoll(poll));
}

/// Deletes the given [poll] from this channel.
Future<EmptyResponse> deletePoll(Poll poll) {
_checkInitialized();
return _pollLock.synchronized(() => _client.deletePoll(poll.id));
}

/// Close the given [poll].
Future<UpdatePollResponse> closePoll(Poll poll) {
_checkInitialized();
return _pollLock.synchronized(() => _client.closePoll(poll.id));
}

/// Deletes the poll with the given [pollId] from this channel.
Future<EmptyResponse> deletePoll(String pollId) {
/// Create a new poll option for the given [poll].
Future<CreatePollOptionResponse> createPollOption(
Poll poll,
PollOption option,
) {
_checkInitialized();
return _pollLock.synchronized(
() => _client.createPollOption(poll.id, option),
);
}

final _pollVoteLock = Lock();

/// Cast a vote on the given [poll] with the given [option].
Future<CastPollVoteResponse> castPollVote(
Message message,
Poll poll,
PollOption option,
) async {
_checkInitialized();
return _client.deletePoll(pollId);

final optionId = option.id;
if (optionId == null) {
throw ArgumentError('Option id cannot be null');
}

return _pollVoteLock.synchronized(
() => _client.castPollVote(
message.id,
poll.id,
optionId: optionId,
),
);
}

/// Add a new answer to the given [poll].
Future<CastPollVoteResponse> addPollAnswer(
Message message,
Poll poll, {
required String answerText,
}) {
_checkInitialized();
return _pollVoteLock.synchronized(
() => _client.addPollAnswer(
message.id,
poll.id,
answerText: answerText,
),
);
}

/// Remove a vote on the given [poll] with the given [vote].
Future<RemovePollVoteResponse> removePollVote(
Message message,
Poll poll,
PollVote vote,
) {
_checkInitialized();

final voteId = vote.id;
if (voteId == null) {
throw ArgumentError('Vote id cannot be null');
}

return _pollVoteLock.synchronized(
() => _client.removePollVote(
message.id,
poll.id,
voteId,
),
);
}

/// Query the poll votes for the given [pollId] with the given [filter] and
/// [sort] options.
Future<QueryPollVotesResponse> queryPollVotes(
String pollId, {
Filter? filter,
List<SortOption>? sort,
PaginationParams pagination = const PaginationParams(),
}) {
_checkInitialized();
return _client.queryPollVotes(
pollId,
filter: filter,
sort: sort,
pagination: pagination,
);
}

/// Send a reaction to this channel.
Expand Down Expand Up @@ -2120,12 +2218,12 @@ class ChannelClientState {

final oldPoll = pollMessage.poll;

final answers = oldPoll?.answers ?? eventPoll.answers;
final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers;
final ownVotesAndAnswers =
oldPoll?.ownVotesAndAnswers ?? eventPoll.ownVotesAndAnswers;

final poll = eventPoll.copyWith(
answers: answers,
latestAnswers: latestAnswers,
ownVotesAndAnswers: ownVotesAndAnswers,
);

Expand Down Expand Up @@ -2160,8 +2258,8 @@ class ChannelClientState {

final oldPoll = pollMessage.poll;

final answers = <String, PollVote>{
for (final ans in oldPoll?.answers ?? []) ans.id: ans,
final latestAnswers = <String, PollVote>{
for (final ans in oldPoll?.latestAnswers ?? []) ans.id: ans,
eventPollVote.id!: eventPollVote,
};

Expand All @@ -2173,7 +2271,7 @@ class ChannelClientState {
};

final poll = eventPoll.copyWith(
answers: [...answers.values],
latestAnswers: [...latestAnswers.values],
ownVotesAndAnswers: [...ownVotesAndAnswers.values],
);

Expand All @@ -2192,7 +2290,7 @@ class ChannelClientState {

final oldPoll = pollMessage.poll;

final answers = oldPoll?.answers ?? eventPoll.answers;
final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers;
final currentUserId = _channel.client.state.currentUser?.id;
final ownVotesAndAnswers = <String, PollVote>{
for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote,
Expand All @@ -2201,7 +2299,7 @@ class ChannelClientState {
};

final poll = eventPoll.copyWith(
answers: answers,
latestAnswers: latestAnswers,
ownVotesAndAnswers: [...ownVotesAndAnswers.values],
);

Expand All @@ -2220,16 +2318,16 @@ class ChannelClientState {

final oldPoll = pollMessage.poll;

final answers = <String, PollVote>{
for (final ans in oldPoll?.answers ?? []) ans.id: ans,
final latestAnswers = <String, PollVote>{
for (final ans in oldPoll?.latestAnswers ?? []) ans.id: ans,
}..remove(eventPollVote.id);

final ownVotesAndAnswers = <String, PollVote>{
for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote,
}..remove(eventPollVote.id);

final poll = eventPoll.copyWith(
answers: [...answers.values],
latestAnswers: [...latestAnswers.values],
ownVotesAndAnswers: [...ownVotesAndAnswers.values],
);

Expand All @@ -2248,13 +2346,13 @@ class ChannelClientState {

final oldPoll = pollMessage.poll;

final answers = oldPoll?.answers ?? eventPoll.answers;
final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers;
final ownVotesAndAnswers = <String, PollVote>{
for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote,
}..remove(eventPollVote.id);

final poll = eventPoll.copyWith(
answers: answers,
latestAnswers: latestAnswers,
ownVotesAndAnswers: [...ownVotesAndAnswers.values],
);

Expand All @@ -2273,7 +2371,7 @@ class ChannelClientState {

final oldPoll = pollMessage.poll;

final answers = oldPoll?.answers ?? eventPoll.answers;
final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers;
final currentUserId = _channel.client.state.currentUser?.id;
final ownVotesAndAnswers = <String, PollVote>{
for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote,
Expand All @@ -2282,7 +2380,7 @@ class ChannelClientState {
};

final poll = eventPoll.copyWith(
answers: answers,
latestAnswers: latestAnswers,
ownVotesAndAnswers: [...ownVotesAndAnswers.values],
);

Expand Down Expand Up @@ -2338,6 +2436,8 @@ class ChannelClientState {
threads[event.message?.parentId]
?.firstWhereOrNull((e) => e.id == event.message?.id);
final message = event.message!.copyWith(
poll: oldMessage?.poll,
pollId: oldMessage?.pollId,
ownReactions: oldMessage?.ownReactions,
);
updateMessage(message);
Expand Down
42 changes: 26 additions & 16 deletions packages/stream_chat/lib/src/core/models/poll.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ class Poll extends Equatable {
this.enforceUniqueVote = true,
this.maxVotesAllowed,
this.allowAnswers = false,
this.answers = const [],
this.latestAnswers = const [],
this.answersCount = 0,
this.allowUserSuggestedOptions = false,
this.isClosed = false,
DateTime? createdAt,
DateTime? updatedAt,
this.voteCountsByOption = const {},
this.voteCount = 0,
this.votesByOption = const {},
this.latestVotesByOption = const {},
this.createdById,
this.createdBy,
this.ownVotesAndAnswers = const [],
Expand Down Expand Up @@ -109,20 +109,18 @@ class Poll extends Equatable {
final Map<String, int> voteCountsByOption;

/// Map of latest votes by option.
@JsonKey(name: 'latest_votes_by_option', includeToJson: false)
final Map<String, List<PollVote>> votesByOption;
@JsonKey(includeToJson: false)
final Map<String, List<PollVote>> latestVotesByOption;

/// List of votes received by the poll.
///
/// Note: This does not include the answers provided by the users,
/// see [answers] for that.
List<PollVote> get votes => [
...votesByOption.values.flattened.where((it) => !it.isAnswer),
];
/// see [latestAnswers] for that.
late final latestVotes = [...latestVotesByOption.values.flattened];

/// List of latest answers received by the poll.
@JsonKey(name: 'latest_answers', includeToJson: false)
final List<PollVote> answers;
@JsonKey(includeToJson: false)
final List<PollVote> latestAnswers;

/// List of votes casted by the current user.
///
Expand All @@ -134,6 +132,18 @@ class Poll extends Equatable {
@JsonKey(includeToJson: false)
final int voteCount;

/// List of votes casted by the current user.
///
/// Note: This does not include the answers provided by the user,
/// see [ownAnswers] for that.
late final ownVotes = [...ownVotesAndAnswers.where((it) => !it.isAnswer)];

/// List of answers provided by the current user.
///
/// Note: This does not include the votes casted by the user,
/// see [ownVotes] for that.
late final ownAnswers = [...ownVotesAndAnswers.where((it) => it.isAnswer)];

/// The id of the user who created the poll.
@JsonKey(includeToJson: false)
final String? createdById;
Expand Down Expand Up @@ -173,8 +183,8 @@ class Poll extends Equatable {
List<PollVote>? ownVotesAndAnswers,
int? voteCount,
int? answersCount,
Map<String, List<PollVote>>? votesByOption,
List<PollVote>? answers,
Map<String, List<PollVote>>? latestVotesByOption,
List<PollVote>? latestAnswers,
String? createdById,
User? createdBy,
DateTime? createdAt,
Expand All @@ -199,8 +209,8 @@ class Poll extends Equatable {
ownVotesAndAnswers: ownVotesAndAnswers ?? this.ownVotesAndAnswers,
voteCount: voteCount ?? this.voteCount,
answersCount: answersCount ?? this.answersCount,
votesByOption: votesByOption ?? this.votesByOption,
answers: answers ?? this.answers,
latestVotesByOption: latestVotesByOption ?? this.latestVotesByOption,
latestAnswers: latestAnswers ?? this.latestAnswers,
createdById: createdById ?? this.createdById,
createdBy: createdBy ?? this.createdBy,
createdAt: createdAt ?? this.createdAt,
Expand Down Expand Up @@ -251,8 +261,8 @@ class Poll extends Equatable {
ownVotesAndAnswers,
voteCount,
answersCount,
votesByOption,
answers,
latestVotesByOption,
latestAnswers,
createdById,
createdBy,
createdAt,
Expand Down
Loading

0 comments on commit 337074e

Please sign in to comment.