yattee/Shared/Views/ControlsBar.swift

318 lines
13 KiB
Swift
Raw Normal View History

import Defaults
import SDWebImageSwiftUI
import SwiftUI
struct ControlsBar: View {
2022-06-30 08:05:32 +00:00
@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 = SubscriptionsModel.shared
2022-06-24 23:21:05 +00:00
2022-09-02 13:11:20 +00:00
@ObservedObject private var controls = PlayerControlsModel.shared
2022-06-24 23:39:29 +00:00
var presentingControls = true
var backgroundEnabled = true
var borderTop = true
var borderBottom = true
var detailsTogglePlayer = true
2022-06-25 16:33:35 +00:00
var detailsToggleFullScreen = false
2022-07-10 17:51:46 +00:00
var titleLineLimit = 2
2022-06-25 16:33:35 +00:00
2022-09-01 23:05:31 +00:00
private let controlsOverlayModel = ControlOverlaysModel.shared
var body: some View {
2022-06-24 23:39:29 +00:00
HStack(spacing: 0) {
detailsButton
if presentingControls {
2022-09-02 13:11:20 +00:00
controlsView
2022-06-24 23:39:29 +00:00
.frame(maxWidth: 120)
}
}
.buttonStyle(.plain)
.labelStyle(.iconOnly)
.padding(.horizontal)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: barHeight)
2022-11-18 21:27:13 +00:00
.borderTop(height: borderTop ? 0.5 : 0, color: Color("ControlsBorderColor"))
.borderBottom(height: borderBottom ? 0.5 : 0, color: Color("ControlsBorderColor"))
2022-06-24 23:39:29 +00:00
.modifier(ControlBackgroundModifier(enabled: backgroundEnabled, edgesIgnoringSafeArea: .bottom))
2022-06-24 23:21:05 +00:00
#if os(iOS)
.background(
EmptyView().sheet(isPresented: $presentingShareSheet) {
2022-09-28 14:27:01 +00:00
if let shareURL {
2022-06-24 23:21:05 +00:00
ShareSheet(activityItems: [shareURL])
}
}
)
#endif
}
2022-06-24 23:39:29 +00:00
@ViewBuilder var detailsButton: some View {
if detailsTogglePlayer {
Button {
model.togglePlayer()
} label: {
details
.contentShape(Rectangle())
}
2022-06-25 16:33:35 +00:00
} else if detailsToggleFullScreen {
Button {
2022-11-17 21:47:45 +00:00
controlsOverlayModel.hide()
2022-09-02 13:11:20 +00:00
controls.presentingControls = false
2022-06-25 16:33:35 +00:00
withAnimation {
fullScreen.toggle()
}
} label: {
details
.contentShape(Rectangle())
}
2022-07-10 17:51:46 +00:00
#if !os(tvOS)
2022-06-25 16:33:35 +00:00
.keyboardShortcut("t")
2022-07-10 17:51:46 +00:00
#endif
2022-06-24 23:39:29 +00:00
} else {
details
}
}
2022-09-02 13:11:20 +00:00
var controlsView: some View {
HStack(spacing: 4) {
Group {
2022-09-02 13:11:20 +00:00
if controls.isPlaying {
Button(action: {
model.pause()
}) {
Label("Pause", systemImage: "pause.fill")
2022-06-24 23:39:29 +00:00
.padding(.vertical, 10)
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
}
} else {
Button(action: {
model.play()
}) {
Label("Play", systemImage: "play.fill")
2022-06-24 23:39:29 +00:00
.padding(.vertical, 10)
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
}
}
}
2022-09-02 13:11:20 +00:00
.disabled(controls.isLoadingVideo || model.currentItem.isNil)
Button(action: { model.advanceToNextItem() }) {
Label("Next", systemImage: "forward.fill")
2022-06-24 23:39:29 +00:00
.padding(.vertical, 10)
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
}
2022-07-10 22:24:56 +00:00
.disabled(!model.isAdvanceToNextItemAvailable)
2022-07-01 22:52:27 +00:00
Button {
model.closeCurrentItem()
} label: {
Label("Close Video", systemImage: "xmark")
.padding(.vertical, 10)
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
}
.disabled(model.currentItem.isNil)
}
2022-07-01 22:52:27 +00:00
.imageScale(.small)
.font(.system(size: 24))
}
var barHeight: Double {
2022-06-24 23:39:29 +00:00
55
}
var details: some View {
HStack {
HStack(spacing: 8) {
2022-11-13 17:52:15 +00:00
Button {
if let video = model.currentVideo, !video.isLocal {
navigation.openChannel(
2022-11-13 17:52:15 +00:00
video.channel,
navigationStyle: navigationStyle
)
}
} label: {
ZStack(alignment: .bottomTrailing) {
authorAvatar
if accounts.app.supportsSubscriptions,
accounts.signedIn,
let video = model.currentVideo,
subscriptions.isSubscribing(video.channel.id)
{
Image(systemName: "star.circle.fill")
#if !os(tvOS)
.background(Color.background)
#endif
.clipShape(Circle())
.foregroundColor(.secondary)
}
2022-06-24 23:21:05 +00:00
}
}
.contextMenu {
if let video = model.currentVideo {
Group {
Section {
2022-11-10 17:11:28 +00:00
if accounts.app.supportsUserPlaylists && accounts.signedIn, !video.isLocal {
2022-06-24 23:21:05 +00:00
Section {
Button {
navigation.presentAddToPlaylist(video)
} label: {
Label("Add to Playlist...", systemImage: "text.badge.plus")
}
2022-06-24 23:21:05 +00:00
if let playlist = playlists.lastUsed, let video = model.currentVideo {
Button {
playlists.addVideo(playlistID: playlist.id, videoID: video.videoID)
} label: {
2022-06-24 23:21:05 +00:00
Label("Add to \(playlist.title)", systemImage: "text.badge.star")
}
}
}
2022-06-24 23:21:05 +00:00
}
2022-07-10 17:51:46 +00:00
#if !os(tvOS)
ShareButton(contentItem: .init(video: model.currentVideo))
#endif
2022-06-24 23:39:29 +00:00
2022-06-24 23:21:05 +00:00
Section {
2022-11-10 17:11:28 +00:00
if !video.isLocal {
Button {
navigation.openChannel(
2022-11-10 17:11:28 +00:00
video.channel,
navigationStyle: navigationStyle
)
} label: {
Label("\(video.author) Channel", systemImage: "rectangle.stack.fill.badge.person.crop")
}
2022-11-10 17:11:28 +00:00
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: "xmark.circle")
}
} else {
Button {
subscriptions.subscribe(video.channel.id) {
navigation.sidebarSectionChanged.toggle()
}
} label: {
Label("Subscribe", systemImage: "star.circle")
2022-06-24 23:21:05 +00:00
}
}
}
}
}
}
2022-06-24 23:39:29 +00:00
Button {
model.closeCurrentItem()
} label: {
Label("Close Video", systemImage: "xmark")
}
}
2022-06-24 23:21:05 +00:00
.labelStyle(.automatic)
}
2022-06-24 23:21:05 +00:00
}
2022-07-10 17:51:46 +00:00
VStack(alignment: .leading, spacing: 0) {
2022-09-04 15:28:30 +00:00
let notPlaying = "Not Playing".localized()
2022-11-10 17:11:28 +00:00
Text(model.currentVideo?.displayTitle ?? notPlaying)
.font(.system(size: 14))
.fontWeight(.semibold)
.foregroundColor(model.currentVideo.isNil ? .secondary : .accentColor)
2022-07-10 17:51:46 +00:00
.fixedSize(horizontal: false, vertical: true)
.lineLimit(titleLineLimit)
.multilineTextAlignment(.leading)
2022-11-10 17:11:28 +00:00
if let video = model.currentVideo, !video.localStreamIsFile {
2022-07-01 22:52:27 +00:00
HStack(spacing: 2) {
2022-11-10 17:11:28 +00:00
Text(video.displayAuthor)
2022-07-01 22:52:27 +00:00
.font(.system(size: 12))
2022-06-25 16:54:05 +00:00
2022-11-10 17:11:28 +00:00
if !presentingControls && !video.isLocal {
2022-07-01 22:52:27 +00:00
HStack(spacing: 2) {
Image(systemName: "person.2.fill")
2022-06-25 16:54:05 +00:00
2022-07-11 17:44:25 +00:00
if let channel = model.currentVideo?.channel {
if let subscriptions = channel.subscriptionsString {
Text(subscriptions)
} else {
Text("1234").redacted(reason: .placeholder)
}
}
2022-07-01 22:52:27 +00:00
}
.padding(.leading, 4)
.font(.system(size: 9))
2022-06-25 16:54:05 +00:00
}
}
2022-07-01 22:52:27 +00:00
.lineLimit(1)
.foregroundColor(.secondary)
2022-06-25 16:54:05 +00:00
}
}
}
.buttonStyle(.plain)
.padding(.vertical)
Spacer()
}
}
private var authorAvatar: some View {
2022-06-24 23:39:29 +00:00
Group {
2022-11-10 17:11:28 +00:00
if let url = model.currentItem?.video?.channel.thumbnailURL {
2022-11-10 18:11:19 +00:00
WebImage(url: url, options: [.lowPriority])
2022-09-11 19:33:08 +00:00
.resizable()
.placeholder {
Rectangle().fill(Color("PlaceholderColor"))
}
2022-09-11 19:33:08 +00:00
.retryOnAppear(true)
.indicator(.activity)
} else {
2022-06-24 23:39:29 +00:00
ZStack {
2022-06-25 16:54:05 +00:00
Color(white: 0.6)
2022-06-24 23:39:29 +00:00
.opacity(0.5)
2022-11-10 17:11:28 +00:00
Group {
if let video = model.currentItem?.video, video.isLocal {
2022-11-12 23:01:04 +00:00
Image(systemName: video.localStreamImageSystemName)
2022-11-10 17:11:28 +00:00
} else {
Image(systemName: "play.rectangle")
}
}
.foregroundColor(.accentColor)
.font(.system(size: 20))
.contentShape(Rectangle())
2022-06-24 23:39:29 +00:00
}
}
}
.frame(width: 44, height: 44, alignment: .leading)
.clipShape(Circle())
}
}
struct ControlsBar_Previews: PreviewProvider {
static var previews: some View {
2022-06-25 16:33:35 +00:00
ControlsBar(fullScreen: .constant(false))
.injectFixtureEnvironmentObjects()
}
}