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 /// Create a UIFont with specified parameters for measurements
/// - Parameters: /// - Parameters:
/// - size: Font size /// - size: Font size

View File

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

View File

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