diff --git a/.fvmrc b/.fvmrc new file mode 100644 index 0000000..906bbb3 --- /dev/null +++ b/.fvmrc @@ -0,0 +1,3 @@ +{ + "flutter": "3.24.3" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index ea211ad..416ceea 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,6 @@ web windows pubspec_overrides.yaml + +# FVM Version Cache +.fvm/ \ No newline at end of file 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 d74573f..f6cc8b9 100644 --- a/packages/chat_repository_interface/lib/src/models/chat_model.dart +++ b/packages/chat_repository_interface/lib/src/models/chat_model.dart @@ -97,6 +97,7 @@ class ChatModel { unreadMessageCount: unreadMessageCount ?? this.unreadMessageCount, ); + /// Creates a map representation of this object Map toMap() => { "users": users, "isGroupChat": isGroupChat, 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 b0329f5..aee1b08 100644 --- a/packages/chat_repository_interface/lib/src/models/message_model.dart +++ b/packages/chat_repository_interface/lib/src/models/message_model.dart @@ -16,6 +16,7 @@ class MessageModel { required this.senderId, }); + /// Creates a message model instance given a map instance factory MessageModel.fromMap(String id, Map map) => MessageModel( chatId: map["chatId"], @@ -62,6 +63,7 @@ class MessageModel { senderId: senderId ?? this.senderId, ); + /// Creates a map representation of this object Map toMap() => { "chatId": chatId, "text": text, 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 89ff7f2..4eb2645 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,7 @@ class UserModel { this.imageUrl, }); + /// Creates a user based on a given map [data] factory UserModel.fromMap(String id, Map data) => UserModel( id: id, firstName: data["first_name"], 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 28a70f4..144fa75 100644 --- a/packages/firebase_chat_repository/lib/src/firebase_chat_repository.dart +++ b/packages/firebase_chat_repository/lib/src/firebase_chat_repository.dart @@ -4,20 +4,25 @@ import "package:chat_repository_interface/chat_repository_interface.dart"; import "package:cloud_firestore/cloud_firestore.dart"; import "package:firebase_storage/firebase_storage.dart"; +/// Firebase implementation of the chat repository class FirebaseChatRepository implements ChatRepositoryInterface { + /// Creates a firebase implementation of the chat repository FirebaseChatRepository({ FirebaseFirestore? firestore, FirebaseStorage? storage, - this.chatCollection = "chats", - this.messageCollection = "messages", - this.mediaPath = "chat", - }) : _firestore = firestore ?? FirebaseFirestore.instance, + String chatCollection = "chats", + String messageCollection = "messages", + String mediaPath = "chat", + }) : _mediaPath = mediaPath, + _messageCollection = messageCollection, + _chatCollection = chatCollection, + _firestore = firestore ?? FirebaseFirestore.instance, _storage = storage ?? FirebaseStorage.instance; final FirebaseFirestore _firestore; final FirebaseStorage _storage; - final String chatCollection; - final String messageCollection; - final String mediaPath; + final String _chatCollection; + final String _messageCollection; + final String _mediaPath; @override Future createChat({ @@ -36,17 +41,17 @@ class FirebaseChatRepository implements ChatRepositoryInterface { "imageUrl": imageUrl, "createdAt": DateTime.now().millisecondsSinceEpoch, }; - await _firestore.collection(chatCollection).add(chatData); + await _firestore.collection(_chatCollection).add(chatData); } @override Future deleteChat({required String chatId}) async { - await _firestore.collection(chatCollection).doc(chatId).delete(); + await _firestore.collection(_chatCollection).doc(chatId).delete(); } @override Stream getChat({required String chatId}) => _firestore - .collection(chatCollection) + .collection(_chatCollection) .doc(chatId) .snapshots() .map((snapshot) { @@ -56,7 +61,7 @@ class FirebaseChatRepository implements ChatRepositoryInterface { @override Stream?> getChats({required String userId}) => _firestore - .collection(chatCollection) + .collection(_chatCollection) .where("users", arrayContains: userId) .snapshots() .map( @@ -72,9 +77,9 @@ class FirebaseChatRepository implements ChatRepositoryInterface { required String messageId, }) => _firestore - .collection(chatCollection) + .collection(_chatCollection) .doc(chatId) - .collection(messageCollection) + .collection(_messageCollection) .doc(messageId) .snapshots() .map((snapshot) { @@ -93,9 +98,9 @@ class FirebaseChatRepository implements ChatRepositoryInterface { required int page, }) => _firestore - .collection(chatCollection) + .collection(_chatCollection) .doc(chatId) - .collection(messageCollection) + .collection(_messageCollection) .orderBy("timestamp") .limit(pageSize) .snapshots() @@ -116,7 +121,7 @@ class FirebaseChatRepository implements ChatRepositoryInterface { String? chatId, }) async* { var query = _firestore - .collection(chatCollection) + .collection(_chatCollection) .where("users", arrayContains: userId) .where("unreadMessageCount", isGreaterThan: 0) .snapshots(); @@ -157,15 +162,15 @@ class FirebaseChatRepository implements ChatRepositoryInterface { ); await _firestore - .collection(chatCollection) + .collection(_chatCollection) .doc(chatId) - .collection(messageCollection) + .collection(_messageCollection) .doc(messageId) .set( message.toMap(), ); - await _firestore.collection(chatCollection).doc(chatId).update( + await _firestore.collection(_chatCollection).doc(chatId).update( { "lastMessage": messageId, "unreadMessageCount": FieldValue.increment(1), @@ -177,7 +182,7 @@ class FirebaseChatRepository implements ChatRepositoryInterface { @override Future updateChat({required ChatModel chat}) async { await _firestore - .collection(chatCollection) + .collection(_chatCollection) .doc(chat.id) .update(chat.toMap()); } @@ -187,7 +192,7 @@ class FirebaseChatRepository implements ChatRepositoryInterface { required String path, required Uint8List image, }) async { - var ref = _storage.ref().child(mediaPath).child(path); + 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 58f8128..3426383 100644 --- a/packages/firebase_chat_repository/lib/src/firebase_user_repository.dart +++ b/packages/firebase_chat_repository/lib/src/firebase_user_repository.dart @@ -1,17 +1,20 @@ import "package:chat_repository_interface/chat_repository_interface.dart"; import "package:cloud_firestore/cloud_firestore.dart"; +/// Firebase implementation of a user respository for chats. class FirebaseUserRepository implements UserRepositoryInterface { + /// Creates a firebase implementation of a user respository for chats. FirebaseUserRepository({ FirebaseFirestore? firestore, - this.userCollection = "users", - }) : _firestore = firestore ?? FirebaseFirestore.instance; + String userCollection = "users", + }) : _userCollection = userCollection, + _firestore = firestore ?? FirebaseFirestore.instance; final FirebaseFirestore _firestore; - final String userCollection; + final String _userCollection; @override Stream> getAllUsers() => - _firestore.collection(userCollection).snapshots().map( + _firestore.collection(_userCollection).snapshots().map( (querySnapshot) => querySnapshot.docs .map( (doc) => UserModel.fromMap( @@ -24,7 +27,7 @@ class FirebaseUserRepository implements UserRepositoryInterface { @override Stream getUser({required String userId}) => - _firestore.collection(userCollection).doc(userId).snapshots().map( + _firestore.collection(_userCollection).doc(userId).snapshots().map( (snapshot) => UserModel.fromMap( snapshot.id, snapshot.data()!, diff --git a/packages/flutter_chat/lib/src/config/screen_types.dart b/packages/flutter_chat/lib/src/config/screen_types.dart index 6f1c03c..ffd334a 100644 --- a/packages/flutter_chat/lib/src/config/screen_types.dart +++ b/packages/flutter_chat/lib/src/config/screen_types.dart @@ -6,22 +6,36 @@ 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"; +/// Type of screen, used in custom screen builders enum ScreenType { + /// Screen displaying an overview of chats chatScreen(screen: ChatScreen), + + /// Screen displaying a single chat chatDetailScreen(screen: ChatDetailScreen), + + /// Screen displaying the profile of a user within a chat chatProfileScreen(screen: ChatProfileScreen), + + /// Screen with a form to create a new chat newChatScreen(screen: NewChatScreen), + + /// Screen with a form to create a new group chat newGroupChatScreen(screen: NewGroupChatScreen), + + /// Screen displaying all group chats newGroupChatOverview(screen: NewGroupChatOverview); const ScreenType({ - required this.screen, - }); + required Type screen, + }) : _screen = screen; - final Type screen; + final Type _screen; } +/// Extension for mapping widgets to [ScreenType]s extension MapFromWidget on Widget { + /// returns corresponding [ScreenType] ScreenType get mapScreenType => - ScreenType.values.firstWhere((e) => e.screen == runtimeType); + ScreenType.values.firstWhere((e) => e._screen == runtimeType); } 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 eb4dcaf..f4cf56c 100644 --- a/packages/flutter_chat/lib/src/screens/chat_profile_screen.dart +++ b/packages/flutter_chat/lib/src/screens/chat_profile_screen.dart @@ -35,6 +35,7 @@ class ChatProfileScreen extends StatelessWidget { /// Callback function triggered when a user is tapped final Function(String)? onTapUser; + /// instance of a chat service final ChatService service; /// Callback function triggered when the start chat button is pressed @@ -135,6 +136,76 @@ class _Body extends StatelessWidget { @override Widget build(BuildContext context) { var theme = Theme.of(context); + + var chatUserDisplay = Wrap( + children: [ + ...chat!.users.map( + (tappedUser) => Padding( + padding: const EdgeInsets.only( + bottom: 8, + right: 8, + ), + child: InkWell( + onTap: () { + onTapUser?.call(tappedUser); + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + FutureBuilder( + future: service.getUser(userId: tappedUser).first, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const CircularProgressIndicator(); + } + + var user = snapshot.data; + + if (user == null) { + return const SizedBox.shrink(); + } + + return options.builders.userAvatarBuilder?.call( + context, + user, + 44, + ) ?? + Avatar( + boxfit: BoxFit.cover, + user: User( + firstName: user.firstName, + lastName: user.lastName, + imageUrl: + user.imageUrl != null || user.imageUrl != "" + ? user.imageUrl + : null, + ), + size: 60, + ); + }, + ), + ], + ), + ), + ), + ), + ], + ); + + var targetUser = user ?? + ( + chat != null + ? UserModel( + id: UniqueKey().toString(), + firstName: chat?.chatName, + imageUrl: chat?.imageUrl, + ) + : UserModel( + id: UniqueKey().toString(), + firstName: options.translations.groupNameEmpty, + ), + ) as UserModel; return Stack( children: [ ListView( @@ -145,20 +216,7 @@ class _Body extends StatelessWidget { children: [ options.builders.userAvatarBuilder?.call( context, - user ?? - ( - chat != null - ? UserModel( - id: UniqueKey().toString(), - firstName: chat?.chatName, - imageUrl: chat?.imageUrl, - ) - : UserModel( - id: UniqueKey().toString(), - firstName: - options.translations.groupNameEmpty, - ), - ) as UserModel, + targetUser, 60, ) ?? Avatar( @@ -223,65 +281,7 @@ class _Body extends StatelessWidget { const SizedBox( height: 12, ), - Wrap( - children: [ - ...chat!.users.map( - (tappedUser) => Padding( - padding: const EdgeInsets.only( - bottom: 8, - right: 8, - ), - child: InkWell( - onTap: () { - onTapUser?.call(tappedUser); - }, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - FutureBuilder( - future: service - .getUser(userId: tappedUser) - .first, - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.waiting) { - return const CircularProgressIndicator(); - } - - var user = snapshot.data; - - if (user == null) { - return const SizedBox.shrink(); - } - - return options.builders.userAvatarBuilder - ?.call( - context, - user, - 44, - ) ?? - Avatar( - boxfit: BoxFit.cover, - user: User( - firstName: user.firstName, - lastName: user.lastName, - imageUrl: user.imageUrl != null || - user.imageUrl != "" - ? user.imageUrl - : null, - ), - size: 60, - ); - }, - ), - ], - ), - ), - ), - ), - ], - ), + chatUserDisplay, ], ), ), diff --git a/packages/flutter_chat/lib/src/screens/creation/widgets/image_picker.dart b/packages/flutter_chat/lib/src/screens/creation/widgets/image_picker.dart index 30b47a8..9077c3a 100644 --- a/packages/flutter_chat/lib/src/screens/creation/widgets/image_picker.dart +++ b/packages/flutter_chat/lib/src/screens/creation/widgets/image_picker.dart @@ -50,6 +50,7 @@ Future onPressSelectImage( ).then( (image) async { if (image == null) return; + if (!context.mounted) return; var messenger = ScaffoldMessenger.of(context) ..showSnackBar( _getImageLoadingSnackbar(options.translations),