mirror of
https://github.com/cranci1/Sora.git
synced 2026-01-11 20:10:24 +00:00
parent
9a0dbd41e9
commit
b8345c2e7f
1 changed files with 48 additions and 163 deletions
|
|
@ -15,15 +15,6 @@ struct SearchItem: Identifiable {
|
|||
let href: String
|
||||
}
|
||||
|
||||
struct SearchHistoryItem: Identifiable, Codable, Equatable {
|
||||
let id = UUID()
|
||||
let query: String
|
||||
let timestamp: Date
|
||||
|
||||
static func == (lhs: SearchHistoryItem, rhs: SearchHistoryItem) -> Bool {
|
||||
return lhs.query == rhs.query
|
||||
}
|
||||
}
|
||||
|
||||
struct SearchView: View {
|
||||
@AppStorage("selectedModuleId") private var selectedModuleId: String?
|
||||
|
|
@ -41,11 +32,6 @@ struct SearchView: View {
|
|||
@State private var hasNoResults = false
|
||||
@State private var isLandscape: Bool = UIDevice.current.orientation.isLandscape
|
||||
@State private var isModuleSelectorPresented = false
|
||||
@State private var searchHistory: [SearchHistoryItem] = []
|
||||
@State private var isShowingResults = false
|
||||
|
||||
private let userDefaults = UserDefaults.standard
|
||||
private let searchHistoryKey = "searchHistory"
|
||||
|
||||
private var selectedModule: ScrapingModule? {
|
||||
guard let id = selectedModuleId else { return nil }
|
||||
|
|
@ -73,17 +59,12 @@ struct SearchView: View {
|
|||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
NavigationView {
|
||||
ScrollView {
|
||||
let columnsCount = determineColumns()
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
SearchBar(text: $searchText, onSearchButtonClicked: {
|
||||
performSearch()
|
||||
if !searchText.isEmpty {
|
||||
isShowingResults = true
|
||||
}
|
||||
})
|
||||
SearchBar(text: $searchText, onSearchButtonClicked: performSearch)
|
||||
.padding(.leading)
|
||||
.padding(.trailing, searchText.isEmpty ? 16 : 0)
|
||||
.disabled(selectedModule == nil)
|
||||
|
|
@ -116,50 +97,58 @@ struct SearchView: View {
|
|||
.shadow(color: Color.black.opacity(0.1), radius: 2, y: 1)
|
||||
}
|
||||
|
||||
if searchText.isEmpty && !searchHistory.isEmpty {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text("Recent Searches")
|
||||
.font(.headline)
|
||||
.padding(.leading)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: clearSearchHistory) {
|
||||
Text("Clear")
|
||||
.foregroundColor(.accentColor)
|
||||
if !searchText.isEmpty {
|
||||
if isSearching {
|
||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) {
|
||||
ForEach(0..<columnsCount*4, id: \.self) { _ in
|
||||
SearchSkeletonCell(cellWidth: cellWidth)
|
||||
}
|
||||
.padding(.trailing)
|
||||
}
|
||||
.padding(.top)
|
||||
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading) {
|
||||
ForEach(searchHistory.sorted(by: { $0.timestamp > $1.timestamp })) { item in
|
||||
Button(action: {
|
||||
searchText = item.query
|
||||
performSearch()
|
||||
isShowingResults = true
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "clock")
|
||||
.foregroundColor(.secondary)
|
||||
Text(item.query)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
Image(systemName: "arrow.up.left")
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.padding(.horizontal)
|
||||
.padding()
|
||||
} else if hasNoResults {
|
||||
VStack(spacing: 8) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
.font(.largeTitle)
|
||||
.foregroundColor(.secondary)
|
||||
Text("No Results Found")
|
||||
.font(.headline)
|
||||
Text("Try different keywords")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top)
|
||||
} else {
|
||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) {
|
||||
ForEach(searchItems) { item in
|
||||
NavigationLink(destination: MediaInfoView(title: item.title, imageUrl: item.imageUrl, href: item.href, module: selectedModule!)) {
|
||||
VStack {
|
||||
KFImage(URL(string: item.imageUrl))
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(height: cellWidth * 3 / 2)
|
||||
.frame(maxWidth: cellWidth)
|
||||
.cornerRadius(10)
|
||||
.clipped()
|
||||
Text(item.title)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(Color.primary)
|
||||
.padding([.leading, .bottom], 8)
|
||||
.lineLimit(1)
|
||||
}
|
||||
Divider()
|
||||
.padding(.leading)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
updateOrientation()
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
||||
updateOrientation()
|
||||
}
|
||||
}
|
||||
.frame(maxHeight: 300)
|
||||
.padding(.top)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -230,21 +219,8 @@ struct SearchView: View {
|
|||
.fixedSize()
|
||||
}
|
||||
}
|
||||
.navigationDestination(isPresented: $isShowingResults) {
|
||||
SearchResultsView(
|
||||
searchText: searchText,
|
||||
searchItems: searchItems,
|
||||
isSearching: isSearching,
|
||||
hasNoResults: hasNoResults,
|
||||
columnsCount: columnsCount,
|
||||
cellWidth: cellWidth,
|
||||
module: selectedModule
|
||||
)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
loadSearchHistory()
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.onChange(of: selectedModuleId) { _ in
|
||||
if !searchText.isEmpty {
|
||||
performSearch()
|
||||
|
|
@ -274,7 +250,6 @@ struct SearchView: View {
|
|||
hasNoResults = false
|
||||
return
|
||||
}
|
||||
addToSearchHistory(query: searchText)
|
||||
|
||||
isSearching = true
|
||||
hasNoResults = false
|
||||
|
|
@ -307,32 +282,6 @@ struct SearchView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private func addToSearchHistory(query: String) {
|
||||
let newItem = SearchHistoryItem(query: query, timestamp: Date())
|
||||
searchHistory.removeAll(where: { $0.query == query })
|
||||
searchHistory.insert(newItem, at: 0)
|
||||
|
||||
saveSearchHistory()
|
||||
}
|
||||
|
||||
private func clearSearchHistory() {
|
||||
searchHistory = []
|
||||
saveSearchHistory()
|
||||
}
|
||||
|
||||
private func saveSearchHistory() {
|
||||
if let encoded = try? JSONEncoder().encode(searchHistory) {
|
||||
userDefaults.set(encoded, forKey: searchHistoryKey)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadSearchHistory() {
|
||||
if let data = userDefaults.data(forKey: searchHistoryKey),
|
||||
let decoded = try? JSONDecoder().decode([SearchHistoryItem].self, from: data) {
|
||||
searchHistory = decoded
|
||||
}
|
||||
}
|
||||
|
||||
private func updateOrientation() {
|
||||
DispatchQueue.main.async {
|
||||
isLandscape = UIDevice.current.orientation.isLandscape
|
||||
|
|
@ -422,67 +371,3 @@ struct SearchBar: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SearchResultsView: View {
|
||||
let searchText: String
|
||||
let searchItems: [SearchItem]
|
||||
let isSearching: Bool
|
||||
let hasNoResults: Bool
|
||||
let columnsCount: Int
|
||||
let cellWidth: CGFloat
|
||||
let module: ScrapingModule?
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
if isSearching {
|
||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) {
|
||||
ForEach(0..<columnsCount*4, id: \.self) { _ in
|
||||
SearchSkeletonCell(cellWidth: cellWidth)
|
||||
}
|
||||
}
|
||||
.padding(.top)
|
||||
.padding()
|
||||
} else if hasNoResults {
|
||||
VStack(spacing: 8) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
.font(.largeTitle)
|
||||
.foregroundColor(.secondary)
|
||||
Text("No Results Found")
|
||||
.font(.headline)
|
||||
Text("Try different keywords")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top)
|
||||
} else {
|
||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) {
|
||||
ForEach(searchItems) { item in
|
||||
NavigationLink(destination: MediaInfoView(title: item.title, imageUrl: item.imageUrl, href: item.href, module: module!)) {
|
||||
VStack {
|
||||
KFImage(URL(string: item.imageUrl))
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(height: cellWidth * 3 / 2)
|
||||
.frame(maxWidth: cellWidth)
|
||||
.cornerRadius(10)
|
||||
.clipped()
|
||||
Text(item.title)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(Color.primary)
|
||||
.padding([.leading, .bottom], 8)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
.navigationTitle(searchText)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue