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
4 changes: 4 additions & 0 deletions packages/stream_chat_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

✅ Added

- Added `MessagePreviewFormatter` interface and `StreamMessagePreviewFormatter` implementation for
customizing message preview text formatting in channel lists and draft messages.
- Added `messagePreviewFormatter` property to `StreamChatConfigurationData` for global customization
of message preview formatting.
- Added formatter properties to theme data classes for customizing date/timestamp
formatting. [[#2312]](https://github.com/GetStream/stream-chat-flutter/issues/2312) [[#2406]](https://github.com/GetStream/stream-chat-flutter/issues/2406)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,13 @@ class StreamDraftMessagePreviewText extends StatelessWidget {

@override
Widget build(BuildContext context) {
final theme = StreamChatTheme.of(context);
final colorTheme = theme.colorTheme;
final config = StreamChatConfiguration.of(context);
final formatter = config.messagePreviewFormatter;

final previewTextSpan = TextSpan(
text: '${context.translations.draftLabel}: ',
style: textStyle?.copyWith(
fontWeight: FontWeight.bold,
color: colorTheme.accentPrimary,
),
children: [
TextSpan(text: draftMessage.text, style: textStyle),
],
final previewTextSpan = formatter.formatDraftMessage(
context,
draftMessage,
textStyle: textStyle,
);

return Text.rich(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,32 +31,15 @@ class StreamMessagePreviewText extends StatelessWidget {
final translatedMessage = message.translate(translationLanguage);
final previewMessage = translatedMessage.replaceMentions(linkify: false);

final previewText = _getPreviewText(context, previewMessage, currentUser);

final mentionedUsers = message.mentionedUsers;
final mentionedUsersRegex = RegExp(
mentionedUsers.map((it) => '@${it.name}').join('|'),
);

final previewTextSpan = TextSpan(
children: [
...previewText.splitByRegExp(mentionedUsersRegex).map(
(text) {
// Bold the text if it is a mention user.
if (mentionedUsers.any((it) => '@${it.name}' == text)) {
return TextSpan(
text: text,
style: textStyle?.copyWith(fontWeight: FontWeight.bold),
);
}

return TextSpan(
text: text,
style: textStyle,
);
},
)
],
final config = StreamChatConfiguration.of(context);
final formatter = config.messagePreviewFormatter;

final previewTextSpan = formatter.formatMessage(
context,
previewMessage,
channel: channel,
currentUser: currentUser,
textStyle: textStyle,
);

return Text.rich(
Expand All @@ -66,144 +49,4 @@ class StreamMessagePreviewText extends StatelessWidget {
textAlign: TextAlign.start,
);
}

String _getPreviewText(
BuildContext context,
Message message,
User currentUser,
) {
final translations = context.translations;

if (message.isDeleted) {
return translations.messageDeletedLabel;
}

if (message.isSystem) {
return message.text ?? translations.systemMessageLabel;
}

if (message.poll case final poll?) {
return _pollPreviewText(context, poll, currentUser);
}

final previewText = _previewMessageContextText(context, message);
if (previewText == null) return translations.emptyMessagePreviewText;

if (channel case final channel?) {
if (message.user?.id == currentUser.id) {
return '${translations.youText}: $previewText';
}

if (channel.memberCount > 2) {
return '${message.user?.name}: $previewText';
}
}

return previewText;
}

String _pollPreviewText(
BuildContext context,
Poll poll,
User currentUser,
) {
final translations = context.translations;

// If the poll already contains some votes, we will preview the latest voter
// and the poll name
if (poll.latestVotes.firstOrNull?.user case final latestVoter?) {
if (latestVoter.id == currentUser.id) {
final youVoted = translations.pollYouVotedText;
return '📊 $youVoted: "${poll.name}"';
}

final someoneVoted = translations.pollSomeoneVotedText(latestVoter.name);
return '📊 $someoneVoted: "${poll.name}"';
}

// Otherwise, we will show the creator of the poll and the poll name
if (poll.createdBy case final creator?) {
if (creator.id == currentUser.id) {
final youCreated = translations.pollYouCreatedText;
return '📊 $youCreated: "${poll.name}"';
}

final someoneCreated = translations.pollSomeoneCreatedText(creator.name);
return '📊 $someoneCreated: "${poll.name}"';
}

// Otherwise, we will show the poll name if it exists.
if (poll.name.trim() case final pollName when pollName.isNotEmpty) {
return '📊 $pollName';
}

// If nothing else, we will show the default poll emoji.
return '📊';
}

String? _previewMessageContextText(
BuildContext context,
Message message,
) {
final translations = context.translations;

final messageText = switch (message.text) {
final messageText? when messageText.isNotEmpty => messageText,
_ => null,
};

// If the message contains some attachments, we will show the first one
// and the text if it exists.
if (message.attachments.firstOrNull case final attachment?) {
final attachmentIcon = switch (attachment.type) {
AttachmentType.audio => '🎧',
AttachmentType.file => '📄',
AttachmentType.image => '📷',
AttachmentType.video => '📹',
AttachmentType.giphy => '/giphy',
AttachmentType.voiceRecording => '🎤',
_ => null,
};

final attachmentTitle = switch (attachment.type) {
AttachmentType.audio => messageText ?? translations.audioAttachmentText,
AttachmentType.file => attachment.title ?? messageText,
AttachmentType.image => messageText ?? translations.imageAttachmentText,
AttachmentType.video => messageText ?? translations.videoAttachmentText,
AttachmentType.giphy => messageText,
AttachmentType.voiceRecording => translations.voiceRecordingText,
_ => null,
};

if (attachmentIcon != null || attachmentTitle != null) {
return [attachmentIcon, attachmentTitle].nonNulls.join(' ');
}
}

return messageText;
}
}

extension on String {
List<String> splitByRegExp(RegExp regex) {
// If the pattern is empty, return the whole string
if (regex.pattern.isEmpty) return [this];

final result = <String>[];
var start = 0;

for (final match in regex.allMatches(this)) {
if (match.start > start) {
result.add(substring(start, match.start));
}
result.add(match.group(0)!);
start = match.end;
}

if (start < length) {
result.add(substring(start));
}

return result;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'package:stream_chat_flutter/src/channel/stream_draft_message_preview_text.dart';
import 'package:stream_chat_flutter/src/message_widget/sending_indicator_builder.dart';
import 'package:stream_chat_flutter/src/misc/empty_widget.dart';
import 'package:stream_chat_flutter/src/misc/timestamp.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/channel/stream_draft_message_preview_text.dart';
import 'package:stream_chat_flutter/src/misc/timestamp.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/channel/stream_draft_message_preview_text.dart';
import 'package:stream_chat_flutter/src/misc/timestamp.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class StreamChatConfigurationData {
List<StreamReactionIcon>? reactionIcons,
bool? enforceUniqueReactions,
bool draftMessagesEnabled = false,
MessagePreviewFormatter? messagePreviewFormatter,
}) {
return StreamChatConfigurationData._(
loadingIndicator: loadingIndicator,
Expand All @@ -123,6 +124,8 @@ class StreamChatConfigurationData {
reactionIcons: reactionIcons ?? _defaultReactionIcons,
enforceUniqueReactions: enforceUniqueReactions ?? true,
draftMessagesEnabled: draftMessagesEnabled,
messagePreviewFormatter:
messagePreviewFormatter ?? MessagePreviewFormatter(),
);
}

Expand All @@ -133,6 +136,7 @@ class StreamChatConfigurationData {
required this.reactionIcons,
required this.enforceUniqueReactions,
required this.draftMessagesEnabled,
required this.messagePreviewFormatter,
});

/// Copies the configuration options from one [StreamChatConfigurationData] to
Expand All @@ -144,6 +148,7 @@ class StreamChatConfigurationData {
List<StreamReactionIcon>? reactionIcons,
bool? enforceUniqueReactions,
bool? draftMessagesEnabled,
MessagePreviewFormatter? messagePreviewFormatter,
}) {
return StreamChatConfigurationData(
reactionIcons: reactionIcons ?? this.reactionIcons,
Expand All @@ -153,6 +158,8 @@ class StreamChatConfigurationData {
enforceUniqueReactions:
enforceUniqueReactions ?? this.enforceUniqueReactions,
draftMessagesEnabled: draftMessagesEnabled ?? this.draftMessagesEnabled,
messagePreviewFormatter:
messagePreviewFormatter ?? this.messagePreviewFormatter,
);
}

Expand All @@ -176,6 +183,11 @@ class StreamChatConfigurationData {
/// Whether a new reaction should replace the existing one.
final bool enforceUniqueReactions;

/// The formatter used for message previews throughout the application.
///
/// Defaults to [MessagePreviewFormatter].
final MessagePreviewFormatter messagePreviewFormatter;

static final _defaultReactionIcons = [
StreamReactionIcon(
type: 'love',
Expand Down
Loading