From 1c9f249289feff95bbe9b0d020418be2f32e9639 Mon Sep 17 00:00:00 2001 From: cheykrym Date: Thu, 23 Oct 2025 21:49:31 +0300 Subject: [PATCH] scroll to top while tap tap --- yobble/Views/Tab/ChatsTab.swift | 115 ++++++++++++++++------------ yobble/Views/Tab/CustomTabBar.swift | 14 +++- 2 files changed, 80 insertions(+), 49 deletions(-) diff --git a/yobble/Views/Tab/ChatsTab.swift b/yobble/Views/Tab/ChatsTab.swift index b71710d..a73e505 100644 --- a/yobble/Views/Tab/ChatsTab.swift +++ b/yobble/Views/Tab/ChatsTab.swift @@ -32,6 +32,7 @@ struct ChatsTab: View { @State private var isPendingChatActive: Bool = false private let searchRevealDistance: CGFloat = 90 + private let scrollToTopAnchorId = "ChatsListTopAnchor" private var currentUserId: String? { let userId = loginViewModel.userId @@ -109,8 +110,9 @@ struct ChatsTab: View { } private var chatList: some View { - ZStack { - List { + ScrollViewReader { proxy in + ZStack { + List { // VStack(spacing: 0) { // searchBar // .padding(.horizontal, 16) @@ -118,63 +120,70 @@ struct ChatsTab: View { // } // .background(Color(UIColor.systemBackground)) - if let message = viewModel.errorMessage { - Section { - HStack(alignment: .top, spacing: 8) { - Image(systemName: "exclamationmark.triangle.fill") - .foregroundColor(.orange) - Text(message) - .font(.subheadline) - .foregroundColor(.orange) - Spacer(minLength: 0) - Button(action: triggerChatsReload) { - Text(NSLocalizedString("Обновить", comment: "")) + if let message = viewModel.errorMessage { + Section { + HStack(alignment: .top, spacing: 8) { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundColor(.orange) + Text(message) .font(.subheadline) + .foregroundColor(.orange) + Spacer(minLength: 0) + Button(action: triggerChatsReload) { + Text(NSLocalizedString("Обновить", comment: "")) + .font(.subheadline) + } } + .padding(.vertical, 4) } - .padding(.vertical, 4) - } - .listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) - } - - if isSearching { - Section(header: localSearchHeader) { - if localSearchResults.isEmpty { - emptySearchResultView - .listRowInsets(EdgeInsets(top: 24, leading: 16, bottom: 24, trailing: 16)) - .listRowSeparator(.hidden) - } else { - ForEach(localSearchResults) { chat in - chatRowItem(for: chat) - } - } + .listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) } - Section(header: globalSearchHeader) { - globalSearchContent - } - } else { + if isSearching { + Section(header: localSearchHeader) { + if localSearchResults.isEmpty { + emptySearchResultView + .listRowInsets(EdgeInsets(top: 24, leading: 16, bottom: 24, trailing: 16)) + .listRowSeparator(.hidden) + } else { + let firstLocalChatId = localSearchResults.first?.chatId + ForEach(localSearchResults) { chat in + chatRowItem(for: chat) + .id(chat.chatId == firstLocalChatId ? scrollToTopAnchorId : chat.chatId) + } + } + } + + Section(header: globalSearchHeader) { + globalSearchContent + } + } else { // if let message = viewModel.errorMessage, viewModel.chats.isEmpty { // errorState(message: message) // } else - if viewModel.chats.isEmpty { - emptyState - } else { + if viewModel.chats.isEmpty { + emptyState + } else { - ForEach(viewModel.chats) { chat in - chatRowItem(for: chat) - } + let firstChatId = viewModel.chats.first?.chatId + ForEach(viewModel.chats) { chat in + chatRowItem(for: chat) + .id(chat.chatId == firstChatId ? scrollToTopAnchorId : chat.chatId) + } - if viewModel.isLoadingMore { - loadingMoreRow + if viewModel.isLoadingMore { + loadingMoreRow + } } } } - } - .listStyle(.plain) - .modifier(ScrollDismissesKeyboardModifier()) - .simultaneousGesture(searchBarGesture) - .simultaneousGesture(tapToDismissKeyboardGesture) + .listStyle(.plain) + .modifier(ScrollDismissesKeyboardModifier()) + .simultaneousGesture(searchBarGesture) + .simultaneousGesture(tapToDismissKeyboardGesture) + .onReceive(NotificationCenter.default.publisher(for: .chatsShouldScrollToTop)) { _ in + scrollChatsToTop(using: proxy) + } // .safeAreaInset(edge: .top) { // VStack(spacing: 0) { // searchBar @@ -186,7 +195,8 @@ struct ChatsTab: View { // .background(Color(UIColor.systemBackground)) // } - pendingChatNavigationLink + pendingChatNavigationLink + } } } @@ -217,6 +227,14 @@ struct ChatsTab: View { } } + private func scrollChatsToTop(using proxy: ScrollViewProxy) { + DispatchQueue.main.async { + withAnimation(.spring(response: 0.35, dampingFraction: 0.75)) { + proxy.scrollTo(scrollToTopAnchorId, anchor: .top) + } + } + } + private var searchBarGesture: some Gesture { DragGesture(minimumDistance: 10, coordinateSpace: .local) .onChanged { value in @@ -319,8 +337,10 @@ struct ChatsTab: View { if globalSearchResults.isEmpty { globalSearchEmptyRow } else { + let firstGlobalUserId = globalSearchResults.first?.id ForEach(globalSearchResults) { user in globalSearchRow(for: user) + .id(user.id == firstGlobalUserId ? AnyHashable(scrollToTopAnchorId) : AnyHashable(user.id)) } } } @@ -1181,4 +1201,5 @@ extension Notification.Name { static let debugRefreshChats = Notification.Name("debugRefreshChats") static let chatsShouldRefresh = Notification.Name("chatsShouldRefresh") static let chatsReloadCompleted = Notification.Name("chatsReloadCompleted") + static let chatsShouldScrollToTop = Notification.Name("chatsShouldScrollToTop") } diff --git a/yobble/Views/Tab/CustomTabBar.swift b/yobble/Views/Tab/CustomTabBar.swift index 80d38f8..68005d4 100644 --- a/yobble/Views/Tab/CustomTabBar.swift +++ b/yobble/Views/Tab/CustomTabBar.swift @@ -14,7 +14,7 @@ struct CustomTabBar: View { } TabBarButton(systemName: "bubble.left.and.bubble.right.fill", text: NSLocalizedString("Чаты", comment: ""), isSelected: selectedTab == 2) { - selectedTab = 2 + handleChatsTabTap() } TabBarButton(systemName: "gearshape.fill", text: NSLocalizedString("Настройки", comment: ""), isSelected: selectedTab == 5) { @@ -34,7 +34,7 @@ struct CustomTabBar: View { } TabBarButton(systemName: "bubble.left.and.bubble.right.fill", text: NSLocalizedString("Чаты", comment: ""), isSelected: selectedTab == 2) { - selectedTab = 2 + handleChatsTabTap() } TabBarButton(systemName: "person.crop.square", text: NSLocalizedString("Лицо", comment: ""), isSelected: selectedTab == 3) { @@ -93,3 +93,13 @@ struct CreateButton: View { .offset(y: -3) } } + +private extension CustomTabBar { + func handleChatsTabTap() { + if selectedTab == 2 { + NotificationCenter.default.post(name: .chatsShouldScrollToTop, object: nil) + } else { + selectedTab = 2 + } + } +}