main
swift 181 lines 5.84 KB
Raw
1 import SwiftUI
2
3 struct AppRootView: View {
4 private enum Tab: Hashable {
5 case discover
6 case search
7 case saved
8 #if os(tvOS)
9 case settings
10 #endif
11 }
12
13 @EnvironmentObject private var appState: AppState
14
15 @State private var selectedTab: Tab = .discover
16 @State private var showingSettings = false
17 @State private var showingOnboarding = false
18
19 // iPad / macOS split-view sidebar selection
20 @State private var sidebarSelection: Tab? = .discover
21
22 #if os(iOS)
23 @Environment(\.horizontalSizeClass) private var horizontalSizeClass
24 private var isRegularWidth: Bool { horizontalSizeClass == .regular }
25 #endif
26
27 var body: some View {
28 #if os(iOS)
29 if isRegularWidth {
30 iPadRootView
31 } else {
32 iPhoneRootView
33 }
34 #else
35 iPhoneRootView
36 #endif
37 }
38
39 // MARK: – iPhone / tvOS root (TabView)
40
41 private var iPhoneRootView: some View {
42 TabView(selection: $selectedTab) {
43 NavigationStack {
44 DiscoverView(showingSettings: $showingSettings)
45 }
46 .tabItem { Label("Discover", systemImage: "sparkles") }
47 .tag(Tab.discover)
48
49 NavigationStack {
50 SearchView()
51 }
52 .tabItem { Label("Search", systemImage: "magnifyingglass") }
53 .tag(Tab.search)
54
55 NavigationStack {
56 SavedView()
57 }
58 .tabItem { Label("Saved", systemImage: "heart") }
59 .tag(Tab.saved)
60
61 #if os(tvOS)
62 NavigationStack {
63 SettingsView()
64 }
65 .tabItem { Label("Settings", systemImage: "gearshape.2") }
66 .tag(Tab.settings)
67 #endif
68 }
69 .tint(KleponColor.accent)
70 .background(KleponColor.background.ignoresSafeArea())
71 #if !os(tvOS)
72 .sheet(isPresented: $showingSettings) {
73 NavigationStack { SettingsView() }
74 #if os(macOS) || os(visionOS)
75 .frame(minWidth: 480, minHeight: 620)
76 #endif
77 }
78 #endif
79 .kleponOnboardingPresentation(isPresented: $showingOnboarding) {
80 OnboardingView(
81 onBrowseFirst: { appState.completeOnboarding() },
82 onComplete: { appState.completeOnboarding() }
83 )
84 #if os(macOS) || os(visionOS)
85 .frame(minWidth: 640, minHeight: 760)
86 #endif
87 }
88 .task {
89 showingOnboarding = !appState.hasCompletedOnboarding
90 appState.guideEngine.refreshStorageUsage()
91 await appState.accountStore.restoreSessionIfNeeded()
92 }
93 .onChange(of: appState.hasCompletedOnboarding) { _, newValue in
94 showingOnboarding = !newValue
95 }
96 #if os(macOS) || os(visionOS)
97 .frame(minWidth: 980, minHeight: 720)
98 #endif
99 }
100
101 // MARK: – iPad root (NavigationSplitView)
102
103 #if os(iOS)
104 private var iPadRootView: some View {
105 NavigationSplitView(columnVisibility: .constant(.all)) {
106 iPadSidebar
107 .navigationSplitViewColumnWidth(min: 240, ideal: 280, max: 320)
108 } detail: {
109 iPadDetail
110 }
111 .navigationSplitViewStyle(.balanced)
112 .tint(KleponColor.accent)
113 .sheet(isPresented: $showingSettings) {
114 NavigationStack { SettingsView() }
115 .presentationDetents([.large])
116 }
117 .kleponOnboardingPresentation(isPresented: $showingOnboarding) {
118 OnboardingView(
119 onBrowseFirst: { appState.completeOnboarding() },
120 onComplete: { appState.completeOnboarding() }
121 )
122 }
123 .task {
124 showingOnboarding = !appState.hasCompletedOnboarding
125 appState.guideEngine.refreshStorageUsage()
126 await appState.accountStore.restoreSessionIfNeeded()
127 }
128 .onChange(of: appState.hasCompletedOnboarding) { _, newValue in
129 showingOnboarding = !newValue
130 }
131 }
132
133 private var iPadSidebar: some View {
134 List(selection: $sidebarSelection) {
135 Section {
136 Label("Discover", systemImage: "sparkles")
137 .tag(Tab.discover)
138 Label("Search", systemImage: "magnifyingglass")
139 .tag(Tab.search)
140 Label("Saved", systemImage: "heart")
141 .tag(Tab.saved)
142 } header: {
143 Text("Klepon")
144 .font(KleponTypography.caption)
145 .foregroundStyle(KleponColor.textSecondary)
146 }
147 }
148 .listStyle(.sidebar)
149 .background(KleponColor.background)
150 .toolbar {
151 ToolbarItem(placement: .topBarTrailing) {
152 Button {
153 showingSettings = true
154 } label: {
155 Image(systemName: "gearshape.2")
156 }
157 .tint(KleponColor.accent)
158 }
159 }
160 .navigationTitle("Klepon")
161 }
162
163 @ViewBuilder
164 private var iPadDetail: some View {
165 switch sidebarSelection ?? .discover {
166 case .discover:
167 NavigationStack {
168 DiscoverView(showingSettings: $showingSettings)
169 }
170 case .search:
171 NavigationStack {
172 SearchView()
173 }
174 case .saved:
175 NavigationStack {
176 SavedView()
177 }
178 }
179 }
180 #endif
181 }