diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml index b0f4ec9..eaad060 100644 --- a/.github/workflows/super-linter.yml +++ b/.github/workflows/super-linter.yml @@ -2,17 +2,17 @@ name: CI - Check code & run tests on: push jobs: - test: - name: Check the source code - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: subosito/flutter-action@v2 - with: - flutter-version: "3.0.5" - - name: Install packages - run: flutter pub get - - name: Linter - run: flutter analyze - - name: Test - run: flutter test + test: + name: Check the source code + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: subosito/flutter-action@v2 + with: + flutter-version: "3.3.0" + - name: Install packages + run: flutter pub get + - name: Linter + run: flutter analyze + - name: Test + run: flutter test diff --git a/lib/api/firebase_message.dart b/lib/api/firebase_message.dart index 2f61bfc..6c246f3 100644 --- a/lib/api/firebase_message.dart +++ b/lib/api/firebase_message.dart @@ -5,6 +5,7 @@ import 'package:pdg_app/model/message.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'firebase_api.dart'; +import 'package:async/async.dart' show StreamGroup; class FirebaseMessage extends FirebaseAPI implements IMessage { FirebaseMessage(FirebaseFirestore db) : super(db, 'message'); @@ -69,18 +70,34 @@ class FirebaseMessage extends FirebaseAPI implements IMessage { return messages; } - Stream followConversation(String firstId, String secondId) { - List userIds = [firstId, secondId]; - final Stream msgStream = collectionReference - .where('fromId', whereIn: userIds) - .where('toId', whereIn: userIds) + @override + Stream followConversation(String firstId, String secondId) { + final Stream> msgStream1 = collectionReference + .where('fromId', isEqualTo: firstId) + .where('toId', isEqualTo: secondId) + .orderBy('time', descending: false) + .limitToLast(1) .withConverter( fromFirestore: Message.fromFirestore, toFirestore: (Message msg, _) => msg.toFirestore()) .snapshots(); + final Stream> msgStream2 = collectionReference + .where('fromId', isEqualTo: secondId) + .where('toId', isEqualTo: firstId) + .orderBy('time', descending: false) + .limitToLast(1) + .withConverter( + fromFirestore: Message.fromFirestore, + toFirestore: (Message msg, _) => msg.toFirestore()) + .snapshots(); + + final msgStream = + StreamGroup.merge>([msgStream1, msgStream2]); + return msgStream - .map((querySnapshot) => querySnapshot.docs.map((doc) => doc.data())) + .map((querySnapshot) => querySnapshot.docs) + .map((doc) => doc.isNotEmpty ? doc.first.data() : null) .cast(); } diff --git a/lib/api/imessage.dart b/lib/api/imessage.dart index 1ce7c0e..3bae565 100644 --- a/lib/api/imessage.dart +++ b/lib/api/imessage.dart @@ -6,4 +6,5 @@ abstract class IMessage { Future?> readConversation(String firstId, String secondId); void updateMessage(Message message); void deleteMessage(String messageId); + Stream followConversation(String firstId, String secondId); } diff --git a/lib/provider/chat_provider.dart b/lib/provider/chat_provider.dart index ebbe4de..a0357f6 100644 --- a/lib/provider/chat_provider.dart +++ b/lib/provider/chat_provider.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:collection'; import 'dart:developer'; @@ -20,6 +21,7 @@ class ChatProvider extends ChangeNotifier { final AuthProvider _auth; final IMessage _messageApi = FirebaseMessage(FirebaseFirestore.instance); final IUser _userApi = FirebaseUser(FirebaseFirestore.instance); + final List> subcriptions = []; /// The list of messages using a [LinkedHashMap] to keep the order of the messages. final Map> _messages = {}; @@ -69,13 +71,42 @@ class ChatProvider extends ChangeNotifier { int index = 0; for (final conv in futureResult) { final sortedList = - SortedList((a, b) => a.time.compareTo(b.time)); + SortedList((a, b) => b.time.compareTo(a.time)); sortedList.addAll(conv!); _messages[users[index]] = sortedList; index++; } - log(_messages.toString()); + notifyListeners(); + } + + Future sendMessage(Message message) async { + _messageApi.createMessage(message); + } + + startNewMessageListener() { + stopNewMessageListener(); + for (final user in _messages.keys) { + final currentUserUid = _auth.userUid; + + final subscription = _messageApi + .followConversation(currentUserUid, user.uid) + .listen((event) { + log('FOLLOW: $event'); + if (event != null) { + _messages[user]!.add(event); + } + + notifyListeners(); + }); + subcriptions.add(subscription); + } + } + + stopNewMessageListener() { + for (final subscription in subcriptions) { + subscription.cancel(); + } } } diff --git a/lib/router/chat_router_page.dart b/lib/router/chat_router_page.dart index 7b35743..8309167 100644 --- a/lib/router/chat_router_page.dart +++ b/lib/router/chat_router_page.dart @@ -14,7 +14,11 @@ class ChatRouterPage extends StatelessWidget { create: (context) => ChatProvider(GetIt.I.get()), builder: (context, child) { return FutureBuilder( - future: context.read().fetchAllMessages(), + future: () async { + final chatProvider = context.read(); + await chatProvider.fetchAllMessages(); + await chatProvider.startNewMessageListener(); + }(), builder: (context, snapshot) => snapshot.connectionState == ConnectionState.done ? const AutoRouter() diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index 4b3dec1..89fdc3e 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -1,17 +1,20 @@ import 'dart:convert'; -import 'dart:developer'; import 'dart:math' as math; import 'package:auto_route/auto_route.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; // ignore: depend_on_referenced_packages import 'package:flutter_chat_types/flutter_chat_types.dart' as types; import 'package:flutter_chat_ui/flutter_chat_ui.dart'; import 'package:get_it/get_it.dart'; +import 'package:pdg_app/api/firebase_message.dart'; +import 'package:pdg_app/api/imessage.dart'; import 'package:pdg_app/router/router.gr.dart'; import 'package:provider/provider.dart'; import '../model/user.dart'; +import '../model/message.dart' as model; import '../provider/auth_provider.dart'; import '../provider/chat_provider.dart'; @@ -42,28 +45,23 @@ class _ChatScreenState extends State { super.initState(); } - void _addMessage(types.Message message) { - setState(() { - //_messages.insert(0, message); - }); - } - void _handleSendPressed(types.PartialText message) { - final textMessage = types.TextMessage( - author: _mainUser, - createdAt: DateTime.now().millisecondsSinceEpoch, - id: randomString(), - text: message.text, + IMessage messageApi = FirebaseMessage(FirebaseFirestore.instance); + + final newMessage = model.Message( + toId: _otherUser.uid, + fromId: _mainUser.id, + content: message.text, + time: DateTime.now(), ); - _addMessage(textMessage); + messageApi.createMessage(newMessage); } @override Widget build(BuildContext context) { final ChatProvider chatProvider = context.watch(); _otherUser = widget._otherUser ?? chatProvider.messages.keys.first; - log(chatProvider.messages.toString()); return ChatInterface( name: '${_otherUser.firstName} ${_otherUser.lastName}', currentUser: _mainUser, diff --git a/pubspec.lock b/pubspec.lock index 6dc2276..39c84d0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -23,7 +23,7 @@ packages: source: hosted version: "2.3.1" async: - dependency: transitive + dependency: "direct main" description: name: async url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index 1011942..cdb5f0b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.17.6 <3.0.0" + sdk: ">=2.17.6 <3.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -27,90 +27,91 @@ environment: # the latest version available on pub.dev. To see which dependencies have newer # versions available, run `flutter pub outdated`. dependencies: - flutter: - sdk: flutter + flutter: + sdk: flutter - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - firebase_core: ^1.21.0 - auto_route: ^5.0.1 - firebase_auth: ^3.6.4 - firebase_storage: ^10.3.6 - cloud_firestore: ^3.4.5 - firebase_database: ^9.1.2 - provider: ^6.0.3 - flutter_chat_ui: ^1.6.4 - table_calendar: ^3.0.6 - intl: ^0.17.0 - uuid: ^3.0.6 - image_picker: ^0.8.5+3 - cross_file_image: ^1.0.0 - day_night_time_picker: ^1.1.2 - google_nav_bar: ^5.0.6 - path_provider: ^2.0.11 - get_it: ^7.2.0 - firebase_auth_mocks: ^0.8.5+1 - firebase_storage_mocks: ^0.5.1 - file_picker: ^3.0.4 - file: ^6.1.4 - awesome_snackbar_content: ^0.0.8 - sorted_list: ^1.0.0 + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + firebase_core: ^1.21.0 + auto_route: ^5.0.1 + firebase_auth: ^3.6.4 + firebase_storage: ^10.3.6 + cloud_firestore: ^3.4.5 + firebase_database: ^9.1.2 + provider: ^6.0.3 + flutter_chat_ui: ^1.6.4 + table_calendar: ^3.0.6 + intl: ^0.17.0 + uuid: ^3.0.6 + image_picker: ^0.8.5+3 + cross_file_image: ^1.0.0 + day_night_time_picker: ^1.1.2 + google_nav_bar: ^5.0.6 + path_provider: ^2.0.11 + get_it: ^7.2.0 + firebase_auth_mocks: ^0.8.5+1 + firebase_storage_mocks: ^0.5.1 + file_picker: ^3.0.4 + file: ^6.1.4 + awesome_snackbar_content: ^0.0.8 + sorted_list: ^1.0.0 + async: ^2.9.0 dev_dependencies: - flutter_test: - sdk: flutter + flutter_test: + sdk: flutter - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^2.0.0 - widgetbook: ^2.4.1 - auto_route_generator: ^5.0.1 - build_runner: - fake_cloud_firestore: ^1.3.1 + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + widgetbook: ^2.4.1 + auto_route_generator: ^5.0.1 + build_runner: + fake_cloud_firestore: ^1.3.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - assets: - - assets/images/ + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + assets: + - assets/images/ - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages