2026-04-18 12:29:32 +02:00
|
|
|
import SwiftUI
|
|
|
|
|
|
2026-04-19 21:50:03 +02:00
|
|
|
/// Legacy theme shim kept only so AppComponents.swift still compiles while
|
|
|
|
|
/// the codebase has finished migrating to `IdP` / `Color.idp*` tokens.
|
|
|
|
|
/// Do not reference these from new code — use `IdP.tint`, `Color.idpGroupedBackground`,
|
|
|
|
|
/// etc. from `Sources/Core/Design/` instead.
|
2026-04-18 12:29:32 +02:00
|
|
|
enum AppTheme {
|
2026-04-19 21:50:03 +02:00
|
|
|
static let accent: Color = IdP.tint
|
|
|
|
|
static let warmAccent: Color = .orange
|
|
|
|
|
static let border: Color = Color.idpSeparator
|
|
|
|
|
static let shadow: Color = Color.black.opacity(0.08)
|
|
|
|
|
static let cardFill: Color = Color.idpSecondaryGroupedBackground
|
|
|
|
|
static let mutedFill: Color = Color.idpSecondaryGroupedBackground
|
|
|
|
|
static let backgroundTop: Color = Color.idpGroupedBackground
|
|
|
|
|
static let backgroundBottom: Color = Color.idpGroupedBackground
|
|
|
|
|
static let backgroundGlow: Color = .clear
|
|
|
|
|
static let chromeFill: Color = Color.idpSecondaryGroupedBackground
|
2026-04-18 12:29:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum AppLayout {
|
|
|
|
|
static let compactHorizontalPadding: CGFloat = 16
|
|
|
|
|
static let regularHorizontalPadding: CGFloat = 28
|
|
|
|
|
static let compactVerticalPadding: CGFloat = 18
|
|
|
|
|
static let regularVerticalPadding: CGFloat = 28
|
|
|
|
|
static let compactContentWidth: CGFloat = 720
|
|
|
|
|
static let regularContentWidth: CGFloat = 920
|
2026-04-19 21:50:03 +02:00
|
|
|
static let cardRadius: CGFloat = IdP.cardRadius
|
|
|
|
|
static let largeCardRadius: CGFloat = 28
|
2026-04-18 12:29:32 +02:00
|
|
|
static let compactSectionPadding: CGFloat = 18
|
|
|
|
|
static let regularSectionPadding: CGFloat = 24
|
|
|
|
|
static let compactSectionSpacing: CGFloat = 18
|
|
|
|
|
static let regularSectionSpacing: CGFloat = 24
|
|
|
|
|
static let compactBottomDockPadding: CGFloat = 120
|
|
|
|
|
static let regularBottomPadding: CGFloat = 56
|
|
|
|
|
|
|
|
|
|
static func horizontalPadding(for compactLayout: Bool) -> CGFloat {
|
|
|
|
|
compactLayout ? compactHorizontalPadding : regularHorizontalPadding
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static func verticalPadding(for compactLayout: Bool) -> CGFloat {
|
|
|
|
|
compactLayout ? compactVerticalPadding : regularVerticalPadding
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static func contentWidth(for compactLayout: Bool) -> CGFloat {
|
|
|
|
|
compactLayout ? compactContentWidth : regularContentWidth
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static func sectionPadding(for compactLayout: Bool) -> CGFloat {
|
|
|
|
|
compactLayout ? compactSectionPadding : regularSectionPadding
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static func sectionSpacing(for compactLayout: Bool) -> CGFloat {
|
|
|
|
|
compactLayout ? compactSectionSpacing : regularSectionSpacing
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension View {
|
|
|
|
|
func appSurface(radius: CGFloat = AppLayout.cardRadius, fill: Color = AppTheme.cardFill) -> some View {
|
|
|
|
|
background(
|
|
|
|
|
fill,
|
|
|
|
|
in: RoundedRectangle(cornerRadius: radius, style: .continuous)
|
|
|
|
|
)
|
|
|
|
|
.overlay(
|
|
|
|
|
RoundedRectangle(cornerRadius: radius, style: .continuous)
|
2026-04-19 21:50:03 +02:00
|
|
|
.stroke(AppTheme.border, lineWidth: 0.5)
|
2026-04-18 12:29:32 +02:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct AppBackground: View {
|
|
|
|
|
var body: some View {
|
2026-04-19 21:50:03 +02:00
|
|
|
Color.idpGroupedBackground.ignoresSafeArea()
|
2026-04-18 12:29:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct AppScrollScreen<Content: View>: View {
|
|
|
|
|
let compactLayout: Bool
|
|
|
|
|
var bottomPadding: CGFloat? = nil
|
|
|
|
|
let content: () -> Content
|
|
|
|
|
|
|
|
|
|
init(
|
|
|
|
|
compactLayout: Bool,
|
|
|
|
|
bottomPadding: CGFloat? = nil,
|
|
|
|
|
@ViewBuilder content: @escaping () -> Content
|
|
|
|
|
) {
|
|
|
|
|
self.compactLayout = compactLayout
|
|
|
|
|
self.bottomPadding = bottomPadding
|
|
|
|
|
self.content = content
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
ScrollView {
|
|
|
|
|
VStack(alignment: .leading, spacing: AppLayout.sectionSpacing(for: compactLayout)) {
|
|
|
|
|
content()
|
|
|
|
|
}
|
|
|
|
|
.frame(maxWidth: AppLayout.contentWidth(for: compactLayout), alignment: .leading)
|
|
|
|
|
.padding(.horizontal, AppLayout.horizontalPadding(for: compactLayout))
|
|
|
|
|
.padding(.top, AppLayout.verticalPadding(for: compactLayout))
|
|
|
|
|
.padding(.bottom, bottomPadding ?? AppLayout.verticalPadding(for: compactLayout))
|
|
|
|
|
.frame(maxWidth: .infinity, alignment: compactLayout ? .leading : .center)
|
|
|
|
|
}
|
|
|
|
|
.scrollIndicators(.hidden)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct AppPanel<Content: View>: View {
|
|
|
|
|
let compactLayout: Bool
|
|
|
|
|
let radius: CGFloat
|
|
|
|
|
let content: () -> Content
|
|
|
|
|
|
|
|
|
|
init(
|
|
|
|
|
compactLayout: Bool,
|
|
|
|
|
radius: CGFloat = AppLayout.cardRadius,
|
|
|
|
|
@ViewBuilder content: @escaping () -> Content
|
|
|
|
|
) {
|
|
|
|
|
self.compactLayout = compactLayout
|
|
|
|
|
self.radius = radius
|
|
|
|
|
self.content = content
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
VStack(alignment: .leading, spacing: 14) {
|
|
|
|
|
content()
|
|
|
|
|
}
|
|
|
|
|
.padding(AppLayout.sectionPadding(for: compactLayout))
|
|
|
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
|
|
|
.appSurface(radius: radius)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct AppBadge: View {
|
|
|
|
|
let title: String
|
|
|
|
|
var tone: Color = AppTheme.accent
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
Text(title)
|
|
|
|
|
.font(.caption.weight(.semibold))
|
|
|
|
|
.foregroundStyle(tone)
|
|
|
|
|
.padding(.horizontal, 12)
|
2026-04-19 21:50:03 +02:00
|
|
|
.padding(.vertical, 6)
|
|
|
|
|
.background(tone.opacity(0.14), in: Capsule())
|
2026-04-18 12:29:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct AppSectionCard<Content: View>: View {
|
|
|
|
|
let title: String
|
|
|
|
|
var subtitle: String? = nil
|
|
|
|
|
let compactLayout: Bool
|
|
|
|
|
let content: () -> Content
|
|
|
|
|
|
|
|
|
|
init(
|
|
|
|
|
title: String,
|
|
|
|
|
subtitle: String? = nil,
|
|
|
|
|
compactLayout: Bool,
|
|
|
|
|
@ViewBuilder content: @escaping () -> Content
|
|
|
|
|
) {
|
|
|
|
|
self.title = title
|
|
|
|
|
self.subtitle = subtitle
|
|
|
|
|
self.compactLayout = compactLayout
|
|
|
|
|
self.content = content
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
AppPanel(compactLayout: compactLayout) {
|
|
|
|
|
AppSectionTitle(title: title, subtitle: subtitle)
|
|
|
|
|
content()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|