Browser player bar as overlay

This commit is contained in:
Arkadiusz Fal
2022-12-10 22:37:14 +01:00
parent 7df397b662
commit 74d65f6ab8
20 changed files with 319 additions and 417 deletions

View File

@@ -4,57 +4,55 @@ struct DocumentsView: View {
@ObservedObject private var model = DocumentsModel.shared
var body: some View {
BrowserPlayerControls {
ScrollView(.vertical, showsIndicators: false) {
if model.directoryContents.isEmpty {
NoDocumentsView()
} else {
ForEach(model.sortedDirectoryContents, id: \.absoluteString) { url in
let video = Video.local(model.standardizedURL(url) ?? url)
PlayerQueueRow(
item: PlayerQueueItem(video)
)
.contextMenu {
VideoContextMenuView(video: video)
}
}
.id(model.refreshID)
.transition(.opacity)
}
Color.clear.padding(.bottom, 50)
}
.onAppear {
if model.directoryURL.isNil {
model.goToTop()
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
if model.canGoBack {
Button {
withAnimation {
model.goBack()
}
} label: {
HStack(spacing: 6) {
Label("Go back", systemImage: "chevron.left")
}
}
.transaction { t in t.animation = .none }
.disabled(!model.canGoBack)
ScrollView(.vertical, showsIndicators: false) {
if model.directoryContents.isEmpty {
NoDocumentsView()
} else {
ForEach(model.sortedDirectoryContents, id: \.absoluteString) { url in
let video = Video.local(model.standardizedURL(url) ?? url)
PlayerQueueRow(
item: PlayerQueueItem(video)
)
.contextMenu {
VideoContextMenuView(video: video)
}
}
.id(model.refreshID)
.transition(.opacity)
}
.navigationTitle(model.directoryLabel)
.padding(.horizontal)
.navigationBarTitleDisplayMode(RefreshControl.navigationBarTitleDisplayMode)
.backport
.refreshable {
DispatchQueue.main.async {
self.refresh()
Color.clear.padding(.bottom, 50)
}
.onAppear {
if model.directoryURL.isNil {
model.goToTop()
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
if model.canGoBack {
Button {
withAnimation {
model.goBack()
}
} label: {
HStack(spacing: 6) {
Label("Go back", systemImage: "chevron.left")
}
}
.transaction { t in t.animation = .none }
.disabled(!model.canGoBack)
}
}
}
.navigationTitle(model.directoryLabel)
.padding(.horizontal)
.navigationBarTitleDisplayMode(RefreshControl.navigationBarTitleDisplayMode)
.backport
.refreshable {
DispatchQueue.main.async {
self.refresh()
}
}
}
func refresh() {

View File

@@ -33,159 +33,157 @@ struct HomeView: View {
private var navigation: NavigationModel { .shared }
var body: some View {
BrowserPlayerControls {
ScrollView(.vertical, showsIndicators: false) {
HStack {
#if os(tvOS)
Group {
if showOpenActionsInHome {
OpenVideosButton(text: "Open Video", imageSystemName: "globe") {
NavigationModel.shared.presentingOpenVideos = true
}
}
OpenVideosButton(text: "Settings", imageSystemName: "gear") {
NavigationModel.shared.presentingSettings = true
}
}
#else
ScrollView(.vertical, showsIndicators: false) {
HStack {
#if os(tvOS)
Group {
if showOpenActionsInHome {
OpenVideosButton(text: "Files", imageSystemName: "folder") {
NavigationModel.shared.presentingFileImporter = true
}
OpenVideosButton(text: "Paste", imageSystemName: "doc.on.clipboard.fill") {
OpenVideosModel.shared.openURLsFromClipboard(playbackMode: .playNow)
}
OpenVideosButton(imageSystemName: "ellipsis") {
OpenVideosButton(text: "Open Video", imageSystemName: "globe") {
NavigationModel.shared.presentingOpenVideos = true
}
.frame(maxWidth: 40)
}
#endif
}
#if os(iOS)
.padding(.top, RefreshControl.navigationBarTitleDisplayMode == .inline ? 15 : 0)
OpenVideosButton(text: "Settings", imageSystemName: "gear") {
NavigationModel.shared.presentingSettings = true
}
}
#else
.padding(.top, 15)
if showOpenActionsInHome {
OpenVideosButton(text: "Files", imageSystemName: "folder") {
NavigationModel.shared.presentingFileImporter = true
}
OpenVideosButton(text: "Paste", imageSystemName: "doc.on.clipboard.fill") {
OpenVideosModel.shared.openURLsFromClipboard(playbackMode: .playNow)
}
OpenVideosButton(imageSystemName: "ellipsis") {
NavigationModel.shared.presentingOpenVideos = true
}
.frame(maxWidth: 40)
}
#endif
}
#if os(iOS)
.padding(.top, RefreshControl.navigationBarTitleDisplayMode == .inline ? 15 : 0)
#else
.padding(.top, 15)
#endif
#if os(tvOS)
.padding(.horizontal, 40)
#else
.padding(.horizontal, 15)
#endif
if !accounts.current.isNil, showFavoritesInHome {
#if os(tvOS)
.padding(.horizontal, 40)
ForEach(Defaults[.favorites]) { item in
FavoriteItemView(item: item, dragging: $dragging)
}
#else
.padding(.horizontal, 15)
#endif
if !accounts.current.isNil, showFavoritesInHome {
#if os(tvOS)
ForEach(Defaults[.favorites]) { item in
FavoriteItemView(item: item, dragging: $dragging)
}
#else
ForEach(favorites) { item in
FavoriteItemView(item: item, dragging: $dragging)
#if os(macOS)
.workaroundForVerticalScrollingBug()
#endif
}
#endif
}
#if os(iOS)
if homeRecentDocumentsItems > 0 {
VStack {
HStack {
sectionLabel("Recent Documents")
Spacer()
Button {
recentDocumentsID = UUID()
} label: {
Label("Refresh", systemImage: "arrow.clockwise")
.font(.headline)
.labelStyle(.iconOnly)
.foregroundColor(.secondary)
}
}
RecentDocumentsView(limit: homeRecentDocumentsItems)
.id(recentDocumentsID)
}
.frame(maxWidth: .infinity, alignment: .leading)
#if os(tvOS)
.padding(.trailing, 40)
#else
.padding(.trailing, 15)
ForEach(favorites) { item in
FavoriteItemView(item: item, dragging: $dragging)
#if os(macOS)
.workaroundForVerticalScrollingBug()
#endif
}
#endif
}
if homeHistoryItems > 0 {
#if os(iOS)
if homeRecentDocumentsItems > 0 {
VStack {
HStack {
sectionLabel("History")
sectionLabel("Recent Documents")
Spacer()
Button {
navigation.presentAlert(
Alert(
title: Text("Are you sure you want to clear history of watched videos?"),
message: Text("It cannot be reverted"),
primaryButton: .destructive(Text("Clear All")) {
PlayerModel.shared.removeHistory()
historyID = UUID()
},
secondaryButton: .cancel()
)
)
recentDocumentsID = UUID()
} label: {
Label("Clear History", systemImage: "trash")
Label("Refresh", systemImage: "arrow.clockwise")
.font(.headline)
.labelStyle(.iconOnly)
.foregroundColor(.secondary)
}
.buttonStyle(.plain)
}
.frame(maxWidth: .infinity, alignment: .leading)
#if os(tvOS)
.padding(.trailing, 40)
#else
.padding(.trailing, 15)
#endif
HistoryView(limit: homeHistoryItems)
.id(historyID)
RecentDocumentsView(limit: homeRecentDocumentsItems)
.id(recentDocumentsID)
}
.frame(maxWidth: .infinity, alignment: .leading)
#if os(tvOS)
.padding(.trailing, 40)
#else
.padding(.trailing, 15)
#endif
}
#endif
#if !os(tvOS)
Color.clear.padding(.bottom, 60)
#endif
}
.onAppear {
Defaults.observe(.favorites) { _ in
favoritesChanged.toggle()
if homeHistoryItems > 0 {
VStack {
HStack {
sectionLabel("History")
Spacer()
Button {
navigation.presentAlert(
Alert(
title: Text("Are you sure you want to clear history of watched videos?"),
message: Text("It cannot be reverted"),
primaryButton: .destructive(Text("Clear All")) {
PlayerModel.shared.removeHistory()
historyID = UUID()
},
secondaryButton: .cancel()
)
)
} label: {
Label("Clear History", systemImage: "trash")
.font(.headline)
.labelStyle(.iconOnly)
.foregroundColor(.secondary)
}
.buttonStyle(.plain)
}
.frame(maxWidth: .infinity, alignment: .leading)
#if os(tvOS)
.padding(.trailing, 40)
#else
.padding(.trailing, 15)
#endif
HistoryView(limit: homeHistoryItems)
.id(historyID)
}
.tieToLifetime(of: accounts)
}
.redrawOn(change: favoritesChanged)
#if os(tvOS)
.edgesIgnoringSafeArea(.horizontal)
#else
.navigationTitle("Home")
#endif
#if os(macOS)
.background(Color.secondaryBackground)
.frame(minWidth: 360)
#endif
#if os(iOS)
.navigationBarTitleDisplayMode(RefreshControl.navigationBarTitleDisplayMode)
#endif
#if !os(macOS)
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
favoritesChanged.toggle()
}
#if !os(tvOS)
Color.clear.padding(.bottom, 60)
#endif
}
.onAppear {
Defaults.observe(.favorites) { _ in
favoritesChanged.toggle()
}
.tieToLifetime(of: accounts)
}
.redrawOn(change: favoritesChanged)
#if os(tvOS)
.edgesIgnoringSafeArea(.horizontal)
#else
.navigationTitle("Home")
#endif
#if os(macOS)
.background(Color.secondaryBackground)
.frame(minWidth: 360)
#endif
#if os(iOS)
.navigationBarTitleDisplayMode(RefreshControl.navigationBarTitleDisplayMode)
#endif
#if !os(macOS)
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
favoritesChanged.toggle()
}
#endif
}
func sectionLabel(_ label: String) -> some View {

View File

@@ -0,0 +1,9 @@
import Foundation
import SwiftUI
struct PlayerOverlayModifier: ViewModifier {
func body(content: Content) -> some View {
content
.overlay(ControlsBar(fullScreen: .constant(false)), alignment: .bottom)
}
}

View File

@@ -42,15 +42,13 @@ struct AppSidebarNavigation: View {
.frame(minWidth: sidebarMinWidth)
VStack {
BrowserPlayerControls {
HStack {
Spacer()
Image(systemName: "4k.tv")
.renderingMode(.original)
.font(.system(size: 60))
.foregroundColor(.accentColor)
Spacer()
}
HStack {
Spacer()
Image(systemName: "4k.tv")
.renderingMode(.original)
.font(.system(size: 60))
.foregroundColor(.accentColor)
Spacer()
}
}
}

View File

@@ -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))
LazyView(PlaylistVideosView(playlist).modifier(PlayerOverlayModifier()))
} label: {
playlistLabel(playlist)
}

View File

@@ -16,17 +16,17 @@ struct AppSidebarRecents: View {
switch recent.type {
case .channel:
RecentNavigationLink(recent: recent) {
LazyView(ChannelVideosView(channel: recent.channel!))
LazyView(ChannelVideosView(channel: recent.channel!).modifier(PlayerOverlayModifier()))
}
case .playlist:
RecentNavigationLink(recent: recent, systemImage: "list.and.film") {
LazyView(ChannelPlaylistView(playlist: recent.playlist!))
LazyView(ChannelPlaylistView(playlist: recent.playlist!).modifier(PlayerOverlayModifier()))
}
case .query:
RecentNavigationLink(recent: recent, systemImage: "magnifyingglass") {
LazyView(SearchView(recent.query!))
LazyView(SearchView(recent.query!).modifier(PlayerOverlayModifier()))
}
}
}

View File

@@ -9,7 +9,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))
LazyView(ChannelVideosView(channel: channel).modifier(PlayerOverlayModifier()))
} label: {
Label(channel.name, systemImage: RecentsModel.symbolSystemImage(channel.name))
}

View File

@@ -16,7 +16,7 @@ struct AppTabNavigation: View {
var body: some View {
TabView(selection: navigation.tabSelectionBinding) {
let tabs = Group {
Group {
if showHome {
homeNavigationView
}
@@ -45,13 +45,7 @@ struct AppTabNavigation: View {
searchNavigationView
}
}
if #available(iOS 16, tvOS 16, *) {
tabs
.toolbar(accounts.isEmpty ? .hidden : .visible, for: .tabBar)
} else {
tabs
}
.overlay(ControlsBar(fullScreen: .constant(false)), alignment: .bottom)
}
.id(accounts.current?.id ?? "")
@@ -196,3 +190,9 @@ struct AppTabNavigation: View {
}
}
}
struct AppTabNavigation_Preview: PreviewProvider {
static var previews: some View {
AppTabNavigation()
}
}

View File

@@ -45,7 +45,7 @@ struct Sidebar: View {
var mainNavigationLinks: some View {
Section(header: Text("Videos")) {
if showHome {
NavigationLink(destination: LazyView(HomeView()), tag: TabSelection.home, selection: $navigation.tabSelection) {
NavigationLink(destination: LazyView(HomeView().modifier(PlayerOverlayModifier())), tag: TabSelection.home, selection: $navigation.tabSelection) {
Label("Home", systemImage: "house")
.accessibility(label: Text("Home"))
}
@@ -54,7 +54,7 @@ struct Sidebar: View {
#if os(iOS)
if showDocuments {
NavigationLink(destination: LazyView(DocumentsView()), tag: TabSelection.documents, selection: $navigation.tabSelection) {
NavigationLink(destination: LazyView(DocumentsView().modifier(PlayerOverlayModifier())), tag: TabSelection.documents, selection: $navigation.tabSelection) {
Label("Documents", systemImage: "folder")
.accessibility(label: Text("Documents"))
}
@@ -66,7 +66,7 @@ struct Sidebar: View {
if visibleSections.contains(.subscriptions),
accounts.app.supportsSubscriptions && accounts.signedIn
{
NavigationLink(destination: LazyView(SubscriptionsView()), tag: TabSelection.subscriptions, selection: $navigation.tabSelection) {
NavigationLink(destination: LazyView(SubscriptionsView().modifier(PlayerOverlayModifier())), tag: TabSelection.subscriptions, selection: $navigation.tabSelection) {
Label("Subscriptions", systemImage: "star.circle")
.accessibility(label: Text("Subscriptions"))
}
@@ -74,7 +74,7 @@ struct Sidebar: View {
}
if visibleSections.contains(.popular), accounts.app.supportsPopular {
NavigationLink(destination: LazyView(PopularView()), tag: TabSelection.popular, selection: $navigation.tabSelection) {
NavigationLink(destination: LazyView(PopularView().modifier(PlayerOverlayModifier())), tag: TabSelection.popular, selection: $navigation.tabSelection) {
Label("Popular", systemImage: "arrow.up.right.circle")
.accessibility(label: Text("Popular"))
}
@@ -82,14 +82,14 @@ struct Sidebar: View {
}
if visibleSections.contains(.trending) {
NavigationLink(destination: LazyView(TrendingView()), tag: TabSelection.trending, selection: $navigation.tabSelection) {
NavigationLink(destination: LazyView(TrendingView().modifier(PlayerOverlayModifier())), tag: TabSelection.trending, selection: $navigation.tabSelection) {
Label("Trending", systemImage: "chart.bar")
.accessibility(label: Text("Trending"))
}
.id("trending")
}
NavigationLink(destination: LazyView(SearchView()), tag: TabSelection.search, selection: $navigation.tabSelection) {
NavigationLink(destination: LazyView(SearchView().modifier(PlayerOverlayModifier())), tag: TabSelection.search, selection: $navigation.tabSelection) {
Label("Search", systemImage: "magnifyingglass")
.accessibility(label: Text("Search"))
}

View File

@@ -56,30 +56,28 @@ struct PlaylistsView: View {
}
var body: some View {
BrowserPlayerControls {
SignInRequiredView(title: "Playlists".localized()) {
Section {
VStack {
#if os(tvOS)
toolbar
#endif
if currentPlaylist != nil, items.isEmpty {
hintText("Playlist is empty\n\nTap and hold on a video and then \n\"Add to Playlist\"".localized())
} else if model.all.isEmpty {
hintText("You have no playlists\n\nTap on \"New Playlist\" to create one".localized())
} else {
Group {
#if os(tvOS)
HorizontalCells(items: items)
.padding(.top, 40)
Spacer()
#else
VerticalCells(items: items)
.environment(\.scrollViewBottomPadding, 70)
#endif
}
.environment(\.currentPlaylistID, currentPlaylist?.id)
SignInRequiredView(title: "Playlists".localized()) {
Section {
VStack {
#if os(tvOS)
toolbar
#endif
if currentPlaylist != nil, items.isEmpty {
hintText("Playlist is empty\n\nTap and hold on a video and then \n\"Add to Playlist\"".localized())
} else if model.all.isEmpty {
hintText("You have no playlists\n\nTap on \"New Playlist\" to create one".localized())
} else {
Group {
#if os(tvOS)
HorizontalCells(items: items)
.padding(.top, 40)
Spacer()
#else
VerticalCells(items: items)
.environment(\.scrollViewBottomPadding, 70)
#endif
}
.environment(\.currentPlaylistID, currentPlaylist?.id)
}
}
}

View File

@@ -37,7 +37,7 @@ struct SearchView: View {
}
var body: some View {
BrowserPlayerControls {
VStack {
#if os(iOS)
VStack {
if accounts.app.supportsSearchSuggestions, state.query.query != state.queryText {

View File

@@ -10,47 +10,45 @@ struct SubscriptionsView: View {
}
var body: some View {
BrowserPlayerControls {
SignInRequiredView(title: "Subscriptions".localized()) {
VerticalCells(items: videos) {
HStack {
Spacer()
SignInRequiredView(title: "Subscriptions".localized()) {
VerticalCells(items: videos) {
HStack {
Spacer()
CacheStatusHeader(refreshTime: model.formattedFeedTime, isLoading: model.isLoading)
CacheStatusHeader(refreshTime: model.formattedFeedTime, isLoading: model.isLoading)
#if os(tvOS)
Button {
model.loadResources(force: true)
} label: {
Label("Refresh", systemImage: "arrow.clockwise")
.labelStyle(.iconOnly)
.imageScale(.small)
.font(.caption2)
}
.padding(.horizontal, 10)
#endif
}
#if os(tvOS)
Button {
model.loadResources(force: true)
} label: {
Label("Refresh", systemImage: "arrow.clockwise")
.labelStyle(.iconOnly)
.imageScale(.small)
.font(.caption2)
}
.padding(.horizontal, 10)
#endif
}
.environment(\.loadMoreContentHandler) { model.loadNextPage() }
.onAppear {
model.loadResources()
}
.onChange(of: accounts.current) { _ in
model.reset()
model.loadResources(force: true)
}
#if os(iOS)
.refreshControl { refreshControl in
model.loadResources(force: true) {
refreshControl.endRefreshing()
}
}
.backport
.refreshable {
await model.loadResources(force: true)
}
#endif
}
.environment(\.loadMoreContentHandler) { model.loadNextPage() }
.onAppear {
model.loadResources()
}
.onChange(of: accounts.current) { _ in
model.reset()
model.loadResources(force: true)
}
#if os(iOS)
.refreshControl { refreshControl in
model.loadResources(force: true) {
refreshControl.endRefreshing()
}
}
.backport
.refreshable {
await model.loadResources(force: true)
}
#endif
}
#if !os(tvOS)

View File

@@ -33,20 +33,18 @@ struct TrendingView: View {
}
var body: some View {
BrowserPlayerControls {
Section {
VStack(spacing: 0) {
#if os(tvOS)
toolbar
HorizontalCells(items: trending)
.padding(.top, 40)
Section {
VStack(spacing: 0) {
#if os(tvOS)
toolbar
HorizontalCells(items: trending)
.padding(.top, 40)
Spacer()
#else
VerticalCells(items: trending)
.environment(\.scrollViewBottomPadding, 70)
#endif
}
Spacer()
#else
VerticalCells(items: trending)
.environment(\.scrollViewBottomPadding, 70)
#endif
}
}

View File

@@ -1,79 +0,0 @@
import Foundation
import SDWebImageSwiftUI
import SwiftUI
struct BrowserPlayerControls<Content: View, Toolbar: View>: View {
enum Context {
case browser, player
}
let content: Content
let toolbar: Toolbar?
init(
context _: Context? = nil,
@ViewBuilder toolbar: @escaping () -> Toolbar? = { nil },
@ViewBuilder content: @escaping () -> Content
) {
self.content = content()
self.toolbar = toolbar()
}
init(
context: Context? = nil,
@ViewBuilder content: @escaping () -> Content
) where Toolbar == EmptyView {
self.init(context: context, toolbar: { EmptyView() }, content: content)
}
var body: some View {
#if os(tvOS)
return content
#else
// TODO: remove
#if DEBUG
if #available(iOS 15.0, macOS 12.0, *) {
Self._printChanges()
}
#endif
return ZStack(alignment: .bottomLeading) {
content
.frame(maxHeight: .infinity)
#if !os(tvOS)
VStack(spacing: 0) {
#if os(iOS)
toolbar
.frame(height: 35)
.frame(maxWidth: .infinity)
.borderTop(height: 0.4, color: Color("ControlsBorderColor"))
.modifier(ControlBackgroundModifier())
#endif
ControlsBar(fullScreen: .constant(false))
.edgesIgnoringSafeArea(.bottom)
}
#endif
}
#endif
}
}
struct PlayerControlsView_Previews: PreviewProvider {
static var previews: some View {
BrowserPlayerControls(context: .player, toolbar: {
Button("Button") {}
}) {
BrowserPlayerControls {
VStack {
Spacer()
TextField("A", text: .constant("abc"))
Spacer()
}
}
.offset(y: -100)
}
.injectFixtureEnvironmentObjects()
}
}

View File

@@ -38,14 +38,10 @@ struct ChannelPlaylistView: View {
var body: some View {
if navigationStyle == .tab {
NavigationView {
BrowserPlayerControls {
content
}
}
} else {
BrowserPlayerControls {
content
}
} else {
content
}
}

View File

@@ -42,14 +42,10 @@ struct ChannelVideosView: View {
var body: some View {
if navigationStyle == .tab {
NavigationView {
BrowserPlayerControls {
content
}
}
} else {
BrowserPlayerControls {
content
}
} else {
content
}
}

View File

@@ -52,39 +52,37 @@ struct PlaylistVideosView: View {
}
var body: some View {
BrowserPlayerControls {
VerticalCells(items: contentItems)
.onAppear {
guard contentItems.isEmpty else { return }
resource?.load()
}
.onChange(of: model.reloadPlaylists) { _ in
resource?.load()
}
#if !os(tvOS)
.navigationTitle("\(playlist.title) Playlist")
#endif
}
.toolbar {
ToolbarItem(placement: playlistButtonsPlacement) {
HStack {
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(playlist.id, playlist.title)))
VerticalCells(items: contentItems)
.onAppear {
guard contentItems.isEmpty else { return }
resource?.load()
}
.onChange(of: model.reloadPlaylists) { _ in
resource?.load()
}
#if !os(tvOS)
.navigationTitle("\(playlist.title) Playlist")
#endif
.toolbar {
ToolbarItem(placement: playlistButtonsPlacement) {
HStack {
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(playlist.id, playlist.title)))
Button {
player.play(videos)
} label: {
Label("Play All", systemImage: "play")
}
.contextMenu {
Button {
player.play(videos, shuffling: true)
player.play(videos)
} label: {
Label("Shuffle All", systemImage: "shuffle")
Label("Play All", systemImage: "play")
}
.contextMenu {
Button {
player.play(videos, shuffling: true)
} label: {
Label("Shuffle All", systemImage: "shuffle")
}
}
}
}
}
}
}
private var playlistButtonsPlacement: ToolbarItemPlacement {

View File

@@ -15,24 +15,20 @@ struct PopularView: View {
}
var body: some View {
BrowserPlayerControls {
VerticalCells(items: videos)
.onAppear {
resource?.addObserver(store)
resource?.loadIfNeeded()
}
#if !os(tvOS)
.navigationTitle("Popular")
#endif
}
#if !os(tvOS)
.background(
Button("Refresh") {
resource?.load()
VerticalCells(items: videos)
.onAppear {
resource?.addObserver(store)
resource?.loadIfNeeded()
}
.keyboardShortcut("r")
.opacity(0)
)
#if !os(tvOS)
.navigationTitle("Popular")
.background(
Button("Refresh") {
resource?.load()
}
.keyboardShortcut("r")
.opacity(0)
)
#endif
#if os(iOS)
.refreshControl { refreshControl in

View File

@@ -61,10 +61,8 @@ struct SignInRequiredView<Content: View>: View {
struct SignInRequiredView_Previews: PreviewProvider {
static var previews: some View {
BrowserPlayerControls {
SignInRequiredView(title: "Subscriptions") {
Text("Only when signed in")
}
SignInRequiredView(title: "Subscriptions") {
Text("Only when signed in")
}
}
}