mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Reorganize toolbars placement
This commit is contained in:
parent
249c7ca7fa
commit
9868a2ef01
@ -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 }
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ struct FavoritesView: View {
|
||||
.padding(.top, item == first && RefreshControl.navigationBarTitleDisplayMode == .inline ? 10 : 0)
|
||||
#endif
|
||||
}
|
||||
Color.clear.padding(.bottom, 30)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -1,14 +1,20 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct PlayerControlsView<Content: View>: View {
|
||||
struct PlayerControlsView<Content: View, Toolbar: View>: View {
|
||||
let content: Content
|
||||
let toolbar: Toolbar?
|
||||
|
||||
@Environment(\.navigationStyle) private var navigationStyle
|
||||
@EnvironmentObject<PlayerModel> 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<Content: View>: 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<Content: View>: View {
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.padding(.vertical, 20)
|
||||
@ -84,6 +104,8 @@ struct PlayerControlsView<Content: View>: View {
|
||||
|
||||
Button(action: { model.advanceToNextItem() }) {
|
||||
Label("Next", systemImage: "forward.fill")
|
||||
.padding(.vertical)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.disabled(model.queue.isEmpty)
|
||||
}
|
||||
@ -91,10 +113,9 @@ struct PlayerControlsView<Content: View>: 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<Content: View>: 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 {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user