mirror of
https://github.com/Iconica-Development/flutter_chat.git
synced 2025-05-19 10:53:51 +02:00
feat: added chat options
This commit is contained in:
parent
7b33ff2bd7
commit
07e29ddd99
6 changed files with 274 additions and 204 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 => (() {
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue