ContactSettingsView.swift 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. // LoopFollow
  2. // ContactSettingsView.swift
  3. import Contacts
  4. import SwiftUI
  5. struct ContactSettingsView: View {
  6. @ObservedObject var viewModel: ContactSettingsViewModel
  7. @State private var showAlert: Bool = false
  8. @State private var alertTitle: String = ""
  9. @State private var alertMessage: String = ""
  10. var body: some View {
  11. Form {
  12. Section(header: Text("Contact Integration")) {
  13. Text("Add the contact named '\(viewModel.contactName)' to your watch face to show the current BG value in real time. Make sure to give the app full access to Contacts when prompted.")
  14. .font(.footnote)
  15. .foregroundColor(.secondary)
  16. .padding(.vertical, 4)
  17. Toggle("Enable Contact BG Updates", isOn: $viewModel.contactEnabled)
  18. .toggleStyle(SwitchToggleStyle())
  19. .onChange(of: viewModel.contactEnabled) { isEnabled in
  20. if isEnabled {
  21. requestContactAccess()
  22. }
  23. }
  24. }
  25. if viewModel.contactEnabled {
  26. Section(header: Text("Color Options")) {
  27. Text("Select the colors for your BG values. Note: not all watch faces allow control over colors. Recommend options like Activity or Modular Duo if you want to customize colors.")
  28. .font(.footnote)
  29. .foregroundColor(.secondary)
  30. .padding(.vertical, 4)
  31. Picker("Background Color", selection: $viewModel.contactBackgroundColor) {
  32. ForEach(ContactColorOption.allCases, id: \.rawValue) { option in
  33. Text(LocalizedStringKey(option.rawValue.capitalized)).tag(option.rawValue)
  34. }
  35. }
  36. Picker("Color Mode", selection: $viewModel.contactColorMode) {
  37. ForEach(ContactColorMode.allCases, id: \.self) { mode in
  38. Text(mode.displayName).tag(mode)
  39. }
  40. }
  41. if viewModel.contactColorMode == .staticColor {
  42. Picker("Text Color", selection: $viewModel.contactTextColor) {
  43. ForEach(ContactColorOption.allCases, id: \.rawValue) { option in
  44. Text(LocalizedStringKey(option.rawValue.capitalized)).tag(option.rawValue)
  45. }
  46. }
  47. } else {
  48. Text("Dynamic mode colors text based on BG range: Green (in range), Yellow (high), Red (low)")
  49. .font(.footnote)
  50. .foregroundColor(.secondary)
  51. }
  52. }
  53. Section(header: Text("Additional Information")) {
  54. Text("To see your trend, delta, or IOB, include them in another contact or create separate contacts. When using 'Include', select which contact to add the value to.")
  55. .font(.footnote)
  56. .foregroundColor(.secondary)
  57. .padding(.vertical, 4)
  58. Text("Trend")
  59. .font(.subheadline)
  60. Picker("Show Trend", selection: $viewModel.contactTrend) {
  61. ForEach(ContactIncludeOption.allCases, id: \.self) { option in
  62. Text(LocalizedStringKey(option.rawValue)).tag(option)
  63. }
  64. }
  65. .pickerStyle(SegmentedPickerStyle())
  66. if viewModel.contactTrend == .include {
  67. Picker("Include Trend in", selection: $viewModel.contactTrendTarget) {
  68. ForEach(viewModel.availableTargets(for: .Trend), id: \.self) { target in
  69. Text(LocalizedStringKey(target.rawValue)).tag(target)
  70. }
  71. }
  72. }
  73. Text("Delta")
  74. .font(.subheadline)
  75. Picker("Show Delta", selection: $viewModel.contactDelta) {
  76. ForEach(ContactIncludeOption.allCases, id: \.self) { option in
  77. Text(LocalizedStringKey(option.rawValue)).tag(option)
  78. }
  79. }
  80. .pickerStyle(SegmentedPickerStyle())
  81. if viewModel.contactDelta == .include {
  82. Picker("Include Delta in", selection: $viewModel.contactDeltaTarget) {
  83. ForEach(viewModel.availableTargets(for: .Delta), id: \.self) { target in
  84. Text(LocalizedStringKey(target.rawValue)).tag(target)
  85. }
  86. }
  87. }
  88. Text("IOB")
  89. .font(.subheadline)
  90. Picker("Show IOB", selection: $viewModel.contactIOB) {
  91. ForEach(ContactIncludeOption.allCases, id: \.self) { option in
  92. Text(LocalizedStringKey(option.rawValue)).tag(option)
  93. }
  94. }
  95. .pickerStyle(SegmentedPickerStyle())
  96. if viewModel.contactIOB == .include {
  97. Picker("Include IOB in", selection: $viewModel.contactIOBTarget) {
  98. ForEach(viewModel.availableTargets(for: .IOB), id: \.self) { target in
  99. Text(LocalizedStringKey(target.rawValue)).tag(target)
  100. }
  101. }
  102. }
  103. }
  104. }
  105. }
  106. .alert(isPresented: $showAlert) {
  107. Alert(title: Text(alertTitle), message: Text(alertMessage), dismissButton: .default(Text("OK")))
  108. }
  109. .preferredColorScheme(Storage.shared.appearanceMode.value.colorScheme)
  110. .navigationBarTitle("Contact", displayMode: .inline)
  111. }
  112. private func requestContactAccess() {
  113. let contactStore = CNContactStore()
  114. let status = CNContactStore.authorizationStatus(for: .contacts)
  115. if status == .authorized {
  116. // Already authorized, do nothing
  117. } else if status == .notDetermined {
  118. contactStore.requestAccess(for: .contacts) { granted, _ in
  119. DispatchQueue.main.async {
  120. if !granted {
  121. viewModel.contactEnabled = false
  122. showAlert(title: String(localized: "Access Denied"), message: String(localized: "Please allow access to Contacts in Settings to enable this feature."))
  123. }
  124. }
  125. }
  126. } else if status == .denied {
  127. viewModel.contactEnabled = false
  128. showAlert(title: String(localized: "Access Denied"), message: String(localized: "Access to Contacts is denied. Please go to Settings and enable Contacts access."))
  129. } else if status == .restricted {
  130. viewModel.contactEnabled = false
  131. showAlert(title: String(localized: "Access Restricted"), message: String(localized: "Access to Contacts is restricted."))
  132. } else {
  133. viewModel.contactEnabled = false
  134. showAlert(title: String(localized: "Error"), message: String(localized: "An unknown error occurred while checking Contacts access."))
  135. }
  136. }
  137. private func showAlert(title: String, message: String) {
  138. alertTitle = title
  139. alertMessage = message
  140. showAlert = true
  141. }
  142. }