Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions packages/stream_chat/lib/src/core/models/reaction_group.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,26 @@
lastReactionAt,
];
}

/// A group of comparators for sorting [ReactionGroup]s.
final class ReactionSorting {
/// Sorts [ReactionGroup]s by the sum of their scores.
static int byScore(ReactionGroup a, ReactionGroup b) {
return a.sumScores.compareTo(b.sumScores);

Check warning on line 65 in packages/stream_chat/lib/src/core/models/reaction_group.dart

View check run for this annotation

Codecov / codecov/patch

packages/stream_chat/lib/src/core/models/reaction_group.dart#L64-L65

Added lines #L64 - L65 were not covered by tests
}

/// Sorts [ReactionGroup]s by the count of reactions.
static int byCount(ReactionGroup a, ReactionGroup b) {
return a.count.compareTo(b.count);

Check warning on line 70 in packages/stream_chat/lib/src/core/models/reaction_group.dart

View check run for this annotation

Codecov / codecov/patch

packages/stream_chat/lib/src/core/models/reaction_group.dart#L69-L70

Added lines #L69 - L70 were not covered by tests
}

/// Sorts [ReactionGroup]s by the date of their first reaction.
static int byFirstReactionAt(ReactionGroup a, ReactionGroup b) {
return a.firstReactionAt.compareTo(b.firstReactionAt);

Check warning on line 75 in packages/stream_chat/lib/src/core/models/reaction_group.dart

View check run for this annotation

Codecov / codecov/patch

packages/stream_chat/lib/src/core/models/reaction_group.dart#L74-L75

Added lines #L74 - L75 were not covered by tests
}

/// Sorts [ReactionGroup]s by the date of their last reaction.
static int byLastReactionAt(ReactionGroup a, ReactionGroup b) {
return a.lastReactionAt.compareTo(b.lastReactionAt);

Check warning on line 80 in packages/stream_chat/lib/src/core/models/reaction_group.dart

View check run for this annotation

Codecov / codecov/patch

packages/stream_chat/lib/src/core/models/reaction_group.dart#L79-L80

Added lines #L79 - L80 were not covered by tests
}
}
1 change: 1 addition & 0 deletions packages/stream_chat_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- `StreamReactionPicker` now requires reactions to be explicitly handled via `onReactionPicked`. *(Automatic handling is no longer supported.)*
- `StreamMessageAction` is now generic `(StreamMessageAction<T>)`, enhancing type safety. Individual onTap callbacks have been removed; actions are now handled centrally by widgets like `StreamMessageWidget.onCustomActionTap` or modals using action types.
- `StreamMessageReactionsModal` no longer requires the `messageTheme` parameter. The theme now automatically derives from the `reverse` property.
- `StreamMessageWidget` no longer requires the `showReactionTail` parameter. The reaction picker tail is now always shown when the reaction picker is visible.

For more details, please refer to the [migration guide](Unpublished).

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/reactions/reactions_align.dart';
import 'package:stream_chat_flutter/src/reactions/picker/reaction_picker_bubble_overlay.dart';

import 'package:stream_chat_flutter/stream_chat_flutter.dart';

Expand Down Expand Up @@ -71,56 +71,30 @@ class StreamMessageActionsModal extends StatelessWidget {
Widget build(BuildContext context) {
final theme = StreamChatTheme.of(context);

final Widget? reactionPicker = switch (showReactionPicker) {
false => null,
true => LayoutBuilder(
builder: (context, constraints) {
final orientation = MediaQuery.of(context).orientation;
final messageTheme = theme.getMessageTheme(reverse: reverse);
final messageFontSize = messageTheme.messageTextStyle?.fontSize;

final alignment = message.calculateReactionPickerAlignment(
constraints: constraints,
fontSize: messageFontSize,
orientation: orientation,
reverse: reverse,
);

final onReactionPicked = switch (onActionTap) {
null => null,
final onActionTap => (reaction) {
return onActionTap.call(
SelectReaction(message: message, reaction: reaction),
);
},
};

return Align(
alignment: alignment,
child: reactionPickerBuilder(context, message, onReactionPicked),
);
},
),
};

final alignment = switch (reverse) {
true => AlignmentDirectional.centerEnd,
false => AlignmentDirectional.centerStart,
};

final onReactionPicked = switch (onActionTap) {
null => null,
final onActionTap => (reaction) => onActionTap(
SelectReaction(message: message, reaction: reaction),
),
};

return StreamMessageModal(
spacing: 4,
alignment: alignment,
headerBuilder: (context) {
return Column(
spacing: 10,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: alignment.toColumnCrossAxisAlignment(),
children: <Widget?>[
reactionPicker,
IgnorePointer(child: messageWidget),
].nonNulls.toList(growable: false),
);
},
headerBuilder: (context) => ReactionPickerBubbleOverlay(
message: message,
reverse: reverse,
visible: showReactionPicker,
anchorOffset: const Offset(0, -8),
onReactionPicked: onReactionPicked,
reactionPickerBuilder: reactionPickerBuilder,
child: IgnorePointer(child: messageWidget),
),
contentBuilder: (context) {
final actions = Column(
mainAxisSize: MainAxisSize.min,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/misc/empty_widget.dart';
import 'package:stream_chat_flutter/src/reactions/reactions_align.dart';
import 'package:stream_chat_flutter/src/reactions/picker/reaction_picker_bubble_overlay.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';

/// {@template streamMessageReactionsModal}
Expand Down Expand Up @@ -64,58 +64,32 @@ class StreamMessageReactionsModal extends StatelessWidget {

@override
Widget build(BuildContext context) {
final theme = StreamChatTheme.of(context);
final messageTheme = theme.getMessageTheme(reverse: reverse);

final Widget? reactionPicker = switch (showReactionPicker) {
false => null,
true => LayoutBuilder(
builder: (context, constraints) {
final orientation = MediaQuery.of(context).orientation;
final messageFontSize = messageTheme.messageTextStyle?.fontSize;

final alignment = message.calculateReactionPickerAlignment(
constraints: constraints,
fontSize: messageFontSize,
orientation: orientation,
reverse: reverse,
);

final onReactionPicked = switch (this.onReactionPicked) {
null => null,
final onPicked => (reaction) {
return onPicked.call(
SelectReaction(message: message, reaction: reaction),
);
},
};

return Align(
alignment: alignment,
child: reactionPickerBuilder(context, message, onReactionPicked),
);
},
),
};

final alignment = switch (reverse) {
true => AlignmentDirectional.centerEnd,
false => AlignmentDirectional.centerStart,
};

final onReactionPicked = switch (this.onReactionPicked) {
null => null,
final onPicked => (reaction) {
return onPicked.call(
SelectReaction(message: message, reaction: reaction),
);
},
};

return StreamMessageModal(
spacing: 4,
alignment: alignment,
headerBuilder: (context) {
return Column(
spacing: 10,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: alignment.toColumnCrossAxisAlignment(),
children: <Widget?>[
reactionPicker,
IgnorePointer(child: messageWidget),
].nonNulls.toList(growable: false),
);
},
headerBuilder: (context) => ReactionPickerBubbleOverlay(
message: message,
reverse: reverse,
visible: showReactionPicker,
anchorOffset: const Offset(0, -8),
onReactionPicked: onReactionPicked,
reactionPickerBuilder: reactionPickerBuilder,
child: IgnorePointer(child: messageWidget),
),
contentBuilder: (context) {
final reactions = message.latestReactions;
final hasReactions = reactions != null && reactions.isNotEmpty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ class _MessageCardState extends State<MessageCard> {

return Container(
constraints: const BoxConstraints().copyWith(maxWidth: widthLimit),
margin: EdgeInsets.symmetric(
horizontal: (widget.isFailedState ? 12.0 : 0.0) +
(widget.showUserAvatar == DisplayWidget.gone ? 0 : 4.0),
margin: EdgeInsetsDirectional.only(
end: widget.reverse && widget.isFailedState ? 12.0 : 0.0,
start: !widget.reverse && widget.isFailedState ? 12.0 : 0.0,
),
clipBehavior: Clip.hardEdge,
decoration: ShapeDecoration(
Expand Down
Loading