From d52ccf2ce6f5f1e8a1213bcdb7e0ca94e2e58396 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Sat, 22 Apr 2023 19:22:13 +0200 Subject: [PATCH] Add video description expanding --- Shared/Constants.swift | 8 ++++ Shared/Defaults.swift | 6 +++ .../Video Details/VideoDescription.swift | 47 +++++++++++++++++-- .../Player/Video Details/VideoDetails.swift | 20 +++++++- Shared/Settings/PlayerSettings.swift | 10 ++++ Shared/Settings/SettingsView.swift | 2 +- 6 files changed, 86 insertions(+), 7 deletions(-) diff --git a/Shared/Constants.swift b/Shared/Constants.swift index f7826ef6..b63a51eb 100644 --- a/Shared/Constants.swift +++ b/Shared/Constants.swift @@ -13,6 +13,14 @@ struct Constants { #endif } + static var isIPad: Bool { + #if os(iOS) + UIDevice.current.userInterfaceIdiom == .pad + #else + false + #endif + } + static var progressViewScale: Double { #if os(macOS) 0.4 diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index 58de5996..370d7482 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -132,6 +132,12 @@ extension Defaults.Keys { static let seekGestureSpeed = Key("seekGestureSpeed", default: 0.5) static let seekGestureSensitivity = Key("seekGestureSensitivity", default: 30.0) static let showKeywords = Key("showKeywords", default: false) + #if os(iOS) + static let expandVideoDescriptionDefault = Constants.isIPad + #else + static let expandVideoDescriptionDefault = true + #endif + static let expandVideoDescription = Key("expandVideoDescription", default: expandVideoDescriptionDefault) #if !os(tvOS) static let commentsPlacement = Key("commentsPlacement", default: .separate) #endif diff --git a/Shared/Player/Video Details/VideoDescription.swift b/Shared/Player/Video Details/VideoDescription.swift index e1cdc325..31fe34f8 100644 --- a/Shared/Player/Video Details/VideoDescription.swift +++ b/Shared/Player/Video Details/VideoDescription.swift @@ -6,27 +6,54 @@ 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) + ActiveLabelDescriptionRepresentable( + description: description, + detailsSize: detailsSize, + expand: shouldExpand + ) #else textDescription #endif keywords } - .id(video.videoID) + } + + var shouldExpand: Bool { + expandVideoDescription || expand } @ViewBuilder var textDescription: some View { @@ -34,14 +61,18 @@ struct VideoDescription: View { 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) } } - .frame(maxWidth: .infinity, alignment: .leading) + .multilineTextAlignment(.leading) .font(.system(size: 14)) .lineSpacing(3) #endif @@ -89,6 +120,7 @@ struct VideoDescription: View { struct ActiveLabelDescriptionRepresentable: UIViewRepresentable { var description: String var detailsSize: CGSize? + var expand: Bool @State private var label = ActiveLabel() @@ -103,12 +135,12 @@ struct VideoDescription: View { func updateUIView(_: UIViewType, context _: Context) { updatePreferredMaxLayoutWidth() + updateNumberOfLines() } func customizeLabel() { label.customize { label in label.enabledTypes = [.url, .timestamp] - label.numberOfLines = 0 label.text = description label.contentMode = .scaleAspectFill label.font = .systemFont(ofSize: 14) @@ -119,12 +151,17 @@ struct VideoDescription: View { 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 @@ -156,7 +193,7 @@ struct VideoDescription: View { struct VideoDescription_Previews: PreviewProvider { static var previews: some View { - VideoDescription(video: .fixture) + VideoDescription(video: .fixture, expand: .constant(false)) .injectFixtureEnvironmentObjects() } } diff --git a/Shared/Player/Video Details/VideoDetails.swift b/Shared/Player/Video Details/VideoDetails.swift index 53daf320..a4fcaa59 100644 --- a/Shared/Player/Video Details/VideoDetails.swift +++ b/Shared/Player/Video Details/VideoDetails.swift @@ -160,6 +160,7 @@ struct VideoDetails: View { @State private var subscribed = false @State private var subscriptionToggleButtonDisabled = false @State private var page = DetailsPage.info + @State private var descriptionExpanded = false @Environment(\.navigationStyle) private var navigationStyle #if os(iOS) @@ -175,6 +176,7 @@ struct VideoDetails: View { @Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike @Default(.playerSidebar) private var playerSidebar @Default(.showInspector) private var showInspector + @Default(.expandVideoDescription) private var expandVideoDescription var body: some View { VStack(alignment: .leading, spacing: 0) { @@ -281,7 +283,9 @@ struct VideoDetails: View { } .frame(maxWidth: .infinity) } else if let description = video.description, !description.isEmpty { - VideoDescription(video: video, detailsSize: detailsSize) + Section(header: descriptionHeader) { + VideoDescription(video: video, detailsSize: detailsSize, expand: $descriptionExpanded) + } } else if !video.isLocal { Text("No description") .font(.caption) @@ -345,6 +349,20 @@ struct VideoDetails: View { } } } + + var descriptionHeader: some View { + HStack { + Text("Description".localized()) + + if !expandVideoDescription, !descriptionExpanded { + Spacer() + Image(systemName: "arrow.up.and.down") + .imageScale(.small) + } + } + .font(.caption) + .foregroundColor(.secondary) + } } struct VideoDetails_Previews: PreviewProvider { diff --git a/Shared/Settings/PlayerSettings.swift b/Shared/Settings/PlayerSettings.swift index 0d7ec0cc..869ad747 100644 --- a/Shared/Settings/PlayerSettings.swift +++ b/Shared/Settings/PlayerSettings.swift @@ -8,6 +8,7 @@ struct PlayerSettings: View { @Default(.playerSidebar) private var playerSidebar @Default(.showKeywords) private var showKeywords + @Default(.expandVideoDescription) private var expandVideoDescription @Default(.pauseOnHidingPlayer) private var pauseOnHidingPlayer #if os(iOS) @Default(.honorSystemOrientationLock) private var honorSystemOrientationLock @@ -85,6 +86,9 @@ struct PlayerSettings: View { if !accounts.isEmpty { keywordsToggle + #if !os(tvOS) + expandVideoDescriptionToggle + #endif returnYouTubeDislikeToggle } } @@ -160,6 +164,10 @@ struct PlayerSettings: View { Toggle("Show keywords", isOn: $showKeywords) } + private var expandVideoDescriptionToggle: some View { + Toggle("Open video description expanded", isOn: $expandVideoDescription) + } + private var returnYouTubeDislikeToggle: some View { Toggle("Enable Return YouTube Dislike", isOn: $enableReturnYouTubeDislike) } @@ -212,7 +220,9 @@ struct PlayerSettings: View { Text("Always").tag(ShowInspectorSetting.always) Text("Only for local files and URLs").tag(ShowInspectorSetting.onlyLocal) } + #if os(macOS) .labelsHidden() + #endif } } diff --git a/Shared/Settings/SettingsView.swift b/Shared/Settings/SettingsView.swift index bbda8791..180ffd44 100644 --- a/Shared/Settings/SettingsView.swift +++ b/Shared/Settings/SettingsView.swift @@ -247,7 +247,7 @@ struct SettingsView: View { case .player: return 450 case .controls: - return 900 + return 920 case .quality: return 420 case .history: