mirror of
				https://github.com/yattee/yattee.git
				synced 2025-10-31 20:52:04 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			227 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			227 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
| import Defaults
 | |
| import SwiftUI
 | |
| 
 | |
| struct VideoActions: View {
 | |
|     enum Action: String, CaseIterable {
 | |
|         case share
 | |
|         case addToPlaylist
 | |
|         case subscribe
 | |
|         case fullScreen
 | |
|         case pip
 | |
|         #if os(iOS)
 | |
|             case lockOrientation
 | |
|         #endif
 | |
|         case restart
 | |
|         case advanceToNextItem
 | |
|         case musicMode
 | |
|         case settings
 | |
|         case hide
 | |
|         case close
 | |
|     }
 | |
| 
 | |
|     @ObservedObject private var accounts = AccountsModel.shared
 | |
|     var navigation = NavigationModel.shared
 | |
|     @ObservedObject private var subscriptions = SubscribedChannelsModel.shared
 | |
|     @ObservedObject private var player = PlayerModel.shared
 | |
| 
 | |
|     var video: Video?
 | |
| 
 | |
|     @Default(.playerActionsButtonLabelStyle) private var playerActionsButtonLabelStyle
 | |
| 
 | |
|     @Default(.actionButtonShareEnabled) private var actionButtonShareEnabled
 | |
|     @Default(.actionButtonAddToPlaylistEnabled) private var actionButtonAddToPlaylistEnabled
 | |
|     @Default(.actionButtonSubscribeEnabled) private var actionButtonSubscribeEnabled
 | |
|     @Default(.actionButtonSettingsEnabled) private var actionButtonSettingsEnabled
 | |
|     @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
 | |
|     @Default(.actionButtonHideEnabled) private var actionButtonHideEnabled
 | |
|     @Default(.actionButtonCloseEnabled) private var actionButtonCloseEnabled
 | |
| 
 | |
|     var body: some View {
 | |
|         HStack(spacing: 6) {
 | |
|             ForEach(Action.allCases, id: \.self) { action in
 | |
|                 actionBody(action)
 | |
|                     .frame(maxWidth: .infinity)
 | |
|             }
 | |
|         }
 | |
|         .multilineTextAlignment(.center)
 | |
|         .frame(maxWidth: .infinity)
 | |
|         .frame(height: 50)
 | |
|         .borderBottom(height: 0.5, color: Color("ControlsBorderColor"))
 | |
|         .foregroundColor(.accentColor)
 | |
|     }
 | |
| 
 | |
|     func isVisible(_ action: Action) -> Bool {
 | |
|         switch action {
 | |
|         case .share:
 | |
|             return actionButtonShareEnabled
 | |
|         case .addToPlaylist:
 | |
|             return actionButtonAddToPlaylistEnabled
 | |
|         case .subscribe:
 | |
|             return actionButtonSubscribeEnabled
 | |
|         case .settings:
 | |
|             return actionButtonSettingsEnabled
 | |
|         case .fullScreen:
 | |
|             return actionButtonFullScreenEnabled
 | |
|         case .pip:
 | |
|             return actionButtonPipEnabled
 | |
|         #if os(iOS)
 | |
|             case .lockOrientation:
 | |
|                 return actionButtonLockOrientationEnabled
 | |
|         #endif
 | |
|         case .restart:
 | |
|             return actionButtonRestartEnabled
 | |
|         case .advanceToNextItem:
 | |
|             return actionButtonAdvanceToNextItemEnabled
 | |
|         case .musicMode:
 | |
|             return actionButtonMusicModeEnabled
 | |
|         case .hide:
 | |
|             return actionButtonHideEnabled
 | |
|         case .close:
 | |
|             return actionButtonCloseEnabled
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     func isActionable(_ action: Action) -> Bool {
 | |
|         switch action {
 | |
|         case .share:
 | |
|             return video?.isShareable ?? false
 | |
|         case .addToPlaylist:
 | |
|             return !(video?.isLocal ?? true) && accounts.signedIn
 | |
|         case .subscribe:
 | |
|             return !(video?.isLocal ?? true) && accounts.signedIn && accounts.app.supportsSubscriptions
 | |
|         case .settings:
 | |
|             return video != nil
 | |
|         case .fullScreen:
 | |
|             return video != nil
 | |
|         case .pip:
 | |
|             return video != nil
 | |
|         case .advanceToNextItem:
 | |
|             return player.isAdvanceToNextItemAvailable
 | |
|         case .restart:
 | |
|             return video != nil
 | |
|         case .musicMode:
 | |
|             return video != nil
 | |
|         default:
 | |
|             return true
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @ViewBuilder func actionBody(_ action: Action) -> some View {
 | |
|         if isVisible(action) {
 | |
|             Group {
 | |
|                 switch action {
 | |
|                 case .share:
 | |
|                     #if os(tvOS)
 | |
|                         EmptyView()
 | |
|                     #else
 | |
|                         ShareButton(contentItem: .init(video: video)) {
 | |
|                             actionButton("Share", systemImage: "square.and.arrow.up")
 | |
|                         }
 | |
|                     #endif
 | |
|                 case .addToPlaylist:
 | |
|                     actionButton("Add", systemImage: "text.badge.plus") {
 | |
|                         guard let video else { return }
 | |
|                         navigation.presentAddToPlaylist(video)
 | |
|                     }
 | |
|                 case .subscribe:
 | |
|                     if let channel = video?.channel,
 | |
|                        subscriptions.isSubscribing(channel.id)
 | |
|                     {
 | |
|                         actionButton("Unsubscribe", systemImage: "xmark.circle") {
 | |
|                             #if os(tvOS)
 | |
|                                 subscriptions.unsubscribe(channel.id)
 | |
|                             #else
 | |
|                                 navigation.presentUnsubscribeAlert(channel, subscriptions: subscriptions)
 | |
|                             #endif
 | |
|                         }
 | |
|                     } else {
 | |
|                         actionButton("Subscribe", systemImage: "star.circle") {
 | |
|                             guard let video else { return }
 | |
| 
 | |
|                             subscriptions.subscribe(video.channel.id) {
 | |
|                                 navigation.sidebarSectionChanged.toggle()
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 case .fullScreen:
 | |
|                     actionButton("Fullscreen", systemImage: player.fullscreenImage, action: player.toggleFullScreenAction)
 | |
|                 case .pip:
 | |
|                     actionButton("PiP", systemImage: player.pipImage, action: player.togglePiPAction)
 | |
|                 #if os(iOS)
 | |
|                     case .lockOrientation:
 | |
|                         actionButton("Lock", systemImage: player.lockOrientationImage, active: player.lockedOrientation != nil, action: player.lockOrientationAction)
 | |
|                 #endif
 | |
|                 case .restart:
 | |
|                     actionButton("Replay", systemImage: "backward.end.fill", action: player.replayAction)
 | |
|                 case .advanceToNextItem:
 | |
|                     actionButton("Next", systemImage: "forward.fill") {
 | |
|                         player.advanceToNextItem()
 | |
|                     }
 | |
|                 case .musicMode:
 | |
|                     actionButton("Music", systemImage: "music.note", active: player.musicMode, action: player.toggleMusicMode)
 | |
| 
 | |
|                 case .settings:
 | |
|                     actionButton("Settings", systemImage: "gear") {
 | |
|                         withAnimation(ControlOverlaysModel.animation) {
 | |
|                             #if os(tvOS)
 | |
|                                 ControlOverlaysModel.shared.show()
 | |
|                             #else
 | |
|                                 navigation.presentingPlaybackSettings = true
 | |
|                             #endif
 | |
|                         }
 | |
|                     }
 | |
|                 case .hide:
 | |
|                     actionButton("Hide", systemImage: "chevron.down") {
 | |
|                         player.hide(animate: true)
 | |
|                     }
 | |
| 
 | |
|                 case .close:
 | |
|                     actionButton("Close", systemImage: "xmark") {
 | |
|                         player.closeCurrentItem()
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             .disabled(!isActionable(action))
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     func actionButton(
 | |
|         _ name: String,
 | |
|         systemImage: String,
 | |
|         active: Bool = false,
 | |
|         action: @escaping () -> Void = {}
 | |
|     ) -> some View {
 | |
|         Button(action: action) {
 | |
|             VStack(spacing: 3) {
 | |
|                 Image(systemName: systemImage)
 | |
|                     .frame(width: 20, height: 20)
 | |
|                     .foregroundColor(active ? Color("AppRedColor") : .accentColor)
 | |
|                 if playerActionsButtonLabelStyle.text {
 | |
|                     Text(name.localized())
 | |
|                         .foregroundColor(active ? Color("AppRedColor") : .secondary)
 | |
|                         .font(.caption2)
 | |
|                         .allowsTightening(true)
 | |
|                         .lineLimit(1)
 | |
|                 }
 | |
|             }
 | |
|             .padding(.horizontal, playerActionsButtonLabelStyle.text ? 6 : 12)
 | |
|             .padding(.vertical, playerActionsButtonLabelStyle.text ? 5 : 10)
 | |
|             .contentShape(Rectangle())
 | |
|         }
 | |
|         .buttonStyle(.plain)
 | |
|         .accessibilityLabel(Text(name))
 | |
|     }
 | |
| }
 | |
| 
 | |
| struct VideoActions_Previews: PreviewProvider {
 | |
|     static var previews: some View {
 | |
|         VideoActions()
 | |
|             .injectFixtureEnvironmentObjects()
 | |
|     }
 | |
| }
 | 
