2022-12-19 12:35:37 +00:00
import Defaults
import SwiftUI
struct PlayerControlsSettings : View {
2023-05-20 20:49:10 +00:00
@ Default ( . avPlayerUsesSystemControls ) private var avPlayerUsesSystemControls
2022-12-19 12:35:37 +00:00
@ Default ( . systemControlsCommands ) private var systemControlsCommands
@ Default ( . playerControlsLayout ) private var playerControlsLayout
@ Default ( . fullScreenPlayerControlsLayout ) private var fullScreenPlayerControlsLayout
@ Default ( . horizontalPlayerGestureEnabled ) private var horizontalPlayerGestureEnabled
@ Default ( . seekGestureSpeed ) private var seekGestureSpeed
@ Default ( . seekGestureSensitivity ) private var seekGestureSensitivity
@ Default ( . buttonBackwardSeekDuration ) private var buttonBackwardSeekDuration
@ Default ( . buttonForwardSeekDuration ) private var buttonForwardSeekDuration
@ Default ( . gestureBackwardSeekDuration ) private var gestureBackwardSeekDuration
@ Default ( . gestureForwardSeekDuration ) private var gestureForwardSeekDuration
@ Default ( . systemControlsSeekDuration ) private var systemControlsSeekDuration
2024-02-01 23:17:06 +00:00
@ Default ( . playerActionsButtonLabelStyle ) private var playerActionsButtonLabelStyle
2022-12-19 12:35:37 +00:00
@ Default ( . actionButtonShareEnabled ) private var actionButtonShareEnabled
@ Default ( . actionButtonSubscribeEnabled ) private var actionButtonSubscribeEnabled
@ Default ( . actionButtonCloseEnabled ) private var actionButtonCloseEnabled
@ Default ( . actionButtonAddToPlaylistEnabled ) private var actionButtonAddToPlaylistEnabled
@ Default ( . actionButtonSettingsEnabled ) private var actionButtonSettingsEnabled
2023-04-22 14:33:08 +00:00
@ Default ( . actionButtonFullScreenEnabled ) private var actionButtonFullScreenEnabled
@ Default ( . actionButtonPipEnabled ) private var actionButtonPipEnabled
@ Default ( . actionButtonLockOrientationEnabled ) private var actionButtonLockOrientationEnabled
@ Default ( . actionButtonRestartEnabled ) private var actionButtonRestartEnabled
@ Default ( . actionButtonAdvanceToNextItemEnabled ) private var actionButtonAdvanceToNextItemEnabled
@ Default ( . actionButtonMusicModeEnabled ) private var actionButtonMusicModeEnabled
2022-12-19 12:35:37 +00:00
@ Default ( . actionButtonHideEnabled ) private var actionButtonHideEnabled
#if os ( iOS )
@ Default ( . playerControlsLockOrientationEnabled ) private var playerControlsLockOrientationEnabled
#endif
@ Default ( . playerControlsSettingsEnabled ) private var playerControlsSettingsEnabled
@ Default ( . playerControlsCloseEnabled ) private var playerControlsCloseEnabled
@ Default ( . playerControlsRestartEnabled ) private var playerControlsRestartEnabled
@ Default ( . playerControlsAdvanceToNextEnabled ) private var playerControlsAdvanceToNextEnabled
@ Default ( . playerControlsPlaybackModeEnabled ) private var playerControlsPlaybackModeEnabled
@ Default ( . playerControlsMusicModeEnabled ) private var playerControlsMusicModeEnabled
2024-09-05 13:06:59 +00:00
@ Default ( . playerControlsBackgroundOpacity ) private var playerControlsBackgroundOpacity
2022-12-19 12:35:37 +00:00
private var player = PlayerModel . shared
var body : some View {
Group {
#if os ( macOS )
sections
Spacer ( )
#else
List {
sections
}
#endif
}
#if os ( tvOS )
. frame ( maxWidth : 1000 )
#elseif os ( iOS )
. listStyle ( . insetGrouped )
#endif
2022-12-20 22:24:39 +00:00
. navigationTitle ( " Controls " )
2022-12-19 12:35:37 +00:00
}
@ ViewBuilder var sections : some View {
#if ! os ( tvOS )
Section ( header : SettingsHeader ( text : " Controls " . localized ( ) ) , footer : controlsLayoutFooter ) {
2023-05-20 20:49:10 +00:00
#if ! os ( tvOS )
avPlayerUsesSystemControlsToggle
#endif
2022-12-19 12:35:37 +00:00
horizontalPlayerGestureEnabledToggle
SettingsHeader ( text : " Seek gesture sensitivity " . localized ( ) , secondary : true )
seekGestureSensitivityPicker
SettingsHeader ( text : " Seek gesture speed " . localized ( ) , secondary : true )
seekGestureSpeedPicker
SettingsHeader ( text : " Regular size " . localized ( ) , secondary : true )
playerControlsLayoutPicker
SettingsHeader ( text : " Fullscreen size " . localized ( ) , secondary : true )
fullScreenPlayerControlsLayoutPicker
2024-09-05 13:06:59 +00:00
SettingsHeader ( text : " Background opacity " . localized ( ) , secondary : true )
playerControlsBackgroundOpacityPicker
2022-12-19 12:35:37 +00:00
}
#endif
2023-05-19 07:55:04 +00:00
Section ( header : SettingsHeader ( text : " Seeking " . localized ( ) ) , footer : seekingGestureSection ) {
2022-12-19 12:35:37 +00:00
systemControlsCommandsPicker
seekingSection
}
#if os ( macOS )
HStack ( alignment : . top ) {
VStack ( alignment : . leading ) {
controlsButtonsSection
}
. frame ( maxWidth : . infinity , alignment : . leading )
VStack ( alignment : . leading ) {
actionsButtonsSection
}
. frame ( maxWidth : . infinity , alignment : . leading )
}
. padding ( . top , 10 )
#else
controlsButtonsSection
#if ! os ( tvOS )
actionsButtonsSection
#endif
#endif
}
var controlsButtonsSection : some View {
2023-05-19 07:55:04 +00:00
Section ( header : SettingsHeader ( text : " Controls Buttons " . localized ( ) ) ) {
2022-12-19 12:35:37 +00:00
controlButtonToggles
}
}
@ ViewBuilder var actionsButtonsSection : some View {
2023-05-19 07:55:04 +00:00
Section ( header : SettingsHeader ( text : " Actions Buttons " . localized ( ) ) ) {
2022-12-19 12:35:37 +00:00
actionButtonToggles
}
2024-02-01 23:17:06 +00:00
Section {
Picker ( " Action button labels " , selection : $ playerActionsButtonLabelStyle ) {
ForEach ( ButtonLabelStyle . allCases , id : \ . rawValue ) { style in
Text ( style . description ) . tag ( style )
}
}
. modifier ( SettingsPickerModifier ( ) )
}
2022-12-19 12:35:37 +00:00
}
private var systemControlsCommandsPicker : some View {
func labelText ( _ label : String ) -> String {
#if os ( macOS )
String ( format : " System controls show buttons for %@ " . localized ( ) , label )
#else
label
#endif
}
return Picker ( " System controls buttons " , selection : $ systemControlsCommands ) {
Text ( labelText ( " Seek " . localized ( ) ) ) . tag ( SystemControlsCommands . seek )
Text ( labelText ( " Restart/Play next " . localized ( ) ) ) . tag ( SystemControlsCommands . restartAndAdvanceToNext )
}
. onChange ( of : systemControlsCommands ) { _ in
player . updateRemoteCommandCenter ( )
}
. modifier ( SettingsPickerModifier ( ) )
}
@ ViewBuilder private var controlsLayoutFooter : some View {
#if os ( iOS )
Text ( " Large layout is not suitable for all devices and using it may cause controls not to fit on the screen. " )
#endif
}
private var horizontalPlayerGestureEnabledToggle : some View {
Toggle ( " Seek with horizontal swipe on video " , isOn : $ horizontalPlayerGestureEnabled )
}
2023-05-20 20:49:10 +00:00
private var avPlayerUsesSystemControlsToggle : some View {
Toggle ( " Use system controls with AVPlayer " , isOn : $ avPlayerUsesSystemControls )
}
2022-12-19 12:35:37 +00:00
private var seekGestureSensitivityPicker : some View {
Picker ( " Seek gesture sensitivity " , selection : $ seekGestureSensitivity ) {
Text ( " Highest " ) . tag ( 1.0 )
Text ( " High " ) . tag ( 10.0 )
Text ( " Normal " ) . tag ( 30.0 )
Text ( " Low " ) . tag ( 50.0 )
Text ( " Lowest " ) . tag ( 100.0 )
}
. disabled ( ! horizontalPlayerGestureEnabled )
. modifier ( SettingsPickerModifier ( ) )
}
private var seekGestureSpeedPicker : some View {
Picker ( " Seek gesture speed " , selection : $ seekGestureSpeed ) {
ForEach ( [ 1 , 0.75 , 0.66 , 0.5 , 0.33 , 0.25 , 0.1 ] , id : \ . self ) { value in
Text ( String ( format : " %.0f%% " , value * 100 ) ) . tag ( value )
}
}
. disabled ( ! horizontalPlayerGestureEnabled )
. modifier ( SettingsPickerModifier ( ) )
}
private var playerControlsLayoutPicker : some View {
Picker ( " Regular Size " , selection : $ playerControlsLayout ) {
ForEach ( PlayerControlsLayout . allCases . filter ( \ . available ) , id : \ . self ) { layout in
Text ( layout . description ) . tag ( layout . rawValue )
}
}
. modifier ( SettingsPickerModifier ( ) )
}
private var fullScreenPlayerControlsLayoutPicker : some View {
Picker ( " Fullscreen size " , selection : $ fullScreenPlayerControlsLayout ) {
ForEach ( PlayerControlsLayout . allCases . filter ( \ . available ) , id : \ . self ) { layout in
Text ( layout . description ) . tag ( layout . rawValue )
}
}
. modifier ( SettingsPickerModifier ( ) )
}
2024-09-05 13:06:59 +00:00
private var playerControlsBackgroundOpacityPicker : some View {
Picker ( " Background opacity " , selection : $ playerControlsBackgroundOpacity ) {
ForEach ( Array ( stride ( from : 0.0 , through : 1.0 , by : 0.1 ) ) , id : \ . self ) { value in
Text ( " \( Int ( value * 100 ) ) % " ) . tag ( value )
}
}
. modifier ( SettingsPickerModifier ( ) )
}
2022-12-19 12:35:37 +00:00
@ ViewBuilder private var seekingSection : some View {
seekingDurationSetting ( " System controls " , $ systemControlsSeekDuration )
. foregroundColor ( systemControlsCommands = = . restartAndAdvanceToNext ? . secondary : . primary )
. disabled ( systemControlsCommands = = . restartAndAdvanceToNext )
seekingDurationSetting ( " Controls button: backwards " , $ buttonBackwardSeekDuration )
seekingDurationSetting ( " Controls button: forwards " , $ buttonForwardSeekDuration )
seekingDurationSetting ( " Gesture: backwards " , $ gestureBackwardSeekDuration )
seekingDurationSetting ( " Gesture: fowards " , $ gestureForwardSeekDuration )
}
private var seekingGestureSection : some View {
#if os ( iOS )
Text ( " Gesture settings control skipping interval for double tap gesture on left/right side of the player. Changing system controls settings requires restart. " )
#elseif os ( macOS )
Text ( " Gesture settings control skipping interval for double click on left/right side of the player. Changing system controls settings requires restart. " )
. foregroundColor ( . secondary )
#else
Text ( " Gesture settings control skipping interval for remote arrow buttons (for 2nd generation Siri Remote or newer). Changing system controls settings requires restart. " )
#endif
}
private func seekingDurationSetting ( _ name : String , _ value : Binding < String > ) -> some View {
HStack {
2023-05-19 07:55:04 +00:00
Text ( name . localized ( ) )
2022-12-19 12:35:37 +00:00
. frame ( minWidth : 140 , alignment : . leading )
Spacer ( )
2022-12-20 23:14:38 +00:00
HStack {
#if ! os ( tvOS )
2023-05-07 19:49:54 +00:00
Label ( " Minus " , systemImage : " minus " )
2022-12-21 20:16:47 +00:00
. imageScale ( . large )
. labelStyle ( . iconOnly )
. padding ( 7 )
. foregroundColor ( . accentColor )
2023-04-22 14:33:08 +00:00
. accessibilityAddTraits ( . isButton )
2022-12-21 20:16:47 +00:00
#if os ( iOS )
2023-05-07 19:49:54 +00:00
. frame ( minHeight : 35 )
2022-12-21 20:16:47 +00:00
. background ( RoundedRectangle ( cornerRadius : 4 ) . strokeBorder ( lineWidth : 1 ) . foregroundColor ( . accentColor ) )
#endif
. contentShape ( Rectangle ( ) )
. onTapGesture {
var intValue = Int ( value . wrappedValue ) ? ? 10
2023-05-07 19:49:54 +00:00
intValue -= 5
2022-12-21 20:16:47 +00:00
if intValue <= 0 {
intValue = 5
}
value . wrappedValue = String ( intValue )
2022-12-20 23:14:38 +00:00
}
#endif
2022-12-19 12:35:37 +00:00
2022-12-20 23:14:38 +00:00
#if os ( tvOS )
let textFieldWidth = 100.00
#else
let textFieldWidth = 30.00
#endif
TextField ( " Duration " , text : value )
. frame ( width : textFieldWidth , alignment : . trailing )
. multilineTextAlignment ( . center )
. labelsHidden ( )
#if ! os ( macOS )
. keyboardType ( . numberPad )
#endif
#if ! os ( tvOS )
2023-05-07 19:49:54 +00:00
Label ( " Plus " , systemImage : " plus " )
2022-12-21 20:16:47 +00:00
. imageScale ( . large )
. labelStyle ( . iconOnly )
. padding ( 7 )
. foregroundColor ( . accentColor )
2023-04-22 14:33:08 +00:00
. accessibilityAddTraits ( . isButton )
2022-12-21 20:16:47 +00:00
#if os ( iOS )
. background ( RoundedRectangle ( cornerRadius : 4 ) . strokeBorder ( lineWidth : 1 ) . foregroundColor ( . accentColor ) )
#endif
. contentShape ( Rectangle ( ) )
. onTapGesture {
var intValue = Int ( value . wrappedValue ) ? ? 10
2023-05-07 19:49:54 +00:00
intValue += 5
2022-12-21 20:16:47 +00:00
if intValue <= 0 {
intValue = 5
}
value . wrappedValue = String ( intValue )
2022-12-20 23:14:38 +00:00
}
#endif
}
2022-12-19 12:35:37 +00:00
}
}
@ ViewBuilder private var actionButtonToggles : some View {
2023-04-22 14:33:08 +00:00
Group {
Toggle ( " Share " , isOn : $ actionButtonShareEnabled )
Toggle ( " Add to Playlist " , isOn : $ actionButtonAddToPlaylistEnabled )
Toggle ( " Subscribe/Unsubscribe " , isOn : $ actionButtonSubscribeEnabled )
Toggle ( " Settings " , isOn : $ actionButtonSettingsEnabled )
Toggle ( " Fullscreen " , isOn : $ actionButtonFullScreenEnabled )
Toggle ( " Picture in Picture " , isOn : $ actionButtonPipEnabled )
}
Group {
#if os ( iOS )
Toggle ( " Lock orientation " , isOn : $ actionButtonLockOrientationEnabled )
#endif
Toggle ( " Restart " , isOn : $ actionButtonRestartEnabled )
Toggle ( " Play next item " , isOn : $ actionButtonAdvanceToNextItemEnabled )
Toggle ( " Music Mode " , isOn : $ actionButtonMusicModeEnabled )
Toggle ( " Hide player " , isOn : $ actionButtonHideEnabled )
Toggle ( " Close video " , isOn : $ actionButtonCloseEnabled )
}
2022-12-19 12:35:37 +00:00
}
@ ViewBuilder private var controlButtonToggles : some View {
#if os ( iOS )
Toggle ( " Lock orientation " , isOn : $ playerControlsLockOrientationEnabled )
#endif
Toggle ( " Settings " , isOn : $ playerControlsSettingsEnabled )
#if ! os ( tvOS )
Toggle ( " Close " , isOn : $ playerControlsCloseEnabled )
#endif
Toggle ( " Restart " , isOn : $ playerControlsRestartEnabled )
Toggle ( " Play next item " , isOn : $ playerControlsAdvanceToNextEnabled )
2023-04-22 14:33:08 +00:00
Toggle ( " Playback Mode " , isOn : $ playerControlsPlaybackModeEnabled )
2022-12-19 12:35:37 +00:00
#if ! os ( tvOS )
2023-04-22 14:33:08 +00:00
Toggle ( " Music Mode " , isOn : $ playerControlsMusicModeEnabled )
2022-12-19 12:35:37 +00:00
#endif
}
}
struct PlayerControlsSettings_Previews : PreviewProvider {
static var previews : some View {
VStack ( alignment : . leading ) {
PlayerControlsSettings ( )
}
. frame ( minHeight : 800 )
}
}