mirror of
https://github.com/cranci1/Sora.git
synced 2026-05-11 20:40:39 +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
|
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 {
|
struct SearchView: View {
|
||||||
@AppStorage("selectedModuleId") private var selectedModuleId: String?
|
@AppStorage("selectedModuleId") private var selectedModuleId: String?
|
||||||
|
|
@ -41,11 +32,6 @@ struct SearchView: View {
|
||||||
@State private var hasNoResults = false
|
@State private var hasNoResults = false
|
||||||
@State private var isLandscape: Bool = UIDevice.current.orientation.isLandscape
|
@State private var isLandscape: Bool = UIDevice.current.orientation.isLandscape
|
||||||
@State private var isModuleSelectorPresented = false
|
@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? {
|
private var selectedModule: ScrapingModule? {
|
||||||
guard let id = selectedModuleId else { return nil }
|
guard let id = selectedModuleId else { return nil }
|
||||||
|
|
@ -73,17 +59,12 @@ struct SearchView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationView {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
let columnsCount = determineColumns()
|
let columnsCount = determineColumns()
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
HStack {
|
HStack {
|
||||||
SearchBar(text: $searchText, onSearchButtonClicked: {
|
SearchBar(text: $searchText, onSearchButtonClicked: performSearch)
|
||||||
performSearch()
|
|
||||||
if !searchText.isEmpty {
|
|
||||||
isShowingResults = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
.padding(.trailing, searchText.isEmpty ? 16 : 0)
|
.padding(.trailing, searchText.isEmpty ? 16 : 0)
|
||||||
.disabled(selectedModule == nil)
|
.disabled(selectedModule == nil)
|
||||||
|
|
@ -116,50 +97,58 @@ struct SearchView: View {
|
||||||
.shadow(color: Color.black.opacity(0.1), radius: 2, y: 1)
|
.shadow(color: Color.black.opacity(0.1), radius: 2, y: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if searchText.isEmpty && !searchHistory.isEmpty {
|
if !searchText.isEmpty {
|
||||||
VStack(alignment: .leading) {
|
if isSearching {
|
||||||
HStack {
|
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) {
|
||||||
Text("Recent Searches")
|
ForEach(0..<columnsCount*4, id: \.self) { _ in
|
||||||
.font(.headline)
|
SearchSkeletonCell(cellWidth: cellWidth)
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Button(action: clearSearchHistory) {
|
|
||||||
Text("Clear")
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
}
|
}
|
||||||
.padding(.trailing)
|
|
||||||
}
|
}
|
||||||
.padding(.top)
|
.padding(.top)
|
||||||
|
.padding()
|
||||||
ScrollView {
|
} else if hasNoResults {
|
||||||
LazyVStack(alignment: .leading) {
|
VStack(spacing: 8) {
|
||||||
ForEach(searchHistory.sorted(by: { $0.timestamp > $1.timestamp })) { item in
|
Image(systemName: "magnifyingglass")
|
||||||
Button(action: {
|
.font(.largeTitle)
|
||||||
searchText = item.query
|
.foregroundColor(.secondary)
|
||||||
performSearch()
|
Text("No Results Found")
|
||||||
isShowingResults = true
|
.font(.headline)
|
||||||
}) {
|
Text("Try different keywords")
|
||||||
HStack {
|
.font(.caption)
|
||||||
Image(systemName: "clock")
|
.foregroundColor(.secondary)
|
||||||
.foregroundColor(.secondary)
|
}
|
||||||
Text(item.query)
|
.padding()
|
||||||
.foregroundColor(.primary)
|
.frame(maxWidth: .infinity)
|
||||||
Spacer()
|
.padding(.top)
|
||||||
Image(systemName: "arrow.up.left")
|
} else {
|
||||||
.foregroundColor(.secondary)
|
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) {
|
||||||
.font(.caption)
|
ForEach(searchItems) { item in
|
||||||
}
|
NavigationLink(destination: MediaInfoView(title: item.title, imageUrl: item.imageUrl, href: item.href, module: selectedModule!)) {
|
||||||
.padding(.vertical, 8)
|
VStack {
|
||||||
.padding(.horizontal)
|
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()
|
.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
|
.onChange(of: selectedModuleId) { _ in
|
||||||
if !searchText.isEmpty {
|
if !searchText.isEmpty {
|
||||||
performSearch()
|
performSearch()
|
||||||
|
|
@ -274,7 +250,6 @@ struct SearchView: View {
|
||||||
hasNoResults = false
|
hasNoResults = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
addToSearchHistory(query: searchText)
|
|
||||||
|
|
||||||
isSearching = true
|
isSearching = true
|
||||||
hasNoResults = false
|
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() {
|
private func updateOrientation() {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
isLandscape = UIDevice.current.orientation.isLandscape
|
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