MainTabView.swift 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. // LoopFollow
  2. // MainTabView.swift
  3. import SwiftUI
  4. struct MainTabView: View {
  5. @ObservedObject private var selectedTab = Observable.shared.selectedTabIndex
  6. @ObservedObject private var appearanceMode = Storage.shared.appearanceMode
  7. @ObservedObject private var homePosition = Storage.shared.homePosition
  8. @ObservedObject private var alarmsPosition = Storage.shared.alarmsPosition
  9. @ObservedObject private var remotePosition = Storage.shared.remotePosition
  10. @ObservedObject private var nightscoutPosition = Storage.shared.nightscoutPosition
  11. @ObservedObject private var snoozerPosition = Storage.shared.snoozerPosition
  12. @ObservedObject private var statisticsPosition = Storage.shared.statisticsPosition
  13. @ObservedObject private var treatmentsPosition = Storage.shared.treatmentsPosition
  14. @State private var showTelemetryConsent = false
  15. private var orderedItems: [TabItem] {
  16. Storage.shared.orderedTabBarItems()
  17. }
  18. var body: some View {
  19. TabView(selection: $selectedTab.value) {
  20. ForEach(Array(orderedItems.prefix(4).enumerated()), id: \.element) { index, item in
  21. tabContent(for: item)
  22. .tabItem {
  23. Label(item.displayName, systemImage: item.icon)
  24. }
  25. .tag(index)
  26. }
  27. NavigationStack {
  28. MoreMenuView()
  29. }
  30. .tabItem {
  31. Label("Menu", systemImage: "line.3.horizontal")
  32. }
  33. .tag(4)
  34. }
  35. .preferredColorScheme(appearanceMode.value.colorScheme)
  36. .onAppear {
  37. // Start the data pipeline as soon as the UI appears, independent of
  38. // tab layout. Without this, a user who moves Home into the Menu would
  39. // have no MainViewController — and therefore no data fetching, alarms,
  40. // or background audio — until they manually opened Home. Tying it to
  41. // onAppear (not app launch) keeps it off the BG-only refresh path.
  42. MainViewController.bootstrap()
  43. // One-time consent prompt. Previously presented by SceneDelegate,
  44. // which was removed in the storyboard→SwiftUI migration; without
  45. // this, fresh installs stay permanently undecided and telemetry
  46. // never sends. The storage flag keeps it to a single appearance.
  47. if !Storage.shared.telemetryConsentDecisionMade.value {
  48. showTelemetryConsent = true
  49. }
  50. }
  51. .sheet(isPresented: $showTelemetryConsent) {
  52. // User must explicitly choose — no swipe-to-dismiss.
  53. TelemetryConsentView()
  54. .interactiveDismissDisabled(true)
  55. }
  56. }
  57. @ViewBuilder
  58. private func tabContent(for item: TabItem) -> some View {
  59. switch item {
  60. case .home:
  61. HomeContentView()
  62. case .alarms:
  63. AlarmsContainerView()
  64. case .remote:
  65. RemoteContentView()
  66. case .nightscout:
  67. NightscoutContentView()
  68. case .snoozer:
  69. SnoozerView()
  70. case .treatments:
  71. TreatmentsView()
  72. case .stats:
  73. NavigationStack {
  74. AggregatedStatsContentView(mainViewController: MainViewController.shared)
  75. }
  76. }
  77. }
  78. }