Skip to content

Commit 74bb460

Browse files
authored
feat(ui)!: refactor poll message into attachment (#2296)
1 parent 8c8dbd2 commit 74bb460

13 files changed

+120
-65
lines changed

packages/stream_chat_flutter/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## Upcoming Beta
2+
3+
🛑️ Breaking
4+
5+
- `PollMessage` widget has been removed and replaced with `PollAttachment` for better integration
6+
with the attachment system. Polls can now be customized through `PollAttachmentBuilder` or by
7+
creating custom poll attachment widgets via the attachment builder system.
8+
19
## 10.0.0-beta.2
210

311
- Included the changes from version [`9.13.0`](https://pub.dev/packages/stream_chat_flutter/changelog).

packages/stream_chat_flutter/lib/src/attachment/attachment.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@ export 'file_attachment.dart';
33
export 'gallery_attachment.dart';
44
export 'giphy_attachment.dart';
55
export 'image_attachment.dart';
6+
export 'poll_attachment.dart';
67
export 'url_attachment.dart';
78
export 'video_attachment.dart';
9+
export 'voice_recording_attachment.dart';
10+
export 'voice_recording_attachment_playlist.dart';

packages/stream_chat_flutter/lib/src/attachment/attachment_widget_catalog.dart

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,6 @@ class AttachmentWidgetCatalog {
3737
Widget build(BuildContext context, Message message) {
3838
assert(!message.isDeleted, 'Cannot build attachment for deleted message');
3939

40-
assert(
41-
message.attachments.isNotEmpty,
42-
'Cannot build attachment for message without attachments',
43-
);
44-
4540
// The list of attachments to build the widget for.
4641
final attachments = message.attachments.grouped;
4742
for (final builder in builders) {

packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ part 'url_attachment_builder.dart';
1212
part 'video_attachment_builder.dart';
1313
part 'voice_recording_attachment_playlist_builder.dart';
1414
part 'voice_recording_attachment_builder/voice_recording_attachment_builder.dart';
15+
part 'poll_attachment_builder.dart';
1516

1617
/// {@template streamAttachmentWidgetTapCallback}
1718
/// Signature for a function that's called when the user taps on an attachment.
@@ -43,6 +44,8 @@ abstract class StreamAttachmentWidgetBuilder {
4344
/// * [FileAttachmentBuilder]
4445
/// * [ImageAttachmentBuilder]
4546
/// * [VideoAttachmentBuilder]
47+
/// * [VoiceRecordingAttachmentPlaylistBuilder]
48+
/// * [PollAttachmentBuilder]
4649
/// * [UrlAttachmentBuilder]
4750
/// * [FallbackAttachmentBuilder]
4851
///
@@ -72,7 +75,14 @@ abstract class StreamAttachmentWidgetBuilder {
7275
return [
7376
...?customAttachmentBuilders,
7477

75-
// Handles a mix of image, gif, video, url and file attachments.
78+
// Handles poll attachments.
79+
PollAttachmentBuilder(
80+
shape: shape,
81+
padding: padding,
82+
),
83+
84+
// Handles a mix of image, gif, video, url, file and voice recording
85+
// attachments.
7686
MixedAttachmentBuilder(
7787
padding: padding,
7888
onAttachmentTap: onAttachmentTap,
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
part of 'attachment_widget_builder.dart';
2+
3+
const _kDefaultPollMessageConstraints = BoxConstraints(
4+
maxWidth: 270,
5+
);
6+
7+
/// {@template pollAttachmentBuilder}
8+
/// A widget builder for Poll attachment type.
9+
///
10+
/// This builder is used when a message contains a poll.
11+
/// {@endtemplate}
12+
class PollAttachmentBuilder extends StreamAttachmentWidgetBuilder {
13+
/// {@macro urlAttachmentBuilder}
14+
const PollAttachmentBuilder({
15+
this.shape,
16+
this.padding = const EdgeInsets.all(8),
17+
this.constraints = _kDefaultPollMessageConstraints,
18+
});
19+
20+
/// The shape of the poll attachment.
21+
final ShapeBorder? shape;
22+
23+
/// The constraints to apply to the poll attachment widget.
24+
final BoxConstraints constraints;
25+
26+
/// The padding to apply to the poll attachment widget.
27+
final EdgeInsetsGeometry padding;
28+
29+
@override
30+
bool canHandle(
31+
Message message,
32+
Map<String, List<Attachment>> attachments,
33+
) {
34+
final poll = message.poll;
35+
return poll != null;
36+
}
37+
38+
@override
39+
Widget build(
40+
BuildContext context,
41+
Message message,
42+
Map<String, List<Attachment>> attachments,
43+
) {
44+
assert(debugAssertCanHandle(message, attachments), '');
45+
46+
return Padding(
47+
padding: padding,
48+
child: PollAttachment(
49+
message: message,
50+
shape: shape,
51+
constraints: constraints,
52+
),
53+
);
54+
}
55+
}

packages/stream_chat_flutter/lib/src/message_widget/poll_message.dart renamed to packages/stream_chat_flutter/lib/src/attachment/poll_attachment.dart

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,41 @@ import 'package:stream_chat_flutter/src/poll/stream_poll_comments_dialog.dart';
88
import 'package:stream_chat_flutter/src/poll/stream_poll_options_dialog.dart';
99
import 'package:stream_chat_flutter/src/poll/stream_poll_results_dialog.dart';
1010
import 'package:stream_chat_flutter/src/stream_chat.dart';
11+
import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart';
1112
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
1213

1314
const _maxVisibleOptionCount = 10;
1415

15-
const _kDefaultPollMessageConstraints = BoxConstraints(
16-
maxWidth: 270,
17-
);
18-
1916
/// {@template pollMessage}
20-
/// A widget that displays a poll message.
21-
///
22-
/// Used in [MessageCard] to display a poll message.
17+
/// A widget that displays a message poll attachment in a [StreamMessageWidget].
2318
/// {@endtemplate}
24-
class PollMessage extends StatefulWidget {
19+
class PollAttachment extends StatefulWidget {
2520
/// {@macro pollMessage}
26-
const PollMessage({
21+
const PollAttachment({
2722
super.key,
2823
required this.message,
24+
this.shape,
25+
this.constraints = const BoxConstraints(),
2926
});
3027

3128
/// The message with the poll to display.
3229
final Message message;
3330

31+
/// The shape of the poll attachment.
32+
final ShapeBorder? shape;
33+
34+
/// The constraints to apply to the poll attachment widget.
35+
final BoxConstraints constraints;
36+
3437
@override
35-
State<PollMessage> createState() => _PollMessageState();
38+
State<PollAttachment> createState() => _PollAttachmentState();
3639
}
3740

38-
class _PollMessageState extends State<PollMessage> {
41+
class _PollAttachmentState extends State<PollAttachment> {
3942
late final _messageNotifier = ValueNotifier(widget.message);
4043

4144
@override
42-
void didUpdateWidget(covariant PollMessage oldWidget) {
45+
void didUpdateWidget(covariant PollAttachment oldWidget) {
4346
super.didUpdateWidget(oldWidget);
4447
if (oldWidget.message != widget.message) {
4548
// If the message changes, schedule an update for the next frame
@@ -57,6 +60,17 @@ class _PollMessageState extends State<PollMessage> {
5760

5861
@override
5962
Widget build(BuildContext context) {
63+
final theme = StreamChatTheme.of(context);
64+
65+
final shape = widget.shape ??
66+
RoundedRectangleBorder(
67+
side: BorderSide(
68+
color: theme.colorTheme.borders,
69+
strokeAlign: BorderSide.strokeAlignOutside,
70+
),
71+
borderRadius: BorderRadius.circular(14),
72+
);
73+
6074
return ValueListenableBuilder(
6175
valueListenable: _messageNotifier,
6276
builder: (context, message, child) {
@@ -96,8 +110,9 @@ class _PollMessageState extends State<PollMessage> {
96110
channel.createPollOption(poll, PollOption(text: optionText));
97111
}
98112

99-
return ConstrainedBox(
100-
constraints: _kDefaultPollMessageConstraints,
113+
return Container(
114+
constraints: widget.constraints,
115+
decoration: ShapeDecoration(shape: shape),
101116
child: StreamPollInteractor(
102117
poll: poll,
103118
currentUser: currentUser,

packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment_playlist.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'package:collection/collection.dart';
22
import 'package:flutter/material.dart';
3-
import 'package:stream_chat_flutter/src/attachment/voice_recording_attachment.dart';
43
import 'package:stream_chat_flutter/src/audio/audio_playlist_controller.dart';
54
import 'package:stream_chat_flutter/src/misc/empty_widget.dart';
65

packages/stream_chat_flutter/lib/src/message_widget/message_card.dart

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ class MessageCard extends StatefulWidget {
1717
required this.hasQuotedMessage,
1818
required this.hasUrlAttachments,
1919
required this.hasNonUrlAttachments,
20-
required this.hasPoll,
2120
required this.isOnlyEmoji,
2221
required this.isGiphy,
2322
required this.attachmentBuilders,
@@ -66,9 +65,6 @@ class MessageCard extends StatefulWidget {
6665
/// {@macro hasNonUrlAttachments}
6766
final bool hasNonUrlAttachments;
6867

69-
/// {@macro hasPoll}
70-
final bool hasPoll;
71-
7268
/// {@macro isOnlyEmoji}
7369
final bool isOnlyEmoji;
7470

@@ -128,10 +124,6 @@ class _MessageCardState extends State<MessageCard> {
128124
final attachmentsKey = GlobalKey();
129125
double? widthLimit;
130126

131-
bool get hasAttachments {
132-
return widget.hasUrlAttachments || widget.hasNonUrlAttachments;
133-
}
134-
135127
void _updateWidthLimit() {
136128
final attachmentContext = attachmentsKey.currentContext;
137129
final renderBox = attachmentContext?.findRenderObject() as RenderBox?;
@@ -150,11 +142,9 @@ class _MessageCardState extends State<MessageCard> {
150142
// If there is an attachment, we need to wait for the attachment to be
151143
// rendered to get the width of the attachment and set it as the width
152144
// limit of the message card.
153-
if (hasAttachments) {
154-
WidgetsBinding.instance.addPostFrameCallback((_) {
155-
_updateWidthLimit();
156-
});
157-
}
145+
WidgetsBinding.instance.addPostFrameCallback((_) {
146+
_updateWidthLimit();
147+
});
158148
}
159149

160150
@override
@@ -201,23 +191,17 @@ class _MessageCardState extends State<MessageCard> {
201191
hasNonUrlAttachments: widget.hasNonUrlAttachments,
202192
),
203193
),
204-
if (hasAttachments)
205-
ParseAttachments(
206-
key: attachmentsKey,
207-
message: widget.message,
208-
attachmentBuilders: widget.attachmentBuilders,
209-
attachmentPadding: widget.attachmentPadding,
210-
attachmentShape: widget.attachmentShape,
211-
onAttachmentTap: widget.onAttachmentTap,
212-
onShowMessage: widget.onShowMessage,
213-
onReplyTap: widget.onReplyTap,
214-
attachmentActionsModalBuilder:
215-
widget.attachmentActionsModalBuilder,
216-
),
217-
if (widget.hasPoll)
218-
PollMessage(
219-
message: widget.message,
220-
),
194+
ParseAttachments(
195+
key: attachmentsKey,
196+
message: widget.message,
197+
attachmentBuilders: widget.attachmentBuilders,
198+
attachmentPadding: widget.attachmentPadding,
199+
attachmentShape: widget.attachmentShape,
200+
onAttachmentTap: widget.onAttachmentTap,
201+
onShowMessage: widget.onShowMessage,
202+
onReplyTap: widget.onReplyTap,
203+
attachmentActionsModalBuilder: widget.attachmentActionsModalBuilder,
204+
),
221205
TextBubble(
222206
messageTheme: widget.messageTheme,
223207
message: widget.message,

packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -600,11 +600,6 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
600600
bool get hasNonUrlAttachments => widget.message.attachments
601601
.any((it) => it.type != AttachmentType.urlPreview);
602602

603-
/// {@template hasPoll}
604-
/// `true` if the [message] contains a poll.
605-
/// {@endtemplate}
606-
bool get hasPoll => widget.message.poll != null;
607-
608603
/// {@template hasUrlAttachments}
609604
/// `true` if any of the [message]'s attachments are a giphy with a
610605
/// [Attachment.titleLink].
@@ -719,7 +714,6 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
719714
reverse: widget.reverse,
720715
message: widget.message,
721716
hasNonUrlAttachments: hasNonUrlAttachments,
722-
hasPoll: hasPoll,
723717
hasQuotedMessage: hasQuotedMessage,
724718
textPadding: widget.textPadding,
725719
attachmentBuilders: widget.attachmentBuilders,

packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ class MessageWidgetContent extends StatelessWidget {
4242
required this.hasQuotedMessage,
4343
required this.hasUrlAttachments,
4444
required this.hasNonUrlAttachments,
45-
required this.hasPoll,
4645
required this.isOnlyEmoji,
4746
required this.isGiphy,
4847
required this.attachmentBuilders,
@@ -137,9 +136,6 @@ class MessageWidgetContent extends StatelessWidget {
137136
/// {@macro hasNonUrlAttachments}
138137
final bool hasNonUrlAttachments;
139138

140-
/// {@macro hasPoll}
141-
final bool hasPoll;
142-
143139
/// {@macro isOnlyEmoji}
144140
final bool isOnlyEmoji;
145141

@@ -363,7 +359,6 @@ class MessageWidgetContent extends StatelessWidget {
363359
hasQuotedMessage: hasQuotedMessage,
364360
hasUrlAttachments: hasUrlAttachments,
365361
hasNonUrlAttachments: hasNonUrlAttachments,
366-
hasPoll: hasPoll,
367362
isOnlyEmoji: isOnlyEmoji,
368363
isGiphy: isGiphy,
369364
attachmentBuilders: attachmentBuilders,

0 commit comments

Comments
 (0)