mirror of
https://github.com/yattee/yattee.git
synced 2024-11-10 00:08:21 +00:00
tvOS filters for all views
Vertical list for trending, popular, playlists, search Fix #413, #415
This commit is contained in:
parent
6596a440a5
commit
83dfdd6c0e
@ -242,7 +242,7 @@ extension Defaults.Keys {
|
||||
static let subscriptionsListingStyle = Key<ListingStyle>("subscriptionsListingStyle", default: .cells)
|
||||
static let popularListingStyle = Key<ListingStyle>("popularListingStyle", default: .cells)
|
||||
static let trendingListingStyle = Key<ListingStyle>("trendingListingStyle", default: .cells)
|
||||
static let playlistListingStyle = Key<ListingStyle>("playlistListingStyle", default: .cells)
|
||||
static let playlistListingStyle = Key<ListingStyle>("playlistListingStyle", default: .list)
|
||||
static let channelPlaylistListingStyle = Key<ListingStyle>("channelPlaylistListingStyle", default: .cells)
|
||||
static let searchListingStyle = Key<ListingStyle>("searchListingStyle", default: .cells)
|
||||
static let hideShorts = Key<Bool>("hideShorts", default: false)
|
||||
|
@ -63,41 +63,17 @@ struct PlaylistsView: View {
|
||||
|
||||
var body: some View {
|
||||
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) {
|
||||
if showCacheStatus {
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
VerticalCells(items: items, allowEmpty: true) { if shouldDisplayHeader { header } }
|
||||
.environment(\.scrollViewBottomPadding, 70)
|
||||
.environment(\.currentPlaylistID, currentPlaylist?.id)
|
||||
.environment(\.listingStyle, playlistListingStyle)
|
||||
.environment(\.hideShorts, hideShorts)
|
||||
|
||||
CacheStatusHeader(
|
||||
refreshTime: cache.getFormattedPlaylistTime(account: accounts.current),
|
||||
isLoading: model.isLoading
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.environment(\.scrollViewBottomPadding, 70)
|
||||
#endif
|
||||
}
|
||||
.environment(\.currentPlaylistID, currentPlaylist?.id)
|
||||
.environment(\.listingStyle, playlistListingStyle)
|
||||
.environment(\.hideShorts, hideShorts)
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -268,37 +244,6 @@ struct PlaylistsView: View {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(tvOS)
|
||||
var toolbar: some View {
|
||||
HStack {
|
||||
if model.isEmpty {
|
||||
Text("No Playlists")
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
Text("Current Playlist")
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
selectPlaylistButton
|
||||
}
|
||||
|
||||
if let playlist = currentPlaylist {
|
||||
editPlaylistButton
|
||||
|
||||
FavoriteButton(item: FavoriteItem(section: .playlist(accounts.current.id, playlist.id)))
|
||||
.labelStyle(.iconOnly)
|
||||
|
||||
playButtons
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
newPlaylistButton
|
||||
.padding(.leading, 40)
|
||||
}
|
||||
.labelStyle(.iconOnly)
|
||||
}
|
||||
#endif
|
||||
|
||||
func hintText(_ text: String) -> some View {
|
||||
VStack {
|
||||
Spacer()
|
||||
@ -341,12 +286,15 @@ struct PlaylistsView: View {
|
||||
|
||||
var selectPlaylistButton: some View {
|
||||
#if os(tvOS)
|
||||
Button(currentPlaylist?.title ?? "Select playlist") {
|
||||
Button {
|
||||
guard currentPlaylist != nil else {
|
||||
return
|
||||
}
|
||||
|
||||
selectedPlaylistID = model.all.next(after: currentPlaylist!)?.id ?? ""
|
||||
} label: {
|
||||
Text(currentPlaylist?.title ?? "Select playlist")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.lineLimit(1)
|
||||
.contextMenu {
|
||||
@ -405,6 +353,64 @@ struct PlaylistsView: View {
|
||||
}
|
||||
return model.find(id: selectedPlaylistID) ?? model.all.first
|
||||
}
|
||||
|
||||
var shouldDisplayHeader: Bool {
|
||||
#if os(tvOS)
|
||||
true
|
||||
#else
|
||||
showCacheStatus
|
||||
#endif
|
||||
}
|
||||
|
||||
var header: some View {
|
||||
HStack {
|
||||
if model.isEmpty {
|
||||
Text("No Playlists")
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
selectPlaylistButton
|
||||
}
|
||||
|
||||
if let playlist = currentPlaylist {
|
||||
editPlaylistButton
|
||||
|
||||
FavoriteButton(item: FavoriteItem(section: .playlist(accounts.current.id, playlist.id)))
|
||||
.labelStyle(.iconOnly)
|
||||
|
||||
playButtons
|
||||
}
|
||||
|
||||
newPlaylistButton
|
||||
|
||||
Spacer()
|
||||
|
||||
ListingStyleButtons(listingStyle: $playlistListingStyle)
|
||||
HideShortsButtons(hide: $hideShorts)
|
||||
|
||||
if let account = accounts.current, showCacheStatus {
|
||||
CacheStatusHeader(
|
||||
refreshTime: cache.getFormattedPlaylistTime(account: account),
|
||||
isLoading: model.isLoading
|
||||
)
|
||||
}
|
||||
|
||||
Button {
|
||||
model.load(force: true)
|
||||
loadResource()
|
||||
} label: {
|
||||
Label("Refresh", systemImage: "arrow.clockwise")
|
||||
.labelStyle(.iconOnly)
|
||||
}
|
||||
}
|
||||
.labelStyle(.iconOnly)
|
||||
.font(.caption)
|
||||
.imageScale(.small)
|
||||
.padding(.leading, 30)
|
||||
#if os(tvOS)
|
||||
.padding(.bottom, 15)
|
||||
.padding(.trailing, 30)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
struct PlaylistsView_Provider: PreviewProvider {
|
||||
|
@ -236,27 +236,12 @@ struct SearchView: View {
|
||||
if showRecentQueries {
|
||||
recentQueries
|
||||
} else {
|
||||
#if os(tvOS)
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
HStack(spacing: 0) {
|
||||
if accounts.app.supportsSearchFilters {
|
||||
filtersHorizontalStack
|
||||
}
|
||||
|
||||
FavoriteButton(item: favoriteItem)
|
||||
.id(favoriteItem?.id)
|
||||
.labelStyle(.iconOnly)
|
||||
.font(.system(size: 25))
|
||||
}
|
||||
|
||||
HorizontalCells(items: state.store.collection)
|
||||
.environment(\.loadMoreContentHandler) { state.loadNextPage() }
|
||||
VerticalCells(items: state.store.collection, allowEmpty: state.query.isEmpty) {
|
||||
if shouldDisplayHeader {
|
||||
header
|
||||
}
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
#else
|
||||
VerticalCells(items: state.store.collection, allowEmpty: state.query.isEmpty)
|
||||
.environment(\.loadMoreContentHandler) { state.loadNextPage() }
|
||||
#endif
|
||||
}
|
||||
.environment(\.loadMoreContentHandler) { state.loadNextPage() }
|
||||
|
||||
if noResults {
|
||||
Text("No results")
|
||||
|
@ -111,7 +111,7 @@ struct ChannelsView: View {
|
||||
Label("Refresh", systemImage: "arrow.clockwise")
|
||||
.labelStyle(.iconOnly)
|
||||
.imageScale(.small)
|
||||
.font(.caption2)
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -68,16 +68,13 @@ struct FeedView: View {
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
if !showCacheStatus {
|
||||
Spacer()
|
||||
}
|
||||
Button {
|
||||
feed.loadResources(force: true)
|
||||
} label: {
|
||||
Label("Refresh", systemImage: "arrow.clockwise")
|
||||
.labelStyle(.iconOnly)
|
||||
.imageScale(.small)
|
||||
.font(.caption2)
|
||||
.font(.caption)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ struct SubscriptionsPageButton: View {
|
||||
} label: {
|
||||
Text(subscriptionsViewPage.rawValue.capitalized)
|
||||
.frame(maxWidth: .infinity)
|
||||
.font(.caption2)
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,20 +39,9 @@ struct TrendingView: View {
|
||||
|
||||
var body: some View {
|
||||
Section {
|
||||
VStack(spacing: 0) {
|
||||
#if os(tvOS)
|
||||
toolbar
|
||||
HorizontalCells(items: trending)
|
||||
.padding(.top, 40)
|
||||
|
||||
Spacer()
|
||||
#else
|
||||
VerticalCells(items: trending)
|
||||
.environment(\.scrollViewBottomPadding, 70)
|
||||
#endif
|
||||
}
|
||||
.environment(\.listingStyle, trendingListingStyle)
|
||||
.environment(\.hideShorts, hideShorts)
|
||||
VerticalCells(items: trending) { if shouldDisplayHeader { header } }
|
||||
.environment(\.listingStyle, trendingListingStyle)
|
||||
.environment(\.hideShorts, hideShorts)
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
@ -66,9 +55,7 @@ struct TrendingView: View {
|
||||
.id(favoriteItem.id)
|
||||
}
|
||||
|
||||
if accounts.app.supportsTrendingCategories {
|
||||
categoryButton
|
||||
}
|
||||
categoryButton
|
||||
countryButton
|
||||
}
|
||||
#endif
|
||||
@ -182,9 +169,7 @@ struct TrendingView: View {
|
||||
Menu {
|
||||
countryButton
|
||||
|
||||
if accounts.app.supportsTrendingCategories {
|
||||
categoryButton
|
||||
}
|
||||
categoryButton
|
||||
|
||||
ListingStyleButtons(listingStyle: $trendingListingStyle)
|
||||
|
||||
@ -210,26 +195,28 @@ struct TrendingView: View {
|
||||
}
|
||||
#endif
|
||||
|
||||
private var categoryButton: some View {
|
||||
#if os(tvOS)
|
||||
Button(category.name) {
|
||||
self.category = category.next()
|
||||
}
|
||||
.contextMenu {
|
||||
ForEach(TrendingCategory.allCases) { category in
|
||||
Button(category.controlLabel) { self.category = category }
|
||||
@ViewBuilder private var categoryButton: some View {
|
||||
if accounts.app.supportsTrendingCategories {
|
||||
#if os(tvOS)
|
||||
Button(category.name) {
|
||||
self.category = category.next()
|
||||
}
|
||||
.contextMenu {
|
||||
ForEach(TrendingCategory.allCases) { category in
|
||||
Button(category.controlLabel) { self.category = category }
|
||||
}
|
||||
|
||||
Button("Cancel", role: .cancel) {}
|
||||
}
|
||||
|
||||
Button("Cancel", role: .cancel) {}
|
||||
}
|
||||
|
||||
#else
|
||||
Picker(category.controlLabel, selection: $category) {
|
||||
ForEach(TrendingCategory.allCases) { category in
|
||||
Label(category.controlLabel, systemImage: category.systemImage).tag(category)
|
||||
#else
|
||||
Picker(category.controlLabel, selection: $category) {
|
||||
ForEach(TrendingCategory.allCases) { category in
|
||||
Label(category.controlLabel, systemImage: category.systemImage).tag(category)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private var countryButton: some View {
|
||||
@ -249,6 +236,42 @@ struct TrendingView: View {
|
||||
private func updateFavoriteItem() {
|
||||
favoriteItem = FavoriteItem(section: .trending(country.rawValue, category.rawValue))
|
||||
}
|
||||
|
||||
var header: some View {
|
||||
HStack {
|
||||
Group {
|
||||
categoryButton
|
||||
countryButton
|
||||
}
|
||||
.font(.caption)
|
||||
|
||||
Spacer()
|
||||
ListingStyleButtons(listingStyle: $trendingListingStyle)
|
||||
HideShortsButtons(hide: $hideShorts)
|
||||
|
||||
Button {
|
||||
resource.load()
|
||||
.onFailure { self.error = $0 }
|
||||
.onSuccess { _ in self.error = nil }
|
||||
} label: {
|
||||
Label("Refresh", systemImage: "arrow.clockwise")
|
||||
.labelStyle(.iconOnly)
|
||||
.imageScale(.small)
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.padding(.leading, 30)
|
||||
.padding(.bottom, 15)
|
||||
.padding(.trailing, 30)
|
||||
}
|
||||
|
||||
var shouldDisplayHeader: Bool {
|
||||
#if os(tvOS)
|
||||
true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
struct TrendingView_Previews: PreviewProvider {
|
||||
|
@ -26,7 +26,7 @@ struct VerticalCells<Header: View>: View {
|
||||
|
||||
var body: some View {
|
||||
ScrollView(.vertical, showsIndicators: scrollViewShowsIndicators) {
|
||||
LazyVGrid(columns: columns, alignment: .center) {
|
||||
LazyVGrid(columns: adaptiveItem, alignment: .center) {
|
||||
Section(header: header) {
|
||||
ForEach(contentItems) { item in
|
||||
ContentItemView(item: item)
|
||||
@ -58,14 +58,6 @@ struct VerticalCells<Header: View>: View {
|
||||
}
|
||||
}
|
||||
|
||||
var columns: [GridItem] {
|
||||
#if os(tvOS)
|
||||
contentItems.count < 3 ? Array(repeating: GridItem(.fixed(500)), count: [contentItems.count, 1].max()!) : adaptiveItem
|
||||
#else
|
||||
adaptiveItem
|
||||
#endif
|
||||
}
|
||||
|
||||
var adaptiveItem: [GridItem] {
|
||||
if listingStyle == .list {
|
||||
return [.init(.flexible())]
|
||||
|
@ -17,7 +17,7 @@ struct HideShortsButtons: View {
|
||||
}
|
||||
}
|
||||
#if os(tvOS)
|
||||
.font(.caption2)
|
||||
.font(.caption)
|
||||
.imageScale(.small)
|
||||
#endif
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ struct ListingStyleButtons: View {
|
||||
} label: {
|
||||
Label(listingStyle.rawValue.capitalized, systemImage: listingStyle.systemImage)
|
||||
#if os(tvOS)
|
||||
.font(.caption2)
|
||||
.font(.caption)
|
||||
.imageScale(.small)
|
||||
#endif
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ struct PopularView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VerticalCells(items: videos)
|
||||
VerticalCells(items: videos) { if shouldDisplayHeader { header } }
|
||||
.onAppear {
|
||||
resource?.addObserver(store)
|
||||
resource?.loadIfNeeded()?
|
||||
@ -116,6 +116,36 @@ struct PopularView: View {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
var shouldDisplayHeader: Bool {
|
||||
#if os(tvOS)
|
||||
true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
}
|
||||
|
||||
var header: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
ListingStyleButtons(listingStyle: $popularListingStyle)
|
||||
HideShortsButtons(hide: $hideShorts)
|
||||
|
||||
Button {
|
||||
resource?.load()
|
||||
.onFailure { self.error = $0 }
|
||||
.onSuccess { _ in self.error = nil }
|
||||
} label: {
|
||||
Label("Refresh", systemImage: "arrow.clockwise")
|
||||
.labelStyle(.iconOnly)
|
||||
.imageScale(.small)
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.padding(.leading, 30)
|
||||
.padding(.bottom, 15)
|
||||
.padding(.trailing, 30)
|
||||
}
|
||||
}
|
||||
|
||||
struct PopularView_Previews: PreviewProvider {
|
||||
|
Loading…
Reference in New Issue
Block a user