From 32dd59e635e3b6688dc33761b194d91a57455877 Mon Sep 17 00:00:00 2001 From: cheykrym Date: Wed, 23 Jul 2025 02:16:37 +0300 Subject: [PATCH] search add --- Shared/Models/User.swift | 13 +++--- Shared/ViewModels/SearchViewModel.swift | 41 ++++++++++++++++ Shared/Views/Tab/SearchTab.swift | 62 +++++++++++++++++++++++-- yobble.xcodeproj/project.pbxproj | 4 ++ 4 files changed, 108 insertions(+), 12 deletions(-) create mode 100644 Shared/ViewModels/SearchViewModel.swift diff --git a/Shared/Models/User.swift b/Shared/Models/User.swift index c8dc186..1665191 100644 --- a/Shared/Models/User.swift +++ b/Shared/Models/User.swift @@ -1,8 +1,7 @@ -// -// User.swift -// volnahub (iOS) -// -// Created by cheykrym on 09/06/2025. -// - import Foundation + +struct User: Identifiable, Decodable { + let id: String + let username: String + let email: String +} \ No newline at end of file diff --git a/Shared/ViewModels/SearchViewModel.swift b/Shared/ViewModels/SearchViewModel.swift new file mode 100644 index 0000000..6ceb15b --- /dev/null +++ b/Shared/ViewModels/SearchViewModel.swift @@ -0,0 +1,41 @@ +import SwiftUI +import Combine + +class SearchViewModel: ObservableObject { + @Published var searchText = "" + @Published var users = [User]() + + private var cancellables = Set() + + init() { + $searchText + .debounce(for: .milliseconds(500), scheduler: RunLoop.main) + .removeDuplicates() + .flatMap { query -> AnyPublisher<[User], Error> in + if query.isEmpty { + return Just([]) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } else { + return self.searchUsers(query: query) + .eraseToAnyPublisher() + } + } + .catch { _ in Just([]) } // При ошибке возвращаем пустой массив + .receive(on: RunLoop.main) + .assign(to: \.users, on: self) + .store(in: &cancellables) + } + + func searchUsers(query: String) -> Future<[User], Error> { + Future { promise in + // Имитация сетевого запроса + let mockUsers = [ + User(id: "1", username: "testuser1", email: "test1@test.com"), + User(id: "2", username: "testuser2", email: "test2@test.com"), + User(id: "3", username: query, email: "test3@test.com") + ] + promise(.success(mockUsers)) + } + } +} \ No newline at end of file diff --git a/Shared/Views/Tab/SearchTab.swift b/Shared/Views/Tab/SearchTab.swift index 92e82c0..4aa30bb 100644 --- a/Shared/Views/Tab/SearchTab.swift +++ b/Shared/Views/Tab/SearchTab.swift @@ -1,16 +1,68 @@ import SwiftUI struct SearchTab: View { + @StateObject private var viewModel = SearchViewModel() + var body: some View { NavigationView { VStack { - Text("Поиск") - .font(.largeTitle) - .bold() - .padding() - Spacer() + SearchBar(text: $viewModel.searchText) + .padding(.top, 8) + + List(viewModel.users) { user in + NavigationLink(destination: ProfileTab(viewModel: LoginViewModel())) { // Placeholder destination + HStack { + Image(systemName: "person.crop.circle") + .resizable() + .frame(width: 40, height: 40) + .clipShape(Circle()) + Text(user.username) + } + } + } + .listStyle(PlainListStyle()) } .navigationTitle("Поиск") } } } + +struct SearchBar: View { + @Binding var text: String + + var body: some View { + HStack { + TextField("Поиск...", text: $text) + .padding(8) + .padding(.horizontal, 25) + .background(Color(.systemGray6)) + .cornerRadius(8) + .overlay( + HStack { + Image(systemName: "magnifyingglass") + .foregroundColor(.gray) + .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + .padding(.leading, 8) + + if !text.isEmpty { + Button(action: { + self.text = "" + }) { + Image(systemName: "multiply.circle.fill") + .foregroundColor(.gray) + .padding(.trailing, 8) + } + } + } + ) + } + .padding(.horizontal, 10) + } +} + +struct SearchTab_Previews: PreviewProvider { + static var previews: some View { + SearchTab() + } +} + diff --git a/yobble.xcodeproj/project.pbxproj b/yobble.xcodeproj/project.pbxproj index 5e74271..82d96ea 100644 --- a/yobble.xcodeproj/project.pbxproj +++ b/yobble.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 1A0276032DF909F900D8BC53 /* refreshtokenex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A0276022DF909F900D8BC53 /* refreshtokenex.swift */; }; 1A0276112DF9247000D8BC53 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A0276102DF9247000D8BC53 /* CustomTextField.swift */; }; + 1A2302112E3050C60067BF4F /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2302102E3050C60067BF4F /* SearchViewModel.swift */; }; 1A79408D2DF77BC3002569DA /* yobbleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A79407A2DF77BC2002569DA /* yobbleApp.swift */; }; 1A79408F2DF77BC3002569DA /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A79407B2DF77BC2002569DA /* ContentView.swift */; }; 1A7940902DF77BC3002569DA /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A79407B2DF77BC2002569DA /* ContentView.swift */; }; @@ -49,6 +50,7 @@ /* Begin PBXFileReference section */ 1A0276022DF909F900D8BC53 /* refreshtokenex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = refreshtokenex.swift; sourceTree = ""; }; 1A0276102DF9247000D8BC53 /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = ""; }; + 1A2302102E3050C60067BF4F /* SearchViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = ""; }; 1A79407A2DF77BC2002569DA /* yobbleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = yobbleApp.swift; sourceTree = ""; }; 1A79407B2DF77BC2002569DA /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 1A79407C2DF77BC3002569DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -177,6 +179,7 @@ 1A79409E2DF77DBD002569DA /* ViewModels */ = { isa = PBXGroup; children = ( + 1A2302102E3050C60067BF4F /* SearchViewModel.swift */, 1A7940A92DF77E05002569DA /* LoginViewModel.swift */, ); path = ViewModels; @@ -387,6 +390,7 @@ 1ACE61212E22FFD000B37AC5 /* ProfileContentTabbedGrid.swift in Sources */, 1AB4F8F72E22ECAC002B6E40 /* FollowingView.swift in Sources */, 1A7940DE2DF7B0D7002569DA /* config.swift in Sources */, + 1A2302112E3050C60067BF4F /* SearchViewModel.swift in Sources */, 1AE587252E23337000254F06 /* ProfileContentGrid.swift in Sources */, 1A0276032DF909F900D8BC53 /* refreshtokenex.swift in Sources */, 1A7940B62DF77F21002569DA /* MainView.swift in Sources */,