Skip to content

Commit 28f74ee

Browse files
authored
Merge branch 'master' into feat/mark-message-delivered
2 parents 9332107 + a9cd6ae commit 28f74ee

29 files changed

+1406
-213
lines changed

CONTRIBUTING.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ PR titles should follow the format below:
160160
4. *types* other than `fix:` and `feat:` are allowed, for example **[@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional)** (based on the **[the Angular convention](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines)**) recommends `build:``chore:``ci:``docs:``style:``refactor:``perf:``test:`, and others.
161161
5. *footers* other than `BREAKING CHANGE: <description>` may be provided and follow a convention similar to **[git trailer format](https://git-scm.com/docs/git-interpret-trailers)**.
162162

163+
### CHANGELOG.md Updates 📝
164+
165+
After making changes, please ensure you update the `CHANGELOG.md` file located in the root of each package you modified.
166+
163167
### Testing
164168

165169
At Stream, we value testing. Every PR should include passing tests for existing and new features. To run our test suite locally, you can use the following *Melos* command:
@@ -169,12 +173,6 @@ At Stream, we value testing. Every PR should include passing tests for existing
169173
> melos run test:flutter
170174
```
171175

172-
### Our Process
173-
174-
By default, our development branch is `develop`. Contributors should create new PRs based on `develop` when working on new features.
175-
176-
Develop is merged into master after the team performs various automated and QA tests on the branch. Master can be considered our stable branch, it represents the latest published release on pub.dev.
177-
178176
---
179177

180178
# Versioning Policy

packages/stream_chat_flutter/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44

55
- Added delivered status to `SendingIndicator` (double grey check for delivered, double accentPrimary check for read).
66
- Added `isMessageDelivered` parameter to `SendingIndicator` widget.
7+
- Added `MessagePreviewFormatter` interface and `StreamMessagePreviewFormatter` implementation for
8+
customizing message preview text formatting in channel lists and draft messages.
9+
- Added `messagePreviewFormatter` property to `StreamChatConfigurationData` for global customization
10+
of message preview formatting.
11+
- Added formatter properties to theme data classes for customizing date/timestamp
12+
formatting. [[#2312]](https://github.com/GetStream/stream-chat-flutter/issues/2312) [[#2406]](https://github.com/GetStream/stream-chat-flutter/issues/2406)
13+
14+
🐞 Fixed
15+
16+
- Fixed mistakenly passing the hyperlink text to the `onLinkTap` callback instead of the actual `href`.
717

818
## 9.19.0
919

packages/stream_chat_flutter/lib/src/channel/stream_draft_message_preview_text.dart

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,13 @@ class StreamDraftMessagePreviewText extends StatelessWidget {
1818

1919
@override
2020
Widget build(BuildContext context) {
21-
final theme = StreamChatTheme.of(context);
22-
final colorTheme = theme.colorTheme;
21+
final config = StreamChatConfiguration.of(context);
22+
final formatter = config.messagePreviewFormatter;
2323

24-
final previewTextSpan = TextSpan(
25-
text: '${context.translations.draftLabel}: ',
26-
style: textStyle?.copyWith(
27-
fontWeight: FontWeight.bold,
28-
color: colorTheme.accentPrimary,
29-
),
30-
children: [
31-
TextSpan(text: draftMessage.text, style: textStyle),
32-
],
24+
final previewTextSpan = formatter.formatDraftMessage(
25+
context,
26+
draftMessage,
27+
textStyle: textStyle,
3328
);
3429

3530
return Text.rich(

packages/stream_chat_flutter/lib/src/channel/stream_message_preview_text.dart

Lines changed: 9 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -31,32 +31,15 @@ class StreamMessagePreviewText extends StatelessWidget {
3131
final translatedMessage = message.translate(translationLanguage);
3232
final previewMessage = translatedMessage.replaceMentions(linkify: false);
3333

34-
final previewText = _getPreviewText(context, previewMessage, currentUser);
35-
36-
final mentionedUsers = message.mentionedUsers;
37-
final mentionedUsersRegex = RegExp(
38-
mentionedUsers.map((it) => '@${it.name}').join('|'),
39-
);
40-
41-
final previewTextSpan = TextSpan(
42-
children: [
43-
...previewText.splitByRegExp(mentionedUsersRegex).map(
44-
(text) {
45-
// Bold the text if it is a mention user.
46-
if (mentionedUsers.any((it) => '@${it.name}' == text)) {
47-
return TextSpan(
48-
text: text,
49-
style: textStyle?.copyWith(fontWeight: FontWeight.bold),
50-
);
51-
}
52-
53-
return TextSpan(
54-
text: text,
55-
style: textStyle,
56-
);
57-
},
58-
)
59-
],
34+
final config = StreamChatConfiguration.of(context);
35+
final formatter = config.messagePreviewFormatter;
36+
37+
final previewTextSpan = formatter.formatMessage(
38+
context,
39+
previewMessage,
40+
channel: channel,
41+
currentUser: currentUser,
42+
textStyle: textStyle,
6043
);
6144

6245
return Text.rich(
@@ -66,144 +49,4 @@ class StreamMessagePreviewText extends StatelessWidget {
6649
textAlign: TextAlign.start,
6750
);
6851
}
69-
70-
String _getPreviewText(
71-
BuildContext context,
72-
Message message,
73-
User currentUser,
74-
) {
75-
final translations = context.translations;
76-
77-
if (message.isDeleted) {
78-
return translations.messageDeletedLabel;
79-
}
80-
81-
if (message.isSystem) {
82-
return message.text ?? translations.systemMessageLabel;
83-
}
84-
85-
if (message.poll case final poll?) {
86-
return _pollPreviewText(context, poll, currentUser);
87-
}
88-
89-
final previewText = _previewMessageContextText(context, message);
90-
if (previewText == null) return translations.emptyMessagePreviewText;
91-
92-
if (channel case final channel?) {
93-
if (message.user?.id == currentUser.id) {
94-
return '${translations.youText}: $previewText';
95-
}
96-
97-
if (channel.memberCount > 2) {
98-
return '${message.user?.name}: $previewText';
99-
}
100-
}
101-
102-
return previewText;
103-
}
104-
105-
String _pollPreviewText(
106-
BuildContext context,
107-
Poll poll,
108-
User currentUser,
109-
) {
110-
final translations = context.translations;
111-
112-
// If the poll already contains some votes, we will preview the latest voter
113-
// and the poll name
114-
if (poll.latestVotes.firstOrNull?.user case final latestVoter?) {
115-
if (latestVoter.id == currentUser.id) {
116-
final youVoted = translations.pollYouVotedText;
117-
return '📊 $youVoted: "${poll.name}"';
118-
}
119-
120-
final someoneVoted = translations.pollSomeoneVotedText(latestVoter.name);
121-
return '📊 $someoneVoted: "${poll.name}"';
122-
}
123-
124-
// Otherwise, we will show the creator of the poll and the poll name
125-
if (poll.createdBy case final creator?) {
126-
if (creator.id == currentUser.id) {
127-
final youCreated = translations.pollYouCreatedText;
128-
return '📊 $youCreated: "${poll.name}"';
129-
}
130-
131-
final someoneCreated = translations.pollSomeoneCreatedText(creator.name);
132-
return '📊 $someoneCreated: "${poll.name}"';
133-
}
134-
135-
// Otherwise, we will show the poll name if it exists.
136-
if (poll.name.trim() case final pollName when pollName.isNotEmpty) {
137-
return '📊 $pollName';
138-
}
139-
140-
// If nothing else, we will show the default poll emoji.
141-
return '📊';
142-
}
143-
144-
String? _previewMessageContextText(
145-
BuildContext context,
146-
Message message,
147-
) {
148-
final translations = context.translations;
149-
150-
final messageText = switch (message.text) {
151-
final messageText? when messageText.isNotEmpty => messageText,
152-
_ => null,
153-
};
154-
155-
// If the message contains some attachments, we will show the first one
156-
// and the text if it exists.
157-
if (message.attachments.firstOrNull case final attachment?) {
158-
final attachmentIcon = switch (attachment.type) {
159-
AttachmentType.audio => '🎧',
160-
AttachmentType.file => '📄',
161-
AttachmentType.image => '📷',
162-
AttachmentType.video => '📹',
163-
AttachmentType.giphy => '/giphy',
164-
AttachmentType.voiceRecording => '🎤',
165-
_ => null,
166-
};
167-
168-
final attachmentTitle = switch (attachment.type) {
169-
AttachmentType.audio => messageText ?? translations.audioAttachmentText,
170-
AttachmentType.file => attachment.title ?? messageText,
171-
AttachmentType.image => messageText ?? translations.imageAttachmentText,
172-
AttachmentType.video => messageText ?? translations.videoAttachmentText,
173-
AttachmentType.giphy => messageText,
174-
AttachmentType.voiceRecording => translations.voiceRecordingText,
175-
_ => null,
176-
};
177-
178-
if (attachmentIcon != null || attachmentTitle != null) {
179-
return [attachmentIcon, attachmentTitle].nonNulls.join(' ');
180-
}
181-
}
182-
183-
return messageText;
184-
}
185-
}
186-
187-
extension on String {
188-
List<String> splitByRegExp(RegExp regex) {
189-
// If the pattern is empty, return the whole string
190-
if (regex.pattern.isEmpty) return [this];
191-
192-
final result = <String>[];
193-
var start = 0;
194-
195-
for (final match in regex.allMatches(this)) {
196-
if (match.start > start) {
197-
result.add(substring(start, match.start));
198-
}
199-
result.add(match.group(0)!);
200-
start = match.end;
201-
}
202-
203-
if (start < length) {
204-
result.add(substring(start));
205-
}
206-
207-
return result;
208-
}
20952
}

packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,13 @@ class BottomRow extends StatelessWidget {
215215
StreamTimestamp(
216216
date: message.createdAt.toLocal(),
217217
style: messageTheme.createdAtStyle,
218-
formatter: (_, date) => Jiffy.parseFromDateTime(date).jm,
218+
formatter: (context, date) {
219+
if (messageTheme.createdAtFormatter case final formatter?) {
220+
return formatter.call(context, date);
221+
}
222+
223+
return Jiffy.parseFromDateTime(date).jm;
224+
},
219225
),
220226
];
221227

packages/stream_chat_flutter/lib/src/message_widget/message_text.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,23 @@ class StreamMessageText extends StatelessWidget {
4747
messageTheme: messageTheme,
4848
selectable: isDesktopDeviceOrWeb,
4949
onTapLink: (
50-
String link,
50+
String text,
5151
String? href,
5252
String title,
5353
) {
54-
if (link.startsWith('@')) {
54+
if (text.startsWith('@')) {
5555
final mentionedUser = message.mentionedUsers.firstWhereOrNull(
56-
(u) => '@${u.name}' == link,
56+
(u) => '@${u.name}' == text,
5757
);
5858

5959
if (mentionedUser == null) return;
6060

6161
onMentionTap?.call(mentionedUser);
62-
} else {
62+
} else if (href != null) {
6363
if (onLinkTap != null) {
64-
onLinkTap!(link);
64+
onLinkTap!(href);
6565
} else {
66-
launchURL(context, link);
66+
launchURL(context, href);
6767
}
6868
}
6969
},

packages/stream_chat_flutter/lib/src/misc/date_divider.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class StreamDateDivider extends StatelessWidget {
1212
super.key,
1313
required this.dateTime,
1414
this.uppercase = false,
15+
this.formatter,
1516
});
1617

1718
/// [DateTime] to display
@@ -20,6 +21,9 @@ class StreamDateDivider extends StatelessWidget {
2021
/// If text is uppercase
2122
final bool uppercase;
2223

24+
/// Custom formatter for the date
25+
final DateFormatter? formatter;
26+
2327
@override
2428
Widget build(BuildContext context) {
2529
final chatThemeData = StreamChatTheme.of(context);
@@ -36,6 +40,12 @@ class StreamDateDivider extends StatelessWidget {
3640
color: chatThemeData.colorTheme.barsBg,
3741
),
3842
formatter: (context, date) {
43+
if (formatter case final formatter?) {
44+
final timestamp = formatter.call(context, date);
45+
if (uppercase) return timestamp.toUpperCase();
46+
return timestamp;
47+
}
48+
3949
final timestamp = switch (date) {
4050
_ when date.isToday => context.translations.todayLabel,
4151
_ when date.isYesterday => context.translations.yesterdayLabel,

packages/stream_chat_flutter/lib/src/misc/timestamp.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ class StreamTimestamp extends StatelessWidget {
1212
const StreamTimestamp({
1313
super.key,
1414
required this.date,
15-
this.formatter = formatDate,
15+
DateFormatter? formatter,
1616
this.style,
1717
this.textAlign,
1818
this.textDirection,
19-
});
19+
}) : formatter = formatter ?? formatDate;
2020

2121
/// The date to show in the timestamp.
2222
final DateTime date;

0 commit comments

Comments
 (0)