Make tvOS details panel use full screen

This commit is contained in:
Arkadiusz Fal
2026-04-15 04:35:19 +02:00
parent f2c2a86d47
commit 4837fc6548

View File

@@ -40,12 +40,6 @@ struct TVDetailsPanel: View {
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
// Drag indicator
Capsule()
.fill(.white.opacity(0.4))
.frame(width: 80, height: 6)
.padding(.top, 20)
// Tab picker (hidden when description scroll is locked) // Tab picker (hidden when description scroll is locked)
if !isDescriptionScrollLocked { if !isDescriptionScrollLocked {
Picker("", selection: $selectedTab) { Picker("", selection: $selectedTab) {
@@ -56,7 +50,7 @@ struct TVDetailsPanel: View {
} }
.pickerStyle(.segmented) .pickerStyle(.segmented)
.padding(.horizontal, 120) .padding(.horizontal, 120)
.padding(.top, 24) .padding(.top, 60)
.padding(.bottom, 20) .padding(.bottom, 20)
.focused($focusedItem, equals: .tabPicker) .focused($focusedItem, equals: .tabPicker)
.transition(.opacity.combined(with: .move(edge: .top))) .transition(.opacity.combined(with: .move(edge: .top)))
@@ -72,17 +66,14 @@ struct TVDetailsPanel: View {
} }
} }
.padding(.horizontal, 88) .padding(.horizontal, 88)
.frame(maxHeight: .infinity)
Spacer()
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.frame(height: UIScreen.main.bounds.height * 0.65)
.background( .background(
RoundedRectangle(cornerRadius: 32, style: .continuous) Rectangle()
.fill(.ultraThinMaterial) .fill(.ultraThinMaterial)
.ignoresSafeArea(edges: .bottom) .ignoresSafeArea()
) )
.frame(maxHeight: .infinity, alignment: .bottom)
.onExitCommand { .onExitCommand {
// If description scroll is locked, unlock it first // If description scroll is locked, unlock it first
if isDescriptionScrollLocked { if isDescriptionScrollLocked {
@@ -120,24 +111,22 @@ struct TVDetailsPanel: View {
VStack(spacing: 0) { VStack(spacing: 0) {
// Top section with title, channel, stats (hidden when description expanded) // Top section with title, channel, stats (hidden when description expanded)
if !isDescriptionScrollLocked { if !isDescriptionScrollLocked {
ScrollView { VStack(alignment: .leading, spacing: 24) {
VStack(alignment: .leading, spacing: 24) { // Video title
// Video title Text(video?.title ?? "")
Text(video?.title ?? "") .font(.title2)
.font(.title2) .fontWeight(.semibold)
.fontWeight(.semibold) .lineLimit(3)
.lineLimit(3) .foregroundStyle(.white)
.foregroundStyle(.white)
// Channel info row // Channel info row
channelRow channelRow
// Stats row // Stats row
statsRow statsRow
}
.padding(.vertical, 16)
} }
.frame(height: 180) .padding(.vertical, 16)
.fixedSize(horizontal: false, vertical: true)
.transition(.opacity.combined(with: .move(edge: .top))) .transition(.opacity.combined(with: .move(edge: .top)))
} }
@@ -151,9 +140,6 @@ struct TVDetailsPanel: View {
.padding(.top, isDescriptionScrollLocked ? 24 : 8) .padding(.top, isDescriptionScrollLocked ? 24 : 8)
} }
if isDescriptionScrollLocked {
Spacer()
}
} }
.animation(.easeInOut(duration: 0.25), value: isDescriptionScrollLocked) .animation(.easeInOut(duration: 0.25), value: isDescriptionScrollLocked)
} }
@@ -334,11 +320,6 @@ struct TVScrollableDescription: View {
private let scrollStep: CGFloat = 80 private let scrollStep: CGFloat = 80
private let maxScroll: CGFloat = 5000 private let maxScroll: CGFloat = 5000
/// Height of description area - expands when locked
private var descriptionHeight: CGFloat {
isScrollLocked ? 500 : 200
}
private var isFocused: Bool { private var isFocused: Bool {
focusedItem == .description focusedItem == .description
} }
@@ -400,16 +381,22 @@ struct TVScrollableDescription: View {
} }
} }
// Clipped container for scrollable text // Clipped container for scrollable text. The Rectangle owns the
Text(description) // layout size (fills available space) so the Text's intrinsic
.font(.body) // height from .fixedSize doesn't push parents.
.foregroundStyle(.white.opacity(0.9)) Rectangle()
.lineLimit(nil) .fill(Color.clear)
.fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity, maxHeight: .infinity)
.multilineTextAlignment(.leading) .overlay(alignment: .topLeading) {
.frame(maxWidth: .infinity, alignment: .topLeading) Text(description)
.offset(y: -scrollOffset) .font(.body)
.frame(height: descriptionHeight, alignment: .top) .foregroundStyle(.white.opacity(0.9))
.lineLimit(nil)
.fixedSize(horizontal: false, vertical: true)
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, alignment: .topLeading)
.offset(y: -scrollOffset)
}
.clipped() .clipped()
} }
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)