Skip to content

Commit 52e81cf

Browse files
committed
Somewhat sane app permissions onboarding flow
1 parent 15ad0fc commit 52e81cf

File tree

4 files changed

+76
-42
lines changed

4 files changed

+76
-42
lines changed

Localizable.xcstrings

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6631,6 +6631,9 @@
66316631
}
66326632
}
66336633
}
6634+
},
6635+
"Continue to next step" : {
6636+
66346637
},
66356638
"Control Type" : {
66366639
"localizations" : {
@@ -9464,9 +9467,6 @@
94649467
}
94659468
}
94669469
}
9467-
},
9468-
"Enable location services" : {
9469-
94709470
},
94719471
"Enable MQTT" : {
94729472

@@ -19219,6 +19219,9 @@
1921919219
}
1922019220
}
1922119221
}
19222+
},
19223+
"Meshtastic uses your phone's location to enable a number of features. You can update your location permissions at any time from Settings > App Setting > Open Settings." : {
19224+
1922219225
},
1922319226
"Meshtastic® Copyright Meshtastic LLC" : {
1922419227
"localizations" : {
@@ -21950,6 +21953,9 @@
2195021953
}
2195121954
}
2195221955
}
21956+
},
21957+
"Phone Location Permissions" : {
21958+
2195321959
},
2195421960
"phone.gps" : {
2195521961
"localizations" : {

Meshtastic/Helpers/BLEManager.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
200200
if UserDefaults.preferredPeripheralId.count < 1 {
201201
UserDefaults.preferredPeripheralId = peripheral.identifier.uuidString
202202
}
203-
if UserDefaults.firstLaunch {
204-
UserDefaults.showOnboarding = true
205-
}
206203
// Invalidate and reset connection timer count
207204
timeoutTimerCount = 0
208205
if timeoutTimer != nil {
@@ -221,6 +218,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
221218
disconnectPeripheral()
222219
return
223220
}
221+
if UserDefaults.firstLaunch {
222+
UserDefaults.showOnboarding = true
223+
}
224224
// Discover Services
225225
peripheral.discoverServices([meshtasticServiceCBUUID])
226226
Logger.services.info("✅ [BLE] Connected: \(peripheral.name ?? "Unknown", privacy: .public)")

Meshtastic/Helpers/LocationHelper.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,20 @@ class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate {
99

1010
// @Published var region = MKCoordinateRegion()
1111
@Published var authorizationStatus: CLAuthorizationStatus?
12+
13+
// The continuation we will use to asynchronously ask the user permission to track their location.
14+
private var permissionContinuation: CheckedContinuation<CLAuthorizationStatus, Never>?
15+
16+
func requestLocationAlwaysPermissions() async -> CLAuthorizationStatus {
17+
self.locationManager.requestAlwaysAuthorization()
18+
return await withCheckedContinuation { continuation in
19+
permissionContinuation = continuation
20+
}
21+
}
1222
override init() {
1323
super.init()
1424
locationManager.delegate = self
1525
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
16-
locationManager.pausesLocationUpdatesAutomatically = true
1726
locationManager.allowsBackgroundLocationUpdates = true
1827
locationManager.activityType = .other
1928
}
@@ -55,14 +64,12 @@ class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate {
5564
authorizationStatus = .authorizedAlways
5665
case .authorizedWhenInUse:
5766
authorizationStatus = .authorizedWhenInUse
58-
locationManager.requestLocation()
5967
case .restricted:
6068
authorizationStatus = .restricted
6169
case .denied:
6270
authorizationStatus = .denied
6371
case .notDetermined:
6472
authorizationStatus = .notDetermined
65-
locationManager.requestAlwaysAuthorization()
6673
default:
6774
break
6875
}

Meshtastic/Views/Onboarding/OnboardingView.swift

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -135,29 +135,54 @@ struct OnboardingView: View {
135135

136136
var locationView: some View {
137137
VStack {
138-
Text("Enable location services")
139-
Spacer()
140-
Button {
141-
Task {
142-
await goToNextStep(after: .location)
143-
}
144-
} label: {
145-
Text("Enable location services")
146-
.frame(maxWidth: .infinity)
138+
VStack {
139+
Text("Phone Location Permissions")
140+
.font(.largeTitle.bold())
141+
.multilineTextAlignment(.center)
142+
.fixedSize(horizontal: false, vertical: true)
143+
}
144+
VStack(alignment: .leading, spacing: 16) {
145+
Text("Meshtastic uses your phone's location to enable a number of features. You can update your location permissions at any time from Settings > App Setting > Open Settings.")
146+
.font(.body.bold())
147+
.multilineTextAlignment(.center)
148+
.fixedSize(horizontal: false, vertical: true)
149+
makeRow(
150+
icon: "location",
151+
title: "Share Location",
152+
subtitle: "Use your phone GPS to send locations to your node to instead of using a hardware GPS on your node."
153+
)
154+
makeRow(
155+
icon: "lines.measurement.horizontal",
156+
title: "Distance Measurements",
157+
subtitle: "Used to display the distance between your phone and other Meshtastic nodes where positions are available."
158+
)
159+
makeRow(
160+
icon: "line.3.horizontal.decrease.circle",
161+
title: "Distance Filters",
162+
subtitle: "Filter the node list and mesh map based on proximity to your phone."
163+
)
164+
makeRow(
165+
icon: "mappin",
166+
title: "Mesh Map Location",
167+
subtitle: "Enables the blue location dot for your phone in the mesh map."
168+
)
147169
}
148170
.padding()
149-
.buttonBorderShape(.capsule)
150-
.controlSize(.large)
151-
.padding()
152-
.buttonStyle(.borderedProminent)
153-
154-
Button {
155-
Task {
156-
await goToNextStep(after: .mqtt)
171+
Spacer()
172+
if LocationHelper.shared.locationManager.authorizationStatus != .notDetermined {
173+
Button {
174+
Task {
175+
await goToNextStep(after: .location)
176+
}
177+
} label: {
178+
Text("Continue to next step")
179+
.frame(maxWidth: .infinity)
157180
}
158-
} label: {
159-
Text("Set up later")
160-
.frame(maxWidth: .infinity)
181+
.padding()
182+
.buttonBorderShape(.capsule)
183+
.controlSize(.large)
184+
.padding()
185+
.buttonStyle(.borderedProminent)
161186
}
162187
}
163188
}
@@ -237,25 +262,23 @@ struct OnboardingView: View {
237262
switch step {
238263
case .none:
239264
let status = await UNUserNotificationCenter.current().notificationSettings().authorizationStatus
240-
if status == .notDetermined {
265+
let criticalAlert = await UNUserNotificationCenter.current().notificationSettings().criticalAlertSetting
266+
if status == .notDetermined && criticalAlert == .notSupported {
241267
navigationPath.append(.notifications)
242268
} else {
243269
fallthrough
244270
}
245271
case .notifications:
246-
let status = await UNUserNotificationCenter.current().notificationSettings().authorizationStatus
247-
let criticalAlert = await UNUserNotificationCenter.current().notificationSettings().criticalAlertSetting
248-
if status == .notDetermined && criticalAlert == .notSupported {
249-
await requestNotificationsPermissions()
272+
let status = LocationHelper.shared.locationManager.authorizationStatus
273+
if status == .notDetermined {
274+
navigationPath.append(.location)
275+
await LocationHelper.shared.requestLocationAlwaysPermissions()
250276
} else {
251277
fallthrough
252278
}
253279
case .location:
254-
let status = LocationHelper.shared
255-
.locationManager
256-
.authorizationStatus
257-
if status == .notDetermined {
258-
navigationPath.append(.location)
280+
if true {
281+
navigationPath.append(.mqtt)
259282
} else {
260283
fallthrough
261284
}
@@ -266,7 +289,7 @@ struct OnboardingView: View {
266289

267290
// MARK: Permission Checks
268291

269-
func requestNotificationsPermissions() async -> UNAuthorizationStatus {
292+
func requestNotificationsPermissions() async {
270293
let center = UNUserNotificationCenter.current()
271294
do {
272295
let success = try await center.requestAuthorization(options: [.alert, .badge, .sound, .criticalAlert])
@@ -275,10 +298,8 @@ struct OnboardingView: View {
275298
} else {
276299
Logger.services.info("Notification permissions denied")
277300
}
278-
return await center.notificationSettings().authorizationStatus
279301
} catch {
280302
Logger.services.error("Notification permissions error: \(error.localizedDescription)")
281-
return .notDetermined
282303
}
283304
}
284305
}

0 commit comments

Comments
 (0)