search_top_bar #1

Merged
cheykrym merged 7 commits from search_top_bar into main 2025-10-07 04:47:37 +03:00
3 changed files with 101 additions and 36 deletions
Showing only changes of commit e51a4ed6b2 - Show all commits

View File

@ -12,9 +12,9 @@ struct TopBarView: View {
// Привязка для управления боковым меню
@Binding var isSideMenuPresented: Bool
@Binding var chatSearchRevealProgress: CGFloat
@State private var searchText: String = ""
@State private var isSearchBarVisible: Bool = false
var isHomeTab: Bool {
return title == "Home"
@ -106,38 +106,47 @@ struct TopBarView: View {
.padding()
.frame(height: 50) // Стандартная высота для нав. бара
if isChatsTab && isSearchBarVisible {
searchBar
.padding(.horizontal)
.padding(.bottom, 8)
.transition(.move(edge: .top).combined(with: .opacity))
if isChatsTab {
revealableSearchBar
}
Divider()
}
.background(Color(UIColor.systemBackground))
.animation(.spring(response: 0.35, dampingFraction: 0.85), value: isSearchBarVisible)
.onReceive(NotificationCenter.default.publisher(for: .chatsTabRevealSearchBar)) { _ in
guard isChatsTab else { return }
withAnimation {
isSearchBarVisible = true
}
}
.onReceive(NotificationCenter.default.publisher(for: .chatsTabHideSearchBar)) { _ in
guard isChatsTab else { return }
withAnimation {
isSearchBarVisible = false
}
}
.onChange(of: isChatsTab) { isChats in
if !isChats {
isSearchBarVisible = false
withAnimation(.spring(response: 0.35, dampingFraction: 0.75)) {
chatSearchRevealProgress = 0
}
}
}
}
}
private extension TopBarView {
private var normalizedRevealProgress: CGFloat {
guard isChatsTab else { return 0 }
return max(0, min(chatSearchRevealProgress, 1))
}
private var revealableSearchBar: some View {
let progress = normalizedRevealProgress
return VStack(spacing: 0) {
Spacer(minLength: 0)
searchBar
.padding(.horizontal)
.padding(.bottom, 8)
}
.frame(height: searchBarRevealHeight)
.clipped()
.scaleEffect(y: max(progress, 0.0001), anchor: .top)
.opacity(progress)
.allowsHitTesting(progress > 0.9)
.accessibilityHidden(progress < 0.9)
}
private var searchBarRevealHeight: CGFloat { 56 }
var searchBar: some View {
HStack(spacing: 8) {
Image(systemName: "magnifyingglass")
@ -165,7 +174,26 @@ private extension TopBarView {
}
struct TopBarView_Previews: PreviewProvider {
struct Wrapper: View {
@State private var selectedAccount = "@user"
@State private var isSideMenuPresented = false
@State private var revealProgress: CGFloat = 1
@StateObject private var viewModel = LoginViewModel()
var body: some View {
TopBarView(
title: "Chats",
selectedAccount: $selectedAccount,
accounts: [selectedAccount],
viewModel: viewModel,
isSideMenuPresented: $isSideMenuPresented,
chatSearchRevealProgress: $revealProgress
)
}
}
static var previews: some View {
/*@START_MENU_TOKEN@*/Text("Hello, World!")/*@END_MENU_TOKEN@*/
Wrapper()
.environmentObject(ThemeManager())
}
}

View File

@ -8,10 +8,20 @@
import SwiftUI
struct ChatsTab: View {
var currentUserId: String? = nil
var currentUserId: String?
@Binding var searchRevealProgress: CGFloat
@StateObject private var viewModel = PrivateChatsViewModel()
@State private var selectedChatId: String?
@State private var searchText: String = ""
@State private var searchDragStartProgress: CGFloat = 0
@State private var isSearchGestureActive: Bool = false
private let searchRevealDistance: CGFloat = 90
init(currentUserId: String? = nil, searchRevealProgress: Binding<CGFloat>) {
self.currentUserId = currentUserId
self._searchRevealProgress = searchRevealProgress
}
var body: some View {
content
@ -154,16 +164,31 @@ struct ChatsTab: View {
}
private var searchBarGesture: some Gesture {
DragGesture(minimumDistance: 24)
.onEnded { value in
DragGesture(minimumDistance: 10, coordinateSpace: .local)
.onChanged { value in
let verticalTranslation = value.translation.height
let horizontalTranslation = value.translation.width
guard abs(verticalTranslation) > abs(horizontalTranslation) else { return }
if verticalTranslation > 24 {
NotificationCenter.default.post(name: .chatsTabRevealSearchBar, object: nil)
} else if verticalTranslation < -24 {
NotificationCenter.default.post(name: .chatsTabHideSearchBar, object: nil)
if !isSearchGestureActive {
guard abs(verticalTranslation) > abs(horizontalTranslation) else { return }
if searchRevealProgress <= 0.001 && verticalTranslation < 0 { return }
isSearchGestureActive = true
searchDragStartProgress = searchRevealProgress
}
guard isSearchGestureActive else { return }
let delta = verticalTranslation / searchRevealDistance
let newProgress = searchDragStartProgress + delta
searchRevealProgress = max(0, min(1, newProgress))
}
.onEnded { _ in
guard isSearchGestureActive else { return }
isSearchGestureActive = false
let target: CGFloat = searchRevealProgress > 0.5 ? 1 : 0
withAnimation(.spring(response: 0.35, dampingFraction: 0.75)) {
searchRevealProgress = target
}
}
}
@ -568,9 +593,17 @@ private struct ChatRowView: View {
}
struct ChatsTab_Previews: PreviewProvider {
struct Wrapper: View {
@State private var progress: CGFloat = 1
var body: some View {
ChatsTab(searchRevealProgress: $progress)
.environmentObject(ThemeManager())
}
}
static var previews: some View {
ChatsTab()
.environmentObject(ThemeManager())
Wrapper()
}
}
@ -636,6 +669,4 @@ private struct ChatPlaceholderView: View {
extension Notification.Name {
static let debugRefreshChats = Notification.Name("debugRefreshChats")
static let chatsTabRevealSearchBar = Notification.Name("chatsTabRevealSearchBar")
static let chatsTabHideSearchBar = Notification.Name("chatsTabHideSearchBar")
}

View File

@ -13,6 +13,7 @@ struct MainView: View {
// Состояния для бокового меню
@State private var isSideMenuPresented = false
@State private var menuOffset: CGFloat = 0
@State private var chatSearchRevealProgress: CGFloat = 0
private var tabTitle: String {
switch selectedTab {
@ -38,7 +39,8 @@ struct MainView: View {
selectedAccount: $selectedAccount,
accounts: accounts,
viewModel: viewModel,
isSideMenuPresented: $isSideMenuPresented
isSideMenuPresented: $isSideMenuPresented,
chatSearchRevealProgress: $chatSearchRevealProgress
)
ZStack {
@ -48,8 +50,12 @@ struct MainView: View {
FeedbackTab()
.opacity(selectedTab == 1 ? 1 : 0)
ChatsTab(currentUserId: viewModel.userId.isEmpty ? nil : viewModel.userId)
ChatsTab(
currentUserId: viewModel.userId.isEmpty ? nil : viewModel.userId,
searchRevealProgress: $chatSearchRevealProgress
)
.opacity(selectedTab == 2 ? 1 : 0)
.allowsHitTesting(selectedTab == 2)
ProfileTab()
.opacity(selectedTab == 3 ? 1 : 0)