diff --git a/packages/flutter_community_chat/lib/flutter_community_chat.dart b/packages/flutter_community_chat/lib/flutter_community_chat.dart index ed9e127..79ebd14 100644 --- a/packages/flutter_community_chat/lib/flutter_community_chat.dart +++ b/packages/flutter_community_chat/lib/flutter_community_chat.dart @@ -38,26 +38,32 @@ class CommunityChat extends StatelessWidget { NewChatScreen( options: options, translations: translations, - onPressCreateChat: (user) => dataProvider.createChat( - PersonalChatModel(user: user), - ), + onPressCreateChat: (user) { + _onPressChat( + context, + PersonalChatModel(user: user), + ); + }, users: users, ), )); - Future _onPressChat(BuildContext context, ChatModel chat) => _push( - context, - ChatDetailScreen( - options: options, - translations: translations, - chat: chat, - chatMessages: dataProvider.getMessagesStream(chat), - onPressSelectImage: (ChatModel chat) => - _onPressSelectImage(context, chat), - onMessageSubmit: (ChatModel chat, String content) => - dataProvider.sendTextMessage(chat, content), - ), - ); + Future _onPressChat(BuildContext context, ChatModel chat) async { + dataProvider.setChat(chat); + _push( + context, + ChatDetailScreen( + options: options, + translations: translations, + chat: chat, + chatMessages: dataProvider.getMessagesStream(), + onPressSelectImage: (ChatModel chat) => + _onPressSelectImage(context, chat), + onMessageSubmit: (ChatModel chat, String content) => + dataProvider.sendTextMessage(content), + ), + ); + } Future _onPressSelectImage(BuildContext context, ChatModel chat) => showModalBottomSheet( @@ -76,7 +82,7 @@ class CommunityChat extends StatelessWidget { ).then( (image) { if (image != null) { - return dataProvider.sendImageMessage(chat, image); + return dataProvider.sendImageMessage(image); } }, ); diff --git a/packages/flutter_community_chat/pubspec.lock b/packages/flutter_community_chat/pubspec.lock index 61f22c6..738363a 100644 --- a/packages/flutter_community_chat/pubspec.lock +++ b/packages/flutter_community_chat/pubspec.lock @@ -179,7 +179,7 @@ packages: description: path: "packages/flutter_community_chat_interface" ref: HEAD - resolved-ref: bfca7ca229a0f9e6749d079a5b447e50bef6d56f + resolved-ref: "7b03c934cfa28fc5d85f3d59974bb0757a439911" url: "https://github.com/Iconica-Development/flutter_community_chat.git" source: git version: "0.0.1" @@ -188,7 +188,7 @@ packages: description: path: "packages/flutter_community_chat_view" ref: HEAD - resolved-ref: "4af8360850b7d8d220f3052e4be25d6eb45ef963" + resolved-ref: "7b03c934cfa28fc5d85f3d59974bb0757a439911" url: "https://github.com/Iconica-Development/flutter_community_chat.git" source: git version: "0.0.1" diff --git a/packages/flutter_community_chat/pubspec.yaml b/packages/flutter_community_chat/pubspec.yaml index 9804254..4030d6b 100644 --- a/packages/flutter_community_chat/pubspec.yaml +++ b/packages/flutter_community_chat/pubspec.yaml @@ -13,12 +13,12 @@ dependencies: sdk: flutter flutter_community_chat_view: git: - url: https://github.com/Iconica-Development/flutter_community_chat.git - path: packages/flutter_community_chat_view + url: https://github.com/Iconica-Development/flutter_community_chat.git + path: packages/flutter_community_chat_view flutter_community_chat_interface: git: - url: https://github.com/Iconica-Development/flutter_community_chat.git - path: packages/flutter_community_chat_interface + url: https://github.com/Iconica-Development/flutter_community_chat.git + path: packages/flutter_community_chat_interface flutter_image_picker: git: url: https://github.com/Iconica-Development/flutter_image_picker diff --git a/packages/flutter_community_chat_firebase/lib/flutter_community_chat_firebase.dart b/packages/flutter_community_chat_firebase/lib/flutter_community_chat_firebase.dart index 8a80a12..bc0579b 100644 --- a/packages/flutter_community_chat_firebase/lib/flutter_community_chat_firebase.dart +++ b/packages/flutter_community_chat_firebase/lib/flutter_community_chat_firebase.dart @@ -38,38 +38,40 @@ class FirebaseCommunityChatDataProvider extends CommunityChatInterface { options: firebaseChatOptoons, ); - _messageService = FirebaseMessageService( - db: db, - storage: storage, - userService: _userService, - options: firebaseChatOptoons, - ); - _chatService = FirebaseChatService( db: db, userService: _userService, options: firebaseChatOptoons, ); + + _messageService = FirebaseMessageService( + db: db, + storage: storage, + userService: _userService, + chatService: _chatService, + options: firebaseChatOptoons, + ); } @override - Stream> getMessagesStream(ChatModel chat) => - _messageService.getMessagesStream(chat); + Stream> getMessagesStream() => + _messageService.getMessagesStream(); @override - Future> getChatUsers() => _userService.getNewUsers(); + Future> getChatUsers() => _userService.getAllUsers(); @override Stream> getChatsStream() => _chatService.getChatsStream(); @override - Future createChat(ChatModel chat) => _chatService.createChat(chat); + Future sendTextMessage(String text) => + _messageService.sendTextMessage(text); @override - Future sendTextMessage(ChatModel chat, String text) => - _messageService.sendTextMessage(chat, text); + Future sendImageMessage(Uint8List image) => + _messageService.sendImageMessage(image); @override - Future sendImageMessage(ChatModel chat, Uint8List image) => - _messageService.sendImageMessage(chat, image); + Future setChat(ChatModel chat) async => + await _messageService.setChat(chat); } diff --git a/packages/flutter_community_chat_firebase/lib/service/firebase_chat_service.dart b/packages/flutter_community_chat_firebase/lib/service/firebase_chat_service.dart index bb16e65..3d7ead6 100644 --- a/packages/flutter_community_chat_firebase/lib/service/firebase_chat_service.dart +++ b/packages/flutter_community_chat_firebase/lib/service/firebase_chat_service.dart @@ -197,43 +197,28 @@ class FirebaseChatService { return controller.stream; } - Future createChat(ChatModel chat) async { - if (chat is! PersonalChatModel) { - return; - } - + Future getChatByUser(ChatUserModel user) async { var currentUser = await userService.getCurrentUser(); + var chatCollection = await db + .collection(options.usersCollectionName) + .doc(currentUser?.id) + .collection('chats') + .get(); - if (currentUser?.id == null || chat.user.id == null) { - return; + for (var element in chatCollection.docs) { + var data = element.data(); + if (data.containsKey('id') && + data.containsKey('users') && + data['users'] is List) { + if (data['users'].contains(user.id)) { + return PersonalChatModel( + id: data['id'], + user: user, + ); + } + } } - List userIds = [ - currentUser!.id!, - chat.user.id!, - ]; - - var reference = await db - .collection(options.chatsCollectionName) - .withConverter( - fromFirestore: (snapshot, _) => - FirebaseChatDocument.fromJson(snapshot.data()!, snapshot.id), - toFirestore: (chat, _) => chat.toJson(), - ) - .add( - FirebaseChatDocument( - personal: true, - users: userIds, - lastUsed: Timestamp.now(), - ), - ); - - for (var userId in userIds) { - await db - .collection(options.usersCollectionName) - .doc(userId) - .collection('chats') - .add({'id': reference.id}); - } + return null; } } diff --git a/packages/flutter_community_chat_firebase/lib/service/firebase_message_service.dart b/packages/flutter_community_chat_firebase/lib/service/firebase_message_service.dart index 619bf10..7fec591 100644 --- a/packages/flutter_community_chat_firebase/lib/service/firebase_message_service.dart +++ b/packages/flutter_community_chat_firebase/lib/service/firebase_message_service.dart @@ -4,11 +4,12 @@ import 'dart:async'; import 'dart:typed_data'; - import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter_community_chat_firebase/config/firebase_chat_options.dart'; +import 'package:flutter_community_chat_firebase/dto/firebase_chat_document.dart'; import 'package:flutter_community_chat_firebase/dto/firebase_message_document.dart'; +import 'package:flutter_community_chat_firebase/service/firebase_chat_service.dart'; import 'package:flutter_community_chat_firebase/service/firebase_user_service.dart'; import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; import 'package:uuid/uuid.dart'; @@ -18,37 +19,35 @@ class FirebaseMessageService { required this.db, required this.storage, required this.userService, + required this.chatService, required this.options, }); final FirebaseFirestore db; final FirebaseStorage storage; final FirebaseUserService userService; + final FirebaseChatService chatService; FirebaseChatOptoons options; + late StreamController> _controller; + StreamSubscription? _subscription; + ChatModel? _chat; - Future sendTextMessage(ChatModel chat, String text) => - _sendMessage(chat, {'text': text}); - - Future sendImageMessage(ChatModel chat, Uint8List image) async { - var ref = storage - .ref('${options.chatsCollectionName}/${chat.id}/${const Uuid().v4()}'); - - return ref.putData(image).then( - (_) => ref.getDownloadURL().then( - (url) { - _sendMessage(chat, {'image_url': url}); - }, - ), - ); + Future setChat(ChatModel chat) async { + if (chat is PersonalChatModel) { + _chat = await chatService.getChatByUser(chat.user) ?? chat; + } } - Future _sendMessage( - ChatModel chat, - Map data, - ) async { + Future _beforeSendMessage() async { + if (_chat != null) { + _chat = await createChatIfNotExists(_chat!); + } + } + + Future _sendMessage(Map data) async { var currentUser = await userService.getCurrentUser(); - if (currentUser == null) { + if (_chat?.id == null || currentUser == null) { return; } @@ -58,9 +57,17 @@ class FirebaseMessageService { ...data }; - var chatReference = db.collection(options.chatsCollectionName).doc(chat.id); + var chatReference = db + .collection( + options.chatsCollectionName, + ) + .doc(_chat!.id); - await chatReference.collection(options.messagesCollectionName).add(message); + await chatReference + .collection( + options.messagesCollectionName, + ) + .add(message); await chatReference.update({ 'last_used': DateTime.now(), @@ -68,6 +75,29 @@ class FirebaseMessageService { }); } + Future sendTextMessage(String text) => _beforeSendMessage().then( + (_) => _sendMessage({'text': text}), + ); + + Future sendImageMessage(Uint8List image) => _beforeSendMessage().then( + (_) { + if (_chat?.id == null) { + return null; + } + + var ref = storage.ref( + '${options.chatsCollectionName}/${_chat!.id}/${const Uuid().v4()}'); + + return ref.putData(image).then( + (_) => ref.getDownloadURL().then( + (url) { + _sendMessage({'image_url': url}); + }, + ), + ); + }, + ); + Query _getMessagesQuery(String chatId) => db .collection(options.chatsCollectionName) .doc(chatId) @@ -79,49 +109,103 @@ class FirebaseMessageService { toFirestore: (user, _) => user.toJson(), ); - Stream> getMessagesStream(ChatModel chat) { - late StreamController> controller; - StreamSubscription? subscription; - controller = StreamController>( + Stream> getMessagesStream() { + _controller = StreamController>( onListen: () { - var snapshots = _getMessagesQuery(chat.id!).snapshots(); - - subscription = snapshots.listen( - (snapshot) async { - List messages = []; - - for (var messageDoc in snapshot.docs) { - var messageData = messageDoc.data(); - - var sender = await userService.getUser(messageData.sender); - - if (sender != null) { - var timestamp = DateTime.fromMillisecondsSinceEpoch( - (messageData.timestamp).millisecondsSinceEpoch, - ); - - messages.add( - messageData.imageUrl != null - ? ChatImageMessageModel( - sender: sender, - imageUrl: messageData.imageUrl!, - timestamp: timestamp, - ) - : ChatTextMessageModel( - sender: sender, - text: messageData.text!, - timestamp: timestamp, - ), - ); - } - } - - controller.add(messages); - }, - ); + if (_chat?.id != null) { + _subscription = _startListeningForMessages(_chat!); + } }, - onCancel: () => subscription?.cancel(), + onCancel: () => _subscription?.cancel(), ); - return controller.stream; + + return _controller.stream; + } + + StreamSubscription _startListeningForMessages(ChatModel chat) { + var snapshots = _getMessagesQuery(chat.id!).snapshots(); + + return snapshots.listen( + (snapshot) async { + List messages = []; + + for (var messageDoc in snapshot.docs) { + var messageData = messageDoc.data(); + + var sender = await userService.getUser(messageData.sender); + + if (sender != null) { + var timestamp = DateTime.fromMillisecondsSinceEpoch( + (messageData.timestamp).millisecondsSinceEpoch, + ); + + messages.add( + messageData.imageUrl != null + ? ChatImageMessageModel( + sender: sender, + imageUrl: messageData.imageUrl!, + timestamp: timestamp, + ) + : ChatTextMessageModel( + sender: sender, + text: messageData.text!, + timestamp: timestamp, + ), + ); + } + } + + _controller.add(messages); + }, + ); + } + + Future createChatIfNotExists(ChatModel chat) async { + if (chat.id == null) { + if (chat is! PersonalChatModel) { + return null; + } + + var currentUser = await userService.getCurrentUser(); + + if (currentUser?.id == null || chat.user.id == null) { + return null; + } + + List userIds = [ + currentUser!.id!, + chat.user.id!, + ]; + + var reference = await db + .collection(options.chatsCollectionName) + .withConverter( + fromFirestore: (snapshot, _) => + FirebaseChatDocument.fromJson(snapshot.data()!, snapshot.id), + toFirestore: (chat, _) => chat.toJson(), + ) + .add( + FirebaseChatDocument( + personal: true, + users: userIds, + lastUsed: Timestamp.now(), + ), + ); + + for (var userId in userIds) { + await db + .collection(options.usersCollectionName) + .doc(userId) + .collection('chats') + .add({'id': reference.id, 'users': userIds}); + } + + chat.id = reference.id; + + _subscription?.cancel(); + _subscription = _startListeningForMessages(chat); + } + + return chat; } } diff --git a/packages/flutter_community_chat_firebase/lib/service/firebase_user_service.dart b/packages/flutter_community_chat_firebase/lib/service/firebase_user_service.dart index 01c1157..5a57e17 100644 --- a/packages/flutter_community_chat_firebase/lib/service/firebase_user_service.dart +++ b/packages/flutter_community_chat_firebase/lib/service/firebase_user_service.dart @@ -5,7 +5,6 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_community_chat_firebase/config/firebase_chat_options.dart'; -import 'package:flutter_community_chat_firebase/dto/firebase_chat_document.dart'; import 'package:flutter_community_chat_firebase/dto/firebase_user_document.dart'; import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; @@ -19,7 +18,6 @@ class FirebaseUserService { FirebaseFirestore db; FirebaseAuth auth; FirebaseChatOptoons options; - ChatUserModel? _currentUser; final Map _users = {}; @@ -33,16 +31,6 @@ class FirebaseUserService { toFirestore: (user, _) => user.toJson(), ); - CollectionReference get _chatsCollection => db - .collection(options.chatsCollectionName) - .withConverter( - fromFirestore: (snapshot, _) => FirebaseChatDocument.fromJson( - snapshot.data()!, - snapshot.id, - ), - toFirestore: (chat, _) => chat.toJson(), - ); - Future getUser(String id) async { if (_users.containsKey(id)) { return _users[id]!; @@ -65,46 +53,17 @@ class FirebaseUserService { }); } - Future getCurrentUser() async { - return _currentUser == null && auth.currentUser?.uid != null - ? _currentUser = await getUser(auth.currentUser!.uid) - : _currentUser; - } + Future getCurrentUser() async => + _currentUser == null && auth.currentUser?.uid != null + ? _currentUser = await getUser(auth.currentUser!.uid) + : _currentUser; - Future> getNewUsers() async { + Future> getAllUsers() async { var currentUser = await getCurrentUser(); - var existingUserIds = []; - - var existingChatCollection = await db - .collection(options.usersCollectionName) - .doc(currentUser?.id) - .collection('chats') - .get(); - - var existingChatsIds = - existingChatCollection.docs.map((chat) => chat['id']).toList(); - - if (existingChatsIds.isNotEmpty) { - for (var existingChatsId in existingChatsIds) { - var existingChat = await _chatsCollection.doc(existingChatsId).get(); - var existingChatData = existingChat.data(); - - if (existingChatData != null) { - existingUserIds.addAll( - existingChatData.users, - ); - } - } - } var data = await _userCollection.get(); - return data.docs - .where( - (user) => - user.id != currentUser?.id && !existingUserIds.contains(user.id), - ) - .map((user) { + return data.docs.where((user) => user.id != currentUser?.id).map((user) { var userData = user.data(); return ChatUserModel( id: user.id, diff --git a/packages/flutter_community_chat_firebase/pubspec.lock b/packages/flutter_community_chat_firebase/pubspec.lock index 807ab1b..937df6f 100644 --- a/packages/flutter_community_chat_firebase/pubspec.lock +++ b/packages/flutter_community_chat_firebase/pubspec.lock @@ -226,11 +226,9 @@ packages: flutter_community_chat_interface: dependency: "direct main" description: - path: "packages/flutter_community_chat_interface" - ref: HEAD - resolved-ref: "43805605932f617103af8895c7bc57703a7fbac7" - url: "https://github.com/Iconica-Development/flutter_community_chat.git" - source: git + path: "/Users/steinmilder/documents/packages/chat/flutter_community_chat/packages/flutter_community_chat_interface" + relative: false + source: path version: "0.0.1" flutter_data_interface: dependency: transitive diff --git a/packages/flutter_community_chat_firebase/pubspec.yaml b/packages/flutter_community_chat_firebase/pubspec.yaml index d063ebe..a9f03ff 100644 --- a/packages/flutter_community_chat_firebase/pubspec.yaml +++ b/packages/flutter_community_chat_firebase/pubspec.yaml @@ -16,8 +16,8 @@ dependencies: sdk: flutter flutter_community_chat_interface: git: - url: https://github.com/Iconica-Development/flutter_community_chat.git - path: packages/flutter_community_chat_interface + url: https://github.com/Iconica-Development/flutter_community_chat.git + path: packages/flutter_community_chat_interface firebase_auth: ^3.11.2 dev_dependencies: diff --git a/packages/flutter_community_chat_interface/lib/src/community_chat_interface.dart b/packages/flutter_community_chat_interface/lib/src/community_chat_interface.dart index 4a83c2f..b29c9f0 100644 --- a/packages/flutter_community_chat_interface/lib/src/community_chat_interface.dart +++ b/packages/flutter_community_chat_interface/lib/src/community_chat_interface.dart @@ -12,10 +12,10 @@ abstract class CommunityChatInterface extends DataInterface { static final Object _token = Object(); - Future createChat(ChatModel chat); - Future sendTextMessage(ChatModel chat, String text); - Future sendImageMessage(ChatModel chat, Uint8List image); - Stream> getMessagesStream(ChatModel chat); + Future setChat(ChatModel chat); + Future sendTextMessage(String text); + Future sendImageMessage(Uint8List image); + Stream> getMessagesStream(); Stream> getChatsStream(); Future> getChatUsers(); } diff --git a/packages/flutter_community_chat_interface/lib/src/model/chat.dart b/packages/flutter_community_chat_interface/lib/src/model/chat.dart index 1075492..7f2c1d4 100644 --- a/packages/flutter_community_chat_interface/lib/src/model/chat.dart +++ b/packages/flutter_community_chat_interface/lib/src/model/chat.dart @@ -5,15 +5,15 @@ import 'package:flutter_community_chat_interface/src/model/chat_message.dart'; abstract class ChatModel { - const ChatModel({ + ChatModel({ this.id, this.messages = const [], this.lastUsed, this.lastMessage, }); - final String? id; - final List? messages; - final DateTime? lastUsed; - final ChatMessageModel? lastMessage; + String? id; + List? messages; + DateTime? lastUsed; + ChatMessageModel? lastMessage; } diff --git a/packages/flutter_community_chat_view/example/pubspec.lock b/packages/flutter_community_chat_view/example/pubspec.lock index 5a0d192..c4cb70e 100644 --- a/packages/flutter_community_chat_view/example/pubspec.lock +++ b/packages/flutter_community_chat_view/example/pubspec.lock @@ -184,11 +184,9 @@ packages: flutter_community_chat_interface: dependency: transitive description: - path: "packages/flutter_community_chat_interface" - ref: HEAD - resolved-ref: "43805605932f617103af8895c7bc57703a7fbac7" - url: "https://github.com/Iconica-Development/flutter_community_chat.git" - source: git + path: "/Users/steinmilder/documents/packages/chat/flutter_community_chat/packages/flutter_community_chat_interface" + relative: false + source: path version: "0.0.1" flutter_community_chat_view: dependency: "direct main" diff --git a/packages/flutter_community_chat_view/lib/src/screens/new_chat_screen.dart b/packages/flutter_community_chat_view/lib/src/screens/new_chat_screen.dart index 3c18813..d4612a9 100644 --- a/packages/flutter_community_chat_view/lib/src/screens/new_chat_screen.dart +++ b/packages/flutter_community_chat_view/lib/src/screens/new_chat_screen.dart @@ -86,10 +86,7 @@ class _NewChatScreenState extends State { title: user.name ?? '', ), ), - onTap: () { - widget.onPressCreateChat(user); - Navigator.of(context).pop(); - }, + onTap: () => widget.onPressCreateChat(user), ), ], ), diff --git a/packages/flutter_community_chat_view/pubspec.lock b/packages/flutter_community_chat_view/pubspec.lock index 7f6801f..a2c0f2e 100644 --- a/packages/flutter_community_chat_view/pubspec.lock +++ b/packages/flutter_community_chat_view/pubspec.lock @@ -177,11 +177,9 @@ packages: flutter_community_chat_interface: dependency: "direct main" description: - path: "packages/flutter_community_chat_interface" - ref: HEAD - resolved-ref: "43805605932f617103af8895c7bc57703a7fbac7" - url: "https://github.com/Iconica-Development/flutter_community_chat.git" - source: git + path: "/Users/steinmilder/documents/packages/chat/flutter_community_chat/packages/flutter_community_chat_interface" + relative: false + source: path version: "0.0.1" flutter_data_interface: dependency: transitive diff --git a/packages/flutter_community_chat_view/pubspec.yaml b/packages/flutter_community_chat_view/pubspec.yaml index e914b9c..40d6aad 100644 --- a/packages/flutter_community_chat_view/pubspec.yaml +++ b/packages/flutter_community_chat_view/pubspec.yaml @@ -18,8 +18,8 @@ dependencies: intl: ^0.17.0 flutter_community_chat_interface: git: - url: https://github.com/Iconica-Development/flutter_community_chat.git - path: packages/flutter_community_chat_interface + url: https://github.com/Iconica-Development/flutter_community_chat.git + path: packages/flutter_community_chat_interface cached_network_image: ^3.2.2 dev_dependencies: