feat: add chatMessageBuilder to the chatoptions to override default behavior

With the chatMessageBuilder it is possible to run a null whenever you still want to use the default but only want to update the chat in very specific cases.
I also slightly refactored the chat_detail_screen.dart to remove duplicate code and make it more readable
This commit is contained in:
Freek van de Ven 2025-01-30 10:38:34 +01:00 committed by FlutterJoey
parent 1ea2887e27
commit 7457602afe
3 changed files with 202 additions and 225 deletions

View file

@ -1,5 +1,6 @@
## 5.0.0 - WIP ## 5.0.0 - WIP
- Get the color for the imagepicker from the Theme's primaryColor - Get the color for the imagepicker from the Theme's primaryColor
- Added chatMessageBuilder to the userstory configuration to customize the chat messages
## 4.0.0 ## 4.0.0
- Move to the new user story architecture - Move to the new user story architecture

View file

@ -17,6 +17,7 @@ class ChatBuilders {
this.newChatButtonBuilder, this.newChatButtonBuilder,
this.noUsersPlaceholderBuilder, this.noUsersPlaceholderBuilder,
this.chatTitleBuilder, this.chatTitleBuilder,
this.chatMessageBuilder,
this.usernameBuilder, this.usernameBuilder,
this.loadingWidgetBuilder, this.loadingWidgetBuilder,
}); });
@ -62,6 +63,9 @@ class ChatBuilders {
/// The chat title builder /// The chat title builder
final Widget Function(String chatTitle)? chatTitleBuilder; final Widget Function(String chatTitle)? chatTitleBuilder;
/// The chat message builder
final ChatMessageBuilder? chatMessageBuilder;
/// The username builder /// The username builder
final Widget Function(String userFullName)? usernameBuilder; final Widget Function(String userFullName)? usernameBuilder;
@ -108,6 +112,17 @@ typedef ContainerBuilder = Widget Function(
Widget child, Widget child,
); );
/// The chat message builder
/// This builder is used to override the default chat message widget
/// If null is returned, the default chat message widget will be used so you can
/// override for specific cases
/// [previousMessage] is the previous message in the chat
typedef ChatMessageBuilder = Widget? Function(
BuildContext context,
MessageModel message,
MessageModel? previousMessage,
);
/// The group avatar builder /// The group avatar builder
typedef GroupAvatarBuilder = Widget Function( typedef GroupAvatarBuilder = Widget Function(
BuildContext context, BuildContext context,

View file

@ -86,15 +86,14 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (widget.chatOptions.builders.baseScreenBuilder == null) { var appBar = _AppBar(
return Scaffold(
appBar: _AppBar(
chatTitle: chatTitle, chatTitle: chatTitle,
chatOptions: widget.chatOptions, chatOptions: widget.chatOptions,
onPressChatTitle: widget.onPressChatTitle, onPressChatTitle: widget.onPressChatTitle,
chatModel: widget.chat, chatModel: widget.chat,
), );
body: _Body(
var body = _Body(
chatService: widget.chatService, chatService: widget.chatService,
options: widget.chatOptions, options: widget.chatOptions,
chat: widget.chat, chat: widget.chat,
@ -103,29 +102,20 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
onUploadImage: widget.onUploadImage, onUploadImage: widget.onUploadImage,
onMessageSubmit: widget.onMessageSubmit, onMessageSubmit: widget.onMessageSubmit,
onReadChat: widget.onReadChat, onReadChat: widget.onReadChat,
), );
if (widget.chatOptions.builders.baseScreenBuilder == null) {
return Scaffold(
appBar: appBar,
body: body,
); );
} }
return widget.chatOptions.builders.baseScreenBuilder!.call( return widget.chatOptions.builders.baseScreenBuilder!.call(
context, context,
widget.mapScreenType, widget.mapScreenType,
_AppBar( appBar,
chatTitle: chatTitle, body,
chatOptions: widget.chatOptions,
onPressChatTitle: widget.onPressChatTitle,
chatModel: widget.chat,
),
_Body(
chatService: widget.chatService,
options: widget.chatOptions,
chat: widget.chat,
currentUserId: widget.userId,
onPressUserProfile: widget.onPressUserProfile,
onUploadImage: widget.onUploadImage,
onMessageSubmit: widget.onMessageSubmit,
onReadChat: widget.onReadChat,
),
); );
} }
} }
@ -375,27 +365,32 @@ class _ChatBottomState extends State<_ChatBottom> {
var theme = Theme.of(context); var theme = Theme.of(context);
_textEditingController.addListener(() { _textEditingController.addListener(() {
if (_textEditingController.text.isEmpty) {
setState(() { setState(() {
_isTyping = false; _isTyping = _textEditingController.text.isNotEmpty;
}); });
} else { });
Future<void> sendMessage() async {
setState(() { setState(() {
_isTyping = true; _isSending = true;
});
var value = _textEditingController.text;
if (value.isNotEmpty) {
await widget.onMessageSubmit(value);
_textEditingController.clear();
}
setState(() {
_isSending = false;
}); });
} }
});
return Padding( Future<void> Function()? onClickSendMessage;
padding: const EdgeInsets.symmetric( if (_isTyping && !_isSending) {
horizontal: 12, onClickSendMessage = () async => sendMessage();
vertical: 16, }
),
child: SizedBox( var messageSendButtons = Row(
height: 45,
child: widget.options.builders.messageInputBuilder?.call(
context,
_textEditingController,
Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -409,30 +404,25 @@ class _ChatBottomState extends State<_ChatBottom> {
IconButton( IconButton(
disabledColor: widget.options.iconDisabledColor, disabledColor: widget.options.iconDisabledColor,
color: widget.options.iconEnabledColor, color: widget.options.iconEnabledColor,
onPressed: _isTyping && !_isSending onPressed: onClickSendMessage,
? () async {
setState(() {
_isSending = true;
});
var value = _textEditingController.text;
if (value.isNotEmpty) {
await widget.onMessageSubmit(value);
_textEditingController.clear();
}
setState(() {
_isSending = false;
});
}
: null,
icon: const Icon( icon: const Icon(
Icons.send, Icons.send,
), ),
), ),
], ],
);
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 16,
), ),
child: SizedBox(
height: 45,
child: widget.options.builders.messageInputBuilder?.call(
context,
_textEditingController,
messageSendButtons,
widget.options.translations, widget.options.translations,
) ?? ) ??
TextField( TextField(
@ -468,43 +458,7 @@ class _ChatBottomState extends State<_ChatBottom> {
), ),
borderSide: BorderSide.none, borderSide: BorderSide.none,
), ),
suffixIcon: Row( suffixIcon: messageSendButtons,
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: widget.onPressSelectImage,
icon: Icon(
Icons.image_outlined,
color: widget.options.iconEnabledColor,
),
),
IconButton(
disabledColor: widget.options.iconDisabledColor,
color: widget.options.iconEnabledColor,
onPressed: _isTyping && !_isSending
? () async {
setState(() {
_isSending = true;
});
var value = _textEditingController.text;
if (value.isNotEmpty) {
await widget.onMessageSubmit(value);
_textEditingController.clear();
}
setState(() {
_isSending = false;
});
}
: null,
icon: const Icon(
Icons.send,
),
),
],
),
), ),
), ),
), ),
@ -557,7 +511,12 @@ class _ChatBubbleState extends State<_ChatBubble> {
var user = snapshot.data!; var user = snapshot.data!;
return Padding( return widget.options.builders.chatMessageBuilder?.call(
context,
widget.message,
widget.previousMessage,
) ??
Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
top: isNewDate || isSameSender ? 25.0 : 0, top: isNewDate || isSameSender ? 25.0 : 0,
), ),
@ -584,8 +543,9 @@ class _ChatBubbleState extends State<_ChatBubble> {
user: User( user: User(
firstName: user.firstName, firstName: user.firstName,
lastName: user.lastName, lastName: user.lastName,
imageUrl: imageUrl: user.imageUrl != ""
user.imageUrl != "" ? user.imageUrl : null, ? user.imageUrl
: null,
), ),
size: 40, size: 40,
), ),
@ -613,7 +573,8 @@ class _ChatBubbleState extends State<_ChatBubble> {
user.fullname ?? "", user.fullname ?? "",
) ?? ) ??
Text( Text(
user.fullname ?? translations.anonymousUser, user.fullname ??
translations.anonymousUser,
style: theme.textTheme.titleMedium, style: theme.textTheme.titleMedium,
), ),
), ),