| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- // LoopFollow
- // TempTargetView.swift
- import HealthKit
- import SwiftUI
- struct TempTargetView: View {
- @Environment(\.presentationMode) private var presentationMode
- private let pushNotificationManager = PushNotificationManager()
- @ObservedObject var device = Storage.shared.device
- @ObservedObject var tempTarget = Observable.shared.tempTarget
- @ObservedObject var tempTargetEndAt = Observable.shared.tempTargetEndAt
- @State private var newHKTarget = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 0.0)
- @State private var duration = HKQuantity(unit: .minute(), doubleValue: 0.0)
- @State private var showAlert: Bool = false
- @State private var alertType: AlertType? = nil
- @State private var alertMessage: String? = nil
- @State private var isLoading: Bool = false
- @State private var statusMessage: String? = nil
- @State private var showPresetSheet: Bool = false
- @State private var presetName = ""
- @ObservedObject var presetManager = TempTargetPresetManager.shared
- @FocusState private var targetFieldIsFocused: Bool
- @FocusState private var durationFieldIsFocused: Bool
- enum AlertType {
- case confirmCommand
- case statusSuccess
- case statusFailure
- case validation
- case confirmCancellation
- }
- var body: some View {
- NavigationView {
- VStack {
- if device.value != "Trio" {
- ErrorMessageView(
- message: "Remote commands are currently only available for Trio."
- )
- } else {
- Form {
- if let tempTargetValue = tempTarget.value {
- Section(header: Text("Existing Temp Target")) {
- HStack {
- Text("Current Target")
- Spacer()
- Text(Localizer.formatQuantity(tempTargetValue))
- Text(Localizer.getPreferredUnit().localizedShortUnitString).foregroundColor(.secondary)
- }
- if let endAt = tempTargetEndAt.value {
- HStack {
- Text("Time Remaining")
- Spacer()
- RemainingTimeText(endAt: endAt)
- .foregroundColor(.secondary)
- }
- }
- Button {
- alertType = .confirmCancellation
- showAlert = true
- } label: {
- HStack {
- Text("Cancel Temp Target")
- Spacer()
- Image(systemName: "xmark.app")
- .font(.title)
- }
- }
- .tint(.red)
- }
- }
- Section(header: Text("Temporary Target")) {
- HStack {
- Text("Target")
- Spacer()
- TextFieldWithToolBar(
- quantity: $newHKTarget,
- maxLength: 4,
- unit: Localizer.getPreferredUnit(),
- minValue: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 80),
- maxValue: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 200),
- onValidationError: { message in
- handleValidationError(message)
- }
- )
- .focused($targetFieldIsFocused)
- Text(Localizer.getPreferredUnit().localizedShortUnitString).foregroundColor(.secondary)
- }
- HStack {
- Text("Duration")
- Spacer()
- TextFieldWithToolBar(
- quantity: $duration,
- maxLength: 4,
- unit: HKUnit.minute(),
- minValue: HKQuantity(unit: .minute(), doubleValue: 5),
- onValidationError: { message in
- handleValidationError(message)
- }
- )
- .focused($durationFieldIsFocused)
- Text("minutes").foregroundColor(.secondary)
- }
- HStack {
- Button {
- alertType = .confirmCommand
- showAlert = true
- targetFieldIsFocused = false
- durationFieldIsFocused = false
- } label: {
- Text("Enact")
- }
- .disabled(isButtonDisabled)
- .buttonStyle(BorderlessButtonStyle())
- .font(.callout)
- .controlSize(.mini)
- Spacer()
- Button {
- showPresetSheet = true
- targetFieldIsFocused = false
- durationFieldIsFocused = false
- } label: {
- Text("Save as Preset")
- }
- .disabled(isButtonDisabled)
- .buttonStyle(BorderlessButtonStyle())
- .font(.callout)
- .controlSize(.mini)
- }
- }
- if !presetManager.presets.isEmpty {
- Section(header: Text("Presets")) {
- ForEach(presetManager.presets) { preset in
- HStack {
- Text(preset.name)
- Spacer()
- }
- .contentShape(Rectangle())
- .onTapGesture {
- alertType = .confirmCommand
- newHKTarget = preset.target
- duration = preset.duration
- showAlert = true
- targetFieldIsFocused = false
- durationFieldIsFocused = false
- }
- .swipeActions {
- Button(role: .destructive) {
- if let index = presetManager.presets.firstIndex(where: { $0.id == preset.id }) {
- presetManager.deletePreset(at: index)
- }
- targetFieldIsFocused = false
- durationFieldIsFocused = false
- } label: {
- Label("Delete", systemImage: "trash")
- }
- }
- }
- }
- }
- }
- if isLoading {
- ProgressView("Please wait...")
- .padding()
- }
- }
- }
- .navigationTitle("Remote")
- .navigationBarTitleDisplayMode(.inline)
- .alert(isPresented: $showAlert) {
- switch alertType {
- case .confirmCommand:
- return Alert(
- title: Text("Confirm Command"),
- message: Text("New Target: \(Localizer.formatQuantity(newHKTarget)) \(Localizer.getPreferredUnit().localizedShortUnitString)\nDuration: \(Int(duration.doubleValue(for: HKUnit.minute()))) minutes"),
- primaryButton: .default(Text("Confirm"), action: {
- enactTempTarget()
- }),
- secondaryButton: .cancel()
- )
- case .statusSuccess:
- return Alert(
- title: Text("Status"),
- message: Text(statusMessage ?? ""),
- dismissButton: .default(Text("OK"), action: {
- presentationMode.wrappedValue.dismiss()
- })
- )
- case .statusFailure:
- return Alert(
- title: Text("Status"),
- message: Text(statusMessage ?? ""),
- dismissButton: .default(Text("OK"))
- )
- case .confirmCancellation:
- return Alert(
- title: Text("Confirm Cancellation"),
- message: Text("Are you sure you want to cancel the existing temp target?"),
- primaryButton: .default(Text("Confirm"), action: {
- cancelTempTarget()
- }),
- secondaryButton: .cancel()
- )
- case .validation:
- return Alert(
- title: Text("Validation Error"),
- message: Text(alertMessage ?? "Invalid input."),
- dismissButton: .default(Text("OK"))
- )
- case .none:
- return Alert(title: Text("Unknown Alert"))
- }
- }
- .sheet(isPresented: $showPresetSheet) {
- VStack {
- Text("Save Preset")
- .font(.headline)
- .padding()
- TextField("Preset Name", text: $presetName)
- .textFieldStyle(RoundedBorderTextFieldStyle())
- .padding()
- HStack {
- Button("Cancel") {
- showPresetSheet = false
- }
- .padding()
- Spacer()
- Button("Save") {
- presetManager.addPreset(name: presetName, target: newHKTarget, duration: duration)
- presetName = ""
- showPresetSheet = false
- }
- .disabled(presetName.isEmpty)
- .padding()
- }
- Spacer()
- }
- .padding()
- }
- }
- }
- private var isButtonDisabled: Bool {
- return newHKTarget.doubleValue(for: Localizer.getPreferredUnit()) == 0 ||
- duration.doubleValue(for: HKUnit.minute()) == 0 || isLoading
- }
- private func enactTempTarget() {
- isLoading = true
- pushNotificationManager.sendTempTargetPushNotification(target: newHKTarget, duration: duration) { success, errorMessage in
- DispatchQueue.main.async {
- self.isLoading = false
- if success {
- self.statusMessage = "Temp target command successfully sent."
- self.alertType = .statusSuccess
- LogManager.shared.log(category: .apns, message: "sendTempTargetPushNotification succeeded with target: \(newHKTarget), duration: \(duration)")
- } else {
- self.statusMessage = errorMessage ?? "Failed to send temp target command."
- self.alertType = .statusFailure
- LogManager.shared.log(category: .apns, message: "sendTempTargetPushNotification failed with target: \(newHKTarget), duration: \(duration), error: \(errorMessage ?? "unknown error")")
- }
- self.showAlert = true
- }
- }
- }
- private func cancelTempTarget() {
- isLoading = true
- pushNotificationManager.sendCancelTempTargetPushNotification { success, errorMessage in
- DispatchQueue.main.async {
- self.isLoading = false
- if success {
- self.statusMessage = "Cancel temp target command successfully sent."
- self.alertType = .statusSuccess
- LogManager.shared.log(category: .apns, message: "sendCancelTempTargetPushNotification succeeded")
- } else {
- self.statusMessage = errorMessage ?? "Failed to send cancel temp target command."
- self.alertType = .statusFailure
- LogManager.shared.log(category: .apns, message: "sendCancelTempTargetPushNotification failed with error: \(errorMessage ?? "unknown error")")
- }
- self.showAlert = true
- }
- }
- }
- private func handleValidationError(_ message: String) {
- alertMessage = message
- alertType = .validation
- showAlert = true
- }
- }
|