From 27dfb9d1fdec1ad21423d95d0eea740785a72adf Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Tue, 28 May 2024 09:01:57 +0200 Subject: [PATCH 01/29] feat(navigation-to-map): add map icon button in user posts and post page --- lib/views/components/content/info_pop_up.dart | 8 ++++---- lib/views/navigation/map_action.dart | 19 +++++++++++++++++++ .../map/components/map_pin_pop_up.dart | 2 +- lib/views/pages/post/post_page.dart | 4 ++++ .../info_cards/profile_info_pop_up.dart | 5 ++++- 5 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 lib/views/navigation/map_action.dart diff --git a/lib/views/components/content/info_pop_up.dart b/lib/views/components/content/info_pop_up.dart index 53bfca3f..f49a9bb3 100644 --- a/lib/views/components/content/info_pop_up.dart +++ b/lib/views/components/content/info_pop_up.dart @@ -5,11 +5,11 @@ class InfoPopUp extends StatelessWidget { static const popUpTitleKey = Key("profilePopUpTitle"); static const popUpDescriptionKey = Key("profilePopUpDescription"); - const InfoPopUp({super.key, this.title, this.content, this.button}); + const InfoPopUp({super.key, this.title, this.content, this.actions}); final String? title; final String? content; - final Widget? button; + final List? actions; @override Widget build(BuildContext context) { @@ -52,14 +52,14 @@ class InfoPopUp extends StatelessWidget { left: 24.0, top: 8.0, right: 24.0, - bottom: button != null ? 12.0 : 0.0, + bottom: actions != null ? 12.0 : 0.0, ), actionsPadding: const EdgeInsets.only( right: 24.0, bottom: 12.0, left: 24.0, ), - actions: button != null ? [button!] : [], + actions: actions ?? [], ); } } diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart new file mode 100644 index 00000000..093e9db1 --- /dev/null +++ b/lib/views/navigation/map_action.dart @@ -0,0 +1,19 @@ +import "package:flutter/material.dart"; + +class MapAction extends StatelessWidget { + final int depth; + + const MapAction({super.key, required this.depth}); + + @override + Widget build(BuildContext context) { + return IconButton( + onPressed: () { + for (var i = 0; i < depth; i++) { + Navigator.pop(context); + } + }, + icon: const Icon(Icons.map), + ); + } +} diff --git a/lib/views/pages/home/content/map/components/map_pin_pop_up.dart b/lib/views/pages/home/content/map/components/map_pin_pop_up.dart index d2302a49..c2043767 100644 --- a/lib/views/pages/home/content/map/components/map_pin_pop_up.dart +++ b/lib/views/pages/home/content/map/components/map_pin_pop_up.dart @@ -36,7 +36,7 @@ class MapPinPopUp extends StatelessWidget { return InfoPopUp( title: mapPinPopUpDetails.title, content: mapPinPopUpDetails.description, - button: arrowAction, + actions: [arrowAction as Widget], ); } } diff --git a/lib/views/pages/post/post_page.dart b/lib/views/pages/post/post_page.dart index b06780d1..7b005ee5 100644 --- a/lib/views/pages/post/post_page.dart +++ b/lib/views/pages/post/post_page.dart @@ -5,6 +5,7 @@ import "package:proxima/viewmodels/comments_view_model.dart"; import "package:proxima/views/components/async/circular_value.dart"; import "package:proxima/views/helpers/types/result.dart"; import "package:proxima/views/navigation/leading_back_button/leading_back_button.dart"; +import "package:proxima/views/navigation/map_action.dart"; import "package:proxima/views/pages/post/components/bottom_bar_add_comment.dart"; import "package:proxima/views/pages/post/components/comment/comment_list.dart"; import "package:proxima/views/pages/post/components/complete_post.dart"; @@ -67,6 +68,9 @@ class PostPage extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: appBarContent, ), + actions: const [ + MapAction(depth: 1), + ], ), body: Padding( padding: const EdgeInsets.only(top: 8, bottom: 8, left: 8, right: 8), diff --git a/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart b/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart index 9c99851c..b824e4b2 100644 --- a/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart +++ b/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart @@ -2,6 +2,7 @@ import "package:flutter/material.dart"; import "package:proxima/views/components/async/loading_icon_button.dart"; import "package:proxima/views/components/content/info_pop_up.dart"; import "package:proxima/views/helpers/types/future_void_callback.dart"; +import "package:proxima/views/navigation/map_action.dart"; class ProfileInfoPopUp extends StatelessWidget { //key of the button @@ -30,10 +31,12 @@ class ProfileInfoPopUp extends StatelessWidget { }, ); + const mapAction = MapAction(depth: 2); + return InfoPopUp( title: title, content: content, - button: deleteAction, + actions: [mapAction, deleteAction], ); } } From 1e8627d501e9311d383abd93629f704c17bba087 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Tue, 28 May 2024 09:57:30 +0200 Subject: [PATCH 02/29] refactor(navigation-to-map): put the home navigation in a viewmodel --- .../selected_page_view_model.dart | 33 +++++++++++++++++++ .../navigation_bottom_bar.dart | 27 +++++++-------- lib/views/pages/home/home_page.dart | 14 ++++---- 3 files changed, 51 insertions(+), 23 deletions(-) create mode 100644 lib/viewmodels/option_selection/selected_page_view_model.dart diff --git a/lib/viewmodels/option_selection/selected_page_view_model.dart b/lib/viewmodels/option_selection/selected_page_view_model.dart new file mode 100644 index 00000000..1dcafb13 --- /dev/null +++ b/lib/viewmodels/option_selection/selected_page_view_model.dart @@ -0,0 +1,33 @@ +import "package:flutter/cupertino.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:proxima/viewmodels/option_selection/options_view_model.dart"; +import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; + +class SelectedPageViewModel extends OptionsViewModel { + static const defaultSelectedPage = NavigationBarRoutes.feed; + + SelectedPageViewModel() : super(defaultSelectedPage); + + @override + void setOption(NavigationBarRoutes option) { + if (option.routeDestination != null) { + throw Exception( + "This page should be pushed and not set as the selected page." + "Use the navigate method instead."); + } + super.setOption(option); + } + + void navigate(NavigationBarRoutes route, BuildContext context) { + if (route.routeDestination == null) { + setOption(route); + } else { + Navigator.pushNamed(context, route.routeDestination!.name); + } + } +} + +final selectedPageViewModelProvider = + NotifierProvider( + () => SelectedPageViewModel(), +); diff --git a/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart b/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart index e9864adb..5f9da707 100644 --- a/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart +++ b/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart @@ -1,20 +1,19 @@ import "package:flutter/material.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:proxima/viewmodels/option_selection/selected_page_view_model.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; /// This widget is the bottom navigation bar of the home page. /// It contains the navigation routes to the different pages. -class NavigationBottomBar extends StatelessWidget { +class NavigationBottomBar extends ConsumerWidget { static const navigationBottomBarKey = Key("navigationBottomBar"); - final ValueNotifier selectedIndex; - const NavigationBottomBar({ super.key, - required this.selectedIndex, }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final destinations = NavigationBarRoutes.values.map((destination) { return NavigationDestination( icon: destination.icon, @@ -22,21 +21,19 @@ class NavigationBottomBar extends StatelessWidget { ); }).toList(); + final selectedRoute = ref.watch(selectedPageViewModelProvider); + final selectedRouteIndex = + NavigationBarRoutes.values.indexOf(selectedRoute); + return NavigationBar( key: navigationBottomBarKey, height: 90, labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, - selectedIndex: selectedIndex.value, + selectedIndex: selectedRouteIndex, onDestinationSelected: (int nextIndex) { - final selectedRoute = NavigationBarRoutes.values[nextIndex]; - - if (nextIndex != selectedIndex.value) { - if (selectedRoute.routeDestination != null) { - Navigator.pushNamed(context, selectedRoute.routeDestination!.name); - } else { - selectedIndex.value = nextIndex; - } - } + ref + .watch(selectedPageViewModelProvider.notifier) + .navigate(NavigationBarRoutes.values[nextIndex], context); }, destinations: destinations, ); diff --git a/lib/views/pages/home/home_page.dart b/lib/views/pages/home/home_page.dart index 141dac69..e324dd52 100644 --- a/lib/views/pages/home/home_page.dart +++ b/lib/views/pages/home/home_page.dart @@ -1,7 +1,7 @@ import "package:flutter/material.dart"; -import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/viewmodels/login_view_model.dart"; +import "package:proxima/viewmodels/option_selection/selected_page_view_model.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart"; import "package:proxima/views/pages/home/home_top_bar/home_top_bar.dart"; @@ -13,21 +13,19 @@ class HomePage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { navigateToLoginPageOnLogout(context, ref); - final currentPageIndex = useState(NavigationBarRoutes.feed.index); + final NavigationBarRoutes currentPage = + ref.watch(selectedPageViewModelProvider); return Scaffold( appBar: AppBar( title: HomeTopBar( - labelText: - NavigationBarRoutes.values[currentPageIndex.value].pageLabel(), + labelText: currentPage.pageLabel(), ), ), - bottomNavigationBar: NavigationBottomBar( - selectedIndex: currentPageIndex, - ), + bottomNavigationBar: const NavigationBottomBar(), body: Padding( padding: const EdgeInsets.only(left: 8, right: 8), - child: NavigationBarRoutes.values[currentPageIndex.value].page(), + child: currentPage.page(), ), ); } From 2b617aaba8e12834485e3aa3a6db150509c9855f Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Tue, 28 May 2024 09:59:17 +0200 Subject: [PATCH 03/29] feat(navigation-to-map): navigate to map in action --- lib/views/navigation/map_action.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index 093e9db1..17268762 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -1,17 +1,25 @@ import "package:flutter/material.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:proxima/viewmodels/option_selection/selected_page_view_model.dart"; -class MapAction extends StatelessWidget { +import "bottom_navigation_bar/navigation_bar_routes.dart"; + +class MapAction extends ConsumerWidget { final int depth; const MapAction({super.key, required this.depth}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return IconButton( onPressed: () { for (var i = 0; i < depth; i++) { Navigator.pop(context); } + ref.watch(selectedPageViewModelProvider.notifier).navigate( + NavigationBarRoutes.map, + context, + ); }, icon: const Icon(Icons.map), ); From 81390966e0581b4f8e70fba7fed6288891394d6b Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Tue, 28 May 2024 10:28:25 +0200 Subject: [PATCH 04/29] fix(navigation-to-map): map popup arguments with no action --- lib/views/navigation/map_action.dart | 3 +-- .../pages/home/content/map/components/map_pin_pop_up.dart | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index 17268762..fea7a568 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -1,8 +1,7 @@ import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/viewmodels/option_selection/selected_page_view_model.dart"; - -import "bottom_navigation_bar/navigation_bar_routes.dart"; +import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; class MapAction extends ConsumerWidget { final int depth; diff --git a/lib/views/pages/home/content/map/components/map_pin_pop_up.dart b/lib/views/pages/home/content/map/components/map_pin_pop_up.dart index c2043767..de956c85 100644 --- a/lib/views/pages/home/content/map/components/map_pin_pop_up.dart +++ b/lib/views/pages/home/content/map/components/map_pin_pop_up.dart @@ -36,7 +36,7 @@ class MapPinPopUp extends StatelessWidget { return InfoPopUp( title: mapPinPopUpDetails.title, content: mapPinPopUpDetails.description, - actions: [arrowAction as Widget], + actions: arrowAction != null ? [arrowAction] : [], ); } } From df4eaa836d343c22675b7a3e1965c1fe22224384 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Tue, 28 May 2024 11:01:28 +0200 Subject: [PATCH 05/29] feat(navigation-to-map): add focus to a post from post view --- lib/models/ui/post_details.dart | 4 +++ lib/views/navigation/map_action.dart | 25 ++++++++++++++++++- lib/views/pages/post/post_page.dart | 9 +++++-- .../info_cards/profile_info_pop_up.dart | 10 +++++++- test/mocks/data/post_overview.dart | 5 ++++ ...osts_feed_view_model_integration_test.dart | 2 ++ .../posts_feed_view_model_unit_test.dart | 4 +++ 7 files changed, 55 insertions(+), 4 deletions(-) diff --git a/lib/models/ui/post_details.dart b/lib/models/ui/post_details.dart index 80d27906..02892eb6 100644 --- a/lib/models/ui/post_details.dart +++ b/lib/models/ui/post_details.dart @@ -1,3 +1,4 @@ +import "package:cloud_firestore/cloud_firestore.dart"; import "package:flutter/foundation.dart"; import "package:geoflutterfire_plus/geoflutterfire_plus.dart"; import "package:proxima/models/database/post/post_firestore.dart"; @@ -21,6 +22,7 @@ class PostDetails { final DateTime publicationDate; final int distance; // in meters final bool isChallenge; + final GeoPoint location; const PostDetails({ required this.postId, @@ -34,6 +36,7 @@ class PostDetails { required this.ownerCentauriPoints, required this.publicationDate, required this.distance, + required this.location, this.isChallenge = false, }); @@ -99,6 +102,7 @@ class PostDetails { ) * 1000) .round(), + location: postFirestore.location.geoPoint, isChallenge: isChallenge, ); } diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index fea7a568..9d6bc105 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -1,12 +1,25 @@ +import "package:cloud_firestore/cloud_firestore.dart"; import "package:flutter/material.dart"; +import "package:google_maps_flutter/google_maps_flutter.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:proxima/viewmodels/map/map_view_model.dart"; +import "package:proxima/viewmodels/option_selection/map_selection_options_view_model.dart"; import "package:proxima/viewmodels/option_selection/selected_page_view_model.dart"; +import "package:proxima/views/components/options/map/map_selection_options.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; class MapAction extends ConsumerWidget { final int depth; - const MapAction({super.key, required this.depth}); + final MapSelectionOptions mapOption; + final GeoPoint? initialLocation; + + const MapAction({ + super.key, + required this.depth, + required this.mapOption, + this.initialLocation, + }); @override Widget build(BuildContext context, WidgetRef ref) { @@ -19,6 +32,16 @@ class MapAction extends ConsumerWidget { NavigationBarRoutes.map, context, ); + + ref.watch(mapSelectionOptionsViewModelProvider.notifier).setOption( + mapOption, + ); + + if (initialLocation != null) { + ref.watch(mapViewModelProvider.notifier).updateCamera( + LatLng(initialLocation!.latitude, initialLocation!.longitude), + ); + } }, icon: const Icon(Icons.map), ); diff --git a/lib/views/pages/post/post_page.dart b/lib/views/pages/post/post_page.dart index 7b005ee5..ae36691d 100644 --- a/lib/views/pages/post/post_page.dart +++ b/lib/views/pages/post/post_page.dart @@ -3,6 +3,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/ui/post_details.dart"; import "package:proxima/viewmodels/comments_view_model.dart"; import "package:proxima/views/components/async/circular_value.dart"; +import "package:proxima/views/components/options/map/map_selection_options.dart"; import "package:proxima/views/helpers/types/result.dart"; import "package:proxima/views/navigation/leading_back_button/leading_back_button.dart"; import "package:proxima/views/navigation/map_action.dart"; @@ -68,8 +69,12 @@ class PostPage extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: appBarContent, ), - actions: const [ - MapAction(depth: 1), + actions: [ + MapAction( + depth: 1, + mapOption: MapSelectionOptions.nearby, + initialLocation: postDetails.location, + ), ], ), body: Padding( diff --git a/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart b/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart index b824e4b2..97337f5b 100644 --- a/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart +++ b/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart @@ -1,6 +1,8 @@ +import "package:cloud_firestore/cloud_firestore.dart"; import "package:flutter/material.dart"; import "package:proxima/views/components/async/loading_icon_button.dart"; import "package:proxima/views/components/content/info_pop_up.dart"; +import "package:proxima/views/components/options/map/map_selection_options.dart"; import "package:proxima/views/helpers/types/future_void_callback.dart"; import "package:proxima/views/navigation/map_action.dart"; @@ -11,11 +13,13 @@ class ProfileInfoPopUp extends StatelessWidget { const ProfileInfoPopUp({ super.key, this.title, + this.location, required this.content, required this.onDelete, }); final String? title; + final GeoPoint? location; final String content; final FutureVoidCallback onDelete; @@ -31,7 +35,11 @@ class ProfileInfoPopUp extends StatelessWidget { }, ); - const mapAction = MapAction(depth: 2); + final mapAction = MapAction( + depth: 2, + mapOption: MapSelectionOptions.myPosts, + initialLocation: location, + ); return InfoPopUp( title: title, diff --git a/test/mocks/data/post_overview.dart b/test/mocks/data/post_overview.dart index 75d0202b..c91e1e06 100644 --- a/test/mocks/data/post_overview.dart +++ b/test/mocks/data/post_overview.dart @@ -3,6 +3,7 @@ import "package:proxima/models/database/user/user_id_firestore.dart"; import "package:proxima/models/ui/post_details.dart"; import "datetime.dart"; +import "geopoint.dart"; final List testPosts = [ PostDetails( @@ -19,6 +20,7 @@ final List testPosts = [ ownerCentauriPoints: 32, publicationDate: publicationDate1, distance: 20, + location: userPosition1, ), PostDetails( postId: const PostIdFirestore(value: "post_2"), @@ -34,6 +36,7 @@ final List testPosts = [ publicationDate: publicationDate1, distance: 20, isChallenge: true, + location: userPosition1, ), PostDetails( postId: const PostIdFirestore(value: "post_3"), @@ -47,6 +50,7 @@ final List testPosts = [ ownerCentauriPoints: 218281828, publicationDate: publicationDate1, distance: 20, + location: userPosition1, ), ]; @@ -63,4 +67,5 @@ final timeDistancePost = PostDetails( ownerCentauriPoints: 911, publicationDate: DateTime.utc(1999), distance: 100, + location: userPosition1, ); diff --git a/test/viewmodels/posts_feed_view_model_integration_test.dart b/test/viewmodels/posts_feed_view_model_integration_test.dart index 6ed0a770..4fb231f8 100644 --- a/test/viewmodels/posts_feed_view_model_integration_test.dart +++ b/test/viewmodels/posts_feed_view_model_integration_test.dart @@ -126,6 +126,7 @@ void main() { .distanceBetweenInKm(geopoint: postPosition) * 1000) .round(), + location: userPosition1, ), ]; @@ -226,6 +227,7 @@ void main() { .distanceBetweenInKm(geopoint: postPositions[index]) * 1000) .round(), + location: userPosition1, ); return postDetails; diff --git a/test/viewmodels/posts_feed_view_model_unit_test.dart b/test/viewmodels/posts_feed_view_model_unit_test.dart index 13a35ee9..75a96179 100644 --- a/test/viewmodels/posts_feed_view_model_unit_test.dart +++ b/test/viewmodels/posts_feed_view_model_unit_test.dart @@ -122,6 +122,7 @@ void main() { ownerCentauriPoints: owner.data.centauriPoints, publicationDate: post.data.publicationTime.toDate(), distance: 0, + location: userPosition1, ), ]; @@ -181,6 +182,7 @@ void main() { ownerCentauriPoints: owner.data.centauriPoints, publicationDate: post.data.publicationTime.toDate(), distance: 0, + location: userPosition1, ); return postDetails; @@ -248,6 +250,7 @@ void main() { ownerCentauriPoints: owners[index].data.centauriPoints, publicationDate: post.data.publicationTime.toDate(), distance: 0, + location: userPosition1, ); return postDetails; @@ -328,6 +331,7 @@ void main() { ownerCentauriPoints: owner.data.centauriPoints, publicationDate: post.data.publicationTime.toDate(), distance: 0, + location: userPosition1, ), ]; From f30722ad301bf364f150a53502ed4bcafcf7a0e4 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Tue, 28 May 2024 11:22:45 +0200 Subject: [PATCH 06/29] feat(navigation-to-map): add focus for my posts --- lib/models/ui/user_post_details.dart | 8 ++++++-- lib/viewmodels/map/map_view_model.dart | 8 ++++++-- lib/viewmodels/user_posts_view_model.dart | 1 + lib/views/navigation/map_action.dart | 12 ++++++++---- .../components/info_cards/profile_info_card.dart | 4 ++++ .../components/profile_data/profile_user_posts.dart | 1 + test/models/ui/user_post_details_test.dart | 8 ++++++++ 7 files changed, 34 insertions(+), 8 deletions(-) diff --git a/lib/models/ui/user_post_details.dart b/lib/models/ui/user_post_details.dart index 278acfd1..0e77d3b2 100644 --- a/lib/models/ui/user_post_details.dart +++ b/lib/models/ui/user_post_details.dart @@ -1,3 +1,4 @@ +import "package:cloud_firestore/cloud_firestore.dart"; import "package:flutter/foundation.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; @@ -8,12 +9,13 @@ class UserPostDetails { final PostIdFirestore postId; final String title; final String description; - // TODO add the location to be able to redirect to the map (see #184) + final GeoPoint location; const UserPostDetails({ required this.postId, required this.title, required this.description, + required this.location, }); @override @@ -23,7 +25,8 @@ class UserPostDetails { return other is UserPostDetails && other.postId == postId && other.title == title && - other.description == description; + other.description == description && + other.location == location; } @override @@ -32,6 +35,7 @@ class UserPostDetails { postId, title, description, + location, ); } } diff --git a/lib/viewmodels/map/map_view_model.dart b/lib/viewmodels/map/map_view_model.dart index 661757c0..2b1a50ed 100644 --- a/lib/viewmodels/map/map_view_model.dart +++ b/lib/viewmodels/map/map_view_model.dart @@ -71,8 +71,12 @@ class MapViewModel extends AutoDisposeAsyncNotifier { /// Move the camera to the target location /// Only moves the camera if the follow user is enabled - Future updateCamera(LatLng userPosition) async { - if (!_followUser) return; + Future updateCamera( + LatLng userPosition, { + bool followEvent = true, + }) async { + if (followEvent && !_followUser) return; + _followUser = followEvent; final GoogleMapController controller = await _mapController.future; // reset zoom to initial // center camera on target diff --git a/lib/viewmodels/user_posts_view_model.dart b/lib/viewmodels/user_posts_view_model.dart index e36c3be9..7926ab6c 100644 --- a/lib/viewmodels/user_posts_view_model.dart +++ b/lib/viewmodels/user_posts_view_model.dart @@ -33,6 +33,7 @@ class UserPostsViewModel extends AutoDisposeAsyncNotifier { postId: post.id, title: post.data.title, description: post.data.description, + location: post.location.geoPoint, ); return userPost; diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index 9d6bc105..15d42d3f 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -24,7 +24,9 @@ class MapAction extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return IconButton( - onPressed: () { + onPressed: () async { + final mapViewModelNotifier = ref.read(mapViewModelProvider.notifier); + for (var i = 0; i < depth; i++) { Navigator.pop(context); } @@ -38,9 +40,11 @@ class MapAction extends ConsumerWidget { ); if (initialLocation != null) { - ref.watch(mapViewModelProvider.notifier).updateCamera( - LatLng(initialLocation!.latitude, initialLocation!.longitude), - ); + await Future.delayed(Duration(seconds: 1)); + mapViewModelNotifier.updateCamera( + LatLng(initialLocation!.latitude, initialLocation!.longitude), + followEvent: false, + ); } }, icon: const Icon(Icons.map), diff --git a/lib/views/pages/profile/components/info_cards/profile_info_card.dart b/lib/views/pages/profile/components/info_cards/profile_info_card.dart index b0c7612c..0367cdb5 100644 --- a/lib/views/pages/profile/components/info_cards/profile_info_card.dart +++ b/lib/views/pages/profile/components/info_cards/profile_info_card.dart @@ -1,3 +1,4 @@ +import "package:cloud_firestore/cloud_firestore.dart"; import "package:flutter/material.dart"; import "package:proxima/views/components/async/loading_icon_button.dart"; import "package:proxima/views/helpers/types/future_void_callback.dart"; @@ -13,11 +14,13 @@ class ProfileInfoCard extends StatelessWidget { required this.content, required this.onDelete, this.title, + this.location, }); final String content; final FutureVoidCallback onDelete; final String? title; + final GeoPoint? location; @override Widget build(BuildContext context) { @@ -58,6 +61,7 @@ class ProfileInfoCard extends StatelessWidget { title: title, content: content, onDelete: onDelete, + location: location, ); }, ), diff --git a/lib/views/pages/profile/components/profile_data/profile_user_posts.dart b/lib/views/pages/profile/components/profile_data/profile_user_posts.dart index 6bc7b298..c90137cd 100644 --- a/lib/views/pages/profile/components/profile_data/profile_user_posts.dart +++ b/lib/views/pages/profile/components/profile_data/profile_user_posts.dart @@ -30,6 +30,7 @@ class ProfileUserPosts extends ConsumerWidget { onDelete: () => ref .read(userPostsViewModelProvider.notifier) .deletePost(p.postId), + location: p.location, ), ) .toList(); diff --git a/test/models/ui/user_post_details_test.dart b/test/models/ui/user_post_details_test.dart index 1e9ffc1e..a1b5318a 100644 --- a/test/models/ui/user_post_details_test.dart +++ b/test/models/ui/user_post_details_test.dart @@ -2,6 +2,8 @@ import "package:flutter_test/flutter_test.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; import "package:proxima/models/ui/user_post_details.dart"; +import "../../mocks/data/geopoint.dart"; + void main() { group("User Post model testing", () { test("hash overrides correctly", () { @@ -9,12 +11,14 @@ void main() { postId: PostIdFirestore(value: "post_1"), title: "title", description: "description", + location: userPosition0, ); final expectedHash = Object.hash( userPost.postId, userPost.title, userPost.description, + userPost.location, ); final actualHash = userPost.hashCode; @@ -27,12 +31,14 @@ void main() { postId: PostIdFirestore(value: "post_1"), title: "title", description: "description", + location: userPosition0, ); const userPostCopy = UserPostDetails( postId: PostIdFirestore(value: "post_1"), title: "title", description: "description", + location: userPosition0, ); expect(userPost, userPostCopy); @@ -43,12 +49,14 @@ void main() { postId: PostIdFirestore(value: "post_1"), title: "title", description: "description 1", + location: userPosition0, ); const userPost2 = UserPostDetails( postId: PostIdFirestore(value: "post_1"), title: "title", description: "description 2", + location: userPosition0, ); expect(userPost1 != userPost2, true); From 3ed2e6953d12f5ba890de3fcd7c6b9bcd0f3e0be Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 09:18:51 +0200 Subject: [PATCH 07/29] refactor(navigation-to-map): remove build context from viewmodel --- .../option_selection/selected_page_view_model.dart | 9 --------- .../bottom_navigation_bar/navigation_bottom_bar.dart | 9 ++++++--- lib/views/navigation/map_action.dart | 5 ++--- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/lib/viewmodels/option_selection/selected_page_view_model.dart b/lib/viewmodels/option_selection/selected_page_view_model.dart index 1dcafb13..18128f92 100644 --- a/lib/viewmodels/option_selection/selected_page_view_model.dart +++ b/lib/viewmodels/option_selection/selected_page_view_model.dart @@ -1,4 +1,3 @@ -import "package:flutter/cupertino.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/viewmodels/option_selection/options_view_model.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; @@ -17,14 +16,6 @@ class SelectedPageViewModel extends OptionsViewModel { } super.setOption(option); } - - void navigate(NavigationBarRoutes route, BuildContext context) { - if (route.routeDestination == null) { - setOption(route); - } else { - Navigator.pushNamed(context, route.routeDestination!.name); - } - } } final selectedPageViewModelProvider = diff --git a/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart b/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart index 5f9da707..e7f17af5 100644 --- a/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart +++ b/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart @@ -31,9 +31,12 @@ class NavigationBottomBar extends ConsumerWidget { labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, selectedIndex: selectedRouteIndex, onDestinationSelected: (int nextIndex) { - ref - .watch(selectedPageViewModelProvider.notifier) - .navigate(NavigationBarRoutes.values[nextIndex], context); + final route = NavigationBarRoutes.values[nextIndex]; + if (route.routeDestination == null) { + ref.watch(selectedPageViewModelProvider.notifier).setOption(route); + } else { + Navigator.pushNamed(context, route.routeDestination!.name); + } }, destinations: destinations, ); diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index 15d42d3f..e4e05e90 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -30,9 +30,8 @@ class MapAction extends ConsumerWidget { for (var i = 0; i < depth; i++) { Navigator.pop(context); } - ref.watch(selectedPageViewModelProvider.notifier).navigate( + ref.watch(selectedPageViewModelProvider.notifier).setOption( NavigationBarRoutes.map, - context, ); ref.watch(mapSelectionOptionsViewModelProvider.notifier).setOption( @@ -40,7 +39,7 @@ class MapAction extends ConsumerWidget { ); if (initialLocation != null) { - await Future.delayed(Duration(seconds: 1)); + await Future.delayed(const Duration(seconds: 1)); mapViewModelNotifier.updateCamera( LatLng(initialLocation!.latitude, initialLocation!.longitude), followEvent: false, From 516a451375898bec69bf1f206fc749a7fe3a3242 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 09:33:04 +0200 Subject: [PATCH 08/29] feat(navigation-to-map): add position to challenges ui --- lib/models/ui/challenge_details.dart | 12 ++++++++++-- lib/viewmodels/challenge_view_model.dart | 2 ++ .../pages/home/content/challenge/challenge_card.dart | 12 +++++++++--- test/mocks/data/challenge_list.dart | 7 +++++++ test/models/ui/challenge_details_test.dart | 6 ++++++ 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/lib/models/ui/challenge_details.dart b/lib/models/ui/challenge_details.dart index 4bc2a18d..6cf76658 100644 --- a/lib/models/ui/challenge_details.dart +++ b/lib/models/ui/challenge_details.dart @@ -1,3 +1,4 @@ +import "package:cloud_firestore/cloud_firestore.dart"; import "package:flutter/foundation.dart"; /// This class was hard to design. We considered multiple possible implementations, @@ -18,6 +19,7 @@ class ChallengeDetails { final int? distance; final int? timeLeft; final int reward; + final GeoPoint location; /// Creates a [ChallengeDetails] with the given parameters. The [title] is the /// post's title, the [distance] is the distance to the challenge in meters, the [timeLeft] @@ -28,27 +30,32 @@ class ChallengeDetails { required int this.distance, required int this.timeLeft, required this.reward, + required this.location, }); const ChallengeDetails.group({ required this.title, required int this.distance, required this.reward, + required this.location, }) : timeLeft = null; const ChallengeDetails.soloFinished({ required this.title, required int this.timeLeft, required this.reward, + required this.location, }) : distance = null; const ChallengeDetails.groupFinished({ required this.title, required this.reward, + required this.location, }) : distance = null, timeLeft = null; bool get isFinished => distance == null; + bool get isGroupChallenge => timeLeft == null; @override @@ -57,11 +64,12 @@ class ChallengeDetails { other.title == title && other.distance == distance && other.timeLeft == timeLeft && - other.reward == reward; + other.reward == reward && + other.location == location; } @override int get hashCode { - return Object.hash(title, distance, timeLeft, reward); + return Object.hash(title, distance, timeLeft, reward, location); } } diff --git a/lib/viewmodels/challenge_view_model.dart b/lib/viewmodels/challenge_view_model.dart index 497eb5c7..187f3132 100644 --- a/lib/viewmodels/challenge_view_model.dart +++ b/lib/viewmodels/challenge_view_model.dart @@ -49,12 +49,14 @@ class ChallengeViewModel distance: distanceM, timeLeft: timeLeft.inHours, reward: ChallengeRepositoryService.soloChallengeReward, + location: post.location.geoPoint, ); } else { return ChallengeDetails.soloFinished( title: post.data.title, timeLeft: timeLeft.inHours, reward: ChallengeRepositoryService.soloChallengeReward, + location: post.location.geoPoint, ); } }); diff --git a/lib/views/pages/home/content/challenge/challenge_card.dart b/lib/views/pages/home/content/challenge/challenge_card.dart index f577ec85..97d85fe6 100644 --- a/lib/views/pages/home/content/challenge/challenge_card.dart +++ b/lib/views/pages/home/content/challenge/challenge_card.dart @@ -1,9 +1,12 @@ import "package:flutter/material.dart"; import "package:flutter_svg/svg.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/ui/challenge_details.dart"; +import "package:proxima/views/components/options/map/map_selection_options.dart"; import "package:proxima/views/helpers/key_value_list_builder.dart"; +import "package:proxima/views/navigation/map_action.dart"; -class ChallengeCard extends StatelessWidget { +class ChallengeCard extends ConsumerWidget { static const challengeGroupIconKey = Key("challenge_group_icon"); static const challengeSingleIconKey = Key("challenge_single_icon"); @@ -16,7 +19,7 @@ class ChallengeCard extends StatelessWidget { const ChallengeCard(this.challenge, {super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final listGenerator = KeyValueListBuilder( style: DefaultTextStyle.of(context).style, ); @@ -51,7 +54,10 @@ class ChallengeCard extends StatelessWidget { return Card( clipBehavior: Clip.hardEdge, child: InkWell( - onTap: () => (), + onTap: () { + MapAction.openMap(ref, context, MapSelectionOptions.challenges, 1, + initialLocation: challenge.location); + }, child: Opacity( opacity: challenge.isFinished ? _opacityWhenChallengedFinished : 1.0, child: ListTile( diff --git a/test/mocks/data/challenge_list.dart b/test/mocks/data/challenge_list.dart index 413b46ed..b8e9994a 100644 --- a/test/mocks/data/challenge_list.dart +++ b/test/mocks/data/challenge_list.dart @@ -1,5 +1,7 @@ import "package:proxima/models/ui/challenge_details.dart"; +import "geopoint.dart"; + // All values are purposely different to test the UI more easily const mockChallengeList = [ @@ -8,6 +10,7 @@ const mockChallengeList = [ distance: 700, timeLeft: 27, reward: 250, + location: userPosition0, ), ChallengeDetails.solo( title: @@ -15,19 +18,23 @@ const mockChallengeList = [ distance: 3200, timeLeft: 28, reward: 400, + location: userPosition0, ), ChallengeDetails.group( title: "I'm moving out", distance: 4000, reward: 350, + location: userPosition0, ), ChallengeDetails.soloFinished( title: "What a view!", timeLeft: 29, reward: 200, + location: userPosition0, ), ChallengeDetails.groupFinished( title: "I saw a bird here once", reward: 50, + location: userPosition0, ), ]; diff --git a/test/models/ui/challenge_details_test.dart b/test/models/ui/challenge_details_test.dart index 05af05c4..ca8d4e74 100644 --- a/test/models/ui/challenge_details_test.dart +++ b/test/models/ui/challenge_details_test.dart @@ -1,6 +1,8 @@ import "package:flutter_test/flutter_test.dart"; import "package:proxima/models/ui/challenge_details.dart"; +import "../../mocks/data/geopoint.dart"; + void main() { group("Challenge card data testing", () { test("hash overrides correctly", () { @@ -8,6 +10,7 @@ void main() { title: "title", distance: 50, reward: 100, + location: userPosition0, ); final expectedHash = Object.hash( @@ -15,6 +18,7 @@ void main() { challengeCard.distance, challengeCard.timeLeft, challengeCard.reward, + challengeCard.location, ); final actualHash = challengeCard.hashCode; @@ -27,11 +31,13 @@ void main() { title: "title", distance: 50, reward: 100, + location: userPosition0, ); const challengeCardDataCopy = ChallengeDetails.group( title: "title", distance: 50, reward: 100, + location: userPosition0, ); expect(challengeCardData, challengeCardDataCopy); From 098cfdc502d75aac699d44aadfcff697598f4464 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 09:33:22 +0200 Subject: [PATCH 09/29] refactor(navigation-to-map): put toLatLng in an extension function --- lib/utils/extensions/geopoint_extensions.dart | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 lib/utils/extensions/geopoint_extensions.dart diff --git a/lib/utils/extensions/geopoint_extensions.dart b/lib/utils/extensions/geopoint_extensions.dart new file mode 100644 index 00000000..34b31a03 --- /dev/null +++ b/lib/utils/extensions/geopoint_extensions.dart @@ -0,0 +1,8 @@ +import "package:cloud_firestore/cloud_firestore.dart"; +import "package:google_maps_flutter/google_maps_flutter.dart"; + +extension ToLatLng on GeoPoint { + LatLng toLatLng() { + return LatLng(latitude, longitude); + } +} From 6a80faaa227c54c7d6c2a207e3822da5712fc4aa Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 09:33:43 +0200 Subject: [PATCH 10/29] refactor(navigation-to-map): factorize the open map function --- lib/views/navigation/map_action.dart | 58 ++++++++++++++++++---------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index e4e05e90..62505195 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -1,7 +1,7 @@ import "package:cloud_firestore/cloud_firestore.dart"; import "package:flutter/material.dart"; -import "package:google_maps_flutter/google_maps_flutter.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:proxima/utils/extensions/geopoint_extensions.dart"; import "package:proxima/viewmodels/map/map_view_model.dart"; import "package:proxima/viewmodels/option_selection/map_selection_options_view_model.dart"; import "package:proxima/viewmodels/option_selection/selected_page_view_model.dart"; @@ -25,28 +25,44 @@ class MapAction extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return IconButton( onPressed: () async { - final mapViewModelNotifier = ref.read(mapViewModelProvider.notifier); - - for (var i = 0; i < depth; i++) { - Navigator.pop(context); - } - ref.watch(selectedPageViewModelProvider.notifier).setOption( - NavigationBarRoutes.map, - ); - - ref.watch(mapSelectionOptionsViewModelProvider.notifier).setOption( - mapOption, - ); - - if (initialLocation != null) { - await Future.delayed(const Duration(seconds: 1)); - mapViewModelNotifier.updateCamera( - LatLng(initialLocation!.latitude, initialLocation!.longitude), - followEvent: false, - ); - } + openMap( + ref, + context, + mapOption, + depth, + initialLocation: initialLocation, + ); }, icon: const Icon(Icons.map), ); } + + static void openMap( + WidgetRef ref, + BuildContext context, + MapSelectionOptions mapOption, + int depth, { + GeoPoint? initialLocation, + }) async { + final mapViewModelNotifier = ref.read(mapViewModelProvider.notifier); + + for (var i = 0; i < depth; i++) { + Navigator.pop(context); + } + ref.watch(selectedPageViewModelProvider.notifier).setOption( + NavigationBarRoutes.map, + ); + + ref.watch(mapSelectionOptionsViewModelProvider.notifier).setOption( + mapOption, + ); + + if (initialLocation != null) { + await Future.delayed(const Duration(seconds: 1)); + mapViewModelNotifier.updateCamera( + initialLocation.toLatLng(), + followEvent: false, + ); + } + } } From a5ec5def8b09d675b26a7faef423e994c20418f1 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 09:34:14 +0200 Subject: [PATCH 11/29] fix(navigation-to-map): map view model overrides --- test/mocks/overrides/override_map_view_model.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/mocks/overrides/override_map_view_model.dart b/test/mocks/overrides/override_map_view_model.dart index 897ecf24..c8195bff 100644 --- a/test/mocks/overrides/override_map_view_model.dart +++ b/test/mocks/overrides/override_map_view_model.dart @@ -67,7 +67,8 @@ class MockMapViewModel extends AutoDisposeAsyncNotifier void enableFollowUser() => _enableFollowUser(); @override - Future updateCamera(LatLng target) => _moveCamera(target); + Future updateCamera(LatLng target, {bool followEvent = true}) => + _moveCamera(target); } final mockNoGPSMapViewModelOverride = [ From 6db3f5f9c8dc559a1c522dfd4882ad1ac42e2134 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 09:37:52 +0200 Subject: [PATCH 12/29] fix(navigation-to-map): correct depth for challenges --- .../pages/home/content/challenge/challenge_card.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/views/pages/home/content/challenge/challenge_card.dart b/lib/views/pages/home/content/challenge/challenge_card.dart index 97d85fe6..7462e50e 100644 --- a/lib/views/pages/home/content/challenge/challenge_card.dart +++ b/lib/views/pages/home/content/challenge/challenge_card.dart @@ -55,8 +55,13 @@ class ChallengeCard extends ConsumerWidget { clipBehavior: Clip.hardEdge, child: InkWell( onTap: () { - MapAction.openMap(ref, context, MapSelectionOptions.challenges, 1, - initialLocation: challenge.location); + MapAction.openMap( + ref, + context, + MapSelectionOptions.challenges, + 0, + initialLocation: challenge.location, + ); }, child: Opacity( opacity: challenge.isFinished ? _opacityWhenChallengedFinished : 1.0, From d0a1a7548b2d81170f97a7724840d372a1fd4af2 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 10:13:52 +0200 Subject: [PATCH 13/29] fix(navigation-to-map): change return type to future and await all futures --- lib/views/navigation/map_action.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index 62505195..c8620e40 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -24,7 +24,7 @@ class MapAction extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return IconButton( - onPressed: () async { + onPressed: () { openMap( ref, context, @@ -37,7 +37,7 @@ class MapAction extends ConsumerWidget { ); } - static void openMap( + static Future openMap( WidgetRef ref, BuildContext context, MapSelectionOptions mapOption, @@ -59,7 +59,7 @@ class MapAction extends ConsumerWidget { if (initialLocation != null) { await Future.delayed(const Duration(seconds: 1)); - mapViewModelNotifier.updateCamera( + await mapViewModelNotifier.updateCamera( initialLocation.toLatLng(), followEvent: false, ); From 5f7d2f332fbcfc5dd9c962b0e4de14d1a1b82847 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 14:42:33 +0200 Subject: [PATCH 14/29] feat(navigation-to-map): add initial position as an optional parameter if initial position is null, than the user position is used and the map follows the user. Otherwise that position is used and the map does not follow the user. --- lib/viewmodels/map/map_view_model.dart | 27 ++++++++++++------- lib/views/navigation/map_action.dart | 2 +- .../home/content/map/components/post_map.dart | 5 +++- .../pages/home/content/map/map_screen.dart | 16 ++++++++--- .../overrides/override_map_view_model.dart | 12 +++++---- .../map/map_view_model_unit_test.dart | 4 +-- 6 files changed, 43 insertions(+), 23 deletions(-) diff --git a/lib/viewmodels/map/map_view_model.dart b/lib/viewmodels/map/map_view_model.dart index 2b1a50ed..54c631ec 100644 --- a/lib/viewmodels/map/map_view_model.dart +++ b/lib/viewmodels/map/map_view_model.dart @@ -5,20 +5,27 @@ import "package:google_maps_flutter/google_maps_flutter.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/ui/map_details.dart"; import "package:proxima/services/sensors/geolocation_service.dart"; +import "package:proxima/utils/extensions/geopoint_extensions.dart"; import "package:proxima/viewmodels/posts_feed_view_model.dart"; /// This view model is responsible for managing the actual location /// of the user on the map and the displayed circles. -class MapViewModel extends AutoDisposeAsyncNotifier { +class MapViewModel extends AutoDisposeFamilyAsyncNotifier { @override - Future build() async { - final actualLocation = - await ref.read(geolocationServiceProvider).getCurrentPosition(); - - return MapDetails( - initialLocation: - LatLng(actualLocation.latitude, actualLocation.longitude), - ); + Future build([LatLng? arg]) async { + if (arg != null) { + disableFollowUser(); + return MapDetails( + initialLocation: arg, + ); + } else { + enableFollowUser(); + final geoPoint = + await ref.read(geolocationServiceProvider).getCurrentPosition(); + return MapDetails( + initialLocation: geoPoint.toLatLng(), + ); + } } final Set _circles = {}; @@ -87,6 +94,6 @@ class MapViewModel extends AutoDisposeAsyncNotifier { } final mapViewModelProvider = - AsyncNotifierProvider.autoDispose( + AsyncNotifierProvider.autoDispose.family( () => MapViewModel(), ); diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index c8620e40..1ac68e09 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -44,7 +44,7 @@ class MapAction extends ConsumerWidget { int depth, { GeoPoint? initialLocation, }) async { - final mapViewModelNotifier = ref.read(mapViewModelProvider.notifier); + final mapViewModelNotifier = ref.read(mapViewModelProvider(null).notifier); for (var i = 0; i < depth; i++) { Navigator.pop(context); diff --git a/lib/views/pages/home/content/map/components/post_map.dart b/lib/views/pages/home/content/map/components/post_map.dart index 43e90567..5b72f256 100644 --- a/lib/views/pages/home/content/map/components/post_map.dart +++ b/lib/views/pages/home/content/map/components/post_map.dart @@ -12,6 +12,7 @@ import "package:proxima/views/pages/home/content/map/components/map_pin_pop_up.d /// This widget displays the Google Map class PostMap extends ConsumerWidget { final MapDetails mapInfo; + final LatLng? initialLocation; static const mapPinPopUpKey = Key("mapPinPopUp"); @@ -21,12 +22,14 @@ class PostMap extends ConsumerWidget { const PostMap({ super.key, required this.mapInfo, + this.initialLocation, }); @override Widget build(BuildContext context, WidgetRef ref) { // This provider is used to get information about the map. - final mapNotifier = ref.watch(mapViewModelProvider.notifier); + final mapNotifier = + ref.watch(mapViewModelProvider(initialLocation).notifier); // This provider is used to get the list of map pins. final mapPinsAsync = ref.watch(mapPinViewModelProvider); diff --git a/lib/views/pages/home/content/map/map_screen.dart b/lib/views/pages/home/content/map/map_screen.dart index fdcf42aa..03e7d8c6 100644 --- a/lib/views/pages/home/content/map/map_screen.dart +++ b/lib/views/pages/home/content/map/map_screen.dart @@ -1,4 +1,5 @@ import "package:flutter/material.dart"; +import "package:google_maps_flutter/google_maps_flutter.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/viewmodels/map/map_view_model.dart"; import "package:proxima/views/components/async/circular_value.dart"; @@ -9,7 +10,12 @@ import "package:proxima/views/pages/home/content/map/components/post_map.dart"; /// This widget displays a map with chips to select the type of map. class MapScreen extends ConsumerWidget { - const MapScreen({super.key}); + final LatLng? initialLocation; + + const MapScreen({ + super.key, + this.initialLocation, + }); static const mapScreenKey = Key("mapScreen"); static const dividerKey = Key("divider"); @@ -17,7 +23,8 @@ class MapScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final mapInfo = ref.watch(mapViewModelProvider.future).mapRes(); + final mapInfo = + ref.watch(mapViewModelProvider(initialLocation).future).mapRes(); return CircularValue( future: mapInfo, @@ -29,14 +36,15 @@ class MapScreen extends ConsumerWidget { MapSelectionOptionChips(mapInfo: value), const Divider(key: dividerKey), //TODO: change the map when clicking on a selection option - PostMap(mapInfo: value), + PostMap(mapInfo: value, initialLocation: initialLocation), ], ), ); }, fallbackBuilder: (context, error) { return ErrorRefreshPage( - onRefresh: ref.read(mapViewModelProvider.notifier).refresh, + onRefresh: + ref.read(mapViewModelProvider(initialLocation).notifier).refresh, ); }, ); diff --git a/test/mocks/overrides/override_map_view_model.dart b/test/mocks/overrides/override_map_view_model.dart index c8195bff..292bda90 100644 --- a/test/mocks/overrides/override_map_view_model.dart +++ b/test/mocks/overrides/override_map_view_model.dart @@ -6,9 +6,10 @@ import "package:proxima/models/ui/map_details.dart"; import "package:proxima/viewmodels/map/map_view_model.dart"; /// A mock implementation of the [MapViewModel] class. -class MockMapViewModel extends AutoDisposeAsyncNotifier +class MockMapViewModel + extends AutoDisposeFamilyAsyncNotifier implements MapViewModel { - final Future Function() _build; + final Future Function(LatLng?) _build; final Future Function() _onRefresh; final void Function(GoogleMapController) _onMapCreated; final Set _circles; @@ -20,7 +21,7 @@ class MockMapViewModel extends AutoDisposeAsyncNotifier final Future Function(LatLng) _moveCamera; MockMapViewModel({ - Future Function()? build, + Future Function([LatLng?])? build, Future Function()? onRefresh, Future Function(LatLng)? animateCamera, void Function(GoogleMapController)? onMapCreated, @@ -31,7 +32,8 @@ class MockMapViewModel extends AutoDisposeAsyncNotifier void Function()? enableFollowUser, void Function(LatLng)? moveCamera, }) : _build = build ?? - (() async => throw Exception("Location services are disabled.")), + (([LatLng? arg]) async => + throw Exception("Location services are disabled.")), _onRefresh = onRefresh ?? (() async {}), _onMapCreated = onMapCreated ?? ((_) {}), _circles = circles ?? {}, @@ -42,7 +44,7 @@ class MockMapViewModel extends AutoDisposeAsyncNotifier _moveCamera = animateCamera ?? ((_) async {}); @override - Future build() => _build(); + Future build([LatLng? arg]) => _build(arg); @override Future refresh() => _onRefresh(); diff --git a/test/viewmodels/map/map_view_model_unit_test.dart b/test/viewmodels/map/map_view_model_unit_test.dart index 56403936..18b664da 100644 --- a/test/viewmodels/map/map_view_model_unit_test.dart +++ b/test/viewmodels/map/map_view_model_unit_test.dart @@ -29,7 +29,7 @@ void main() { ], ); - mapViewModel = container.read(mapViewModelProvider.notifier); + mapViewModel = container.read(mapViewModelProvider(null).notifier); }); //test the redraw circle method test("Redraw circle", () { @@ -94,7 +94,7 @@ void main() { await mapViewModel.refresh(); // Verify the state is updated with the new location - final newState = container.read(mapViewModelProvider); + final newState = container.read(mapViewModelProvider(null)); expect(newState, isInstanceOf>()); expect( newState.value!.initialLocation, From 969cbb826a03a901965f7e2c39b194227b00c61b Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 14:57:28 +0200 Subject: [PATCH 15/29] feat(navigation-to-map): add arguments to home navigation --- .../option_selection/home_page_options.dart | 16 ++++++++++++++++ .../selected_page_view_model.dart | 16 +++++++++++----- .../navigation_bar_routes.dart | 11 +++++++++-- .../navigation_bottom_bar.dart | 6 +++--- lib/views/navigation/map_action.dart | 11 ++--------- lib/views/pages/home/home_page.dart | 6 +++--- 6 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 lib/viewmodels/option_selection/home_page_options.dart diff --git a/lib/viewmodels/option_selection/home_page_options.dart b/lib/viewmodels/option_selection/home_page_options.dart new file mode 100644 index 00000000..f1a1dcaf --- /dev/null +++ b/lib/viewmodels/option_selection/home_page_options.dart @@ -0,0 +1,16 @@ +import "package:flutter/material.dart"; +import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; + +class HomePageOptions { + final NavigationBarRoutes route; + final Object? args; + + const HomePageOptions({ + required this.route, + this.args, + }); + + Widget page() { + return route.page(args); + } +} diff --git a/lib/viewmodels/option_selection/selected_page_view_model.dart b/lib/viewmodels/option_selection/selected_page_view_model.dart index 18128f92..30deb691 100644 --- a/lib/viewmodels/option_selection/selected_page_view_model.dart +++ b/lib/viewmodels/option_selection/selected_page_view_model.dart @@ -1,24 +1,30 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:proxima/viewmodels/option_selection/home_page_options.dart"; import "package:proxima/viewmodels/option_selection/options_view_model.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; -class SelectedPageViewModel extends OptionsViewModel { - static const defaultSelectedPage = NavigationBarRoutes.feed; +class SelectedPageViewModel extends OptionsViewModel { + static const defaultSelectedPage = + HomePageOptions(route: NavigationBarRoutes.feed); SelectedPageViewModel() : super(defaultSelectedPage); @override - void setOption(NavigationBarRoutes option) { - if (option.routeDestination != null) { + void setOption(HomePageOptions option) { + if (option.route.routeDestination != null) { throw Exception( "This page should be pushed and not set as the selected page." "Use the navigate method instead."); } super.setOption(option); } + + void navigate(NavigationBarRoutes route, [Object? args]) { + setOption(HomePageOptions(route: route, args: args)); + } } final selectedPageViewModelProvider = - NotifierProvider( + NotifierProvider( () => SelectedPageViewModel(), ); diff --git a/lib/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart b/lib/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart index c23fe2af..be6c0327 100644 --- a/lib/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart +++ b/lib/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart @@ -1,4 +1,5 @@ import "package:flutter/material.dart"; +import "package:google_maps_flutter/google_maps_flutter.dart"; import "package:proxima/views/navigation/routes.dart"; import "package:proxima/views/pages/home/content/challenge/challenge_list.dart"; import "package:proxima/views/pages/home/content/feed/post_feed.dart"; @@ -34,7 +35,7 @@ enum NavigationBarRoutes { this.routeDestination, ); - Widget page() { + Widget page([Object? args]) { if (routeDestination != null) { throw Exception("Route must be pushed."); } @@ -43,7 +44,13 @@ enum NavigationBarRoutes { case feed: return const PostFeed(); case map: - return const MapScreen(); + if (args is LatLng) { + return MapScreen(initialLocation: args); + } else if (args == null) { + return const MapScreen(); + } else { + throw Exception("LatLng object required"); + } case challenge: return const ChallengeList(); case ranking: diff --git a/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart b/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart index e7f17af5..e927e166 100644 --- a/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart +++ b/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart @@ -21,9 +21,9 @@ class NavigationBottomBar extends ConsumerWidget { ); }).toList(); - final selectedRoute = ref.watch(selectedPageViewModelProvider); + final selectedOption = ref.watch(selectedPageViewModelProvider); final selectedRouteIndex = - NavigationBarRoutes.values.indexOf(selectedRoute); + NavigationBarRoutes.values.indexOf(selectedOption.route); return NavigationBar( key: navigationBottomBarKey, @@ -33,7 +33,7 @@ class NavigationBottomBar extends ConsumerWidget { onDestinationSelected: (int nextIndex) { final route = NavigationBarRoutes.values[nextIndex]; if (route.routeDestination == null) { - ref.watch(selectedPageViewModelProvider.notifier).setOption(route); + ref.watch(selectedPageViewModelProvider.notifier).navigate(route); } else { Navigator.pushNamed(context, route.routeDestination!.name); } diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index 1ac68e09..d84b7231 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -49,20 +49,13 @@ class MapAction extends ConsumerWidget { for (var i = 0; i < depth; i++) { Navigator.pop(context); } - ref.watch(selectedPageViewModelProvider.notifier).setOption( + ref.watch(selectedPageViewModelProvider.notifier).navigate( NavigationBarRoutes.map, + initialLocation?.toLatLng(), ); ref.watch(mapSelectionOptionsViewModelProvider.notifier).setOption( mapOption, ); - - if (initialLocation != null) { - await Future.delayed(const Duration(seconds: 1)); - await mapViewModelNotifier.updateCamera( - initialLocation.toLatLng(), - followEvent: false, - ); - } } } diff --git a/lib/views/pages/home/home_page.dart b/lib/views/pages/home/home_page.dart index e324dd52..60cc86d6 100644 --- a/lib/views/pages/home/home_page.dart +++ b/lib/views/pages/home/home_page.dart @@ -1,8 +1,8 @@ import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/viewmodels/login_view_model.dart"; +import "package:proxima/viewmodels/option_selection/home_page_options.dart"; import "package:proxima/viewmodels/option_selection/selected_page_view_model.dart"; -import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart"; import "package:proxima/views/pages/home/home_top_bar/home_top_bar.dart"; @@ -13,13 +13,13 @@ class HomePage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { navigateToLoginPageOnLogout(context, ref); - final NavigationBarRoutes currentPage = + final HomePageOptions currentPage = ref.watch(selectedPageViewModelProvider); return Scaffold( appBar: AppBar( title: HomeTopBar( - labelText: currentPage.pageLabel(), + labelText: currentPage.route.pageLabel(), ), ), bottomNavigationBar: const NavigationBottomBar(), From 431fd88b90da3eebfa8b1290af8e8ee0f4e1099e Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 15:01:33 +0200 Subject: [PATCH 16/29] fix(navigation-to-map): use the same icon for map action and map in bar --- lib/views/navigation/map_action.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index d84b7231..03f5d0d5 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -33,7 +33,7 @@ class MapAction extends ConsumerWidget { initialLocation: initialLocation, ); }, - icon: const Icon(Icons.map), + icon: NavigationBarRoutes.map.icon, ); } From c83eeabbf012015360d76037d8455c24852606f6 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 15:01:58 +0200 Subject: [PATCH 17/29] fix(navigation-to-map): removed unused viewmodel --- lib/views/navigation/map_action.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index 03f5d0d5..e9635cd2 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -2,7 +2,6 @@ import "package:cloud_firestore/cloud_firestore.dart"; import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/utils/extensions/geopoint_extensions.dart"; -import "package:proxima/viewmodels/map/map_view_model.dart"; import "package:proxima/viewmodels/option_selection/map_selection_options_view_model.dart"; import "package:proxima/viewmodels/option_selection/selected_page_view_model.dart"; import "package:proxima/views/components/options/map/map_selection_options.dart"; @@ -44,8 +43,6 @@ class MapAction extends ConsumerWidget { int depth, { GeoPoint? initialLocation, }) async { - final mapViewModelNotifier = ref.read(mapViewModelProvider(null).notifier); - for (var i = 0; i < depth; i++) { Navigator.pop(context); } From cf016cdb87fd99cdbd6052cb13ee840c9dab6377 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 15:12:19 +0200 Subject: [PATCH 18/29] fix(navigation-to-map): no map action in comment popup --- .../info_cards/profile_info_pop_up.dart | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart b/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart index 97337f5b..124b2b84 100644 --- a/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart +++ b/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart @@ -35,16 +35,22 @@ class ProfileInfoPopUp extends StatelessWidget { }, ); - final mapAction = MapAction( - depth: 2, - mapOption: MapSelectionOptions.myPosts, - initialLocation: location, - ); + List actions = [deleteAction]; + if (location != null) { + actions.insert( + 0, + MapAction( + depth: 2, + mapOption: MapSelectionOptions.myPosts, + initialLocation: location, + ), + ); + } return InfoPopUp( title: title, content: content, - actions: [mapAction, deleteAction], + actions: actions, ); } } From 49e66d3ddf99669eee1a541e3554a14c076a297f Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 15:23:07 +0200 Subject: [PATCH 19/29] fix(navigation-to-map): add equality and hash tests --- .../option_selection/selected_page_view_model.dart | 4 ++-- .../options/home}/home_page_options.dart | 14 ++++++++++++++ lib/views/pages/home/home_page.dart | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) rename lib/{viewmodels/option_selection => views/components/options/home}/home_page_options.dart (55%) diff --git a/lib/viewmodels/option_selection/selected_page_view_model.dart b/lib/viewmodels/option_selection/selected_page_view_model.dart index 30deb691..1e91b639 100644 --- a/lib/viewmodels/option_selection/selected_page_view_model.dart +++ b/lib/viewmodels/option_selection/selected_page_view_model.dart @@ -1,6 +1,6 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:proxima/viewmodels/option_selection/home_page_options.dart"; import "package:proxima/viewmodels/option_selection/options_view_model.dart"; +import "package:proxima/views/components/options/home/home_page_options.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; class SelectedPageViewModel extends OptionsViewModel { @@ -14,7 +14,7 @@ class SelectedPageViewModel extends OptionsViewModel { if (option.route.routeDestination != null) { throw Exception( "This page should be pushed and not set as the selected page." - "Use the navigate method instead."); + "push it manually from your context instead."); } super.setOption(option); } diff --git a/lib/viewmodels/option_selection/home_page_options.dart b/lib/views/components/options/home/home_page_options.dart similarity index 55% rename from lib/viewmodels/option_selection/home_page_options.dart rename to lib/views/components/options/home/home_page_options.dart index f1a1dcaf..87515497 100644 --- a/lib/viewmodels/option_selection/home_page_options.dart +++ b/lib/views/components/options/home/home_page_options.dart @@ -13,4 +13,18 @@ class HomePageOptions { Widget page() { return route.page(args); } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is HomePageOptions && + other.route == route && + other.args == args; + } + + @override + int get hashCode { + return Object.hash(route, args); + } } diff --git a/lib/views/pages/home/home_page.dart b/lib/views/pages/home/home_page.dart index 60cc86d6..f1a8e830 100644 --- a/lib/views/pages/home/home_page.dart +++ b/lib/views/pages/home/home_page.dart @@ -1,8 +1,8 @@ import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/viewmodels/login_view_model.dart"; -import "package:proxima/viewmodels/option_selection/home_page_options.dart"; import "package:proxima/viewmodels/option_selection/selected_page_view_model.dart"; +import "package:proxima/views/components/options/home/home_page_options.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart"; import "package:proxima/views/pages/home/home_top_bar/home_top_bar.dart"; From 895a22d6e7612c021bd3cfd284940723b1eec0e5 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 15:25:59 +0200 Subject: [PATCH 20/29] refactor(navigation-to-map): make selected page a details instead Did this since it is not an enum --- .../ui/selected_page_details.dart} | 6 +++--- .../option_selection/selected_page_view_model.dart | 14 +++++++------- .../navigation_bottom_bar.dart | 2 +- lib/views/navigation/map_action.dart | 2 +- lib/views/pages/home/home_page.dart | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) rename lib/{views/components/options/home/home_page_options.dart => models/ui/selected_page_details.dart} (83%) diff --git a/lib/views/components/options/home/home_page_options.dart b/lib/models/ui/selected_page_details.dart similarity index 83% rename from lib/views/components/options/home/home_page_options.dart rename to lib/models/ui/selected_page_details.dart index 87515497..756c69bf 100644 --- a/lib/views/components/options/home/home_page_options.dart +++ b/lib/models/ui/selected_page_details.dart @@ -1,11 +1,11 @@ import "package:flutter/material.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; -class HomePageOptions { +class SelectedPageDetails { final NavigationBarRoutes route; final Object? args; - const HomePageOptions({ + const SelectedPageDetails({ required this.route, this.args, }); @@ -18,7 +18,7 @@ class HomePageOptions { bool operator ==(Object other) { if (identical(this, other)) return true; - return other is HomePageOptions && + return other is SelectedPageDetails && other.route == route && other.args == args; } diff --git a/lib/viewmodels/option_selection/selected_page_view_model.dart b/lib/viewmodels/option_selection/selected_page_view_model.dart index 1e91b639..e0167fab 100644 --- a/lib/viewmodels/option_selection/selected_page_view_model.dart +++ b/lib/viewmodels/option_selection/selected_page_view_model.dart @@ -1,16 +1,16 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:proxima/models/ui/selected_page_details.dart"; import "package:proxima/viewmodels/option_selection/options_view_model.dart"; -import "package:proxima/views/components/options/home/home_page_options.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; -class SelectedPageViewModel extends OptionsViewModel { +class SelectedPageViewModel extends OptionsViewModel { static const defaultSelectedPage = - HomePageOptions(route: NavigationBarRoutes.feed); + SelectedPageDetails(route: NavigationBarRoutes.feed); SelectedPageViewModel() : super(defaultSelectedPage); @override - void setOption(HomePageOptions option) { + void setOption(SelectedPageDetails option) { if (option.route.routeDestination != null) { throw Exception( "This page should be pushed and not set as the selected page." @@ -19,12 +19,12 @@ class SelectedPageViewModel extends OptionsViewModel { super.setOption(option); } - void navigate(NavigationBarRoutes route, [Object? args]) { - setOption(HomePageOptions(route: route, args: args)); + void selectPage(NavigationBarRoutes route, [Object? args]) { + setOption(SelectedPageDetails(route: route, args: args)); } } final selectedPageViewModelProvider = - NotifierProvider( + NotifierProvider( () => SelectedPageViewModel(), ); diff --git a/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart b/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart index e927e166..dd2ee6cf 100644 --- a/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart +++ b/lib/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart @@ -33,7 +33,7 @@ class NavigationBottomBar extends ConsumerWidget { onDestinationSelected: (int nextIndex) { final route = NavigationBarRoutes.values[nextIndex]; if (route.routeDestination == null) { - ref.watch(selectedPageViewModelProvider.notifier).navigate(route); + ref.watch(selectedPageViewModelProvider.notifier).selectPage(route); } else { Navigator.pushNamed(context, route.routeDestination!.name); } diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index e9635cd2..3b18640f 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -46,7 +46,7 @@ class MapAction extends ConsumerWidget { for (var i = 0; i < depth; i++) { Navigator.pop(context); } - ref.watch(selectedPageViewModelProvider.notifier).navigate( + ref.watch(selectedPageViewModelProvider.notifier).selectPage( NavigationBarRoutes.map, initialLocation?.toLatLng(), ); diff --git a/lib/views/pages/home/home_page.dart b/lib/views/pages/home/home_page.dart index f1a8e830..ebcaa82f 100644 --- a/lib/views/pages/home/home_page.dart +++ b/lib/views/pages/home/home_page.dart @@ -1,8 +1,8 @@ import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:proxima/models/ui/selected_page_details.dart"; import "package:proxima/viewmodels/login_view_model.dart"; import "package:proxima/viewmodels/option_selection/selected_page_view_model.dart"; -import "package:proxima/views/components/options/home/home_page_options.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bottom_bar.dart"; import "package:proxima/views/pages/home/home_top_bar/home_top_bar.dart"; @@ -13,7 +13,7 @@ class HomePage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { navigateToLoginPageOnLogout(context, ref); - final HomePageOptions currentPage = + final SelectedPageDetails currentPage = ref.watch(selectedPageViewModelProvider); return Scaffold( From 0c3075fa55a37706adaf8ff35d4455a7c3f6349a Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 15:49:42 +0200 Subject: [PATCH 21/29] test(navigation-to-map): test selected page details --- .../models/ui/selected_page_details_test.dart | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 test/models/ui/selected_page_details_test.dart diff --git a/test/models/ui/selected_page_details_test.dart b/test/models/ui/selected_page_details_test.dart new file mode 100644 index 00000000..1273423e --- /dev/null +++ b/test/models/ui/selected_page_details_test.dart @@ -0,0 +1,43 @@ +import "package:flutter_test/flutter_test.dart"; +import "package:proxima/models/ui/selected_page_details.dart"; +import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; + +import "../../mocks/data/geopoint.dart"; + +void main() { + test("Equality overrides correctly", () { + const feedPageDetails = SelectedPageDetails( + route: NavigationBarRoutes.feed, + ); + + const feedPageDetailsCopy = SelectedPageDetails( + route: NavigationBarRoutes.feed, + ); + + const mapPageDetails = SelectedPageDetails( + route: NavigationBarRoutes.map, + args: userPosition0, + ); + + const mapPageDetailsCopy = SelectedPageDetails( + route: NavigationBarRoutes.map, + args: userPosition0, + ); + + expect(feedPageDetails, equals(feedPageDetailsCopy)); + expect(mapPageDetails, equals(mapPageDetailsCopy)); + expect(feedPageDetails, isNot(equals(mapPageDetails))); + }); + + test("Hash overrides correctly", () { + final expectedHash = Object.hash( + NavigationBarRoutes.feed, + null, + ); + + final actualHash = const SelectedPageDetails( + route: NavigationBarRoutes.feed, + ).hashCode; + expect(actualHash, equals(expectedHash)); + }); +} From a5a6c125617c0beddab9159730033ddd376065f0 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 15:49:59 +0200 Subject: [PATCH 22/29] test(navigation-to-map): test navigation to map from post page --- lib/views/navigation/map_action.dart | 3 +++ .../pages/post/static_post_page_test.dart | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index 3b18640f..0239a37c 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -8,6 +8,8 @@ import "package:proxima/views/components/options/map/map_selection_options.dart" import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; class MapAction extends ConsumerWidget { + static const mapActionKey = Key("MapAction"); + final int depth; final MapSelectionOptions mapOption; @@ -33,6 +35,7 @@ class MapAction extends ConsumerWidget { ); }, icon: NavigationBarRoutes.map.icon, + key: mapActionKey, ); } diff --git a/test/views/pages/post/static_post_page_test.dart b/test/views/pages/post/static_post_page_test.dart index fa831517..de7ce76e 100644 --- a/test/views/pages/post/static_post_page_test.dart +++ b/test/views/pages/post/static_post_page_test.dart @@ -6,9 +6,11 @@ import "package:proxima/views/components/content/user_avatar/user_avatar.dart"; import "package:proxima/views/components/content/user_profile_pop_up.dart"; import "package:proxima/views/components/feedback/centauri_snack_bar.dart"; import "package:proxima/views/navigation/leading_back_button/leading_back_button.dart"; +import "package:proxima/views/navigation/map_action.dart"; import "package:proxima/views/pages/home/content/feed/components/post_card.dart"; import "package:proxima/views/pages/home/content/feed/components/post_card_header.dart"; import "package:proxima/views/pages/home/content/feed/post_feed.dart"; +import "package:proxima/views/pages/home/content/map/map_screen.dart"; import "package:proxima/views/pages/post/components/comment/comment_post_widget.dart"; import "package:proxima/views/pages/post/components/complete_post.dart"; import "package:proxima/views/pages/post/components/new_comment/new_comment_button.dart"; @@ -112,6 +114,26 @@ void main() { testSnackbarNavigation(false); }); + testWidgets("Navigation to the map", (tester) async { + await tester.pumpWidget(nonEmptyHomePageWidget); + await tester.pumpAndSettle(); + + // Tap of the first post + await tester.tap(find.byKey(PostCard.postCardKey).first); + await tester.pumpAndSettle(); + + // Check if the post page is displayed, with the correct title + expect(find.byType(CompletePost), findsOneWidget); + expect(find.text(testPosts.first.title), findsAtLeastNWidgets(1)); + + // tap on the map icon + await tester.tap(find.byKey(MapAction.mapActionKey)); + await tester.pumpAndSettle(); + + // Check if the map is displayed + expect(find.byKey(MapScreen.mapScreenKey), findsOneWidget); + }); + group("Widgets display", () { testWidgets("Check displayed post information", (tester) async { await tester.pumpWidget(emptyPostPageWidget); From e6b1b20a4a1a8952549a3d5651c31ee0043d9208 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 16:17:04 +0200 Subject: [PATCH 23/29] test(navigation-to-map): test map action --- test/views/navigation/map_action_test.dart | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test/views/navigation/map_action_test.dart diff --git a/test/views/navigation/map_action_test.dart b/test/views/navigation/map_action_test.dart new file mode 100644 index 00000000..d944ed57 --- /dev/null +++ b/test/views/navigation/map_action_test.dart @@ -0,0 +1,44 @@ +import "package:flutter/material.dart"; +import "package:flutter_test/flutter_test.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:proxima/models/ui/selected_page_details.dart"; +import "package:proxima/viewmodels/option_selection/selected_page_view_model.dart"; +import "package:proxima/views/components/options/map/map_selection_options.dart"; +import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; +import "package:proxima/views/navigation/map_action.dart"; + +void main() { + late SelectedPageViewModel selectedPageViewModel; + late ProviderScope mapActionWidget; + + setUp(() { + selectedPageViewModel = SelectedPageViewModel(); + + mapActionWidget = ProviderScope( + overrides: [ + selectedPageViewModelProvider.overrideWith(() => selectedPageViewModel), + ], + child: const MaterialApp( + home: MapAction( + depth: 0, + mapOption: MapSelectionOptions.myPosts, + ), + ), + ); + }); + + testWidgets("MapAction shows and navigates correctly", + (WidgetTester tester) async { + await tester.pumpWidget(mapActionWidget); + + expect(find.byKey(MapAction.mapActionKey), findsOneWidget); + + expect(find.byIcon(Icons.place), findsOneWidget); + + await tester.tap(find.byKey(MapAction.mapActionKey)); + await tester.pumpAndSettle(); + + expect(selectedPageViewModel.state, + const SelectedPageDetails(route: NavigationBarRoutes.map)); + }); +} From 4a47e6e0514dcf016e58f244cf38d7ef0c4476a9 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 16:19:20 +0200 Subject: [PATCH 24/29] refactor(navigation-to-map): add comma --- test/views/navigation/map_action_test.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/views/navigation/map_action_test.dart b/test/views/navigation/map_action_test.dart index d944ed57..52492c30 100644 --- a/test/views/navigation/map_action_test.dart +++ b/test/views/navigation/map_action_test.dart @@ -38,7 +38,9 @@ void main() { await tester.tap(find.byKey(MapAction.mapActionKey)); await tester.pumpAndSettle(); - expect(selectedPageViewModel.state, - const SelectedPageDetails(route: NavigationBarRoutes.map)); + expect( + selectedPageViewModel.state, + const SelectedPageDetails(route: NavigationBarRoutes.map), + ); }); } From bc28008c5abbb00d2786b52cd04f462d52e24535 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 16:35:29 +0200 Subject: [PATCH 25/29] refactor(navigation-to-map): make focus location mandatory --- lib/views/navigation/map_action.dart | 16 +++++++++------- .../home/content/challenge/challenge_card.dart | 2 +- .../info_cards/profile_info_pop_up.dart | 2 +- test/views/navigation/map_action_test.dart | 9 ++++++++- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index 0239a37c..aa6ef9c5 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -13,13 +13,13 @@ class MapAction extends ConsumerWidget { final int depth; final MapSelectionOptions mapOption; - final GeoPoint? initialLocation; + final GeoPoint initialLocation; const MapAction({ super.key, required this.depth, required this.mapOption, - this.initialLocation, + required this.initialLocation, }); @override @@ -31,7 +31,7 @@ class MapAction extends ConsumerWidget { context, mapOption, depth, - initialLocation: initialLocation, + initialLocation, ); }, icon: NavigationBarRoutes.map.icon, @@ -39,19 +39,21 @@ class MapAction extends ConsumerWidget { ); } + /// Open the map with the given [mapOption] and [initialLocation]. + /// The [depth] is used to pop the navigation stack back to the root page. static Future openMap( WidgetRef ref, BuildContext context, MapSelectionOptions mapOption, - int depth, { - GeoPoint? initialLocation, - }) async { + int depth, + GeoPoint initialLocation, + ) async { for (var i = 0; i < depth; i++) { Navigator.pop(context); } ref.watch(selectedPageViewModelProvider.notifier).selectPage( NavigationBarRoutes.map, - initialLocation?.toLatLng(), + initialLocation.toLatLng(), ); ref.watch(mapSelectionOptionsViewModelProvider.notifier).setOption( diff --git a/lib/views/pages/home/content/challenge/challenge_card.dart b/lib/views/pages/home/content/challenge/challenge_card.dart index 7462e50e..46eafb22 100644 --- a/lib/views/pages/home/content/challenge/challenge_card.dart +++ b/lib/views/pages/home/content/challenge/challenge_card.dart @@ -60,7 +60,7 @@ class ChallengeCard extends ConsumerWidget { context, MapSelectionOptions.challenges, 0, - initialLocation: challenge.location, + challenge.location, ); }, child: Opacity( diff --git a/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart b/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart index 124b2b84..0407391e 100644 --- a/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart +++ b/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart @@ -42,7 +42,7 @@ class ProfileInfoPopUp extends StatelessWidget { MapAction( depth: 2, mapOption: MapSelectionOptions.myPosts, - initialLocation: location, + initialLocation: location!, ), ); } diff --git a/test/views/navigation/map_action_test.dart b/test/views/navigation/map_action_test.dart index 52492c30..6c65dbdc 100644 --- a/test/views/navigation/map_action_test.dart +++ b/test/views/navigation/map_action_test.dart @@ -2,11 +2,14 @@ import "package:flutter/material.dart"; import "package:flutter_test/flutter_test.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/ui/selected_page_details.dart"; +import "package:proxima/utils/extensions/geopoint_extensions.dart"; import "package:proxima/viewmodels/option_selection/selected_page_view_model.dart"; import "package:proxima/views/components/options/map/map_selection_options.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; import "package:proxima/views/navigation/map_action.dart"; +import "../../mocks/data/geopoint.dart"; + void main() { late SelectedPageViewModel selectedPageViewModel; late ProviderScope mapActionWidget; @@ -22,6 +25,7 @@ void main() { home: MapAction( depth: 0, mapOption: MapSelectionOptions.myPosts, + initialLocation: userPosition0, ), ), ); @@ -40,7 +44,10 @@ void main() { expect( selectedPageViewModel.state, - const SelectedPageDetails(route: NavigationBarRoutes.map), + SelectedPageDetails( + route: NavigationBarRoutes.map, + args: userPosition0.toLatLng(), + ), ); }); } From 3478c8a56991fa73abfb4af614a7279c9ffb9268 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 16:39:41 +0200 Subject: [PATCH 26/29] doc(navigation-to-map): add doc to new methods --- lib/models/ui/selected_page_details.dart | 2 ++ .../option_selection/selected_page_view_model.dart | 6 ++++-- lib/views/navigation/map_action.dart | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/models/ui/selected_page_details.dart b/lib/models/ui/selected_page_details.dart index 756c69bf..46fc0e4b 100644 --- a/lib/models/ui/selected_page_details.dart +++ b/lib/models/ui/selected_page_details.dart @@ -1,6 +1,8 @@ import "package:flutter/material.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; +/// A class that holds the currently open page in the home screen, with optional +/// arguments. class SelectedPageDetails { final NavigationBarRoutes route; final Object? args; diff --git a/lib/viewmodels/option_selection/selected_page_view_model.dart b/lib/viewmodels/option_selection/selected_page_view_model.dart index e0167fab..2093f47a 100644 --- a/lib/viewmodels/option_selection/selected_page_view_model.dart +++ b/lib/viewmodels/option_selection/selected_page_view_model.dart @@ -3,6 +3,9 @@ import "package:proxima/models/ui/selected_page_details.dart"; import "package:proxima/viewmodels/option_selection/options_view_model.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; +/// A view model that holds the currently open page in the home screen. This +/// should never contain a page that does not have a bottom navigation bar. +/// Those should instead be pushed directly to the navigation stack. class SelectedPageViewModel extends OptionsViewModel { static const defaultSelectedPage = SelectedPageDetails(route: NavigationBarRoutes.feed); @@ -13,8 +16,7 @@ class SelectedPageViewModel extends OptionsViewModel { void setOption(SelectedPageDetails option) { if (option.route.routeDestination != null) { throw Exception( - "This page should be pushed and not set as the selected page." - "push it manually from your context instead."); + "This page should be pushed and not set as the selected page, push it directly from your context instead."); } super.setOption(option); } diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index aa6ef9c5..158e46da 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -7,6 +7,10 @@ import "package:proxima/viewmodels/option_selection/selected_page_view_model.dar import "package:proxima/views/components/options/map/map_selection_options.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; +/// An icon that opens the map with the given [mapOption] and [initialLocation] +/// when pressed. The [depth] is how many pop backs should be made to reach the +/// home page. This is useful when the map action is nested in a popup. Shows +/// the map icon. class MapAction extends ConsumerWidget { static const mapActionKey = Key("MapAction"); From fcce0c89c1f759e8b5133a66930dd5a473e336e8 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 16:41:21 +0200 Subject: [PATCH 27/29] doc(navigation-to-map): update map doc --- lib/viewmodels/map/map_view_model.dart | 4 +++- lib/views/pages/home/content/map/components/post_map.dart | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/viewmodels/map/map_view_model.dart b/lib/viewmodels/map/map_view_model.dart index 54c631ec..004b5d50 100644 --- a/lib/viewmodels/map/map_view_model.dart +++ b/lib/viewmodels/map/map_view_model.dart @@ -9,7 +9,9 @@ import "package:proxima/utils/extensions/geopoint_extensions.dart"; import "package:proxima/viewmodels/posts_feed_view_model.dart"; /// This view model is responsible for managing the actual location -/// of the user on the map and the displayed circles. +/// of the user on the map and the displayed circles. Building with a [LatLng] +/// will set the initial location of the map to the given [LatLng]. Otherwise +/// it will use the current location of the user. class MapViewModel extends AutoDisposeFamilyAsyncNotifier { @override Future build([LatLng? arg]) async { diff --git a/lib/views/pages/home/content/map/components/post_map.dart b/lib/views/pages/home/content/map/components/post_map.dart index 5b72f256..843ac53b 100644 --- a/lib/views/pages/home/content/map/components/post_map.dart +++ b/lib/views/pages/home/content/map/components/post_map.dart @@ -9,7 +9,7 @@ import "package:proxima/viewmodels/map/map_view_model.dart"; import "package:proxima/views/components/async/error_alert.dart"; import "package:proxima/views/pages/home/content/map/components/map_pin_pop_up.dart"; -/// This widget displays the Google Map +/// This widget displays the Google Map. Initially centered on initialLocation. class PostMap extends ConsumerWidget { final MapDetails mapInfo; final LatLng? initialLocation; From 7f2b06bc608a461b67b348f8558446793cf54333 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 19:41:48 +0200 Subject: [PATCH 28/29] refactor(navigation-to-map): use LatLng instead of GeoPoint in UI --- lib/models/ui/challenge_details.dart | 6 +++--- lib/models/ui/post_details.dart | 7 ++++--- lib/models/ui/user_post_details.dart | 4 ++-- lib/viewmodels/challenge_view_model.dart | 5 +++-- .../option_selection/selected_page_view_model.dart | 3 ++- lib/viewmodels/user_posts_view_model.dart | 3 ++- lib/views/navigation/map_action.dart | 9 ++++----- .../components/info_cards/profile_info_card.dart | 4 ++-- .../components/info_cards/profile_info_pop_up.dart | 4 ++-- test/mocks/data/challenge_list.dart | 12 ++++++------ test/mocks/data/post_overview.dart | 10 +++++----- test/models/ui/challenge_details_test.dart | 8 ++++---- test/models/ui/user_post_details_test.dart | 12 ++++++------ .../posts_feed_view_model_integration_test.dart | 5 +++-- test/viewmodels/posts_feed_view_model_unit_test.dart | 9 +++++---- test/views/navigation/map_action_test.dart | 3 ++- 16 files changed, 55 insertions(+), 49 deletions(-) diff --git a/lib/models/ui/challenge_details.dart b/lib/models/ui/challenge_details.dart index 6cf76658..330c8b7c 100644 --- a/lib/models/ui/challenge_details.dart +++ b/lib/models/ui/challenge_details.dart @@ -1,12 +1,12 @@ -import "package:cloud_firestore/cloud_firestore.dart"; import "package:flutter/foundation.dart"; +import "package:google_maps_flutter/google_maps_flutter.dart"; /// This class was hard to design. We considered multiple possible implementations, /// and decided to use this one. If we add a parameter requiring to double the number of /// constructors once more, it should be changed. Here are all the possibilities: /// 1) Use one constructor per possibility, storing unused parameter as null internally (it is /// the possibility we chose here). -/// 2) Use a single constructor, asking the developer instanciating it to set the correct +/// 2) Use a single constructor, asking the developer instantiating it to set the correct /// parameters to null (a null distance to finish the challenge, for instance). /// 3) Use a single constructor, but with additional boolean parameters to specify if the /// class is a group challenge or if it is finished. This requires asserts to check that @@ -19,7 +19,7 @@ class ChallengeDetails { final int? distance; final int? timeLeft; final int reward; - final GeoPoint location; + final LatLng location; /// Creates a [ChallengeDetails] with the given parameters. The [title] is the /// post's title, the [distance] is the distance to the challenge in meters, the [timeLeft] diff --git a/lib/models/ui/post_details.dart b/lib/models/ui/post_details.dart index 02892eb6..34e7c568 100644 --- a/lib/models/ui/post_details.dart +++ b/lib/models/ui/post_details.dart @@ -1,10 +1,11 @@ -import "package:cloud_firestore/cloud_firestore.dart"; import "package:flutter/foundation.dart"; import "package:geoflutterfire_plus/geoflutterfire_plus.dart"; +import "package:google_maps_flutter/google_maps_flutter.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/database/user/user_id_firestore.dart"; +import "package:proxima/utils/extensions/geopoint_extensions.dart"; @immutable @@ -22,7 +23,7 @@ class PostDetails { final DateTime publicationDate; final int distance; // in meters final bool isChallenge; - final GeoPoint location; + final LatLng location; const PostDetails({ required this.postId, @@ -102,7 +103,7 @@ class PostDetails { ) * 1000) .round(), - location: postFirestore.location.geoPoint, + location: postFirestore.location.geoPoint.toLatLng(), isChallenge: isChallenge, ); } diff --git a/lib/models/ui/user_post_details.dart b/lib/models/ui/user_post_details.dart index 0e77d3b2..190a4665 100644 --- a/lib/models/ui/user_post_details.dart +++ b/lib/models/ui/user_post_details.dart @@ -1,5 +1,5 @@ -import "package:cloud_firestore/cloud_firestore.dart"; import "package:flutter/foundation.dart"; +import "package:google_maps_flutter/google_maps_flutter.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; /// A post that belongs to the current logged in user. @@ -9,7 +9,7 @@ class UserPostDetails { final PostIdFirestore postId; final String title; final String description; - final GeoPoint location; + final LatLng location; const UserPostDetails({ required this.postId, diff --git a/lib/viewmodels/challenge_view_model.dart b/lib/viewmodels/challenge_view_model.dart index 187f3132..06638315 100644 --- a/lib/viewmodels/challenge_view_model.dart +++ b/lib/viewmodels/challenge_view_model.dart @@ -5,6 +5,7 @@ import "package:proxima/models/ui/challenge_details.dart"; import "package:proxima/services/database/challenge_repository_service.dart"; import "package:proxima/services/database/post_repository_service.dart"; import "package:proxima/services/sensors/geolocation_service.dart"; +import "package:proxima/utils/extensions/geopoint_extensions.dart"; import "package:proxima/viewmodels/dynamic_user_avatar_view_model.dart"; import "package:proxima/viewmodels/login_view_model.dart"; import "package:proxima/viewmodels/map/map_pin_view_model.dart"; @@ -49,14 +50,14 @@ class ChallengeViewModel distance: distanceM, timeLeft: timeLeft.inHours, reward: ChallengeRepositoryService.soloChallengeReward, - location: post.location.geoPoint, + location: post.location.geoPoint.toLatLng(), ); } else { return ChallengeDetails.soloFinished( title: post.data.title, timeLeft: timeLeft.inHours, reward: ChallengeRepositoryService.soloChallengeReward, - location: post.location.geoPoint, + location: post.location.geoPoint.toLatLng(), ); } }); diff --git a/lib/viewmodels/option_selection/selected_page_view_model.dart b/lib/viewmodels/option_selection/selected_page_view_model.dart index 2093f47a..3f590212 100644 --- a/lib/viewmodels/option_selection/selected_page_view_model.dart +++ b/lib/viewmodels/option_selection/selected_page_view_model.dart @@ -16,7 +16,8 @@ class SelectedPageViewModel extends OptionsViewModel { void setOption(SelectedPageDetails option) { if (option.route.routeDestination != null) { throw Exception( - "This page should be pushed and not set as the selected page, push it directly from your context instead."); + "This page should be pushed and not set as the selected page, push it directly from your context instead.", + ); } super.setOption(option); } diff --git a/lib/viewmodels/user_posts_view_model.dart b/lib/viewmodels/user_posts_view_model.dart index 7926ab6c..0e97b9a9 100644 --- a/lib/viewmodels/user_posts_view_model.dart +++ b/lib/viewmodels/user_posts_view_model.dart @@ -3,6 +3,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; import "package:proxima/models/ui/user_post_details.dart"; import "package:proxima/services/database/post_repository_service.dart"; +import "package:proxima/utils/extensions/geopoint_extensions.dart"; import "package:proxima/viewmodels/login_view_model.dart"; import "package:proxima/viewmodels/map/map_pin_view_model.dart"; import "package:proxima/viewmodels/posts_feed_view_model.dart"; @@ -33,7 +34,7 @@ class UserPostsViewModel extends AutoDisposeAsyncNotifier { postId: post.id, title: post.data.title, description: post.data.description, - location: post.location.geoPoint, + location: post.location.geoPoint.toLatLng(), ); return userPost; diff --git a/lib/views/navigation/map_action.dart b/lib/views/navigation/map_action.dart index 158e46da..63986a45 100644 --- a/lib/views/navigation/map_action.dart +++ b/lib/views/navigation/map_action.dart @@ -1,7 +1,6 @@ -import "package:cloud_firestore/cloud_firestore.dart"; import "package:flutter/material.dart"; +import "package:google_maps_flutter/google_maps_flutter.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:proxima/utils/extensions/geopoint_extensions.dart"; import "package:proxima/viewmodels/option_selection/map_selection_options_view_model.dart"; import "package:proxima/viewmodels/option_selection/selected_page_view_model.dart"; import "package:proxima/views/components/options/map/map_selection_options.dart"; @@ -17,7 +16,7 @@ class MapAction extends ConsumerWidget { final int depth; final MapSelectionOptions mapOption; - final GeoPoint initialLocation; + final LatLng initialLocation; const MapAction({ super.key, @@ -50,14 +49,14 @@ class MapAction extends ConsumerWidget { BuildContext context, MapSelectionOptions mapOption, int depth, - GeoPoint initialLocation, + LatLng initialLocation, ) async { for (var i = 0; i < depth; i++) { Navigator.pop(context); } ref.watch(selectedPageViewModelProvider.notifier).selectPage( NavigationBarRoutes.map, - initialLocation.toLatLng(), + initialLocation, ); ref.watch(mapSelectionOptionsViewModelProvider.notifier).setOption( diff --git a/lib/views/pages/profile/components/info_cards/profile_info_card.dart b/lib/views/pages/profile/components/info_cards/profile_info_card.dart index 0367cdb5..33911d51 100644 --- a/lib/views/pages/profile/components/info_cards/profile_info_card.dart +++ b/lib/views/pages/profile/components/info_cards/profile_info_card.dart @@ -1,5 +1,5 @@ -import "package:cloud_firestore/cloud_firestore.dart"; import "package:flutter/material.dart"; +import "package:google_maps_flutter/google_maps_flutter.dart"; import "package:proxima/views/components/async/loading_icon_button.dart"; import "package:proxima/views/helpers/types/future_void_callback.dart"; import "package:proxima/views/pages/profile/components/info_cards/profile_info_pop_up.dart"; @@ -20,7 +20,7 @@ class ProfileInfoCard extends StatelessWidget { final String content; final FutureVoidCallback onDelete; final String? title; - final GeoPoint? location; + final LatLng? location; @override Widget build(BuildContext context) { diff --git a/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart b/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart index 0407391e..8f12ff48 100644 --- a/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart +++ b/lib/views/pages/profile/components/info_cards/profile_info_pop_up.dart @@ -1,5 +1,5 @@ -import "package:cloud_firestore/cloud_firestore.dart"; import "package:flutter/material.dart"; +import "package:google_maps_flutter/google_maps_flutter.dart"; import "package:proxima/views/components/async/loading_icon_button.dart"; import "package:proxima/views/components/content/info_pop_up.dart"; import "package:proxima/views/components/options/map/map_selection_options.dart"; @@ -19,7 +19,7 @@ class ProfileInfoPopUp extends StatelessWidget { }); final String? title; - final GeoPoint? location; + final LatLng? location; final String content; final FutureVoidCallback onDelete; diff --git a/test/mocks/data/challenge_list.dart b/test/mocks/data/challenge_list.dart index b8e9994a..b86bbe17 100644 --- a/test/mocks/data/challenge_list.dart +++ b/test/mocks/data/challenge_list.dart @@ -1,6 +1,6 @@ import "package:proxima/models/ui/challenge_details.dart"; -import "geopoint.dart"; +import "latlng.dart"; // All values are purposely different to test the UI more easily @@ -10,7 +10,7 @@ const mockChallengeList = [ distance: 700, timeLeft: 27, reward: 250, - location: userPosition0, + location: latLngLocation0, ), ChallengeDetails.solo( title: @@ -18,23 +18,23 @@ const mockChallengeList = [ distance: 3200, timeLeft: 28, reward: 400, - location: userPosition0, + location: latLngLocation0, ), ChallengeDetails.group( title: "I'm moving out", distance: 4000, reward: 350, - location: userPosition0, + location: latLngLocation0, ), ChallengeDetails.soloFinished( title: "What a view!", timeLeft: 29, reward: 200, - location: userPosition0, + location: latLngLocation0, ), ChallengeDetails.groupFinished( title: "I saw a bird here once", reward: 50, - location: userPosition0, + location: latLngLocation0, ), ]; diff --git a/test/mocks/data/post_overview.dart b/test/mocks/data/post_overview.dart index c91e1e06..37f7dc70 100644 --- a/test/mocks/data/post_overview.dart +++ b/test/mocks/data/post_overview.dart @@ -3,7 +3,7 @@ import "package:proxima/models/database/user/user_id_firestore.dart"; import "package:proxima/models/ui/post_details.dart"; import "datetime.dart"; -import "geopoint.dart"; +import "latlng.dart"; final List testPosts = [ PostDetails( @@ -20,7 +20,7 @@ final List testPosts = [ ownerCentauriPoints: 32, publicationDate: publicationDate1, distance: 20, - location: userPosition1, + location: latLngLocation0, ), PostDetails( postId: const PostIdFirestore(value: "post_2"), @@ -36,7 +36,7 @@ final List testPosts = [ publicationDate: publicationDate1, distance: 20, isChallenge: true, - location: userPosition1, + location: latLngLocation0, ), PostDetails( postId: const PostIdFirestore(value: "post_3"), @@ -50,7 +50,7 @@ final List testPosts = [ ownerCentauriPoints: 218281828, publicationDate: publicationDate1, distance: 20, - location: userPosition1, + location: latLngLocation0, ), ]; @@ -67,5 +67,5 @@ final timeDistancePost = PostDetails( ownerCentauriPoints: 911, publicationDate: DateTime.utc(1999), distance: 100, - location: userPosition1, + location: latLngLocation0, ); diff --git a/test/models/ui/challenge_details_test.dart b/test/models/ui/challenge_details_test.dart index ca8d4e74..487f8505 100644 --- a/test/models/ui/challenge_details_test.dart +++ b/test/models/ui/challenge_details_test.dart @@ -1,7 +1,7 @@ import "package:flutter_test/flutter_test.dart"; import "package:proxima/models/ui/challenge_details.dart"; -import "../../mocks/data/geopoint.dart"; +import "../../mocks/data/latlng.dart"; void main() { group("Challenge card data testing", () { @@ -10,7 +10,7 @@ void main() { title: "title", distance: 50, reward: 100, - location: userPosition0, + location: latLngLocation0, ); final expectedHash = Object.hash( @@ -31,13 +31,13 @@ void main() { title: "title", distance: 50, reward: 100, - location: userPosition0, + location: latLngLocation0, ); const challengeCardDataCopy = ChallengeDetails.group( title: "title", distance: 50, reward: 100, - location: userPosition0, + location: latLngLocation0, ); expect(challengeCardData, challengeCardDataCopy); diff --git a/test/models/ui/user_post_details_test.dart b/test/models/ui/user_post_details_test.dart index a1b5318a..2fab09e1 100644 --- a/test/models/ui/user_post_details_test.dart +++ b/test/models/ui/user_post_details_test.dart @@ -2,7 +2,7 @@ import "package:flutter_test/flutter_test.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; import "package:proxima/models/ui/user_post_details.dart"; -import "../../mocks/data/geopoint.dart"; +import "../../mocks/data/latlng.dart"; void main() { group("User Post model testing", () { @@ -11,7 +11,7 @@ void main() { postId: PostIdFirestore(value: "post_1"), title: "title", description: "description", - location: userPosition0, + location: latLngLocation0, ); final expectedHash = Object.hash( @@ -31,14 +31,14 @@ void main() { postId: PostIdFirestore(value: "post_1"), title: "title", description: "description", - location: userPosition0, + location: latLngLocation0, ); const userPostCopy = UserPostDetails( postId: PostIdFirestore(value: "post_1"), title: "title", description: "description", - location: userPosition0, + location: latLngLocation0, ); expect(userPost, userPostCopy); @@ -49,14 +49,14 @@ void main() { postId: PostIdFirestore(value: "post_1"), title: "title", description: "description 1", - location: userPosition0, + location: latLngLocation0, ); const userPost2 = UserPostDetails( postId: PostIdFirestore(value: "post_1"), title: "title", description: "description 2", - location: userPosition0, + location: latLngLocation0, ); expect(userPost1 != userPost2, true); diff --git a/test/viewmodels/posts_feed_view_model_integration_test.dart b/test/viewmodels/posts_feed_view_model_integration_test.dart index 4fb231f8..ca5858e0 100644 --- a/test/viewmodels/posts_feed_view_model_integration_test.dart +++ b/test/viewmodels/posts_feed_view_model_integration_test.dart @@ -15,6 +15,7 @@ import "package:test/test.dart"; import "../mocks/data/firestore_user.dart"; import "../mocks/data/geopoint.dart"; +import "../mocks/data/latlng.dart"; import "../mocks/data/post_data.dart"; import "../mocks/services/mock_geo_location_service.dart"; @@ -126,7 +127,7 @@ void main() { .distanceBetweenInKm(geopoint: postPosition) * 1000) .round(), - location: userPosition1, + location: latLngLocation0, ), ]; @@ -227,7 +228,7 @@ void main() { .distanceBetweenInKm(geopoint: postPositions[index]) * 1000) .round(), - location: userPosition1, + location: latLngLocation0, ); return postDetails; diff --git a/test/viewmodels/posts_feed_view_model_unit_test.dart b/test/viewmodels/posts_feed_view_model_unit_test.dart index 75a96179..01e1e861 100644 --- a/test/viewmodels/posts_feed_view_model_unit_test.dart +++ b/test/viewmodels/posts_feed_view_model_unit_test.dart @@ -18,6 +18,7 @@ import "../mocks/data/firestore_challenge.dart"; import "../mocks/data/firestore_post.dart"; import "../mocks/data/firestore_user.dart"; import "../mocks/data/geopoint.dart"; +import "../mocks/data/latlng.dart"; import "../mocks/data/post_data.dart"; import "../mocks/services/mock_challenge_repository_service.dart"; import "../mocks/services/mock_geo_location_service.dart"; @@ -122,7 +123,7 @@ void main() { ownerCentauriPoints: owner.data.centauriPoints, publicationDate: post.data.publicationTime.toDate(), distance: 0, - location: userPosition1, + location: latLngLocation0, ), ]; @@ -182,7 +183,7 @@ void main() { ownerCentauriPoints: owner.data.centauriPoints, publicationDate: post.data.publicationTime.toDate(), distance: 0, - location: userPosition1, + location: latLngLocation0, ); return postDetails; @@ -250,7 +251,7 @@ void main() { ownerCentauriPoints: owners[index].data.centauriPoints, publicationDate: post.data.publicationTime.toDate(), distance: 0, - location: userPosition1, + location: latLngLocation0, ); return postDetails; @@ -331,7 +332,7 @@ void main() { ownerCentauriPoints: owner.data.centauriPoints, publicationDate: post.data.publicationTime.toDate(), distance: 0, - location: userPosition1, + location: latLngLocation0, ), ]; diff --git a/test/views/navigation/map_action_test.dart b/test/views/navigation/map_action_test.dart index 6c65dbdc..4ec38430 100644 --- a/test/views/navigation/map_action_test.dart +++ b/test/views/navigation/map_action_test.dart @@ -9,6 +9,7 @@ import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_ro import "package:proxima/views/navigation/map_action.dart"; import "../../mocks/data/geopoint.dart"; +import "../../mocks/data/latlng.dart"; void main() { late SelectedPageViewModel selectedPageViewModel; @@ -25,7 +26,7 @@ void main() { home: MapAction( depth: 0, mapOption: MapSelectionOptions.myPosts, - initialLocation: userPosition0, + initialLocation: latLngLocation0, ), ), ); From dfe883957adb8f24c8fd21a1133a980677ab8fe9 Mon Sep 17 00:00:00 2001 From: Alberts Reisons Date: Wed, 29 May 2024 20:22:34 +0200 Subject: [PATCH 29/29] refactor(navigation-to-map): remove old TODO --- lib/views/pages/home/content/map/map_screen.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/views/pages/home/content/map/map_screen.dart b/lib/views/pages/home/content/map/map_screen.dart index 03e7d8c6..af87e286 100644 --- a/lib/views/pages/home/content/map/map_screen.dart +++ b/lib/views/pages/home/content/map/map_screen.dart @@ -35,7 +35,6 @@ class MapScreen extends ConsumerWidget { children: [ MapSelectionOptionChips(mapInfo: value), const Divider(key: dividerKey), - //TODO: change the map when clicking on a selection option PostMap(mapInfo: value, initialLocation: initialLocation), ], ),