diff --git a/Meshtastic/Views/Settings/RouteRecorder.swift b/Meshtastic/Views/Settings/RouteRecorder.swift index 88c4a44e..71794725 100644 --- a/Meshtastic/Views/Settings/RouteRecorder.swift +++ b/Meshtastic/Views/Settings/RouteRecorder.swift @@ -1,295 +1,295 @@ +//// +//// Routes.swift +//// Meshtastic +//// +//// Created by Garth Vander Houwen on 11/21/23. +//// // -// Routes.swift -// Meshtastic +//import SwiftUI +//import CoreData +//import MapKit +//import CoreLocation +//import CoreMotion // -// Created by Garth Vander Houwen on 11/21/23. +//@available(iOS 17.0, macOS 14.0, *) +//struct RouteRecorder: View { +// +// @ObservedObject var locationsHandler: LocationsHandler = LocationsHandler.shared +// @Environment(\.managedObjectContext) var context +// @State private var position: MapCameraPosition = .userLocation(followsHeading: true, fallback: .automatic) +// //@State var mapStyle: MapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: .all, showsTraffic: true) +// @State var mapStyle: MapStyle = MapStyle.standard(elevation: .realistic) +// @State var isShowingDetails = false +// @Namespace var namespace +// @Namespace var routerecorderscope +// @State var recording: RouteEntity? +// @State var color: Color = .blue +// +// var body: some View { +// VStack { +// ZStack { +// Map(position: $position, scope: routerecorderscope) { +// UserAnnotation() +// /// Route Lines +// let lineCoords = locationsHandler.locationsArray.compactMap({(position) -> CLLocationCoordinate2D in +// return position.coordinate +// }) +// +// let gradient = LinearGradient( +// colors: [color], +// startPoint: .leading, endPoint: .trailing +// ) +// let dashed = StrokeStyle( +// lineWidth: 3, +// lineCap: .round, lineJoin: .round, dash: [10, 10] +// ) +// MapPolyline(coordinates: lineCoords) +// .stroke(gradient, style: dashed) // - -import SwiftUI -import CoreData -import MapKit -import CoreLocation -import CoreMotion - -@available(iOS 17.0, macOS 14.0, *) -struct RouteRecorder: View { - - @ObservedObject var locationsHandler: LocationsHandler = LocationsHandler.shared - @Environment(\.managedObjectContext) var context - @State private var position: MapCameraPosition = .userLocation(followsHeading: true, fallback: .automatic) - //@State var mapStyle: MapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: .all, showsTraffic: true) - @State var mapStyle: MapStyle = MapStyle.standard(elevation: .realistic) - @State var isShowingDetails = false - @Namespace var namespace - @Namespace var routerecorderscope - @State var recording: RouteEntity? - @State var color: Color = .blue - - var body: some View { - VStack { - ZStack { - Map(position: $position, scope: routerecorderscope) { - UserAnnotation() - /// Route Lines - let lineCoords = locationsHandler.locationsArray.compactMap({(position) -> CLLocationCoordinate2D in - return position.coordinate - }) - - let gradient = LinearGradient( - colors: [color], - startPoint: .leading, endPoint: .trailing - ) - let dashed = StrokeStyle( - lineWidth: 3, - lineCap: .round, lineJoin: .round, dash: [10, 10] - ) - MapPolyline(coordinates: lineCoords) - .stroke(gradient, style: dashed) - - } - .mapStyle(mapStyle) - } - .mapScope(routerecorderscope) - .safeAreaInset(edge: .bottom) { - ZStack { - VStack { - HStack(spacing: 10) { - Spacer() - - Button { - isShowingDetails = true - } label: { - Image(systemName: locationsHandler.isRecording ? "record.circle.fill" : "record.circle") - .font(.system(size: 72)) - .symbolRenderingMode(.multicolor) - .foregroundColor(.red) - } - .buttonStyle(.bordered) - .foregroundColor(.red) - .buttonBorderShape(.circle) - .matchedGeometryEffect(id: "Details Button", in: namespace) - - Spacer() - } - } - } - .padding() - } - .sheet(isPresented: $isShowingDetails) { - NavigationStack { - VStack { - if locationsHandler.isRecording { - HStack (alignment: .center) { - Image(systemName: "record.circle.fill") - .symbolRenderingMode(.multicolor) - .font(.title) - .foregroundColor(.red) - Text("Recording route") - .font(.title) - Spacer() - Text("\(locationsHandler.count)") - .foregroundColor(.red) - .font(.title2) - } - .padding() - } else if locationsHandler.isRecordingPaused { - HStack (alignment: .center) { - - Image(systemName: "playpause") - .symbolRenderingMode(.multicolor) - .font(.title3) - .foregroundColor(.red) - Text("Route recording paused") - .font(.title) - } - .padding(.top) - } - - if locationsHandler.isRecording || locationsHandler.isRecordingPaused { - Divider() - HStack { - VStack { - Text(locationsHandler.recordingStarted ?? Date(), style: .timer) - .font(.title) - .fixedSize() - Text("Time") - .font(.callout) - .fixedSize() - } - .padding(.horizontal) - Divider() - VStack { - let distance = Measurement(value: locationsHandler.distanceTraveled, unit: UnitLength.meters) - Text("\(distance.formatted())") - .font(.title) - .fixedSize() - Text("Distance") - .font(.callout) - .fixedSize() - } - .padding(.horizontal) - Divider() - VStack { - let gain = Measurement(value: locationsHandler.elevationGain, unit: UnitLength.meters) - Text(gain.formatted()) - .font(.title) - Text("Elev. Gain") - .font(.callout) - } - .padding(.horizontal) - } - .frame(maxHeight: 90) - } - Divider() - VStack(alignment: .leading) { - List { - GPSStatus(largeFont: .body, smallFont: .callout) - } - .listStyle(.plain) - HStack { - Spacer() - if !locationsHandler.isRecording && !locationsHandler.isRecordingPaused { - /// We are not recording or paused, show start recording button - Button { - locationsHandler.isRecording = true - locationsHandler.count = 0 - locationsHandler.distanceTraveled = 0.0 - locationsHandler.elevationGain = 0.0 - locationsHandler.locationsArray.removeAll() - locationsHandler.recordingStarted = Date() - let newRoute = RouteEntity(context: context) - newRoute.name = String("Route Recording") - newRoute.id = Int32.random(in: Int32(Int8.max) ... Int32.max) - newRoute.color = Int64(UIColor.random.hex) - newRoute.date = Date() - newRoute.enabled = false - color = Color(UIColor(hex: UInt32(newRoute.color))) - self.recording = newRoute - do { - try context.save() - print("💾 Saved a new route") - } catch { - context.rollback() - let nsError = error as NSError - print("💥 Error Saving RouteEntity from the Route Recorder \(nsError)") - } - } label: { - Label("start", systemImage: "play") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding(.bottom) - - } else if locationsHandler.isRecording { - /// We are recording show pause button - Button { - locationsHandler.isRecording = false - locationsHandler.isRecordingPaused = true - } label: { - Label("pause", systemImage: "pause") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding(.bottom) - } else if locationsHandler.isRecordingPaused { - /// We are paused show resume button - Button { - locationsHandler.isRecording = true - locationsHandler.isRecordingPaused = false - } label: { - Label("resume", systemImage: "playpause") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding(.bottom) - } - - if locationsHandler.isRecording || locationsHandler.isRecordingPaused { - /// We are recording or paused, show finish button - Button { - locationsHandler.isRecording = false - locationsHandler.isRecordingPaused = false - locationsHandler.distanceTraveled = 0.0 - locationsHandler.elevationGain = 0.0 - locationsHandler.locationsArray.removeAll() - locationsHandler.recordingStarted = nil - if let rec = recording { - rec.enabled = true - context.refresh(rec, mergeChanges:true) - } - - do { - try context.save() - print("💾 Saved a route finish") - } catch { - context.rollback() - let nsError = error as NSError - print("💥 Error Saving RouteEntity from the Route Recorder \(nsError)") - } - } label: { - Label("finish", systemImage: "flag.checkered") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding(.bottom) - } -#if targetEnvironment(macCatalyst) - Button(role: .cancel) { - isShowingDetails = false - } label: { - Label("close", systemImage: "xmark") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding(.bottom) -#endif - Spacer() - } - - } - } - } - .presentationDetents([.fraction(0.30), .fraction(0.65)]) - .presentationDragIndicator(.hidden) - .interactiveDismissDisabled(false) - .onAppear { - UIApplication.shared.isIdleTimerDisabled = true - } - .onDisappear(perform: { - UIApplication.shared.isIdleTimerDisabled = false - }) - .onChange(of: locationsHandler.locationsArray.last) { newLoc in - if locationsHandler.isRecording { - if let loc = newLoc { - if recording != nil { - let locationEntity = LocationEntity(context: context) - locationEntity.routeLocation = recording - locationEntity.id = Int32(locationsHandler.count) - locationEntity.altitude = Int32(loc.altitude) - locationEntity.heading = Int32(loc.course) - locationEntity.speed = Int32(loc.speed) - locationEntity.latitudeI = Int32(loc.coordinate.latitude * 1e7) - locationEntity.longitudeI = Int32(loc.coordinate.longitude * 1e7) - do { - try context.save() - print("💾 Saved a new route location") - //print("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num)") - } catch { - context.rollback() - let nsError = error as NSError - print("💥 Error Saving LocationEntity from the Route Recorder \(nsError)") - } - } - } - } - } - } - } - .ignoresSafeArea(.all, edges: [.top, .leading, .trailing]) - } -} +// } +// .mapStyle(mapStyle) +// } +// .mapScope(routerecorderscope) +// .safeAreaInset(edge: .bottom) { +// ZStack { +// VStack { +// HStack(spacing: 10) { +// Spacer() +// +// Button { +// isShowingDetails = true +// } label: { +// Image(systemName: locationsHandler.isRecording ? "record.circle.fill" : "record.circle") +// .font(.system(size: 72)) +// .symbolRenderingMode(.multicolor) +// .foregroundColor(.red) +// } +// .buttonStyle(.bordered) +// .foregroundColor(.red) +// .buttonBorderShape(.circle) +// .matchedGeometryEffect(id: "Details Button", in: namespace) +// +// Spacer() +// } +// } +// } +// .padding() +// } +// .sheet(isPresented: $isShowingDetails) { +// NavigationStack { +// VStack { +// if locationsHandler.isRecording { +// HStack (alignment: .center) { +// Image(systemName: "record.circle.fill") +// .symbolRenderingMode(.multicolor) +// .font(.title) +// .foregroundColor(.red) +// Text("Recording route") +// .font(.title) +// Spacer() +// Text("\(locationsHandler.count)") +// .foregroundColor(.red) +// .font(.title2) +// } +// .padding() +// } else if locationsHandler.isRecordingPaused { +// HStack (alignment: .center) { +// +// Image(systemName: "playpause") +// .symbolRenderingMode(.multicolor) +// .font(.title3) +// .foregroundColor(.red) +// Text("Route recording paused") +// .font(.title) +// } +// .padding(.top) +// } +// +// if locationsHandler.isRecording || locationsHandler.isRecordingPaused { +// Divider() +// HStack { +// VStack { +// Text(locationsHandler.recordingStarted ?? Date(), style: .timer) +// .font(.title) +// .fixedSize() +// Text("Time") +// .font(.callout) +// .fixedSize() +// } +// .padding(.horizontal) +// Divider() +// VStack { +// let distance = Measurement(value: locationsHandler.distanceTraveled, unit: UnitLength.meters) +// Text("\(distance.formatted())") +// .font(.title) +// .fixedSize() +// Text("Distance") +// .font(.callout) +// .fixedSize() +// } +// .padding(.horizontal) +// Divider() +// VStack { +// let gain = Measurement(value: locationsHandler.elevationGain, unit: UnitLength.meters) +// Text(gain.formatted()) +// .font(.title) +// Text("Elev. Gain") +// .font(.callout) +// } +// .padding(.horizontal) +// } +// .frame(maxHeight: 90) +// } +// Divider() +// VStack(alignment: .leading) { +// List { +// GPSStatus(largeFont: .body, smallFont: .callout) +// } +// .listStyle(.plain) +// HStack { +// Spacer() +// if !locationsHandler.isRecording && !locationsHandler.isRecordingPaused { +// /// We are not recording or paused, show start recording button +// Button { +// locationsHandler.isRecording = true +// locationsHandler.count = 0 +// locationsHandler.distanceTraveled = 0.0 +// locationsHandler.elevationGain = 0.0 +// locationsHandler.locationsArray.removeAll() +// locationsHandler.recordingStarted = Date() +// let newRoute = RouteEntity(context: context) +// newRoute.name = String("Route Recording") +// newRoute.id = Int32.random(in: Int32(Int8.max) ... Int32.max) +// newRoute.color = Int64(UIColor.random.hex) +// newRoute.date = Date() +// newRoute.enabled = false +// color = Color(UIColor(hex: UInt32(newRoute.color))) +// self.recording = newRoute +// do { +// try context.save() +// print("💾 Saved a new route") +// } catch { +// context.rollback() +// let nsError = error as NSError +// print("💥 Error Saving RouteEntity from the Route Recorder \(nsError)") +// } +// } label: { +// Label("start", systemImage: "play") +// } +// .buttonStyle(.bordered) +// .buttonBorderShape(.capsule) +// .controlSize(.large) +// .padding(.bottom) +// +// } else if locationsHandler.isRecording { +// /// We are recording show pause button +// Button { +// locationsHandler.isRecording = false +// locationsHandler.isRecordingPaused = true +// } label: { +// Label("pause", systemImage: "pause") +// } +// .buttonStyle(.bordered) +// .buttonBorderShape(.capsule) +// .controlSize(.large) +// .padding(.bottom) +// } else if locationsHandler.isRecordingPaused { +// /// We are paused show resume button +// Button { +// locationsHandler.isRecording = true +// locationsHandler.isRecordingPaused = false +// } label: { +// Label("resume", systemImage: "playpause") +// } +// .buttonStyle(.bordered) +// .buttonBorderShape(.capsule) +// .controlSize(.large) +// .padding(.bottom) +// } +// +// if locationsHandler.isRecording || locationsHandler.isRecordingPaused { +// /// We are recording or paused, show finish button +// Button { +// locationsHandler.isRecording = false +// locationsHandler.isRecordingPaused = false +// locationsHandler.distanceTraveled = 0.0 +// locationsHandler.elevationGain = 0.0 +// locationsHandler.locationsArray.removeAll() +// locationsHandler.recordingStarted = nil +// if let rec = recording { +// rec.enabled = true +// context.refresh(rec, mergeChanges:true) +// } +// +// do { +// try context.save() +// print("💾 Saved a route finish") +// } catch { +// context.rollback() +// let nsError = error as NSError +// print("💥 Error Saving RouteEntity from the Route Recorder \(nsError)") +// } +// } label: { +// Label("finish", systemImage: "flag.checkered") +// } +// .buttonStyle(.bordered) +// .buttonBorderShape(.capsule) +// .controlSize(.large) +// .padding(.bottom) +// } +//#if targetEnvironment(macCatalyst) +// Button(role: .cancel) { +// isShowingDetails = false +// } label: { +// Label("close", systemImage: "xmark") +// } +// .buttonStyle(.bordered) +// .buttonBorderShape(.capsule) +// .controlSize(.large) +// .padding(.bottom) +//#endif +// Spacer() +// } +// +// } +// } +// } +// .presentationDetents([.fraction(0.30), .fraction(0.65)]) +// .presentationDragIndicator(.hidden) +// .interactiveDismissDisabled(false) +// .onAppear { +// UIApplication.shared.isIdleTimerDisabled = true +// } +// .onDisappear(perform: { +// UIApplication.shared.isIdleTimerDisabled = false +// }) +// .onChange(of: locationsHandler.locationsArray.last) { newLoc in +// if locationsHandler.isRecording { +// if let loc = newLoc { +// if recording != nil { +// let locationEntity = LocationEntity(context: context) +// locationEntity.routeLocation = recording +// locationEntity.id = Int32(locationsHandler.count) +// locationEntity.altitude = Int32(loc.altitude) +// locationEntity.heading = Int32(loc.course) +// locationEntity.speed = Int32(loc.speed) +// locationEntity.latitudeI = Int32(loc.coordinate.latitude * 1e7) +// locationEntity.longitudeI = Int32(loc.coordinate.longitude * 1e7) +// do { +// try context.save() +// print("💾 Saved a new route location") +// //print("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num)") +// } catch { +// context.rollback() +// let nsError = error as NSError +// print("💥 Error Saving LocationEntity from the Route Recorder \(nsError)") +// } +// } +// } +// } +// } +// } +// } +// .ignoresSafeArea(.all, edges: [.top, .leading, .trailing]) +// } +//} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index d9d6f598..65a94f63 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -69,14 +69,14 @@ struct Settings: View { Text("routes") } .tag(SettingsSidebar.routes) - NavigationLink { - RouteRecorder() - } label: { - Image(systemName: "record.circle") - .symbolRenderingMode(.hierarchical) - Text("route.recorder") - } - .tag(SettingsSidebar.routeRecorder) +// NavigationLink { +// RouteRecorder() +// } label: { +// Image(systemName: "record.circle") +// .symbolRenderingMode(.hierarchical) +// Text("route.recorder") +// } +// .tag(SettingsSidebar.routeRecorder) } let node = nodes.first(where: { $0.num == preferredNodeNum })