open chat everywhere

This commit is contained in:
cheykrym 2025-10-21 20:26:20 +03:00
parent 3658d5a963
commit ed91efacf5
6 changed files with 43 additions and 88 deletions

View File

@ -9,6 +9,7 @@ struct TopBarView: View {
var accounts: [String] var accounts: [String]
// var viewModel: LoginViewModel // var viewModel: LoginViewModel
@ObservedObject var viewModel: LoginViewModel @ObservedObject var viewModel: LoginViewModel
@Binding var isSettingsPresented: Bool
// Привязка для управления боковым меню // Привязка для управления боковым меню
@Binding var isSideMenuPresented: Bool @Binding var isSideMenuPresented: Bool
@ -101,7 +102,9 @@ struct TopBarView: View {
} }
} }
} else if isProfileTab { } else if isProfileTab {
NavigationLink(destination: SettingsView(viewModel: viewModel)) { NavigationLink(isActive: $isSettingsPresented) {
SettingsView(viewModel: viewModel)
} label: {
Image(systemName: "wrench") Image(systemName: "wrench")
.imageScale(.large) .imageScale(.large)
.foregroundColor(.primary) .foregroundColor(.primary)
@ -213,6 +216,7 @@ struct TopBarView_Previews: PreviewProvider {
@State private var revealProgress: CGFloat = 1 @State private var revealProgress: CGFloat = 1
@StateObject private var viewModel = LoginViewModel() @StateObject private var viewModel = LoginViewModel()
@State private var searchText: String = "" @State private var searchText: String = ""
@State private var isSettingsPresented = false
var body: some View { var body: some View {
TopBarView( TopBarView(
@ -220,6 +224,7 @@ struct TopBarView_Previews: PreviewProvider {
selectedAccount: $selectedAccount, selectedAccount: $selectedAccount,
accounts: [selectedAccount], accounts: [selectedAccount],
viewModel: viewModel, viewModel: viewModel,
isSettingsPresented: $isSettingsPresented,
isSideMenuPresented: $isSideMenuPresented, isSideMenuPresented: $isSideMenuPresented,
chatSearchRevealProgress: $revealProgress, chatSearchRevealProgress: $revealProgress,
chatSearchText: $searchText chatSearchText: $searchText

View File

@ -1,8 +0,0 @@
import Foundation
struct ChatDeepLink: Identifiable {
let id = UUID()
let chatId: String
let chatProfile: ChatProfile?
let message: MessageItem?
}

View File

@ -3,7 +3,7 @@ import Combine
final class IncomingMessageCenter: ObservableObject { final class IncomingMessageCenter: ObservableObject {
@Published private(set) var banner: IncomingMessageBanner? @Published private(set) var banner: IncomingMessageBanner?
@Published var pendingDeepLink: ChatDeepLink? @Published var presentedChat: PrivateChatListItem?
var currentUserId: String? var currentUserId: String?
private var dismissWorkItem: DispatchWorkItem? private var dismissWorkItem: DispatchWorkItem?
@ -29,11 +29,7 @@ final class IncomingMessageCenter: ObservableObject {
func openCurrentChat() { func openCurrentChat() {
guard let banner else { return } guard let banner else { return }
pendingDeepLink = ChatDeepLink( presentedChat = makeChatItem(from: banner.message)
chatId: banner.message.chatId,
chatProfile: banner.message.senderData,
message: banner.message
)
dismissBanner() dismissBanner()
} }
@ -85,6 +81,18 @@ final class IncomingMessageCenter: ObservableObject {
} }
} }
private func makeChatItem(from message: MessageItem) -> PrivateChatListItem {
let profile = message.senderData
return PrivateChatListItem(
chatId: message.chatId,
chatType: .privateChat,
chatData: profile,
lastMessage: message,
createdAt: message.createdAt,
unreadCount: 0
)
}
private func scheduleDismiss(after delay: TimeInterval = 5) { private func scheduleDismiss(after delay: TimeInterval = 5) {
dismissWorkItem?.cancel() dismissWorkItem?.cancel()
let workItem = DispatchWorkItem { [weak self] in let workItem = DispatchWorkItem { [weak self] in

View File

@ -12,7 +12,6 @@ import UIKit
struct ChatsTab: View { struct ChatsTab: View {
@ObservedObject private var loginViewModel: LoginViewModel @ObservedObject private var loginViewModel: LoginViewModel
@Binding private var pendingDeepLink: ChatDeepLink?
@Binding var searchRevealProgress: CGFloat @Binding var searchRevealProgress: CGFloat
@Binding var searchText: String @Binding var searchText: String
private let searchService = SearchService() private let searchService = SearchService()
@ -41,12 +40,10 @@ struct ChatsTab: View {
init( init(
loginViewModel: LoginViewModel, loginViewModel: LoginViewModel,
pendingDeepLink: Binding<ChatDeepLink?>,
searchRevealProgress: Binding<CGFloat>, searchRevealProgress: Binding<CGFloat>,
searchText: Binding<String> searchText: Binding<String>
) { ) {
self._loginViewModel = ObservedObject(wrappedValue: loginViewModel) self._loginViewModel = ObservedObject(wrappedValue: loginViewModel)
self._pendingDeepLink = pendingDeepLink
self._searchRevealProgress = searchRevealProgress self._searchRevealProgress = searchRevealProgress
self._searchText = searchText self._searchText = searchText
} }
@ -100,13 +97,6 @@ struct ChatsTab: View {
globalSearchTask?.cancel() globalSearchTask?.cancel()
globalSearchTask = nil globalSearchTask = nil
} }
.onChange(of: pendingDeepLink?.id) { _ in
guard let link = pendingDeepLink else { return }
handle(chatDeepLink: link)
DispatchQueue.main.async {
pendingDeepLink = nil
}
}
} }
@ViewBuilder @ViewBuilder
@ -552,44 +542,6 @@ private extension ChatsTab {
#endif #endif
} }
func handle(chatDeepLink: ChatDeepLink) {
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 == chatDeepLink.chatId })
pendingChatItem = existingChat ?? makeChatItem(from: chatDeepLink)
selectedChatId = chatDeepLink.chatId
isPendingChatActive = true
if existingChat == nil {
if loginViewModel.chatLoadingState != .loading {
loginViewModel.chatLoadingState = .loading
}
viewModel.refresh()
}
}
func makeChatItem(from deepLink: ChatDeepLink) -> PrivateChatListItem {
let profile = deepLink.chatProfile ?? deepLink.message?.senderData
let lastMessage = deepLink.message
let createdAt = deepLink.message?.createdAt
return PrivateChatListItem(
chatId: deepLink.chatId,
chatType: .privateChat,
chatData: profile,
lastMessage: lastMessage,
createdAt: createdAt,
unreadCount: 0
)
}
func handleSearchQueryChange(_ query: String) { func handleSearchQueryChange(_ query: String) {
let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines) let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines)
@ -1209,12 +1161,10 @@ struct ChatsTab_Previews: PreviewProvider {
@State private var progress: CGFloat = 1 @State private var progress: CGFloat = 1
@State private var searchText: String = "" @State private var searchText: String = ""
@StateObject private var loginViewModel = LoginViewModel() @StateObject private var loginViewModel = LoginViewModel()
@State private var deepLink: ChatDeepLink?
var body: some View { var body: some View {
ChatsTab( ChatsTab(
loginViewModel: loginViewModel, loginViewModel: loginViewModel,
pendingDeepLink: $deepLink,
searchRevealProgress: $progress, searchRevealProgress: $progress,
searchText: $searchText searchText: $searchText
) )

View File

@ -2,7 +2,6 @@ import SwiftUI
struct MainView: View { struct MainView: View {
@ObservedObject var viewModel: LoginViewModel @ObservedObject var viewModel: LoginViewModel
@EnvironmentObject private var messageCenter: IncomingMessageCenter
@State private var selectedTab: Int = 0 @State private var selectedTab: Int = 0
// @StateObject private var newHomeTabViewModel = NewHomeTabViewModel() // @StateObject private var newHomeTabViewModel = NewHomeTabViewModel()
@ -16,6 +15,7 @@ struct MainView: View {
@State private var menuOffset: CGFloat = 0 @State private var menuOffset: CGFloat = 0
@State private var chatSearchRevealProgress: CGFloat = 0 @State private var chatSearchRevealProgress: CGFloat = 0
@State private var chatSearchText: String = "" @State private var chatSearchText: String = ""
@State private var isSettingsPresented = false
private var tabTitle: String { private var tabTitle: String {
switch selectedTab { switch selectedTab {
@ -42,6 +42,7 @@ struct MainView: View {
selectedAccount: $selectedAccount, selectedAccount: $selectedAccount,
accounts: accounts, accounts: accounts,
viewModel: viewModel, viewModel: viewModel,
isSettingsPresented: $isSettingsPresented,
isSideMenuPresented: $isSideMenuPresented, isSideMenuPresented: $isSideMenuPresented,
chatSearchRevealProgress: $chatSearchRevealProgress, chatSearchRevealProgress: $chatSearchRevealProgress,
chatSearchText: $chatSearchText chatSearchText: $chatSearchText
@ -56,10 +57,6 @@ struct MainView: View {
ChatsTab( ChatsTab(
loginViewModel: viewModel, loginViewModel: viewModel,
pendingDeepLink: Binding(
get: { messageCenter.pendingDeepLink },
set: { messageCenter.pendingDeepLink = $0 }
),
searchRevealProgress: $chatSearchRevealProgress, searchRevealProgress: $chatSearchRevealProgress,
searchText: $chatSearchText searchText: $chatSearchText
) )
@ -140,23 +137,6 @@ struct MainView: View {
menuOffset = presented ? menuWidth : 0 menuOffset = presented ? menuWidth : 0
} }
} }
.onAppear {
messageCenter.currentUserId = viewModel.userId.isEmpty ? nil : viewModel.userId
}
.onChange(of: viewModel.userId) { newValue in
messageCenter.currentUserId = newValue.isEmpty ? nil : newValue
}
.onChange(of: messageCenter.pendingDeepLink?.id) { _ in
guard messageCenter.pendingDeepLink != nil else { return }
withAnimation(.easeInOut) {
selectedTab = 2
isSideMenuPresented = false
menuOffset = 0
}
}
.onDisappear {
messageCenter.currentUserId = nil
}
} }
} }
@ -164,7 +144,6 @@ struct MainView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
let mockViewModel = LoginViewModel() let mockViewModel = LoginViewModel()
MainView(viewModel: mockViewModel) MainView(viewModel: mockViewModel)
.environmentObject(IncomingMessageCenter())
.environmentObject(ThemeManager()) .environmentObject(ThemeManager())
} }
} }

View File

@ -41,10 +41,31 @@ struct yobbleApp: App {
} }
} }
.animation(.spring(response: 0.35, dampingFraction: 0.8), value: messageCenter.banner != nil) .animation(.spring(response: 0.35, dampingFraction: 0.8), value: messageCenter.banner != nil)
.sheet(item: $messageCenter.presentedChat) { chatItem in
NavigationView {
PrivateChatView(
chat: chatItem,
currentUserId: messageCenter.currentUserId
)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button(NSLocalizedString("Закрыть", comment: "")) {
messageCenter.presentedChat = nil
}
}
}
}
}
.environmentObject(messageCenter) .environmentObject(messageCenter)
.environmentObject(themeManager) .environmentObject(themeManager)
.preferredColorScheme(themeManager.theme.colorScheme) .preferredColorScheme(themeManager.theme.colorScheme)
.environment(\.managedObjectContext, persistenceController.viewContext) .environment(\.managedObjectContext, persistenceController.viewContext)
.onAppear {
messageCenter.currentUserId = viewModel.userId.isEmpty ? nil : viewModel.userId
}
.onChange(of: viewModel.userId) { newValue in
messageCenter.currentUserId = newValue.isEmpty ? nil : newValue
}
} }
} }
} }