import SwiftUI struct TopNav: View { @EnvironmentObject private var app: AppModel @State private var now = Date() private let clockTimer = Timer.publish(every: 30, on: .main, in: .common).autoconnect() var body: some View { HStack(spacing: 8) { BrandLogo(size: 34) .padding(.trailing, 20) HStack(spacing: 10) { ForEach(visibleTabs) { tab in if tab == .search || tab == .settings { navIconButton(tab) } else { navTextButton(tab) } } } .focusSection() Spacer(minLength: 16) Text(timeString) .font(.system(size: 22, weight: .semibold, design: .rounded)) .foregroundStyle(TvColors.textDim) .monospacedDigit() .padding(.trailing, 12) Button { app.tab = .settings } label: { FocusAwareProfileBadge(avatarId: app.store.profileAvatarId) } .buttonStyle(NavTabButtonStyle()) } .padding(.horizontal, 48) .padding(.vertical, 18) .background { Rectangle() .fill(.ultraThinMaterial) .overlay( LinearGradient( colors: [Color.black.opacity(0.55), Color.black.opacity(0.15)], startPoint: .top, endPoint: .bottom ) ) .ignoresSafeArea(edges: .top) } .onReceive(clockTimer) { now = $0 } } private var timeString: String { let f = DateFormatter() f.locale = Locale(identifier: "nl_NL") f.dateFormat = "HH:mm" return f.string(from: now) } private var visibleTabs: [AppModel.MainTab] { AppModel.MainTab.allCases.filter { tab in if tab == .tv { return app.iptvEnabled } return true } } private func navTextButton(_ tab: AppModel.MainTab) -> some View { Button { withAnimation(.easeOut(duration: 0.2)) { app.tab = tab } } label: { FocusAwareNavLabel(title: tab.title, selected: app.tab == tab) } .buttonStyle(NavTabButtonStyle()) } private func navIconButton(_ tab: AppModel.MainTab) -> some View { Button { withAnimation(.easeOut(duration: 0.2)) { app.tab = tab } } label: { FocusAwareNavIcon(systemName: tab.icon, selected: app.tab == tab) } .buttonStyle(NavTabButtonStyle()) .accessibilityLabel(tab.title) } } private struct FocusAwareNavLabel: View { let title: String let selected: Bool @Environment(\.isFocused) private var isFocused var body: some View { Text(title) .font(TvFonts.nav) .foregroundStyle(isFocused || selected ? TvColors.text : TvColors.textDim) .padding(.horizontal, 20) .padding(.vertical, 12) .background( Capsule().fill(backgroundFill) ) .overlay { Capsule() .strokeBorder(borderColor, lineWidth: isFocused ? 3 : (selected ? 1.5 : 0)) } } private var backgroundFill: Color { if isFocused { return Color.white.opacity(0.18) } if selected { return TvColors.accent.opacity(0.28) } return .clear } private var borderColor: Color { if isFocused { return Color.white } if selected { return TvColors.accent.opacity(0.7) } return .clear } } private struct FocusAwareNavIcon: View { let systemName: String let selected: Bool @Environment(\.isFocused) private var isFocused var body: some View { Image(systemName: systemName) .font(.system(size: 24, weight: .semibold)) .foregroundStyle(isFocused || selected ? Color.white : TvColors.textDim) .frame(width: 54, height: 54) .background(Circle().fill(backgroundFill)) .overlay { Circle() .strokeBorder(borderColor, lineWidth: isFocused ? 3 : (selected ? 1.5 : 0)) } } private var backgroundFill: Color { if isFocused { return Color.white.opacity(0.18) } if selected { return TvColors.accent.opacity(0.28) } return .clear } private var borderColor: Color { if isFocused { return Color.white } if selected { return TvColors.accent.opacity(0.7) } return .clear } } private struct FocusAwareProfileBadge: View { let avatarId: String? @Environment(\.isFocused) private var isFocused var body: some View { Text(ProfileAvatars.emoji(for: avatarId)) .font(.system(size: 32)) .frame(width: 56, height: 56) .background(.ultraThinMaterial, in: Circle()) .overlay( Circle().strokeBorder( isFocused ? Color.white : TvColors.accent.opacity(0.45), lineWidth: isFocused ? 3 : 1.5 ) ) } }