| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- //
- // RemoteSettingsView.swift
- // LoopFollow
- //
- // Created by Jonas Björkert on 2024-08-25.
- // Updated on 2024-09-16.
- // Copyright © 2024 Jon Fawcett. All rights reserved.
- //
- import SwiftUI
- import HealthKit
- struct RemoteSettingsView: View {
- @ObservedObject var viewModel: RemoteSettingsViewModel
- @Environment(\.presentationMode) var presentationMode
- @State private var showAlert: Bool = false
- @State private var alertType: AlertType? = nil
- @State private var alertMessage: String? = nil
- enum AlertType {
- case validation
- }
- var body: some View {
- NavigationView {
- Form {
- // MARK: - Remote Type Section (Custom Rows)
- Section(header: Text("Remote Type")) {
- remoteTypeRow(type: .none, label: "None", isEnabled: true)
- remoteTypeRow(type: .nightscout, label: "Nightscout", isEnabled: true)
- remoteTypeRow(
- type: .trc,
- label: "Trio Remote Control",
- isEnabled: viewModel.isTrioDevice
- )
- Text("Nightscout is the only option for the released version of Trio and Loop.")
- .font(.footnote)
- .foregroundColor(.secondary)
- }
- // MARK: - User Information Section
- if viewModel.remoteType != .none {
- Section(header: Text("User Information")) {
- HStack {
- Text("User")
- TextField("Enter User", text: $viewModel.user)
- .autocapitalization(.none)
- .disableAutocorrection(true)
- .multilineTextAlignment(.trailing)
- }
- }
- }
- // MARK: - Trio Remote Control Settings
- if viewModel.remoteType == .trc {
- Section(header: Text("Trio Remote Control Settings")) {
- HStack {
- Text("Shared Secret")
- TextField("Enter Shared Secret", text: $viewModel.sharedSecret)
- .autocapitalization(.none)
- .disableAutocorrection(true)
- .multilineTextAlignment(.trailing)
- }
- HStack {
- Text("APNS Key ID")
- TextField("Enter APNS Key ID", text: $viewModel.keyId)
- .autocapitalization(.none)
- .disableAutocorrection(true)
- .multilineTextAlignment(.trailing)
- }
- VStack(alignment: .leading) {
- Text("APNS Key")
- TextEditor(text: $viewModel.apnsKey)
- .frame(height: 100)
- .autocapitalization(.none)
- .disableAutocorrection(true)
- .overlay(
- RoundedRectangle(cornerRadius: 8)
- .stroke(Color.gray.opacity(0.5), lineWidth: 1)
- )
- }
- }
- // MARK: - Guardrails
- Section(header: Text("Guardrails")) {
- HStack {
- Text("Max Bolus")
- Spacer()
- TextFieldWithToolBar(
- quantity: $viewModel.maxBolus,
- maxLength: 4,
- unit: HKUnit.internationalUnit(),
- allowDecimalSeparator: true,
- minValue: HKQuantity(unit: .internationalUnit(), doubleValue: 0.0),
- maxValue: HKQuantity(unit: .internationalUnit(), doubleValue: 10.0),
- onValidationError: { message in
- handleValidationError(message)
- }
- )
- .frame(width: 100)
- Text("U")
- .foregroundColor(.secondary)
- }
- HStack {
- Text("Max Carbs")
- Spacer()
- TextFieldWithToolBar(
- quantity: $viewModel.maxCarbs,
- maxLength: 4,
- unit: HKUnit.gram(),
- allowDecimalSeparator: true,
- minValue: HKQuantity(unit: .gram(), doubleValue: 0),
- maxValue: HKQuantity(unit: .gram(), doubleValue: 100),
- onValidationError: { message in
- handleValidationError(message)
- }
- )
- .frame(width: 100)
- Text("g")
- .foregroundColor(.secondary)
- }
- HStack {
- Text("Max Protein")
- Spacer()
- TextFieldWithToolBar(
- quantity: $viewModel.maxProtein,
- maxLength: 4,
- unit: HKUnit.gram(),
- allowDecimalSeparator: true,
- minValue: HKQuantity(unit: .gram(), doubleValue: 0),
- maxValue: HKQuantity(unit: .gram(), doubleValue: 100),
- onValidationError: { message in
- handleValidationError(message)
- }
- )
- .frame(width: 100)
- Text("g")
- .foregroundColor(.secondary)
- }
- HStack {
- Text("Max Fat")
- Spacer()
- TextFieldWithToolBar(
- quantity: $viewModel.maxFat,
- maxLength: 4,
- unit: HKUnit.gram(),
- allowDecimalSeparator: true,
- minValue: HKQuantity(unit: .gram(), doubleValue: 0),
- maxValue: HKQuantity(unit: .gram(), doubleValue: 100),
- onValidationError: { message in
- handleValidationError(message)
- }
- )
- .frame(width: 100)
- Text("g")
- .foregroundColor(.secondary)
- }
- }
- // MARK: - Meal Section
- Section(header: Text("Meal Settings")) {
- Toggle("Meal with Bolus", isOn: $viewModel.mealWithBolus)
- .toggleStyle(SwitchToggleStyle())
- Toggle("Meal with Fat/Protein", isOn: $viewModel.mealWithFatProtein)
- .toggleStyle(SwitchToggleStyle())
- }
- // MARK: - Debug / Info
- Section(header: Text("Debug / Info")) {
- Text("Device Token: \(Storage.shared.deviceToken.value)")
- Text("Production Env.: \(Storage.shared.productionEnvironment.value ? "True" : "False")")
- Text("Team ID: \(Storage.shared.teamId.value ?? "")")
- Text("Bundle ID: \(Storage.shared.bundleId.value)")
- }
- }
- }
- .navigationBarTitle("Remote Settings", displayMode: .inline)
- .toolbar {
- ToolbarItem(placement: .navigationBarTrailing) {
- Button("Done") {
- presentationMode.wrappedValue.dismiss()
- }
- }
- }
- .alert(isPresented: $showAlert) {
- switch alertType {
- 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"))
- }
- }
- }
- }
- // MARK: - Custom Row for Remote Type Selection
- private func remoteTypeRow(type: RemoteType, label: String, isEnabled: Bool) -> some View {
- Button(action: {
- if isEnabled {
- viewModel.remoteType = type
- }
- }) {
- HStack {
- Text(label)
- Spacer()
- if viewModel.remoteType == type {
- Image(systemName: "checkmark")
- .foregroundColor(.accentColor)
- }
- }
- }
- // If isEnabled is false, user can see the row but not tap it.
- .disabled(!isEnabled)
- .foregroundColor(isEnabled ? .primary : .gray)
- }
- // MARK: - Validation Error Handler
- private func handleValidationError(_ message: String) {
- alertMessage = message
- alertType = .validation
- showAlert = true
- }
- }
|