Skip to content

Commit e267c27

Browse files
feat: allow for top widgets within the bubble (#814)
* Add empty package * Add old lib utils * Bump dependencies, rename * Add new preview widget * Adapt base text widgets to the new intended layout * Add example * Update callback to include if sentByMe and update example * Lowercase text, iOS has a tendency to put HTTPS which does not work * Revert previous commit * rebase and fix dependencies * Implement in FlyerChatTextMessage * Implement in FlyerChatImageMessage * Demo * Add for SimpleTextMessage * Add support for fileMessages * rebase * rename topWidgets to topWidget for consistency * support image --------- Co-authored-by: Alex Demchenko <[email protected]>
1 parent 40f2615 commit e267c27

File tree

5 files changed

+216
-151
lines changed

5 files changed

+216
-151
lines changed

examples/flyer_chat/lib/create_message.dart

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ Future<Message> createMessage(
1515
const uuid = Uuid();
1616
Message message;
1717

18-
if (Random().nextBool() || textOnly == true || text != null) {
18+
final randomType = Random().nextInt(3) + 1; // 1, 2, or 3
19+
20+
if (randomType == 1 || textOnly == true || text != null) {
1921
message = TextMessage(
2022
id: uuid.v4(),
2123
authorId: authorId,
@@ -50,15 +52,27 @@ Future<Message> createMessage(
5052
),
5153
);
5254

53-
message = ImageMessage(
54-
id: uuid.v4(),
55-
authorId: authorId,
56-
createdAt: DateTime.now().toUtc(),
57-
sentAt: localOnly == true ? DateTime.now().toUtc() : null,
58-
source: response.data['img'],
59-
thumbhash: response.data['thumbhash'],
60-
blurhash: response.data['blurhash'],
61-
);
55+
if (randomType == 2) {
56+
message = ImageMessage(
57+
id: uuid.v4(),
58+
authorId: authorId,
59+
createdAt: DateTime.now().toUtc(),
60+
sentAt: localOnly == true ? DateTime.now().toUtc() : null,
61+
source: response.data['img'],
62+
thumbhash: response.data['thumbhash'],
63+
blurhash: response.data['blurhash'],
64+
);
65+
} else {
66+
message = FileMessage(
67+
id: uuid.v4(),
68+
name: 'image.png',
69+
authorId: authorId,
70+
createdAt: DateTime.now().toUtc(),
71+
sentAt: localOnly == true ? DateTime.now().toUtc() : null,
72+
source: response.data['img'],
73+
size: 1000000,
74+
);
75+
}
6276
}
6377

6478
// return ImageMessage(

packages/flutter_chat_ui/lib/src/simple_text_message.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ class SimpleTextMessage extends StatelessWidget {
6666
/// A [LinkPreviewBuilder] must be provided for the preview to be displayed.
6767
final LinkPreviewPosition linkPreviewPosition;
6868

69+
/// The widget to display on top of the message.
70+
final Widget? topWidget;
71+
6972
/// Creates a widget to display a simple text message.
7073
const SimpleTextMessage({
7174
super.key,
@@ -85,6 +88,7 @@ class SimpleTextMessage extends StatelessWidget {
8588
this.timeAndStatusPosition = TimeAndStatusPosition.end,
8689
this.timeAndStatusPositionInlineInsets = const EdgeInsets.only(bottom: 2),
8790
this.linkPreviewPosition = LinkPreviewPosition.bottom,
91+
this.topWidget,
8892
});
8993

9094
bool get _isOnlyEmoji => message.metadata?['isOnlyEmoji'] == true;
@@ -185,6 +189,7 @@ class SimpleTextMessage extends StatelessWidget {
185189
mainAxisSize: MainAxisSize.min,
186190
crossAxisAlignment: CrossAxisAlignment.start,
187191
children: [
192+
if (topWidget != null) topWidget!,
188193
if (effectiveLinkPreviewPosition == LinkPreviewPosition.top)
189194
linkPreviewWidget!,
190195
timeAndStatusPosition == TimeAndStatusPosition.inline

packages/flyer_chat_file_message/lib/src/flyer_chat_file_message.dart

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ class FlyerChatFileMessage extends StatelessWidget {
7474
/// Position of the timestamp and status indicator relative to the text content.
7575
final TimeAndStatusPosition timeAndStatusPosition;
7676

77+
/// The widget to display on top of the message.
78+
final Widget? topWidget;
79+
7780
/// Creates a widget to display a file message.
7881
const FlyerChatFileMessage({
7982
super.key,
@@ -95,6 +98,7 @@ class FlyerChatFileMessage extends StatelessWidget {
9598
this.showTime = true,
9699
this.showStatus = true,
97100
this.timeAndStatusPosition = TimeAndStatusPosition.end,
101+
this.topWidget,
98102
});
99103

100104
@override
@@ -190,42 +194,36 @@ class FlyerChatFileMessage extends StatelessWidget {
190194
required TimeAndStatus? timeAndStatus,
191195
required TextStyle? textStyle,
192196
}) {
193-
if (timeAndStatus == null ||
194-
timeAndStatusPosition == TimeAndStatusPosition.inline) {
195-
return fileContent;
196-
}
197-
198197
final textDirection = Directionality.of(context);
199-
switch (timeAndStatusPosition) {
200-
case TimeAndStatusPosition.start:
201-
return Column(
202-
crossAxisAlignment: CrossAxisAlignment.start,
203-
mainAxisSize: MainAxisSize.min,
204-
children: [fileContent, timeAndStatus],
205-
);
206-
case TimeAndStatusPosition.inline:
207-
return Row(
198+
199+
return Stack(
200+
children: [
201+
Column(
208202
mainAxisSize: MainAxisSize.min,
209-
crossAxisAlignment: CrossAxisAlignment.end,
210-
children: [fileContent, const SizedBox(width: 4), timeAndStatus],
211-
);
212-
case TimeAndStatusPosition.end:
213-
return Stack(
203+
crossAxisAlignment: CrossAxisAlignment.start,
214204
children: [
215-
Padding(
216-
padding: EdgeInsets.only(bottom: textStyle?.lineHeight ?? 0),
217-
child: fileContent,
218-
),
219-
Opacity(opacity: 0, child: timeAndStatus),
220-
Positioned.directional(
221-
textDirection: textDirection,
222-
end: 0,
223-
bottom: 0,
224-
child: timeAndStatus,
225-
),
205+
if (topWidget != null) topWidget!,
206+
// In comparison to other messages types, if timeAndStatusPosition is inline,
207+
// the fileContent is already a Row with the timeAndStatus widget inside it.
208+
fileContent,
209+
if (timeAndStatusPosition != TimeAndStatusPosition.inline)
210+
// Ensure the width is not smaller than the timeAndStatus widget
211+
// Ensure the height accounts for it's height
212+
Opacity(opacity: 0, child: timeAndStatus),
226213
],
227-
);
228-
}
214+
),
215+
if (timeAndStatusPosition != TimeAndStatusPosition.inline &&
216+
timeAndStatus != null)
217+
Positioned.directional(
218+
textDirection: textDirection,
219+
end: timeAndStatusPosition == TimeAndStatusPosition.end ? 0 : null,
220+
start:
221+
timeAndStatusPosition == TimeAndStatusPosition.start ? 0 : null,
222+
bottom: 0,
223+
child: timeAndStatus,
224+
),
225+
],
226+
);
229227
}
230228

231229
String _formatFileSize(int sizeInBytes) {
@@ -286,12 +284,6 @@ class FlyerChatFileMessage extends StatelessWidget {
286284
}
287285
}
288286

289-
/// Internal extension for calculating the visual line height of a TextStyle.
290-
extension on TextStyle {
291-
/// Calculates the line height based on the style's `height` and `fontSize`.
292-
double get lineHeight => (height ?? 1) * (fontSize ?? 0);
293-
}
294-
295287
/// A widget to display the message timestamp and status indicator.
296288
class TimeAndStatus extends StatelessWidget {
297289
/// The time the message was created.

0 commit comments

Comments
 (0)