Skip to content
Open
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
15 changes: 15 additions & 0 deletions astro.sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,19 @@ export const sidebar = [
{ label: "More Guides", slug: "docs/flutter/guides/more-guides" },
],
},
{
label: "Layout",
items: [
{
label: "Understanding Layout",
slug: "docs/flutter/layout/understanding-layout",
},

{ label: "Dynamic Theming", slug: "docs/flutter/guides/dynamic-theming" },
{ label: "Add Avatars", slug: "docs/flutter/guides/add-avatars" },
{ label: "Add Usernames", slug: "docs/flutter/guides/add-usernames" },
{ label: "Add Date Separators", slug: "docs/flutter/guides/add-date-separators" },
{ label: "Add Reactions", slug: "docs/flutter/guides/add-reactions" },
],
},
] satisfies StarlightUserConfig["sidebar"];
75 changes: 75 additions & 0 deletions src/content/docs/docs/flutter/guides/add-avatars.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@

---
title: Add Avatars 👩‍🎤
---

Avatars are usually displayed outside the message's bubble, therefore there are built in the `chatMessageBuilder`.

It's up to you to define your own logic to display (or not) the avatar based on the message.

The `Avatar` widget is exposed by the library but your are free to build any widget you like.


## Example usage


```dart
chatMessageBuilder:
(
context,
message,
index,
animation,
child, {
bool? isRemoved,
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) {
final isSystemMessage = message.authorId == 'system';
final isFirstInGroup = groupStatus?.isFirst ?? true;
final isLastInGroup = groupStatus?.isLast ?? true;
final shouldShowAvatar =
!isSystemMessage && isLastInGroup && isRemoved != true;
final isCurrentUser = message.authorId == _currentUser.id;

Widget? avatar;
if (shouldShowAvatar) {
avatar = Padding(
padding: EdgeInsets.only(
left: isCurrentUser ? 8 : 0,
right: isCurrentUser ? 0 : 8,
),
child: Avatar(userId: message.authorId),
);
} else if (!isSystemMessage) {
avatar = const SizedBox(width: 40);
}

return ChatMessage(
message: message,
index: index,
animation: animation,
isRemoved: isRemoved,
groupStatus: groupStatus,
leadingWidget: !isCurrentUser
? avatar
: isSystemMessage
? null
: const SizedBox(width: 40),
trailingWidget: isCurrentUser
? avatar
: isSystemMessage
? null
: const SizedBox(width: 40),
receivedMessageScaleAnimationAlignment:
(message is SystemMessage)
? Alignment.center
: Alignment.centerLeft,
receivedMessageAlignment: (message is SystemMessage)
? AlignmentDirectional.center
: AlignmentDirectional.centerStart,
horizontalPadding: (message is SystemMessage) ? 0 : 8,
child: child,
);
},
```
82 changes: 82 additions & 0 deletions src/content/docs/docs/flutter/guides/add-date-separators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
title: Add date separators 📆
---

Usually date separators are displayed above the message bubbles and are centered horizontally within the chat's view.

The best way to achieve this is to use the either the `topWidget` or `headerWidget` parameter of the `ChatMessage` (see [Understanding Layout](./understanding-layout.md)). It's up to you to defined the logic of how and when to display the separator.

## Example

```dart
Widget _chatMessageBuilder(
BuildContext context,
Message message,
int index,
Animation<double> animation,
Widget child, {
bool? isRemoved,
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) {


return ChatMessage(
message: message,
index: index,
animation: animation,
groupStatus: groupStatus,
headerWidget: _getDisplayDateHeader(index),
child: child,
);
}
```

```dart
Widget? _getDisplayDateHeader(int index) {
try {
final DateTime now = DateTime.now();
final Message? currentMessage = _controller?.messages[index];
if (currentMessage == null) return null;
final currentMessageDate = currentMessage.createdAt!;

final Message? previousMessage =
index > 0 ? _controller?.messages[index - 1] : null;
const differenceThreshold = 15;

final previousMessageDate = previousMessage?.createdAt!;

String? dateString;

// It's today, display the time if messages are more than X minutes appart
// We could also use the groupStatus
if (currentMessageDate.isSameDay(now)) {
if (previousMessageDate == null ||
currentMessageDate.difference(previousMessageDate).inMinutes >=
differenceThreshold) {
dateString = DateFormat.jm().format(currentMessageDate.toLocal());
}
return null;
}
// Else one header per day
if (previousMessageDate != null &&
currentMessageDate.isSameDay(previousMessageDate)) {
return null;
}

if (currentMessageDate.isSameWeek(now)) {
dateString =
DateFormat.EEEE().add_jm().format(currentMessageDate.toLocal());
}
if (currentMessageDate.isSameYear(now)) {
dateString = DateFormat('d MMMM').format(currentMessageDate.toLocal());
}
dateString = DateFormat.yMd().format(currentMessageDate.toLocal());

return YourHeaderWidget(dateString: dateString);
} catch (e) {
log.e('Error getting display date header', error: e);
return null;
}
}
```
107 changes: 107 additions & 0 deletions src/content/docs/docs/flutter/guides/add-reactions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
title: Add reactions 👍
---

:::tip[Reactions position]
Flyer Chat assumes reactions to be displayed under the bubble, overlapping the bubble.
Should you want to display the reactions another way it may be achievable using the the parameters of `ChatMessage` but this has not been tested by the team.
:::


:::danger
To give user's full flexibility in the reactions behavior he library does not automatically call the controller to perform message update when the user interacts with reactions. It's up to you to implement the logic you prefer.

For example:
WhatsApp/Signal: open reactions' list on tap and long press. Allow only one reaction.
Slack: react on tap, open list on long press. Allow several reactions.
:::


Adding reactions to your messages is a two step process.

This example uses the `flyer_chat_reactions` package to add reactions to your messages.

## Displaying reactions under the message bubble

Flyer Chat uses the `reactionsBuilder` on to display the reactions.
Those will appear under the message bubble, overlapping and you can fully customize what happens when a user interacts with them.

```dart
Widget _reactionsBuilder(
BuildContext context, Message message, bool isSentByMe) {
final reactions = reactionsFromMessageReactions(
reactions: message.reactions,
currentUserId: "the_user_id",
);
return FlyerChatReactionsRow(
reactions: reactions,
alignment: isSentByMe ? MainAxisAlignment.start : MainAxisAlignment.end,
onReactionTap: (reaction) => _handleReactionTap(message, reaction),
removeOrAddLocallyOnTap: true, // Allows to visually remove the reaction on tap so it's visually instantaneous. This will not update the controller.
onReactionLongPress: (reaction) =>
// The package exposes a method to show all the reactions list
showReactionsList(context: context, reactions: reactions),
onSurplusReactionTap: () =>
showReactionsList(context: context, reactions: reactions),
);
}

void _handleReactionTap(Message message, String reaction) {
// Implement you message update logic here
}
```

### Displaying the reactions dialog to add a reaction

Once again it's up to you when to display this dialog, usually on a message long press.

```dart
void _handleMessageLongPress(
BuildContext context,
Message message, {
int? index,
LongPressStartDetails? details,
bool isSentByMe = false,
}) async {
final currentUserId = 'user_id';
showReactionsDialog(
context,
message,
isSentByMe: isSentByMe,
// reactions: ['😄','😂'] // You can change default reactions here
userReactions: getUserReactions(message.reactions, currentUserId),
onReactionTap: (reaction) => _handleReactionTap(message, reaction),
onMoreReactionsTap: () async {
// Called when the 'more' reactions is tapped. It's up to you to decide what to do. Usually use an emoji-picker as showcased here.
final picked = await _showEmojiPicker();
if (picked != null) {
_handleReactionTap(message, picked);
}
},
menuItems: _getMenuItems(message), // Display context menu items
);
}

Future<String?> _showEmojiPicker() {
return showModalBottomSheet(
context: context,
useSafeArea: true,
builder: (context) => EmojiPicker(
onEmojiSelected: (Category? category, Emoji emoji) {
Navigator.of(context).pop(emoji.emoji);
},
config: Config(
height: 250,
checkPlatformCompatibility: false,
viewOrderConfig: const ViewOrderConfig(),
skinToneConfig: const SkinToneConfig(),
categoryViewConfig: const CategoryViewConfig(),
bottomActionBarConfig: const BottomActionBarConfig(enabled: false),
searchViewConfig: const SearchViewConfig(),
),
),
);
}


```
79 changes: 79 additions & 0 deletions src/content/docs/docs/flutter/guides/add-usernames.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
title: Add usernames
---

## Above the bubble

To add usernames abobe the message bubble, you can use `topWidget` or `headerWidget` of the `ChatMessage`.

It's up to you to implement the logic to display or not the username, and which widget to user. The library exposes a `Username` widget.

```dart
chatMessageBuilder:
(
context,
message,
index,
animation,
child, {
bool? isRemoved,
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) {
final isSystemMessage = message.authorId == 'system';
final isFirstInGroup = groupStatus?.isFirst ?? true;
final isLastInGroup = groupStatus?.isLast ?? true;
final isCurrentUser = message.authorId == _currentUser.id;
final shouldShowUsername =
!isSystemMessage && isFirstInGroup && isRemoved != true;

return ChatMessage(
message: message,
index: index,
animation: animation,
isRemoved: isRemoved,
groupStatus: groupStatus,
topWidget: shouldShowUsername
? Padding(
padding: EdgeInsets.only(
bottom: 4,
left: isCurrentUser ? 0 : 48,
right: isCurrentUser ? 48 : 0,
),
child: Username(userId: message.authorId),
)
: null,

child: child,
);
},
```

## In the message bubble

To display the username within the bubble the logic remains the same but we will use the `topWidget` parameter for each individual message builder.

**Example for textMessage**

```dart
Widget _textMessageBuilder(
BuildContext context,
TextMessage message,
int index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) {
final isSystemMessage = message.authorId == 'system';
final isFirstInGroup = groupStatus?.isFirst ?? true;
final isLastInGroup = groupStatus?.isLast ?? true;
final isCurrentUser = message.authorId == _currentUser.id;
final shouldShowUsername =
!isSystemMessage && isFirstInGroup && isRemoved != true;

return FlyerChatTextMessage(
topWidget: shouldShowUsername ? YourWidget() : null
message: message,
index: index
);
}
```
Loading