diff --git a/Shared/EnvironmentValues.swift b/Shared/EnvironmentValues.swift index c84564b8..3094aa88 100644 --- a/Shared/EnvironmentValues.swift +++ b/Shared/EnvironmentValues.swift @@ -29,11 +29,15 @@ private struct CurrentPlaylistID: EnvironmentKey { static let defaultValue: String? = nil } +typealias LoadMoreContentHandlerType = () -> Void + private struct LoadMoreContentHandler: EnvironmentKey { static let defaultValue: LoadMoreContentHandlerType = {} } -typealias LoadMoreContentHandlerType = () -> Void +private struct ScrollViewBottomPaddingKey: EnvironmentKey { + static let defaultValue: Double = 30 +} extension EnvironmentValues { var inNavigationView: Bool { @@ -70,4 +74,9 @@ extension EnvironmentValues { get { self[LoadMoreContentHandler.self] } set { self[LoadMoreContentHandler.self] = newValue } } + + var scrollViewBottomPadding: Double { + get { self[ScrollViewBottomPaddingKey.self] } + set { self[ScrollViewBottomPaddingKey.self] = newValue } + } } diff --git a/Shared/Favorites/FavoritesView.swift b/Shared/Favorites/FavoritesView.swift index 31425a7a..343fca44 100644 --- a/Shared/Favorites/FavoritesView.swift +++ b/Shared/Favorites/FavoritesView.swift @@ -39,6 +39,7 @@ struct FavoritesView: View { .padding(.top, item == first && RefreshControl.navigationBarTitleDisplayMode == .inline ? 10 : 0) #endif } + Color.clear.padding(.bottom, 30) #endif } } diff --git a/Shared/Playlists/PlaylistsView.swift b/Shared/Playlists/PlaylistsView.swift index 0a3deaae..2af9cad0 100644 --- a/Shared/Playlists/PlaylistsView.swift +++ b/Shared/Playlists/PlaylistsView.swift @@ -53,7 +53,43 @@ struct PlaylistsView: View { } var body: some View { - PlayerControlsView { + PlayerControlsView(toolbar: { + HStack { + HStack { + newPlaylistButton + .offset(x: -10) + if currentPlaylist != nil { + editPlaylistButton + } + } + + if !model.isEmpty { + Spacer() + } + + HStack { + if model.isEmpty { + Text("No Playlists") + .foregroundColor(.secondary) + } else { + selectPlaylistButton + .transaction { t in t.animation = .none } + } + } + + Spacer() + + if currentPlaylist != nil { + HStack(spacing: 0) { + playButton + + shuffleButton + } + .offset(x: 10) + } + } + .padding(.horizontal) + }) { SignInRequiredView(title: "Playlists") { VStack { #if os(tvOS) @@ -72,6 +108,7 @@ struct PlaylistsView: View { Spacer() #else VerticalCells(items: items) + .environment(\.scrollViewBottomPadding, 70) #endif } .environment(\.currentPlaylistID, currentPlaylist?.id) @@ -79,6 +116,18 @@ struct PlaylistsView: View { } } } + .onAppear { + model.load() + } + .onChange(of: accounts.current) { _ in + model.load(force: true) + } + .onChange(of: selectedPlaylistID) { _ in + resource?.load() + } + .onChange(of: model.reloadPlaylists) { _ in + resource?.load() + } #if os(tvOS) .fullScreenCover(isPresented: $showingNewPlaylist, onDismiss: selectCreatedPlaylist) { PlaylistFormView(playlist: $createdPlaylist) @@ -88,74 +137,25 @@ struct PlaylistsView: View { PlaylistFormView(playlist: $editedPlaylist) .environmentObject(accounts) } + .focusScope(focusNamespace) #else - .background( - EmptyView() - .sheet(isPresented: $showingNewPlaylist, onDismiss: selectCreatedPlaylist) { - PlaylistFormView(playlist: $createdPlaylist) - .environmentObject(accounts) - } - ) - .background( - EmptyView() - .sheet(isPresented: $showingEditPlaylist, onDismiss: selectEditedPlaylist) { - PlaylistFormView(playlist: $editedPlaylist) - .environmentObject(accounts) - } - ) + .background( + EmptyView() + .sheet(isPresented: $showingNewPlaylist, onDismiss: selectCreatedPlaylist) { + PlaylistFormView(playlist: $createdPlaylist) + .environmentObject(accounts) + } + ) + .background( + EmptyView() + .sheet(isPresented: $showingEditPlaylist, onDismiss: selectEditedPlaylist) { + PlaylistFormView(playlist: $editedPlaylist) + .environmentObject(accounts) + } + ) #endif - .toolbar { - #if os(iOS) - ToolbarItemGroup(placement: .bottomBar) { - Group { - if model.isEmpty { - Text("No Playlists") - .foregroundColor(.secondary) - } else { - selectPlaylistButton - .transaction { t in t.animation = .none } - } - - Spacer() - - if currentPlaylist != nil { - HStack(spacing: 10) { - playButton - shuffleButton - } - - Spacer() - } - - HStack(spacing: 2) { - newPlaylistButton - - if currentPlaylist != nil { - editPlaylistButton - } - } - } - } - #endif - } - #if os(tvOS) - .focusScope(focusNamespace) - #endif - .onAppear { - model.load() - resource?.load() - } - .onChange(of: accounts.current) { _ in - model.load(force: true) - } - .onChange(of: selectedPlaylistID) { _ in - resource?.load() - } - .onChange(of: model.reloadPlaylists) { _ in - resource?.load() - } #if os(iOS) - .navigationBarTitleDisplayMode(RefreshControl.navigationBarTitleDisplayMode) + .navigationBarTitleDisplayMode(RefreshControl.navigationBarTitleDisplayMode) #endif } @@ -261,7 +261,7 @@ struct PlaylistsView: View { } } label: { Text(currentPlaylist?.title ?? "Select playlist") - .frame(maxWidth: 140, alignment: .leading) + .frame(maxWidth: 140, alignment: .center) } #endif } @@ -272,16 +272,17 @@ struct PlaylistsView: View { self.showingEditPlaylist = true }) { HStack(spacing: 8) { - Image(systemName: "slider.horizontal.3") - Text("Edit") + Image(systemName: "rectangle.and.pencil.and.ellipsis") } } } var newPlaylistButton: some View { Button(action: { self.showingNewPlaylist = true }) { - HStack(spacing: 8) { + HStack(spacing: 0) { Image(systemName: "plus") + .padding(8) + .contentShape(Rectangle()) #if os(tvOS) Text("New Playlist") #endif @@ -294,6 +295,8 @@ struct PlaylistsView: View { player.play(items.compactMap(\.video)) } label: { Image(systemName: "play") + .padding(8) + .contentShape(Rectangle()) } } @@ -302,6 +305,8 @@ struct PlaylistsView: View { player.play(items.compactMap(\.video), shuffling: true) } label: { Image(systemName: "shuffle") + .padding(8) + .contentShape(Rectangle()) } } diff --git a/Shared/Search/SearchView.swift b/Shared/Search/SearchView.swift index b87628bd..6c5bbd37 100644 --- a/Shared/Search/SearchView.swift +++ b/Shared/Search/SearchView.swift @@ -41,7 +41,23 @@ struct SearchView: View { } var body: some View { - PlayerControlsView { + PlayerControlsView(toolbar: { + #if os(iOS) + if accounts.app.supportsSearchFilters { + HStack(spacing: 0) { + Menu("Sort: \(searchSortOrder.name)") { + searchSortOrderPicker + } + .transaction { t in t.animation = .none } + + Spacer() + + filtersMenu + } + .padding() + } + #endif + }) { #if os(iOS) VStack { SearchTextField(favoriteItem: $favoriteItem) @@ -70,27 +86,19 @@ struct SearchView: View { #endif } .toolbar { - #if !os(tvOS) + #if os(macOS) ToolbarItemGroup(placement: toolbarPlacement) { - #if os(macOS) - FavoriteButton(item: favoriteItem) - .id(favoriteItem?.id) - #endif + FavoriteButton(item: favoriteItem) + .id(favoriteItem?.id) if accounts.app.supportsSearchFilters { Section { - #if os(macOS) - HStack { - Text("Sort:") - .foregroundColor(.secondary) + HStack { + Text("Sort:") + .foregroundColor(.secondary) - searchSortOrderPicker - } - #else - Menu("Sort: \(searchSortOrder.name)") { - searchSortOrderPicker - } - #endif + searchSortOrderPicker + } } .transaction { t in t.animation = .none } } @@ -99,9 +107,7 @@ struct SearchView: View { filtersMenu } - #if os(macOS) - SearchTextField() - #endif + SearchTextField() } #endif } diff --git a/Shared/Trending/TrendingView.swift b/Shared/Trending/TrendingView.swift index 80722ca8..b637ac1b 100644 --- a/Shared/Trending/TrendingView.swift +++ b/Shared/Trending/TrendingView.swift @@ -33,7 +33,39 @@ struct TrendingView: View { } var body: some View { - PlayerControlsView { + PlayerControlsView(toolbar: { + HStack { + if accounts.app.supportsTrendingCategories { + HStack { + Text("Category") + .foregroundColor(.secondary) + + categoryButton + // only way to disable Menu animation is to + // force redraw of the view when it changes + .id(UUID()) + } + + Spacer() + } + + if let favoriteItem = favoriteItem { + FavoriteButton(item: favoriteItem, labelPadding: true) + .id(favoriteItem.id) + .labelStyle(.iconOnly) + + Spacer() + } + + HStack { + Text("Country") + .foregroundColor(.secondary) + + countryButton + } + } + .padding(.horizontal) + }) { Section { VStack(alignment: .center, spacing: 0) { #if os(tvOS) @@ -44,6 +76,7 @@ struct TrendingView: View { Spacer() #else VerticalCells(items: trending) + .environment(\.scrollViewBottomPadding, 70) #endif } } @@ -62,38 +95,6 @@ struct TrendingView: View { } countryButton } - #elseif os(iOS) - ToolbarItemGroup(placement: .bottomBar) { - Group { - if accounts.app.supportsTrendingCategories { - HStack { - Text("Category") - .foregroundColor(.secondary) - - categoryButton - // only way to disable Menu animation is to - // force redraw of the view when it changes - .id(UUID()) - } - - Spacer() - } - - if let favoriteItem = favoriteItem { - FavoriteButton(item: favoriteItem) - .id(favoriteItem.id) - - Spacer() - } - - HStack { - Text("Country") - .foregroundColor(.secondary) - - countryButton - } - } - } #endif } .onChange(of: resource) { _ in diff --git a/Shared/Videos/VerticalCells.swift b/Shared/Videos/VerticalCells.swift index 7cc16d38..7e17fbf9 100644 --- a/Shared/Videos/VerticalCells.swift +++ b/Shared/Videos/VerticalCells.swift @@ -6,6 +6,7 @@ struct VerticalCells: View { @Environment(\.verticalSizeClass) private var verticalSizeClass #endif + @Environment(\.scrollViewBottomPadding) private var scrollViewBottomPadding @Environment(\.loadMoreContentHandler) private var loadMoreContentHandler var items = [ContentItem]() @@ -20,6 +21,9 @@ struct VerticalCells: View { } } .padding() + #if !os(tvOS) + Color.clear.padding(.bottom, scrollViewBottomPadding) + #endif } .edgesIgnoringSafeArea(.horizontal) #if os(macOS) diff --git a/Shared/Views/FavoriteButton.swift b/Shared/Views/FavoriteButton.swift index 8ce088be..9b98dbd8 100644 --- a/Shared/Views/FavoriteButton.swift +++ b/Shared/Views/FavoriteButton.swift @@ -5,6 +5,12 @@ import SwiftUI struct FavoriteButton: View { let item: FavoriteItem! let favorites = FavoritesModel.shared + let labelPadding: Bool + + init(item: FavoriteItem?, labelPadding: Bool = false) { + self.item = item + self.labelPadding = labelPadding + } @State private var isFavorite = false @@ -19,11 +25,17 @@ struct FavoriteButton: View { favorites.toggle(item) isFavorite.toggle() } label: { - if isFavorite { - Label("Remove from Favorites", systemImage: "heart.fill") - } else { - Label("Add to Favorites", systemImage: "heart") + Group { + if isFavorite { + Label("Remove from Favorites", systemImage: "heart.fill") + } else { + Label("Add to Favorites", systemImage: "heart") + } } + #if os(iOS) + .padding(labelPadding ? 10 : 0) + .contentShape(Rectangle()) + #endif } .disabled(item.isNil) .onAppear { diff --git a/Shared/Views/PlayerControlsView.swift b/Shared/Views/PlayerControlsView.swift index b006bac1..b6b19f0d 100644 --- a/Shared/Views/PlayerControlsView.swift +++ b/Shared/Views/PlayerControlsView.swift @@ -1,14 +1,20 @@ import Foundation import SwiftUI -struct PlayerControlsView: View { +struct PlayerControlsView: View { let content: Content + let toolbar: Toolbar? @Environment(\.navigationStyle) private var navigationStyle @EnvironmentObject private var model - init(@ViewBuilder content: @escaping () -> Content) { + init(@ViewBuilder toolbar: @escaping () -> Toolbar? = { nil }, @ViewBuilder content: @escaping () -> Content) { self.content = content() + self.toolbar = toolbar() + } + + init(@ViewBuilder content: @escaping () -> Content) where Toolbar == EmptyView { + self.init(toolbar: { EmptyView() }, content: content) } var body: some View { @@ -16,17 +22,30 @@ struct PlayerControlsView: View { content #if !os(tvOS) .frame(minHeight: 0, maxHeight: .infinity) - .padding(.bottom, 50) #endif - #if !os(tvOS) - controls + Group { + #if !os(tvOS) + #if !os(macOS) + toolbar + .frame(height: 100) + .offset(x: 0, y: -28) + #endif + controls + + #endif + } + .borderTop(height: 0.4, color: Color("ControlsBorderColor")) + #if os(macOS) + .background(VisualEffectBlur(material: .sidebar)) + #elseif os(iOS) + .background(VisualEffectBlur(blurStyle: .systemThinMaterial).edgesIgnoringSafeArea(.all)) #endif } } private var controls: some View { - let controls = HStack { + HStack { Button(action: { model.togglePlayer() }) { @@ -57,6 +76,7 @@ struct PlayerControlsView: View { Spacer() } + .padding(.vertical) .contentShape(Rectangle()) } .padding(.vertical, 20) @@ -84,6 +104,8 @@ struct PlayerControlsView: View { Button(action: { model.advanceToNextItem() }) { Label("Next", systemImage: "forward.fill") + .padding(.vertical) + .contentShape(Rectangle()) } .disabled(model.queue.isEmpty) } @@ -91,10 +113,9 @@ struct PlayerControlsView: View { ProgressView(value: progressViewValue, total: progressViewTotal) .progressViewStyle(.linear) #if os(iOS) - .offset(x: 0, y: 8) .frame(maxWidth: 60) #else - .offset(x: 0, y: 15) + .offset(y: 6) .frame(maxWidth: 70) #endif } @@ -111,20 +132,6 @@ struct PlayerControlsView: View { model.show() }) #endif - - return Group { - if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) { - controls - .background(Material.ultraThinMaterial) - } else { - controls - #if os(macOS) - .background(VisualEffectBlur(material: .hudWindow)) - #elseif os(iOS) - .background(VisualEffectBlur(blurStyle: .systemUltraThinMaterial)) - #endif - } - } } private var progressViewValue: Double { diff --git a/Shared/Views/VideoContextMenuView.swift b/Shared/Views/VideoContextMenuView.swift index 9cff70c2..611032dd 100644 --- a/Shared/Views/VideoContextMenuView.swift +++ b/Shared/Views/VideoContextMenuView.swift @@ -195,7 +195,7 @@ struct VideoContextMenuView: View { Button { navigation.presentAddToPlaylist(video) } label: { - Label("Add to playlist...", systemImage: "text.badge.plus") + Label("Add to Playlist...", systemImage: "text.badge.plus") } } @@ -203,7 +203,7 @@ struct VideoContextMenuView: View { Button { playlists.removeVideo(index: video.indexID!, playlistID: playlistID) } label: { - Label("Remove from playlist", systemImage: "text.badge.minus") + Label("Remove from Playlist", systemImage: "text.badge.minus") } } }