mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Location updates and route recorder mock up (does everything but save the route) Improve speed and heading formatting, Updated about view
This commit is contained in:
parent
c523b05d23
commit
91407e65ea
10 changed files with 270 additions and 150 deletions
23
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/Contents.json
vendored
Normal file
23
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "solarnode.jpeg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "solarnode 1.jpeg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "solar_node.jpeg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solar_node.jpeg
vendored
Normal file
BIN
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solar_node.jpeg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 206 KiB |
BIN
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solarnode 1.jpeg
vendored
Normal file
BIN
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solarnode 1.jpeg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
BIN
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solarnode.jpeg
vendored
Normal file
BIN
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solarnode.jpeg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
|
|
@ -16,12 +16,16 @@ import CoreLocation
|
|||
static let shared = LocationsHandler() // Create a single, shared instance of the object.
|
||||
private let manager: CLLocationManager
|
||||
private var background: CLBackgroundActivitySession?
|
||||
var locationsArray: [CLLocation]
|
||||
var enableSmartPosition: Bool
|
||||
|
||||
//@Published var lastLocation = CLLocation()
|
||||
@Published var locationsArray: [CLLocation]
|
||||
@Published var isStationary = false
|
||||
@Published var count = 0
|
||||
@Published var isRecording = false
|
||||
@Published var isRecordingPaused = false
|
||||
@Published var recordingStarted: Date?
|
||||
@Published var distanceTraveled = 0.0
|
||||
@Published var elevationGain = 0.0
|
||||
|
||||
@Published
|
||||
var updatesStarted: Bool = UserDefaults.standard.bool(forKey: "liveUpdatesStarted") {
|
||||
|
|
@ -95,6 +99,16 @@ import CoreLocation
|
|||
print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)")
|
||||
return false
|
||||
}
|
||||
if isRecording {
|
||||
if let lastLocation = locationsArray.last {
|
||||
let distance = location.distance(from: lastLocation)
|
||||
let gain = location.altitude - lastLocation.altitude
|
||||
distanceTraveled += distance
|
||||
if gain > 0 {
|
||||
elevationGain += gain
|
||||
}
|
||||
}
|
||||
}
|
||||
locationsArray.append(location)
|
||||
return true
|
||||
}
|
||||
|
|
@ -103,7 +117,7 @@ import CoreLocation
|
|||
|
||||
static var satsInView: Int {
|
||||
var sats = 0
|
||||
if let newLocation = shared.locationsArray.last{
|
||||
if let newLocation = shared.locationsArray.last {
|
||||
sats = 1
|
||||
if newLocation.verticalAccuracy > 0 {
|
||||
sats = 4
|
||||
|
|
|
|||
|
|
@ -47,12 +47,12 @@ struct PositionLog: View {
|
|||
}
|
||||
TableColumn("Speed") { position in
|
||||
let speed = Measurement(value: Double(position.speed), unit: UnitSpeed.kilometersPerHour)
|
||||
Text(speed.formatted())
|
||||
Text(speed.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))))
|
||||
}
|
||||
TableColumn("Heading") { position in
|
||||
let degrees = Angle.degrees(Double(position.heading))
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
|
||||
Text("\(heading.formatted())")
|
||||
Text(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))
|
||||
}
|
||||
TableColumn("SNR") { position in
|
||||
Text("\(String(format: "%.2f", position.snr)) dB")
|
||||
|
|
|
|||
|
|
@ -17,11 +17,32 @@ struct AboutMeshtastic: View {
|
|||
|
||||
List {
|
||||
Section(header: Text("What is Meshtastic?")) {
|
||||
Text("An open source, off-grid, decentralized, mesh network built to run on affordable, low-power devices.")
|
||||
Text("An open source, off-grid, decentralized, mesh network that runs on affordable, low-power radios.")
|
||||
.font(.title3)
|
||||
|
||||
}
|
||||
Section(header: Text("Apple Apps")) {
|
||||
|
||||
if locale.region?.identifier ?? "US" == "US" {
|
||||
HStack {
|
||||
Image("SOLAR_NODE")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 75)
|
||||
.cornerRadius(5)
|
||||
.padding()
|
||||
VStack(alignment: .leading) {
|
||||
Link("Buy Complete Radios", destination: URL(string: "http://garthvh.com")!)
|
||||
.font(.title2)
|
||||
Text("Get custom waterproof solar and detection sensor router nodes, aluminium desktop nodes and rugged handsets.")
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
}
|
||||
Link("Sponsor App Development", destination: URL(string: "https://github.com/sponsors/garthvh")!)
|
||||
.font(.title2)
|
||||
Link("GitHub Repository", destination: URL(string: "https://github.com/meshtastic/Meshtastic-Apple")!)
|
||||
.font(.title2)
|
||||
Button("Review the app") {
|
||||
if let scene = UIApplication.shared.connectedScenes
|
||||
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
|
||||
|
|
@ -29,17 +50,8 @@ struct AboutMeshtastic: View {
|
|||
}
|
||||
}
|
||||
.font(.title2)
|
||||
Link("Sponsor App Development", destination: URL(string: "https://github.com/sponsors/garthvh")!)
|
||||
.font(.title2)
|
||||
Link("GitHub Repository", destination: URL(string: "https://github.com/meshtastic/Meshtastic-Apple")!)
|
||||
.font(.title2)
|
||||
}
|
||||
if locale.region?.identifier ?? "no locale" == "US" {
|
||||
Section(header: Text("Get Devices")) {
|
||||
Link("Buy Complete Radios", destination: URL(string: "http://garthvh.com")!)
|
||||
.font(.title2)
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("Project information")) {
|
||||
Link("Website", destination: URL(string: "https://meshtastic.org")!)
|
||||
.font(.title2)
|
||||
|
|
|
|||
|
|
@ -11,33 +11,37 @@ import CoreLocation
|
|||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct GPSStatus: View {
|
||||
|
||||
var largeFont: Font = .footnote
|
||||
var smallFont: Font = .caption2
|
||||
|
||||
@ObservedObject var locationsHandler: LocationsHandler = LocationsHandler.shared
|
||||
var body: some View {
|
||||
|
||||
if let newLocation = locationsHandler.locationsArray.last {
|
||||
let horizontalAccuracy = Measurement(value: newLocation.horizontalAccuracy, unit: UnitLength.meters)
|
||||
let verticalAccuracy = Measurement(value: newLocation.verticalAccuracy, unit: UnitLength.meters)
|
||||
let altitiude = Measurement(value: newLocation.altitude, unit: UnitLength.meters)
|
||||
let speed = Measurement(value: newLocation.speed, unit: UnitSpeed.kilometersPerHour)
|
||||
let speedAccuracy = Measurement(value: newLocation.speedAccuracy, unit: UnitSpeed.metersPerSecond)
|
||||
let courseAccuracy = Measurement(value: newLocation.courseAccuracy, unit: UnitAngle.degrees)
|
||||
let horizontalAccuracy = Measurement(value: newLocation.horizontalAccuracy, unit: UnitLength.meters)
|
||||
let verticalAccuracy = Measurement(value: newLocation.verticalAccuracy, unit: UnitLength.meters)
|
||||
let altitiude = Measurement(value: newLocation.altitude, unit: UnitLength.meters)
|
||||
let speed = Measurement(value: newLocation.speed, unit: UnitSpeed.kilometersPerHour)
|
||||
let speedAccuracy = Measurement(value: newLocation.speedAccuracy, unit: UnitSpeed.metersPerSecond)
|
||||
let courseAccuracy = Measurement(value: newLocation.courseAccuracy, unit: UnitAngle.degrees)
|
||||
|
||||
Label("Coordinate \(String(format: "%.5f", newLocation.coordinate.latitude)), \(String(format: "%.5f", newLocation.coordinate.longitude))", systemImage: "mappin")
|
||||
.font(.footnote)
|
||||
.font(largeFont)
|
||||
.textSelection(.enabled)
|
||||
HStack {
|
||||
Label("Accuracy \(horizontalAccuracy.formatted())", systemImage: "scope")
|
||||
.font(.footnote)
|
||||
.font(largeFont)
|
||||
Label("Sats Estimate \(LocationsHandler.satsInView)", systemImage: "sparkles")
|
||||
.font(.footnote)
|
||||
.font(largeFont)
|
||||
|
||||
}
|
||||
HStack {
|
||||
if newLocation.verticalAccuracy > 0 {
|
||||
Label("Altitude \(altitiude.formatted())", systemImage: "mountain.2")
|
||||
.font(.footnote)
|
||||
.font(largeFont)
|
||||
}
|
||||
Label("Accuracy \(verticalAccuracy.formatted())", systemImage: "lines.measurement.vertical")
|
||||
.font(.caption2)
|
||||
.font(smallFont)
|
||||
}
|
||||
HStack {
|
||||
let degrees = Angle.degrees(newLocation.course)
|
||||
|
|
@ -49,15 +53,15 @@ struct GPSStatus: View {
|
|||
.symbolRenderingMode(.hierarchical)
|
||||
.rotationEffect(degrees)
|
||||
}
|
||||
.font(.footnote)
|
||||
.font(largeFont)
|
||||
Label("Accuracy \(courseAccuracy.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))", systemImage: "safari")
|
||||
.font(.caption2)
|
||||
.font(smallFont)
|
||||
}
|
||||
HStack {
|
||||
Label("Speed \(speed.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))))", systemImage: "speedometer")
|
||||
.font(.footnote)
|
||||
.font(largeFont)
|
||||
Label("Accuracy \(speedAccuracy.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))))", systemImage: "gauge.with.dots.needle.bottom.50percent.badge.plus")
|
||||
.font(.caption2)
|
||||
.font(smallFont)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,142 +30,209 @@ struct TimerDisplayObject {
|
|||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct RouteRecorder: View {
|
||||
|
||||
@ObservedObject var locationsHandler = LocationsHandler.shared
|
||||
@ObservedObject var locationsHandler: LocationsHandler = LocationsHandler.shared
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@State private var position: MapCameraPosition = .userLocation(followsHeading: true, fallback: .automatic)
|
||||
@State var isTimerRunning = false
|
||||
@State var isShowingDetails = false
|
||||
@State var timer: Timer?
|
||||
@Namespace var namespace
|
||||
@Namespace var routerecorderscope
|
||||
@State var timeElapsed: TimerDisplayObject = TimerDisplayObject()
|
||||
@State var timerDisplay = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
VStack {
|
||||
VStack {
|
||||
Map(position: $position, scope: routerecorderscope) {
|
||||
UserAnnotation()
|
||||
Map(position: $position, scope: routerecorderscope) {
|
||||
UserAnnotation()
|
||||
// ForEach(locations, id: \.id) { location in
|
||||
// Marker(location.name, systemImage: location.icon, coordinate: location.location)
|
||||
// .tint(location.colour)
|
||||
// }
|
||||
}
|
||||
}
|
||||
.mapScope(routerecorderscope)
|
||||
.mapControls {
|
||||
MapUserLocationButton()
|
||||
MapCompass()
|
||||
MapScaleView()
|
||||
MapPitchToggle()
|
||||
}
|
||||
.mapStyle(.hybrid(elevation: .realistic, showsTraffic: true))
|
||||
.transition(.slide)
|
||||
.mapControlVisibility(.visible)
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
ZStack {
|
||||
VStack {
|
||||
HStack(spacing: 10) {
|
||||
Spacer()
|
||||
if isTimerRunning {
|
||||
Button {
|
||||
isShowingDetails = true
|
||||
isTimerRunning = false
|
||||
} label: {
|
||||
Image(systemName: "pause.fill")
|
||||
.frame(width: 60, height: 60)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.circle)
|
||||
.matchedGeometryEffect(id: "Pause Button", in: namespace)
|
||||
} else {
|
||||
Button {
|
||||
isShowingDetails = true
|
||||
isTimerRunning = true
|
||||
timeElapsed.seconds -= 1
|
||||
} label: {
|
||||
Image(systemName: "play.fill")
|
||||
.frame(width: 60, height: 60)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.circle)
|
||||
.matchedGeometryEffect(id: "Play Button", in: namespace)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.onReceive(timerDisplay) { _ in
|
||||
if isTimerRunning {
|
||||
timeElapsed.seconds += 1
|
||||
if timeElapsed.seconds == 60 {
|
||||
timeElapsed.seconds = 0
|
||||
timeElapsed.minutes += 1
|
||||
if timeElapsed.minutes == 60 {
|
||||
timeElapsed.minutes = 0
|
||||
timeElapsed.hours += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
.mapScope(routerecorderscope)
|
||||
.mapControls {
|
||||
MapUserLocationButton()
|
||||
MapCompass()
|
||||
MapScaleView()
|
||||
MapPitchToggle()
|
||||
}
|
||||
.mapStyle(.hybrid(elevation: .realistic, showsTraffic: true))
|
||||
.transition(.slide)
|
||||
.mapControlVisibility(.visible)
|
||||
.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 {
|
||||
HStack {
|
||||
Text(timeElapsed.display)
|
||||
.font(.largeTitle)
|
||||
Text("Time Elapseed")
|
||||
.font(.callout)
|
||||
.padding()
|
||||
}
|
||||
.sheet(isPresented: $isShowingDetails) {
|
||||
NavigationStack {
|
||||
VStack {
|
||||
if locationsHandler.isRecording {
|
||||
HStack (alignment: .center) {
|
||||
Image(systemName: "record.circle.fill")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.font(.title3)
|
||||
.foregroundColor(.red)
|
||||
Text("Recording route - \(locationsHandler.count) locations")
|
||||
.font(.title3)
|
||||
}
|
||||
.padding()
|
||||
.padding(.top)
|
||||
} else if locationsHandler.isRecordingPaused {
|
||||
HStack (alignment: .center) {
|
||||
|
||||
Image(systemName: "playpause")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.font(.title3)
|
||||
.foregroundColor(.red)
|
||||
Text("Route recording paused")
|
||||
.font(.title3)
|
||||
}
|
||||
.padding(.top)
|
||||
}
|
||||
|
||||
|
||||
if locationsHandler.isRecording || locationsHandler.isRecordingPaused {
|
||||
Divider()
|
||||
VStack(alignment: .leading) {
|
||||
if let lastLocation = locationsHandler.locationsArray.last {
|
||||
|
||||
let horizontalAccuracy = Measurement(value: lastLocation.horizontalAccuracy, unit: UnitLength.meters)
|
||||
let verticalAccuracy = Measurement(value: lastLocation.verticalAccuracy, unit: UnitLength.meters)
|
||||
let altitiude = Measurement(value: lastLocation.altitude, unit: UnitLength.meters)
|
||||
let speed = Measurement(value: lastLocation.speed, unit: UnitSpeed.kilometersPerHour)
|
||||
List {
|
||||
Label("Coordinate \(String(format: "%.5f", lastLocation.coordinate.latitude)), \(String(format: "%.5f", lastLocation.coordinate.longitude))", systemImage: "mappin")
|
||||
.textSelection(.enabled)
|
||||
Label("Horizontal Accuracy \(horizontalAccuracy.formatted())", systemImage: "scope")
|
||||
if lastLocation.verticalAccuracy > 0 {
|
||||
Label("Altitude \(altitiude.formatted())", systemImage: "mountain.2")
|
||||
}
|
||||
Label("Vertical Accuracy \(verticalAccuracy.formatted())", systemImage: "lines.measurement.vertical")
|
||||
Label("Satellites Estimate \(LocationHelper.satsInView)", systemImage: "sparkles")
|
||||
Label("\(locationsHandler.isStationary ? "Moving" : "Stationary")", systemImage: locationsHandler.isStationary ? "figure.walk.motion" : "figure.stand")
|
||||
if lastLocation.speedAccuracy > 0 {
|
||||
Label("Speed \(speed.formatted())", systemImage: "speedometer")
|
||||
}
|
||||
if lastLocation.courseAccuracy > 0 {
|
||||
/// Heading
|
||||
let degrees = Angle.degrees(Double(lastLocation.course))
|
||||
Label {
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
|
||||
/// Text("Heading: \(heading.formatted())")
|
||||
Text("Heading \(String(format: "%.2f", lastLocation.course))°")
|
||||
.foregroundColor(.primary)
|
||||
} icon: {
|
||||
Image(systemName: "location.circle")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
.rotationEffect(degrees)
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
HStack {
|
||||
VStack {
|
||||
Text(locationsHandler.recordingStarted ?? Date(), style: .timer)
|
||||
.font(.largeTitle)
|
||||
.fixedSize()
|
||||
Text("Time")
|
||||
.font(.callout)
|
||||
.fixedSize()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
Divider()
|
||||
VStack {
|
||||
let distance = Measurement(value: locationsHandler.distanceTraveled, unit: UnitLength.meters)
|
||||
Text("\(distance.formatted())")
|
||||
.font(.largeTitle)
|
||||
.fixedSize()
|
||||
Text("Distance")
|
||||
.font(.callout)
|
||||
.fixedSize()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
Divider()
|
||||
VStack {
|
||||
let gain = Measurement(value: locationsHandler.elevationGain, unit: UnitLength.meters)
|
||||
Text(gain.formatted())
|
||||
.font(.largeTitle)
|
||||
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 a new recording
|
||||
Button {
|
||||
locationsHandler.isRecording = true
|
||||
locationsHandler.count = 0
|
||||
locationsHandler.distanceTraveled = 0.0
|
||||
locationsHandler.elevationGain = 0.0
|
||||
locationsHandler.locationsArray.removeAll()
|
||||
locationsHandler.recordingStarted = Date()
|
||||
} label: {
|
||||
Label("start", systemImage: "start")
|
||||
}
|
||||
.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 recording show pause 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 show pause button
|
||||
Button {
|
||||
locationsHandler.isRecording = false
|
||||
locationsHandler.isRecordingPaused = false
|
||||
locationsHandler.distanceTraveled = 0.0
|
||||
locationsHandler.elevationGain = 0.0
|
||||
locationsHandler.locationsArray.removeAll()
|
||||
locationsHandler.recordingStarted = nil
|
||||
} label: {
|
||||
Label("finish", systemImage: "flag.checkered")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
}
|
||||
|
||||
Button(role: .cancel) {
|
||||
isShowingDetails = false
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.presentationDetents([.fraction(0.6)])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
.presentationDetents([.fraction(0.65)])
|
||||
.presentationDragIndicator(.hidden)
|
||||
.interactiveDismissDisabled()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,14 +70,14 @@ struct Settings: View {
|
|||
}
|
||||
.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 })
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue