mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 21:43:41 +00:00
New chapters layout
This commit is contained in:
parent
d52ccf2ce6
commit
6596a440a5
@ -11,6 +11,19 @@ struct ChapterView: View {
|
|||||||
Button {
|
Button {
|
||||||
player.backend.seek(to: chapter.start, seekType: .userInteracted)
|
player.backend.seek(to: chapter.start, seekType: .userInteracted)
|
||||||
} label: {
|
} label: {
|
||||||
|
Group {
|
||||||
|
#if os(tvOS)
|
||||||
|
horizontalChapter
|
||||||
|
#else
|
||||||
|
verticalChapter
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
var horizontalChapter: some View {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
if !chapter.image.isNil {
|
if !chapter.image.isNil {
|
||||||
smallImage(chapter)
|
smallImage(chapter)
|
||||||
@ -25,9 +38,24 @@ struct ChapterView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.contentShape(Rectangle())
|
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
|
||||||
|
var verticalChapter: some View {
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
if !chapter.image.isNil {
|
||||||
|
smallImage(chapter)
|
||||||
|
}
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text(chapter.title)
|
||||||
|
.lineLimit(2)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
.font(.headline)
|
||||||
|
Text(chapter.start.formattedAsPlaybackTime(allowZero: true) ?? "")
|
||||||
|
.font(.system(.subheadline).monospacedDigit())
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: Self.thumbnailWidth, alignment: .leading)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func smallImage(_ chapter: Chapter) -> some View {
|
@ViewBuilder func smallImage(_ chapter: Chapter) -> some View {
|
||||||
@ -37,21 +65,20 @@ struct ChapterView: View {
|
|||||||
ProgressView()
|
ProgressView()
|
||||||
}
|
}
|
||||||
.indicator(.activity)
|
.indicator(.activity)
|
||||||
|
.frame(width: Self.thumbnailWidth, height: Self.thumbnailHeight)
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
.frame(width: thumbnailWidth, height: 140)
|
|
||||||
.mask(RoundedRectangle(cornerRadius: 12))
|
.mask(RoundedRectangle(cornerRadius: 12))
|
||||||
#else
|
#else
|
||||||
.frame(width: thumbnailWidth, height: 60)
|
|
||||||
.mask(RoundedRectangle(cornerRadius: 6))
|
.mask(RoundedRectangle(cornerRadius: 6))
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private var thumbnailWidth: Double {
|
static var thumbnailWidth: Double {
|
||||||
#if os(tvOS)
|
|
||||||
250
|
250
|
||||||
#else
|
}
|
||||||
100
|
|
||||||
#endif
|
static var thumbnailHeight: Double {
|
||||||
|
thumbnailWidth / 1.7777
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,8 +5,13 @@ import SwiftUI
|
|||||||
struct ChaptersView: View {
|
struct ChaptersView: View {
|
||||||
@ObservedObject private var player = PlayerModel.shared
|
@ObservedObject private var player = PlayerModel.shared
|
||||||
|
|
||||||
|
var chapters: [Chapter] {
|
||||||
|
player.videoForDisplay?.chapters ?? []
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if let chapters = player.currentVideo?.chapters, !chapters.isEmpty {
|
if !chapters.isEmpty {
|
||||||
|
#if os(tvOS)
|
||||||
List {
|
List {
|
||||||
Section {
|
Section {
|
||||||
ForEach(chapters) { chapter in
|
ForEach(chapters) { chapter in
|
||||||
@ -15,14 +20,17 @@ struct ChaptersView: View {
|
|||||||
}
|
}
|
||||||
.listRowBackground(Color.clear)
|
.listRowBackground(Color.clear)
|
||||||
}
|
}
|
||||||
#if os(macOS)
|
|
||||||
.listStyle(.inset)
|
|
||||||
#elseif os(iOS)
|
|
||||||
.listStyle(.grouped)
|
|
||||||
.backport
|
|
||||||
.scrollContentBackground(false)
|
|
||||||
#else
|
|
||||||
.listStyle(.plain)
|
.listStyle(.plain)
|
||||||
|
#else
|
||||||
|
ScrollView(.horizontal) {
|
||||||
|
LazyHStack(spacing: 20) {
|
||||||
|
ForEach(chapters) { chapter in
|
||||||
|
ChapterView(chapter: chapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 15)
|
||||||
|
}
|
||||||
|
.frame(minHeight: ChapterView.thumbnailHeight + 100)
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
NoCommentsView(text: "No chapters information available".localized(), systemImage: "xmark.circle.fill")
|
NoCommentsView(text: "No chapters information available".localized(), systemImage: "xmark.circle.fill")
|
||||||
|
@ -50,6 +50,7 @@ struct VideoDescription: View {
|
|||||||
|
|
||||||
keywords
|
keywords
|
||||||
}
|
}
|
||||||
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
|
|
||||||
var shouldExpand: Bool {
|
var shouldExpand: Bool {
|
||||||
|
@ -285,21 +285,37 @@ struct VideoDetails: View {
|
|||||||
} else if let description = video.description, !description.isEmpty {
|
} else if let description = video.description, !description.isEmpty {
|
||||||
Section(header: descriptionHeader) {
|
Section(header: descriptionHeader) {
|
||||||
VideoDescription(video: video, detailsSize: detailsSize, expand: $descriptionExpanded)
|
VideoDescription(video: video, detailsSize: detailsSize, expand: $descriptionExpanded)
|
||||||
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
} else if !video.isLocal {
|
} else if !video.isLocal {
|
||||||
Text("No description")
|
Text("No description")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
|
|
||||||
if video.isLocal || showInspector == .always {
|
if player.videoBeingOpened.isNil,
|
||||||
|
!video.isLocal,
|
||||||
|
!video.chapters.isEmpty
|
||||||
|
{
|
||||||
|
Section(header: chaptersHeader) {
|
||||||
|
ChaptersView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if player.videoBeingOpened.isNil,
|
||||||
|
video.isLocal || showInspector == .always
|
||||||
|
{
|
||||||
InspectorView(video: player.videoForDisplay)
|
InspectorView(video: player.videoForDisplay)
|
||||||
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !sidebarQueue,
|
if player.videoBeingOpened.isNil,
|
||||||
|
!sidebarQueue,
|
||||||
!(player.videoForDisplay?.related.isEmpty ?? true)
|
!(player.videoForDisplay?.related.isEmpty ?? true)
|
||||||
{
|
{
|
||||||
RelatedView()
|
RelatedView()
|
||||||
|
.padding(.horizontal)
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -313,7 +329,6 @@ struct VideoDetails: View {
|
|||||||
}
|
}
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
.animation(nil, value: player.currentItem)
|
.animation(nil, value: player.currentItem)
|
||||||
.padding(.horizontal)
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.frame(maxWidth: YatteeApp.isForPreviews ? .infinity : maxWidth)
|
.frame(maxWidth: YatteeApp.isForPreviews ? .infinity : maxWidth)
|
||||||
#endif
|
#endif
|
||||||
@ -360,6 +375,14 @@ struct VideoDetails: View {
|
|||||||
.imageScale(.small)
|
.imageScale(.small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
var chaptersHeader: some View {
|
||||||
|
Text("Chapters".localized())
|
||||||
|
.padding(.horizontal)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
|
@ -412,7 +412,7 @@ struct VideoPlayerView: View {
|
|||||||
List {
|
List {
|
||||||
PlayerQueueView(sidebarQueue: true)
|
PlayerQueueView(sidebarQueue: true)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: 350)
|
.frame(maxWidth: 450)
|
||||||
.background(colorScheme == .dark ? Color.black : Color.white)
|
.background(colorScheme == .dark ? Color.black : Color.white)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -431,6 +431,7 @@ struct VideoPlayerView: View {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
#endif
|
#endif
|
||||||
|
.ignoresSafeArea(edges: .horizontal)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullScreenPlayer: Bool {
|
var fullScreenPlayer: Bool {
|
||||||
|
@ -561,6 +561,48 @@ struct SearchView: View {
|
|||||||
searchSortOrder.rawValue
|
searchSortOrder.rawValue
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var shouldDisplayHeader: Bool {
|
||||||
|
#if os(tvOS)
|
||||||
|
!state.query.isEmpty
|
||||||
|
#else
|
||||||
|
false
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
var header: some View {
|
||||||
|
HStack {
|
||||||
|
clearButton
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
|
if accounts.app.supportsSearchFilters {
|
||||||
|
filtersHorizontalStack
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
FavoriteButton(item: favoriteItem)
|
||||||
|
.id(favoriteItem?.id)
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.font(.system(size: 25))
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
ListingStyleButtons(listingStyle: $searchListingStyle)
|
||||||
|
HideShortsButtons(hide: $hideShorts)
|
||||||
|
}
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.padding(.leading, 30)
|
||||||
|
.padding(.bottom, 15)
|
||||||
|
.padding(.trailing, 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
var clearButton: some View {
|
||||||
|
Button {
|
||||||
|
state.queryText = ""
|
||||||
|
} label: {
|
||||||
|
Label("Clear", systemImage: "xmark")
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
}
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SearchView_Previews: PreviewProvider {
|
struct SearchView_Previews: PreviewProvider {
|
||||||
|
@ -131,6 +131,7 @@ struct NowPlayingView: View {
|
|||||||
Section(header: Text("Chapters")) {
|
Section(header: Text("Chapters")) {
|
||||||
ForEach(video.chapters) { chapter in
|
ForEach(video.chapters) { chapter in
|
||||||
ChapterView(chapter: chapter)
|
ChapterView(chapter: chapter)
|
||||||
|
.padding(.horizontal, 40)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user