import SwiftUI struct MainTabView: View { @EnvironmentObject var appState: AppState @StateObject private var realtime = RealtimeService() @State private var selectedTab = 0 @State private var showCompose = false @State private var showSettings = false var body: some View { ZStack(alignment: .bottom) { Color.nightBase.ignoresSafeArea() // Content TabContent( selectedTab: selectedTab, realtime: realtime ) .environmentObject(appState) // Floating Tab Bar FloatingTabBar( selectedTab: $selectedTab, windowState: appState.windowState, onCompose: { showCompose = true }, onSettings: { showSettings = true } ) } .ignoresSafeArea(edges: .bottom) .sheet(isPresented: $showCompose) { ComposeView().environmentObject(appState) } .sheet(isPresented: $showSettings) { SettingsView().environmentObject(appState) } .onDisappear { Task { await realtime.stopListening() } } } } // MARK: - Tab Content private struct TabContent: View { let selectedTab: Int @ObservedObject var realtime: RealtimeService @EnvironmentObject var appState: AppState var body: some View { ZStack { FeedView(realtime: realtime) .environmentObject(appState) .opacity(selectedTab == 0 ? 1 : 0) .allowsHitTesting(selectedTab == 0) DiaryView() .environmentObject(appState) .opacity(selectedTab == 1 ? 1 : 0) .allowsHitTesting(selectedTab == 1) ProfileView( user: appState.currentUser ?? .preview, isCurrentUser: true ) .environmentObject(appState) .opacity(selectedTab == 2 ? 1 : 0) .allowsHitTesting(selectedTab == 2) } } } // MARK: - Floating Tab Bar struct FloatingTabBar: View { @Binding var selectedTab: Int let windowState: AppState.WindowState let onCompose: () -> Void let onSettings: () -> Void var body: some View { HStack(spacing: 0) { // Feed TabIcon(icon: "moon.stars", activeIcon: "moon.stars.fill", isSelected: selectedTab == 0) { selectedTab = 0 } Spacer() // Diary TabIcon(icon: "book.closed", activeIcon: "book.closed.fill", isSelected: selectedTab == 1) { selectedTab = 1 } Spacer() // Center: Compose ComposeTabButton(windowState: windowState, onTap: onCompose) Spacer() // Profile TabIcon(icon: "person", activeIcon: "person.fill", isSelected: selectedTab == 2) { selectedTab = 2 } Spacer() // Settings TabIcon(icon: "gearshape", activeIcon: "gearshape.fill", isSelected: false) { onSettings() } } .padding(.horizontal, 20) .padding(.vertical, 10) .padding(.bottom, 18) .background( Rectangle() .fill(.ultraThinMaterial.opacity(0.8)) .background(Color.nightBase.opacity(0.85)) .ignoresSafeArea() ) .overlay( Rectangle().fill(Color.nightBorder).frame(height: 1), alignment: .top ) } } struct TabIcon: View { let icon: String let activeIcon: String let isSelected: Bool let action: () -> Void var body: some View { Button(action: action) { Image(systemName: isSelected ? activeIcon : icon) .font(.system(size: 21)) .foregroundColor(isSelected ? .nightPrimary : .nightSecondary) .frame(width: 44, height: 44) } } } struct ComposeTabButton: View { let windowState: AppState.WindowState let onTap: () -> Void @State private var glow = false var body: some View { Button { guard windowState == .open else { return } onTap() } label: { ZStack { if windowState == .open { Circle() .fill(Color.nightPurple.opacity(0.18)) .frame(width: 62, height: 62) .scaleEffect(glow ? 1.15 : 1.0) .animation(.easeInOut(duration: 2.2).repeatForever(autoreverses: true), value: glow) } Circle() .fill(buttonFill) .frame(width: 50, height: 50) Image(systemName: buttonIcon) .font(.system(size: 19, weight: .semibold)) .foregroundColor(.white) } } .onAppear { glow = true } .animation(.easeInOut(duration: 0.4), value: windowState) } var buttonFill: AnyShapeStyle { switch windowState { case .open: return AnyShapeStyle(LinearGradient( colors: [Color(hex: "8B5CF6"), Color(hex: "6D28D9")], startPoint: .topLeading, endPoint: .bottomTrailing )) case .posted: return AnyShapeStyle(LinearGradient( colors: [Color(hex: "059669"), Color(hex: "047857")], startPoint: .top, endPoint: .bottom )) default: return AnyShapeStyle(Color.nightRaised) } } var buttonIcon: String { switch windowState { case .open: return "plus" case .posted: return "checkmark" default: return "moon.zzz" } } }