Compare commits

...

2 Commits

Author SHA1 Message Date
6d8b322688 fix open in settings 2025-10-22 03:44:25 +03:00
edbf4faf00 delete error 2025-10-22 02:46:21 +03:00
4 changed files with 58 additions and 51 deletions

View File

@ -1,6 +1,11 @@
import Foundation import Foundation
import Combine import Combine
struct ChatNavigationTarget: Identifiable {
let id = UUID()
let chat: PrivateChatListItem
}
final class IncomingMessageCenter: ObservableObject { final class IncomingMessageCenter: ObservableObject {
@Published private(set) var banner: IncomingMessageBanner? @Published private(set) var banner: IncomingMessageBanner?
@Published var presentedChat: PrivateChatListItem? @Published var presentedChat: PrivateChatListItem?
@ -122,9 +127,4 @@ final class IncomingMessageCenter: ObservableObject {
dismissWorkItem = workItem dismissWorkItem = workItem
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem) DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem)
} }
struct ChatNavigationTarget: Identifiable {
let id = UUID()
let chat: PrivateChatListItem
}
} }

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 pendingNavigation: IncomingMessageCenter.ChatNavigationTarget?
@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,
pendingNavigation: Binding<IncomingMessageCenter.ChatNavigationTarget?>,
searchRevealProgress: Binding<CGFloat>, searchRevealProgress: Binding<CGFloat>,
searchText: Binding<String> searchText: Binding<String>
) { ) {
self._loginViewModel = ObservedObject(wrappedValue: loginViewModel) self._loginViewModel = ObservedObject(wrappedValue: loginViewModel)
self._pendingNavigation = pendingNavigation
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: pendingNavigation?.id) { _ in
guard let target = pendingNavigation else { return }
handleNavigationTarget(target.chat)
DispatchQueue.main.async {
pendingNavigation = nil
}
}
} }
@ViewBuilder @ViewBuilder
@ -552,30 +542,6 @@ private extension ChatsTab {
#endif #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) { func handleSearchQueryChange(_ query: String) {
let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines) let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines)
@ -1195,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 pendingNavigation: IncomingMessageCenter.ChatNavigationTarget?
var body: some View { var body: some View {
ChatsTab( ChatsTab(
loginViewModel: loginViewModel, loginViewModel: loginViewModel,
pendingNavigation: $pendingNavigation,
searchRevealProgress: $progress, searchRevealProgress: $progress,
searchText: $searchText searchText: $searchText
) )

View File

@ -17,6 +17,8 @@ struct MainView: View {
@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 @State private var isSettingsPresented = false
@State private var deepLinkChatItem: PrivateChatListItem?
@State private var isDeepLinkChatActive = false
private var tabTitle: String { private var tabTitle: String {
switch selectedTab { switch selectedTab {
@ -34,12 +36,6 @@ struct MainView: View {
var body: some View { var body: some View {
NavigationView { 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: .top) {
ZStack(alignment: .leading) { // Выравниваем ZStack по левому краю ZStack(alignment: .leading) { // Выравниваем ZStack по левому краю
// Основной контент // Основной контент
@ -64,7 +60,6 @@ struct MainView: View {
ChatsTab( ChatsTab(
loginViewModel: viewModel, loginViewModel: viewModel,
pendingNavigation: pendingNavigationBinding,
searchRevealProgress: $chatSearchRevealProgress, searchRevealProgress: $chatSearchRevealProgress,
searchText: $chatSearchText searchText: $chatSearchText
) )
@ -104,6 +99,8 @@ struct MainView: View {
.offset(x: -menuWidth + menuOffset) // Новая логика смещения .offset(x: -menuWidth + menuOffset) // Новая логика смещения
.ignoresSafeArea(edges: .vertical) .ignoresSafeArea(edges: .vertical)
} }
deepLinkNavigationLink
} }
.gesture( .gesture(
DragGesture() DragGesture()
@ -147,12 +144,25 @@ struct MainView: View {
} }
.onChange(of: messageCenter.pendingNavigation?.id) { _ in .onChange(of: messageCenter.pendingNavigation?.id) { _ in
guard !AppConfig.PRESENT_CHAT_AS_SHEET, guard !AppConfig.PRESENT_CHAT_AS_SHEET,
messageCenter.pendingNavigation != nil else { return } let target = messageCenter.pendingNavigation else { return }
withAnimation(.easeInOut) { withAnimation(.easeInOut) {
selectedTab = 2
isSideMenuPresented = false isSideMenuPresented = false
menuOffset = 0 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 .onChange(of: selectedTab) { newValue in
if newValue != 3 { 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 { struct MainView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
let mockViewModel = LoginViewModel() let mockViewModel = LoginViewModel()

View File

@ -14,7 +14,7 @@ struct AppConfig {
static let DISABLE_DB = false static let DISABLE_DB = false
/// Controls whether incoming chat opens as a modal sheet (`true`) or navigates to Chats tab (`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. /// Fallback SQLCipher key used until the user sets an application password.
static let DEFAULT_DATABASE_ENCRYPTION_KEY = "yobble_dev_change_me" static let DEFAULT_DATABASE_ENCRYPTION_KEY = "yobble_dev_change_me"
} }