From 4df2adb984b9151e844f316ff29b41864b0b9ad1 Mon Sep 17 00:00:00 2001 From: Stein Milder Date: Fri, 16 Dec 2022 13:10:57 +0100 Subject: [PATCH] feat: refactor --- .../lib/flutter_community_chat.dart | 24 --- .../lib/service/chat_service.dart | 130 ------------ packages/flutter_community_chat/pubspec.lock | 14 +- packages/flutter_community_chat/pubspec.yaml | 6 +- .../lib/config/firebase_chat_options.dart | 13 -- .../lib/flutter_community_chat_firebase.dart | 80 ------- .../lib/service/firebase_chat_service.dart | 156 +++++++++----- .../lib/service/firebase_message_service.dart | 196 +++++++----------- .../lib/service/firebase_user_service.dart | 41 ++-- .../pubspec.lock | 26 +-- .../lib/flutter_community_chat_interface.dart | 11 +- .../lib/src/chat_data_provider.dart | 19 ++ .../lib/src/community_chat_interface.dart | 22 -- .../lib/src/model/model.dart | 7 + .../lib/src/service/chat_service.dart | 8 + .../lib/src/service/message_service.dart | 18 ++ .../lib/src/service/service.dart | 3 + .../lib/src/service/user_service.dart | 7 + .../pubspec.lock | 6 +- .../example/pubspec.lock | 74 ++++++- .../lib/src/components/chat_bottom.dart | 15 +- .../components/image_loading_snackbar.dart | 2 +- .../lib/src/config/chat_options.dart | 32 +-- .../lib/src/screens/chat_detail_screen.dart | 128 +++++++----- .../lib/src/screens/chat_screen.dart | 24 +-- .../flutter_community_chat_view/pubspec.lock | 74 ++++++- .../flutter_community_chat_view/pubspec.yaml | 5 +- 27 files changed, 553 insertions(+), 588 deletions(-) delete mode 100644 packages/flutter_community_chat/lib/service/chat_service.dart create mode 100644 packages/flutter_community_chat_interface/lib/src/chat_data_provider.dart delete mode 100644 packages/flutter_community_chat_interface/lib/src/community_chat_interface.dart create mode 100644 packages/flutter_community_chat_interface/lib/src/model/model.dart create mode 100644 packages/flutter_community_chat_interface/lib/src/service/chat_service.dart create mode 100644 packages/flutter_community_chat_interface/lib/src/service/message_service.dart create mode 100644 packages/flutter_community_chat_interface/lib/src/service/service.dart create mode 100644 packages/flutter_community_chat_interface/lib/src/service/user_service.dart rename packages/{flutter_community_chat/lib/ui => flutter_community_chat_view/lib/src}/components/image_loading_snackbar.dart (86%) diff --git a/packages/flutter_community_chat/lib/flutter_community_chat.dart b/packages/flutter_community_chat/lib/flutter_community_chat.dart index e7a45fa..2a9ff29 100644 --- a/packages/flutter_community_chat/lib/flutter_community_chat.dart +++ b/packages/flutter_community_chat/lib/flutter_community_chat.dart @@ -4,29 +4,5 @@ library flutter_community_chat; -import 'package:flutter/material.dart'; -import 'package:flutter_community_chat/service/chat_service.dart'; -import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; -import '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/service/chat_service.dart'; export 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; - -class CommunityChat extends StatelessWidget { - const CommunityChat({ - required this.chatService, - super.key, - }); - - final ChatService chatService; - - @override - Widget build(BuildContext context) => ChatScreen( - chats: chatService.dataProvider.getChatsStream(), - onPressStartChat: () => chatService.onPressStartChat(context), - onPressChat: (chat) => chatService.onPressChat(context, chat), - onDeleteChat: (ChatModel chat) => chatService.deleteChat(chat), - options: chatService.options, - translations: chatService.translations(context), - ); -} diff --git a/packages/flutter_community_chat/lib/service/chat_service.dart b/packages/flutter_community_chat/lib/service/chat_service.dart deleted file mode 100644 index 9e61a58..0000000 --- a/packages/flutter_community_chat/lib/service/chat_service.dart +++ /dev/null @@ -1,130 +0,0 @@ -import 'dart:typed_data'; -import 'package:flutter/material.dart'; -import 'package:flutter_community_chat/ui/components/image_loading_snackbar.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_image_picker/flutter_image_picker.dart'; - -abstract class ChatService { - ChatService({ - required this.options, - required this.imagePickerConfig, - required this.dataProvider, - }); - - final CommunityChatInterface dataProvider; - final ChatOptions options; - final ImagePickerConfig imagePickerConfig; - bool _isFetchingUsers = false; - - ImagePickerTheme imagePickerTheme(BuildContext context) => - const ImagePickerTheme(); - - ChatTranslations translations(BuildContext context); - - Future _push(BuildContext context, Widget widget) => - Navigator.of(context).push( - MaterialPageRoute(builder: (context) => widget), - ); - - void _pop(BuildContext context) => Navigator.of(context).pop(); - - Future onPressStartChat(BuildContext context) async { - if (!_isFetchingUsers) { - _isFetchingUsers = true; - await dataProvider.getChatUsers().then( - (users) { - _isFetchingUsers = false; - _push( - context, - buildNewChatScreen(context, users), - ); - }, - ); - } - } - - Widget buildNewChatScreen( - BuildContext context, - List users, - ) => - NewChatScreen( - options: options, - translations: translations(context), - onPressCreateChat: (user) => onPressChat( - context, - PersonalChatModel(user: user), - popBeforePush: true, - ), - users: users, - ); - - Future onPressChat( - BuildContext context, - ChatModel chat, { - bool popBeforePush = false, - }) => - dataProvider.setChat(chat).then((_) { - if (popBeforePush) { - _pop(context); - } - _push( - context, - buildChatDetailScreen(context, chat), - ); - }); - - Widget buildChatDetailScreen( - BuildContext context, - ChatModel chat, - ) => - ChatDetailScreen( - options: options, - translations: translations(context), - 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( - context: context, - builder: (BuildContext context) => options.imagePickerContainerBuilder( - ImagePicker( - customButton: options.closeImagePickerButtonBuilder( - context, - () => Navigator.of(context).pop(), - translations(context), - ), - imagePickerConfig: imagePickerConfig, - imagePickerTheme: imagePickerTheme(context), - ), - ), - ).then( - (image) async { - var messenger = ScaffoldMessenger.of(context); - - messenger.showSnackBar( - getImageLoadingSnackbar( - translations(context), - ), - ); - - if (image != null) { - await dataProvider.sendImageMessage(image); - } - - messenger.hideCurrentSnackBar(); - }, - ); - - Future deleteChat(ChatModel chat) => dataProvider.deleteChat(chat); -} diff --git a/packages/flutter_community_chat/pubspec.lock b/packages/flutter_community_chat/pubspec.lock index fb422b2..383bc72 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: "6a9e88ec8d07118e1543b1a82aa5482d6832cbf8" + resolved-ref: c644e1affb1dd4f570bf0e4ae2e950f5e9d83c37 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: "6a9e88ec8d07118e1543b1a82aa5482d6832cbf8" + resolved-ref: c644e1affb1dd4f570bf0e4ae2e950f5e9d83c37 url: "https://github.com/Iconica-Development/flutter_community_chat.git" source: git version: "0.0.1" @@ -202,7 +202,7 @@ packages: source: git version: "1.0.0" flutter_image_picker: - dependency: "direct main" + dependency: transitive description: path: "." ref: "1.0.3" @@ -319,7 +319,7 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.13" + version: "0.12.14" material_color_utilities: dependency: transitive description: @@ -361,7 +361,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.8.3" path_provider: dependency: transitive description: @@ -478,7 +478,7 @@ packages: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "2.2.0+3" + version: "2.2.2" sqflite_common: dependency: transitive description: @@ -527,7 +527,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.16" + version: "0.4.17" typed_data: dependency: transitive description: diff --git a/packages/flutter_community_chat/pubspec.yaml b/packages/flutter_community_chat/pubspec.yaml index 87ccb3b..938d9ea 100644 --- a/packages/flutter_community_chat/pubspec.yaml +++ b/packages/flutter_community_chat/pubspec.yaml @@ -19,11 +19,7 @@ dependencies: git: 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 - ref: 1.0.3 - + dev_dependencies: flutter_lints: ^2.0.0 diff --git a/packages/flutter_community_chat_firebase/lib/config/firebase_chat_options.dart b/packages/flutter_community_chat_firebase/lib/config/firebase_chat_options.dart index 7374d4a..cf35c00 100644 --- a/packages/flutter_community_chat_firebase/lib/config/firebase_chat_options.dart +++ b/packages/flutter_community_chat_firebase/lib/config/firebase_chat_options.dart @@ -7,22 +7,9 @@ class FirebaseChatOptions { this.chatsCollectionName = 'chats', this.messagesCollectionName = 'messages', this.usersCollectionName = 'users', - this.userFilter, }); final String chatsCollectionName; final String messagesCollectionName; final String usersCollectionName; - - final FirebaseUserFilter? userFilter; -} - -class FirebaseUserFilter { - const FirebaseUserFilter({ - required this.field, - required this.expectedValue, - }); - - final String field; - final Object expectedValue; } 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 98a1ad9..4f314b8 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 @@ -4,84 +4,4 @@ library flutter_community_chat_firebase; -import 'dart:async'; -import 'dart:typed_data'; -import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:firebase_core/firebase_core.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/service/service.dart'; -import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; export 'package:flutter_community_chat_firebase/service/service.dart'; - -class FirebaseCommunityChatDataProvider extends CommunityChatInterface { - late final FirebaseUserService _userService; - late final FirebaseMessageService _messageService; - late final FirebaseChatService _chatService; - final FirebaseChatOptions firebaseChatOptions; - - FirebaseCommunityChatDataProvider({ - this.firebaseChatOptions = const FirebaseChatOptions(), - FirebaseApp? app, - FirebaseUserService? firebaseUserService, - FirebaseMessageService? firebaseMessageService, - FirebaseChatService? firebaseChatService, - }) { - var appInstance = app ?? Firebase.app(); - - var db = FirebaseFirestore.instanceFor(app: appInstance); - var storage = FirebaseStorage.instanceFor(app: appInstance); - var auth = FirebaseAuth.instanceFor(app: appInstance); - - _userService = firebaseUserService ?? - FirebaseUserService( - db: db, - auth: auth, - options: firebaseChatOptions, - ); - - _chatService = firebaseChatService ?? - FirebaseChatService( - db: db, - storage: storage, - userService: _userService, - options: firebaseChatOptions, - ); - - _messageService = firebaseMessageService ?? - FirebaseMessageService( - db: db, - storage: storage, - userService: _userService, - chatService: _chatService, - options: firebaseChatOptions, - ); - } - - @override - Stream> getMessagesStream() => - _messageService.getMessagesStream(); - - @override - Future> getChatUsers() => _userService.getAllUsers(); - - @override - Stream> getChatsStream() => _chatService.getChatsStream(); - - @override - Future sendTextMessage(String text) => - _messageService.sendTextMessage(text); - - @override - Future sendImageMessage(Uint8List image) => - _messageService.sendImageMessage(image); - - @override - Future setChat(ChatModel chat) async => - await _messageService.setChat(chat); - - @override - Future deleteChat(ChatModel chat) async => - await _chatService.deleteChat(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 33d0d9d..2e881a5 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 @@ -4,31 +4,38 @@ import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/material.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_interface/flutter_community_chat_interface.dart'; -import 'firebase_user_service.dart'; -class FirebaseChatService { +class FirebaseChatService implements ChatService { + late FirebaseFirestore _db; + late FirebaseStorage _storage; + late ChatUserService _userService; + late FirebaseChatOptions _options; + FirebaseChatService({ - required this.db, - required this.storage, - required this.userService, - required this.options, - }); + required ChatUserService userService, + FirebaseApp? app, + FirebaseChatOptions? options, + }) { + var appInstance = app ?? Firebase.app(); - FirebaseFirestore db; - FirebaseStorage storage; - FirebaseUserService userService; - FirebaseChatOptions options; + _db = FirebaseFirestore.instanceFor(app: appInstance); + _storage = FirebaseStorage.instanceFor(app: appInstance); + _userService = userService; + _options = options ?? const FirebaseChatOptions(); + } StreamSubscription _addChatSubscription( List chatIds, - Function(List) onReceivedChats, + Function(List) onReceivedChats, ) { - var snapshots = db - .collection(options.chatsCollectionName) + var snapshots = _db + .collection(_options.chatsCollectionName) .where( FieldPath.documentId, whereIn: chatIds, @@ -41,8 +48,8 @@ class FirebaseChatService { .snapshots(); return snapshots.listen((snapshot) async { - var currentUser = await userService.getCurrentUser(); - List chats = []; + var currentUser = await _userService.getCurrentUser(); + List chats = []; for (var chatDoc in snapshot.docs) { var chatData = chatDoc.data(); @@ -51,7 +58,7 @@ class FirebaseChatService { if (chatData.lastMessage != null) { var messageData = chatData.lastMessage!; - var sender = await userService.getUser(messageData.sender); + var sender = await _userService.getUser(messageData.sender); if (sender != null) { var timestamp = DateTime.fromMillisecondsSinceEpoch( @@ -77,7 +84,7 @@ class FirebaseChatService { var otherUserId = List.from(chatData.users).firstWhere( (element) => element != currentUser?.id, ); - var otherUser = await userService.getUser(otherUserId); + var otherUser = await _userService.getUser(otherUserId); if (otherUser != null) { chats.add( @@ -117,14 +124,15 @@ class FirebaseChatService { return result; } - Stream> _getSpecificChatsStream(List chatIds) { - late StreamController> controller; + Stream> _getSpecificChatsStream( + List chatIds) { + late StreamController> controller; List> subscriptions = []; var splittedChatIds = _splitChatIds(chatIds: chatIds); - controller = StreamController>( + controller = StreamController>( onListen: () { - var chats = >{}; + var chats = >{}; for (var chatIdPair in splittedChatIds.asMap().entries) { subscriptions.add( @@ -133,7 +141,7 @@ class FirebaseChatService { (data) { chats[chatIdPair.key] = data; - List mergedChats = []; + List mergedChats = []; mergedChats.addAll( chats.values.expand((element) => element), @@ -160,15 +168,17 @@ class FirebaseChatService { return controller.stream; } - Stream> getChatsStream() { - late StreamController> controller; + @override + Stream> getChatsStream() { + late StreamController> controller; StreamSubscription? userChatsSubscription; StreamSubscription? chatsSubscription; controller = StreamController( onListen: () async { - var currentUser = await userService.getCurrentUser(); - userChatsSubscription = db - .collection(options.usersCollectionName) + debugPrint('Start listening to chats'); + var currentUser = await _userService.getCurrentUser(); + userChatsSubscription = _db + .collection(_options.usersCollectionName) .doc(currentUser?.id) .collection('chats') .snapshots() @@ -185,37 +195,34 @@ class FirebaseChatService { onCancel: () { chatsSubscription?.cancel(); userChatsSubscription?.cancel(); + debugPrint('Stop listening to chats'); }, ); return controller.stream; } - Future getChatByUser(ChatUserModel user) async { - var currentUser = await userService.getCurrentUser(); - var chatCollection = await db - .collection(options.usersCollectionName) + @override + Future getOrCreateChatByUser(ChatUserModel user) async { + var currentUser = await _userService.getCurrentUser(); + var collection = await _db + .collection(_options.usersCollectionName) .doc(currentUser?.id) .collection('chats') + .where('users', arrayContains: user.id) .get(); - for (var element in chatCollection.docs) { - var data = element.data(); - if (data.containsKey('users') && data['users'] is List) { - if (data['users'].contains(user.id)) { - return PersonalChatModel( - id: element.id, - user: user, - ); - } - } - } + var doc = collection.docs.isNotEmpty ? collection.docs.first : null; - return null; + return PersonalChatModel( + id: doc?.id, + user: user, + ); } + @override Future deleteChat(ChatModel chat) async { - var chatCollection = await db - .collection(options.chatsCollectionName) + var chatCollection = await _db + .collection(_options.chatsCollectionName) .doc(chat.id) .withConverter( fromFirestore: (snapshot, _) => @@ -228,8 +235,8 @@ class FirebaseChatService { if (chatData != null) { for (var userId in chatData.users) { - db - .collection(options.usersCollectionName) + _db + .collection(_options.usersCollectionName) .doc(userId) .collection('chats') .doc(chat.id) @@ -237,9 +244,12 @@ class FirebaseChatService { } if (chat.id != null) { - await db.collection(options.chatsCollectionName).doc(chat.id).delete(); - await storage - .ref(options.chatsCollectionName) + await _db + .collection(_options.chatsCollectionName) + .doc(chat.id) + .delete(); + await _storage + .ref(_options.chatsCollectionName) .child(chat.id!) .listAll() .then((value) { @@ -250,4 +260,48 @@ class FirebaseChatService { } } } + + @override + Future storeChatIfNot(PersonalChatModel chat) async { + if (chat.id == null) { + var currentUser = await _userService.getCurrentUser(); + + if (currentUser?.id == null || chat.user.id == null) { + return chat; + } + + 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') + .doc(reference.id) + .set({'users': userIds}); + } + + chat.id = reference.id; + } + + return chat; + } } 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 cce14dc..c8b3ae7 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 @@ -5,56 +5,40 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/material.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'; -class FirebaseMessageService { - FirebaseMessageService({ - required this.db, - required this.storage, - required this.userService, - required this.chatService, - required this.options, - }); +class FirebaseMessageService implements MessageService { + late final FirebaseFirestore _db; + late final FirebaseStorage _storage; + late final ChatUserService _userService; + late FirebaseChatOptions _options; - final FirebaseFirestore db; - final FirebaseStorage storage; - final FirebaseUserService userService; - final FirebaseChatService chatService; - FirebaseChatOptions options; late StreamController> _controller; StreamSubscription? _subscription; - ChatModel? _chat; - Future setChat(ChatModel chat) async { - if (chat.id == null && chat is PersonalChatModel) { - var chatWithUser = await chatService.getChatByUser(chat.user); + FirebaseMessageService({ + required ChatUserService userService, + FirebaseApp? app, + FirebaseChatOptions? options, + }) { + var appInstance = app ?? Firebase.app(); - if (chatWithUser != null) { - _chat = chatWithUser; - return; - } - } - - _chat = chat; + _db = FirebaseFirestore.instanceFor(app: appInstance); + _storage = FirebaseStorage.instanceFor(app: appInstance); + _userService = userService; + _options = options ?? const FirebaseChatOptions(); } - Future _beforeSendMessage() async { - if (_chat != null) { - _chat = await createChatIfNotExists(_chat!); - } - } + Future _sendMessage(ChatModel chat, Map data) async { + var currentUser = await _userService.getCurrentUser(); - Future _sendMessage(Map data) async { - var currentUser = await userService.getCurrentUser(); - - if (_chat?.id == null || currentUser == null) { + if (chat.id == null || currentUser == null) { return; } @@ -64,15 +48,15 @@ class FirebaseMessageService { ...data }; - var chatReference = db + var chatReference = _db .collection( - options.chatsCollectionName, + _options.chatsCollectionName, ) - .doc(_chat!.id); + .doc(chat.id); await chatReference .collection( - options.messagesCollectionName, + _options.messagesCollectionName, ) .add(message); @@ -80,35 +64,54 @@ class FirebaseMessageService { 'last_used': DateTime.now(), 'last_message': message, }); + + if (chat.id != null && _controller.hasListener && (_subscription == null)) { + _subscription = _startListeningForMessages(chat); + } } - 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}); - }, - ), - ); + @override + Future sendTextMessage({ + required String text, + required ChatModel chat, + }) => + _sendMessage( + chat, + { + 'text': text, }, ); - Query _getMessagesQuery(String chatId) => db - .collection(options.chatsCollectionName) - .doc(chatId) - .collection(options.messagesCollectionName) + @override + Future sendImageMessage({ + required ChatModel chat, + required Uint8List image, + }) async { + if (chat.id == null) { + return; + } + + 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, + }, + ); + }, + ), + ); + } + + Query _getMessagesQuery(ChatModel chat) => _db + .collection(_options.chatsCollectionName) + .doc(chat.id) + .collection(_options.messagesCollectionName) .orderBy('timestamp', descending: false) .withConverter( fromFirestore: (snapshot, _) => @@ -116,15 +119,18 @@ class FirebaseMessageService { toFirestore: (user, _) => user.toJson(), ); - Stream> getMessagesStream() { + @override + Stream> getMessagesStream(ChatModel chat) { _controller = StreamController>( onListen: () { - if (_chat?.id != null) { - _subscription = _startListeningForMessages(_chat!); + if (chat.id != null) { + _subscription = _startListeningForMessages(chat); } }, onCancel: () { _subscription?.cancel(); + _subscription = null; + debugPrint('Canceling messages stream'); }, ); @@ -132,7 +138,9 @@ class FirebaseMessageService { } StreamSubscription _startListeningForMessages(ChatModel chat) { - var snapshots = _getMessagesQuery(chat.id!).snapshots(); + debugPrint('Start listening for messages in chat ${chat.id}'); + + var snapshots = _getMessagesQuery(chat).snapshots(); return snapshots.listen( (snapshot) async { @@ -141,7 +149,7 @@ class FirebaseMessageService { for (var messageDoc in snapshot.docs) { var messageData = messageDoc.data(); - var sender = await userService.getUser(messageData.sender); + var sender = await _userService.getUser(messageData.sender); if (sender != null) { var timestamp = DateTime.fromMillisecondsSinceEpoch( @@ -168,54 +176,4 @@ class FirebaseMessageService { }, ); } - - 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') - .doc(reference.id) - .set({'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 c8fe25d..e417f95 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 @@ -4,25 +4,32 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter_community_chat_firebase/config/firebase_chat_options.dart'; import 'package:flutter_community_chat_firebase/dto/firebase_user_document.dart'; import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; -class FirebaseUserService { +class FirebaseUserService implements ChatUserService { FirebaseUserService({ - required this.db, - required this.auth, - required this.options, - }); + FirebaseApp? app, + FirebaseChatOptions? options, + }) { + var appInstance = app ?? Firebase.app(); + + _db = FirebaseFirestore.instanceFor(app: appInstance); + _auth = FirebaseAuth.instanceFor(app: appInstance); + _options = options ?? const FirebaseChatOptions(); + } + + late FirebaseFirestore _db; + late FirebaseAuth _auth; + late FirebaseChatOptions _options; - FirebaseFirestore db; - FirebaseAuth auth; - FirebaseChatOptions options; ChatUserModel? _currentUser; final Map _users = {}; - CollectionReference get _userCollection => db - .collection(options.usersCollectionName) + CollectionReference get _userCollection => _db + .collection(_options.usersCollectionName) .withConverter( fromFirestore: (snapshot, _) => FirebaseUserDocument.fromJson( snapshot.data()!, @@ -31,6 +38,7 @@ class FirebaseUserService { toFirestore: (user, _) => user.toJson(), ); + @override Future getUser(String id) async { if (_users.containsKey(id)) { return _users[id]!; @@ -54,11 +62,13 @@ class FirebaseUserService { }); } + @override Future getCurrentUser() async => - _currentUser == null && auth.currentUser?.uid != null - ? _currentUser = await getUser(auth.currentUser!.uid) + _currentUser == null && _auth.currentUser?.uid != null + ? _currentUser = await getUser(_auth.currentUser!.uid) : _currentUser; + @override Future> getAllUsers() async { var currentUser = await getCurrentUser(); @@ -67,13 +77,6 @@ class FirebaseUserService { isNotEqualTo: currentUser?.id, ); - if (options.userFilter != null) { - query = query.where( - options.userFilter!.field, - isEqualTo: options.userFilter!.expectedValue, - ); - } - var data = await query.get(); return data.docs.map((user) { diff --git a/packages/flutter_community_chat_firebase/pubspec.lock b/packages/flutter_community_chat_firebase/pubspec.lock index 3bc1453..53ee65b 100644 --- a/packages/flutter_community_chat_firebase/pubspec.lock +++ b/packages/flutter_community_chat_firebase/pubspec.lock @@ -14,7 +14,7 @@ packages: name: _flutterfire_internals url: "https://pub.dartlang.org" source: hosted - version: "1.0.9" + version: "1.0.10" analyzer: dependency: transitive description: @@ -84,21 +84,21 @@ packages: name: cloud_firestore url: "https://pub.dartlang.org" source: hosted - version: "4.1.0" + version: "4.2.0" cloud_firestore_platform_interface: dependency: transitive description: name: cloud_firestore_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "5.9.0" + version: "5.9.1" cloud_firestore_web: dependency: transitive description: name: cloud_firestore_web url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "3.1.1" code_builder: dependency: transitive description: @@ -154,28 +154,28 @@ packages: name: firebase_auth url: "https://pub.dartlang.org" source: hosted - version: "4.1.4" + version: "4.2.1" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "6.11.3" + version: "6.11.5" firebase_auth_web: dependency: transitive description: name: firebase_auth_web url: "https://pub.dartlang.org" source: hosted - version: "5.1.3" + version: "5.2.1" firebase_core: dependency: "direct main" description: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "2.3.0" + version: "2.4.0" firebase_core_platform_interface: dependency: transitive description: @@ -189,28 +189,28 @@ packages: name: firebase_core_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.2" firebase_storage: dependency: "direct main" description: name: firebase_storage url: "https://pub.dartlang.org" source: hosted - version: "11.0.6" + version: "11.0.8" firebase_storage_platform_interface: dependency: transitive description: name: firebase_storage_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "4.1.24" + version: "4.1.25" firebase_storage_web: dependency: transitive description: name: firebase_storage_web url: "https://pub.dartlang.org" source: hosted - version: "3.3.16" + version: "3.3.17" fixnum: dependency: transitive description: @@ -228,7 +228,7 @@ packages: description: path: "packages/flutter_community_chat_interface" ref: HEAD - resolved-ref: "6a9e88ec8d07118e1543b1a82aa5482d6832cbf8" + resolved-ref: c644e1affb1dd4f570bf0e4ae2e950f5e9d83c37 url: "https://github.com/Iconica-Development/flutter_community_chat.git" source: git version: "0.0.1" diff --git a/packages/flutter_community_chat_interface/lib/flutter_community_chat_interface.dart b/packages/flutter_community_chat_interface/lib/flutter_community_chat_interface.dart index b4d87b3..bb7b9d7 100644 --- a/packages/flutter_community_chat_interface/lib/flutter_community_chat_interface.dart +++ b/packages/flutter_community_chat_interface/lib/flutter_community_chat_interface.dart @@ -4,11 +4,6 @@ library flutter_community_chat_interface; -export 'src/community_chat_interface.dart'; -export 'src/model/chat.dart'; -export 'src/model/chat_image_message.dart'; -export 'src/model/chat_message.dart'; -export 'src/model/chat_text_message.dart'; -export 'src/model/chat_user.dart'; -export 'src/model/group_chat.dart'; -export 'src/model/personal_chat.dart'; +export 'package:flutter_community_chat_interface/src/chat_data_provider.dart'; +export 'package:flutter_community_chat_interface/src/model/model.dart'; +export 'package:flutter_community_chat_interface/src/service/service.dart'; diff --git a/packages/flutter_community_chat_interface/lib/src/chat_data_provider.dart b/packages/flutter_community_chat_interface/lib/src/chat_data_provider.dart new file mode 100644 index 0000000..a152c49 --- /dev/null +++ b/packages/flutter_community_chat_interface/lib/src/chat_data_provider.dart @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; +import 'package:flutter_data_interface/flutter_data_interface.dart'; + +class ChatDataProvider extends DataInterface { + ChatDataProvider({ + required this.chatService, + required this.userService, + required this.messageService, + }) : super(token: _token); + + static final Object _token = Object(); + final ChatUserService userService; + final ChatService chatService; + final MessageService messageService; +} 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 deleted file mode 100644 index f80ff1e..0000000 --- a/packages/flutter_community_chat_interface/lib/src/community_chat_interface.dart +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -import 'dart:typed_data'; - -import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; -import 'package:flutter_data_interface/flutter_data_interface.dart'; - -abstract class CommunityChatInterface extends DataInterface { - CommunityChatInterface() : super(token: _token); - - static final Object _token = Object(); - - Future setChat(ChatModel chat); - Future sendTextMessage(String text); - Future sendImageMessage(Uint8List image); - Stream> getMessagesStream(); - Stream> getChatsStream(); - Future> getChatUsers(); - Future deleteChat(ChatModel chat); -} diff --git a/packages/flutter_community_chat_interface/lib/src/model/model.dart b/packages/flutter_community_chat_interface/lib/src/model/model.dart new file mode 100644 index 0000000..907dd83 --- /dev/null +++ b/packages/flutter_community_chat_interface/lib/src/model/model.dart @@ -0,0 +1,7 @@ +export 'chat.dart'; +export 'chat_image_message.dart'; +export 'chat_text_message.dart'; +export 'chat_user.dart'; +export 'group_chat.dart'; +export 'personal_chat.dart'; +export 'chat_message.dart'; diff --git a/packages/flutter_community_chat_interface/lib/src/service/chat_service.dart b/packages/flutter_community_chat_interface/lib/src/service/chat_service.dart new file mode 100644 index 0000000..a6edaac --- /dev/null +++ b/packages/flutter_community_chat_interface/lib/src/service/chat_service.dart @@ -0,0 +1,8 @@ +import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; + +abstract class ChatService { + Stream> getChatsStream(); + Future getOrCreateChatByUser(ChatUserModel user); + Future deleteChat(PersonalChatModel chat); + Future storeChatIfNot(PersonalChatModel chat); +} diff --git a/packages/flutter_community_chat_interface/lib/src/service/message_service.dart b/packages/flutter_community_chat_interface/lib/src/service/message_service.dart new file mode 100644 index 0000000..ca84fbd --- /dev/null +++ b/packages/flutter_community_chat_interface/lib/src/service/message_service.dart @@ -0,0 +1,18 @@ +import 'dart:typed_data'; +import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; + +abstract class MessageService { + Future sendTextMessage({ + required ChatModel chat, + required String text, + }); + + Future sendImageMessage({ + required ChatModel chat, + required Uint8List image, + }); + + Stream> getMessagesStream( + ChatModel chat, + ); +} diff --git a/packages/flutter_community_chat_interface/lib/src/service/service.dart b/packages/flutter_community_chat_interface/lib/src/service/service.dart new file mode 100644 index 0000000..8043b14 --- /dev/null +++ b/packages/flutter_community_chat_interface/lib/src/service/service.dart @@ -0,0 +1,3 @@ +export 'chat_service.dart'; +export 'user_service.dart'; +export 'message_service.dart'; diff --git a/packages/flutter_community_chat_interface/lib/src/service/user_service.dart b/packages/flutter_community_chat_interface/lib/src/service/user_service.dart new file mode 100644 index 0000000..372689f --- /dev/null +++ b/packages/flutter_community_chat_interface/lib/src/service/user_service.dart @@ -0,0 +1,7 @@ +import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; + +abstract class ChatUserService { + Future getUser(String id); + Future getCurrentUser(); + Future> getAllUsers(); +} diff --git a/packages/flutter_community_chat_interface/pubspec.lock b/packages/flutter_community_chat_interface/pubspec.lock index c177e0c..42a3249 100644 --- a/packages/flutter_community_chat_interface/pubspec.lock +++ b/packages/flutter_community_chat_interface/pubspec.lock @@ -161,7 +161,7 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.13" + version: "0.12.14" material_color_utilities: dependency: transitive description: @@ -196,7 +196,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.8.3" pub_semver: dependency: transitive description: @@ -257,7 +257,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.16" + version: "0.4.17" typed_data: dependency: transitive description: diff --git a/packages/flutter_community_chat_view/example/pubspec.lock b/packages/flutter_community_chat_view/example/pubspec.lock index c27acc2..7ed5892 100644 --- a/packages/flutter_community_chat_view/example/pubspec.lock +++ b/packages/flutter_community_chat_view/example/pubspec.lock @@ -113,6 +113,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3+2" crypto: dependency: transitive description: @@ -186,7 +193,7 @@ packages: description: path: "packages/flutter_community_chat_interface" ref: HEAD - resolved-ref: "6a9e88ec8d07118e1543b1a82aa5482d6832cbf8" + resolved-ref: c644e1affb1dd4f570bf0e4ae2e950f5e9d83c37 url: "https://github.com/Iconica-Development/flutter_community_chat.git" source: git version: "0.0.1" @@ -206,6 +213,15 @@ packages: url: "https://github.com/Iconica-Development/flutter_data_interface.git" source: git version: "1.0.0" + flutter_image_picker: + dependency: transitive + description: + path: "." + ref: "1.0.3" + resolved-ref: "20814755cca74296600a0ae3e016e46979e66a7e" + url: "https://github.com/Iconica-Development/flutter_image_picker" + source: git + version: "1.0.3" flutter_lints: dependency: "direct dev" description: @@ -213,11 +229,23 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" glob: dependency: transitive description: @@ -239,6 +267,41 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.2" + image_picker: + dependency: transitive + description: + name: image_picker + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.6" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.5+3" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.10" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.6+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.6.2" intl: dependency: transitive description: @@ -246,6 +309,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.17.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" lints: dependency: transitive description: @@ -425,7 +495,7 @@ packages: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "2.2.0+3" + version: "2.2.2" sqflite_common: dependency: transitive description: diff --git a/packages/flutter_community_chat_view/lib/src/components/chat_bottom.dart b/packages/flutter_community_chat_view/lib/src/components/chat_bottom.dart index a3a8c05..eca757b 100644 --- a/packages/flutter_community_chat_view/lib/src/components/chat_bottom.dart +++ b/packages/flutter_community_chat_view/lib/src/components/chat_bottom.dart @@ -16,9 +16,9 @@ class ChatBottom extends StatefulWidget { super.key, }); - final Future Function(ChatModel chat, String text) onMessageSubmit; + final Future Function(String text) onMessageSubmit; final TextInputBuilder messageInputBuilder; - final Function(ChatModel)? onPressSelectImage; + final VoidCallback? onPressSelectImage; final ChatModel chat; final ChatTranslations translations; @@ -44,17 +44,16 @@ class _ChatBottomState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - if (widget.onPressSelectImage != null) - IconButton( - onPressed: () => widget.onPressSelectImage!(widget.chat), - icon: const Icon(Icons.image), - ), + IconButton( + onPressed: widget.onPressSelectImage, + icon: const Icon(Icons.image), + ), IconButton( onPressed: () { var value = _textEditingController.text; if (value.isNotEmpty) { - widget.onMessageSubmit(widget.chat, value); + widget.onMessageSubmit(value); _textEditingController.clear(); } }, diff --git a/packages/flutter_community_chat/lib/ui/components/image_loading_snackbar.dart b/packages/flutter_community_chat_view/lib/src/components/image_loading_snackbar.dart similarity index 86% rename from packages/flutter_community_chat/lib/ui/components/image_loading_snackbar.dart rename to packages/flutter_community_chat_view/lib/src/components/image_loading_snackbar.dart index a84f80b..18e42e0 100644 --- a/packages/flutter_community_chat/lib/ui/components/image_loading_snackbar.dart +++ b/packages/flutter_community_chat_view/lib/src/components/image_loading_snackbar.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_community_chat/flutter_community_chat.dart'; +import 'package:flutter_community_chat_view/flutter_community_chat_view.dart'; SnackBar getImageLoadingSnackbar(ChatTranslations translations) => SnackBar( duration: const Duration(minutes: 1), diff --git a/packages/flutter_community_chat_view/lib/src/config/chat_options.dart b/packages/flutter_community_chat_view/lib/src/config/chat_options.dart index 41917cf..0381f21 100644 --- a/packages/flutter_community_chat_view/lib/src/config/chat_options.dart +++ b/packages/flutter_community_chat_view/lib/src/config/chat_options.dart @@ -6,6 +6,7 @@ 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_image_picker/flutter_image_picker.dart'; class ChatOptions { const ChatOptions({ @@ -13,7 +14,6 @@ class ChatOptions { this.messageInputBuilder = _createMessageInput, this.chatRowContainerBuilder = _createChatRowContainer, this.imagePickerContainerBuilder = _createImagePickerContainer, - this.closeImagePickerButtonBuilder = _createCloseImagePickerButton, this.scaffoldBuilder = _createScaffold, this.userAvatarBuilder = _createUserAvatar, this.noChatsPlaceholderBuilder = _createNoChatsPlaceholder, @@ -22,8 +22,7 @@ class ChatOptions { final ButtonBuilder newChatButtonBuilder; final TextInputBuilder messageInputBuilder; final ContainerBuilder chatRowContainerBuilder; - final ContainerBuilder imagePickerContainerBuilder; - final ButtonBuilder closeImagePickerButtonBuilder; + final ImagePickerContainerBuilder imagePickerContainerBuilder; final ScaffoldBuilder scaffoldBuilder; final UserAvatarBuilder userAvatarBuilder; final NoChatsPlaceholderBuilder noChatsPlaceholderBuilder; @@ -70,23 +69,19 @@ Widget _createChatRowContainer( ); Widget _createImagePickerContainer( - Widget imagePicker, + VoidCallback onClose, + ChatTranslations translations, ) => Container( padding: const EdgeInsets.all(8.0), color: Colors.black, - child: imagePicker, - ); - -Widget _createCloseImagePickerButton( - BuildContext context, - VoidCallback onPressed, - ChatTranslations translations, -) => - ElevatedButton( - onPressed: onPressed, - child: Text( - translations.cancelImagePickerBtn, + child: ImagePicker( + customButton: ElevatedButton( + onPressed: onClose, + child: Text( + translations.cancelImagePickerBtn, + ), + ), ), ); @@ -138,6 +133,11 @@ typedef ContainerBuilder = Widget Function( Widget child, ); +typedef ImagePickerContainerBuilder = Widget Function( + VoidCallback onClose, + ChatTranslations translations, +); + typedef ScaffoldBuilder = Scaffold Function( AppBar appBar, Widget body, diff --git a/packages/flutter_community_chat_view/lib/src/screens/chat_detail_screen.dart b/packages/flutter_community_chat_view/lib/src/screens/chat_detail_screen.dart index e173af6..6b2d8e8 100644 --- a/packages/flutter_community_chat_view/lib/src/screens/chat_detail_screen.dart +++ b/packages/flutter_community_chat_view/lib/src/screens/chat_detail_screen.dart @@ -2,86 +2,114 @@ // // SPDX-License-Identifier: BSD-3-Clause +import 'dart:typed_data'; + 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_bottom.dart'; import 'package:flutter_community_chat_view/src/components/chat_detail_row.dart'; +import 'package:flutter_community_chat_view/src/components/image_loading_snackbar.dart'; class ChatDetailScreen extends StatelessWidget { const ChatDetailScreen({ required this.options, - required this.chat, required this.onMessageSubmit, + required this.onUploadImage, this.translations = const ChatTranslations(), + this.chat, this.chatMessages, - this.onPressSelectImage, this.onPressChatTitle, super.key, }); - final ChatModel chat; + final PersonalChatModel? chat; final ChatOptions options; final ChatTranslations translations; final Stream>? chatMessages; - final Function(ChatModel)? onPressSelectImage; - final Future Function(ChatModel chat, String text) onMessageSubmit; - final Future Function(ChatModel chat)? onPressChatTitle; + final Future Function(Uint8List image) onUploadImage; + final Future Function(String text) onMessageSubmit; + final VoidCallback? onPressChatTitle; @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar( - centerTitle: true, - title: GestureDetector( - onTap: () => - onPressChatTitle != null ? onPressChatTitle!(chat) : {}, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (chat is PersonalChatModel) - options.userAvatarBuilder( - (chat as PersonalChatModel).user, - 36.0, - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 15.5), - child: Text( - (chat as PersonalChatModel).user.fullName, - style: const TextStyle(fontSize: 18), + Widget build(BuildContext context) { + Future onPressSelectImage() => showModalBottomSheet( + context: context, + builder: (BuildContext context) => + options.imagePickerContainerBuilder( + () => Navigator.of(context).pop(), + translations, + ), + ).then( + (image) async { + var messenger = ScaffoldMessenger.of(context) + ..showSnackBar( + getImageLoadingSnackbar(translations), + ); + + if (image != null) { + await onUploadImage(image); + } + + messenger.hideCurrentSnackBar(); + }, + ); + + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: GestureDetector( + onTap: onPressChatTitle, + child: Row( + mainAxisSize: MainAxisSize.min, + children: chat == null + ? [] + : [ + options.userAvatarBuilder( + chat!.user, + 36.0, ), - ), - ), - ], - ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 15.5), + child: Text( + chat!.user.fullName, + style: const TextStyle(fontSize: 18), + ), + ), + ), + ], ), ), - body: Column( - children: [ - Expanded( - child: StreamBuilder>( - stream: chatMessages, - builder: (BuildContext context, snapshot) => ListView( - reverse: true, - padding: const EdgeInsets.only(top: 24.0), - children: [ - for (var message - in (snapshot.data ?? chat.messages ?? []).reversed) - ChatDetailRow( - message: message, - ), - ], - ), + ), + body: Column( + children: [ + Expanded( + child: StreamBuilder>( + stream: chatMessages, + builder: (BuildContext context, snapshot) => ListView( + reverse: true, + padding: const EdgeInsets.only(top: 24.0), + children: [ + for (var message + in (snapshot.data ?? chat?.messages ?? []).reversed) + ChatDetailRow( + message: message, + ), + ], ), ), + ), + if (chat != null) ChatBottom( - chat: chat, + chat: chat!, messageInputBuilder: options.messageInputBuilder, onPressSelectImage: onPressSelectImage, onMessageSubmit: onMessageSubmit, translations: translations, ), - ], - ), - ); + ], + ), + ); + } } diff --git a/packages/flutter_community_chat_view/lib/src/screens/chat_screen.dart b/packages/flutter_community_chat_view/lib/src/screens/chat_screen.dart index e1aec6c..97564c9 100644 --- a/packages/flutter_community_chat_view/lib/src/screens/chat_screen.dart +++ b/packages/flutter_community_chat_view/lib/src/screens/chat_screen.dart @@ -20,10 +20,10 @@ class ChatScreen extends StatefulWidget { final ChatOptions options; final ChatTranslations translations; - final Stream> chats; + final Stream> chats; final VoidCallback? onPressStartChat; - final void Function(ChatModel chat) onDeleteChat; - final void Function(ChatModel chat) onPressChat; + final void Function(PersonalChatModel chat) onDeleteChat; + final void Function(PersonalChatModel chat) onPressChat; @override State createState() => _ChatScreenState(); @@ -43,11 +43,11 @@ class _ChatScreenState extends State { child: ListView( padding: const EdgeInsets.only(top: 15.0), children: [ - StreamBuilder>( + StreamBuilder>( stream: widget.chats, builder: (BuildContext context, snapshot) => Column( children: [ - for (ChatModel chat in snapshot.data ?? []) + for (PersonalChatModel chat in snapshot.data ?? []) Builder( builder: (context) => Dismissible( confirmDismiss: (_) => showDialog( @@ -100,15 +100,11 @@ class _ChatScreenState extends State { onTap: () => widget.onPressChat(chat), child: widget.options.chatRowContainerBuilder( ChatRow( - avatar: chat is PersonalChatModel - ? widget.options.userAvatarBuilder( - chat.user, - 40.0, - ) - : Container(), - title: chat is PersonalChatModel - ? chat.user.fullName - : (chat as GroupChatModel).title, + avatar: widget.options.userAvatarBuilder( + chat.user, + 40.0, + ), + title: chat.user.fullName, subTitle: chat.lastMessage != null ? chat.lastMessage is ChatTextMessageModel diff --git a/packages/flutter_community_chat_view/pubspec.lock b/packages/flutter_community_chat_view/pubspec.lock index d6cd088..a0d836d 100644 --- a/packages/flutter_community_chat_view/pubspec.lock +++ b/packages/flutter_community_chat_view/pubspec.lock @@ -113,6 +113,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3+2" crypto: dependency: transitive description: @@ -179,7 +186,7 @@ packages: description: path: "packages/flutter_community_chat_interface" ref: HEAD - resolved-ref: "6a9e88ec8d07118e1543b1a82aa5482d6832cbf8" + resolved-ref: c644e1affb1dd4f570bf0e4ae2e950f5e9d83c37 url: "https://github.com/Iconica-Development/flutter_community_chat.git" source: git version: "0.0.1" @@ -192,6 +199,15 @@ packages: url: "https://github.com/Iconica-Development/flutter_data_interface.git" source: git version: "1.0.0" + flutter_image_picker: + dependency: "direct main" + description: + path: "." + ref: "1.0.3" + resolved-ref: "20814755cca74296600a0ae3e016e46979e66a7e" + url: "https://github.com/Iconica-Development/flutter_image_picker" + source: git + version: "1.0.3" flutter_lints: dependency: "direct dev" description: @@ -199,11 +215,23 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" glob: dependency: transitive description: @@ -225,6 +253,41 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.2" + image_picker: + dependency: transitive + description: + name: image_picker + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.6" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.5+3" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.10" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.6+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.6.2" intl: dependency: "direct main" description: @@ -232,6 +295,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.17.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" lints: dependency: transitive description: @@ -411,7 +481,7 @@ packages: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "2.2.0+3" + version: "2.2.2" sqflite_common: dependency: transitive description: diff --git a/packages/flutter_community_chat_view/pubspec.yaml b/packages/flutter_community_chat_view/pubspec.yaml index 40d6aad..f052191 100644 --- a/packages/flutter_community_chat_view/pubspec.yaml +++ b/packages/flutter_community_chat_view/pubspec.yaml @@ -21,7 +21,10 @@ dependencies: url: https://github.com/Iconica-Development/flutter_community_chat.git path: packages/flutter_community_chat_interface cached_network_image: ^3.2.2 - + flutter_image_picker: + git: + url: https://github.com/Iconica-Development/flutter_image_picker + ref: 1.0.3 dev_dependencies: flutter_test: sdk: flutter