feat(ui): added support for MessageWidget.onReactionsHover.
Signed-off-by: Sahil Kumar <xdsahil@gmail.com>
This commit is contained in:
@@ -1,3 +1,11 @@
|
||||
## Upcoming
|
||||
|
||||
✅ Added
|
||||
|
||||
- Added support for overriding the `MessageWidget.onReactionsHover` callback.
|
||||
> **Note**
|
||||
> Used only in desktop devices (web and desktop).
|
||||
|
||||
## 6.9.0
|
||||
|
||||
🐞 Fixed
|
||||
@@ -11,7 +19,8 @@
|
||||
|
||||
- Added support for listening error events in AttachmentPickerBottomSheet.
|
||||
- Added support for overriding the `MessageWidget.onReactionTap` callback.
|
||||
- Added support for `StreamMessageInput.contentInsertionConfiguration` to specify the content insertion configuration.
|
||||
- Added support for `StreamMessageInput.contentInsertionConfiguration` to specify the content
|
||||
insertion configuration.
|
||||
[#1613](https://github.com/GetStream/stream-chat-flutter/issues/1613)
|
||||
|
||||
```dart
|
||||
|
||||
@@ -159,7 +159,6 @@
|
||||
C4CD72858CD59598795BB48E /* Pods-Runner.release.xcconfig */,
|
||||
2BCA7399119839DE435DACD6 /* Pods-Runner.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -203,7 +202,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1430;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
@@ -427,7 +426,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
@@ -554,7 +553,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -575,7 +574,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1430"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -56,6 +56,7 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
this.onMentionTap,
|
||||
this.onMessageTap,
|
||||
this.onReactionsTap,
|
||||
this.onReactionsHover,
|
||||
bool? showReactionPicker,
|
||||
@Deprecated('Use `showReactionPicker` instead')
|
||||
bool showReactionPickerIndicator = true,
|
||||
@@ -474,7 +475,8 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
|
||||
/// {@template showReactionPickerIndicator}
|
||||
/// Used in [StreamMessageReactionsModal] and [MessageActionsModal]
|
||||
/// {@endtemplate} @Deprecated('Use `showReactionPicker` instead')
|
||||
/// {@endtemplate}
|
||||
@Deprecated('Use `showReactionPicker` instead')
|
||||
bool get showReactionPickerIndicator => showReactionPicker;
|
||||
|
||||
/// {@template showReactionPickerTail}
|
||||
@@ -565,8 +567,16 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
final void Function(Message)? onMessageTap;
|
||||
|
||||
/// {@macro onReactionsTap}
|
||||
///
|
||||
/// Note: Only used in mobile devices (iOS and Android). Do not confuse this
|
||||
/// with the tap action on the reactions picker.
|
||||
final OnReactionsTap? onReactionsTap;
|
||||
|
||||
/// {@template onReactionsHover}
|
||||
///
|
||||
/// Note: Only used in desktop devices (web and desktop).
|
||||
final OnReactionsHover? onReactionsHover;
|
||||
|
||||
/// {@template customActions}
|
||||
/// List of custom actions shown on message long tap
|
||||
/// {@endtemplate}
|
||||
@@ -640,6 +650,7 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
bool? showInChannelIndicator,
|
||||
void Function(User)? onUserAvatarTap,
|
||||
void Function(String)? onLinkTap,
|
||||
bool? showReactionBrowser,
|
||||
bool? showReactionPicker,
|
||||
@Deprecated('Use `showReactionPicker` instead')
|
||||
bool? showReactionPickerIndicator,
|
||||
@@ -662,6 +673,7 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
OnQuotedMessageTap? onQuotedMessageTap,
|
||||
void Function(Message)? onMessageTap,
|
||||
OnReactionsTap? onReactionsTap,
|
||||
OnReactionsHover? onReactionsHover,
|
||||
List<StreamMessageAction>? customActions,
|
||||
void Function(Message message, Attachment attachment)? onAttachmentTap,
|
||||
Widget Function(BuildContext, User)? userAvatarBuilder,
|
||||
@@ -752,6 +764,7 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap,
|
||||
onMessageTap: onMessageTap ?? this.onMessageTap,
|
||||
onReactionsTap: onReactionsTap ?? this.onReactionsTap,
|
||||
onReactionsHover: onReactionsHover ?? this.onReactionsHover,
|
||||
customActions: customActions ?? this.customActions,
|
||||
onAttachmentTap: onAttachmentTap ?? this.onAttachmentTap,
|
||||
userAvatarBuilder: userAvatarBuilder ?? this.userAvatarBuilder,
|
||||
@@ -978,7 +991,6 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
|
||||
reverse: widget.reverse,
|
||||
message: widget.message,
|
||||
hasNonUrlAttachments: hasNonUrlAttachments,
|
||||
shouldShowReactions: shouldShowReactions,
|
||||
hasQuotedMessage: hasQuotedMessage,
|
||||
textPadding: widget.textPadding,
|
||||
attachmentBuilders: widget.attachmentBuilders,
|
||||
@@ -997,6 +1009,7 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
|
||||
? widget.onReactionsTap!(widget.message)
|
||||
: _showMessageReactionsModal(context);
|
||||
},
|
||||
onReactionsHover: widget.onReactionsHover,
|
||||
showUserAvatar: widget.showUserAvatar,
|
||||
streamChat: _streamChat,
|
||||
translateUserAvatar: widget.translateUserAvatar,
|
||||
@@ -1258,8 +1271,9 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
|
||||
translateUserAvatar: false,
|
||||
showSendingIndicator: false,
|
||||
padding: EdgeInsets.zero,
|
||||
// Show both the tail and indicator if the indicator is shown.
|
||||
showReactionPickerTail: widget.showReactionPickerIndicator,
|
||||
// Show both the tail if the picker is shown.
|
||||
showReactionPicker: widget.showReactionPicker,
|
||||
showReactionPickerTail: widget.showReactionPicker,
|
||||
showPinHighlight: false,
|
||||
showUserAvatar: widget.message.user!.id ==
|
||||
channel.client.state.currentUser!.id
|
||||
@@ -1281,7 +1295,6 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
|
||||
showResendMessage: shouldShowResendAction,
|
||||
showCopyMessage: shouldShowCopyAction,
|
||||
showEditMessage: shouldShowEditAction,
|
||||
showReactionPicker: widget.showReactionPickerIndicator,
|
||||
showReplyMessage: shouldShowReplyAction,
|
||||
showThreadReplyMessage: shouldShowThreadReplyAction,
|
||||
showFlagButton: widget.showFlagButton,
|
||||
|
||||
@@ -36,8 +36,8 @@ class MessageWidgetContent extends StatelessWidget {
|
||||
required this.avatarWidth,
|
||||
required this.showReactions,
|
||||
required this.onReactionsTap,
|
||||
required this.onReactionsHover,
|
||||
required this.messageTheme,
|
||||
required this.shouldShowReactions,
|
||||
required this.streamChatTheme,
|
||||
required this.isFailedState,
|
||||
required this.hasQuotedMessage,
|
||||
@@ -114,17 +114,15 @@ class MessageWidgetContent extends StatelessWidget {
|
||||
/// {@macro showReactions}
|
||||
final bool showReactions;
|
||||
|
||||
/// Callback called when the reactions icon is tapped.
|
||||
///
|
||||
/// Do not confuse this with the tap action on the reactions picker.
|
||||
/// {@macro onReactionsTap}
|
||||
final VoidCallback onReactionsTap;
|
||||
|
||||
/// {@macro onReactionsHover}
|
||||
final OnReactionsHover? onReactionsHover;
|
||||
|
||||
/// {@macro messageTheme}
|
||||
final StreamMessageThemeData messageTheme;
|
||||
|
||||
/// {@macro shouldShowReactions}
|
||||
final bool shouldShowReactions;
|
||||
|
||||
/// {@macro onUserAvatarTap}
|
||||
final void Function(User)? onUserAvatarTap;
|
||||
|
||||
@@ -295,7 +293,6 @@ class MessageWidgetContent extends StatelessWidget {
|
||||
messageTheme: messageTheme,
|
||||
ownId: streamChat.currentUser!.id,
|
||||
reverse: reverse,
|
||||
shouldShowReactions: shouldShowReactions,
|
||||
onTap: onReactionsTap,
|
||||
)
|
||||
: null,
|
||||
@@ -314,25 +311,15 @@ class MessageWidgetContent extends StatelessWidget {
|
||||
children: [
|
||||
Padding(
|
||||
padding: showReactions
|
||||
? EdgeInsets.only(
|
||||
top: message.reactionCounts
|
||||
?.isNotEmpty ==
|
||||
true
|
||||
? 18
|
||||
: 0,
|
||||
)
|
||||
? const EdgeInsets.only(top: 18)
|
||||
: EdgeInsets.zero,
|
||||
child: (message.isDeleted && !isFailedState)
|
||||
? Container(
|
||||
// ignore: lines_longer_than_80_chars
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal:
|
||||
// ignore: lines_longer_than_80_chars
|
||||
showUserAvatar ==
|
||||
// ignore: lines_longer_than_80_chars
|
||||
DisplayWidget.gone
|
||||
? 0
|
||||
: 4.0,
|
||||
horizontal: showUserAvatar ==
|
||||
DisplayWidget.gone
|
||||
? 0
|
||||
: 4.0,
|
||||
),
|
||||
child: StreamDeletedMessage(
|
||||
borderRadiusGeometry:
|
||||
@@ -405,7 +392,7 @@ class MessageWidgetContent extends StatelessWidget {
|
||||
SizedBox(width: avatarWidth + 4),
|
||||
],
|
||||
),
|
||||
if (isDesktopDeviceOrWeb && shouldShowReactions) ...[
|
||||
if (isDesktopDeviceOrWeb && showReactions) ...[
|
||||
Padding(
|
||||
padding: showUserAvatar != DisplayWidget.gone
|
||||
? EdgeInsets.only(
|
||||
@@ -416,7 +403,7 @@ class MessageWidgetContent extends StatelessWidget {
|
||||
child: DesktopReactionsBuilder(
|
||||
message: message,
|
||||
messageTheme: messageTheme,
|
||||
shouldShowReactions: shouldShowReactions,
|
||||
onHover: onReactionsHover,
|
||||
borderSide: borderSide,
|
||||
reverse: reverse,
|
||||
),
|
||||
|
||||
+27
-38
@@ -1,3 +1,5 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -5,8 +7,6 @@ import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:stream_chat_flutter/src/message_widget/reactions/reactions_card.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
/// {@template desktopReactionsBuilder}
|
||||
/// Builds a list of reactions to a message on desktop & web.
|
||||
///
|
||||
@@ -16,16 +16,13 @@ class DesktopReactionsBuilder extends StatefulWidget {
|
||||
/// {@macro desktopReactionsBuilder}
|
||||
const DesktopReactionsBuilder({
|
||||
super.key,
|
||||
required this.shouldShowReactions,
|
||||
required this.message,
|
||||
required this.messageTheme,
|
||||
this.onHover,
|
||||
this.borderSide,
|
||||
required this.reverse,
|
||||
});
|
||||
|
||||
/// Whether reactions should be shown.
|
||||
final bool shouldShowReactions;
|
||||
|
||||
/// The message to show reactions for.
|
||||
final Message message;
|
||||
|
||||
@@ -35,6 +32,9 @@ class DesktopReactionsBuilder extends StatefulWidget {
|
||||
/// reactions matches the design spec for messages.
|
||||
final StreamMessageThemeData messageTheme;
|
||||
|
||||
/// Callback to run when the mouse enters or exits the reactions.
|
||||
final OnReactionsHover? onHover;
|
||||
|
||||
/// {@macro borderSide}
|
||||
final BorderSide? borderSide;
|
||||
|
||||
@@ -48,12 +48,6 @@ class DesktopReactionsBuilder extends StatefulWidget {
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(
|
||||
DiagnosticsProperty<bool>(
|
||||
'shouldShowReactions',
|
||||
shouldShowReactions,
|
||||
),
|
||||
);
|
||||
properties.add(
|
||||
DiagnosticsProperty<Message>('message', message),
|
||||
);
|
||||
@@ -81,18 +75,15 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
|
||||
final streamChatTheme = StreamChatTheme.of(context);
|
||||
|
||||
final reactionsMap = <String, Reaction>{};
|
||||
var reactionsList = <Reaction>[];
|
||||
if (widget.shouldShowReactions) {
|
||||
widget.message.latestReactions?.forEach((element) {
|
||||
if (!reactionsMap.containsKey(element.type) ||
|
||||
element.user!.id == currentUser.id) {
|
||||
reactionsMap[element.type] = element;
|
||||
}
|
||||
});
|
||||
widget.message.latestReactions?.forEach((element) {
|
||||
if (!reactionsMap.containsKey(element.type) ||
|
||||
element.user!.id == currentUser.id) {
|
||||
reactionsMap[element.type] = element;
|
||||
}
|
||||
});
|
||||
|
||||
reactionsList = reactionsMap.values.toList()
|
||||
..sort((a, b) => a.user!.id == currentUser.id ? 1 : -1);
|
||||
}
|
||||
final reactionsList = reactionsMap.values.toList()
|
||||
..sort((a, b) => a.user!.id == currentUser.id ? 1 : -1);
|
||||
|
||||
return PortalTarget(
|
||||
visible: _showReactionsPopup,
|
||||
@@ -100,17 +91,11 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
|
||||
anchor: Aligned(
|
||||
target: widget.reverse ? Alignment.topRight : Alignment.topLeft,
|
||||
follower: widget.reverse ? Alignment.bottomRight : Alignment.bottomLeft,
|
||||
shiftToWithinBound: const AxisFlag(
|
||||
y: true,
|
||||
),
|
||||
shiftToWithinBound: const AxisFlag(y: true),
|
||||
),
|
||||
portalFollower: MouseRegion(
|
||||
onEnter: (event) async {
|
||||
setState(() => _showReactionsPopup = !_showReactionsPopup);
|
||||
},
|
||||
onExit: (event) {
|
||||
setState(() => _showReactionsPopup = !_showReactionsPopup);
|
||||
},
|
||||
onEnter: (_) => _onReactionsHover(true),
|
||||
onExit: (_) => _onReactionsHover(false),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 336,
|
||||
@@ -125,12 +110,8 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
|
||||
),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (event) async {
|
||||
setState(() => _showReactionsPopup = !_showReactionsPopup);
|
||||
},
|
||||
onExit: (event) {
|
||||
setState(() => _showReactionsPopup = !_showReactionsPopup);
|
||||
},
|
||||
onEnter: (_) => _onReactionsHover(true),
|
||||
onExit: (_) => _onReactionsHover(false),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 2,
|
||||
@@ -161,6 +142,14 @@ class _DesktopReactionsBuilderState extends State<DesktopReactionsBuilder> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onReactionsHover(bool isHovering) {
|
||||
if (widget.onHover != null) {
|
||||
return widget.onHover!(isHovering);
|
||||
}
|
||||
|
||||
setState(() => _showReactionsPopup = isHovering);
|
||||
}
|
||||
}
|
||||
|
||||
class _BottomReaction extends StatelessWidget {
|
||||
|
||||
+15
-26
@@ -13,7 +13,6 @@ class ReactionIndicator extends StatelessWidget {
|
||||
super.key,
|
||||
required this.ownId,
|
||||
required this.message,
|
||||
required this.shouldShowReactions,
|
||||
required this.onTap,
|
||||
required this.reverse,
|
||||
required this.messageTheme,
|
||||
@@ -25,9 +24,6 @@ class ReactionIndicator extends StatelessWidget {
|
||||
/// {@macro message}
|
||||
final Message message;
|
||||
|
||||
/// {@macro shouldShowReactions}
|
||||
final bool shouldShowReactions;
|
||||
|
||||
/// The callback to perform when the widget is tapped or clicked.
|
||||
final VoidCallback onTap;
|
||||
|
||||
@@ -50,34 +46,27 @@ class ReactionIndicator extends StatelessWidget {
|
||||
..sort((a, b) => a.user!.id == ownId ? 1 : -1);
|
||||
|
||||
return Transform(
|
||||
transform: Matrix4.translationValues(
|
||||
reverse ? 12 : -12,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
transform: Matrix4.translationValues(reverse ? 12 : -12, 0, 0),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 22 * 6.0,
|
||||
),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: shouldShowReactions
|
||||
? GestureDetector(
|
||||
onTap: onTap,
|
||||
child: StreamReactionBubble(
|
||||
key: ValueKey('${message.id}.reactions'),
|
||||
reverse: reverse,
|
||||
flipTail: reverse,
|
||||
backgroundColor: messageTheme.reactionsBackgroundColor ??
|
||||
Colors.transparent,
|
||||
borderColor:
|
||||
messageTheme.reactionsBorderColor ?? Colors.transparent,
|
||||
maskColor:
|
||||
messageTheme.reactionsMaskColor ?? Colors.transparent,
|
||||
reactions: reactionsList,
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: StreamReactionBubble(
|
||||
key: ValueKey('${message.id}.reactions'),
|
||||
reverse: reverse,
|
||||
flipTail: reverse,
|
||||
backgroundColor:
|
||||
messageTheme.reactionsBackgroundColor ?? Colors.transparent,
|
||||
borderColor:
|
||||
messageTheme.reactionsBorderColor ?? Colors.transparent,
|
||||
maskColor: messageTheme.reactionsMaskColor ?? Colors.transparent,
|
||||
reactions: reactionsList,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -235,6 +235,12 @@ typedef OnMessageTap = void Function(Message);
|
||||
/// {@endtemplate}
|
||||
typedef OnReactionsTap = void Function(Message);
|
||||
|
||||
/// {@template onReactionsHover}
|
||||
/// The action to perform when a message's reactions are hovered.
|
||||
/// {@endtemplate}
|
||||
// ignore: avoid_positional_boolean_parameters
|
||||
typedef OnReactionsHover = void Function(bool isHovering);
|
||||
|
||||
/// {@template messageSearchItemTapCallback}
|
||||
/// The action to perform when tapping or clicking on a user in a
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
|
||||
Reference in New Issue
Block a user