Skip to content

Commit 19fe2bf

Browse files
committed
Fix onboarding step-skip, Dexcom autofill, and Skip label
1 parent 5cd98a1 commit 19fe2bf

3 files changed

Lines changed: 47 additions & 33 deletions

File tree

‎LoopFollow/Helpers/Views/TogglableSecureInput.swift‎

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ struct TogglableSecureInput: View {
2828
.submitLabel(.done)
2929
.focused($isFocused)
3030
} else {
31-
HStack {
32-
Spacer()
33-
Text(maskString)
34-
.font(.body.monospaced())
35-
.foregroundColor(.primary)
36-
.allowsHitTesting(false)
37-
}
31+
// A real (masked) SecureField, not static text, so the
32+
// value stays editable while hidden and — crucially —
33+
// iOS AutoFill/keychain has a secure field to fill.
34+
SecureField(placeholder, text: $text)
35+
.multilineTextAlignment(.trailing)
36+
.textContentType(textContentType)
37+
.submitLabel(.done)
38+
.focused($isFocused)
3839
}
3940
}
4041

@@ -82,19 +83,15 @@ struct TogglableSecureInput: View {
8283
}
8384
.contentShape(Rectangle())
8485
.onTapGesture {
85-
if !isVisible {
86-
isVisible = true
87-
if style == .singleLine {
88-
isFocused = true
89-
} else if style == .multiLine {
90-
isMultilineFocused = true
91-
}
92-
} else {
93-
if style == .singleLine {
94-
isFocused = true
95-
} else if style == .multiLine {
96-
isMultilineFocused = true
97-
}
86+
switch style {
87+
case .singleLine:
88+
// The hidden state is already an editable SecureField, so tapping
89+
// just focuses it — no need to reveal the plaintext to type.
90+
isFocused = true
91+
case .multiLine:
92+
// The multi-line editor is only editable once revealed.
93+
if !isVisible { isVisible = true }
94+
isMultilineFocused = true
9895
}
9996
}
10097
}

‎LoopFollow/Onboarding/OnboardingViewModel.swift‎

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ final class OnboardingViewModel: ObservableObject {
8585
self.onClose = onClose
8686
includeTelemetryStep = !Storage.shared.telemetryConsentDecisionMade.value
8787
existingAlarmTypes = Set(Storage.shared.alarms.value.map(\.type))
88+
let hasNightscout = !Storage.shared.url.value.isEmpty
89+
let hasDexcom = !Storage.shared.shareUserName.value.isEmpty
90+
&& !Storage.shared.sharePassword.value.isEmpty
91+
isAlreadyConfigured = hasNightscout || hasDexcom
8892
seedAlarms = OnboardingViewModel.defaultSeedAlarms()
8993

9094
// Re-publish child changes so the footer's `canProceed` stays in sync
@@ -106,14 +110,14 @@ final class OnboardingViewModel: ObservableObject {
106110

107111
// MARK: - Derived state
108112

109-
/// True when the user already has a working data source — used to make
110-
/// skipping prominent for returning users.
111-
var isAlreadyConfigured: Bool {
112-
let nightscout = !Storage.shared.url.value.isEmpty
113-
let dexcom = !Storage.shared.shareUserName.value.isEmpty
114-
&& !Storage.shared.sharePassword.value.isEmpty
115-
return nightscout || dexcom
116-
}
113+
/// True when the user already had a working data source at launch — used to
114+
/// make skipping prominent for returning users and to decide whether the
115+
/// data-source/connect phases are shown. Captured once in `init`, not read
116+
/// live: the connect step persists the URL/credentials as the user types, so
117+
/// a live read would flip this to `true` mid-`.connect`, drop those phases
118+
/// from `activeSteps`, and make `advance()` fall through to `finish()` —
119+
/// silently skipping units, alarms, and the rest of setup.
120+
let isAlreadyConfigured: Bool
117121

118122
var canProceed: Bool {
119123
switch step {
@@ -234,13 +238,26 @@ final class OnboardingViewModel: ObservableObject {
234238

235239
func advance() {
236240
phaseProgress = nil
237-
guard let index = activeSteps.firstIndex(of: step),
238-
index + 1 < activeSteps.count
239-
else {
241+
let steps = activeSteps
242+
guard let index = steps.firstIndex(of: step) else {
243+
// Defensive: the current step should always be in `activeSteps`.
244+
// If it ever isn't (a state change removed it while we were on it),
245+
// continue to the next still-active step in canonical order rather
246+
// than silently ending setup via `finish()`.
247+
let canonical = OnboardingStep.allCases
248+
let currentRank = canonical.firstIndex(of: step) ?? canonical.count
249+
if let next = steps.first(where: { (canonical.firstIndex(of: $0) ?? 0) > currentRank }) {
250+
step = next
251+
} else {
252+
finish()
253+
}
254+
return
255+
}
256+
guard index + 1 < steps.count else {
240257
finish()
241258
return
242259
}
243-
step = activeSteps[index + 1]
260+
step = steps[index + 1]
244261
}
245262

246263
func goBack() {

‎LoopFollow/Onboarding/Steps/WelcomeStepView.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ struct WelcomeStepView: View {
5050
.buttonStyle(.borderedProminent)
5151

5252
Button { viewModel.skip() } label: {
53-
Text("Skip for now")
53+
Text("Skip")
5454
.font(.body.weight(.medium))
5555
}
5656
}

0 commit comments

Comments
 (0)