mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Add Background Activity onboarding step, firmware version screens, and security nag
Agent-Logs-Url: https://github.com/meshtastic/Meshtastic-Apple/sessions/449fe2d6-dec9-4509-920e-e6196ca11d65 Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com>
This commit is contained in:
parent
218412f82a
commit
eabb9a9d30
6 changed files with 283 additions and 45 deletions
|
|
@ -116,7 +116,8 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
|
|||
// Constants
|
||||
let NONCE_ONLY_CONFIG = 69420
|
||||
let NONCE_ONLY_DB = 69421
|
||||
let minimumVersion = "2.3.15"
|
||||
let minimumVersion = "2.5.18"
|
||||
let securityVersion = "2.6.0"
|
||||
|
||||
// Global Objects
|
||||
// Chicken/Egg problem. Set in the App object immediately after
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ struct Connect: View {
|
|||
@State var node: NodeInfoEntity?
|
||||
@State var isUnsetRegion = false
|
||||
@State var invalidFirmwareVersion = false
|
||||
@State var showSecurityVersionNag = false
|
||||
@State var liveActivityStarted = false
|
||||
@ObservedObject var manualConnections = ManualConnectionList.shared
|
||||
|
||||
|
|
@ -347,6 +348,16 @@ struct Connect: View {
|
|||
// .onChange(of: accessoryManager) {
|
||||
// invalidFirmwareVersion = self.bleManager.invalidVersion
|
||||
// }
|
||||
.sheet(isPresented: $invalidFirmwareVersion) {
|
||||
InvalidVersion(minimumVersion: accessoryManager.minimumVersion, version: accessoryManager.activeConnection?.device.firmwareVersion ?? "?.?.?")
|
||||
.presentationDetents([.large])
|
||||
.presentationDragIndicator(.automatic)
|
||||
}
|
||||
.sheet(isPresented: $showSecurityVersionNag) {
|
||||
SecurityVersionNag(minimumSecureVersion: accessoryManager.securityVersion, version: accessoryManager.activeConnection?.device.firmwareVersion ?? "?.?.?")
|
||||
.presentationDetents([.large])
|
||||
.presentationDragIndicator(.automatic)
|
||||
}
|
||||
.onChange(of: self.accessoryManager.state) { _, state in
|
||||
|
||||
if let deviceNum = accessoryManager.activeDeviceNum, UserDefaults.preferredPeripheralId.count > 0 && state == .subscribed {
|
||||
|
|
@ -364,6 +375,11 @@ struct Connect: View {
|
|||
} catch {
|
||||
Logger.data.error("💥 Error fetching node info: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
// Check firmware version on connection
|
||||
invalidFirmwareVersion = !accessoryManager.checkIsVersionSupported(forVersion: accessoryManager.minimumVersion)
|
||||
if !invalidFirmwareVersion {
|
||||
showSecurityVersionNag = !accessoryManager.checkIsVersionSupported(forVersion: accessoryManager.securityVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,51 +14,93 @@ struct InvalidVersion: View {
|
|||
@State var version = ""
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
ScrollView {
|
||||
VStack(spacing: 20) {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.font(.system(size: 60))
|
||||
.foregroundColor(.orange)
|
||||
.padding(.top, 40)
|
||||
|
||||
VStack {
|
||||
|
||||
Text("Update Your Firmware")
|
||||
.font(.largeTitle)
|
||||
.foregroundColor(.orange)
|
||||
|
||||
Divider()
|
||||
VStack {
|
||||
Text("The Meshtastic Apple apps support firmware version \(minimumVersion) and above.")
|
||||
.font(.title2)
|
||||
.padding(.bottom)
|
||||
Link("Firmware update docs", destination: URL(string: "https://meshtastic.org/docs/getting-started/flashing-firmware/")!)
|
||||
.font(.title)
|
||||
.padding()
|
||||
Link("Additional help", destination: URL(string: "https://meshtastic.org/docs/faq")!)
|
||||
.font(.title)
|
||||
.padding()
|
||||
}
|
||||
.padding()
|
||||
Divider()
|
||||
.padding(.top)
|
||||
VStack {
|
||||
Text("🦕 End of life Version 🦖 ☄️")
|
||||
.font(.title3)
|
||||
.foregroundColor(.orange)
|
||||
.padding(.bottom)
|
||||
Text("Version \(minimumVersion) includes substantial network optimizations and extensive changes to devices and client apps. Only nodes version \(minimumVersion) and above are supported.")
|
||||
.font(.callout)
|
||||
.padding([.leading, .trailing, .bottom])
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("Close", systemImage: "xmark")
|
||||
Text("Firmware Update Required")
|
||||
.font(.largeTitle.bold())
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
if !version.isEmpty {
|
||||
Label {
|
||||
Text("Connected firmware: **\(version)**")
|
||||
} icon: {
|
||||
Image(systemName: "wifi.slash")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.font(.body)
|
||||
}
|
||||
Label {
|
||||
Text("Minimum required: **\(minimumVersion)**")
|
||||
} icon: {
|
||||
Image(systemName: "checkmark.shield.fill")
|
||||
.foregroundColor(.green)
|
||||
}
|
||||
.font(.body)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
#endif
|
||||
.background(Color(.secondarySystemBackground))
|
||||
.cornerRadius(12)
|
||||
|
||||
}.padding()
|
||||
Text("The Meshtastic Apple app requires firmware version \(minimumVersion) or later. Older firmware versions are no longer supported and may have compatibility issues or missing features.")
|
||||
.font(.body)
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.horizontal)
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("How to Update")
|
||||
.font(.headline)
|
||||
Link(destination: URL(string: "https://flasher.meshtastic.org")!) {
|
||||
Label("Open Web Flasher", systemImage: "bolt.fill")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.controlSize(.regular)
|
||||
.buttonBorderShape(.capsule)
|
||||
Link(destination: URL(string: "https://meshtastic.org/docs/getting-started/flashing-firmware/")!) {
|
||||
Label("Firmware Update Docs", systemImage: "book.fill")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.controlSize(.regular)
|
||||
.buttonBorderShape(.capsule)
|
||||
Link(destination: URL(string: "https://meshtastic.org/docs/faq")!) {
|
||||
Label("Additional Help", systemImage: "questionmark.circle.fill")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.controlSize(.regular)
|
||||
.buttonBorderShape(.capsule)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.secondarySystemBackground))
|
||||
.cornerRadius(12)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.padding(.bottom, 20)
|
||||
}
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("Close", systemImage: "xmark")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
103
Meshtastic/Views/Connect/SecurityVersionNag.swift
Normal file
103
Meshtastic/Views/Connect/SecurityVersionNag.swift
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
//
|
||||
// SecurityVersionNag.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 2024.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct SecurityVersionNag: View {
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State var minimumSecureVersion = ""
|
||||
@State var version = ""
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
ScrollView {
|
||||
VStack(spacing: 20) {
|
||||
Image(systemName: "shield.slash.fill")
|
||||
.font(.system(size: 60))
|
||||
.foregroundColor(.red)
|
||||
.padding(.top, 40)
|
||||
|
||||
Text("Security Update Recommended")
|
||||
.font(.largeTitle.bold())
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
if !version.isEmpty {
|
||||
Label {
|
||||
Text("Connected firmware: **\(version)**")
|
||||
} icon: {
|
||||
Image(systemName: "wifi.exclamationmark")
|
||||
.foregroundColor(.orange)
|
||||
}
|
||||
.font(.body)
|
||||
}
|
||||
Label {
|
||||
Text("Recommended secure version: **\(minimumSecureVersion)**")
|
||||
} icon: {
|
||||
Image(systemName: "checkmark.shield.fill")
|
||||
.foregroundColor(.green)
|
||||
}
|
||||
.font(.body)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.secondarySystemBackground))
|
||||
.cornerRadius(12)
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Security Advisory")
|
||||
.font(.headline)
|
||||
Text("Your connected device is running firmware older than **\(minimumSecureVersion)**, which contains known security vulnerabilities. Updating your firmware is strongly recommended to protect your device and mesh network.")
|
||||
.font(.body)
|
||||
.foregroundColor(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.secondarySystemBackground))
|
||||
.cornerRadius(12)
|
||||
.padding(.horizontal)
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("How to Update")
|
||||
.font(.headline)
|
||||
Link(destination: URL(string: "https://flasher.meshtastic.org")!) {
|
||||
Label("Open Web Flasher", systemImage: "bolt.fill")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.controlSize(.regular)
|
||||
.buttonBorderShape(.capsule)
|
||||
Link(destination: URL(string: "https://meshtastic.org/docs/getting-started/flashing-firmware/")!) {
|
||||
Label("Firmware Update Docs", systemImage: "book.fill")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.controlSize(.regular)
|
||||
.buttonBorderShape(.capsule)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.secondarySystemBackground))
|
||||
.cornerRadius(12)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.padding(.bottom, 20)
|
||||
}
|
||||
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("Dismiss")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ struct DeviceOnboarding: View {
|
|||
enum SetupGuide: Hashable {
|
||||
case notifications
|
||||
case location
|
||||
case backgroundActivity
|
||||
case localNetwork
|
||||
case bluetooth
|
||||
}
|
||||
|
|
@ -209,6 +210,69 @@ struct DeviceOnboarding: View {
|
|||
}
|
||||
}
|
||||
|
||||
var backgroundActivityView: some View {
|
||||
VStack {
|
||||
ScrollView(.vertical) {
|
||||
VStack {
|
||||
Text("Background Activity")
|
||||
.font(.largeTitle.bold())
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text(createBackgroundActivityString())
|
||||
.font(.body.bold())
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
makeRow(
|
||||
icon: "location.fill",
|
||||
title: String(localized: "Continuous Location Updates"),
|
||||
subtitle: String(localized: "Keep the mesh map updated and send your position to the mesh even while using other apps.")
|
||||
)
|
||||
makeRow(
|
||||
icon: "antenna.radiowaves.left.and.right",
|
||||
title: String(localized: "Background Mesh Tracking"),
|
||||
subtitle: String(localized: "Receive position updates from other nodes and maintain an accurate picture of the mesh while in the background.")
|
||||
)
|
||||
makeRow(
|
||||
icon: "battery.100.bolt",
|
||||
title: String(localized: "Battery Usage"),
|
||||
subtitle: String(localized: "Enabling background activity may increase battery usage. You can toggle this at any time in the app settings.")
|
||||
)
|
||||
Toggle(isOn: Binding(
|
||||
get: { LocationsHandler.shared.backgroundActivity },
|
||||
set: { LocationsHandler.shared.backgroundActivity = $0 }
|
||||
)) {
|
||||
Label {
|
||||
Text("Enable Background Activity")
|
||||
} icon: {
|
||||
Image(systemName: "location.circle")
|
||||
}
|
||||
}
|
||||
.fixedSize()
|
||||
.scaleEffect(0.85)
|
||||
.padding(.leading, 52)
|
||||
.tint(.accentColor)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
Spacer()
|
||||
Button {
|
||||
Task {
|
||||
await goToNextStep(after: .backgroundActivity)
|
||||
}
|
||||
} label: {
|
||||
Text("Continue")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding()
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
}
|
||||
|
||||
var localNetworkView: some View {
|
||||
VStack {
|
||||
ScrollView(.vertical) {
|
||||
|
|
@ -313,6 +377,8 @@ struct DeviceOnboarding: View {
|
|||
notificationView
|
||||
case .location:
|
||||
locationView
|
||||
case .backgroundActivity:
|
||||
backgroundActivityView
|
||||
case .bluetooth:
|
||||
bluetoothView
|
||||
case .localNetwork:
|
||||
|
|
@ -382,8 +448,10 @@ struct DeviceOnboarding: View {
|
|||
case .location:
|
||||
locationStatus = LocationsHandler.shared.manager.authorizationStatus
|
||||
if locationStatus != .notDetermined && locationStatus != .restricted {
|
||||
navigationPath.append(.localNetwork)
|
||||
navigationPath.append(.backgroundActivity)
|
||||
}
|
||||
case .backgroundActivity:
|
||||
navigationPath.append(.localNetwork)
|
||||
case .localNetwork:
|
||||
navigationPath.append(.bluetooth)
|
||||
|
||||
|
|
@ -393,6 +461,15 @@ struct DeviceOnboarding: View {
|
|||
}
|
||||
|
||||
// MARK: Formatting
|
||||
func createBackgroundActivityString() -> AttributedString {
|
||||
var fullText = AttributedString("Meshtastic can track your location in the background to keep the mesh map updated and send your position to the mesh even when the app is not in the foreground. You can update this setting at any time from settings.")
|
||||
if let range = fullText.range(of: "settings") {
|
||||
fullText[range].link = URL(string: UIApplication.openSettingsURLString)!
|
||||
fullText[range].foregroundColor = .blue
|
||||
}
|
||||
return fullText
|
||||
}
|
||||
|
||||
func createLocationString() -> AttributedString {
|
||||
var fullText = AttributedString(localized: "Meshtastic uses your phone's location to enable a number of features. You can update your location permissions at any time from settings.")
|
||||
if let range = fullText.range(of: String(localized: "settings")) {
|
||||
|
|
|
|||
|
|
@ -13,14 +13,13 @@ struct Firmware: View {
|
|||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var accessoryManager: AccessoryManager
|
||||
var node: NodeInfoEntity?
|
||||
@State var minimumVersion = "2.5.4"
|
||||
@State var version = ""
|
||||
@State private var currentDevice: DeviceHardware?
|
||||
@State private var latestStable: FirmwareRelease?
|
||||
@State private var latestAlpha: FirmwareRelease?
|
||||
|
||||
var body: some View {
|
||||
let supportedVersion = accessoryManager.checkIsVersionSupported(forVersion: minimumVersion)
|
||||
let supportedVersion = accessoryManager.checkIsVersionSupported(forVersion: accessoryManager.minimumVersion)
|
||||
let connectedVersion = accessoryManager.activeConnection?.device.firmwareVersion ?? "Unknown"
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
|
|
@ -63,7 +62,7 @@ struct Firmware: View {
|
|||
.foregroundStyle(.red)
|
||||
.font(.title2)
|
||||
.padding(.bottom)
|
||||
Text("Current Firmware Version: \(connectedVersion), Latest Firmware Version: \(minimumVersion)")
|
||||
Text("Current Firmware Version: \(connectedVersion), Latest Firmware Version: \(accessoryManager.minimumVersion)")
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.font(.title3)
|
||||
.padding(.bottom)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue