Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2026-01-31 09:43:40 -06:00
parent 14f8c13f3c
commit 9ae5aef89b
10 changed files with 66 additions and 64 deletions

View File

@ -7,15 +7,15 @@
<BreakpointProxy <BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent <BreakpointContent
uuid = "14EE7C9C-8F78-417E-AEF3-D655DB3F17B0" uuid = "86059BA3-9776-4EB9-B3CA-4D26F156BEEC"
shouldBeEnabled = "No" shouldBeEnabled = "No"
ignoreCount = "0" ignoreCount = "0"
continueAfterRunningActions = "No" continueAfterRunningActions = "No"
filePath = "TheNoiseClock/Views/Clock/Components/TimeDisplayView.swift" filePath = "TheNoiseClock/Views/Clock/Components/TimeDisplayView.swift"
startingColumnNumber = "9223372036854775807" startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "64" startingLineNumber = "67"
endingLineNumber = "64" endingLineNumber = "67"
landmarkName = "body" landmarkName = "body"
landmarkType = "24"> landmarkType = "24">
</BreakpointContent> </BreakpointContent>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "container:TheNoiseClock.xcodeproj">
</FileRef>
</Workspace>

View File

@ -15,9 +15,7 @@ enum FontFamily: String, CaseIterable {
case georgia = "Georgia" case georgia = "Georgia"
case verdana = "Verdana" case verdana = "Verdana"
case courier = "Courier" case courier = "Courier"
case futura = "Futura"
case avenir = "Avenir" case avenir = "Avenir"
case roboto = "Roboto"
init?(rawValue: String) { init?(rawValue: String) {
switch rawValue { switch rawValue {
@ -28,9 +26,7 @@ enum FontFamily: String, CaseIterable {
case "Georgia": self = .georgia case "Georgia": self = .georgia
case "Verdana": self = .verdana case "Verdana": self = .verdana
case "Monaco", "Courier": self = .courier case "Monaco", "Courier": self = .courier
case "Futura": self = .futura
case "Avenir": self = .avenir case "Avenir": self = .avenir
case "Roboto": self = .roboto
default: self = .system default: self = .system
} }
} }
@ -38,12 +34,13 @@ enum FontFamily: String, CaseIterable {
var percentageDownsize: CGFloat { var percentageDownsize: CGFloat {
switch self { switch self {
case .system: case .system:
return 0.99 return 0.95
case .georgia: case .georgia:
return 0.90 return 0.90
default: return 1 default: return 1
} }
} }
var fontWeights: [Font.Weight] { var fontWeights: [Font.Weight] {
guard let weightDict = Self.fontMapWeights[self.rawValue] else { guard let weightDict = Self.fontMapWeights[self.rawValue] else {
return [] return []
@ -85,18 +82,10 @@ enum FontFamily: String, CaseIterable {
.regular: "Courier", .regular: "Courier",
.bold: "Courier-Bold" .bold: "Courier-Bold"
], ],
FontFamily.futura.rawValue: [
.regular: "Futura-Medium",
.bold: "Futura-Bold"
],
FontFamily.avenir.rawValue: [ FontFamily.avenir.rawValue: [
.regular: "Avenir-Roman", .regular: "Avenir-Roman",
.bold: "Avenir-Bold" .bold: "Avenir-Bold"
], ],
FontFamily.roboto.rawValue: [
.regular: "Roboto-Regular",
.bold: "Roboto-Bold"
]
] ]
} }

View File

@ -17,7 +17,8 @@ struct FontUtils {
fontName: FontFamily, fontName: FontFamily,
weight: Font.Weight, weight: Font.Weight,
design: Font.Design, design: Font.Design,
for size: CGSize for size: CGSize,
isDisplayMode: Bool = false
) -> CGFloat { ) -> CGFloat {
var low: CGFloat = 1.0 var low: CGFloat = 1.0
var high: CGFloat = 2000.0 var high: CGFloat = 2000.0
@ -39,7 +40,9 @@ struct FontUtils {
} }
} }
return low * fontName.percentageDownsize // Apply more conservative sizing in full screen mode
let baseSize = low * fontName.percentageDownsize
return isDisplayMode ? baseSize * 0.95 : baseSize
} }
static func createFont( static func createFont(
@ -180,7 +183,8 @@ private struct TestContentView: View {
fontFamily: font, fontFamily: font,
fontWeight: fontWeight, fontWeight: fontWeight,
fontDesign: fontDesign, fontDesign: fontDesign,
forceHorizontalMode: true) forceHorizontalMode: true,
isDisplayMode: false)
TimeSegment( TimeSegment(
text: digit, text: digit,
@ -191,6 +195,7 @@ private struct TestContentView: View {
fontFamily: font, // FontFamily fontFamily: font, // FontFamily
fontWeight: fontWeight, fontWeight: fontWeight,
fontDesign: fontDesign, fontDesign: fontDesign,
isDisplayMode: false
) )
} }
.frame(width: 400, height: 200) .frame(width: 400, height: 200)
@ -255,7 +260,8 @@ private struct FontSampleCell: View {
glowIntensity: 0.5, // Double glowIntensity: 0.5, // Double
fontFamily: font, // FontFamily fontFamily: font, // FontFamily
fontWeight: weight, fontWeight: weight,
fontDesign: design) fontDesign: design,
isDisplayMode: false)
.frame(width: 100, height: 100) .frame(width: 100, height: 100)
.border(Color.black) .border(Color.black)
} }

View File

@ -69,9 +69,6 @@ class ClockViewModel {
} }
func updateStyle(_ newStyle: ClockStyle) { func updateStyle(_ newStyle: ClockStyle) {
DebugLogger.log("ClockViewModel: updateStyle called", category: .settings)
DebugLogger.log("ClockViewModel: old fontFamily = \(style.fontFamily)", category: .settings)
DebugLogger.log("ClockViewModel: new fontFamily = \(newStyle.fontFamily)", category: .settings)
// Update properties of the existing style object instead of replacing it // Update properties of the existing style object instead of replacing it
// This preserves the @Observable chain // This preserves the @Observable chain
@ -103,7 +100,6 @@ class ClockViewModel {
style.dateFormat = newStyle.dateFormat style.dateFormat = newStyle.dateFormat
style.respectFocusModes = newStyle.respectFocusModes style.respectFocusModes = newStyle.respectFocusModes
DebugLogger.log("ClockViewModel: after update fontFamily = \(style.fontFamily)", category: .settings)
saveStyle() saveStyle()
updateTimersIfNeeded() updateTimersIfNeeded()

View File

@ -244,11 +244,19 @@ private struct FontSection: View {
} }
} }
// Computed property for sorted font families (System first, then alphabetical)
private var sortedFontFamilies: [FontFamily] {
let allFamilies = FontFamily.allCases
let systemFamily = allFamilies.filter { $0 == .system }
let otherFamilies = allFamilies.filter { $0 != .system }.sorted { $0.rawValue < $1.rawValue }
return systemFamily + otherFamilies
}
var body: some View { var body: some View {
Section(header: Text("Font")) { Section(header: Text("Font")) {
// Font Family // Font Family
Picker("Family", selection: $style.fontFamily) { Picker("Family", selection: $style.fontFamily) {
ForEach(FontFamily.allCases, id: \.self) { family in ForEach(sortedFontFamilies, id: \.self) { family in
Text(family.rawValue).tag(family) Text(family.rawValue).tag(family)
} }
} }

View File

@ -17,8 +17,6 @@ struct ClockDisplayContainer: View {
// MARK: - Body // MARK: - Body
var body: some View { var body: some View {
DebugLogger.log("ClockDisplayContainer: body called with fontFamily=\(style.fontFamily), fontWeight=\(style.fontWeight), fontDesign=\(style.fontDesign)", category: .general)
return GeometryReader { geometry in return GeometryReader { geometry in
let isPortrait = geometry.size.height >= geometry.size.width let isPortrait = geometry.size.height >= geometry.size.width
let hasOverlay = style.showBattery || style.showDate let hasOverlay = style.showBattery || style.showDate
@ -44,13 +42,13 @@ struct ClockDisplayContainer: View {
fontFamily: style.fontFamily, fontFamily: style.fontFamily,
fontWeight: style.fontWeight, fontWeight: style.fontWeight,
fontDesign: style.fontDesign, fontDesign: style.fontDesign,
forceHorizontalMode: style.forceHorizontalMode forceHorizontalMode: style.forceHorizontalMode,
isDisplayMode: isDisplayMode
) )
.transition(.opacity) .transition(.opacity)
Spacer() Spacer()
} }
.scaleEffect(isDisplayMode ? 1.0 : 0.995)
.animation(UIConstants.AnimationCurves.smooth, value: isDisplayMode) .animation(UIConstants.AnimationCurves.smooth, value: isDisplayMode)
} }
} }

View File

@ -18,6 +18,7 @@ struct DigitView: View {
let opacity: Double let opacity: Double
let digitColor: Color let digitColor: Color
let glowIntensity: Double let glowIntensity: Double
let isDisplayMode: Bool
@Binding var fontSize: CGFloat @Binding var fontSize: CGFloat
@State private var lastCalculatedSize: CGSize = .zero @State private var lastCalculatedSize: CGSize = .zero
@ -30,8 +31,8 @@ struct DigitView: View {
digitColor: Color = .black, digitColor: Color = .black,
opacity: Double = 1, opacity: Double = 1,
glowIntensity: Double = 0, glowIntensity: Double = 0,
fontSize: Binding<CGFloat>) { fontSize: Binding<CGFloat>,
DebugLogger.log("DigitView: init called with fontName=\(fontName), weight=\(weight), design=\(design)", category: .general) isDisplayMode: Bool = false) {
self.digit = (digit.count == 1 && "0123456789".contains(digit)) ? digit : "0" self.digit = (digit.count == 1 && "0123456789".contains(digit)) ? digit : "0"
self.fontName = fontName self.fontName = fontName
self.weight = weight self.weight = weight
@ -39,6 +40,7 @@ struct DigitView: View {
self.opacity = opacity self.opacity = opacity
self.digitColor = digitColor self.digitColor = digitColor
self.glowIntensity = glowIntensity self.glowIntensity = glowIntensity
self.isDisplayMode = isDisplayMode
self._fontSize = fontSize self._fontSize = fontSize
} }
@ -98,15 +100,12 @@ struct DigitView: View {
calculateOptimalFontSize(for: geometry.size) calculateOptimalFontSize(for: geometry.size)
} }
.onChange(of: fontName) { _, _ in .onChange(of: fontName) { _, _ in
DebugLogger.log("DigitView: fontName changed to \(fontName)", category: .general)
calculateOptimalFontSize(for: geometry.size) calculateOptimalFontSize(for: geometry.size)
} }
.onChange(of: weight) { _, _ in .onChange(of: weight) { _, _ in
DebugLogger.log("DigitView: weight changed to \(weight)", category: .general)
calculateOptimalFontSize(for: geometry.size) calculateOptimalFontSize(for: geometry.size)
} }
.onChange(of: design) { _, _ in .onChange(of: design) { _, _ in
DebugLogger.log("DigitView: design changed to \(design)", category: .general)
calculateOptimalFontSize(for: geometry.size) calculateOptimalFontSize(for: geometry.size)
} }
} }
@ -124,7 +123,8 @@ struct DigitView: View {
fontName: fontName, fontName: fontName,
weight: weight, weight: weight,
design: design, design: design,
for: size) for: size,
isDisplayMode: isDisplayMode)
// Only update if the size is significantly different to prevent micro-adjustments // Only update if the size is significantly different to prevent micro-adjustments
fontSize = optimalSize fontSize = optimalSize

View File

@ -23,6 +23,7 @@ struct TimeDisplayView: View {
let fontWeight: Font.Weight let fontWeight: Font.Weight
let fontDesign: Font.Design let fontDesign: Font.Design
let forceHorizontalMode: Bool let forceHorizontalMode: Bool
let isDisplayMode: Bool
@State var fontSize: CGFloat = 100 @State var fontSize: CGFloat = 100
// MARK: - Formatters // MARK: - Formatters
@ -56,8 +57,6 @@ struct TimeDisplayView: View {
// MARK: - Body // MARK: - Body
var body: some View { var body: some View {
DebugLogger.log("TimeDisplayView: body called with fontFamily=\(fontFamily), fontWeight=\(fontWeight), fontDesign=\(fontDesign)", category: .general)
return GeometryReader { proxy in return GeometryReader { proxy in
let containerSize = proxy.size let containerSize = proxy.size
let portraitMode = containerSize.height >= containerSize.width let portraitMode = containerSize.height >= containerSize.width
@ -77,25 +76,25 @@ struct TimeDisplayView: View {
let finalScale = stretched ? 1.0 : CGFloat(max(0.1, min(manualScale, 1.0))) let finalScale = stretched ? 1.0 : CGFloat(max(0.1, min(manualScale, 1.0)))
// Time display with consistent centering and stable layout // Time display with consistent centering and stable layout
Group { return Group {
if portrait { if portrait {
VStack(alignment: .center) { VStack(alignment: .center) {
TimeSegment(text: hour, fontSize: $fontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign) 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) 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) TimeSegment(text: minute, fontSize: $fontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign, isDisplayMode: isDisplayMode)
if showSeconds { if showSeconds {
ColonView(dotDiameter: dotDiameter, spacing: dotSpacing, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontWeight: fontWeight, isHorizontal: true) 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) TimeSegment(text: secondsText, fontSize: $fontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign, isDisplayMode: isDisplayMode)
} }
} }
} else { } else {
HStack(alignment: .center) { HStack(alignment: .center) {
TimeSegment(text: hour, fontSize: $fontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign) 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) 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) TimeSegment(text: minute, fontSize: $fontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign, isDisplayMode: isDisplayMode)
if showSeconds { if showSeconds {
ColonView(dotDiameter: dotDiameter, spacing: dotSpacing, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontWeight: fontWeight, isHorizontal: false) 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) 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)
@ -120,23 +119,20 @@ struct TimeDisplayView: View {
// MARK: - Preview // MARK: - Preview
#Preview { #Preview {
let style = ClockStyle() TimeDisplayView(
style.fontFamily = .verdana
style.fontWeight = .medium
return TimeDisplayView(
date: Date(), date: Date(),
use24Hour: style.use24Hour, use24Hour: true,
showSeconds: style.showSeconds, showSeconds: false,
digitColor: style.digitColor, digitColor: .white,
glowIntensity: style.glowIntensity, glowIntensity: 0.2,
manualScale: style.digitScale, manualScale: 1.0,
stretched: style.stretched, stretched: true,
clockOpacity: style.clockOpacity, clockOpacity: 1.0,
fontFamily: style.fontFamily, fontFamily: .verdana,
fontWeight: style.fontWeight, fontWeight: .medium,
fontDesign: style.fontDesign, fontDesign: .default,
forceHorizontalMode: style.forceHorizontalMode forceHorizontalMode: false,
isDisplayMode: false
) )
.background(Color.black) .background(Color.black)
} }

View File

@ -18,10 +18,10 @@ struct TimeSegment: View {
let fontFamily: FontFamily let fontFamily: FontFamily
let fontWeight: Font.Weight let fontWeight: Font.Weight
let fontDesign: Font.Design let fontDesign: Font.Design
let isDisplayMode: Bool
var body: some View { var body: some View {
DebugLogger.log("TimeSegment: body called with fontFamily=\(fontFamily), fontWeight=\(fontWeight), fontDesign=\(fontDesign)", category: .general) HStack(alignment: .center, spacing: 0) {
return HStack(alignment: .center, spacing: 0) {
ForEach(Array(text.enumerated()), id: \.offset) { index, character in ForEach(Array(text.enumerated()), id: \.offset) { index, character in
DigitView( DigitView(
digit: String(character), digit: String(character),
@ -31,7 +31,8 @@ struct TimeSegment: View {
digitColor: digitColor, digitColor: digitColor,
opacity: clampedOpacity, opacity: clampedOpacity,
glowIntensity: glowIntensity, glowIntensity: glowIntensity,
fontSize: $fontSize fontSize: $fontSize,
isDisplayMode: isDisplayMode
) )
//.border(.red, width: 1) //.border(.red, width: 1)
} }
@ -57,7 +58,8 @@ struct TimeSegment: View {
glowIntensity: 0.2, glowIntensity: 0.2,
fontFamily: .system, fontFamily: .system,
fontWeight: .regular, fontWeight: .regular,
fontDesign: .default fontDesign: .default,
isDisplayMode: true
) )
.background(Color.black) .background(Color.black)
} }