Skip to content
Merged
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: 10 additions & 0 deletions Loop/Extensions/UserDefaults+Loop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ extension UserDefaults {
case inFlightAutomaticDose = "com.loopkit.Loop.inFlightAutomaticDose"
case favoriteFoods = "com.loopkit.Loop.favoriteFoods"
case automationHistory = "com.loopkit.Loop.automationHistory"
case showsActivityPresets = "com.loopkit.Loop.showsActivityPresets"
}

var legacyPumpManagerRawValue: PumpManager.RawValue? {
Expand Down Expand Up @@ -130,4 +131,13 @@ extension UserDefaults {
}
}
}

var showsActivityPresets: Bool {
get {
object(forKey: Key.showsActivityPresets.rawValue) as? Bool ?? true
}
set {
set(newValue, forKey: Key.showsActivityPresets.rawValue)
}
}
}
39 changes: 37 additions & 2 deletions Loop/Managers/TemporaryPresetsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,31 @@ class TemporaryPresetsManager {

private var now: Date { TestingDate.currentTestingDate() }

init(settingsProvider: SettingsProvider, alertIssuer: AlertIssuer? = nil, presetHistory: TemporaryScheduleOverrideHistory? = nil) {
@ObservationIgnored private let userDefaults: UserDefaults

var showsActivityPresets: Bool {
didSet {
guard oldValue != showsActivityPresets else {
return
}
userDefaults.showsActivityPresets = showsActivityPresets
Task { @MainActor in
if !showsActivityPresets {
for activityType in ActivityPreset.ActivityType.allCases {
await alertIssuer?.retractAlert(identifier: Alert.Identifier(managerIdentifier: managerIdentifier, alertIdentifier: activityType.id))
}
}
await scheduleNextPresetReminder()
}
notify(forChange: .preferences)
}
}

init(settingsProvider: SettingsProvider, alertIssuer: AlertIssuer? = nil, presetHistory: TemporaryScheduleOverrideHistory? = nil, userDefaults: UserDefaults = .standard) {
self.settingsProvider = settingsProvider
self.alertIssuer = alertIssuer
self.userDefaults = userDefaults
self.showsActivityPresets = userDefaults.showsActivityPresets

self.presetHistory = presetHistory ?? TemporaryScheduleOverrideHistoryContainer.shared.fetch()
TemporaryScheduleOverrideHistory.relevantTimeWindow = Bundle.main.localCacheDuration
Expand Down Expand Up @@ -165,6 +187,17 @@ class TemporaryPresetsManager {
return presets
}

/// The presets offered for selection; `selectablePresets` still resolves
/// hidden ones, so history and an already running preset keep their name.
var listedPresets: [SelectablePreset] {
selectablePresets.filter { preset in
if case .activity = preset {
return showsActivityPresets
}
return true
}
}

var clearOverrideTimer: Timer?
public func scheduleClearOverride(override: TemporaryScheduleOverride) {
clearOverrideTimer?.invalidate()
Expand Down Expand Up @@ -409,7 +442,9 @@ class TemporaryPresetsManager {

let now = now

let preset = settings.overridePresets.reduce(into: nil as TemporaryPreset?) { result, preset in
let schedulable = settings.overridePresets.filter { showsActivityPresets || ActivityPreset(preset: $0) == nil }

let preset = schedulable.reduce(into: nil as TemporaryPreset?) { result, preset in
if let nextScheduledTime = preset.nextScheduledStartAfter(now) {
if result == nil || nextScheduledTime < (result!.nextScheduledStartAfter(now)!) {
result = preset
Expand Down
6 changes: 4 additions & 2 deletions Loop/Managers/WatchDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ final class WatchDataManager: NSObject {
private func sendSettingsIfNeeded() {
let userInfo = LoopSettingsUserInfo(
loopSettings: settingsManager.loopSettings,
scheduleOverride: temporaryPresetsManager.scheduleOverride)
scheduleOverride: temporaryPresetsManager.scheduleOverride,
showsActivityPresets: temporaryPresetsManager.showsActivityPresets)

guard let session = watchSession, session.isPaired, session.isWatchAppInstalled else {
return
Expand Down Expand Up @@ -456,7 +457,8 @@ final class WatchDataManager: NSObject {
case SettingsRequestUserInfo.name?:
let userInfo = LoopSettingsUserInfo(
loopSettings: settingsManager.loopSettings,
scheduleOverride: temporaryPresetsManager.scheduleOverride)
scheduleOverride: temporaryPresetsManager.scheduleOverride,
showsActivityPresets: temporaryPresetsManager.showsActivityPresets)
return userInfo.rawValue
case GetBolusRecommendationUserInfo.name?:
if let request = GetBolusRecommendationUserInfo(rawValue: message) {
Expand Down
82 changes: 72 additions & 10 deletions Loop/Views/Presets/PresetsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ struct PresetsView: View {
var isDescending: Bool { !presetsSortAscending }

var presetsSorted: [SelectablePreset] {
temporaryPresetsManager.selectablePresets
temporaryPresetsManager.listedPresets
.filter { $0.id != temporaryPresetsManager.activeOverride?.presetId }
.sorted(by: {
switch (selectedSortOption) {
Expand Down Expand Up @@ -151,13 +151,7 @@ struct PresetsView: View {
sortMenu
}

Button(action: {
if trainingCompletion.isComplete {
presentCreateView = true
} else {
presentTrainingNeededAlert = true
}
}) {
Button(action: createPreset) {
Image(systemName: "plus")
}
.foregroundStyle(trainingCompletion.isComplete ? Color.accentColor : Color.secondary)
Expand All @@ -184,9 +178,13 @@ struct PresetsView: View {
activeSheet = .presetDetent(preset)
}
}

if presetsSorted.isEmpty {
createPresetButton
}
}
}

// Support Section
VStack(alignment: .leading, spacing: 16) {
Text("Support")
Expand Down Expand Up @@ -237,6 +235,15 @@ struct PresetsView: View {
.frame(maxWidth: .infinity))
}
}

// Options Section
VStack(alignment: .leading, spacing: 16) {
Text("Options", comment: "Presets screen section header for display options")
.font(.headline.weight(.semibold))
.padding(.horizontal, 10)

activityPresetsToggle
}
}
.padding()
.animation(.default, value: temporaryPresetsManager.activeOverride)
Expand Down Expand Up @@ -322,8 +329,63 @@ struct PresetsView: View {
.alert(isPresented: $presentTrainingNeededAlert) {
trainingNeededAlert
}
.onChange(of: temporaryPresetsManager.showsActivityPresets, initial: true) { _, showsActivityPresets in
trainingCompletion.exerciseRequired = showsActivityPresets
}
}


private func createPreset() {
if trainingCompletion.isComplete {
presentCreateView = true
} else {
presentTrainingNeededAlert = true
}
}

private var createPresetButton: some View {
Button(action: createPreset) {
VStack(spacing: 12) {
Image(systemName: "plus.circle.fill")
.font(.system(size: 44))
Text("Create New Preset", comment: "Button shown in place of the preset list when there are no presets")
.font(.headline)
}
.foregroundStyle(trainingCompletion.isComplete ? Color.accentColor : Color.secondary)
.frame(maxWidth: .infinity)
.padding(.vertical, 32)
.background(RoundedRectangle(cornerRadius: 12)
.fill(Color(UIColor.tertiarySystemBackground)))
}
.accessibilityIdentifier("button_CreateNewPreset")
}

private var activityPresetsToggle: some View {
VStack(alignment: .leading, spacing: 8) {
Toggle(isOn: Binding(
get: { temporaryPresetsManager.showsActivityPresets },
set: { newValue in withAnimation { temporaryPresetsManager.showsActivityPresets = newValue } }
)) {
Text("Show Activity Presets", comment: "Title of the toggle that shows or hides the built-in activity presets")
}
.padding(10)
.background(RoundedRectangle(cornerRadius: 8)
.fill(Color(UIColor.tertiarySystemBackground))
.stroke(Color(UIColor.secondarySystemBackground), lineWidth: 1)
.frame(maxWidth: .infinity))
.accessibilityIdentifier("toggle_ShowActivityPresets")

Text(activityPresetsFooter)
.font(.footnote)
.foregroundStyle(.secondary)
.padding(.horizontal, 10)
}
}

private var activityPresetsFooter: String {
let names = ListFormatter.localizedString(byJoining: ActivityPreset.ActivityType.allCases.map(\.name))
return String(format: NSLocalizedString("%1$@. While hidden, the Presets for Exercise training chapter is optional.", comment: "Footer under the Show Activity Presets toggle. (1: list of activity preset names)"), names)
}

private var trainingNeededAlert: SwiftUI.Alert {
Alert(title: Text("Training Required for New Presets", comment: "Preset training needed alert title"),
message: Text("To create a new preset, you must complete the required training.", comment: "Preset training needed alert message"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public struct PresetsTrainingCard: View {
public init(trainingCompletion: PresetsTrainingCompletion) {
if trainingCompletion.completedChapters[.customizingPresets] != true {
self.imageName = "PresetsTrainingCreditEditStartCard"
} else if trainingCompletion.completedChapters[.trainingComplete] != true {
} else if !trainingCompletion.isComplete {
self.imageName = "PresetsTrainingCreditEditResumeCard"
} else {
self.imageName = nil
Expand Down
Loading