stremio/apps/ios/VMCTV/Theme/TvTheme.swift
Jos Vooges | STH ac951617cb Add Live Events across API/admin/Android TV, mobile ClearKey/DASH fixes, and tvOS app.
Wire FAAB schedule-events into master-api and admin IPTV settings, add Android TV Events UI, fix mobile ClearKey/stream playback (DASH + bump 1.0.23), and scaffold the Apple TV (VMCTV) client with docs/scripts.
2026-09-18 23:34:42 +02:00

127 lines
4.9 KiB
Swift

import SwiftUI
enum TvColors {
static let bg = Color(red: 0.02, green: 0.02, blue: 0.025)
static let bgElevated = Color(red: 0.06, green: 0.06, blue: 0.07)
static let surface = Color(red: 0.11, green: 0.11, blue: 0.125)
static let surfaceSoft = Color(red: 0.16, green: 0.16, blue: 0.18)
/// Netflix-red #E50914
static let accent = Color(red: 0.898, green: 0.035, blue: 0.078)
static let accentSoft = Color(red: 0.898, green: 0.035, blue: 0.078).opacity(0.28)
static let text = Color(red: 0.97, green: 0.97, blue: 0.98)
static let textDim = Color(red: 0.62, green: 0.62, blue: 0.68)
static let danger = Color(red: 1.0, green: 0.42, blue: 0.42)
static let success = Color(red: 0.275, green: 0.827, blue: 0.412)
}
enum TvFonts {
static let brand = Font.system(size: 72, weight: .black, design: .rounded)
static let hero = Font.system(size: 56, weight: .black, design: .rounded)
static let title = Font.system(size: 42, weight: .bold, design: .rounded)
static let headline = Font.system(size: 28, weight: .semibold, design: .rounded)
static let body = Font.system(size: 24, weight: .regular, design: .rounded)
static let caption = Font.system(size: 20, weight: .medium, design: .rounded)
static let nav = Font.system(size: 22, weight: .semibold, design: .rounded)
static let code = Font.system(size: 64, weight: .bold, design: .monospaced)
}
struct FocusableCardStyle: ButtonStyle {
var scale: CGFloat = 1.08
func makeBody(configuration: Configuration) -> some View {
FocusableCardLabel(configuration: configuration, scale: scale)
}
}
private struct FocusableCardLabel: View {
let configuration: ButtonStyleConfiguration
let scale: CGFloat
@Environment(\.isFocused) private var isFocused
var body: some View {
configuration.label
.scaleEffect(isFocused ? scale : (configuration.isPressed ? 0.98 : 1.0))
.shadow(color: isFocused ? TvColors.accent.opacity(0.55) : .clear, radius: isFocused ? 28 : 0, y: 10)
.animation(.easeOut(duration: 0.18), value: isFocused)
.animation(.easeOut(duration: 0.12), value: configuration.isPressed)
}
}
/// Top-nav: duidelijke focus-ring, aparte “actieve tab”-styling.
struct NavTabButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
NavTabButtonLabel(configuration: configuration)
}
}
private struct NavTabButtonLabel: View {
let configuration: ButtonStyleConfiguration
@Environment(\.isFocused) private var isFocused
var body: some View {
configuration.label
.scaleEffect(isFocused ? 1.06 : (configuration.isPressed ? 0.97 : 1.0))
.shadow(color: isFocused ? Color.white.opacity(0.35) : .clear, radius: isFocused ? 16 : 0, y: 0)
.animation(.easeOut(duration: 0.15), value: isFocused)
.animation(.easeOut(duration: 0.1), value: configuration.isPressed)
}
}
struct TvPrimaryButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
TvPrimaryButtonLabel(configuration: configuration)
}
}
private struct TvPrimaryButtonLabel: View {
let configuration: ButtonStyleConfiguration
@Environment(\.isFocused) private var isFocused
var body: some View {
configuration.label
.font(TvFonts.headline)
.foregroundStyle(.white)
.padding(.horizontal, 36)
.padding(.vertical, 18)
.background(
LinearGradient(
colors: [TvColors.accent, TvColors.accent.opacity(0.82)],
startPoint: .topLeading,
endPoint: .bottomTrailing
),
in: Capsule()
)
.scaleEffect(isFocused ? 1.06 : (configuration.isPressed ? 0.97 : 1))
.shadow(color: TvColors.accent.opacity(isFocused ? 0.55 : 0.28), radius: isFocused ? 22 : 10, y: 6)
.animation(.easeOut(duration: 0.18), value: isFocused)
}
}
struct TvSecondaryButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
TvSecondaryButtonLabel(configuration: configuration)
}
}
private struct TvSecondaryButtonLabel: View {
let configuration: ButtonStyleConfiguration
@Environment(\.isFocused) private var isFocused
var body: some View {
configuration.label
.font(TvFonts.headline)
.foregroundStyle(TvColors.text)
.padding(.horizontal, 32)
.padding(.vertical, 16)
.background(
.ultraThinMaterial,
in: Capsule()
)
.overlay {
Capsule()
.strokeBorder(isFocused ? TvColors.accent.opacity(0.7) : Color.white.opacity(0.12), lineWidth: isFocused ? 2 : 1)
}
.scaleEffect(isFocused ? 1.05 : 1)
.animation(.easeOut(duration: 0.18), value: isFocused)
}
}