scroll to top while tap tap

This commit is contained in:
cheykrym 2025-10-23 21:49:31 +03:00
parent 198b51bd91
commit 1c9f249289
2 changed files with 80 additions and 49 deletions

View File

@ -32,6 +32,7 @@ struct ChatsTab: View {
@State private var isPendingChatActive: Bool = false @State private var isPendingChatActive: Bool = false
private let searchRevealDistance: CGFloat = 90 private let searchRevealDistance: CGFloat = 90
private let scrollToTopAnchorId = "ChatsListTopAnchor"
private var currentUserId: String? { private var currentUserId: String? {
let userId = loginViewModel.userId let userId = loginViewModel.userId
@ -109,6 +110,7 @@ struct ChatsTab: View {
} }
private var chatList: some View { private var chatList: some View {
ScrollViewReader { proxy in
ZStack { ZStack {
List { List {
// VStack(spacing: 0) { // VStack(spacing: 0) {
@ -144,8 +146,10 @@ struct ChatsTab: View {
.listRowInsets(EdgeInsets(top: 24, leading: 16, bottom: 24, trailing: 16)) .listRowInsets(EdgeInsets(top: 24, leading: 16, bottom: 24, trailing: 16))
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
} else { } else {
let firstLocalChatId = localSearchResults.first?.chatId
ForEach(localSearchResults) { chat in ForEach(localSearchResults) { chat in
chatRowItem(for: chat) chatRowItem(for: chat)
.id(chat.chatId == firstLocalChatId ? scrollToTopAnchorId : chat.chatId)
} }
} }
} }
@ -161,8 +165,10 @@ struct ChatsTab: View {
emptyState emptyState
} else { } else {
let firstChatId = viewModel.chats.first?.chatId
ForEach(viewModel.chats) { chat in ForEach(viewModel.chats) { chat in
chatRowItem(for: chat) chatRowItem(for: chat)
.id(chat.chatId == firstChatId ? scrollToTopAnchorId : chat.chatId)
} }
if viewModel.isLoadingMore { if viewModel.isLoadingMore {
@ -175,6 +181,9 @@ struct ChatsTab: View {
.modifier(ScrollDismissesKeyboardModifier()) .modifier(ScrollDismissesKeyboardModifier())
.simultaneousGesture(searchBarGesture) .simultaneousGesture(searchBarGesture)
.simultaneousGesture(tapToDismissKeyboardGesture) .simultaneousGesture(tapToDismissKeyboardGesture)
.onReceive(NotificationCenter.default.publisher(for: .chatsShouldScrollToTop)) { _ in
scrollChatsToTop(using: proxy)
}
// .safeAreaInset(edge: .top) { // .safeAreaInset(edge: .top) {
// VStack(spacing: 0) { // VStack(spacing: 0) {
// searchBar // searchBar
@ -189,6 +198,7 @@ struct ChatsTab: View {
pendingChatNavigationLink pendingChatNavigationLink
} }
} }
}
private var pendingChatNavigationLink: some View { private var pendingChatNavigationLink: some View {
NavigationLink( NavigationLink(
@ -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 { private var searchBarGesture: some Gesture {
DragGesture(minimumDistance: 10, coordinateSpace: .local) DragGesture(minimumDistance: 10, coordinateSpace: .local)
.onChanged { value in .onChanged { value in
@ -319,8 +337,10 @@ struct ChatsTab: View {
if globalSearchResults.isEmpty { if globalSearchResults.isEmpty {
globalSearchEmptyRow globalSearchEmptyRow
} else { } else {
let firstGlobalUserId = globalSearchResults.first?.id
ForEach(globalSearchResults) { user in ForEach(globalSearchResults) { user in
globalSearchRow(for: user) 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 debugRefreshChats = Notification.Name("debugRefreshChats")
static let chatsShouldRefresh = Notification.Name("chatsShouldRefresh") static let chatsShouldRefresh = Notification.Name("chatsShouldRefresh")
static let chatsReloadCompleted = Notification.Name("chatsReloadCompleted") static let chatsReloadCompleted = Notification.Name("chatsReloadCompleted")
static let chatsShouldScrollToTop = Notification.Name("chatsShouldScrollToTop")
} }

View File

@ -14,7 +14,7 @@ struct CustomTabBar: View {
} }
TabBarButton(systemName: "bubble.left.and.bubble.right.fill", text: NSLocalizedString("Чаты", comment: ""), isSelected: selectedTab == 2) { 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) { 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) { 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) { TabBarButton(systemName: "person.crop.square", text: NSLocalizedString("Лицо", comment: ""), isSelected: selectedTab == 3) {
@ -93,3 +93,13 @@ struct CreateButton: View {
.offset(y: -3) .offset(y: -3)
} }
} }
private extension CustomTabBar {
func handleChatsTabTap() {
if selectedTab == 2 {
NotificationCenter.default.post(name: .chatsShouldScrollToTop, object: nil)
} else {
selectedTab = 2
}
}
}