From 28709a2c80d9fa255480f4f95e3a01d11574f03a Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Sat, 23 Oct 2021 12:13:05 +0200 Subject: [PATCH] Improve video banner and playback queue view --- Model/PlaylistsModel.swift | 6 +- Pearvidious.xcodeproj/project.pbxproj | 2 - Shared/Player/PlayerQueueView.swift | 44 ++++++----- Shared/Player/PlayerViewController.swift | 9 +-- Shared/Player/VideoDetails.swift | 18 +++-- Shared/Playlists/PlaylistsView.swift | 10 ++- Shared/Videos/VideoBanner.swift | 42 ++++++++--- tvOS/NowPlayingView.swift | 96 +++++++++++------------- 8 files changed, 124 insertions(+), 103 deletions(-) diff --git a/Model/PlaylistsModel.swift b/Model/PlaylistsModel.swift index eb994312..690de9bc 100644 --- a/Model/PlaylistsModel.swift +++ b/Model/PlaylistsModel.swift @@ -26,7 +26,7 @@ final class PlaylistsModel: ObservableObject { } func load(force: Bool = false, onSuccess: @escaping () -> Void = {}) { - let request = force ? resource.load() : resource.loadIfNeeded() + let request = force ? resource?.load() : resource?.loadIfNeeded() request? .onSuccess { resource in @@ -66,8 +66,8 @@ final class PlaylistsModel: ObservableObject { selectedPlaylistID = id ?? "" } - private var resource: Resource { - accounts.api.playlists! + private var resource: Resource? { + accounts.api.playlists } private var selectedPlaylist: Playlist? { diff --git a/Pearvidious.xcodeproj/project.pbxproj b/Pearvidious.xcodeproj/project.pbxproj index 6162b951..5757269c 100644 --- a/Pearvidious.xcodeproj/project.pbxproj +++ b/Pearvidious.xcodeproj/project.pbxproj @@ -292,7 +292,6 @@ 37CC3F47270CE30600608308 /* PlayerQueueItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC3F44270CE30600608308 /* PlayerQueueItem.swift */; }; 37CC3F4C270CFE1700608308 /* PlayerQueueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC3F4B270CFE1700608308 /* PlayerQueueView.swift */; }; 37CC3F4D270CFE1700608308 /* PlayerQueueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC3F4B270CFE1700608308 /* PlayerQueueView.swift */; }; - 37CC3F4E270CFE1700608308 /* PlayerQueueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC3F4B270CFE1700608308 /* PlayerQueueView.swift */; }; 37CC3F50270D010D00608308 /* VideoBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC3F4F270D010D00608308 /* VideoBanner.swift */; }; 37CC3F51270D010D00608308 /* VideoBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC3F4F270D010D00608308 /* VideoBanner.swift */; }; 37CC3F52270D010D00608308 /* VideoBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC3F4F270D010D00608308 /* VideoBanner.swift */; }; @@ -1714,7 +1713,6 @@ 37AAF2A226741C97007FC770 /* SubscriptionsView.swift in Sources */, 37484C1B26FC837400287258 /* PlaybackSettingsView.swift in Sources */, 372915E82687E3B900F5A35B /* Defaults.swift in Sources */, - 37CC3F4E270CFE1700608308 /* PlayerQueueView.swift in Sources */, 37BAB54C269B39FD00E75ED1 /* TVNavigationView.swift in Sources */, 3797758D2689345500DD52A8 /* Store.swift in Sources */, 37484C2F26FC844700287258 /* AccountsSettingsView.swift in Sources */, diff --git a/Shared/Player/PlayerQueueView.swift b/Shared/Player/PlayerQueueView.swift index caadc18f..5eee0a02 100644 --- a/Shared/Player/PlayerQueueView.swift +++ b/Shared/Player/PlayerQueueView.swift @@ -8,14 +8,21 @@ struct PlayerQueueView: View { var body: some View { List { - playingNext - playedPreviously + Group { + playingNext + playedPreviously + } + .padding(.vertical, 5) + .listRowInsets(EdgeInsets()) + #if os(iOS) + .padding(.horizontal, 10) + #endif } #if os(macOS) .listStyle(.inset) #elseif os(iOS) - .listStyle(.insetGrouped) + .listStyle(.grouped) #else .listStyle(.plain) #endif @@ -44,23 +51,22 @@ struct PlayerQueueView: View { } var playedPreviously: some View { - Section(header: Text("Played Previously")) { - if player.history.isEmpty { - Text("History is empty") - .foregroundColor(.secondary) - } - - ForEach(player.history) { item in - PlayerQueueRow(item: item, history: true, fullScreen: $fullScreen) - .contextMenu { - removeButton(item, history: true) - removeAllButton(history: true) + Group { + if !player.history.isEmpty { + Section(header: Text("Played Previously")) { + ForEach(player.history) { item in + PlayerQueueRow(item: item, history: true, fullScreen: $fullScreen) + .contextMenu { + removeButton(item, history: true) + removeAllButton(history: true) + } + #if os(iOS) + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + removeButton(item, history: true) + } + #endif } - #if os(iOS) - .swipeActions(edge: .trailing, allowsFullSwipe: true) { - removeButton(item, history: true) - } - #endif + } } } } diff --git a/Shared/Player/PlayerViewController.swift b/Shared/Player/PlayerViewController.swift index 6bc73558..dfcdf4df 100644 --- a/Shared/Player/PlayerViewController.swift +++ b/Shared/Player/PlayerViewController.swift @@ -6,7 +6,6 @@ final class PlayerViewController: UIViewController { var playerLoaded = false var playerModel: PlayerModel! var playerViewController = AVPlayerViewController() - var shouldResume = false override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) @@ -73,15 +72,9 @@ extension PlayerViewController: AVPlayerViewControllerDelegate { false } - func playerViewControllerWillBeginDismissalTransition(_: AVPlayerViewController) { - shouldResume = playerModel.isPlaying - } + func playerViewControllerWillBeginDismissalTransition(_: AVPlayerViewController) {} func playerViewControllerDidEndDismissalTransition(_: AVPlayerViewController) { - if shouldResume { - playerModel.play() - } - dismiss(animated: false) } diff --git a/Shared/Player/VideoDetails.swift b/Shared/Player/VideoDetails.swift index 0ddf6b42..e0196f8d 100644 --- a/Shared/Player/VideoDetails.swift +++ b/Shared/Player/VideoDetails.swift @@ -87,6 +87,10 @@ struct VideoDetails: View { } } .onAppear { + if video.isNil { + currentPage = .queue + } + guard video != nil, accounts.app.supportsSubscriptions else { subscribed = false return @@ -94,6 +98,15 @@ struct VideoDetails: View { subscribed = subscriptions.isSubscribing(video!.channel.id) } + .onChange(of: sidebarQueue) { queue in + #if !os(macOS) + if queue { + currentPage = .details + } else { + currentPage = .queue + } + #endif + } .edgesIgnoringSafeArea(.horizontal) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) } @@ -112,11 +125,6 @@ struct VideoDetails: View { } else { Text("Not playing") .foregroundColor(.secondary) - .onAppear { - #if !os(macOS) - currentPage = .queue - #endif - } } Spacer() diff --git a/Shared/Playlists/PlaylistsView.swift b/Shared/Playlists/PlaylistsView.swift index 7d4c70c7..78f5894e 100644 --- a/Shared/Playlists/PlaylistsView.swift +++ b/Shared/Playlists/PlaylistsView.swift @@ -3,15 +3,16 @@ import Siesta import SwiftUI struct PlaylistsView: View { - @EnvironmentObject private var player - @EnvironmentObject private var model - @State private var showingNewPlaylist = false @State private var createdPlaylist: Playlist? @State private var showingEditPlaylist = false @State private var editedPlaylist: Playlist? + @EnvironmentObject private var accounts + @EnvironmentObject private var player + @EnvironmentObject private var model + @Namespace private var focusNamespace var items: [ContentItem] { @@ -101,6 +102,9 @@ struct PlaylistsView: View { .onAppear { model.load() } + .onChange(of: accounts.current) { _ in + model.load(force: true) + } } #if os(tvOS) diff --git a/Shared/Videos/VideoBanner.swift b/Shared/Videos/VideoBanner.swift index 29b7f13d..c2bb6c08 100644 --- a/Shared/Videos/VideoBanner.swift +++ b/Shared/Videos/VideoBanner.swift @@ -15,15 +15,13 @@ struct VideoBanner: View { } var body: some View { - HStack(alignment: .top, spacing: 12) { + HStack(alignment: stackAlignment, spacing: 12) { VStack(spacing: thumbnailStackSpacing) { smallThumbnail - if !playbackTime.isNil { - ProgressView(value: progressViewValue, total: progressViewTotal) - .progressViewStyle(.linear) - .frame(maxWidth: thumbnailWidth) - } + #if !os(tvOS) + progressView + #endif } VStack(alignment: .leading, spacing: 4) { Text(video.title) @@ -38,6 +36,10 @@ struct VideoBanner: View { Spacer() + #if os(tvOS) + progressView + #endif + if let time = (videoDuration ?? video.length).formattedAsPlaybackTime() { Text(time) .fontWeight(.light) @@ -52,11 +54,19 @@ struct VideoBanner: View { .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 100, alignment: .center) } + private var stackAlignment: VerticalAlignment { + #if os(macOS) + playbackTime.isNil ? .center : .top + #else + .center + #endif + } + private var thumbnailStackSpacing: Double { #if os(tvOS) 8 #else - 3 + 2 #endif } @@ -68,22 +78,32 @@ struct VideoBanner: View { } .indicator(.activity) #if os(tvOS) - .frame(width: thumbnailWidth, height: 100) + .frame(width: thumbnailWidth, height: 140) .mask(RoundedRectangle(cornerRadius: 12)) #else - .frame(width: thumbnailWidth, height: 50) + .frame(width: thumbnailWidth, height: 60) .mask(RoundedRectangle(cornerRadius: 6)) #endif } private var thumbnailWidth: Double { #if os(tvOS) - 177 + 230 #else - 88 + 100 #endif } + private var progressView: some View { + Group { + if !playbackTime.isNil { + ProgressView(value: progressViewValue, total: progressViewTotal) + .progressViewStyle(.linear) + .frame(maxWidth: thumbnailWidth) + } + } + } + private var progressViewValue: Double { [playbackTime?.seconds, videoDuration].compactMap { $0 }.min() ?? 0 } diff --git a/tvOS/NowPlayingView.swift b/tvOS/NowPlayingView.swift index 1acc88f4..754c6b3f 100644 --- a/tvOS/NowPlayingView.swift +++ b/tvOS/NowPlayingView.swift @@ -16,12 +16,10 @@ struct NowPlayingView: View { } var content: some View { - ScrollView(.vertical) { - VStack(alignment: .leading) { + List { + Group { if !inInfoViewController, let item = player.currentItem { - Group { - header("Now Playing") - + Section(header: Text("Now Playing")) { Button { player.presentPlayer() } label: { @@ -29,72 +27,66 @@ struct NowPlayingView: View { } } .onPlayPauseCommand(perform: player.togglePlay) - .padding(.bottom, 20) } - header("Playing Next") - - if player.queue.isEmpty { - Spacer() - - Text("Playback queue is empty") - .padding([.vertical, .leading], 40) - .foregroundColor(.secondary) - } - - ForEach(player.queue) { item in - Button { - player.advanceToItem(item) - player.presentPlayer() - } label: { - VideoBanner(video: item.video) + Section(header: Text("Playing Next")) { + if player.queue.isEmpty { + Text("Playback queue is empty") + .padding([.vertical, .leading], 40) + .foregroundColor(.secondary) } - .contextMenu { - Button("Delete", role: .destructive) { - player.remove(item) + + ForEach(player.queue) { item in + Button { + player.advanceToItem(item) + player.presentPlayer() + } label: { + VideoBanner(video: item.video) + } + .contextMenu { + Button("Delete", role: .destructive) { + player.remove(item) + } } } } - header("Played Previously") + if !player.history.isEmpty { + Section(header: Text("Played Previously")) { + ForEach(player.history) { item in + Button { + player.playHistory(item) + player.presentPlayer() + } label: { + VideoBanner(video: item.video, playbackTime: item.playbackTime, videoDuration: item.videoDuration) + } + .contextMenu { + Button("Delete", role: .destructive) { + player.removeHistory(item) + } - if player.history.isEmpty { - Spacer() - - Text("History is empty") - .padding([.vertical, .leading], 40) - .foregroundColor(.secondary) - } - - ForEach(player.history) { item in - Button { - player.playHistory(item) - player.presentPlayer() - } label: { - VideoBanner(video: item.video, playbackTime: item.playbackTime, videoDuration: item.videoDuration) - } - .contextMenu { - Button("Delete", role: .destructive) { - player.removeHistory(item) - } - - Button("Delete History", role: .destructive) { - player.removeHistoryItems() + Button("Delete History", role: .destructive) { + player.removeHistoryItems() + } + } } } } } - .padding(.vertical) - .padding(.horizontal, 40) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 20)) + .padding(.vertical, 20) +// .padding(.horizontal, 40) } - .frame(minWidth: 0, maxWidth: .infinity, minHeight: 260, maxHeight: .infinity, alignment: .leading) + .padding(.horizontal, inInfoViewController ? 40 : 0) + .listStyle(.grouped) + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 560, maxHeight: .infinity, alignment: .leading) } func header(_ text: String) -> some View { Text(text) .font((inInfoViewController ? Font.system(size: 40) : .title3).bold()) .foregroundColor(.secondary) - .padding(.leading, 40) +// .padding(.leading, 40) } }