Skip to content
Draft
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
1 change: 1 addition & 0 deletions packages/stream_core_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- Added `isFloating` to the default `StreamButton` constructor — the floating (elevated) appearance was previously reachable only through `StreamButton.icon`. Labelled buttons now get the same treatment: elevation for every type, plus a `backgroundElevation1` fill for `outline` and `ghost`.
- Added `StreamElevation` — the four elevation levels of the design system as logical pixels, for passing to `Material.elevation` or a component theme's `elevation` field. Like `StreamRadius` and `StreamSpacing` it is a theme primitive: reachable as `StreamTheme.elevation` or `context.streamElevation`, overridable per theme through the `StreamTheme` constructor, and lerped on theme transitions. `StreamElevation.none` is a fixed `0` rather than a themeable level, so "flat" cannot be redefined as elevated. `StreamAvatar` and `StreamButton` now resolve their elevations from it instead of hard-coded numbers; the rendered values are unchanged.
- Added `chipStyle` to `StreamReactionsThemeData` for overriding the per-reaction chip appearance (background, size, etc.); it is merged over the default reaction chip style.
- Added `StreamMessagePresentation` and `StreamMessageLayoutData.presentation`, describing whether a message is drawn inline in the list (`standard`) or as a preview above a scrim (`preview`, e.g. the long-press message-actions modal). Read it with `StreamMessageLayout.presentationOf(context)`, or resolve per-presentation styling through `StreamMessageLayoutProperty.resolveWith`. For `preview`, the default metadata (username, timestamp, edited, status), annotation (text, icon, trailing) and replies-label colors now resolve to `StreamColorScheme.textOnAccent` so they stay legible against `StreamColorScheme.backgroundScrim`. Also added `StreamMessageLayoutData.copyWith`.

### 🐛 Bug Fixes

Expand Down
1 change: 1 addition & 0 deletions packages/stream_core_flutter/lib/chat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export 'src/components/message_layout/stream_message_channel_kind.dart';
export 'src/components/message_layout/stream_message_content_kind.dart';
export 'src/components/message_layout/stream_message_layout.dart';
export 'src/components/message_layout/stream_message_list_kind.dart';
export 'src/components/message_layout/stream_message_presentation.dart';
export 'src/components/message_layout/stream_message_stack_position.dart';
export 'src/components/reaction/stream_reaction_picker.dart';
export 'src/components/reaction/stream_reactions.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,14 +240,23 @@ class _StreamMessageAnnotationDefaults extends StreamMessageAnnotationStyle {
late final StreamTextTheme _textTheme = _context.streamTextTheme;
late final StreamSpacing _spacing = _context.streamSpacing;

// Resolves to [standard] for inline messages, and to white for previews,
// where the message sits on a scrim and annotations need the extra contrast.
StreamMessageLayoutProperty<Color> _presentationAware(Color standard) => .resolveWith(
(layout) => switch (layout.presentation) {
.standard => standard,
.preview => _colorScheme.textOnAccent,
},
);

@override
StreamMessageLayoutProperty<TextStyle> get textStyle => .all(_textTheme.metadataEmphasis);

@override
StreamMessageLayoutProperty<Color> get textColor => .all(_colorScheme.textPrimary);
StreamMessageLayoutProperty<Color> get textColor => _presentationAware(_colorScheme.textPrimary);

@override
StreamMessageLayoutProperty<Color> get iconColor => .all(_colorScheme.textPrimary);
StreamMessageLayoutProperty<Color> get iconColor => _presentationAware(_colorScheme.textPrimary);

@override
StreamMessageLayoutProperty<double> get iconSize => .all(16);
Expand All @@ -262,5 +271,5 @@ class _StreamMessageAnnotationDefaults extends StreamMessageAnnotationStyle {
StreamMessageLayoutProperty<TextStyle> get trailingTextStyle => .all(_textTheme.metadataDefault);

@override
StreamMessageLayoutProperty<Color> get trailingTextColor => .all(_colorScheme.textPrimary);
StreamMessageLayoutProperty<Color> get trailingTextColor => _presentationAware(_colorScheme.textPrimary);
}
Original file line number Diff line number Diff line change
Expand Up @@ -229,29 +229,38 @@ class _StreamMessageMetadataDefaults extends StreamMessageMetadataStyle {
late final StreamTextTheme _textTheme = _context.streamTextTheme;
late final StreamSpacing _spacing = _context.streamSpacing;

// Resolves to [standard] for inline messages, and to white for previews,
// where the message sits on a scrim and metadata needs the extra contrast.
StreamMessageLayoutProperty<Color> _presentationAware(Color standard) => .resolveWith(
(layout) => switch (layout.presentation) {
.standard => standard,
.preview => _colorScheme.textOnAccent,
},
);

@override
StreamMessageLayoutProperty<TextStyle> get usernameTextStyle => .all(_textTheme.metadataEmphasis);

@override
StreamMessageLayoutProperty<Color> get usernameColor => .all(_colorScheme.textSecondary);
StreamMessageLayoutProperty<Color> get usernameColor => _presentationAware(_colorScheme.textSecondary);

@override
StreamMessageLayoutProperty<TextStyle> get timestampTextStyle => .all(_textTheme.metadataDefault);

@override
StreamMessageLayoutProperty<Color> get timestampColor => .all(_colorScheme.textTertiary);
StreamMessageLayoutProperty<Color> get timestampColor => _presentationAware(_colorScheme.textTertiary);

@override
StreamMessageLayoutProperty<TextStyle> get editedTextStyle => .all(_textTheme.metadataDefault);

@override
StreamMessageLayoutProperty<Color> get editedColor => .all(_colorScheme.textTertiary);
StreamMessageLayoutProperty<Color> get editedColor => _presentationAware(_colorScheme.textTertiary);

@override
StreamMessageLayoutProperty<TextStyle> get statusTextStyle => .all(_textTheme.metadataDefault);

@override
StreamMessageLayoutProperty<Color> get statusColor => .all(_colorScheme.textTertiary);
StreamMessageLayoutProperty<Color> get statusColor => _presentationAware(_colorScheme.textTertiary);

@override
StreamMessageLayoutProperty<double> get statusIconSize => .all(16);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,15 @@ class _StreamMessageRepliesDefaults extends StreamMessageRepliesStyle {
@override
StreamMessageLayoutProperty<TextStyle> get labelTextStyle => .all(_textTheme.captionEmphasis);

// Turns white for previews, where the message sits on a scrim and the label
// needs the extra contrast.
@override
StreamMessageLayoutProperty<Color> get labelColor => .all(_colorScheme.textLink);
StreamMessageLayoutProperty<Color> get labelColor => .resolveWith(
(layout) => switch (layout.presentation) {
.standard => _colorScheme.textLink,
.preview => _colorScheme.textOnAccent,
},
);

@override
StreamMessageLayoutProperty<double> get spacing => .all(_spacing.xs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'stream_message_alignment.dart';
import 'stream_message_channel_kind.dart';
import 'stream_message_content_kind.dart';
import 'stream_message_list_kind.dart';
import 'stream_message_presentation.dart';
import 'stream_message_stack_position.dart';

// The aspect of a [StreamMessageLayoutData] that a widget depends on.
Expand All @@ -26,6 +27,9 @@ enum _StreamMessageLayoutAspect {

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

// The presentation (standard / preview).
presentation,
}

/// Provides [StreamMessageLayoutData] to descendant widgets.
Expand All @@ -47,11 +51,14 @@ enum _StreamMessageLayoutAspect {
/// changes).
/// * [contentKindOf] — returns only the content kind (ignores other
/// field changes).
/// * [presentationOf] — returns only the presentation (ignores other
/// field changes).
///
/// When no [StreamMessageLayout] is found in the tree, a default layout of
/// [StreamMessageAlignment.start] + [StreamMessageStackPosition.single] +
/// [StreamMessageChannelKind.group] + [StreamMessageListKind.channel] +
/// [StreamMessageContentKind.standard] is returned.
/// [StreamMessageContentKind.standard] +
/// [StreamMessagePresentation.standard] is returned.
///
/// {@tool snippet}
///
Expand Down Expand Up @@ -98,7 +105,8 @@ class StreamMessageLayout extends InheritedModel<_StreamMessageLayoutAspect> {
/// If there is no [StreamMessageLayout] in scope, a default layout of
/// [StreamMessageAlignment.start] + [StreamMessageStackPosition.single] +
/// [StreamMessageChannelKind.group] + [StreamMessageListKind.channel] +
/// [StreamMessageContentKind.standard] is returned.
/// [StreamMessageContentKind.standard] +
/// [StreamMessagePresentation.standard] is returned.
static StreamMessageLayoutData of(BuildContext context) => _of(context);

static StreamMessageLayoutData _of(BuildContext context, [_StreamMessageLayoutAspect? aspect]) {
Expand Down Expand Up @@ -227,6 +235,21 @@ class StreamMessageLayout extends InheritedModel<_StreamMessageLayoutAspect> {
return _of(context, _StreamMessageLayoutAspect.contentKind).contentKind;
}

/// Returns [StreamMessageLayoutData.presentation] from the nearest
/// [StreamMessageLayout] ancestor.
///
/// Use of this method will cause the given [context] to rebuild any time
/// that the [StreamMessageLayoutData.presentation] property of the
/// ancestor [StreamMessageLayout] changes.
///
/// Prefer using this function over getting the attribute directly from the
/// [StreamMessageLayoutData] returned from [of], because using this
/// function will only rebuild the [context] when this specific attribute
/// changes, not when _any_ attribute changes.
static StreamMessagePresentation presentationOf(BuildContext context) {
return _of(context, _StreamMessageLayoutAspect.presentation).presentation;
}

@override
bool updateShouldNotify(StreamMessageLayout oldWidget) => data != oldWidget.data;

Expand All @@ -243,6 +266,7 @@ class StreamMessageLayout extends InheritedModel<_StreamMessageLayoutAspect> {
.channelKind => data.channelKind != oldWidget.data.channelKind,
.listKind => data.listKind != oldWidget.data.listKind,
.contentKind => data.contentKind != oldWidget.data.contentKind,
.presentation => data.presentation != oldWidget.data.presentation,
};
},
);
Expand All @@ -253,10 +277,10 @@ class StreamMessageLayout extends InheritedModel<_StreamMessageLayoutAspect> {
/// Combines positional properties — [alignment] (start vs end),
/// [stackPosition] (single, top, middle, bottom) — with environmental
/// context — [channelKind] (direct vs group), [listKind] (channel vs
/// thread) — and content classification[contentKind] (standard,
/// singleAttachment, jumbomoji) — into a single value that
/// [StreamMessageLayoutProperty] resolvers use to compute
/// layout-dependent styling.
/// thread), [presentation] (standard vs preview)and content
/// classification — [contentKind] (standard, singleAttachment, jumbomoji) —
/// into a single value that [StreamMessageLayoutProperty] resolvers use to
/// compute layout-dependent styling.
///
/// {@tool snippet}
///
Expand All @@ -270,13 +294,15 @@ class StreamMessageLayout extends InheritedModel<_StreamMessageLayoutAspect> {
/// channelKind: StreamMessageChannelKind.group,
/// listKind: StreamMessageListKind.channel,
/// contentKind: StreamMessageContentKind.singleAttachment,
/// presentation: StreamMessagePresentation.standard,
/// );
///
/// print(layout.alignment); // StreamMessageAlignment.end
/// print(layout.stackPosition); // StreamMessageStackPosition.top
/// print(layout.channelKind); // StreamMessageChannelKind.group
/// print(layout.listKind); // StreamMessageListKind.channel
/// print(layout.contentKind); // StreamMessageContentKind.singleAttachment
/// print(layout.presentation); // StreamMessagePresentation.standard
/// ```
/// {@end-tool}
///
Expand All @@ -287,21 +313,24 @@ class StreamMessageLayout extends InheritedModel<_StreamMessageLayoutAspect> {
/// * [StreamMessageChannelKind], the kind of channel the message is displayed in.
/// * [StreamMessageListKind], the kind of list the message is displayed in.
/// * [StreamMessageContentKind], the kind of content the message carries.
/// * [StreamMessagePresentation], how the message is presented to the user.
/// * [StreamMessageLayoutProperty], which resolves values from this context.
@immutable
class StreamMessageLayoutData {
/// Creates message layout data.
///
/// Defaults to a start-aligned, standalone message in a group channel list
/// with standard content ([StreamMessageAlignment.start] +
/// with standard content, presented inline ([StreamMessageAlignment.start] +
/// [StreamMessageStackPosition.single] + [StreamMessageChannelKind.group] +
/// [StreamMessageListKind.channel] + [StreamMessageContentKind.standard]).
/// [StreamMessageListKind.channel] + [StreamMessageContentKind.standard] +
/// [StreamMessagePresentation.standard]).
const StreamMessageLayoutData({
this.alignment = .start,
this.stackPosition = .single,
this.channelKind = .group,
this.listKind = .channel,
this.contentKind = .standard,
this.presentation = .standard,
});

/// The horizontal alignment of the message.
Expand All @@ -319,6 +348,29 @@ class StreamMessageLayoutData {
/// The kind of content this message carries.
final StreamMessageContentKind contentKind;

/// How this message is presented to the user.
final StreamMessagePresentation presentation;

/// Returns a copy of this [StreamMessageLayoutData] with the given fields
/// replaced with new values.
StreamMessageLayoutData copyWith({
StreamMessageAlignment? alignment,
StreamMessageStackPosition? stackPosition,
StreamMessageChannelKind? channelKind,
StreamMessageListKind? listKind,
StreamMessageContentKind? contentKind,
StreamMessagePresentation? presentation,
}) {
return StreamMessageLayoutData(
alignment: alignment ?? this.alignment,
stackPosition: stackPosition ?? this.stackPosition,
channelKind: channelKind ?? this.channelKind,
listKind: listKind ?? this.listKind,
contentKind: contentKind ?? this.contentKind,
presentation: presentation ?? this.presentation,
);
}

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
Expand All @@ -327,13 +379,14 @@ class StreamMessageLayoutData {
other.stackPosition == stackPosition &&
other.channelKind == channelKind &&
other.listKind == listKind &&
other.contentKind == contentKind;
other.contentKind == contentKind &&
other.presentation == presentation;
}

@override
int get hashCode => Object.hash(alignment, stackPosition, channelKind, listKind, contentKind);
int get hashCode => Object.hash(alignment, stackPosition, channelKind, listKind, contentKind, presentation);

@override
String toString() =>
'StreamMessageLayoutData(alignment: $alignment, stackPosition: $stackPosition, channelKind: $channelKind, listKind: $listKind, contentKind: $contentKind)';
'StreamMessageLayoutData(alignment: $alignment, stackPosition: $stackPosition, channelKind: $channelKind, listKind: $listKind, contentKind: $contentKind, presentation: $presentation)';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// How a message is presented to the user.
///
/// Used by [StreamMessageLayoutData] to let descendant widgets adapt their
/// appearance based on what the message is drawn on top of — for example,
/// switching secondary text and icons to white when the message is previewed
/// above a scrim.
///
/// See also:
///
/// * [StreamMessageLayoutData], which carries this value.
/// * [StreamMessageLayout], the [InheritedModel] that provides it.
enum StreamMessagePresentation {
/// Rendered inline in the message list, on the app background.
standard,

/// Rendered as a preview above a scrim, for example in the long-press
/// message-actions modal.
///
/// Secondary foreground — metadata, annotations, status icons and the
/// thread-reply label — switches to `StreamColorScheme.textOnAccent` to keep
/// sufficient contrast against `StreamColorScheme.backgroundScrim`.
preview,
}
Loading
Loading