feat: added chat options

This commit is contained in:
mike doornenbal 2023-12-13 09:09:28 +01:00 committed by Freek van de Ven
parent 7b33ff2bd7
commit 07e29ddd99
6 changed files with 274 additions and 204 deletions

View file

@ -1,6 +1,7 @@
## 0.6.0 - December 1 2023 ## 0.6.0 - December 1 2023
- Made the message controller nullable - Made the message controller nullable
- Improved chat UI and added showTime option for chatDetailScreen to always show the time
## 0.5.0 - November 29 2023 ## 0.5.0 - November 29 2023

View file

@ -75,6 +75,7 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
users: [pietUser, janUser], users: [pietUser, janUser],
lastUsed: DateTime.now().subtract(const Duration(days: 1)), lastUsed: DateTime.now().subtract(const Duration(days: 1)),
messages: messages, messages: messages,
canBeDeleted: false,
); );
Stream<List<ChatModel>> get chatStream => (() { Stream<List<ChatModel>> get chatStream => (() {

View file

@ -15,10 +15,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_community_chat_view: flutter_community_chat_view:
git: path: ..
url: https://github.com/Iconica-Development/flutter_community_chat
path: packages/flutter_community_chat_view
ref: 0.6.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

@ -11,16 +11,18 @@ import 'package:flutter_community_chat_view/src/services/date_formatter.dart';
class ChatDetailRow extends StatefulWidget { class ChatDetailRow extends StatefulWidget {
const ChatDetailRow({ const ChatDetailRow({
required this.translations, required this.translations,
required this.isFirstMessage,
required this.message, required this.message,
required this.userAvatarBuilder, required this.userAvatarBuilder,
this.previousMessage,
this.showTime = false,
super.key, super.key,
}); });
final ChatTranslations translations; final ChatTranslations translations;
final bool isFirstMessage;
final ChatMessageModel message; final ChatMessageModel message;
final UserAvatarBuilder userAvatarBuilder; final UserAvatarBuilder userAvatarBuilder;
final bool showTime;
final ChatMessageModel? previousMessage;
@override @override
State<ChatDetailRow> createState() => _ChatDetailRowState(); State<ChatDetailRow> createState() => _ChatDetailRowState();
@ -30,14 +32,25 @@ class _ChatDetailRowState extends State<ChatDetailRow> {
final DateFormatter _dateFormatter = DateFormatter(); final DateFormatter _dateFormatter = DateFormatter();
@override @override
Widget build(BuildContext context) => Padding( Widget build(BuildContext context) {
padding: EdgeInsets.only(top: widget.isFirstMessage ? 25.0 : 0), var isNewDate = widget.previousMessage != null &&
widget.message.timestamp.day != widget.previousMessage!.timestamp.day;
return Padding(
padding: EdgeInsets.only(
top: isNewDate ||
widget.previousMessage == null ||
widget.previousMessage?.sender.id != widget.message.sender.id
? 25.0
: 0,
),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Opacity( if (isNewDate ||
opacity: widget.isFirstMessage ? 1 : 0, widget.previousMessage == null ||
child: Padding( widget.previousMessage?.sender.id !=
widget.message.sender.id) ...[
Padding(
padding: const EdgeInsets.only(left: 10.0), padding: const EdgeInsets.only(left: 10.0),
child: widget.message.sender.imageUrl != null && child: widget.message.sender.imageUrl != null &&
widget.message.sender.imageUrl!.isNotEmpty widget.message.sender.imageUrl!.isNotEmpty
@ -49,7 +62,11 @@ class _ChatDetailRowState extends State<ChatDetailRow> {
30, 30,
), ),
), ),
] else ...[
const SizedBox(
width: 50,
), ),
],
Expanded( Expanded(
child: Container( child: Container(
child: Padding( child: Padding(
@ -58,7 +75,10 @@ class _ChatDetailRowState extends State<ChatDetailRow> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
if (widget.isFirstMessage) if (isNewDate ||
widget.previousMessage == null ||
widget.previousMessage?.sender.id !=
widget.message.sender.id)
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -88,9 +108,27 @@ class _ChatDetailRowState extends State<ChatDetailRow> {
Padding( Padding(
padding: const EdgeInsets.only(top: 3.0), padding: const EdgeInsets.only(top: 3.0),
child: widget.message is ChatTextMessageModel child: widget.message is ChatTextMessageModel
? Text( ? RichText(
(widget.message as ChatTextMessageModel).text, text: TextSpan(
text: (widget.message as ChatTextMessageModel)
.text,
style: const TextStyle(fontSize: 16), style: const TextStyle(fontSize: 16),
children: <TextSpan>[
if (widget.showTime)
TextSpan(
text: " ${_dateFormatter.format(
date: widget.message.timestamp,
showFullDate: true,
).split(' ').last}",
style: const TextStyle(
fontSize: 12,
color: Color(0xFFBBBBBB),
),
)
else
const TextSpan(),
],
),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 999, maxLines: 999,
) )
@ -108,4 +146,5 @@ class _ChatDetailRowState extends State<ChatDetailRow> {
], ],
), ),
); );
}
} }

View file

@ -23,6 +23,7 @@ class ChatDetailScreen extends StatefulWidget {
this.chatMessages, this.chatMessages,
this.onPressChatTitle, this.onPressChatTitle,
this.iconColor, this.iconColor,
this.showTime = false,
super.key, super.key,
}); });
@ -43,6 +44,7 @@ class ChatDetailScreen extends StatefulWidget {
/// The color of the icon buttons in the chat bottom. /// The color of the icon buttons in the chat bottom.
final Color? iconColor; final Color? iconColor;
final bool showTime;
@override @override
State<ChatDetailScreen> createState() => _ChatDetailScreenState(); State<ChatDetailScreen> createState() => _ChatDetailScreenState();
@ -154,21 +156,21 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
stream: _chatMessages, stream: _chatMessages,
builder: (BuildContext context, snapshot) { builder: (BuildContext context, snapshot) {
var messages = snapshot.data ?? widget.chat?.messages ?? []; var messages = snapshot.data ?? widget.chat?.messages ?? [];
ChatMessageModel? lastMessage; ChatMessageModel? previousMessage;
var messageWidgets = <Widget>[]; var messageWidgets = <Widget>[];
for (var message in messages) { for (var message in messages) {
var isFirstMessage = lastMessage == null ||
lastMessage.sender.id != message.sender.id;
messageWidgets.add( messageWidgets.add(
ChatDetailRow( ChatDetailRow(
previousMessage: previousMessage,
showTime: widget.showTime,
translations: widget.translations, translations: widget.translations,
message: message, message: message,
isFirstMessage: isFirstMessage,
userAvatarBuilder: widget.options.userAvatarBuilder, userAvatarBuilder: widget.options.userAvatarBuilder,
), ),
); );
lastMessage = message; previousMessage = message;
} }
return ListView( return ListView(

View file

@ -2,6 +2,8 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
// ignore_for_file: lines_longer_than_80_chars
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_community_chat_view/flutter_community_chat_view.dart'; import 'package:flutter_community_chat_view/flutter_community_chat_view.dart';
import 'package:flutter_community_chat_view/src/services/date_formatter.dart'; import 'package:flutter_community_chat_view/src/services/date_formatter.dart';
@ -16,6 +18,7 @@ class ChatScreen extends StatefulWidget {
this.deleteChatDialog, this.deleteChatDialog,
this.unreadChats, this.unreadChats,
this.translations = const ChatTranslations(), this.translations = const ChatTranslations(),
this.disableDismiss = false,
super.key, super.key,
}); });
@ -26,6 +29,7 @@ class ChatScreen extends StatefulWidget {
final VoidCallback? onPressStartChat; final VoidCallback? onPressStartChat;
final void Function(ChatModel chat) onDeleteChat; final void Function(ChatModel chat) onDeleteChat;
final void Function(ChatModel chat) onPressChat; final void Function(ChatModel chat) onPressChat;
final bool disableDismiss;
/// Method to optionally change the bottomsheetdialog /// Method to optionally change the bottomsheetdialog
final Future<bool?> Function(BuildContext, ChatModel)? deleteChatDialog; final Future<bool?> Function(BuildContext, ChatModel)? deleteChatDialog;
@ -76,12 +80,15 @@ class _ChatScreenState extends State<ChatScreen> {
children: [ children: [
for (ChatModel chat in snapshot.data ?? []) ...[ for (ChatModel chat in snapshot.data ?? []) ...[
Builder( Builder(
builder: (context) => Dismissible( builder: (context) => !widget.disableDismiss
? Dismissible(
confirmDismiss: (_) => confirmDismiss: (_) =>
widget.deleteChatDialog?.call(context, chat) ?? widget.deleteChatDialog
?.call(context, chat) ??
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (BuildContext context) => Container( builder: (BuildContext context) =>
Container(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -90,7 +97,8 @@ class _ChatScreenState extends State<ChatScreen> {
chat.canBeDeleted chat.canBeDeleted
? translations ? translations
.deleteChatModalTitle .deleteChatModalTitle
: translations.chatCantBeDeleted, : translations
.chatCantBeDeleted,
style: const TextStyle( style: const TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -101,8 +109,9 @@ class _ChatScreenState extends State<ChatScreen> {
Text( Text(
translations translations
.deleteChatModalDescription, .deleteChatModalDescription,
style: style: const TextStyle(
const TextStyle(fontSize: 16), fontSize: 16,
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
@ -156,62 +165,18 @@ class _ChatScreenState extends State<ChatScreen> {
key: ValueKey( key: ValueKey(
chat.id.toString(), chat.id.toString(),
), ),
child: GestureDetector( child: ChatListItem(
onTap: () => widget.onPressChat(chat), widget: widget,
child: widget.options.chatRowContainerBuilder( chat: chat,
(chat is PersonalChatModel) translations: translations,
? ChatRow( dateFormatter: _dateFormatter,
unreadMessages:
chat.unreadMessages ?? 0,
avatar:
widget.options.userAvatarBuilder(
chat.user,
40.0,
), ),
title: chat.user.fullName ??
translations.anonymousUser,
subTitle: chat.lastMessage != null
? chat.lastMessage
is ChatTextMessageModel
? (chat.lastMessage!
as ChatTextMessageModel)
.text
: '📷 '
'${translations.image}'
: '',
lastUsed: chat.lastUsed != null
? _dateFormatter.format(
date: chat.lastUsed!,
) )
: null, : ChatListItem(
) widget: widget,
: ChatRow( chat: chat,
title: (chat as GroupChatModel).title, translations: translations,
unreadMessages: dateFormatter: _dateFormatter,
chat.unreadMessages ?? 0,
subTitle: chat.lastMessage != null
? chat.lastMessage
is ChatTextMessageModel
? (chat.lastMessage!
as ChatTextMessageModel)
.text
: '📷 '
'${translations.image}'
: '',
avatar:
widget.options.groupAvatarBuilder(
chat.title,
chat.imageUrl,
40.0,
),
lastUsed: chat.lastUsed != null
? _dateFormatter.format(
date: chat.lastUsed!,
)
: null,
),
),
),
), ),
), ),
], ],
@ -232,3 +197,68 @@ class _ChatScreenState extends State<ChatScreen> {
); );
} }
} }
class ChatListItem extends StatelessWidget {
const ChatListItem({
required this.widget,
required this.chat,
required this.translations,
required DateFormatter dateFormatter,
super.key,
}) : _dateFormatter = dateFormatter;
final ChatScreen widget;
final ChatModel chat;
final ChatTranslations translations;
final DateFormatter _dateFormatter;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => widget.onPressChat(chat),
child: widget.options.chatRowContainerBuilder(
(chat is PersonalChatModel)
? ChatRow(
unreadMessages: chat.unreadMessages ?? 0,
avatar: widget.options.userAvatarBuilder(
(chat as PersonalChatModel).user,
40.0,
),
title: (chat as PersonalChatModel).user.fullName ??
translations.anonymousUser,
subTitle: chat.lastMessage != null
? chat.lastMessage is ChatTextMessageModel
? (chat.lastMessage! as ChatTextMessageModel).text
: '📷 '
'${translations.image}'
: '',
lastUsed: chat.lastUsed != null
? _dateFormatter.format(
date: chat.lastUsed!,
)
: null,
)
: ChatRow(
title: (chat as GroupChatModel).title,
unreadMessages: chat.unreadMessages ?? 0,
subTitle: chat.lastMessage != null
? chat.lastMessage is ChatTextMessageModel
? (chat.lastMessage! as ChatTextMessageModel).text
: '📷 '
'${translations.image}'
: '',
avatar: widget.options.groupAvatarBuilder(
(chat as GroupChatModel).title,
(chat as GroupChatModel).imageUrl,
40.0,
),
lastUsed: chat.lastUsed != null
? _dateFormatter.format(
date: chat.lastUsed!,
)
: null,
),
),
);
}
}