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

139 lines
6.4 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
let isDisplayMode: Bool
@State var fontSize: CGFloat = 100
// 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 {
return 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
return Group {
if portrait {
VStack(alignment: .center) {
TimeSegment(text: hour, fontSize: $fontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign, isDisplayMode: isDisplayMode)
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, isDisplayMode: isDisplayMode)
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, isDisplayMode: isDisplayMode)
}
}
} else {
HStack(alignment: .center) {
TimeSegment(text: hour, fontSize: $fontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign, isDisplayMode: isDisplayMode)
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, isDisplayMode: isDisplayMode)
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, isDisplayMode: isDisplayMode)
}
}
.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 {
TimeDisplayView(
date: Date(),
use24Hour: true,
showSeconds: false,
digitColor: .white,
glowIntensity: 0.2,
manualScale: 1.0,
stretched: true,
clockOpacity: 1.0,
fontFamily: .verdana,
fontWeight: .medium,
fontDesign: .default,
forceHorizontalMode: false,
isDisplayMode: false
)
.background(Color.black)
}