mirror of
https://github.com/Iconica-Development/flutter_chat.git
synced 2025-05-19 10:53:51 +02:00
feat: improve user story
This commit is contained in:
parent
b3b9ceb07d
commit
4760e281ee
12 changed files with 590 additions and 296 deletions
|
@ -6,3 +6,6 @@ library flutter_community_chat;
|
||||||
|
|
||||||
export 'package:flutter_community_chat_view/flutter_community_chat_view.dart';
|
export 'package:flutter_community_chat_view/flutter_community_chat_view.dart';
|
||||||
export 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
|
export 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
|
||||||
|
export 'package:flutter_community_chat/src/routes.dart';
|
||||||
|
export 'package:flutter_community_chat/src/models/community_chat_configuration.dart';
|
||||||
|
export 'package:flutter_community_chat/src/flutter_community_chat_userstory.dart';
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Iconica
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_community_chat/src/models/community_chat_configuration.dart';
|
||||||
|
import 'package:flutter_community_chat/src/go_router.dart';
|
||||||
|
import 'package:flutter_community_chat/src/routes.dart';
|
||||||
|
import 'package:flutter_community_chat_view/flutter_community_chat_view.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
List<GoRoute> getCommunityChatStoryRoutes(
|
||||||
|
CommunityChatUserStoryConfiguration configuration,
|
||||||
|
) =>
|
||||||
|
<GoRoute>[
|
||||||
|
GoRoute(
|
||||||
|
path: CommunityChatUserStoryRoutes.chatScreen,
|
||||||
|
pageBuilder: (context, state) {
|
||||||
|
var chatScreen = ChatScreen(
|
||||||
|
service: configuration.service,
|
||||||
|
options: configuration.chatOptionsBuilder(context),
|
||||||
|
onNoChats: () =>
|
||||||
|
context.push(CommunityChatUserStoryRoutes.newChatScreen),
|
||||||
|
onPressStartChat: () =>
|
||||||
|
configuration.onPressStartChat?.call() ??
|
||||||
|
context.push(CommunityChatUserStoryRoutes.newChatScreen),
|
||||||
|
onPressChat: (chat) =>
|
||||||
|
configuration.onPressChat?.call(context, chat) ??
|
||||||
|
context.push(
|
||||||
|
CommunityChatUserStoryRoutes.chatDetailViewPath(chat.id!)),
|
||||||
|
onDeleteChat: (chat) =>
|
||||||
|
configuration.onDeleteChat?.call(context, chat),
|
||||||
|
deleteChatDialog: configuration.deleteChatDialog,
|
||||||
|
translations: configuration.translations,
|
||||||
|
);
|
||||||
|
return buildScreenWithoutTransition(
|
||||||
|
context: context,
|
||||||
|
state: state,
|
||||||
|
child: configuration.chatPageBuilder?.call(
|
||||||
|
context,
|
||||||
|
chatScreen,
|
||||||
|
) ??
|
||||||
|
Scaffold(
|
||||||
|
body: chatScreen,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: CommunityChatUserStoryRoutes.chatDetailScreen,
|
||||||
|
pageBuilder: (context, state) {
|
||||||
|
var chatId = state.pathParameters['id'];
|
||||||
|
var chat = PersonalChatModel(user: ChatUserModel(), id: chatId);
|
||||||
|
var chatDetailScreen = ChatDetailScreen(
|
||||||
|
options: configuration.chatOptionsBuilder(context),
|
||||||
|
translations: configuration.translations,
|
||||||
|
chatUserService: configuration.userService,
|
||||||
|
service: configuration.service,
|
||||||
|
messageService: configuration.messageService,
|
||||||
|
chat: chat,
|
||||||
|
onMessageSubmit: (message) async {
|
||||||
|
configuration.onMessageSubmit?.call(message) ??
|
||||||
|
configuration.messageService
|
||||||
|
.sendTextMessage(chat: chat, text: message);
|
||||||
|
configuration.afterMessageSent?.call(chat);
|
||||||
|
},
|
||||||
|
onUploadImage: (image) async {
|
||||||
|
configuration.onUploadImage?.call(image) ??
|
||||||
|
configuration.messageService
|
||||||
|
.sendImageMessage(chat: chat, image: image);
|
||||||
|
configuration.afterMessageSent?.call(chat);
|
||||||
|
},
|
||||||
|
onReadChat: (chat) =>
|
||||||
|
configuration.onReadChat?.call(chat) ??
|
||||||
|
configuration.service.readChat(chat),
|
||||||
|
onPressChatTitle: (context, chat) =>
|
||||||
|
configuration.onPressChatTitle?.call(context, chat),
|
||||||
|
iconColor: configuration.iconColor,
|
||||||
|
);
|
||||||
|
return buildScreenWithoutTransition(
|
||||||
|
context: context,
|
||||||
|
state: state,
|
||||||
|
child: configuration.chatPageBuilder?.call(
|
||||||
|
context,
|
||||||
|
chatDetailScreen,
|
||||||
|
) ??
|
||||||
|
Scaffold(
|
||||||
|
body: chatDetailScreen,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: CommunityChatUserStoryRoutes.newChatScreen,
|
||||||
|
pageBuilder: (context, state) {
|
||||||
|
var newChatScreen = NewChatScreen(
|
||||||
|
options: configuration.chatOptionsBuilder(context),
|
||||||
|
translations: configuration.translations,
|
||||||
|
service: configuration.service,
|
||||||
|
userService: configuration.userService,
|
||||||
|
onPressCreateChat: (user) async {
|
||||||
|
configuration.onPressCreateChat?.call(user);
|
||||||
|
if (configuration.onPressChat != null) return;
|
||||||
|
var chat = await configuration.service.getChatByUser(user);
|
||||||
|
if (chat.id == null) {
|
||||||
|
chat = await configuration.service.storeChatIfNot(
|
||||||
|
PersonalChatModel(
|
||||||
|
user: user,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (context.mounted) {
|
||||||
|
context.push(CommunityChatUserStoryRoutes.chatDetailViewPath(
|
||||||
|
chat.id ?? ''));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return buildScreenWithoutTransition(
|
||||||
|
context: context,
|
||||||
|
state: state,
|
||||||
|
child: configuration.chatPageBuilder?.call(
|
||||||
|
context,
|
||||||
|
newChatScreen,
|
||||||
|
) ??
|
||||||
|
Scaffold(
|
||||||
|
body: newChatScreen,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
30
packages/flutter_community_chat/lib/src/go_router.dart
Normal file
30
packages/flutter_community_chat/lib/src/go_router.dart
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Iconica
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
CustomTransitionPage buildScreenWithFadeTransition<T>({
|
||||||
|
required BuildContext context,
|
||||||
|
required GoRouterState state,
|
||||||
|
required Widget child,
|
||||||
|
}) =>
|
||||||
|
CustomTransitionPage<T>(
|
||||||
|
key: state.pageKey,
|
||||||
|
child: child,
|
||||||
|
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
|
||||||
|
FadeTransition(opacity: animation, child: child),
|
||||||
|
);
|
||||||
|
|
||||||
|
CustomTransitionPage buildScreenWithoutTransition<T>({
|
||||||
|
required BuildContext context,
|
||||||
|
required GoRouterState state,
|
||||||
|
required Widget child,
|
||||||
|
}) =>
|
||||||
|
CustomTransitionPage<T>(
|
||||||
|
key: state.pageKey,
|
||||||
|
child: child,
|
||||||
|
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
|
||||||
|
child,
|
||||||
|
);
|
|
@ -0,0 +1,57 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Iconica
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_community_chat_view/flutter_community_chat_view.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class CommunityChatUserStoryConfiguration {
|
||||||
|
const CommunityChatUserStoryConfiguration({
|
||||||
|
required this.userService,
|
||||||
|
required this.messageService,
|
||||||
|
required this.service,
|
||||||
|
required this.chatOptionsBuilder,
|
||||||
|
this.onPressStartChat,
|
||||||
|
this.onPressChat,
|
||||||
|
this.onDeleteChat,
|
||||||
|
this.onMessageSubmit,
|
||||||
|
this.onReadChat,
|
||||||
|
this.onUploadImage,
|
||||||
|
this.onPressCreateChat,
|
||||||
|
this.iconColor,
|
||||||
|
this.deleteChatDialog,
|
||||||
|
this.disableDismissForPermanentChats = false,
|
||||||
|
this.routeToNewChatIfEmpty = true,
|
||||||
|
this.translations = const ChatTranslations(),
|
||||||
|
this.chatPageBuilder,
|
||||||
|
this.onPressChatTitle,
|
||||||
|
this.afterMessageSent,
|
||||||
|
});
|
||||||
|
final ChatService service;
|
||||||
|
final ChatUserService userService;
|
||||||
|
final MessageService messageService;
|
||||||
|
final Function(BuildContext, ChatModel)? onPressChat;
|
||||||
|
final Function(BuildContext, ChatModel)? onDeleteChat;
|
||||||
|
final ChatTranslations translations;
|
||||||
|
final bool disableDismissForPermanentChats;
|
||||||
|
final Future<void> Function(Uint8List image)? onUploadImage;
|
||||||
|
final Future<void> Function(String text)? onMessageSubmit;
|
||||||
|
|
||||||
|
/// Called after a new message is sent. This can be used to do something extra like sending a push notification.
|
||||||
|
final Function(ChatModel chat)? afterMessageSent;
|
||||||
|
final Future<void> Function(ChatModel chat)? onReadChat;
|
||||||
|
final Function(ChatUserModel)? onPressCreateChat;
|
||||||
|
final ChatOptions Function(BuildContext context) chatOptionsBuilder;
|
||||||
|
|
||||||
|
/// If true, the user will be routed to the new chat screen if there are no chats.
|
||||||
|
final bool routeToNewChatIfEmpty;
|
||||||
|
|
||||||
|
final Future<bool?> Function(BuildContext, ChatModel)? deleteChatDialog;
|
||||||
|
final Function(BuildContext context, ChatModel chat)? onPressChatTitle;
|
||||||
|
final Color? iconColor;
|
||||||
|
final Widget Function(BuildContext context, Widget child)? chatPageBuilder;
|
||||||
|
final Function()? onPressStartChat;
|
||||||
|
}
|
10
packages/flutter_community_chat/lib/src/routes.dart
Normal file
10
packages/flutter_community_chat/lib/src/routes.dart
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Iconica
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
mixin CommunityChatUserStoryRoutes {
|
||||||
|
static const String chatScreen = '/chat';
|
||||||
|
static String chatDetailViewPath(String chatId) => '/chat-detail/$chatId';
|
||||||
|
static const String chatDetailScreen = '/chat-detail/:id';
|
||||||
|
static const String newChatScreen = '/new-chat';
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
go_router: ^12.1.1
|
||||||
flutter_community_chat_view:
|
flutter_community_chat_view:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_community_chat
|
url: https://github.com/Iconica-Development/flutter_community_chat
|
||||||
|
|
|
@ -234,6 +234,7 @@ class FirebaseChatService implements ChatService {
|
||||||
controller = StreamController(
|
controller = StreamController(
|
||||||
onListen: () async {
|
onListen: () async {
|
||||||
var currentUser = await _userService.getCurrentUser();
|
var currentUser = await _userService.getCurrentUser();
|
||||||
|
|
||||||
var userSnapshot = await _db
|
var userSnapshot = await _db
|
||||||
.collection(_options.usersCollectionName)
|
.collection(_options.usersCollectionName)
|
||||||
.doc(currentUser?.id)
|
.doc(currentUser?.id)
|
||||||
|
@ -264,7 +265,7 @@ class FirebaseChatService implements ChatService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ChatModel> getOrCreateChatByUser(ChatUserModel user) async {
|
Future<ChatModel> getChatByUser(ChatUserModel user) async {
|
||||||
var currentUser = await _userService.getCurrentUser();
|
var currentUser = await _userService.getCurrentUser();
|
||||||
var collection = await _db
|
var collection = await _db
|
||||||
.collection(_options.usersCollectionName)
|
.collection(_options.usersCollectionName)
|
||||||
|
|
|
@ -2,8 +2,7 @@ import 'package:flutter_community_chat_interface/flutter_community_chat_interfac
|
||||||
|
|
||||||
abstract class ChatService {
|
abstract class ChatService {
|
||||||
Stream<List<ChatModel>> getChatsStream();
|
Stream<List<ChatModel>> getChatsStream();
|
||||||
@Deprecated('Use getChatById instead')
|
Future<ChatModel> getChatByUser(ChatUserModel user);
|
||||||
Future<ChatModel> getOrCreateChatByUser(ChatUserModel user);
|
|
||||||
Future<ChatModel> getChatById(String id);
|
Future<ChatModel> getChatById(String id);
|
||||||
Future<void> deleteChat(ChatModel chat);
|
Future<void> deleteChat(ChatModel chat);
|
||||||
Future<void> readChat(ChatModel chat);
|
Future<void> readChat(ChatModel chat);
|
||||||
|
|
|
@ -115,37 +115,37 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var options = const ChatOptions();
|
// return ChatScreen(
|
||||||
return ChatScreen(
|
// service: ,
|
||||||
chats: chatStream,
|
// options: options,
|
||||||
options: options,
|
// onPressChat: (chat) => Navigator.of(context).push(
|
||||||
onPressChat: (chat) => Navigator.of(context).push(
|
// MaterialPageRoute(
|
||||||
MaterialPageRoute(
|
// builder: (context) => ChatDetailScreen(
|
||||||
builder: (context) => ChatDetailScreen(
|
// userId: 'piet',
|
||||||
userId: 'piet',
|
// chat: chat,
|
||||||
chat: chat,
|
// chatMessages: messageStream,
|
||||||
chatMessages: messageStream,
|
// options: options,
|
||||||
options: options,
|
// onMessageSubmit: (text) async {
|
||||||
onMessageSubmit: (text) async {
|
// return Future.delayed(
|
||||||
return Future.delayed(
|
// const Duration(
|
||||||
const Duration(
|
// milliseconds: 500,
|
||||||
milliseconds: 500,
|
// ),
|
||||||
),
|
// () => debugPrint('onMessageSubmit'),
|
||||||
() => debugPrint('onMessageSubmit'),
|
// );
|
||||||
);
|
// },
|
||||||
},
|
// onReadChat: (chat) async {},
|
||||||
onReadChat: (chat) async {},
|
// onUploadImage: (image) async {},
|
||||||
onUploadImage: (image) async {},
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// onDeleteChat: (chat) => Future.delayed(
|
||||||
onDeleteChat: (chat) => Future.delayed(
|
// const Duration(
|
||||||
const Duration(
|
// milliseconds: 500,
|
||||||
milliseconds: 500,
|
// ),
|
||||||
),
|
// () => debugPrint('onDeleteChat'),
|
||||||
() => debugPrint('onDeleteChat'),
|
// ),
|
||||||
),
|
// onPressStartChat: () => debugPrint('onPressStartChat'),
|
||||||
onPressStartChat: () => debugPrint('onPressStartChat'),
|
// );
|
||||||
);
|
return const Text('Example ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,15 @@ import 'package:flutter_community_chat_view/src/components/image_loading_snackba
|
||||||
|
|
||||||
class ChatDetailScreen extends StatefulWidget {
|
class ChatDetailScreen extends StatefulWidget {
|
||||||
const ChatDetailScreen({
|
const ChatDetailScreen({
|
||||||
required this.userId,
|
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.onMessageSubmit,
|
required this.onMessageSubmit,
|
||||||
required this.onUploadImage,
|
required this.onUploadImage,
|
||||||
required this.onReadChat,
|
required this.onReadChat,
|
||||||
|
required this.service,
|
||||||
|
required this.chatUserService,
|
||||||
|
required this.messageService,
|
||||||
this.translations = const ChatTranslations(),
|
this.translations = const ChatTranslations(),
|
||||||
this.chat,
|
this.chat,
|
||||||
this.chatMessages,
|
|
||||||
this.onPressChatTitle,
|
this.onPressChatTitle,
|
||||||
this.iconColor,
|
this.iconColor,
|
||||||
this.showTime = false,
|
this.showTime = false,
|
||||||
|
@ -30,21 +31,22 @@ class ChatDetailScreen extends StatefulWidget {
|
||||||
final ChatModel? chat;
|
final ChatModel? chat;
|
||||||
|
|
||||||
/// The id of the current user that is viewing the chat.
|
/// The id of the current user that is viewing the chat.
|
||||||
final String userId;
|
|
||||||
|
|
||||||
final ChatOptions options;
|
final ChatOptions options;
|
||||||
final ChatTranslations translations;
|
final ChatTranslations translations;
|
||||||
final Stream<List<ChatMessageModel>>? chatMessages;
|
|
||||||
final Future<void> Function(Uint8List image) onUploadImage;
|
final Future<void> Function(Uint8List image) onUploadImage;
|
||||||
final Future<void> Function(String text) onMessageSubmit;
|
final Future<void> Function(String text) onMessageSubmit;
|
||||||
// called at the start of the screen to set the chat to read
|
// called at the start of the screen to set the chat to read
|
||||||
// or when a new message is received
|
// or when a new message is received
|
||||||
final Future<void> Function(ChatModel chat) onReadChat;
|
final Future<void> Function(ChatModel chat) onReadChat;
|
||||||
final VoidCallback? onPressChatTitle;
|
final Function(BuildContext context, ChatModel chat)? onPressChatTitle;
|
||||||
|
|
||||||
/// 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;
|
final bool showTime;
|
||||||
|
final ChatService service;
|
||||||
|
final ChatUserService chatUserService;
|
||||||
|
final MessageService messageService;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChatDetailScreen> createState() => _ChatDetailScreenState();
|
State<ChatDetailScreen> createState() => _ChatDetailScreenState();
|
||||||
|
@ -54,17 +56,26 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
||||||
// stream listener that needs to be disposed later
|
// stream listener that needs to be disposed later
|
||||||
StreamSubscription<List<ChatMessageModel>>? _chatMessagesSubscription;
|
StreamSubscription<List<ChatMessageModel>>? _chatMessagesSubscription;
|
||||||
Stream<List<ChatMessageModel>>? _chatMessages;
|
Stream<List<ChatMessageModel>>? _chatMessages;
|
||||||
|
ChatModel? chat;
|
||||||
|
ChatUserModel? currentUser;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// create a broadcast stream from the chat messages
|
// create a broadcast stream from the chat messages
|
||||||
_chatMessages = widget.chatMessages?.asBroadcastStream();
|
if (widget.chat != null) {
|
||||||
|
_chatMessages = widget.messageService
|
||||||
|
.getMessagesStream(widget.chat!)
|
||||||
|
.asBroadcastStream();
|
||||||
|
}
|
||||||
_chatMessagesSubscription = _chatMessages?.listen((event) {
|
_chatMessagesSubscription = _chatMessages?.listen((event) {
|
||||||
// check if the last message is from the current user
|
// check if the last message is from the current user
|
||||||
// if so, set the chat to read
|
// if so, set the chat to read
|
||||||
|
Future.delayed(Duration.zero, () async {
|
||||||
|
currentUser = await widget.chatUserService.getCurrentUser();
|
||||||
|
});
|
||||||
if (event.isNotEmpty &&
|
if (event.isNotEmpty &&
|
||||||
event.last.sender.id != widget.userId &&
|
event.last.sender.id != currentUser?.id &&
|
||||||
widget.chat != null) {
|
widget.chat != null) {
|
||||||
widget.onReadChat(widget.chat!);
|
widget.onReadChat(widget.chat!);
|
||||||
}
|
}
|
||||||
|
@ -106,92 +117,97 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return Scaffold(
|
return FutureBuilder<ChatModel>(
|
||||||
appBar: AppBar(
|
future: widget.service.getChatById(widget.chat?.id ?? ''),
|
||||||
centerTitle: true,
|
builder: (context, AsyncSnapshot<ChatModel> snapshot) {
|
||||||
title: GestureDetector(
|
var chatModel = snapshot.data;
|
||||||
onTap: widget.onPressChatTitle,
|
|
||||||
child: Row(
|
return Scaffold(
|
||||||
mainAxisSize: MainAxisSize.min,
|
appBar: AppBar(
|
||||||
children: widget.chat == null
|
centerTitle: true,
|
||||||
? []
|
title: GestureDetector(
|
||||||
: [
|
onTap: () => widget.onPressChatTitle?.call(context, chatModel!),
|
||||||
if (widget.chat is GroupChatModel) ...[
|
child: Row(
|
||||||
widget.options.groupAvatarBuilder(
|
mainAxisSize: MainAxisSize.min,
|
||||||
(widget.chat! as GroupChatModel).title,
|
children: widget.chat == null
|
||||||
(widget.chat! as GroupChatModel).imageUrl,
|
? []
|
||||||
36.0,
|
: [
|
||||||
),
|
if (chatModel is GroupChatModel) ...[
|
||||||
] else if (widget.chat is PersonalChatModel) ...[
|
widget.options.groupAvatarBuilder(
|
||||||
widget.options.userAvatarBuilder(
|
chatModel.title,
|
||||||
(widget.chat! as PersonalChatModel).user,
|
chatModel.imageUrl,
|
||||||
36.0,
|
36.0,
|
||||||
),
|
),
|
||||||
] else
|
] else if (chatModel is PersonalChatModel) ...[
|
||||||
...[],
|
widget.options.userAvatarBuilder(
|
||||||
Expanded(
|
chatModel.user,
|
||||||
child: Padding(
|
36.0,
|
||||||
padding: const EdgeInsets.only(left: 15.5),
|
),
|
||||||
child: Text(
|
] else
|
||||||
(widget.chat is GroupChatModel)
|
...[],
|
||||||
? (widget.chat! as GroupChatModel).title
|
Expanded(
|
||||||
: (widget.chat is PersonalChatModel)
|
child: Padding(
|
||||||
? (widget.chat! as PersonalChatModel)
|
padding: const EdgeInsets.only(left: 15.5),
|
||||||
.user
|
child: Text(
|
||||||
.fullName ??
|
(chatModel is GroupChatModel)
|
||||||
widget.translations.anonymousUser
|
? chatModel.title
|
||||||
: '',
|
: (chatModel is PersonalChatModel)
|
||||||
style: const TextStyle(fontSize: 18),
|
? chatModel.user.fullName ??
|
||||||
|
widget.translations.anonymousUser
|
||||||
|
: '',
|
||||||
|
style: const TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: StreamBuilder<List<ChatMessageModel>>(
|
|
||||||
stream: _chatMessages,
|
|
||||||
builder: (BuildContext context, snapshot) {
|
|
||||||
var messages = snapshot.data ?? widget.chat?.messages ?? [];
|
|
||||||
ChatMessageModel? previousMessage;
|
|
||||||
|
|
||||||
var messageWidgets = <Widget>[];
|
|
||||||
|
|
||||||
for (var message in messages) {
|
|
||||||
messageWidgets.add(
|
|
||||||
ChatDetailRow(
|
|
||||||
previousMessage: previousMessage,
|
|
||||||
showTime: widget.showTime,
|
|
||||||
translations: widget.translations,
|
|
||||||
message: message,
|
|
||||||
userAvatarBuilder: widget.options.userAvatarBuilder,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
previousMessage = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListView(
|
|
||||||
reverse: true,
|
|
||||||
padding: const EdgeInsets.only(top: 24.0),
|
|
||||||
children: messageWidgets.reversed.toList(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.chat != null)
|
body: Column(
|
||||||
ChatBottom(
|
children: [
|
||||||
chat: widget.chat!,
|
Expanded(
|
||||||
messageInputBuilder: widget.options.messageInputBuilder,
|
child: StreamBuilder<List<ChatMessageModel>>(
|
||||||
onPressSelectImage: onPressSelectImage,
|
stream: _chatMessages,
|
||||||
onMessageSubmit: widget.onMessageSubmit,
|
builder: (context, snapshot) {
|
||||||
translations: widget.translations,
|
var messages = snapshot.data ?? chatModel?.messages ?? [];
|
||||||
iconColor: widget.iconColor,
|
ChatMessageModel? previousMessage;
|
||||||
),
|
|
||||||
],
|
var messageWidgets = <Widget>[];
|
||||||
),
|
|
||||||
|
for (var message in messages) {
|
||||||
|
messageWidgets.add(
|
||||||
|
ChatDetailRow(
|
||||||
|
previousMessage: previousMessage,
|
||||||
|
showTime: widget.showTime,
|
||||||
|
translations: widget.translations,
|
||||||
|
message: message,
|
||||||
|
userAvatarBuilder: widget.options.userAvatarBuilder,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
previousMessage = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
reverse: true,
|
||||||
|
padding: const EdgeInsets.only(top: 24.0),
|
||||||
|
children: messageWidgets.reversed.toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (chatModel != null)
|
||||||
|
ChatBottom(
|
||||||
|
chat: chatModel,
|
||||||
|
messageInputBuilder: widget.options.messageInputBuilder,
|
||||||
|
onPressSelectImage: onPressSelectImage,
|
||||||
|
onMessageSubmit: widget.onMessageSubmit,
|
||||||
|
translations: widget.translations,
|
||||||
|
iconColor: widget.iconColor,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,12 @@ import 'package:flutter_community_chat_view/src/services/date_formatter.dart';
|
||||||
class ChatScreen extends StatefulWidget {
|
class ChatScreen extends StatefulWidget {
|
||||||
const ChatScreen({
|
const ChatScreen({
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.chats,
|
|
||||||
required this.onPressStartChat,
|
required this.onPressStartChat,
|
||||||
required this.onPressChat,
|
required this.onPressChat,
|
||||||
required this.onDeleteChat,
|
required this.onDeleteChat,
|
||||||
|
required this.service,
|
||||||
|
this.onNoChats,
|
||||||
this.deleteChatDialog,
|
this.deleteChatDialog,
|
||||||
this.unreadChats,
|
|
||||||
this.translations = const ChatTranslations(),
|
this.translations = const ChatTranslations(),
|
||||||
this.disableDismissForPermanentChats = false,
|
this.disableDismissForPermanentChats = false,
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -24,11 +24,12 @@ class ChatScreen extends StatefulWidget {
|
||||||
|
|
||||||
final ChatOptions options;
|
final ChatOptions options;
|
||||||
final ChatTranslations translations;
|
final ChatTranslations translations;
|
||||||
final Stream<List<ChatModel>> chats;
|
final ChatService service;
|
||||||
final Stream<int>? unreadChats;
|
|
||||||
final VoidCallback? onPressStartChat;
|
final VoidCallback? onPressStartChat;
|
||||||
|
final VoidCallback? onNoChats;
|
||||||
final void Function(ChatModel chat) onDeleteChat;
|
final void Function(ChatModel chat) onDeleteChat;
|
||||||
final void Function(ChatModel chat) onPressChat;
|
final void Function(ChatModel chat) onPressChat;
|
||||||
|
|
||||||
/// Disable the swipe to dismiss feature for chats that are not deletable
|
/// Disable the swipe to dismiss feature for chats that are not deletable
|
||||||
final bool disableDismissForPermanentChats;
|
final bool disableDismissForPermanentChats;
|
||||||
|
|
||||||
|
@ -40,6 +41,7 @@ class ChatScreen extends StatefulWidget {
|
||||||
|
|
||||||
class _ChatScreenState extends State<ChatScreen> {
|
class _ChatScreenState extends State<ChatScreen> {
|
||||||
final DateFormatter _dateFormatter = DateFormatter();
|
final DateFormatter _dateFormatter = DateFormatter();
|
||||||
|
bool _hasCalledOnNoChats = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -48,26 +50,24 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||||
AppBar(
|
AppBar(
|
||||||
title: Text(translations.chatsTitle),
|
title: Text(translations.chatsTitle),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
actions: widget.unreadChats != null
|
actions: [
|
||||||
? [
|
StreamBuilder<int>(
|
||||||
StreamBuilder<int>(
|
stream: widget.service.getUnreadChatsCountStream(),
|
||||||
stream: widget.unreadChats,
|
builder: (BuildContext context, snapshot) => Align(
|
||||||
builder: (BuildContext context, snapshot) => Align(
|
alignment: Alignment.centerRight,
|
||||||
alignment: Alignment.centerRight,
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.only(right: 22.0),
|
||||||
padding: const EdgeInsets.only(right: 22.0),
|
child: Text(
|
||||||
child: Text(
|
'${snapshot.data ?? 0} ${translations.chatsUnread}',
|
||||||
'${snapshot.data ?? 0} ${translations.chatsUnread}',
|
style: const TextStyle(
|
||||||
style: const TextStyle(
|
color: Color(0xFFBBBBBB),
|
||||||
color: Color(0xFFBBBBBB),
|
fontSize: 14,
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
),
|
||||||
: [],
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -76,113 +76,133 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||||
padding: const EdgeInsets.only(top: 15.0),
|
padding: const EdgeInsets.only(top: 15.0),
|
||||||
children: [
|
children: [
|
||||||
StreamBuilder<List<ChatModel>>(
|
StreamBuilder<List<ChatModel>>(
|
||||||
stream: widget.chats,
|
stream: widget.service.getChatsStream(),
|
||||||
builder: (BuildContext context, snapshot) => Column(
|
builder: (BuildContext context, snapshot) {
|
||||||
children: [
|
// if the stream is done, empty and noChats is set we should call that
|
||||||
for (ChatModel chat in snapshot.data ?? []) ...[
|
if (snapshot.connectionState == ConnectionState.done &&
|
||||||
Builder(
|
(snapshot.data?.isEmpty ?? true)) {
|
||||||
builder: (context) => !(widget.disableDismissForPermanentChats && !chat.canBeDeleted)
|
if (widget.onNoChats != null && !_hasCalledOnNoChats) {
|
||||||
? Dismissible(
|
_hasCalledOnNoChats = true; // Set the flag to true
|
||||||
confirmDismiss: (_) =>
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
widget.deleteChatDialog
|
widget.onNoChats!.call();
|
||||||
?.call(context, chat) ??
|
});
|
||||||
showModalBottomSheet(
|
}
|
||||||
context: context,
|
} else {
|
||||||
builder: (BuildContext context) =>
|
_hasCalledOnNoChats =
|
||||||
Container(
|
false; // Reset the flag if there are chats
|
||||||
padding: const EdgeInsets.all(16.0),
|
}
|
||||||
child: Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
for (ChatModel chat in snapshot.data ?? []) ...[
|
||||||
Text(
|
Builder(
|
||||||
chat.canBeDeleted
|
builder: (context) => !(widget
|
||||||
? translations
|
.disableDismissForPermanentChats &&
|
||||||
.deleteChatModalTitle
|
!chat.canBeDeleted)
|
||||||
: translations
|
? Dismissible(
|
||||||
.chatCantBeDeleted,
|
confirmDismiss: (_) =>
|
||||||
style: const TextStyle(
|
widget.deleteChatDialog
|
||||||
fontSize: 20,
|
?.call(context, chat) ??
|
||||||
fontWeight: FontWeight.bold,
|
showModalBottomSheet(
|
||||||
),
|
context: context,
|
||||||
),
|
builder: (BuildContext context) =>
|
||||||
const SizedBox(height: 16),
|
Container(
|
||||||
if (chat.canBeDeleted)
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
Text(
|
Text(
|
||||||
translations
|
chat.canBeDeleted
|
||||||
.deleteChatModalDescription,
|
? translations
|
||||||
|
.deleteChatModalTitle
|
||||||
|
: translations
|
||||||
|
.chatCantBeDeleted,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Row(
|
if (chat.canBeDeleted)
|
||||||
mainAxisAlignment:
|
Text(
|
||||||
MainAxisAlignment.center,
|
translations
|
||||||
children: [
|
.deleteChatModalDescription,
|
||||||
TextButton(
|
style: const TextStyle(
|
||||||
child: Text(
|
fontSize: 16,
|
||||||
translations
|
|
||||||
.deleteChatModalCancel,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
onPressed: () =>
|
|
||||||
Navigator.of(context)
|
|
||||||
.pop(false),
|
|
||||||
),
|
),
|
||||||
if (chat.canBeDeleted)
|
const SizedBox(height: 16),
|
||||||
ElevatedButton(
|
Row(
|
||||||
onPressed: () =>
|
mainAxisAlignment:
|
||||||
Navigator.of(context)
|
MainAxisAlignment.center,
|
||||||
.pop(true),
|
children: [
|
||||||
|
TextButton(
|
||||||
child: Text(
|
child: Text(
|
||||||
translations
|
translations
|
||||||
.deleteChatModalConfirm,
|
.deleteChatModalCancel,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
onPressed: () =>
|
||||||
|
Navigator.of(context)
|
||||||
|
.pop(false),
|
||||||
),
|
),
|
||||||
],
|
if (chat.canBeDeleted)
|
||||||
),
|
ElevatedButton(
|
||||||
],
|
onPressed: () =>
|
||||||
|
Navigator.of(
|
||||||
|
context,
|
||||||
|
).pop(true),
|
||||||
|
child: Text(
|
||||||
|
translations
|
||||||
|
.deleteChatModalConfirm,
|
||||||
|
style:
|
||||||
|
const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onDismissed: (_) =>
|
||||||
|
widget.onDeleteChat(chat),
|
||||||
|
background: Container(
|
||||||
|
color: Colors.red,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
translations.deleteChatButton,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onDismissed: (_) => widget.onDeleteChat(chat),
|
|
||||||
background: Container(
|
|
||||||
color: Colors.red,
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
translations.deleteChatButton,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
key: ValueKey(
|
||||||
key: ValueKey(
|
chat.id.toString(),
|
||||||
chat.id.toString(),
|
),
|
||||||
),
|
child: ChatListItem(
|
||||||
child: ChatListItem(
|
widget: widget,
|
||||||
|
chat: chat,
|
||||||
|
translations: translations,
|
||||||
|
dateFormatter: _dateFormatter,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ChatListItem(
|
||||||
widget: widget,
|
widget: widget,
|
||||||
chat: chat,
|
chat: chat,
|
||||||
translations: translations,
|
translations: translations,
|
||||||
dateFormatter: _dateFormatter,
|
dateFormatter: _dateFormatter,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
: ChatListItem(
|
],
|
||||||
widget: widget,
|
|
||||||
chat: chat,
|
|
||||||
translations: translations,
|
|
||||||
dateFormatter: _dateFormatter,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
],
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -8,15 +8,17 @@ import 'package:flutter_community_chat_view/flutter_community_chat_view.dart';
|
||||||
class NewChatScreen extends StatefulWidget {
|
class NewChatScreen extends StatefulWidget {
|
||||||
const NewChatScreen({
|
const NewChatScreen({
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.users,
|
|
||||||
required this.onPressCreateChat,
|
required this.onPressCreateChat,
|
||||||
|
required this.service,
|
||||||
|
required this.userService,
|
||||||
this.translations = const ChatTranslations(),
|
this.translations = const ChatTranslations(),
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ChatOptions options;
|
final ChatOptions options;
|
||||||
final ChatTranslations translations;
|
final ChatTranslations translations;
|
||||||
final List<ChatUserModel> users;
|
final ChatService service;
|
||||||
|
final ChatUserService userService;
|
||||||
final Function(ChatUserModel) onPressCreateChat;
|
final Function(ChatUserModel) onPressCreateChat;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -25,79 +27,104 @@ class NewChatScreen extends StatefulWidget {
|
||||||
|
|
||||||
class _NewChatScreenState extends State<NewChatScreen> {
|
class _NewChatScreenState extends State<NewChatScreen> {
|
||||||
final FocusNode _textFieldFocusNode = FocusNode();
|
final FocusNode _textFieldFocusNode = FocusNode();
|
||||||
|
|
||||||
bool _isSearching = false;
|
bool _isSearching = false;
|
||||||
List<ChatUserModel>? _filteredUsers;
|
String query = '';
|
||||||
|
|
||||||
void filterUsers(String query) => setState(
|
|
||||||
() => _filteredUsers = query.isEmpty
|
|
||||||
? null
|
|
||||||
: widget.users
|
|
||||||
.where(
|
|
||||||
(user) =>
|
|
||||||
user.fullName?.toLowerCase().contains(
|
|
||||||
query.toLowerCase(),
|
|
||||||
) ??
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var users = _filteredUsers ?? widget.users;
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: _isSearching
|
title: _buildSearchField(),
|
||||||
? Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
|
||||||
child: TextField(
|
|
||||||
focusNode: _textFieldFocusNode,
|
|
||||||
onChanged: filterUsers,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: widget.translations.searchPlaceholder,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Text(widget.translations.newChatButton),
|
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
_buildSearchIcon(),
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_isSearching = !_isSearching;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (_isSearching) {
|
|
||||||
_textFieldFocusNode.requestFocus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
_isSearching ? Icons.close : Icons.search,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: users.isEmpty
|
body: FutureBuilder<List<ChatUserModel>>(
|
||||||
? widget.options.noChatsPlaceholderBuilder(widget.translations)
|
future: widget.userService.getAllUsers(),
|
||||||
: ListView(
|
builder: (context, snapshot) {
|
||||||
children: [
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
for (var user in users)
|
return const Center(child: CircularProgressIndicator());
|
||||||
GestureDetector(
|
} else if (snapshot.hasError) {
|
||||||
child: widget.options.chatRowContainerBuilder(
|
return Text('Error: ${snapshot.error}');
|
||||||
ChatRow(
|
} else if (snapshot.hasData) {
|
||||||
avatar: widget.options.userAvatarBuilder(
|
return _buildUserList(snapshot.data!);
|
||||||
user,
|
} else {
|
||||||
40.0,
|
return widget.options
|
||||||
),
|
.noChatsPlaceholderBuilder(widget.translations);
|
||||||
title:
|
}
|
||||||
user.fullName ?? widget.translations.anonymousUser,
|
},
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
onTap: () => widget.onPressCreateChat(user),
|
}
|
||||||
),
|
|
||||||
],
|
Widget _buildSearchField() {
|
||||||
|
return _isSearching
|
||||||
|
? Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _textFieldFocusNode,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
query = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: widget.translations.searchPlaceholder,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
: Text(widget.translations.newChatButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSearchIcon() {
|
||||||
|
return IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_isSearching = !_isSearching;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (_isSearching) {
|
||||||
|
_textFieldFocusNode.requestFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
_isSearching ? Icons.close : Icons.search,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildUserList(List<ChatUserModel> users) {
|
||||||
|
var filteredUsers = users
|
||||||
|
.where(
|
||||||
|
(user) =>
|
||||||
|
user.fullName?.toLowerCase().contains(
|
||||||
|
query.toLowerCase(),
|
||||||
|
) ??
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (filteredUsers.isEmpty) {
|
||||||
|
return widget.options.noChatsPlaceholderBuilder(widget.translations);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: filteredUsers.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
var user = filteredUsers[index];
|
||||||
|
return GestureDetector(
|
||||||
|
child: widget.options.chatRowContainerBuilder(
|
||||||
|
ChatRow(
|
||||||
|
avatar: widget.options.userAvatarBuilder(
|
||||||
|
user,
|
||||||
|
40.0,
|
||||||
|
),
|
||||||
|
title: user.fullName ?? widget.translations.anonymousUser,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () => widget.onPressCreateChat(user),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue