1
0
mirror of https://github.com/yattee/yattee.git synced 2024-12-15 14:50:32 +05:30
yattee/Shared/Channels/ChannelVideosView.swift

426 lines
14 KiB
Swift
Raw Normal View History

2022-12-12 05:48:29 +05:30
import Defaults
2022-11-27 16:12:16 +05:30
import SDWebImageSwiftUI
2021-08-30 03:06:18 +05:30
import Siesta
import SwiftUI
struct ChannelVideosView: View {
2022-05-29 23:56:56 +05:30
var channel: Channel?
var showCloseButton = false
2021-08-30 03:06:18 +05:30
2021-10-27 04:29:59 +05:30
@State private var presentingShareSheet = false
2021-11-13 21:15:47 +05:30
@State private var shareURL: URL?
2022-03-26 19:07:55 +05:30
@State private var subscriptionToggleButtonDisabled = false
2021-10-27 04:29:59 +05:30
2022-11-27 16:12:16 +05:30
@State private var contentType = Channel.ContentType.videos
@StateObject private var contentTypeItems = Store<[ContentItem]>()
2022-12-17 18:54:09 +05:30
@State private var descriptionExpanded = false
2021-09-25 13:48:22 +05:30
@StateObject private var store = Store<Channel>()
@Environment(\.colorScheme) private var colorScheme
#if os(iOS)
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
#endif
@ObservedObject private var accounts = AccountsModel.shared
@ObservedObject private var feed = FeedModel.shared
@ObservedObject private var navigation = NavigationModel.shared
@ObservedObject private var recents = RecentsModel.shared
2022-12-11 20:45:42 +05:30
@ObservedObject private var subscriptions = SubscribedChannelsModel.shared
@Namespace private var focusNamespace
2021-08-30 03:06:18 +05:30
2022-12-12 05:48:29 +05:30
@Default(.channelPlaylistListingStyle) private var channelPlaylistListingStyle
2022-12-19 06:07:09 +05:30
@Default(.expandChannelDescription) private var expandChannelDescription
2022-12-12 05:48:29 +05:30
2022-05-29 23:56:56 +05:30
var presentedChannel: Channel? {
2022-11-27 16:12:16 +05:30
store.item ?? channel ?? recents.presentedChannel
2022-05-29 23:56:56 +05:30
}
2022-11-27 16:12:16 +05:30
var contentItems: [ContentItem] {
guard contentType != .videos else {
return ContentItem.array(of: presentedChannel?.videos ?? [])
}
return contentTypeItems.collection
}
2021-08-30 03:06:18 +05:30
var body: some View {
2021-11-28 20:07:55 +05:30
let content = VStack {
#if os(tvOS)
2022-11-27 16:12:16 +05:30
VStack {
HStack(spacing: 24) {
thumbnail
2022-11-27 16:12:16 +05:30
Text(navigationTitle)
.font(.headline)
2022-11-27 16:12:16 +05:30
.frame(alignment: .leading)
2022-11-27 16:12:16 +05:30
Spacer()
2021-11-02 03:26:18 +05:30
2022-11-27 16:12:16 +05:30
subscriptionsLabel
viewsLabel
2022-11-27 16:12:16 +05:30
subscriptionToggleButton
}
contentTypePicker
.pickerStyle(.automatic)
}
.frame(maxWidth: .infinity)
#endif
2022-11-27 16:12:16 +05:30
VerticalCells(items: contentItems) {
2022-12-17 18:54:09 +05:30
if let description = presentedChannel?.description, !description.isEmpty {
Button {
withAnimation(.spring()) {
descriptionExpanded.toggle()
}
} label: {
VStack(alignment: .leading) {
banner
ZStack(alignment: .topTrailing) {
Text(description)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(descriptionExpanded ? 50 : 1)
.multilineTextAlignment(.leading)
#if os(tvOS)
.foregroundColor(.primary)
#else
.foregroundColor(.secondary)
#endif
}
}
2022-12-19 00:13:16 +05:30
.padding(.bottom, 10)
2022-12-17 18:54:09 +05:30
}
.buttonStyle(.plain)
} else {
banner
}
2022-11-27 16:12:16 +05:30
}
.environment(\.inChannelView, true)
2022-12-12 05:48:29 +05:30
.environment(\.listingStyle, channelPlaylistListingStyle)
#if os(tvOS)
.prefersDefaultFocus(in: focusNamespace)
#endif
}
2021-11-28 20:07:55 +05:30
2021-08-30 03:06:18 +05:30
#if !os(tvOS)
.toolbar {
2022-11-27 16:12:16 +05:30
#if os(iOS)
ToolbarItem(placement: .principal) {
channelMenu
}
#endif
2022-08-26 13:28:08 +05:30
ToolbarItem(placement: .cancellationAction) {
if showCloseButton {
2022-08-26 13:28:08 +05:30
Button {
2022-08-25 22:39:55 +05:30
withAnimation(Constants.overlayAnimation) {
2022-07-09 05:51:04 +05:30
navigation.presentingChannel = false
}
2022-08-26 13:28:08 +05:30
} label: {
Label("Close", systemImage: "xmark")
2022-05-29 23:56:56 +05:30
}
2022-11-27 16:12:16 +05:30
.buttonStyle(.plain)
2022-05-29 23:56:56 +05:30
}
}
2022-11-27 16:12:16 +05:30
#if !os(iOS)
ToolbarItem(placement: .navigation) {
thumbnail
}
2022-12-12 05:48:29 +05:30
ToolbarItem {
ListingStyleButtons(listingStyle: $channelPlaylistListingStyle)
}
2022-11-27 16:12:16 +05:30
ToolbarItem {
contentTypePicker
}
2021-10-27 04:29:59 +05:30
2022-11-27 16:12:16 +05:30
ToolbarItem {
HStack(spacing: 3) {
2022-11-27 16:12:16 +05:30
subscriptionsLabel
viewsLabel
}
2022-11-27 16:12:16 +05:30
}
2022-05-29 23:56:56 +05:30
2022-11-27 16:12:16 +05:30
ToolbarItem {
if let contentItem = presentedChannel?.contentItem {
ShareButton(contentItem: contentItem)
}
}
2022-11-27 16:12:16 +05:30
ToolbarItem {
subscriptionToggleButton
2022-11-27 16:12:16 +05:30
.layoutPriority(2)
}
2022-11-27 16:12:16 +05:30
ToolbarItem {
if let presentedChannel {
FavoriteButton(item: FavoriteItem(section: .channel(accounts.app.appType.rawValue, presentedChannel.id, presentedChannel.name)))
2022-05-29 23:56:56 +05:30
}
}
ToolbarItem {
toggleWatchedButton
}
2022-11-27 16:12:16 +05:30
#endif
}
#endif
2021-11-08 21:59:35 +05:30
.onAppear {
2022-12-19 06:07:09 +05:30
descriptionExpanded = expandChannelDescription
2022-12-14 04:37:32 +05:30
if let channel,
let cache = ChannelsCacheModel.shared.retrieve(channel.cacheKey),
store.item.isNil
{
store.replace(cache)
}
resource?.loadIfNeeded()?.onSuccess { response in
if let channel: Channel = response.typedContent() {
ChannelsCacheModel.shared.store(channel)
}
}
2021-11-08 21:59:35 +05:30
}
2022-11-27 16:12:16 +05:30
.onChange(of: contentType) { _ in
resource?.load()
}
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
2022-05-29 23:56:56 +05:30
#if !os(tvOS)
2021-11-08 21:59:35 +05:30
.navigationTitle(navigationTitle)
2022-05-29 23:56:56 +05:30
#endif
2021-11-28 20:07:55 +05:30
return Group {
if #available(macOS 12.0, *) {
content
#if os(tvOS)
.background(Color.background(scheme: colorScheme))
#endif
2021-11-28 20:07:55 +05:30
#if !os(iOS)
.focusScope(focusNamespace)
#endif
} else {
content
}
}
}
2022-11-27 16:12:16 +05:30
var thumbnail: some View {
ChannelAvatarView(channel: store.item)
2022-11-27 16:12:16 +05:30
#if os(tvOS)
.frame(width: 80, height: 80, alignment: .trailing)
2022-11-27 16:12:16 +05:30
#else
.frame(width: 30, height: 30, alignment: .trailing)
2022-11-27 16:12:16 +05:30
#endif
}
2022-05-29 23:56:56 +05:30
2022-11-27 16:12:16 +05:30
@ViewBuilder var banner: some View {
if let banner = presentedChannel?.bannerURL {
WebImage(url: banner)
.resizable()
.placeholder { Color.clear.frame(height: 0) }
.scaledToFit()
.clipShape(RoundedRectangle(cornerRadius: 3))
}
}
var subscriptionsLabel: some View {
Group {
if let subscribers = store.item?.subscriptionsString {
HStack(spacing: 0) {
Text(subscribers)
Image(systemName: "person.2.fill")
}
} else if store.item.isNil {
HStack(spacing: 0) {
Text("1234")
.redacted(reason: .placeholder)
Image(systemName: "person.2.fill")
}
2022-11-27 16:12:16 +05:30
}
}
.imageScale(.small)
2022-11-27 16:12:16 +05:30
.foregroundColor(.secondary)
}
var viewsLabel: some View {
HStack(spacing: 0) {
if let views = store.item?.totalViewsString {
2022-11-27 16:12:16 +05:30
Text(views)
Image(systemName: "eye.fill")
.imageScale(.small)
}
}
.foregroundColor(.secondary)
}
#if !os(tvOS)
var channelMenu: some View {
Menu {
if let channel = presentedChannel {
contentTypePicker
Section {
subscriptionToggleButton
2022-12-11 20:30:20 +05:30
FavoriteButton(item: FavoriteItem(section: .channel(accounts.app.appType.rawValue, channel.id, channel.name)))
2022-11-27 16:12:16 +05:30
}
2022-12-12 05:48:29 +05:30
if subscriptions.isSubscribing(channel.id) {
toggleWatchedButton
}
2022-12-12 05:48:29 +05:30
ListingStyleButtons(listingStyle: $channelPlaylistListingStyle)
2022-11-27 16:12:16 +05:30
}
} label: {
HStack(spacing: 12) {
thumbnail
VStack(alignment: .leading) {
Text(presentedChannel?.name ?? "Channel")
.font(.headline)
.foregroundColor(.primary)
.layoutPriority(1)
.frame(minWidth: 160, alignment: .leading)
2022-11-27 16:12:16 +05:30
Group {
HStack(spacing: 12) {
subscriptionsLabel
if presentedChannel?.verified ?? false {
Image(systemName: "checkmark.seal.fill")
.imageScale(.small)
2022-11-27 16:12:16 +05:30
}
viewsLabel
}
.frame(minWidth: 160, alignment: .leading)
2022-11-27 16:12:16 +05:30
}
.font(.caption2.bold())
.foregroundColor(.secondary)
}
Image(systemName: "chevron.down.circle.fill")
.foregroundColor(.accentColor)
.imageScale(.small)
}
.frame(maxWidth: 320)
2022-11-27 16:12:16 +05:30
}
}
#endif
private var contentTypePicker: some View {
Picker("Content type", selection: $contentType) {
if let channel = presentedChannel {
2022-12-04 17:31:05 +05:30
ForEach(Channel.ContentType.allCases, id: \.self) { type in
if channel.hasData(for: type) {
Label(type.description, systemImage: type.systemImage).tag(type)
}
2022-11-27 16:12:16 +05:30
}
}
}
}
private var resource: Resource? {
guard let channel = presentedChannel else { return nil }
let data = contentType != .videos ? channel.tabs.first(where: { $0.contentType == contentType })?.data : nil
let resource = accounts.api.channel(channel.id, contentType: contentType, data: data)
if contentType == .videos {
resource.addObserver(store)
} else {
resource.addObserver(contentTypeItems)
}
2021-09-25 13:48:22 +05:30
return resource
}
2022-05-29 23:56:56 +05:30
@ViewBuilder private var subscriptionToggleButton: some View {
if let channel = presentedChannel {
Group {
if accounts.app.supportsSubscriptions && accounts.signedIn {
if subscriptions.isSubscribing(channel.id) {
2022-08-22 04:07:52 +05:30
Button {
2022-05-29 23:56:56 +05:30
subscriptionToggleButtonDisabled = true
subscriptions.unsubscribe(channel.id) {
subscriptionToggleButtonDisabled = false
}
2022-08-22 04:07:52 +05:30
} label: {
Label("Unsubscribe", systemImage: "xmark.circle")
2022-08-22 04:07:52 +05:30
#if os(iOS)
.labelStyle(.automatic)
#else
.labelStyle(.titleOnly)
#endif
2022-03-26 19:07:55 +05:30
}
2022-05-29 23:56:56 +05:30
} else {
2022-08-22 04:07:52 +05:30
Button {
2022-05-29 23:56:56 +05:30
subscriptionToggleButtonDisabled = true
subscriptions.subscribe(channel.id) {
subscriptionToggleButtonDisabled = false
navigation.sidebarSectionChanged.toggle()
}
2022-08-22 04:07:52 +05:30
} label: {
2022-08-23 20:40:14 +05:30
Label("Subscribe", systemImage: "circle")
2022-08-22 04:07:52 +05:30
#if os(iOS)
.labelStyle(.automatic)
#else
.labelStyle(.titleOnly)
#endif
2021-10-21 03:51:50 +05:30
}
}
}
}
2022-05-29 23:56:56 +05:30
.disabled(subscriptionToggleButtonDisabled)
2021-08-30 03:06:18 +05:30
}
}
2021-10-27 04:29:59 +05:30
private var navigationTitle: String {
2022-11-27 16:12:16 +05:30
presentedChannel?.name ?? "No channel"
}
@ViewBuilder var toggleWatchedButton: some View {
if let channel = presentedChannel {
if feed.canMarkChannelAsWatched(channel.id) {
markChannelAsWatchedButton
} else {
markChannelAsUnwatchedButton
}
}
}
var markChannelAsWatchedButton: some View {
Button {
guard let channel = presentedChannel else { return }
feed.markChannelAsWatched(channel.id)
} label: {
Label("Mark channel feed as watched", systemImage: "checkmark.circle.fill")
}
.disabled(!feed.canMarkAllFeedAsWatched)
}
var markChannelAsUnwatchedButton: some View {
Button {
guard let channel = presentedChannel else { return }
feed.markChannelAsUnwatched(channel.id)
} label: {
Label("Mark channel feed as unwatched", systemImage: "checkmark.circle")
}
}
2021-08-30 03:06:18 +05:30
}
2022-08-23 20:40:14 +05:30
struct ChannelVideosView_Previews: PreviewProvider {
static var previews: some View {
2022-12-17 18:54:09 +05:30
#if os(macOS)
2022-11-27 16:12:16 +05:30
ChannelVideosView(channel: Video.fixture.channel)
.environment(\.navigationStyle, .sidebar)
2022-12-17 18:54:09 +05:30
#else
NavigationView {
ChannelVideosView(channel: Video.fixture.channel)
}
#endif
2022-08-23 20:40:14 +05:30
}
}