diff --git a/.github/workflows/melos-ci.yml b/.github/workflows/melos-ci.yml index dab1703..a7ad1d2 100644 --- a/.github/workflows/melos-ci.yml +++ b/.github/workflows/melos-ci.yml @@ -12,4 +12,3 @@ jobs: permissions: write-all with: subfolder: '.' # add optional subfolder to run workflow in - flutter_version: 3.19.6 diff --git a/packages/chat_repository_interface/lib/src/local/local_user_repository.dart b/packages/chat_repository_interface/lib/src/local/local_user_repository.dart index c57ef7f..f68fbc2 100644 --- a/packages/chat_repository_interface/lib/src/local/local_user_repository.dart +++ b/packages/chat_repository_interface/lib/src/local/local_user_repository.dart @@ -10,25 +10,25 @@ class LocalUserRepository implements UserRepositoryInterface { BehaviorSubject>(); final List _users = [ - UserModel( + const UserModel( id: "1", firstName: "John", lastName: "Doe", imageUrl: "https://picsum.photos/200/300", ), - UserModel( + const UserModel( id: "2", firstName: "Jane", lastName: "Doe", imageUrl: "https://picsum.photos/200/300", ), - UserModel( + const UserModel( id: "3", firstName: "Frans", lastName: "Timmermans", imageUrl: "https://picsum.photos/200/300", ), - UserModel( + const UserModel( id: "4", firstName: "Hendrik-Jan", lastName: "De derde", 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 b523498..d74573f 100644 --- a/packages/chat_repository_interface/lib/src/models/chat_model.dart +++ b/packages/chat_repository_interface/lib/src/models/chat_model.dart @@ -25,6 +25,22 @@ class ChatModel { this.unreadMessageCount = 0, }); + /// The factory chat model that creates a chat model from a map + factory ChatModel.fromMap(String id, Map data) => 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, + ); + /// The chat id final String id; @@ -81,34 +97,16 @@ class ChatModel { 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, + "users": users, + "isGroupChat": isGroupChat, + "chatName": chatName, + "description": description, + "imageUrl": imageUrl, + "canBeDeleted": canBeDeleted, + "lastUsed": lastUsed?.millisecondsSinceEpoch, + "lastMessage": lastMessage, + "unreadMessageCount": unreadMessageCount, }; } 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 ce9a25d..b0329f5 100644 --- a/packages/chat_repository_interface/lib/src/models/message_model.dart +++ b/packages/chat_repository_interface/lib/src/models/message_model.dart @@ -15,6 +15,17 @@ class MessageModel { required this.timestamp, required this.senderId, }); + + factory MessageModel.fromMap(String id, Map map) => + MessageModel( + chatId: map["chatId"], + id: id, + text: map["text"], + imageUrl: map["imageUrl"], + timestamp: DateTime.fromMillisecondsSinceEpoch(map["timestamp"]), + senderId: map["senderId"], + ); + /// The chat id final String chatId; @@ -51,26 +62,13 @@ class MessageModel { 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, - }; - } + Map toMap() => { + "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 3673615..b8b664b 100644 --- a/packages/chat_repository_interface/lib/src/models/user_model.dart +++ b/packages/chat_repository_interface/lib/src/models/user_model.dart @@ -14,6 +14,13 @@ class UserModel { this.imageUrl, }); + factory UserModel.fromMap(String id, Map data) => UserModel( + id: id, + firstName: data["firstName"], + lastName: data["lastName"], + imageUrl: data["imageUrl"], + ); + /// The user id final String id; @@ -25,15 +32,6 @@ 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 5113b01..e542ee4 100644 --- a/packages/chat_repository_interface/lib/src/services/chat_service.dart +++ b/packages/chat_repository_interface/lib/src/services/chat_service.dart @@ -188,9 +188,8 @@ class ChatService { /// Returns a [Stream] of [int]. Stream getUnreadMessagesCount({ required String userId, - }) { - return chatRepository.getUnreadMessagesCount(userId: userId); - } + }) => + chatRepository.getUnreadMessagesCount(userId: userId); /// Upload an image with the given parameters. /// [path] is the image path. diff --git a/packages/firebase_chat_repository/lib/firebase_chat_repository.dart b/packages/firebase_chat_repository/lib/firebase_chat_repository.dart index 0257c1d..e6effcb 100644 --- a/packages/firebase_chat_repository/lib/firebase_chat_repository.dart +++ b/packages/firebase_chat_repository/lib/firebase_chat_repository.dart @@ -1,2 +1,2 @@ -export 'src/firebase_chat_repository.dart'; -export 'src/firebase_user_repository.dart'; +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 index ec26ad4..28a70f4 100644 --- a/packages/firebase_chat_repository/lib/src/firebase_chat_repository.dart +++ b/packages/firebase_chat_repository/lib/src/firebase_chat_repository.dart @@ -1,25 +1,24 @@ -import 'dart:typed_data'; +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'; +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 { + FirebaseChatRepository({ + FirebaseFirestore? firestore, + FirebaseStorage? storage, + this.chatCollection = "chats", + this.messageCollection = "messages", + this.mediaPath = "chat", + }) : _firestore = firestore ?? FirebaseFirestore.instance, + _storage = storage ?? FirebaseStorage.instance; 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, @@ -29,13 +28,13 @@ class FirebaseChatRepository implements ChatRepositoryInterface { String? imageUrl, List? messages, }) async { - final chatData = { - 'users': users, - 'isGroupChat': isGroupChat, - 'chatName': chatName, - 'description': description, - 'imageUrl': imageUrl, - 'createdAt': DateTime.now().millisecondsSinceEpoch, + var chatData = { + "users": users, + "isGroupChat": isGroupChat, + "chatName": chatName, + "description": description, + "imageUrl": imageUrl, + "createdAt": DateTime.now().millisecondsSinceEpoch, }; await _firestore.collection(chatCollection).add(chatData); } @@ -46,48 +45,45 @@ class FirebaseChatRepository implements ChatRepositoryInterface { } @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); - }); - } + Stream getChat({required String chatId}) => _firestore + .collection(chatCollection) + .doc(chatId) + .snapshots() + .map((snapshot) { + var data = snapshot.data()!; + 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, + Stream?> getChats({required String userId}) => _firestore + .collection(chatCollection) + .where("users", arrayContains: userId) + .snapshots() + .map( + (querySnapshot) => querySnapshot.docs.map((doc) { + var data = doc.data(); + return ChatModel.fromMap(doc.id, data); + }).toList(), ); - }); - } + + @override + Stream getMessage({ + required String chatId, + required String messageId, + }) => + _firestore + .collection(chatCollection) + .doc(chatId) + .collection(messageCollection) + .doc(messageId) + .snapshots() + .map((snapshot) { + var data = snapshot.data()!; + return MessageModel.fromMap( + snapshot.id, + data, + ); + }); @override Stream?> getMessages({ @@ -95,41 +91,46 @@ class FirebaseChatRepository implements ChatRepositoryInterface { 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()); - } + }) => + _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* { + Stream getUnreadMessagesCount({ + required String userId, + String? chatId, + }) async* { var query = _firestore .collection(chatCollection) - .where('users', arrayContains: userId) - .where('unreadMessageCount', isGreaterThan: 0) + .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 lastMessageKey = data["lastMessage"]; var message = await getMessage(chatId: doc.id, messageId: lastMessageKey).first; if (message?.senderId != userId) { - count += data['unreadMessageCount'] as int; + count += data["unreadMessageCount"] as int; } } @@ -147,12 +148,13 @@ class FirebaseChatRepository implements ChatRepositoryInterface { DateTime? timestamp, }) async { var message = MessageModel( - chatId: chatId, - id: messageId, - text: text, - imageUrl: imageUrl, - timestamp: timestamp ?? DateTime.now(), - senderId: senderId); + chatId: chatId, + id: messageId, + text: text, + imageUrl: imageUrl, + timestamp: timestamp ?? DateTime.now(), + senderId: senderId, + ); await _firestore .collection(chatCollection) @@ -165,9 +167,9 @@ class FirebaseChatRepository implements ChatRepositoryInterface { await _firestore.collection(chatCollection).doc(chatId).update( { - 'lastMessage': messageId, - 'unreadMessageCount': FieldValue.increment(1), - 'lastUsed': DateTime.now().millisecondsSinceEpoch, + "lastMessage": messageId, + "unreadMessageCount": FieldValue.increment(1), + "lastUsed": DateTime.now().millisecondsSinceEpoch, }, ); } @@ -181,11 +183,13 @@ class FirebaseChatRepository implements ChatRepositoryInterface { } @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(); + Future uploadImage({ + required String path, + required Uint8List image, + }) async { + var ref = _storage.ref().child(mediaPath).child(path); + var uploadTask = ref.putData(image); + var snapshot = await uploadTask.whenComplete(() => {}); + return 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 index 7a25aef..58f8128 100644 --- a/packages/firebase_chat_repository/lib/src/firebase_user_repository.dart +++ b/packages/firebase_chat_repository/lib/src/firebase_user_repository.dart @@ -1,41 +1,33 @@ -import 'package:chat_repository_interface/chat_repository_interface.dart'; -import 'package:cloud_firestore/cloud_firestore.dart'; +import "package:chat_repository_interface/chat_repository_interface.dart"; +import "package:cloud_firestore/cloud_firestore.dart"; class FirebaseUserRepository implements UserRepositoryInterface { + FirebaseUserRepository({ + FirebaseFirestore? firestore, + this.userCollection = "users", + }) : _firestore = firestore ?? FirebaseFirestore.instance; final FirebaseFirestore _firestore; final String userCollection; - FirebaseUserRepository({ - FirebaseFirestore? firestore, - this.userCollection = 'users', - }) : _firestore = firestore ?? FirebaseFirestore.instance; + @override + Stream> getAllUsers() => + _firestore.collection(userCollection).snapshots().map( + (querySnapshot) => querySnapshot.docs + .map( + (doc) => UserModel.fromMap( + doc.id, + doc.data(), + ), + ) + .toList(), + ); @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, - ); - }); - } + Stream getUser({required String userId}) => + _firestore.collection(userCollection).doc(userId).snapshots().map( + (snapshot) => UserModel.fromMap( + snapshot.id, + snapshot.data()!, + ), + ); } diff --git a/packages/flutter_chat/example/pubspec.yaml b/packages/flutter_chat/example/pubspec.yaml index e285bac..db0bbd0 100644 --- a/packages/flutter_chat/example/pubspec.yaml +++ b/packages/flutter_chat/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: ^3.5.0 + sdk: ^3.4.3 dependencies: flutter: diff --git a/packages/flutter_chat/lib/src/config/chat_translations.dart b/packages/flutter_chat/lib/src/config/chat_translations.dart index 2113167..fb0685f 100644 --- a/packages/flutter_chat/lib/src/config/chat_translations.dart +++ b/packages/flutter_chat/lib/src/config/chat_translations.dart @@ -56,7 +56,7 @@ class ChatTranslations { this.chatsTitle = "Chats", this.chatsUnread = "unread", this.newChatButton = "Start chat", - this.newGroupChatButton = "Create a groupchat", + this.newGroupChatButton = "Start a groupchat", this.newChatTitle = "Start a chat", this.image = "Image", this.searchPlaceholder = "Search...", diff --git a/packages/flutter_chat/lib/src/config/screen_types.dart b/packages/flutter_chat/lib/src/config/screen_types.dart index 7ae873c..6f1c03c 100644 --- a/packages/flutter_chat/lib/src/config/screen_types.dart +++ b/packages/flutter_chat/lib/src/config/screen_types.dart @@ -1,10 +1,10 @@ -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'; +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), @@ -22,7 +22,6 @@ enum ScreenType { } extension MapFromWidget on Widget { - ScreenType get mapScreenType { - return ScreenType.values.firstWhere((e) => e.screen == this.runtimeType); - } + ScreenType get mapScreenType => + ScreenType.values.firstWhere((e) => e.screen == runtimeType); } 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 b0b1a80..256a63a 100644 --- a/packages/flutter_chat/lib/src/flutter_chat_navigator_userstory.dart +++ b/packages/flutter_chat/lib/src/flutter_chat_navigator_userstory.dart @@ -117,7 +117,9 @@ class _NavigatorWrapper extends StatelessWidget { route(context, chatProfileScreen(context, user, null)), onUploadImage: (data) async { var path = await chatService.uploadImage( - path: "chats/${chat.id}-$userId-${DateTime.now()}", image: data); + path: "chats/${chat.id}-$userId-${DateTime.now()}", + image: data, + ); await chatService.sendMessage( messageId: "${chat.id}-$userId-${DateTime.now()}", @@ -191,7 +193,9 @@ class _NavigatorWrapper extends StatelessWidget { String? path; if (image != null) { path = await chatService.uploadImage( - path: "groups/$title", image: image); + path: "groups/$title", + image: image, + ); } var chat = await createGroupChat( users, 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 81ad20f..eb4dcaf 100644 --- a/packages/flutter_chat/lib/src/screens/chat_profile_screen.dart +++ b/packages/flutter_chat/lib/src/screens/chat_profile_screen.dart @@ -63,7 +63,7 @@ class ChatProfileScreen extends StatelessWidget { return options.builders.baseScreenBuilder!.call( context, - this.mapScreenType, + mapScreenType, _AppBar( user: userModel, chat: chatModel, @@ -288,7 +288,7 @@ class _Body extends StatelessWidget { ], ], ), - if (user?.id != currentUser) ...[ + if (user != null && user?.id != currentUser) ...[ Align( alignment: Alignment.bottomCenter, child: Padding( diff --git a/packages/flutter_chat/lib/src/screens/chat_screen.dart b/packages/flutter_chat/lib/src/screens/chat_screen.dart index ac1dd84..88d6e8d 100644 --- a/packages/flutter_chat/lib/src/screens/chat_screen.dart +++ b/packages/flutter_chat/lib/src/screens/chat_screen.dart @@ -60,7 +60,7 @@ class ChatScreen extends StatelessWidget { return chatOptions.builders.baseScreenBuilder!.call( context, - this.mapScreenType, + mapScreenType, _AppBar( userId: userId, chatOptions: chatOptions, 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 4612053..0645542 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 @@ -50,7 +50,7 @@ class NewGroupChatOverview extends StatelessWidget { return options.builders.baseScreenBuilder!.call( context, - this.mapScreenType, + mapScreenType, _AppBar( options: options, ),