import SwiftUI struct MainView: View { @ObservedObject var viewModel: LoginViewModel @State private var selectedTab: Int = 0 // @StateObject private var newHomeTabViewModel = NewHomeTabViewModel() // Состояния для TopBarView @State private var selectedAccount = "@user1" @State private var accounts = ["@user1", "@user2", "@user3"] // @State private var sheetType: ProfileTab.SheetType? = nil // Состояния для бокового меню @State private var isSideMenuPresented = false @State private var menuOffset: CGFloat = 0 @State private var chatSearchRevealProgress: CGFloat = 0 @State private var chatSearchText: String = "" private var tabTitle: String { switch selectedTab { case 0: return "Home" case 1: return "Concept" case 2: return "Chats" case 3: return "Profile" default: return "Home" } } private var menuWidth: CGFloat { UIScreen.main.bounds.width * 0.8 } var body: some View { NavigationView { ZStack(alignment: .leading) { // Выравниваем ZStack по левому краю // Основной контент VStack(spacing: 0) { TopBarView( title: tabTitle, selectedAccount: $selectedAccount, accounts: accounts, viewModel: viewModel, isSideMenuPresented: $isSideMenuPresented, chatSearchRevealProgress: $chatSearchRevealProgress, chatSearchText: $chatSearchText ) ZStack { NewHomeTab() .opacity(selectedTab == 0 ? 1 : 0) ConceptTab() .opacity(selectedTab == 1 ? 1 : 0) ChatsTab( currentUserId: viewModel.userId.isEmpty ? nil : viewModel.userId, searchRevealProgress: $chatSearchRevealProgress, searchText: $chatSearchText ) .opacity(selectedTab == 2 ? 1 : 0) .allowsHitTesting(selectedTab == 2) ProfileTab() .opacity(selectedTab == 3 ? 1 : 0) } .frame(maxWidth: .infinity, maxHeight: .infinity) CustomTabBar(selectedTab: $selectedTab) { print("Create button tapped") } } .frame(maxWidth: .infinity, maxHeight: .infinity) // Убедимся, что основной контент занимает все пространство .ignoresSafeArea(edges: .bottom) .navigationBarHidden(true) // .sheet(item: $sheetType) { type in // // ... sheet presentation logic // } // Затемнение и закрытие по тапу Color.black .opacity(Double(menuOffset / menuWidth) * 0.4) .ignoresSafeArea() .onTapGesture { withAnimation(.easeInOut) { isSideMenuPresented = false } } .allowsHitTesting(menuOffset > 0) // Боковое меню SideMenuView(viewModel: viewModel, isPresented: $isSideMenuPresented) .frame(width: menuWidth) .offset(x: -menuWidth + menuOffset) // Новая логика смещения .ignoresSafeArea(edges: .vertical) } .gesture( DragGesture() .onChanged { gesture in if !isSideMenuPresented && gesture.startLocation.x > 60 { return } let translation = gesture.translation.width // Определяем базовое смещение в зависимости от того, открыто меню или нет let baseOffset = isSideMenuPresented ? menuWidth : 0 // Новое смещение — это база плюс текущий свайп let newOffset = baseOffset + translation // Жестко ограничиваем итоговое смещение между 0 и шириной меню self.menuOffset = max(0, min(menuWidth, newOffset)) } .onEnded { gesture in if !isSideMenuPresented && gesture.startLocation.x > 60 { return } let threshold = menuWidth * 0.4 withAnimation(.easeInOut) { if self.menuOffset > threshold { isSideMenuPresented = true } else { isSideMenuPresented = false } // Устанавливаем финальное смещение после анимации self.menuOffset = isSideMenuPresented ? menuWidth : 0 } } ) } .navigationViewStyle(StackNavigationViewStyle()) .onChange(of: isSideMenuPresented) { presented in // Плавная анимация при нажатии на кнопку, а не только при жесте withAnimation(.easeInOut) { menuOffset = presented ? menuWidth : 0 } } } } struct MainView_Previews: PreviewProvider { static var previews: some View { let mockViewModel = LoginViewModel() MainView(viewModel: mockViewModel) .environmentObject(ThemeManager()) } }