import SwiftUI struct HomeRootView: View { @ObservedObject var model: AppViewModel @Environment(\.horizontalSizeClass) private var horizontalSizeClass @State private var selectedRequestID: ApprovalRequest.ID? @State private var searchText = "" @State private var isSearchPresented = false var body: some View { Group { if usesRegularNavigation { RegularHomeContainer( model: model, selectedRequestID: $selectedRequestID, searchText: $searchText, isSearchPresented: $isSearchPresented ) } else { CompactHomeContainer( model: model, selectedRequestID: $selectedRequestID, searchText: $searchText, isSearchPresented: $isSearchPresented ) } } .onAppear(perform: syncSelection) .onChange(of: model.requests.map(\.id)) { _, _ in syncSelection() } } private var usesRegularNavigation: Bool { #if os(iOS) horizontalSizeClass == .regular #else false #endif } private func syncSelection() { if let selectedRequestID, model.requests.contains(where: { $0.id == selectedRequestID }) { return } selectedRequestID = model.pendingRequests.first?.id ?? model.requests.first?.id } } private struct CompactHomeContainer: View { @ObservedObject var model: AppViewModel @Binding var selectedRequestID: ApprovalRequest.ID? @Binding var searchText: String @Binding var isSearchPresented: Bool var body: some View { TabView(selection: $model.selectedSection) { ForEach(AppSection.allCases) { section in NavigationStack { sectionContent(for: section) .navigationDestination(for: ApprovalRequest.ID.self) { requestID in ApprovalDetailView(model: model, requestID: requestID, dismissOnResolve: true) } .toolbar { if section == .inbox { InboxToolbar(model: model, isSearchPresented: $isSearchPresented) } } } .tag(section) .tabItem { Label(section.title, systemImage: section.systemImage) } } } .idpTabBarChrome() } @ViewBuilder private func sectionContent(for section: AppSection) -> some View { switch section { case .inbox: InboxListView( model: model, selectedRequestID: $selectedRequestID, searchText: $searchText, isSearchPresented: $isSearchPresented, usesSelection: false ) case .notifications: NotificationCenterView(model: model) case .devices: DevicesView(model: model) case .identity: IdentityView(model: model) case .settings: SettingsView(model: model) } } } private struct RegularHomeContainer: View { @ObservedObject var model: AppViewModel @Binding var selectedRequestID: ApprovalRequest.ID? @Binding var searchText: String @Binding var isSearchPresented: Bool var body: some View { NavigationSplitView { SidebarView(model: model) .navigationSplitViewColumnWidth(min: 250, ideal: 280, max: 320) } content: { contentColumn } detail: { detailColumn } .navigationSplitViewStyle(.balanced) } @ViewBuilder private var contentColumn: some View { switch model.selectedSection { case .inbox: InboxListView( model: model, selectedRequestID: $selectedRequestID, searchText: $searchText, isSearchPresented: $isSearchPresented, usesSelection: true ) .toolbar { InboxToolbar(model: model, isSearchPresented: $isSearchPresented) } case .notifications: NotificationCenterView(model: model) case .devices: DevicesView(model: model) case .identity: IdentityView(model: model) case .settings: SettingsView(model: model) } } @ViewBuilder private var detailColumn: some View { switch model.selectedSection { case .inbox: ApprovalDetailView(model: model, requestID: selectedRequestID) case .notifications: EmptyPaneView( title: "Notification history", message: "Select the inbox to review request context side by side.", systemImage: "bell" ) case .devices: EmptyPaneView( title: "Trusted hardware", message: "Device trust and last-seen state appear here while you manage your passport.", systemImage: "desktopcomputer" ) case .identity: EmptyPaneView( title: "Identity overview", message: "Your profile, recovery status, and pairing state stay visible here.", systemImage: "person.crop.rectangle.stack" ) case .settings: EmptyPaneView( title: "Preferences", message: "Notification delivery and demo controls live in settings.", systemImage: "gearshape" ) } } } struct SidebarView: View { @ObservedObject var model: AppViewModel var body: some View { List { ForEach(Array(AppSection.allCases.enumerated()), id: \.element.id) { index, section in Button { model.selectedSection = section Haptics.selection() } label: { HStack(spacing: 12) { Label(section.title, systemImage: section.systemImage) Spacer() if badgeCount(for: section) > 0 { StatusPill(title: "\(badgeCount(for: section))", color: IdP.tint) } } .padding(.vertical, 6) } .buttonStyle(.plain) .listRowBackground(model.selectedSection == section ? IdP.tint.opacity(0.08) : Color.clear) .keyboardShortcut(shortcut(for: index), modifiers: .command) } } .navigationTitle("idp.global") } private func badgeCount(for section: AppSection) -> Int { switch section { case .inbox: model.pendingRequests.count case .notifications: model.unreadNotificationCount case .devices: max((model.profile?.deviceCount ?? 1) - 1, 0) case .identity, .settings: 0 } } private func shortcut(for index: Int) -> KeyEquivalent { let value = max(1, min(index + 1, 9)) return KeyEquivalent(Character("\(value)")) } } private struct InboxToolbar: ToolbarContent { @ObservedObject var model: AppViewModel @Binding var isSearchPresented: Bool var body: some ToolbarContent { ToolbarItem(placement: .idpTrailingToolbar) { HStack(spacing: 8) { Button { isSearchPresented = true } label: { Image(systemName: "magnifyingglass") .font(.headline) .foregroundStyle(.primary) } .accessibilityLabel("Search inbox") Button { model.selectedSection = .identity } label: { MonogramAvatar(title: model.profile?.name ?? "idp.global", size: 28) } .accessibilityLabel("Open identity") } .padding(.horizontal, 10) .padding(.vertical, 8) .background( RoundedRectangle(cornerRadius: 18, style: .continuous) .fill(.clear) .idpGlassChrome() ) .overlay( RoundedRectangle(cornerRadius: 18, style: .continuous) .stroke(Color.white.opacity(0.16), lineWidth: 1) ) } } }