diff --git a/packages/chat_repository_interface/lib/src/models/chat_model.dart b/packages/chat_repository_interface/lib/src/models/chat_model.dart index 3997414..b523498 100644 --- a/packages/chat_repository_interface/lib/src/models/chat_model.dart +++ b/packages/chat_repository_interface/lib/src/models/chat_model.dart @@ -80,6 +80,36 @@ class ChatModel { lastMessage: lastMessage ?? this.lastMessage, unreadMessageCount: unreadMessageCount ?? this.unreadMessageCount, ); + + /// The factory chat model that creates a chat model from a map + factory ChatModel.fromMap(String id, Map data) { + return ChatModel( + id: id, + users: List.from(data['users']), + isGroupChat: data['isGroupChat'], + chatName: data['chatName'], + description: data['description'], + imageUrl: data['imageUrl'], + canBeDeleted: data['canBeDeleted'] ?? true, + lastUsed: data['lastUsed'] != null + ? DateTime.fromMillisecondsSinceEpoch(data['lastUsed']) + : null, + lastMessage: data['lastMessage'], + unreadMessageCount: data['unreadMessageCount'] ?? 0, + ); + } + + Map toMap() => { + 'users': users, + 'isGroupChat': isGroupChat, + 'chatName': chatName, + 'description': description, + 'imageUrl': imageUrl, + 'canBeDeleted': canBeDeleted, + 'lastUsed': lastUsed?.millisecondsSinceEpoch, + 'lastMessage': lastMessage, + 'unreadMessageCount': unreadMessageCount, + }; } /// The chat model extension diff --git a/packages/chat_repository_interface/lib/src/models/message_model.dart b/packages/chat_repository_interface/lib/src/models/message_model.dart index b2c8c8f..ce9a25d 100644 --- a/packages/chat_repository_interface/lib/src/models/message_model.dart +++ b/packages/chat_repository_interface/lib/src/models/message_model.dart @@ -15,7 +15,7 @@ class MessageModel { required this.timestamp, required this.senderId, }); - + /// The chat id final String chatId; /// The message id @@ -50,6 +50,27 @@ class MessageModel { timestamp: timestamp ?? this.timestamp, senderId: senderId ?? this.senderId, ); + + factory MessageModel.fromMap(String id, Map map) { + return MessageModel( + chatId: map['chatId'], + id: id, + text: map['text'], + imageUrl: map['imageUrl'], + timestamp: DateTime.fromMillisecondsSinceEpoch(map['timestamp']), + senderId: map['senderId'], + ); + } + + Map toMap() { + return { + 'chatId': chatId, + 'text': text, + 'imageUrl': imageUrl, + 'timestamp': timestamp.millisecondsSinceEpoch, + 'senderId': senderId, + }; + } } /// Extension on [MessageModel] to check the message type diff --git a/packages/chat_repository_interface/lib/src/models/user_model.dart b/packages/chat_repository_interface/lib/src/models/user_model.dart index 87fbe2c..3673615 100644 --- a/packages/chat_repository_interface/lib/src/models/user_model.dart +++ b/packages/chat_repository_interface/lib/src/models/user_model.dart @@ -25,6 +25,15 @@ class UserModel { /// The user image url final String? imageUrl; + + factory UserModel.fromMap(String id, Map data) { + return UserModel( + id: id, + firstName: data['firstName'], + lastName: data['lastName'], + imageUrl: data['imageUrl'], + ); + } } /// Extension on [UserModel] to get the user full name diff --git a/packages/chat_repository_interface/lib/src/services/chat_service.dart b/packages/chat_repository_interface/lib/src/services/chat_service.dart index 2f9c07e..5113b01 100644 --- a/packages/chat_repository_interface/lib/src/services/chat_service.dart +++ b/packages/chat_repository_interface/lib/src/services/chat_service.dart @@ -188,16 +188,8 @@ class ChatService { /// Returns a [Stream] of [int]. Stream getUnreadMessagesCount({ required String userId, - String? chatId, }) { - if (chatId == null) { - return chatRepository.getUnreadMessagesCount(userId: userId); - } - - return chatRepository.getUnreadMessagesCount( - userId: userId, - chatId: chatId, - ); + return chatRepository.getUnreadMessagesCount(userId: userId); } /// Upload an image with the given parameters. @@ -218,9 +210,18 @@ class ChatService { /// Returns a [Future] of [void]. Future markAsRead({ required String chatId, + required String userId, }) async { var chat = await chatRepository.getChat(chatId: chatId).first; + if (chat.lastMessage == null) return; + + var lastMessage = await chatRepository + .getMessage(chatId: chatId, messageId: chat.lastMessage!) + .first; + + if (lastMessage != null && lastMessage.senderId == userId) return; + var newChat = chat.copyWith( lastUsed: DateTime.now(), unreadMessageCount: 0, diff --git a/packages/firebase_chat_repository/README.md b/packages/firebase_chat_repository/README.md index 02fe8ec..ee1be5e 100644 --- a/packages/firebase_chat_repository/README.md +++ b/packages/firebase_chat_repository/README.md @@ -1,39 +1,16 @@ - - -TODO: Put a short description of the package here that helps potential users -know whether this package might be useful for them. - -## Features - -TODO: List what your package can do. Maybe include images, gifs, or videos. - -## Getting started - -TODO: List prerequisites and provide or point to information on how to -start using the package. +# Firebase chat repository +The firebase implementation of the chat_repository_interface ## Usage - -TODO: Include short and useful examples for package users. Add longer examples -to `/example` folder. - ```dart -const like = 'sample'; -``` - -## Additional information - -TODO: Tell users more about the package: where to find more information, how to -contribute to the package, how to file issues, what response they can expect -from the package authors, and more. +chatService: ChatService( + chatRepository: FirebaseChatRepository( + chatCollection: 'chats', + messageCollection: 'messages', + mediaPath: 'chat', + ), + userRepository: FirebaseUserRepository( + userCollection: 'users', + ), +), +``` \ No newline at end of file diff --git a/packages/firebase_chat_repository/lib/firebase_chat_repository.dart b/packages/firebase_chat_repository/lib/firebase_chat_repository.dart index 298576d..0257c1d 100644 --- a/packages/firebase_chat_repository/lib/firebase_chat_repository.dart +++ b/packages/firebase_chat_repository/lib/firebase_chat_repository.dart @@ -1,5 +1,2 @@ -/// A Calculator. -class Calculator { - /// Returns [value] plus 1. - int addOne(int value) => value + 1; -} +export 'src/firebase_chat_repository.dart'; +export 'src/firebase_user_repository.dart'; diff --git a/packages/firebase_chat_repository/lib/src/firebase_chat_repository.dart b/packages/firebase_chat_repository/lib/src/firebase_chat_repository.dart new file mode 100644 index 0000000..ec26ad4 --- /dev/null +++ b/packages/firebase_chat_repository/lib/src/firebase_chat_repository.dart @@ -0,0 +1,191 @@ +import 'dart:typed_data'; + +import 'package:chat_repository_interface/chat_repository_interface.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_storage/firebase_storage.dart'; + +class FirebaseChatRepository implements ChatRepositoryInterface { + final FirebaseFirestore _firestore; + final FirebaseStorage _storage; + final String chatCollection; + final String messageCollection; + final String mediaPath; + + FirebaseChatRepository({ + FirebaseFirestore? firestore, + FirebaseStorage? storage, + this.chatCollection = 'chats', + this.messageCollection = 'messages', + this.mediaPath = 'chat', + }) : _firestore = firestore ?? FirebaseFirestore.instance, + _storage = storage ?? FirebaseStorage.instance; + + @override + Future createChat({ + required List users, + required bool isGroupChat, + String? chatName, + String? description, + String? imageUrl, + List? messages, + }) async { + final chatData = { + 'users': users, + 'isGroupChat': isGroupChat, + 'chatName': chatName, + 'description': description, + 'imageUrl': imageUrl, + 'createdAt': DateTime.now().millisecondsSinceEpoch, + }; + await _firestore.collection(chatCollection).add(chatData); + } + + @override + Future deleteChat({required String chatId}) async { + await _firestore.collection(chatCollection).doc(chatId).delete(); + } + + @override + Stream getChat({required String chatId}) { + return _firestore + .collection(chatCollection) + .doc(chatId) + .snapshots() + .map((snapshot) { + var data = snapshot.data() as Map; + return ChatModel.fromMap(snapshot.id, data); + }); + } + + @override + Stream?> getChats({required String userId}) { + return _firestore + .collection(chatCollection) + .where('users', arrayContains: userId) + .snapshots() + .map((querySnapshot) { + return querySnapshot.docs.map((doc) { + var data = doc.data(); + return ChatModel.fromMap(doc.id, data); + }).toList(); + }); + } + + @override + Stream getMessage( + {required String chatId, required String messageId}) { + return _firestore + .collection(chatCollection) + .doc(chatId) + .collection(messageCollection) + .doc(messageId) + .snapshots() + .map((snapshot) { + var data = snapshot.data() as Map; + return MessageModel.fromMap( + snapshot.id, + data, + ); + }); + } + + @override + Stream?> getMessages({ + required String chatId, + required String userId, + required int pageSize, + required int page, + }) { + return _firestore + .collection(chatCollection) + .doc(chatId) + .collection(messageCollection) + .orderBy('timestamp') + .limit(pageSize) + .snapshots() + .map((query) => query.docs + .map((snapshot) => MessageModel.fromMap( + snapshot.id, + snapshot.data(), + )) + .toList()); + } + + @override + Stream getUnreadMessagesCount( + {required String userId, String? chatId}) async* { + var query = _firestore + .collection(chatCollection) + .where('users', arrayContains: userId) + .where('unreadMessageCount', isGreaterThan: 0) + .snapshots(); + + await for (var snapshot in query) { + var count = 0; + for (var doc in snapshot.docs) { + var data = doc.data(); + var lastMessageKey = data['lastMessage']; + + var message = + await getMessage(chatId: doc.id, messageId: lastMessageKey).first; + if (message?.senderId != userId) { + count += data['unreadMessageCount'] as int; + } + } + + yield count; + } + } + + @override + Future sendMessage({ + required String chatId, + required String senderId, + required String messageId, + String? text, + String? imageUrl, + DateTime? timestamp, + }) async { + var message = MessageModel( + chatId: chatId, + id: messageId, + text: text, + imageUrl: imageUrl, + timestamp: timestamp ?? DateTime.now(), + senderId: senderId); + + await _firestore + .collection(chatCollection) + .doc(chatId) + .collection(messageCollection) + .doc(messageId) + .set( + message.toMap(), + ); + + await _firestore.collection(chatCollection).doc(chatId).update( + { + 'lastMessage': messageId, + 'unreadMessageCount': FieldValue.increment(1), + 'lastUsed': DateTime.now().millisecondsSinceEpoch, + }, + ); + } + + @override + Future updateChat({required ChatModel chat}) async { + await _firestore + .collection(chatCollection) + .doc(chat.id) + .update(chat.toMap()); + } + + @override + Future uploadImage( + {required String path, required Uint8List image}) async { + final ref = _storage.ref().child(mediaPath).child(path); + final uploadTask = ref.putData(image); + final snapshot = await uploadTask.whenComplete(() => {}); + return await snapshot.ref.getDownloadURL(); + } +} diff --git a/packages/firebase_chat_repository/lib/src/firebase_user_repository.dart b/packages/firebase_chat_repository/lib/src/firebase_user_repository.dart new file mode 100644 index 0000000..7a25aef --- /dev/null +++ b/packages/firebase_chat_repository/lib/src/firebase_user_repository.dart @@ -0,0 +1,41 @@ +import 'package:chat_repository_interface/chat_repository_interface.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; + +class FirebaseUserRepository implements UserRepositoryInterface { + final FirebaseFirestore _firestore; + final String userCollection; + + FirebaseUserRepository({ + FirebaseFirestore? firestore, + this.userCollection = 'users', + }) : _firestore = firestore ?? FirebaseFirestore.instance; + + @override + Stream> getAllUsers() { + return _firestore + .collection(userCollection) + .snapshots() + .map((querySnapshot) { + return querySnapshot.docs + .map((doc) => UserModel.fromMap( + doc.id, + doc.data(), + )) + .toList(); + }); + } + + @override + Stream getUser({required String userId}) { + return _firestore + .collection(userCollection) + .doc(userId) + .snapshots() + .map((snapshot) { + return UserModel.fromMap( + snapshot.id, + snapshot.data() as Map, + ); + }); + } +} diff --git a/packages/firebase_chat_repository/pubspec.yaml b/packages/firebase_chat_repository/pubspec.yaml index cf291da..b1045d0 100644 --- a/packages/firebase_chat_repository/pubspec.yaml +++ b/packages/firebase_chat_repository/pubspec.yaml @@ -2,6 +2,7 @@ name: firebase_chat_repository description: "A new Flutter package project." version: 0.0.1 homepage: +publish_to: 'none' environment: sdk: '>=3.4.3 <4.0.0' @@ -11,6 +12,12 @@ dependencies: flutter: sdk: flutter + chat_repository_interface: + path: ../chat_repository_interface + + firebase_storage: any + cloud_firestore: any + dev_dependencies: flutter_test: sdk: flutter diff --git a/packages/flutter_chat/example/.gitignore b/packages/flutter_chat/example/.gitignore index 29a3a50..2078b96 100644 --- a/packages/flutter_chat/example/.gitignore +++ b/packages/flutter_chat/example/.gitignore @@ -41,3 +41,5 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +firebase_options.dart \ No newline at end of file diff --git a/packages/flutter_chat/example/firebase.json b/packages/flutter_chat/example/firebase.json new file mode 100644 index 0000000..bea05ef --- /dev/null +++ b/packages/flutter_chat/example/firebase.json @@ -0,0 +1 @@ +{"flutter":{"platforms":{"dart":{"lib/firebase_options.dart":{"projectId":"appshell-demo","configurations":{"android":"1:431820621472:android:6a0f2bc3559d17781babc5","web":"1:431820621472:web:f4b27eea24be24fd1babc5"}}},"android":{"default":{"projectId":"appshell-demo","appId":"1:431820621472:android:6a0f2bc3559d17781babc5","fileOutput":"android/app/google-services.json"}}}}} \ No newline at end of file diff --git a/packages/flutter_chat/example/lib/main.dart b/packages/flutter_chat/example/lib/main.dart index c2a0dca..83e9873 100644 --- a/packages/flutter_chat/example/lib/main.dart +++ b/packages/flutter_chat/example/lib/main.dart @@ -1,3 +1,7 @@ +// import 'package:example/firebase_options.dart'; +// import 'package:firebase_auth/firebase_auth.dart'; +// import 'package:firebase_chat_repository/firebase_chat_repository.dart'; +// import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_chat/flutter_chat.dart'; @@ -7,7 +11,6 @@ void main() { class MyApp extends StatelessWidget { const MyApp({super.key}); - @override Widget build(BuildContext context) { return MaterialApp( @@ -16,6 +19,18 @@ class MyApp extends StatelessWidget { useMaterial3: true, ), home: const MyHomePage(), + // FutureBuilder( + // future: Firebase.initializeApp( + // options: DefaultFirebaseOptions.currentPlatform, + // ), + // builder: (context, snapshot) { + // if (snapshot.connectionState != ConnectionState.done) { + // return const Center( + // child: CircularProgressIndicator(), + // ); + // } + // return const MyHomePage(); + // }), ); } } @@ -28,6 +43,12 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { + // @override + // void initState() { + // FirebaseAuth.instance.signInAnonymously(); + // super.initState(); + // } + @override Widget build(BuildContext context) { return Scaffold( diff --git a/packages/flutter_chat/example/pubspec.yaml b/packages/flutter_chat/example/pubspec.yaml index 11f06ec..e285bac 100644 --- a/packages/flutter_chat/example/pubspec.yaml +++ b/packages/flutter_chat/example/pubspec.yaml @@ -13,6 +13,10 @@ dependencies: cupertino_icons: ^1.0.8 flutter_chat: path: ../ + # firebase_chat_repository: + # path: ../../firebase_chat_repository + # firebase_core: any + # firebase_auth: any dev_dependencies: flutter_test: diff --git a/packages/flutter_chat/lib/flutter_chat.dart b/packages/flutter_chat/lib/flutter_chat.dart index 72f5e02..6f8053e 100644 --- a/packages/flutter_chat/lib/flutter_chat.dart +++ b/packages/flutter_chat/lib/flutter_chat.dart @@ -11,6 +11,7 @@ export "package:flutter_chat/src/flutter_chat_navigator_userstory.dart"; export "src/config/chat_builders.dart"; export "src/config/chat_options.dart"; export "src/config/chat_translations.dart"; +export 'src/config/screen_types.dart'; // Screens export "src/screens/chat_detail_screen.dart"; diff --git a/packages/flutter_chat/lib/src/config/chat_builders.dart b/packages/flutter_chat/lib/src/config/chat_builders.dart index f093d96..921b096 100644 --- a/packages/flutter_chat/lib/src/config/chat_builders.dart +++ b/packages/flutter_chat/lib/src/config/chat_builders.dart @@ -1,18 +1,14 @@ import "package:chat_repository_interface/chat_repository_interface.dart"; import "package:flutter/material.dart"; import "package:flutter_chat/src/config/chat_translations.dart"; +import "package:flutter_chat/src/config/screen_types.dart"; /// The chat builders class ChatBuilders { /// The chat builders constructor const ChatBuilders({ - this.chatScreenScaffoldBuilder, - this.newChatScreenScaffoldBuilder, - this.newGroupChatScreenScaffoldBuilder, - this.newGroupChatOverviewScaffoldBuilder, - this.chatProfileScaffoldBuilder, + this.baseScreenBuilder, this.messageInputBuilder, - this.chatDetailScaffoldBuilder, this.chatRowContainerBuilder, this.groupAvatarBuilder, this.imagePickerContainerBuilder, @@ -25,23 +21,21 @@ class ChatBuilders { this.loadingWidgetBuilder, }); - /// The chat screen scaffold builder - final ScaffoldBuilder? chatScreenScaffoldBuilder; - - /// The new chat screen scaffold builder - final ScaffoldBuilder? newChatScreenScaffoldBuilder; - - /// The new group chat overview scaffold builder - final ScaffoldBuilder? newGroupChatOverviewScaffoldBuilder; - - /// The new group chat screen scaffold builder - final ScaffoldBuilder? newGroupChatScreenScaffoldBuilder; - - /// The chat detail scaffold builder - final ScaffoldBuilder? chatDetailScaffoldBuilder; - - /// The chat profile scaffold builder - final ScaffoldBuilder? chatProfileScaffoldBuilder; + /// The base screen builder + /// This builder is used to build the base screen for the chat + /// You can switch on the [screenType] to build different screens + /// ```dart + /// baseScreenBuilder: (context, screenType, appBar, body) { + /// switch (screenType) { + /// case ScreenType.chatScreen: + /// return Scaffold( + /// appBar: appBar, + /// body: body, + /// ); + /// case ScreenType.chatDetailScreen: + /// // And so on.... + /// ``` + final BaseScreenBuilder? baseScreenBuilder; /// The message input builder final TextInputBuilder? messageInputBuilder; @@ -100,12 +94,12 @@ typedef TextInputBuilder = Widget Function( ChatTranslations translations, ); -/// The scaffold builder -typedef ScaffoldBuilder = Scaffold Function( +/// The base screen builder +typedef BaseScreenBuilder = Widget Function( BuildContext context, + ScreenType screenType, PreferredSizeWidget appBar, Widget body, - Color backgroundColor, ); /// The container builder diff --git a/packages/flutter_chat/lib/src/config/screen_types.dart b/packages/flutter_chat/lib/src/config/screen_types.dart new file mode 100644 index 0000000..7ae873c --- /dev/null +++ b/packages/flutter_chat/lib/src/config/screen_types.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_chat/src/screens/chat_detail_screen.dart'; +import 'package:flutter_chat/src/screens/chat_profile_screen.dart'; +import 'package:flutter_chat/src/screens/chat_screen.dart'; +import 'package:flutter_chat/src/screens/creation/new_chat_screen.dart'; +import 'package:flutter_chat/src/screens/creation/new_group_chat_overview.dart'; +import 'package:flutter_chat/src/screens/creation/new_group_chat_screen.dart'; + +enum ScreenType { + chatScreen(screen: ChatScreen), + chatDetailScreen(screen: ChatDetailScreen), + chatProfileScreen(screen: ChatProfileScreen), + newChatScreen(screen: NewChatScreen), + newGroupChatScreen(screen: NewGroupChatScreen), + newGroupChatOverview(screen: NewGroupChatOverview); + + const ScreenType({ + required this.screen, + }); + + final Type screen; +} + +extension MapFromWidget on Widget { + ScreenType get mapScreenType { + return ScreenType.values.firstWhere((e) => e.screen == this.runtimeType); + } +} diff --git a/packages/flutter_chat/lib/src/flutter_chat_entry_widget.dart b/packages/flutter_chat/lib/src/flutter_chat_entry_widget.dart index 87c4385..a0f1bbd 100644 --- a/packages/flutter_chat/lib/src/flutter_chat_entry_widget.dart +++ b/packages/flutter_chat/lib/src/flutter_chat_entry_widget.dart @@ -9,6 +9,7 @@ class FlutterChatEntryWidget extends StatefulWidget { const FlutterChatEntryWidget({ required this.userId, this.chatService, + this.options, this.onTap, this.widgetSize = 75, this.backgroundColor = Colors.grey, @@ -46,6 +47,9 @@ class FlutterChatEntryWidget extends StatefulWidget { /// Text style for the counter. final TextStyle? textStyle; + /// The chat options + final ChatOptions? options; + @override State createState() => _FlutterChatEntryWidgetState(); } @@ -69,6 +73,7 @@ class _FlutterChatEntryWidgetState extends State { builder: (context) => FlutterChatNavigatorUserstory( userId: widget.userId, chatService: chatService, + chatOptions: widget.options, ), ), ), diff --git a/packages/flutter_chat/lib/src/flutter_chat_navigator_userstory.dart b/packages/flutter_chat/lib/src/flutter_chat_navigator_userstory.dart index deee7a0..b0b1a80 100644 --- a/packages/flutter_chat/lib/src/flutter_chat_navigator_userstory.dart +++ b/packages/flutter_chat/lib/src/flutter_chat_navigator_userstory.dart @@ -100,9 +100,8 @@ class _NavigatorWrapper extends StatelessWidget { chatService: chatService, chatOptions: chatOptions, chat: chat, - onReadChat: (chat) async => chatService.markAsRead( - chatId: chat.id, - ), + onReadChat: (chat) async => + chatService.markAsRead(chatId: chat.id, userId: userId), onPressChatTitle: (chat) async { if (chat.isGroupChat) { return route(context, chatProfileScreen(context, null, chat)); @@ -117,7 +116,8 @@ class _NavigatorWrapper extends StatelessWidget { onPressUserProfile: (user) => route(context, chatProfileScreen(context, user, null)), onUploadImage: (data) async { - var path = await chatService.uploadImage(path: "chats", image: data); + var path = await chatService.uploadImage( + path: "chats/${chat.id}-$userId-${DateTime.now()}", image: data); await chatService.sendMessage( messageId: "${chat.id}-$userId-${DateTime.now()}", @@ -190,7 +190,8 @@ class _NavigatorWrapper extends StatelessWidget { onComplete: (users, title, description, image) async { String? path; if (image != null) { - path = await chatService.uploadImage(path: "groups", image: image); + path = await chatService.uploadImage( + path: "groups/$title", image: image); } var chat = await createGroupChat( users, diff --git a/packages/flutter_chat/lib/src/screens/chat_detail_screen.dart b/packages/flutter_chat/lib/src/screens/chat_detail_screen.dart index d5529ba..d77734b 100644 --- a/packages/flutter_chat/lib/src/screens/chat_detail_screen.dart +++ b/packages/flutter_chat/lib/src/screens/chat_detail_screen.dart @@ -5,6 +5,7 @@ import "package:cached_network_image/cached_network_image.dart"; import "package:chat_repository_interface/chat_repository_interface.dart"; import "package:flutter/material.dart"; import "package:flutter_chat/src/config/chat_options.dart"; +import "package:flutter_chat/src/config/screen_types.dart"; import "package:flutter_chat/src/screens/creation/widgets/image_picker.dart"; import "package:flutter_chat/src/services/date_formatter.dart"; import "package:flutter_profile/flutter_profile.dart"; @@ -85,46 +86,47 @@ class _ChatDetailScreenState extends State { @override Widget build(BuildContext context) { - var theme = Theme.of(context); + if (widget.chatOptions.builders.baseScreenBuilder == null) { + return Scaffold( + appBar: _AppBar( + chatTitle: chatTitle, + chatOptions: widget.chatOptions, + onPressChatTitle: widget.onPressChatTitle, + chatModel: widget.chat, + ), + body: _Body( + chatService: widget.chatService, + options: widget.chatOptions, + chat: widget.chat, + currentUserId: widget.userId, + onPressUserProfile: widget.onPressUserProfile, + onUploadImage: widget.onUploadImage, + onMessageSubmit: widget.onMessageSubmit, + onReadChat: widget.onReadChat, + ), + ); + } - return widget.chatOptions.builders.chatDetailScaffoldBuilder?.call( - context, - _AppBar( - chatTitle: chatTitle, - chatOptions: widget.chatOptions, - onPressChatTitle: widget.onPressChatTitle, - chatModel: widget.chat, - ), - _Body( - chatService: widget.chatService, - options: widget.chatOptions, - chat: widget.chat, - currentUserId: widget.userId, - onPressUserProfile: widget.onPressUserProfile, - onUploadImage: widget.onUploadImage, - onMessageSubmit: widget.onMessageSubmit, - onReadChat: widget.onReadChat, - ), - theme.scaffoldBackgroundColor, - ) ?? - Scaffold( - appBar: _AppBar( - chatTitle: chatTitle, - chatOptions: widget.chatOptions, - onPressChatTitle: widget.onPressChatTitle, - chatModel: widget.chat, - ), - body: _Body( - chatService: widget.chatService, - options: widget.chatOptions, - chat: widget.chat, - currentUserId: widget.userId, - onPressUserProfile: widget.onPressUserProfile, - onUploadImage: widget.onUploadImage, - onMessageSubmit: widget.onMessageSubmit, - onReadChat: widget.onReadChat, - ), - ); + return widget.chatOptions.builders.baseScreenBuilder!.call( + context, + widget.mapScreenType, + _AppBar( + chatTitle: chatTitle, + chatOptions: widget.chatOptions, + onPressChatTitle: widget.onPressChatTitle, + chatModel: widget.chat, + ), + _Body( + chatService: widget.chatService, + options: widget.chatOptions, + chat: widget.chat, + currentUserId: widget.userId, + onPressUserProfile: widget.onPressUserProfile, + onUploadImage: widget.onUploadImage, + onMessageSubmit: widget.onMessageSubmit, + onReadChat: widget.onReadChat, + ), + ); } } diff --git a/packages/flutter_chat/lib/src/screens/chat_profile_screen.dart b/packages/flutter_chat/lib/src/screens/chat_profile_screen.dart index fb0f6cf..81ad20f 100644 --- a/packages/flutter_chat/lib/src/screens/chat_profile_screen.dart +++ b/packages/flutter_chat/lib/src/screens/chat_profile_screen.dart @@ -1,6 +1,7 @@ import "package:chat_repository_interface/chat_repository_interface.dart"; import "package:flutter/material.dart"; import "package:flutter_chat/src/config/chat_options.dart"; +import "package:flutter_chat/src/config/screen_types.dart"; import "package:flutter_profile/flutter_profile.dart"; /// The chat profile screen @@ -41,42 +42,43 @@ class ChatProfileScreen extends StatelessWidget { @override Widget build(BuildContext context) { - var theme = Theme.of(context); + if (options.builders.baseScreenBuilder == null) { + return Scaffold( + appBar: _AppBar( + user: userModel, + chat: chatModel, + options: options, + ), + body: _Body( + currentUser: userId, + options: options, + service: service, + user: userModel, + chat: chatModel, + onTapUser: onTapUser, + onPressStartChat: onPressStartChat, + ), + ); + } - return options.builders.chatProfileScaffoldBuilder?.call( - context, - _AppBar( - user: userModel, - chat: chatModel, - options: options, - ), - _Body( - service: service, - currentUser: userId, - options: options, - user: userModel, - chat: chatModel, - onTapUser: onTapUser, - onPressStartChat: onPressStartChat, - ), - theme.scaffoldBackgroundColor, - ) ?? - Scaffold( - appBar: _AppBar( - user: userModel, - chat: chatModel, - options: options, - ), - body: _Body( - currentUser: userId, - options: options, - service: service, - user: userModel, - chat: chatModel, - onTapUser: onTapUser, - onPressStartChat: onPressStartChat, - ), - ); + return options.builders.baseScreenBuilder!.call( + context, + this.mapScreenType, + _AppBar( + user: userModel, + chat: chatModel, + options: options, + ), + _Body( + currentUser: userId, + options: options, + service: service, + user: userModel, + chat: chatModel, + onTapUser: onTapUser, + onPressStartChat: onPressStartChat, + ), + ); } } diff --git a/packages/flutter_chat/lib/src/screens/chat_screen.dart b/packages/flutter_chat/lib/src/screens/chat_screen.dart index 8939730..ac1dd84 100644 --- a/packages/flutter_chat/lib/src/screens/chat_screen.dart +++ b/packages/flutter_chat/lib/src/screens/chat_screen.dart @@ -2,6 +2,7 @@ import "package:chat_repository_interface/chat_repository_interface.dart"; import "package:flutter/material.dart"; import "package:flutter_chat/src/config/chat_options.dart"; import "package:flutter_chat/src/config/chat_translations.dart"; +import "package:flutter_chat/src/config/screen_types.dart"; import "package:flutter_chat/src/services/date_formatter.dart"; import "package:flutter_profile/flutter_profile.dart"; @@ -39,39 +40,41 @@ class ChatScreen extends StatelessWidget { @override Widget build(BuildContext context) { - var theme = Theme.of(context); - return chatOptions.builders.chatScreenScaffoldBuilder?.call( - context, - _AppBar( - userId: userId, - chatOptions: chatOptions, - chatService: chatService, - ), - _Body( - userId: userId, - chatOptions: chatOptions, - chatService: chatService, - onPressChat: onPressChat, - onPressStartChat: onPressStartChat, - onDeleteChat: onDeleteChat, - ), - theme.scaffoldBackgroundColor, - ) ?? - Scaffold( - appBar: _AppBar( - userId: userId, - chatOptions: chatOptions, - chatService: chatService, - ), - body: _Body( - userId: userId, - chatOptions: chatOptions, - chatService: chatService, - onPressChat: onPressChat, - onPressStartChat: onPressStartChat, - onDeleteChat: onDeleteChat, - ), - ); + if (chatOptions.builders.baseScreenBuilder == null) { + return Scaffold( + appBar: _AppBar( + userId: userId, + chatOptions: chatOptions, + chatService: chatService, + ), + body: _Body( + userId: userId, + chatOptions: chatOptions, + chatService: chatService, + onPressChat: onPressChat, + onPressStartChat: onPressStartChat, + onDeleteChat: onDeleteChat, + ), + ); + } + + return chatOptions.builders.baseScreenBuilder!.call( + context, + this.mapScreenType, + _AppBar( + userId: userId, + chatOptions: chatOptions, + chatService: chatService, + ), + _Body( + userId: userId, + chatOptions: chatOptions, + chatService: chatService, + onPressChat: onPressChat, + onPressStartChat: onPressStartChat, + onDeleteChat: onDeleteChat, + ), + ); } } @@ -395,9 +398,13 @@ class _ChatListItem extends StatelessWidget { var data = snapshot.data; + var showUnreadMessageCount = + data != null && data.senderId != currentUserId; + return _ChatRow( title: chat.chatName ?? translations.groupNameEmpty, - unreadMessages: chat.unreadMessageCount, + unreadMessages: + showUnreadMessageCount ? chat.unreadMessageCount : 0, subTitle: data != null ? data.isTextMessage ? data.text @@ -465,8 +472,12 @@ class _ChatListItem extends StatelessWidget { var data = snapshot.data; + var showUnreadMessageCount = + data != null && data.senderId != currentUserId; + return _ChatRow( - unreadMessages: chat.unreadMessageCount, + unreadMessages: + showUnreadMessageCount ? chat.unreadMessageCount : 0, avatar: options.builders.userAvatarBuilder?.call( context, otherUser, diff --git a/packages/flutter_chat/lib/src/screens/creation/new_chat_screen.dart b/packages/flutter_chat/lib/src/screens/creation/new_chat_screen.dart index 58f4465..dd72687 100644 --- a/packages/flutter_chat/lib/src/screens/creation/new_chat_screen.dart +++ b/packages/flutter_chat/lib/src/screens/creation/new_chat_screen.dart @@ -1,6 +1,7 @@ import "package:chat_repository_interface/chat_repository_interface.dart"; import "package:flutter/material.dart"; import "package:flutter_chat/src/config/chat_options.dart"; +import "package:flutter_chat/src/config/screen_types.dart"; import "package:flutter_chat/src/screens/creation/widgets/search_field.dart"; import "package:flutter_chat/src/screens/creation/widgets/search_icon.dart"; import "package:flutter_chat/src/screens/creation/widgets/user_list.dart"; @@ -44,74 +45,75 @@ class _NewChatScreenState extends State { @override Widget build(BuildContext context) { - var theme = Theme.of(context); + if (widget.chatOptions.builders.baseScreenBuilder == null) { + return Scaffold( + appBar: _AppBar( + chatOptions: widget.chatOptions, + isSearching: _isSearching, + onSearch: (query) { + setState(() { + _isSearching = query.isNotEmpty; + this.query = query; + }); + }, + onPressedSearchIcon: () { + setState(() { + _isSearching = !_isSearching; + query = ""; + }); - return widget.chatOptions.builders.newChatScreenScaffoldBuilder?.call( - context, - _AppBar( - chatOptions: widget.chatOptions, - isSearching: _isSearching, - onSearch: (query) { - setState(() { - _isSearching = query.isNotEmpty; - this.query = query; - }); - }, - onPressedSearchIcon: () { - setState(() { - _isSearching = !_isSearching; - query = ""; - }); + if (_isSearching) { + _textFieldFocusNode.requestFocus(); + } + }, + focusNode: _textFieldFocusNode, + ), + body: _Body( + chatOptions: widget.chatOptions, + chatService: widget.chatService, + isSearching: _isSearching, + onPressCreateGroupChat: widget.onPressCreateGroupChat, + onPressCreateChat: widget.onPressCreateChat, + userId: widget.userId, + query: query, + ), + ); + } - if (_isSearching) { - _textFieldFocusNode.requestFocus(); - } - }, - focusNode: _textFieldFocusNode, - ), - _Body( - chatOptions: widget.chatOptions, - chatService: widget.chatService, - isSearching: _isSearching, - onPressCreateGroupChat: widget.onPressCreateGroupChat, - onPressCreateChat: widget.onPressCreateChat, - userId: widget.userId, - query: query, - ), - theme.scaffoldBackgroundColor, - ) ?? - Scaffold( - appBar: _AppBar( - chatOptions: widget.chatOptions, - isSearching: _isSearching, - onSearch: (query) { - setState(() { - _isSearching = query.isNotEmpty; - this.query = query; - }); - }, - onPressedSearchIcon: () { - setState(() { - _isSearching = !_isSearching; - query = ""; - }); + return widget.chatOptions.builders.baseScreenBuilder!.call( + context, + widget.mapScreenType, + _AppBar( + chatOptions: widget.chatOptions, + isSearching: _isSearching, + onSearch: (query) { + setState(() { + _isSearching = query.isNotEmpty; + this.query = query; + }); + }, + onPressedSearchIcon: () { + setState(() { + _isSearching = !_isSearching; + query = ""; + }); - if (_isSearching) { - _textFieldFocusNode.requestFocus(); - } - }, - focusNode: _textFieldFocusNode, - ), - body: _Body( - chatOptions: widget.chatOptions, - chatService: widget.chatService, - isSearching: _isSearching, - onPressCreateGroupChat: widget.onPressCreateGroupChat, - onPressCreateChat: widget.onPressCreateChat, - userId: widget.userId, - query: query, - ), - ); + if (_isSearching) { + _textFieldFocusNode.requestFocus(); + } + }, + focusNode: _textFieldFocusNode, + ), + _Body( + chatOptions: widget.chatOptions, + chatService: widget.chatService, + isSearching: _isSearching, + onPressCreateGroupChat: widget.onPressCreateGroupChat, + onPressCreateChat: widget.onPressCreateChat, + userId: widget.userId, + query: query, + ), + ); } } diff --git a/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart b/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart index 45811f2..4612053 100644 --- a/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart +++ b/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart @@ -3,6 +3,7 @@ import "dart:typed_data"; import "package:chat_repository_interface/chat_repository_interface.dart"; import "package:flutter/material.dart"; import "package:flutter_chat/src/config/chat_options.dart"; +import "package:flutter_chat/src/config/screen_types.dart"; import "package:flutter_chat/src/screens/creation/widgets/image_picker.dart"; import "package:flutter_profile/flutter_profile.dart"; @@ -34,30 +35,31 @@ class NewGroupChatOverview extends StatelessWidget { @override Widget build(BuildContext context) { - var theme = Theme.of(context); + if (options.builders.baseScreenBuilder == null) { + return Scaffold( + appBar: _AppBar( + options: options, + ), + body: _Body( + options: options, + users: users, + onComplete: onComplete, + ), + ); + } - return options.builders.newGroupChatOverviewScaffoldBuilder?.call( - context, - _AppBar( - options: options, - ), - _Body( - options: options, - users: users, - onComplete: onComplete, - ), - theme.scaffoldBackgroundColor, - ) ?? - Scaffold( - appBar: _AppBar( - options: options, - ), - body: _Body( - options: options, - users: users, - onComplete: onComplete, - ), - ); + return options.builders.baseScreenBuilder!.call( + context, + this.mapScreenType, + _AppBar( + options: options, + ), + _Body( + options: options, + users: users, + onComplete: onComplete, + ), + ); } } diff --git a/packages/flutter_chat/lib/src/screens/creation/new_group_chat_screen.dart b/packages/flutter_chat/lib/src/screens/creation/new_group_chat_screen.dart index 7a1503f..f2c6bcf 100644 --- a/packages/flutter_chat/lib/src/screens/creation/new_group_chat_screen.dart +++ b/packages/flutter_chat/lib/src/screens/creation/new_group_chat_screen.dart @@ -1,6 +1,7 @@ import "package:chat_repository_interface/chat_repository_interface.dart"; import "package:flutter/material.dart"; import "package:flutter_chat/src/config/chat_options.dart"; +import "package:flutter_chat/src/config/screen_types.dart"; import "package:flutter_chat/src/screens/creation/widgets/search_field.dart"; import "package:flutter_chat/src/screens/creation/widgets/search_icon.dart"; import "package:flutter_chat/src/screens/creation/widgets/user_list.dart"; @@ -42,76 +43,77 @@ class _NewGroupChatScreenState extends State { @override Widget build(BuildContext context) { - var theme = Theme.of(context); + if (widget.chatOptions.builders.baseScreenBuilder == null) { + return Scaffold( + appBar: _AppBar( + chatOptions: widget.chatOptions, + isSearching: _isSearching, + onSearch: (query) { + setState(() { + _isSearching = query.isNotEmpty; + this.query = query; + }); + }, + onPressedSearchIcon: () { + setState(() { + _isSearching = !_isSearching; + query = ""; + }); - return widget.chatOptions.builders.newGroupChatScreenScaffoldBuilder?.call( - context, - _AppBar( - chatOptions: widget.chatOptions, - isSearching: _isSearching, - onSearch: (query) { - setState(() { - _isSearching = query.isNotEmpty; - this.query = query; - }); - }, - onPressedSearchIcon: () { - setState(() { - _isSearching = !_isSearching; - query = ""; - }); + if (_isSearching) { + _textFieldFocusNode.requestFocus(); + } + }, + focusNode: _textFieldFocusNode, + ), + body: _Body( + onSelectedUser: handleUserTap, + selectedUsers: selectedUsers, + onPressGroupChatOverview: widget.onContinue, + chatOptions: widget.chatOptions, + chatService: widget.chatService, + isSearching: _isSearching, + userId: widget.userId, + query: query, + ), + ); + } - if (_isSearching) { - _textFieldFocusNode.requestFocus(); - } - }, - focusNode: _textFieldFocusNode, - ), - _Body( - onSelectedUser: handleUserTap, - selectedUsers: selectedUsers, - onPressGroupChatOverview: widget.onContinue, - chatOptions: widget.chatOptions, - chatService: widget.chatService, - isSearching: _isSearching, - userId: widget.userId, - query: query, - ), - theme.scaffoldBackgroundColor, - ) ?? - Scaffold( - appBar: _AppBar( - chatOptions: widget.chatOptions, - isSearching: _isSearching, - onSearch: (query) { - setState(() { - _isSearching = query.isNotEmpty; - this.query = query; - }); - }, - onPressedSearchIcon: () { - setState(() { - _isSearching = !_isSearching; - query = ""; - }); + return widget.chatOptions.builders.baseScreenBuilder!.call( + context, + widget.mapScreenType, + _AppBar( + chatOptions: widget.chatOptions, + isSearching: _isSearching, + onSearch: (query) { + setState(() { + _isSearching = query.isNotEmpty; + this.query = query; + }); + }, + onPressedSearchIcon: () { + setState(() { + _isSearching = !_isSearching; + query = ""; + }); - if (_isSearching) { - _textFieldFocusNode.requestFocus(); - } - }, - focusNode: _textFieldFocusNode, - ), - body: _Body( - onSelectedUser: handleUserTap, - selectedUsers: selectedUsers, - onPressGroupChatOverview: widget.onContinue, - chatOptions: widget.chatOptions, - chatService: widget.chatService, - isSearching: _isSearching, - userId: widget.userId, - query: query, - ), - ); + if (_isSearching) { + _textFieldFocusNode.requestFocus(); + } + }, + focusNode: _textFieldFocusNode, + ), + _Body( + onSelectedUser: handleUserTap, + selectedUsers: selectedUsers, + onPressGroupChatOverview: widget.onContinue, + chatOptions: widget.chatOptions, + chatService: widget.chatService, + isSearching: _isSearching, + userId: widget.userId, + query: query, + ), + ); } void handleUserTap(UserModel user) {