mirror of
https://github.com/yattee/yattee.git
synced 2025-08-04 01:34:10 +00:00
Add channels thumbnails to cells and list
This commit is contained in:
76
Shared/Channels/ChannelLinkView.swift
Normal file
76
Shared/Channels/ChannelLinkView.swift
Normal file
@@ -0,0 +1,76 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct ChannelLinkView<ChannelLabel: View>: View {
|
||||
let channel: Channel
|
||||
let channelLabel: ChannelLabel
|
||||
|
||||
@Environment(\.inChannelView) private var inChannelView
|
||||
@Environment(\.navigationStyle) private var navigationStyle
|
||||
|
||||
init(
|
||||
channel: Channel,
|
||||
@ViewBuilder channelLabel: () -> ChannelLabel
|
||||
) {
|
||||
self.channel = channel
|
||||
self.channelLabel = channelLabel()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
channelControl
|
||||
}
|
||||
|
||||
@ViewBuilder private var channelControl: some View {
|
||||
if !channel.name.isEmpty {
|
||||
#if os(tvOS)
|
||||
channelLabel
|
||||
#else
|
||||
if navigationStyle == .tab {
|
||||
channelNavigationLink
|
||||
} else {
|
||||
channelButton
|
||||
#if os(macOS)
|
||||
.onHover(perform: onHover(_:))
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private var channelNavigationLink: some View {
|
||||
NavigationLink(destination: ChannelVideosView(channel: channel)) {
|
||||
channelLabel
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private var channelButton: some View {
|
||||
Button {
|
||||
guard !inChannelView else {
|
||||
return
|
||||
}
|
||||
|
||||
NavigationModel.shared.openChannel(
|
||||
channel,
|
||||
navigationStyle: navigationStyle
|
||||
)
|
||||
} label: {
|
||||
channelLabel
|
||||
}
|
||||
#if os(tvOS)
|
||||
.buttonStyle(.card)
|
||||
#else
|
||||
.buttonStyle(.plain)
|
||||
#endif
|
||||
.help("\(channel.name) Channel")
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
private func onHover(_ inside: Bool) {
|
||||
if inside {
|
||||
NSCursor.pointingHand.push()
|
||||
} else {
|
||||
NSCursor.pop()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
@@ -11,4 +11,20 @@ struct Constants {
|
||||
0.6
|
||||
#endif
|
||||
}
|
||||
|
||||
static var channelThumbnailSize: Double {
|
||||
#if os(tvOS)
|
||||
50
|
||||
#else
|
||||
30
|
||||
#endif
|
||||
}
|
||||
|
||||
static var channelDetailsStackSpacing: Double {
|
||||
#if os(tvOS)
|
||||
12
|
||||
#else
|
||||
6
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@@ -44,7 +44,7 @@ extension Defaults.Keys {
|
||||
#if os(iOS)
|
||||
static let lockPortraitWhenBrowsing = Key<Bool>("lockPortraitWhenBrowsing", default: UIDevice.current.userInterfaceIdiom == .phone)
|
||||
#endif
|
||||
static let channelOnThumbnail = Key<Bool>("channelOnThumbnail", default: true)
|
||||
static let channelOnThumbnail = Key<Bool>("channelOnThumbnail", default: false)
|
||||
static let timeOnThumbnail = Key<Bool>("timeOnThumbnail", default: true)
|
||||
static let roundedThumbnails = Key<Bool>("roundedThumbnails", default: true)
|
||||
static let thumbnailsQuality = Key<ThumbnailsQuality>("thumbnailsQuality", default: .highest)
|
||||
|
@@ -114,11 +114,11 @@ struct PlayerControls: View {
|
||||
Text(player.currentVideo?.displayAuthor ?? "")
|
||||
.fontWeight(.semibold)
|
||||
.shadow(radius: 10)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(.init(white: 0.8))
|
||||
.font(.system(size: playerControlsLayout.authorLineFontSize))
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.offset(y: -40)
|
||||
}
|
||||
|
@@ -74,21 +74,22 @@ struct VideoBanner: View {
|
||||
|
||||
HStack {
|
||||
HStack {
|
||||
if !inChannelView,
|
||||
let video,
|
||||
let url = video.channel.thumbnailURLOrCached
|
||||
{
|
||||
ThumbnailView(url: url)
|
||||
.frame(width: 30, height: 30)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Group {
|
||||
if let video {
|
||||
if !inChannelView, !video.isLocal || video.localStreamIsRemoteURL {
|
||||
channelControl
|
||||
.font(.subheadline)
|
||||
ChannelLinkView(channel: video.channel) {
|
||||
HStack(spacing: Constants.channelDetailsStackSpacing) {
|
||||
if let url = video.channel.thumbnailURLOrCached {
|
||||
ThumbnailView(url: url)
|
||||
.frame(width: Constants.channelThumbnailSize, height: Constants.channelThumbnailSize)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
channelLabel
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#if os(iOS)
|
||||
if DocumentsModel.shared.isDocument(video) {
|
||||
@@ -220,7 +221,7 @@ struct VideoBanner: View {
|
||||
|
||||
private var thumbnailWidth: Double {
|
||||
#if os(tvOS)
|
||||
250
|
||||
356
|
||||
#else
|
||||
120
|
||||
#endif
|
||||
@@ -228,7 +229,7 @@ struct VideoBanner: View {
|
||||
|
||||
private var thumbnailHeight: Double {
|
||||
#if os(tvOS)
|
||||
140
|
||||
200
|
||||
#else
|
||||
72
|
||||
#endif
|
||||
@@ -308,50 +309,7 @@ struct VideoBanner: View {
|
||||
(progressViewValue / progressViewTotal) * 100 > Double(Defaults[.watchedThreshold])
|
||||
}
|
||||
|
||||
@ViewBuilder private var channelControl: some View {
|
||||
if let video, !video.displayAuthor.isEmpty {
|
||||
#if os(tvOS)
|
||||
displayAuthor
|
||||
#else
|
||||
if navigationStyle == .tab, inNavigationView {
|
||||
channelNavigationLink
|
||||
} else {
|
||||
channelButton
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private var channelNavigationLink: some View {
|
||||
if let channel = video?.channel {
|
||||
NavigationLink(destination: ChannelVideosView(channel: channel)) {
|
||||
displayAuthor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private var channelButton: some View {
|
||||
if let video {
|
||||
Button {
|
||||
guard !inChannelView else { return }
|
||||
|
||||
NavigationModel.shared.openChannel(
|
||||
video.channel,
|
||||
navigationStyle: navigationStyle
|
||||
)
|
||||
} label: {
|
||||
displayAuthor
|
||||
}
|
||||
#if os(tvOS)
|
||||
.buttonStyle(.card)
|
||||
#else
|
||||
.buttonStyle(.plain)
|
||||
#endif
|
||||
.help("\(video.channel.name) Channel")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private var displayAuthor: some View {
|
||||
@ViewBuilder private var channelLabel: some View {
|
||||
if let video, !video.displayAuthor.isEmpty {
|
||||
Text(video.displayAuthor)
|
||||
.fontWeight(.semibold)
|
||||
|
@@ -41,7 +41,7 @@ struct VideoCell: View {
|
||||
Button(action: playAction) {
|
||||
content
|
||||
#if os(tvOS)
|
||||
.frame(width: 580, height: 470)
|
||||
.frame(width: 580, height: channelOnThumbnail ? 470 : 500)
|
||||
#endif
|
||||
}
|
||||
.opacity(contentOpacity)
|
||||
@@ -166,17 +166,22 @@ struct VideoCell: View {
|
||||
videoDetail(video.displayTitle, lineLimit: 5)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
HStack(spacing: Constants.channelDetailsStackSpacing) {
|
||||
if !inChannelView,
|
||||
let video,
|
||||
let url = video.channel.thumbnailURLOrCached
|
||||
{
|
||||
ThumbnailView(url: url)
|
||||
.frame(width: 30, height: 30)
|
||||
.clipShape(Circle())
|
||||
ChannelLinkView(channel: video.channel) {
|
||||
ThumbnailView(url: url)
|
||||
.frame(width: Constants.channelThumbnailSize, height: Constants.channelThumbnailSize)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
}
|
||||
|
||||
if !channelOnThumbnail, !inChannelView {
|
||||
channelControl(badge: false)
|
||||
ChannelLinkView(channel: video.channel) {
|
||||
channelLabel(badge: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,14 +264,22 @@ struct VideoCell: View {
|
||||
#if os(tvOS)
|
||||
.frame(minHeight: 60, alignment: .top)
|
||||
#elseif os(macOS)
|
||||
.frame(minHeight: 32, alignment: .top)
|
||||
.frame(minHeight: 35, alignment: .top)
|
||||
#else
|
||||
.frame(minHeight: 40, alignment: .top)
|
||||
.frame(minHeight: 43, alignment: .top)
|
||||
#endif
|
||||
if !channelOnThumbnail, !inChannelView {
|
||||
channelControl(badge: false)
|
||||
.padding(.top, 4)
|
||||
.padding(.bottom, 6)
|
||||
ChannelLinkView(channel: video.channel) {
|
||||
HStack(spacing: Constants.channelDetailsStackSpacing) {
|
||||
if let url = video.channel.thumbnailURLOrCached {
|
||||
ThumbnailView(url: url)
|
||||
.frame(width: Constants.channelThumbnailSize, height: Constants.channelThumbnailSize)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
channelLabel(badge: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
@@ -274,20 +287,23 @@ struct VideoCell: View {
|
||||
#if os(tvOS)
|
||||
.frame(minHeight: channelOnThumbnail ? 80 : 120, alignment: .top)
|
||||
#elseif os(macOS)
|
||||
.frame(minHeight: 35, alignment: .top)
|
||||
.frame(minHeight: channelOnThumbnail ? 52 : 75, alignment: .top)
|
||||
#else
|
||||
.frame(minHeight: 50, alignment: .top)
|
||||
.frame(minHeight: channelOnThumbnail ? 50 : 70, alignment: .top)
|
||||
#endif
|
||||
.padding(.bottom, 4)
|
||||
|
||||
HStack(spacing: 8) {
|
||||
if !inChannelView,
|
||||
if channelOnThumbnail,
|
||||
!inChannelView,
|
||||
let video,
|
||||
let url = video.channel.thumbnailURLOrCached
|
||||
{
|
||||
ThumbnailView(url: url)
|
||||
.frame(width: 30, height: 30)
|
||||
.clipShape(Circle())
|
||||
ChannelLinkView(channel: video.channel) {
|
||||
ThumbnailView(url: url)
|
||||
.frame(width: Constants.channelThumbnailSize, height: Constants.channelThumbnailSize)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
}
|
||||
|
||||
if let date = video.publishedDate {
|
||||
@@ -314,7 +330,7 @@ struct VideoCell: View {
|
||||
}
|
||||
.lineLimit(1)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity, minHeight: 30, alignment: .topLeading)
|
||||
.frame(maxWidth: .infinity, minHeight: 35, alignment: .topLeading)
|
||||
#if os(tvOS)
|
||||
.padding(.bottom, 10)
|
||||
#endif
|
||||
@@ -327,47 +343,6 @@ struct VideoCell: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func channelControl(badge: Bool = true) -> some View {
|
||||
if !video.channel.name.isEmpty {
|
||||
#if os(tvOS)
|
||||
channelButton(badge: badge)
|
||||
#else
|
||||
if navigationStyle == .tab {
|
||||
channelNavigationLink(badge: badge)
|
||||
} else {
|
||||
channelButton(badge: badge)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func channelNavigationLink(badge: Bool = true) -> some View {
|
||||
NavigationLink(destination: ChannelVideosView(channel: video.channel)) {
|
||||
channelLabel(badge: badge)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func channelButton(badge: Bool = true) -> some View {
|
||||
Button {
|
||||
guard !inChannelView else {
|
||||
return
|
||||
}
|
||||
|
||||
NavigationModel.shared.openChannel(
|
||||
video.channel,
|
||||
navigationStyle: navigationStyle
|
||||
)
|
||||
} label: {
|
||||
channelLabel(badge: badge)
|
||||
}
|
||||
#if os(tvOS)
|
||||
.buttonStyle(.card)
|
||||
#else
|
||||
.buttonStyle(.plain)
|
||||
#endif
|
||||
.help("\(video.channel.name) Channel")
|
||||
}
|
||||
|
||||
@ViewBuilder private func channelLabel(badge: Bool = true) -> some View {
|
||||
if badge {
|
||||
DetailBadge(text: video.author, style: .prominent)
|
||||
@@ -415,7 +390,9 @@ struct VideoCell: View {
|
||||
Spacer()
|
||||
|
||||
if channelOnThumbnail, !inChannelView {
|
||||
channelControl()
|
||||
ChannelLinkView(channel: video.channel) {
|
||||
channelLabel()
|
||||
}
|
||||
}
|
||||
}
|
||||
#if os(tvOS)
|
||||
|
Reference in New Issue
Block a user