Reorganize toolbars placement

This commit is contained in:
Arkadiusz Fal 2022-02-04 18:38:29 +01:00
parent 249c7ca7fa
commit 9868a2ef01
9 changed files with 197 additions and 152 deletions

View File

@ -29,11 +29,15 @@ private struct CurrentPlaylistID: EnvironmentKey {
static let defaultValue: String? = nil static let defaultValue: String? = nil
} }
typealias LoadMoreContentHandlerType = () -> Void
private struct LoadMoreContentHandler: EnvironmentKey { private struct LoadMoreContentHandler: EnvironmentKey {
static let defaultValue: LoadMoreContentHandlerType = {} static let defaultValue: LoadMoreContentHandlerType = {}
} }
typealias LoadMoreContentHandlerType = () -> Void private struct ScrollViewBottomPaddingKey: EnvironmentKey {
static let defaultValue: Double = 30
}
extension EnvironmentValues { extension EnvironmentValues {
var inNavigationView: Bool { var inNavigationView: Bool {
@ -70,4 +74,9 @@ extension EnvironmentValues {
get { self[LoadMoreContentHandler.self] } get { self[LoadMoreContentHandler.self] }
set { self[LoadMoreContentHandler.self] = newValue } set { self[LoadMoreContentHandler.self] = newValue }
} }
var scrollViewBottomPadding: Double {
get { self[ScrollViewBottomPaddingKey.self] }
set { self[ScrollViewBottomPaddingKey.self] = newValue }
}
} }

View File

@ -39,6 +39,7 @@ struct FavoritesView: View {
.padding(.top, item == first && RefreshControl.navigationBarTitleDisplayMode == .inline ? 10 : 0) .padding(.top, item == first && RefreshControl.navigationBarTitleDisplayMode == .inline ? 10 : 0)
#endif #endif
} }
Color.clear.padding(.bottom, 30)
#endif #endif
} }
} }

View File

@ -53,7 +53,43 @@ struct PlaylistsView: View {
} }
var body: some 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") { SignInRequiredView(title: "Playlists") {
VStack { VStack {
#if os(tvOS) #if os(tvOS)
@ -72,6 +108,7 @@ struct PlaylistsView: View {
Spacer() Spacer()
#else #else
VerticalCells(items: items) VerticalCells(items: items)
.environment(\.scrollViewBottomPadding, 70)
#endif #endif
} }
.environment(\.currentPlaylistID, currentPlaylist?.id) .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) #if os(tvOS)
.fullScreenCover(isPresented: $showingNewPlaylist, onDismiss: selectCreatedPlaylist) { .fullScreenCover(isPresented: $showingNewPlaylist, onDismiss: selectCreatedPlaylist) {
PlaylistFormView(playlist: $createdPlaylist) PlaylistFormView(playlist: $createdPlaylist)
@ -88,74 +137,25 @@ struct PlaylistsView: View {
PlaylistFormView(playlist: $editedPlaylist) PlaylistFormView(playlist: $editedPlaylist)
.environmentObject(accounts) .environmentObject(accounts)
} }
.focusScope(focusNamespace)
#else #else
.background( .background(
EmptyView() EmptyView()
.sheet(isPresented: $showingNewPlaylist, onDismiss: selectCreatedPlaylist) { .sheet(isPresented: $showingNewPlaylist, onDismiss: selectCreatedPlaylist) {
PlaylistFormView(playlist: $createdPlaylist) PlaylistFormView(playlist: $createdPlaylist)
.environmentObject(accounts) .environmentObject(accounts)
} }
) )
.background( .background(
EmptyView() EmptyView()
.sheet(isPresented: $showingEditPlaylist, onDismiss: selectEditedPlaylist) { .sheet(isPresented: $showingEditPlaylist, onDismiss: selectEditedPlaylist) {
PlaylistFormView(playlist: $editedPlaylist) PlaylistFormView(playlist: $editedPlaylist)
.environmentObject(accounts) .environmentObject(accounts)
} }
) )
#endif #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) #if os(iOS)
.navigationBarTitleDisplayMode(RefreshControl.navigationBarTitleDisplayMode) .navigationBarTitleDisplayMode(RefreshControl.navigationBarTitleDisplayMode)
#endif #endif
} }
@ -261,7 +261,7 @@ struct PlaylistsView: View {
} }
} label: { } label: {
Text(currentPlaylist?.title ?? "Select playlist") Text(currentPlaylist?.title ?? "Select playlist")
.frame(maxWidth: 140, alignment: .leading) .frame(maxWidth: 140, alignment: .center)
} }
#endif #endif
} }
@ -272,16 +272,17 @@ struct PlaylistsView: View {
self.showingEditPlaylist = true self.showingEditPlaylist = true
}) { }) {
HStack(spacing: 8) { HStack(spacing: 8) {
Image(systemName: "slider.horizontal.3") Image(systemName: "rectangle.and.pencil.and.ellipsis")
Text("Edit")
} }
} }
} }
var newPlaylistButton: some View { var newPlaylistButton: some View {
Button(action: { self.showingNewPlaylist = true }) { Button(action: { self.showingNewPlaylist = true }) {
HStack(spacing: 8) { HStack(spacing: 0) {
Image(systemName: "plus") Image(systemName: "plus")
.padding(8)
.contentShape(Rectangle())
#if os(tvOS) #if os(tvOS)
Text("New Playlist") Text("New Playlist")
#endif #endif
@ -294,6 +295,8 @@ struct PlaylistsView: View {
player.play(items.compactMap(\.video)) player.play(items.compactMap(\.video))
} label: { } label: {
Image(systemName: "play") Image(systemName: "play")
.padding(8)
.contentShape(Rectangle())
} }
} }
@ -302,6 +305,8 @@ struct PlaylistsView: View {
player.play(items.compactMap(\.video), shuffling: true) player.play(items.compactMap(\.video), shuffling: true)
} label: { } label: {
Image(systemName: "shuffle") Image(systemName: "shuffle")
.padding(8)
.contentShape(Rectangle())
} }
} }

View File

@ -41,7 +41,23 @@ struct SearchView: View {
} }
var body: some 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) #if os(iOS)
VStack { VStack {
SearchTextField(favoriteItem: $favoriteItem) SearchTextField(favoriteItem: $favoriteItem)
@ -70,27 +86,19 @@ struct SearchView: View {
#endif #endif
} }
.toolbar { .toolbar {
#if !os(tvOS) #if os(macOS)
ToolbarItemGroup(placement: toolbarPlacement) { ToolbarItemGroup(placement: toolbarPlacement) {
#if os(macOS) FavoriteButton(item: favoriteItem)
FavoriteButton(item: favoriteItem) .id(favoriteItem?.id)
.id(favoriteItem?.id)
#endif
if accounts.app.supportsSearchFilters { if accounts.app.supportsSearchFilters {
Section { Section {
#if os(macOS) HStack {
HStack { Text("Sort:")
Text("Sort:") .foregroundColor(.secondary)
.foregroundColor(.secondary)
searchSortOrderPicker searchSortOrderPicker
} }
#else
Menu("Sort: \(searchSortOrder.name)") {
searchSortOrderPicker
}
#endif
} }
.transaction { t in t.animation = .none } .transaction { t in t.animation = .none }
} }
@ -99,9 +107,7 @@ struct SearchView: View {
filtersMenu filtersMenu
} }
#if os(macOS) SearchTextField()
SearchTextField()
#endif
} }
#endif #endif
} }

View File

@ -33,7 +33,39 @@ struct TrendingView: View {
} }
var body: some 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 { Section {
VStack(alignment: .center, spacing: 0) { VStack(alignment: .center, spacing: 0) {
#if os(tvOS) #if os(tvOS)
@ -44,6 +76,7 @@ struct TrendingView: View {
Spacer() Spacer()
#else #else
VerticalCells(items: trending) VerticalCells(items: trending)
.environment(\.scrollViewBottomPadding, 70)
#endif #endif
} }
} }
@ -62,38 +95,6 @@ struct TrendingView: View {
} }
countryButton 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 #endif
} }
.onChange(of: resource) { _ in .onChange(of: resource) { _ in

View File

@ -6,6 +6,7 @@ struct VerticalCells: View {
@Environment(\.verticalSizeClass) private var verticalSizeClass @Environment(\.verticalSizeClass) private var verticalSizeClass
#endif #endif
@Environment(\.scrollViewBottomPadding) private var scrollViewBottomPadding
@Environment(\.loadMoreContentHandler) private var loadMoreContentHandler @Environment(\.loadMoreContentHandler) private var loadMoreContentHandler
var items = [ContentItem]() var items = [ContentItem]()
@ -20,6 +21,9 @@ struct VerticalCells: View {
} }
} }
.padding() .padding()
#if !os(tvOS)
Color.clear.padding(.bottom, scrollViewBottomPadding)
#endif
} }
.edgesIgnoringSafeArea(.horizontal) .edgesIgnoringSafeArea(.horizontal)
#if os(macOS) #if os(macOS)

View File

@ -5,6 +5,12 @@ import SwiftUI
struct FavoriteButton: View { struct FavoriteButton: View {
let item: FavoriteItem! let item: FavoriteItem!
let favorites = FavoritesModel.shared let favorites = FavoritesModel.shared
let labelPadding: Bool
init(item: FavoriteItem?, labelPadding: Bool = false) {
self.item = item
self.labelPadding = labelPadding
}
@State private var isFavorite = false @State private var isFavorite = false
@ -19,11 +25,17 @@ struct FavoriteButton: View {
favorites.toggle(item) favorites.toggle(item)
isFavorite.toggle() isFavorite.toggle()
} label: { } label: {
if isFavorite { Group {
Label("Remove from Favorites", systemImage: "heart.fill") if isFavorite {
} else { Label("Remove from Favorites", systemImage: "heart.fill")
Label("Add to Favorites", systemImage: "heart") } else {
Label("Add to Favorites", systemImage: "heart")
}
} }
#if os(iOS)
.padding(labelPadding ? 10 : 0)
.contentShape(Rectangle())
#endif
} }
.disabled(item.isNil) .disabled(item.isNil)
.onAppear { .onAppear {

View File

@ -1,14 +1,20 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
struct PlayerControlsView<Content: View>: View { struct PlayerControlsView<Content: View, Toolbar: View>: View {
let content: Content let content: Content
let toolbar: Toolbar?
@Environment(\.navigationStyle) private var navigationStyle @Environment(\.navigationStyle) private var navigationStyle
@EnvironmentObject<PlayerModel> private var model @EnvironmentObject<PlayerModel> private var model
init(@ViewBuilder content: @escaping () -> Content) { init(@ViewBuilder toolbar: @escaping () -> Toolbar? = { nil }, @ViewBuilder content: @escaping () -> Content) {
self.content = 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 { var body: some View {
@ -16,17 +22,30 @@ struct PlayerControlsView<Content: View>: View {
content content
#if !os(tvOS) #if !os(tvOS)
.frame(minHeight: 0, maxHeight: .infinity) .frame(minHeight: 0, maxHeight: .infinity)
.padding(.bottom, 50)
#endif #endif
#if !os(tvOS) Group {
controls #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 #endif
} }
} }
private var controls: some View { private var controls: some View {
let controls = HStack { HStack {
Button(action: { Button(action: {
model.togglePlayer() model.togglePlayer()
}) { }) {
@ -57,6 +76,7 @@ struct PlayerControlsView<Content: View>: View {
Spacer() Spacer()
} }
.padding(.vertical)
.contentShape(Rectangle()) .contentShape(Rectangle())
} }
.padding(.vertical, 20) .padding(.vertical, 20)
@ -84,6 +104,8 @@ struct PlayerControlsView<Content: View>: View {
Button(action: { model.advanceToNextItem() }) { Button(action: { model.advanceToNextItem() }) {
Label("Next", systemImage: "forward.fill") Label("Next", systemImage: "forward.fill")
.padding(.vertical)
.contentShape(Rectangle())
} }
.disabled(model.queue.isEmpty) .disabled(model.queue.isEmpty)
} }
@ -91,10 +113,9 @@ struct PlayerControlsView<Content: View>: View {
ProgressView(value: progressViewValue, total: progressViewTotal) ProgressView(value: progressViewValue, total: progressViewTotal)
.progressViewStyle(.linear) .progressViewStyle(.linear)
#if os(iOS) #if os(iOS)
.offset(x: 0, y: 8)
.frame(maxWidth: 60) .frame(maxWidth: 60)
#else #else
.offset(x: 0, y: 15) .offset(y: 6)
.frame(maxWidth: 70) .frame(maxWidth: 70)
#endif #endif
} }
@ -111,20 +132,6 @@ struct PlayerControlsView<Content: View>: View {
model.show() model.show()
}) })
#endif #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 { private var progressViewValue: Double {

View File

@ -195,7 +195,7 @@ struct VideoContextMenuView: View {
Button { Button {
navigation.presentAddToPlaylist(video) navigation.presentAddToPlaylist(video)
} label: { } 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 { Button {
playlists.removeVideo(index: video.indexID!, playlistID: playlistID) playlists.removeVideo(index: video.indexID!, playlistID: playlistID)
} label: { } label: {
Label("Remove from playlist", systemImage: "text.badge.minus") Label("Remove from Playlist", systemImage: "text.badge.minus")
} }
} }
} }