import SwiftUI struct MainView: View { @ObservedObject var viewModel: LoginViewModel @EnvironmentObject private var messageCenter: IncomingMessageCenter @State private var selectedTab: Int = 0 @AppStorage("messengerModeEnabled") private var isMessengerModeEnabled: Bool = false // @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 = "" @State private var isSettingsPresented = false @State private var isQrPresented = false @State private var deepLinkChatItem: PrivateChatListItem? @State private var isDeepLinkChatActive = false @State private var hasTriggeredSecuritySettingsOnboarding = false private var tabTitle: String { switch selectedTab { case 0: return NSLocalizedString("Home", comment: "") case 1: return NSLocalizedString("Concept", comment: "") case 2: return NSLocalizedString("Чаты", comment: "") case 3: return NSLocalizedString("Profile", comment: "") case 4: return NSLocalizedString("Контакты", comment: "") case 5: return NSLocalizedString("Настройки", comment: "") default: return NSLocalizedString("Home", comment: "") } } private var menuWidth: CGFloat { UIScreen.main.bounds.width * 0.8 } var body: some View { NavigationView { ZStack(alignment: .top) { ZStack(alignment: .leading) { // Выравниваем ZStack по левому краю // Основной контент VStack(spacing: 0) { TopBarView( title: tabTitle, isMessengerModeEnabled: isMessengerModeEnabled, selectedAccount: $selectedAccount, accounts: accounts, viewModel: viewModel, isSettingsPresented: $isSettingsPresented, isQrPresented: $isQrPresented, isSideMenuPresented: $isSideMenuPresented, chatSearchRevealProgress: $chatSearchRevealProgress, chatSearchText: $chatSearchText ) ZStack { if isMessengerModeEnabled { ChatsTab( loginViewModel: viewModel, searchRevealProgress: $chatSearchRevealProgress, searchText: $chatSearchText ) .opacity(selectedTab == 2 ? 1 : 0) .allowsHitTesting(selectedTab == 2) ContactsTab() .opacity(selectedTab == 4 ? 1 : 0) SettingsView(viewModel: viewModel) .opacity(selectedTab == 5 ? 1 : 0) } else { NewHomeTab() .opacity(selectedTab == 0 ? 1 : 0) ConceptTab() .opacity(selectedTab == 1 ? 1 : 0) ChatsTab( loginViewModel: viewModel, 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, isMessengerModeEnabled: isMessengerModeEnabled) { 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) // Боковое меню if !isMessengerModeEnabled { SideMenuView(viewModel: viewModel, isPresented: $isSideMenuPresented) .frame(width: menuWidth) .offset(x: -menuWidth + menuOffset) // Новая логика смещения .ignoresSafeArea(edges: .vertical) } } deepLinkNavigationLink } .gesture( DragGesture() .onChanged { gesture in if !isMessengerModeEnabled { 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 !isMessengerModeEnabled { 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 } } .onAppear { enforceTabSelectionForMessengerMode() handleTwoFactorOnboardingIfNeeded() } .onChange(of: isMessengerModeEnabled) { _ in enforceTabSelectionForMessengerMode() handleTwoFactorOnboardingIfNeeded() } .onChange(of: viewModel.onboardingDestination) { _ in handleTwoFactorOnboardingIfNeeded() } .onChange(of: messageCenter.pendingNavigation?.id) { _ in guard !AppConfig.PRESENT_CHAT_AS_SHEET, let target = messageCenter.pendingNavigation else { return } withAnimation(.easeInOut) { isSideMenuPresented = false menuOffset = 0 } if !chatSearchText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { chatSearchText = "" } if chatSearchRevealProgress > 0 { withAnimation(.spring(response: 0.35, dampingFraction: 0.75)) { chatSearchRevealProgress = 0 } } deepLinkChatItem = target.chat isDeepLinkChatActive = true NotificationCenter.default.post(name: .chatsShouldRefresh, object: nil) DispatchQueue.main.async { messageCenter.pendingNavigation = nil } } .onChange(of: selectedTab) { newValue in if newValue != 3 { isSettingsPresented = false } } } } private extension MainView { func enforceTabSelectionForMessengerMode() { if isMessengerModeEnabled { if selectedTab < 2 { selectedTab = 2 } } else if selectedTab > 3 { selectedTab = 0 } } func handleTwoFactorOnboardingIfNeeded() { guard viewModel.onboardingDestination == .securitySettings else { hasTriggeredSecuritySettingsOnboarding = false return } guard !hasTriggeredSecuritySettingsOnboarding else { return } hasTriggeredSecuritySettingsOnboarding = true if isMessengerModeEnabled { if selectedTab != 5 { selectedTab = 5 } } else { if selectedTab != 3 { selectedTab = 3 } DispatchQueue.main.async { isSettingsPresented = true } } } var deepLinkNavigationLink: some View { NavigationLink( destination: deepLinkChatDestination, isActive: Binding( get: { isDeepLinkChatActive && deepLinkChatItem != nil }, set: { newValue in if !newValue { isDeepLinkChatActive = false deepLinkChatItem = nil } } ) ) { EmptyView() } .hidden() } @ViewBuilder var deepLinkChatDestination: some View { if let chatItem = deepLinkChatItem { PrivateChatView( chat: chatItem, currentUserId: messageCenter.currentUserId ) .id(chatItem.chatId) } else { EmptyView() } } } struct MainView_Previews: PreviewProvider { static var previews: some View { let mockViewModel = LoginViewModel() MainView(viewModel: mockViewModel) .environmentObject(IncomingMessageCenter()) .environmentObject(ThemeManager()) } }