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

This commit is contained in:
Matt Bruce 2025-09-08 11:07:05 -05:00
parent 2fafae909d
commit c8fa702382
5 changed files with 311 additions and 53 deletions

View File

@ -121,6 +121,147 @@ enum FontUtils {
return baseFontSize * 0.20 return baseFontSize * 0.20
} }
// MARK: - Font Customization
/// Convert font family string to Font
/// - Parameter family: Font family name
/// - Returns: SwiftUI Font
static func fontFamily(_ family: String) -> Font {
switch family {
case "San Francisco":
return .system(.body, design: .default)
case "System":
return .system(.body, design: .default)
case "Monaco":
return .system(.body, design: .monospaced)
case "Courier":
return .system(.body, design: .monospaced)
default:
return .system(.body, design: .default)
}
}
/// Convert font weight string to Font.Weight
/// - Parameter weight: Font weight name
/// - Returns: Font.Weight
static func fontWeight(_ weight: String) -> Font.Weight {
switch weight {
case "Ultra Light":
return .ultraLight
case "Thin":
return .thin
case "Light":
return .light
case "Regular":
return .regular
case "Medium":
return .medium
case "Semibold":
return .semibold
case "Bold":
return .bold
case "Heavy":
return .heavy
case "Black":
return .black
default:
return .bold
}
}
/// Convert font design string to Font.Design
/// - Parameter design: Font design name
/// - Returns: Font.Design
static func fontDesign(_ design: String) -> Font.Design {
switch design {
case "Default":
return .default
case "Serif":
return .serif
case "Rounded":
return .rounded
case "Monospaced":
return .monospaced
default:
return .rounded
}
}
/// Create a custom font with specified parameters
/// - Parameters:
/// - size: Font size
/// - family: Font family name
/// - weight: Font weight name
/// - design: Font design name
/// - Returns: SwiftUI Font
static func customFont(
size: CGFloat,
family: String,
weight: String,
design: String
) -> Font {
return .system(
size: size,
weight: fontWeight(weight),
design: fontDesign(design)
)
}
/// Create a UIFont with specified parameters for measurements
/// - Parameters:
/// - size: Font size
/// - family: Font family name
/// - weight: Font weight name
/// - design: Font design name
/// - Returns: UIFont
static func customUIFont(
size: CGFloat,
family: String,
weight: String,
design: String
) -> UIFont {
let uiWeight: UIFont.Weight
switch weight {
case "Ultra Light":
uiWeight = .ultraLight
case "Thin":
uiWeight = .thin
case "Light":
uiWeight = .light
case "Regular":
uiWeight = .regular
case "Medium":
uiWeight = .medium
case "Semibold":
uiWeight = .semibold
case "Bold":
uiWeight = .bold
case "Heavy":
uiWeight = .heavy
case "Black":
uiWeight = .black
default:
uiWeight = .bold
}
let uiDesign: UIFontDescriptor.SystemDesign
switch design {
case "Serif":
uiDesign = .serif
case "Rounded":
uiDesign = .rounded
case "Monospaced":
uiDesign = .monospaced
default:
uiDesign = .default
}
let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body)
.withDesign(uiDesign) ?? UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body)
return UIFont(descriptor: descriptor.addingAttributes([.traits: [UIFontDescriptor.TraitKey.weight: uiWeight]]), size: size)
}
/// Measure text size with given font /// Measure text size with given font
/// - Parameters: /// - Parameters:
/// - text: Text to measure /// - text: Text to measure

View File

@ -25,6 +25,11 @@ class ClockStyle: Codable, Equatable {
var stretched: Bool = true var stretched: Bool = true
var backgroundHex: String = AppConstants.Defaults.backgroundColorHex var backgroundHex: String = AppConstants.Defaults.backgroundColorHex
// MARK: - Font Settings
var fontFamily: String = "System" // System, San Francisco, etc.
var fontWeight: String = "Bold" // Ultra Light, Thin, Light, Regular, Medium, Semibold, Bold, Heavy, Black
var fontDesign: String = "Rounded" // Default, Serif, Rounded, Monospaced
// MARK: - Overlay Settings // MARK: - Overlay Settings
var showBattery: Bool = true var showBattery: Bool = true
var showDate: Bool = true var showDate: Bool = true
@ -46,6 +51,9 @@ class ClockStyle: Codable, Equatable {
case digitScale case digitScale
case stretched case stretched
case backgroundHex case backgroundHex
case fontFamily
case fontWeight
case fontDesign
case showBattery case showBattery
case showDate case showDate
case clockOpacity case clockOpacity
@ -70,6 +78,9 @@ class ClockStyle: Codable, Equatable {
self.digitScale = try container.decodeIfPresent(Double.self, forKey: .digitScale) ?? self.digitScale self.digitScale = try container.decodeIfPresent(Double.self, forKey: .digitScale) ?? self.digitScale
self.stretched = try container.decodeIfPresent(Bool.self, forKey: .stretched) ?? self.stretched self.stretched = try container.decodeIfPresent(Bool.self, forKey: .stretched) ?? self.stretched
self.backgroundHex = try container.decodeIfPresent(String.self, forKey: .backgroundHex) ?? self.backgroundHex self.backgroundHex = try container.decodeIfPresent(String.self, forKey: .backgroundHex) ?? self.backgroundHex
self.fontFamily = try container.decodeIfPresent(String.self, forKey: .fontFamily) ?? self.fontFamily
self.fontWeight = try container.decodeIfPresent(String.self, forKey: .fontWeight) ?? self.fontWeight
self.fontDesign = try container.decodeIfPresent(String.self, forKey: .fontDesign) ?? self.fontDesign
self.showBattery = try container.decodeIfPresent(Bool.self, forKey: .showBattery) ?? self.showBattery self.showBattery = try container.decodeIfPresent(Bool.self, forKey: .showBattery) ?? self.showBattery
self.showDate = try container.decodeIfPresent(Bool.self, forKey: .showDate) ?? self.showDate self.showDate = try container.decodeIfPresent(Bool.self, forKey: .showDate) ?? self.showDate
self.clockOpacity = try container.decodeIfPresent(Double.self, forKey: .clockOpacity) ?? self.clockOpacity self.clockOpacity = try container.decodeIfPresent(Double.self, forKey: .clockOpacity) ?? self.clockOpacity
@ -89,6 +100,9 @@ class ClockStyle: Codable, Equatable {
try container.encode(digitScale, forKey: .digitScale) try container.encode(digitScale, forKey: .digitScale)
try container.encode(stretched, forKey: .stretched) try container.encode(stretched, forKey: .stretched)
try container.encode(backgroundHex, forKey: .backgroundHex) try container.encode(backgroundHex, forKey: .backgroundHex)
try container.encode(fontFamily, forKey: .fontFamily)
try container.encode(fontWeight, forKey: .fontWeight)
try container.encode(fontDesign, forKey: .fontDesign)
try container.encode(showBattery, forKey: .showBattery) try container.encode(showBattery, forKey: .showBattery)
try container.encode(showDate, forKey: .showDate) try container.encode(showDate, forKey: .showDate)
try container.encode(clockOpacity, forKey: .clockOpacity) try container.encode(clockOpacity, forKey: .clockOpacity)
@ -131,6 +145,9 @@ class ClockStyle: Codable, Equatable {
lhs.digitScale == rhs.digitScale && lhs.digitScale == rhs.digitScale &&
lhs.stretched == rhs.stretched && lhs.stretched == rhs.stretched &&
lhs.backgroundHex == rhs.backgroundHex && lhs.backgroundHex == rhs.backgroundHex &&
lhs.fontFamily == rhs.fontFamily &&
lhs.fontWeight == rhs.fontWeight &&
lhs.fontDesign == rhs.fontDesign &&
lhs.showBattery == rhs.showBattery && lhs.showBattery == rhs.showBattery &&
lhs.showDate == rhs.showDate && lhs.showDate == rhs.showDate &&
lhs.clockOpacity == rhs.clockOpacity && lhs.clockOpacity == rhs.clockOpacity &&

View File

@ -28,6 +28,7 @@ struct ClockSettingsView: View {
NavigationView { NavigationView {
Form { Form {
TimeFormatSection(style: $style) TimeFormatSection(style: $style)
FontSection(style: $style)
AppearanceSection( AppearanceSection(
style: $style, style: $style,
digitColor: $digitColor, digitColor: $digitColor,
@ -56,6 +57,57 @@ struct ClockSettingsView: View {
} }
// MARK: - Supporting Views // MARK: - Supporting Views
private struct FontSection: View {
@Binding var style: ClockStyle
private let fontFamilies = ["System", "San Francisco", "Monaco", "Courier"]
private let fontWeights = ["Ultra Light", "Thin", "Light", "Regular", "Medium", "Semibold", "Bold", "Heavy", "Black"]
private let fontDesigns = ["Default", "Serif", "Rounded", "Monospaced"]
var body: some View {
Section(header: Text("Font")) {
// Font Family
Picker("Family", selection: $style.fontFamily) {
ForEach(fontFamilies, id: \.self) { family in
Text(family).tag(family)
}
}
.pickerStyle(.menu)
// Font Weight
Picker("Weight", selection: $style.fontWeight) {
ForEach(fontWeights, id: \.self) { weight in
Text(weight).tag(weight)
}
}
.pickerStyle(.menu)
// Font Design
Picker("Design", selection: $style.fontDesign) {
ForEach(fontDesigns, id: \.self) { design in
Text(design).tag(design)
}
}
.pickerStyle(.menu)
// Font Preview
HStack {
Text("Preview:")
.foregroundColor(.secondary)
Spacer()
Text("12:34")
.font(FontUtils.customFont(
size: 24,
family: style.fontFamily,
weight: style.fontWeight,
design: style.fontDesign
))
.foregroundColor(.primary)
}
}
}
}
private struct TimeFormatSection: View { private struct TimeFormatSection: View {
@Binding var style: ClockStyle @Binding var style: ClockStyle

View File

@ -20,41 +20,45 @@ struct ClockView: View {
viewModel.style.backgroundColor viewModel.style.backgroundColor
.ignoresSafeArea() .ignoresSafeArea()
VStack(spacing: UIConstants.Spacing.medium) { GeometryReader { geometry in
// Top overlay ZStack {
if viewModel.style.showBattery || viewModel.style.showDate { // Time display - fills available space within safe areas
TopOverlayView( TimeDisplayView(
showBattery: viewModel.style.showBattery, date: viewModel.currentTime,
showDate: viewModel.style.showDate, use24Hour: viewModel.style.use24Hour,
color: viewModel.style.digitColor.opacity(UIConstants.Opacity.primary), showSeconds: viewModel.style.showSeconds,
opacity: viewModel.style.overlayOpacity digitColor: viewModel.style.digitColor,
glowIntensity: viewModel.style.glowIntensity,
manualScale: viewModel.style.digitScale,
stretched: viewModel.style.stretched,
showAmPmBadge: viewModel.style.showAmPmBadge,
clockOpacity: viewModel.style.clockOpacity,
fontFamily: viewModel.style.fontFamily,
fontWeight: viewModel.style.fontWeight,
fontDesign: viewModel.style.fontDesign
) )
.padding(.top, UIConstants.Spacing.small) .frame(width: geometry.size.width, height: geometry.size.height)
.padding(.horizontal, UIConstants.Spacing.large)
.transition(.opacity) .transition(.opacity)
// Top overlay - positioned at top with safe area consideration
VStack {
if viewModel.style.showBattery || viewModel.style.showDate {
TopOverlayView(
showBattery: viewModel.style.showBattery,
showDate: viewModel.style.showDate,
color: viewModel.style.digitColor.opacity(UIConstants.Opacity.primary),
opacity: viewModel.style.overlayOpacity
)
.padding(.top, UIConstants.Spacing.small)
.padding(.horizontal, UIConstants.Spacing.large)
.transition(.opacity)
}
Spacer()
}
} }
.scaleEffect(viewModel.isDisplayMode ? 1.0 : 0.995)
Spacer() .animation(UIConstants.AnimationCurves.smooth, value: viewModel.isDisplayMode)
// Time display
TimeDisplayView(
date: viewModel.currentTime,
use24Hour: viewModel.style.use24Hour,
showSeconds: viewModel.style.showSeconds,
digitColor: viewModel.style.digitColor,
glowIntensity: viewModel.style.glowIntensity,
manualScale: viewModel.style.digitScale,
stretched: viewModel.style.stretched,
showAmPmBadge: viewModel.style.showAmPmBadge,
clockOpacity: viewModel.style.clockOpacity
)
.padding(.horizontal, UIConstants.Spacing.medium)
.transition(.opacity)
Spacer()
} }
.scaleEffect(viewModel.isDisplayMode ? 1.0 : 0.995)
.animation(UIConstants.AnimationCurves.smooth, value: viewModel.isDisplayMode)
} }
.navigationTitle(viewModel.isDisplayMode ? "" : "Clock") .navigationTitle(viewModel.isDisplayMode ? "" : "Clock")
.toolbar { .toolbar {
@ -72,6 +76,7 @@ struct ClockView: View {
} }
.navigationBarBackButtonHidden(viewModel.isDisplayMode) .navigationBarBackButtonHidden(viewModel.isDisplayMode)
.toolbar(viewModel.isDisplayMode ? .hidden : .automatic) .toolbar(viewModel.isDisplayMode ? .hidden : .automatic)
.statusBarHidden(viewModel.isDisplayMode)
.onAppear { .onAppear {
setTabBarHidden(viewModel.isDisplayMode, animated: false) setTabBarHidden(viewModel.isDisplayMode, animated: false)
} }
@ -90,6 +95,7 @@ struct ClockView: View {
.onEnded { _ in .onEnded { _ in
viewModel.toggleDisplayMode() viewModel.toggleDisplayMode()
setTabBarHidden(viewModel.isDisplayMode, animated: true) setTabBarHidden(viewModel.isDisplayMode, animated: true)
// Status bar hiding is handled by the .statusBarHidden modifier
} }
) )
} }

View File

@ -20,6 +20,9 @@ struct TimeDisplayView: View {
let stretched: Bool let stretched: Bool
let showAmPmBadge: Bool let showAmPmBadge: Bool
let clockOpacity: Double let clockOpacity: Double
let fontFamily: String
let fontWeight: String
let fontDesign: String
// MARK: - Formatters // MARK: - Formatters
private static let hour24DF: DateFormatter = { private static let hour24DF: DateFormatter = {
@ -63,18 +66,29 @@ struct TimeDisplayView: View {
let size = proxy.size let size = proxy.size
let portrait = size.height >= size.width let portrait = size.height >= size.width
// Use optimal font sizing that maximizes space usage // 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)
let fullScreenSize = size
// Use optimal font sizing that maximizes space usage within safe areas
let baseFontSize = stretched ? let baseFontSize = stretched ?
FontUtils.maximumStretchedFontSize( FontUtils.maximumStretchedFontSize(
containerWidth: size.width, containerWidth: availableSize.width,
containerHeight: size.height, containerHeight: availableSize.height,
isPortrait: portrait, isPortrait: portrait,
showSeconds: showSeconds, showSeconds: showSeconds,
showAmPm: !use24Hour && showAmPmBadge showAmPm: !use24Hour && showAmPmBadge
) : ) :
FontUtils.optimalFontSize( FontUtils.optimalFontSize(
containerWidth: size.width, containerWidth: availableSize.width,
containerHeight: size.height, containerHeight: availableSize.height,
isPortrait: portrait, isPortrait: portrait,
showSeconds: showSeconds, showSeconds: showSeconds,
showAmPm: !use24Hour && showAmPmBadge showAmPm: !use24Hour && showAmPmBadge
@ -89,8 +103,18 @@ struct TimeDisplayView: View {
let showAMPM = !use24Hour && showAmPmBadge let showAMPM = !use24Hour && showAmPmBadge
// Calculate sizes using fixed-width approach to prevent jumping // Calculate sizes using fixed-width approach to prevent jumping
let digitUIFont = UIFont.systemFont(ofSize: baseFontSize, weight: .bold) let digitUIFont = FontUtils.customUIFont(
let ampmUIFont = UIFont.systemFont(ofSize: ampmFontSize, weight: .bold) size: baseFontSize,
family: fontFamily,
weight: fontWeight,
design: fontDesign
)
let ampmUIFont = FontUtils.customUIFont(
size: ampmFontSize,
family: fontFamily,
weight: fontWeight,
design: fontDesign
)
// Use fixed-width calculations to prevent layout jumping // Use fixed-width calculations to prevent layout jumping
let digitWidth = FontUtils.maxDigitWidth(font: digitUIFont) let digitWidth = FontUtils.maxDigitWidth(font: digitUIFont)
@ -122,10 +146,10 @@ struct TimeDisplayView: View {
showAMPM: showAMPM showAMPM: showAMPM
) )
// Calculate scale with maximum space utilization // Calculate scale with maximum space utilization using available space
let safeInset = AppConstants.Defaults.safeInset let safeInset = AppConstants.Defaults.safeInset
let availableW = max(1, size.width - safeInset * 2) let availableW = max(1, availableSize.width - safeInset * 2)
let availableH = max(1, size.height - safeInset * 2) let availableH = max(1, availableSize.height - safeInset * 2)
// Calculate scaling factors // Calculate scaling factors
let widthScale = availableW / max(totalWidth, 1) let widthScale = availableW / max(totalWidth, 1)
@ -142,16 +166,16 @@ struct TimeDisplayView: View {
Group { Group {
if portrait { if portrait {
VStack(spacing: 0) { VStack(spacing: 0) {
TimeSegment(text: hour, fontSize: baseFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity) TimeSegment(text: hour, fontSize: baseFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign)
if showAMPM { if showAMPM {
TimeSegment(text: ampmText, fontSize: ampmFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity) 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)
} }
TimeSegment(text: minute, fontSize: baseFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity) 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)
TimeSegment(text: secondsText, fontSize: baseFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity) 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
Spacer() Spacer()
@ -160,16 +184,16 @@ struct TimeDisplayView: View {
} }
} else { } else {
HStack(spacing: 0) { HStack(spacing: 0) {
TimeSegment(text: hour, fontSize: baseFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity) TimeSegment(text: hour, fontSize: baseFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity, fontFamily: fontFamily, fontWeight: fontWeight, fontDesign: fontDesign)
if showAMPM { if showAMPM {
TimeSegment(text: ampmText, fontSize: ampmFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity) 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)
} }
TimeSegment(text: minute, fontSize: baseFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity) 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)
TimeSegment(text: secondsText, fontSize: baseFontSize, opacity: clockOpacity, digitColor: digitColor, glowIntensity: glowIntensity) 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
Spacer() Spacer()
@ -178,7 +202,7 @@ struct TimeDisplayView: View {
} }
} }
} }
.frame(width: size.width, height: size.height, alignment: .center) .frame(width: fullScreenSize.width, height: fullScreenSize.height, alignment: .center)
.scaleEffect(finalScale, anchor: .center) .scaleEffect(finalScale, anchor: .center)
.animation(UIConstants.AnimationCurves.smooth, value: finalScale) .animation(UIConstants.AnimationCurves.smooth, value: finalScale)
.animation(UIConstants.AnimationCurves.smooth, value: showSeconds) // Smooth animation for seconds toggle .animation(UIConstants.AnimationCurves.smooth, value: showSeconds) // Smooth animation for seconds toggle
@ -258,20 +282,38 @@ private struct TimeSegment: View {
let opacity: Double let opacity: Double
let digitColor: Color let digitColor: Color
let glowIntensity: Double let glowIntensity: Double
let fontFamily: String
let fontWeight: String
let fontDesign: String
var body: some View { var body: some View {
let clamped = ColorUtils.clampOpacity(opacity) let clamped = ColorUtils.clampOpacity(opacity)
let font = UIFont.systemFont(ofSize: fontSize, weight: .bold) let font = FontUtils.customUIFont(
size: fontSize,
family: fontFamily,
weight: fontWeight,
design: fontDesign
)
let maxWidth = FontUtils.maxDigitWidth(font: font) let maxWidth = FontUtils.maxDigitWidth(font: font)
ZStack { ZStack {
Text(text) Text(text)
.font(.system(size: fontSize, weight: .bold, design: .rounded)) .font(FontUtils.customFont(
size: fontSize,
family: fontFamily,
weight: fontWeight,
design: fontDesign
))
.foregroundColor(digitColor) .foregroundColor(digitColor)
.blur(radius: ColorUtils.glowRadius(intensity: glowIntensity)) .blur(radius: ColorUtils.glowRadius(intensity: glowIntensity))
.opacity(ColorUtils.glowOpacity(intensity: glowIntensity) * clamped) .opacity(ColorUtils.glowOpacity(intensity: glowIntensity) * clamped)
Text(text) Text(text)
.font(.system(size: fontSize, weight: .bold, design: .rounded)) .font(FontUtils.customFont(
size: fontSize,
family: fontFamily,
weight: fontWeight,
design: fontDesign
))
.foregroundColor(digitColor) .foregroundColor(digitColor)
.opacity(clamped) .opacity(clamped)
} }