diff --git a/lib/viewmodels/map/map_pin_view_model.dart b/lib/viewmodels/map/map_pin_view_model.dart index 430c35ed..eade329e 100644 --- a/lib/viewmodels/map/map_pin_view_model.dart +++ b/lib/viewmodels/map/map_pin_view_model.dart @@ -47,6 +47,21 @@ class MapPinViewModel extends AutoDisposeAsyncNotifier> { /// Get nearby posts Future> _getNearbyPosts() async { + //checking if the location services are enabled directly here so that + //we throw the same exception as the challenge case + final locationCheck = + await ref.read(geolocationServiceProvider).checkLocationServices(); + + if (locationCheck != null) { + throw locationCheck; + } + + try { + await ref.watch(livePositionStreamProvider.future); + } catch (e) { + ref.invalidate(livePositionStreamProvider); + } + final position = await ref.watch(livePositionStreamProvider.future); final postRepository = ref.watch(postRepositoryServiceProvider); final userRepository = ref.watch(userRepositoryServiceProvider); @@ -106,14 +121,15 @@ class MapPinViewModel extends AutoDisposeAsyncNotifier> { /// Get user active challenges Future> _getUserChallenges() async { - final postRepository = ref.watch(postRepositoryServiceProvider); - final challengeRepostory = ref.watch(challengeRepositoryServiceProvider); - final userId = ref.watch(validLoggedInUserIdProvider); // Only doing a read here, to decrease the number of database reads // (we don't want to re-read the challenges when the position changes). final position = await ref.read(geolocationServiceProvider).getCurrentPosition(); + final postRepository = ref.watch(postRepositoryServiceProvider); + final challengeRepostory = ref.watch(challengeRepositoryServiceProvider); + final userId = ref.watch(validLoggedInUserIdProvider); + final userChallenges = await challengeRepostory.getChallenges( userId, position, diff --git a/lib/views/components/options/map/map_selection_option_chips.dart b/lib/views/components/options/map/map_selection_option_chips.dart index 8a8d8f24..8d889256 100644 --- a/lib/views/components/options/map/map_selection_option_chips.dart +++ b/lib/views/components/options/map/map_selection_option_chips.dart @@ -1,6 +1,5 @@ import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:proxima/models/ui/map_details.dart"; import "package:proxima/viewmodels/option_selection/map_selection_options_view_model.dart"; import "package:proxima/views/components/options/map/map_selection_options.dart"; @@ -13,12 +12,9 @@ class MapSelectionOptionChips extends ConsumerWidget { ); const MapSelectionOptionChips({ - required this.mapInfo, super.key, }); - final MapDetails mapInfo; - @override Widget build(BuildContext context, WidgetRef ref) { final currentOption = ref.watch(mapSelectionOptionsViewModelProvider); 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 843ac53b..171c142a 100644 --- a/lib/views/pages/home/content/map/components/post_map.dart +++ b/lib/views/pages/home/content/map/components/post_map.dart @@ -1,4 +1,5 @@ import "package:flutter/material.dart"; +import "package:geolocator/geolocator.dart"; import "package:google_maps_flutter/google_maps_flutter.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/ui/map_details.dart"; @@ -67,11 +68,15 @@ class PostMap extends ConsumerWidget { }, error: (error, _) { //Pop up an error dialog if an error occurs - final dialog = ErrorAlert(error: error); - WidgetsBinding.instance.addPostFrameCallback((timestamp) { - showDialog(context: context, builder: dialog.build); - }); + //ignore the location service disabled exception + //because we are already handling it in the mapPinViewModel + if (error is! LocationServiceDisabledException) { + final dialog = ErrorAlert(error: error); + WidgetsBinding.instance.addPostFrameCallback((timestamp) { + showDialog(context: context, builder: dialog.build); + }); + } }, loading: () => (), ); diff --git a/lib/views/pages/home/content/map/map_screen.dart b/lib/views/pages/home/content/map/map_screen.dart index af87e286..ef32d156 100644 --- a/lib/views/pages/home/content/map/map_screen.dart +++ b/lib/views/pages/home/content/map/map_screen.dart @@ -33,7 +33,7 @@ class MapScreen extends ConsumerWidget { key: mapScreenKey, body: Column( children: [ - MapSelectionOptionChips(mapInfo: value), + const MapSelectionOptionChips(), const Divider(key: dividerKey), PostMap(mapInfo: value, initialLocation: initialLocation), ], diff --git a/test/end2end/app_test.dart b/test/end2end/app_test.dart index a2e3edfb..ed3ea66f 100644 --- a/test/end2end/app_test.dart +++ b/test/end2end/app_test.dart @@ -44,6 +44,9 @@ void main() { when(geoLocationService.getPositionStream()).thenAnswer( (_) => Stream.value(startLocation), ); + when(geoLocationService.checkLocationServices()).thenAnswer( + (_) => Future.value(null), + ); firestorePostGenerator = FirestorePostGenerator(); }); diff --git a/test/mocks/services/mock_geo_location_service.dart b/test/mocks/services/mock_geo_location_service.dart index a209b7e3..66e8943a 100644 --- a/test/mocks/services/mock_geo_location_service.dart +++ b/test/mocks/services/mock_geo_location_service.dart @@ -21,4 +21,12 @@ class MockGeolocationService extends Mock implements GeolocationService { returnValue: Stream.value(userPosition0), ); } + + @override + Future checkLocationServices() { + return super.noSuchMethod( + Invocation.method(#checkLocationServices, []), + returnValue: Future.value(null), + ); + } } diff --git a/test/views/pages/map/dynamic_map_test.dart b/test/views/pages/map/dynamic_map_test.dart index 0dff324e..a245da53 100644 --- a/test/views/pages/map/dynamic_map_test.dart +++ b/test/views/pages/map/dynamic_map_test.dart @@ -54,6 +54,9 @@ void main() { when(geoLocationService.getPositionStream()).thenAnswer( (_) => Stream.value(userPosition0), ); + when(geoLocationService.checkLocationServices()).thenAnswer( + (_) => Future.value(null), + ); postGenerator = FirestorePostGenerator(); }); @@ -476,4 +479,89 @@ void main() { verifyResult: null, ); }); + + group("location errors", () { + testWidgets("one error pop up occurs", (tester) async { + //make the location services fail + + when(geoLocationService.checkLocationServices()).thenAnswer( + (_) => Future.value(Exception("Location services not enabled")), + ); + + await beginTest(tester); + + //find an dialog with the error message + final errorPopUp = find.byType(Dialog); + + expect(errorPopUp, findsExactly(1)); + + //find the error message + final errorText = find.text("Exception: Location services not enabled"); + expect(errorText, findsOneWidget); + }); + + testWidgets("no error with user posts", (tester) async { + await beginTest(tester); + + //disable the location services + when(geoLocationService.checkLocationServices()).thenAnswer( + (_) => Future.value(Exception("Location services not enabled")), + ); + + //click on user posts tab + final chip = find.byKey( + MapSelectionOptionChips.optionChipKeys[MapSelectionOptions.myPosts]!, + ); + expect(chip, findsOneWidget); + await tester.tap(chip); + await tester.pumpAndSettle(); + + //no error dialog should appear + final errorPopUp = find.byType(Dialog); + expect(errorPopUp, findsNothing); + }); + + testWidgets("no errors when location services are re-enabled", + (tester) async { + //disable the location services + when(geoLocationService.checkLocationServices()).thenAnswer( + (_) => Future.value(Exception("Location services not enabled")), + ); + + await beginTest(tester); + + //expect an error dialog + final errorPopUp = find.byType(Dialog); + expect(errorPopUp, findsExactly(1)); + + //make the Dialog disappear by tapping next to it + await tester.tapAt(const Offset(0, 0)); // Top-left corner of the screen + await tester.pumpAndSettle(); + + //click on the user posts tab + final chip = find.byKey( + MapSelectionOptionChips.optionChipKeys[MapSelectionOptions.myPosts]!, + ); + expect(chip, findsOneWidget); + await tester.tap(chip); + await tester.pumpAndSettle(); + + //re-enable the location services + when(geoLocationService.checkLocationServices()).thenAnswer( + (_) => Future.value(null), + ); + + //click on the nearby posts tab + final nearbyChip = find.byKey( + MapSelectionOptionChips.optionChipKeys[MapSelectionOptions.nearby]!, + ); + expect(nearbyChip, findsOneWidget); + await tester.tap(nearbyChip); + await tester.pumpAndSettle(); + + //expect no error dialog + final errorPopUpAfter = find.byType(Dialog); + expect(errorPopUpAfter, findsNothing); + }); + }); }