From dbe119c977205e2dd9f3f95bce6684ec4fbfa161 Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 00:34:21 +0200 Subject: [PATCH 01/21] feat(post-feed): add field to post details --- lib/models/ui/post_details.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/models/ui/post_details.dart b/lib/models/ui/post_details.dart index 8b2e2ac8..eaa65b72 100644 --- a/lib/models/ui/post_details.dart +++ b/lib/models/ui/post_details.dart @@ -19,6 +19,7 @@ class PostDetails { final DateTime publicationDate; final int distance; // in meters final bool isChallenge; + final bool hasUserCommented; const PostDetails({ required this.postId, @@ -32,6 +33,7 @@ class PostDetails { required this.publicationDate, required this.distance, this.isChallenge = false, + this.hasUserCommented = false, }); @override @@ -49,7 +51,8 @@ class PostDetails { other.ownerCentauriPoints == ownerCentauriPoints && other.publicationDate == publicationDate && other.distance == distance && - other.isChallenge == isChallenge; + other.isChallenge == isChallenge && + other.hasUserCommented == hasUserCommented; } @override @@ -66,6 +69,7 @@ class PostDetails { publicationDate, distance, isChallenge, + hasUserCommented, ); } @@ -77,6 +81,7 @@ class PostDetails { UserFirestore userFirestore, GeoFirePoint geoFirePoint, bool isChallenge, + bool hasUserCommented, ) { return PostDetails( postId: postFirestore.id, @@ -94,6 +99,7 @@ class PostDetails { 1000) .round(), isChallenge: isChallenge, + hasUserCommented: hasUserCommented, ); } } From 7d5397f2d569afa1a4aabac2d78fec99980d8580 Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 00:35:15 +0200 Subject: [PATCH 02/21] test(post-feed): update hash test of post details --- test/models/ui/post_details_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/test/models/ui/post_details_test.dart b/test/models/ui/post_details_test.dart index 4da7267c..25561a76 100644 --- a/test/models/ui/post_details_test.dart +++ b/test/models/ui/post_details_test.dart @@ -19,6 +19,7 @@ void main() { postDetails.publicationDate, postDetails.distance, postDetails.isChallenge, + postDetails.hasUserCommented, ); final actualHash = postDetails.hashCode; From 1004f8c10281716f85ac557317d0de3d2d123082 Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 00:36:48 +0200 Subject: [PATCH 03/21] fix(post-feed): set to false in map popup --- lib/viewmodels/map/map_pin_view_model.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/viewmodels/map/map_pin_view_model.dart b/lib/viewmodels/map/map_pin_view_model.dart index 430c35ed..42da7e02 100644 --- a/lib/viewmodels/map/map_pin_view_model.dart +++ b/lib/viewmodels/map/map_pin_view_model.dart @@ -77,6 +77,7 @@ class MapPinViewModel extends AutoDisposeAsyncNotifier> { users[index], GeoFirePoint(position), false, + false, ), ), ), From fcbae2886c64035eb0220daaca38ad1764ea582e Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 00:41:02 +0200 Subject: [PATCH 04/21] feat(post-feed): add method to know is a user has commented under a post --- .../comment/comment_repository_service.dart | 8 ++++++++ .../user_comment_repository_service.dart | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/services/database/comment/comment_repository_service.dart b/lib/services/database/comment/comment_repository_service.dart index 745236f9..0455502d 100644 --- a/lib/services/database/comment/comment_repository_service.dart +++ b/lib/services/database/comment/comment_repository_service.dart @@ -117,6 +117,14 @@ class CommentRepositoryService { // The post comments are deleted in a batch await _postCommentRepo.deleteAllComments(parentPostId, batch); } + + /// Check if the user with id [userId] has commented at least once + /// under the post with id [parentPostId]. + Future hasUserCommentedUnderPost( + UserIdFirestore userId, + PostIdFirestore parentPostId, + ) => + _userCommentRepo.hasUserCommentedUnderPost(userId, parentPostId); } final commentRepositoryServiceProvider = Provider( diff --git a/lib/services/database/comment/user_comment_repository_service.dart b/lib/services/database/comment/user_comment_repository_service.dart index 375374ed..79deaf05 100644 --- a/lib/services/database/comment/user_comment_repository_service.dart +++ b/lib/services/database/comment/user_comment_repository_service.dart @@ -1,7 +1,9 @@ import "package:cloud_firestore/cloud_firestore.dart"; import "package:proxima/models/database/comment/comment_id_firestore.dart"; +import "package:proxima/models/database/post/post_id_firestore.dart"; import "package:proxima/models/database/user/user_firestore.dart"; import "package:proxima/models/database/user/user_id_firestore.dart"; +import "package:proxima/models/database/user_comment/user_comment_data.dart"; import "package:proxima/models/database/user_comment/user_comment_firestore.dart"; /// This class is responsible for managing the user's comments in the firestore database. @@ -58,4 +60,20 @@ class UserCommentRepositoryService { batch.delete(commentToDelete); } + + /// Check if the user with id [userId] has commented at least once + /// under the post with id [parentPostId]. + Future hasUserCommentedUnderPost( + UserIdFirestore userId, + PostIdFirestore parentPostId, + ) async { + final userCommentCollection = _userCommentCollection(userId); + + final userCommentQuery = await userCommentCollection + .where(UserCommentData.parentPostIdField, isEqualTo: parentPostId.value) + .limit(1) + .get(); + + return userCommentQuery.docs.isNotEmpty; + } } From 0e335119807880c411051e6e00e8e1f863f68268 Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 00:42:01 +0200 Subject: [PATCH 05/21] test(post-feed): update comment repository mock --- .../services/mock_comment_repository_service.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/mocks/services/mock_comment_repository_service.dart b/test/mocks/services/mock_comment_repository_service.dart index ab071740..7cb291a8 100644 --- a/test/mocks/services/mock_comment_repository_service.dart +++ b/test/mocks/services/mock_comment_repository_service.dart @@ -63,4 +63,15 @@ class MockCommentRepositoryService extends Mock returnValue: Future.value(), ); } + + @override + Future hasUserCommentedUnderPost( + UserIdFirestore? userId, + PostIdFirestore? parentPostId, + ) { + return super.noSuchMethod( + Invocation.method(#hasUserCommentedUnderPost, [userId, parentPostId]), + returnValue: Future.value(false), + ); + } } From 8c03ec2b860fb5730c38214b91c2142731553b51 Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 00:42:55 +0200 Subject: [PATCH 06/21] test(post-feed): add test for the method --- .../comment/comment_repository_test.dart | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/services/database/comment/comment_repository_test.dart b/test/services/database/comment/comment_repository_test.dart index 993982ba..6f08286c 100644 --- a/test/services/database/comment/comment_repository_test.dart +++ b/test/services/database/comment/comment_repository_test.dart @@ -330,5 +330,36 @@ void main() { } }); }); + + group( + "getting if user has commented on post", + () { + test("should return true if the user has commented on the post", + () async { + await commentGenerator.addComment( + post.id, + user.uid, + commentRepo, + ); + + final hasUserCommented = await commentRepo.hasUserCommentedUnderPost( + user.uid, + post.id, + ); + + expect(hasUserCommented, isTrue); + }); + + test("should return false if the user has not commented on the post", + () async { + final hasUserCommented = await commentRepo.hasUserCommentedUnderPost( + user.uid, + post.id, + ); + + expect(hasUserCommented, isFalse); + }); + }, + ); }); } From c7cdc2d49e697f57f94351d0a792722e11263f4e Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 00:44:32 +0200 Subject: [PATCH 07/21] feat(post-feed): expose if the user has commented on the posts in the feed --- lib/viewmodels/posts_feed_view_model.dart | 35 ++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/lib/viewmodels/posts_feed_view_model.dart b/lib/viewmodels/posts_feed_view_model.dart index c842a57b..28d4397f 100644 --- a/lib/viewmodels/posts_feed_view_model.dart +++ b/lib/viewmodels/posts_feed_view_model.dart @@ -4,8 +4,10 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/database/challenge/challenge_firestore.dart"; import "package:proxima/models/database/post/post_firestore.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; +import "package:proxima/models/database/user/user_firestore.dart"; import "package:proxima/models/ui/post_details.dart"; import "package:proxima/services/database/challenge_repository_service.dart"; +import "package:proxima/services/database/comment/comment_repository_service.dart"; import "package:proxima/services/database/post_repository_service.dart"; import "package:proxima/services/database/user_repository_service.dart"; import "package:proxima/services/sensors/geolocation_service.dart"; @@ -28,6 +30,7 @@ class PostsFeedViewModel extends AutoDisposeAsyncNotifier> { final geoLocationService = ref.watch(geolocationServiceProvider); final postRepository = ref.watch(postRepositoryServiceProvider); final userRepository = ref.watch(userRepositoryServiceProvider); + final commentRepository = ref.watch(commentRepositoryServiceProvider); final challengeRepositoryService = ref.watch( challengeRepositoryServiceProvider, ); @@ -68,13 +71,42 @@ class PostsFeedViewModel extends AutoDisposeAsyncNotifier> { putOnTop: uncompletedChallengesId.toSet(), ); + // Check if the user has commented on the posts + final commentedPostsFuture = Future.wait( + postsFirestore.map((post) async { + final hasUserCommented = + await commentRepository.hasUserCommentedUnderPost( + ref.read(loggedInUserIdProvider)!, + post.id, + ); + + return MapEntry(post.id, hasUserCommented); + }), + ); + + // Get the owners of the posts final postOwnersId = postsFirestore.map((post) => post.data.ownerId).toSet(); - final postOwners = await Future.wait( + final postOwnersFuture = Future.wait( postOwnersId.map((userId) => userRepository.getUser(userId)), ); + // Await in parallel for optimisation + final results2 = await Future.wait([ + postOwnersFuture, + commentedPostsFuture, + ]); + + final postOwners = results2[0] as List; + + // Create a set of the post ids that the user has commented on for efficient lookup + final commentedPosts = results2[1] as List>; + final commentedPostIds = commentedPosts + .where((entry) => entry.value) + .map((entry) => entry.key) + .toSet(); + final posts = postsFirestore.map((post) { final owner = postOwners.firstWhere( (user) => user.uid == post.data.ownerId, @@ -88,6 +120,7 @@ class PostsFeedViewModel extends AutoDisposeAsyncNotifier> { owner, GeoFirePoint(position), uncompletedChallengesId.contains(post.id), + commentedPostIds.contains(post.id), ); return postDetails; From 9702385b60220e7f5214d15984814ca780d7ae5a Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 00:45:52 +0200 Subject: [PATCH 08/21] test(post-feed): mock the comment repositories for the unit tests --- test/viewmodels/posts_feed_view_model_unit_test.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/viewmodels/posts_feed_view_model_unit_test.dart b/test/viewmodels/posts_feed_view_model_unit_test.dart index 01b5b1c0..b4ca2d0d 100644 --- a/test/viewmodels/posts_feed_view_model_unit_test.dart +++ b/test/viewmodels/posts_feed_view_model_unit_test.dart @@ -6,6 +6,7 @@ import "package:proxima/models/database/post/post_data.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; import "package:proxima/models/ui/post_details.dart"; import "package:proxima/services/database/challenge_repository_service.dart"; +import "package:proxima/services/database/comment/comment_repository_service.dart"; import "package:proxima/services/database/post_repository_service.dart"; import "package:proxima/services/database/user_repository_service.dart"; import "package:proxima/services/sensors/geolocation_service.dart"; @@ -20,6 +21,7 @@ import "../mocks/data/firestore_user.dart"; import "../mocks/data/geopoint.dart"; import "../mocks/data/post_data.dart"; import "../mocks/services/mock_challenge_repository_service.dart"; +import "../mocks/services/mock_comment_repository_service.dart"; import "../mocks/services/mock_geo_location_service.dart"; import "../mocks/services/mock_post_repository_service.dart"; import "../mocks/services/mock_user_repository_service.dart"; @@ -29,6 +31,7 @@ void main() { late MockGeolocationService geoLocationService; late PostRepositoryService postRepository; late UserRepositoryService userRepository; + late MockCommentRepositoryService commentRepository; late ChallengeRepositoryService challengeRepository; late ProviderContainer container; @@ -36,6 +39,7 @@ void main() { setUp(() { geoLocationService = MockGeolocationService(); postRepository = MockPostRepositoryService(); + commentRepository = MockCommentRepositoryService(); userRepository = MockUserRepositoryService(); challengeRepository = MockChallengeRepositoryService(); @@ -47,6 +51,9 @@ void main() { postRepositoryServiceProvider.overrideWithValue( postRepository, ), + commentRepositoryServiceProvider.overrideWithValue( + commentRepository, + ), userRepositoryServiceProvider.overrideWithValue( userRepository, ), @@ -57,6 +64,10 @@ void main() { ], ); + when(commentRepository.hasUserCommentedUnderPost(any, any)).thenAnswer( + (_) async => false, + ); + when( challengeRepository.getChallenges( testingUserFirestoreId, From 44c38463c15699d32ff68e90d55d9da83276fc11 Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 00:47:39 +0200 Subject: [PATCH 09/21] test(post-feed): add test for exposing if the user has commented under a post --- ...osts_feed_view_model_integration_test.dart | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/test/viewmodels/posts_feed_view_model_integration_test.dart b/test/viewmodels/posts_feed_view_model_integration_test.dart index f4a90635..2e28bf80 100644 --- a/test/viewmodels/posts_feed_view_model_integration_test.dart +++ b/test/viewmodels/posts_feed_view_model_integration_test.dart @@ -4,7 +4,9 @@ import "package:geoflutterfire_plus/geoflutterfire_plus.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:mockito/mockito.dart"; import "package:proxima/models/database/post/post_data.dart"; +import "package:proxima/models/database/post/post_id_firestore.dart"; import "package:proxima/models/ui/post_details.dart"; +import "package:proxima/services/database/comment/comment_repository_service.dart"; import "package:proxima/services/database/firestore_service.dart"; import "package:proxima/services/database/post_repository_service.dart"; import "package:proxima/services/database/user_repository_service.dart"; @@ -13,6 +15,8 @@ import "package:proxima/viewmodels/login_view_model.dart"; import "package:proxima/viewmodels/posts_feed_view_model.dart"; import "package:test/test.dart"; +import "../mocks/data/comment_data.dart"; +import "../mocks/data/firestore_post.dart"; import "../mocks/data/firestore_user.dart"; import "../mocks/data/geopoint.dart"; import "../mocks/data/post_data.dart"; @@ -28,6 +32,10 @@ void main() { late UserRepositoryService userRepo; late PostRepositoryService postRepo; + late CommentRepositoryService commentRepo; + + late FirestorePostGenerator postGenerator; + late CommentDataGenerator commentGenerator; late ProviderContainer container; @@ -37,6 +45,8 @@ void main() { setUp(() async { fakeFireStore = FakeFirebaseFirestore(); geoLocationService = MockGeolocationService(); + postGenerator = FirestorePostGenerator(); + commentGenerator = CommentDataGenerator(); userRepo = UserRepositoryService( firestore: fakeFireStore, @@ -53,6 +63,8 @@ void main() { ], ); + commentRepo = container.read(commentRepositoryServiceProvider); + when(geoLocationService.getCurrentPosition()).thenAnswer( (_) async => userPosition, ); @@ -234,5 +246,47 @@ void main() { expect(actualPosts, unorderedEquals(expectedPosts)); }); + + test( + "Exposes correctly if the user has commented on a post", + () async { + final user = testingUserFirestore; + + // Add the user to the database + await setUserFirestore(fakeFireStore, user); + + const nbPosts = 10; + final posts = + postGenerator.createUserPosts(user.uid, userPosition0, nbPosts); + + // Add the posts to the database + await setPostsFirestore(posts, fakeFireStore); + + // Comment under half the posts + const commentEvery = 2; + final commentedPostIds = {}; + for (var i = 0; i < nbPosts; i += commentEvery) { + final commentData = + commentGenerator.createMockCommentData(ownerId: user.uid); + final post = posts[i]; + + await commentRepo.addComment( + post.id, + commentData, + ); + + commentedPostIds.add(post.id); + } + + // Get the posts + final actualPosts = + await container.read(postsFeedViewModelProvider.future); + + // Check if the user has commented on the right posts + for (final post in actualPosts) { + expect(post.hasUserCommented, commentedPostIds.contains(post.postId)); + } + }, + ); }); } From 020605b3f16ed85c3800f10d21890872edac27db Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 00:48:45 +0200 Subject: [PATCH 10/21] feat(post-feed): set the comment count blue is the user has commented on the post --- .../home/content/feed/components/comment_count.dart | 12 +++++++++--- .../home/content/feed/components/post_card.dart | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/views/pages/home/content/feed/components/comment_count.dart b/lib/views/pages/home/content/feed/components/comment_count.dart index bf5bafcf..27f5246f 100644 --- a/lib/views/pages/home/content/feed/components/comment_count.dart +++ b/lib/views/pages/home/content/feed/components/comment_count.dart @@ -4,22 +4,28 @@ import "package:flutter/material.dart"; /// It contains the comment icon and the number of comments. class CommentCount extends StatelessWidget { final int count; + final bool setBlue; const CommentCount({ super.key, required this.count, + required this.setBlue, }); @override Widget build(BuildContext context) { - const icon = Icon(Icons.comment, size: 20); + final icon = Icon( + Icons.comment, + size: 20, + color: setBlue ? Colors.blue : null, + ); final countText = Text(count.toString()); final content = Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - const Padding( - padding: EdgeInsets.all(4), + Padding( + padding: const EdgeInsets.all(4), child: icon, ), Padding( diff --git a/lib/views/pages/home/content/feed/components/post_card.dart b/lib/views/pages/home/content/feed/components/post_card.dart index da25ec41..060798bc 100644 --- a/lib/views/pages/home/content/feed/components/post_card.dart +++ b/lib/views/pages/home/content/feed/components/post_card.dart @@ -80,6 +80,7 @@ class PostCard extends ConsumerWidget { child: CommentCount( key: postCardCommentsNumberKey, count: postDetails.commentNumber, + setBlue: postDetails.hasUserCommented, ), ), ], From 1e6e67b82e9f8cc96983aa6f561adef731fc684a Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 14:37:24 +0200 Subject: [PATCH 11/21] Revert "fix(post-feed): revert commits for good integration with the comment count viewmodel" This reverts commit 020605b3f16ed85c3800f10d21890872edac27db. --- lib/models/ui/post_details.dart | 8 +-- lib/viewmodels/map/map_pin_view_model.dart | 1 - lib/viewmodels/posts_feed_view_model.dart | 35 +----------- .../feed/components/comment_count.dart | 12 ++--- .../content/feed/components/post_card.dart | 1 - test/models/ui/post_details_test.dart | 1 - ...osts_feed_view_model_integration_test.dart | 54 ------------------- .../posts_feed_view_model_unit_test.dart | 11 ---- 8 files changed, 5 insertions(+), 118 deletions(-) diff --git a/lib/models/ui/post_details.dart b/lib/models/ui/post_details.dart index eaa65b72..8b2e2ac8 100644 --- a/lib/models/ui/post_details.dart +++ b/lib/models/ui/post_details.dart @@ -19,7 +19,6 @@ class PostDetails { final DateTime publicationDate; final int distance; // in meters final bool isChallenge; - final bool hasUserCommented; const PostDetails({ required this.postId, @@ -33,7 +32,6 @@ class PostDetails { required this.publicationDate, required this.distance, this.isChallenge = false, - this.hasUserCommented = false, }); @override @@ -51,8 +49,7 @@ class PostDetails { other.ownerCentauriPoints == ownerCentauriPoints && other.publicationDate == publicationDate && other.distance == distance && - other.isChallenge == isChallenge && - other.hasUserCommented == hasUserCommented; + other.isChallenge == isChallenge; } @override @@ -69,7 +66,6 @@ class PostDetails { publicationDate, distance, isChallenge, - hasUserCommented, ); } @@ -81,7 +77,6 @@ class PostDetails { UserFirestore userFirestore, GeoFirePoint geoFirePoint, bool isChallenge, - bool hasUserCommented, ) { return PostDetails( postId: postFirestore.id, @@ -99,7 +94,6 @@ class PostDetails { 1000) .round(), isChallenge: isChallenge, - hasUserCommented: hasUserCommented, ); } } diff --git a/lib/viewmodels/map/map_pin_view_model.dart b/lib/viewmodels/map/map_pin_view_model.dart index 42da7e02..430c35ed 100644 --- a/lib/viewmodels/map/map_pin_view_model.dart +++ b/lib/viewmodels/map/map_pin_view_model.dart @@ -77,7 +77,6 @@ class MapPinViewModel extends AutoDisposeAsyncNotifier> { users[index], GeoFirePoint(position), false, - false, ), ), ), diff --git a/lib/viewmodels/posts_feed_view_model.dart b/lib/viewmodels/posts_feed_view_model.dart index 28d4397f..c842a57b 100644 --- a/lib/viewmodels/posts_feed_view_model.dart +++ b/lib/viewmodels/posts_feed_view_model.dart @@ -4,10 +4,8 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/database/challenge/challenge_firestore.dart"; import "package:proxima/models/database/post/post_firestore.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; -import "package:proxima/models/database/user/user_firestore.dart"; import "package:proxima/models/ui/post_details.dart"; import "package:proxima/services/database/challenge_repository_service.dart"; -import "package:proxima/services/database/comment/comment_repository_service.dart"; import "package:proxima/services/database/post_repository_service.dart"; import "package:proxima/services/database/user_repository_service.dart"; import "package:proxima/services/sensors/geolocation_service.dart"; @@ -30,7 +28,6 @@ class PostsFeedViewModel extends AutoDisposeAsyncNotifier> { final geoLocationService = ref.watch(geolocationServiceProvider); final postRepository = ref.watch(postRepositoryServiceProvider); final userRepository = ref.watch(userRepositoryServiceProvider); - final commentRepository = ref.watch(commentRepositoryServiceProvider); final challengeRepositoryService = ref.watch( challengeRepositoryServiceProvider, ); @@ -71,42 +68,13 @@ class PostsFeedViewModel extends AutoDisposeAsyncNotifier> { putOnTop: uncompletedChallengesId.toSet(), ); - // Check if the user has commented on the posts - final commentedPostsFuture = Future.wait( - postsFirestore.map((post) async { - final hasUserCommented = - await commentRepository.hasUserCommentedUnderPost( - ref.read(loggedInUserIdProvider)!, - post.id, - ); - - return MapEntry(post.id, hasUserCommented); - }), - ); - - // Get the owners of the posts final postOwnersId = postsFirestore.map((post) => post.data.ownerId).toSet(); - final postOwnersFuture = Future.wait( + final postOwners = await Future.wait( postOwnersId.map((userId) => userRepository.getUser(userId)), ); - // Await in parallel for optimisation - final results2 = await Future.wait([ - postOwnersFuture, - commentedPostsFuture, - ]); - - final postOwners = results2[0] as List; - - // Create a set of the post ids that the user has commented on for efficient lookup - final commentedPosts = results2[1] as List>; - final commentedPostIds = commentedPosts - .where((entry) => entry.value) - .map((entry) => entry.key) - .toSet(); - final posts = postsFirestore.map((post) { final owner = postOwners.firstWhere( (user) => user.uid == post.data.ownerId, @@ -120,7 +88,6 @@ class PostsFeedViewModel extends AutoDisposeAsyncNotifier> { owner, GeoFirePoint(position), uncompletedChallengesId.contains(post.id), - commentedPostIds.contains(post.id), ); return postDetails; diff --git a/lib/views/pages/home/content/feed/components/comment_count.dart b/lib/views/pages/home/content/feed/components/comment_count.dart index 27f5246f..bf5bafcf 100644 --- a/lib/views/pages/home/content/feed/components/comment_count.dart +++ b/lib/views/pages/home/content/feed/components/comment_count.dart @@ -4,28 +4,22 @@ import "package:flutter/material.dart"; /// It contains the comment icon and the number of comments. class CommentCount extends StatelessWidget { final int count; - final bool setBlue; const CommentCount({ super.key, required this.count, - required this.setBlue, }); @override Widget build(BuildContext context) { - final icon = Icon( - Icons.comment, - size: 20, - color: setBlue ? Colors.blue : null, - ); + const icon = Icon(Icons.comment, size: 20); final countText = Text(count.toString()); final content = Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - Padding( - padding: const EdgeInsets.all(4), + const Padding( + padding: EdgeInsets.all(4), child: icon, ), Padding( diff --git a/lib/views/pages/home/content/feed/components/post_card.dart b/lib/views/pages/home/content/feed/components/post_card.dart index 060798bc..da25ec41 100644 --- a/lib/views/pages/home/content/feed/components/post_card.dart +++ b/lib/views/pages/home/content/feed/components/post_card.dart @@ -80,7 +80,6 @@ class PostCard extends ConsumerWidget { child: CommentCount( key: postCardCommentsNumberKey, count: postDetails.commentNumber, - setBlue: postDetails.hasUserCommented, ), ), ], diff --git a/test/models/ui/post_details_test.dart b/test/models/ui/post_details_test.dart index 25561a76..4da7267c 100644 --- a/test/models/ui/post_details_test.dart +++ b/test/models/ui/post_details_test.dart @@ -19,7 +19,6 @@ void main() { postDetails.publicationDate, postDetails.distance, postDetails.isChallenge, - postDetails.hasUserCommented, ); final actualHash = postDetails.hashCode; diff --git a/test/viewmodels/posts_feed_view_model_integration_test.dart b/test/viewmodels/posts_feed_view_model_integration_test.dart index 2e28bf80..f4a90635 100644 --- a/test/viewmodels/posts_feed_view_model_integration_test.dart +++ b/test/viewmodels/posts_feed_view_model_integration_test.dart @@ -4,9 +4,7 @@ import "package:geoflutterfire_plus/geoflutterfire_plus.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:mockito/mockito.dart"; import "package:proxima/models/database/post/post_data.dart"; -import "package:proxima/models/database/post/post_id_firestore.dart"; import "package:proxima/models/ui/post_details.dart"; -import "package:proxima/services/database/comment/comment_repository_service.dart"; import "package:proxima/services/database/firestore_service.dart"; import "package:proxima/services/database/post_repository_service.dart"; import "package:proxima/services/database/user_repository_service.dart"; @@ -15,8 +13,6 @@ import "package:proxima/viewmodels/login_view_model.dart"; import "package:proxima/viewmodels/posts_feed_view_model.dart"; import "package:test/test.dart"; -import "../mocks/data/comment_data.dart"; -import "../mocks/data/firestore_post.dart"; import "../mocks/data/firestore_user.dart"; import "../mocks/data/geopoint.dart"; import "../mocks/data/post_data.dart"; @@ -32,10 +28,6 @@ void main() { late UserRepositoryService userRepo; late PostRepositoryService postRepo; - late CommentRepositoryService commentRepo; - - late FirestorePostGenerator postGenerator; - late CommentDataGenerator commentGenerator; late ProviderContainer container; @@ -45,8 +37,6 @@ void main() { setUp(() async { fakeFireStore = FakeFirebaseFirestore(); geoLocationService = MockGeolocationService(); - postGenerator = FirestorePostGenerator(); - commentGenerator = CommentDataGenerator(); userRepo = UserRepositoryService( firestore: fakeFireStore, @@ -63,8 +53,6 @@ void main() { ], ); - commentRepo = container.read(commentRepositoryServiceProvider); - when(geoLocationService.getCurrentPosition()).thenAnswer( (_) async => userPosition, ); @@ -246,47 +234,5 @@ void main() { expect(actualPosts, unorderedEquals(expectedPosts)); }); - - test( - "Exposes correctly if the user has commented on a post", - () async { - final user = testingUserFirestore; - - // Add the user to the database - await setUserFirestore(fakeFireStore, user); - - const nbPosts = 10; - final posts = - postGenerator.createUserPosts(user.uid, userPosition0, nbPosts); - - // Add the posts to the database - await setPostsFirestore(posts, fakeFireStore); - - // Comment under half the posts - const commentEvery = 2; - final commentedPostIds = {}; - for (var i = 0; i < nbPosts; i += commentEvery) { - final commentData = - commentGenerator.createMockCommentData(ownerId: user.uid); - final post = posts[i]; - - await commentRepo.addComment( - post.id, - commentData, - ); - - commentedPostIds.add(post.id); - } - - // Get the posts - final actualPosts = - await container.read(postsFeedViewModelProvider.future); - - // Check if the user has commented on the right posts - for (final post in actualPosts) { - expect(post.hasUserCommented, commentedPostIds.contains(post.postId)); - } - }, - ); }); } diff --git a/test/viewmodels/posts_feed_view_model_unit_test.dart b/test/viewmodels/posts_feed_view_model_unit_test.dart index b4ca2d0d..01b5b1c0 100644 --- a/test/viewmodels/posts_feed_view_model_unit_test.dart +++ b/test/viewmodels/posts_feed_view_model_unit_test.dart @@ -6,7 +6,6 @@ import "package:proxima/models/database/post/post_data.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; import "package:proxima/models/ui/post_details.dart"; import "package:proxima/services/database/challenge_repository_service.dart"; -import "package:proxima/services/database/comment/comment_repository_service.dart"; import "package:proxima/services/database/post_repository_service.dart"; import "package:proxima/services/database/user_repository_service.dart"; import "package:proxima/services/sensors/geolocation_service.dart"; @@ -21,7 +20,6 @@ import "../mocks/data/firestore_user.dart"; import "../mocks/data/geopoint.dart"; import "../mocks/data/post_data.dart"; import "../mocks/services/mock_challenge_repository_service.dart"; -import "../mocks/services/mock_comment_repository_service.dart"; import "../mocks/services/mock_geo_location_service.dart"; import "../mocks/services/mock_post_repository_service.dart"; import "../mocks/services/mock_user_repository_service.dart"; @@ -31,7 +29,6 @@ void main() { late MockGeolocationService geoLocationService; late PostRepositoryService postRepository; late UserRepositoryService userRepository; - late MockCommentRepositoryService commentRepository; late ChallengeRepositoryService challengeRepository; late ProviderContainer container; @@ -39,7 +36,6 @@ void main() { setUp(() { geoLocationService = MockGeolocationService(); postRepository = MockPostRepositoryService(); - commentRepository = MockCommentRepositoryService(); userRepository = MockUserRepositoryService(); challengeRepository = MockChallengeRepositoryService(); @@ -51,9 +47,6 @@ void main() { postRepositoryServiceProvider.overrideWithValue( postRepository, ), - commentRepositoryServiceProvider.overrideWithValue( - commentRepository, - ), userRepositoryServiceProvider.overrideWithValue( userRepository, ), @@ -64,10 +57,6 @@ void main() { ], ); - when(commentRepository.hasUserCommentedUnderPost(any, any)).thenAnswer( - (_) async => false, - ); - when( challengeRepository.getChallenges( testingUserFirestoreId, From 029fc7f9be641f52b93d235a7ab6702759979181 Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 14:52:45 +0200 Subject: [PATCH 12/21] feat(comment): add class to represent comment counts UI --- lib/models/ui/comment_count_details.dart | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 lib/models/ui/comment_count_details.dart diff --git a/lib/models/ui/comment_count_details.dart b/lib/models/ui/comment_count_details.dart new file mode 100644 index 00000000..5daec389 --- /dev/null +++ b/lib/models/ui/comment_count_details.dart @@ -0,0 +1,29 @@ +import "package:flutter/material.dart"; + +@immutable + +/// This class represents the details to display a comment count/icon on the post card. +class CommentCountDetails { + final int count; + final bool isIconBlue; + + const CommentCountDetails({ + required this.count, + required this.isIconBlue, + }); + + @override + bool operator ==(Object other) { + return other is CommentCountDetails && + other.count == count && + other.isIconBlue == isIconBlue; + } + + @override + int get hashCode { + return Object.hash( + count, + isIconBlue, + ); + } +} From 0e949fde14f9724245c19d3e779ae4cc221d063e Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 14:53:14 +0200 Subject: [PATCH 13/21] test(comment): add tests for the data class CommentCountDetails --- .../models/ui/comment_count_details_test.dart | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 test/models/ui/comment_count_details_test.dart diff --git a/test/models/ui/comment_count_details_test.dart b/test/models/ui/comment_count_details_test.dart new file mode 100644 index 00000000..f257d939 --- /dev/null +++ b/test/models/ui/comment_count_details_test.dart @@ -0,0 +1,32 @@ +import "package:flutter_test/flutter_test.dart"; +import "package:proxima/models/ui/comment_count_details.dart"; + +void main() { + group("Testing comment count details", () { + late CommentCountDetails comment; + + setUp(() { + comment = const CommentCountDetails(count: 1324, isIconBlue: true); + }); + + test("hash overrides correctly", () { + final actualHash = comment.hashCode; + + final expectedHash = Object.hash( + comment.count, + comment.isIconBlue, + ); + + expect(actualHash, expectedHash); + }); + + test("equality overrides correctly", () { + final commentCopy = CommentCountDetails( + count: comment.count, + isIconBlue: comment.isIconBlue, + ); + + expect(comment, commentCopy); + }); + }); +} From 188f4e358b5a842898711b92fecfce9933480a3f Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 21:15:22 +0200 Subject: [PATCH 14/21] feat(comment): add empty instance --- lib/models/ui/comment_count_details.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/models/ui/comment_count_details.dart b/lib/models/ui/comment_count_details.dart index 5daec389..7db24f96 100644 --- a/lib/models/ui/comment_count_details.dart +++ b/lib/models/ui/comment_count_details.dart @@ -7,6 +7,13 @@ class CommentCountDetails { final int count; final bool isIconBlue; + // An empty instance of CommentCountDetails + // to be used as a default value. + static const CommentCountDetails empty = CommentCountDetails( + count: 0, + isIconBlue: false, + ); + const CommentCountDetails({ required this.count, required this.isIconBlue, From 5cbac731c57f3d274562bf8742db25fe0ec1dc95 Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 21:16:36 +0200 Subject: [PATCH 15/21] feat(post-feed): expose the comment icon color in post comment count viewmodel --- .../post_comment_count_view_model.dart | 36 +++++++++++++++---- .../feed/components/comment_count.dart | 15 +++++--- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/lib/viewmodels/post_comment_count_view_model.dart b/lib/viewmodels/post_comment_count_view_model.dart index 85ee31ad..bd86cbad 100644 --- a/lib/viewmodels/post_comment_count_view_model.dart +++ b/lib/viewmodels/post_comment_count_view_model.dart @@ -1,19 +1,43 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; +import "package:proxima/models/ui/comment_count_details.dart"; +import "package:proxima/services/database/comment/comment_repository_service.dart"; import "package:proxima/services/database/post_repository_service.dart"; +import "package:proxima/viewmodels/login_view_model.dart"; -/// This view model is used to keep in memory the number of comments of a post. +/// This view model is used to keep in memory the state of the comment count of a post. /// It is refreshed every time the post comment list is refreshed, to stay consistent /// with it. It is also refreshed when a comment is deleted. /// This cannot be auto-dispose because, otherwise, it might get unmounted in the middle /// of its refresh method. See https://github.com/rrousselGit/riverpod/discussions/2502. class PostCommentCountViewModel - extends FamilyAsyncNotifier { + extends FamilyAsyncNotifier { @override - Future build(PostIdFirestore arg) async { + Future build(PostIdFirestore arg) async { final postRepo = ref.watch(postRepositoryServiceProvider); - final post = await postRepo.getPost(arg); - return post.data.commentCount; + final commentRepo = ref.watch(commentRepositoryServiceProvider); + final userId = ref.read(loggedInUserIdProvider); + if (userId == null) { + throw Exception("User not logged in"); + } + + final postFuture = postRepo.getPost(arg); + final hasUserCommentedFuture = + commentRepo.hasUserCommentedUnderPost(userId, arg); + + // Wait for both futures in parallel + final results = await ( + postFuture, + hasUserCommentedFuture, + ).wait; + + final post = results.$1; + final hasUserCommented = results.$2; + + return CommentCountDetails( + count: post.data.commentCount, + isIconBlue: hasUserCommented, + ); } Future refresh() async { @@ -23,6 +47,6 @@ class PostCommentCountViewModel } final postCommentCountProvider = AsyncNotifierProvider.family< - PostCommentCountViewModel, int, PostIdFirestore>( + PostCommentCountViewModel, CommentCountDetails, PostIdFirestore>( () => PostCommentCountViewModel(), ); diff --git a/lib/views/pages/home/content/feed/components/comment_count.dart b/lib/views/pages/home/content/feed/components/comment_count.dart index e622b6f8..51bebe10 100644 --- a/lib/views/pages/home/content/feed/components/comment_count.dart +++ b/lib/views/pages/home/content/feed/components/comment_count.dart @@ -1,6 +1,7 @@ import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; +import "package:proxima/models/ui/comment_count_details.dart"; import "package:proxima/viewmodels/post_comment_count_view_model.dart"; /// This widget is used to display the comment number in the post card. @@ -16,16 +17,20 @@ class CommentCount extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final asyncCount = ref.watch(postCommentCountProvider(postId)); - final count = asyncCount.value ?? 0; + final countDetails = asyncCount.value ?? CommentCountDetails.empty; - const icon = Icon(Icons.comment, size: 20); - final countText = Text(count.toString()); + final icon = Icon( + Icons.comment, + size: 20, + color: countDetails.isIconBlue ? Colors.blue : null, + ); + final countText = Text(countDetails.count.toString()); final content = Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - const Padding( - padding: EdgeInsets.all(4), + Padding( + padding: const EdgeInsets.all(4), child: icon, ), Padding( From 59eff2de06f3f5d22617703c9bf2d57177ca6d7e Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 21:17:57 +0200 Subject: [PATCH 16/21] test(post-feed): update mock comment count view model --- .../override_post_comment_count_view_model.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/mocks/overrides/override_post_comment_count_view_model.dart b/test/mocks/overrides/override_post_comment_count_view_model.dart index 7782a9fe..fb6bc3a3 100644 --- a/test/mocks/overrides/override_post_comment_count_view_model.dart +++ b/test/mocks/overrides/override_post_comment_count_view_model.dart @@ -1,21 +1,24 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; +import "package:proxima/models/ui/comment_count_details.dart"; import "package:proxima/viewmodels/post_comment_count_view_model.dart"; /// A mock implementation of the [PostCommentCountViewModel] class. /// Its state is always the same and can be set in the constructor. class MockPostCommentCountViewModel - extends FamilyAsyncNotifier + extends FamilyAsyncNotifier implements PostCommentCountViewModel { - final int count; + final CommentCountDetails countDetails; - /// Creates a new [MockPostCommentCountViewModel] with the given [count], + /// Creates a new [MockPostCommentCountViewModel] with the given [countDetails], /// which is the value that will always be returned by this view-model. - MockPostCommentCountViewModel({this.count = 0}); + MockPostCommentCountViewModel({ + this.countDetails = CommentCountDetails.empty, + }); @override - Future build(PostIdFirestore arg) async { - return count; + Future build(PostIdFirestore arg) async { + return countDetails; } @override From 3fec84142f0ea4e07b585d8e805eaafd45a35abd Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 21:18:41 +0200 Subject: [PATCH 17/21] test(post-feed): update expected comment count --- .../post_comment_count_view_model_integration_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/viewmodels/post_comment_count_view_model_integration_test.dart b/test/viewmodels/post_comment_count_view_model_integration_test.dart index 0678bae4..04ed9d3d 100644 --- a/test/viewmodels/post_comment_count_view_model_integration_test.dart +++ b/test/viewmodels/post_comment_count_view_model_integration_test.dart @@ -53,10 +53,10 @@ void main() { PostIdFirestore postId, int expectedCount, ) async { - final actualCount = await container.read( + final actualCountDetails = await container.read( postCommentCountProvider(postId).future, ); - expect(actualCount, equals(expectedCount)); + expect(actualCountDetails.count, equals(expectedCount)); } group("Comment count refresh", () { From 665d8b89129872efd122b17e9fced95dcd5f6fef Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Sun, 26 May 2024 21:33:29 +0200 Subject: [PATCH 18/21] test(post-feed): add test for the exposing of isIconBlue --- ...ent_count_view_model_integration_test.dart | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/test/viewmodels/post_comment_count_view_model_integration_test.dart b/test/viewmodels/post_comment_count_view_model_integration_test.dart index 04ed9d3d..7195c821 100644 --- a/test/viewmodels/post_comment_count_view_model_integration_test.dart +++ b/test/viewmodels/post_comment_count_view_model_integration_test.dart @@ -6,6 +6,7 @@ import "package:mockito/mockito.dart"; import "package:proxima/models/database/comment/comment_firestore.dart"; import "package:proxima/models/database/post/post_firestore.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; +import "package:proxima/models/database/user/user_firestore.dart"; import "package:proxima/services/database/comment/comment_repository_service.dart"; import "package:proxima/services/database/firestore_service.dart"; import "package:proxima/services/sensors/geolocation_service.dart"; @@ -28,6 +29,7 @@ void main() { late ProviderContainer container; late CommentRepositoryService commentRepository; + late UserFirestore user; setUp(() { setupFirebaseAuthMocks(); @@ -38,10 +40,12 @@ void main() { (_) async => userPosition0, ); + user = testingUserFirestore; + container = ProviderContainer( overrides: [ geolocationServiceProvider.overrideWithValue(geoLocationService), - loggedInUserIdProvider.overrideWithValue(testingUserFirestoreId), + loggedInUserIdProvider.overrideWithValue(user.uid), firestoreProvider.overrideWithValue(fakeFireStore), ], ); @@ -131,4 +135,54 @@ void main() { await expectCommentCount(post.id, startCommentCount - 1); }); }); + + group("Comment count icon color", () { + late CommentDataGenerator commentDataGenerator; + late FirestorePostGenerator postGenerator; + + setUp(() async { + commentDataGenerator = CommentDataGenerator(); + postGenerator = FirestorePostGenerator(); + + // Add the user to the database + await setUserFirestore(fakeFireStore, user); + }); + + test("Exposes correctly the color of the icon", () async { + // Add posts to the database + const nbPosts = 10; + final posts = + postGenerator.createUserPosts(user.uid, userPosition0, nbPosts); + + await setPostsFirestore(posts, fakeFireStore); + + // Comment under half the posts + const commentEvery = 2; + final commentedPostIds = {}; + for (var i = 0; i < nbPosts; i += commentEvery) { + final commentData = + commentDataGenerator.createMockCommentData(ownerId: user.uid); + final post = posts[i]; + + await commentRepository.addComment( + post.id, + commentData, + ); + + commentedPostIds.add(post.id); + } + + // Check that the [isIconBlue] is correctly exposed + for (final post in posts) { + final actualCountDetails = await container.read( + postCommentCountProvider(post.id).future, + ); + + expect( + actualCountDetails.isIconBlue, + commentedPostIds.contains(post.id), + ); + } + }); + }); } From f807963a94e94ade3f22c067bd6c8768ed0c783d Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Tue, 28 May 2024 23:03:59 +0200 Subject: [PATCH 19/21] docs(comment): explain query limit for hasUserCommentedUnderPost --- .../database/comment/user_comment_repository_service.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/services/database/comment/user_comment_repository_service.dart b/lib/services/database/comment/user_comment_repository_service.dart index 79deaf05..2697898b 100644 --- a/lib/services/database/comment/user_comment_repository_service.dart +++ b/lib/services/database/comment/user_comment_repository_service.dart @@ -69,6 +69,9 @@ class UserCommentRepositoryService { ) async { final userCommentCollection = _userCommentCollection(userId); + // Here, we limit the query to 1 because we only need to know if the user + // has commented at least once under the post. This limits the number of + // documents that need to be retrieved and improves the performance. final userCommentQuery = await userCommentCollection .where(UserCommentData.parentPostIdField, isEqualTo: parentPostId.value) .limit(1) From f82cf5505b0d8d0dfd6c22ea702e9c2a3c115ff1 Mon Sep 17 00:00:00 2001 From: Yoann Lafore Date: Tue, 28 May 2024 23:13:24 +0200 Subject: [PATCH 20/21] test(post-feed): add mock data for comment count details --- test/mocks/data/comment_count.dart | 10 ++++++++++ test/models/ui/comment_count_details_test.dart | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 test/mocks/data/comment_count.dart diff --git a/test/mocks/data/comment_count.dart b/test/mocks/data/comment_count.dart new file mode 100644 index 00000000..ea3c9b86 --- /dev/null +++ b/test/mocks/data/comment_count.dart @@ -0,0 +1,10 @@ +import "package:proxima/models/ui/comment_count_details.dart"; + +// Comment count details for testing purposes. +const List testCommentCounts = [ + CommentCountDetails(count: 1324, isIconBlue: true), + CommentCountDetails(count: 0, isIconBlue: false), + CommentCountDetails(count: 1, isIconBlue: true), + CommentCountDetails(count: 100, isIconBlue: false), + CommentCountDetails(count: 999, isIconBlue: true), +]; diff --git a/test/models/ui/comment_count_details_test.dart b/test/models/ui/comment_count_details_test.dart index f257d939..ea3f1994 100644 --- a/test/models/ui/comment_count_details_test.dart +++ b/test/models/ui/comment_count_details_test.dart @@ -1,12 +1,14 @@ import "package:flutter_test/flutter_test.dart"; import "package:proxima/models/ui/comment_count_details.dart"; +import "../../mocks/data/comment_count.dart"; + void main() { group("Testing comment count details", () { late CommentCountDetails comment; setUp(() { - comment = const CommentCountDetails(count: 1324, isIconBlue: true); + comment = testCommentCounts[0]; }); test("hash overrides correctly", () { From 7f00ad820f1761c5f4e05dc0952bb9d95bd57e47 Mon Sep 17 00:00:00 2001 From: gruvw Date: Wed, 29 May 2024 11:55:47 +0200 Subject: [PATCH 21/21] fix(comment): fix the comment circular value bug --- .../ui/validation/new_comment_validation.dart | 7 +- lib/viewmodels/new_comment_view_model.dart | 14 ++-- .../components/bottom_bar_add_comment.dart | 71 ++++++++----------- ...w_comment_view_model_integration_test.dart | 10 +-- 4 files changed, 46 insertions(+), 56 deletions(-) diff --git a/lib/models/ui/validation/new_comment_validation.dart b/lib/models/ui/validation/new_comment_validation.dart index f8ca4419..8a05b69a 100644 --- a/lib/models/ui/validation/new_comment_validation.dart +++ b/lib/models/ui/validation/new_comment_validation.dart @@ -2,10 +2,15 @@ /// It contains the content error message (null if there is no error) /// and a flag to indicate if the comment was posted class NewCommentValidation { + static const defaultValue = NewCommentValidation( + contentError: null, + posted: false, + ); + final String? contentError; final bool posted; - NewCommentValidation({ + const NewCommentValidation({ required this.contentError, required this.posted, }); diff --git a/lib/viewmodels/new_comment_view_model.dart b/lib/viewmodels/new_comment_view_model.dart index 5bd972aa..1beb0942 100644 --- a/lib/viewmodels/new_comment_view_model.dart +++ b/lib/viewmodels/new_comment_view_model.dart @@ -1,7 +1,6 @@ import "dart:async"; import "package:cloud_firestore/cloud_firestore.dart"; -import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/database/comment/comment_data.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; @@ -13,16 +12,11 @@ import "package:proxima/viewmodels/login_view_model.dart"; /// post id [PostIdFirestore] is provided as an argument. class NewCommentViewModel extends FamilyAsyncNotifier { - static const String contentEmptyError = "Please fill out your comment"; - - // The controller for the content of the comment - // is kept in the view model to avoid losing the content of the comment - // if the user navigates away from the page inadvertedly. - final contentController = TextEditingController(); + static const contentEmptyError = "Please fill out your comment"; @override Future build(PostIdFirestore arg) async { - return NewCommentValidation(contentError: null, posted: false); + return NewCommentValidation.defaultValue; } /// Validates that the content is not empty. @@ -30,7 +24,7 @@ class NewCommentViewModel /// Returns true if the content is not empty, false otherwise. bool validate(String content) { if (content.isEmpty) { - state = AsyncData( + state = const AsyncData( NewCommentValidation( contentError: contentEmptyError, posted: false, @@ -85,7 +79,7 @@ class NewCommentViewModel await commentRepository.addComment(postId, commentData); - state = AsyncData( + state = const AsyncData( NewCommentValidation( contentError: null, posted: true, diff --git a/lib/views/pages/post/components/bottom_bar_add_comment.dart b/lib/views/pages/post/components/bottom_bar_add_comment.dart index 6666d3c1..d571de22 100644 --- a/lib/views/pages/post/components/bottom_bar_add_comment.dart +++ b/lib/views/pages/post/components/bottom_bar_add_comment.dart @@ -1,14 +1,14 @@ import "package:flutter/material.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; +import "package:proxima/models/ui/validation/new_comment_validation.dart"; import "package:proxima/viewmodels/new_comment_view_model.dart"; -import "package:proxima/views/components/async/circular_value.dart"; -import "package:proxima/views/helpers/types/result.dart"; import "package:proxima/views/pages/post/components/new_comment/new_comment_button.dart"; import "package:proxima/views/pages/post/components/new_comment/new_comment_textfield.dart"; import "package:proxima/views/pages/post/components/new_comment/new_comment_user_avatar.dart"; -class BottomBarAddComment extends ConsumerWidget { +class BottomBarAddComment extends HookConsumerWidget { final PostIdFirestore parentPostId; const BottomBarAddComment({ @@ -18,45 +18,36 @@ class BottomBarAddComment extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final newCommentViewModel = ref.read( - newCommentViewModelProvider(parentPostId).notifier, + final asyncNewCommentState = + ref.watch(newCommentViewModelProvider(parentPostId)); + + final contentController = useTextEditingController(); + + // The avatar of the current user on the left + const userAvatar = NewCommentUserAvatar(); + + // The field in which the user can write a comment + final commentTextField = NewCommentTextField( + commentContentController: contentController, + newCommentState: asyncNewCommentState.maybeWhen( + data: (data) => data, + orElse: () => NewCommentValidation.defaultValue, + ), + ); + + // The button to post the comment + final addCommentButton = NewCommentButton( + commentContentController: contentController, + parentPostId: parentPostId, ); - final asyncNewCommentState = ref - .watch( - newCommentViewModelProvider(parentPostId).future, - ) - .mapRes(); - - final contentController = newCommentViewModel.contentController; - - return CircularValue( - future: asyncNewCommentState, - builder: (context, newCommentState) { - // The avatar of the current user on the left - const userAvatar = NewCommentUserAvatar(); - - // The field in which the user can write a comment - final commentTextField = NewCommentTextField( - commentContentController: contentController, - newCommentState: newCommentState, - ); - - // The button to post the comment - final addCommentButton = NewCommentButton( - commentContentController: contentController, - parentPostId: parentPostId, - ); - - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - userAvatar, - commentTextField, - addCommentButton, - ], - ); - }, + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + userAvatar, + commentTextField, + addCommentButton, + ], ); } } diff --git a/test/viewmodels/new_comment_view_model_integration_test.dart b/test/viewmodels/new_comment_view_model_integration_test.dart index 928e1541..da2dfe2a 100644 --- a/test/viewmodels/new_comment_view_model_integration_test.dart +++ b/test/viewmodels/new_comment_view_model_integration_test.dart @@ -61,7 +61,7 @@ void main() { final stateBeforeAdd = await newCommentViewModel.future; expect( stateBeforeAdd, - NewCommentValidation(contentError: null, posted: false), + const NewCommentValidation(contentError: null, posted: false), ); // Add the comment @@ -95,7 +95,7 @@ void main() { final stateAfterAdd = await newCommentViewModel.future; expect( stateAfterAdd, - NewCommentValidation(contentError: null, posted: true), + const NewCommentValidation(contentError: null, posted: true), ); // Reset the state @@ -105,7 +105,7 @@ void main() { final stateAfterReset = await newCommentViewModel.future; expect( stateAfterReset, - NewCommentValidation(contentError: null, posted: false), + const NewCommentValidation(contentError: null, posted: false), ); }); @@ -119,7 +119,7 @@ void main() { final stateBeforeAdd = await newCommentViewModel.future; expect( stateBeforeAdd, - NewCommentValidation(contentError: null, posted: false), + const NewCommentValidation(contentError: null, posted: false), ); // Add the comment @@ -131,7 +131,7 @@ void main() { final stateAfterAdd = await newCommentViewModel.future; expect( stateAfterAdd, - NewCommentValidation( + const NewCommentValidation( contentError: NewCommentViewModel.contentEmptyError, posted: false, ),