2021-08-20 07:56:05 -07:00
//
2021-11-29 21:35:23 -08:00
// C o n n e c t . s w i f t
2022-07-07 00:29:52 -07:00
// M e s h t a s t i c A p p l e
2021-08-20 07:56:05 -07:00
//
2022-07-07 00:29:52 -07:00
// C o p y r i g h t ( c ) G a r t h V a n d e r H o u w e n 8 / 1 8 / 2 1 .
2021-08-20 07:56:05 -07:00
//
import SwiftUI
import MapKit
2022-10-18 13:53:50 -07:00
import CoreData
2021-08-20 07:56:05 -07:00
import CoreLocation
2021-10-03 20:47:04 -07:00
import CoreBluetooth
2023-03-01 23:41:10 -08:00
#if canImport ( ActivityKit )
import ActivityKit
#endif
2021-08-20 07:56:05 -07:00
2021-09-18 17:10:22 -07:00
struct Connect : View {
2023-03-06 10:33:18 -08:00
2021-12-12 17:17:46 -08:00
@ Environment ( \ . managedObjectContext ) var context
@ EnvironmentObject var bleManager : BLEManager
2023-04-26 09:19:45 -07:00
// @ E n v i r o n m e n t O b j e c t v a r u s e r S e t t i n g s : U s e r S e t t i n g s
2023-03-06 10:33:18 -08:00
@ State var node : NodeInfoEntity ?
2022-10-18 13:53:50 -07:00
@ State var isUnsetRegion = false
2022-09-27 06:34:13 -07:00
@ State var invalidFirmwareVersion = false
2023-03-01 23:41:10 -08:00
@ State var liveActivityStarted = false
2023-03-03 18:55:23 -08:00
@ State var presentingSwitchPreferredPeripheral = false
@ State var selectedPeripherialId = " "
2023-03-06 10:33:18 -08:00
2023-03-26 09:08:08 -07:00
init ( ) {
let notificationCenter = UNUserNotificationCenter . current ( )
notificationCenter . getNotificationSettings ( completionHandler : { ( settings ) in
if settings . authorizationStatus = = . notDetermined {
UNUserNotificationCenter . current ( ) . requestAuthorization ( options : [ . alert , . badge , . sound ] ) { success , error in
if success {
print ( " Notifications are all set! " )
} else if let error = error {
print ( error . localizedDescription )
}
}
}
} )
}
2023-03-01 23:41:10 -08:00
var body : some View {
2023-03-06 10:33:18 -08:00
2022-10-17 19:26:52 -07:00
NavigationStack {
2023-02-22 14:16:27 -08:00
VStack {
2022-07-07 00:29:52 -07:00
List {
if bleManager . isSwitchedOn {
2023-03-01 23:41:10 -08:00
Section ( header : Text ( " connected.radio " ) . font ( . title ) ) {
if bleManager . connectedPeripheral != nil && bleManager . connectedPeripheral . peripheral . state = = . connected {
HStack {
2023-05-10 13:44:27 -07:00
VStack ( alignment : . center ) {
CircleText ( text : node ? . user ? . shortName ? ? " ??? " , color : Color ( UIColor ( hex : UInt32 ( node ? . num ? ? 0 ) ) ) , circleSize : 80 , fontSize : ( node ? . user ? . shortName ? ? " ??? " ) . isEmoji ( ) ? 52 : 30 , textColor : UIColor ( hex : UInt32 ( node ? . num ? ? 0 ) ) . isLight ( ) ? . black : . white )
}
. padding ( . trailing )
2023-03-03 18:55:23 -08:00
VStack ( alignment : . leading ) {
if node != nil {
2023-03-03 22:29:31 -08:00
Text ( bleManager . connectedPeripheral . longName ) . font ( . title2 )
2023-03-03 18:55:23 -08:00
}
2023-05-05 09:27:24 -07:00
Text ( " ble.name " ) . font ( . callout ) + Text ( " : \( bleManager . connectedPeripheral . peripheral . name ? ? " unknown " . localized ) " )
2023-03-03 18:55:23 -08:00
. font ( . callout ) . foregroundColor ( Color . gray )
if node != nil {
2023-06-07 17:19:36 -07:00
Text ( " firmware.version " ) . font ( . callout ) + Text ( " : \( node ? . metadata ? . firmwareVersion ? ? " unknown " . localized ) " )
2023-03-03 18:55:23 -08:00
. font ( . callout ) . foregroundColor ( Color . gray )
}
if bleManager . isSubscribed {
Text ( " subscribed " ) . font ( . callout )
. foregroundColor ( . green )
} else {
Text ( " communicating " ) . font ( . callout )
. foregroundColor ( . orange )
2022-02-25 18:13:12 -10:00
}
2021-10-20 00:31:22 -07:00
}
2022-07-07 00:29:52 -07:00
}
2023-05-10 13:44:27 -07:00
. font ( . caption )
. foregroundColor ( Color . gray )
2023-03-01 23:41:10 -08:00
. padding ( [ . top , . bottom ] )
. swipeActions {
2023-03-06 10:33:18 -08:00
2023-03-01 23:41:10 -08:00
Button ( role : . destructive ) {
if bleManager . connectedPeripheral != nil && bleManager . connectedPeripheral . peripheral . state = = CBPeripheralState . connected {
bleManager . disconnectPeripheral ( reconnect : false )
}
} label : {
Label ( " disconnect " , systemImage : " antenna.radiowaves.left.and.right.slash " )
2021-10-17 15:08:12 -07:00
}
}
2023-03-06 10:33:18 -08:00
. contextMenu {
2023-03-01 23:41:10 -08:00
if node != nil {
2023-03-03 20:28:56 -08:00
#if ! targetEnvironment ( macCatalyst )
2023-03-01 23:41:10 -08:00
if #available ( iOS 16.2 , * ) {
Button {
if ! liveActivityStarted {
2023-03-03 20:28:56 -08:00
#if canImport ( ActivityKit )
2023-03-01 23:41:10 -08:00
print ( " Start live activity. " )
startNodeActivity ( )
2023-03-03 20:28:56 -08:00
#endif
2023-03-01 23:41:10 -08:00
} else {
2023-03-03 20:28:56 -08:00
#if canImport ( ActivityKit )
2023-03-01 23:41:10 -08:00
print ( " Stop live activity. " )
endActivity ( )
2023-03-03 20:28:56 -08:00
#endif
2023-03-01 23:41:10 -08:00
}
} label : {
2023-03-26 09:46:51 -07:00
Label ( " mesh.live.activity " , systemImage : liveActivityStarted ? " stop " : " play " )
2023-03-01 23:41:10 -08:00
}
}
2023-03-03 20:28:56 -08:00
#endif
2023-03-01 23:41:10 -08:00
Text ( " Num: \( String ( node ! . num ) ) " )
Text ( " Short Name: \( node ? . user ? . shortName ? ? " ???? " ) " )
2023-05-05 09:27:24 -07:00
Text ( " Long Name: \( node ? . user ? . longName ? ? " unknown " . localized ) " )
2023-03-01 23:41:10 -08:00
Text ( " BLE RSSI: \( bleManager . connectedPeripheral . rssi ) " )
2022-10-18 13:53:50 -07:00
}
}
2023-03-01 23:41:10 -08:00
if isUnsetRegion {
HStack {
NavigationLink {
LoRaConfig ( node : node )
} label : {
Label ( " set.region " , systemImage : " globe.americas.fill " )
. foregroundColor ( . red )
. font ( . title )
2022-10-12 15:26:25 -07:00
}
}
}
} else {
2023-03-06 10:33:18 -08:00
2023-03-01 23:41:10 -08:00
if bleManager . isConnecting {
HStack {
Image ( systemName : " antenna.radiowaves.left.and.right " )
2023-03-03 22:29:31 -08:00
. resizable ( )
2023-03-01 23:41:10 -08:00
. symbolRenderingMode ( . hierarchical )
2023-03-03 22:29:31 -08:00
. foregroundColor ( . orange )
. frame ( width : 60 , height : 60 )
2023-03-01 23:41:10 -08:00
. padding ( . trailing )
if bleManager . timeoutTimerCount = = 0 {
Text ( " connecting " )
2023-03-03 22:29:31 -08:00
. font ( . title2 )
2023-03-01 23:41:10 -08:00
. foregroundColor ( . orange )
2022-07-07 00:29:52 -07:00
} else {
2023-03-01 23:41:10 -08:00
VStack {
2023-03-06 10:33:18 -08:00
2023-03-01 23:41:10 -08:00
Text ( " Connection Attempt \( bleManager . timeoutTimerCount ) of 10 " )
. font ( . callout )
. foregroundColor ( . orange )
}
2021-10-17 21:10:36 -07:00
}
2022-07-07 00:29:52 -07:00
}
2023-03-01 23:41:10 -08:00
. padding ( )
2023-03-06 10:33:18 -08:00
2023-03-01 23:41:10 -08:00
} else {
2023-03-06 10:33:18 -08:00
2023-03-01 23:41:10 -08:00
if bleManager . lastConnectionError . count > 0 {
Text ( bleManager . lastConnectionError ) . font ( . callout ) . foregroundColor ( . red )
}
HStack {
Image ( systemName : " antenna.radiowaves.left.and.right.slash " )
2023-03-03 22:29:31 -08:00
. resizable ( )
2023-03-01 23:41:10 -08:00
. symbolRenderingMode ( . hierarchical )
2023-03-03 22:29:31 -08:00
. foregroundColor ( . red )
. frame ( width : 60 , height : 60 )
2023-03-01 23:41:10 -08:00
. padding ( . trailing )
Text ( " not.connected " ) . font ( . title3 )
2022-12-04 00:28:26 -08:00
}
2023-03-01 23:41:10 -08:00
. padding ( )
}
2022-07-07 00:29:52 -07:00
}
2023-03-01 23:41:10 -08:00
}
. textCase ( nil )
2023-03-06 10:33:18 -08:00
2023-03-01 23:41:10 -08:00
if ! self . bleManager . isConnected {
Section ( header : Text ( " available.radios " ) . font ( . title ) ) {
2023-03-16 17:46:44 -07:00
ForEach ( bleManager . peripherals . filter ( { $0 . peripheral . state = = CBPeripheralState . disconnected } ) . sorted ( by : { $0 . name < $1 . name } ) ) { peripheral in
2023-03-01 23:41:10 -08:00
HStack {
2023-04-26 09:19:45 -07:00
if UserDefaults . preferredPeripheralId = = peripheral . peripheral . identifier . uuidString {
2023-03-14 20:45:09 -07:00
Image ( systemName : " star.fill " )
. imageScale ( . large ) . foregroundColor ( . yellow )
. padding ( . trailing )
} else {
Image ( systemName : " circle.fill " )
. imageScale ( . large ) . foregroundColor ( . gray )
. padding ( . trailing )
}
2023-03-01 23:41:10 -08:00
Button ( action : {
2023-04-26 09:19:45 -07:00
if UserDefaults . preferredPeripheralId . count > 0 && peripheral . peripheral . identifier . uuidString != UserDefaults . preferredPeripheralId {
2023-03-03 18:55:23 -08:00
presentingSwitchPreferredPeripheral = true
selectedPeripherialId = peripheral . peripheral . identifier . uuidString
2023-03-01 23:41:10 -08:00
} else {
2023-03-03 18:55:23 -08:00
self . bleManager . connectTo ( peripheral : peripheral . peripheral )
2023-03-01 23:41:10 -08:00
}
} ) {
2023-03-14 20:45:09 -07:00
Text ( peripheral . name ) . font ( . callout )
2023-03-01 23:41:10 -08:00
}
Spacer ( )
VStack {
SignalStrengthIndicator ( signalStrength : peripheral . getSignalStrength ( ) )
}
} . padding ( [ . bottom , . top ] )
}
2023-03-03 22:54:46 -08:00
}
. confirmationDialog ( " Connecting to a new radio will clear all local app data on the phone. " , isPresented : $ presentingSwitchPreferredPeripheral , titleVisibility : . visible ) {
2023-03-06 10:33:18 -08:00
2023-03-03 22:54:46 -08:00
Button ( " Connect to new radio? " , role : . destructive ) {
bleManager . stopScanning ( )
bleManager . connectedPeripheral = nil
2023-04-26 09:19:45 -07:00
UserDefaults . preferredPeripheralId = " "
2023-03-03 22:54:46 -08:00
if bleManager . connectedPeripheral != nil && bleManager . connectedPeripheral . peripheral . state = = CBPeripheralState . connected {
bleManager . disconnectPeripheral ( )
}
2023-03-06 10:33:18 -08:00
2023-03-03 22:54:46 -08:00
clearCoreDataDatabase ( context : context )
2023-03-06 10:33:18 -08:00
let radio = bleManager . peripherals . first ( where : { $0 . peripheral . identifier . uuidString = = selectedPeripherialId } )
2023-03-03 22:54:46 -08:00
bleManager . connectTo ( peripheral : radio ! . peripheral )
presentingSwitchPreferredPeripheral = false
selectedPeripherialId = " "
}
}
. textCase ( nil )
2023-03-01 23:41:10 -08:00
}
2023-03-06 10:33:18 -08:00
2022-07-07 00:29:52 -07:00
} else {
2023-01-02 01:27:57 -08:00
Text ( " bluetooth.off " )
2022-07-07 00:29:52 -07:00
. foregroundColor ( . red )
. font ( . title )
}
}
2023-03-06 10:33:18 -08:00
2022-09-28 15:44:42 -07:00
HStack ( alignment : . center ) {
2022-06-22 23:54:49 -07:00
Spacer ( )
2023-03-03 20:28:56 -08:00
#if targetEnvironment ( macCatalyst )
2022-06-22 23:54:49 -07:00
if bleManager . connectedPeripheral != nil {
Button ( role : . destructive , action : {
if bleManager . connectedPeripheral != nil && bleManager . connectedPeripheral . peripheral . state = = CBPeripheralState . connected {
2023-01-06 00:56:44 -08:00
bleManager . disconnectPeripheral ( reconnect : false )
2022-02-25 18:13:12 -10:00
}
2022-06-22 23:54:49 -07:00
} ) {
2022-12-12 21:19:22 -08:00
Label ( " disconnect " , systemImage : " antenna.radiowaves.left.and.right.slash " )
2022-02-25 18:13:12 -10:00
}
2022-06-22 23:54:49 -07:00
. buttonStyle ( . bordered )
. buttonBorderShape ( . capsule )
. controlSize ( . large )
. padding ( )
}
2023-03-03 20:28:56 -08:00
#endif
2023-03-01 23:41:10 -08:00
Spacer ( )
}
. padding ( . bottom , 10 )
2023-02-22 14:16:27 -08:00
}
. navigationTitle ( " bluetooth " )
2022-10-17 18:52:49 -07:00
. navigationBarItems ( leading : MeshtasticLogo ( ) , trailing :
2023-03-01 23:41:10 -08:00
ZStack {
ConnectedDevice ( bluetoothOn : bleManager . isSwitchedOn , deviceConnected : bleManager . connectedPeripheral != nil , name : ( bleManager . connectedPeripheral != nil ) ? bleManager . connectedPeripheral . shortName : " ???? " )
} )
2023-02-22 14:16:27 -08:00
}
2023-03-06 10:33:18 -08:00
. sheet ( isPresented : $ invalidFirmwareVersion , onDismiss : didDismissSheet ) {
2022-09-27 06:34:13 -07:00
InvalidVersion ( minimumVersion : self . bleManager . minimumVersion , version : self . bleManager . connectedVersion )
2022-10-07 19:07:36 -07:00
. presentationDetents ( [ . large ] )
. presentationDragIndicator ( . automatic )
2022-07-13 08:32:34 -07:00
}
2023-03-06 10:33:18 -08:00
. onChange ( of : ( self . bleManager . invalidVersion ) ) { _ in
2022-09-27 06:34:13 -07:00
invalidFirmwareVersion = self . bleManager . invalidVersion
2022-07-13 08:32:34 -07:00
}
2022-10-18 13:53:50 -07:00
. onChange ( of : ( self . bleManager . isSubscribed ) ) { sub in
2023-03-06 10:33:18 -08:00
2023-04-26 09:19:45 -07:00
if UserDefaults . preferredPeripheralId . count > 0 && sub {
2023-03-06 10:33:18 -08:00
2022-10-18 13:53:50 -07:00
let fetchNodeInfoRequest : NSFetchRequest < NSFetchRequestResult > = NSFetchRequest . init ( entityName : " NodeInfoEntity " )
2023-03-04 08:52:40 -08:00
fetchNodeInfoRequest . predicate = NSPredicate ( format : " num == %lld " , Int64 ( bleManager . connectedPeripheral ? . num ? ? - 1 ) )
2023-03-06 10:33:18 -08:00
2022-10-18 13:53:50 -07:00
do {
2023-03-06 15:30:10 -08:00
guard let fetchedNode = try context . fetch ( fetchNodeInfoRequest ) as ? [ NodeInfoEntity ] else {
return
}
2022-10-18 13:53:50 -07:00
// F o u n d a n o d e , c h e c k i t f o r a r e g i o n
if ! fetchedNode . isEmpty {
node = fetchedNode [ 0 ]
if node ! . loRaConfig != nil && node ! . loRaConfig ? . regionCode ? ? 0 = = RegionCodes . unset . rawValue {
isUnsetRegion = true
} else {
isUnsetRegion = false
}
}
} catch {
2023-03-06 10:33:18 -08:00
2022-10-18 13:53:50 -07:00
}
}
}
2023-02-22 14:16:27 -08:00
. onAppear ( perform : {
2022-10-17 18:52:49 -07:00
self . bleManager . context = context
2021-10-23 00:19:23 -07:00
} )
2023-02-22 14:16:27 -08:00
}
2023-03-26 09:08:08 -07:00
#if canImport ( ActivityKit )
2023-03-03 20:08:04 -08:00
func startNodeActivity ( ) {
if #available ( iOS 16.2 , * ) {
liveActivityStarted = true
let timerSeconds = 60
2023-03-22 09:58:46 -07:00
let deviceMetrics = node ? . telemetries ? . filtered ( using : NSPredicate ( format : " metricsType == 0 " ) )
let mostRecent = deviceMetrics ? . lastObject as ? TelemetryEntity
2023-03-06 10:33:18 -08:00
2023-03-03 20:08:04 -08:00
let activityAttributes = MeshActivityAttributes ( nodeNum : Int ( node ? . num ? ? 0 ) , name : node ? . user ? . longName ? ? " unknown " )
2023-03-06 10:33:18 -08:00
2023-03-03 20:08:04 -08:00
let future = Date ( timeIntervalSinceNow : Double ( timerSeconds ) )
2023-03-06 10:33:18 -08:00
2023-03-14 13:01:35 -07:00
let initialContentState = MeshActivityAttributes . ContentState ( timerRange : Date . now . . . future , connected : true , channelUtilization : mostRecent ? . channelUtilization ? ? 0.0 , airtime : mostRecent ? . airUtilTx ? ? 0.0 , batteryLevel : UInt32 ( mostRecent ? . batteryLevel ? ? 0 ) )
2023-03-06 10:33:18 -08:00
2023-03-03 20:08:04 -08:00
let activityContent = ActivityContent ( state : initialContentState , staleDate : Calendar . current . date ( byAdding : . minute , value : 2 , to : Date ( ) ) ! )
2023-03-06 10:33:18 -08:00
2023-03-03 20:08:04 -08:00
do {
let myActivity = try Activity < MeshActivityAttributes > . request ( attributes : activityAttributes , content : activityContent ,
pushType : nil )
print ( " Requested MyActivity live activity. ID: \( myActivity . id ) " )
} catch let error {
print ( " Error requesting live activity: \( error . localizedDescription ) " )
}
2023-03-01 23:41:10 -08:00
}
}
2023-03-06 10:33:18 -08:00
2023-03-03 20:08:04 -08:00
func endActivity ( ) {
liveActivityStarted = false
Task {
if #available ( iOS 16.2 , * ) {
for activity in Activity < MeshActivityAttributes > . activities {
// C h e c k i f t h i s i s t h e a c t i v i t y a s s o c i a t e d w i t h t h i s o r d e r .
if activity . attributes . nodeNum = = node ? . num ? ? 0 {
await activity . end ( nil , dismissalPolicy : . immediate )
}
2023-03-01 23:41:10 -08:00
}
}
}
}
2023-03-26 09:08:08 -07:00
#endif
2023-03-06 10:33:18 -08:00
2022-09-27 06:34:13 -07:00
func didDismissSheet ( ) {
2023-01-03 21:45:10 -08:00
bleManager . disconnectPeripheral ( reconnect : false )
2022-09-27 06:34:13 -07:00
}
2021-08-20 07:56:05 -07:00
}