From 23b96e5ce3732a7d0ae203bbb36b564d93f6ab0c Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Fri, 12 Jan 2024 16:52:14 +0100 Subject: [PATCH] fix: chat --- .github/dependabot.yml | 9 +- .gitignore | 8 +- README.md | 78 ++-- melos.yaml | 4 +- .../analysis_options.yaml | 0 packages/flutter_chat/lib/flutter_chat.dart | 11 + .../lib/src/flutter_chat_userstory.dart} | 106 +++-- .../lib/src/go_router.dart | 0 .../lib/src/models/chat_configuration.dart} | 18 +- .../lib/src/routes.dart | 5 +- .../pubspec.yaml | 14 +- .../analysis_options.yaml | 0 .../lib/config/firebase_chat_options.dart | 0 .../lib/dto/firebase_chat_document.dart | 2 +- .../lib/dto/firebase_message_document.dart | 0 .../lib/dto/firebase_user_document.dart | 0 .../lib/flutter_chat_firebase.dart | 7 + .../firebase_chat_detail_service.dart} | 232 +++-------- .../firebase_chat_overview_service.dart} | 382 ++++++------------ .../service/firebase_chat_user_service.dart} | 10 +- .../lib/service/service.dart | 3 + .../pubspec.yaml | 8 +- .../analysis_options.yaml | 0 .../lib/flutter_chat_interface.dart | 9 + .../lib/src/chat_data_provider.dart | 6 +- .../lib/src/model/chat.dart | 39 ++ .../lib/src/model/chat_image_message.dart | 29 ++ .../lib/src/model/chat_message.dart | 23 ++ .../lib/src/model/chat_text_message.dart | 29 ++ .../lib/src/model/chat_user.dart | 18 +- .../lib/src/model/group_chat.dart | 91 +++++ .../lib/src/model/model.dart | 0 .../lib/src/model/personal_chat.dart | 78 ++++ .../lib/src/service/chat_detail_service.dart} | 14 +- .../src/service/chat_overview_service.dart} | 6 +- .../lib/src/service/chat_service.dart | 14 + .../lib/src/service/service.dart | 4 + .../lib/src/service/user_service.dart | 2 +- .../pubspec.yaml | 2 +- .../analysis_options.yaml | 0 .../example/.gitignore | 0 .../example/README.md | 0 .../example/analysis_options.yaml | 0 .../example/lib/main.dart | 2 +- .../example/pubspec.yaml | 4 +- .../example/test/widget_test.dart | 0 .../ios/Flutter/Generated.xcconfig | 2 +- .../ios/Flutter/flutter_export_environment.sh | 2 +- .../ios/Podfile | 0 .../ios/Runner/GeneratedPluginRegistrant.h | 0 .../ios/Runner/GeneratedPluginRegistrant.m | 0 .../lib/flutter_chat_view.dart} | 6 +- .../lib/src/components/chat_bottom.dart | 2 +- .../lib/src/components/chat_detail_row.dart | 6 +- .../lib/src/components/chat_image.dart | 0 .../lib/src/components/chat_row.dart | 0 .../components/image_loading_snackbar.dart | 2 +- .../lib/src/config/chat_options.dart | 4 +- .../lib/src/config/chat_translations.dart | 2 + .../lib/src/screens/chat_detail_screen.dart | 74 ++-- .../lib/src/screens/chat_entry_widget.dart | 146 +++++++ .../lib/src/screens/chat_profile_screen.dart | 129 ++++++ .../lib/src/screens/chat_screen.dart | 312 ++++++++++++++ .../lib/src/screens/new_chat_screen.dart | 6 +- .../lib/src/services/date_formatter.dart | 0 .../lib/src/services/profile_service.dart | 24 ++ .../pubspec.yaml | 12 +- .../test/flutter_community_chat_test.dart | 0 .../lib/flutter_community_chat.dart | 11 - .../lib/flutter_community_chat_firebase.dart | 7 - .../lib/service/service.dart | 3 - .../lib/flutter_community_chat_interface.dart | 9 - .../lib/src/model/chat.dart | 23 -- .../lib/src/model/chat_image_message.dart | 15 - .../lib/src/model/chat_message.dart | 15 - .../lib/src/model/chat_text_message.dart | 15 - .../lib/src/model/group_chat.dart | 46 --- .../lib/src/model/personal_chat.dart | 38 -- .../lib/src/service/service.dart | 3 - .../lib/src/screens/chat_screen.dart | 367 ----------------- pubspec.yaml | 4 +- 81 files changed, 1400 insertions(+), 1152 deletions(-) rename packages/{flutter_community_chat => flutter_chat}/analysis_options.yaml (100%) create mode 100644 packages/flutter_chat/lib/flutter_chat.dart rename packages/{flutter_community_chat/lib/src/flutter_community_chat_userstory.dart => flutter_chat/lib/src/flutter_chat_userstory.dart} (52%) rename packages/{flutter_community_chat => flutter_chat}/lib/src/go_router.dart (100%) rename packages/{flutter_community_chat/lib/src/models/community_chat_configuration.dart => flutter_chat/lib/src/models/chat_configuration.dart} (81%) rename packages/{flutter_community_chat => flutter_chat}/lib/src/routes.dart (60%) rename packages/{flutter_community_chat => flutter_chat}/pubspec.yaml (52%) rename packages/{flutter_community_chat_firebase => flutter_chat_firebase}/analysis_options.yaml (100%) rename packages/{flutter_community_chat_firebase => flutter_chat_firebase}/lib/config/firebase_chat_options.dart (100%) rename packages/{flutter_community_chat_firebase => flutter_chat_firebase}/lib/dto/firebase_chat_document.dart (94%) rename packages/{flutter_community_chat_firebase => flutter_chat_firebase}/lib/dto/firebase_message_document.dart (100%) rename packages/{flutter_community_chat_firebase => flutter_chat_firebase}/lib/dto/firebase_user_document.dart (100%) create mode 100644 packages/flutter_chat_firebase/lib/flutter_chat_firebase.dart rename packages/{flutter_community_chat_firebase/lib/service/firebase_message_service.dart => flutter_chat_firebase/lib/service/firebase_chat_detail_service.dart} (54%) rename packages/{flutter_community_chat_firebase/lib/service/firebase_chat_service.dart => flutter_chat_firebase/lib/service/firebase_chat_overview_service.dart} (50%) rename packages/{flutter_community_chat_firebase/lib/service/firebase_user_service.dart => flutter_chat_firebase/lib/service/firebase_chat_user_service.dart} (87%) create mode 100644 packages/flutter_chat_firebase/lib/service/service.dart rename packages/{flutter_community_chat_firebase => flutter_chat_firebase}/pubspec.yaml (69%) rename packages/{flutter_community_chat_interface => flutter_chat_interface}/analysis_options.yaml (100%) create mode 100644 packages/flutter_chat_interface/lib/flutter_chat_interface.dart rename packages/{flutter_community_chat_interface => flutter_chat_interface}/lib/src/chat_data_provider.dart (72%) create mode 100644 packages/flutter_chat_interface/lib/src/model/chat.dart create mode 100644 packages/flutter_chat_interface/lib/src/model/chat_image_message.dart create mode 100644 packages/flutter_chat_interface/lib/src/model/chat_message.dart create mode 100644 packages/flutter_chat_interface/lib/src/model/chat_text_message.dart rename packages/{flutter_community_chat_interface => flutter_chat_interface}/lib/src/model/chat_user.dart (64%) create mode 100644 packages/flutter_chat_interface/lib/src/model/group_chat.dart rename packages/{flutter_community_chat_interface => flutter_chat_interface}/lib/src/model/model.dart (100%) create mode 100644 packages/flutter_chat_interface/lib/src/model/personal_chat.dart rename packages/{flutter_community_chat_interface/lib/src/service/message_service.dart => flutter_chat_interface/lib/src/service/chat_detail_service.dart} (50%) rename packages/{flutter_community_chat_interface/lib/src/service/chat_service.dart => flutter_chat_interface/lib/src/service/chat_overview_service.dart} (61%) create mode 100644 packages/flutter_chat_interface/lib/src/service/chat_service.dart create mode 100644 packages/flutter_chat_interface/lib/src/service/service.dart rename packages/{flutter_community_chat_interface => flutter_chat_interface}/lib/src/service/user_service.dart (65%) rename packages/{flutter_community_chat_interface => flutter_chat_interface}/pubspec.yaml (91%) rename packages/{flutter_community_chat_view => flutter_chat_view}/analysis_options.yaml (100%) rename packages/{flutter_community_chat_view => flutter_chat_view}/example/.gitignore (100%) rename packages/{flutter_community_chat_view => flutter_chat_view}/example/README.md (100%) rename packages/{flutter_community_chat_view => flutter_chat_view}/example/analysis_options.yaml (100%) rename packages/{flutter_community_chat_view => flutter_chat_view}/example/lib/main.dart (98%) rename packages/{flutter_community_chat_view => flutter_chat_view}/example/pubspec.yaml (85%) rename packages/{flutter_community_chat_view => flutter_chat_view}/example/test/widget_test.dart (100%) rename packages/{flutter_community_chat_view => flutter_chat_view}/ios/Flutter/Generated.xcconfig (89%) rename packages/{flutter_community_chat_view => flutter_chat_view}/ios/Flutter/flutter_export_environment.sh (88%) rename packages/{flutter_community_chat_view => flutter_chat_view}/ios/Podfile (100%) rename packages/{flutter_community_chat_view => flutter_chat_view}/ios/Runner/GeneratedPluginRegistrant.h (100%) rename packages/{flutter_community_chat_view => flutter_chat_view}/ios/Runner/GeneratedPluginRegistrant.m (100%) rename packages/{flutter_community_chat_view/lib/flutter_community_chat_view.dart => flutter_chat_view/lib/flutter_chat_view.dart} (64%) rename packages/{flutter_community_chat_view => flutter_chat_view}/lib/src/components/chat_bottom.dart (96%) rename packages/{flutter_community_chat_view => flutter_chat_view}/lib/src/components/chat_detail_row.dart (96%) rename packages/{flutter_community_chat_view => flutter_chat_view}/lib/src/components/chat_image.dart (100%) rename packages/{flutter_community_chat_view => flutter_chat_view}/lib/src/components/chat_row.dart (100%) rename packages/{flutter_community_chat_view => flutter_chat_view}/lib/src/components/image_loading_snackbar.dart (86%) rename packages/{flutter_community_chat_view => flutter_chat_view}/lib/src/config/chat_options.dart (96%) rename packages/{flutter_community_chat_view => flutter_chat_view}/lib/src/config/chat_translations.dart (95%) rename packages/{flutter_community_chat_view => flutter_chat_view}/lib/src/screens/chat_detail_screen.dart (82%) create mode 100644 packages/flutter_chat_view/lib/src/screens/chat_entry_widget.dart create mode 100644 packages/flutter_chat_view/lib/src/screens/chat_profile_screen.dart create mode 100644 packages/flutter_chat_view/lib/src/screens/chat_screen.dart rename packages/{flutter_community_chat_view => flutter_chat_view}/lib/src/screens/new_chat_screen.dart (94%) rename packages/{flutter_community_chat_view => flutter_chat_view}/lib/src/services/date_formatter.dart (100%) create mode 100644 packages/flutter_chat_view/lib/src/services/profile_service.dart rename packages/{flutter_community_chat_view => flutter_chat_view}/pubspec.yaml (66%) rename packages/{flutter_community_chat_view => flutter_chat_view}/test/flutter_community_chat_test.dart (100%) delete mode 100644 packages/flutter_community_chat/lib/flutter_community_chat.dart delete mode 100644 packages/flutter_community_chat_firebase/lib/flutter_community_chat_firebase.dart delete mode 100644 packages/flutter_community_chat_firebase/lib/service/service.dart delete mode 100644 packages/flutter_community_chat_interface/lib/flutter_community_chat_interface.dart delete mode 100644 packages/flutter_community_chat_interface/lib/src/model/chat.dart delete mode 100644 packages/flutter_community_chat_interface/lib/src/model/chat_image_message.dart delete mode 100644 packages/flutter_community_chat_interface/lib/src/model/chat_message.dart delete mode 100644 packages/flutter_community_chat_interface/lib/src/model/chat_text_message.dart delete mode 100644 packages/flutter_community_chat_interface/lib/src/model/group_chat.dart delete mode 100644 packages/flutter_community_chat_interface/lib/src/model/personal_chat.dart delete mode 100644 packages/flutter_community_chat_interface/lib/src/service/service.dart delete mode 100644 packages/flutter_community_chat_view/lib/src/screens/chat_screen.dart diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 90c096a..4404fba 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,22 +2,21 @@ version: 2 updates: - package-ecosystem: "pub" - directory: "/packages/flutter_community_chat" + directory: "/packages/flutter_chat" schedule: interval: "weekly" - package-ecosystem: "pub" - directory: "/packages/flutter_community_chat_firebase" + directory: "/packages/flutter_chat_firebase" schedule: interval: "weekly" - package-ecosystem: "pub" - directory: "/packages/flutter_community_chat_interface" + directory: "/packages/flutter_chat_interface" schedule: interval: "weekly" - package-ecosystem: "pub" - directory: "/packages/flutter_community_chat_view" + directory: "/packages/flutter_chat_view" schedule: interval: "weekly" - diff --git a/.gitignore b/.gitignore index ec070e9..7a871bb 100644 --- a/.gitignore +++ b/.gitignore @@ -37,9 +37,9 @@ build/ .metadata pubspec.lock -packages/flutter_community_chat/pubspec.lock -packages/flutter_community_chat_firebase/pubspec.lock -packages/flutter_community_chat_interface/pubspec.lock -packages/flutter_community_chat_view/pubspec.lock +packages/flutter_chat/pubspec.lock +packages/flutter_chat_firebase/pubspec.lock +packages/flutter_chat_interface/pubspec.lock +packages/flutter_chat_view/pubspec.lock pubspec_overrides.yaml diff --git a/README.md b/README.md index ac12805..bb0fe2e 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,52 @@ -# Flutter Community Chat +# Flutter Chat -Flutter Community Chat is a package which gives the possibility to add a (personal or group) chat to your Flutter-application. Default this package adds support for a Firebase back-end. You can add your custom back-end (like a Websocket-API) by extending the `CommunityChatInterface` interface from the `flutter_community_chat_interface` package. +Flutter Chat is a package which gives the possibility to add a (personal or group) chat to your Flutter-application. Default this package adds support for a Firebase back-end. You can add your custom back-end (like a Websocket-API) by extending the `ChatInterface` interface from the `flutter_chat_interface` package. -![Flutter Community Chat GIF](example.gif) +![Flutter Chat GIF](example.gif) Figma Design that defines this component (only accessible for Iconica developers): https://www.figma.com/file/4WkjwynOz5wFeFBRqTHPeP/Iconica-Design-System?type=design&node-id=357%3A3342&mode=design&t=XulkAJNPQ32ARxWh-1 Figma clickable prototype that demonstrates this component (only accessible for Iconica developers): https://www.figma.com/proto/PRJoVXQ5aOjAICfkQdAq2A/Iconica-User-Stories?page-id=1%3A2&type=design&node-id=56-6837&viewport=279%2C2452%2C0.2&t=E7Al3Xng2WXnbCEQ-1&scaling=scale-down&starting-point-node-id=56%3A6837&mode=design ## Setup -To use this package, add flutter_community_chat as a dependency in your pubspec.yaml file: +To use this package, add flutter_chat as a dependency in your pubspec.yaml file: ``` - flutter_community_chat: + flutter_chat: git: - url: https://github.com/Iconica-Development/flutter_community_chat.git - path: packages/flutter_community_chat + url: https://github.com/Iconica-Development/flutter_chat + path: packages/flutter_chat ``` If you are going to use Firebase as the back-end of the Community Chat, you should also add the following package as a dependency to your pubspec.yaml file: ``` - flutter_community_chat_firebase: + flutter_chat_firebase: git: - url: https://github.com/Iconica-Development/flutter_community_chat.git - path: packages/flutter_community_chat_firebase + url: https://github.com/Iconica-Development/flutter_chat + path: packages/flutter_chat_firebase ``` Create a Firebase project for your application and add firebase firestore and storage. +To use the camera or photo library to send photos add the following to your project: + +For ios add the following lines to your info.plist: + +``` + NSCameraUsageDescription + Access camera + NSPhotoLibraryUsageDescription + Library +``` + +For android add the following lines to your AndroidManifest.xml: + +``` + + +``` + ## How to use To use the module within your Flutter-application with predefined `Go_router` routes you should add the following: @@ -37,18 +55,15 @@ Add go_router as dependency to your project. Add the following configuration to your flutter_application: ``` -List getCommunityChatRoutes() => getCommunityChatStoryRoutes( - CommunityChatUserStoryConfiguration( - service: FirebaseChatService(userService: FirebaseUserService()), - userService: FirebaseUserService(), - messageService: - FirebaseMessageService(userService: FirebaseUserService()), +List getChatRoutes() => getChatStoryRoutes( + ChatUserStoryConfiguration( + chatService: chatService, chatOptionsBuilder: (ctx) => const ChatOptions(), ), ); ``` -Add the `getCommunityChatRoutes()` to your go_router routes like so: +Add the `getChatRoutes()` to your go_router routes like so: ``` final GoRouter _router = GoRouter( @@ -61,13 +76,14 @@ final GoRouter _router = GoRouter( ); }, ), - ...getCommunityChatRoutes() + ...getChatRoutes() ], ); ``` To use the module within your Flutter-application without predefined `Go_router` routes add the following code to the build-method of a chosen widget: +The `ChatScreen` shows all chats that you currently have with their latest messages. To add the `ChatScreen` add the following code: ```` @@ -81,6 +97,7 @@ ChatScreen( ); ``` +The `ChatDetailScreen` shows the messages that are in the current chat you selected. To add the `ChatDetailScreen` add the following code: ``` @@ -90,12 +107,10 @@ ChatDetailScreen( onUploadImage: onUploadImage, onReadChat: onReadChat, service: service, - chatUserService: chatUserService, - messageService: messageService, - pageSize: pageSize, ); ``` +On the `NewChatScreen` you can select a person to chat. To add the `NewChatScreen` add the following code: ``` @@ -103,10 +118,23 @@ NewChatScreen( options: options, onPressCreateChat: onPressCreateChat, service: service, - userService: userService, ); ``` +The `ChatEntryWidget` is a widget you can put anywhere in your app. +It displays the amount of unread messages you currently have. +You can choose to add a onTap to the `ChatEntryWidget` so it routes to the `ChatScreen`, +where all your chats are shown. + +To add the `ChatEntryWidget` add the follwoing code: + +``` +ChatEntryWidget( + chatService: chatService, + onTap: onTap, +); +``` + The `ChatOptions` has its own parameters, as specified below: | Parameter | Explanation | |-----------|-------------| @@ -121,13 +149,13 @@ The `ImagePickerTheme` also has its own parameters, how to use these parameters ## Issues -Please file any issues, bugs or feature request as an issue on our [GitHub](https://github.com/Iconica-Development/flutter_community_chat/pulls) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl). +Please file any issues, bugs or feature request as an issue on our [GitHub](https://github.com/Iconica-Development/flutter_chat/pulls) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl). ## Want to contribute -If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](../CONTRIBUTING.md) and send us your [pull request](https://github.com/Iconica-Development/flutter_community_chat/pulls). +If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](../CONTRIBUTING.md) and send us your [pull request](https://github.com/Iconica-Development/flutter_chat/pulls). ## Author -This `flutter_community_chat` for Flutter is developed by [Iconica](https://iconica.nl). You can contact us at +This `flutter_chat` for Flutter is developed by [Iconica](https://iconica.nl). You can contact us at ```` diff --git a/melos.yaml b/melos.yaml index 64b1af5..c49d6d4 100644 --- a/melos.yaml +++ b/melos.yaml @@ -1,4 +1,4 @@ -name: flutter_community_chat +name: flutter_chat packages: - packages/** @@ -21,7 +21,7 @@ scripts: run: melos exec -c 1 -- "flutter pub upgrade" create: - # run create in the example folder of flutter_community_chat_view + # run create in the example folder of flutter_chat_view run: melos exec --scope="*example*" -c 1 -- "flutter create ." analyze: diff --git a/packages/flutter_community_chat/analysis_options.yaml b/packages/flutter_chat/analysis_options.yaml similarity index 100% rename from packages/flutter_community_chat/analysis_options.yaml rename to packages/flutter_chat/analysis_options.yaml diff --git a/packages/flutter_chat/lib/flutter_chat.dart b/packages/flutter_chat/lib/flutter_chat.dart new file mode 100644 index 0000000..7082893 --- /dev/null +++ b/packages/flutter_chat/lib/flutter_chat.dart @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +library flutter_chat; + +export 'package:flutter_chat_view/flutter_chat_view.dart'; +export 'package:flutter_chat_interface/flutter_chat_interface.dart'; +export 'package:flutter_chat/src/routes.dart'; +export 'package:flutter_chat/src/models/chat_configuration.dart'; +export 'package:flutter_chat/src/flutter_chat_userstory.dart'; diff --git a/packages/flutter_community_chat/lib/src/flutter_community_chat_userstory.dart b/packages/flutter_chat/lib/src/flutter_chat_userstory.dart similarity index 52% rename from packages/flutter_community_chat/lib/src/flutter_community_chat_userstory.dart rename to packages/flutter_chat/lib/src/flutter_chat_userstory.dart index 9fc0052..c92b79f 100644 --- a/packages/flutter_community_chat/lib/src/flutter_community_chat_userstory.dart +++ b/packages/flutter_chat/lib/src/flutter_chat_userstory.dart @@ -3,40 +3,35 @@ // SPDX-License-Identifier: BSD-3-Clause import 'package:flutter/material.dart'; -import 'package:flutter_community_chat/src/models/community_chat_configuration.dart'; -import 'package:flutter_community_chat/src/go_router.dart'; -import 'package:flutter_community_chat/src/routes.dart'; -import 'package:flutter_community_chat_view/flutter_community_chat_view.dart'; +import 'package:flutter_chat/flutter_chat.dart'; +import 'package:flutter_chat/src/go_router.dart'; import 'package:go_router/go_router.dart'; -List getCommunityChatStoryRoutes( - CommunityChatUserStoryConfiguration configuration, +List getChatStoryRoutes( + ChatUserStoryConfiguration configuration, ) => [ GoRoute( - path: CommunityChatUserStoryRoutes.chatScreen, + path: ChatUserStoryRoutes.chatScreen, pageBuilder: (context, state) { var chatScreen = ChatScreen( - pageSize: configuration.pageSize, - service: configuration.service, + service: configuration.chatService, options: configuration.chatOptionsBuilder(context), onNoChats: () async => - await context.push(CommunityChatUserStoryRoutes.newChatScreen), + await context.push(ChatUserStoryRoutes.newChatScreen), onPressStartChat: () async { if (configuration.onPressStartChat != null) { return await configuration.onPressStartChat?.call(); } - return await context - .push(CommunityChatUserStoryRoutes.newChatScreen); + return await context.push(ChatUserStoryRoutes.newChatScreen); }, onPressChat: (chat) => configuration.onPressChat?.call(context, chat) ?? - context.push( - CommunityChatUserStoryRoutes.chatDetailViewPath(chat.id!)), + context.push(ChatUserStoryRoutes.chatDetailViewPath(chat.id!)), onDeleteChat: (chat) => configuration.onDeleteChat?.call(context, chat) ?? - configuration.service.deleteChat(chat), + configuration.chatService.chatOverviewService.deleteChat(chat), deleteChatDialog: configuration.deleteChatDialog, translations: configuration.translations, ); @@ -54,35 +49,38 @@ List getCommunityChatStoryRoutes( }, ), GoRoute( - path: CommunityChatUserStoryRoutes.chatDetailScreen, + path: ChatUserStoryRoutes.chatDetailScreen, pageBuilder: (context, state) { var chatId = state.pathParameters['id']; - var chat = PersonalChatModel(user: ChatUserModel(), id: chatId); var chatDetailScreen = ChatDetailScreen( pageSize: configuration.messagePageSize, options: configuration.chatOptionsBuilder(context), translations: configuration.translations, - chatUserService: configuration.userService, - service: configuration.service, - messageService: configuration.messageService, - chat: chat, + service: configuration.chatService, + chatId: chatId!, onMessageSubmit: (message) async { configuration.onMessageSubmit?.call(message) ?? - configuration.messageService - .sendTextMessage(chat: chat, text: message); - configuration.afterMessageSent?.call(chat); + configuration.chatService.chatDetailService + .sendTextMessage(chatId: chatId, text: message); + configuration.afterMessageSent?.call(chatId); }, onUploadImage: (image) async { configuration.onUploadImage?.call(image) ?? - configuration.messageService - .sendImageMessage(chat: chat, image: image); - configuration.afterMessageSent?.call(chat); + configuration.chatService.chatDetailService + .sendImageMessage(chatId: chatId, image: image); + configuration.afterMessageSent?.call(chatId); }, onReadChat: (chat) => configuration.onReadChat?.call(chat) ?? - configuration.service.readChat(chat), - onPressChatTitle: (context, chat) => - configuration.onPressChatTitle?.call(context, chat), + configuration.chatService.chatOverviewService.readChat(chat), + onPressChatTitle: (context, chat) { + if (configuration.onPressChatTitle?.call(context, chat) != null) { + return configuration.onPressChatTitle?.call(context, chat); + } + + return context.push( + ChatUserStoryRoutes.chatProfileScreenPath(chat.id!, null)); + }, iconColor: configuration.iconColor, ); return buildScreenWithoutTransition( @@ -99,19 +97,20 @@ List getCommunityChatStoryRoutes( }, ), GoRoute( - path: CommunityChatUserStoryRoutes.newChatScreen, + path: ChatUserStoryRoutes.newChatScreen, pageBuilder: (context, state) { var newChatScreen = NewChatScreen( options: configuration.chatOptionsBuilder(context), translations: configuration.translations, - service: configuration.service, - userService: configuration.userService, + service: configuration.chatService, onPressCreateChat: (user) async { configuration.onPressCreateChat?.call(user); if (configuration.onPressChat != null) return; - var chat = await configuration.service.getChatByUser(user); + var chat = await configuration.chatService.chatOverviewService + .getChatByUser(user); if (chat.id == null) { - chat = await configuration.service.storeChatIfNot( + chat = await configuration.chatService.chatOverviewService + .storeChatIfNot( PersonalChatModel( user: user, ), @@ -119,8 +118,7 @@ List getCommunityChatStoryRoutes( } if (context.mounted) { await context.push( - CommunityChatUserStoryRoutes.chatDetailViewPath( - chat.id ?? '')); + ChatUserStoryRoutes.chatDetailViewPath(chat.id ?? '')); } }); return buildScreenWithoutTransition( @@ -136,4 +134,38 @@ List getCommunityChatStoryRoutes( ); }, ), + GoRoute( + path: ChatUserStoryRoutes.chatProfileScreen, + pageBuilder: (context, state) { + var chatId = state.pathParameters['id']; + var userId = state.pathParameters['userId']; + var id = userId == 'null' ? null : userId; + var profileScreen = ChatProfileScreen( + translations: configuration.translations, + chatService: configuration.chatService, + chatId: chatId!, + userId: id, + onTapUser: (user) async { + if (configuration.onPressUserProfile != null) { + return configuration.onPressUserProfile!.call(); + } + + return await context.push( + ChatUserStoryRoutes.chatProfileScreenPath(chatId, user), + ); + }, + ); + return buildScreenWithoutTransition( + context: context, + state: state, + child: configuration.chatPageBuilder?.call( + context, + profileScreen, + ) ?? + Scaffold( + body: profileScreen, + ), + ); + }, + ), ]; diff --git a/packages/flutter_community_chat/lib/src/go_router.dart b/packages/flutter_chat/lib/src/go_router.dart similarity index 100% rename from packages/flutter_community_chat/lib/src/go_router.dart rename to packages/flutter_chat/lib/src/go_router.dart diff --git a/packages/flutter_community_chat/lib/src/models/community_chat_configuration.dart b/packages/flutter_chat/lib/src/models/chat_configuration.dart similarity index 81% rename from packages/flutter_community_chat/lib/src/models/community_chat_configuration.dart rename to packages/flutter_chat/lib/src/models/chat_configuration.dart index 6d8e04d..93c6e97 100644 --- a/packages/flutter_community_chat/lib/src/models/community_chat_configuration.dart +++ b/packages/flutter_chat/lib/src/models/chat_configuration.dart @@ -5,14 +5,12 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:flutter_community_chat_view/flutter_community_chat_view.dart'; +import 'package:flutter_chat_view/flutter_chat_view.dart'; @immutable -class CommunityChatUserStoryConfiguration { - const CommunityChatUserStoryConfiguration({ - required this.userService, - required this.messageService, - required this.service, +class ChatUserStoryConfiguration { + const ChatUserStoryConfiguration({ + required this.chatService, required this.chatOptionsBuilder, this.pageSize = 10, this.onPressStartChat, @@ -31,10 +29,9 @@ class CommunityChatUserStoryConfiguration { this.onPressChatTitle, this.afterMessageSent, this.messagePageSize = 20, + this.onPressUserProfile, }); - final ChatService service; - final ChatUserService userService; - final MessageService messageService; + final ChatService chatService; final Function(BuildContext, ChatModel)? onPressChat; final Function(BuildContext, ChatModel)? onDeleteChat; final ChatTranslations translations; @@ -43,7 +40,7 @@ class CommunityChatUserStoryConfiguration { final Future Function(String text)? onMessageSubmit; /// Called after a new message is sent. This can be used to do something extra like sending a push notification. - final Function(ChatModel chat)? afterMessageSent; + final Function(String chatId)? afterMessageSent; final Future Function(ChatModel chat)? onReadChat; final Function(ChatUserModel)? onPressCreateChat; final ChatOptions Function(BuildContext context) chatOptionsBuilder; @@ -58,4 +55,5 @@ class CommunityChatUserStoryConfiguration { final Color? iconColor; final Widget Function(BuildContext context, Widget child)? chatPageBuilder; final Function()? onPressStartChat; + final Function()? onPressUserProfile; } diff --git a/packages/flutter_community_chat/lib/src/routes.dart b/packages/flutter_chat/lib/src/routes.dart similarity index 60% rename from packages/flutter_community_chat/lib/src/routes.dart rename to packages/flutter_chat/lib/src/routes.dart index d77e8aa..32e2a57 100644 --- a/packages/flutter_community_chat/lib/src/routes.dart +++ b/packages/flutter_chat/lib/src/routes.dart @@ -2,9 +2,12 @@ // // SPDX-License-Identifier: BSD-3-Clause -mixin CommunityChatUserStoryRoutes { +mixin ChatUserStoryRoutes { static const String chatScreen = '/chat'; static String chatDetailViewPath(String chatId) => '/chat-detail/$chatId'; static const String chatDetailScreen = '/chat-detail/:id'; static const String newChatScreen = '/new-chat'; + static String chatProfileScreenPath(String chatId, String? userId) => + '/chat-profile/$chatId/$userId'; + static const String chatProfileScreen = '/chat-profile/:id/:userId'; } diff --git a/packages/flutter_community_chat/pubspec.yaml b/packages/flutter_chat/pubspec.yaml similarity index 52% rename from packages/flutter_community_chat/pubspec.yaml rename to packages/flutter_chat/pubspec.yaml index ca93837..d5fdb59 100644 --- a/packages/flutter_community_chat/pubspec.yaml +++ b/packages/flutter_chat/pubspec.yaml @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -name: flutter_community_chat +name: flutter_chat description: A new Flutter package project. version: 1.0.0 @@ -16,15 +16,15 @@ dependencies: flutter: sdk: flutter go_router: any - flutter_community_chat_view: + flutter_chat_view: git: - url: https://github.com/Iconica-Development/flutter_community_chat - path: packages/flutter_community_chat_view + url: https://github.com/Iconica-Development/flutter_chat + path: packages/flutter_chat_view ref: 1.0.0 - flutter_community_chat_interface: + flutter_chat_interface: git: - url: https://github.com/Iconica-Development/flutter_community_chat - path: packages/flutter_community_chat_interface + url: https://github.com/Iconica-Development/flutter_chat + path: packages/flutter_chat_interface ref: 1.0.0 dev_dependencies: diff --git a/packages/flutter_community_chat_firebase/analysis_options.yaml b/packages/flutter_chat_firebase/analysis_options.yaml similarity index 100% rename from packages/flutter_community_chat_firebase/analysis_options.yaml rename to packages/flutter_chat_firebase/analysis_options.yaml diff --git a/packages/flutter_community_chat_firebase/lib/config/firebase_chat_options.dart b/packages/flutter_chat_firebase/lib/config/firebase_chat_options.dart similarity index 100% rename from packages/flutter_community_chat_firebase/lib/config/firebase_chat_options.dart rename to packages/flutter_chat_firebase/lib/config/firebase_chat_options.dart diff --git a/packages/flutter_community_chat_firebase/lib/dto/firebase_chat_document.dart b/packages/flutter_chat_firebase/lib/dto/firebase_chat_document.dart similarity index 94% rename from packages/flutter_community_chat_firebase/lib/dto/firebase_chat_document.dart rename to packages/flutter_chat_firebase/lib/dto/firebase_chat_document.dart index 410a390..12c5002 100644 --- a/packages/flutter_community_chat_firebase/lib/dto/firebase_chat_document.dart +++ b/packages/flutter_chat_firebase/lib/dto/firebase_chat_document.dart @@ -4,7 +4,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_community_chat_firebase/dto/firebase_message_document.dart'; +import 'package:flutter_chat_firebase/dto/firebase_message_document.dart'; @immutable class FirebaseChatDocument { diff --git a/packages/flutter_community_chat_firebase/lib/dto/firebase_message_document.dart b/packages/flutter_chat_firebase/lib/dto/firebase_message_document.dart similarity index 100% rename from packages/flutter_community_chat_firebase/lib/dto/firebase_message_document.dart rename to packages/flutter_chat_firebase/lib/dto/firebase_message_document.dart diff --git a/packages/flutter_community_chat_firebase/lib/dto/firebase_user_document.dart b/packages/flutter_chat_firebase/lib/dto/firebase_user_document.dart similarity index 100% rename from packages/flutter_community_chat_firebase/lib/dto/firebase_user_document.dart rename to packages/flutter_chat_firebase/lib/dto/firebase_user_document.dart diff --git a/packages/flutter_chat_firebase/lib/flutter_chat_firebase.dart b/packages/flutter_chat_firebase/lib/flutter_chat_firebase.dart new file mode 100644 index 0000000..7ad2e3a --- /dev/null +++ b/packages/flutter_chat_firebase/lib/flutter_chat_firebase.dart @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +library flutter_chat_firebase; + +export 'package:flutter_chat_firebase/service/service.dart'; diff --git a/packages/flutter_community_chat_firebase/lib/service/firebase_message_service.dart b/packages/flutter_chat_firebase/lib/service/firebase_chat_detail_service.dart similarity index 54% rename from packages/flutter_community_chat_firebase/lib/service/firebase_message_service.dart rename to packages/flutter_chat_firebase/lib/service/firebase_chat_detail_service.dart index 6ad72c8..b5a23ed 100644 --- a/packages/flutter_community_chat_firebase/lib/service/firebase_message_service.dart +++ b/packages/flutter_chat_firebase/lib/service/firebase_chat_detail_service.dart @@ -8,12 +8,14 @@ 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_message_document.dart'; -import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; +import 'package:flutter_chat_firebase/config/firebase_chat_options.dart'; +import 'package:flutter_chat_firebase/dto/firebase_message_document.dart'; +import 'package:flutter_chat_interface/flutter_chat_interface.dart'; import 'package:uuid/uuid.dart'; -class FirebaseMessageService with ChangeNotifier implements MessageService { +class FirebaseChatDetailService + with ChangeNotifier + implements ChatDetailService { late final FirebaseFirestore _db; late final FirebaseStorage _storage; late final ChatUserService _userService; @@ -23,11 +25,11 @@ class FirebaseMessageService with ChangeNotifier implements MessageService { StreamSubscription? _subscription; DocumentSnapshot? lastMessage; List _cumulativeMessages = []; - ChatModel? lastChat; + String? lastChat; int? chatPageSize; DateTime timestampToFilter = DateTime.now(); - FirebaseMessageService({ + FirebaseChatDetailService({ required ChatUserService userService, FirebaseApp? app, FirebaseChatOptions? options, @@ -40,10 +42,10 @@ class FirebaseMessageService with ChangeNotifier implements MessageService { _options = options ?? const FirebaseChatOptions(); } - Future _sendMessage(ChatModel chat, Map data) async { + Future _sendMessage(String chatId, Map data) async { var currentUser = await _userService.getCurrentUser(); - if (chat.id == null || currentUser == null) { + if (currentUser == null) { return; } @@ -57,7 +59,7 @@ class FirebaseMessageService with ChangeNotifier implements MessageService { .collection( _options.chatsCollectionName, ) - .doc(chat.id); + .doc(chatId); var newMessage = await chatReference .collection( @@ -78,21 +80,13 @@ class FirebaseMessageService with ChangeNotifier implements MessageService { .collection( _options.chatsMetaDataCollectionName, ) - .doc(chat.id); + .doc(chatId); await metadataReference.update({ 'last_used': DateTime.now(), 'last_message': message, }); - if (_controller != null) { - if (chat.id != null && - _controller!.hasListener && - (_subscription == null)) { - _subscription = _startListeningForMessages(chat); - } - } - // update the chat counter for the other users // get all users from the chat // there is a field in the chat document called users that has a list of user ids @@ -107,7 +101,7 @@ class FirebaseMessageService with ChangeNotifier implements MessageService { ) .doc(userId) .collection(_options.userChatsCollectionName) - .doc(chat.id); + .doc(chatId); // what if the amount_unread_messages field does not exist? // it should be created when the chat is create if ((await userReference.get()) @@ -120,7 +114,7 @@ class FirebaseMessageService with ChangeNotifier implements MessageService { } else { await userReference.set({ 'amount_unread_messages': 1, - }); + }, SetOptions(merge: true)); } } } @@ -129,10 +123,10 @@ class FirebaseMessageService with ChangeNotifier implements MessageService { @override Future sendTextMessage({ required String text, - required ChatModel chat, + required String chatId, }) { return _sendMessage( - chat, + chatId, { 'text': text, }, @@ -141,21 +135,17 @@ class FirebaseMessageService with ChangeNotifier implements MessageService { @override Future sendImageMessage({ - required ChatModel chat, + required String chatId, required Uint8List image, }) async { - if (chat.id == null) { - return; - } - var ref = _storage - .ref('${_options.chatsCollectionName}/${chat.id}/${const Uuid().v4()}'); + .ref('${_options.chatsCollectionName}/$chatId/${const Uuid().v4()}'); return ref.putData(image).then( (_) => ref.getDownloadURL().then( (url) { _sendMessage( - chat, + chatId, { 'image_url': url, }, @@ -165,52 +155,16 @@ class FirebaseMessageService with ChangeNotifier implements MessageService { ); } - Query _getMessagesQuery(ChatModel chat) { - if (lastChat == null) { - lastChat = chat; - } else if (lastChat?.id != chat.id) { - _cumulativeMessages = []; - lastChat = chat; - lastMessage = null; - } - - var query = _db - .collection(_options.chatsCollectionName) - .doc(chat.id) - .collection(_options.messagesCollectionName) - .orderBy('timestamp', descending: true) - .limit(chatPageSize!); - - if (lastMessage == null) { - return query.withConverter( - fromFirestore: (snapshot, _) => - FirebaseMessageDocument.fromJson(snapshot.data()!, snapshot.id), - toFirestore: (user, _) => user.toJson(), - ); - } - return query - .startAfterDocument(lastMessage!) - .withConverter( - fromFirestore: (snapshot, _) => - FirebaseMessageDocument.fromJson(snapshot.data()!, snapshot.id), - toFirestore: (user, _) => user.toJson(), - ); - } - @override - Stream> getMessagesStream(ChatModel chat) { + Stream> getMessagesStream(String chatId) { + timestampToFilter = DateTime.now(); + var messages = []; _controller = StreamController>( onListen: () { var messagesCollection = _db .collection(_options.chatsCollectionName) - .doc(chat.id) + .doc(chatId) .collection(_options.messagesCollectionName) - .withConverter( - fromFirestore: (snapshot, _) => FirebaseMessageDocument.fromJson( - snapshot.data()!, snapshot.id), - toFirestore: (user, _) => user.toJson(), - ); - var query = messagesCollection .where( 'timestamp', isGreaterThan: timestampToFilter, @@ -219,67 +173,45 @@ class FirebaseMessageService with ChangeNotifier implements MessageService { fromFirestore: (snapshot, _) => FirebaseMessageDocument.fromJson( snapshot.data()!, snapshot.id), toFirestore: (user, _) => user.toJson(), - ); + ) + .snapshots(); - var stream = query.snapshots(); - // Subscribe to the stream and process the updates - _subscription = stream.listen((snapshot) async { - var messages = []; - - for (var messageDoc in snapshot.docs) { - var messageData = messageDoc.data(); + _subscription = messagesCollection.listen((event) async { + for (var message in event.docChanges) { + var data = message.doc.data(); + var sender = await _userService.getUser(data!.sender); var timestamp = DateTime.fromMillisecondsSinceEpoch( - (messageData.timestamp).millisecondsSinceEpoch, + (data.timestamp).millisecondsSinceEpoch, ); - // Check if the message is already in the list to avoid duplicates - if (timestampToFilter.isBefore(timestamp)) { - if (!messages.any((message) { - var timestamp = DateTime.fromMillisecondsSinceEpoch( - (messageData.timestamp).millisecondsSinceEpoch, - ); - return timestamp == message.timestamp; - })) { - var sender = await _userService.getUser(messageData.sender); - - if (sender != null) { - var timestamp = DateTime.fromMillisecondsSinceEpoch( - (messageData.timestamp).millisecondsSinceEpoch, - ); - - messages.add( - messageData.imageUrl != null - ? ChatImageMessageModel( - sender: sender, - imageUrl: messageData.imageUrl!, - timestamp: timestamp, - ) - : ChatTextMessageModel( - sender: sender, - text: messageData.text!, - timestamp: timestamp, - ), - ); - } - } + if (timestamp.isBefore(timestampToFilter)) { + return; } + messages.add( + data.imageUrl != null + ? ChatImageMessageModel( + sender: sender!, + imageUrl: data.imageUrl!, + timestamp: timestamp, + ) + : ChatTextMessageModel( + sender: sender!, + text: data.text!, + timestamp: timestamp, + ), + ); + timestampToFilter = DateTime.now(); } - - // Add the filtered messages to the controller - _controller?.add(messages); _cumulativeMessages = [ ..._cumulativeMessages, ...messages, ]; - - // remove all double elements List uniqueObjects = _cumulativeMessages.toSet().toList(); _cumulativeMessages = uniqueObjects; _cumulativeMessages .sort((a, b) => a.timestamp.compareTo(b.timestamp)); notifyListeners(); - timestampToFilter = DateTime.now(); }); }, onCancel: () { @@ -288,72 +220,25 @@ class FirebaseMessageService with ChangeNotifier implements MessageService { debugPrint('Canceling messages stream'); }, ); + return _controller!.stream; } - StreamSubscription _startListeningForMessages(ChatModel chat) { - debugPrint('Start listening for messages in chat ${chat.id}'); - var snapshots = _getMessagesQuery(chat).snapshots(); - return snapshots.listen( - (snapshot) async { - List messages = - List.from(_cumulativeMessages); - - if (snapshot.docs.isNotEmpty) { - lastMessage = snapshot.docs.last; - - for (var messageDoc in snapshot.docs) { - var messageData = messageDoc.data(); - - // Check if the message is already in the list to avoid duplicates - if (!messages.any((message) { - var timestamp = DateTime.fromMillisecondsSinceEpoch( - (messageData.timestamp).millisecondsSinceEpoch, - ); - return timestamp == message.timestamp; - })) { - var sender = await _userService.getUser(messageData.sender); - - if (sender != null) { - var timestamp = DateTime.fromMillisecondsSinceEpoch( - (messageData.timestamp).millisecondsSinceEpoch, - ); - - messages.add( - messageData.imageUrl != null - ? ChatImageMessageModel( - sender: sender, - imageUrl: messageData.imageUrl!, - timestamp: timestamp, - ) - : ChatTextMessageModel( - sender: sender, - text: messageData.text!, - timestamp: timestamp, - ), - ); - } - } - } - } - - _cumulativeMessages = messages; - - messages.sort((a, b) => a.timestamp.compareTo(b.timestamp)); - - _controller?.add(messages); - notifyListeners(); - }, - ); + @override + void stopListeningForMessages() { + _subscription?.cancel(); + _subscription = null; + _controller?.close(); + _controller = null; } @override - Future fetchMoreMessage(int pageSize, ChatModel chat) async { + Future fetchMoreMessage(int pageSize, String chatId) async { if (lastChat == null) { - lastChat = chat; - } else if (lastChat?.id != chat.id) { + lastChat = chatId; + } else if (lastChat != chatId) { _cumulativeMessages = []; - lastChat = chat; + lastChat = chatId; lastMessage = null; } // get the x amount of last messages from the oldest message that is in cumulative messages and add that to the list @@ -361,7 +246,7 @@ class FirebaseMessageService with ChangeNotifier implements MessageService { QuerySnapshot? messagesQuerySnapshot; var query = _db .collection(_options.chatsCollectionName) - .doc(chat.id) + .doc(chatId) .collection(_options.messagesCollectionName) .orderBy('timestamp', descending: true) .limit(pageSize); @@ -393,7 +278,6 @@ class FirebaseMessageService with ChangeNotifier implements MessageService { List messageDocuments = messagesQuerySnapshot.docs .map((QueryDocumentSnapshot doc) => doc.data()) .toList(); - for (var message in messageDocuments) { var sender = await _userService.getUser(message.sender); if (sender != null) { diff --git a/packages/flutter_community_chat_firebase/lib/service/firebase_chat_service.dart b/packages/flutter_chat_firebase/lib/service/firebase_chat_overview_service.dart similarity index 50% rename from packages/flutter_community_chat_firebase/lib/service/firebase_chat_service.dart rename to packages/flutter_chat_firebase/lib/service/firebase_chat_overview_service.dart index cd6178e..9510149 100644 --- a/packages/flutter_community_chat_firebase/lib/service/firebase_chat_service.dart +++ b/packages/flutter_chat_firebase/lib/service/firebase_chat_overview_service.dart @@ -1,26 +1,24 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first // SPDX-FileCopyrightText: 2022 Iconica // // SPDX-License-Identifier: BSD-3-Clause 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_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 'package:flutter_chat_firebase/config/firebase_chat_options.dart'; +import 'package:flutter_chat_firebase/dto/firebase_chat_document.dart'; +import 'package:flutter_chat_interface/flutter_chat_interface.dart'; -class FirebaseChatService implements ChatService { +class FirebaseChatOverviewService implements ChatOverviewService { late FirebaseFirestore _db; late FirebaseStorage _storage; late ChatUserService _userService; late FirebaseChatOptions _options; - DocumentSnapshot? lastUserDocument; - String? lastGroupId; - List chatIds = []; - int pageNumber = 1; - FirebaseChatService({ + FirebaseChatOverviewService({ required ChatUserService userService, FirebaseApp? app, FirebaseChatOptions? options, @@ -33,266 +31,152 @@ class FirebaseChatService implements ChatService { _options = options ?? const FirebaseChatOptions(); } - StreamSubscription _addUnreadChatSubscription( + Future _addUnreadChatSubscription( String chatId, String userId, - Function(int) onUnreadChatsUpdated, - ) { - var snapshots = _db + ) async { + var snapshots = await _db .collection(_options.usersCollectionName) .doc(userId) .collection(_options.userChatsCollectionName) .doc(chatId) - .snapshots(); + .get(); - return snapshots.listen((snapshot) { - var data = snapshot.data(); - onUnreadChatsUpdated(data?['amount_unread_messages'] ?? 0); - }); - } - - StreamSubscription _addChatSubscription( - List chatIds, - Function(List) onReceivedChats, - ) { - var snapshots = _db - .collection(_options.chatsMetaDataCollectionName) - .where( - FieldPath.documentId, - whereIn: chatIds, - ) - .withConverter( - fromFirestore: (snapshot, _) => - FirebaseChatDocument.fromJson(snapshot.data()!, snapshot.id), - toFirestore: (chat, _) => chat.toJson(), - ) - .snapshots(); - - return snapshots.listen((snapshot) async { - var currentUser = await _userService.getCurrentUser(); - var chats = []; - - for (var chatDoc in snapshot.docs) { - var chatData = chatDoc.data(); - - var messages = []; - - if (chatData.lastMessage != null) { - var messageData = chatData.lastMessage!; - var sender = await _userService.getUser(messageData.sender); - - if (sender != null) { - var timestamp = DateTime.fromMillisecondsSinceEpoch( - messageData.timestamp.millisecondsSinceEpoch, - ); - - messages.add( - messageData.imageUrl != null - ? ChatImageMessageModel( - sender: sender, - imageUrl: messageData.imageUrl!, - timestamp: timestamp, - ) - : ChatTextMessageModel( - sender: sender, - text: messageData.text!, - timestamp: timestamp, - ), - ); - } - } - ChatModel? chatModel; - - if (chatData.personal) { - var otherUserId = List.from(chatData.users).firstWhere( - (element) => element != currentUser?.id, - ); - var otherUser = await _userService.getUser(otherUserId); - - if (otherUser != null) { - chatModel = PersonalChatModel( - id: chatDoc.id, - user: otherUser, - lastMessage: messages.isNotEmpty ? messages.last : null, - messages: messages, - canBeDeleted: chatData.canBeDeleted, - lastUsed: chatData.lastUsed == null - ? null - : DateTime.fromMillisecondsSinceEpoch( - chatData.lastUsed!.millisecondsSinceEpoch, - ), - ); - } - } else { - // group chat - var users = []; - for (var userId in chatData.users) { - var user = await _userService.getUser(userId); - if (user != null) { - users.add(user); - } - } - chatModel = GroupChatModel( - id: chatDoc.id, - title: chatData.title ?? '', - imageUrl: chatData.imageUrl ?? '', - lastMessage: messages.isNotEmpty ? messages.last : null, - messages: messages, - users: users, - canBeDeleted: chatData.canBeDeleted, - lastUsed: chatData.lastUsed == null - ? null - : DateTime.fromMillisecondsSinceEpoch( - chatData.lastUsed!.millisecondsSinceEpoch, - ), - ); - } - if (chatModel != null) { - _addUnreadChatSubscription(chatModel.id ?? '', currentUser?.id ?? '', - (unreadMessages) { - // the chatmodel should be updated to reflect the amount of unread messages - if (chatModel is PersonalChatModel) { - chatModel = (chatModel as PersonalChatModel) - .copyWith(unreadMessages: unreadMessages); - } else if (chatModel is GroupChatModel) { - chatModel = (chatModel as GroupChatModel) - .copyWith(unreadMessages: unreadMessages); - } - - chats = chats - .map((chat) => chat.id == chatModel?.id ? chatModel! : chat) - .toList(); - onReceivedChats(chats); - }); - chats.add(chatModel!); - } - } - onReceivedChats(chats); - }); - } - - List> _splitChatIds({ - required List chatIds, - int chunkSize = 10, - }) { - var result = >[]; - var length = chatIds.length; - - for (var i = 0; i < length; i += chunkSize) { - var lastIndex = i + chunkSize; - result.add( - chatIds.sublist(i, lastIndex > length ? length : lastIndex), - ); - } - - return result; - } - - Stream> _getSpecificChatsStream(List chatIds) { - late StreamController> controller; - List> subscriptions = []; - var splittedChatIds = _splitChatIds(chatIds: chatIds); - - controller = StreamController>( - onListen: () { - var chats = >{}; - - for (var chatIdPair in splittedChatIds.asMap().entries) { - subscriptions.add( - _addChatSubscription( - chatIdPair.value, - (data) { - chats[chatIdPair.key] = data; - - var mergedChats = []; - - mergedChats.addAll( - chats.values.expand((element) => element), - ); - - mergedChats.sort( - (a, b) => (b.lastUsed ?? DateTime.now()).compareTo( - a.lastUsed ?? DateTime.now(), - ), - ); - - controller.add(mergedChats); - }, - ), - ); - } - }, - onCancel: () { - for (var subscription in subscriptions) { - subscription.cancel(); - } - }, - ); - return controller.stream; + return snapshots.data()?['amount_unread_messages']; } @override - Stream> getChatsStream(int pageSize) { + Stream> getChatsStream() { + StreamSubscription? chatSubscription; late StreamController> controller; - StreamSubscription? chatsSubscription; controller = StreamController( onListen: () async { - QuerySnapshot> userSnapshot; - List userChatIds; var currentUser = await _userService.getCurrentUser(); - - var userQuery = _db + var userSnapshot = _db .collection(_options.usersCollectionName) .doc(currentUser?.id) - .collection(_options.userChatsCollectionName); - if (lastUserDocument == null) { - userSnapshot = await userQuery.limit(pageSize).get(); - userChatIds = userSnapshot.docs.map((chat) => chat.id).toList(); - } else { - userSnapshot = await userQuery - .limit(pageSize) - .startAfterDocument(lastUserDocument!) - .get(); - userChatIds = userSnapshot.docs.map((chat) => chat.id).toList(); - } + .collection(_options.userChatsCollectionName) + .snapshots(); - var userGroupChatIds = await _db - .collection(_options.usersCollectionName) - .doc(currentUser?.id) - .get() - .then((userCollection) => - userCollection.data()?[_options.groupChatsCollectionName]) - .then((groupChatLabels) => groupChatLabels?.cast()) - .then((groupChatIds) { - var startIndex = (pageNumber - 1) * pageSize; - var endIndex = startIndex + pageSize; + userSnapshot.listen((event) { + var chatIds = event.docs.map((e) => e.id).toList(); + var chatSnapshot = _db + .collection(_options.chatsMetaDataCollectionName) + .where( + FieldPath.documentId, + whereIn: chatIds, + ) + .withConverter( + fromFirestore: (snapshot, _) => FirebaseChatDocument.fromJson( + snapshot.data()!, snapshot.id), + toFirestore: (chat, _) => chat.toJson(), + ) + .snapshots(); + List chats = []; + ChatModel? chatModel; - if (groupChatIds != null) { - if (startIndex >= groupChatIds.length) { - return []; + chatSubscription = chatSnapshot.listen((event) async { + for (var element in event.docChanges) { + var chat = element.doc.data(); + if (chat == null) return; + var otherUser = await _userService.getUser( + chat.users.firstWhere( + (element) => element != currentUser?.id, + ), + ); + int? unread = + await _addUnreadChatSubscription(chat.id!, currentUser!.id!); + + if (chat.personal) { + chatModel = PersonalChatModel( + id: chat.id, + user: otherUser!, + unreadMessages: unread, + lastUsed: chat.lastUsed == null + ? null + : DateTime.fromMillisecondsSinceEpoch( + chat.lastUsed!.millisecondsSinceEpoch, + ), + lastMessage: chat.lastMessage != null && + chat.lastMessage!.imageUrl != null + ? ChatImageMessageModel( + sender: otherUser, + imageUrl: chat.lastMessage!.imageUrl!, + timestamp: DateTime.fromMillisecondsSinceEpoch( + chat.lastMessage!.timestamp.millisecondsSinceEpoch, + ), + ) + : chat.lastMessage != null + ? ChatTextMessageModel( + sender: otherUser, + text: chat.lastMessage!.text!, + timestamp: DateTime.fromMillisecondsSinceEpoch( + chat.lastMessage!.timestamp + .millisecondsSinceEpoch, + ), + ) + : null, + ); + } else { + var users = []; + for (var userId in chat.users) { + var user = await _userService.getUser(userId); + if (user != null) { + users.add(user); + } + } + chatModel = GroupChatModel( + id: chat.id, + title: chat.title ?? '', + imageUrl: chat.imageUrl ?? '', + unreadMessages: unread, + users: users, + lastMessage: chat.lastMessage != null && + chat.lastMessage!.imageUrl == null + ? ChatTextMessageModel( + sender: otherUser!, + text: chat.lastMessage!.text!, + timestamp: DateTime.fromMillisecondsSinceEpoch( + chat.lastMessage!.timestamp.millisecondsSinceEpoch, + ), + ) + : ChatImageMessageModel( + sender: otherUser!, + imageUrl: chat.lastMessage!.imageUrl!, + timestamp: DateTime.fromMillisecondsSinceEpoch( + chat.lastMessage!.timestamp.millisecondsSinceEpoch, + ), + ), + canBeDeleted: chat.canBeDeleted, + lastUsed: chat.lastUsed == null + ? null + : DateTime.fromMillisecondsSinceEpoch( + chat.lastUsed!.millisecondsSinceEpoch, + ), + ); + } + chats.add(chatModel!); } - var groupIds = groupChatIds.sublist( - startIndex, endIndex.clamp(0, groupChatIds.length)); - lastGroupId = groupIds.last; - return groupIds; - } - return []; - }); + Set uniqueIds = {}; + List uniqueChatModels = []; - if (userSnapshot.docs.isNotEmpty) { - lastUserDocument = userSnapshot.docs.last; - } + for (ChatModel chatModel in chats) { + if (uniqueIds.add(chatModel.id!)) { + uniqueChatModels.add(chatModel); + } + } + uniqueChatModels.sort( + (a, b) => (b.lastUsed ?? DateTime.now()).compareTo( + a.lastUsed ?? DateTime.now(), + ), + ); - pageNumber++; - chatIds.addAll([...userChatIds, ...userGroupChatIds]); - var chatsStream = _getSpecificChatsStream(chatIds); - - chatsSubscription = chatsStream.listen((event) { - controller.add(event); + controller.add(uniqueChatModels); + }); }); }, onCancel: () { - chatsSubscription?.cancel(); + chatSubscription?.cancel(); }, ); return controller.stream; @@ -338,7 +222,7 @@ class FirebaseChatService implements ChatService { ); } else { var groupChatCollection = await _db - .collection(_options.chatsCollectionName) + .collection(_options.chatsMetaDataCollectionName) .doc(chatId) .withConverter( fromFirestore: (snapshot, _) => @@ -442,11 +326,10 @@ class FirebaseChatService implements ChatService { .doc(userId) .collection(_options.userChatsCollectionName) .doc(reference.id) - .set({'users': userIds}); + .set({'users': userIds}, SetOptions(merge: true)); } chat.id = reference.id; - chatIds.add(chat.id!); } else if (chat is GroupChatModel) { if (currentUser?.id == null) { return chat; @@ -479,11 +362,10 @@ class FirebaseChatService implements ChatService { .doc(userId) .collection(_options.groupChatsCollectionName) .doc(reference.id) - .set({'users': userIds}); + .set({'users': userIds}, SetOptions(merge: true)); } chat.id = reference.id; - chatIds.add(chat.id!); } else { throw Exception('Chat type not supported for firebase'); } diff --git a/packages/flutter_community_chat_firebase/lib/service/firebase_user_service.dart b/packages/flutter_chat_firebase/lib/service/firebase_chat_user_service.dart similarity index 87% rename from packages/flutter_community_chat_firebase/lib/service/firebase_user_service.dart rename to packages/flutter_chat_firebase/lib/service/firebase_chat_user_service.dart index e417f95..5d81ccb 100644 --- a/packages/flutter_community_chat_firebase/lib/service/firebase_user_service.dart +++ b/packages/flutter_chat_firebase/lib/service/firebase_chat_user_service.dart @@ -5,12 +5,12 @@ 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'; +import 'package:flutter_chat_firebase/config/firebase_chat_options.dart'; +import 'package:flutter_chat_firebase/dto/firebase_user_document.dart'; +import 'package:flutter_chat_interface/flutter_chat_interface.dart'; -class FirebaseUserService implements ChatUserService { - FirebaseUserService({ +class FirebaseChatUserService implements ChatUserService { + FirebaseChatUserService({ FirebaseApp? app, FirebaseChatOptions? options, }) { diff --git a/packages/flutter_chat_firebase/lib/service/service.dart b/packages/flutter_chat_firebase/lib/service/service.dart new file mode 100644 index 0000000..99331f3 --- /dev/null +++ b/packages/flutter_chat_firebase/lib/service/service.dart @@ -0,0 +1,3 @@ +export 'package:flutter_chat_firebase/service/firebase_chat_user_service.dart'; +export 'package:flutter_chat_firebase/service/firebase_chat_detail_service.dart'; +export 'package:flutter_chat_firebase/service/firebase_chat_overview_service.dart'; diff --git a/packages/flutter_community_chat_firebase/pubspec.yaml b/packages/flutter_chat_firebase/pubspec.yaml similarity index 69% rename from packages/flutter_community_chat_firebase/pubspec.yaml rename to packages/flutter_chat_firebase/pubspec.yaml index c0d976c..db7b393 100644 --- a/packages/flutter_community_chat_firebase/pubspec.yaml +++ b/packages/flutter_chat_firebase/pubspec.yaml @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -name: flutter_community_chat_firebase +name: flutter_chat_firebase description: A new Flutter package project. version: 1.0.0 publish_to: none @@ -19,10 +19,10 @@ dependencies: firebase_storage: ^11.0.5 firebase_auth: ^4.1.2 uuid: ^4.0.0 - flutter_community_chat_interface: + flutter_chat_interface: git: - url: https://github.com/Iconica-Development/flutter_community_chat - path: packages/flutter_community_chat_interface + url: https://github.com/Iconica-Development/flutter_chat + path: packages/flutter_chat_interface ref: 1.0.0 dev_dependencies: diff --git a/packages/flutter_community_chat_interface/analysis_options.yaml b/packages/flutter_chat_interface/analysis_options.yaml similarity index 100% rename from packages/flutter_community_chat_interface/analysis_options.yaml rename to packages/flutter_chat_interface/analysis_options.yaml diff --git a/packages/flutter_chat_interface/lib/flutter_chat_interface.dart b/packages/flutter_chat_interface/lib/flutter_chat_interface.dart new file mode 100644 index 0000000..986f170 --- /dev/null +++ b/packages/flutter_chat_interface/lib/flutter_chat_interface.dart @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +library flutter_chat_interface; + +export 'package:flutter_chat_interface/src/model/model.dart'; +export 'package:flutter_chat_interface/src/service/service.dart'; +export 'package:flutter_chat_interface/src/chat_data_provider.dart'; diff --git a/packages/flutter_community_chat_interface/lib/src/chat_data_provider.dart b/packages/flutter_chat_interface/lib/src/chat_data_provider.dart similarity index 72% rename from packages/flutter_community_chat_interface/lib/src/chat_data_provider.dart rename to packages/flutter_chat_interface/lib/src/chat_data_provider.dart index a152c49..61bc4de 100644 --- a/packages/flutter_community_chat_interface/lib/src/chat_data_provider.dart +++ b/packages/flutter_chat_interface/lib/src/chat_data_provider.dart @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; +import 'package:flutter_chat_interface/flutter_chat_interface.dart'; import 'package:flutter_data_interface/flutter_data_interface.dart'; class ChatDataProvider extends DataInterface { @@ -14,6 +14,6 @@ class ChatDataProvider extends DataInterface { static final Object _token = Object(); final ChatUserService userService; - final ChatService chatService; - final MessageService messageService; + final ChatOverviewService chatService; + final ChatDetailService messageService; } diff --git a/packages/flutter_chat_interface/lib/src/model/chat.dart b/packages/flutter_chat_interface/lib/src/model/chat.dart new file mode 100644 index 0000000..fa3bfd9 --- /dev/null +++ b/packages/flutter_chat_interface/lib/src/model/chat.dart @@ -0,0 +1,39 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +import 'package:flutter_chat_interface/flutter_chat_interface.dart'; + +abstract class ChatModelInterface { + String? get id; + List? get messages; + int? get unreadMessages; + DateTime? get lastUsed; + ChatMessageModel? get lastMessage; + bool get canBeDeleted; +} + +class ChatModel implements ChatModelInterface { + ChatModel({ + this.id, + this.messages = const [], + this.unreadMessages, + this.lastUsed, + this.lastMessage, + this.canBeDeleted = true, + }); + + @override + String? id; + @override + final List? messages; + @override + final int? unreadMessages; + @override + final DateTime? lastUsed; + @override + final ChatMessageModel? lastMessage; + @override + final bool canBeDeleted; +} diff --git a/packages/flutter_chat_interface/lib/src/model/chat_image_message.dart b/packages/flutter_chat_interface/lib/src/model/chat_image_message.dart new file mode 100644 index 0000000..37c2ac3 --- /dev/null +++ b/packages/flutter_chat_interface/lib/src/model/chat_image_message.dart @@ -0,0 +1,29 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +import 'package:flutter_chat_interface/flutter_chat_interface.dart'; + +abstract class ChatImageMessageModelInterface extends ChatMessageModel { + ChatImageMessageModelInterface({ + required super.sender, + required super.timestamp, + }); + + String get imageUrl; +} + +class ChatImageMessageModel implements ChatImageMessageModelInterface { + ChatImageMessageModel({ + required this.sender, + required this.timestamp, + required this.imageUrl, + }); + @override + final ChatUserModel sender; + @override + final DateTime timestamp; + @override + final String imageUrl; +} diff --git a/packages/flutter_chat_interface/lib/src/model/chat_message.dart b/packages/flutter_chat_interface/lib/src/model/chat_message.dart new file mode 100644 index 0000000..b248127 --- /dev/null +++ b/packages/flutter_chat_interface/lib/src/model/chat_message.dart @@ -0,0 +1,23 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +import 'package:flutter_chat_interface/src/model/chat_user.dart'; + +abstract class ChatMessageModelInterface { + ChatUserModel get sender; + DateTime get timestamp; +} + +class ChatMessageModel implements ChatMessageModelInterface { + ChatMessageModel({ + required this.sender, + required this.timestamp, + }); + + @override + final ChatUserModel sender; + @override + final DateTime timestamp; +} diff --git a/packages/flutter_chat_interface/lib/src/model/chat_text_message.dart b/packages/flutter_chat_interface/lib/src/model/chat_text_message.dart new file mode 100644 index 0000000..18a6cdd --- /dev/null +++ b/packages/flutter_chat_interface/lib/src/model/chat_text_message.dart @@ -0,0 +1,29 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +import 'package:flutter_chat_interface/flutter_chat_interface.dart'; + +abstract class ChatTextMessageModelInterface extends ChatMessageModel { + ChatTextMessageModelInterface({ + required super.sender, + required super.timestamp, + }); + + String get text; +} + +class ChatTextMessageModel implements ChatTextMessageModelInterface { + ChatTextMessageModel({ + required this.sender, + required this.timestamp, + required this.text, + }); + @override + final ChatUserModel sender; + @override + final DateTime timestamp; + @override + final String text; +} diff --git a/packages/flutter_community_chat_interface/lib/src/model/chat_user.dart b/packages/flutter_chat_interface/lib/src/model/chat_user.dart similarity index 64% rename from packages/flutter_community_chat_interface/lib/src/model/chat_user.dart rename to packages/flutter_chat_interface/lib/src/model/chat_user.dart index 6b28765..68a7e99 100644 --- a/packages/flutter_community_chat_interface/lib/src/model/chat_user.dart +++ b/packages/flutter_chat_interface/lib/src/model/chat_user.dart @@ -1,8 +1,18 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first // SPDX-FileCopyrightText: 2022 Iconica // // SPDX-License-Identifier: BSD-3-Clause -class ChatUserModel { +abstract class ChatUserModelInterface { + String? get id; + String? get firstName; + String? get lastName; + String? get imageUrl; + + String? get fullName; +} + +class ChatUserModel implements ChatUserModelInterface { ChatUserModel({ this.id, this.firstName, @@ -10,11 +20,15 @@ class ChatUserModel { this.imageUrl, }); + @override final String? id; + @override final String? firstName; + @override final String? lastName; + @override final String? imageUrl; - + @override String? get fullName { var fullName = ''; diff --git a/packages/flutter_chat_interface/lib/src/model/group_chat.dart b/packages/flutter_chat_interface/lib/src/model/group_chat.dart new file mode 100644 index 0000000..80b0ddf --- /dev/null +++ b/packages/flutter_chat_interface/lib/src/model/group_chat.dart @@ -0,0 +1,91 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +import 'package:flutter_chat_interface/flutter_chat_interface.dart'; + +abstract class GroupChatModelInterface extends ChatModel { + GroupChatModelInterface({ + super.id, + super.messages, + super.lastUsed, + super.lastMessage, + super.unreadMessages, + super.canBeDeleted, + }); + + String get title; + String get imageUrl; + List get users; + + GroupChatModelInterface copyWith({ + String? id, + List? messages, + int? unreadMessages, + DateTime? lastUsed, + ChatMessageModel? lastMessage, + String? title, + String? imageUrl, + List? users, + bool? canBeDeleted, + }); +} + +class GroupChatModel implements GroupChatModelInterface { + GroupChatModel({ + this.id, + this.messages, + this.unreadMessages, + this.lastUsed, + this.lastMessage, + required this.canBeDeleted, + required this.title, + required this.imageUrl, + required this.users, + }); + + @override + String? id; + @override + final List? messages; + @override + final int? unreadMessages; + @override + final DateTime? lastUsed; + @override + final ChatMessageModel? lastMessage; + @override + final bool canBeDeleted; + @override + final String title; + @override + final String imageUrl; + @override + final List users; + + @override + GroupChatModel copyWith({ + String? id, + List? messages, + int? unreadMessages, + DateTime? lastUsed, + ChatMessageModel? lastMessage, + bool? canBeDeleted, + String? title, + String? imageUrl, + List? users, + }) { + return GroupChatModel( + id: id ?? this.id, + messages: messages ?? this.messages, + unreadMessages: unreadMessages ?? this.unreadMessages, + lastUsed: lastUsed ?? this.lastUsed, + lastMessage: lastMessage ?? this.lastMessage, + canBeDeleted: canBeDeleted ?? this.canBeDeleted, + title: title ?? this.title, + imageUrl: imageUrl ?? this.imageUrl, + users: users ?? this.users, + ); + } +} diff --git a/packages/flutter_community_chat_interface/lib/src/model/model.dart b/packages/flutter_chat_interface/lib/src/model/model.dart similarity index 100% rename from packages/flutter_community_chat_interface/lib/src/model/model.dart rename to packages/flutter_chat_interface/lib/src/model/model.dart diff --git a/packages/flutter_chat_interface/lib/src/model/personal_chat.dart b/packages/flutter_chat_interface/lib/src/model/personal_chat.dart new file mode 100644 index 0000000..a264ab4 --- /dev/null +++ b/packages/flutter_chat_interface/lib/src/model/personal_chat.dart @@ -0,0 +1,78 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +import 'package:flutter_chat_interface/flutter_chat_interface.dart'; + +abstract class PersonalChatModelInterface extends ChatModel { + PersonalChatModelInterface({ + super.id, + super.messages, + super.unreadMessages, + super.lastUsed, + super.lastMessage, + super.canBeDeleted, + }); + + ChatUserModel get user; + + PersonalChatModel copyWith({ + String? id, + List? messages, + int? unreadMessages, + DateTime? lastUsed, + ChatMessageModel? lastMessage, + ChatUserModel? user, + bool? canBeDeleted, + }); +} + +class PersonalChatModel implements PersonalChatModelInterface { + PersonalChatModel({ + this.id, + this.messages = const [], + this.unreadMessages, + this.lastUsed, + this.lastMessage, + this.canBeDeleted = true, + required this.user, + }); + + @override + String? id; + @override + final List? messages; + @override + final int? unreadMessages; + @override + final DateTime? lastUsed; + @override + final ChatMessageModel? lastMessage; + @override + final bool canBeDeleted; + + @override + final ChatUserModel user; + + @override + PersonalChatModel copyWith({ + String? id, + List? messages, + int? unreadMessages, + DateTime? lastUsed, + ChatMessageModel? lastMessage, + bool? canBeDeleted, + ChatUserModel? user, + }) { + return PersonalChatModel( + id: id ?? this.id, + messages: messages ?? this.messages, + unreadMessages: unreadMessages ?? this.unreadMessages, + lastUsed: lastUsed ?? this.lastUsed, + lastMessage: lastMessage ?? this.lastMessage, + user: user ?? this.user, + canBeDeleted: canBeDeleted ?? this.canBeDeleted, + ); + } +} diff --git a/packages/flutter_community_chat_interface/lib/src/service/message_service.dart b/packages/flutter_chat_interface/lib/src/service/chat_detail_service.dart similarity index 50% rename from packages/flutter_community_chat_interface/lib/src/service/message_service.dart rename to packages/flutter_chat_interface/lib/src/service/chat_detail_service.dart index d317b17..d7e9e28 100644 --- a/packages/flutter_community_chat_interface/lib/src/service/message_service.dart +++ b/packages/flutter_chat_interface/lib/src/service/chat_detail_service.dart @@ -1,23 +1,25 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; +import 'package:flutter_chat_interface/flutter_chat_interface.dart'; -abstract class MessageService with ChangeNotifier { +abstract class ChatDetailService with ChangeNotifier { Future sendTextMessage({ - required ChatModel chat, + required String chatId, required String text, }); Future sendImageMessage({ - required ChatModel chat, + required String chatId, required Uint8List image, }); Stream> getMessagesStream( - ChatModel chat, + String chatId, ); - Future fetchMoreMessage(int pageSize, ChatModel chat); + Future fetchMoreMessage(int pageSize, String chatId); List getMessages(); + + void stopListeningForMessages(); } diff --git a/packages/flutter_community_chat_interface/lib/src/service/chat_service.dart b/packages/flutter_chat_interface/lib/src/service/chat_overview_service.dart similarity index 61% rename from packages/flutter_community_chat_interface/lib/src/service/chat_service.dart rename to packages/flutter_chat_interface/lib/src/service/chat_overview_service.dart index e0e668b..2f7d2e9 100644 --- a/packages/flutter_community_chat_interface/lib/src/service/chat_service.dart +++ b/packages/flutter_chat_interface/lib/src/service/chat_overview_service.dart @@ -1,7 +1,7 @@ -import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; +import 'package:flutter_chat_interface/flutter_chat_interface.dart'; -abstract class ChatService { - Stream> getChatsStream(int pageSize); +abstract class ChatOverviewService { + Stream> getChatsStream(); Future getChatByUser(ChatUserModel user); Future getChatById(String id); Future deleteChat(ChatModel chat); diff --git a/packages/flutter_chat_interface/lib/src/service/chat_service.dart b/packages/flutter_chat_interface/lib/src/service/chat_service.dart new file mode 100644 index 0000000..c6b87f0 --- /dev/null +++ b/packages/flutter_chat_interface/lib/src/service/chat_service.dart @@ -0,0 +1,14 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:flutter_chat_interface/flutter_chat_interface.dart'; + +class ChatService { + final ChatUserService chatUserService; + final ChatOverviewService chatOverviewService; + final ChatDetailService chatDetailService; + + ChatService({ + required this.chatUserService, + required this.chatOverviewService, + required this.chatDetailService, + }); +} diff --git a/packages/flutter_chat_interface/lib/src/service/service.dart b/packages/flutter_chat_interface/lib/src/service/service.dart new file mode 100644 index 0000000..a65b80e --- /dev/null +++ b/packages/flutter_chat_interface/lib/src/service/service.dart @@ -0,0 +1,4 @@ +export 'chat_overview_service.dart'; +export 'user_service.dart'; +export 'chat_detail_service.dart'; +export 'chat_service.dart'; diff --git a/packages/flutter_community_chat_interface/lib/src/service/user_service.dart b/packages/flutter_chat_interface/lib/src/service/user_service.dart similarity index 65% rename from packages/flutter_community_chat_interface/lib/src/service/user_service.dart rename to packages/flutter_chat_interface/lib/src/service/user_service.dart index 372689f..ba65fd6 100644 --- a/packages/flutter_community_chat_interface/lib/src/service/user_service.dart +++ b/packages/flutter_chat_interface/lib/src/service/user_service.dart @@ -1,4 +1,4 @@ -import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; +import 'package:flutter_chat_interface/flutter_chat_interface.dart'; abstract class ChatUserService { Future getUser(String id); diff --git a/packages/flutter_community_chat_interface/pubspec.yaml b/packages/flutter_chat_interface/pubspec.yaml similarity index 91% rename from packages/flutter_community_chat_interface/pubspec.yaml rename to packages/flutter_chat_interface/pubspec.yaml index 73753b7..5f96d22 100644 --- a/packages/flutter_community_chat_interface/pubspec.yaml +++ b/packages/flutter_chat_interface/pubspec.yaml @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -name: flutter_community_chat_interface +name: flutter_chat_interface description: A new Flutter package project. version: 1.0.0 publish_to: none diff --git a/packages/flutter_community_chat_view/analysis_options.yaml b/packages/flutter_chat_view/analysis_options.yaml similarity index 100% rename from packages/flutter_community_chat_view/analysis_options.yaml rename to packages/flutter_chat_view/analysis_options.yaml diff --git a/packages/flutter_community_chat_view/example/.gitignore b/packages/flutter_chat_view/example/.gitignore similarity index 100% rename from packages/flutter_community_chat_view/example/.gitignore rename to packages/flutter_chat_view/example/.gitignore diff --git a/packages/flutter_community_chat_view/example/README.md b/packages/flutter_chat_view/example/README.md similarity index 100% rename from packages/flutter_community_chat_view/example/README.md rename to packages/flutter_chat_view/example/README.md diff --git a/packages/flutter_community_chat_view/example/analysis_options.yaml b/packages/flutter_chat_view/example/analysis_options.yaml similarity index 100% rename from packages/flutter_community_chat_view/example/analysis_options.yaml rename to packages/flutter_chat_view/example/analysis_options.yaml diff --git a/packages/flutter_community_chat_view/example/lib/main.dart b/packages/flutter_chat_view/example/lib/main.dart similarity index 98% rename from packages/flutter_community_chat_view/example/lib/main.dart rename to packages/flutter_chat_view/example/lib/main.dart index 0aca106..35b723e 100644 --- a/packages/flutter_community_chat_view/example/lib/main.dart +++ b/packages/flutter_chat_view/example/lib/main.dart @@ -4,7 +4,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter_community_chat_view/flutter_community_chat_view.dart'; +import 'package:flutter_chat_view/flutter_chat_view.dart'; void main() { runApp(const MaterialApp(home: MyStatefulWidget())); diff --git a/packages/flutter_community_chat_view/example/pubspec.yaml b/packages/flutter_chat_view/example/pubspec.yaml similarity index 85% rename from packages/flutter_community_chat_view/example/pubspec.yaml rename to packages/flutter_chat_view/example/pubspec.yaml index 29a9f3d..754f658 100644 --- a/packages/flutter_community_chat_view/example/pubspec.yaml +++ b/packages/flutter_chat_view/example/pubspec.yaml @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -name: flutter_community_chat_view_example +name: flutter_chat_view_example description: A standard flutter package. publish_to: "none" # Remove this line if you wish to publish to pub.dev @@ -14,7 +14,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_community_chat_view: + flutter_chat_view: path: .. dev_dependencies: diff --git a/packages/flutter_community_chat_view/example/test/widget_test.dart b/packages/flutter_chat_view/example/test/widget_test.dart similarity index 100% rename from packages/flutter_community_chat_view/example/test/widget_test.dart rename to packages/flutter_chat_view/example/test/widget_test.dart diff --git a/packages/flutter_community_chat_view/ios/Flutter/Generated.xcconfig b/packages/flutter_chat_view/ios/Flutter/Generated.xcconfig similarity index 89% rename from packages/flutter_community_chat_view/ios/Flutter/Generated.xcconfig rename to packages/flutter_chat_view/ios/Flutter/Generated.xcconfig index 1167b5a..8778ec4 100644 --- a/packages/flutter_community_chat_view/ios/Flutter/Generated.xcconfig +++ b/packages/flutter_chat_view/ios/Flutter/Generated.xcconfig @@ -1,6 +1,6 @@ // This is a generated file; do not edit or check into version control. FLUTTER_ROOT=/opt/homebrew/Caskroom/flutter/3.10.2/flutter -FLUTTER_APPLICATION_PATH=/Users/mikedoornenbal/Documents/iconica/flutter_community_chat/packages/flutter_community_chat_view +FLUTTER_APPLICATION_PATH=/Users/mikedoornenbal/Documents/iconica/flutter_chat/packages/flutter_chat_view COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_TARGET=lib/main.dart FLUTTER_BUILD_DIR=build diff --git a/packages/flutter_community_chat_view/ios/Flutter/flutter_export_environment.sh b/packages/flutter_chat_view/ios/Flutter/flutter_export_environment.sh similarity index 88% rename from packages/flutter_community_chat_view/ios/Flutter/flutter_export_environment.sh rename to packages/flutter_chat_view/ios/Flutter/flutter_export_environment.sh index 8559eeb..5b36b6b 100755 --- a/packages/flutter_community_chat_view/ios/Flutter/flutter_export_environment.sh +++ b/packages/flutter_chat_view/ios/Flutter/flutter_export_environment.sh @@ -1,7 +1,7 @@ #!/bin/sh # This is a generated file; do not edit or check into version control. export "FLUTTER_ROOT=/opt/homebrew/Caskroom/flutter/3.10.2/flutter" -export "FLUTTER_APPLICATION_PATH=/Users/mikedoornenbal/Documents/iconica/flutter_community_chat/packages/flutter_community_chat_view" +export "FLUTTER_APPLICATION_PATH=/Users/mikedoornenbal/Documents/iconica/flutter_chat/packages/flutter_chat_view" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_TARGET=lib/main.dart" export "FLUTTER_BUILD_DIR=build" diff --git a/packages/flutter_community_chat_view/ios/Podfile b/packages/flutter_chat_view/ios/Podfile similarity index 100% rename from packages/flutter_community_chat_view/ios/Podfile rename to packages/flutter_chat_view/ios/Podfile diff --git a/packages/flutter_community_chat_view/ios/Runner/GeneratedPluginRegistrant.h b/packages/flutter_chat_view/ios/Runner/GeneratedPluginRegistrant.h similarity index 100% rename from packages/flutter_community_chat_view/ios/Runner/GeneratedPluginRegistrant.h rename to packages/flutter_chat_view/ios/Runner/GeneratedPluginRegistrant.h diff --git a/packages/flutter_community_chat_view/ios/Runner/GeneratedPluginRegistrant.m b/packages/flutter_chat_view/ios/Runner/GeneratedPluginRegistrant.m similarity index 100% rename from packages/flutter_community_chat_view/ios/Runner/GeneratedPluginRegistrant.m rename to packages/flutter_chat_view/ios/Runner/GeneratedPluginRegistrant.m diff --git a/packages/flutter_community_chat_view/lib/flutter_community_chat_view.dart b/packages/flutter_chat_view/lib/flutter_chat_view.dart similarity index 64% rename from packages/flutter_community_chat_view/lib/flutter_community_chat_view.dart rename to packages/flutter_chat_view/lib/flutter_chat_view.dart index f855097..063ac8b 100644 --- a/packages/flutter_community_chat_view/lib/flutter_community_chat_view.dart +++ b/packages/flutter_chat_view/lib/flutter_chat_view.dart @@ -2,13 +2,15 @@ // // SPDX-License-Identifier: BSD-3-Clause -library flutter_community_chat_view; +library flutter_chat_view; -export 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; +export 'package:flutter_chat_interface/flutter_chat_interface.dart'; export 'src/components/chat_row.dart'; export 'src/config/chat_options.dart'; export 'src/config/chat_translations.dart'; export 'src/screens/chat_detail_screen.dart'; +export 'src/screens/chat_entry_widget.dart'; +export 'src/screens/chat_profile_screen.dart'; export 'src/screens/chat_screen.dart'; export 'src/screens/new_chat_screen.dart'; diff --git a/packages/flutter_community_chat_view/lib/src/components/chat_bottom.dart b/packages/flutter_chat_view/lib/src/components/chat_bottom.dart similarity index 96% rename from packages/flutter_community_chat_view/lib/src/components/chat_bottom.dart rename to packages/flutter_chat_view/lib/src/components/chat_bottom.dart index e9fb8dd..df56ea4 100644 --- a/packages/flutter_community_chat_view/lib/src/components/chat_bottom.dart +++ b/packages/flutter_chat_view/lib/src/components/chat_bottom.dart @@ -3,7 +3,7 @@ // SPDX-License-Identifier: BSD-3-Clause import 'package:flutter/material.dart'; -import 'package:flutter_community_chat_view/flutter_community_chat_view.dart'; +import 'package:flutter_chat_view/flutter_chat_view.dart'; class ChatBottom extends StatefulWidget { const ChatBottom({ diff --git a/packages/flutter_community_chat_view/lib/src/components/chat_detail_row.dart b/packages/flutter_chat_view/lib/src/components/chat_detail_row.dart similarity index 96% rename from packages/flutter_community_chat_view/lib/src/components/chat_detail_row.dart rename to packages/flutter_chat_view/lib/src/components/chat_detail_row.dart index 217b037..d296920 100644 --- a/packages/flutter_community_chat_view/lib/src/components/chat_detail_row.dart +++ b/packages/flutter_chat_view/lib/src/components/chat_detail_row.dart @@ -4,9 +4,9 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.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_community_chat_view/src/services/date_formatter.dart'; +import 'package:flutter_chat_view/flutter_chat_view.dart'; +import 'package:flutter_chat_view/src/components/chat_image.dart'; +import 'package:flutter_chat_view/src/services/date_formatter.dart'; class ChatDetailRow extends StatefulWidget { const ChatDetailRow({ diff --git a/packages/flutter_community_chat_view/lib/src/components/chat_image.dart b/packages/flutter_chat_view/lib/src/components/chat_image.dart similarity index 100% rename from packages/flutter_community_chat_view/lib/src/components/chat_image.dart rename to packages/flutter_chat_view/lib/src/components/chat_image.dart diff --git a/packages/flutter_community_chat_view/lib/src/components/chat_row.dart b/packages/flutter_chat_view/lib/src/components/chat_row.dart similarity index 100% rename from packages/flutter_community_chat_view/lib/src/components/chat_row.dart rename to packages/flutter_chat_view/lib/src/components/chat_row.dart diff --git a/packages/flutter_community_chat_view/lib/src/components/image_loading_snackbar.dart b/packages/flutter_chat_view/lib/src/components/image_loading_snackbar.dart similarity index 86% rename from packages/flutter_community_chat_view/lib/src/components/image_loading_snackbar.dart rename to packages/flutter_chat_view/lib/src/components/image_loading_snackbar.dart index 18e42e0..68e1ac0 100644 --- a/packages/flutter_community_chat_view/lib/src/components/image_loading_snackbar.dart +++ b/packages/flutter_chat_view/lib/src/components/image_loading_snackbar.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_community_chat_view/flutter_community_chat_view.dart'; +import 'package:flutter_chat_view/flutter_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_chat_view/lib/src/config/chat_options.dart similarity index 96% rename from packages/flutter_community_chat_view/lib/src/config/chat_options.dart rename to packages/flutter_chat_view/lib/src/config/chat_options.dart index ad72a7d..dc5e1b3 100644 --- a/packages/flutter_community_chat_view/lib/src/config/chat_options.dart +++ b/packages/flutter_chat_view/lib/src/config/chat_options.dart @@ -3,8 +3,8 @@ // SPDX-License-Identifier: BSD-3-Clause import 'package:flutter/material.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_chat_view/flutter_chat_view.dart'; +import 'package:flutter_chat_view/src/components/chat_image.dart'; import 'package:flutter_image_picker/flutter_image_picker.dart'; class ChatOptions { diff --git a/packages/flutter_community_chat_view/lib/src/config/chat_translations.dart b/packages/flutter_chat_view/lib/src/config/chat_translations.dart similarity index 95% rename from packages/flutter_community_chat_view/lib/src/config/chat_translations.dart rename to packages/flutter_chat_view/lib/src/config/chat_translations.dart index da8fd9a..f4a2d56 100644 --- a/packages/flutter_community_chat_view/lib/src/config/chat_translations.dart +++ b/packages/flutter_chat_view/lib/src/config/chat_translations.dart @@ -22,6 +22,7 @@ class ChatTranslations { this.noUsersFound = 'No users found', this.anonymousUser = 'Anonymous user', this.chatCantBeDeleted = 'This chat can\'t be deleted', + this.chatProfileUsers = 'Users:', }); final String chatsTitle; @@ -40,6 +41,7 @@ class ChatTranslations { final String deleteChatModalConfirm; final String noUsersFound; final String chatCantBeDeleted; + final String chatProfileUsers; /// Shown when the user has no name final String anonymousUser; diff --git a/packages/flutter_community_chat_view/lib/src/screens/chat_detail_screen.dart b/packages/flutter_chat_view/lib/src/screens/chat_detail_screen.dart similarity index 82% rename from packages/flutter_community_chat_view/lib/src/screens/chat_detail_screen.dart rename to packages/flutter_chat_view/lib/src/screens/chat_detail_screen.dart index 26f6ae5..288f7ab 100644 --- a/packages/flutter_community_chat_view/lib/src/screens/chat_detail_screen.dart +++ b/packages/flutter_chat_view/lib/src/screens/chat_detail_screen.dart @@ -7,10 +7,10 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.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'; +import 'package:flutter_chat_view/flutter_chat_view.dart'; +import 'package:flutter_chat_view/src/components/chat_bottom.dart'; +import 'package:flutter_chat_view/src/components/chat_detail_row.dart'; +import 'package:flutter_chat_view/src/components/image_loading_snackbar.dart'; class ChatDetailScreen extends StatefulWidget { const ChatDetailScreen({ @@ -19,18 +19,16 @@ class ChatDetailScreen extends StatefulWidget { required this.onUploadImage, required this.onReadChat, required this.service, - required this.chatUserService, - required this.messageService, required this.pageSize, + required this.chatId, this.translations = const ChatTranslations(), - this.chat, this.onPressChatTitle, this.iconColor, this.showTime = false, super.key, }); - final ChatModel? chat; + final String chatId; /// The id of the current user that is viewing the chat. @@ -47,8 +45,6 @@ class ChatDetailScreen extends StatefulWidget { final Color? iconColor; final bool showTime; final ChatService service; - final ChatUserService chatUserService; - final MessageService messageService; final int pageSize; @override @@ -60,36 +56,39 @@ class _ChatDetailScreenState extends State { ChatUserModel? currentUser; ScrollController controller = ScrollController(); bool showIndicator = false; - late MessageService messageSubscription; + late ChatDetailService messageSubscription; Stream>? stream; ChatMessageModel? previousMessage; List detailRows = []; + ChatModel? chat; @override void initState() { super.initState(); - messageSubscription = widget.messageService; + messageSubscription = widget.service.chatDetailService; messageSubscription.addListener(onListen); - if (widget.chat != null) { - stream = widget.messageService.getMessagesStream(widget.chat!); - stream?.listen((event) {}); - Future.delayed(Duration.zero, () async { - if (detailRows.isEmpty) { - await widget.messageService - .fetchMoreMessage(widget.pageSize, widget.chat!); - } - }); - } - WidgetsBinding.instance.addPostFrameCallback((_) { - if (widget.chat != null) { - widget.onReadChat(widget.chat!); + Future.delayed(Duration.zero, () async { + chat = + await widget.service.chatOverviewService.getChatById(widget.chatId); + + if (detailRows.isEmpty) { + await widget.service.chatDetailService.fetchMoreMessage( + widget.pageSize, + chat!.id!, + ); } + stream = widget.service.chatDetailService.getMessagesStream(chat!.id!); + stream?.listen((event) {}); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + await widget.onReadChat(chat!); + }); }); } void onListen() { var chatMessages = []; - chatMessages = widget.messageService.getMessages(); + chatMessages = widget.service.chatDetailService.getMessages(); detailRows = []; previousMessage = null; for (var message in chatMessages) { @@ -106,8 +105,11 @@ class _ChatDetailScreenState extends State { } detailRows = detailRows.reversed.toList(); - widget.onReadChat(widget.chat!); if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + await widget.onReadChat(chat!); + }); + setState(() {}); } } @@ -115,6 +117,7 @@ class _ChatDetailScreenState extends State { @override void dispose() { messageSubscription.removeListener(onListen); + widget.service.chatDetailService.stopListeningForMessages(); super.dispose(); } @@ -132,18 +135,19 @@ class _ChatDetailScreenState extends State { var messenger = ScaffoldMessenger.of(context) ..showSnackBar( getImageLoadingSnackbar(widget.translations), - ); - + ) + ..activate(); if (image != null) { await widget.onUploadImage(image); } - - messenger.hideCurrentSnackBar(); + Future.delayed(const Duration(seconds: 1), () { + messenger.hideCurrentSnackBar(); + }); }, ); return FutureBuilder( - future: widget.service.getChatById(widget.chat?.id ?? ''), + future: widget.service.chatOverviewService.getChatById(widget.chatId), builder: (context, AsyncSnapshot snapshot) { var chatModel = snapshot.data; return Scaffold( @@ -153,7 +157,7 @@ class _ChatDetailScreenState extends State { onTap: () => widget.onPressChatTitle?.call(context, chatModel!), child: Row( mainAxisSize: MainAxisSize.min, - children: widget.chat == null + children: chat == null ? [] : [ if (chatModel is GroupChatModel) ...[ @@ -202,8 +206,8 @@ class _ChatDetailScreenState extends State { setState(() { showIndicator = true; }); - await widget.messageService - .fetchMoreMessage(widget.pageSize, widget.chat!); + await widget.service.chatDetailService + .fetchMoreMessage(widget.pageSize, widget.chatId); Future.delayed(const Duration(seconds: 2), () { if (mounted) { setState(() { diff --git a/packages/flutter_chat_view/lib/src/screens/chat_entry_widget.dart b/packages/flutter_chat_view/lib/src/screens/chat_entry_widget.dart new file mode 100644 index 0000000..3be4ad2 --- /dev/null +++ b/packages/flutter_chat_view/lib/src/screens/chat_entry_widget.dart @@ -0,0 +1,146 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_chat_view/flutter_chat_view.dart'; + +class ChatEntryWidget extends StatefulWidget { + const ChatEntryWidget({ + required this.chatService, + required this.onTap, + this.widgetSize = 75, + this.backgroundColor = Colors.grey, + this.icon = Icons.chat, + this.iconColor = Colors.black, + this.counterBackgroundColor = Colors.red, + this.textStyle, + super.key, + }); + + final ChatService chatService; + final Color backgroundColor; + final double widgetSize; + final Color counterBackgroundColor; + final Function() onTap; + final IconData icon; + final Color iconColor; + final TextStyle? textStyle; + + @override + State createState() => _ChatEntryWidgetState(); +} + +class _ChatEntryWidgetState extends State { + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => widget.onTap.call(), + child: StreamBuilder( + stream: + widget.chatService.chatOverviewService.getUnreadChatsCountStream(), + builder: (BuildContext context, snapshot) { + return Stack( + alignment: Alignment.center, + children: [ + Container( + width: widget.widgetSize, + height: widget.widgetSize, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: widget.backgroundColor, + ), + child: _AnimatedNotificationIcon( + icon: Icon( + widget.icon, + color: widget.iconColor, + size: widget.widgetSize / 1.5, + ), + notifications: snapshot.data ?? 0, + ), + ), + Positioned( + right: 0.0, + top: 0.0, + child: Container( + width: widget.widgetSize / 2, + height: widget.widgetSize / 2, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: widget.counterBackgroundColor, + ), + child: Center( + child: Text( + '${snapshot.data ?? 0}', + style: widget.textStyle, + ), + ), + ), + ), + ], + ); + }, + ), + ); + } +} + +class _AnimatedNotificationIcon extends StatefulWidget { + const _AnimatedNotificationIcon({ + required this.notifications, + required this.icon, + }); + + final int notifications; + final Icon icon; + + @override + State<_AnimatedNotificationIcon> createState() => + _AnimatedNotificationIconState(); +} + +class _AnimatedNotificationIconState extends State<_AnimatedNotificationIcon> + with SingleTickerProviderStateMixin { + late AnimationController _animationController; + + @override + void initState() { + _animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 500), + ); + + if (widget.notifications != 0) { + unawaited(_runAnimation()); + } + super.initState(); + } + + @override + void dispose() { + super.dispose(); + _animationController.dispose(); + } + + @override + void didUpdateWidget(covariant _AnimatedNotificationIcon oldWidget) { + super.didUpdateWidget(oldWidget); + + if (oldWidget.notifications != widget.notifications) { + _runAnimation(); + } + } + + Future _runAnimation() async { + await _animationController.forward(); + await _animationController.reverse(); + } + + @override + Widget build(BuildContext context) { + return RotationTransition( + turns: Tween(begin: 0.0, end: -.1) + .chain(CurveTween(curve: Curves.elasticIn)) + .animate(_animationController), + child: widget.icon, + ); + } +} diff --git a/packages/flutter_chat_view/lib/src/screens/chat_profile_screen.dart b/packages/flutter_chat_view/lib/src/screens/chat_profile_screen.dart new file mode 100644 index 0000000..f432231 --- /dev/null +++ b/packages/flutter_chat_view/lib/src/screens/chat_profile_screen.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_chat_view/flutter_chat_view.dart'; +import 'package:flutter_chat_view/src/services/profile_service.dart'; +import 'package:flutter_profile/flutter_profile.dart'; + +class ChatProfileScreen extends StatefulWidget { + const ChatProfileScreen({ + required this.chatService, + required this.chatId, + required this.translations, + required this.onTapUser, + this.userId, + super.key, + }); + + final ChatTranslations translations; + final ChatService chatService; + final String chatId; + final String? userId; + final Function(String userId) onTapUser; + + @override + State createState() => _ProfileScreenState(); +} + +class _ProfileScreenState extends State { + @override + Widget build(BuildContext context) { + var size = MediaQuery.of(context).size; + var hasUser = widget.userId == null; + return FutureBuilder( + future: hasUser + ? widget.chatService.chatOverviewService.getChatById(widget.chatId) + : widget.chatService.chatUserService.getUser(widget.userId!), + builder: (context, snapshot) { + var data = snapshot.data; + User? user; + + if (data is ChatUserModel) { + user = User( + firstName: data.firstName, + lastName: data.lastName, + imageUrl: data.imageUrl, + ); + } + if (data is PersonalChatModel) { + user = User( + firstName: data.user.firstName, + lastName: data.user.lastName, + imageUrl: data.user.imageUrl, + ); + } else if (data is GroupChatModel) { + user = User( + firstName: data.title, + imageUrl: data.imageUrl, + ); + } + + return Scaffold( + appBar: AppBar( + title: Text( + (data is ChatUserModel) + ? '${data.firstName ?? ''} ${data.lastName ?? ''}' + : (data is PersonalChatModel) + ? data.user.fullName ?? '' + : (data is GroupChatModel) + ? data.title + : '', + ), + ), + body: snapshot.hasData + ? ListView( + children: [ + const SizedBox( + height: 10, + ), + SizedBox( + height: 200, + width: size.width, + child: ProfilePage( + user: user!, + service: ChatProfileService(), + ), + ), + if (data is GroupChatModel) ...[ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 100), + child: Text( + widget.translations.chatProfileUsers, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + ...data.users.map((e) { + var user = User( + firstName: e.firstName ?? '', + lastName: e.lastName ?? '', + imageUrl: e.imageUrl, + ); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: GestureDetector( + onTap: () => widget.onTapUser.call(e.id!), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Avatar( + user: user, + ), + Text( + user.firstName!, + ), + ], + ), + ), + ); + }), + ], + ], + ) + : const Center(child: CircularProgressIndicator()), + ); + }, + ); + } +} diff --git a/packages/flutter_chat_view/lib/src/screens/chat_screen.dart b/packages/flutter_chat_view/lib/src/screens/chat_screen.dart new file mode 100644 index 0000000..192a2da --- /dev/null +++ b/packages/flutter_chat_view/lib/src/screens/chat_screen.dart @@ -0,0 +1,312 @@ +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +// ignore_for_file: lines_longer_than_80_chars + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_chat_view/flutter_chat_view.dart'; +import 'package:flutter_chat_view/src/services/date_formatter.dart'; + +class ChatScreen extends StatefulWidget { + const ChatScreen({ + required this.options, + required this.onPressStartChat, + required this.onPressChat, + required this.onDeleteChat, + required this.service, + this.onNoChats, + this.deleteChatDialog, + this.translations = const ChatTranslations(), + this.disableDismissForPermanentChats = false, + super.key, + }); + + final ChatOptions options; + final ChatTranslations translations; + final ChatService service; + final Function()? onPressStartChat; + final Function()? onNoChats; + final void Function(ChatModel chat) onDeleteChat; + final void Function(ChatModel chat) onPressChat; + + /// Disable the swipe to dismiss feature for chats that are not deletable + final bool disableDismissForPermanentChats; + + /// Method to optionally change the bottomsheetdialog + final Future Function(BuildContext, ChatModel)? deleteChatDialog; + @override + State createState() => _ChatScreenState(); +} + +class _ChatScreenState extends State { + final DateFormatter _dateFormatter = DateFormatter(); + bool _hasCalledOnNoChats = false; + ScrollController controller = ScrollController(); + bool showIndicator = false; + Stream>? chats; + List deletedChats = []; + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var translations = widget.translations; + return widget.options.scaffoldBuilder( + AppBar( + title: Text(translations.chatsTitle), + centerTitle: true, + actions: [ + StreamBuilder( + stream: + widget.service.chatOverviewService.getUnreadChatsCountStream(), + builder: (BuildContext context, snapshot) => Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.only(right: 22.0), + child: Text( + '${snapshot.data ?? 0} ${translations.chatsUnread}', + style: const TextStyle( + color: Color(0xFFBBBBBB), + fontSize: 14, + ), + ), + ), + ), + ), + ], + ), + Column( + children: [ + Expanded( + child: ListView( + controller: controller, + physics: const AlwaysScrollableScrollPhysics(), + padding: const EdgeInsets.only(top: 15.0), + children: [ + StreamBuilder>( + stream: widget.service.chatOverviewService.getChatsStream(), + builder: (BuildContext context, snapshot) { + // if the stream is done, empty and noChats is set we should call that + if (snapshot.connectionState == ConnectionState.done && + (snapshot.data?.isEmpty ?? true)) { + if (widget.onNoChats != null && !_hasCalledOnNoChats) { + _hasCalledOnNoChats = true; // Set the flag to true + WidgetsBinding.instance.addPostFrameCallback((_) async { + await widget.onNoChats!.call(); + }); + } + } else { + _hasCalledOnNoChats = + false; // Reset the flag if there are chats + } + return Column( + children: [ + for (ChatModel chat in (snapshot.data ?? []).where( + (chat) => !deletedChats.contains(chat.id), + )) ...[ + Builder( + builder: (context) => !(widget + .disableDismissForPermanentChats && + !chat.canBeDeleted) + ? Dismissible( + confirmDismiss: (_) async => + widget.deleteChatDialog + ?.call(context, chat) ?? + showModalBottomSheet( + context: context, + builder: (BuildContext context) => + Container( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + chat.canBeDeleted + ? translations + .deleteChatModalTitle + : translations + .chatCantBeDeleted, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + if (chat.canBeDeleted) + Text( + translations + .deleteChatModalDescription, + style: const TextStyle( + fontSize: 16, + ), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + TextButton( + child: Text( + translations + .deleteChatModalCancel, + style: const TextStyle( + fontSize: 16, + ), + ), + onPressed: () => + Navigator.of( + context, + ).pop(false), + ), + if (chat.canBeDeleted) + ElevatedButton( + onPressed: () => + Navigator.of( + context, + ).pop(true), + child: Text( + translations + .deleteChatModalConfirm, + style: + const TextStyle( + fontSize: 16, + ), + ), + ), + ], + ), + ], + ), + ), + ), + onDismissed: (_) { + setState(() { + deletedChats.add(chat.id!); + }); + widget.onDeleteChat(chat); + }, + background: Container( + color: Colors.red, + child: Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + translations.deleteChatButton, + ), + ), + ), + ), + key: ValueKey( + chat.id.toString(), + ), + child: ChatListItem( + widget: widget, + chat: chat, + translations: translations, + dateFormatter: _dateFormatter, + ), + ) + : ChatListItem( + widget: widget, + chat: chat, + translations: translations, + dateFormatter: _dateFormatter, + ), + ), + ], + ], + ); + }, + ), + ], + ), + ), + if (widget.onPressStartChat != null) + widget.options.newChatButtonBuilder( + context, + () async { + await widget.onPressStartChat!.call(); + }, + translations, + ), + ], + ), + ); + } +} + +class ChatListItem extends StatelessWidget { + const ChatListItem({ + required this.widget, + required this.chat, + required this.translations, + required DateFormatter dateFormatter, + super.key, + }) : _dateFormatter = dateFormatter; + + final ChatScreen widget; + final ChatModel chat; + final ChatTranslations translations; + final DateFormatter _dateFormatter; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => widget.onPressChat(chat), + child: Container( + color: Colors.transparent, + child: widget.options.chatRowContainerBuilder( + (chat is PersonalChatModel) + ? ChatRow( + unreadMessages: chat.unreadMessages ?? 0, + avatar: widget.options.userAvatarBuilder( + (chat as PersonalChatModel).user, + 40.0, + ), + title: (chat as PersonalChatModel).user.fullName ?? + translations.anonymousUser, + subTitle: chat.lastMessage != null + ? chat.lastMessage is ChatTextMessageModel + ? (chat.lastMessage! as ChatTextMessageModel).text + : '📷 ' + '${translations.image}' + : '', + lastUsed: chat.lastUsed != null + ? _dateFormatter.format( + date: chat.lastUsed!, + ) + : null, + ) + : ChatRow( + title: (chat as GroupChatModel).title, + unreadMessages: chat.unreadMessages ?? 0, + subTitle: chat.lastMessage != null + ? chat.lastMessage is ChatTextMessageModel + ? (chat.lastMessage! as ChatTextMessageModel).text + : '📷 ' + '${translations.image}' + : '', + avatar: widget.options.groupAvatarBuilder( + (chat as GroupChatModel).title, + (chat as GroupChatModel).imageUrl, + 40.0, + ), + lastUsed: chat.lastUsed != null + ? _dateFormatter.format( + date: chat.lastUsed!, + ) + : null, + ), + ), + ), + ); + } +} diff --git a/packages/flutter_community_chat_view/lib/src/screens/new_chat_screen.dart b/packages/flutter_chat_view/lib/src/screens/new_chat_screen.dart similarity index 94% rename from packages/flutter_community_chat_view/lib/src/screens/new_chat_screen.dart rename to packages/flutter_chat_view/lib/src/screens/new_chat_screen.dart index 80f0610..b18395a 100644 --- a/packages/flutter_community_chat_view/lib/src/screens/new_chat_screen.dart +++ b/packages/flutter_chat_view/lib/src/screens/new_chat_screen.dart @@ -3,14 +3,13 @@ // SPDX-License-Identifier: BSD-3-Clause import 'package:flutter/material.dart'; -import 'package:flutter_community_chat_view/flutter_community_chat_view.dart'; +import 'package:flutter_chat_view/flutter_chat_view.dart'; class NewChatScreen extends StatefulWidget { const NewChatScreen({ required this.options, required this.onPressCreateChat, required this.service, - required this.userService, this.translations = const ChatTranslations(), super.key, }); @@ -18,7 +17,6 @@ class NewChatScreen extends StatefulWidget { final ChatOptions options; final ChatTranslations translations; final ChatService service; - final ChatUserService userService; final Function(ChatUserModel) onPressCreateChat; @override @@ -40,7 +38,7 @@ class _NewChatScreenState extends State { ], ), body: FutureBuilder>( - future: widget.userService.getAllUsers(), + future: widget.service.chatUserService.getAllUsers(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); diff --git a/packages/flutter_community_chat_view/lib/src/services/date_formatter.dart b/packages/flutter_chat_view/lib/src/services/date_formatter.dart similarity index 100% rename from packages/flutter_community_chat_view/lib/src/services/date_formatter.dart rename to packages/flutter_chat_view/lib/src/services/date_formatter.dart diff --git a/packages/flutter_chat_view/lib/src/services/profile_service.dart b/packages/flutter_chat_view/lib/src/services/profile_service.dart new file mode 100644 index 0000000..644e10a --- /dev/null +++ b/packages/flutter_chat_view/lib/src/services/profile_service.dart @@ -0,0 +1,24 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_profile/flutter_profile.dart'; + +class ChatProfileService extends ProfileService { + @override + FutureOr editProfile(User user, String key, String? value) { + throw UnimplementedError(); + } + + @override + FutureOr pageBottomAction() { + throw UnimplementedError(); + } + + @override + FutureOr uploadImage( + BuildContext context, { + required Function(bool isUploading) onUploadStateChanged, + }) { + throw UnimplementedError(); + } +} diff --git a/packages/flutter_community_chat_view/pubspec.yaml b/packages/flutter_chat_view/pubspec.yaml similarity index 66% rename from packages/flutter_community_chat_view/pubspec.yaml rename to packages/flutter_chat_view/pubspec.yaml index ba3f64c..68ac749 100644 --- a/packages/flutter_community_chat_view/pubspec.yaml +++ b/packages/flutter_chat_view/pubspec.yaml @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -name: flutter_community_chat_view +name: flutter_chat_view description: A standard flutter package. version: 1.0.0 @@ -16,16 +16,20 @@ dependencies: flutter: sdk: flutter intl: any - flutter_community_chat_interface: + flutter_chat_interface: git: - url: https://github.com/Iconica-Development/flutter_community_chat - path: packages/flutter_community_chat_interface + url: https://github.com/Iconica-Development/flutter_chat + path: packages/flutter_chat_interface ref: 1.0.0 cached_network_image: ^3.2.2 flutter_image_picker: git: url: https://github.com/Iconica-Development/flutter_image_picker ref: 1.0.4 + flutter_profile: + git: + ref: 1.1.5 + url: https://github.com/Iconica-Development/flutter_profile dev_dependencies: flutter_test: diff --git a/packages/flutter_community_chat_view/test/flutter_community_chat_test.dart b/packages/flutter_chat_view/test/flutter_community_chat_test.dart similarity index 100% rename from packages/flutter_community_chat_view/test/flutter_community_chat_test.dart rename to packages/flutter_chat_view/test/flutter_community_chat_test.dart diff --git a/packages/flutter_community_chat/lib/flutter_community_chat.dart b/packages/flutter_community_chat/lib/flutter_community_chat.dart deleted file mode 100644 index 98b5f46..0000000 --- a/packages/flutter_community_chat/lib/flutter_community_chat.dart +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -library flutter_community_chat; - -export 'package:flutter_community_chat_view/flutter_community_chat_view.dart'; -export 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; -export 'package:flutter_community_chat/src/routes.dart'; -export 'package:flutter_community_chat/src/models/community_chat_configuration.dart'; -export 'package:flutter_community_chat/src/flutter_community_chat_userstory.dart'; 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 deleted file mode 100644 index 4f314b8..0000000 --- a/packages/flutter_community_chat_firebase/lib/flutter_community_chat_firebase.dart +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -library flutter_community_chat_firebase; - -export 'package:flutter_community_chat_firebase/service/service.dart'; diff --git a/packages/flutter_community_chat_firebase/lib/service/service.dart b/packages/flutter_community_chat_firebase/lib/service/service.dart deleted file mode 100644 index 065193e..0000000 --- a/packages/flutter_community_chat_firebase/lib/service/service.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'package:flutter_community_chat_firebase/service/firebase_user_service.dart'; -export 'package:flutter_community_chat_firebase/service/firebase_message_service.dart'; -export 'package:flutter_community_chat_firebase/service/firebase_chat_service.dart'; 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 deleted file mode 100644 index bb7b9d7..0000000 --- a/packages/flutter_community_chat_interface/lib/flutter_community_chat_interface.dart +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -library flutter_community_chat_interface; - -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/model/chat.dart b/packages/flutter_community_chat_interface/lib/src/model/chat.dart deleted file mode 100644 index 7ea2761..0000000 --- a/packages/flutter_community_chat_interface/lib/src/model/chat.dart +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -import 'package:flutter_community_chat_interface/src/model/chat_message.dart'; - -abstract class ChatModel { - ChatModel({ - this.id, - this.messages = const [], - this.unreadMessages, - this.lastUsed, - this.lastMessage, - this.canBeDeleted = true, - }); - - String? id; - List? messages; - int? unreadMessages; - DateTime? lastUsed; - ChatMessageModel? lastMessage; - bool canBeDeleted; -} diff --git a/packages/flutter_community_chat_interface/lib/src/model/chat_image_message.dart b/packages/flutter_community_chat_interface/lib/src/model/chat_image_message.dart deleted file mode 100644 index 6f5b207..0000000 --- a/packages/flutter_community_chat_interface/lib/src/model/chat_image_message.dart +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -import 'package:flutter_community_chat_interface/src/model/chat_message.dart'; - -class ChatImageMessageModel extends ChatMessageModel { - ChatImageMessageModel({ - required super.sender, - required super.timestamp, - required this.imageUrl, - }); - - final String imageUrl; -} diff --git a/packages/flutter_community_chat_interface/lib/src/model/chat_message.dart b/packages/flutter_community_chat_interface/lib/src/model/chat_message.dart deleted file mode 100644 index 05fbba0..0000000 --- a/packages/flutter_community_chat_interface/lib/src/model/chat_message.dart +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -import 'package:flutter_community_chat_interface/src/model/chat_user.dart'; - -abstract class ChatMessageModel { - const ChatMessageModel({ - required this.sender, - required this.timestamp, - }); - - final ChatUserModel sender; - final DateTime timestamp; -} diff --git a/packages/flutter_community_chat_interface/lib/src/model/chat_text_message.dart b/packages/flutter_community_chat_interface/lib/src/model/chat_text_message.dart deleted file mode 100644 index 5eb2af3..0000000 --- a/packages/flutter_community_chat_interface/lib/src/model/chat_text_message.dart +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -import 'package:flutter_community_chat_interface/src/model/chat_message.dart'; - -class ChatTextMessageModel extends ChatMessageModel { - ChatTextMessageModel({ - required super.sender, - required super.timestamp, - required this.text, - }); - - final String text; -} diff --git a/packages/flutter_community_chat_interface/lib/src/model/group_chat.dart b/packages/flutter_community_chat_interface/lib/src/model/group_chat.dart deleted file mode 100644 index 3caeca5..0000000 --- a/packages/flutter_community_chat_interface/lib/src/model/group_chat.dart +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; - -class GroupChatModel extends ChatModel { - GroupChatModel({ - required this.title, - required this.imageUrl, - required this.users, - super.id, - super.messages, - super.lastUsed, - super.lastMessage, - super.unreadMessages, - super.canBeDeleted, - }); - - final String title; - final String imageUrl; - final List users; - - GroupChatModel copyWith({ - String? id, - List? messages, - int? unreadMessages, - DateTime? lastUsed, - ChatMessageModel? lastMessage, - String? title, - String? imageUrl, - List? users, - bool? canBeDeleted, - }) => - GroupChatModel( - id: id ?? this.id, - messages: messages ?? this.messages, - unreadMessages: unreadMessages ?? this.unreadMessages, - lastUsed: lastUsed ?? this.lastUsed, - lastMessage: lastMessage ?? this.lastMessage, - title: title ?? this.title, - imageUrl: imageUrl ?? this.imageUrl, - users: users ?? this.users, - canBeDeleted: canBeDeleted ?? this.canBeDeleted, - ); -} diff --git a/packages/flutter_community_chat_interface/lib/src/model/personal_chat.dart b/packages/flutter_community_chat_interface/lib/src/model/personal_chat.dart deleted file mode 100644 index 28dd123..0000000 --- a/packages/flutter_community_chat_interface/lib/src/model/personal_chat.dart +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart'; - -class PersonalChatModel extends ChatModel { - PersonalChatModel({ - required this.user, - super.id, - super.messages, - super.unreadMessages, - super.lastUsed, - super.lastMessage, - super.canBeDeleted, - }); - - final ChatUserModel user; - - PersonalChatModel copyWith({ - String? id, - List? messages, - int? unreadMessages, - DateTime? lastUsed, - ChatMessageModel? lastMessage, - ChatUserModel? user, - bool? canBeDeleted, - }) => - PersonalChatModel( - id: id ?? this.id, - messages: messages ?? this.messages, - unreadMessages: unreadMessages ?? this.unreadMessages, - lastUsed: lastUsed ?? this.lastUsed, - lastMessage: lastMessage ?? this.lastMessage, - user: user ?? this.user, - canBeDeleted: canBeDeleted ?? this.canBeDeleted, - ); -} diff --git a/packages/flutter_community_chat_interface/lib/src/service/service.dart b/packages/flutter_community_chat_interface/lib/src/service/service.dart deleted file mode 100644 index 8043b14..0000000 --- a/packages/flutter_community_chat_interface/lib/src/service/service.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'chat_service.dart'; -export 'user_service.dart'; -export 'message_service.dart'; 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 deleted file mode 100644 index 524c1b2..0000000 --- a/packages/flutter_community_chat_view/lib/src/screens/chat_screen.dart +++ /dev/null @@ -1,367 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -// ignore_for_file: lines_longer_than_80_chars - -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter_community_chat_view/flutter_community_chat_view.dart'; -import 'package:flutter_community_chat_view/src/services/date_formatter.dart'; - -class ChatScreen extends StatefulWidget { - const ChatScreen({ - required this.options, - required this.onPressStartChat, - required this.onPressChat, - required this.onDeleteChat, - required this.service, - required this.pageSize, - this.onNoChats, - this.deleteChatDialog, - this.translations = const ChatTranslations(), - this.disableDismissForPermanentChats = false, - super.key, - }); - - final ChatOptions options; - final ChatTranslations translations; - final ChatService service; - final Function()? onPressStartChat; - final Function()? onNoChats; - final void Function(ChatModel chat) onDeleteChat; - final void Function(ChatModel chat) onPressChat; - final int pageSize; - - /// Disable the swipe to dismiss feature for chats that are not deletable - final bool disableDismissForPermanentChats; - - /// Method to optionally change the bottomsheetdialog - final Future Function(BuildContext, ChatModel)? deleteChatDialog; - @override - State createState() => _ChatScreenState(); -} - -class _ChatScreenState extends State { - final DateFormatter _dateFormatter = DateFormatter(); - bool _hasCalledOnNoChats = false; - ScrollController controller = ScrollController(); - bool showIndicator = false; - Stream>? chats; - List deletedChats = []; - - @override - void initState() { - getChats(); - super.initState(); - } - - @override - void dispose() { - controller.dispose(); - super.dispose(); - } - - void getChats() { - setState(() { - chats = widget.service.getChatsStream(widget.pageSize); - }); - } - - @override - Widget build(BuildContext context) { - var translations = widget.translations; - return widget.options.scaffoldBuilder( - AppBar( - title: Text(translations.chatsTitle), - centerTitle: true, - actions: [ - StreamBuilder( - stream: widget.service.getUnreadChatsCountStream(), - builder: (BuildContext context, snapshot) => Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.only(right: 22.0), - child: Text( - '${snapshot.data ?? 0} ${translations.chatsUnread}', - style: const TextStyle( - color: Color(0xFFBBBBBB), - fontSize: 14, - ), - ), - ), - ), - ), - ], - ), - Column( - children: [ - Expanded( - child: Listener( - onPointerMove: (event) { - var isTop = controller.position.pixels == - controller.position.maxScrollExtent; - - if (showIndicator == false && - !isTop && - controller.position.userScrollDirection == - ScrollDirection.reverse) { - setState(() { - showIndicator = true; - }); - getChats(); - Future.delayed(const Duration(seconds: 2), () { - if (mounted) { - setState(() { - showIndicator = false; - }); - } - }); - } - }, - child: ListView( - controller: controller, - physics: const AlwaysScrollableScrollPhysics(), - padding: const EdgeInsets.only(top: 15.0), - children: [ - StreamBuilder>( - stream: chats, - builder: (BuildContext context, snapshot) { - // if the stream is done, empty and noChats is set we should call that - if (snapshot.connectionState == ConnectionState.done && - (snapshot.data?.isEmpty ?? true)) { - if (widget.onNoChats != null && !_hasCalledOnNoChats) { - _hasCalledOnNoChats = true; // Set the flag to true - WidgetsBinding.instance - .addPostFrameCallback((_) async { - await widget.onNoChats!.call(); - getChats(); - }); - } - } else { - _hasCalledOnNoChats = - false; // Reset the flag if there are chats - } - return Column( - children: [ - for (ChatModel chat in (snapshot.data ?? []).where( - (chat) => !deletedChats.contains(chat.id), - )) ...[ - Builder( - builder: (context) => !(widget - .disableDismissForPermanentChats && - !chat.canBeDeleted) - ? Dismissible( - confirmDismiss: (_) async => - widget.deleteChatDialog - ?.call(context, chat) ?? - showModalBottomSheet( - context: context, - builder: (BuildContext context) => - Container( - padding: - const EdgeInsets.all(16.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - chat.canBeDeleted - ? translations - .deleteChatModalTitle - : translations - .chatCantBeDeleted, - style: const TextStyle( - fontSize: 20, - fontWeight: - FontWeight.bold, - ), - ), - const SizedBox(height: 16), - if (chat.canBeDeleted) - Text( - translations - .deleteChatModalDescription, - style: const TextStyle( - fontSize: 16, - ), - ), - const SizedBox(height: 16), - Row( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - TextButton( - child: Text( - translations - .deleteChatModalCancel, - style: - const TextStyle( - fontSize: 16, - ), - ), - onPressed: () => - Navigator.of( - context, - ).pop(false), - ), - if (chat.canBeDeleted) - ElevatedButton( - onPressed: () => - Navigator.of( - context, - ).pop(true), - child: Text( - translations - .deleteChatModalConfirm, - style: - const TextStyle( - fontSize: 16, - ), - ), - ), - ], - ), - ], - ), - ), - ), - onDismissed: (_) { - setState(() { - deletedChats.add(chat.id!); - }); - widget.onDeleteChat(chat); - }, - background: Container( - color: Colors.red, - child: Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - translations.deleteChatButton, - ), - ), - ), - ), - key: ValueKey( - chat.id.toString(), - ), - child: ChatListItem( - widget: widget, - chat: chat, - translations: translations, - dateFormatter: _dateFormatter, - ), - ) - : ChatListItem( - widget: widget, - chat: chat, - translations: translations, - dateFormatter: _dateFormatter, - ), - ), - ], - if (showIndicator && - snapshot.connectionState != - ConnectionState.done) ...[ - const SizedBox( - height: 10, - ), - const CircularProgressIndicator(), - const SizedBox( - height: 10, - ), - ], - ], - ); - }, - ), - ], - ), - ), - ), - if (widget.onPressStartChat != null) - widget.options.newChatButtonBuilder( - context, - () async { - await widget.onPressStartChat!.call(); - getChats(); - }, - translations, - ), - ], - ), - ); - } -} - -class ChatListItem extends StatelessWidget { - const ChatListItem({ - required this.widget, - required this.chat, - required this.translations, - required DateFormatter dateFormatter, - super.key, - }) : _dateFormatter = dateFormatter; - - final ChatScreen widget; - final ChatModel chat; - final ChatTranslations translations; - final DateFormatter _dateFormatter; - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () => widget.onPressChat(chat), - child: Container( - color: Colors.transparent, - child: widget.options.chatRowContainerBuilder( - (chat is PersonalChatModel) - ? ChatRow( - unreadMessages: chat.unreadMessages ?? 0, - avatar: widget.options.userAvatarBuilder( - (chat as PersonalChatModel).user, - 40.0, - ), - title: (chat as PersonalChatModel).user.fullName ?? - translations.anonymousUser, - subTitle: chat.lastMessage != null - ? chat.lastMessage is ChatTextMessageModel - ? (chat.lastMessage! as ChatTextMessageModel).text - : '📷 ' - '${translations.image}' - : '', - lastUsed: chat.lastUsed != null - ? _dateFormatter.format( - date: chat.lastUsed!, - ) - : null, - ) - : ChatRow( - title: (chat as GroupChatModel).title, - unreadMessages: chat.unreadMessages ?? 0, - subTitle: chat.lastMessage != null - ? chat.lastMessage is ChatTextMessageModel - ? (chat.lastMessage! as ChatTextMessageModel).text - : '📷 ' - '${translations.image}' - : '', - avatar: widget.options.groupAvatarBuilder( - (chat as GroupChatModel).title, - (chat as GroupChatModel).imageUrl, - 40.0, - ), - lastUsed: chat.lastUsed != null - ? _dateFormatter.format( - date: chat.lastUsed!, - ) - : null, - ), - ), - ), - ); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index fe778f0..ace4fbe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ -name: flutter_community_chat_workspace +name: flutter_chat_workspace environment: - sdk: '>=3.1.0 <4.0.0' + sdk: ">=3.1.0 <4.0.0" dev_dependencies: melos: ^3.0.1