diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 1acb788..f6e1f71 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -709,20 +709,16 @@ class CustomMediaPlayerViewController: UIViewController { ) } - // Watch Next Button Logic: - let hideNext = UserDefaults.standard.bool(forKey: "hideNextButton") let isNearEnd = (self.duration - self.currentTimeVal) <= (self.duration * 0.10) && self.currentTimeVal != self.duration && self.showWatchNextButton && self.duration != 0 if isNearEnd { - // First appearance: show the button in its normal position. if !self.isWatchNextVisible { self.isWatchNextVisible = true self.watchNextButtonAppearedAt = self.currentTimeVal - // Choose constraints based on current controls visibility. if self.isControlsVisible { NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints) NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints) @@ -730,7 +726,6 @@ class CustomMediaPlayerViewController: UIViewController { NSLayoutConstraint.deactivate(self.watchNextButtonControlsConstraints) NSLayoutConstraint.activate(self.watchNextButtonNormalConstraints) } - // Soft fade-in. self.watchNextButton.isHidden = false self.watchNextButton.alpha = 0.0 UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: { @@ -738,23 +733,19 @@ class CustomMediaPlayerViewController: UIViewController { }, completion: nil) } - // When 5 seconds have elapsed from when the button first appeared: if let appearedAt = self.watchNextButtonAppearedAt, (self.currentTimeVal - appearedAt) >= 5, !self.isWatchNextRepositioned { - // Fade out the button first (even if controls are visible). UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: { self.watchNextButton.alpha = 0.0 }, completion: { _ in self.watchNextButton.isHidden = true - // Then lock it to the controls-attached constraints. NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints) NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints) self.isWatchNextRepositioned = true }) } } else { - // Not near end: reset the watch-next button state. self.watchNextButtonAppearedAt = nil self.isWatchNextVisible = false self.isWatchNextRepositioned = false @@ -771,7 +762,6 @@ class CustomMediaPlayerViewController: UIViewController { func repositionWatchNextButton() { self.isWatchNextRepositioned = true - // Update constraints so the button is now attached next to the playback controls. UIView.animate(withDuration: 0.3, animations: { NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints) NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints) @@ -799,7 +789,6 @@ class CustomMediaPlayerViewController: UIViewController { self.skip85Button.alpha = self.isControlsVisible ? 0.8 : 0 if self.isControlsVisible { - // Always use the controls-attached constraints. NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints) NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints) if self.isWatchNextRepositioned || self.isWatchNextVisible { @@ -809,7 +798,6 @@ class CustomMediaPlayerViewController: UIViewController { }) } } else { - // When controls are hidden: if !self.isWatchNextRepositioned && self.isWatchNextVisible { NSLayoutConstraint.deactivate(self.watchNextButtonControlsConstraints) NSLayoutConstraint.activate(self.watchNextButtonNormalConstraints) diff --git a/Sora/Views/SearchView.swift b/Sora/Views/SearchView.swift index 0b978b8..cfd739d 100644 --- a/Sora/Views/SearchView.swift +++ b/Sora/Views/SearchView.swift @@ -165,23 +165,9 @@ struct SearchView: View { .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Menu { - let modulesByLanguage = Dictionary(grouping: moduleManager.modules) { module in - guard let language = module.metadata.language else { return "Unknown" } - - let cleanLanguage = language.replacingOccurrences( - of: "\\s*\\([^\\)]*\\)", - with: "", - options: .regularExpression - ).trimmingCharacters(in: .whitespaces) - - return cleanLanguage.isEmpty ? "Unknown" : cleanLanguage - } - - let sortedLanguages = modulesByLanguage.keys.sorted() - - ForEach(sortedLanguages, id: \.self) { language in + ForEach(getModuleLanguageGroups(), id: \.self) { language in Menu(language) { - ForEach(modulesByLanguage[language] ?? [], id: \.id) { module in + ForEach(getModulesForLanguage(language), id: \.id) { module in Button { selectedModuleId = module.id.uuidString } label: { @@ -287,6 +273,41 @@ struct SearchView: View { return verticalSizeClass == .compact ? mediaColumnsLandscape : mediaColumnsPortrait } } + + private func cleanLanguageName(_ language: String?) -> String { + guard let language = language else { return "Unknown" } + + let cleaned = language.replacingOccurrences( + of: "\\s*\\([^\\)]*\\)", + with: "", + options: .regularExpression + ).trimmingCharacters(in: .whitespaces) + + return cleaned.isEmpty ? "Unknown" : cleaned + } + + private func getModulesByLanguage() -> [String: [ScrapingModule]] { + var result = [String: [ScrapingModule]]() + + for module in moduleManager.modules { + let language = cleanLanguageName(module.metadata.language) + if result[language] == nil { + result[language] = [module] + } else { + result[language]?.append(module) + } + } + + return result + } + + private func getModuleLanguageGroups() -> [String] { + return getModulesByLanguage().keys.sorted() + } + + private func getModulesForLanguage(_ language: String) -> [ScrapingModule] { + return getModulesByLanguage()[language] ?? [] + } } struct SearchBar: View {