diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index 7611fae2..5b2cee48 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -461,4 +461,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 9bf7626a50066852dd21123c4db8e9fe30622485 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/app/ios/Runner.xcodeproj/project.pbxproj b/app/ios/Runner.xcodeproj/project.pbxproj index effe8ba4..9cf03eb0 100644 --- a/app/ios/Runner.xcodeproj/project.pbxproj +++ b/app/ios/Runner.xcodeproj/project.pbxproj @@ -19,7 +19,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - 9A5FB9BC2D005B6600F14D83 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5FB9BB2D005B5F00F14D83 /* LocationManager.swift */; }; + 9A95678A2D117BD100F43643 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9567892D117BD000F43643 /* LocationManager.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -71,7 +71,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 9A5FB9BB2D005B5F00F14D83 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; }; + 9A9567892D117BD000F43643 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; }; B09B463B48F6F6C17BEDC2CB /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -138,7 +138,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 9A5FB9BB2D005B5F00F14D83 /* LocationManager.swift */, + 9A9567892D117BD000F43643 /* LocationManager.swift */, 63CBD3CD2C5A08DC00283B36 /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, @@ -431,10 +431,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 9A5FB9BC2D005B6600F14D83 /* LocationManager.swift in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 6371AA752C7D958A00C5843A /* GeofencePlugin.swift in Sources */, + 9A95678A2D117BD100F43643 /* LocationManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/app/ios/Runner/AppDelegate.swift b/app/ios/Runner/AppDelegate.swift index 313ce543..bd10704c 100644 --- a/app/ios/Runner/AppDelegate.swift +++ b/app/ios/Runner/AppDelegate.swift @@ -28,26 +28,19 @@ import Combine geofencePluginRegistration() registerLocationChannel() - let locationsHandler = LocationsHandler.shared - if locationsHandler.updatesStarted { - locationsHandler.startLocationUpdates() + let liveLocationUpdates = LocationManager.shared + if liveLocationUpdates.updatesStarted { + liveLocationUpdates.startLocationUpdates() } - - locationsHandler.$lastLocation.sink(receiveValue: { [weak self] location in - let locationData: [String: Any] = [ - "latitude": location.coordinate.latitude, - "longitude": location.coordinate.longitude, - "timestamp": location.timestamp.timeIntervalSince1970 * 1000, - ] + if liveLocationUpdates.bgActivitySessionStarted { + liveLocationUpdates.bgActivitySessionStarted = true + } - guard let self = self else { return } - if let controller = window?.rootViewController as? FlutterViewController { - let methodChannel = FlutterMethodChannel(name: "com.grouptrack/location", binaryMessenger: controller.binaryMessenger) - methodChannel.invokeMethod("onLocationUpdate", arguments: locationData) - } + liveLocationUpdates.$lastLocation.sink(receiveValue: { [weak self] location in + self?.sendLocationToFlutter(location: location) }).store(in: &cancellables) - + GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } @@ -67,12 +60,29 @@ import Combine (call: FlutterMethodCall, result: @escaping FlutterResult) in if call.method == "getCurrentLocation" { - LocationsHandler.shared.getCurrentLocation(result: result) + LocationManager.shared.getCurrentLocation(result: result) } else { result(FlutterMethodNotImplemented) } } } + + func sendLocationToFlutter(location: CLLocation) { + + let locationData: [String: Any] = [ + "latitude": location.coordinate.latitude, + "longitude": location.coordinate.longitude, + "timestamp": location.timestamp.timeIntervalSince1970 * 1000, + ] + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + if let controller = self.window?.rootViewController as? FlutterViewController { + let methodChannel = FlutterMethodChannel(name: "com.grouptrack/location", binaryMessenger: controller.binaryMessenger) + methodChannel.invokeMethod("onLocationUpdate", arguments: locationData) + } + } + } } // Geofence methods @@ -87,18 +97,17 @@ extension AppDelegate { (call: FlutterMethodCall, result: @escaping FlutterResult) in guard self != nil else { return } if call.method == "startTracking" { - LocationsHandler.shared.startLocationUpdates() + LocationManager.shared.startLocationUpdates() result(true) } else if call.method == "stopTracking" { - LocationsHandler.shared.stopLocationUpdates() + LocationManager.shared.stopLocationUpdates() result(true) } else { result(FlutterMethodNotImplemented) } } } - - + private func geofencePluginRegistration() { let controller: FlutterViewController = window?.rootViewController as! FlutterViewController diff --git a/app/ios/Runner/LocationManager.swift b/app/ios/Runner/LocationManager.swift index 1b1271f7..e4598a59 100644 --- a/app/ios/Runner/LocationManager.swift +++ b/app/ios/Runner/LocationManager.swift @@ -1,18 +1,23 @@ // -// LocationManager.swift +// UnifiedLocationManager.swift // Runner // -// Created by Radhika S on 04/12/24. +// Created by Radhika S on 17/12/24. // + import CoreLocation +import Combine -class LocationsHandler: NSObject, ObservableObject, CLLocationManagerDelegate { - static let shared = LocationsHandler() - private let manager: CLLocationManager +class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate { + static let shared = LocationManager() - @Published var lastLocation = CLLocation() + private var manager: CLLocationManager + private var bgActivitySession: Any? + private let distanceThreshold: CLLocationDistance = 10.0 + + @Published var lastLocation = CLLocation() @Published var updatesStarted: Bool = UserDefaults.standard.bool(forKey: "liveLocationUpdatesStarted") { didSet { @@ -20,27 +25,90 @@ class LocationsHandler: NSObject, ObservableObject, CLLocationManagerDelegate { } } + @Published + var bgActivitySessionStarted: Bool = UserDefaults.standard.bool(forKey: "BGActivitySessionStarted") { + didSet { + if #available(iOS 17.0, *) { + bgActivitySessionStarted ? self.bgActivitySession = CLBackgroundActivitySession(): (self.bgActivitySession as? CLBackgroundActivitySession)?.invalidate() + } + UserDefaults.standard.set(bgActivitySessionStarted, forKey: "BGActivitySessionStarted") + } + } + private override init() { self.manager = CLLocationManager() super.init() - manager.delegate = self - manager.distanceFilter = 10 - manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation - manager.allowsBackgroundLocationUpdates = true - manager.pausesLocationUpdatesAutomatically = false + if #unavailable(iOS 17.0) { + manager.delegate = self + manager.distanceFilter = 10 + manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation + manager.allowsBackgroundLocationUpdates = true + manager.pausesLocationUpdatesAutomatically = false + } } func startLocationUpdates() { - if self.manager.authorizationStatus == .notDetermined { + guard updatesStarted else { return } + if manager.authorizationStatus == .notDetermined { return } updatesStarted = true - manager.startUpdatingLocation() + if #available(iOS 17.0, *) { + startLiveLocationUpdates() + } else { + manager.startUpdatingLocation() + } + } func stopLocationUpdates() { - manager.stopUpdatingLocation() - updatesStarted = false + self.updatesStarted = false + if #available(iOS 17.0, *) { + (bgActivitySession as? CLBackgroundActivitySession)?.invalidate() + bgActivitySessionStarted = false + bgActivitySession = nil + } else { + manager.stopUpdatingLocation() + } + } + + @available(iOS 17.0, *) + private func startLiveLocationUpdates() { + + Task { + do { + self.bgActivitySession = CLBackgroundActivitySession() + + let locationUpdates = CLLocationUpdate.liveUpdates() + .filter { [weak self] update in + guard let self = self else { return false } + let distanceMoved = (update.location?.distance(from: lastLocation) ?? 0.0) + let hasLastLocation = lastLocation.coordinate.latitude != 0 && lastLocation.coordinate.longitude != 0 + + return distanceMoved >= distanceThreshold || !hasLastLocation + } + + for try await update in locationUpdates { + if !self.updatesStarted { break } + if let currentLocation = update.location { + onLocationChanged(location: currentLocation) + if update.isStationary { break } + } + + } + } catch { + print("Error with live updates: \(error.localizedDescription)") + } + } + } + + private func onLocationChanged(location: CLLocation) { + let lastUpdateTime = lastLocation.timestamp + let hasLastLocation = lastLocation.coordinate.latitude != 0 && lastLocation.coordinate.longitude != 0 + let timeInterval = Date().timeIntervalSince(lastUpdateTime) + if (timeInterval >= 10 || !hasLastLocation) && lastLocation != location { + lastLocation = location + } } func locationManagerDidChangeAuthorization(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { @@ -55,15 +123,7 @@ class LocationsHandler: NSObject, ObservableObject, CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { guard let currentLocation = locations.last else { return } - - let lastUpdateTime = lastLocation.timestamp - - let timeInterval = Date().timeIntervalSince(lastUpdateTime) - if timeInterval < 10 { - return - } - - lastLocation = currentLocation + onLocationChanged(location: currentLocation) } func getCurrentLocation(result: @escaping FlutterResult) { diff --git a/app/lib/ui/flow/home/map/map_view_model.dart b/app/lib/ui/flow/home/map/map_view_model.dart index 0c84e02d..29944fc6 100644 --- a/app/lib/ui/flow/home/map/map_view_model.dart +++ b/app/lib/ui/flow/home/map/map_view_model.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'dart:ui' as ui; import 'package:data/api/auth/auth_models.dart'; @@ -17,7 +16,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:geolocator/geolocator.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:image/image.dart' as img; @@ -54,16 +52,16 @@ class MapViewNotifier extends StateNotifier { StreamSubscription>? _placeSubscription; MapViewNotifier( - this._currentUser, - this.spaceService, - this.placeService, - this.permissionService, - this.locationManager, - this.authService, - this.mapTypeController, + this._currentUser, + this.spaceService, + this.placeService, + this.permissionService, + this.locationManager, + this.authService, + this.mapTypeController, ) : super(MapViewState(mapType: mapTypeController.state)) { checkUserPermission(); - _getCurrentUserLastLocation(); + getUserLastLocation(); } void loadData(String? spaceId) { @@ -89,8 +87,9 @@ class MapViewNotifier extends StateNotifier { if (state.loading) return; try { state = state.copyWith(loading: true, selectedUser: null); - await _userInfoSubscription?.cancel(); - _userInfoSubscription = spaceService.getMemberWithLocation(spaceId).listen((userInfo) { + _userInfoSubscription?.cancel(); + _userInfoSubscription = + spaceService.getMemberWithLocation(spaceId).listen((userInfo) { state = state.copyWith(userInfo: userInfo, loading: false); _userMapPositions(userInfo); }); @@ -106,8 +105,9 @@ class MapViewNotifier extends StateNotifier { void _listenPlaces(String spaceId) async { try { - await _placeSubscription?.cancel(); - _placeSubscription = placeService.getAllPlacesStream(spaceId).listen((places) { + _placeSubscription?.cancel(); + _placeSubscription = + placeService.getAllPlacesStream(spaceId).listen((places) { state = state.copyWith(places: places); }); } catch (error, stack) { @@ -161,8 +161,8 @@ class MapViewNotifier extends StateNotifier { if (image != null) { final resizedImage = img.copyResize( image, - width: (markerSize/1.25).toInt(), - height: (markerSize/1.25).toInt(), + width: (markerSize / 1.25).toInt(), + height: (markerSize / 1.25).toInt(), ); final circularImage = img.copyCropCircle(resizedImage); @@ -325,27 +325,6 @@ class MapViewNotifier extends StateNotifier { } } - void _getCurrentUserLastLocation() async { - try { - final location = await _currentUserLocation(); - state = state.copyWith(currentUserLocation: LatLng(location.latitude, location.longitude)); - } catch (error, stack) { - logger.e('MapViewNotifier: error while get current location', - error: error, stackTrace: stack); - } - } - - Future _currentUserLocation() async { - if (Platform.isIOS) { - const platform = MethodChannel('com.grouptrack/current_location'); - final locationFromIOS = await platform.invokeMethod('getCurrentLocation'); - return LatLng(locationFromIOS['latitude'], locationFromIOS['longitude']); - } else { - var location = await Geolocator.getCurrentPosition(); - return LatLng(location.latitude, location.longitude); - } - } - void setMapType(String type) { mapTypeController.state = type; state = state.copyWith(mapType: type); diff --git a/app/lib/ui/flow/journey/timeline/journey_timeline_view_model.dart b/app/lib/ui/flow/journey/timeline/journey_timeline_view_model.dart index 01d5dfb9..f50aaf9c 100644 --- a/app/lib/ui/flow/journey/timeline/journey_timeline_view_model.dart +++ b/app/lib/ui/flow/journey/timeline/journey_timeline_view_model.dart @@ -118,7 +118,7 @@ class JourneyTimelineViewModel extends StateNotifier { } void loadMoreJourney() async { - if (state.hasMore && !state.appending) { + if (state.hasMore && !state.appending && !state.isLoading) { await Future.delayed(const Duration(milliseconds: 200)); _loadJourney(loadMore: true); } diff --git a/app/lib/ui/flow/permission/enable_permission_view.dart b/app/lib/ui/flow/permission/enable_permission_view.dart index 7719bf4f..3b61942d 100644 --- a/app/lib/ui/flow/permission/enable_permission_view.dart +++ b/app/lib/ui/flow/permission/enable_permission_view.dart @@ -73,22 +73,22 @@ class _EnablePermissionViewState extends ConsumerState _permissionView( title: context.l10n.enable_location_access_title, subTitle: context.l10n.enable_location_access_sub_title, - buttonValue: state.isLocationGranted, - onTapRadio: notifier.requestLocationPermission, + isChecked: state.isLocationGranted, + onChanged: notifier.requestLocationPermission, ), const SizedBox(height: 24), _permissionView( title: context.l10n.enable_background_location_access_title, subTitle: context.l10n.enable_background_location_access_sub_title, - buttonValue: state.isBackGroundLocationGranted, - onTapRadio: notifier.requestBackgroundLocationPermission, + isChecked: state.isBackGroundLocationGranted, + onChanged: notifier.requestBackgroundLocationPermission, ), const SizedBox(height: 24), _permissionView( title: context.l10n.enable_notification_access_title, subTitle: context.l10n.enable_notification_access_sun_title, - buttonValue: state.isNotificationGranted, - onTapRadio: notifier.requestNotificationPermission, + isChecked: state.isNotificationGranted, + onChanged: notifier.requestNotificationPermission, ), const SizedBox(height: 64), ], @@ -112,8 +112,8 @@ class _EnablePermissionViewState extends ConsumerState Widget _permissionView({ required String title, required String subTitle, - required bool buttonValue, - required VoidCallback onTapRadio, + required bool isChecked, + required VoidCallback onChanged, }) { return Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -121,12 +121,13 @@ class _EnablePermissionViewState extends ConsumerState SizedBox( width: 24, height: 24, - child: Radio( - value: true, - groupValue: buttonValue, + child: Checkbox( + value: isChecked, activeColor: context.colorScheme.primary, onChanged: (value) { - onTapRadio(); + if (value == true) { + onChanged(); + } }, ), ), @@ -135,8 +136,8 @@ class _EnablePermissionViewState extends ConsumerState child: InkWell( splashColor: Colors.transparent, onTap: () { - if (!buttonValue) { - onTapRadio(); + if (!isChecked) { + onChanged(); } }, child: Column( diff --git a/app/lib/ui/flow/permission/enable_permission_view_model.dart b/app/lib/ui/flow/permission/enable_permission_view_model.dart index fc01e06e..8031d4f7 100644 --- a/app/lib/ui/flow/permission/enable_permission_view_model.dart +++ b/app/lib/ui/flow/permission/enable_permission_view_model.dart @@ -39,9 +39,11 @@ class PermissionViewNotifier extends StateNotifier { } Future requestLocationPermission() async { - final permissionState = await permissionService.requestLocationPermission(); + final permissionState = await permissionService.requestLocationPermissionStatus(); if (permissionState.isGranted) { state = state.copyWith(isLocationGranted: true); + } else if (permissionState.isDenied) { + await permissionService.requestLocationPermission(); } else { state = state.copyWith(showLocationPrompt: DateTime.now()); } diff --git a/data/lib/api/location/journey/api_journey_service.dart b/data/lib/api/location/journey/api_journey_service.dart index 8774cf16..52b0d958 100644 --- a/data/lib/api/location/journey/api_journey_service.dart +++ b/data/lib/api/location/journey/api_journey_service.dart @@ -26,10 +26,11 @@ class ApiJourneyService { CollectionReference _journeyRef(String userId) => _userRef.doc(userId).collection("user_journeys"); - Future saveCurrentJourney({ + Future saveCurrentJourney({ required String userId, required double fromLatitude, required double fromLongitude, + required String type, double? toLatitude, double? toLongitude, LatLng? toLatLng, @@ -38,7 +39,7 @@ class ApiJourneyService { int? routeDuration, int? created_at, int? updated_at, - String? type, + }) async { final fromLatLng = LatLng(fromLatitude, fromLongitude); final toLatLng = (toLatitude != null && toLongitude != null) @@ -59,10 +60,10 @@ class ApiJourneyService { route_duration: routeDuration, created_at: created_at ?? DateTime.now().millisecondsSinceEpoch, update_at: updated_at ?? DateTime.now().millisecondsSinceEpoch, - type: type ?? (toLatLng == null ? JOURNEY_TYPE_STEADY : JOURNEY_TYPE_MOVING) + type: type ); await docRef.set(journey.toJson()); - return docRef.id; + return journey; } Future updateLastLocationJourney( diff --git a/data/lib/api/location/journey/journey.dart b/data/lib/api/location/journey/journey.dart index 834e99f3..fd6b0435 100644 --- a/data/lib/api/location/journey/journey.dart +++ b/data/lib/api/location/journey/journey.dart @@ -1,4 +1,5 @@ // ignore_for_file: constant_identifier_names + import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/location/location.dart'; import 'package:data/domain/journey_lat_lng_entension.dart'; @@ -6,6 +7,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; part 'journey.freezed.dart'; + part 'journey.g.dart'; const JOURNEY_TYPE_STEADY = 'steady'; @@ -40,10 +42,24 @@ class ApiLocationJourney with _$ApiLocationJourney { return ApiLocationJourney.fromJson(data!); } + bool isSteady() { + if (type != null) { + return type == JOURNEY_TYPE_STEADY; + } + return to_latitude == null || to_longitude == null; + } + + bool isMoving() { + if (type != null) { + return type == JOURNEY_TYPE_MOVING; + } + return to_latitude != null && to_longitude != null; + } + List toRoute() { - if (type == JOURNEY_TYPE_STEADY) { + if (isSteady()) { return []; - } else if (type == JOURNEY_TYPE_MOVING) { + } else if (isMoving()) { List result = [LatLng(from_latitude, from_longitude)]; result.addAll(routes.map((route) => route.toLatLng())); if (to_latitude != null && to_longitude != null) { @@ -55,11 +71,17 @@ class ApiLocationJourney with _$ApiLocationJourney { } LocationData toLocationFromSteadyJourney() { - return LocationData(latitude: from_latitude, longitude: from_longitude, timestamp: DateTime.now()); + return LocationData( + latitude: from_latitude, + longitude: from_longitude, + timestamp: DateTime.now()); } LocationData toLocationFromMovingJourney() { - return LocationData(latitude: to_latitude ?? 0, longitude: to_longitude ?? 0, timestamp: DateTime.now()); + return LocationData( + latitude: to_latitude ?? 0, + longitude: to_longitude ?? 0, + timestamp: DateTime.now()); } } @@ -72,4 +94,4 @@ class JourneyRoute with _$JourneyRoute { factory JourneyRoute.fromJson(Map json) => _$JourneyRouteFromJson(json); -} \ No newline at end of file +} diff --git a/data/lib/repository/journey_repository.dart b/data/lib/repository/journey_repository.dart index 5cbba5f0..033411bc 100644 --- a/data/lib/repository/journey_repository.dart +++ b/data/lib/repository/journey_repository.dart @@ -6,7 +6,6 @@ import 'dart:math'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/location/journey/journey.dart'; import 'package:data/api/location/location.dart'; -import 'package:data/domain/location_data_extension.dart'; import 'package:data/log/logger.dart'; import 'package:data/service/location_manager.dart'; import 'package:geolocator/geolocator.dart'; @@ -77,7 +76,7 @@ class JourneyRepository { var lastLocation = locationCache.getLastJourney(userId)?.toLocationFromSteadyJourney(); var lastLocationJourney = await getLastKnownLocation(userId, position); - if (lastLocation == null || lastLocationJourney.type == JOURNEY_TYPE_STEADY) { + if (lastLocation == null || lastLocationJourney.isSteady()) { return; } @@ -134,16 +133,12 @@ class JourneyRepository { Future _saveJourneyOnDayChanged( String userId, LocationData extractedLocation) async { - String newJourneyId = await journeyService.saveCurrentJourney( - userId: userId, - fromLatitude: extractedLocation.latitude, - fromLongitude: extractedLocation.longitude, - type: JOURNEY_TYPE_STEADY - ); + final newJourney = await journeyService.saveCurrentJourney( + userId: userId, + fromLatitude: extractedLocation.latitude, + fromLongitude: extractedLocation.longitude, + type: JOURNEY_TYPE_STEADY); - var newJourney = extractedLocation - .toLocationJourney(userId, newJourneyId) - .copyWith(id: newJourneyId, type: JOURNEY_TYPE_STEADY); locationCache.putLastJourney(newJourney, userId); } @@ -172,17 +167,15 @@ class JourneyRepository { // Possibly user is new or no location journey available // Save extracted location as new location journey with steady state in cache - String newJourneyId = await journeyService.saveCurrentJourney( - userId: userId, - fromLatitude: extractedLocation?.latitude ?? 0, - fromLongitude: extractedLocation?.longitude ?? 0, - created_at: DateTime.now().millisecondsSinceEpoch, - type: JOURNEY_TYPE_STEADY - ); - var locationJourney = - extractedLocation!.toLocationJourney(userId, newJourneyId).copyWith(type: JOURNEY_TYPE_STEADY); - locationCache.putLastJourney(locationJourney, userId); - return locationJourney; + final newJourney = await journeyService.saveCurrentJourney( + userId: userId, + fromLatitude: extractedLocation?.latitude ?? 0, + fromLongitude: extractedLocation?.longitude ?? 0, + created_at: DateTime.now().millisecondsSinceEpoch, + type: JOURNEY_TYPE_STEADY); + + locationCache.putLastJourney(newJourney, userId); + return newJourney; } } } @@ -197,10 +190,10 @@ class JourneyRepository { locations.isNotEmpty ? _geometricMedianCalculation(locations) : null; double distance = 0; - if (lastKnownJourney.type == JOURNEY_TYPE_STEADY) { + if (lastKnownJourney.isSteady()) { distance = _distanceBetween(geometricMedian ?? extractedLocation, lastKnownJourney.toLocationFromSteadyJourney()); - } else if (lastKnownJourney.type == JOURNEY_TYPE_MOVING) { + } else if (lastKnownJourney.isMoving()) { distance = _distanceBetween(geometricMedian ?? extractedLocation, lastKnownJourney.toLocationFromMovingJourney()); } @@ -209,14 +202,15 @@ class JourneyRepository { extractedLocation.timestamp.millisecondsSinceEpoch) - (lastKnownJourney.update_at ?? 0); - if (lastKnownJourney.type == JOURNEY_TYPE_STEADY) { + if (lastKnownJourney.isSteady()) { if (distance > MIN_DISTANCE) { // Here, means last known journey is steady and and now user has started moving // Save journey for moving user and update cache as well: + await _saveJourneyWhenUserStartsMoving( userId, extractedLocation, lastKnownJourney, timeDifference); } - } else if (lastKnownJourney.type == JOURNEY_TYPE_MOVING) { + } else if (lastKnownJourney.isMoving()) { // Here, means last known journey is moving and user is still moving // Save journey for moving user and update last known journey. // Note: Need to use lastKnownJourney.id as journey id because we are updating the journey @@ -239,33 +233,18 @@ class JourneyRepository { lastKnownJourney.copyWith( update_at: DateTime.now().millisecondsSinceEpoch)); - String newJourneyId = await journeyService.saveCurrentJourney( - userId: userId, - fromLatitude: lastKnownJourney.from_latitude, - fromLongitude: lastKnownJourney.from_longitude, - toLatitude: extractedLocation.latitude, - toLongitude: extractedLocation.longitude, - routeDistance: distance, - routeDuration: duration, - type: JOURNEY_TYPE_MOVING - ); - - var journey = ApiLocationJourney( - id: newJourneyId, - user_id: userId, - from_latitude: lastKnownJourney.from_latitude, - from_longitude: lastKnownJourney.from_longitude, - to_latitude: extractedLocation.latitude, - to_longitude: extractedLocation.longitude, - routes: _getRoute(userId), - route_distance: distance, - route_duration: duration, - created_at: DateTime.now().millisecondsSinceEpoch, - update_at: DateTime.now().millisecondsSinceEpoch, - type: JOURNEY_TYPE_MOVING - ); + final newJourney = await journeyService.saveCurrentJourney( + userId: userId, + fromLatitude: lastKnownJourney.from_latitude, + fromLongitude: lastKnownJourney.from_longitude, + toLatitude: extractedLocation.latitude, + toLongitude: extractedLocation.longitude, + routes: _getRoute(userId), + routeDistance: distance, + routeDuration: duration, + type: JOURNEY_TYPE_MOVING); - locationCache.putLastJourney(journey, userId); + locationCache.putLastJourney(newJourney, userId); } /// Update journey for continued moving user i.e., state is moving and user is still moving @@ -278,26 +257,25 @@ class JourneyRepository { lastKnownJourney.toLocationFromMovingJourney(), extractedLocation); var journey = ApiLocationJourney( - id: lastKnownJourney.id, - user_id: userId, - from_latitude: lastKnownJourney.from_latitude, - from_longitude: lastKnownJourney.from_longitude, - to_latitude: extractedLocation.latitude, - to_longitude: extractedLocation.longitude, - route_distance: distance + (lastKnownJourney.route_distance ?? 0), - route_duration: (lastKnownJourney.update_at ?? 0) - - (lastKnownJourney.created_at ?? 0), - routes: lastKnownJourney.routes + - [ - JourneyRoute( - latitude: extractedLocation.latitude, - longitude: extractedLocation.longitude, - ) - ], - created_at: lastKnownJourney.created_at, - update_at: DateTime.now().millisecondsSinceEpoch, - type: JOURNEY_TYPE_MOVING - ); + id: lastKnownJourney.id, + user_id: userId, + from_latitude: lastKnownJourney.from_latitude, + from_longitude: lastKnownJourney.from_longitude, + to_latitude: extractedLocation.latitude, + to_longitude: extractedLocation.longitude, + route_distance: distance + (lastKnownJourney.route_distance ?? 0), + route_duration: (lastKnownJourney.update_at ?? 0) - + (lastKnownJourney.created_at ?? 0), + routes: lastKnownJourney.routes + + [ + JourneyRoute( + latitude: extractedLocation.latitude, + longitude: extractedLocation.longitude, + ) + ], + created_at: lastKnownJourney.created_at, + update_at: DateTime.now().millisecondsSinceEpoch, + type: JOURNEY_TYPE_MOVING); await journeyService.updateLastLocationJourney(userId, journey); locationCache.putLastJourney(journey, userId); @@ -329,24 +307,14 @@ class JourneyRepository { journeyService.updateLastLocationJourney(userId, movingJourney); // Save journey for steady user and update cache as well: - var newJourneyId = await journeyService.saveCurrentJourney( - userId: userId, - fromLatitude: extractedLocation.latitude, - fromLongitude: extractedLocation.longitude, - created_at: lastKnownJourney.update_at, - type: JOURNEY_TYPE_STEADY - ); - - var steadyJourney = ApiLocationJourney( - id: newJourneyId, - user_id: userId, - from_latitude: extractedLocation.latitude, - from_longitude: extractedLocation.longitude, - created_at: lastKnownJourney.update_at, - type: JOURNEY_TYPE_STEADY - ); + var newJourney = await journeyService.saveCurrentJourney( + userId: userId, + fromLatitude: extractedLocation.latitude, + fromLongitude: extractedLocation.longitude, + created_at: lastKnownJourney.update_at, + type: JOURNEY_TYPE_STEADY); - locationCache.putLastJourney(steadyJourney, userId); + locationCache.putLastJourney(newJourney, userId); } List _getRoute(String userId) { diff --git a/data/lib/service/permission_service.dart b/data/lib/service/permission_service.dart index a6c9ee03..064555c5 100644 --- a/data/lib/service/permission_service.dart +++ b/data/lib/service/permission_service.dart @@ -16,6 +16,9 @@ class PermissionService { Future requestLocationPermission() async { return await Permission.location.request(); } + Future requestLocationPermissionStatus() async { + return await Permission.location.status; + } Future isBackgroundLocationPermissionGranted() async { return await Permission.locationAlways.isGranted;