diff --git a/Sora/ContentView.swift b/Sora/ContentView.swift index 6bbf386..5839e39 100644 --- a/Sora/ContentView.swift +++ b/Sora/ContentView.swift @@ -52,7 +52,7 @@ struct ContentView: View { } } } - .searchable(text: $searchQuery) + //.searchable(text: $searchQuery) } else { ZStack(alignment: .bottom) { ZStack { diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 6c253df..53f2275 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -632,10 +632,12 @@ struct MediaInfoView: View { @ViewBuilder private var flatEpisodeList: some View { - VStack(spacing: 15) { - ForEach(currentEpisodeList.indices.filter { selectedRange.contains($0) }, id: \.self) { i in - let ep = currentEpisodeList[i] - createEpisodeCell(episode: ep, index: i, season: isGroupedBySeasons ? selectedSeason + 1 : 1) + ScrollView { + VStack(spacing: 15) { + ForEach(currentEpisodeList.indices.filter { selectedRange.contains($0) }, id: \.self) { i in + let ep = currentEpisodeList[i] + createEpisodeCell(episode: ep, index: i, season: isGroupedBySeasons ? selectedSeason + 1 : 1) + } } } } @@ -644,10 +646,12 @@ struct MediaInfoView: View { private var seasonsEpisodeList: some View { let seasons = groupedEpisodes() if !seasons.isEmpty, selectedSeason < seasons.count { - VStack(spacing: 15) { - ForEach(seasons[selectedSeason].indices.filter { selectedRange.contains($0) }, id: \.self) { i in - let ep = seasons[selectedSeason][i] - createEpisodeCell(episode: ep, index: i, season: selectedSeason + 1) + ScrollView { + VStack(spacing: 15) { + ForEach(seasons[selectedSeason].indices.filter { selectedRange.contains($0) }, id: \.self) { i in + let ep = seasons[selectedSeason][i] + createEpisodeCell(episode: ep, index: i, season: selectedSeason + 1) + } } } } else { diff --git a/Sora/Views/SearchView/SearchComponents.swift b/Sora/Views/SearchView/SearchComponents.swift index f86153f..9771c90 100644 --- a/Sora/Views/SearchView/SearchComponents.swift +++ b/Sora/Views/SearchView/SearchComponents.swift @@ -69,3 +69,46 @@ struct SearchHistoryRow: View { } } } + +struct SearchBar: View { + @Binding var text: String + @Binding var isSearching: Bool + @FocusState private var isFocused: Bool + + var body: some View { + HStack { + HStack { + Image(systemName: "magnifyingglass") + .foregroundColor(.gray) + + TextField("Search", text: $text) + .focused($isFocused) + .submitLabel(.search) + .autocorrectionDisabled(true) + .autocapitalization(.none) + .disableAutocorrection(true) + .textInputAutocapitalization(.never) + .onChange(of: text) { _ in + // This triggers search while typing + NotificationCenter.default.post(name: .tabBarSearchQueryUpdated, object: nil, userInfo: ["searchQuery": text]) + } + .onSubmit { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } + + if !text.isEmpty { + Button(action: { + text = "" + isFocused = false + }) { + Image(systemName: "xmark.circle.fill") + .foregroundColor(.gray) + } + } + } + .padding(8) + .background(Color(.systemGray6)) + .cornerRadius(10) + } + } +} diff --git a/Sora/Views/SearchView/SearchView.swift b/Sora/Views/SearchView/SearchView.swift index e251581..9e4b38b 100644 --- a/Sora/Views/SearchView/SearchView.swift +++ b/Sora/Views/SearchView/SearchView.swift @@ -20,6 +20,7 @@ struct SearchView: View { @AppStorage("selectedModuleId") private var selectedModuleId: String? @AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2 @AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4 + @AppStorage("useNativeTabBar") private var useNativeTabBar: Bool = false @StateObject private var jsController = JSController.shared @EnvironmentObject var moduleManager: ModuleManager @@ -74,51 +75,64 @@ struct SearchView: View { } private var mainContent: some View { - VStack(alignment: .leading) { - HStack { - Text(LocalizedStringKey("Search")) - .font(.largeTitle) - .fontWeight(.bold) + VStack(alignment: .leading) { + HStack { + Text(LocalizedStringKey("Search")) + .font(.largeTitle) + .fontWeight(.bold) + + Spacer() + + ModuleSelectorMenu( + selectedModule: selectedModule, + moduleGroups: getModuleLanguageGroups(), + modulesByLanguage: getModulesByLanguage(), + selectedModuleId: selectedModuleId, + onModuleSelected: { moduleId in + selectedModuleId = moduleId + } + ) + } + .padding(.horizontal, 20) + .padding(.top, 20) - Spacer() + if useNativeTabBar { + SearchBar(text: $searchQuery, isSearching: $isSearching) + .padding(.horizontal, 20) + .padding(.top, 10) + } - ModuleSelectorMenu( - selectedModule: selectedModule, - moduleGroups: getModuleLanguageGroups(), - modulesByLanguage: getModulesByLanguage(), - selectedModuleId: selectedModuleId, - onModuleSelected: { moduleId in - selectedModuleId = moduleId - } - ) - } - .padding(.horizontal, 20) - .padding(.top, 20) - - ScrollView(showsIndicators: false) { - SearchContent( - selectedModule: selectedModule, - searchQuery: searchQuery, - searchHistory: searchHistory, - searchItems: searchItems, - isSearching: isSearching, - hasNoResults: hasNoResults, - columns: columns, - columnsCount: columnsCount, - cellWidth: cellWidth, - onHistoryItemSelected: { query in - searchQuery = query - searchDebounceTimer?.invalidate() - + ScrollView(showsIndicators: false) { + SearchContent( + selectedModule: selectedModule, + searchQuery: searchQuery, + searchHistory: searchHistory, + searchItems: searchItems, + isSearching: isSearching, + hasNoResults: hasNoResults, + columns: columns, + columnsCount: columnsCount, + cellWidth: cellWidth, + onHistoryItemSelected: { query in + searchQuery = query + searchDebounceTimer?.invalidate() + + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + NotificationCenter.default.post(name: .tabBarSearchQueryUpdated, object: nil, userInfo: ["searchQuery": query]) + + performSearch() + }, + onHistoryItemDeleted: { index in + removeFromHistory(at: index) + }, + onClearHistory: clearSearchHistory + ) + } + .scrollViewBottomPadding() + .simultaneousGesture( + DragGesture().onChanged { _ in UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) - NotificationCenter.default.post(name: .tabBarSearchQueryUpdated, object: nil, userInfo: ["searchQuery": query]) - - performSearch() - }, - onHistoryItemDeleted: { index in - removeFromHistory(at: index) - }, - onClearHistory: clearSearchHistory + } ) } .scrollViewBottomPadding() @@ -128,8 +142,6 @@ struct SearchView: View { } ) } - .navigationBarHidden(true) - } var body: some View { Group { @@ -348,49 +360,3 @@ struct SearchView: View { return getModulesByLanguage()[language] ?? [] } } - - -struct SearchBar: View { - @State private var debounceTimer: Timer? - @Binding var text: String - @Binding var isFocused: Bool - var onSearchButtonClicked: () -> Void - - var body: some View { - HStack { - TextField(LocalizedStringKey("Search..."), text: $text, onEditingChanged: { isEditing in - isFocused = isEditing - }, onCommit: onSearchButtonClicked) - .padding(7) - .padding(.horizontal, 25) - .background(Color(.systemGray6)) - .cornerRadius(8) - .onChange(of: text) { newValue in - debounceTimer?.invalidate() - if !newValue.isEmpty { - debounceTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in - onSearchButtonClicked() - } - } - } - .overlay( - HStack { - Image(systemName: "magnifyingglass") - .foregroundColor(.secondary) - .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) - .padding(.leading, 8) - - if !text.isEmpty { - Button(action: { - self.text = "" - }) { - Image(systemName: "multiply.circle.fill") - .foregroundColor(.secondary) - .padding(.trailing, 8) - } - } - } - ) - } - } -}