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

This commit is contained in:
Matt Bruce 2025-09-08 11:21:34 -05:00
parent c8fa702382
commit 2d4d76cc8d
3 changed files with 60 additions and 29 deletions

View File

@ -207,6 +207,34 @@ enum FontUtils {
)
}
/// Get weight multiplier for visual consistency with font weight
/// - Parameter weight: Font weight name
/// - Returns: Multiplier for dot size (0.7 to 1.3)
static func weightMultiplier(for weight: String) -> CGFloat {
switch weight {
case "Ultra Light":
return 0.7
case "Thin":
return 0.75
case "Light":
return 0.8
case "Regular":
return 0.85
case "Medium":
return 0.9
case "Semibold":
return 1.0
case "Bold":
return 1.1
case "Heavy":
return 1.2
case "Black":
return 1.3
default:
return 1.0
}
}
/// Create a UIFont with specified parameters for measurements
/// - Parameters:
/// - size: Font size

View File

@ -22,7 +22,7 @@ struct ClockView: View {
GeometryReader { geometry in
ZStack {
// Time display - fills available space within safe areas
// Time display - fills entire available space
TimeDisplayView(
date: viewModel.currentTime,
use24Hour: viewModel.style.use24Hour,
@ -37,7 +37,6 @@ struct ClockView: View {
fontWeight: viewModel.style.fontWeight,
fontDesign: viewModel.style.fontDesign
)
.frame(width: geometry.size.width, height: geometry.size.height)
.transition(.opacity)
// Top overlay - positioned at top with safe area consideration
@ -60,6 +59,7 @@ struct ClockView: View {
.animation(UIConstants.AnimationCurves.smooth, value: viewModel.isDisplayMode)
}
}
.ignoresSafeArea(.all, edges: viewModel.isDisplayMode ? .bottom : [])
.navigationTitle(viewModel.isDisplayMode ? "" : "Clock")
.toolbar {
if !viewModel.isDisplayMode {

View File

@ -66,29 +66,26 @@ struct TimeDisplayView: View {
let size = proxy.size
let portrait = size.height >= size.width
// Get safe area information to avoid Dynamic Island overlap
let safeAreaInsets = proxy.safeAreaInsets
let availableWidth = size.width - safeAreaInsets.leading - safeAreaInsets.trailing
let availableHeight = size.height - safeAreaInsets.top - safeAreaInsets.bottom
// Use available space within safe areas for font sizing calculations
let availableSize = CGSize(width: availableWidth, height: availableHeight)
// Use full GeometryReader size for frame (to fill entire space)
// Use the full GeometryReader size for maximum space usage
let fullScreenSize = size
// Use optimal font sizing that maximizes space usage within safe areas
// Get safe area information for font sizing to avoid Dynamic Island overlap
let safeAreaInsets = proxy.safeAreaInsets
let _ = size.width - safeAreaInsets.leading - safeAreaInsets.trailing // availableWidth
let _ = size.height - safeAreaInsets.top - safeAreaInsets.bottom // availableHeight
// Use optimal font sizing that maximizes space usage with full screen
let baseFontSize = stretched ?
FontUtils.maximumStretchedFontSize(
containerWidth: availableSize.width,
containerHeight: availableSize.height,
containerWidth: fullScreenSize.width,
containerHeight: fullScreenSize.height,
isPortrait: portrait,
showSeconds: showSeconds,
showAmPm: !use24Hour && showAmPmBadge
) :
FontUtils.optimalFontSize(
containerWidth: availableSize.width,
containerHeight: availableSize.height,
containerWidth: fullScreenSize.width,
containerHeight: fullScreenSize.height,
isPortrait: portrait,
showSeconds: showSeconds,
showAmPm: !use24Hour && showAmPmBadge
@ -146,10 +143,10 @@ struct TimeDisplayView: View {
showAMPM: showAMPM
)
// Calculate scale with maximum space utilization using available space
// Calculate scale with maximum space utilization using full screen
let safeInset = AppConstants.Defaults.safeInset
let availableW = max(1, availableSize.width - safeInset * 2)
let availableH = max(1, availableSize.height - safeInset * 2)
let availableW = max(1, fullScreenSize.width - safeInset * 2)
let availableH = max(1, fullScreenSize.height - safeInset * 2)
// Calculate scaling factors
let widthScale = availableW / max(totalWidth, 1)
@ -170,11 +167,11 @@ struct TimeDisplayView: View {
if showAMPM {
TimeSegment(text: ampmText, fontSize: ampmFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign)
} else {
HorizontalColon(dotDiameter: dotDiameter, spacing: hSpacing, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity)
HorizontalColon(dotDiameter: dotDiameter, spacing: hSpacing, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontWeight: fontWeight)
}
TimeSegment(text: minute, fontSize: baseFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign)
if showSeconds {
HorizontalColon(dotDiameter: dotDiameter, spacing: hSpacing, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity)
HorizontalColon(dotDiameter: dotDiameter, spacing: hSpacing, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontWeight: fontWeight)
TimeSegment(text: secondsText, fontSize: baseFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign)
} else {
// Invisible placeholder to maintain consistent spacing
@ -188,11 +185,11 @@ struct TimeDisplayView: View {
if showAMPM {
TimeSegment(text: ampmText, fontSize: ampmFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign)
} else {
VerticalColon(dotDiameter: dotDiameter, spacing: vSpacing, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity)
VerticalColon(dotDiameter: dotDiameter, spacing: vSpacing, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontWeight: fontWeight)
}
TimeSegment(text: minute, fontSize: baseFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign)
if showSeconds {
VerticalColon(dotDiameter: dotDiameter, spacing: vSpacing, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity)
VerticalColon(dotDiameter: dotDiameter, spacing: vSpacing, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontWeight: fontWeight)
TimeSegment(text: secondsText, fontSize: baseFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign)
} else {
// Invisible placeholder to maintain consistent spacing
@ -331,12 +328,13 @@ private struct HorizontalColon: View {
let opacity: Double
let digitColor: Color
let glowIntensity: Double
let fontWeight: String
var body: some View {
let clamped = ColorUtils.clampOpacity(opacity)
HStack(spacing: spacing) {
DotCircle(size: dotDiameter, opacity: clamped, digitColor: digitColor, glowIntensity: glowIntensity)
DotCircle(size: dotDiameter, opacity: clamped, digitColor: digitColor, glowIntensity: glowIntensity)
DotCircle(size: dotDiameter, opacity: clamped, digitColor: digitColor, glowIntensity: glowIntensity, fontWeight: fontWeight)
DotCircle(size: dotDiameter, opacity: clamped, digitColor: digitColor, glowIntensity: glowIntensity, fontWeight: fontWeight)
}
.fixedSize(horizontal: true, vertical: true)
.accessibilityHidden(true)
@ -349,12 +347,13 @@ private struct VerticalColon: View {
let opacity: Double
let digitColor: Color
let glowIntensity: Double
let fontWeight: String
var body: some View {
let clamped = ColorUtils.clampOpacity(opacity)
VStack(spacing: spacing) {
DotCircle(size: dotDiameter, opacity: clamped, digitColor: digitColor, glowIntensity: glowIntensity)
DotCircle(size: dotDiameter, opacity: clamped, digitColor: digitColor, glowIntensity: glowIntensity)
DotCircle(size: dotDiameter, opacity: clamped, digitColor: digitColor, glowIntensity: glowIntensity, fontWeight: fontWeight)
DotCircle(size: dotDiameter, opacity: clamped, digitColor: digitColor, glowIntensity: glowIntensity, fontWeight: fontWeight)
}
.fixedSize(horizontal: true, vertical: true)
.accessibilityHidden(true)
@ -366,17 +365,21 @@ private struct DotCircle: View {
let opacity: Double
let digitColor: Color
let glowIntensity: Double
let fontWeight: String
var body: some View {
let weightMultiplier = FontUtils.weightMultiplier(for: fontWeight)
let adjustedSize = size * weightMultiplier
ZStack {
Circle()
.fill(digitColor)
.frame(width: size, height: size)
.frame(width: adjustedSize, height: adjustedSize)
.blur(radius: ColorUtils.glowRadius(intensity: glowIntensity))
.opacity(ColorUtils.glowOpacity(intensity: glowIntensity) * opacity)
Circle()
.fill(digitColor)
.frame(width: size, height: size)
.frame(width: adjustedSize, height: adjustedSize)
.opacity(opacity)
}
}