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
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,17 @@ Widget buildStreamMessageContentPlayground(BuildContext context) {
final textTheme = context.streamTextTheme;
final palette = colorScheme.avatarPalette;

final emojiCount = StreamMessageText.emojiOnlyCount(text);
final isEmojiOnly = emojiCount != null && emojiCount <= 3;
final emojiCount = StreamMessageText.emojiCount(text);
final isJumbomoji = emojiCount != null && emojiCount <= 3;

final messageText = StreamMessageText(text);
final Widget messageWidget = isEmojiOnly ? messageText : StreamMessageBubble(child: messageText);
final Widget messageWidget = isJumbomoji ? messageText : StreamMessageBubble(child: messageText);

final replies = showReplies
? StreamMessageReplies(
label: const Text('3 replies'),
avatars: _sampleAvatars(3, palette),
showConnector: !isEmojiOnly,
showConnector: !isJumbomoji,
)
: null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ class _ConversationMessage extends StatelessWidget {
data: layout,
child: Builder(
builder: (context) {
final emojiCount = StreamMessageText.emojiOnlyCount(message.text);
final emojiCount = StreamMessageText.emojiCount(message.text);
final hideBubble = emojiCount != null && emojiCount <= 3;

Widget text = StreamMessageText(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import 'package:flutter/widgets.dart';
/// ```dart
/// StreamMessageItemTheme(
/// data: StreamMessageItemThemeData(
/// leadingVisibility: StreamMessageLayoutProperty.resolveWith(
/// avatarVisibility: StreamMessageLayoutProperty.resolveWith(
/// (p) => switch (p.stackPosition) {
/// StreamMessageStackPosition.bottom ||
/// StreamMessageStackPosition.single => StreamVisibility.visible,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class StreamEmojiChipItem<T> {
/// For reaction filtering, this is typically a reaction type identifier.
final T value;

/// The emoji display model to render inside the chip.
/// The content model describing what to render inside the chip.
///
/// Typically a [StreamUnicodeEmoji] for Unicode reactions or a
/// [StreamImageEmoji] for custom server emoji.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,15 @@ class _StreamMessageAttachmentDefaults extends StreamMessageAttachmentStyle {
@override
StreamMessageLayoutProperty<EdgeInsetsGeometry> get padding => .resolveWith(
(layout) => switch (layout.contentKind) {
.standard || .emojiOnly => .symmetric(horizontal: _spacing.xs),
.standard || .jumbomoji => .symmetric(horizontal: _spacing.xs),
.singleAttachment => .zero,
},
);

@override
StreamMessageLayoutProperty<OutlinedBorder> get shape => .resolveWith(
(layout) => switch (layout.contentKind) {
.standard || .emojiOnly => RoundedSuperellipseBorder(borderRadius: .all(_radius.lg)),
.standard || .jumbomoji => RoundedSuperellipseBorder(borderRadius: .all(_radius.lg)),
.singleAttachment => LinearBorder.none,
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class _StreamMessageBubbleDefaults extends StreamMessageBubbleStyle {
@override
StreamMessageLayoutProperty<Color> get backgroundColor => .resolveWith(
(layout) => switch ((layout.alignment, layout.contentKind)) {
(_, .emojiOnly) => StreamColors.transparent,
(_, .jumbomoji) => StreamColors.transparent,
(.start, _) => _colorScheme.backgroundSurface,
(.end, _) => _colorScheme.brand.shade100,
},
Expand Down Expand Up @@ -175,7 +175,7 @@ class _StreamMessageBubbleDefaults extends StreamMessageBubbleStyle {
@override
StreamMessageLayoutBorderSide get side => .resolveWith(
(layout) => switch ((layout.alignment, layout.contentKind)) {
(_, .emojiOnly) => BorderSide.none,
(_, .jumbomoji) => BorderSide.none,
(.start, _) => BorderSide(color: _colorScheme.borderSubtle),
(.end, _) => BorderSide(color: _colorScheme.brand.shade100),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,20 +114,20 @@ class StreamMessageText extends StatelessWidget {
/// emojis (ignoring whitespace), or `null` if the text is empty or contains
/// any non-emoji characters.
///
/// Useful for determining emoji-specific rendering such as larger font sizes
/// Useful for determining jumbomoji rendering such as larger font sizes
/// or hiding the message bubble.
///
/// ```dart
/// StreamMessageText.emojiOnlyCount('🚀') // 1
/// StreamMessageText.emojiOnlyCount('👍🔥') // 2
/// StreamMessageText.emojiOnlyCount('❤️🎉😍') // 3
/// StreamMessageText.emojiOnlyCount('🎉🎉🎉🎉') // 4
/// StreamMessageText.emojiOnlyCount('Hello 👋') // null (mixed)
/// StreamMessageText.emojiOnlyCount('👨‍👩‍👧') // 1 (ZWJ family)
/// StreamMessageText.emojiOnlyCount('🇺🇸') // 1 (flag)
/// StreamMessageText.emojiOnlyCount('👍🏽') // 1 (skin tone)
/// StreamMessageText.emojiCount('🚀') // 1
/// StreamMessageText.emojiCount('👍🔥') // 2
/// StreamMessageText.emojiCount('❤️🎉😍') // 3
/// StreamMessageText.emojiCount('🎉🎉🎉🎉') // 4
/// StreamMessageText.emojiCount('Hello 👋') // null (mixed)
/// StreamMessageText.emojiCount('👨‍👩‍👧') // 1 (ZWJ family)
/// StreamMessageText.emojiCount('🇺🇸') // 1 (flag)
/// StreamMessageText.emojiCount('👍🏽') // 1 (skin tone)
/// ```
static int? emojiOnlyCount(String? text) {
static int? emojiCount(String? text) {
final trimmed = text?.trim();
if (trimmed == null || trimmed.isEmpty) return null;

Expand Down Expand Up @@ -260,8 +260,8 @@ class DefaultStreamMessageText extends StatelessWidget {
final effectiveMentionStyle = resolve((s) => s?.mentionStyle).copyWith(color: effectiveMentionColor);

final contentType = layout.contentKind;
final emojiCount = StreamMessageText.emojiOnlyCount(props.text);
if (emojiCount case final count? when contentType == .emojiOnly) {
final emojiCount = StreamMessageText.emojiCount(props.text);
if (emojiCount case final count? when contentType == .jumbomoji) {
final emojiStyle = switch (count) {
1 => resolve((s) => s?.singleEmojiStyle),
2 => resolve((s) => s?.doubleEmojiStyle),
Expand Down Expand Up @@ -394,7 +394,7 @@ class _StreamMessageTextDefaults extends StreamMessageTextStyle {
@override
StreamMessageLayoutProperty<EdgeInsetsGeometry> get padding => .resolveWith(
(layout) => switch (layout.contentKind) {
.emojiOnly => EdgeInsets.zero,
.jumbomoji => EdgeInsets.zero,
_ => .symmetric(horizontal: _context.streamSpacing.sm),
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ enum StreamMessageContentKind {
/// A single attachment without text or a quoted reply.
singleAttachment,

/// An emoji-only message (typically 1-3 emojis) without other content.
emojiOnly,
/// A jumbomoji message (typically 1-3 emojis) without other content.
jumbomoji,
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ enum _StreamMessageLayoutAspect {
// The list kind (channel / thread).
listKind,

// The content kind (standard / singleAttachment / emojiOnly).
// The content kind (standard / singleAttachment / jumbomoji).
contentKind,
}

Expand Down Expand Up @@ -254,7 +254,7 @@ class StreamMessageLayout extends InheritedModel<_StreamMessageLayoutAspect> {
/// [stackPosition] (single, top, middle, bottom) — with environmental
/// context — [channelKind] (direct vs group), [listKind] (channel vs
/// thread) — and content classification — [contentKind] (standard,
/// singleAttachment, emojiOnly) — into a single value that
/// singleAttachment, jumbomoji) — into a single value that
/// [StreamMessageLayoutProperty] resolvers use to compute
/// layout-dependent styling.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ class StreamReactionsItem {
this.count,
});

/// The display model representing this reaction's emoji.
/// The content model describing what to render.
///
/// Typically a [StreamUnicodeEmoji] (e.g. `StreamUnicodeEmoji('👍')`)
/// or a [StreamImageEmoji] for custom server emoji.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ class StreamMessageItemTheme extends InheritedTheme {
/// Properties are organized in two groups:
///
/// 1. **Item-level** — visual and layout properties for the message item
/// itself ([backgroundColor], [leadingVisibility], [headerVisibility],
/// [footerVisibility], [padding], [spacing], [avatarSize]).
/// itself ([backgroundColor], [avatarVisibility], [annotationVisibility],
/// [metadataVisibility], [repliesVisibility], [padding], [spacing],
/// [avatarSize]).
/// 2. **Sub-component styles** — grouped style overrides for each child
/// component ([text], [bubble], [annotation], [metadata], [replies]).
///
Expand All @@ -91,9 +92,10 @@ class StreamMessageItemThemeData with _$StreamMessageItemThemeData {
/// Creates message item theme data with optional overrides.
const StreamMessageItemThemeData({
this.backgroundColor,
this.leadingVisibility,
this.headerVisibility,
this.footerVisibility,
this.avatarVisibility,
this.annotationVisibility,
this.metadataVisibility,
this.repliesVisibility,
this.padding,
this.spacing,
this.avatarSize,
Expand All @@ -111,32 +113,41 @@ class StreamMessageItemThemeData with _$StreamMessageItemThemeData {
/// When null, the message item has no background.
final Color? backgroundColor;

/// Controls the visibility of the leading widget based on placement.
/// Controls the visibility of the avatar based on placement.
///
/// This resolves a [StreamVisibility] value from the current
/// [StreamMessageLayoutData], allowing visibility to vary by stack
/// position (e.g. only show the avatar on the bottom message of a stack).
///
/// When null, the leading widget defaults to [StreamVisibility.visible].
final StreamMessageLayoutVisibility? leadingVisibility;
/// When null, the avatar defaults to [StreamVisibility.visible].
final StreamMessageLayoutVisibility? avatarVisibility;

/// Controls the visibility of the header (annotations) based on placement.
/// Controls the visibility of the annotation (header) based on placement.
///
/// This resolves a [StreamVisibility] value from the current
/// [StreamMessageLayoutData], allowing visibility to vary by placement.
///
/// When null, the header defaults to [StreamVisibility.visible].
final StreamMessageLayoutVisibility? headerVisibility;
/// When null, the annotation defaults to [StreamVisibility.visible].
final StreamMessageLayoutVisibility? annotationVisibility;

/// Controls the visibility of the footer (metadata) based on placement.
/// Controls the visibility of the metadata (footer) based on placement.
///
/// This resolves a [StreamVisibility] value from the current
/// [StreamMessageLayoutData], allowing visibility to vary by stack
/// position (e.g. only show metadata on the bottom message of a stack).
///
/// When null, the footer defaults to visible for single/bottom messages
/// When null, the metadata defaults to visible for single/bottom messages
/// and gone for top/middle messages.
final StreamMessageLayoutVisibility? footerVisibility;
final StreamMessageLayoutVisibility? metadataVisibility;

/// Controls the visibility of the replies indicator based on placement.
///
/// This resolves a [StreamVisibility] value from the current
/// [StreamMessageLayoutData], allowing visibility to vary by list kind
/// (e.g. hide replies when viewing a thread).
///
/// When null, the replies indicator defaults to [StreamVisibility.visible].
final StreamMessageLayoutVisibility? repliesVisibility;

/// Outer padding around the entire message item.
final EdgeInsetsGeometry? padding;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading