mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Player bar visibility modes and settings
This commit is contained in:
parent
8e5bafba58
commit
fcf527fa87
@ -574,7 +574,9 @@ final class PlayerModel: ObservableObject {
|
||||
closePiP()
|
||||
|
||||
prepareCurrentItemForHistory(finished: finished)
|
||||
currentItem = nil
|
||||
withAnimation {
|
||||
currentItem = nil
|
||||
}
|
||||
updateNowPlayingInfo()
|
||||
|
||||
backend.closeItem()
|
||||
|
@ -48,7 +48,9 @@ extension PlayerModel {
|
||||
|
||||
comments.reset()
|
||||
stream = nil
|
||||
currentItem = item
|
||||
withAnimation {
|
||||
currentItem = item
|
||||
}
|
||||
|
||||
if !time.isNil {
|
||||
currentItem.playbackTime = time
|
||||
@ -204,7 +206,9 @@ extension PlayerModel {
|
||||
let item = PlayerQueueItem(video, playbackTime: atTime)
|
||||
|
||||
if play {
|
||||
currentItem = item
|
||||
withAnimation {
|
||||
currentItem = item
|
||||
}
|
||||
videoBeingOpened = video
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ struct ChannelCell: View {
|
||||
}
|
||||
|
||||
var navigationLink: some View {
|
||||
NavigationLink(destination: ChannelVideosView(channel: channel).modifier(PlayerOverlayModifier())) {
|
||||
NavigationLink(destination: ChannelVideosView(channel: channel)) {
|
||||
labelContent
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ struct ChannelLinkView<ChannelLabel: View>: View {
|
||||
@ViewBuilder private var channelNavigationLink: some View {
|
||||
NavigationLink(destination: ChannelVideosView(channel: channel)) {
|
||||
channelLabel
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,10 @@ extension Defaults.Keys {
|
||||
static let homeHistoryItems = Key<Int>("homeHistoryItems", default: 10)
|
||||
static let favorites = Key<[FavoriteItem]>("favorites", default: [])
|
||||
|
||||
static let playerButtonSingleTapGesture = Key<PlayerTapGestureAction>("playerButtonSingleTapGesture", default: .togglePlayer)
|
||||
static let playerButtonDoubleTapGesture = Key<PlayerTapGestureAction>("playerButtonDoubleTapGesture", default: .togglePlayerVisibility)
|
||||
static let playerButtonShowsControlButtonsWhenMinimized = Key<Bool>("playerButtonShowsControlButtonsWhenMinimized", default: false)
|
||||
|
||||
#if !os(tvOS)
|
||||
#if os(macOS)
|
||||
static let accountPickerDisplaysUsernameDefault = true
|
||||
@ -363,3 +367,23 @@ enum DetailsToolbarPositionSetting: String, CaseIterable, Defaults.Serializable
|
||||
self == .center || self == .left
|
||||
}
|
||||
}
|
||||
|
||||
enum PlayerTapGestureAction: String, CaseIterable, Defaults.Serializable {
|
||||
case togglePlayerVisibility
|
||||
case togglePlayer
|
||||
case openChannel
|
||||
case nothing
|
||||
|
||||
var label: String {
|
||||
switch self {
|
||||
case .togglePlayerVisibility:
|
||||
return "Toggle size"
|
||||
case .togglePlayer:
|
||||
return "Toggle player"
|
||||
case .openChannel:
|
||||
return "Open channel"
|
||||
case .nothing:
|
||||
return "Do nothing"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -170,23 +170,20 @@ struct FavoriteItemView: View {
|
||||
}
|
||||
|
||||
@ViewBuilder var itemNavigationLinkDestination: some View {
|
||||
Group {
|
||||
switch item.section {
|
||||
case let .channel(_, id, name):
|
||||
ChannelVideosView(channel: .init(app: .invidious, id: id, name: name))
|
||||
case let .channelPlaylist(_, id, title):
|
||||
ChannelPlaylistView(playlist: .init(id: id, title: title))
|
||||
case let .playlist(_, id):
|
||||
ChannelPlaylistView(playlist: .init(id: id, title: label))
|
||||
case .subscriptions:
|
||||
SubscriptionsView()
|
||||
case .popular:
|
||||
PopularView()
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
switch item.section {
|
||||
case let .channel(_, id, name):
|
||||
ChannelVideosView(channel: .init(app: .invidious, id: id, name: name))
|
||||
case let .channelPlaylist(_, id, title):
|
||||
ChannelPlaylistView(playlist: .init(id: id, title: title))
|
||||
case let .playlist(_, id):
|
||||
ChannelPlaylistView(playlist: .init(id: id, title: label))
|
||||
case .subscriptions:
|
||||
SubscriptionsView()
|
||||
case .popular:
|
||||
PopularView()
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
.modifier(PlayerOverlayModifier())
|
||||
}
|
||||
|
||||
func itemButtonAction() {
|
||||
|
@ -1,11 +1,38 @@
|
||||
import Defaults
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct PlayerOverlayModifier: ViewModifier {
|
||||
@ObservedObject private var player = PlayerModel.shared
|
||||
@State private var expansionState = ControlsBar.ExpansionState.mini
|
||||
|
||||
@Environment(\.navigationStyle) private var navigationStyle
|
||||
|
||||
@Default(.playerButtonShowsControlButtonsWhenMinimized) private var controlsWhenMinimized
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
#if !os(tvOS)
|
||||
.overlay(ControlsBar(fullScreen: .constant(false)), alignment: .bottom)
|
||||
.overlay(overlay, alignment: .bottomTrailing)
|
||||
#endif
|
||||
}
|
||||
|
||||
@ViewBuilder var overlay: some View {
|
||||
Group {
|
||||
if player.currentItem != nil {
|
||||
ControlsBar(fullScreen: .constant(false), expansionState: $expansionState, playerBar: true)
|
||||
.offset(x: expansionState == .mini && !controlsWhenMinimized ? 10 : 0, y: 0)
|
||||
.transition(.opacity)
|
||||
}
|
||||
}
|
||||
.animation(.default, value: player.currentItem)
|
||||
}
|
||||
}
|
||||
|
||||
struct PlayerOverlayModifier_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
HStack {}
|
||||
.frame(maxWidth: .infinity, maxHeight: 100)
|
||||
.modifier(PlayerOverlayModifier())
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ struct AppSidebarNavigation: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.modifier(PlayerOverlayModifier())
|
||||
.environment(\.navigationStyle, .sidebar)
|
||||
}
|
||||
|
||||
@ -75,7 +76,7 @@ struct AppSidebarNavigation: View {
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItemGroup(placement: accountsMenuToolbarItemPlacement) {
|
||||
ToolbarItemGroup {
|
||||
AccountViewButton()
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ struct AppSidebarPlaylists: View {
|
||||
Section(header: Text("Playlists")) {
|
||||
ForEach(playlists.playlists.sorted { $0.title.lowercased() < $1.title.lowercased() }) { playlist in
|
||||
NavigationLink(tag: TabSelection.playlist(playlist.id), selection: $navigation.tabSelection) {
|
||||
LazyView(PlaylistVideosView(playlist).modifier(PlayerOverlayModifier()))
|
||||
LazyView(PlaylistVideosView(playlist))
|
||||
} label: {
|
||||
playlistLabel(playlist)
|
||||
}
|
||||
|
@ -16,17 +16,17 @@ struct AppSidebarRecents: View {
|
||||
switch recent.type {
|
||||
case .channel:
|
||||
RecentNavigationLink(recent: recent) {
|
||||
LazyView(ChannelVideosView(channel: recent.channel!).modifier(PlayerOverlayModifier()))
|
||||
LazyView(ChannelVideosView(channel: recent.channel!))
|
||||
}
|
||||
|
||||
case .playlist:
|
||||
RecentNavigationLink(recent: recent, systemImage: "list.and.film") {
|
||||
LazyView(ChannelPlaylistView(playlist: recent.playlist!).modifier(PlayerOverlayModifier()))
|
||||
LazyView(ChannelPlaylistView(playlist: recent.playlist!))
|
||||
}
|
||||
|
||||
case .query:
|
||||
RecentNavigationLink(recent: recent, systemImage: "magnifyingglass") {
|
||||
LazyView(SearchView(recent.query!).modifier(PlayerOverlayModifier()))
|
||||
LazyView(SearchView(recent.query!))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ struct AppSidebarSubscriptions: View {
|
||||
Section(header: Text("Subscriptions")) {
|
||||
ForEach(subscriptions.all) { channel in
|
||||
NavigationLink(tag: TabSelection.channel(channel.id), selection: $navigation.tabSelection) {
|
||||
LazyView(ChannelVideosView(channel: channel).modifier(PlayerOverlayModifier()))
|
||||
LazyView(ChannelVideosView(channel: channel))
|
||||
} label: {
|
||||
HStack {
|
||||
if channel.thumbnailURL != nil {
|
||||
|
@ -47,7 +47,7 @@ struct AppTabNavigation: View {
|
||||
searchNavigationView
|
||||
}
|
||||
}
|
||||
.overlay(ControlsBar(fullScreen: .constant(false)), alignment: .bottom)
|
||||
.modifier(PlayerOverlayModifier())
|
||||
}
|
||||
.onAppear {
|
||||
feed.calculateUnwatchedFeed()
|
||||
|
@ -53,7 +53,7 @@ struct Sidebar: View {
|
||||
var mainNavigationLinks: some View {
|
||||
Section(header: Text("Videos")) {
|
||||
if showHome {
|
||||
NavigationLink(destination: LazyView(HomeView().modifier(PlayerOverlayModifier())), tag: TabSelection.home, selection: $navigation.tabSelection) {
|
||||
NavigationLink(destination: LazyView(HomeView()), tag: TabSelection.home, selection: $navigation.tabSelection) {
|
||||
Label("Home", systemImage: "house")
|
||||
.accessibility(label: Text("Home"))
|
||||
}
|
||||
@ -62,7 +62,7 @@ struct Sidebar: View {
|
||||
|
||||
#if os(iOS)
|
||||
if showDocuments {
|
||||
NavigationLink(destination: LazyView(DocumentsView().modifier(PlayerOverlayModifier())), tag: TabSelection.documents, selection: $navigation.tabSelection) {
|
||||
NavigationLink(destination: LazyView(DocumentsView()), tag: TabSelection.documents, selection: $navigation.tabSelection) {
|
||||
Label("Documents", systemImage: "folder")
|
||||
.accessibility(label: Text("Documents"))
|
||||
}
|
||||
@ -74,7 +74,7 @@ struct Sidebar: View {
|
||||
if visibleSections.contains(.subscriptions),
|
||||
accounts.app.supportsSubscriptions && accounts.signedIn
|
||||
{
|
||||
NavigationLink(destination: LazyView(SubscriptionsView().modifier(PlayerOverlayModifier())), tag: TabSelection.subscriptions, selection: $navigation.tabSelection) {
|
||||
NavigationLink(destination: LazyView(SubscriptionsView()), tag: TabSelection.subscriptions, selection: $navigation.tabSelection) {
|
||||
Label("Subscriptions", systemImage: "star.circle")
|
||||
.accessibility(label: Text("Subscriptions"))
|
||||
}
|
||||
@ -88,7 +88,7 @@ struct Sidebar: View {
|
||||
}
|
||||
|
||||
if visibleSections.contains(.popular), accounts.app.supportsPopular {
|
||||
NavigationLink(destination: LazyView(PopularView().modifier(PlayerOverlayModifier())), tag: TabSelection.popular, selection: $navigation.tabSelection) {
|
||||
NavigationLink(destination: LazyView(PopularView()), tag: TabSelection.popular, selection: $navigation.tabSelection) {
|
||||
Label("Popular", systemImage: "arrow.up.right.circle")
|
||||
.accessibility(label: Text("Popular"))
|
||||
}
|
||||
@ -96,14 +96,14 @@ struct Sidebar: View {
|
||||
}
|
||||
|
||||
if visibleSections.contains(.trending) {
|
||||
NavigationLink(destination: LazyView(TrendingView().modifier(PlayerOverlayModifier())), tag: TabSelection.trending, selection: $navigation.tabSelection) {
|
||||
NavigationLink(destination: LazyView(TrendingView()), tag: TabSelection.trending, selection: $navigation.tabSelection) {
|
||||
Label("Trending", systemImage: "chart.bar")
|
||||
.accessibility(label: Text("Trending"))
|
||||
}
|
||||
.id("trending")
|
||||
}
|
||||
|
||||
NavigationLink(destination: LazyView(SearchView().modifier(PlayerOverlayModifier())), tag: TabSelection.search, selection: $navigation.tabSelection) {
|
||||
NavigationLink(destination: LazyView(SearchView()), tag: TabSelection.search, selection: $navigation.tabSelection) {
|
||||
Label("Search", systemImage: "magnifyingglass")
|
||||
.accessibility(label: Text("Search"))
|
||||
}
|
||||
@ -159,3 +159,9 @@ struct Sidebar: View {
|
||||
scrollView.scrollTo(selection.stringValue)
|
||||
}
|
||||
}
|
||||
|
||||
struct Sidebar_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Sidebar()
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ struct PlayerControls: View {
|
||||
model.presentingDetailsOverlay = true
|
||||
}
|
||||
} label: {
|
||||
ControlsBar(fullScreen: $model.presentingDetailsOverlay, presentingControls: false, detailsTogglePlayer: false, detailsToggleFullScreen: false)
|
||||
ControlsBar(fullScreen: $model.presentingDetailsOverlay, expansionState: .constant(.full), presentingControls: false, detailsTogglePlayer: false, detailsToggleFullScreen: false)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||||
.frame(maxWidth: 300, alignment: .leading)
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ struct VideoDetails: View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ControlsBar(
|
||||
fullScreen: $fullScreen,
|
||||
expansionState: .constant(.full),
|
||||
presentingControls: false,
|
||||
backgroundEnabled: false,
|
||||
borderTop: false,
|
||||
|
@ -21,6 +21,9 @@ struct BrowsingSettings: View {
|
||||
@Default(.showOpenActionsToolbarItem) private var showOpenActionsToolbarItem
|
||||
@Default(.homeHistoryItems) private var homeHistoryItems
|
||||
@Default(.visibleSections) private var visibleSections
|
||||
@Default(.playerButtonSingleTapGesture) private var playerButtonSingleTapGesture
|
||||
@Default(.playerButtonDoubleTapGesture) private var playerButtonDoubleTapGesture
|
||||
@Default(.playerButtonShowsControlButtonsWhenMinimized) private var playerButtonShowsControlButtonsWhenMinimized
|
||||
|
||||
@ObservedObject private var accounts = AccountsModel.shared
|
||||
|
||||
@ -65,6 +68,7 @@ struct BrowsingSettings: View {
|
||||
interface
|
||||
}
|
||||
#else
|
||||
playerBarSettings
|
||||
interface
|
||||
#endif
|
||||
if !accounts.isEmpty {
|
||||
@ -150,6 +154,32 @@ struct BrowsingSettings: View {
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
private var playerBarSettings: some View {
|
||||
Section(header: SettingsHeader(text: "Player Bar".localized()), footer: playerBarFooter) {
|
||||
Toggle("Always show controls buttons", isOn: $playerButtonShowsControlButtonsWhenMinimized)
|
||||
playerBarGesturePicker("Single tap gesture", selection: $playerButtonSingleTapGesture)
|
||||
playerBarGesturePicker("Double tap gesture", selection: $playerButtonDoubleTapGesture)
|
||||
}
|
||||
}
|
||||
|
||||
func playerBarGesturePicker(_ label: String, selection: Binding<PlayerTapGestureAction>) -> some View {
|
||||
Picker(label, selection: selection) {
|
||||
ForEach(PlayerTapGestureAction.allCases, id: \.rawValue) { action in
|
||||
Text(action.label).tag(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var playerBarFooter: some View {
|
||||
#if os(iOS)
|
||||
Text("Tap and hold channel thumbnail to open context menu with more actions")
|
||||
#elseif os(macOS)
|
||||
Text("Right click channel thumbnail to open context menu with more actions")
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
private var interfaceSettings: some View {
|
||||
Section(header: SettingsHeader(text: "Interface".localized())) {
|
||||
#if !os(tvOS)
|
||||
|
@ -10,7 +10,7 @@ struct SettingsView: View {
|
||||
case browsing, player, quality, history, sponsorBlock, locations, advanced, help
|
||||
}
|
||||
|
||||
@State private var selection: Tabs?
|
||||
@State private var selection: Tabs = .browsing
|
||||
#endif
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@ -224,10 +224,8 @@ struct SettingsView: View {
|
||||
#if os(macOS)
|
||||
private var windowHeight: Double {
|
||||
switch selection {
|
||||
case nil:
|
||||
return accounts.isEmpty ? 680 : 580
|
||||
case .browsing:
|
||||
return 580
|
||||
return 680
|
||||
case .player:
|
||||
return 900
|
||||
case .quality:
|
||||
|
@ -14,7 +14,7 @@ struct ChannelsView: View {
|
||||
List {
|
||||
Section(header: header) {
|
||||
ForEach(subscriptions.all) { channel in
|
||||
NavigationLink(destination: ChannelVideosView(channel: channel).modifier(PlayerOverlayModifier())) {
|
||||
NavigationLink(destination: ChannelVideosView(channel: channel)) {
|
||||
HStack {
|
||||
if let url = channel.thumbnailURLOrCached {
|
||||
ThumbnailView(url: url)
|
||||
|
@ -3,20 +3,15 @@ import SDWebImageSwiftUI
|
||||
import SwiftUI
|
||||
|
||||
struct ControlsBar: View {
|
||||
@Binding var fullScreen: Bool
|
||||
enum ExpansionState {
|
||||
case mini
|
||||
case full
|
||||
}
|
||||
|
||||
@Binding var fullScreen: Bool
|
||||
@State private var presentingShareSheet = false
|
||||
@State private var shareURL: URL?
|
||||
|
||||
@Environment(\.navigationStyle) private var navigationStyle
|
||||
|
||||
@ObservedObject private var accounts = AccountsModel.shared
|
||||
var navigation = NavigationModel.shared
|
||||
@ObservedObject private var model = PlayerModel.shared
|
||||
@ObservedObject private var playlists = PlaylistsModel.shared
|
||||
@ObservedObject private var subscriptions = SubscribedChannelsModel.shared
|
||||
|
||||
@ObservedObject private var controls = PlayerControlsModel.shared
|
||||
@Binding var expansionState: ExpansionState
|
||||
|
||||
var presentingControls = true
|
||||
var backgroundEnabled = true
|
||||
@ -24,34 +19,57 @@ struct ControlsBar: View {
|
||||
var borderBottom = true
|
||||
var detailsTogglePlayer = true
|
||||
var detailsToggleFullScreen = false
|
||||
var playerBar = false
|
||||
var titleLineLimit = 2
|
||||
|
||||
@ObservedObject private var accounts = AccountsModel.shared
|
||||
@ObservedObject private var model = PlayerModel.shared
|
||||
@ObservedObject private var playlists = PlaylistsModel.shared
|
||||
@ObservedObject private var subscriptions = SubscribedChannelsModel.shared
|
||||
@ObservedObject private var controls = PlayerControlsModel.shared
|
||||
|
||||
@Environment(\.navigationStyle) private var navigationStyle
|
||||
|
||||
private let navigation = NavigationModel.shared
|
||||
private let controlsOverlayModel = ControlOverlaysModel.shared
|
||||
|
||||
@Default(.playerButtonShowsControlButtonsWhenMinimized) private var controlsWhenMinimized
|
||||
@Default(.playerButtonSingleTapGesture) private var playerButtonSingleTapGesture
|
||||
@Default(.playerButtonDoubleTapGesture) private var playerButtonDoubleTapGesture
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
detailsButton
|
||||
|
||||
if presentingControls {
|
||||
if presentingControls, expansionState == .full || (controlsWhenMinimized && model.currentItem != nil) {
|
||||
if expansionState == .full {
|
||||
Spacer()
|
||||
}
|
||||
controlsView
|
||||
.frame(maxWidth: 120)
|
||||
}
|
||||
}
|
||||
|
||||
.buttonStyle(.plain)
|
||||
.labelStyle(.iconOnly)
|
||||
.padding(.horizontal)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: barHeight)
|
||||
.borderTop(height: borderTop ? 0.5 : 0, color: Color("ControlsBorderColor"))
|
||||
.borderBottom(height: borderBottom ? 0.5 : 0, color: Color("ControlsBorderColor"))
|
||||
.modifier(ControlBackgroundModifier(enabled: backgroundEnabled, edgesIgnoringSafeArea: .bottom))
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 2)
|
||||
.frame(maxHeight: barHeight)
|
||||
.padding(.trailing, expansionState == .mini && !controlsWhenMinimized ? 8 : 0)
|
||||
.modifier(ControlBackgroundModifier(enabled: backgroundEnabled))
|
||||
.clipShape(RoundedRectangle(cornerRadius: expansionState == .full || !playerBar ? 0 : 6))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: expansionState == .full || !playerBar ? 0 : 6)
|
||||
.stroke(Color("ControlsBorderColor"), lineWidth: playerBar ? 0 : 0.5)
|
||||
)
|
||||
#if os(iOS)
|
||||
.background(
|
||||
EmptyView().sheet(isPresented: $presentingShareSheet) {
|
||||
if let shareURL {
|
||||
ShareSheet(activityItems: [shareURL])
|
||||
}
|
||||
.background(
|
||||
EmptyView().sheet(isPresented: $presentingShareSheet) {
|
||||
if let shareURL {
|
||||
ShareSheet(activityItems: [shareURL])
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -136,139 +154,187 @@ struct ControlsBar: View {
|
||||
var details: some View {
|
||||
HStack {
|
||||
HStack(spacing: 8) {
|
||||
Button {
|
||||
if let video = model.currentVideo, !video.isLocal {
|
||||
navigation.openChannel(
|
||||
video.channel,
|
||||
navigationStyle: navigationStyle
|
||||
if !playerBar {
|
||||
Button {
|
||||
if let video = model.currentVideo, !video.isLocal {
|
||||
navigation.openChannel(
|
||||
video.channel,
|
||||
navigationStyle: navigationStyle
|
||||
)
|
||||
}
|
||||
} label: {
|
||||
ChannelAvatarView(
|
||||
channel: model.currentVideo?.channel,
|
||||
video: model.currentVideo
|
||||
)
|
||||
.frame(width: barHeight - 10, height: barHeight - 10)
|
||||
}
|
||||
} label: {
|
||||
.contextMenu { contextMenu }
|
||||
.zIndex(3)
|
||||
} else {
|
||||
ChannelAvatarView(
|
||||
channel: model.currentVideo?.channel,
|
||||
video: model.currentVideo
|
||||
)
|
||||
#if !os(tvOS)
|
||||
.highPriorityGesture(playerButtonDoubleTapGesture != .nothing ? doubleTapGesture : nil)
|
||||
.gesture(playerButtonSingleTapGesture != .nothing ? singleTapGesture : nil)
|
||||
#endif
|
||||
.frame(width: barHeight - 10, height: barHeight - 10)
|
||||
.contextMenu { contextMenu }
|
||||
}
|
||||
.contextMenu {
|
||||
if let video = model.currentVideo {
|
||||
Group {
|
||||
Section {
|
||||
if accounts.app.supportsUserPlaylists && accounts.signedIn, !video.isLocal {
|
||||
Section {
|
||||
Button {
|
||||
navigation.presentAddToPlaylist(video)
|
||||
} label: {
|
||||
Label("Add to Playlist...", systemImage: "text.badge.plus")
|
||||
}
|
||||
|
||||
if let playlist = playlists.lastUsed, let video = model.currentVideo {
|
||||
Button {
|
||||
playlists.addVideo(playlistID: playlist.id, videoID: video.videoID)
|
||||
} label: {
|
||||
Label("Add to \(playlist.title)", systemImage: "text.badge.star")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if expansionState == .full {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
let notPlaying = "Not Playing".localized()
|
||||
Text(model.currentVideo?.displayTitle ?? notPlaying)
|
||||
.font(.system(size: 14))
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(model.currentVideo.isNil ? .secondary : .accentColor)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.lineLimit(titleLineLimit)
|
||||
.multilineTextAlignment(.leading)
|
||||
|
||||
#if !os(tvOS)
|
||||
ShareButton(contentItem: .init(video: model.currentVideo))
|
||||
#endif
|
||||
if let video = model.currentVideo, !video.localStreamIsFile {
|
||||
HStack(spacing: 2) {
|
||||
Text(video.displayAuthor)
|
||||
.font(.system(size: 12))
|
||||
|
||||
Section {
|
||||
if !video.isLocal {
|
||||
Button {
|
||||
navigation.openChannel(
|
||||
video.channel,
|
||||
navigationStyle: navigationStyle
|
||||
)
|
||||
} label: {
|
||||
Label("\(video.author) Channel", systemImage: "rectangle.stack.fill.badge.person.crop")
|
||||
}
|
||||
if !presentingControls && !video.isLocal {
|
||||
HStack(spacing: 2) {
|
||||
Image(systemName: "person.2.fill")
|
||||
|
||||
if accounts.app.supportsSubscriptions, accounts.signedIn {
|
||||
if subscriptions.isSubscribing(video.channel.id) {
|
||||
Button {
|
||||
#if os(tvOS)
|
||||
subscriptions.unsubscribe(video.channel.id)
|
||||
#else
|
||||
navigation.presentUnsubscribeAlert(video.channel, subscriptions: subscriptions)
|
||||
#endif
|
||||
} label: {
|
||||
Label("Unsubscribe", systemImage: "star.circle")
|
||||
}
|
||||
if let channel = model.currentVideo?.channel {
|
||||
if let subscriptions = channel.subscriptionsString {
|
||||
Text(subscriptions)
|
||||
} else {
|
||||
Button {
|
||||
subscriptions.subscribe(video.channel.id) {
|
||||
navigation.sidebarSectionChanged.toggle()
|
||||
}
|
||||
} label: {
|
||||
Label("Subscribe", systemImage: "star.circle")
|
||||
}
|
||||
Text("1234").redacted(reason: .placeholder)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.leading, 4)
|
||||
.font(.system(size: 9))
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
model.closeCurrentItem()
|
||||
} label: {
|
||||
Label("Close Video", systemImage: "xmark")
|
||||
}
|
||||
.lineLimit(1)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.labelStyle(.automatic)
|
||||
}
|
||||
}
|
||||
.zIndex(0)
|
||||
.transition(.opacity)
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
let notPlaying = "Not Playing".localized()
|
||||
Text(model.currentVideo?.displayTitle ?? notPlaying)
|
||||
.font(.system(size: 14))
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(model.currentVideo.isNil ? .secondary : .accentColor)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.lineLimit(titleLineLimit)
|
||||
.multilineTextAlignment(.leading)
|
||||
|
||||
if let video = model.currentVideo, !video.localStreamIsFile {
|
||||
HStack(spacing: 2) {
|
||||
Text(video.displayAuthor)
|
||||
.font(.system(size: 12))
|
||||
|
||||
if !presentingControls && !video.isLocal {
|
||||
HStack(spacing: 2) {
|
||||
Image(systemName: "person.2.fill")
|
||||
|
||||
if let channel = model.currentVideo?.channel {
|
||||
if let subscriptions = channel.subscriptionsString {
|
||||
Text(subscriptions)
|
||||
} else {
|
||||
Text("1234").redacted(reason: .placeholder)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.leading, 4)
|
||||
.font(.system(size: 9))
|
||||
}
|
||||
}
|
||||
.lineLimit(1)
|
||||
.foregroundColor(.secondary)
|
||||
if !playerBar {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(.vertical)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
#if !os(tvOS)
|
||||
|
||||
var singleTapGesture: some Gesture {
|
||||
TapGesture(count: 1).onEnded { gestureAction(playerButtonSingleTapGesture) }
|
||||
}
|
||||
|
||||
var doubleTapGesture: some Gesture {
|
||||
TapGesture(count: 2).onEnded { gestureAction(playerButtonDoubleTapGesture) }
|
||||
}
|
||||
|
||||
func gestureAction(_ action: PlayerTapGestureAction) {
|
||||
switch action {
|
||||
case .togglePlayer:
|
||||
model.togglePlayer()
|
||||
case .openChannel:
|
||||
guard let channel = model.currentVideo?.channel else { return }
|
||||
navigation.openChannel(channel, navigationStyle: navigationStyle)
|
||||
case .togglePlayerVisibility:
|
||||
withAnimation(.spring(response: 0.25)) {
|
||||
expansionState = expansionState == .full ? .mini : .full
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ViewBuilder var contextMenu: some View {
|
||||
if let video = model.currentVideo {
|
||||
Group {
|
||||
Section {
|
||||
if accounts.app.supportsUserPlaylists && accounts.signedIn, !video.isLocal {
|
||||
Section {
|
||||
Button {
|
||||
navigation.presentAddToPlaylist(video)
|
||||
} label: {
|
||||
Label("Add to Playlist...", systemImage: "text.badge.plus")
|
||||
}
|
||||
|
||||
if let playlist = playlists.lastUsed, let video = model.currentVideo {
|
||||
Button {
|
||||
playlists.addVideo(playlistID: playlist.id, videoID: video.videoID)
|
||||
} label: {
|
||||
Label("Add to \(playlist.title)", systemImage: "text.badge.star")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
ShareButton(contentItem: .init(video: model.currentVideo))
|
||||
#endif
|
||||
|
||||
Section {
|
||||
if !video.isLocal {
|
||||
Button {
|
||||
navigation.openChannel(
|
||||
video.channel,
|
||||
navigationStyle: navigationStyle
|
||||
)
|
||||
} label: {
|
||||
Label("\(video.author) Channel", systemImage: "rectangle.stack.fill.badge.person.crop")
|
||||
}
|
||||
|
||||
if accounts.app.supportsSubscriptions, accounts.signedIn {
|
||||
if subscriptions.isSubscribing(video.channel.id) {
|
||||
Button {
|
||||
#if os(tvOS)
|
||||
subscriptions.unsubscribe(video.channel.id)
|
||||
#else
|
||||
navigation.presentUnsubscribeAlert(video.channel, subscriptions: subscriptions)
|
||||
#endif
|
||||
} label: {
|
||||
Label("Unsubscribe", systemImage: "star.circle")
|
||||
}
|
||||
} else {
|
||||
Button {
|
||||
subscriptions.subscribe(video.channel.id) {
|
||||
navigation.sidebarSectionChanged.toggle()
|
||||
}
|
||||
} label: {
|
||||
Label("Subscribe", systemImage: "star.circle")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
model.closeCurrentItem()
|
||||
} label: {
|
||||
Label("Close Video", systemImage: "xmark")
|
||||
}
|
||||
}
|
||||
.labelStyle(.automatic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ControlsBar_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ControlsBar(fullScreen: .constant(false))
|
||||
ControlsBar(fullScreen: .constant(false), expansionState: .constant(.full))
|
||||
.injectFixtureEnvironmentObjects()
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
#if os(iOS)
|
||||
UIViewController.swizzleHomeIndicatorProperty()
|
||||
|
||||
UITabBar.appearance().shadowImage = UIImage()
|
||||
UITabBar.appearance().backgroundImage = UIImage()
|
||||
UITabBar.appearance().isTranslucent = true
|
||||
UITabBar.appearance().backgroundColor = .clear
|
||||
|
||||
OrientationTracker.shared.startDeviceOrientationTracking()
|
||||
#endif
|
||||
return true
|
||||
|
Loading…
Reference in New Issue
Block a user