Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions LoopFollow/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// window — poisoned defaults would discard real history. Runs synchronously
// on a normal launch.
StorageReadiness.whenReady {
// SHA change fires an immediate ping (the scheduler can't notice an app
// update); otherwise the 24h scheduler handles cadence. See Telemetry.swift.
TelemetryClient.shared.recordColdLaunch()
Task.detached {
if TelemetryClient.shared.buildShaChangedSinceLastSend() {
await TelemetryClient.shared.maybeSend()
}
TelemetryClient.shared.scheduleRecurring()
}
TelemetryClient.shared.start()
}

return true
Expand Down
38 changes: 6 additions & 32 deletions LoopFollow/Application/MainTabView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ struct MainTabView: View {
@ObservedObject private var treatmentsPosition = Storage.shared.treatmentsPosition
@ObservedObject private var activeBanner = Observable.shared.activeBanner

@State private var showTelemetryConsent = false
@State private var showOnboarding = false

private var orderedItems: [TabItem] {
Expand Down Expand Up @@ -65,48 +64,23 @@ struct MainTabView: View {
MainViewController.bootstrap()

// Show the first-run onboarding once for everyone. Returning users
// get a prominent Skip on the welcome screen. The telemetry consent
// prompt is deferred until onboarding is dismissed so the two never
// appear on top of one another.
// get a prominent Skip on the welcome screen.
if !Storage.shared.hasCompletedOnboarding.value {
Observable.shared.isOnboardingActive.value = true
showOnboarding = true
} else {
runPostOnboardingPrompts()
requestNotificationsIfAlarmsEnabled()
}
}
.fullScreenCover(isPresented: $showOnboarding, onDismiss: {
Observable.shared.isOnboardingActive.value = false

// Covers both finishing and skipping onboarding — the telemetry and
// notification steps live inside the flow, so anyone who skips still
// needs these handled here.
runPostOnboardingPrompts()
}) {
OnboardingContainerView(onClose: { showOnboarding = false })
}
.sheet(isPresented: $showTelemetryConsent, onDismiss: {
// Ask for notifications only once telemetry is resolved, so the system
// prompt never stacks on top of the consent sheet.
// Covers both finishing and skipping onboarding — the notification
// step lives inside the flow, so anyone who skips still needs it
// handled here.
requestNotificationsIfAlarmsEnabled()
}) {
// User must explicitly choose — no swipe-to-dismiss.
TelemetryConsentView()
.interactiveDismissDisabled(true)
}
}

/// Runs after onboarding closes, whether it was completed or skipped. Telemetry
/// consent and notification permission both live inside the onboarding flow, so
/// a skip would otherwise bypass them. Telemetry consent goes first (as a
/// sheet); the notification request follows on its dismissal so the two never
/// appear at once. When the user completed the flow these are already decided,
/// so both calls are no-ops.
private func runPostOnboardingPrompts() {
if !Storage.shared.telemetryConsentDecisionMade.value {
showTelemetryConsent = true // notifications requested on its dismiss
} else {
requestNotificationsIfAlarmsEnabled()
OnboardingContainerView(onClose: { showOnboarding = false })
}
}

Expand Down
Loading