Files
omnivore/apple/OmnivoreKit/Sources/App/Views/Home/LibrarySearchView.swift
2024-03-08 13:20:22 +08:00

158 lines
5.0 KiB
Swift

#if os(iOS)
import Introspect
import Models
import Services
import SwiftUI
import UIKit
import Views
struct LibrarySearchView: View {
@State private var searchBar: UISearchBar?
@State private var recents: [String] = []
@StateObject var viewModel = LibrarySearchViewModel()
@EnvironmentObject var dataService: DataService
@Environment(\.isSearching) var isSearching
@Environment(\.dismiss) private var dismiss
let homeFeedViewModel: HomeFeedViewModel
init(homeFeedViewModel: HomeFeedViewModel) {
self.homeFeedViewModel = homeFeedViewModel
}
func performTypeahead(_ searchTerm: String) {
Task {
await viewModel.search(dataService: self.dataService, searchTerm: searchTerm)
}
}
func setSearchTerm(_ searchTerm: String) {
viewModel.searchTerm = searchTerm
searchBar?.becomeFirstResponder()
performTypeahead(searchTerm)
}
func performSearch(_ searchTerm: String) {
let term = searchTerm.trimmingCharacters(in: Foundation.CharacterSet.whitespacesAndNewlines)
viewModel.saveRecentSearch(dataService: dataService, searchTerm: term)
recents = viewModel.recentSearches(dataService: dataService)
homeFeedViewModel.searchTerm = term
dismiss()
}
func recentSearchRow(_ term: String) -> some View {
HStack {
HStack {
Image(systemName: "clock.arrow.circlepath")
Text(term).foregroundColor(.appGrayText)
}.onTapGesture {
performSearch(term)
}
Spacer()
Image(systemName: "arrow.up.backward")
.onTapGesture {
setSearchTerm(viewModel.searchTerm + (viewModel.searchTerm.count > 0 ? " " : "") + term)
}
.searchCompletion(term)
}.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button {
withAnimation(.linear(duration: 0.4)) {
viewModel.removeRecentSearch(dataService: dataService, searchTerm: term)
self.recents = viewModel.recentSearches(dataService: dataService)
}
} label: {
Label("Remove", systemImage: "trash")
}.tint(.red)
}
}
var body: some View {
NavigationView {
innerBody
}.introspectViewController { controller in
searchBar = Introspect.findChild(ofType: UISearchBar.self, in: controller.view)
searchBar?.smartQuotesType = .no
}
}
var innerBody: some View {
ZStack {
if let linkRequest = viewModel.linkRequest {
NavigationLink(
destination: WebReaderLoadingContainer(requestID: linkRequest.serverID),
tag: linkRequest,
selection: $viewModel.linkRequest
) {
EmptyView()
}
}
listBody
.navigationTitle("Search")
.navigationBarItems(trailing: Button(action: { dismiss() }, label: { Text(LocalText.genericClose) }))
.navigationBarTitleDisplayMode(NavigationBarItem.TitleDisplayMode.inline)
.searchable(text: $viewModel.searchTerm, placement: .navigationBarDrawer(displayMode: .always)) {
ForEach(viewModel.items) { item in
HStack {
Text(item.title)
Spacer()
Image(systemName: "chevron.right")
}.onTapGesture {
homeFeedViewModel.pushLinkedRequest(request: LinkRequest(id: UUID(), serverID: item.id))
dismiss()
}
}
}
.onAppear {
self.recents = viewModel.recentSearches(dataService: dataService)
}
.onSubmit(of: .search) {
performSearch(viewModel.searchTerm)
}
.onChange(of: viewModel.searchTerm) { term in
performTypeahead(term)
}
}
}
var listBody: some View {
VStack {
List {
if viewModel.searchTerm.count == 0 {
if recents.count > 0 {
Section("Recent Searches") {
ForEach(recents, id: \.self) { term in
recentSearchRow(term)
}
}
}
Section("Narrow with advanced search") {
(Text("**in:** ") + Text("filter to inbox, archive, or all"))
.foregroundColor(.appGrayText)
.onTapGesture { setSearchTerm("is:") }
(Text("**title:** ") + Text("search for a specific title"))
.foregroundColor(.appGrayText)
.onTapGesture { setSearchTerm("site:") }
(Text("**has:highlights** ") + Text("any saved read with highlights"))
.foregroundColor(.appGrayText)
.onTapGesture { setSearchTerm("has:highlights") }
Button(action: {}, label: {
Text("[More on Advanced Search](https://docs.omnivore.app/using/search.html)")
.underline()
.padding(.top, 25)
})
}
}
}.listStyle(PlainListStyle())
}
}
}
#endif