mirror of
https://github.com/jellyfin/Swiftfin.git
synced 2024-11-27 00:00:37 +00:00
tvOS - Show and interact with the video menu (#1066)
* Made the menu accessable and fixed visual padding bug * Moved away from .onExitCommand etc * Minor refactoring * wip * Update Overlay.swift --------- Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
parent
5913c308a6
commit
081a316843
@ -43,3 +43,14 @@ struct SupportedOrientationsPreferenceKey: PreferenceKey {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(tvOS)
|
||||
struct PressCommandsPreferenceKey: PreferenceKey {
|
||||
|
||||
static var defaultValue: [PressCommandAction] = []
|
||||
|
||||
static func reduce(value: inout [PressCommandAction], nextValue: () -> [PressCommandAction]) {
|
||||
value.append(contentsOf: nextValue())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -0,0 +1,34 @@
|
||||
//
|
||||
// Swiftfin is subject to the terms of the Mozilla Public
|
||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
public struct PressCommandAction {
|
||||
|
||||
let title: String
|
||||
let press: UIPress.PressType
|
||||
let action: () -> Void
|
||||
|
||||
public init(
|
||||
title: String,
|
||||
press: UIPress.PressType,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.title = title
|
||||
self.press = press
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
|
||||
extension PressCommandAction: Equatable {
|
||||
|
||||
public static func == (lhs: PressCommandAction, rhs: PressCommandAction) -> Bool {
|
||||
lhs.press == rhs.press
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
//
|
||||
// Swiftfin is subject to the terms of the Mozilla Public
|
||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@resultBuilder
|
||||
public enum PressCommandsBuilder {
|
||||
|
||||
public static func buildBlock(_ components: [PressCommandAction]...) -> [PressCommandAction] {
|
||||
components.flatMap { $0 }
|
||||
}
|
||||
|
||||
public static func buildExpression(_ expression: PressCommandAction) -> [PressCommandAction] {
|
||||
[expression]
|
||||
}
|
||||
|
||||
public static func buildOptional(_ component: [PressCommandAction]?) -> [PressCommandAction] {
|
||||
component ?? []
|
||||
}
|
||||
|
||||
public static func buildEither(first component: [PressCommandAction]) -> [PressCommandAction] {
|
||||
component
|
||||
}
|
||||
|
||||
public static func buildEither(second component: [PressCommandAction]) -> [PressCommandAction] {
|
||||
component
|
||||
}
|
||||
|
||||
public static func buildArray(_ components: [[PressCommandAction]]) -> [PressCommandAction] {
|
||||
components.flatMap { $0 }
|
||||
}
|
||||
}
|
@ -27,6 +27,10 @@ public class UIPreferencesHostingController: UIHostingController<AnyView> {
|
||||
.onPreferenceChange(SupportedOrientationsPreferenceKey.self) {
|
||||
box.value?._orientations = $0
|
||||
}
|
||||
#elseif os(tvOS)
|
||||
.onPreferenceChange(PressCommandsPreferenceKey.self) {
|
||||
box.value?._pressCommandActions = $0
|
||||
}
|
||||
#endif
|
||||
)
|
||||
|
||||
@ -112,6 +116,30 @@ public class UIPreferencesHostingController: UIHostingController<AnyView> {
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if os(tvOS)
|
||||
|
||||
override public func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let gesture = UITapGestureRecognizer(target: self, action: #selector(ignorePress))
|
||||
gesture.allowedPressTypes = [NSNumber(value: UIPress.PressType.menu.rawValue)]
|
||||
view.addGestureRecognizer(gesture)
|
||||
}
|
||||
|
||||
@objc
|
||||
func ignorePress() {}
|
||||
|
||||
private var _pressCommandActions: [PressCommandAction] = []
|
||||
|
||||
override public func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
|
||||
guard let buttonPress = presses.first?.type else { return }
|
||||
|
||||
guard let action = _pressCommandActions
|
||||
.first(where: { $0.press == buttonPress }) else { return }
|
||||
action.action()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// TODO: remove after iOS 15 support removed
|
||||
|
@ -27,4 +27,10 @@ public extension View {
|
||||
preference(key: SupportedOrientationsPreferenceKey.self, value: supportedOrientations)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(tvOS)
|
||||
func pressCommands(@PressCommandsBuilder _ commands: @escaping () -> [PressCommandAction]) -> some View {
|
||||
preference(key: PressCommandsPreferenceKey.self, value: commands())
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ final class VideoPlayerCoordinator: NavigationCoordinatable {
|
||||
PreferencesView {
|
||||
VideoPlayer(manager: self.videoPlayerManager)
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
} else {
|
||||
NativeVideoPlayer(manager: self.videoPlayerManager)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
|
||||
//
|
||||
|
||||
import PreferencesView
|
||||
import SwiftUI
|
||||
import VLCUI
|
||||
|
||||
@ -21,6 +22,8 @@ extension VideoPlayer {
|
||||
private var proxy: VLCVideoPlayer.Proxy
|
||||
@EnvironmentObject
|
||||
private var router: VideoPlayerCoordinator.Router
|
||||
@EnvironmentObject
|
||||
private var videoPlayerManager: VideoPlayerManager
|
||||
|
||||
@State
|
||||
private var confirmCloseWorkItem: DispatchWorkItem?
|
||||
@ -50,6 +53,11 @@ extension VideoPlayer {
|
||||
.animation(.linear(duration: 0.1), value: currentOverlayType)
|
||||
.environment(\.currentOverlayType, $currentOverlayType)
|
||||
.environmentObject(overlayTimer)
|
||||
.onChange(of: isPresentingOverlay) {
|
||||
if !isPresentingOverlay {
|
||||
currentOverlayType = .main
|
||||
}
|
||||
}
|
||||
.onChange(of: currentOverlayType) { _, newValue in
|
||||
if [.smallMenu, .chapters].contains(newValue) {
|
||||
overlayTimer.pause()
|
||||
@ -64,39 +72,64 @@ extension VideoPlayer {
|
||||
isPresentingOverlay = false
|
||||
}
|
||||
}
|
||||
// .onSelectPressed {
|
||||
// currentOverlayType = .main
|
||||
// isPresentingOverlay = true
|
||||
// overlayTimer.start(5)
|
||||
// }
|
||||
// .onMenuPressed {
|
||||
//
|
||||
// overlayTimer.start(5)
|
||||
// confirmCloseWorkItem?.cancel()
|
||||
//
|
||||
// if isPresentingOverlay && currentOverlayType == .confirmClose {
|
||||
// proxy.stop()
|
||||
// router.dismissCoordinator()
|
||||
// } else if isPresentingOverlay && currentOverlayType == .smallMenu {
|
||||
// currentOverlayType = .main
|
||||
// } else {
|
||||
// withAnimation {
|
||||
// currentOverlayType = .confirmClose
|
||||
// isPresentingOverlay = true
|
||||
// }
|
||||
//
|
||||
// let task = DispatchWorkItem {
|
||||
// withAnimation {
|
||||
// isPresentingOverlay = false
|
||||
// overlayTimer.stop()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// confirmCloseWorkItem = task
|
||||
//
|
||||
// DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: task)
|
||||
// }
|
||||
// }
|
||||
.pressCommands {
|
||||
PressCommandAction(title: L10n.back, press: .menu, action: menuPress)
|
||||
PressCommandAction(title: L10n.playAndPause, press: .playPause) {
|
||||
if videoPlayerManager.state == .playing {
|
||||
videoPlayerManager.proxy.pause()
|
||||
withAnimation(.linear(duration: 0.3)) {
|
||||
isPresentingOverlay = true
|
||||
}
|
||||
} else if videoPlayerManager.state == .paused {
|
||||
videoPlayerManager.proxy.play()
|
||||
withAnimation(.linear(duration: 0.3)) {
|
||||
isPresentingOverlay = false
|
||||
}
|
||||
}
|
||||
}
|
||||
PressCommandAction(title: L10n.pressDownForMenu, press: .upArrow, action: arrowPress)
|
||||
PressCommandAction(title: L10n.pressDownForMenu, press: .downArrow, action: arrowPress)
|
||||
PressCommandAction(title: L10n.pressDownForMenu, press: .leftArrow, action: arrowPress)
|
||||
PressCommandAction(title: L10n.pressDownForMenu, press: .rightArrow, action: arrowPress)
|
||||
PressCommandAction(title: L10n.pressDownForMenu, press: .select, action: arrowPress)
|
||||
}
|
||||
}
|
||||
|
||||
func arrowPress() {
|
||||
if isPresentingOverlay { return }
|
||||
currentOverlayType = .main
|
||||
overlayTimer.start(5)
|
||||
withAnimation {
|
||||
isPresentingOverlay = true
|
||||
}
|
||||
}
|
||||
|
||||
func menuPress() {
|
||||
overlayTimer.start(5)
|
||||
confirmCloseWorkItem?.cancel()
|
||||
|
||||
if isPresentingOverlay && currentOverlayType == .confirmClose {
|
||||
proxy.stop()
|
||||
router.dismissCoordinator()
|
||||
} else if isPresentingOverlay && currentOverlayType == .smallMenu {
|
||||
currentOverlayType = .main
|
||||
} else {
|
||||
withAnimation {
|
||||
currentOverlayType = .confirmClose
|
||||
isPresentingOverlay = true
|
||||
}
|
||||
|
||||
let task = DispatchWorkItem {
|
||||
withAnimation {
|
||||
isPresentingOverlay = false
|
||||
overlayTimer.stop()
|
||||
}
|
||||
}
|
||||
|
||||
confirmCloseWorkItem = task
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: task)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user