chore: add mobile app

This commit is contained in:
2026-02-07 22:33:02 +01:00
parent db34640acc
commit e89384ad4e
29327 changed files with 3886944 additions and 12 deletions
@@ -0,0 +1,17 @@
// Copyright 2024-present 650 Industries. All rights reserved.
import CoreLocation
internal class BaseLocationProvider: NSObject, CLLocationManagerDelegate {
internal let manager = CLLocationManager()
// CLLocationManager must be created on the main thread.
@MainActor
init(options: LocationOptions) {
super.init()
manager.allowsBackgroundLocationUpdates = false
manager.distanceFilter = options.distanceInterval
manager.desiredAccuracy = options.accuracy.toCLLocationAccuracy()
manager.delegate = self
}
}
@@ -0,0 +1,10 @@
// Copyright 2024-present 650 Industries. All rights reserved.
import CoreLocation
import ExpoModulesCore
internal class BaseStreamer: BaseLocationProvider {
func stopStreaming() {
// Default empty implementation
}
}
@@ -0,0 +1,53 @@
// Copyright 2024-present 650 Industries. All rights reserved.
import CoreLocation
import ExpoModulesCore
internal class DeviceHeadingStreamer: BaseStreamer {
typealias DeviceHeadingStream = AsyncThrowingStream<CLHeading, Error>
private var headingStream: DeviceHeadingStream?
private var continuation: DeviceHeadingStream.Continuation?
deinit {
if continuation != nil {
stopStreaming()
}
}
func streamDeviceHeading() throws -> DeviceHeadingStream {
if !CLLocationManager.headingAvailable() {
// Throw error
throw Exceptions.HeadingUnavailableException()
}
if let stream = headingStream {
return stream
}
let stream = DeviceHeadingStream { continuation in
self.continuation = continuation
manager.startUpdatingHeading()
}
headingStream = stream
return stream
}
override func stopStreaming() {
manager.stopUpdatingHeading()
continuation?.finish()
headingStream = nil
continuation = nil
}
// MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
continuation?.yield(newHeading)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) {
continuation?.finish(throwing: Exceptions.HeadingUnavailableException().causedBy(error))
headingStream = nil
continuation = nil
}
}
@@ -0,0 +1,36 @@
// Copyright 2024-present 650 Industries. All rights reserved.
import CoreLocation
import ExpoModulesCore
internal class LocationRequester: BaseLocationProvider {
private var continuation: CheckedContinuation<CLLocation, Error>?
deinit {
continuation?.resume(throwing: Exceptions.LocationRequestCanceled())
continuation = nil
}
func requestLocation() async throws -> CLLocation {
return try await withCheckedThrowingContinuation { continuation in
self.continuation = continuation
manager.requestLocation()
}
}
// MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let lastLocation = locations.last {
continuation?.resume(returning: lastLocation)
} else {
continuation?.resume(throwing: Exceptions.LocationUnavailable())
}
continuation = nil
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) {
continuation?.resume(throwing: Exceptions.LocationUnavailable().causedBy(error))
continuation = nil
}
}
@@ -0,0 +1,55 @@
// Copyright 2024-present 650 Industries. All rights reserved.
import CoreLocation
import ExpoModulesCore
internal class LocationsStreamer: BaseStreamer {
typealias LocationsStream = AsyncThrowingStream<[CLLocation], Error>
private var locationsStream: LocationsStream?
private var continuation: LocationsStream.Continuation?
deinit {
if continuation != nil {
stopStreaming()
}
}
func streamLocations() throws -> LocationsStream {
if !CLLocationManager.locationServicesEnabled() {
throw Exceptions.LocationServicesDisabled()
}
if let stream = locationsStream {
return stream
}
let stream = LocationsStream { continuation in
self.continuation = continuation
manager.startUpdatingLocation()
}
locationsStream = stream
return stream
}
override func stopStreaming() {
manager.stopUpdatingLocation()
continuation?.finish()
locationsStream = nil
continuation = nil
}
// MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if !locations.isEmpty {
continuation?.yield(locations)
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) {
continuation?.finish(throwing: Exceptions.LocationUnavailable().causedBy(error))
locationsStream = nil
continuation = nil
}
}