LocationManager.swift 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import Foundation
  2. import CoreLocation
  3. class LocationManager: NSObject, CLLocationManagerDelegate {
  4. let locationManager: CLLocationManager
  5. let dcContext: DcContext
  6. var lastLocation: CLLocation?
  7. init(context: DcContext) {
  8. dcContext = context
  9. locationManager = CLLocationManager()
  10. locationManager.distanceFilter = 25
  11. locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
  12. locationManager.allowsBackgroundLocationUpdates = true
  13. locationManager.pausesLocationUpdatesAutomatically = false
  14. locationManager.activityType = CLActivityType.fitness
  15. super.init()
  16. locationManager.delegate = self
  17. }
  18. func shareLocation(chatId: Int, duration: Int) {
  19. dcContext.sendLocationsToChat(chatId: chatId, seconds: duration)
  20. if duration > 0 {
  21. locationManager.requestAlwaysAuthorization()
  22. } else {
  23. if !dcContext.isSendingLocationsToChat(chatId: 0) {
  24. locationManager.stopUpdatingLocation()
  25. }
  26. }
  27. }
  28. func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
  29. logger.debug("LOCATION: didUpdateLocations")
  30. guard let newLocation = locations.last else {
  31. logger.debug("LOCATION: new location is emtpy")
  32. return
  33. }
  34. let isBetter = isBetterLocation(newLocation: newLocation, lastLocation: lastLocation)
  35. logger.debug("LOCATION: isBetterLocation: \(isBetter)")
  36. if isBetter {
  37. if dcContext.isSendingLocationsToChat(chatId: 0) {
  38. dcContext.setLocation(latitude: newLocation.coordinate.latitude,
  39. longitude: newLocation.coordinate.longitude,
  40. accuracy: newLocation.horizontalAccuracy)
  41. lastLocation = newLocation
  42. } else {
  43. locationManager.stopUpdatingLocation()
  44. }
  45. }
  46. }
  47. func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
  48. if let error = error as? CLError, error.code == .denied {
  49. logger.warning("LOCATION MANAGER: didFailWithError: \(error.localizedDescription)")
  50. // Location updates are not authorized.
  51. disableLocationStreamingInAllChats()
  52. return
  53. }
  54. }
  55. func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
  56. logger.debug("LOCATION MANAGER: didChangeAuthorization: \(status)")
  57. switch status {
  58. case .denied, .restricted:
  59. disableLocationStreamingInAllChats()
  60. case .authorizedWhenInUse:
  61. logger.warning("Location streaming will only work as long as the app is in foreground.")
  62. locationManager.startUpdatingLocation()
  63. case .authorizedAlways:
  64. locationManager.startUpdatingLocation()
  65. default:
  66. break
  67. }
  68. }
  69. func disableLocationStreamingInAllChats() {
  70. if dcContext.isSendingLocationsToChat(chatId: 0) {
  71. let dcChatlist = dcContext.getChatlist(flags: 0, queryString: nil, queryId: 0)
  72. for i in 0...dcChatlist.length {
  73. let chatId = dcChatlist.getChatId(index: i)
  74. if dcContext.isSendingLocationsToChat(chatId: chatId) {
  75. dcContext.sendLocationsToChat(chatId: chatId, seconds: 0)
  76. }
  77. }
  78. locationManager.stopUpdatingLocation()
  79. }
  80. }
  81. func isBetterLocation(newLocation: CLLocation, lastLocation: CLLocation?) -> Bool {
  82. guard let lastLocation = lastLocation else {
  83. return !isNewLocationOutdated(newLocation: newLocation) && hasValidAccuracy(newLocation: newLocation)
  84. }
  85. return !isNewLocationOutdated(newLocation: newLocation) &&
  86. hasValidAccuracy(newLocation: newLocation) &&
  87. (isMoreAccurate(newLocation: newLocation, lastLocation: lastLocation) && hasLocationChanged(newLocation: newLocation, lastLocation: lastLocation) ||
  88. hasLocationSignificantlyChanged(newLocation: newLocation, lastLocation: lastLocation))
  89. }
  90. func hasValidAccuracy(newLocation: CLLocation) -> Bool {
  91. return newLocation.horizontalAccuracy >= 0
  92. }
  93. func isMoreAccurate(newLocation: CLLocation, lastLocation: CLLocation) -> Bool {
  94. // logger.debug("LOCATION: isMoreAccurate \(lastLocation.horizontalAccuracy - newLocation.horizontalAccuracy > 0)")
  95. return lastLocation.horizontalAccuracy - newLocation.horizontalAccuracy > 0
  96. }
  97. func hasLocationChanged(newLocation: CLLocation, lastLocation: CLLocation) -> Bool {
  98. // logger.debug("LOCATION: hasLocationChanged \(newLocation.distance(from: lastLocation) > 10)")
  99. return newLocation.distance(from: lastLocation) > 10
  100. }
  101. func hasLocationSignificantlyChanged(newLocation: CLLocation, lastLocation: CLLocation) -> Bool {
  102. // logger.debug("LOCATION: hasLocationSignificantlyChanged \(newLocation.distance(from: lastLocation) > 30)")
  103. return newLocation.distance(from: lastLocation) > 30
  104. }
  105. /**
  106. Locations can be cached by iOS, timestamp comparison checks if the location has been tracked within the last 5 minutes
  107. */
  108. func isNewLocationOutdated(newLocation: CLLocation) -> Bool {
  109. let timeDelta = DateUtils.getRelativeTimeInSeconds(timeStamp: Double(newLocation.timestamp.timeIntervalSince1970))
  110. // logger.debug("LOCATION: isLocationOutdated timeDelta: \(timeDelta) -> \(Double(Time.fiveMinutes)) -> \(timeDelta < Double(Time.fiveMinutes))")
  111. return timeDelta > Double(Time.fiveMinutes)
  112. }
  113. }