Compare commits
	
		
			2 Commits
		
	
	
		
			1bc4dda14c
			...
			6d8b322688
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 6d8b322688 | |||
| edbf4faf00 | 
@ -1,6 +1,11 @@
 | 
			
		||||
import Foundation
 | 
			
		||||
import Combine
 | 
			
		||||
 | 
			
		||||
struct ChatNavigationTarget: Identifiable {
 | 
			
		||||
    let id = UUID()
 | 
			
		||||
    let chat: PrivateChatListItem
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
final class IncomingMessageCenter: ObservableObject {
 | 
			
		||||
    @Published private(set) var banner: IncomingMessageBanner?
 | 
			
		||||
    @Published var presentedChat: PrivateChatListItem?
 | 
			
		||||
@ -122,9 +127,4 @@ final class IncomingMessageCenter: ObservableObject {
 | 
			
		||||
        dismissWorkItem = workItem
 | 
			
		||||
        DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    struct ChatNavigationTarget: Identifiable {
 | 
			
		||||
        let id = UUID()
 | 
			
		||||
        let chat: PrivateChatListItem
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,6 @@ import UIKit
 | 
			
		||||
 | 
			
		||||
struct ChatsTab: View {
 | 
			
		||||
    @ObservedObject private var loginViewModel: LoginViewModel
 | 
			
		||||
    @Binding private var pendingNavigation: IncomingMessageCenter.ChatNavigationTarget?
 | 
			
		||||
    @Binding var searchRevealProgress: CGFloat
 | 
			
		||||
    @Binding var searchText: String
 | 
			
		||||
    private let searchService = SearchService()
 | 
			
		||||
@ -41,12 +40,10 @@ struct ChatsTab: View {
 | 
			
		||||
 | 
			
		||||
    init(
 | 
			
		||||
        loginViewModel: LoginViewModel,
 | 
			
		||||
        pendingNavigation: Binding<IncomingMessageCenter.ChatNavigationTarget?>,
 | 
			
		||||
        searchRevealProgress: Binding<CGFloat>,
 | 
			
		||||
        searchText: Binding<String>
 | 
			
		||||
    ) {
 | 
			
		||||
        self._loginViewModel = ObservedObject(wrappedValue: loginViewModel)
 | 
			
		||||
        self._pendingNavigation = pendingNavigation
 | 
			
		||||
        self._searchRevealProgress = searchRevealProgress
 | 
			
		||||
        self._searchText = searchText
 | 
			
		||||
    }
 | 
			
		||||
@ -100,13 +97,6 @@ struct ChatsTab: View {
 | 
			
		||||
                globalSearchTask?.cancel()
 | 
			
		||||
                globalSearchTask = nil
 | 
			
		||||
            }
 | 
			
		||||
            .onChange(of: pendingNavigation?.id) { _ in
 | 
			
		||||
                guard let target = pendingNavigation else { return }
 | 
			
		||||
                handleNavigationTarget(target.chat)
 | 
			
		||||
                DispatchQueue.main.async {
 | 
			
		||||
                    pendingNavigation = nil
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @ViewBuilder
 | 
			
		||||
@ -552,30 +542,6 @@ private extension ChatsTab {
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    func handleNavigationTarget(_ chatItem: PrivateChatListItem) {
 | 
			
		||||
        dismissKeyboard()
 | 
			
		||||
        if !searchText.isEmpty {
 | 
			
		||||
            searchText = ""
 | 
			
		||||
        }
 | 
			
		||||
        if searchRevealProgress > 0 {
 | 
			
		||||
            withAnimation(.spring(response: 0.35, dampingFraction: 0.75)) {
 | 
			
		||||
                searchRevealProgress = 0
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let existingChat = viewModel.chats.first(where: { $0.chatId == chatItem.chatId })
 | 
			
		||||
        pendingChatItem = existingChat ?? chatItem
 | 
			
		||||
        selectedChatId = chatItem.chatId
 | 
			
		||||
        isPendingChatActive = true
 | 
			
		||||
 | 
			
		||||
        if existingChat == nil {
 | 
			
		||||
            if loginViewModel.chatLoadingState != .loading {
 | 
			
		||||
                loginViewModel.chatLoadingState = .loading
 | 
			
		||||
            }
 | 
			
		||||
            viewModel.refresh()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    func handleSearchQueryChange(_ query: String) {
 | 
			
		||||
        let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines)
 | 
			
		||||
 | 
			
		||||
@ -1195,12 +1161,10 @@ struct ChatsTab_Previews: PreviewProvider {
 | 
			
		||||
        @State private var progress: CGFloat = 1
 | 
			
		||||
        @State private var searchText: String = ""
 | 
			
		||||
        @StateObject private var loginViewModel = LoginViewModel()
 | 
			
		||||
        @State private var pendingNavigation: IncomingMessageCenter.ChatNavigationTarget?
 | 
			
		||||
 | 
			
		||||
        var body: some View {
 | 
			
		||||
            ChatsTab(
 | 
			
		||||
                loginViewModel: loginViewModel,
 | 
			
		||||
                pendingNavigation: $pendingNavigation,
 | 
			
		||||
                searchRevealProgress: $progress,
 | 
			
		||||
                searchText: $searchText
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,8 @@ struct MainView: View {
 | 
			
		||||
    @State private var chatSearchRevealProgress: CGFloat = 0
 | 
			
		||||
    @State private var chatSearchText: String = ""
 | 
			
		||||
    @State private var isSettingsPresented = false
 | 
			
		||||
    @State private var deepLinkChatItem: PrivateChatListItem?
 | 
			
		||||
    @State private var isDeepLinkChatActive = false
 | 
			
		||||
 | 
			
		||||
    private var tabTitle: String {
 | 
			
		||||
        switch selectedTab {
 | 
			
		||||
@ -34,12 +36,6 @@ struct MainView: View {
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        NavigationView {
 | 
			
		||||
            let pendingNavigationBinding: Binding<IncomingMessageCenter.ChatNavigationTarget?> = AppConfig.PRESENT_CHAT_AS_SHEET
 | 
			
		||||
                ? .constant(nil)
 | 
			
		||||
                : Binding(
 | 
			
		||||
                    get: { messageCenter.pendingNavigation },
 | 
			
		||||
                    set: { messageCenter.pendingNavigation = $0 }
 | 
			
		||||
                )
 | 
			
		||||
            ZStack(alignment: .top) {
 | 
			
		||||
                ZStack(alignment: .leading) { // Выравниваем ZStack по левому краю
 | 
			
		||||
                    // Основной контент
 | 
			
		||||
@ -64,7 +60,6 @@ struct MainView: View {
 | 
			
		||||
 | 
			
		||||
                            ChatsTab(
 | 
			
		||||
                                loginViewModel: viewModel,
 | 
			
		||||
                                pendingNavigation: pendingNavigationBinding,
 | 
			
		||||
                                searchRevealProgress: $chatSearchRevealProgress,
 | 
			
		||||
                                searchText: $chatSearchText
 | 
			
		||||
                            )
 | 
			
		||||
@ -104,6 +99,8 @@ struct MainView: View {
 | 
			
		||||
                        .offset(x: -menuWidth + menuOffset) // Новая логика смещения
 | 
			
		||||
                        .ignoresSafeArea(edges: .vertical)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                deepLinkNavigationLink
 | 
			
		||||
            }
 | 
			
		||||
            .gesture(
 | 
			
		||||
                DragGesture()
 | 
			
		||||
@ -147,12 +144,25 @@ struct MainView: View {
 | 
			
		||||
        }
 | 
			
		||||
        .onChange(of: messageCenter.pendingNavigation?.id) { _ in
 | 
			
		||||
            guard !AppConfig.PRESENT_CHAT_AS_SHEET,
 | 
			
		||||
                  messageCenter.pendingNavigation != nil else { return }
 | 
			
		||||
                  let target = messageCenter.pendingNavigation else { return }
 | 
			
		||||
            withAnimation(.easeInOut) {
 | 
			
		||||
                selectedTab = 2
 | 
			
		||||
                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 {
 | 
			
		||||
@ -162,6 +172,39 @@ struct MainView: View {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private extension MainView {
 | 
			
		||||
    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()
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@ struct AppConfig {
 | 
			
		||||
 | 
			
		||||
    static let DISABLE_DB = false
 | 
			
		||||
    /// Controls whether incoming chat opens as a modal sheet (`true`) or navigates to Chats tab (`false`).
 | 
			
		||||
    static let PRESENT_CHAT_AS_SHEET = true
 | 
			
		||||
    static let PRESENT_CHAT_AS_SHEET = false
 | 
			
		||||
    /// Fallback SQLCipher key used until the user sets an application password.
 | 
			
		||||
    static let DEFAULT_DATABASE_ENCRYPTION_KEY = "yobble_dev_change_me"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user