mirror of
https://github.com/Iconica-Development/flutter_chat.git
synced 2025-05-19 02:43:50 +02:00
commit
b7e5c51413
19 changed files with 299 additions and 98 deletions
|
@ -1,3 +1,9 @@
|
||||||
|
## 0.4.0 - November 6 2023
|
||||||
|
|
||||||
|
- Show amount of unread messages per chat
|
||||||
|
- More intuitive chat UI
|
||||||
|
- Fix default profile avatars
|
||||||
|
|
||||||
## 0.3.4 - October 25 2023
|
## 0.3.4 - October 25 2023
|
||||||
|
|
||||||
- Add interface methods for getting amount of unread messages
|
- Add interface methods for getting amount of unread messages
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
name: flutter_community_chat
|
name: flutter_community_chat
|
||||||
description: A new Flutter package project.
|
description: A new Flutter package project.
|
||||||
version: 0.3.4
|
version: 0.4.0
|
||||||
|
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
|
@ -19,12 +19,12 @@ dependencies:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_community_chat
|
url: https://github.com/Iconica-Development/flutter_community_chat
|
||||||
path: packages/flutter_community_chat_view
|
path: packages/flutter_community_chat_view
|
||||||
ref: 0.3.4
|
ref: 0.4.0
|
||||||
flutter_community_chat_interface:
|
flutter_community_chat_interface:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_community_chat
|
url: https://github.com/Iconica-Development/flutter_community_chat
|
||||||
path: packages/flutter_community_chat_interface
|
path: packages/flutter_community_chat_interface
|
||||||
ref: 0.3.4
|
ref: 0.4.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^2.0.0
|
flutter_lints: ^2.0.0
|
||||||
|
|
|
@ -29,6 +29,24 @@ class FirebaseChatService implements ChatService {
|
||||||
_options = options ?? const FirebaseChatOptions();
|
_options = options ?? const FirebaseChatOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StreamSubscription<DocumentSnapshot> _addUnreadChatSubscription(
|
||||||
|
String chatId,
|
||||||
|
String userId,
|
||||||
|
Function(int) onUnreadChatsUpdated,
|
||||||
|
) {
|
||||||
|
var snapshots = _db
|
||||||
|
.collection(_options.usersCollectionName)
|
||||||
|
.doc(userId)
|
||||||
|
.collection('chats')
|
||||||
|
.doc(chatId)
|
||||||
|
.snapshots();
|
||||||
|
|
||||||
|
return snapshots.listen((snapshot) {
|
||||||
|
var data = snapshot.data();
|
||||||
|
onUnreadChatsUpdated(data?['amount_unread_messages'] ?? 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
StreamSubscription<QuerySnapshot> _addChatSubscription(
|
StreamSubscription<QuerySnapshot> _addChatSubscription(
|
||||||
List<String> chatIds,
|
List<String> chatIds,
|
||||||
Function(List<ChatModel>) onReceivedChats,
|
Function(List<ChatModel>) onReceivedChats,
|
||||||
|
@ -79,6 +97,8 @@ class FirebaseChatService implements ChatService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ChatModel? chatModel;
|
||||||
|
|
||||||
if (chatData.personal) {
|
if (chatData.personal) {
|
||||||
var otherUserId = List<String>.from(chatData.users).firstWhere(
|
var otherUserId = List<String>.from(chatData.users).firstWhere(
|
||||||
(element) => element != currentUser?.id,
|
(element) => element != currentUser?.id,
|
||||||
|
@ -86,18 +106,16 @@ class FirebaseChatService implements ChatService {
|
||||||
var otherUser = await _userService.getUser(otherUserId);
|
var otherUser = await _userService.getUser(otherUserId);
|
||||||
|
|
||||||
if (otherUser != null) {
|
if (otherUser != null) {
|
||||||
chats.add(
|
chatModel = PersonalChatModel(
|
||||||
PersonalChatModel(
|
id: chatDoc.id,
|
||||||
id: chatDoc.id,
|
user: otherUser,
|
||||||
user: otherUser,
|
lastMessage: messages.isNotEmpty ? messages.last : null,
|
||||||
lastMessage: messages.isNotEmpty ? messages.last : null,
|
messages: messages,
|
||||||
messages: messages,
|
lastUsed: chatData.lastUsed == null
|
||||||
lastUsed: chatData.lastUsed == null
|
? null
|
||||||
? null
|
: DateTime.fromMillisecondsSinceEpoch(
|
||||||
: DateTime.fromMillisecondsSinceEpoch(
|
chatData.lastUsed!.millisecondsSinceEpoch,
|
||||||
chatData.lastUsed!.millisecondsSinceEpoch,
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -109,22 +127,39 @@ class FirebaseChatService implements ChatService {
|
||||||
users.add(user);
|
users.add(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chats.add(
|
chatModel = GroupChatModel(
|
||||||
GroupChatModel(
|
id: chatDoc.id,
|
||||||
id: chatDoc.id,
|
title: chatData.title ?? '',
|
||||||
title: chatData.title ?? '',
|
imageUrl: chatData.imageUrl ?? '',
|
||||||
imageUrl: chatData.imageUrl ?? '',
|
lastMessage: messages.isNotEmpty ? messages.last : null,
|
||||||
lastMessage: messages.isNotEmpty ? messages.last : null,
|
messages: messages,
|
||||||
messages: messages,
|
users: users,
|
||||||
users: users,
|
lastUsed: chatData.lastUsed == null
|
||||||
lastUsed: chatData.lastUsed == null
|
? null
|
||||||
? null
|
: DateTime.fromMillisecondsSinceEpoch(
|
||||||
: DateTime.fromMillisecondsSinceEpoch(
|
chatData.lastUsed!.millisecondsSinceEpoch,
|
||||||
chatData.lastUsed!.millisecondsSinceEpoch,
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (chatModel != null) {
|
||||||
|
_addUnreadChatSubscription(chatModel.id ?? '', currentUser?.id ?? '',
|
||||||
|
(unreadMessages) {
|
||||||
|
// the chatmodel should be updated to reflect the amount of unread messages
|
||||||
|
if (chatModel is PersonalChatModel) {
|
||||||
|
chatModel = (chatModel as PersonalChatModel)
|
||||||
|
.copyWith(unreadMessages: unreadMessages);
|
||||||
|
} else if (chatModel is GroupChatModel) {
|
||||||
|
chatModel = (chatModel as GroupChatModel)
|
||||||
|
.copyWith(unreadMessages: unreadMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
chats = chats
|
||||||
|
.map((chat) => chat.id == chatModel?.id ? chatModel! : chat)
|
||||||
|
.toList();
|
||||||
|
onReceivedChats(chats);
|
||||||
|
});
|
||||||
|
chats.add(chatModel!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onReceivedChats(chats);
|
onReceivedChats(chats);
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
name: flutter_community_chat_firebase
|
name: flutter_community_chat_firebase
|
||||||
description: A new Flutter package project.
|
description: A new Flutter package project.
|
||||||
version: 0.3.4
|
version: 0.4.0
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
@ -23,7 +23,7 @@ dependencies:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_community_chat
|
url: https://github.com/Iconica-Development/flutter_community_chat
|
||||||
path: packages/flutter_community_chat_interface
|
path: packages/flutter_community_chat_interface
|
||||||
ref: 0.3.4
|
ref: 0.4.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^2.0.0
|
flutter_lints: ^2.0.0
|
||||||
|
|
|
@ -8,12 +8,14 @@ abstract class ChatModel {
|
||||||
ChatModel({
|
ChatModel({
|
||||||
this.id,
|
this.id,
|
||||||
this.messages = const [],
|
this.messages = const [],
|
||||||
|
this.unreadMessages,
|
||||||
this.lastUsed,
|
this.lastUsed,
|
||||||
this.lastMessage,
|
this.lastMessage,
|
||||||
});
|
});
|
||||||
|
|
||||||
String? id;
|
String? id;
|
||||||
List<ChatMessageModel>? messages;
|
List<ChatMessageModel>? messages;
|
||||||
|
int? unreadMessages;
|
||||||
DateTime? lastUsed;
|
DateTime? lastUsed;
|
||||||
ChatMessageModel? lastMessage;
|
ChatMessageModel? lastMessage;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,31 @@ class GroupChatModel extends ChatModel {
|
||||||
super.messages,
|
super.messages,
|
||||||
super.lastUsed,
|
super.lastUsed,
|
||||||
super.lastMessage,
|
super.lastMessage,
|
||||||
|
super.unreadMessages,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final String imageUrl;
|
final String imageUrl;
|
||||||
final List<ChatUserModel> users;
|
final List<ChatUserModel> users;
|
||||||
|
|
||||||
|
GroupChatModel copyWith({
|
||||||
|
String? id,
|
||||||
|
List<ChatMessageModel>? messages,
|
||||||
|
int? unreadMessages,
|
||||||
|
DateTime? lastUsed,
|
||||||
|
ChatMessageModel? lastMessage,
|
||||||
|
String? title,
|
||||||
|
String? imageUrl,
|
||||||
|
List<ChatUserModel>? users,
|
||||||
|
}) =>
|
||||||
|
GroupChatModel(
|
||||||
|
id: id ?? this.id,
|
||||||
|
messages: messages ?? this.messages,
|
||||||
|
unreadMessages: unreadMessages ?? this.unreadMessages,
|
||||||
|
lastUsed: lastUsed ?? this.lastUsed,
|
||||||
|
lastMessage: lastMessage ?? this.lastMessage,
|
||||||
|
title: title ?? this.title,
|
||||||
|
imageUrl: imageUrl ?? this.imageUrl,
|
||||||
|
users: users ?? this.users,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,27 @@ class PersonalChatModel extends ChatModel {
|
||||||
required this.user,
|
required this.user,
|
||||||
super.id,
|
super.id,
|
||||||
super.messages,
|
super.messages,
|
||||||
|
super.unreadMessages,
|
||||||
super.lastUsed,
|
super.lastUsed,
|
||||||
super.lastMessage,
|
super.lastMessage,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ChatUserModel user;
|
final ChatUserModel user;
|
||||||
|
|
||||||
|
PersonalChatModel copyWith({
|
||||||
|
String? id,
|
||||||
|
List<ChatMessageModel>? messages,
|
||||||
|
int? unreadMessages,
|
||||||
|
DateTime? lastUsed,
|
||||||
|
ChatMessageModel? lastMessage,
|
||||||
|
ChatUserModel? user,
|
||||||
|
}) =>
|
||||||
|
PersonalChatModel(
|
||||||
|
id: id ?? this.id,
|
||||||
|
messages: messages ?? this.messages,
|
||||||
|
unreadMessages: unreadMessages ?? this.unreadMessages,
|
||||||
|
lastUsed: lastUsed ?? this.lastUsed,
|
||||||
|
lastMessage: lastMessage ?? this.lastMessage,
|
||||||
|
user: user ?? this.user,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
name: flutter_community_chat_interface
|
name: flutter_community_chat_interface
|
||||||
description: A new Flutter package project.
|
description: A new Flutter package project.
|
||||||
version: 0.3.4
|
version: 0.4.0
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -41,7 +41,22 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
|
||||||
sender: janUser,
|
sender: janUser,
|
||||||
text: 'Met mij gaat het goed, dankje!',
|
text: 'Met mij gaat het goed, dankje!',
|
||||||
timestamp: DateTime.now().subtract(const Duration(days: 2)),
|
timestamp: DateTime.now().subtract(const Duration(days: 2)),
|
||||||
)
|
),
|
||||||
|
ChatTextMessageModel(
|
||||||
|
sender: pietUser,
|
||||||
|
text: 'Mooi zo!',
|
||||||
|
timestamp: DateTime.now().subtract(const Duration(days: 1)),
|
||||||
|
),
|
||||||
|
ChatTextMessageModel(
|
||||||
|
sender: pietUser,
|
||||||
|
text: 'Hoe gaat het?',
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
),
|
||||||
|
ChatTextMessageModel(
|
||||||
|
sender: janUser,
|
||||||
|
text: 'Met mij gaat het goed, dankje!',
|
||||||
|
timestamp: DateTime.now().subtract(const Duration(days: 2)),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
static final chat = PersonalChatModel(
|
static final chat = PersonalChatModel(
|
||||||
|
|
|
@ -18,7 +18,7 @@ dependencies:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_community_chat
|
url: https://github.com/Iconica-Development/flutter_community_chat
|
||||||
path: packages/flutter_community_chat_view
|
path: packages/flutter_community_chat_view
|
||||||
ref: 0.3.4
|
ref: 0.4.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -4,17 +4,23 @@
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
|
import 'package:flutter_community_chat_view/flutter_community_chat_view.dart';
|
||||||
import 'package:flutter_community_chat_view/src/components/chat_image.dart';
|
import 'package:flutter_community_chat_view/src/components/chat_image.dart';
|
||||||
import 'package:flutter_community_chat_view/src/services/date_formatter.dart';
|
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.isFirstMessage,
|
||||||
required this.message,
|
required this.message,
|
||||||
|
required this.userAvatarBuilder,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final ChatTranslations translations;
|
||||||
|
final bool isFirstMessage;
|
||||||
final ChatMessageModel message;
|
final ChatMessageModel message;
|
||||||
|
final UserAvatarBuilder userAvatarBuilder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChatDetailRow> createState() => _ChatDetailRowState();
|
State<ChatDetailRow> createState() => _ChatDetailRowState();
|
||||||
|
@ -25,14 +31,23 @@ class _ChatDetailRowState extends State<ChatDetailRow> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Padding(
|
Widget build(BuildContext context) => Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 43.0),
|
padding: EdgeInsets.only(top: widget.isFirstMessage ? 25.0 : 0),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Opacity(
|
||||||
padding: const EdgeInsets.only(left: 10.0),
|
opacity: widget.isFirstMessage ? 1 : 0,
|
||||||
child: ChatImage(
|
child: Padding(
|
||||||
image: widget.message.sender.imageUrl,
|
padding: const EdgeInsets.only(left: 10.0),
|
||||||
|
child: widget.message.sender.imageUrl != null &&
|
||||||
|
widget.message.sender.imageUrl!.isNotEmpty
|
||||||
|
? ChatImage(
|
||||||
|
image: widget.message.sender.imageUrl!,
|
||||||
|
)
|
||||||
|
: widget.userAvatarBuilder(
|
||||||
|
widget.message.sender,
|
||||||
|
30,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
@ -43,13 +58,33 @@ class _ChatDetailRowState extends State<ChatDetailRow> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
if (widget.isFirstMessage)
|
||||||
widget.message.sender.fullName?.toUpperCase() ?? '',
|
Row(
|
||||||
style: const TextStyle(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
fontSize: 14,
|
children: [
|
||||||
fontWeight: FontWeight.w500,
|
Text(
|
||||||
|
widget.message.sender.fullName?.toUpperCase() ??
|
||||||
|
widget.translations.anonymousUser,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 5.0),
|
||||||
|
child: Text(
|
||||||
|
_dateFormatter.format(
|
||||||
|
date: widget.message.timestamp,
|
||||||
|
showFullDate: true,
|
||||||
|
),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Color(0xFFBBBBBB),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
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
|
||||||
|
@ -65,19 +100,6 @@ class _ChatDetailRowState extends State<ChatDetailRow> {
|
||||||
.imageUrl,
|
.imageUrl,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 5.0),
|
|
||||||
child: Text(
|
|
||||||
_dateFormatter.format(
|
|
||||||
date: widget.message.timestamp,
|
|
||||||
showFullDate: true,
|
|
||||||
),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Color(0xFFBBBBBB),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -7,12 +7,12 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ChatImage extends StatelessWidget {
|
class ChatImage extends StatelessWidget {
|
||||||
const ChatImage({
|
const ChatImage({
|
||||||
super.key,
|
required this.image,
|
||||||
this.image,
|
|
||||||
this.size = 40,
|
this.size = 40,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String? image;
|
final String image;
|
||||||
final double size;
|
final double size;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -24,11 +24,9 @@ class ChatImage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
child: image == null || image!.isEmpty
|
child: CachedNetworkImage(
|
||||||
? const Center(child: Icon(Icons.person))
|
imageUrl: image,
|
||||||
: CachedNetworkImage(
|
fit: BoxFit.cover,
|
||||||
imageUrl: image!,
|
),
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,14 @@ import 'package:flutter/material.dart';
|
||||||
class ChatRow extends StatelessWidget {
|
class ChatRow extends StatelessWidget {
|
||||||
const ChatRow({
|
const ChatRow({
|
||||||
required this.title,
|
required this.title,
|
||||||
|
this.unreadMessages = 0,
|
||||||
this.lastUsed,
|
this.lastUsed,
|
||||||
this.subTitle,
|
this.subTitle,
|
||||||
this.avatar,
|
this.avatar,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
final String title;
|
final String title;
|
||||||
|
final int unreadMessages;
|
||||||
final Widget? avatar;
|
final Widget? avatar;
|
||||||
final String? subTitle;
|
final String? subTitle;
|
||||||
final String? lastUsed;
|
final String? lastUsed;
|
||||||
|
@ -35,9 +37,11 @@ class ChatRow extends StatelessWidget {
|
||||||
title,
|
title,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: unreadMessages > 0
|
||||||
|
? FontWeight.w600
|
||||||
|
: FontWeight.w400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (subTitle != null)
|
if (subTitle != null)
|
||||||
|
@ -45,8 +49,11 @@ class ChatRow extends StatelessWidget {
|
||||||
padding: const EdgeInsets.only(top: 3.0),
|
padding: const EdgeInsets.only(top: 3.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
subTitle!,
|
subTitle!,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
fontWeight: unreadMessages > 0
|
||||||
|
? FontWeight.w600
|
||||||
|
: FontWeight.w400,
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
|
@ -56,12 +63,39 @@ class ChatRow extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Column(
|
||||||
lastUsed ?? '',
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
style: const TextStyle(
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
color: Color(0xFFBBBBBB),
|
children: [
|
||||||
fontSize: 14,
|
Text(
|
||||||
),
|
lastUsed ?? '',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFFBBBBBB),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (unreadMessages > 0) ...[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4.0),
|
||||||
|
child: Container(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
unreadMessages.toString(),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -100,7 +100,7 @@ Widget _createUserAvatar(
|
||||||
double size,
|
double size,
|
||||||
) =>
|
) =>
|
||||||
ChatImage(
|
ChatImage(
|
||||||
image: user.imageUrl,
|
image: user.imageUrl ?? '',
|
||||||
size: size,
|
size: size,
|
||||||
);
|
);
|
||||||
Widget _createGroupAvatar(
|
Widget _createGroupAvatar(
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
class ChatTranslations {
|
class ChatTranslations {
|
||||||
const ChatTranslations({
|
const ChatTranslations({
|
||||||
this.chatsTitle = 'Chats',
|
this.chatsTitle = 'Chats',
|
||||||
|
this.chatsUnread = 'unread',
|
||||||
this.newChatButton = 'Start chat',
|
this.newChatButton = 'Start chat',
|
||||||
this.newChatTitle = 'Start chat',
|
this.newChatTitle = 'Start chat',
|
||||||
this.image = 'Image',
|
this.image = 'Image',
|
||||||
|
@ -19,9 +20,11 @@ class ChatTranslations {
|
||||||
this.deleteChatModalCancel = 'Cancel',
|
this.deleteChatModalCancel = 'Cancel',
|
||||||
this.deleteChatModalConfirm = 'Delete',
|
this.deleteChatModalConfirm = 'Delete',
|
||||||
this.noUsersFound = 'No users found',
|
this.noUsersFound = 'No users found',
|
||||||
|
this.anonymousUser = 'Anonymous user',
|
||||||
});
|
});
|
||||||
|
|
||||||
final String chatsTitle;
|
final String chatsTitle;
|
||||||
|
final String chatsUnread;
|
||||||
final String newChatButton;
|
final String newChatButton;
|
||||||
final String newChatTitle;
|
final String newChatTitle;
|
||||||
final String deleteChatButton;
|
final String deleteChatButton;
|
||||||
|
@ -35,4 +38,7 @@ class ChatTranslations {
|
||||||
final String deleteChatModalCancel;
|
final String deleteChatModalCancel;
|
||||||
final String deleteChatModalConfirm;
|
final String deleteChatModalConfirm;
|
||||||
final String noUsersFound;
|
final String noUsersFound;
|
||||||
|
|
||||||
|
/// Shown when the user has no name
|
||||||
|
final String anonymousUser;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,8 @@ class ChatDetailScreen extends StatefulWidget {
|
||||||
|
|
||||||
class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
||||||
// stream listener that needs to be disposed later
|
// stream listener that needs to be disposed later
|
||||||
late StreamSubscription<List<ChatMessageModel>>? _chatMessagesSubscription;
|
StreamSubscription<List<ChatMessageModel>>? _chatMessagesSubscription;
|
||||||
late Stream<List<ChatMessageModel>>? _chatMessages;
|
Stream<List<ChatMessageModel>>? _chatMessages;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -67,10 +67,11 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
||||||
widget.onReadChat(widget.chat!);
|
widget.onReadChat(widget.chat!);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// set the chat to read when opening the screen
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (widget.chat != null) {
|
if (widget.chat != null) {
|
||||||
widget.onReadChat(widget.chat!);
|
widget.onReadChat(widget.chat!);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -135,7 +136,7 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
||||||
? (widget.chat! as PersonalChatModel)
|
? (widget.chat! as PersonalChatModel)
|
||||||
.user
|
.user
|
||||||
.fullName ??
|
.fullName ??
|
||||||
''
|
widget.translations.anonymousUser
|
||||||
: '',
|
: '',
|
||||||
style: const TextStyle(fontSize: 18),
|
style: const TextStyle(fontSize: 18),
|
||||||
),
|
),
|
||||||
|
@ -150,18 +151,31 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: StreamBuilder<List<ChatMessageModel>>(
|
child: StreamBuilder<List<ChatMessageModel>>(
|
||||||
stream: _chatMessages,
|
stream: _chatMessages,
|
||||||
builder: (BuildContext context, snapshot) => ListView(
|
builder: (BuildContext context, snapshot) {
|
||||||
reverse: true,
|
var messages = snapshot.data ?? widget.chat?.messages ?? [];
|
||||||
padding: const EdgeInsets.only(top: 24.0),
|
ChatMessageModel? lastMessage;
|
||||||
children: [
|
var messageWidgets = <Widget>[];
|
||||||
for (var message
|
|
||||||
in (snapshot.data ?? widget.chat?.messages ?? [])
|
for (var message in messages) {
|
||||||
.reversed)
|
var isFirstMessage = lastMessage == null ||
|
||||||
|
lastMessage.sender.id != message.sender.id;
|
||||||
|
messageWidgets.add(
|
||||||
ChatDetailRow(
|
ChatDetailRow(
|
||||||
|
translations: widget.translations,
|
||||||
message: message,
|
message: message,
|
||||||
|
isFirstMessage: isFirstMessage,
|
||||||
|
userAvatarBuilder: widget.options.userAvatarBuilder,
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
),
|
lastMessage = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
reverse: true,
|
||||||
|
padding: const EdgeInsets.only(top: 24.0),
|
||||||
|
children: messageWidgets.reversed.toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.chat != null)
|
if (widget.chat != null)
|
||||||
|
|
|
@ -13,6 +13,7 @@ class ChatScreen extends StatefulWidget {
|
||||||
required this.onPressStartChat,
|
required this.onPressStartChat,
|
||||||
required this.onPressChat,
|
required this.onPressChat,
|
||||||
required this.onDeleteChat,
|
required this.onDeleteChat,
|
||||||
|
this.unreadChats,
|
||||||
this.translations = const ChatTranslations(),
|
this.translations = const ChatTranslations(),
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
@ -20,6 +21,7 @@ class ChatScreen extends StatefulWidget {
|
||||||
final ChatOptions options;
|
final ChatOptions options;
|
||||||
final ChatTranslations translations;
|
final ChatTranslations translations;
|
||||||
final Stream<List<ChatModel>> chats;
|
final Stream<List<ChatModel>> chats;
|
||||||
|
final Stream<int>? unreadChats;
|
||||||
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;
|
||||||
|
@ -37,6 +39,27 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||||
return widget.options.scaffoldBuilder(
|
return widget.options.scaffoldBuilder(
|
||||||
AppBar(
|
AppBar(
|
||||||
title: Text(translations.chatsTitle),
|
title: Text(translations.chatsTitle),
|
||||||
|
centerTitle: true,
|
||||||
|
actions: widget.unreadChats != null
|
||||||
|
? [
|
||||||
|
StreamBuilder<int>(
|
||||||
|
stream: widget.unreadChats,
|
||||||
|
builder: (BuildContext context, snapshot) => Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 22.0),
|
||||||
|
child: Text(
|
||||||
|
'${snapshot.data ?? 0} ${translations.chatsUnread}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFFBBBBBB),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [],
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -99,12 +122,15 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||||
child: widget.options.chatRowContainerBuilder(
|
child: widget.options.chatRowContainerBuilder(
|
||||||
(chat is PersonalChatModel)
|
(chat is PersonalChatModel)
|
||||||
? ChatRow(
|
? ChatRow(
|
||||||
|
unreadMessages:
|
||||||
|
chat.unreadMessages ?? 0,
|
||||||
avatar:
|
avatar:
|
||||||
widget.options.userAvatarBuilder(
|
widget.options.userAvatarBuilder(
|
||||||
chat.user,
|
chat.user,
|
||||||
40.0,
|
40.0,
|
||||||
),
|
),
|
||||||
title: chat.user.fullName ?? '',
|
title: chat.user.fullName ??
|
||||||
|
translations.anonymousUser,
|
||||||
subTitle: chat.lastMessage != null
|
subTitle: chat.lastMessage != null
|
||||||
? chat.lastMessage
|
? chat.lastMessage
|
||||||
is ChatTextMessageModel
|
is ChatTextMessageModel
|
||||||
|
@ -122,6 +148,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||||
)
|
)
|
||||||
: ChatRow(
|
: ChatRow(
|
||||||
title: (chat as GroupChatModel).title,
|
title: (chat as GroupChatModel).title,
|
||||||
|
unreadMessages:
|
||||||
|
chat.unreadMessages ?? 0,
|
||||||
subTitle: chat.lastMessage != null
|
subTitle: chat.lastMessage != null
|
||||||
? chat.lastMessage
|
? chat.lastMessage
|
||||||
is ChatTextMessageModel
|
is ChatTextMessageModel
|
||||||
|
|
|
@ -90,7 +90,8 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
||||||
user,
|
user,
|
||||||
40.0,
|
40.0,
|
||||||
),
|
),
|
||||||
title: user.fullName ?? '',
|
title:
|
||||||
|
user.fullName ?? widget.translations.anonymousUser,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () => widget.onPressCreateChat(user),
|
onTap: () => widget.onPressCreateChat(user),
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
name: flutter_community_chat_view
|
name: flutter_community_chat_view
|
||||||
description: A standard flutter package.
|
description: A standard flutter package.
|
||||||
version: 0.3.4
|
version: 0.4.0
|
||||||
|
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ dependencies:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_community_chat
|
url: https://github.com/Iconica-Development/flutter_community_chat
|
||||||
path: packages/flutter_community_chat_interface
|
path: packages/flutter_community_chat_interface
|
||||||
ref: 0.3.4
|
ref: 0.4.0
|
||||||
cached_network_image: ^3.2.2
|
cached_network_image: ^3.2.2
|
||||||
flutter_image_picker:
|
flutter_image_picker:
|
||||||
git:
|
git:
|
||||||
|
|
Loading…
Reference in a new issue