Skip to content

Commit 31b2809

Browse files
authored
refactor!(ui): add support for customising reaction picker (#2248)
Co-authored-by: xsahil03x <25670178+xsahil03x@users.noreply.github.com>
1 parent 08600ee commit 31b2809

File tree

60 files changed

+842
-641
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+842
-641
lines changed

packages/stream_chat_flutter/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ For more details, please refer to the [migration guide](Unpublished).
4343
- Updated `share_plus` dependency to `^11.0.0`.
4444
- Updated `desktop_drop` dependency to `">=0.5.0 <0.7.0"`.
4545

46+
✅ Added
47+
48+
- Added `desktopOrWeb` parameter to `PlatformWidgetBuilder` to allow specifying a single builder for both desktop and web platforms.
49+
- Added `reactionPickerBuilder` to `StreamMessageActionsModal`, `StreamMessageReactionsModal`, and `StreamMessageWidget` to enable custom reaction picker widgets.
50+
- Added `StreamReactionIcon.defaultReactions` providing a predefined list of common reaction icons.
51+
4652
## 9.9.0
4753

4854
✅ Added

packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_builder.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class PlatformWidgetBuilder extends StatelessWidget {
2525
this.mobile,
2626
this.desktop,
2727
this.web,
28+
this.desktopOrWeb,
2829
});
2930

3031
/// The child widget.
@@ -39,12 +40,21 @@ class PlatformWidgetBuilder extends StatelessWidget {
3940
/// The widget to build for web platforms.
4041
final PlatformTargetBuilder? web;
4142

43+
/// The widget to build for desktop or web platforms.
44+
///
45+
/// Note: The widget will prefer the [desktop] or [web] widget if a
46+
/// combination of desktop/web and desktopOrWeb is provided.
47+
final PlatformTargetBuilder? desktopOrWeb;
48+
4249
@override
4350
Widget build(BuildContext context) {
51+
final webWidget = web ?? desktopOrWeb;
52+
final desktopWidget = desktop ?? desktopOrWeb;
53+
4454
return PlatformWidget(
45-
desktop: (context) => desktop?.call(context, child),
55+
desktop: (context) => desktopWidget?.call(context, child),
4656
mobile: (context) => mobile?.call(context, child),
47-
web: (context) => web?.call(context, child),
57+
web: (context) => webWidget?.call(context, child),
4858
);
4959
}
5060
}

packages/stream_chat_flutter/lib/src/message_action/message_action_type.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ final class MarkUnread extends MessageAction {
7272
/// Action to mute a user to prevent notifications from their messages
7373
final class MuteUser extends MessageAction {
7474
/// Create a new mute user action
75-
const MuteUser({required super.message, required this.user});
75+
const MuteUser({
76+
required super.message,
77+
required this.user,
78+
});
7679

7780
/// The user to be muted.
7881
final User user;
@@ -81,7 +84,10 @@ final class MuteUser extends MessageAction {
8184
/// Action to unmute a user to receive notifications from their messages
8285
final class UnmuteUser extends MessageAction {
8386
/// Create a new unmute user action
84-
const UnmuteUser({required super.message, required this.user});
87+
const UnmuteUser({
88+
required super.message,
89+
required this.user,
90+
});
8591

8692
/// The user to be unmuted.
8793
final User user;

packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,31 +1474,41 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
14741474
}
14751475

14761476
void _getOnThreadTap() {
1477-
if (widget.onThreadTap != null) {
1478-
_onThreadTap = (Message message) {
1479-
final threadBuilder = widget.threadBuilder;
1480-
widget.onThreadTap!(
1481-
message,
1482-
threadBuilder != null ? threadBuilder(context, message) : null,
1483-
);
1484-
};
1485-
} else if (widget.threadBuilder != null) {
1486-
_onThreadTap = (Message message) {
1487-
Navigator.of(context).push(
1488-
MaterialPageRoute(
1489-
builder: (_) => BetterStreamBuilder<Message>(
1490-
stream: streamChannel!.channel.state!.messagesStream.map(
1491-
(messages) => messages.firstWhere((m) => m.id == message.id),
1492-
),
1493-
initialData: message,
1494-
builder: (_, data) => StreamChannel(
1495-
channel: streamChannel!.channel,
1496-
child: widget.threadBuilder!(context, data),
1477+
_onThreadTap = switch ((widget.onThreadTap, widget.threadBuilder)) {
1478+
// Case 1: widget.onThreadTap is provided.
1479+
// The created callback will use widget.onThreadTap, passing the result
1480+
// of widget.threadBuilder (if provided) as the second argument.
1481+
(final onThreadTap?, final threadBuilder) => (Message message) {
1482+
onThreadTap(
1483+
message,
1484+
threadBuilder?.call(context, message),
1485+
);
1486+
},
1487+
// Case 2: widget.onThreadTap is null, but widget.threadBuilder is provided.
1488+
// The created callback will perform the default navigation action,
1489+
// using widget.threadBuilder to build the thread page.
1490+
(null, final threadBuilder?) => (Message message) {
1491+
final threadPage = StreamChatConfiguration(
1492+
// This is needed to provide the nearest reaction icons to the
1493+
// StreamMessageReactionsModal.
1494+
data: StreamChatConfiguration.of(context),
1495+
child: StreamChannel(
1496+
channel: streamChannel!.channel,
1497+
child: BetterStreamBuilder<Message>(
1498+
initialData: message,
1499+
stream: streamChannel!.channel.state?.messagesStream.map(
1500+
(it) => it.firstWhere((m) => m.id == message.id),
1501+
),
1502+
builder: (_, data) => threadBuilder(context, data),
14971503
),
14981504
),
1499-
),
1500-
);
1501-
};
1502-
}
1505+
);
1506+
1507+
Navigator.of(context).push(
1508+
MaterialPageRoute(builder: (_) => threadPage),
1509+
);
1510+
},
1511+
_ => null,
1512+
};
15031513
}
15041514
}

packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import 'package:flutter/material.dart';
2-
import 'package:stream_chat_flutter/src/message_widget/reactions/reactions_align.dart';
2+
import 'package:stream_chat_flutter/src/reactions/reactions_align.dart';
33

44
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
55

@@ -21,6 +21,7 @@ class StreamMessageActionsModal extends StatelessWidget {
2121
required this.messageWidget,
2222
this.reverse = false,
2323
this.showReactionPicker = false,
24+
this.reactionPickerBuilder = StreamReactionPicker.builder,
2425
this.onActionTap,
2526
});
2627

@@ -58,6 +59,9 @@ class StreamMessageActionsModal extends StatelessWidget {
5859
/// Defaults to `false`.
5960
final bool showReactionPicker;
6061

62+
/// {@macro reactionPickerBuilder}
63+
final ReactionPickerBuilder reactionPickerBuilder;
64+
6165
/// Callback triggered when a message action is tapped.
6266
///
6367
/// Provides the tapped [MessageAction] object to the callback.
@@ -91,16 +95,9 @@ class StreamMessageActionsModal extends StatelessWidget {
9195
},
9296
};
9397

94-
final config = StreamChatConfiguration.of(context);
95-
final reactionIcons = config.reactionIcons;
96-
9798
return Align(
9899
alignment: alignment,
99-
child: StreamReactionPicker(
100-
message: message,
101-
reactionIcons: reactionIcons,
102-
onReactionPicked: onReactionPicked,
103-
),
100+
child: reactionPickerBuilder(context, message, onReactionPicked),
104101
);
105102
},
106103
),

packages/stream_chat_flutter/lib/src/message_modal/message_modal.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class StreamMessageModal extends StatelessWidget {
141141
crossAxisAlignment: alignment.toColumnCrossAxisAlignment(),
142142
children: [
143143
if (headerBuilder case final builder?) builder(context),
144-
contentBuilder(context),
144+
Flexible(child: contentBuilder(context)),
145145
],
146146
),
147147
),

packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import 'package:flutter/material.dart';
2-
import 'package:stream_chat_flutter/src/message_widget/reactions/reactions_align.dart';
3-
import 'package:stream_chat_flutter/src/message_widget/reactions/reactions_card.dart';
42
import 'package:stream_chat_flutter/src/misc/empty_widget.dart';
3+
import 'package:stream_chat_flutter/src/reactions/reactions_align.dart';
54
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
65

76
/// {@template streamMessageReactionsModal}
@@ -24,6 +23,7 @@ class StreamMessageReactionsModal extends StatelessWidget {
2423
required this.messageWidget,
2524
this.reverse = false,
2625
this.showReactionPicker = true,
26+
this.reactionPickerBuilder = StreamReactionPicker.builder,
2727
this.onReactionPicked,
2828
this.onUserAvatarTap,
2929
});
@@ -49,6 +49,9 @@ class StreamMessageReactionsModal extends StatelessWidget {
4949
/// When `false`, the reaction picker is hidden.
5050
final bool showReactionPicker;
5151

52+
/// {@macro reactionPickerBuilder}
53+
final ReactionPickerBuilder reactionPickerBuilder;
54+
5255
/// Callback triggered when a user adds or toggles a reaction.
5356
///
5457
/// Provides the selected [Reaction] object to the callback.
@@ -87,16 +90,9 @@ class StreamMessageReactionsModal extends StatelessWidget {
8790
},
8891
};
8992

90-
final config = StreamChatConfiguration.of(context);
91-
final reactionIcons = config.reactionIcons;
92-
9393
return Align(
9494
alignment: alignment,
95-
child: StreamReactionPicker(
96-
message: message,
97-
reactionIcons: reactionIcons,
98-
onReactionPicked: onReactionPicked,
99-
),
95+
child: reactionPickerBuilder(context, message, onReactionPicked),
10096
);
10197
},
10298
),
@@ -121,19 +117,14 @@ class StreamMessageReactionsModal extends StatelessWidget {
121117
);
122118
},
123119
contentBuilder: (context) {
124-
final currentUser = StreamChat.of(context).currentUser;
125-
if (currentUser == null) return const Empty();
126-
127120
final reactions = message.latestReactions;
128121
final hasReactions = reactions != null && reactions.isNotEmpty;
129122
if (!hasReactions) return const Empty();
130123

131124
return FractionallySizedBox(
132125
widthFactor: 0.78,
133-
child: ReactionsCard(
126+
child: StreamUserReactions(
134127
message: message,
135-
currentUser: currentUser,
136-
messageTheme: messageTheme,
137128
onUserAvatarTap: onUserAvatarTap,
138129
),
139130
);

0 commit comments

Comments
 (0)