diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 9db9b58..54e6401 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -207,12 +207,24 @@ class CustomMediaPlayerViewController: UIViewController { blackCoverView.trailingAnchor.constraint(equalTo: view.trailingAnchor) ]) - backwardButton = UIImageView(image: UIImage(systemName: "gobackward.10")) + backwardButton = UIImageView(image: UIImage(systemName: "gobackward")) backwardButton.tintColor = .white backwardButton.contentMode = .scaleAspectFit backwardButton.isUserInteractionEnabled = true + + // 1) Tap gesture → normal skip let backwardTap = UITapGestureRecognizer(target: self, action: #selector(seekBackward)) + backwardTap.numberOfTapsRequired = 1 backwardButton.addGestureRecognizer(backwardTap) + + // 2) Long-press gesture → hold skip + let backwardLongPress = UILongPressGestureRecognizer(target: self, action: #selector(seekBackwardLongPress(_:))) + backwardLongPress.minimumPressDuration = 0.5 // Adjust as needed + backwardButton.addGestureRecognizer(backwardLongPress) + + // Make sure the tap doesn’t fire if the long-press is recognized + backwardTap.require(toFail: backwardLongPress) + controlsContainerView.addSubview(backwardButton) backwardButton.translatesAutoresizingMaskIntoConstraints = false @@ -225,14 +237,23 @@ class CustomMediaPlayerViewController: UIViewController { controlsContainerView.addSubview(playPauseButton) playPauseButton.translatesAutoresizingMaskIntoConstraints = false - forwardButton = UIImageView(image: UIImage(systemName: "goforward.10")) - forwardButton.tintColor = .white - forwardButton.contentMode = .scaleAspectFit - forwardButton.isUserInteractionEnabled = true - let forwardTap = UITapGestureRecognizer(target: self, action: #selector(seekForward)) - forwardButton.addGestureRecognizer(forwardTap) - controlsContainerView.addSubview(forwardButton) - forwardButton.translatesAutoresizingMaskIntoConstraints = false + forwardButton = UIImageView(image: UIImage(systemName: "goforward")) + forwardButton.tintColor = .white + forwardButton.contentMode = .scaleAspectFit + forwardButton.isUserInteractionEnabled = true + + let forwardTap = UITapGestureRecognizer(target: self, action: #selector(seekForward)) + forwardTap.numberOfTapsRequired = 1 + forwardButton.addGestureRecognizer(forwardTap) + + let forwardLongPress = UILongPressGestureRecognizer(target: self, action: #selector(seekForwardLongPress(_:))) + forwardLongPress.minimumPressDuration = 0.5 + forwardButton.addGestureRecognizer(forwardLongPress) + + forwardTap.require(toFail: forwardLongPress) + + controlsContainerView.addSubview(forwardButton) + forwardButton.translatesAutoresizingMaskIntoConstraints = false let sliderView = MusicProgressSlider( value: Binding(get: { self.sliderViewModel.sliderValue }, @@ -538,13 +559,36 @@ class CustomMediaPlayerViewController: UIViewController { } } - @objc func seekBackward() { - currentTimeVal = max(currentTimeVal - 10, 0) - player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) + @objc func seekBackwardLongPress(_ gesture: UILongPressGestureRecognizer) { + // Only do the skip when the gesture first begins + if gesture.state == .began { + let holdValue = UserDefaults.standard.double(forKey: "skipIncrementHold") + let finalSkip = holdValue > 0 ? holdValue : 30 // fallback to 30 if not set + currentTimeVal = max(currentTimeVal - finalSkip, 0) + player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) + } + } + + @objc func seekForwardLongPress(_ gesture: UILongPressGestureRecognizer) { + if gesture.state == .began { + let holdValue = UserDefaults.standard.double(forKey: "skipIncrementHold") + let finalSkip = holdValue > 0 ? holdValue : 30 + currentTimeVal = min(currentTimeVal + finalSkip, duration) + player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) + } } + @objc func seekBackward() { + let skipValue = UserDefaults.standard.double(forKey: "skipIncrement") + let finalSkip = skipValue > 0 ? skipValue : 10 + currentTimeVal = max(currentTimeVal - finalSkip, 0) + player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) + } + @objc func seekForward() { - currentTimeVal = min(currentTimeVal + 10, duration) + let skipValue = UserDefaults.standard.double(forKey: "skipIncrement") + let finalSkip = skipValue > 0 ? skipValue : 10 + currentTimeVal = min(currentTimeVal + finalSkip, duration) player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) } diff --git a/Sora/Views/LibraryView/LibraryManager.swift b/Sora/Views/LibraryView/LibraryManager.swift index 19712fb..b65d6ae 100644 --- a/Sora/Views/LibraryView/LibraryManager.swift +++ b/Sora/Views/LibraryView/LibraryManager.swift @@ -35,6 +35,14 @@ class LibraryManager: ObservableObject { loadBookmarks() } + func removeBookmark(item: LibraryItem) { + if let index = bookmarks.firstIndex(where: { $0.id == item.id }) { + bookmarks.remove(at: index) + Logger.shared.log("Removed series \(item.id) from bookmarks.",type: "Debug") + saveBookmarks() + } + } + private func loadBookmarks() { guard let data = UserDefaults.standard.data(forKey: bookmarksKey) else { Logger.shared.log("No bookmarks data found in UserDefaults.", type: "Error") diff --git a/Sora/Views/LibraryView/LibraryView.swift b/Sora/Views/LibraryView/LibraryView.swift index 1e5612f..87087c0 100644 --- a/Sora/Views/LibraryView/LibraryView.swift +++ b/Sora/Views/LibraryView/LibraryView.swift @@ -114,6 +114,13 @@ struct LibraryView: View { .multilineTextAlignment(.leading) } } + .contextMenu { + Button(role: .destructive, action: { + libraryManager.removeBookmark(item: item) + }) { + Label("Remove from Bookmarks", systemImage: "trash") + } + } } } } @@ -179,7 +186,7 @@ struct ContinueWatchingSection: View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { ForEach(Array(items.reversed())) { item in - ContinueWatchingCell(item: item,markAsWatched: { + ContinueWatchingCell(item: item, markAsWatched: { markAsWatched(item) }, removeItem: { removeItem(item) diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 2ad0986..a6d33d1 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -237,7 +237,7 @@ struct MediaInfoView: View { UserDefaults.standard.set(99999999.0, forKey: "totalTime_\(href)") } refreshTrigger.toggle() - Logger.shared.log("Marked \(ep.number) episodes watched within anime \"\(title)\".", type: "General") + Logger.shared.log("Marked \(ep.number - 1) episodes watched within anime \"\(title)\".", type: "General") } ) .id(refreshTrigger) diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift index 7eadefe..c37ecd2 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift @@ -13,12 +13,14 @@ struct SettingsViewPlayer: View { @AppStorage("hideNextButton") private var isHideNextButton = false @AppStorage("rememberPlaySpeed") private var isRememberPlaySpeed = false @AppStorage("holdSpeedPlayer") private var holdSpeedPlayer: Double = 2.0 + @AppStorage("skipIncrement") private var skipIncrement: Double = 10.0 + @AppStorage("skipIncrementHold") private var skipIncrementHold: Double = 30.0 private let mediaPlayers = ["Default", "VLC", "OutPlayer", "Infuse", "nPlayer", "Sora"] var body: some View { Form { - Section(header: Text("Media Player"), footer: Text("Some features are limited to the Sora and Default player, such as ForceLandscape and holdSpeed")) { + Section(header: Text("Media Player"), footer: Text("Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.")) { HStack { Text("Media Player") Spacer() @@ -56,7 +58,21 @@ struct SettingsViewPlayer: View { } } } - + Section(header: Text("Skip Settings")) { + // Normal skip + HStack { + Text("Tap Skip:") + Spacer() + Stepper("\(Int(skipIncrement))s", value: $skipIncrement, in: 5...300, step: 5) + } + + // Long-press skip + HStack { + Text("Long press Skip:") + Spacer() + Stepper("\(Int(skipIncrementHold))s", value: $skipIncrementHold, in: 5...300, step: 5) + } + } SubtitleSettingsSection() } .navigationTitle("Player") diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index d5494e1..7ec2d98 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -58,9 +58,9 @@ 13DC0C462D302C7500D0F966 /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DC0C452D302C7500D0F966 /* VideoPlayer.swift */; }; 13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD12D32D97400C1EBD7 /* CustomPlayer.swift */; }; 13EA2BD62D32D97400C1EBD7 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD32D32D97400C1EBD7 /* Double+Extension.swift */; }; - 13EA2BD72D32D97400C1EBD7 /* MusicProgressSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD42D32D97400C1EBD7 /* MusicProgressSlider.swift */; }; 13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */; }; 1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */; }; + 1EAC7A322D888BC50083984D /* MusicProgressSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -115,9 +115,9 @@ 13DC0C452D302C7500D0F966 /* VideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = ""; }; 13EA2BD12D32D97400C1EBD7 /* CustomPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomPlayer.swift; sourceTree = ""; }; 13EA2BD32D32D97400C1EBD7 /* Double+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; - 13EA2BD42D32D97400C1EBD7 /* MusicProgressSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = ""; }; 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NormalPlayer.swift; sourceTree = ""; }; 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLoggerFilter.swift; sourceTree = ""; }; + 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -437,7 +437,7 @@ isa = PBXGroup; children = ( 13EA2BD32D32D97400C1EBD7 /* Double+Extension.swift */, - 13EA2BD42D32D97400C1EBD7 /* MusicProgressSlider.swift */, + 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */, ); path = Components; sourceTree = ""; @@ -532,7 +532,6 @@ 1334FF4F2D786C9E007E289F /* TMDB-Trending.swift in Sources */, 13CBEFDA2D5F7D1200D011EE /* String.swift in Sources */, 130C6BFA2D53AB1F00DC1432 /* SettingsViewData.swift in Sources */, - 13EA2BD72D32D97400C1EBD7 /* MusicProgressSlider.swift in Sources */, 1334FF542D787217007E289F /* TMDBRequest.swift in Sources */, 1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */, 13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */, @@ -556,6 +555,7 @@ 1327FBA92D758DEA00FC6689 /* UIDevice+Model.swift in Sources */, 138AA1B82D2D66FD0021F9DF /* EpisodeCell.swift in Sources */, 133D7C8C2D2BE2640075467E /* SearchView.swift in Sources */, + 1EAC7A322D888BC50083984D /* MusicProgressSlider.swift in Sources */, 133D7C942D2BE2640075467E /* JSController.swift in Sources */, 133D7C922D2BE2640075467E /* URLSession.swift in Sources */, 133D7C912D2BE2640075467E /* SettingsViewModule.swift in Sources */,