mirror of
https://github.com/yattee/yattee.git
synced 2024-12-13 22:00:31 +05:30
- Improved Subscription View for tvOS
- Refined Subscriptions view on tvOS - Refined Subscriptions view on tvOS - Refined Subscriptions view on tvOS - Refined Subscriptions view on tvOS
This commit is contained in:
parent
d6cfadab9a
commit
65ec675859
@ -4,6 +4,8 @@ import SwiftUI
|
|||||||
|
|
||||||
struct FeedView: View {
|
struct FeedView: View {
|
||||||
@ObservedObject private var feed = FeedModel.shared
|
@ObservedObject private var feed = FeedModel.shared
|
||||||
|
@ObservedObject private var subscriptions = SubscribedChannelsModel.shared
|
||||||
|
@ObservedObject private var feedCount = UnwatchedFeedCountModel.shared
|
||||||
|
|
||||||
@Default(.showCacheStatus) private var showCacheStatus
|
@Default(.showCacheStatus) private var showCacheStatus
|
||||||
|
|
||||||
@ -12,10 +14,153 @@ struct FeedView: View {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
var videos: [ContentItem] {
|
var videos: [ContentItem] {
|
||||||
ContentItem.array(of: feed.videos)
|
guard let selectedChannel = selectedChannel else {
|
||||||
|
return ContentItem.array(of: feed.videos)
|
||||||
|
}
|
||||||
|
return ContentItem.array(of: feed.videos.filter {
|
||||||
|
$0.channel.id == selectedChannel.id
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var channels: [Channel] {
|
||||||
|
feed.videos.map {
|
||||||
|
$0.channel
|
||||||
|
}.unique()
|
||||||
|
}
|
||||||
|
|
||||||
|
@State private var selectedChannel: Channel?
|
||||||
|
@FocusState private var focusedChannel: String?
|
||||||
|
@State private var feedChannelsViewVisible: Bool = false
|
||||||
|
private var navigation = NavigationModel.shared
|
||||||
|
private let dismiss_channel_list_id = "dismiss_channel_list_id"
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
#if os(tvOS)
|
||||||
|
GeometryReader { geometry in
|
||||||
|
ZStack {
|
||||||
|
// selected channel feed view
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
// sidebar - show channels
|
||||||
|
if feedChannelsViewVisible {
|
||||||
|
Spacer()
|
||||||
|
.frame(width: geometry.size.width * 0.3)
|
||||||
|
}
|
||||||
|
selectedFeedView
|
||||||
|
}
|
||||||
|
.disabled(feedChannelsViewVisible)
|
||||||
|
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||||
|
|
||||||
|
if feedChannelsViewVisible {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
// sidebar - show channels
|
||||||
|
feedChannelsView
|
||||||
|
.padding(.all)
|
||||||
|
.frame(width: geometry.size.width * 0.3)
|
||||||
|
.background()
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||||
|
.contentShape(RoundedRectangle(cornerRadius: 16))
|
||||||
|
Rectangle()
|
||||||
|
.fill(.clear)
|
||||||
|
.id(dismiss_channel_list_id)
|
||||||
|
.focusable()
|
||||||
|
.focused(self.$focusedChannel, equals: dismiss_channel_list_id)
|
||||||
|
}
|
||||||
|
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||||
|
.clipped()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
selectedFeedView
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
var feedChannelsView: some View {
|
||||||
|
ScrollViewReader { proxy in
|
||||||
|
VStack {
|
||||||
|
Text("Channels")
|
||||||
|
.font(.subheadline)
|
||||||
|
if #available(tvOS 17.0, *) {
|
||||||
|
List(selection: $selectedChannel) {
|
||||||
|
Button(action: {
|
||||||
|
self.selectedChannel = nil
|
||||||
|
self.feedChannelsViewVisible = false
|
||||||
|
}) {
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
Image(systemName: RecentsModel.symbolSystemImage("A"))
|
||||||
|
.imageScale(.large)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
.frame(width: 35, height: 35)
|
||||||
|
Text("All")
|
||||||
|
Spacer()
|
||||||
|
feedCount.unwatchedText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.all)
|
||||||
|
.background(RoundedRectangle(cornerRadius: 8.0)
|
||||||
|
.fill(self.selectedChannel == nil ? Color.secondary : Color.clear))
|
||||||
|
.font(.caption)
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.focused(self.$focusedChannel, equals: "all")
|
||||||
|
|
||||||
|
ForEach(channels, id: \.self) { channel in
|
||||||
|
Button(action: {
|
||||||
|
self.selectedChannel = channel
|
||||||
|
self.feedChannelsViewVisible = false
|
||||||
|
}) {
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
ChannelAvatarView(channel: channel, subscribedBadge: false)
|
||||||
|
.frame(width: 50, height: 50)
|
||||||
|
Text(channel.name)
|
||||||
|
.lineLimit(1)
|
||||||
|
Spacer()
|
||||||
|
if let unwatchedCount = feedCount.unwatchedByChannelText(channel) {
|
||||||
|
unwatchedCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.all)
|
||||||
|
.background(RoundedRectangle(cornerRadius: 8.0)
|
||||||
|
.fill(self.selectedChannel == channel ? Color.secondary : Color.clear))
|
||||||
|
.font(.caption)
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.focused(self.$focusedChannel, equals: channel.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: self.focusedChannel, {
|
||||||
|
if self.focusedChannel == "all" {
|
||||||
|
withAnimation {
|
||||||
|
self.selectedChannel = nil
|
||||||
|
}
|
||||||
|
} else if self.focusedChannel == dismiss_channel_list_id {
|
||||||
|
self.feedChannelsViewVisible = false
|
||||||
|
} else {
|
||||||
|
withAnimation {
|
||||||
|
self.selectedChannel = channels.first {
|
||||||
|
$0.id == self.focusedChannel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onAppear {
|
||||||
|
guard let selectedChannel = self.selectedChannel else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
proxy.scrollTo(selectedChannel, anchor: .top)
|
||||||
|
}
|
||||||
|
.onExitCommand {
|
||||||
|
withAnimation {
|
||||||
|
self.feedChannelsViewVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback on earlier versions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedFeedView: some View {
|
||||||
VerticalCells(items: videos) { if shouldDisplayHeader { header } }
|
VerticalCells(items: videos) { if shouldDisplayHeader { header } }
|
||||||
.environment(\.loadMoreContentHandler) { feed.loadNextPage() }
|
.environment(\.loadMoreContentHandler) { feed.loadNextPage() }
|
||||||
.onAppear {
|
.onAppear {
|
||||||
@ -49,33 +194,52 @@ struct FeedView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var header: some View {
|
var header: some View {
|
||||||
HStack {
|
HStack(spacing: 16) {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
SubscriptionsPageButton()
|
Button(action: {
|
||||||
ListingStyleButtons(listingStyle: $subscriptionsListingStyle)
|
withAnimation {
|
||||||
HideWatchedButtons()
|
self.feedChannelsViewVisible = true
|
||||||
HideShortsButtons()
|
self.focusedChannel = selectedChannel?.id ?? "all"
|
||||||
#endif
|
}
|
||||||
|
}) {
|
||||||
if showCacheStatus {
|
Label("Channels", systemImage: "filemenu.and.selection")
|
||||||
Spacer()
|
|
||||||
|
|
||||||
CacheStatusHeader(
|
|
||||||
refreshTime: feed.formattedFeedTime,
|
|
||||||
isLoading: feed.isLoading
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#if os(tvOS)
|
|
||||||
Button {
|
|
||||||
feed.loadResources(force: true)
|
|
||||||
} label: {
|
|
||||||
Label("Refresh", systemImage: "arrow.clockwise")
|
|
||||||
.labelStyle(.iconOnly)
|
.labelStyle(.iconOnly)
|
||||||
.imageScale(.small)
|
.imageScale(.small)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
|
.opacity(feedChannelsViewVisible ? 0 : 1)
|
||||||
|
.frame(minWidth: (feedChannelsViewVisible ? 0 : nil), maxWidth: (feedChannelsViewVisible ? 0 : nil))
|
||||||
|
channelHeaderView
|
||||||
|
if (selectedChannel == nil) {
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
if feedChannelsViewVisible == false {
|
||||||
|
ListingStyleButtons(listingStyle: $subscriptionsListingStyle)
|
||||||
|
HideWatchedButtons()
|
||||||
|
HideShortsButtons()
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if feedChannelsViewVisible == false {
|
||||||
|
if showCacheStatus {
|
||||||
|
|
||||||
|
CacheStatusHeader(
|
||||||
|
refreshTime: feed.formattedFeedTime,
|
||||||
|
isLoading: feed.isLoading
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
|
Button {
|
||||||
|
feed.loadResources(force: true)
|
||||||
|
} label: {
|
||||||
|
Label("Refresh", systemImage: "arrow.clockwise")
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.imageScale(.small)
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(.leading, 30)
|
.padding(.leading, 30)
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
@ -83,6 +247,46 @@ struct FeedView: View {
|
|||||||
.padding(.trailing, 30)
|
.padding(.trailing, 30)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var channelHeaderView: some View {
|
||||||
|
guard let selectedChannel = self.selectedChannel else {
|
||||||
|
return AnyView(
|
||||||
|
Text("All Channels")
|
||||||
|
.font(.caption)
|
||||||
|
.frame(alignment: .leading)
|
||||||
|
.lineLimit(1)
|
||||||
|
.padding(0)
|
||||||
|
.padding(.leading, 16)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnyView(
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
ChannelAvatarView(channel: selectedChannel, subscribedBadge: false)
|
||||||
|
.id("channel-avatar-\(selectedChannel.id)")
|
||||||
|
.frame(width: 80, height: 80)
|
||||||
|
Text("\(selectedChannel.name)")
|
||||||
|
.font(.caption)
|
||||||
|
.frame(alignment: .leading)
|
||||||
|
.lineLimit(1)
|
||||||
|
.fixedSize(horizontal: true, vertical: false)
|
||||||
|
Spacer()
|
||||||
|
if feedChannelsViewVisible == false {
|
||||||
|
Button(action: {
|
||||||
|
navigation.openChannel(selectedChannel, navigationStyle: .tab)
|
||||||
|
}) {
|
||||||
|
Text("Visit Channel")
|
||||||
|
.font(.caption)
|
||||||
|
.frame(alignment: .leading)
|
||||||
|
.lineLimit(1)
|
||||||
|
.fixedSize(horizontal: true, vertical: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(0)
|
||||||
|
.padding(.leading, 16)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var shouldDisplayHeader: Bool {
|
var shouldDisplayHeader: Bool {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
|
Loading…
Reference in New Issue
Block a user