Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
2fafae909d
commit
c8fa702382
@ -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
|
||||||
|
|||||||
@ -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 &&
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user