mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Add UserLocationService that handles live location sharing for the current user
This commit is contained in:
parent
67a3965d86
commit
20a7ad9c6c
2 changed files with 288 additions and 0 deletions
258
Riot/Modules/LocationSharing/UserLocationService.swift
Normal file
258
Riot/Modules/LocationSharing/UserLocationService.swift
Normal file
|
@ -0,0 +1,258 @@
|
|||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreLocation
|
||||
import MatrixSDK
|
||||
|
||||
/// UserLocationService handles live location sharing for the current user
|
||||
class UserLocationService: UserLocationServiceProtocol {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
private enum Constants {
|
||||
|
||||
/// Minimum delay in milliseconds to send consecutive location for a beacon info
|
||||
static let beaconSendMinInterval: UInt64 = 5000 // 5s
|
||||
|
||||
/// Delay to check for experied beacons
|
||||
static let beaconExpiredVerificationInterval: TimeInterval = 5 // 5s
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let locationManager: LocationManager
|
||||
private let session: MXSession
|
||||
|
||||
/// All active beacon info summaries that belongs to this device
|
||||
/// Do not update location for beacon info started on another device
|
||||
private var deviceBeaconInfoSummaries: [MXBeaconInfoSummaryProtocol] = []
|
||||
|
||||
private var beaconInfoSummaryListener: Any?
|
||||
|
||||
private var expiredBeaconVerificationTimer: Timer?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(session: MXSession) {
|
||||
self.locationManager = LocationManager(accuracy: .full, allowsBackgroundLocationUpdates: false)
|
||||
self.session = session
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func requestAuthorization(_ handler: @escaping LocationAuthorizationHandler) {
|
||||
|
||||
self.locationManager.requestAuthorization(handler)
|
||||
}
|
||||
|
||||
func start() {
|
||||
self.startLocationTracking()
|
||||
self.startListeningBeaconInfoSummaries()
|
||||
self.startVerifyingExpiredBeaconInfoSummaries()
|
||||
}
|
||||
|
||||
func stop() {
|
||||
self.stopLocationTracking()
|
||||
self.stopListeningBeaconInfoSummaries()
|
||||
self.stopVerifyingExpiredBeaconInfoSummaries()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
// MARK: Beacon info summary
|
||||
|
||||
private func startVerifyingExpiredBeaconInfoSummaries() {
|
||||
|
||||
let timer = Timer.scheduledTimer(withTimeInterval: Constants.beaconExpiredVerificationInterval, repeats: false) { [weak self] _ in
|
||||
|
||||
self?.verifyExpiredBeaconInfoSummaries()
|
||||
}
|
||||
|
||||
self.expiredBeaconVerificationTimer = timer
|
||||
}
|
||||
|
||||
private func stopVerifyingExpiredBeaconInfoSummaries() {
|
||||
self.expiredBeaconVerificationTimer?.invalidate()
|
||||
self.expiredBeaconVerificationTimer = nil
|
||||
}
|
||||
|
||||
private func verifyExpiredBeaconInfoSummaries() {
|
||||
|
||||
for beaconInfoSummary in deviceBeaconInfoSummaries where beaconInfoSummary.isActive == false && beaconInfoSummary.hasStopped == false {
|
||||
|
||||
// TODO: Prevent to stop several times
|
||||
// Wait for isStopping status
|
||||
self.session.locationService.stopUserLocationSharing(withBeaconInfoEventId: beaconInfoSummary.id, roomId: beaconInfoSummary.roomId) { response in
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Remove non active beacon info summaries
|
||||
self.deviceBeaconInfoSummaries = self.deviceBeaconInfoSummaries.filter({ beaconInfoSummary in
|
||||
return beaconInfoSummary.isActive
|
||||
})
|
||||
}
|
||||
|
||||
private func startListeningBeaconInfoSummaries() {
|
||||
let beaconInfoSummaryListener = self.session.aggregations.beaconAggregations.listenToBeaconInfoSummaryUpdate { roomId, beaconInfoSummary in
|
||||
|
||||
if self.isDeviceBeaconInfoSummary(beaconInfoSummary) {
|
||||
|
||||
let existingIndex = self.deviceBeaconInfoSummaries.firstIndex(where: { beaconInfoSum in
|
||||
beaconInfoSum.id == beaconInfoSummary.id
|
||||
})
|
||||
|
||||
if beaconInfoSummary.isActive {
|
||||
|
||||
if let index = existingIndex {
|
||||
self.deviceBeaconInfoSummaries[index] = beaconInfoSummary
|
||||
} else {
|
||||
self.deviceBeaconInfoSummaries.append(beaconInfoSummary)
|
||||
|
||||
// Send location if possible to a new beacon info summary
|
||||
self.didReceiveDeviceNewBeaconInfoSummary(beaconInfoSummary)
|
||||
}
|
||||
} else {
|
||||
|
||||
if let index = existingIndex {
|
||||
self.deviceBeaconInfoSummaries.remove(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
self.updateLocationTrackingIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
self.beaconInfoSummaryListener = beaconInfoSummaryListener
|
||||
}
|
||||
|
||||
private func stopListeningBeaconInfoSummaries() {
|
||||
|
||||
if let listener = self.beaconInfoSummaryListener {
|
||||
self.session.aggregations.beaconAggregations.removeListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func isDeviceBeaconInfoSummary(_ beaconInfoSummary: MXBeaconInfoSummaryProtocol) -> Bool {
|
||||
return beaconInfoSummary.userId == self.session.myUserId && beaconInfoSummary.deviceId == self.session.myDeviceId
|
||||
}
|
||||
|
||||
|
||||
private func didReceiveDeviceNewBeaconInfoSummary(_ beaconInfoSummary: MXBeaconInfoSummaryProtocol) {
|
||||
|
||||
guard let lastLocation = self.locationManager.lastLocation else {
|
||||
return
|
||||
}
|
||||
|
||||
self.sendLocation(lastLocation, for: beaconInfoSummary)
|
||||
}
|
||||
|
||||
// MARK: Location sending
|
||||
|
||||
private func sendLocation(_ location: CLLocation, for beaconInfoSummary: MXBeaconInfoSummaryProtocol) {
|
||||
guard self.canSendBeaconRequest(for: beaconInfoSummary) else {
|
||||
return
|
||||
}
|
||||
|
||||
var localEcho: MXEvent?
|
||||
|
||||
self.session.locationService.sendLocation(withBeaconInfoEventId: beaconInfoSummary.id,
|
||||
latitude: location.coordinate.latitude,
|
||||
longitude: location.coordinate.longitude,
|
||||
inRoomWithId: beaconInfoSummary.roomId,
|
||||
localEcho: &localEcho) { response in
|
||||
|
||||
switch response {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
MXLog.error("Fail to send location with error \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func didReceiveLocation(_ location: CLLocation) {
|
||||
|
||||
for deviceBaconInfoSummary in deviceBeaconInfoSummaries {
|
||||
self.sendLocation(location, for: deviceBaconInfoSummary)
|
||||
}
|
||||
}
|
||||
|
||||
private func canSendBeaconRequest(for beaconInfoSummary: MXBeaconInfoSummaryProtocol) -> Bool {
|
||||
|
||||
// Check if location manager is started
|
||||
guard self.locationManager.isUpdatingLocation else {
|
||||
return false
|
||||
}
|
||||
|
||||
let canSendBeaconRequest: Bool
|
||||
|
||||
if let lastBeaconTimestamp = beaconInfoSummary.lastBeacon?.timestamp {
|
||||
|
||||
let currentTimestamp = Date().timeIntervalSince1970 * 1000
|
||||
|
||||
canSendBeaconRequest = UInt64(currentTimestamp) - lastBeaconTimestamp >= Constants.beaconSendMinInterval
|
||||
} else {
|
||||
// The beacon info summary have no last beacon, we can send a request immediatly
|
||||
canSendBeaconRequest = true
|
||||
}
|
||||
|
||||
return canSendBeaconRequest
|
||||
}
|
||||
|
||||
// MARK: Device location
|
||||
|
||||
private func startLocationTracking() {
|
||||
self.locationManager.start()
|
||||
self.locationManager.delegate = self
|
||||
}
|
||||
|
||||
private func stopLocationTracking() {
|
||||
self.locationManager.stop()
|
||||
self.locationManager.delegate = nil
|
||||
}
|
||||
|
||||
private func updateLocationTrackingIfNeeded() {
|
||||
|
||||
if self.deviceBeaconInfoSummaries.isEmpty {
|
||||
|
||||
// Stop location tracking if there is no active beacon info summaries
|
||||
if self.locationManager.isUpdatingLocation {
|
||||
self.locationManager.stop()
|
||||
}
|
||||
} else {
|
||||
|
||||
// Start location tracking if there is beacon info summaries and location tracking is stopped
|
||||
if self.locationManager.isUpdatingLocation == false {
|
||||
self.locationManager.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - LocationManagerDelegate
|
||||
extension UserLocationService: LocationManagerDelegate {
|
||||
|
||||
func locationManager(_ manager: LocationManager, didUpdateLocation location: CLLocation) {
|
||||
self.didReceiveLocation(location)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Describes service that monitor and share current user device location to rooms where user shared is location
|
||||
protocol UserLocationServiceProtocol {
|
||||
|
||||
/// Request location permissions that enables live location sharing
|
||||
func requestAuthorization(_ handler: @escaping LocationAuthorizationHandler)
|
||||
|
||||
/// Start monitoring user location and look to rooms where location should be sent
|
||||
func start()
|
||||
|
||||
/// Stop monitoring user location
|
||||
func stop()
|
||||
}
|
Loading…
Reference in a new issue