yattee/Shared/Player/Video Details/VideoDescription.swift
2023-10-15 13:35:23 +02:00

202 lines
6.2 KiB
Swift

#if os(iOS)
import ActiveLabel
#endif
import Defaults
import Foundation
import SwiftUI
struct VideoDescription: View {
static let collapsedLines = 5
private var search: SearchModel { .shared }
@Default(.showKeywords) private var showKeywords
@Default(.expandVideoDescription) private var expandVideoDescription
var video: Video
var detailsSize: CGSize?
@Binding var expand: Bool
var description: String {
video.description ?? ""
}
var body: some View {
Group {
if !expandVideoDescription && !expand {
Button {
expand = true
} label: {
descriptionView
}
.buttonStyle(.plain)
} else {
descriptionView
}
}
.id(video.videoID)
}
var descriptionView: some View {
VStack {
#if os(iOS)
ActiveLabelDescriptionRepresentable(
description: description,
detailsSize: detailsSize,
expand: shouldExpand
)
#else
textDescription
#endif
keywords
}
.contentShape(Rectangle())
}
var shouldExpand: Bool {
expandVideoDescription || expand
}
@ViewBuilder var textDescription: some View {
#if !os(iOS)
Group {
if #available(macOS 12, *) {
Text(description)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(shouldExpand ? 500 : Self.collapsedLines)
#if !os(tvOS)
.textSelection(.enabled)
#endif
} else {
Text(description)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(shouldExpand ? 500 : Self.collapsedLines)
}
}
.multilineTextAlignment(.leading)
.font(.system(size: 14))
.lineSpacing(3)
#endif
}
@ViewBuilder var keywords: some View {
if showKeywords {
ScrollView(.horizontal, showsIndicators: showScrollIndicators) {
HStack {
ForEach(video.keywords, id: \.self) { keyword in
Button {
NavigationModel.shared.openSearchQuery(keyword)
} label: {
HStack(spacing: 0) {
Text("#")
.font(.system(size: 14).bold())
Text(keyword)
.frame(maxWidth: 500)
}
.font(.caption)
.foregroundColor(.white)
.padding(.vertical, 4)
.padding(.horizontal, 8)
.background(Color("KeywordBackgroundColor"))
.mask(RoundedRectangle(cornerRadius: 3))
}
.buttonStyle(.plain)
}
}
}
}
}
var showScrollIndicators: Bool {
#if os(macOS)
false
#else
true
#endif
}
}
#if os(iOS)
struct ActiveLabelDescriptionRepresentable: UIViewRepresentable {
var description: String
var detailsSize: CGSize?
var expand: Bool
@State private var label = ActiveLabel()
@Environment(\.openURL) private var openURL
var player = PlayerModel.shared
func makeUIView(context _: Context) -> some UIView {
customizeLabel()
return label
}
func updateUIView(_: UIViewType, context _: Context) {
updatePreferredMaxLayoutWidth()
updateNumberOfLines()
}
func customizeLabel() {
label.customize { label in
label.enabledTypes = [.url, .timestamp]
label.text = description
label.contentMode = .scaleAspectFill
label.font = .systemFont(ofSize: 14)
label.lineSpacing = 3
label.preferredMaxLayoutWidth = (detailsSize?.width ?? 330) - 30
label.URLColor = UIColor(Color.accentColor)
label.timestampColor = UIColor(Color.accentColor)
label.handleURLTap(urlTapHandler(_:))
label.handleTimestampTap(timestampTapHandler(_:))
}
updateNumberOfLines()
}
func updatePreferredMaxLayoutWidth() {
label.preferredMaxLayoutWidth = (detailsSize?.width ?? 330) - 30
}
func updateNumberOfLines() {
label.numberOfLines = expand ? 0 : VideoDescription.collapsedLines
}
func urlTapHandler(_ url: URL) {
var urlToOpen = url
if var components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
components.scheme = "yattee"
if let yatteeURL = components.url {
let parser = URLParser(url: urlToOpen, allowFileURLs: false)
let destination = parser.destination
if destination == .video,
parser.videoID == player.currentVideo?.videoID,
let time = parser.time
{
player.backend.seek(to: Double(time), seekType: .userInteracted)
return
}
if destination != nil {
urlToOpen = yatteeURL
}
}
}
openURL(urlToOpen)
}
func timestampTapHandler(_ timestamp: Timestamp) {
player.backend.seek(to: timestamp.timeInterval, seekType: .userInteracted)
}
}
#endif
struct VideoDescription_Previews: PreviewProvider {
static var previews: some View {
VideoDescription(video: .fixture, expand: .constant(false))
.injectFixtureEnvironmentObjects()
}
}