TheNoiseClock/TheNoiseClock/Views/Clock/Components/TimeDisplayView.swift

141 lines
6.3 KiB
Swift

//
// TimeDisplayView.swift
// TheNoiseClock
//
// Created by Matt Bruce on 9/7/25.
//
import SwiftUI
/// Component for displaying segmented time with customizable formatting
struct TimeDisplayView: View {
// MARK: - Properties
let date: Date
let use24Hour: Bool
let showSeconds: Bool
let digitColor: Color
let glowIntensity: Double
let manualScale: Double
let stretched: Bool
let clockOpacity: Double
let fontFamily: FontFamily
let fontWeight: Font.Weight
let fontDesign: Font.Design
let forceHorizontalMode: Bool
@State var fontSize: CGFloat = 1000
// MARK: - Formatters
private static let hour24DF: DateFormatter = {
let df = DateFormatter()
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = "HH"
return df
}()
private static let hour12DF: DateFormatter = {
let df = DateFormatter()
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = "h"
return df
}()
private static let minuteDF: DateFormatter = {
let df = DateFormatter()
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = "mm"
return df
}()
private static let secondDF: DateFormatter = {
let df = DateFormatter()
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = "ss"
return df
}()
// MARK: - Body
var body: some View {
GeometryReader { proxy in
let containerSize = proxy.size
let portraitMode = containerSize.height >= containerSize.width
let portrait = !forceHorizontalMode && containerSize.height >= containerSize.width
// Time components
let hour = use24Hour ? Self.hour24DF.string(from: date) : Self.hour12DF.string(from: date)
let minute = Self.minuteDF.string(from: date)
let secondsText = Self.secondDF.string(from: date)
// Separators - reasonable spacing with extra padding in landscape
let dotDiameter = fontSize * 0.75
let dotSpacing = portrait ? fontSize * 0.18 : fontSize * 0.25 // More spacing in landscape
// Simple scaling - let the content size naturally and apply manual scale
let finalScale = stretched ? 1.0 : CGFloat(max(0.1, min(manualScale, 1.0)))
// Time display with consistent centering and stable layout
Group {
if portrait {
VStack(alignment: .center) {
TimeSegment(text: hour, fontSize: $fontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign)
ColonView(dotDiameter: dotDiameter, spacing: dotSpacing, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontWeight: fontWeight, isHorizontal: true)
TimeSegment(text: minute, fontSize: $fontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign)
if showSeconds {
ColonView(dotDiameter: dotDiameter, spacing: dotSpacing, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontWeight: fontWeight, isHorizontal: true)
TimeSegment(text: secondsText, fontSize: $fontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign)
}
}
} else {
HStack(alignment: .center) {
TimeSegment(text: hour, fontSize: $fontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign)
ColonView(dotDiameter: dotDiameter, spacing: dotSpacing, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontWeight: fontWeight, isHorizontal: false)
TimeSegment(text: minute, fontSize: $fontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign)
if showSeconds {
ColonView(dotDiameter: dotDiameter, spacing: dotSpacing, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontWeight: fontWeight, isHorizontal: false)
TimeSegment(text: secondsText, fontSize: $fontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign)
}
}
.frame(maxWidth: .infinity)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.offset(y: portraitMode && forceHorizontalMode ? -containerSize.height * 0.10 : 0) // Push up in horizontal mode
.scaleEffect(finalScale, anchor: .center)
.animation(UIConstants.AnimationCurves.smooth, value: finalScale)
.animation(UIConstants.AnimationCurves.smooth, value: showSeconds) // Smooth animation for seconds toggle
.minimumScaleFactor(0.1)
.clipped() // Prevent overflow beyond bounds
}
//.border(.yellow, width: 1)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onOrientationChange() // Force updates on orientation changes
}
}
// MARK: - Preview
#Preview {
let style = ClockStyle()
style.fontFamily = .verdana
style.fontWeight = .medium
return TimeDisplayView(
date: Date(),
use24Hour: style.use24Hour,
showSeconds: style.showSeconds,
digitColor: style.digitColor,
glowIntensity: style.glowIntensity,
manualScale: style.digitScale,
stretched: style.stretched,
clockOpacity: style.clockOpacity,
fontFamily: style.fontFamily,
fontWeight: style.fontWeight,
fontDesign: style.fontDesign,
forceHorizontalMode: style.forceHorizontalMode
)
.background(Color.black)
}