2023-12-06 12:32:17 -08:00
//
// L o c a t i o n s H a n d l e r . s w i f t
// M e s h t a s t i c
//
2025-07-17 19:03:41 -07:00
// C o p y r i g h t G a r t h V a n d e r H o u w e n 1 2 / 4 / 2 3 .
2023-12-06 12:32:17 -08:00
//
import SwiftUI
import CoreLocation
2024-06-03 02:17:55 -07:00
import OSLog
2023-12-06 12:32:17 -08:00
2025-07-17 19:03:41 -07:00
// T h e @ M a i n A c t o r a n n o t a t i o n e n s u r e s t h a t a l l s t a t e c h a n g e s a n d U I u p d a t e s h a p p e n o n t h e m a i n t h r e a d ,
// p r e v e n t i n g p o t e n t i a l r a c e c o n d i t i o n s a n d c r a s h e s r e l a t e d t o U I u p d a t e s f r o m b a c k g r o u n d t h r e a d s .
2025-07-14 20:27:02 -07:00
@ MainActor class LocationsHandler : NSObject , ObservableObject , @ preconcurrency CLLocationManagerDelegate {
2023-12-25 20:26:50 -08:00
2023-12-06 12:32:17 -08:00
static let shared = LocationsHandler ( ) // C r e a t e a s i n g l e , s h a r e d i n s t a n c e o f t h e o b j e c t .
2025-07-14 20:27:02 -07:00
public var manager = CLLocationManager ( )
2023-12-06 12:32:17 -08:00
private var background : CLBackgroundActivitySession ?
2024-02-05 21:46:16 -08:00
var enableSmartPosition : Bool = UserDefaults . enableSmartPosition
2024-05-29 16:40:07 -05:00
2025-07-14 20:27:02 -07:00
@ Published var locationsArray : [ CLLocation ] = [ CLLocation ] ( )
2023-12-06 12:32:17 -08:00
@ Published var isStationary = false
@ Published var count = 0
2023-12-24 22:42:07 -08:00
@ Published var isRecording = false
@ Published var isRecordingPaused = false
@ Published var recordingStarted : Date ?
@ Published var distanceTraveled = 0.0
@ Published var elevationGain = 0.0
2025-12-15 11:15:54 -08:00
@ Published var heading : Double = 0.0 // C u r r e n t h e a d i n g i n d e g r e e s
@ Published var headingUpdatesStarted : Bool = false // T r a c k h e a d i n g u p d a t e s s t a t e
2024-05-29 16:40:07 -05:00
2023-12-06 12:32:17 -08:00
@ Published
var updatesStarted : Bool = UserDefaults . standard . bool ( forKey : " liveUpdatesStarted " ) {
didSet { UserDefaults . standard . set ( updatesStarted , forKey : " liveUpdatesStarted " ) }
}
2024-05-29 16:40:07 -05:00
2023-12-06 12:32:17 -08:00
@ Published
var backgroundActivity : Bool = UserDefaults . standard . bool ( forKey : " BGActivitySessionStarted " ) {
didSet {
2025-07-17 19:03:41 -07:00
// I n v a l i d a t e o r c r e a t e t h e b a c k g r o u n d a c t i v i t y s e s s i o n b a s e d o n t h e n e w v a l u e .
2023-12-06 12:32:17 -08:00
backgroundActivity ? self . background = CLBackgroundActivitySession ( ) : self . background ? . invalidate ( )
UserDefaults . standard . set ( backgroundActivity , forKey : " BGActivitySessionStarted " )
}
}
2025-07-20 09:45:47 -07:00
2025-05-03 20:44:12 -07:00
// T h e c o n t i n u a t i o n w e w i l l u s e t o a s y n c h r o n o u s l y a s k t h e u s e r p e r m i s s i o n t o t r a c k t h e i r l o c a t i o n .
2025-07-17 19:03:41 -07:00
// T h i s i s a n O p t i o n a l t o e n s u r e i t c a n b e n i l l e d o u t a f t e r u s e .
2025-07-14 20:27:02 -07:00
private var permissionContinuation : CheckedContinuation < CLAuthorizationStatus , Never > ?
2025-07-20 09:45:47 -07:00
// A f l a g t o p r e v e n t m u l t i p l e c o n c u r r e n t p e r m i s s i o n r e q u e s t s
private var isRequestingPermission = false
2025-07-17 19:03:41 -07:00
// / R e q u e s t s " A l w a y s " l o c a t i o n a u t h o r i z a t i o n f r o m t h e u s e r .
// / T h i s m e t h o d u s e s S w i f t ' s s t r u c t u r e d c o n c u r r e n c y t o a w a i t t h e u s e r ' s d e c i s i o n .
2025-07-20 09:45:47 -07:00
// / I t i n c l u d e s a t i m e o u t t o p r e v e n t c o n t i n u a t i o n l e a k s i f t h e d e l e g a t e m e t h o d i s n ' t c a l l e d .
2025-07-17 19:03:41 -07:00
// / - R e t u r n s : T h e ` C L A u t h o r i z a t i o n S t a t u s ` r e f l e c t i n g t h e u s e r ' s c h o i c e .
2025-05-03 20:44:12 -07:00
func requestLocationAlwaysPermissions ( ) async -> CLAuthorizationStatus {
2025-07-20 09:45:47 -07:00
// I f a r e q u e s t i s a l r e a d y i n p r o g r e s s , r e t u r n t h e c u r r e n t s t a t u s i m m e d i a t e l y .
// T h i s p r e v e n t s c r e a t i n g m u l t i p l e c o n t i n u a t i o n s a n d p o t e n t i a l l e a k s .
guard ! isRequestingPermission else {
Logger . services . debug ( " 📍 [App] requestLocationAlwaysPermissions called while a request is already active. Returning current status. " )
return manager . authorizationStatus
}
// S e t f l a g t o i n d i c a t e a r e q u e s t i s i n p r o g r e s s
isRequestingPermission = true
2025-05-03 20:44:12 -07:00
return await withCheckedContinuation { continuation in
2025-07-17 19:03:41 -07:00
// S t o r e t h e c o n t i n u a t i o n .
2025-07-14 20:27:02 -07:00
self . permissionContinuation = continuation
2025-07-20 09:45:47 -07:00
2025-07-17 19:03:41 -07:00
// R e q u e s t a u t h o r i z a t i o n . T h e r e s p o n s e w i l l c o m e v i a ` l o c a t i o n M a n a g e r D i d C h a n g e A u t h o r i z a t i o n ` .
2025-07-14 20:27:02 -07:00
manager . requestAlwaysAuthorization ( )
2025-07-20 09:45:47 -07:00
// A d d a t i m e o u t t o e n s u r e t h e c o n t i n u a t i o n i s a l w a y s r e s u m e d .
// I f t h e d e l e g a t e m e t h o d d o e s n ' t f i r e w i t h i n a r e a s o n a b l e t i m e ( e . g . , 1 0 s e c o n d s ) ,
// w e ' l l r e s u m e t h e c o n t i n u a t i o n w i t h . n o t D e t e r m i n e d t o p r e v e n t a l e a k .
Task { @ MainActor in // E n s u r e t h i s t a s k r u n s o n t h e M a i n A c t o r
do {
2025-09-18 13:19:45 -07:00
try await Task . sleep ( for : . seconds ( 5 ) ) // W a i t f o r 5 s e c o n d s
2025-07-20 09:45:47 -07:00
if let currentContinuation = self . permissionContinuation {
// I f t h e c o n t i n u a t i o n h a s n ' t b e e n n i l l e d o u t y e t , i t m e a n s
// l o c a t i o n M a n a g e r D i d C h a n g e A u t h o r i z a t i o n h a s n ' t b e e n c a l l e d .
Logger . services . warning ( " 📍 [App] Location permission request timed out. Resuming continuation with .notDetermined. " )
2025-09-18 13:19:45 -07:00
currentContinuation . resume ( returning : . denied )
2025-07-20 09:45:47 -07:00
self . permissionContinuation = nil // C l e a r t h e r e f e r e n c e
}
} catch is CancellationError {
// T h i s t a s k w a s c a n c e l l e d , l i k e l y b e c a u s e t h e m a i n c o n t i n u a t i o n w a s a l r e a d y r e s u m e d
// b y l o c a t i o n M a n a g e r D i d C h a n g e A u t h o r i z a t i o n . T h i s i s e x p e c t e d a n d s a f e .
Logger . services . debug ( " 📍 [App] Permission timeout task cancelled. " )
} catch {
Logger . services . error ( " 💥 [App] Error in permission timeout task: \( error . localizedDescription , privacy : . public ) " )
}
}
}
// T h i s d e f e r b l o c k e n s u r e s ` i s R e q u e s t i n g P e r m i s s i o n ` i s r e s e t a n d ` p e r m i s s i o n C o n t i n u a t i o n ` i s n i l l e d o u t
// r e g a r d l e s s o f h o w t h e ` w i t h C h e c k e d C o n t i n u a t i o n ` b l o c k e x i t s ( s u c c e s s , e r r o r , o r c a n c e l l a t i o n ) .
// I t a c t s a s a f i n a l c l e a n u p m e c h a n i s m .
defer {
self . isRequestingPermission = false
// T h i s n i l a s s i g n m e n t i s s o m e w h a t r e d u n d a n t w i t h t h e o n e i n l o c a t i o n M a n a g e r D i d C h a n g e A u t h o r i z a t i o n
// a n d t h e t i m e o u t T a s k , b u t i t p r o v i d e s a n e x t r a l a y e r o f s a f e t y .
self . permissionContinuation = nil
2025-05-03 20:44:12 -07:00
}
}
2025-07-20 09:45:47 -07:00
2025-07-17 19:03:41 -07:00
// / D e l e g a t e m e t h o d c a l l e d w h e n t h e l o c a t i o n a u t h o r i z a t i o n s t a t u s c h a n g e s .
// / - P a r a m e t e r m a n a g e r : T h e C L L o c a t i o n M a n a g e r i n s t a n c e .
2025-07-14 20:27:02 -07:00
func locationManagerDidChangeAuthorization ( _ manager : CLLocationManager ) {
2025-07-17 19:03:41 -07:00
// E n s u r e t h e c o n t i n u a t i o n e x i s t s b e f o r e a t t e m p t i n g t o r e s u m e i t .
2025-07-20 09:45:47 -07:00
// I f i t ' s n i l , i t m e a n s e i t h e r n o r e q u e s t w a s p e n d i n g o r i t w a s a l r e a d y r e s u m e d ( e . g . , b y t h e t i m e o u t ) .
2025-07-17 19:03:41 -07:00
guard let continuation = permissionContinuation else {
2025-07-20 09:45:47 -07:00
Logger . services . debug ( " 📍 [App] locationManagerDidChangeAuthorization called but no permissionContinuation is active or it was already handled. " )
2025-07-17 19:03:41 -07:00
return
}
// R e s u m e t h e c o n t i n u a t i o n w i t h t h e c u r r e n t a u t h o r i z a t i o n s t a t u s .
continuation . resume ( returning : manager . authorizationStatus )
// C R U C I A L : N i l o u t t h e c o n t i n u a t i o n i m m e d i a t e l y a f t e r r e s u m i n g i t .
// T h i s p r e v e n t s a t t e m p t i n g t o r e s u m e t h e s a m e c o n t i n u a t i o n m u l t i p l e t i m e s ,
// w h i c h w o u l d l e a d t o a r u n t i m e c r a s h .
self . permissionContinuation = nil
2025-07-20 09:45:47 -07:00
self . isRequestingPermission = false // R e s e t t h e f l a g a s t h e r e q u e s t h a s c o m p l e t e d
2025-07-14 20:27:02 -07:00
}
2025-07-20 09:45:47 -07:00
2025-07-14 20:27:02 -07:00
override init ( ) {
super . init ( )
self . manager . delegate = self
2025-07-17 19:03:41 -07:00
// A l l o w b a c k g r o u n d l o c a t i o n u p d a t e s f o r c o n t i n u o u s t r a c k i n g .
2024-02-05 21:46:16 -08:00
self . manager . allowsBackgroundLocationUpdates = true
2025-07-17 19:03:41 -07:00
// S e t d e s i r e d a c c u r a c y f o r l o c a t i o n u p d a t e s .
// C o n s i d e r y o u r a p p ' s n e e d s : k C L L o c a t i o n A c c u r a c y B e s t F o r N a v i g a t i o n , k C L L o c a t i o n A c c u r a c y B e s t , e t c .
// F o r g e n e r a l t r a c k i n g , k C L L o c a t i o n A c c u r a c y H u n d r e d M e t e r s m i g h t b e s u f f i c i e n t t o s a v e b a t t e r y .
self . manager . desiredAccuracy = kCLLocationAccuracyBest
// S e t t h e d i s t a n c e f i l t e r t o o n l y r e c e i v e u p d a t e s w h e n t h e d e v i c e h a s m o v e d a c e r t a i n d i s t a n c e .
self . manager . distanceFilter = kCLDistanceFilterNone // R e c e i v e a l l u p d a t e s i n i t i a l l y
2025-12-15 11:15:54 -08:00
if CLLocationManager . headingAvailable ( ) {
self . manager . headingFilter = 1 // U p d a t e h e a d i n g w h e n i t c h a n g e s b y 1 d e g r e e
self . manager . headingOrientation = . portrait // A d j u s t b a s e d o n d e v i c e o r i e n t a t i o n
}
2023-12-06 12:32:17 -08:00
}
2024-05-29 16:40:07 -05:00
2023-12-06 12:32:17 -08:00
func startLocationUpdates ( ) {
2025-05-03 20:44:12 -07:00
let status = self . manager . authorizationStatus
2025-07-17 19:03:41 -07:00
// G u a r d a g a i n s t s t a r t i n g u p d a t e s w i t h o u t p r o p e r a u t h o r i z a t i o n .
2025-05-03 20:44:12 -07:00
guard status = = . authorizedAlways || status = = . authorizedWhenInUse else {
2025-07-17 19:03:41 -07:00
Logger . services . warning ( " 📍 [App] Cannot start location updates: insufficient authorization status: \( status . rawValue ) " )
2025-05-03 20:44:12 -07:00
return
}
2024-06-23 16:11:02 -07:00
Logger . services . info ( " 📍 [App] Starting location updates " )
2025-07-17 19:03:41 -07:00
// U s i n g a T a s k f o r a s y n c h r o n o u s o p e r a t i o n s . T h e @ M a i n A c t o r i s o l a t i o n o f t h e c l a s s
// e n s u r e s t h a t a l l s t a t e c h a n g e s w i t h i n t h i s T a s k ( a c c e s s i n g @ P u b l i s h e d p r o p e r t i e s )
// w i l l b e p e r f o r m e d o n t h e m a i n a c t o r .
Task { @ MainActor in
2023-12-06 12:32:17 -08:00
do {
self . updatesStarted = true
2025-07-17 19:03:41 -07:00
// ` l i v e U p d a t e s ( ) ` p r o v i d e s a s t r e a m o f l o c a t i o n u p d a t e s .
2023-12-06 12:32:17 -08:00
let updates = CLLocationUpdate . liveUpdates ( )
for try await update in updates {
2025-07-17 19:03:41 -07:00
// C h e c k f o r t a s k c a n c e l l a t i o n t o a l l o w g r a c e f u l s t o p p i n g .
try Task . checkCancellation ( )
// I f ` u p d a t e s S t a r t e d ` i s s e t t o f a l s e ( e . g . , b y ` s t o p L o c a t i o n U p d a t e s ` ) ,
// b r e a k o u t o f t h e l o o p t o s t o p p r o c e s s i n g u p d a t e s .
if ! self . updatesStarted {
Logger . services . info ( " 🛑 [App] Location updates loop stopped due to updatesStarted being false. " )
break
}
2023-12-06 12:32:17 -08:00
if let loc = update . location {
self . isStationary = update . isStationary
2025-07-17 19:03:41 -07:00
let locationAdded = addLocation ( loc , smartPostion : enableSmartPosition )
2024-03-10 20:17:54 -07:00
if ! isRecording && locationAdded {
self . count = 1
} else if locationAdded && isRecording {
2023-12-27 12:45:46 -08:00
self . count += 1
2023-12-21 10:59:09 -08:00
}
2023-12-06 12:32:17 -08:00
}
}
2025-07-17 19:03:41 -07:00
} catch is CancellationError {
// H a n d l e e x p l i c i t t a s k c a n c e l l a t i o n g r a c e f u l l y .
Logger . services . info ( " 📍 [App] Location updates task was cancelled. " )
2023-12-06 12:32:17 -08:00
} catch {
2025-07-17 19:03:41 -07:00
// C a t c h a n y o t h e r e r r o r s d u r i n g l o c a t i o n u p d a t e s .
2025-03-31 22:06:00 -07:00
Logger . services . error ( " 💥 [App] Could not start location updates: \( error . localizedDescription , privacy : . public ) " )
2023-12-06 12:32:17 -08:00
}
2025-07-17 19:03:41 -07:00
// T h e T a s k c o m p l e t e s i m p l i c i t l y h e r e .
2023-12-06 12:32:17 -08:00
}
}
2025-12-15 11:15:54 -08:00
// N e w m e t h o d t o s t a r t h e a d i n g u p d a t e s
func startHeadingUpdates ( ) {
guard CLLocationManager . headingAvailable ( ) else {
Logger . services . warning ( " 📍 [App] Heading updates not available on this device. " )
return
}
guard manager . authorizationStatus = = . authorizedAlways || manager . authorizationStatus = = . authorizedWhenInUse else {
Logger . services . warning ( " 📍 [App] Cannot start heading updates: insufficient authorization status. " )
return
}
Logger . services . info ( " 📍 [App] Starting heading updates " )
manager . startUpdatingHeading ( )
headingUpdatesStarted = true
}
// N e w m e t h o d t o s t o p h e a d i n g u p d a t e s
func stopHeadingUpdates ( ) {
Logger . services . info ( " 🛑 [App] Stopping heading updates " )
manager . stopUpdatingHeading ( )
headingUpdatesStarted = false
}
// I m p l e m e n t t h e C L L o c a t i o n M a n a g e r D e l e g a t e m e t h o d f o r h e a d i n g u p d a t e s
func locationManager ( _ manager : CLLocationManager , didUpdateHeading newHeading : CLHeading ) {
// U p d a t e h e a d i n g o n t h e m a i n t h r e a d
Task { @ MainActor in
self . heading = newHeading . trueHeading >= 0 ? newHeading . trueHeading : newHeading . magneticHeading
}
}
2025-07-17 19:03:41 -07:00
// / S t o p s r e c e i v i n g l i v e l o c a t i o n u p d a t e s .
2023-12-06 12:32:17 -08:00
func stopLocationUpdates ( ) {
2024-06-23 16:11:02 -07:00
Logger . services . info ( " 🛑 [App] Stopping location updates " )
2025-07-17 19:03:41 -07:00
// S e t t i n g ` u p d a t e s S t a r t e d ` t o f a l s e w i l l c a u s e t h e ` l i v e U p d a t e s ( ) ` l o o p t o b r e a k .
2023-12-06 12:32:17 -08:00
self . updatesStarted = false
}
2025-07-17 19:03:41 -07:00
// / A d d s a l o c a t i o n t o t h e a r r a y a n d u p d a t e s t r a c k i n g m e t r i c s , a p p l y i n g s m a r t p o s i t i o n f i l t e r s i f e n a b l e d .
// / - P a r a m e t e r s :
// / - l o c a t i o n : T h e ` C L L o c a t i o n ` o b j e c t t o a d d .
// / - s m a r t P o s t i o n : A b o o l e a n i n d i c a t i n g w h e t h e r t o a p p l y s m a r t p o s i t i o n f i l t e r i n g .
// / - R e t u r n s : ` t r u e ` i f t h e l o c a t i o n w a s a d d e d , ` f a l s e ` i f i t w a s f i l t e r e d o u t b y s m a r t p o s i t i o n .
2024-03-10 20:17:54 -07:00
func addLocation ( _ location : CLLocation , smartPostion : Bool ) -> Bool {
if smartPostion {
let age = - location . timestamp . timeIntervalSinceNow
if age > 10 {
2025-04-01 17:33:39 -07:00
Logger . services . info ( " 📍 [App] Smart Position - Bad Location: Too Old \( age , privacy : . public ) seconds ago \( location , privacy : . private ( mask : . none ) ) " )
2024-03-10 20:17:54 -07:00
return false
}
if location . horizontalAccuracy < 0 {
2025-03-31 23:11:40 -07:00
Logger . services . info ( " 📍 [App] Smart Position - Bad Location: Horizontal Accuracy: \( location . horizontalAccuracy ) \( location , privacy : . private ( mask : . none ) ) " )
2024-03-10 20:17:54 -07:00
return false
}
2025-07-17 19:03:41 -07:00
// C o n s i d e r a d j u s t i n g t h i s t h r e s h o l d b a s e d o n y o u r n e e d s . 5 m e t e r s i s q u i t e s t r i c t .
2024-04-11 19:30:05 -07:00
if location . horizontalAccuracy > 5 {
2025-03-31 23:11:40 -07:00
Logger . services . info ( " 📍 [App] Smart Position - Bad Location: Horizontal Accuracy: \( location . horizontalAccuracy ) \( location , privacy : . private ( mask : . none ) ) " )
2024-03-10 20:17:54 -07:00
return false
}
2023-12-21 10:59:09 -08:00
}
2023-12-24 22:42:07 -08:00
if isRecording {
2024-01-21 12:24:18 -08:00
if let lastLocation = locationsArray . last {
let distance = location . distance ( from : lastLocation )
let gain = location . altitude - lastLocation . altitude
distanceTraveled += distance
if gain > 0 {
elevationGain += gain
}
2023-12-24 22:42:07 -08:00
}
2024-03-10 20:17:54 -07:00
locationsArray . append ( location )
} else {
2025-07-17 19:03:41 -07:00
// I f n o t r e c o r d i n g , o n l y k e e p t h e l a t e s t l o c a t i o n .
2024-03-10 20:17:54 -07:00
locationsArray = [ location ]
2023-12-24 22:42:07 -08:00
}
2023-12-21 10:59:09 -08:00
return true
}
2025-07-17 19:03:41 -07:00
// D e f a u l t l o c a t i o n ( A p p l e P a r k ) u s e d a s a f a l l b a c k .
Transports Interface to Support TCP for all Platforms and Serial on Mac (#1341)
* Initial implementation of transports
* Initial LogRadio implementation
* Fixes for Settings view (caused by debug commenting)
* Refinement of the object and actor model
* Connect view text and tab updates
* Fix mac catalyst and tests
* Warning and logging clean-up
* In progress commit
* Serial Transport and Reconnect draft work
* Serial transport and reconnection draft work
* Quick fix for BLE - still more work to do
* interim commit
* More in progress changes
* Minor improvements
* Pretty good initial implementation
* Bump version beyond the app store
* Fix for disconnection swipeAction
* Tweaks to TCPConnection implementation
* Retry for NONCE_ONLY_DB
* Revert json string change
* Simplified some of the API + "Anti-discovery"
* Tweaks for devices leaving the discovery process
* Bump version
* iOS26 Tweaks
* Tweaks and bug fixes
* Add link with slash sf symbol
* update symbol image on connect view
* BLE disconnect handling
* Log privacy attributes
* Onboarding and minor fixes.
* change database to nodes, add emoji to tcp logs
* Error handling improvements
* More logging emojis
* Suppressed unnecessary errors on disconnect
* Heartbeat emoji
* Add bluetooth symbol
* add privacy attributes to [TCP] logs, add custom bluetooth logo
* Improve routing logs
* Emoji for connect logs
* Heartbeat emoji
* Add CBCentralManagerScanOptionAllowDuplicatesKey options to central for bluetooth
* fix nav errors by switching from observableobject to state
* Update connection indicator icon
* fix for BLE disconnects
* Connection process fixes
* More fixes/tweaks to connection process
* Strict concurrency
* Fix some warnings, remove wifi warning
* delete stale keys
* interim commit
* Update privacy for log, fix wrong space
* fix a couple of linting items
* Switch to targeted
* interim commit
* BLE Signal strenth on connect view
* Remove BLE RSSI from long press menu
* Modem lights
* minor spacing tweak
* Additional BLE logging and a scanning fix.
* Discovery and BLE RSSI improvements
* Background suspension
* Update isConnected to enable UI during db load
* update protobufs
* Replace config if statements with switches, Fix unknown module config logging, make dark mode modem circle stroke color white so they are visible
* Additional logging cleanup
* hast
* Set unmessagable to true if the longname has the unmessagable emoji
* Connect error handling improvements
* Admin popup list icon and activity lights updates
* Revert use of .toolbar back to .navigationBarItems
* More public logging
* Better BLE error handling
* Node DB progress meter
* minor tweak to activity light interaction timing
* Fix comment linting, remove stale keys
* Remove stale keys
* Easy linting fixes
* Two more simple linting fixes
* clean up meshtasticapp
* More public logging
* Replay config
* Logging
* Fix for unselected node on Settings
* Tweak to progress meter based on device idiom
* Update protos
* Session replay redaction of messages
* Serial fix for old devices, and a let statement
* Mask text too
* Fix typo
* BLE poweredOff is now an auto-reconnectable error
* Update logging
* Fix for peerRemovedPairingInformation
* Logging for BLE peripheral:didUpdateValueFor errors.
* Fix for inconsistent swipe disconnect behavior
* periperal:didUpdateValueFor error handling
* Fix for BLEConnection continuation guarding
* BLEConnection actor deadlock on disconnect
* Heartbeat nonce
* Fix for swipe disconnect and task cancellation
* Fix for swipe actions not honoring .disabled()
* Tell BLETransport when BLEConnection is cancelled
* Update navigation logging
* Logging updates
* Bump version to 2.7.0
* Organize into folders and heartbeat stuff
* Minor improvements to manual TCP connection
* Auto-connect toggle
* Possible BLE bug, still waiting to see in logs
* Concurrency tweaks
* Concurrency improvements
* requestDeviceMetadata fix. fixes remote admin
* Minor typo fixes
* "All" button for log filters: category and level
* More robust continuation handling for BLE
* @FetchRequest based ChannelMessageList
* Update info.plist and device hardware file
* Move auto connect toggle to app settings and debug mode, tint properly with the accent color
* Add label to auto connect toggle
* Update log for node info received from ourselves over the mesh
* Remove unused scrollViewProxy
* Update Meshtastic/Views/Onboarding/DeviceOnboarding.swift
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update target for connect view
* Properly Set datadog environment
* Comment out ble manager
* Adjust cyclomatic complexity thresholds in .swiftlint.yml
* Linting fixes, delete ble manager
* Make session replay debug only
---------
Co-authored-by: jake-b <jake-b@users.noreply.github.com>
Co-authored-by: jake <jake@jakes-Mac-mini.local>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-27 08:09:02 -07:00
// n o n i s o l a t e d b e c a u s e i t i s n e v e r m u t a t e d
nonisolated static let DefaultLocation = CLLocationCoordinate2D ( latitude : 37.3346 , longitude : - 122.0090 )
2025-07-17 19:03:41 -07:00
// / P r o v i d e s t h e c u r r e n t l o c a t i o n , f a l l i n g b a c k t o l a s t k n o w n o r a d e f a u l t i f n e c e s s a r y .
2025-10-05 17:51:18 -07:00
static var currentLocation : CLLocationCoordinate2D ? {
2025-07-17 19:03:41 -07:00
// A t t e m p t t o g e t t h e m o s t r e c e n t l o c a t i o n f r o m t h e m a n a g e r .
2025-05-07 19:43:47 -07:00
if let location = shared . manager . location {
return location . coordinate
} else {
2025-10-05 17:51:18 -07:00
return nil
2025-01-21 09:19:14 -08:00
}
}
2025-07-17 19:03:41 -07:00
// / E s t i m a t e s t h e n u m b e r o f s a t e l l i t e s i n v i e w b a s e d o n h o r i z o n t a l a n d v e r t i c a l a c c u r a c y .
// / T h i s i s a h e u r i s t i c a n d n o t a d i r e c t r e p o r t o f s a t e l l i t e c o u n t .
2023-12-06 12:32:17 -08:00
static var satsInView : Int {
2023-12-23 22:47:56 -08:00
var sats = 0
2024-01-21 12:24:18 -08:00
if let newLocation = shared . locationsArray . last {
sats = 1
if newLocation . verticalAccuracy > 0 {
sats = 4
if 0. . . 5 ~= newLocation . horizontalAccuracy {
sats = 12
} else if 6. . . 15 ~= newLocation . horizontalAccuracy {
sats = 10
} else if 16. . . 30 ~= newLocation . horizontalAccuracy {
sats = 9
} else if 31. . . 45 ~= newLocation . horizontalAccuracy {
sats = 7
} else if 46. . . 60 ~= newLocation . horizontalAccuracy {
sats = 5
}
} else if newLocation . verticalAccuracy < 0 && 60. . . 300 ~= newLocation . horizontalAccuracy {
sats = 3
} else if newLocation . verticalAccuracy < 0 && newLocation . horizontalAccuracy > 300 {
sats = 2
2023-12-06 12:32:17 -08:00
}
}
return sats
}
}