LiveActivitySettingsView.swift 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. // LoopFollow
  2. // LiveActivitySettingsView.swift
  3. #if !targetEnvironment(macCatalyst)
  4. import SwiftUI
  5. struct LiveActivitySettingsView: View {
  6. @State private var laEnabled: Bool = Storage.shared.laEnabled.value
  7. @State private var restartConfirmed = false
  8. @State private var slots: [LiveActivitySlotOption] = LAAppGroupSettings.slots()
  9. @State private var smallWidgetSlot: LiveActivitySlotOption = LAAppGroupSettings.smallWidgetSlot()
  10. @State private var keyId: String = Storage.shared.lfKeyId.value
  11. @State private var apnsKey: String = Storage.shared.lfApnsKey.value
  12. private let slotLabels = ["Top left", "Top right", "Bottom left", "Bottom right"]
  13. private var apnsConfigured: Bool {
  14. APNsCredentialValidator.isFullyConfigured(keyId: keyId, apnsKey: apnsKey)
  15. }
  16. var body: some View {
  17. Form {
  18. Section(
  19. header: Text("Live Activity"),
  20. footer: Text("Live Activity updates require APNs credentials. Configure them in Settings → APN.")
  21. ) {
  22. Toggle("Enable Live Activity", isOn: $laEnabled)
  23. }
  24. if laEnabled {
  25. if !apnsConfigured {
  26. Section {
  27. Label {
  28. Text("APNs credentials are missing or invalid — Live Activity updates will not work. Open Settings → APN to fix.")
  29. .font(.callout)
  30. } icon: {
  31. Image(systemName: "exclamationmark.triangle.fill")
  32. .foregroundColor(.orange)
  33. }
  34. }
  35. }
  36. Section {
  37. Button(restartConfirmed ? "Live Activity Restarted" : "Restart Live Activity") {
  38. LiveActivityManager.shared.forceRestart()
  39. restartConfirmed = true
  40. DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
  41. restartConfirmed = false
  42. }
  43. }
  44. .disabled(restartConfirmed)
  45. }
  46. }
  47. Section(header: Text("Grid Slots - Live Activity")) {
  48. ForEach(0 ..< 4, id: \.self) { index in
  49. Picker(slotLabels[index], selection: Binding(
  50. get: { slots[index] },
  51. set: { selectSlot($0, at: index) }
  52. )) {
  53. ForEach(LiveActivitySlotOption.gridCases, id: \.self) { option in
  54. Text(option.displayName).tag(option)
  55. }
  56. }
  57. }
  58. }
  59. Section(header: Text("Grid Slot - CarPlay / Watch")) {
  60. Picker("Right slot", selection: Binding(
  61. get: { smallWidgetSlot },
  62. set: { newValue in
  63. smallWidgetSlot = newValue
  64. LAAppGroupSettings.setSmallWidgetSlot(newValue)
  65. LiveActivityManager.shared.refreshFromCurrentState(reason: "small widget slot changed")
  66. }
  67. )) {
  68. ForEach(LiveActivitySlotOption.allCases, id: \.self) { option in
  69. Text(option.displayName).tag(option)
  70. }
  71. }
  72. }
  73. }
  74. .onReceive(Storage.shared.laEnabled.$value) { newValue in
  75. if newValue != laEnabled { laEnabled = newValue }
  76. }
  77. .onReceive(Storage.shared.lfKeyId.$value) { newValue in
  78. if newValue != keyId { keyId = newValue }
  79. }
  80. .onReceive(Storage.shared.lfApnsKey.$value) { newValue in
  81. if newValue != apnsKey { apnsKey = newValue }
  82. }
  83. .onChange(of: laEnabled) { newValue in
  84. Storage.shared.laEnabled.value = newValue
  85. if newValue {
  86. LiveActivityManager.shared.forceRestart()
  87. } else {
  88. LiveActivityManager.shared.end(dismissalPolicy: .immediate)
  89. }
  90. }
  91. .preferredColorScheme(Storage.shared.appearanceMode.value.colorScheme)
  92. .navigationTitle("Live Activity")
  93. .navigationBarTitleDisplayMode(.inline)
  94. }
  95. /// Selects an option for the given slot index, enforcing uniqueness:
  96. /// if the chosen option is already in another slot, that slot is cleared to `.none`.
  97. private func selectSlot(_ option: LiveActivitySlotOption, at index: Int) {
  98. if option != .none {
  99. for i in 0 ..< slots.count where i != index && slots[i] == option {
  100. slots[i] = .none
  101. }
  102. }
  103. slots[index] = option
  104. LAAppGroupSettings.setSlots(slots)
  105. LiveActivityManager.shared.refreshFromCurrentState(reason: "slot config changed")
  106. }
  107. }
  108. #endif