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
4 changes: 4 additions & 0 deletions LoopFollow.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@
FCC6886B24898FD800A0279D /* ObservationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6886A24898FD800A0279D /* ObservationToken.swift */; };
FCC6886D2489909D00A0279D /* AnyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6886C2489909D00A0279D /* AnyConvertible.swift */; };
FCC6886F2489A53800A0279D /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6886E2489A53800A0279D /* AppConstants.swift */; };
9C7FB98C98BE4FF98F4815EE /* Telemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFBE69CEF18416D84959974 /* Telemetry.swift */; };
FCD2A27D24C9D044009F7B7B /* Globals.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD2A27C24C9D044009F7B7B /* Globals.swift */; };
FCE537BC249A4D7D00F80BF8 /* carbBolusArrays.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE537BB249A4D7D00F80BF8 /* carbBolusArrays.swift */; };
FCEF87AC24A141A700AE6FA0 /* Localizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCEF87AA24A1417900AE6FA0 /* Localizer.swift */; };
Expand Down Expand Up @@ -875,6 +876,7 @@
FCC6886A24898FD800A0279D /* ObservationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservationToken.swift; sourceTree = "<group>"; };
FCC6886C2489909D00A0279D /* AnyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyConvertible.swift; sourceTree = "<group>"; };
FCC6886E2489A53800A0279D /* AppConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = "<group>"; };
BDFBE69CEF18416D84959974 /* Telemetry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Telemetry.swift; sourceTree = "<group>"; };
FCC688702489A57C00A0279D /* Loop Follow.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Loop Follow.entitlements"; sourceTree = "<group>"; };
FCD2A27C24C9D044009F7B7B /* Globals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Globals.swift; sourceTree = "<group>"; };
FCE537BB249A4D7D00F80BF8 /* carbBolusArrays.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = carbBolusArrays.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1676,6 +1678,7 @@
DD7B0D432D730A320063DCB6 /* CycleHelper.swift */,
DDF6999C2C5AAA4C0058A8D9 /* Views */,
FCC6886E2489A53800A0279D /* AppConstants.swift */,
BDFBE69CEF18416D84959974 /* Telemetry.swift */,
FCC6886A24898FD800A0279D /* ObservationToken.swift */,
FCC6886C2489909D00A0279D /* AnyConvertible.swift */,
FCC688592489554800A0279D /* BackgroundTaskAudio.swift */,
Expand Down Expand Up @@ -2143,6 +2146,7 @@
DD0650ED2DCE9371004D3B41 /* HighBgAlarmEditor.swift in Sources */,
DD7F4C172DD63FA700D449E9 /* RecBolusCondition.swift in Sources */,
FCC6886F2489A53800A0279D /* AppConstants.swift in Sources */,
9C7FB98C98BE4FF98F4815EE /* Telemetry.swift in Sources */,
DDE75D2D2DE71401007C1FC1 /* TogglableSecureInput.swift in Sources */,
DD7E19842ACDA50C00DBD158 /* Overrides.swift in Sources */,
DD0B9D562DE1EC8A0090C337 /* AlarmType+Snooze.swift in Sources */,
Expand Down
15 changes: 15 additions & 0 deletions LoopFollow/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

BackgroundRefreshManager.shared.register()

// Telemetry: record this cold launch (used by the rolling
// coldLaunches7d signal). If the running build's SHA differs from
// the one we last sent for, fire an immediate ping — the scheduler
// alone can't notice an app update. Otherwise let the 24h scheduler
// handle cadence: its first run is lastSentAt + 24h, so a relaunch
// a few hours after the previous send simply waits out the
// remainder. See Helpers/Telemetry.swift.
TelemetryClient.shared.recordColdLaunch()
Task.detached {
if TelemetryClient.shared.buildShaChangedSinceLastSend() {
await TelemetryClient.shared.maybeSend()
}
TelemetryClient.shared.scheduleRecurring()
}

// Detect Before-First-Unlock launch. If protected data is unavailable here,
// StorageValues were cached from encrypted UserDefaults and need a reload
// on the first foreground after the user unlocks.
Expand Down
36 changes: 36 additions & 0 deletions LoopFollow/Application/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@
// SceneDelegate.swift

import AVFoundation
import SwiftUI
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
let synthesizer = AVSpeechSynthesizer()

/// One-shot guard so the consent prompt is only attempted once per
/// process lifetime even if the scene activates repeatedly.
private var consentPromptShownThisProcess = false

func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
Expand All @@ -32,6 +37,37 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func sceneDidBecomeActive(_: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
runTelemetryFirstForegroundHook()
}

/// Presents the one-time consent sheet on first foreground. Sending is
/// handled by AppDelegate at launch and by TaskScheduler thereafter —
/// firing maybeSend here would duplicate the launch-time send.
private func runTelemetryFirstForegroundHook() {
if !Storage.shared.telemetryConsentDecisionMade.value,
!consentPromptShownThisProcess
{
consentPromptShownThisProcess = true
presentTelemetryConsentSheet()
}
}

private func presentTelemetryConsentSheet() {
guard let root = window?.rootViewController else { return }
// Find the topmost presented controller so we don't try to present
// over a sheet that's already up.
var top = root
while let presented = top.presentedViewController {
top = presented
}

let host = UIHostingController(rootView: TelemetryConsentView())
host.isModalInPresentation = true // user must explicitly choose
// Defer to the next runloop so view hierarchy is settled when the
// scene first becomes active on a fresh install.
DispatchQueue.main.async {
top.present(host, animated: true)
}
}

func scene(_: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
Expand Down
6 changes: 5 additions & 1 deletion LoopFollow/Helpers/BuildDetails.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ class BuildDetails {
return dict["com-LoopFollow-branch"] as? String
}

var commitSha: String? {
return dict["com-LoopFollow-commit-sha"] as? String
}

var branchAndSha: String {
let branch = branch ?? "Unknown"
let sha = dict["com-LoopFollow-commit-sha"] as? String ?? "Unknown"
let sha = commitSha ?? "Unknown"
return "\(branch) \(sha)"
}

Expand Down
Loading
Loading